diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 83a3922..d9c1bb0 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -117,7 +117,7 @@ jobs: run: | cat > example/platformio.ini << EOF [env] - platform = https://github.com/h2zero/platform-n-able.git#1.1.3 + platform = https://github.com/h2zero/platform-n-able.git platform_packages = framework-n-able-arduino @ file://./framework framework = arduino ${{ matrix.flags }} diff --git a/README.md b/README.md index 77b53dd..21fa224 100644 --- a/README.md +++ b/README.md @@ -139,7 +139,10 @@ For example: `'-DCONFIG_MAIN_TASK_STACK_SIZE=512'` This will set the main task s * `CONFIG_RTOS_MIN_TASK_STACK_SIZE` - set the minimum task stack size. * `CONFIG_RTOS_TIMER_QUEUE_LENGTH` - set the queue size for the FreeRTOS timers. * `CONFIG_RTOS_TIMER_STACK_DEPTH` - set the timer task stack size **in 32bit words**. + * `CONFIG_RTOS_THREAD_LOCAL_STORAGE_POINTERS` - set the number of thread local storage pointers for FreeRTOS tasks (default 1, used for printf buffer management). * `CONFIG_WDT_TIMEOUT_SECONDS` - set the number of seconds before the watchdog times out (0 = disable watchdog, default = 0). + * `CONFIG_PRINTF_BUFFER_SIZE` - set the size of the buffer used for `printf` and `Serial.printf` (default 128 bytes, set to 1 for no buffer). + * `CONFIG_PRINTF_BUFFER_INDEX` - set the FreeRTOS storage pointer index used for managing the `printf` buffer (default 0). * Nimble configuration options can also be included, the list of those can be found [here](https://h2zero.github.io/NimBLE-Arduino/md__command_line_config.html) * Other compiler options or definitions for other libraries can also be specified. @@ -158,6 +161,10 @@ There are a few useful functions available to help with your project. * `uint32_t getResetReason();` - Returns the reset reason for the last boot. * `void systemPowerOff();` - Shuts down the MCU. * `void systemRestart();` - Reboot. +* For USB supported boards, additional USB functions beyond CDC are available by including the TinyUSB library (built in) by adding the following to your sketch: +```cpp +#include "Adafruit_TinyUSB.h" +``` ## Bootloader Currently only some boards have Adafruit bootloaders available which are provided as options. You may choose to use the bootloader or none. diff --git a/cores/nRF5/Arduino.h b/cores/nRF5/Arduino.h index efae147..9406454 100644 --- a/cores/nRF5/Arduino.h +++ b/cores/nRF5/Arduino.h @@ -62,7 +62,7 @@ void loop( void ) ; #endif #ifdef USE_TINYUSB - #include "Adafruit_TinyUSB_Core.h" + #include "Adafruit_USBD_CDC.h" #endif // Include board variant diff --git a/cores/nRF5/TinyUSB/Adafruit_TinyUSB_ArduinoCore/Adafruit_TinyUSB_API.cpp b/cores/nRF5/TinyUSB/Adafruit_TinyUSB_ArduinoCore/Adafruit_TinyUSB_API.cpp new file mode 100644 index 0000000..62a22a7 --- /dev/null +++ b/cores/nRF5/TinyUSB/Adafruit_TinyUSB_ArduinoCore/Adafruit_TinyUSB_API.cpp @@ -0,0 +1,103 @@ +/* + * The MIT License (MIT) + * + * Copyright (c) 2019 Ha Thach for Adafruit Industries + * + * 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. + */ + +#include "tusb_option.h" + +#if CFG_TUD_ENABLED || CFG_TUH_ENABLED + +#include "Adafruit_USBD_Device.h" +#include "Arduino.h" + +extern "C" { + +uint32_t tusb_time_millis_api(void) { return millis(); } + +//--------------------------------------------------------------------+ +// Device +//--------------------------------------------------------------------+ +#if CFG_TUD_ENABLED +void TinyUSB_Device_Init(uint8_t rhport) { + // Init USB Device controller and stack + TinyUSBDevice.begin(rhport); +} + +// RP2040 has its own implementation since it needs mutex for dual core +#ifndef ARDUINO_ARCH_RP2040 +void TinyUSB_Device_Task(void) { + // Run tinyusb device task + tud_task(); +} +#endif + +#ifndef ARDUINO_ARCH_ESP32 +void TinyUSB_Device_FlushCDC(void) { + uint8_t const cdc_instance = Adafruit_USBD_CDC::getInstanceCount(); + for (uint8_t instance = 0; instance < cdc_instance; instance++) { + tud_cdc_n_write_flush(instance); + } +} +#endif +#endif // CFG_TUD_ENABLED + +//------------- Debug log with Serial1 -------------// +#if CFG_TUSB_DEBUG && defined(CFG_TUSB_DEBUG_PRINTF) && \ + !defined(ARDUINO_ARCH_ESP32) + +// #define USE_SEGGER_RTT +#ifndef SERIAL_TUSB_DEBUG +#define SERIAL_TUSB_DEBUG Serial1 +#endif + +#ifdef USE_SEGGER_RTT +#include "SEGGER_RTT/RTT/SEGGER_RTT.h" +#endif + +__attribute__((used)) int CFG_TUSB_DEBUG_PRINTF(const char *__restrict format, + ...) { + char buf[256]; + int len; + va_list ap; + va_start(ap, format); + len = vsnprintf(buf, sizeof(buf), format, ap); + +#ifdef USE_SEGGER_RTT + SEGGER_RTT_Write(0, buf, len); +#else + static volatile bool ser_inited = false; + if (!ser_inited) { + ser_inited = true; + SERIAL_TUSB_DEBUG.begin(115200); + // SERIAL_TUSB_DEBUG.begin(921600); + } + SERIAL_TUSB_DEBUG.write(buf); +#endif + + va_end(ap); + return len; +} +#endif // CFG_TUSB_DEBUG + +} // extern C + +#endif // CFG_TUD_ENABLED || CFG_TUH_ENABLED diff --git a/cores/nRF5/TinyUSB/Adafruit_TinyUSB_ArduinoCore/Adafruit_TinyUSB_API.h b/cores/nRF5/TinyUSB/Adafruit_TinyUSB_ArduinoCore/Adafruit_TinyUSB_API.h new file mode 100644 index 0000000..14dbbfd --- /dev/null +++ b/cores/nRF5/TinyUSB/Adafruit_TinyUSB_ArduinoCore/Adafruit_TinyUSB_API.h @@ -0,0 +1,76 @@ +/* + * The MIT License (MIT) + * + * Copyright (c) 2019 Ha Thach for Adafruit Industries + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#ifndef ADAFRUIT_TINYUSB_API_H_ +#define ADAFRUIT_TINYUSB_API_H_ + +#include +#include + +// API Version, need to be updated when there is changes for +// TinyUSB_API, USBD_CDC, USBD_Device, USBD_Interface, +#define TINYUSB_API_VERSION 30000 + +//--------------------------------------------------------------------+ +// Core API +// Should be called by BSP Core to initialize, process task +// Weak function allow compile arduino core before linking with this library +//--------------------------------------------------------------------+ +#ifdef __cplusplus +extern "C" { +#endif + +// Called by core/sketch to initialize usb device hardware and stack +// This also initialize Serial as CDC device +void TinyUSB_Device_Init(uint8_t rhport) __attribute__((weak)); + +// Called by core/sketch to handle device event +void TinyUSB_Device_Task(void) __attribute__((weak)); + +// Called by core/sketch to flush write on CDC +void TinyUSB_Device_FlushCDC(void) __attribute__((weak)); + +#ifdef __cplusplus +} +#endif + +//--------------------------------------------------------------------+ +// Port API +// Must be implemented by each BSP core/platform +//--------------------------------------------------------------------+ + +// To enter/reboot to bootloader +// usually when host disconnects cdc at baud 1200 (touch 1200) +void TinyUSB_Port_EnterDFU(void); + +// Init device hardware. +// Called by TinyUSB_Device_Init() +void TinyUSB_Port_InitDevice(uint8_t rhport); + +// Get unique serial number, needed for Serial String Descriptor +// Fill serial_id (raw bytes) and return its length (limit to 16 bytes) +// Note: Serial descriptor can be overwritten by user API +uint8_t TinyUSB_Port_GetSerialNumber(uint8_t serial_id[16]); + +#endif diff --git a/cores/nRF5/TinyUSB/Adafruit_TinyUSB_ArduinoCore/Adafruit_TinyUSB_nrf.cpp b/cores/nRF5/TinyUSB/Adafruit_TinyUSB_ArduinoCore/Adafruit_TinyUSB_nrf.cpp new file mode 100644 index 0000000..ed3792a --- /dev/null +++ b/cores/nRF5/TinyUSB/Adafruit_TinyUSB_ArduinoCore/Adafruit_TinyUSB_nrf.cpp @@ -0,0 +1,243 @@ +/* + * The MIT License (MIT) + * + * Copyright (c) 2019, hathach for Adafruit + * + * 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. + */ + +#include "tusb_option.h" + +#if CFG_TUD_ENABLED + +#include "nrfx.h" +#include "nrfx_power.h" + +#include "Arduino.h" +#include "Adafruit_USBD_Device.h" + +//--------------------------------------------------------------------+ +// MACRO TYPEDEF CONSTANT ENUM DECLARATION +//--------------------------------------------------------------------+ + +#define USBD_STACK_SZ (200) + +//--------------------------------------------------------------------+ +// Forward USB interrupt events to TinyUSB IRQ Handler +//--------------------------------------------------------------------+ +extern "C" void USBD_IRQHandler(void) { +#if CFG_SYSVIEW + SEGGER_SYSVIEW_RecordEnterISR(); +#endif + + tud_int_handler(0); + +#if CFG_SYSVIEW + SEGGER_SYSVIEW_RecordExitISR(); +#endif +} + +//--------------------------------------------------------------------+ +// Porting API +//--------------------------------------------------------------------+ + +static void usb_hardware_init(void); + +// USB Device Driver task +// This top level thread process all usb events and invoke callbacks +static void usb_device_task(void *param) { + (void)param; + + // Priorities 0, 1, 4 (nRF52) are reserved for SoftDevice + // 2 is highest for application + NVIC_SetPriority(USBD_IRQn, 2); + + // init device on rhport0 + tud_init(0); + + usb_hardware_init(); + + // RTOS forever loop + while (1) { + tud_task(); + TinyUSB_Device_FlushCDC(); + } +} + +void TinyUSB_Port_InitDevice(uint8_t rhport) { + (void)rhport; + + // Create a task for tinyusb device stack + xTaskCreate(usb_device_task, "usbd", USBD_STACK_SZ, NULL, configMAX_PRIORITIES - 1, NULL); +} + +void TinyUSB_Port_EnterDFU(void) { + // Reset to Bootloader + enterSerialDfu(); +} + +uint8_t TinyUSB_Port_GetSerialNumber(uint8_t serial_id[16]) { + uint32_t *serial_32 = (uint32_t *)serial_id; + + serial_32[0] = __builtin_bswap32(NRF_FICR->DEVICEID[1]); + serial_32[1] = __builtin_bswap32(NRF_FICR->DEVICEID[0]); + + return 8; +} + +//--------------------------------------------------------------------+ +// Helper +//--------------------------------------------------------------------+ + +// tinyusb function that handles power event (detected, ready, removed) +// We must call it within SD's SOC event handler, or set it as power event +// handler if SD is not enabled. +extern "C" void tusb_hal_nrf_power_event(uint32_t event); + +static void power_event_handler(nrfx_power_usb_evt_t event) { + tusb_hal_nrf_power_event((uint32_t)event); +} + +// Init usb hardware when starting up. Softdevice is not enabled yet +static void usb_hardware_init(void) { + // USB power may already be ready at this time -> no event generated + // We need to invoke the handler based on the status initially + uint32_t usb_reg = NRF_POWER->USBREGSTATUS; + + // Power module init + const nrfx_power_config_t pwr_cfg = {}; + nrfx_power_init(&pwr_cfg); + + // Register tusb function as USB power handler + const nrfx_power_usbevt_config_t config = {.handler = power_event_handler}; + + nrfx_power_usbevt_init(&config); + nrfx_power_usbevt_enable(); + + if (usb_reg & POWER_USBREGSTATUS_VBUSDETECT_Msk) { + tusb_hal_nrf_power_event(NRFX_POWER_USB_EVT_DETECTED); + } +} + +//--------------------------------------------------------------------+ +// Dummy callbacks to allow linking when certain class is not used +//--------------------------------------------------------------------+ +__attribute__((weak)) uint8_t const * tud_hid_descriptor_report_cb(uint8_t instance) { + (void) instance; + return NULL; +} + +__attribute__((weak)) uint16_t tud_hid_get_report_cb(uint8_t instance, uint8_t report_id, hid_report_type_t report_type, uint8_t* buffer, uint16_t reqlen) { + (void) instance; + (void) report_id; + (void) report_type; + (void) buffer; + (void) reqlen; + return 0; +} + +__attribute__((weak)) void tud_hid_set_report_cb(uint8_t instance, uint8_t report_id, hid_report_type_t report_type, uint8_t const* buffer, uint16_t bufsize) { + (void) instance; + (void) report_id; + (void) report_type; + (void) buffer; + (void) bufsize; +} + +__attribute__((weak)) void tud_hid_set_protocol_cb(uint8_t instance, uint8_t protocol) { + (void) instance; + (void) protocol; +} + +__attribute__((weak)) bool tud_hid_set_idle_cb(uint8_t instance, uint8_t idle_rate) { + (void) instance; + (void) idle_rate; + return false; +} + +__attribute__((weak)) void tud_hid_report_complete_cb(uint8_t instance, uint8_t const* report, uint16_t len) { + (void) instance; + (void) report; + (void) len; +} + +__attribute__((weak)) void tud_hid_report_failed_cb(uint8_t instance, hid_report_type_t report_type, uint8_t const* report, uint16_t xferred_bytes) { + (void) instance; + (void) report_type; + (void) report; + (void) xferred_bytes; +} + +__attribute__((weak)) int32_t tud_msc_read10_cb(uint8_t lun, uint32_t lba, uint32_t offset, void* buffer, uint32_t bufsize) { + (void) lun; + (void) lba; + (void) offset; + (void) buffer; + (void) bufsize; + return -1; +} + +__attribute__((weak)) int32_t tud_msc_write10_cb(uint8_t lun, uint32_t lba, uint32_t offset, uint8_t* buffer, uint32_t bufsize) { + (void) lun; + (void) lba; + (void) offset; + (void) buffer; + (void) bufsize; + return -1; +} + +__attribute__((weak)) bool tud_msc_test_unit_ready_cb(uint8_t lun) { + (void) lun; + return false; +} + +__attribute__((weak)) void tud_msc_capacity_cb(uint8_t lun, uint32_t* block_count, uint16_t* block_size) { + (void) lun; + *block_count = 0; + *block_size = 0; +} + +__attribute__((weak)) int32_t tud_msc_scsi_cb(uint8_t lun, uint8_t const scsi_cmd[16], void* buffer, uint16_t bufsize) { + (void) lun; + (void) scsi_cmd; + (void) buffer; + (void) bufsize; + return -1; +} + +// MAX3421 Host Controller API +__attribute__((weak)) void tuh_max3421_spi_cs_api(uint8_t rhport, bool active) { + (void) rhport; + (void) active; +} + +__attribute__((weak)) bool tuh_max3421_spi_xfer_api(uint8_t rhport, uint8_t const* tx_buf, uint8_t* rx_buf, size_t xfer_bytes) { + (void) rhport; + (void) tx_buf; + (void) rx_buf; + (void) xfer_bytes; + return false; +} + +__attribute__((weak)) void tuh_max3421_int_api(uint8_t rhport, bool enabled) { + (void) rhport; + (void) enabled; +} + +#endif // USE_TINYUSB diff --git a/cores/nRF5/TinyUSB/Adafruit_TinyUSB_ArduinoCore/Adafruit_USBD_CDC.cpp b/cores/nRF5/TinyUSB/Adafruit_TinyUSB_ArduinoCore/Adafruit_USBD_CDC.cpp index b3b2399..11c5673 100644 --- a/cores/nRF5/TinyUSB/Adafruit_TinyUSB_ArduinoCore/Adafruit_USBD_CDC.cpp +++ b/cores/nRF5/TinyUSB/Adafruit_TinyUSB_ArduinoCore/Adafruit_USBD_CDC.cpp @@ -22,167 +22,325 @@ * THE SOFTWARE. */ -#ifdef USE_TINYUSB +#include "tusb_option.h" + +// esp32 use built-in core cdc +#if CFG_TUD_CDC && !defined(ARDUINO_ARCH_ESP32) #include "Arduino.h" + +#include "Adafruit_TinyUSB_API.h" + #include "Adafruit_USBD_CDC.h" +#include "Adafruit_USBD_Device.h" -#define EPOUT 0x00 -#define EPIN 0x80 +#ifndef TINYUSB_API_VERSION +#define TINYUSB_API_VERSION 0 +#endif -Adafruit_USBD_CDC Serial; +// SerialTinyUSB can be macro expanding to "Serial" on supported cores +Adafruit_USBD_CDC SerialTinyUSB; -Adafruit_USBD_CDC::Adafruit_USBD_CDC(void) -{ +uint8_t Adafruit_USBD_CDC::_instance_count = 0; +Adafruit_USBD_CDC::Adafruit_USBD_CDC(void) { _instance = INVALID_INSTANCE; } -} +#if CFG_TUD_ENABLED + +uint16_t Adafruit_USBD_CDC::getInterfaceDescriptor(uint8_t itfnum_deprecated, + uint8_t *buf, + uint16_t bufsize) { + (void)itfnum_deprecated; -uint16_t Adafruit_USBD_CDC::getDescriptor(uint8_t itfnum, uint8_t* buf, uint16_t bufsize) -{ // CDC is mostly always existed for DFU - // usb core will automatically update endpoint number - uint8_t desc[] = { TUD_CDC_DESCRIPTOR(itfnum, 0, EPIN, 8, EPOUT, EPIN, 64) }; + uint8_t itfnum = 0; + uint8_t ep_notif = 0; + uint8_t ep_in = 0; + uint8_t ep_out = 0; + + if (buf) { + itfnum = TinyUSBDevice.allocInterface(2); + ep_notif = TinyUSBDevice.allocEndpoint(TUSB_DIR_IN); + ep_in = TinyUSBDevice.allocEndpoint(TUSB_DIR_IN); + ep_out = TinyUSBDevice.allocEndpoint(TUSB_DIR_OUT); + } + +#if TINYUSB_API_VERSION < 20400 + // backward compatible for core that include pre-2.4.0 TinyUSB + uint8_t _strid = 0; +#endif + + uint16_t const mps = + (TUD_OPT_HIGH_SPEED ? 512 : 64); // TODO actual link speed + uint8_t const desc[] = { + TUD_CDC_DESCRIPTOR(itfnum, _strid, ep_notif, 8, ep_out, ep_in, mps)}; + uint16_t const len = sizeof(desc); - if ( bufsize < len ) return 0; + // null buffer is used to get the length of descriptor only + if (buf) { + if (bufsize < len) { + return 0; + } + memcpy(buf, desc, len); + } - memcpy(buf, desc, len); return len; } // Baud and config is ignore in CDC -void Adafruit_USBD_CDC::begin (uint32_t baud) -{ - (void) baud; +void Adafruit_USBD_CDC::begin(uint32_t baud) { + (void)baud; + + // already called begin() + if (isValid()) { + return; + } + + // too many instances + if (!(_instance_count < CFG_TUD_CDC)) { + return; + } + + _instance = _instance_count++; + this->setStringDescriptor("TinyUSB Serial"); + TinyUSBDevice.addInterface(*this); } -void Adafruit_USBD_CDC::begin (uint32_t baud, uint8_t config) -{ - (void) baud; - (void) config; +void Adafruit_USBD_CDC::begin(uint32_t baud, uint8_t config) { + (void)config; + this->begin(baud); } -void Adafruit_USBD_CDC::end(void) -{ - // nothing to do +void Adafruit_USBD_CDC::end(void) { + // Reset configuration descriptor without Serial as CDC + TinyUSBDevice.clearConfiguration(); + _instance_count = 0; + _instance = INVALID_INSTANCE; } -uint32_t Adafruit_USBD_CDC::baud(void) -{ +uint32_t Adafruit_USBD_CDC::baud(void) { + if (!isValid()) { + return 0; + } + cdc_line_coding_t coding; - tud_cdc_get_line_coding(&coding); + tud_cdc_n_get_line_coding(_instance, &coding); return coding.bit_rate; } -uint8_t Adafruit_USBD_CDC::stopbits(void) -{ +uint8_t Adafruit_USBD_CDC::stopbits(void) { + if (!isValid()) { + return 0; + } + cdc_line_coding_t coding; - tud_cdc_get_line_coding(&coding); + tud_cdc_n_get_line_coding(_instance, &coding); return coding.stop_bits; } -uint8_t Adafruit_USBD_CDC::paritytype(void) -{ +uint8_t Adafruit_USBD_CDC::paritytype(void) { + if (!isValid()) { + return 0; + } + cdc_line_coding_t coding; - tud_cdc_get_line_coding(&coding); + tud_cdc_n_get_line_coding(_instance, &coding); return coding.parity; } -uint8_t Adafruit_USBD_CDC::numbits(void) -{ +uint8_t Adafruit_USBD_CDC::numbits(void) { + if (!isValid()) { + return 0; + } + cdc_line_coding_t coding; - tud_cdc_get_line_coding(&coding); + tud_cdc_n_get_line_coding(_instance, &coding); return coding.data_bits; } -Adafruit_USBD_CDC::operator bool() -{ - bool ret = tud_cdc_connected(); +int Adafruit_USBD_CDC::dtr(void) { + if (!isValid()) { + return 0; + } + + return tud_cdc_n_connected(_instance); +} + +Adafruit_USBD_CDC::operator bool() { + if (!isValid()) { + return false; + } + + bool ret = tud_cdc_n_connected(_instance); // Add an yield to run usb background in case sketch block wait as follows // while( !Serial ) {} - if ( !ret ) yield(); - + if (!ret) { + yield(); + } return ret; } -int Adafruit_USBD_CDC::available(void) -{ - uint32_t count = tud_cdc_available(); +int Adafruit_USBD_CDC::available(void) { + if (!isValid()) { + return 0; + } + + uint32_t count = tud_cdc_n_available(_instance); // Add an yield to run usb background in case sketch block wait as follows // while( !Serial.available() ) {} - if (!count) yield(); + if (!count) { + yield(); + } return count; } -int Adafruit_USBD_CDC::peek(void) -{ +int Adafruit_USBD_CDC::peek(void) { + if (!isValid()) { + return -1; + } + uint8_t ch; - return tud_cdc_peek(0, &ch) ? (int) ch : -1; + return tud_cdc_n_peek(_instance, &ch) ? (int)ch : -1; } -int Adafruit_USBD_CDC::read(void) -{ - return (int) tud_cdc_read_char(); +int Adafruit_USBD_CDC::read(void) { + if (!isValid()) { + return -1; + } + return (int)tud_cdc_n_read_char(_instance); } -void Adafruit_USBD_CDC::flush(void) -{ - tud_cdc_write_flush(); +#if TINYUSB_API_VERSION >= 10700 +size_t Adafruit_USBD_CDC::read(uint8_t *buffer, size_t size) { + if (!isValid()) { + return 0; + } + + return tud_cdc_n_read(_instance, buffer, size); } +#endif -size_t Adafruit_USBD_CDC::write(uint8_t ch) -{ - return write(&ch, 1); +void Adafruit_USBD_CDC::flush(void) { + if (!isValid()) { + return; + } + + tud_cdc_n_write_flush(_instance); } -size_t Adafruit_USBD_CDC::write(const uint8_t *buffer, size_t size) -{ +size_t Adafruit_USBD_CDC::write(uint8_t ch) { return write(&ch, 1); } + +size_t Adafruit_USBD_CDC::write(const uint8_t *buffer, size_t size) { + if (!isValid()) { + return 0; + } + size_t remain = size; - while ( remain && tud_cdc_connected() ) - { - size_t wrcount = tud_cdc_write(buffer, remain); + while (remain && tud_cdc_n_connected(_instance)) { + size_t wrcount = tud_cdc_n_write(_instance, buffer, remain); remain -= wrcount; buffer += wrcount; // Write FIFO is full, run usb background to flush - if ( remain ) yield(); + if (remain) { + yield(); + } } return size - remain; } -int Adafruit_USBD_CDC::availableForWrite(void) -{ - return tud_cdc_write_available(); +int Adafruit_USBD_CDC::availableForWrite(void) { + if (!isValid()) { + return 0; + } + return tud_cdc_n_write_available(_instance); } -extern "C" -{ +extern "C" { // Invoked when cdc when line state changed e.g connected/disconnected // Use to reset to DFU when disconnect with 1200 bps -void tud_cdc_line_state_cb(uint8_t itf, bool dtr, bool rts) -{ - (void) itf; // interface ID, not used - (void) rts; +void tud_cdc_line_state_cb(uint8_t instance, bool dtr, bool rts) { + (void)rts; // DTR = false is counted as disconnected - if ( !dtr ) - { - cdc_line_coding_t coding; - tud_cdc_get_line_coding(&coding); - - if ( coding.bit_rate == 1200 ) Adafruit_TinyUSB_Core_touch1200(); + if (!dtr) { + // touch1200 only with first CDC instance (Serial) + if (instance == 0) { + cdc_line_coding_t coding; + tud_cdc_get_line_coding(&coding); + + if (coding.bit_rate == 1200) { + TinyUSB_Port_EnterDFU(); + } + } } } +} + +#else + +// Device stack is not enabled (probably in host mode) +#warning "TinyUSB Host selected. No output to Serial will occur!" +uint16_t Adafruit_USBD_CDC::getInterfaceDescriptor(uint8_t itfnum_deprecated, + uint8_t *buf, + uint16_t bufsize) { + (void)itfnum_deprecated; + (void)buf; + (void)bufsize; + + return 0; } -#endif // USE_TINYUSB +// Baud and config is ignore in CDC +void Adafruit_USBD_CDC::begin(uint32_t baud) { (void)baud; } + +void Adafruit_USBD_CDC::begin(uint32_t baud, uint8_t config) { (void)config; } + +void Adafruit_USBD_CDC::end(void) {} + +uint32_t Adafruit_USBD_CDC::baud(void) { return 0; } + +uint8_t Adafruit_USBD_CDC::stopbits(void) { return 0; } + +uint8_t Adafruit_USBD_CDC::paritytype(void) { return 0; } + +uint8_t Adafruit_USBD_CDC::numbits(void) { return 0; } + +int Adafruit_USBD_CDC::dtr(void) { return 0; } + +Adafruit_USBD_CDC::operator bool() { return false; } + +int Adafruit_USBD_CDC::available(void) { return 0; } + +int Adafruit_USBD_CDC::peek(void) { return -1; } + +int Adafruit_USBD_CDC::read(void) { return -1; } + +size_t Adafruit_USBD_CDC::read(uint8_t *buffer, size_t size) { + (void)buffer; + (void)size; + return 0; +} + +void Adafruit_USBD_CDC::flush(void) {} + +size_t Adafruit_USBD_CDC::write(uint8_t ch) { return -1; } + +size_t Adafruit_USBD_CDC::write(const uint8_t *buffer, size_t size) { + return 0; +} + +int Adafruit_USBD_CDC::availableForWrite(void) { return 0; } + +#endif // CFG_TUD_ENABLED +#endif // CDC + ESP32 diff --git a/cores/nRF5/TinyUSB/Adafruit_TinyUSB_ArduinoCore/Adafruit_USBD_CDC.h b/cores/nRF5/TinyUSB/Adafruit_TinyUSB_ArduinoCore/Adafruit_USBD_CDC.h index 6b4663d..9c0952f 100644 --- a/cores/nRF5/TinyUSB/Adafruit_TinyUSB_ArduinoCore/Adafruit_USBD_CDC.h +++ b/cores/nRF5/TinyUSB/Adafruit_TinyUSB_ArduinoCore/Adafruit_USBD_CDC.h @@ -25,44 +25,85 @@ #ifndef ADAFRUIT_USBD_CDC_H_ #define ADAFRUIT_USBD_CDC_H_ -#include "Adafruit_USBD_Device.h" +#include "Adafruit_TinyUSB_API.h" + +#if defined(__cplusplus) + +#if defined(ARDUINO_ARCH_ESP32) + +// For ESP32 use USBCDC as it is compatible +#define Adafruit_USBD_CDC USBCDC +#define SerialTinyUSB Serial + +#else + +#include "Adafruit_USBD_Interface.h" #include "Stream.h" -class Adafruit_USBD_CDC : public Stream, public Adafruit_USBD_Interface -{ +class Adafruit_USBD_CDC : public Stream, public Adafruit_USBD_Interface { public: - Adafruit_USBD_CDC(void); - - // fron Adafruit_USBD_Interface - virtual uint16_t getDescriptor(uint8_t itfnum, uint8_t* buf, uint16_t bufsize); - - void setPins(uint8_t pin_rx, uint8_t pin_tx) { (void) pin_rx; (void) pin_tx; } - void begin(uint32_t baud_count); - void begin(uint32_t baud, uint8_t config); - void end(void); - - // return line coding set by host - uint32_t baud(void); - uint8_t stopbits(void); - uint8_t paritytype(void); - uint8_t numbits(void); - - virtual int available(void); - virtual int peek(void); - virtual int read(void); - virtual void flush(void); - virtual size_t write(uint8_t); - - virtual size_t write(const uint8_t *buffer, size_t size); - size_t write(const char *buffer, size_t size) { - return write((const uint8_t *)buffer, size); - } - - virtual int availableForWrite(void); - using Print::write; // pull in write(str) from Print - operator bool(); + Adafruit_USBD_CDC(void); + + static uint8_t getInstanceCount(void) { return _instance_count; } + + void setPins(uint8_t pin_rx, uint8_t pin_tx) { + (void)pin_rx; + (void)pin_tx; + } + void begin(uint32_t baud); + void begin(uint32_t baud, uint8_t config); + void end(void); + + // return line coding set by host + uint32_t baud(void); + uint8_t stopbits(void); + uint8_t paritytype(void); + uint8_t numbits(void); + int dtr(void); + + // Stream API + virtual int available(void); + virtual int peek(void); + + virtual int read(void); + size_t read(uint8_t *buffer, size_t size); + + virtual void flush(void); + virtual size_t write(uint8_t); + + virtual size_t write(const uint8_t *buffer, size_t size); + size_t write(const char *buffer, size_t size) { + return write((const uint8_t *)buffer, size); + } + + virtual int availableForWrite(void); + using Print::write; // pull in write(str) from Print + operator bool(); + + // from Adafruit_USBD_Interface + virtual uint16_t getInterfaceDescriptor(uint8_t itfnum_deprecated, + uint8_t *buf, uint16_t bufsize); + +private: + enum { INVALID_INSTANCE = 0xffu }; + static uint8_t _instance_count; + + uint8_t _instance; + + bool isValid(void) { return _instance != INVALID_INSTANCE; } }; -extern Adafruit_USBD_CDC Serial; +extern Adafruit_USBD_CDC SerialTinyUSB; + +// Built-in support "Serial" is assigned to TinyUSB CDC +// CH32 defines Serial as alias in WSerial.h +#if defined(USE_TINYUSB) && !defined(ARDUINO_ARCH_CH32) +#define SerialTinyUSB Serial +#endif + +extern Adafruit_USBD_CDC SerialTinyUSB; + +#endif // else of ESP32 +#endif // __cplusplus -#endif /* ADAFRUIT_USBD_CDC_H_ */ +#endif diff --git a/cores/nRF5/TinyUSB/Adafruit_TinyUSB_ArduinoCore/Adafruit_USBD_Device.cpp b/cores/nRF5/TinyUSB/Adafruit_TinyUSB_ArduinoCore/Adafruit_USBD_Device.cpp index 1f51925..b044e15 100644 --- a/cores/nRF5/TinyUSB/Adafruit_TinyUSB_ArduinoCore/Adafruit_USBD_Device.cpp +++ b/cores/nRF5/TinyUSB/Adafruit_TinyUSB_ArduinoCore/Adafruit_USBD_Device.cpp @@ -1,4 +1,4 @@ -/* +/* * The MIT License (MIT) * * Copyright (c) 2019 Ha Thach for Adafruit Industries @@ -22,89 +22,186 @@ * THE SOFTWARE. */ -#ifdef USE_TINYUSB +#include "tusb_option.h" +#if CFG_TUD_ENABLED + +#include "Adafruit_TinyUSB_API.h" + +#include "Adafruit_USBD_CDC.h" #include "Adafruit_USBD_Device.h" +// USB Information can be defined in variant file e.g pins_arduino.h +#include "Arduino.h" + +/* VID, PID, Manufacturer and Product name: + * - For most ports: USB_VID, USB_PID, USB_MANUFACTURER, USB_PRODUCT are + * defined. + * - For ESP32: Default USB_MANUFACTURER is Espressif (instead of Adafruit), + * ARDUINO_BOARD as USB_PRODUCT + * - For mbed core: BOARD_VENDORID, BOARD_PRODUCTID, BOARD_MANUFACTURER, + * BOARD_NAME are defined + */ + +#ifndef USB_VID +#ifdef BOARD_VENDORID +#define USB_VID BOARD_VENDORID +#else +#define USB_VID 0x239a +#endif +#endif + +#ifndef USB_PID +#ifdef BOARD_PRODUCTID +#define USB_PID BOARD_PRODUCTID +#else +#define USB_PID 0xcafe +#endif +#endif + #ifndef USB_MANUFACTURER - #define USB_MANUFACTURER "Unknown" +#ifdef BOARD_MANUFACTURER +#define USB_MANUFACTURER BOARD_MANUFACTURER +#elif defined(ARDUINO_ARCH_ESP32) +#define USB_MANUFACTURER "Espressif Systems" +#else +#define USB_MANUFACTURER "Adafruit" +#endif #endif #ifndef USB_PRODUCT - #define USB_PRODUCT "Unknown" +#if defined(ARDUINO_BOARD) +#define USB_PRODUCT ARDUINO_BOARD +#elif defined(BOARD_NAME) +#define USB_PRODUCT BOARD_NAME +#else +#define USB_PRODUCT "Unknown" +#endif #endif #ifndef USB_LANGUAGE - #define USB_LANGUAGE 0x0409 // default is English +#define USB_LANGUAGE 0x0409 // default is English #endif #ifndef USB_CONFIG_POWER - #define USB_CONFIG_POWER 100 +#define USB_CONFIG_POWER 100 #endif -enum -{ - STRID_LANGUAGE = 0, - STRID_MANUFACTURER, - STRID_PRODUCT, - STRID_SERIAL -}; - -Adafruit_USBD_Device USBDevice; - -Adafruit_USBD_Device::Adafruit_USBD_Device(void) -{ - tusb_desc_device_t const desc_dev = - { - .bLength = sizeof(tusb_desc_device_t), - .bDescriptorType = TUSB_DESC_DEVICE, - .bcdUSB = 0x0200, - - // Use Interface Association Descriptor (IAD) for CDC - // As required by USB Specs IAD's subclass must be common class (2) and protocol must be IAD (1) - .bDeviceClass = TUSB_CLASS_MISC, - .bDeviceSubClass = MISC_SUBCLASS_COMMON, - .bDeviceProtocol = MISC_PROTOCOL_IAD, - - .bMaxPacketSize0 = CFG_TUD_ENDPOINT0_SIZE, - - .idVendor = 0, - .idProduct = 0, - .bcdDevice = 0x0100, - .iManufacturer = STRID_MANUFACTURER, - .iProduct = STRID_PRODUCT, - .iSerialNumber = STRID_SERIAL, - .bNumConfigurations = 0x01 - }; +enum { STRID_LANGUAGE = 0, STRID_MANUFACTURER, STRID_PRODUCT, STRID_SERIAL }; + +Adafruit_USBD_Device TinyUSBDevice; + +Adafruit_USBD_Device::Adafruit_USBD_Device(void) { +#if defined(ARDUINO_ARCH_ESP32) && ARDUINO_USB_CDC_ON_BOOT && !ARDUINO_USB_MODE + // auto begin for ESP32 USB OTG Mode with CDC on boot + begin(0); +#endif +} + +void Adafruit_USBD_Device::setConfigurationBuffer(uint8_t *buf, + uint32_t buflen) { + if (buflen < _desc_cfg_maxlen) { + return; + } + + memcpy(buf, _desc_cfg, _desc_cfg_len); + _desc_cfg = buf; + _desc_cfg_maxlen = buflen; +} + +void Adafruit_USBD_Device::setID(uint16_t vid, uint16_t pid) { + _desc_device.idVendor = vid; + _desc_device.idProduct = pid; +} + +void Adafruit_USBD_Device::setVersion(uint16_t bcd) { + _desc_device.bcdUSB = bcd; +} + +void Adafruit_USBD_Device::setDeviceVersion(uint16_t bcd) { + _desc_device.bcdDevice = bcd; +} + +void Adafruit_USBD_Device::setLanguageDescriptor(uint16_t language_id) { + _desc_str_arr[STRID_LANGUAGE] = (const char *)((uint32_t)language_id); +} + +void Adafruit_USBD_Device::setManufacturerDescriptor(const char *s) { + _desc_str_arr[STRID_MANUFACTURER] = s; +} + +void Adafruit_USBD_Device::setProductDescriptor(const char *s) { + _desc_str_arr[STRID_PRODUCT] = s; +} + +void Adafruit_USBD_Device::setSerialDescriptor(const char *s) { + _desc_str_arr[STRID_SERIAL] = s; +} + +// Add a string descriptor to the device's pool +// Return string index +uint8_t Adafruit_USBD_Device::addStringDescriptor(const char *s) { + if (_desc_str_count >= STRING_DESCRIPTOR_MAX || s == NULL) { + return 0; + } + + uint8_t index = _desc_str_count++; + _desc_str_arr[index] = s; + return index; +} + +void Adafruit_USBD_Device::task(void) { + tud_task(); + +#ifdef TINYUSB_NEED_POLLING_TASK + // can also be used with port with built-in support + if (SerialTinyUSB) { + SerialTinyUSB.flush(); + } +#endif +} + +void Adafruit_USBD_Device::clearConfiguration(void) { + tusb_desc_device_t const desc_dev = {.bLength = sizeof(tusb_desc_device_t), + .bDescriptorType = TUSB_DESC_DEVICE, + .bcdUSB = 0x0200, + .bDeviceClass = 0, + .bDeviceSubClass = 0, + .bDeviceProtocol = 0, + .bMaxPacketSize0 = + CFG_TUD_ENDPOINT0_SIZE, + .idVendor = USB_VID, + .idProduct = USB_PID, + .bcdDevice = 0x0100, + .iManufacturer = STRID_MANUFACTURER, + .iProduct = STRID_PRODUCT, + .iSerialNumber = STRID_SERIAL, + .bNumConfigurations = 0x01}; _desc_device = desc_dev; - tusb_desc_configuration_t const dev_cfg = - { - .bLength = sizeof(tusb_desc_configuration_t), - .bDescriptorType = TUSB_DESC_CONFIGURATION, - - // Total Length & Interface Number will be updated later - .wTotalLength = 0, - .bNumInterfaces = 0, - .bConfigurationValue = 1, - .iConfiguration = 0x00, - .bmAttributes = TU_BIT(7) | TUSB_DESC_CONFIG_ATT_REMOTE_WAKEUP, - .bMaxPower = TUSB_DESC_CONFIG_POWER_MA(USB_CONFIG_POWER) + // Config number, interface count, string index, total length, + // attribute (bit 7 set to 1), power in mA. + // Note: Total Length Interface Number will be updated later + uint8_t const dev_cfg[sizeof(tusb_desc_configuration_t)] = { + TUD_CONFIG_DESCRIPTOR(1, 0, 0, sizeof(tusb_desc_configuration_t), + TUSB_DESC_CONFIG_ATT_REMOTE_WAKEUP | TU_BIT(7), + USB_CONFIG_POWER), }; - memcpy(_desc_cfg_buffer, &dev_cfg, sizeof(tusb_desc_configuration_t)); - _desc_cfg = _desc_cfg_buffer; + memcpy(_desc_cfg_buffer, dev_cfg, sizeof(tusb_desc_configuration_t)); + _desc_cfg = _desc_cfg_buffer; _desc_cfg_maxlen = sizeof(_desc_cfg_buffer); - _desc_cfg_len = sizeof(tusb_desc_configuration_t); + _desc_cfg_len = sizeof(tusb_desc_configuration_t); - _itf_count = 0; - _epin_count = _epout_count = 1; + _itf_count = 0; + _epin_count = _epout_count = 1; memset(_desc_str_arr, 0, sizeof(_desc_str_arr)); - _desc_str_arr[STRID_LANGUAGE] = (const char*) ((uint32_t) USB_LANGUAGE); + _desc_str_arr[STRID_LANGUAGE] = (const char *)((uint32_t)USB_LANGUAGE); _desc_str_arr[STRID_MANUFACTURER] = USB_MANUFACTURER; _desc_str_arr[STRID_PRODUCT] = USB_PRODUCT; + _desc_str_arr[STRID_SERIAL] = nullptr; // STRID_SERIAL is platform dependent _desc_str_count = 4; @@ -113,145 +210,121 @@ Adafruit_USBD_Device::Adafruit_USBD_Device(void) // Add interface descriptor // - Interface number will be updated to match current count // - Endpoint number is updated to be unique -bool Adafruit_USBD_Device::addInterface(Adafruit_USBD_Interface& itf) -{ - uint8_t* desc = _desc_cfg+_desc_cfg_len; - uint16_t const len = itf.getDescriptor(_itf_count, desc, _desc_cfg_maxlen-_desc_cfg_len); - uint8_t* desc_end = desc+len; - - const char* desc_str = itf.getStringDescriptor(); - - if ( !len ) return false; - - // Parse interface descriptor to update - // - Interface Number & string descrioptor - // - Endpoint address - while (desc < desc_end) - { - if (desc[1] == TUSB_DESC_INTERFACE) - { - tusb_desc_interface_t* desc_itf = (tusb_desc_interface_t*) desc; - if (desc_itf->bAlternateSetting == 0) - { - _itf_count++; - if (desc_str && (_desc_str_count < STRING_DESCRIPTOR_MAX) ) - { - _desc_str_arr[_desc_str_count] = desc_str; - desc_itf->iInterface = _desc_str_count; - _desc_str_count++; - - // only assign string index to first interface - desc_str = NULL; - } - } - }else if (desc[1] == TUSB_DESC_ENDPOINT) - { - tusb_desc_endpoint_t* desc_ep = (tusb_desc_endpoint_t*) desc; - desc_ep->bEndpointAddress |= (desc_ep->bEndpointAddress & 0x80) ? _epin_count++ : _epout_count++; - } +bool Adafruit_USBD_Device::addInterface(Adafruit_USBD_Interface &itf) { + uint8_t *desc = _desc_cfg + _desc_cfg_len; + uint16_t const len = itf.getInterfaceDescriptor( + _itf_count, desc, _desc_cfg_maxlen - _desc_cfg_len); - if (desc[0] == 0) return false; - desc += desc[0]; // next + if (!len) { + return false; } _desc_cfg_len += len; // Update configuration descriptor - tusb_desc_configuration_t* config = (tusb_desc_configuration_t*)_desc_cfg; + tusb_desc_configuration_t *config = (tusb_desc_configuration_t *)_desc_cfg; config->wTotalLength = _desc_cfg_len; config->bNumInterfaces = _itf_count; return true; } -void Adafruit_USBD_Device::setDescriptorBuffer(uint8_t* buf, uint32_t buflen) -{ - if (buflen < _desc_cfg_maxlen) - return; +bool Adafruit_USBD_Device::begin(uint8_t rhport) { + clearConfiguration(); + + // Serial is always added by default + // Use Interface Association Descriptor (IAD) for CDC + // As required by USB Specs IAD's subclass must be common class (2) and + // protocol must be IAD (1) + _desc_device.bDeviceClass = TUSB_CLASS_MISC; + _desc_device.bDeviceSubClass = MISC_SUBCLASS_COMMON; + _desc_device.bDeviceProtocol = MISC_PROTOCOL_IAD; + +#if defined(ARDUINO_ARCH_ESP32) +#if ARDUINO_USB_CDC_ON_BOOT && !ARDUINO_USB_MODE + // follow USBCDC cdc descriptor + uint8_t itfnum = allocInterface(2); + uint8_t strid = addStringDescriptor("TinyUSB Serial"); + uint16_t const mps = + (TUD_OPT_HIGH_SPEED ? 512 : 64); // TODO actual link speed + uint8_t const desc_cdc[TUD_CDC_DESC_LEN] = { + TUD_CDC_DESCRIPTOR(itfnum, strid, 0x85, 64, 0x03, 0x84, mps)}; + + memcpy(_desc_cfg + _desc_cfg_len, desc_cdc, sizeof(desc_cdc)); + _desc_cfg_len += sizeof(desc_cdc); - memcpy(buf, _desc_cfg, _desc_cfg_len); - _desc_cfg = buf; - _desc_cfg_maxlen = buflen; -} + // Update configuration descriptor + tusb_desc_configuration_t *config = (tusb_desc_configuration_t *)_desc_cfg; + config->wTotalLength = _desc_cfg_len; + config->bNumInterfaces = _itf_count; +#endif +#else + SerialTinyUSB.begin(115200); -void Adafruit_USBD_Device::setID(uint16_t vid, uint16_t pid) -{ - _desc_device.idVendor = vid; - _desc_device.idProduct = pid; -} + // Init device hardware and call tusb_init() + TinyUSB_Port_InitDevice(rhport); +#endif -void Adafruit_USBD_Device::setVersion(uint16_t bcd) -{ - _desc_device.bcdUSB = bcd; + return true; } -void Adafruit_USBD_Device::setDeviceVersion(uint16_t bcd) -{ - _desc_device.bcdDevice = bcd; +bool Adafruit_USBD_Device::isInitialized(uint8_t rhport) { + (void)rhport; + return tud_inited(); } +static int strcpy_utf16(const char *s, uint16_t *buf, int bufsize); -void Adafruit_USBD_Device::setLanguageDescriptor (uint16_t language_id) -{ - _desc_str_arr[STRID_LANGUAGE] = (const char*) ((uint32_t) language_id); -} - -void Adafruit_USBD_Device::setManufacturerDescriptor(const char *s) -{ - _desc_str_arr[STRID_MANUFACTURER] = s; -} +uint8_t Adafruit_USBD_Device::getSerialDescriptor(uint16_t *serial_utf16) { -void Adafruit_USBD_Device::setProductDescriptor(const char *s) -{ - _desc_str_arr[STRID_PRODUCT] = s; -} + if (!_desc_str_arr[STRID_SERIAL]) { + uint8_t serial_id[16] __attribute__((aligned(4))); + uint8_t const serial_len = TinyUSB_Port_GetSerialNumber(serial_id); -bool Adafruit_USBD_Device::begin(void) -{ - return true; -} + for (uint8_t i = 0; i < serial_len; i++) { + for (uint8_t j = 0; j < 2; j++) { + const char nibble_to_hex[16] = {'0', '1', '2', '3', '4', '5', '6', '7', + '8', '9', 'A', 'B', 'C', 'D', 'E', 'F'}; -bool Adafruit_USBD_Device::detach(void) -{ - return tud_disconnect(); -} + uint8_t nibble = (serial_id[i] >> (j * 4)) & 0xf; + serial_utf16[1 + i * 2 + (1 - j)] = nibble_to_hex[nibble]; // UTF-16-LE + } + } -bool Adafruit_USBD_Device::attach(void) -{ - return tud_connect(); + return 2 * serial_len; + } else { + return strcpy_utf16(_desc_str_arr[STRID_SERIAL], serial_utf16 + 1, 32); + } } -static int strcpy_utf16(const char *s, uint16_t *buf, int bufsize); -uint16_t const* Adafruit_USBD_Device::descriptor_string_cb(uint8_t index, uint16_t langid) -{ - (void) langid; +uint16_t const *Adafruit_USBD_Device::descriptor_string_cb(uint8_t index, + uint16_t langid) { + (void)langid; - // up to 32 unicode characters (header make it 33) - static uint16_t _desc_str[33]; uint8_t chr_count; - switch (index) - { - case 0: - _desc_str[1] = ((uint16_t) ((uint32_t) _desc_str_arr[STRID_LANGUAGE])); - chr_count = 1; + switch (index) { + case STRID_LANGUAGE: + _desc_str[1] = ((uint16_t)((uint32_t)_desc_str_arr[STRID_LANGUAGE])); + chr_count = 1; break; - case 3: - // serial Number - chr_count = this->getSerialDescriptor(_desc_str+1); + case STRID_SERIAL: + chr_count = getSerialDescriptor(_desc_str); break; - default: - // Invalid index - if (index >= _desc_str_count ) return NULL; + default: + // Invalid index + if (index >= _desc_str_count) { + return NULL; + } - chr_count = strcpy_utf16(_desc_str_arr[index], _desc_str + 1, 32); + chr_count = strcpy_utf16(_desc_str_arr[index], _desc_str + 1, 32); break; } // first byte is length (including header), second byte is string type - _desc_str[0] = (TUSB_DESC_STRING << 8 ) | (2*chr_count + 2); + _desc_str[0] = (TUSB_DESC_STRING << 8) | (2 * chr_count + 2); return _desc_str; } @@ -259,52 +332,32 @@ uint16_t const* Adafruit_USBD_Device::descriptor_string_cb(uint8_t index, uint16 //--------------------------------------------------------------------+ // TinyUSB stack callbacks //--------------------------------------------------------------------+ -extern "C" -{ + +extern "C" { // Invoked when received GET DEVICE DESCRIPTOR // Application return pointer to descriptor -uint8_t const * tud_descriptor_device_cb(void) -{ - return (uint8_t const *) &USBDevice._desc_device; +uint8_t const *tud_descriptor_device_cb(void) { + return (uint8_t const *)&TinyUSBDevice._desc_device; } // Invoked when received GET CONFIGURATION DESCRIPTOR -// Application return pointer to descriptor, whose contents must exist long enough for transfer to complete -uint8_t const * tud_descriptor_configuration_cb(uint8_t index) -{ - (void) index; // for multiple configurations - return USBDevice._desc_cfg; +// Application return pointer to descriptor, whose contents must exist long +// enough for transfer to complete +uint8_t const *tud_descriptor_configuration_cb(uint8_t index) { + (void)index; + return TinyUSBDevice._desc_cfg; } // Invoked when received GET STRING DESCRIPTOR request -// Application return pointer to descriptor, whose contents must exist long enough for transfer to complete -// Note: the 0xEE index string is a Microsoft OS 1.0 Descriptors. +// Application return pointer to descriptor, whose contents must exist long +// enough for transfer to complete Note: the 0xEE index string is a Microsoft +// OS 1.0 Descriptors. // https://docs.microsoft.com/en-us/windows-hardware/drivers/usbcon/microsoft-defined-usb-descriptors -uint16_t const* tud_descriptor_string_cb(uint8_t index, uint16_t langid) -{ - return USBDevice.descriptor_string_cb(index, langid); +uint16_t const *tud_descriptor_string_cb(uint8_t index, uint16_t langid) { + return TinyUSBDevice.descriptor_string_cb(index, langid); } -//--------------------------------------------------------------------+ -// Some of TinyUSB class driver requires strong callbacks, which cause -// link error if 'Adafruit_TinyUSB_Arduino' is not included, provide -// weak callback here to prevent linking error -//--------------------------------------------------------------------+ - -// HID -TU_ATTR_WEAK uint8_t const * tud_hid_descriptor_report_cb(void) { return NULL; } -TU_ATTR_WEAK uint16_t tud_hid_get_report_cb(uint8_t report_id, hid_report_type_t report_type, uint8_t* buffer, uint16_t reqlen) { return 0; } -TU_ATTR_WEAK void tud_hid_set_report_cb(uint8_t report_id, hid_report_type_t report_type, uint8_t const* buffer, uint16_t bufsize) { } - -// MSC -TU_ATTR_WEAK int32_t tud_msc_read10_cb (uint8_t lun, uint32_t lba, uint32_t offset, void* buffer, uint32_t bufsize) { return -1; } -TU_ATTR_WEAK int32_t tud_msc_write10_cb (uint8_t lun, uint32_t lba, uint32_t offset, uint8_t* buffer, uint32_t bufsize) { return -1; } -TU_ATTR_WEAK void tud_msc_inquiry_cb(uint8_t lun, uint8_t vendor_id[8], uint8_t product_id[16], uint8_t product_rev[4]) {} -TU_ATTR_WEAK bool tud_msc_test_unit_ready_cb(uint8_t lun) { return false; } -TU_ATTR_WEAK void tud_msc_capacity_cb(uint8_t lun, uint32_t* block_count, uint16_t* block_size) { } -TU_ATTR_WEAK int32_t tud_msc_scsi_cb (uint8_t lun, uint8_t const scsi_cmd[16], void* buffer, uint16_t bufsize) { return -1; } - } // extern C //--------------------------------------------------------------------+ @@ -320,8 +373,8 @@ constexpr static inline bool isInvalidUtf8Octet(uint8_t t) { // 1. Pre-validated as legal UTF-8, -OR- // 2. has a trailing zero-value octet/byte/uint8_t (aka null-terminated string) // -// If the above are not true, this decoder may read past the end of the allocated -// buffer, by up to three bytes. +// If the above are not true, this decoder may read past the end of the +// allocated buffer, by up to three bytes. // // U+1F47F == 👿 ("IMP") // == 0001_1111_0100_0111_1111 ==> requires four-byte encoding in UTF-8 @@ -339,58 +392,71 @@ constexpr static inline bool isInvalidUtf8Octet(uint8_t t) { // // NOTE: evilUTF8 could just contain a single byte 0xF9 .... // -// Attempting to decode evilUTF8 will progress to whatever is next to it on the stack. -// The above should work when optimizations are turned +// Attempting to decode evilUTF8 will progress to whatever is next to it on the +// stack. The above should work when optimizations are turned // -static int8_t utf8Codepoint(const uint8_t *utf8, uint32_t *codepointp) -{ - const uint32_t CODEPOINT_LOWEST_SURROGATE_HALF = 0xD800; - const uint32_t CODEPOINT_HIGHEST_SURROGATE_HALF = 0xDFFF; +static int8_t utf8Codepoint(const uint8_t *utf8, uint32_t *codepointp) { + const uint32_t CODEPOINT_LOWEST_SURROGATE_HALF = 0xD800; + const uint32_t CODEPOINT_HIGHEST_SURROGATE_HALF = 0xDFFF; - *codepointp = 0xFFFD; // always initialize output to known value ... 0xFFFD (REPLACEMENT CHARACTER) seems the natural choice + *codepointp = 0xFFFD; // always initialize output to known value ... 0xFFFD + // (REPLACEMENT CHARACTER) seems the natural choice uint32_t codepoint; int len; - // The upper bits define both the length of additional bytes for the multi-byte encoding, - // as well as defining how many bits of the first byte are included in the codepoint. - // Each additional byte starts with 0b10xxxxxx, encoding six additional bits for the codepoint. + // The upper bits define both the length of additional bytes for the + // multi-byte encoding, as well as defining how many bits of the first byte + // are included in the codepoint. Each additional byte starts with 0b10xxxxxx, + // encoding six additional bits for the codepoint. // // For key summary points, see: // * https://tools.ietf.org/html/rfc3629#section-3 // - if (isInvalidUtf8Octet(utf8[0])) { // do not allow illegal octet sequences (e.g., 0xC0 0x80 should NOT decode to NULL) + if (isInvalidUtf8Octet(utf8[0])) { + // do not allow illegal octet sequences (e.g., 0xC0 0x80 + // should NOT decode to NULL) return -1; } - if (utf8[0] < 0x80) { // characters 0000 0000..0000 007F (up to 7 significant bits) + if (utf8[0] < 0x80) { + // characters 0000 0000..0000 007F (up to 7 significant bits) len = 1; codepoint = utf8[0]; - } else if ((utf8[0] & 0xe0) == 0xc0) { // characters 0000 0080..0000 07FF (up to 11 significant bits, so first byte encodes five bits) + } else if ((utf8[0] & 0xe0) == 0xc0) { + // characters 0000 0080..0000 07FF + // (up to 11 significant bits, so first byte encodes five bits) len = 2; codepoint = utf8[0] & 0x1f; - } else if ((utf8[0] & 0xf0) == 0xe0) { // characters 0000 8000..0000 FFFF (up to 16 significant bits, so first byte encodes four bits) + } else if ((utf8[0] & 0xf0) == 0xe0) { + // characters 0000 8000..0000 FFFF + // (up to 16 significant bits, so first byte encodes four bits) len = 3; codepoint = utf8[0] & 0x0f; - } else if ((utf8[0] & 0xf8) == 0xf0) { // characters 0001 0000..0010 FFFF (up to 21 significant bits, so first byte encodes three bits) + } else if ((utf8[0] & 0xf8) == 0xf0) { + // characters 0001 0000..0010 FFFF + // (up to 21 significantbits, so first byte encodes three bits) len = 4; codepoint = utf8[0] & 0x07; - } else { // UTF-8 is defined to only map to Unicode -- 0x00000000..0x0010FFFF + } else { + // UTF-8 is defined to only map to Unicode -- 0x00000000..0x0010FFFF // 5-byte and 6-byte sequences are not legal per RFC3629 return -1; } for (int i = 1; i < len; i++) { if ((utf8[i] & 0xc0) != 0x80) { - // the additional bytes in a valid UTF-8 multi-byte encoding cannot have either of the top two bits set - // This is more restrictive than isInvalidUtf8Octet() + // the additional bytes in a valid UTF-8 multi-byte encoding cannot have + // either of the top two bits set This is more restrictive than + // isInvalidUtf8Octet() return -1; } - codepoint <<= 6; // each continuation byte adds six bits to the codepoint - codepoint |= utf8[i] & 0x3f; // mask off the top two continuation bits, and add the six relevant bits + codepoint <<= 6; // each continuation byte adds six bits to the codepoint + codepoint |= utf8[i] & 0x3f; // mask off the top two continuation bits, and + // add the six relevant bits } // explicit validation to prevent overlong encodings - if ( (len == 1) && (codepoint > 0x00007F)) { + if ((len == 1) && (codepoint > 0x00007F)) { return -1; } else if ((len == 2) && ((codepoint < 0x000080) || (codepoint > 0x0007FF))) { return -1; @@ -406,7 +472,8 @@ static int8_t utf8Codepoint(const uint8_t *utf8, uint32_t *codepointp) // high and low surrogate halves (U+D800 through U+DFFF) used by UTF-16 are // not legal Unicode values ... see RFC 3629. - if ((codepoint >= CODEPOINT_LOWEST_SURROGATE_HALF) && (codepoint <= CODEPOINT_HIGHEST_SURROGATE_HALF)) { + if ((codepoint >= CODEPOINT_LOWEST_SURROGATE_HALF) && + (codepoint <= CODEPOINT_HIGHEST_SURROGATE_HALF)) { return -1; } @@ -414,8 +481,7 @@ static int8_t utf8Codepoint(const uint8_t *utf8, uint32_t *codepointp) return len; } -static int strcpy_utf16(const char *s, uint16_t *buf, int bufsize) -{ +static int strcpy_utf16(const char *s, uint16_t *buf, int bufsize) { int i = 0; int buflen = 0; @@ -451,4 +517,11 @@ static int strcpy_utf16(const char *s, uint16_t *buf, int bufsize) return buflen; } -#endif // USE_TINYUSB +// TODO just for compiling, will move to DFU specific file +#if CFG_TUD_DFU_RUNTIME && !defined(ARDUINO_ARCH_ESP32) +void tud_dfu_runtime_reboot_to_dfu_cb(void) { + // TinyUSB_Port_EnterDFU(); +} +#endif + +#endif // CFG_TUD_ENABLED diff --git a/cores/nRF5/TinyUSB/Adafruit_TinyUSB_ArduinoCore/Adafruit_USBD_Device.h b/cores/nRF5/TinyUSB/Adafruit_TinyUSB_ArduinoCore/Adafruit_USBD_Device.h index 2773022..b2271ed 100644 --- a/cores/nRF5/TinyUSB/Adafruit_TinyUSB_ArduinoCore/Adafruit_USBD_Device.h +++ b/cores/nRF5/TinyUSB/Adafruit_TinyUSB_ArduinoCore/Adafruit_USBD_Device.h @@ -1,4 +1,4 @@ -/* +/* * The MIT License (MIT) * * Copyright (c) 2019 Ha Thach for Adafruit Industries @@ -25,76 +25,143 @@ #ifndef ADAFRUIT_USBD_DEVICE_H_ #define ADAFRUIT_USBD_DEVICE_H_ +#include "Adafruit_USBD_Interface.h" + #include "tusb.h" -class Adafruit_USBD_Interface -{ - protected: - const char* _desc_str; +#ifdef ARDUINO_ARCH_ESP32 +#include "esp32-hal-tinyusb.h" +#endif + +#if CFG_TUD_ENABLED - public: - Adafruit_USBD_Interface(void) { _desc_str = NULL; } +class Adafruit_USBD_Device { +private: + enum { STRING_DESCRIPTOR_MAX = 12 }; - virtual uint16_t getDescriptor(uint8_t itfnum, uint8_t* buf, uint16_t bufsize) = 0; - void setStringDescriptor(const char* str) { _desc_str = str; } - const char* getStringDescriptor(void) { return _desc_str; } + // Device descriptor + tusb_desc_device_t _desc_device __attribute__((aligned(4))); + + // Configuration descriptor + uint8_t *_desc_cfg; + uint8_t _desc_cfg_buffer[256]; + uint16_t _desc_cfg_len; + uint16_t _desc_cfg_maxlen; + + uint8_t _itf_count; + + uint8_t _epin_count; + uint8_t _epout_count; + + // String descriptor + const char *_desc_str_arr[STRING_DESCRIPTOR_MAX]; + uint8_t _desc_str_count; + uint16_t _desc_str[32 + 1]; // up to 32 unicode characters with headers + +public: + Adafruit_USBD_Device(void); + + //------------- Device descriptor -------------// + + // Set VID, PID + void setID(uint16_t vid, uint16_t pid); + + // Set bcdUSB version e.g 1.0, 2.0, 2.1 + void setVersion(uint16_t bcd); + + // Set bcdDevice version + void setDeviceVersion(uint16_t bcd); + + //------------- Configuration descriptor -------------// + + // Add a new interface + bool addInterface(Adafruit_USBD_Interface &itf); + + // Clear/Reset configuration descriptor + void clearConfiguration(void); + + // Set configuration attribute + void setConfigurationAttribute(uint8_t attribute) { + _desc_cfg[offsetof(tusb_desc_configuration_t, bmAttributes)] = attribute; + } + + // Set max power consumption in mA (absolute max is 510ma) + bool setConfigurationMaxPower(uint16_t power_ma) { + if (power_ma > 255 * 2u) { + return false; + } + _desc_cfg[offsetof(tusb_desc_configuration_t, bMaxPower)] = + (uint8_t)(power_ma / 2); + return true; + } + + // Provide user buffer for configuration descriptor, if total length > 256 + void setConfigurationBuffer(uint8_t *buf, uint32_t buflen); + + // Allocate a new interface number + uint8_t allocInterface(uint8_t count = 1) { + uint8_t ret = _itf_count; + _itf_count += count; + return ret; + } + + uint8_t allocEndpoint(uint8_t in) { + uint8_t ret = in ? (0x80 | _epin_count++) : _epout_count++; +#if defined(ARDUINO_ARCH_ESP32) && ARDUINO_USB_CDC_ON_BOOT && !ARDUINO_USB_MODE + // ESP32 reserves 0x03, 0x84, 0x85 for CDC Serial + if (ret == 0x03) { + ret = _epout_count++; + } else if (ret == 0x84 || ret == 0x85) { + // Note: ESP32 does not have this much of EP IN + _epin_count = 6; + ret = 0x86; + } +#endif + return ret; + } + + //------------- String descriptor -------------// + void setLanguageDescriptor(uint16_t language_id); + void setManufacturerDescriptor(const char *s); + void setProductDescriptor(const char *s); + void setSerialDescriptor(const char *s); + uint8_t getSerialDescriptor(uint16_t *serial_utf16); + + uint8_t addStringDescriptor(const char *s); + + //------------- Control -------------// + + bool begin(uint8_t rhport = 0); + bool isInitialized(uint8_t rhport = 0); + void task(void); + + // physical disable/enable pull-up + bool detach(void) { return tud_disconnect(); } + bool attach(void) { return tud_connect(); } + + //------------- status -------------// + bool mounted(void) { return tud_mounted(); } + bool suspended(void) { return tud_suspended(); } + bool ready(void) { return tud_ready(); } + bool remoteWakeup(void) { return tud_remote_wakeup(); } + tusb_speed_t getSpeed(void) { return tud_speed_get(); } + +private: + uint16_t const *descriptor_string_cb(uint8_t index, uint16_t langid); + + friend uint8_t const *tud_descriptor_device_cb(void); + friend uint8_t const *tud_descriptor_configuration_cb(uint8_t index); + friend uint16_t const *tud_descriptor_string_cb(uint8_t index, + uint16_t langid); }; -class Adafruit_USBD_Device -{ - private: - enum { STRING_DESCRIPTOR_MAX = 8 }; - - tusb_desc_device_t _desc_device; - - uint8_t *_desc_cfg; - uint8_t _desc_cfg_buffer[256]; - uint16_t _desc_cfg_len; - uint16_t _desc_cfg_maxlen; - - uint8_t _itf_count; - - uint8_t _epin_count; - uint8_t _epout_count; - - const char* _desc_str_arr[STRING_DESCRIPTOR_MAX]; - uint8_t _desc_str_count; - - public: - Adafruit_USBD_Device(void); - - bool addInterface(Adafruit_USBD_Interface& itf); - void setDescriptorBuffer(uint8_t* buf, uint32_t buflen); - - void setID(uint16_t vid, uint16_t pid); - void setVersion(uint16_t bcd); - void setDeviceVersion(uint16_t bcd); - - void setLanguageDescriptor(uint16_t language_id); - void setManufacturerDescriptor(const char *s); - void setProductDescriptor(const char *s); - - bool begin(void); - - bool mounted (void) { return tud_mounted(); } - bool suspended (void) { return tud_suspended(); } - bool ready (void) { return tud_ready(); } - bool remoteWakeup (void) { return tud_remote_wakeup(); } - - bool detach (void); // physical detach by disable pull-up - bool attach (void); // physical attach by enable pull-up - - //------------- Platform Dependent APIs -------------// - uint8_t getSerialDescriptor(uint16_t* serial_str); - - private: - uint16_t const* descriptor_string_cb(uint8_t index, uint16_t langid); - - friend uint8_t const * tud_descriptor_device_cb(void); - friend uint8_t const * tud_descriptor_configuration_cb(uint8_t index); - friend uint16_t const* tud_descriptor_string_cb(uint8_t index, uint16_t langid); -}; +extern Adafruit_USBD_Device TinyUSBDevice; -extern Adafruit_USBD_Device USBDevice; +// USBDevice has a high chance to conflict with other usb stack +// only define if supported BSP +#ifdef USE_TINYUSB +#define USBDevice TinyUSBDevice +#endif -#endif /* ADAFRUIT_USBD_DEVICE_H_ */ +#endif /* CFG_TUD_ENABLED */ +#endif /* ADAFRUIT_USBD_DEVICE_H_ */ \ No newline at end of file diff --git a/cores/nRF5/TinyUSB/Adafruit_TinyUSB_ArduinoCore/tinyusb/src/portable/valentyusb/eptri/dcd_eptri.h b/cores/nRF5/TinyUSB/Adafruit_TinyUSB_ArduinoCore/Adafruit_USBD_Interface.cpp similarity index 77% rename from cores/nRF5/TinyUSB/Adafruit_TinyUSB_ArduinoCore/tinyusb/src/portable/valentyusb/eptri/dcd_eptri.h rename to cores/nRF5/TinyUSB/Adafruit_TinyUSB_ArduinoCore/Adafruit_USBD_Interface.cpp index 0fa6ecc..eabc757 100644 --- a/cores/nRF5/TinyUSB/Adafruit_TinyUSB_ArduinoCore/tinyusb/src/portable/valentyusb/eptri/dcd_eptri.h +++ b/cores/nRF5/TinyUSB/Adafruit_TinyUSB_ArduinoCore/Adafruit_USBD_Interface.cpp @@ -1,7 +1,7 @@ -/* +/* * The MIT License (MIT) * - * Copyright (c) 2019 Ha Thach (tinyusb.org) + * Copyright (c) 2021 Ha Thach (tinyusb.org) for Adafruit Industries * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -20,20 +20,16 @@ * 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_DCD_VALENTYUSB_EPTRI_H_ -#define _TUSB_DCD_VALENTYUSB_EPTRI_H_ +#include "tusb_option.h" + +#if CFG_TUD_ENABLED -#include "common/tusb_common.h" -#ifdef __cplusplus - extern "C" { -#endif +#include "Adafruit_USBD_Device.h" -#ifdef __cplusplus - } -#endif +void Adafruit_USBD_Interface::setStringDescriptor(const char *str) { + _strid = TinyUSBDevice.addStringDescriptor(str); +} -#endif /* _TUSB_DCD_VALENTYUSB_EPTRI_H_ */ +#endif \ No newline at end of file diff --git a/cores/nRF5/TinyUSB/Adafruit_TinyUSB_ArduinoCore/tinyusb/src/portable/nxp/lpc18_43/hcd_lpc18_43.c b/cores/nRF5/TinyUSB/Adafruit_TinyUSB_ArduinoCore/Adafruit_USBD_Interface.h similarity index 54% rename from cores/nRF5/TinyUSB/Adafruit_TinyUSB_ArduinoCore/tinyusb/src/portable/nxp/lpc18_43/hcd_lpc18_43.c rename to cores/nRF5/TinyUSB/Adafruit_TinyUSB_ArduinoCore/Adafruit_USBD_Interface.h index cb509e7..8457f69 100644 --- a/cores/nRF5/TinyUSB/Adafruit_TinyUSB_ArduinoCore/tinyusb/src/portable/nxp/lpc18_43/hcd_lpc18_43.c +++ b/cores/nRF5/TinyUSB/Adafruit_TinyUSB_ArduinoCore/Adafruit_USBD_Interface.h @@ -1,7 +1,7 @@ -/* +/* * The MIT License (MIT) * - * Copyright (c) 2019 Ha Thach (tinyusb.org) + * Copyright (c) 2021 Ha Thach (tinyusb.org) for Adafruit Industries * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -20,31 +20,37 @@ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. - * - * This file is part of the TinyUSB stack. */ -#include "tusb_option.h" +#ifndef ADAFRUIT_USBD_INTERFACE_H_ +#define ADAFRUIT_USBD_INTERFACE_H_ -#if TUSB_OPT_HOST_ENABLED && (CFG_TUSB_MCU == OPT_MCU_LPC18XX || CFG_TUSB_MCU == OPT_MCU_LPC43XX) +#include +#include -#include "chip.h" +#if defined(ARDUINO_ARCH_CH32) || defined(CH32V20x) || defined(CH32V30x) +// HACK: required for ch32 core version 1.0.4 or prior, removed when 1.0.5 is +// released +extern "C" void yield(void); +#endif -// LPC18xx and 43xx use EHCI driver +class Adafruit_USBD_Interface { +protected: + uint8_t _strid; -void hcd_int_enable(uint8_t rhport) -{ - NVIC_EnableIRQ(rhport ? USB1_IRQn : USB0_IRQn); -} +public: + Adafruit_USBD_Interface(void) { _strid = 0; } -void hcd_int_disable(uint8_t rhport) -{ - NVIC_DisableIRQ(rhport ? USB1_IRQn : USB0_IRQn); -} + // Get Interface Descriptor + // Fill the descriptor (if buf is not NULL) and return its length + virtual uint16_t getInterfaceDescriptor(uint8_t itfnum_deprecated, + uint8_t *buf, uint16_t bufsize) = 0; + // Get Interface Descriptor Length + uint16_t getInterfaceDescriptorLen() { + return getInterfaceDescriptor(0, NULL, 0); + } -uint32_t hcd_ehci_register_addr(uint8_t rhport) -{ - return (uint32_t) (rhport ? &LPC_USB1->USBCMD_H : &LPC_USB0->USBCMD_H ); -} + void setStringDescriptor(const char *str); +}; #endif diff --git a/cores/nRF5/TinyUSB/Adafruit_TinyUSB_ArduinoCore/README.md b/cores/nRF5/TinyUSB/Adafruit_TinyUSB_ArduinoCore/README.md deleted file mode 100644 index dc0a72f..0000000 --- a/cores/nRF5/TinyUSB/Adafruit_TinyUSB_ArduinoCore/README.md +++ /dev/null @@ -1,32 +0,0 @@ -# Adafruit TinyUSB Core for Arduino - -This repo is Arduino compatible variant from [TinyUSB stack](https://github.com/hathach/tinyusb) project to provide core USB functionality and CDC support for Arduino. Other class drivers such as Mass Storage, HID, MIDI etc ... are provided by [Adafruit_TinyUSB_Arduino](https://github.com/adafruit/Adafruit_TinyUSB_Arduino). - -## Porting - -Currently Arduino TinyUSB is used by following cores - -- [Adafruit_nRF52_Arduino](https://github.com/adafruit/Adafruit_nRF52_Arduino) -- [Adafruit ArduinoCore-samd](https://github.com/adafruit/ArduinoCore-samd) **TinyUSB** must be selected in menu `Tools->USB Stack` - -But it is also easy to port it to your own BSP as follows: - -### Add the codes - -Include this repo as submodule to your BPS cores folder e.g - -``` -$ git submodule add https://github.com/adafruit/Adafruit_TinyUSB_ArduinoCore.git cores/arduino/TinyUSB/Adafruit_TinyUSB_ArduinoCore -``` - -Alternatively you could just copy the files over but will need to periodically sync to get the latest patches, features. - -### Write the platform dependent codes - -You will need to create 2 files - -- `tusb_config.h` for configuration that best suites your port and -- `Adafruit_TinyUSB_port.cpp` to implement platform-dependent functions - - **Adafruit_TinyUSB_Core_init()** to initialize USB hardware (clock, pullups) and tinyusb stack - - **Adafruit_TinyUSB_Core_touch1200()** callback that fired when IDE use touch 1200 feature to put board into DFU mode - - **Adafruit_USBD_Device** getSerialDescriptor(), detach(), attach() diff --git a/cores/nRF5/TinyUSB/Adafruit_TinyUSB_ArduinoCore/tinyusb/src/class/audio/audio.h b/cores/nRF5/TinyUSB/Adafruit_TinyUSB_ArduinoCore/tinyusb/src/class/audio/audio.h index 5bec14d..0d1acad 100644 --- a/cores/nRF5/TinyUSB/Adafruit_TinyUSB_ArduinoCore/tinyusb/src/class/audio/audio.h +++ b/cores/nRF5/TinyUSB/Adafruit_TinyUSB_ArduinoCore/tinyusb/src/class/audio/audio.h @@ -1,7 +1,8 @@ -/* +/* * The MIT License (MIT) * * Copyright (c) 2019 Ha Thach (tinyusb.org) + * Copyright (c) 2020 Reinhard Panhuber * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -35,28 +36,44 @@ #include "common/tusb_common.h" #ifdef __cplusplus - extern "C" { +extern "C" { #endif -/// Audio Interface Subclass Codes +/// Audio Device Class Codes + +/// A.2 - Audio Function Subclass Codes +typedef enum +{ + AUDIO_FUNCTION_SUBCLASS_UNDEFINED = 0x00, +} audio_function_subclass_type_t; + +/// A.3 - Audio Function Protocol Codes +typedef enum +{ + AUDIO_FUNC_PROTOCOL_CODE_UNDEF = 0x00, + AUDIO_FUNC_PROTOCOL_CODE_V2 = 0x20, ///< Version 2.0 +} audio_function_protocol_code_t; + +/// A.5 - Audio Interface Subclass Codes typedef enum { - AUDIO_SUBCLASS_CONTROL = 0x01 , ///< Audio Control + AUDIO_SUBCLASS_UNDEFINED = 0x00, + AUDIO_SUBCLASS_CONTROL , ///< Audio Control AUDIO_SUBCLASS_STREAMING , ///< Audio Streaming AUDIO_SUBCLASS_MIDI_STREAMING , ///< MIDI Streaming } audio_subclass_type_t; -/// Audio Protocol Codes +/// A.6 - Audio Interface Protocol Codes typedef enum { - AUDIO_PROTOCOL_V1 = 0x00, ///< Version 1.0 - AUDIO_PROTOCOL_V2 = 0x20, ///< Version 2.0 - AUDIO_PROTOCOL_V3 = 0x30, ///< Version 3.0 -} audio_protocol_type_t; + AUDIO_INT_PROTOCOL_CODE_UNDEF = 0x00, + AUDIO_INT_PROTOCOL_CODE_V2 = 0x20, ///< Version 2.0 +} audio_interface_protocol_code_t; -/// Audio Function Category Codes +/// A.7 - Audio Function Category Codes typedef enum { + AUDIO_FUNC_UNDEF = 0x00, AUDIO_FUNC_DESKTOP_SPEAKER = 0x01, AUDIO_FUNC_HOME_THEATER = 0x02, AUDIO_FUNC_MICROPHONE = 0x03, @@ -68,31 +85,875 @@ typedef enum AUDIO_FUNC_MUSICAL_INSTRUMENT = 0x09, AUDIO_FUNC_PRO_AUDIO = 0x0A, AUDIO_FUNC_AUDIO_VIDEO = 0x0B, - AUDIO_FUNC_CONTROL_PANEL = 0x0C -} audio_function_t; - -/// Audio Class-Specific AC Interface Descriptor Subtypes -typedef enum -{ - AUDIO_CS_INTERFACE_HEADER = 0x01, - AUDIO_CS_INTERFACE_INPUT_TERMINAL = 0x02, - AUDIO_CS_INTERFACE_OUTPUT_TERMINAL = 0x03, - AUDIO_CS_INTERFACE_MIXER_UNIT = 0x04, - AUDIO_CS_INTERFACE_SELECTOR_UNIT = 0x05, - AUDIO_CS_INTERFACE_FEATURE_UNIT = 0x06, - AUDIO_CS_INTERFACE_EFFECT_UNIT = 0x07, - AUDIO_CS_INTERFACE_PROCESSING_UNIT = 0x08, - AUDIO_CS_INTERFACE_EXTENSION_UNIT = 0x09, - AUDIO_CS_INTERFACE_CLOCK_SOURCE = 0x0A, - AUDIO_CS_INTERFACE_CLOCK_SELECTOR = 0x0B, - AUDIO_CS_INTERFACE_CLOCK_MULTIPLIER = 0x0C, - AUDIO_CS_INTERFACE_SAMPLE_RATE_CONVERTER = 0x0D, -} audio_cs_interface_subtype_t; + AUDIO_FUNC_CONTROL_PANEL = 0x0C, + AUDIO_FUNC_OTHER = 0xFF, +} audio_function_code_t; + +/// A.9 - Audio Class-Specific AC Interface Descriptor Subtypes UAC2 +typedef enum +{ + AUDIO_CS_AC_INTERFACE_AC_DESCRIPTOR_UNDEF = 0x00, + AUDIO_CS_AC_INTERFACE_HEADER = 0x01, + AUDIO_CS_AC_INTERFACE_INPUT_TERMINAL = 0x02, + AUDIO_CS_AC_INTERFACE_OUTPUT_TERMINAL = 0x03, + AUDIO_CS_AC_INTERFACE_MIXER_UNIT = 0x04, + AUDIO_CS_AC_INTERFACE_SELECTOR_UNIT = 0x05, + AUDIO_CS_AC_INTERFACE_FEATURE_UNIT = 0x06, + AUDIO_CS_AC_INTERFACE_EFFECT_UNIT = 0x07, + AUDIO_CS_AC_INTERFACE_PROCESSING_UNIT = 0x08, + AUDIO_CS_AC_INTERFACE_EXTENSION_UNIT = 0x09, + AUDIO_CS_AC_INTERFACE_CLOCK_SOURCE = 0x0A, + AUDIO_CS_AC_INTERFACE_CLOCK_SELECTOR = 0x0B, + AUDIO_CS_AC_INTERFACE_CLOCK_MULTIPLIER = 0x0C, + AUDIO_CS_AC_INTERFACE_SAMPLE_RATE_CONVERTER = 0x0D, +} audio_cs_ac_interface_subtype_t; + +/// A.10 - Audio Class-Specific AS Interface Descriptor Subtypes UAC2 +typedef enum +{ + AUDIO_CS_AS_INTERFACE_AS_DESCRIPTOR_UNDEF = 0x00, + AUDIO_CS_AS_INTERFACE_AS_GENERAL = 0x01, + AUDIO_CS_AS_INTERFACE_FORMAT_TYPE = 0x02, + AUDIO_CS_AS_INTERFACE_ENCODER = 0x03, + AUDIO_CS_AS_INTERFACE_DECODER = 0x04, +} audio_cs_as_interface_subtype_t; + +/// A.11 - Effect Unit Effect Types +typedef enum +{ + AUDIO_EFFECT_TYPE_UNDEF = 0x00, + AUDIO_EFFECT_TYPE_PARAM_EQ_SECTION = 0x01, + AUDIO_EFFECT_TYPE_REVERBERATION = 0x02, + AUDIO_EFFECT_TYPE_MOD_DELAY = 0x03, + AUDIO_EFFECT_TYPE_DYN_RANGE_COMP = 0x04, +} audio_effect_unit_effect_type_t; + +/// A.12 - Processing Unit Process Types +typedef enum +{ + AUDIO_PROCESS_TYPE_UNDEF = 0x00, + AUDIO_PROCESS_TYPE_UP_DOWN_MIX = 0x01, + AUDIO_PROCESS_TYPE_DOLBY_PROLOGIC = 0x02, + AUDIO_PROCESS_TYPE_STEREO_EXTENDER = 0x03, +} audio_processing_unit_process_type_t; + +/// A.13 - Audio Class-Specific EP Descriptor Subtypes UAC2 +typedef enum +{ + AUDIO_CS_EP_SUBTYPE_UNDEF = 0x00, + AUDIO_CS_EP_SUBTYPE_GENERAL = 0x01, +} audio_cs_ep_subtype_t; + +/// A.14 - Audio Class-Specific Request Codes +typedef enum +{ + AUDIO_CS_REQ_UNDEF = 0x00, + AUDIO_CS_REQ_CUR = 0x01, + AUDIO_CS_REQ_RANGE = 0x02, + AUDIO_CS_REQ_MEM = 0x03, +} audio_cs_req_t; + +/// A.17 - Control Selector Codes + +/// A.17.1 - Clock Source Control Selectors +typedef enum +{ + AUDIO_CS_CTRL_UNDEF = 0x00, + AUDIO_CS_CTRL_SAM_FREQ = 0x01, + AUDIO_CS_CTRL_CLK_VALID = 0x02, +} audio_clock_src_control_selector_t; + +/// A.17.2 - Clock Selector Control Selectors +typedef enum +{ + AUDIO_CX_CTRL_UNDEF = 0x00, + AUDIO_CX_CTRL_CONTROL = 0x01, +} audio_clock_sel_control_selector_t; + +/// A.17.3 - Clock Multiplier Control Selectors +typedef enum +{ + AUDIO_CM_CTRL_UNDEF = 0x00, + AUDIO_CM_CTRL_NUMERATOR_CONTROL = 0x01, + AUDIO_CM_CTRL_DENOMINATOR_CONTROL = 0x02, +} audio_clock_mul_control_selector_t; + +/// A.17.4 - Terminal Control Selectors +typedef enum +{ + AUDIO_TE_CTRL_UNDEF = 0x00, + AUDIO_TE_CTRL_COPY_PROTECT = 0x01, + AUDIO_TE_CTRL_CONNECTOR = 0x02, + AUDIO_TE_CTRL_OVERLOAD = 0x03, + AUDIO_TE_CTRL_CLUSTER = 0x04, + AUDIO_TE_CTRL_UNDERFLOW = 0x05, + AUDIO_TE_CTRL_OVERFLOW = 0x06, + AUDIO_TE_CTRL_LATENCY = 0x07, +} audio_terminal_control_selector_t; + +/// A.17.5 - Mixer Control Selectors +typedef enum +{ + AUDIO_MU_CTRL_UNDEF = 0x00, + AUDIO_MU_CTRL_MIXER = 0x01, + AUDIO_MU_CTRL_CLUSTER = 0x02, + AUDIO_MU_CTRL_UNDERFLOW = 0x03, + AUDIO_MU_CTRL_OVERFLOW = 0x04, + AUDIO_MU_CTRL_LATENCY = 0x05, +} audio_mixer_control_selector_t; + +/// A.17.6 - Selector Control Selectors +typedef enum +{ + AUDIO_SU_CTRL_UNDEF = 0x00, + AUDIO_SU_CTRL_SELECTOR = 0x01, + AUDIO_SU_CTRL_LATENCY = 0x02, +} audio_sel_control_selector_t; + +/// A.17.7 - Feature Unit Control Selectors +typedef enum +{ + AUDIO_FU_CTRL_UNDEF = 0x00, + AUDIO_FU_CTRL_MUTE = 0x01, + AUDIO_FU_CTRL_VOLUME = 0x02, + AUDIO_FU_CTRL_BASS = 0x03, + AUDIO_FU_CTRL_MID = 0x04, + AUDIO_FU_CTRL_TREBLE = 0x05, + AUDIO_FU_CTRL_GRAPHIC_EQUALIZER = 0x06, + AUDIO_FU_CTRL_AGC = 0x07, + AUDIO_FU_CTRL_DELAY = 0x08, + AUDIO_FU_CTRL_BASS_BOOST = 0x09, + AUDIO_FU_CTRL_LOUDNESS = 0x0A, + AUDIO_FU_CTRL_INPUT_GAIN = 0x0B, + AUDIO_FU_CTRL_GAIN_PAD = 0x0C, + AUDIO_FU_CTRL_INVERTER = 0x0D, + AUDIO_FU_CTRL_UNDERFLOW = 0x0E, + AUDIO_FU_CTRL_OVERVLOW = 0x0F, + AUDIO_FU_CTRL_LATENCY = 0x10, +} audio_feature_unit_control_selector_t; + +/// A.17.8 Effect Unit Control Selectors + +/// A.17.8.1 Parametric Equalizer Section Effect Unit Control Selectors +typedef enum +{ + AUDIO_PE_CTRL_UNDEF = 0x00, + AUDIO_PE_CTRL_ENABLE = 0x01, + AUDIO_PE_CTRL_CENTERFREQ = 0x02, + AUDIO_PE_CTRL_QFACTOR = 0x03, + AUDIO_PE_CTRL_GAIN = 0x04, + AUDIO_PE_CTRL_UNDERFLOW = 0x05, + AUDIO_PE_CTRL_OVERFLOW = 0x06, + AUDIO_PE_CTRL_LATENCY = 0x07, +} audio_parametric_equalizer_control_selector_t; + +/// A.17.8.2 Reverberation Effect Unit Control Selectors +typedef enum +{ + AUDIO_RV_CTRL_UNDEF = 0x00, + AUDIO_RV_CTRL_ENABLE = 0x01, + AUDIO_RV_CTRL_TYPE = 0x02, + AUDIO_RV_CTRL_LEVEL = 0x03, + AUDIO_RV_CTRL_TIME = 0x04, + AUDIO_RV_CTRL_FEEDBACK = 0x05, + AUDIO_RV_CTRL_PREDELAY = 0x06, + AUDIO_RV_CTRL_DENSITY = 0x07, + AUDIO_RV_CTRL_HIFREQ_ROLLOFF = 0x08, + AUDIO_RV_CTRL_UNDERFLOW = 0x09, + AUDIO_RV_CTRL_OVERFLOW = 0x0A, + AUDIO_RV_CTRL_LATENCY = 0x0B, +} audio_reverberation_effect_control_selector_t; + +/// A.17.8.3 Modulation Delay Effect Unit Control Selectors +typedef enum +{ + AUDIO_MD_CTRL_UNDEF = 0x00, + AUDIO_MD_CTRL_ENABLE = 0x01, + AUDIO_MD_CTRL_BALANCE = 0x02, + AUDIO_MD_CTRL_RATE = 0x03, + AUDIO_MD_CTRL_DEPTH = 0x04, + AUDIO_MD_CTRL_TIME = 0x05, + AUDIO_MD_CTRL_FEEDBACK = 0x06, + AUDIO_MD_CTRL_UNDERFLOW = 0x07, + AUDIO_MD_CTRL_OVERFLOW = 0x08, + AUDIO_MD_CTRL_LATENCY = 0x09, +} audio_modulation_delay_control_selector_t; + +/// A.17.8.4 Dynamic Range Compressor Effect Unit Control Selectors +typedef enum +{ + AUDIO_DR_CTRL_UNDEF = 0x00, + AUDIO_DR_CTRL_ENABLE = 0x01, + AUDIO_DR_CTRL_COMPRESSION_RATE = 0x02, + AUDIO_DR_CTRL_MAXAMPL = 0x03, + AUDIO_DR_CTRL_THRESHOLD = 0x04, + AUDIO_DR_CTRL_ATTACK_TIME = 0x05, + AUDIO_DR_CTRL_RELEASE_TIME = 0x06, + AUDIO_DR_CTRL_UNDERFLOW = 0x07, + AUDIO_DR_CTRL_OVERFLOW = 0x08, + AUDIO_DR_CTRL_LATENCY = 0x09, +} audio_dynamic_range_compression_control_selector_t; + +/// A.17.9 Processing Unit Control Selectors + +/// A.17.9.1 Up/Down-mix Processing Unit Control Selectors +typedef enum +{ + AUDIO_UD_CTRL_UNDEF = 0x00, + AUDIO_UD_CTRL_ENABLE = 0x01, + AUDIO_UD_CTRL_MODE_SELECT = 0x02, + AUDIO_UD_CTRL_CLUSTER = 0x03, + AUDIO_UD_CTRL_UNDERFLOW = 0x04, + AUDIO_UD_CTRL_OVERFLOW = 0x05, + AUDIO_UD_CTRL_LATENCY = 0x06, +} audio_up_down_mix_control_selector_t; + +/// A.17.9.2 Dolby Prologic ™ Processing Unit Control Selectors +typedef enum +{ + AUDIO_DP_CTRL_UNDEF = 0x00, + AUDIO_DP_CTRL_ENABLE = 0x01, + AUDIO_DP_CTRL_MODE_SELECT = 0x02, + AUDIO_DP_CTRL_CLUSTER = 0x03, + AUDIO_DP_CTRL_UNDERFLOW = 0x04, + AUDIO_DP_CTRL_OVERFLOW = 0x05, + AUDIO_DP_CTRL_LATENCY = 0x06, +} audio_dolby_prologic_control_selector_t; + +/// A.17.9.3 Stereo Extender Processing Unit Control Selectors +typedef enum +{ + AUDIO_ST_EXT_CTRL_UNDEF = 0x00, + AUDIO_ST_EXT_CTRL_ENABLE = 0x01, + AUDIO_ST_EXT_CTRL_WIDTH = 0x02, + AUDIO_ST_EXT_CTRL_UNDERFLOW = 0x03, + AUDIO_ST_EXT_CTRL_OVERFLOW = 0x04, + AUDIO_ST_EXT_CTRL_LATENCY = 0x05, +} audio_stereo_extender_control_selector_t; + +/// A.17.10 Extension Unit Control Selectors +typedef enum +{ + AUDIO_XU_CTRL_UNDEF = 0x00, + AUDIO_XU_CTRL_ENABLE = 0x01, + AUDIO_XU_CTRL_CLUSTER = 0x02, + AUDIO_XU_CTRL_UNDERFLOW = 0x03, + AUDIO_XU_CTRL_OVERFLOW = 0x04, + AUDIO_XU_CTRL_LATENCY = 0x05, +} audio_extension_unit_control_selector_t; + +/// A.17.11 AudioStreaming Interface Control Selectors +typedef enum +{ + AUDIO_AS_CTRL_UNDEF = 0x00, + AUDIO_AS_CTRL_ACT_ALT_SETTING = 0x01, + AUDIO_AS_CTRL_VAL_ALT_SETTINGS = 0x02, + AUDIO_AS_CTRL_AUDIO_DATA_FORMAT = 0x03, +} audio_audiostreaming_interface_control_selector_t; + +/// A.17.12 Encoder Control Selectors +typedef enum +{ + AUDIO_EN_CTRL_UNDEF = 0x00, + AUDIO_EN_CTRL_BIT_RATE = 0x01, + AUDIO_EN_CTRL_QUALITY = 0x02, + AUDIO_EN_CTRL_VBR = 0x03, + AUDIO_EN_CTRL_TYPE = 0x04, + AUDIO_EN_CTRL_UNDERFLOW = 0x05, + AUDIO_EN_CTRL_OVERFLOW = 0x06, + AUDIO_EN_CTRL_ENCODER_ERROR = 0x07, + AUDIO_EN_CTRL_PARAM1 = 0x08, + AUDIO_EN_CTRL_PARAM2 = 0x09, + AUDIO_EN_CTRL_PARAM3 = 0x0A, + AUDIO_EN_CTRL_PARAM4 = 0x0B, + AUDIO_EN_CTRL_PARAM5 = 0x0C, + AUDIO_EN_CTRL_PARAM6 = 0x0D, + AUDIO_EN_CTRL_PARAM7 = 0x0E, + AUDIO_EN_CTRL_PARAM8 = 0x0F, +} audio_encoder_control_selector_t; + +/// A.17.13 Decoder Control Selectors + +/// A.17.13.1 MPEG Decoder Control Selectors +typedef enum +{ + AUDIO_MPD_CTRL_UNDEF = 0x00, + AUDIO_MPD_CTRL_DUAL_CHANNEL = 0x01, + AUDIO_MPD_CTRL_SECOND_STEREO = 0x02, + AUDIO_MPD_CTRL_MULTILINGUAL = 0x03, + AUDIO_MPD_CTRL_DYN_RANGE = 0x04, + AUDIO_MPD_CTRL_SCALING = 0x05, + AUDIO_MPD_CTRL_HILO_SCALING = 0x06, + AUDIO_MPD_CTRL_UNDERFLOW = 0x07, + AUDIO_MPD_CTRL_OVERFLOW = 0x08, + AUDIO_MPD_CTRL_DECODER_ERROR = 0x09, +} audio_MPEG_decoder_control_selector_t; + +/// A.17.13.2 AC-3 Decoder Control Selectors +typedef enum +{ + AUDIO_AD_CTRL_UNDEF = 0x00, + AUDIO_AD_CTRL_MODE = 0x01, + AUDIO_AD_CTRL_DYN_RANGE = 0x02, + AUDIO_AD_CTRL_SCALING = 0x03, + AUDIO_AD_CTRL_HILO_SCALING = 0x04, + AUDIO_AD_CTRL_UNDERFLOW = 0x05, + AUDIO_AD_CTRL_OVERFLOW = 0x06, + AUDIO_AD_CTRL_DECODER_ERROR = 0x07, +} audio_AC3_decoder_control_selector_t; + +/// A.17.13.3 WMA Decoder Control Selectors +typedef enum +{ + AUDIO_WD_CTRL_UNDEF = 0x00, + AUDIO_WD_CTRL_UNDERFLOW = 0x01, + AUDIO_WD_CTRL_OVERFLOW = 0x02, + AUDIO_WD_CTRL_DECODER_ERROR = 0x03, +} audio_WMA_decoder_control_selector_t; + +/// A.17.13.4 DTS Decoder Control Selectors +typedef enum +{ + AUDIO_DD_CTRL_UNDEF = 0x00, + AUDIO_DD_CTRL_UNDERFLOW = 0x01, + AUDIO_DD_CTRL_OVERFLOW = 0x02, + AUDIO_DD_CTRL_DECODER_ERROR = 0x03, +} audio_DTS_decoder_control_selector_t; + +/// A.17.14 Endpoint Control Selectors +typedef enum +{ + AUDIO_EP_CTRL_UNDEF = 0x00, + AUDIO_EP_CTRL_PITCH = 0x01, + AUDIO_EP_CTRL_DATA_OVERRUN = 0x02, + AUDIO_EP_CTRL_DATA_UNDERRUN = 0x03, +} audio_EP_control_selector_t; + +/// Terminal Types + +/// 2.1 - Audio Class-Terminal Types UAC2 +typedef enum +{ + AUDIO_TERM_TYPE_USB_UNDEFINED = 0x0100, + AUDIO_TERM_TYPE_USB_STREAMING = 0x0101, + AUDIO_TERM_TYPE_USB_VENDOR_SPEC = 0x01FF, +} audio_terminal_type_t; + +/// 2.2 - Audio Class-Input Terminal Types UAC2 +typedef enum +{ + AUDIO_TERM_TYPE_IN_UNDEFINED = 0x0200, + AUDIO_TERM_TYPE_IN_GENERIC_MIC = 0x0201, + AUDIO_TERM_TYPE_IN_DESKTOP_MIC = 0x0202, + AUDIO_TERM_TYPE_IN_PERSONAL_MIC = 0x0203, + AUDIO_TERM_TYPE_IN_OMNI_MIC = 0x0204, + AUDIO_TERM_TYPE_IN_ARRAY_MIC = 0x0205, + AUDIO_TERM_TYPE_IN_PROC_ARRAY_MIC = 0x0206, +} audio_terminal_input_type_t; + +/// 2.3 - Audio Class-Output Terminal Types UAC2 +typedef enum +{ + AUDIO_TERM_TYPE_OUT_UNDEFINED = 0x0300, + AUDIO_TERM_TYPE_OUT_GENERIC_SPEAKER = 0x0301, + AUDIO_TERM_TYPE_OUT_HEADPHONES = 0x0302, + AUDIO_TERM_TYPE_OUT_HEAD_MNT_DISP_AUIDO = 0x0303, + AUDIO_TERM_TYPE_OUT_DESKTOP_SPEAKER = 0x0304, + AUDIO_TERM_TYPE_OUT_ROOM_SPEAKER = 0x0305, + AUDIO_TERM_TYPE_OUT_COMMUNICATION_SPEAKER = 0x0306, + AUDIO_TERM_TYPE_OUT_LOW_FRQ_EFFECTS_SPEAKER = 0x0307, +} audio_terminal_output_type_t; + +/// Rest is yet to be implemented + +/// Additional Audio Device Class Codes - Source: Audio Data Formats + +/// A.1 - Audio Class-Format Type Codes UAC2 +typedef enum +{ + AUDIO_FORMAT_TYPE_UNDEFINED = 0x00, + AUDIO_FORMAT_TYPE_I = 0x01, + AUDIO_FORMAT_TYPE_II = 0x02, + AUDIO_FORMAT_TYPE_III = 0x03, + AUDIO_FORMAT_TYPE_IV = 0x04, + AUDIO_EXT_FORMAT_TYPE_I = 0x81, + AUDIO_EXT_FORMAT_TYPE_II = 0x82, + AUDIO_EXT_FORMAT_TYPE_III = 0x83, +} audio_format_type_t; + +// A.2.1 - Audio Class-Audio Data Format Type I UAC2 +typedef enum +{ + AUDIO_DATA_FORMAT_TYPE_I_PCM = (uint32_t) (1 << 0), + AUDIO_DATA_FORMAT_TYPE_I_PCM8 = (uint32_t) (1 << 1), + AUDIO_DATA_FORMAT_TYPE_I_IEEE_FLOAT = (uint32_t) (1 << 2), + AUDIO_DATA_FORMAT_TYPE_I_ALAW = (uint32_t) (1 << 3), + AUDIO_DATA_FORMAT_TYPE_I_MULAW = (uint32_t) (1 << 4), + AUDIO_DATA_FORMAT_TYPE_I_RAW_DATA = 0x80000000u, +} audio_data_format_type_I_t; + +/// All remaining definitions are taken from the descriptor descriptions in the UAC2 main specification + +/// Audio Class-Control Values UAC2 +typedef enum +{ + AUDIO_CTRL_NONE = 0x00, ///< No Host access + AUDIO_CTRL_R = 0x01, ///< Host read access only + AUDIO_CTRL_RW = 0x03, ///< Host read write access +} audio_control_t; + +/// Audio Class-Specific AC Interface Descriptor Controls UAC2 +typedef enum +{ + AUDIO_CS_AS_INTERFACE_CTRL_LATENCY_POS = 0, +} audio_cs_ac_interface_control_pos_t; + +/// Audio Class-Specific AS Interface Descriptor Controls UAC2 +typedef enum +{ + AUDIO_CS_AS_INTERFACE_CTRL_ACTIVE_ALT_SET_POS = 0, + AUDIO_CS_AS_INTERFACE_CTRL_VALID_ALT_SET_POS = 2, +} audio_cs_as_interface_control_pos_t; + +/// Audio Class-Specific AS Isochronous Data EP Attributes UAC2 +typedef enum +{ + AUDIO_CS_AS_ISO_DATA_EP_ATT_MAX_PACKETS_ONLY = 0x80, + AUDIO_CS_AS_ISO_DATA_EP_ATT_NON_MAX_PACKETS_OK = 0x00, +} audio_cs_as_iso_data_ep_attribute_t; + +/// Audio Class-Specific AS Isochronous Data EP Controls UAC2 +typedef enum +{ + AUDIO_CS_AS_ISO_DATA_EP_CTRL_PITCH_POS = 0, + AUDIO_CS_AS_ISO_DATA_EP_CTRL_DATA_OVERRUN_POS = 2, + AUDIO_CS_AS_ISO_DATA_EP_CTRL_DATA_UNDERRUN_POS = 4, +} audio_cs_as_iso_data_ep_control_pos_t; + +/// Audio Class-Specific AS Isochronous Data EP Lock Delay Units UAC2 +typedef enum +{ + AUDIO_CS_AS_ISO_DATA_EP_LOCK_DELAY_UNIT_UNDEFINED = 0x00, + AUDIO_CS_AS_ISO_DATA_EP_LOCK_DELAY_UNIT_MILLISEC = 0x01, + AUDIO_CS_AS_ISO_DATA_EP_LOCK_DELAY_UNIT_PCM_SAMPLES = 0x02, +} audio_cs_as_iso_data_ep_lock_delay_unit_t; + +/// Audio Class-Clock Source Attributes UAC2 +typedef enum +{ + AUDIO_CLOCK_SOURCE_ATT_EXT_CLK = 0x00, + AUDIO_CLOCK_SOURCE_ATT_INT_FIX_CLK = 0x01, + AUDIO_CLOCK_SOURCE_ATT_INT_VAR_CLK = 0x02, + AUDIO_CLOCK_SOURCE_ATT_INT_PRO_CLK = 0x03, + AUDIO_CLOCK_SOURCE_ATT_CLK_SYC_SOF = 0x04, +} audio_clock_source_attribute_t; + +/// Audio Class-Clock Source Controls UAC2 +typedef enum +{ + AUDIO_CLOCK_SOURCE_CTRL_CLK_FRQ_POS = 0, + AUDIO_CLOCK_SOURCE_CTRL_CLK_VAL_POS = 2, +} audio_clock_source_control_pos_t; + +/// Audio Class-Clock Selector Controls UAC2 +typedef enum +{ + AUDIO_CLOCK_SELECTOR_CTRL_POS = 0, +} audio_clock_selector_control_pos_t; + +/// Audio Class-Clock Multiplier Controls UAC2 +typedef enum +{ + AUDIO_CLOCK_MULTIPLIER_CTRL_NUMERATOR_POS = 0, + AUDIO_CLOCK_MULTIPLIER_CTRL_DENOMINATOR_POS = 2, +} audio_clock_multiplier_control_pos_t; + +/// Audio Class-Input Terminal Controls UAC2 +typedef enum +{ + AUDIO_IN_TERM_CTRL_CPY_PROT_POS = 0, + AUDIO_IN_TERM_CTRL_CONNECTOR_POS = 2, + AUDIO_IN_TERM_CTRL_OVERLOAD_POS = 4, + AUDIO_IN_TERM_CTRL_CLUSTER_POS = 6, + AUDIO_IN_TERM_CTRL_UNDERFLOW_POS = 8, + AUDIO_IN_TERM_CTRL_OVERFLOW_POS = 10, +} audio_terminal_input_control_pos_t; + +/// Audio Class-Output Terminal Controls UAC2 +typedef enum +{ + AUDIO_OUT_TERM_CTRL_CPY_PROT_POS = 0, + AUDIO_OUT_TERM_CTRL_CONNECTOR_POS = 2, + AUDIO_OUT_TERM_CTRL_OVERLOAD_POS = 4, + AUDIO_OUT_TERM_CTRL_UNDERFLOW_POS = 6, + AUDIO_OUT_TERM_CTRL_OVERFLOW_POS = 8, +} audio_terminal_output_control_pos_t; + +/// Audio Class-Feature Unit Controls UAC2 +typedef enum +{ + AUDIO_FEATURE_UNIT_CTRL_MUTE_POS = 0, + AUDIO_FEATURE_UNIT_CTRL_VOLUME_POS = 2, + AUDIO_FEATURE_UNIT_CTRL_BASS_POS = 4, + AUDIO_FEATURE_UNIT_CTRL_MID_POS = 6, + AUDIO_FEATURE_UNIT_CTRL_TREBLE_POS = 8, + AUDIO_FEATURE_UNIT_CTRL_GRAPHIC_EQU_POS = 10, + AUDIO_FEATURE_UNIT_CTRL_AGC_POS = 12, + AUDIO_FEATURE_UNIT_CTRL_DELAY_POS = 14, + AUDIO_FEATURE_UNIT_CTRL_BASS_BOOST_POS = 16, + AUDIO_FEATURE_UNIT_CTRL_LOUDNESS_POS = 18, + AUDIO_FEATURE_UNIT_CTRL_INPUT_GAIN_POS = 20, + AUDIO_FEATURE_UNIT_CTRL_INPUT_GAIN_PAD_POS = 22, + AUDIO_FEATURE_UNIT_CTRL_PHASE_INV_POS = 24, + AUDIO_FEATURE_UNIT_CTRL_UNDERFLOW_POS = 26, + AUDIO_FEATURE_UNIT_CTRL_OVERFLOW_POS = 28, +} audio_feature_unit_control_pos_t; + +/// Audio Class-Audio Channel Configuration UAC2 +typedef enum +{ + AUDIO_CHANNEL_CONFIG_NON_PREDEFINED = 0x00000000, + AUDIO_CHANNEL_CONFIG_FRONT_LEFT = 0x00000001, + AUDIO_CHANNEL_CONFIG_FRONT_RIGHT = 0x00000002, + AUDIO_CHANNEL_CONFIG_FRONT_CENTER = 0x00000004, + AUDIO_CHANNEL_CONFIG_LOW_FRQ_EFFECTS = 0x00000008, + AUDIO_CHANNEL_CONFIG_BACK_LEFT = 0x00000010, + AUDIO_CHANNEL_CONFIG_BACK_RIGHT = 0x00000020, + AUDIO_CHANNEL_CONFIG_FRONT_LEFT_OF_CENTER = 0x00000040, + AUDIO_CHANNEL_CONFIG_FRONT_RIGHT_OF_CENTER = 0x00000080, + AUDIO_CHANNEL_CONFIG_BACK_CENTER = 0x00000100, + AUDIO_CHANNEL_CONFIG_SIDE_LEFT = 0x00000200, + AUDIO_CHANNEL_CONFIG_SIDE_RIGHT = 0x00000400, + AUDIO_CHANNEL_CONFIG_TOP_CENTER = 0x00000800, + AUDIO_CHANNEL_CONFIG_TOP_FRONT_LEFT = 0x00001000, + AUDIO_CHANNEL_CONFIG_TOP_FRONT_CENTER = 0x00002000, + AUDIO_CHANNEL_CONFIG_TOP_FRONT_RIGHT = 0x00004000, + AUDIO_CHANNEL_CONFIG_TOP_BACK_LEFT = 0x00008000, + AUDIO_CHANNEL_CONFIG_TOP_BACK_CENTER = 0x00010000, + AUDIO_CHANNEL_CONFIG_TOP_BACK_RIGHT = 0x00020000, + AUDIO_CHANNEL_CONFIG_TOP_FRONT_LEFT_OF_CENTER = 0x00040000, + AUDIO_CHANNEL_CONFIG_TOP_FRONT_RIGHT_OF_CENTER = 0x00080000, + AUDIO_CHANNEL_CONFIG_LEFT_LOW_FRQ_EFFECTS = 0x00100000, + AUDIO_CHANNEL_CONFIG_RIGHT_LOW_FRQ_EFFECTS = 0x00200000, + AUDIO_CHANNEL_CONFIG_TOP_SIDE_LEFT = 0x00400000, + AUDIO_CHANNEL_CONFIG_TOP_SIDE_RIGHT = 0x00800000, + AUDIO_CHANNEL_CONFIG_BOTTOM_CENTER = 0x01000000, + AUDIO_CHANNEL_CONFIG_BACK_LEFT_OF_CENTER = 0x02000000, + AUDIO_CHANNEL_CONFIG_BACK_RIGHT_OF_CENTER = 0x04000000, + AUDIO_CHANNEL_CONFIG_RAW_DATA = 0x80000000u, +} audio_channel_config_t; + +/// AUDIO Channel Cluster Descriptor (4.1) +typedef struct TU_ATTR_PACKED { + uint8_t bNrChannels; ///< Number of channels currently connected. + audio_channel_config_t bmChannelConfig; ///< Bitmap according to 'audio_channel_config_t' with a 1 set if channel is connected and 0 else. In case channels are non-predefined ignore them here (see UAC2 specification 4.1 Audio Channel Cluster Descriptor. + uint8_t iChannelNames; ///< Index of a string descriptor, describing the name of the first inserted channel with a non-predefined spatial location. +} audio_desc_channel_cluster_t; + +/// AUDIO Class-Specific AC Interface Header Descriptor (4.7.2) +typedef struct TU_ATTR_PACKED +{ + uint8_t bLength ; ///< Size of this descriptor in bytes: 9. + uint8_t bDescriptorType ; ///< Descriptor Type. Value: TUSB_DESC_CS_INTERFACE. + uint8_t bDescriptorSubType ; ///< Descriptor SubType. Value: AUDIO_CS_AC_INTERFACE_HEADER. + uint16_t bcdADC ; ///< Audio Device Class Specification Release Number in Binary-Coded Decimal. Value: U16_TO_U8S_LE(0x0200). + uint8_t bCategory ; ///< Constant, indicating the primary use of this audio function, as intended by the manufacturer. See: audio_function_t. + uint16_t wTotalLength ; ///< Total number of bytes returned for the class-specific AudioControl interface descriptor. Includes the combined length of this descriptor header and all Clock Source, Unit and Terminal descriptors. + uint8_t bmControls ; ///< See: audio_cs_ac_interface_control_pos_t. +} audio_desc_cs_ac_interface_t; +TU_VERIFY_STATIC(sizeof(audio_desc_cs_ac_interface_t) == 9, "size is not correct"); + +/// AUDIO Clock Source Descriptor (4.7.2.1) +typedef struct TU_ATTR_PACKED +{ + uint8_t bLength ; ///< Size of this descriptor in bytes: 8. + uint8_t bDescriptorType ; ///< Descriptor Type. Value: TUSB_DESC_CS_INTERFACE. + uint8_t bDescriptorSubType ; ///< Descriptor SubType. Value: AUDIO_CS_AC_INTERFACE_CLOCK_SOURCE. + uint8_t bClockID ; ///< Constant uniquely identifying the Clock Source Entity within the audio function. This value is used in all requests to address this Entity. + uint8_t bmAttributes ; ///< See: audio_clock_source_attribute_t. + uint8_t bmControls ; ///< See: audio_clock_source_control_pos_t. + uint8_t bAssocTerminal ; ///< Terminal ID of the Terminal that is associated with this Clock Source. + uint8_t iClockSource ; ///< Index of a string descriptor, describing the Clock Source Entity. +} audio_desc_clock_source_t; + +/// AUDIO Clock Selector Descriptor (4.7.2.2) for ONE pin +typedef struct TU_ATTR_PACKED +{ + uint8_t bLength ; ///< Size of this descriptor, in bytes: 7+p. + uint8_t bDescriptorType ; ///< Descriptor Type. Value: TUSB_DESC_CS_INTERFACE. + uint8_t bDescriptorSubType ; ///< Descriptor SubType. Value: AUDIO_CS_AC_INTERFACE_CLOCK_SELECTOR. + uint8_t bClockID ; ///< Constant uniquely identifying the Clock Selector Entity within the audio function. This value is used in all requests to address this Entity. + uint8_t bNrInPins ; ///< Number of Input Pins of this Unit: p = 1 thus bNrInPins = 1. + uint8_t baCSourceID ; ///< ID of the Clock Entity to which the first Clock Input Pin of this Clock Selector Entity is connected.. + uint8_t bmControls ; ///< See: audio_clock_selector_control_pos_t. + uint8_t iClockSource ; ///< Index of a string descriptor, describing the Clock Selector Entity. +} audio_desc_clock_selector_t; + +/// AUDIO Clock Selector Descriptor (4.7.2.2) for multiple pins +#define audio_desc_clock_selector_n_t(source_num) \ + struct TU_ATTR_PACKED { \ + uint8_t bLength ; \ + uint8_t bDescriptorType ; \ + uint8_t bDescriptorSubType ; \ + uint8_t bClockID ; \ + uint8_t bNrInPins ; \ + struct TU_ATTR_PACKED { \ + uint8_t baSourceID ; \ + } sourceID[source_num] ; \ + uint8_t bmControls ; \ + uint8_t iClockSource ; \ +} + +/// AUDIO Clock Multiplier Descriptor (4.7.2.3) +typedef struct TU_ATTR_PACKED +{ + uint8_t bLength ; ///< Size of this descriptor, in bytes: 7. + uint8_t bDescriptorType ; ///< Descriptor Type. Value: TUSB_DESC_CS_INTERFACE. + uint8_t bDescriptorSubType ; ///< Descriptor SubType. Value: AUDIO_CS_AC_INTERFACE_CLOCK_MULTIPLIER. + uint8_t bClockID ; ///< Constant uniquely identifying the Clock Multiplier Entity within the audio function. This value is used in all requests to address this Entity. + uint8_t bCSourceID ; ///< ID of the Clock Entity to which the last Clock Input Pin of this Clock Selector Entity is connected. + uint8_t bmControls ; ///< See: audio_clock_multiplier_control_pos_t. + uint8_t iClockSource ; ///< Index of a string descriptor, describing the Clock Multiplier Entity. +} audio_desc_clock_multiplier_t; + +/// AUDIO Input Terminal Descriptor(4.7.2.4) +typedef struct TU_ATTR_PACKED +{ + uint8_t bLength ; ///< Size of this descriptor, in bytes: 17. + uint8_t bDescriptorType ; ///< Descriptor Type. Value: TUSB_DESC_CS_INTERFACE. + uint8_t bDescriptorSubType ; ///< Descriptor SubType. Value: AUDIO_CS_AC_INTERFACE_INPUT_TERMINAL. + uint8_t bTerminalID ; ///< Constant uniquely identifying the Terminal within the audio function. This value is used in all requests to address this terminal. + uint16_t wTerminalType ; ///< Constant characterizing the type of Terminal. See: audio_terminal_type_t for USB streaming and audio_terminal_input_type_t for other input types. + uint8_t bAssocTerminal ; ///< ID of the Output Terminal to which this Input Terminal is associated. + uint8_t bCSourceID ; ///< ID of the Clock Entity to which this Input Terminal is connected. + uint8_t bNrChannels ; ///< Number of logical output channels in the Terminal’s output audio channel cluster. + uint32_t bmChannelConfig ; ///< Describes the spatial location of the logical channels. See:audio_channel_config_t. + uint8_t iChannelNames ; ///< Index of a string descriptor, describing the name of the first logical channel. + uint16_t bmControls ; ///< See: audio_terminal_input_control_pos_t. + uint8_t iTerminal ; ///< Index of a string descriptor, describing the Input Terminal. +} audio_desc_input_terminal_t; + +/// AUDIO Output Terminal Descriptor(4.7.2.5) +typedef struct TU_ATTR_PACKED +{ + uint8_t bLength ; ///< Size of this descriptor, in bytes: 12. + uint8_t bDescriptorType ; ///< Descriptor Type. Value: TUSB_DESC_CS_INTERFACE. + uint8_t bDescriptorSubType ; ///< Descriptor SubType. Value: AUDIO_CS_AC_INTERFACE_OUTPUT_TERMINAL. + uint8_t bTerminalID ; ///< Constant uniquely identifying the Terminal within the audio function. This value is used in all requests to address this Terminal. + uint16_t wTerminalType ; ///< Constant characterizing the type of Terminal. See: audio_terminal_type_t for USB streaming and audio_terminal_output_type_t for other output types. + uint8_t bAssocTerminal ; ///< Constant, identifying the Input Terminal to which this Output Terminal is associated. + uint8_t bSourceID ; ///< ID of the Unit or Terminal to which this Terminal is connected. + uint8_t bCSourceID ; ///< ID of the Clock Entity to which this Output Terminal is connected. + uint16_t bmControls ; ///< See: audio_terminal_output_type_t. + uint8_t iTerminal ; ///< Index of a string descriptor, describing the Output Terminal. +} audio_desc_output_terminal_t; + +/// AUDIO Feature Unit Descriptor(4.7.2.8) for ONE channel +typedef struct TU_ATTR_PACKED +{ + uint8_t bLength ; ///< Size of this descriptor, in bytes: 14. + uint8_t bDescriptorType ; ///< Descriptor Type. Value: TUSB_DESC_CS_INTERFACE. + uint8_t bDescriptorSubType ; ///< Descriptor SubType. Value: AUDIO_CS_AC_INTERFACE_FEATURE_UNIT. + uint8_t bUnitID ; ///< Constant uniquely identifying the Unit within the audio function. This value is used in all requests to address this Unit. + uint8_t bSourceID ; ///< ID of the Unit or Terminal to which this Feature Unit is connected. + struct TU_ATTR_PACKED { + uint32_t bmaControls ; ///< See: audio_feature_unit_control_pos_t. Controls0 is master channel 0 (always present) and Controls1 is logical channel 1. + } controls[2] ; + uint8_t iTerminal ; ///< Index of a string descriptor, describing this Feature Unit. +} audio_desc_feature_unit_t; + +/// AUDIO Feature Unit Descriptor(4.7.2.8) for multiple channels +#define audio_desc_feature_unit_n_t(ch_num)\ + struct TU_ATTR_PACKED { \ + uint8_t bLength ; /* 6+(ch_num+1)*4 */\ + uint8_t bDescriptorType ; \ + uint8_t bDescriptorSubType ; \ + uint8_t bUnitID ; \ + uint8_t bSourceID ; \ + struct TU_ATTR_PACKED { \ + uint32_t bmaControls ; \ + } controls[ch_num+1] ; \ + uint8_t iTerminal ; \ +} + +/// AUDIO Class-Specific AS Interface Descriptor(4.9.2) +typedef struct TU_ATTR_PACKED +{ + uint8_t bLength ; ///< Size of this descriptor, in bytes: 16. + uint8_t bDescriptorType ; ///< Descriptor Type. Value: TUSB_DESC_CS_INTERFACE. + uint8_t bDescriptorSubType ; ///< Descriptor SubType. Value: AUDIO_CS_AS_INTERFACE_AS_GENERAL. + uint8_t bTerminalLink ; ///< The Terminal ID of the Terminal to which this interface is connected. + uint8_t bmControls ; ///< See: audio_cs_as_interface_control_pos_t. + uint8_t bFormatType ; ///< Constant identifying the Format Type the AudioStreaming interface is using. See: audio_format_type_t. + uint32_t bmFormats ; ///< The Audio Data Format(s) that can be used to communicate with this interface.See: audio_data_format_type_I_t. + uint8_t bNrChannels ; ///< Number of physical channels in the AS Interface audio channel cluster. + uint32_t bmChannelConfig ; ///< Describes the spatial location of the physical channels. See: audio_channel_config_t. + uint8_t iChannelNames ; ///< Index of a string descriptor, describing the name of the first physical channel. +} audio_desc_cs_as_interface_t; + +/// AUDIO Type I Format Type Descriptor(2.3.1.6 - Audio Formats) +typedef struct TU_ATTR_PACKED +{ + uint8_t bLength ; ///< Size of this descriptor, in bytes: 6. + uint8_t bDescriptorType ; ///< Descriptor Type. Value: TUSB_DESC_CS_INTERFACE. + uint8_t bDescriptorSubType ; ///< Descriptor SubType. Value: AUDIO_CS_AS_INTERFACE_FORMAT_TYPE. + uint8_t bFormatType ; ///< Constant identifying the Format Type the AudioStreaming interface is using. Value: AUDIO_FORMAT_TYPE_I. + uint8_t bSubslotSize ; ///< The number of bytes occupied by one audio subslot. Can be 1, 2, 3 or 4. + uint8_t bBitResolution ; ///< The number of effectively used bits from the available bits in an audio subslot. +} audio_desc_type_I_format_t; + +/// AUDIO Class-Specific AS Isochronous Audio Data Endpoint Descriptor(4.10.1.2) +typedef struct TU_ATTR_PACKED +{ + uint8_t bLength ; ///< Size of this descriptor, in bytes: 8. + uint8_t bDescriptorType ; ///< Descriptor Type. Value: TUSB_DESC_CS_ENDPOINT. + uint8_t bDescriptorSubType ; ///< Descriptor SubType. Value: AUDIO_CS_EP_SUBTYPE_GENERAL. + uint8_t bmAttributes ; ///< See: audio_cs_as_iso_data_ep_attribute_t. + uint8_t bmControls ; ///< See: audio_cs_as_iso_data_ep_control_pos_t. + uint8_t bLockDelayUnits ; ///< Indicates the units used for the wLockDelay field. See: audio_cs_as_iso_data_ep_lock_delay_unit_t. + uint16_t wLockDelay ; ///< Indicates the time it takes this endpoint to reliably lock its internal clock recovery circuitry. Units used depend on the value of the bLockDelayUnits field. +} audio_desc_cs_as_iso_data_ep_t; + +// 5.2.2 Control Request Layout +typedef struct TU_ATTR_PACKED +{ + union + { + struct TU_ATTR_PACKED + { + uint8_t recipient : 5; ///< Recipient type tusb_request_recipient_t. + uint8_t type : 2; ///< Request type tusb_request_type_t. + uint8_t direction : 1; ///< Direction type. tusb_dir_t + } bmRequestType_bit; + + uint8_t bmRequestType; + }; + + uint8_t bRequest; ///< Request type audio_cs_req_t + uint8_t bChannelNumber; + uint8_t bControlSelector; + union + { + uint8_t bInterface; + uint8_t bEndpoint; + }; + uint8_t bEntityID; + uint16_t wLength; +} audio_control_request_t; + +//// 5.2.3 Control Request Parameter Block Layout + +// 5.2.3.1 1-byte Control CUR Parameter Block +typedef struct TU_ATTR_PACKED +{ + int8_t bCur ; ///< The setting for the CUR attribute of the addressed Control +} audio_control_cur_1_t; + +// 5.2.3.2 2-byte Control CUR Parameter Block +typedef struct TU_ATTR_PACKED +{ + int16_t bCur ; ///< The setting for the CUR attribute of the addressed Control +} audio_control_cur_2_t; + +// 5.2.3.3 4-byte Control CUR Parameter Block +typedef struct TU_ATTR_PACKED +{ + int32_t bCur ; ///< The setting for the CUR attribute of the addressed Control +} audio_control_cur_4_t; + +// Use the following ONLY for RECEIVED data - compiler does not know how many subranges are defined! Use the one below for predefined lengths - or if you know what you are doing do what you like +// 5.2.3.1 1-byte Control RANGE Parameter Block +typedef struct TU_ATTR_PACKED { + uint16_t wNumSubRanges; + struct TU_ATTR_PACKED { + int8_t bMin ; /*The setting for the MIN attribute of the nth subrange of the addressed Control*/ + int8_t bMax ; /*The setting for the MAX attribute of the nth subrange of the addressed Control*/ + uint8_t bRes ; /*The setting for the RES attribute of the nth subrange of the addressed Control*/ + } subrange[] ; +} audio_control_range_1_t; + +// 5.2.3.2 2-byte Control RANGE Parameter Block +typedef struct TU_ATTR_PACKED { + uint16_t wNumSubRanges; + struct TU_ATTR_PACKED { + int16_t bMin ; /*The setting for the MIN attribute of the nth subrange of the addressed Control*/ + int16_t bMax ; /*The setting for the MAX attribute of the nth subrange of the addressed Control*/ + uint16_t bRes ; /*The setting for the RES attribute of the nth subrange of the addressed Control*/ + } subrange[] ; +} audio_control_range_2_t; + +// 5.2.3.3 4-byte Control RANGE Parameter Block +typedef struct TU_ATTR_PACKED { + uint16_t wNumSubRanges; + struct TU_ATTR_PACKED { + int32_t bMin ; /*The setting for the MIN attribute of the nth subrange of the addressed Control*/ + int32_t bMax ; /*The setting for the MAX attribute of the nth subrange of the addressed Control*/ + uint32_t bRes ; /*The setting for the RES attribute of the nth subrange of the addressed Control*/ + } subrange[] ; +} audio_control_range_4_t; + +// 5.2.3.1 1-byte Control RANGE Parameter Block +#define audio_control_range_1_n_t(numSubRanges) \ + struct TU_ATTR_PACKED { \ + uint16_t wNumSubRanges; \ + struct TU_ATTR_PACKED { \ + int8_t bMin ; /*The setting for the MIN attribute of the nth subrange of the addressed Control*/\ + int8_t bMax ; /*The setting for the MAX attribute of the nth subrange of the addressed Control*/\ + uint8_t bRes ; /*The setting for the RES attribute of the nth subrange of the addressed Control*/\ + } subrange[numSubRanges] ; \ +} + +/// 5.2.3.2 2-byte Control RANGE Parameter Block +#define audio_control_range_2_n_t(numSubRanges) \ + struct TU_ATTR_PACKED { \ + uint16_t wNumSubRanges; \ + struct TU_ATTR_PACKED { \ + int16_t bMin ; /*The setting for the MIN attribute of the nth subrange of the addressed Control*/\ + int16_t bMax ; /*The setting for the MAX attribute of the nth subrange of the addressed Control*/\ + uint16_t bRes ; /*The setting for the RES attribute of the nth subrange of the addressed Control*/\ + } subrange[numSubRanges]; \ +} + +// 5.2.3.3 4-byte Control RANGE Parameter Block +#define audio_control_range_4_n_t(numSubRanges) \ + struct TU_ATTR_PACKED { \ + uint16_t wNumSubRanges; \ + struct TU_ATTR_PACKED { \ + int32_t bMin ; /*The setting for the MIN attribute of the nth subrange of the addressed Control*/\ + int32_t bMax ; /*The setting for the MAX attribute of the nth subrange of the addressed Control*/\ + uint32_t bRes ; /*The setting for the RES attribute of the nth subrange of the addressed Control*/\ + } subrange[numSubRanges]; \ +} + +// 6.1 Interrupt Data Message Format +typedef struct TU_ATTR_PACKED +{ + uint8_t bInfo; + uint8_t bAttribute; + union + { + uint16_t wValue; + struct + { + uint8_t wValue_cn_or_mcn; + uint8_t wValue_cs; + }; + }; + union + { + uint16_t wIndex; + struct + { + uint8_t wIndex_ep_or_int; + uint8_t wIndex_entity_id; + }; + }; +} audio_interrupt_data_t; /** @} */ #ifdef __cplusplus - } +} #endif #endif diff --git a/cores/nRF5/TinyUSB/Adafruit_TinyUSB_ArduinoCore/tinyusb/src/class/audio/audio_device.c b/cores/nRF5/TinyUSB/Adafruit_TinyUSB_ArduinoCore/tinyusb/src/class/audio/audio_device.c new file mode 100644 index 0000000..7014114 --- /dev/null +++ b/cores/nRF5/TinyUSB/Adafruit_TinyUSB_ArduinoCore/tinyusb/src/class/audio/audio_device.c @@ -0,0 +1,1835 @@ +/* + * The MIT License (MIT) + * + * Copyright (c) 2020 Reinhard Panhuber, Jerzy Kasenberg + * Copyright (c) 2023 HiFiPhile + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + * This file is part of the TinyUSB stack. + */ + +/* + * This driver supports at most one out EP, one in EP, one control EP, and one feedback EP and one alternative interface other than zero. Hence, only one input terminal and one output terminal are support, if you need more adjust the driver! + * It supports multiple TX and RX channels. + * + * In case you need more alternate interfaces, you need to define additional defines for this specific alternate interface. Just define them and set them in the set_interface function. + * + * There are three data flow structures currently implemented, where at least one SW-FIFO is used to decouple the asynchronous processes MCU vs. host + * + * 1. Input data -> SW-FIFO -> MCU USB + * + * The most easiest version, available in case the target MCU can handle the software FIFO (SW-FIFO) and if it is implemented in the device driver (if yes then dcd_edpt_xfer_fifo() is available) + * + * 2. Input data -> SW-FIFO -> Linear buffer -> MCU USB + * + * In case the target MCU can not handle a SW-FIFO, a linear buffer is used. This uses the default function dcd_edpt_xfer(). In this case more memory is required. + * + * 3. (Input data 1 | Input data 2 | ... | Input data N) -> (SW-FIFO 1 | SW-FIFO 2 | ... | SW-FIFO N) -> Linear buffer -> MCU USB + * + * This case is used if you have more channels which need to be combined into one stream. Every channel has its own SW-FIFO. All data is encoded into an Linear buffer. + * + * The same holds in the RX case. + * + * */ + +#include "tusb_option.h" + +#if (CFG_TUD_ENABLED && CFG_TUD_AUDIO) + +//--------------------------------------------------------------------+ +// INCLUDE +//--------------------------------------------------------------------+ +#include "device/usbd.h" +#include "device/usbd_pvt.h" + +#include "audio_device.h" + +//--------------------------------------------------------------------+ +// MACRO CONSTANT TYPEDEF +//--------------------------------------------------------------------+ + +// Use ring buffer if it's available, some MCUs need extra RAM requirements +// For DWC2 enable ring buffer will disable DMA (if available) +#ifndef TUD_AUDIO_PREFER_RING_BUFFER + #if CFG_TUSB_MCU == OPT_MCU_LPC43XX || CFG_TUSB_MCU == OPT_MCU_LPC18XX || CFG_TUSB_MCU == OPT_MCU_MIMXRT1XXX || \ + defined(TUP_USBIP_DWC2) + #define TUD_AUDIO_PREFER_RING_BUFFER 0 + #else + #define TUD_AUDIO_PREFER_RING_BUFFER 1 + #endif +#endif + +// Linear buffer in case target MCU is not capable of handling a ring buffer FIFO e.g. no hardware buffer +// is available or driver is would need to be changed dramatically + +// Only STM32 and dcd_transdimension use non-linear buffer for now +// dwc2 except esp32sx (since it may use dcd_esp32sx) +// Ring buffer is incompatible with dcache, since neither address nor size is aligned to cache line +#if (defined(TUP_USBIP_DWC2) && !TU_CHECK_MCU(OPT_MCU_ESP32S2, OPT_MCU_ESP32S3)) || \ + defined(TUP_USBIP_FSDEV) || \ + CFG_TUSB_MCU == OPT_MCU_RX63X || \ + CFG_TUSB_MCU == OPT_MCU_RX65X || \ + CFG_TUSB_MCU == OPT_MCU_RX72N || \ + CFG_TUSB_MCU == OPT_MCU_LPC18XX || \ + CFG_TUSB_MCU == OPT_MCU_LPC43XX || \ + CFG_TUSB_MCU == OPT_MCU_MIMXRT1XXX || \ + CFG_TUSB_MCU == OPT_MCU_MSP432E4 + #if TUD_AUDIO_PREFER_RING_BUFFER && !CFG_TUD_MEM_DCACHE_ENABLE + #define USE_LINEAR_BUFFER 0 + #else + #define USE_LINEAR_BUFFER 1 + #endif +#else + #define USE_LINEAR_BUFFER 1 +#endif + +// Declaration of buffers + +// Check for maximum supported numbers +#if CFG_TUD_AUDIO > 3 + #error Maximum number of audio functions restricted to three! +#endif + +// Put swap buffer in USB section only if necessary +#if USE_LINEAR_BUFFER + #define IN_SW_BUF_MEM_ATTR TU_ATTR_ALIGNED(4) +#else + #define IN_SW_BUF_MEM_ATTR CFG_TUD_MEM_SECTION CFG_TUD_MEM_ALIGN +#endif +#if USE_LINEAR_BUFFER + #define OUT_SW_BUF_MEM_ATTR TU_ATTR_ALIGNED(4) +#else + #define OUT_SW_BUF_MEM_ATTR CFG_TUD_MEM_SECTION CFG_TUD_MEM_ALIGN +#endif + +// EP IN software buffers +#if CFG_TUD_AUDIO_ENABLE_EP_IN +tu_static IN_SW_BUF_MEM_ATTR struct { + #if CFG_TUD_AUDIO_FUNC_1_EP_IN_SW_BUF_SZ > 0 + TUD_EPBUF_DEF(buf_1, CFG_TUD_AUDIO_FUNC_1_EP_IN_SW_BUF_SZ); + #endif + #if CFG_TUD_AUDIO > 1 && CFG_TUD_AUDIO_FUNC_2_EP_IN_SW_BUF_SZ > 0 + TUD_EPBUF_DEF(buf_2, CFG_TUD_AUDIO_FUNC_2_EP_IN_SW_BUF_SZ); + #endif + #if CFG_TUD_AUDIO > 2 && CFG_TUD_AUDIO_FUNC_3_EP_IN_SW_BUF_SZ > 0 + TUD_EPBUF_DEF(buf_3, CFG_TUD_AUDIO_FUNC_3_EP_IN_SW_BUF_SZ); + #endif +} ep_in_sw_buf; +#endif// CFG_TUD_AUDIO_ENABLE_EP_IN + +// Linear buffer TX in case: +// - target MCU is not capable of handling a ring buffer FIFO e.g. no hardware buffer is available or driver is would need to be changed dramatically OR +#if CFG_TUD_AUDIO_ENABLE_EP_IN && USE_LINEAR_BUFFER +tu_static CFG_TUD_MEM_SECTION struct { + #if CFG_TUD_AUDIO_FUNC_1_EP_IN_SZ_MAX > 0 + TUD_EPBUF_DEF(buf_1, CFG_TUD_AUDIO_FUNC_1_EP_IN_SZ_MAX); + #endif + #if CFG_TUD_AUDIO > 1 && CFG_TUD_AUDIO_FUNC_2_EP_IN_SZ_MAX > 0 + TUD_EPBUF_DEF(buf_2, CFG_TUD_AUDIO_FUNC_2_EP_IN_SZ_MAX); + #endif + #if CFG_TUD_AUDIO > 2 && CFG_TUD_AUDIO_FUNC_3_EP_IN_SZ_MAX > 0 + TUD_EPBUF_DEF(buf_3, CFG_TUD_AUDIO_FUNC_3_EP_IN_SZ_MAX); + #endif +} lin_buf_in; +#endif// CFG_TUD_AUDIO_ENABLE_EP_IN && USE_LINEAR_BUFFER + +// EP OUT software buffers +#if CFG_TUD_AUDIO_ENABLE_EP_OUT +tu_static OUT_SW_BUF_MEM_ATTR struct { + #if CFG_TUD_AUDIO_FUNC_1_EP_OUT_SW_BUF_SZ > 0 + TUD_EPBUF_DEF(buf_1, CFG_TUD_AUDIO_FUNC_1_EP_OUT_SW_BUF_SZ); + #endif + #if CFG_TUD_AUDIO > 1 && CFG_TUD_AUDIO_FUNC_2_EP_OUT_SW_BUF_SZ > 0 + TUD_EPBUF_DEF(buf_2, CFG_TUD_AUDIO_FUNC_2_EP_OUT_SW_BUF_SZ); + #endif + #if CFG_TUD_AUDIO > 2 && CFG_TUD_AUDIO_FUNC_3_EP_OUT_SW_BUF_SZ > 0 + TUD_EPBUF_DEF(buf_3, CFG_TUD_AUDIO_FUNC_3_EP_OUT_SW_BUF_SZ); + #endif +} ep_out_sw_buf; +#endif// CFG_TUD_AUDIO_ENABLE_EP_OUT + +// Linear buffer RX in case: +// - target MCU is not capable of handling a ring buffer FIFO e.g. no hardware buffer is available or driver is would need to be changed dramatically OR +#if CFG_TUD_AUDIO_ENABLE_EP_OUT && USE_LINEAR_BUFFER +tu_static CFG_TUD_MEM_SECTION struct { + #if CFG_TUD_AUDIO_FUNC_1_EP_OUT_SZ_MAX > 0 + TUD_EPBUF_DEF(buf_1, CFG_TUD_AUDIO_FUNC_1_EP_OUT_SZ_MAX); + #endif + #if CFG_TUD_AUDIO > 1 && CFG_TUD_AUDIO_FUNC_2_EP_OUT_SZ_MAX > 0 + TUD_EPBUF_DEF(buf_2, CFG_TUD_AUDIO_FUNC_2_EP_OUT_SZ_MAX); + #endif + #if CFG_TUD_AUDIO > 2 && CFG_TUD_AUDIO_FUNC_3_EP_OUT_SZ_MAX > 0 + TUD_EPBUF_DEF(buf_3, CFG_TUD_AUDIO_FUNC_3_EP_OUT_SZ_MAX); + #endif +} lin_buf_out; +#endif// CFG_TUD_AUDIO_ENABLE_EP_OUT && USE_LINEAR_BUFFER + +// Control buffers +tu_static CFG_TUD_MEM_SECTION struct { + TUD_EPBUF_DEF(buf1, CFG_TUD_AUDIO_FUNC_1_CTRL_BUF_SZ); + #if CFG_TUD_AUDIO > 1 + TUD_EPBUF_DEF(buf2, CFG_TUD_AUDIO_FUNC_2_CTRL_BUF_SZ); + #endif + #if CFG_TUD_AUDIO > 2 + TUD_EPBUF_DEF(buf3, CFG_TUD_AUDIO_FUNC_3_CTRL_BUF_SZ); + #endif +} ctrl_buf; + +// Aligned buffer for feedback EP +#if CFG_TUD_AUDIO_ENABLE_EP_OUT && CFG_TUD_AUDIO_ENABLE_FEEDBACK_EP +tu_static CFG_TUD_MEM_SECTION struct { + #if CFG_TUD_AUDIO_FUNC_1_EP_OUT_SZ_MAX > 0 + TUD_EPBUF_TYPE_DEF(uint32_t, buf_1); + #endif + #if CFG_TUD_AUDIO > 1 && CFG_TUD_AUDIO_FUNC_2_EP_OUT_SZ_MAX > 0 + TUD_EPBUF_TYPE_DEF(uint32_t, buf_2); + #endif + #if CFG_TUD_AUDIO > 2 && CFG_TUD_AUDIO_FUNC_3_EP_OUT_SZ_MAX > 0 + TUD_EPBUF_TYPE_DEF(uint32_t, buf_3); + #endif +} fb_ep_buf; +#endif + +// Aligned buffer for interrupt EP +#if CFG_TUD_AUDIO_ENABLE_INTERRUPT_EP +tu_static CFG_TUD_MEM_SECTION struct { + TUD_EPBUF_DEF(buf, CFG_TUD_AUDIO_INTERRUPT_EP_SZ); +} int_ep_buf[CFG_TUD_AUDIO]; +#endif + +typedef struct +{ + uint8_t rhport; + uint8_t const *p_desc;// Pointer pointing to Standard AC Interface Descriptor(4.7.1) - Audio Control descriptor defining audio function + +#if CFG_TUD_AUDIO_ENABLE_EP_IN + uint8_t ep_in; // TX audio data EP. + uint16_t ep_in_sz; // Current size of TX EP + uint8_t ep_in_as_intf_num;// Corresponding Standard AS Interface Descriptor (4.9.1) belonging to output terminal to which this EP belongs - 0 is invalid (this fits to UAC2 specification since AS interfaces can not have interface number equal to zero) + uint8_t ep_in_alt; // Current alternate setting of TX EP + #endif + +#if CFG_TUD_AUDIO_ENABLE_EP_OUT + uint8_t ep_out; // Incoming (into uC) audio data EP. + uint16_t ep_out_sz; // Current size of RX EP + uint8_t ep_out_as_intf_num;// Corresponding Standard AS Interface Descriptor (4.9.1) belonging to input terminal to which this EP belongs - 0 is invalid (this fits to UAC2 specification since AS interfaces can not have interface number equal to zero) + uint8_t ep_out_alt; // Current alternate setting of RX EP + #if CFG_TUD_AUDIO_ENABLE_FEEDBACK_EP + uint8_t ep_fb;// Feedback EP. + #endif + +#endif + +#if CFG_TUD_AUDIO_ENABLE_INTERRUPT_EP + uint8_t ep_int;// Audio control interrupt EP. +#endif + + bool mounted;// Device opened + + uint16_t desc_length;// Length of audio function descriptor + +#if CFG_TUD_AUDIO_ENABLE_FEEDBACK_EP + struct { + uint32_t value; // Feedback value for asynchronous mode (in 16.16 format). + uint32_t min_value;// min value according to UAC2 FMT-2.0 section 2.3.1.1. + uint32_t max_value;// max value according to UAC2 FMT-2.0 section 2.3.1.1. + + uint8_t frame_shift;// bInterval-1 in unit of frame (FS), micro-frame (HS) + uint8_t compute_method; + bool format_correction; + union { + uint8_t power_of_2;// pre-computed power of 2 shift + float float_const; // pre-computed float constant + + struct { + uint32_t sample_freq; + uint32_t mclk_freq; + } fixed; + + struct { + uint32_t nom_value; // In 16.16 format + uint32_t fifo_lvl_avg; // In 16.16 format + uint16_t fifo_lvl_thr; // fifo level threshold + uint16_t rate_const[2];// pre-computed feedback/fifo_depth rate + } fifo_count; + } compute; + + } feedback; +#endif// CFG_TUD_AUDIO_ENABLE_FEEDBACK_EP + +#if CFG_TUD_AUDIO_ENABLE_EP_IN && CFG_TUD_AUDIO_EP_IN_FLOW_CONTROL + uint32_t sample_rate_tx; + uint16_t packet_sz_tx[3]; + uint8_t bclock_id_tx; + uint8_t interval_tx; +#endif + +// Encoding parameters - parameters are set when alternate AS interface is set by host +#if CFG_TUD_AUDIO_ENABLE_EP_IN && CFG_TUD_AUDIO_EP_IN_FLOW_CONTROL + audio_format_type_t format_type_tx; + uint8_t n_channels_tx; + uint8_t n_bytes_per_sample_tx; +#endif + + /*------------- From this point, data is not cleared by bus reset -------------*/ + + // Buffer for control requests + uint8_t *ctrl_buf; + uint8_t ctrl_buf_sz; + +// EP Transfer buffers and FIFOs +#if CFG_TUD_AUDIO_ENABLE_EP_OUT + tu_fifo_t ep_out_ff; +#endif + +#if CFG_TUD_AUDIO_ENABLE_EP_IN + tu_fifo_t ep_in_ff; +#endif + +// Linear buffer in case target MCU is not capable of handling a ring buffer FIFO e.g. no hardware buffer is available or driver is would need to be changed dramatically +#if CFG_TUD_AUDIO_ENABLE_EP_OUT && USE_LINEAR_BUFFER + uint8_t *lin_buf_out; + #define USE_LINEAR_BUFFER_RX 1 +#endif + +#if CFG_TUD_AUDIO_ENABLE_EP_IN && USE_LINEAR_BUFFER + uint8_t *lin_buf_in; + #define USE_LINEAR_BUFFER_TX 1 +#endif + +#if CFG_TUD_AUDIO_ENABLE_FEEDBACK_EP + uint32_t *fb_buf; +#endif +} audiod_function_t; + +#ifndef USE_LINEAR_BUFFER_TX + #define USE_LINEAR_BUFFER_TX 0 +#endif + +#ifndef USE_LINEAR_BUFFER_RX + #define USE_LINEAR_BUFFER_RX 0 +#endif + +#define ITF_MEM_RESET_SIZE offsetof(audiod_function_t, ctrl_buf) + +//--------------------------------------------------------------------+ +// WEAK FUNCTION STUBS +//--------------------------------------------------------------------+ + +#if CFG_TUD_AUDIO_ENABLE_EP_IN +TU_ATTR_WEAK bool tud_audio_tx_done_isr(uint8_t rhport, uint16_t n_bytes_sent, uint8_t func_id, uint8_t ep_in, uint8_t cur_alt_setting) { + (void) rhport; + (void) n_bytes_sent; + (void) func_id; + (void) ep_in; + (void) cur_alt_setting; + return true; +} + +#endif + +#if CFG_TUD_AUDIO_ENABLE_EP_OUT +TU_ATTR_WEAK bool tud_audio_rx_done_isr(uint8_t rhport, uint16_t n_bytes_received, uint8_t func_id, uint8_t ep_out, uint8_t cur_alt_setting) { + (void) rhport; + (void) n_bytes_received; + (void) func_id; + (void) ep_out; + (void) cur_alt_setting; + return true; +} +#endif + +#if CFG_TUD_AUDIO_ENABLE_EP_OUT && CFG_TUD_AUDIO_ENABLE_FEEDBACK_EP +TU_ATTR_WEAK void tud_audio_feedback_params_cb(uint8_t func_id, uint8_t alt_itf, audio_feedback_params_t *feedback_param) { + (void) func_id; + (void) alt_itf; + feedback_param->method = AUDIO_FEEDBACK_METHOD_DISABLED; +} + +TU_ATTR_WEAK bool tud_audio_feedback_format_correction_cb(uint8_t func_id) { + (void) func_id; + return CFG_TUD_AUDIO_ENABLE_FEEDBACK_FORMAT_CORRECTION; +} + +TU_ATTR_WEAK TU_ATTR_FAST_FUNC void tud_audio_feedback_interval_isr(uint8_t func_id, uint32_t frame_number, uint8_t interval_shift) { + (void) func_id; + (void) frame_number; + (void) interval_shift; +} +#endif + +#if CFG_TUD_AUDIO_ENABLE_INTERRUPT_EP +TU_ATTR_WEAK void tud_audio_int_xfer_cb(uint8_t rhport) { + (void) rhport; +} +#endif + +// Invoked when audio set interface request received +TU_ATTR_WEAK bool tud_audio_set_itf_cb(uint8_t rhport, tusb_control_request_t const *p_request) { + (void) rhport; + (void) p_request; + return true; +} + +// Invoked when audio set interface request received which closes an EP +TU_ATTR_WEAK bool tud_audio_set_itf_close_ep_cb(uint8_t rhport, tusb_control_request_t const *p_request) { + (void) rhport; + (void) p_request; + return true; +} + +// Invoked when audio class specific set request received for an EP +TU_ATTR_WEAK bool tud_audio_set_req_ep_cb(uint8_t rhport, tusb_control_request_t const *p_request, uint8_t *pBuff) { + (void) rhport; + (void) p_request; + (void) pBuff; + TU_LOG2(" No EP set request callback available!\r\n"); + return false;// In case no callback function is present or request can not be conducted we stall it +} + +// Invoked when audio class specific set request received for an interface +TU_ATTR_WEAK bool tud_audio_set_req_itf_cb(uint8_t rhport, tusb_control_request_t const *p_request, uint8_t *pBuff) { + (void) rhport; + (void) p_request; + (void) pBuff; + TU_LOG2(" No interface set request callback available!\r\n"); + return false;// In case no callback function is present or request can not be conducted we stall it +} + +// Invoked when audio class specific set request received for an entity +TU_ATTR_WEAK bool tud_audio_set_req_entity_cb(uint8_t rhport, tusb_control_request_t const *p_request, uint8_t *pBuff) { + (void) rhport; + (void) p_request; + (void) pBuff; + TU_LOG2(" No entity set request callback available!\r\n"); + return false;// In case no callback function is present or request can not be conducted we stall it +} + +// Invoked when audio class specific get request received for an EP +TU_ATTR_WEAK bool tud_audio_get_req_ep_cb(uint8_t rhport, tusb_control_request_t const *p_request) { + (void) rhport; + (void) p_request; + TU_LOG2(" No EP get request callback available!\r\n"); + return false;// Stall +} + +// Invoked when audio class specific get request received for an interface +TU_ATTR_WEAK bool tud_audio_get_req_itf_cb(uint8_t rhport, tusb_control_request_t const *p_request) { + (void) rhport; + (void) p_request; + TU_LOG2(" No interface get request callback available!\r\n"); + return false;// Stall +} + +// Invoked when audio class specific get request received for an entity +TU_ATTR_WEAK bool tud_audio_get_req_entity_cb(uint8_t rhport, tusb_control_request_t const *p_request) { + (void) rhport; + (void) p_request; + TU_LOG2(" No entity get request callback available!\r\n"); + return false;// Stall +} + +//--------------------------------------------------------------------+ +// INTERNAL OBJECT & FUNCTION DECLARATION +//--------------------------------------------------------------------+ +tu_static CFG_TUD_MEM_SECTION audiod_function_t _audiod_fct[CFG_TUD_AUDIO]; + +#if CFG_TUD_AUDIO_ENABLE_EP_OUT +static bool audiod_rx_xfer_isr(uint8_t rhport, audiod_function_t* audio, uint16_t n_bytes_received); +#endif + +#if CFG_TUD_AUDIO_ENABLE_EP_IN +static bool audiod_tx_xfer_isr(uint8_t rhport, audiod_function_t* audio, uint16_t n_bytes_sent); +#endif + +static bool audiod_get_interface(uint8_t rhport, tusb_control_request_t const *p_request); +static bool audiod_set_interface(uint8_t rhport, tusb_control_request_t const *p_request); + +static bool audiod_verify_entity_exists(uint8_t itf, uint8_t entityID, uint8_t *func_id); +static bool audiod_verify_itf_exists(uint8_t itf, uint8_t *func_id); +static bool audiod_verify_ep_exists(uint8_t ep, uint8_t *func_id); +static uint8_t audiod_get_audio_fct_idx(audiod_function_t *audio); + +#if CFG_TUD_AUDIO_ENABLE_EP_IN && CFG_TUD_AUDIO_EP_IN_FLOW_CONTROL +static void audiod_parse_flow_control_params(audiod_function_t *audio, uint8_t const *p_desc); +static bool audiod_calc_tx_packet_sz(audiod_function_t *audio); +static uint16_t audiod_tx_packet_size(const uint16_t *norminal_size, uint16_t data_count, uint16_t fifo_depth, uint16_t max_size); +#endif + +#if CFG_TUD_AUDIO_ENABLE_EP_OUT && CFG_TUD_AUDIO_ENABLE_FEEDBACK_EP +static bool audiod_set_fb_params_freq(audiod_function_t *audio, uint32_t sample_freq, uint32_t mclk_freq); +static void audiod_fb_fifo_count_update(audiod_function_t *audio, uint16_t lvl_new); +#endif + +bool tud_audio_n_mounted(uint8_t func_id) { + TU_VERIFY(func_id < CFG_TUD_AUDIO); + audiod_function_t *audio = &_audiod_fct[func_id]; + + return audio->mounted; +} + +//--------------------------------------------------------------------+ +// READ API +//--------------------------------------------------------------------+ + +#if CFG_TUD_AUDIO_ENABLE_EP_OUT + +uint16_t tud_audio_n_available(uint8_t func_id) { + TU_VERIFY(func_id < CFG_TUD_AUDIO && _audiod_fct[func_id].p_desc != NULL); + return tu_fifo_count(&_audiod_fct[func_id].ep_out_ff); +} + +uint16_t tud_audio_n_read(uint8_t func_id, void *buffer, uint16_t bufsize) { + TU_VERIFY(func_id < CFG_TUD_AUDIO && _audiod_fct[func_id].p_desc != NULL); + return tu_fifo_read_n(&_audiod_fct[func_id].ep_out_ff, buffer, bufsize); +} + +bool tud_audio_n_clear_ep_out_ff(uint8_t func_id) { + TU_VERIFY(func_id < CFG_TUD_AUDIO && _audiod_fct[func_id].p_desc != NULL); + return tu_fifo_clear(&_audiod_fct[func_id].ep_out_ff); +} + +tu_fifo_t *tud_audio_n_get_ep_out_ff(uint8_t func_id) { + if (func_id < CFG_TUD_AUDIO && _audiod_fct[func_id].p_desc != NULL) { + return &_audiod_fct[func_id].ep_out_ff; + } + return NULL; +} + +static bool audiod_rx_xfer_isr(uint8_t rhport, audiod_function_t* audio, uint16_t n_bytes_received) { + uint8_t idx_audio_fct = audiod_get_audio_fct_idx(audio); + + #if USE_LINEAR_BUFFER_RX + // Data currently is in linear buffer, copy into EP OUT FIFO + TU_VERIFY(tu_fifo_write_n(&audio->ep_out_ff, audio->lin_buf_out, n_bytes_received)); + + // Schedule for next receive + TU_VERIFY(usbd_edpt_xfer(rhport, audio->ep_out, audio->lin_buf_out, audio->ep_out_sz), false); + #else + // Data is already placed in EP FIFO, schedule for next receive + TU_VERIFY(usbd_edpt_xfer_fifo(rhport, audio->ep_out, &audio->ep_out_ff, audio->ep_out_sz), false); + #endif + + #if CFG_TUD_AUDIO_ENABLE_FEEDBACK_EP + if (audio->feedback.compute_method == AUDIO_FEEDBACK_METHOD_FIFO_COUNT) { + audiod_fb_fifo_count_update(audio, tu_fifo_count(&audio->ep_out_ff)); + } + #endif + + // Call a weak callback here - a possibility for user to get informed an audio packet was received and data gets now loaded into EP FIFO + TU_VERIFY(tud_audio_rx_done_isr(rhport, n_bytes_received, idx_audio_fct, audio->ep_out, audio->ep_out_alt)); + + return true; +} + +#endif//CFG_TUD_AUDIO_ENABLE_EP_OUT + +//--------------------------------------------------------------------+ +// WRITE API +//--------------------------------------------------------------------+ + +#if CFG_TUD_AUDIO_ENABLE_EP_IN + +uint16_t tud_audio_n_write(uint8_t func_id, const void *data, uint16_t len) { + TU_VERIFY(func_id < CFG_TUD_AUDIO && _audiod_fct[func_id].p_desc != NULL); + return tu_fifo_write_n(&_audiod_fct[func_id].ep_in_ff, data, len); +} + +bool tud_audio_n_clear_ep_in_ff(uint8_t func_id) { + TU_VERIFY(func_id < CFG_TUD_AUDIO && _audiod_fct[func_id].p_desc != NULL); + return tu_fifo_clear(&_audiod_fct[func_id].ep_in_ff); +} + +tu_fifo_t *tud_audio_n_get_ep_in_ff(uint8_t func_id) { + if (func_id < CFG_TUD_AUDIO && _audiod_fct[func_id].p_desc != NULL) { + return &_audiod_fct[func_id].ep_in_ff; + } + return NULL; +} + +static bool audiod_tx_xfer_isr(uint8_t rhport, audiod_function_t * audio, uint16_t n_bytes_sent) { + uint8_t idx_audio_fct = audiod_get_audio_fct_idx(audio); + + // Only send something if current alternate interface is not 0 as in this case nothing is to be sent due to UAC2 specifications + if (audio->ep_in_alt == 0) { return false; } + + // Send everything in ISO EP FIFO + uint16_t n_bytes_tx; + + #if CFG_TUD_AUDIO_EP_IN_FLOW_CONTROL + // packet_sz_tx is based on total packet size, here we want size for each support buffer. + n_bytes_tx = audiod_tx_packet_size(audio->packet_sz_tx, tu_fifo_count(&audio->ep_in_ff), audio->ep_in_ff.depth, audio->ep_in_sz); + #else + n_bytes_tx = tu_min16(tu_fifo_count(&audio->ep_in_ff), audio->ep_in_sz);// Limit up to max packet size, more can not be done for ISO + #endif + #if USE_LINEAR_BUFFER_TX + tu_fifo_read_n(&audio->ep_in_ff, audio->lin_buf_in, n_bytes_tx); + TU_VERIFY(usbd_edpt_xfer(rhport, audio->ep_in, audio->lin_buf_in, n_bytes_tx)); + #else + // Send everything in ISO EP FIFO + TU_VERIFY(usbd_edpt_xfer_fifo(rhport, audio->ep_in, &audio->ep_in_ff, n_bytes_tx)); + #endif + + // Call a weak callback here - a possibility for user to get informed former TX was completed and data gets now loaded into EP in buffer + TU_VERIFY(tud_audio_tx_done_isr(rhport, n_bytes_sent, idx_audio_fct, audio->ep_in, audio->ep_in_alt)); + + return true; +} + +#endif + +#if CFG_TUD_AUDIO_ENABLE_INTERRUPT_EP +// If no interrupt transmit is pending bytes get written into buffer and a transmit is scheduled - once transmit completed tud_audio_int_done_cb() is called in inform user +bool tud_audio_int_n_write(uint8_t func_id, const audio_interrupt_data_t *data) { + TU_VERIFY(func_id < CFG_TUD_AUDIO && _audiod_fct[func_id].p_desc != NULL); + + TU_VERIFY(_audiod_fct[func_id].ep_int != 0); + + // We write directly into the EP's buffer - abort if previous transfer not complete + TU_VERIFY(usbd_edpt_claim(_audiod_fct[func_id].rhport, _audiod_fct[func_id].ep_int)); + + // Check length + if (tu_memcpy_s(int_ep_buf[func_id].buf, sizeof(int_ep_buf[func_id].buf), data, sizeof(audio_interrupt_data_t)) == 0) { + // Schedule transmit + TU_ASSERT(usbd_edpt_xfer(_audiod_fct[func_id].rhport, _audiod_fct[func_id].ep_int, int_ep_buf[func_id].buf, sizeof(int_ep_buf[func_id].buf)), 0); + } else { + // Release endpoint since we don't make any transfer + usbd_edpt_release(_audiod_fct[func_id].rhport, _audiod_fct[func_id].ep_int); + } + + return true; +} +#endif + +#if CFG_TUD_AUDIO_ENABLE_EP_OUT && CFG_TUD_AUDIO_ENABLE_FEEDBACK_EP +// This function is called once a transmit of a feedback packet was successfully completed. Here, we get the next feedback value to be sent +static inline bool audiod_fb_send(audiod_function_t *audio) { + bool apply_correction = (TUSB_SPEED_FULL == tud_speed_get()) && audio->feedback.format_correction; + // Format the feedback value + if (apply_correction) { + uint8_t *fb = (uint8_t *) audio->fb_buf; + + // For FS format is 10.14 + *(fb++) = (audio->feedback.value >> 2) & 0xFF; + *(fb++) = (audio->feedback.value >> 10) & 0xFF; + *(fb++) = (audio->feedback.value >> 18) & 0xFF; + *fb = 0; + } else { + *audio->fb_buf = audio->feedback.value; + } + + // About feedback format on FS + // + // 3 variables: Format | packetSize | sendSize | Working OS: + // 16.16 4 4 Linux, Windows + // 16.16 4 3 Linux + // 16.16 3 4 Linux + // 16.16 3 3 Linux + // 10.14 4 4 Linux + // 10.14 4 3 Linux + // 10.14 3 4 Linux, OSX + // 10.14 3 3 Linux, OSX + // + // We send 3 bytes since sending packet larger than wMaxPacketSize is pretty ugly + return usbd_edpt_xfer(audio->rhport, audio->ep_fb, (uint8_t *) audio->fb_buf, apply_correction ? 3 : 4); +} +#endif + +//--------------------------------------------------------------------+ +// USBD Driver API +//--------------------------------------------------------------------+ +void audiod_init(void) { + tu_memclr(_audiod_fct, sizeof(_audiod_fct)); + + for (uint8_t i = 0; i < CFG_TUD_AUDIO; i++) { + audiod_function_t *audio = &_audiod_fct[i]; + + // Initialize control buffers + switch (i) { + case 0: + audio->ctrl_buf = ctrl_buf.buf1; + audio->ctrl_buf_sz = CFG_TUD_AUDIO_FUNC_1_CTRL_BUF_SZ; + break; +#if CFG_TUD_AUDIO > 1 && CFG_TUD_AUDIO_FUNC_2_CTRL_BUF_SZ > 0 + case 1: + audio->ctrl_buf = ctrl_buf.buf2; + audio->ctrl_buf_sz = CFG_TUD_AUDIO_FUNC_2_CTRL_BUF_SZ; + break; +#endif +#if CFG_TUD_AUDIO > 2 && CFG_TUD_AUDIO_FUNC_3_CTRL_BUF_SZ > 0 + case 2: + audio->ctrl_buf = ctrl_buf.buf3; + audio->ctrl_buf_sz = CFG_TUD_AUDIO_FUNC_3_CTRL_BUF_SZ; + break; +#endif + } + + // Initialize IN EP FIFO if required +#if CFG_TUD_AUDIO_ENABLE_EP_IN + + switch (i) { + #if CFG_TUD_AUDIO_FUNC_1_EP_IN_SW_BUF_SZ > 0 + case 0: + tu_fifo_config(&audio->ep_in_ff, ep_in_sw_buf.buf_1, CFG_TUD_AUDIO_FUNC_1_EP_IN_SW_BUF_SZ, 1, true); + break; + #endif + #if CFG_TUD_AUDIO > 1 && CFG_TUD_AUDIO_FUNC_2_EP_IN_SW_BUF_SZ > 0 + case 1: + tu_fifo_config(&audio->ep_in_ff, ep_in_sw_buf.buf_2, CFG_TUD_AUDIO_FUNC_2_EP_IN_SW_BUF_SZ, 1, true); + break; + #endif + #if CFG_TUD_AUDIO > 2 && CFG_TUD_AUDIO_FUNC_3_EP_IN_SW_BUF_SZ > 0 + case 2: + tu_fifo_config(&audio->ep_in_ff, ep_in_sw_buf.buf_3, CFG_TUD_AUDIO_FUNC_3_EP_IN_SW_BUF_SZ, 1, true); + break; + #endif + } +#endif// CFG_TUD_AUDIO_ENABLE_EP_IN + + // Initialize linear buffers +#if USE_LINEAR_BUFFER_TX + switch (i) { + #if CFG_TUD_AUDIO_FUNC_1_EP_IN_SZ_MAX > 0 + case 0: + audio->lin_buf_in = lin_buf_in.buf_1; + break; + #endif + #if CFG_TUD_AUDIO > 1 && CFG_TUD_AUDIO_FUNC_2_EP_IN_SZ_MAX > 0 + case 1: + audio->lin_buf_in = lin_buf_in.buf_2; + break; + #endif + #if CFG_TUD_AUDIO > 2 && CFG_TUD_AUDIO_FUNC_3_EP_IN_SZ_MAX > 0 + case 2: + audio->lin_buf_in = lin_buf_in.buf_3; + break; + #endif + } +#endif// USE_LINEAR_BUFFER_TX + + // Initialize OUT EP FIFO if required +#if CFG_TUD_AUDIO_ENABLE_EP_OUT + + switch (i) { + #if CFG_TUD_AUDIO_FUNC_1_EP_OUT_SW_BUF_SZ > 0 + case 0: + tu_fifo_config(&audio->ep_out_ff, ep_out_sw_buf.buf_1, CFG_TUD_AUDIO_FUNC_1_EP_OUT_SW_BUF_SZ, 1, true); + break; + #endif + #if CFG_TUD_AUDIO > 1 && CFG_TUD_AUDIO_FUNC_2_EP_OUT_SW_BUF_SZ > 0 + case 1: + tu_fifo_config(&audio->ep_out_ff, ep_out_sw_buf.buf_2, CFG_TUD_AUDIO_FUNC_2_EP_OUT_SW_BUF_SZ, 1, true); + break; + #endif + #if CFG_TUD_AUDIO > 2 && CFG_TUD_AUDIO_FUNC_3_EP_OUT_SW_BUF_SZ > 0 + case 2: + tu_fifo_config(&audio->ep_out_ff, ep_out_sw_buf.buf_3, CFG_TUD_AUDIO_FUNC_3_EP_OUT_SW_BUF_SZ, 1, true); + break; + #endif + } +#endif// CFG_TUD_AUDIO_ENABLE_EP_OUT + + // Initialize linear buffers +#if USE_LINEAR_BUFFER_RX + switch (i) { + #if CFG_TUD_AUDIO_FUNC_1_EP_OUT_SZ_MAX > 0 + case 0: + audio->lin_buf_out = lin_buf_out.buf_1; + break; + #endif + #if CFG_TUD_AUDIO > 1 && CFG_TUD_AUDIO_FUNC_2_EP_OUT_SZ_MAX > 0 + case 1: + audio->lin_buf_out = lin_buf_out.buf_2; + break; + #endif + #if CFG_TUD_AUDIO > 2 && CFG_TUD_AUDIO_FUNC_3_EP_OUT_SZ_MAX > 0 + case 2: + audio->lin_buf_out = lin_buf_out.buf_3; + break; + #endif + } +#endif// USE_LINEAR_BUFFER_RX + +#if CFG_TUD_AUDIO_ENABLE_FEEDBACK_EP + switch (i) { + #if CFG_TUD_AUDIO_FUNC_1_EP_OUT_SZ_MAX > 0 + case 0: + audio->fb_buf = &fb_ep_buf.buf_1; + break; + #endif + #if CFG_TUD_AUDIO > 1 && CFG_TUD_AUDIO_FUNC_2_EP_OUT_SZ_MAX > 0 + case 1: + audio->fb_buf = &fb_ep_buf.buf_2; + break; + #endif + #if CFG_TUD_AUDIO > 2 && CFG_TUD_AUDIO_FUNC_3_EP_OUT_SZ_MAX > 0 + case 2: + audio->fb_buf = &fb_ep_buf.buf_3; + break; + #endif + } +#endif// CFG_TUD_AUDIO_ENABLE_FEEDBACK_EP + } +} + +bool audiod_deinit(void) { + return false;// TODO not implemented yet +} + +void audiod_reset(uint8_t rhport) { + (void) rhport; + + for (uint8_t i = 0; i < CFG_TUD_AUDIO; i++) { + audiod_function_t *audio = &_audiod_fct[i]; + tu_memclr(audio, ITF_MEM_RESET_SIZE); + +#if CFG_TUD_AUDIO_ENABLE_EP_IN + tu_fifo_clear(&audio->ep_in_ff); +#endif + +#if CFG_TUD_AUDIO_ENABLE_EP_OUT + tu_fifo_clear(&audio->ep_out_ff); +#endif + } +} + +uint16_t audiod_open(uint8_t rhport, tusb_desc_interface_t const *itf_desc, uint16_t max_len) { + (void) max_len; + + TU_VERIFY(TUSB_CLASS_AUDIO == itf_desc->bInterfaceClass && + AUDIO_SUBCLASS_CONTROL == itf_desc->bInterfaceSubClass); + + // Verify version is correct - this check can be omitted + TU_VERIFY(itf_desc->bInterfaceProtocol == AUDIO_INT_PROTOCOL_CODE_V2); + + // Verify interrupt control EP is enabled if demanded by descriptor + TU_ASSERT(itf_desc->bNumEndpoints <= 1);// 0 or 1 EPs are allowed + if (itf_desc->bNumEndpoints == 1) { + TU_ASSERT(CFG_TUD_AUDIO_ENABLE_INTERRUPT_EP); + } + + // Alternate setting MUST be zero - this check can be omitted + TU_VERIFY(itf_desc->bAlternateSetting == 0); + + // Find available audio driver interface + uint8_t i; + for (i = 0; i < CFG_TUD_AUDIO; i++) { + if (!_audiod_fct[i].p_desc) { + _audiod_fct[i].p_desc = (uint8_t const *) itf_desc;// Save pointer to AC descriptor which is by specification always the first one + _audiod_fct[i].rhport = rhport; + + // Setup descriptor lengths + switch (i) { + case 0: + _audiod_fct[i].desc_length = CFG_TUD_AUDIO_FUNC_1_DESC_LEN; + break; +#if CFG_TUD_AUDIO > 1 + case 1: + _audiod_fct[i].desc_length = CFG_TUD_AUDIO_FUNC_2_DESC_LEN; + break; +#endif +#if CFG_TUD_AUDIO > 2 + case 2: + _audiod_fct[i].desc_length = CFG_TUD_AUDIO_FUNC_3_DESC_LEN; + break; +#endif + } + +#ifdef TUP_DCD_EDPT_ISO_ALLOC + { + #if CFG_TUD_AUDIO_ENABLE_EP_IN + uint8_t ep_in = 0; + uint16_t ep_in_size = 0; + #endif + + #if CFG_TUD_AUDIO_ENABLE_EP_OUT + uint8_t ep_out = 0; + uint16_t ep_out_size = 0; + #endif + + #if CFG_TUD_AUDIO_ENABLE_FEEDBACK_EP + uint8_t ep_fb = 0; + #endif + uint8_t const *p_desc = _audiod_fct[i].p_desc; + uint8_t const *p_desc_end = p_desc + _audiod_fct[i].desc_length - TUD_AUDIO_DESC_IAD_LEN; + // Condition modified from p_desc < p_desc_end to prevent gcc>=12 strict-overflow warning + while (p_desc_end - p_desc > 0) { + if (tu_desc_type(p_desc) == TUSB_DESC_ENDPOINT) { + tusb_desc_endpoint_t const *desc_ep = (tusb_desc_endpoint_t const *) p_desc; + if (desc_ep->bmAttributes.xfer == TUSB_XFER_ISOCHRONOUS) { + #if CFG_TUD_AUDIO_ENABLE_FEEDBACK_EP + // Explicit feedback EP + if (desc_ep->bmAttributes.usage == 1) { + ep_fb = desc_ep->bEndpointAddress; + } + #endif + #if CFG_TUD_AUDIO_ENABLE_EP_IN + // Data or data with implicit feedback IN EP + if (tu_edpt_dir(desc_ep->bEndpointAddress) == TUSB_DIR_IN + && (desc_ep->bmAttributes.usage == 0 || desc_ep->bmAttributes.usage == 2)) { + ep_in = desc_ep->bEndpointAddress; + ep_in_size = TU_MAX(tu_edpt_packet_size(desc_ep), ep_in_size); + } + #endif + #if CFG_TUD_AUDIO_ENABLE_EP_OUT + // Data OUT EP + if (tu_edpt_dir(desc_ep->bEndpointAddress) == TUSB_DIR_OUT + && desc_ep->bmAttributes.usage == 0) { + ep_out = desc_ep->bEndpointAddress; + ep_out_size = TU_MAX(tu_edpt_packet_size(desc_ep), ep_out_size); + } + #endif + } + } + + p_desc = tu_desc_next(p_desc); + } + + #if CFG_TUD_AUDIO_ENABLE_EP_IN + if (ep_in) { + usbd_edpt_iso_alloc(rhport, ep_in, ep_in_size); + } + #endif + + #if CFG_TUD_AUDIO_ENABLE_EP_OUT + if (ep_out) { + usbd_edpt_iso_alloc(rhport, ep_out, ep_out_size); + } + #endif + + #if CFG_TUD_AUDIO_ENABLE_FEEDBACK_EP + if (ep_fb) { + usbd_edpt_iso_alloc(rhport, ep_fb, 4); + } + #endif + } +#endif// TUP_DCD_EDPT_ISO_ALLOC + +#if CFG_TUD_AUDIO_ENABLE_EP_IN && CFG_TUD_AUDIO_EP_IN_FLOW_CONTROL + { + uint8_t const *p_desc = _audiod_fct[i].p_desc; + uint8_t const *p_desc_end = p_desc + _audiod_fct[i].desc_length - TUD_AUDIO_DESC_IAD_LEN; + // Condition modified from p_desc < p_desc_end to prevent gcc>=12 strict-overflow warning + while (p_desc_end - p_desc > 0) { + if (tu_desc_type(p_desc) == TUSB_DESC_ENDPOINT) { + tusb_desc_endpoint_t const *desc_ep = (tusb_desc_endpoint_t const *) p_desc; + if (desc_ep->bmAttributes.xfer == TUSB_XFER_ISOCHRONOUS) { + // For data or data with implicit feedback IN EP + if (tu_edpt_dir(desc_ep->bEndpointAddress) == TUSB_DIR_IN + && (desc_ep->bmAttributes.usage == 0 || desc_ep->bmAttributes.usage == 2)) { + _audiod_fct[i].interval_tx = desc_ep->bInterval; + } + } + } else if (tu_desc_type(p_desc) == TUSB_DESC_CS_INTERFACE && tu_desc_subtype(p_desc) == AUDIO_CS_AC_INTERFACE_OUTPUT_TERMINAL) { + if (tu_unaligned_read16(p_desc + 4) == AUDIO_TERM_TYPE_USB_STREAMING) { + _audiod_fct[i].bclock_id_tx = p_desc[8]; + } + } + p_desc = tu_desc_next(p_desc); + } + } +#endif// CFG_TUD_AUDIO_EP_IN_FLOW_CONTROL + +#if CFG_TUD_AUDIO_ENABLE_INTERRUPT_EP + { + uint8_t const *p_desc = _audiod_fct[i].p_desc; + uint8_t const *p_desc_end = p_desc + _audiod_fct[i].desc_length - TUD_AUDIO_DESC_IAD_LEN; + // Condition modified from p_desc < p_desc_end to prevent gcc>=12 strict-overflow warning + while (p_desc_end - p_desc > 0) { + // For each endpoint + if (tu_desc_type(p_desc) == TUSB_DESC_ENDPOINT) { + tusb_desc_endpoint_t const *desc_ep = (tusb_desc_endpoint_t const *) p_desc; + uint8_t const ep_addr = desc_ep->bEndpointAddress; + // If endpoint is input-direction and interrupt-type + if (tu_edpt_dir(ep_addr) == TUSB_DIR_IN && desc_ep->bmAttributes.xfer == TUSB_XFER_INTERRUPT) { + // Store endpoint number and open endpoint + _audiod_fct[i].ep_int = ep_addr; + TU_ASSERT(usbd_edpt_open(_audiod_fct[i].rhport, desc_ep)); + } + } + p_desc = tu_desc_next(p_desc); + } + } +#endif + + _audiod_fct[i].mounted = true; + break; + } + } + + // Verify we found a free one + TU_ASSERT(i < CFG_TUD_AUDIO); + + // This is all we need so far - the EPs are setup by a later set_interface request (as per UAC2 specification) + uint16_t drv_len = _audiod_fct[i].desc_length - TUD_AUDIO_DESC_IAD_LEN;// - TUD_AUDIO_DESC_IAD_LEN since tinyUSB already handles the IAD descriptor + + return drv_len; +} + +static bool audiod_get_interface(uint8_t rhport, tusb_control_request_t const *p_request) { + uint8_t const itf = tu_u16_low(p_request->wIndex); + + // Find index of audio streaming interface + uint8_t func_id; + TU_VERIFY(audiod_verify_itf_exists(itf, &func_id)); + + // Default to 0 if interface not yet activated + uint8_t alt = 0; +#if CFG_TUD_AUDIO_ENABLE_EP_IN + if (_audiod_fct[func_id].ep_in_as_intf_num == itf) { + alt = _audiod_fct[func_id].ep_in_alt; + } +#endif +#if CFG_TUD_AUDIO_ENABLE_EP_OUT + if (_audiod_fct[func_id].ep_out_as_intf_num == itf) { + alt = _audiod_fct[func_id].ep_out_alt; + } +#endif + + TU_VERIFY(tud_control_xfer(rhport, p_request, &alt, 1)); + + TU_LOG2(" Get itf: %u - current alt: %u\r\n", itf, alt); + + return true; +} + +static bool audiod_set_interface(uint8_t rhport, tusb_control_request_t const *p_request) { + (void) rhport; + + // Here we need to do the following: + + // 1. Find the audio driver assigned to the given interface to be set + // Since one audio driver interface has to be able to cover an unknown number of interfaces (AC, AS + its alternate settings), the best memory efficient way to solve this is to always search through the descriptors. + // The audio driver is mapped to an audio function by a reference pointer to the corresponding AC interface of this audio function which serves as a starting point for searching + + // 2. Close EPs which are currently open + // To do so it is not necessary to know the current active alternate interface since we already save the current EP addresses - we simply close them + + // 3. Open new EP + + uint8_t const itf = tu_u16_low(p_request->wIndex); + uint8_t const alt = tu_u16_low(p_request->wValue); + + TU_LOG2(" Set itf: %u - alt: %u\r\n", itf, alt); + + // Find index of audio streaming interface and index of interface + uint8_t func_id; + TU_VERIFY(audiod_verify_itf_exists(itf, &func_id)); + + audiod_function_t *audio = &_audiod_fct[func_id]; + +// Look if there is an EP to be closed - for this driver, there are only 3 possible EPs which may be closed (only AS related EPs can be closed, AC EP (if present) is always open) +#if CFG_TUD_AUDIO_ENABLE_EP_IN + if (audio->ep_in_as_intf_num == itf) { + audio->ep_in_as_intf_num = 0; + audio->ep_in_alt = 0; + #ifndef TUP_DCD_EDPT_ISO_ALLOC + usbd_edpt_close(rhport, audio->ep_in); + #endif + + // Clear FIFOs, since data is no longer valid + tu_fifo_clear(&audio->ep_in_ff); + + // Invoke callback - can be used to stop data sampling + TU_VERIFY(tud_audio_set_itf_close_ep_cb(rhport, p_request)); + + audio->ep_in = 0;// Necessary? + + #if CFG_TUD_AUDIO_EP_IN_FLOW_CONTROL + audio->packet_sz_tx[0] = 0; + audio->packet_sz_tx[1] = 0; + audio->packet_sz_tx[2] = 0; + #endif + } +#endif// CFG_TUD_AUDIO_ENABLE_EP_IN + +#if CFG_TUD_AUDIO_ENABLE_EP_OUT + if (audio->ep_out_as_intf_num == itf) { + audio->ep_out_as_intf_num = 0; + audio->ep_out_alt = 0; + #ifndef TUP_DCD_EDPT_ISO_ALLOC + usbd_edpt_close(rhport, audio->ep_out); + #endif + + // Clear FIFOs, since data is no longer valid + tu_fifo_clear(&audio->ep_out_ff); + + // Invoke callback - can be used to stop data sampling + TU_VERIFY(tud_audio_set_itf_close_ep_cb(rhport, p_request)); + + audio->ep_out = 0;// Necessary? + + // Close corresponding feedback EP + #if CFG_TUD_AUDIO_ENABLE_FEEDBACK_EP + #ifndef TUP_DCD_EDPT_ISO_ALLOC + usbd_edpt_close(rhport, audio->ep_fb); + #endif + audio->ep_fb = 0; + tu_memclr(&audio->feedback, sizeof(audio->feedback)); + #endif + } +#endif// CFG_TUD_AUDIO_ENABLE_EP_OUT + + // Open new EP if necessary - EPs are only to be closed or opened for AS interfaces - Look for AS interface with correct alternate interface + uint8_t const *p_desc = tu_desc_next(audio->p_desc); + // Skip entire AC descriptor block + p_desc += ((audio_desc_cs_ac_interface_t const *) p_desc)->wTotalLength; + // Get pointer at end + uint8_t const *p_desc_end = audio->p_desc + audio->desc_length - TUD_AUDIO_DESC_IAD_LEN; + + // p_desc starts at required interface with alternate setting zero + // Condition modified from p_desc < p_desc_end to prevent gcc>=12 strict-overflow warning + while (p_desc_end - p_desc > 0) { + // Find correct interface + if (tu_desc_type(p_desc) == TUSB_DESC_INTERFACE && ((tusb_desc_interface_t const *) p_desc)->bInterfaceNumber == itf && ((tusb_desc_interface_t const *) p_desc)->bAlternateSetting == alt) { +#if CFG_TUD_AUDIO_ENABLE_EP_IN && CFG_TUD_AUDIO_EP_IN_FLOW_CONTROL + uint8_t const *p_desc_parse_for_params = p_desc; +#endif + // From this point forward follow the EP descriptors associated to the current alternate setting interface - Open EPs if necessary + uint8_t foundEPs = 0, nEps = ((tusb_desc_interface_t const *) p_desc)->bNumEndpoints; + // Condition modified from p_desc < p_desc_end to prevent gcc>=12 strict-overflow warning + while (foundEPs < nEps && (p_desc_end - p_desc > 0)) { + if (tu_desc_type(p_desc) == TUSB_DESC_ENDPOINT) { + tusb_desc_endpoint_t const *desc_ep = (tusb_desc_endpoint_t const *) p_desc; +#ifdef TUP_DCD_EDPT_ISO_ALLOC + TU_ASSERT(usbd_edpt_iso_activate(rhport, desc_ep)); +#else + TU_ASSERT(usbd_edpt_open(rhport, desc_ep)); +#endif + uint8_t const ep_addr = desc_ep->bEndpointAddress; + + //TODO: We need to set EP non busy since this is not taken care of right now in ep_close() - THIS IS A WORKAROUND! + usbd_edpt_clear_stall(rhport, ep_addr); + +#if CFG_TUD_AUDIO_ENABLE_EP_IN + // For data or data with implicit feedback IN EP + if (tu_edpt_dir(ep_addr) == TUSB_DIR_IN && (desc_ep->bmAttributes.usage == 0 || desc_ep->bmAttributes.usage == 2)) + { + // Save address + audio->ep_in = ep_addr; + audio->ep_in_as_intf_num = itf; + audio->ep_in_alt = alt; + audio->ep_in_sz = tu_edpt_packet_size(desc_ep); + + // If flow control is enabled, parse for the corresponding parameters - doing this here means only AS interfaces with EPs get scanned for parameters + #if CFG_TUD_AUDIO_EP_IN_FLOW_CONTROL + audiod_parse_flow_control_params(audio, p_desc_parse_for_params); + #endif + // Schedule first transmit if alternate interface is not zero, as sample data is available a ZLP is loaded + #if USE_LINEAR_BUFFER_TX + TU_VERIFY(usbd_edpt_xfer(rhport, audio->ep_in, audio->lin_buf_in, 0)); + #else + // Send everything in ISO EP FIFO + TU_VERIFY(usbd_edpt_xfer_fifo(rhport, audio->ep_in, &audio->ep_in_ff, 0)); + #endif + } +#endif// CFG_TUD_AUDIO_ENABLE_EP_IN + +#if CFG_TUD_AUDIO_ENABLE_EP_OUT + // Checking usage not necessary + if (tu_edpt_dir(ep_addr) == TUSB_DIR_OUT) { + // Save address + audio->ep_out = ep_addr; + audio->ep_out_as_intf_num = itf; + audio->ep_out_alt = alt; + audio->ep_out_sz = tu_edpt_packet_size(desc_ep); + + // Prepare for incoming data + #if USE_LINEAR_BUFFER_RX + TU_VERIFY(usbd_edpt_xfer(rhport, audio->ep_out, audio->lin_buf_out, audio->ep_out_sz), false); + #else + TU_VERIFY(usbd_edpt_xfer_fifo(rhport, audio->ep_out, &audio->ep_out_ff, audio->ep_out_sz), false); + #endif + } + + #if CFG_TUD_AUDIO_ENABLE_FEEDBACK_EP + // Check if usage is explicit data feedback + if (tu_edpt_dir(ep_addr) == TUSB_DIR_IN && desc_ep->bmAttributes.usage == 1) { + audio->ep_fb = ep_addr; + audio->feedback.frame_shift = desc_ep->bInterval - 1; + // Schedule first feedback transmit + audiod_fb_send(audio); + } + #endif +#endif// CFG_TUD_AUDIO_ENABLE_EP_OUT + + foundEPs += 1; + } + p_desc = tu_desc_next(p_desc); + } + + TU_VERIFY(foundEPs == nEps); + + // Invoke one callback for a final set interface + TU_VERIFY(tud_audio_set_itf_cb(rhport, p_request)); + +#if CFG_TUD_AUDIO_ENABLE_FEEDBACK_EP + // Prepare feedback computation if endpoint is available + if (audio->ep_fb != 0) { + audio_feedback_params_t fb_param; + + tud_audio_feedback_params_cb(func_id, alt, &fb_param); + audio->feedback.compute_method = fb_param.method; + + if (TUSB_SPEED_FULL == tud_speed_get()) + audio->feedback.format_correction = tud_audio_feedback_format_correction_cb(func_id); + + // Minimal/Maximum value in 16.16 format for full speed (1ms per frame) or high speed (125 us per frame) + uint32_t const frame_div = (TUSB_SPEED_FULL == tud_speed_get()) ? 1000 : 8000; + audio->feedback.min_value = ((fb_param.sample_freq - 1) / frame_div) << 16; + audio->feedback.max_value = (fb_param.sample_freq / frame_div + 1) << 16; + + switch (fb_param.method) { + case AUDIO_FEEDBACK_METHOD_FREQUENCY_FIXED: + case AUDIO_FEEDBACK_METHOD_FREQUENCY_FLOAT: + case AUDIO_FEEDBACK_METHOD_FREQUENCY_POWER_OF_2: + audiod_set_fb_params_freq(audio, fb_param.sample_freq, fb_param.frequency.mclk_freq); + break; + + case AUDIO_FEEDBACK_METHOD_FIFO_COUNT: { + // Initialize the threshold level to half filled + uint16_t fifo_lvl_thr = tu_fifo_depth(&audio->ep_out_ff) / 2; + audio->feedback.compute.fifo_count.fifo_lvl_thr = fifo_lvl_thr; + audio->feedback.compute.fifo_count.fifo_lvl_avg = ((uint32_t) fifo_lvl_thr) << 16; + // Avoid 64bit division + uint32_t nominal = ((fb_param.sample_freq / 100) << 16) / (frame_div / 100); + audio->feedback.compute.fifo_count.nom_value = nominal; + audio->feedback.compute.fifo_count.rate_const[0] = (uint16_t) ((audio->feedback.max_value - nominal) / fifo_lvl_thr); + audio->feedback.compute.fifo_count.rate_const[1] = (uint16_t) ((nominal - audio->feedback.min_value) / fifo_lvl_thr); + // On HS feedback is more sensitive since packet size can vary every MSOF, could cause instability + if (tud_speed_get() == TUSB_SPEED_HIGH) { + audio->feedback.compute.fifo_count.rate_const[0] /= 8; + audio->feedback.compute.fifo_count.rate_const[1] /= 8; + } + } break; + + // nothing to do + default: + break; + } + } +#endif// CFG_TUD_AUDIO_ENABLE_FEEDBACK_EP + + // We are done - abort loop + break; + } + + // Moving forward + p_desc = tu_desc_next(p_desc); + } + +#if CFG_TUD_AUDIO_ENABLE_FEEDBACK_EP + // Disable SOF interrupt if no driver has any enabled feedback EP + bool enable_sof = false; + for (uint8_t i = 0; i < CFG_TUD_AUDIO; i++) { + if (_audiod_fct[i].ep_fb != 0 && + (_audiod_fct[i].feedback.compute_method == AUDIO_FEEDBACK_METHOD_FREQUENCY_FIXED || + _audiod_fct[i].feedback.compute_method == AUDIO_FEEDBACK_METHOD_FREQUENCY_FLOAT || + _audiod_fct[i].feedback.compute_method == AUDIO_FEEDBACK_METHOD_FREQUENCY_POWER_OF_2)) { + enable_sof = true; + break; + } + } + usbd_sof_enable(rhport, SOF_CONSUMER_AUDIO, enable_sof); +#endif + +#if CFG_TUD_AUDIO_ENABLE_EP_IN && CFG_TUD_AUDIO_EP_IN_FLOW_CONTROL + audiod_calc_tx_packet_sz(audio); +#endif + + tud_control_status(rhport, p_request); + + return true; +} + +// Invoked when class request DATA stage is finished. +// return false to stall control EP (e.g Host send non-sense DATA) +static bool audiod_control_complete(uint8_t rhport, tusb_control_request_t const *p_request) { + // Handle audio class specific set requests + if (p_request->bmRequestType_bit.type == TUSB_REQ_TYPE_CLASS && p_request->bmRequestType_bit.direction == TUSB_DIR_OUT) { + uint8_t func_id; + + switch (p_request->bmRequestType_bit.recipient) { + case TUSB_REQ_RCPT_INTERFACE: { + uint8_t itf = TU_U16_LOW(p_request->wIndex); + uint8_t entityID = TU_U16_HIGH(p_request->wIndex); + + if (entityID != 0) { + // Check if entity is present and get corresponding driver index + TU_VERIFY(audiod_verify_entity_exists(itf, entityID, &func_id)); + +#if CFG_TUD_AUDIO_ENABLE_EP_IN && CFG_TUD_AUDIO_EP_IN_FLOW_CONTROL + uint8_t ctrlSel = TU_U16_HIGH(p_request->wValue); + if (_audiod_fct[func_id].bclock_id_tx == entityID && ctrlSel == AUDIO_CS_CTRL_SAM_FREQ && p_request->bRequest == AUDIO_CS_REQ_CUR) { + _audiod_fct[func_id].sample_rate_tx = tu_unaligned_read32(_audiod_fct[func_id].ctrl_buf); + } +#endif + + // Invoke callback + return tud_audio_set_req_entity_cb(rhport, p_request, _audiod_fct[func_id].ctrl_buf); + } else { + // Find index of audio driver structure and verify interface really exists + TU_VERIFY(audiod_verify_itf_exists(itf, &func_id)); + + // Invoke callback + return tud_audio_set_req_itf_cb(rhport, p_request, _audiod_fct[func_id].ctrl_buf); + } + } break; + + case TUSB_REQ_RCPT_ENDPOINT: { + uint8_t ep = TU_U16_LOW(p_request->wIndex); + + // Check if entity is present and get corresponding driver index + TU_VERIFY(audiod_verify_ep_exists(ep, &func_id)); + + // Invoke callback + return tud_audio_set_req_ep_cb(rhport, p_request, _audiod_fct[func_id].ctrl_buf); + } break; + // Unknown/Unsupported recipient + default: + TU_BREAKPOINT(); + return false; + } + } + return true; +} + +// Handle class control request +// return false to stall control endpoint (e.g unsupported request) +static bool audiod_control_request(uint8_t rhport, tusb_control_request_t const *p_request) { + (void) rhport; + + // Handle standard requests - standard set requests usually have no data stage so we also handle set requests here + if (p_request->bmRequestType_bit.type == TUSB_REQ_TYPE_STANDARD) { + switch (p_request->bRequest) { + case TUSB_REQ_GET_INTERFACE: + return audiod_get_interface(rhport, p_request); + + case TUSB_REQ_SET_INTERFACE: + return audiod_set_interface(rhport, p_request); + + case TUSB_REQ_CLEAR_FEATURE: + return true; + + // Unknown/Unsupported request + default: + TU_BREAKPOINT(); + return false; + } + } + + // Handle class requests + if (p_request->bmRequestType_bit.type == TUSB_REQ_TYPE_CLASS) { + uint8_t itf = TU_U16_LOW(p_request->wIndex); + uint8_t func_id; + + // Conduct checks which depend on the recipient + switch (p_request->bmRequestType_bit.recipient) { + case TUSB_REQ_RCPT_INTERFACE: { + uint8_t entityID = TU_U16_HIGH(p_request->wIndex); + + // Verify if entity is present + if (entityID != 0) { + // Find index of audio driver structure and verify entity really exists + TU_VERIFY(audiod_verify_entity_exists(itf, entityID, &func_id)); + + // In case we got a get request invoke callback - callback needs to answer as defined in UAC2 specification page 89 - 5. Requests + if (p_request->bmRequestType_bit.direction == TUSB_DIR_IN) { + return tud_audio_get_req_entity_cb(rhport, p_request); + } + } else { + // Find index of audio driver structure and verify interface really exists + TU_VERIFY(audiod_verify_itf_exists(itf, &func_id)); + + // In case we got a get request invoke callback - callback needs to answer as defined in UAC2 specification page 89 - 5. Requests + if (p_request->bmRequestType_bit.direction == TUSB_DIR_IN) { + return tud_audio_get_req_itf_cb(rhport, p_request); + } + } + } break; + + case TUSB_REQ_RCPT_ENDPOINT: { + uint8_t ep = TU_U16_LOW(p_request->wIndex); + + // Find index of audio driver structure and verify EP really exists + TU_VERIFY(audiod_verify_ep_exists(ep, &func_id)); + + // In case we got a get request invoke callback - callback needs to answer as defined in UAC2 specification page 89 - 5. Requests + if (p_request->bmRequestType_bit.direction == TUSB_DIR_IN) { + return tud_audio_get_req_ep_cb(rhport, p_request); + } + } break; + + // Unknown/Unsupported recipient + default: + TU_LOG2(" Unsupported recipient: %d\r\n", p_request->bmRequestType_bit.recipient); + TU_BREAKPOINT(); + return false; + } + + // If we end here, the received request is a set request - we schedule a receive for the data stage and return true here. We handle the rest later in audiod_control_complete() once the data stage was finished + TU_VERIFY(tud_control_xfer(rhport, p_request, _audiod_fct[func_id].ctrl_buf, _audiod_fct[func_id].ctrl_buf_sz)); + return true; + } + + // There went something wrong - unsupported control request type + TU_BREAKPOINT(); + return false; +} + +bool audiod_control_xfer_cb(uint8_t rhport, uint8_t stage, tusb_control_request_t const *request) { + if (stage == CONTROL_STAGE_SETUP) { + return audiod_control_request(rhport, request); + } else if (stage == CONTROL_STAGE_DATA) { + return audiod_control_complete(rhport, request); + } + + return true; +} + +bool audiod_xfer_cb(uint8_t rhport, uint8_t ep_addr, xfer_result_t result, uint32_t xferred_bytes) { + (void) result; + (void) xferred_bytes; + + #if CFG_TUD_AUDIO_ENABLE_INTERRUPT_EP + // Search for interface belonging to given end point address and proceed as required + for (uint8_t func_id = 0; func_id < CFG_TUD_AUDIO; func_id++) { + audiod_function_t *audio = &_audiod_fct[func_id]; + + // Data transmission of control interrupt finished + if (audio->ep_int == ep_addr) { + // According to USB2 specification, maximum payload of interrupt EP is 8 bytes on low speed, 64 bytes on full speed, and 1024 bytes on high speed (but only if an alternate interface other than 0 is used - see specification p. 49) + // In case there is nothing to send we have to return a NAK - this is taken care of by PHY ??? + // In case of an erroneous transmission a retransmission is conducted - this is taken care of by PHY ??? + + // I assume here, that things above are handled by PHY + // All transmission is done - what remains to do is to inform job was completed + + tud_audio_int_xfer_cb(rhport); + return true; + } + + } + #else + (void) rhport; + (void) ep_addr; + #endif + + return false; +} + +bool audiod_xfer_isr(uint8_t rhport, uint8_t ep_addr, xfer_result_t result, uint32_t xferred_bytes) { + (void) result; + (void) xferred_bytes; + + // Search for interface belonging to given end point address and proceed as required + for (uint8_t func_id = 0; func_id < CFG_TUD_AUDIO; func_id++) + { + audiod_function_t* audio = &_audiod_fct[func_id]; + +#if CFG_TUD_AUDIO_ENABLE_EP_IN + + // Data transmission of audio packet finished + if (audio->ep_in == ep_addr) { + // USB 2.0, section 5.6.4, third paragraph, states "An isochronous endpoint must specify its required bus access period. However, an isochronous endpoint must be prepared to handle poll rates faster than the one specified." + // That paragraph goes on to say "An isochronous IN endpoint must return a zero-length packet whenever data is requested at a faster interval than the specified interval and data is not available." + // This can only be solved reliably if we load a ZLP after every IN transmission since we can not say if the host requests samples earlier than we declared! Once all samples are collected we overwrite the loaded ZLP. + + // Check if there is data to load into EPs buffer - if not load it with ZLP + // Be aware - we as a device are not able to know if the host polls for data with a faster rate as we stated this in the descriptors. Therefore we always have to put something into the EPs buffer. However, once we did that, there is no way of aborting this or replacing what we put into the buffer before! + // This is the only place where we can fill something into the EPs buffer! + + // Load new data + audiod_tx_xfer_isr(rhport, audio, (uint16_t) xferred_bytes); + return true; + } +#endif + +#if CFG_TUD_AUDIO_ENABLE_EP_OUT + // New audio packet received + if (audio->ep_out == ep_addr) { + audiod_rx_xfer_isr(rhport, audio, (uint16_t) xferred_bytes); + return true; + } + #if CFG_TUD_AUDIO_ENABLE_FEEDBACK_EP + // Transmission of feedback EP finished + if (audio->ep_fb == ep_addr) { + // Schedule a transmit with the new value if EP is not busy + // Schedule next transmission - value is changed bytud_audio_n_fb_set() in the meantime or the old value gets sent + audiod_fb_send(audio); + return true; + } + #endif +#endif + } + + return false; +} + +#if CFG_TUD_AUDIO_ENABLE_EP_OUT && CFG_TUD_AUDIO_ENABLE_FEEDBACK_EP + +static bool audiod_set_fb_params_freq(audiod_function_t *audio, uint32_t sample_freq, uint32_t mclk_freq) { + // Check if frame interval is within sane limits + // The interval value n_frames was taken from the descriptors within audiod_set_interface() + + // n_frames_min is ceil(2^10 * f_s / f_m) for full speed and ceil(2^13 * f_s / f_m) for high speed + // this lower limit ensures the measures feedback value has sufficient precision + uint32_t const k = (TUSB_SPEED_FULL == tud_speed_get()) ? 10 : 13; + uint32_t const n_frame = (1UL << audio->feedback.frame_shift); + + if ((((1UL << k) * sample_freq / mclk_freq) + 1) > n_frame) { + TU_LOG1(" UAC2 feedback interval too small\r\n"); + TU_BREAKPOINT(); + return false; + } + + // Check if parameters really allow for a power of two division + if ((mclk_freq % sample_freq) == 0 && tu_is_power_of_two(mclk_freq / sample_freq)) { + audio->feedback.compute_method = AUDIO_FEEDBACK_METHOD_FREQUENCY_POWER_OF_2; + audio->feedback.compute.power_of_2 = (uint8_t) (16 - (audio->feedback.frame_shift - 1) - tu_log2(mclk_freq / sample_freq)); + } else if (audio->feedback.compute_method == AUDIO_FEEDBACK_METHOD_FREQUENCY_FLOAT) { + audio->feedback.compute.float_const = (float) sample_freq / (float) mclk_freq * (1UL << (16 - (audio->feedback.frame_shift - 1))); + } else { + audio->feedback.compute.fixed.sample_freq = sample_freq; + audio->feedback.compute.fixed.mclk_freq = mclk_freq; + } + + return true; +} + +static void audiod_fb_fifo_count_update(audiod_function_t *audio, uint16_t lvl_new) { + /* Low-pass (averaging) filter */ + uint32_t lvl = audio->feedback.compute.fifo_count.fifo_lvl_avg; + lvl = (uint32_t) (((uint64_t) lvl * 63 + ((uint32_t) lvl_new << 16)) >> 6); + audio->feedback.compute.fifo_count.fifo_lvl_avg = lvl; + + uint32_t const ff_lvl = lvl >> 16; + uint16_t const ff_thr = audio->feedback.compute.fifo_count.fifo_lvl_thr; + uint16_t const *rate = audio->feedback.compute.fifo_count.rate_const; + + uint32_t feedback; + + if (ff_lvl < ff_thr) { + feedback = audio->feedback.compute.fifo_count.nom_value + (ff_thr - ff_lvl) * rate[0]; + } else { + feedback = audio->feedback.compute.fifo_count.nom_value - (ff_lvl - ff_thr) * rate[1]; + } + + if (feedback > audio->feedback.max_value) feedback = audio->feedback.max_value; + if (feedback < audio->feedback.min_value) feedback = audio->feedback.min_value; + audio->feedback.value = feedback; +} + +uint32_t tud_audio_feedback_update(uint8_t func_id, uint32_t cycles) { + audiod_function_t *audio = &_audiod_fct[func_id]; + uint32_t feedback; + + switch (audio->feedback.compute_method) { + case AUDIO_FEEDBACK_METHOD_FREQUENCY_POWER_OF_2: + feedback = (cycles << audio->feedback.compute.power_of_2); + break; + + case AUDIO_FEEDBACK_METHOD_FREQUENCY_FLOAT: + feedback = (uint32_t) ((float) cycles * audio->feedback.compute.float_const); + break; + + case AUDIO_FEEDBACK_METHOD_FREQUENCY_FIXED: { + uint64_t fb64 = (((uint64_t) cycles) * audio->feedback.compute.fixed.sample_freq) << (16 - (audio->feedback.frame_shift - 1)); + feedback = (uint32_t) (fb64 / audio->feedback.compute.fixed.mclk_freq); + } break; + + default: + return 0; + } + + // For Windows: https://docs.microsoft.com/en-us/windows-hardware/drivers/audio/usb-2-0-audio-drivers + // The size of isochronous packets created by the device must be within the limits specified in FMT-2.0 section 2.3.1.1. + // This means that the deviation of actual packet size from nominal size must not exceed +/- one audio slot + // (audio slot = channel count samples). + if (feedback > audio->feedback.max_value) feedback = audio->feedback.max_value; + if (feedback < audio->feedback.min_value) feedback = audio->feedback.min_value; + + tud_audio_n_fb_set(func_id, feedback); + + return feedback; +} + +bool tud_audio_n_fb_set(uint8_t func_id, uint32_t feedback) { + TU_VERIFY(func_id < CFG_TUD_AUDIO && _audiod_fct[func_id].p_desc != NULL); + + _audiod_fct[func_id].feedback.value = feedback; + + return true; +} +#endif + +TU_ATTR_FAST_FUNC void audiod_sof_isr(uint8_t rhport, uint32_t frame_count) { + (void) rhport; + (void) frame_count; + +#if CFG_TUD_AUDIO_ENABLE_EP_OUT && CFG_TUD_AUDIO_ENABLE_FEEDBACK_EP + // Determine feedback value - The feedback method is described in 5.12.4.2 of the USB 2.0 spec + // Boiled down, the feedback value Ff = n_samples / (micro)frame. + // Since an accuracy of less than 1 Sample / second is desired, at least n_frames = ceil(2^K * f_s / f_m) frames need to be measured, where K = 10 for full speed and K = 13 for high speed, f_s is the sampling frequency e.g. 48 kHz and f_m is the cpu clock frequency e.g. 100 MHz (or any other master clock whose clock count is available and locked to f_s) + // The update interval in the (4.10.2.1) Feedback Endpoint Descriptor must be less or equal to 2^(K - P), where P = min( ceil(log2(f_m / f_s)), K) + // feedback = n_cycles / n_frames * f_s / f_m in 16.16 format, where n_cycles are the number of main clock cycles within fb_n_frames + + // Iterate over audio functions and set feedback value + for (uint8_t i = 0; i < CFG_TUD_AUDIO; i++) { + audiod_function_t *audio = &_audiod_fct[i]; + + if (audio->ep_fb != 0) { + // HS shift need to be adjusted since SOF event is generated for frame only + uint8_t const hs_adjust = (TUSB_SPEED_HIGH == tud_speed_get()) ? 3 : 0; + uint32_t const interval = 1UL << (audio->feedback.frame_shift - hs_adjust); + if (0 == (frame_count & (interval - 1))) { + tud_audio_feedback_interval_isr(i, frame_count, audio->feedback.frame_shift); + } + } + } +#endif// CFG_TUD_AUDIO_ENABLE_EP_OUT && CFG_TUD_AUDIO_ENABLE_FEEDBACK_EP +} + +bool tud_audio_buffer_and_schedule_control_xfer(uint8_t rhport, tusb_control_request_t const *p_request, void *data, uint16_t len) { + // Handles only sending of data not receiving + if (p_request->bmRequestType_bit.direction == TUSB_DIR_OUT) return false; + + // Get corresponding driver index + uint8_t func_id; + uint8_t itf = TU_U16_LOW(p_request->wIndex); + + // Conduct checks which depend on the recipient + switch (p_request->bmRequestType_bit.recipient) { + case TUSB_REQ_RCPT_INTERFACE: { + uint8_t entityID = TU_U16_HIGH(p_request->wIndex); + + // Verify if entity is present + if (entityID != 0) { + // Find index of audio driver structure and verify entity really exists + TU_VERIFY(audiod_verify_entity_exists(itf, entityID, &func_id)); + } else { + // Find index of audio driver structure and verify interface really exists + TU_VERIFY(audiod_verify_itf_exists(itf, &func_id)); + } + } break; + + case TUSB_REQ_RCPT_ENDPOINT: { + uint8_t ep = TU_U16_LOW(p_request->wIndex); + + // Find index of audio driver structure and verify EP really exists + TU_VERIFY(audiod_verify_ep_exists(ep, &func_id)); + } break; + + // Unknown/Unsupported recipient + default: + TU_LOG2(" Unsupported recipient: %d\r\n", p_request->bmRequestType_bit.recipient); + TU_BREAKPOINT(); + return false; + } + + // Crop length + if (len > _audiod_fct[func_id].ctrl_buf_sz) len = _audiod_fct[func_id].ctrl_buf_sz; + + // Copy into buffer + TU_VERIFY(0 == tu_memcpy_s(_audiod_fct[func_id].ctrl_buf, _audiod_fct[func_id].ctrl_buf_sz, data, (size_t) len)); + +#if CFG_TUD_AUDIO_ENABLE_EP_IN && CFG_TUD_AUDIO_EP_IN_FLOW_CONTROL + // Find data for sampling_frequency_control + if (p_request->bmRequestType_bit.type == TUSB_REQ_TYPE_CLASS && p_request->bmRequestType_bit.recipient == TUSB_REQ_RCPT_INTERFACE) { + uint8_t entityID = TU_U16_HIGH(p_request->wIndex); + uint8_t ctrlSel = TU_U16_HIGH(p_request->wValue); + if (_audiod_fct[func_id].bclock_id_tx == entityID && ctrlSel == AUDIO_CS_CTRL_SAM_FREQ && p_request->bRequest == AUDIO_CS_REQ_CUR) { + _audiod_fct[func_id].sample_rate_tx = tu_unaligned_read32(_audiod_fct[func_id].ctrl_buf); + } + } +#endif + + // Schedule transmit + return tud_control_xfer(rhport, p_request, (void *) _audiod_fct[func_id].ctrl_buf, len); +} + +// Verify an entity with the given ID exists and returns also the corresponding driver index +static bool audiod_verify_entity_exists(uint8_t itf, uint8_t entityID, uint8_t *func_id) { + uint8_t i; + for (i = 0; i < CFG_TUD_AUDIO; i++) { + // Look for the correct driver by checking if the unique standard AC interface number fits + if (_audiod_fct[i].p_desc && ((tusb_desc_interface_t const *) _audiod_fct[i].p_desc)->bInterfaceNumber == itf) { + // Get pointers after class specific AC descriptors and end of AC descriptors - entities are defined in between + uint8_t const *p_desc = tu_desc_next(_audiod_fct[i].p_desc);// Points to CS AC descriptor + uint8_t const *p_desc_end = ((audio_desc_cs_ac_interface_t const *) p_desc)->wTotalLength + p_desc; + p_desc = tu_desc_next(p_desc);// Get past CS AC descriptor + + // Condition modified from p_desc < p_desc_end to prevent gcc>=12 strict-overflow warning + while (p_desc_end - p_desc > 0) { + // Entity IDs are always at offset 3 + if (p_desc[3] == entityID) { + *func_id = i; + return true; + } + p_desc = tu_desc_next(p_desc); + } + } + } + return false; +} + +static bool audiod_verify_itf_exists(uint8_t itf, uint8_t *func_id) { + uint8_t i; + for (i = 0; i < CFG_TUD_AUDIO; i++) { + if (_audiod_fct[i].p_desc) { + // Get pointer at beginning and end + uint8_t const *p_desc = _audiod_fct[i].p_desc; + uint8_t const *p_desc_end = _audiod_fct[i].p_desc + _audiod_fct[i].desc_length - TUD_AUDIO_DESC_IAD_LEN; + // Condition modified from p_desc < p_desc_end to prevent gcc>=12 strict-overflow warning + while (p_desc_end - p_desc > 0) { + if (tu_desc_type(p_desc) == TUSB_DESC_INTERFACE && ((tusb_desc_interface_t const *)p_desc)->bInterfaceNumber == itf) { + *func_id = i; + return true; + } + p_desc = tu_desc_next(p_desc); + } + } + } + return false; +} + +static bool audiod_verify_ep_exists(uint8_t ep, uint8_t *func_id) { + uint8_t i; + for (i = 0; i < CFG_TUD_AUDIO; i++) { + if (_audiod_fct[i].p_desc) { + // Get pointer at end + uint8_t const *p_desc_end = _audiod_fct[i].p_desc + _audiod_fct[i].desc_length; + + // Advance past AC descriptors - EP we look for are streaming EPs + uint8_t const *p_desc = tu_desc_next(_audiod_fct[i].p_desc); + p_desc += ((audio_desc_cs_ac_interface_t const *) p_desc)->wTotalLength; + + // Condition modified from p_desc < p_desc_end to prevent gcc>=12 strict-overflow warning + while (p_desc_end - p_desc > 0) { + if (tu_desc_type(p_desc) == TUSB_DESC_ENDPOINT && ((tusb_desc_endpoint_t const *) p_desc)->bEndpointAddress == ep) { + *func_id = i; + return true; + } + p_desc = tu_desc_next(p_desc); + } + } + } + return false; +} + +#if CFG_TUD_AUDIO_ENABLE_EP_IN && CFG_TUD_AUDIO_EP_IN_FLOW_CONTROL +static void audiod_parse_flow_control_params(audiod_function_t *audio, uint8_t const *p_desc) { + + p_desc = tu_desc_next(p_desc);// Exclude standard AS interface descriptor of current alternate interface descriptor + + // Look for a Class-Specific AS Interface Descriptor(4.9.2) to verify format type and format and also to get number of physical channels + if (tu_desc_type(p_desc) == TUSB_DESC_CS_INTERFACE && tu_desc_subtype(p_desc) == AUDIO_CS_AS_INTERFACE_AS_GENERAL) { + audio->n_channels_tx = ((audio_desc_cs_as_interface_t const *) p_desc)->bNrChannels; + audio->format_type_tx = (audio_format_type_t) (((audio_desc_cs_as_interface_t const *) p_desc)->bFormatType); + // Look for a Type I Format Type Descriptor(2.3.1.6 - Audio Formats) + p_desc = tu_desc_next(p_desc); + if (tu_desc_type(p_desc) == TUSB_DESC_CS_INTERFACE && tu_desc_subtype(p_desc) == AUDIO_CS_AS_INTERFACE_FORMAT_TYPE && ((audio_desc_type_I_format_t const *) p_desc)->bFormatType == AUDIO_FORMAT_TYPE_I) { + audio->n_bytes_per_sample_tx = ((audio_desc_type_I_format_t const *) p_desc)->bSubslotSize; + } + } +} + +static bool audiod_calc_tx_packet_sz(audiod_function_t *audio) { + TU_VERIFY(audio->format_type_tx == AUDIO_FORMAT_TYPE_I); + TU_VERIFY(audio->n_channels_tx); + TU_VERIFY(audio->n_bytes_per_sample_tx); + TU_VERIFY(audio->interval_tx); + TU_VERIFY(audio->sample_rate_tx); + + const uint8_t interval = (tud_speed_get() == TUSB_SPEED_FULL) ? audio->interval_tx : 1 << (audio->interval_tx - 1); + + const uint16_t sample_normimal = (uint16_t) (audio->sample_rate_tx * interval / ((tud_speed_get() == TUSB_SPEED_FULL) ? 1000 : 8000)); + const uint16_t sample_reminder = (uint16_t) (audio->sample_rate_tx * interval % ((tud_speed_get() == TUSB_SPEED_FULL) ? 1000 : 8000)); + + const uint16_t packet_sz_tx_min = (uint16_t) ((sample_normimal - 1) * audio->n_channels_tx * audio->n_bytes_per_sample_tx); + const uint16_t packet_sz_tx_norm = (uint16_t) (sample_normimal * audio->n_channels_tx * audio->n_bytes_per_sample_tx); + const uint16_t packet_sz_tx_max = (uint16_t) ((sample_normimal + 1) * audio->n_channels_tx * audio->n_bytes_per_sample_tx); + + // Endpoint size must larger than packet size + TU_ASSERT(packet_sz_tx_max <= audio->ep_in_sz); + + // Frmt20.pdf 2.3.1.1 USB Packets + if (sample_reminder) { + // All virtual frame packets must either contain INT(nav) audio slots (small VFP) or INT(nav)+1 (large VFP) audio slots + audio->packet_sz_tx[0] = packet_sz_tx_norm; + audio->packet_sz_tx[1] = packet_sz_tx_norm; + audio->packet_sz_tx[2] = packet_sz_tx_max; + } else { + // In the case where nav = INT(nav), ni may vary between INT(nav)-1 (small VFP), INT(nav) + // (medium VFP) and INT(nav)+1 (large VFP). + audio->packet_sz_tx[0] = packet_sz_tx_min; + audio->packet_sz_tx[1] = packet_sz_tx_norm; + audio->packet_sz_tx[2] = packet_sz_tx_max; + } + + return true; +} + +static uint16_t audiod_tx_packet_size(const uint16_t *norminal_size, uint16_t data_count, uint16_t fifo_depth, uint16_t max_depth) { + // Flow control need a FIFO size of at least 4*Navg + if (norminal_size[1] && norminal_size[1] <= fifo_depth * 4) { + // Use blackout to prioritize normal size packet + static int ctrl_blackout = 0; + uint16_t packet_size; + uint16_t slot_size = norminal_size[2] - norminal_size[1]; + if (data_count < norminal_size[0]) { + // If you get here frequently, then your I2S clock deviation is too big ! + packet_size = 0; + } else if (data_count < fifo_depth / 2 - slot_size && !ctrl_blackout) { + packet_size = norminal_size[0]; + ctrl_blackout = 10; + } else if (data_count > fifo_depth / 2 + slot_size && !ctrl_blackout) { + packet_size = norminal_size[2]; + if (norminal_size[0] == norminal_size[1]) { + // nav > INT(nav), eg. 44.1k, 88.2k + ctrl_blackout = 0; + } else { + // nav = INT(nav), eg. 48k, 96k + ctrl_blackout = 10; + } + } else { + packet_size = norminal_size[1]; + if (ctrl_blackout) { + ctrl_blackout--; + } + } + // Normally this cap is not necessary + return tu_min16(packet_size, max_depth); + } else { + return tu_min16(data_count, max_depth); + } +} + +#endif + +// No security checks here - internal function only which should always succeed +static uint8_t audiod_get_audio_fct_idx(audiod_function_t *audio) { + for (uint8_t cnt = 0; cnt < CFG_TUD_AUDIO; cnt++) { + if (&_audiod_fct[cnt] == audio) return cnt; + } + return 0; +} + +#endif // (CFG_TUD_ENABLED && CFG_TUD_AUDIO) diff --git a/cores/nRF5/TinyUSB/Adafruit_TinyUSB_ArduinoCore/tinyusb/src/class/audio/audio_device.h b/cores/nRF5/TinyUSB/Adafruit_TinyUSB_ArduinoCore/tinyusb/src/class/audio/audio_device.h new file mode 100644 index 0000000..fd47c64 --- /dev/null +++ b/cores/nRF5/TinyUSB/Adafruit_TinyUSB_ArduinoCore/tinyusb/src/class/audio/audio_device.h @@ -0,0 +1,467 @@ +/* + * The MIT License (MIT) + * + * Copyright (c) 2020 Ha Thach (tinyusb.org) + * Copyright (c) 2020 Reinhard Panhuber + * Copyright (c) 2023 HiFiPhile + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + * This file is part of the TinyUSB stack. + */ + +#ifndef TUSB_AUDIO_DEVICE_H_ +#define TUSB_AUDIO_DEVICE_H_ + +#include "audio.h" + +//--------------------------------------------------------------------+ +// Class Driver Configuration +//--------------------------------------------------------------------+ + +// All sizes are in bytes! + +#ifndef CFG_TUD_AUDIO_FUNC_1_DESC_LEN +#error You must tell the driver the length of the audio function descriptor including IAD descriptor +#endif +#if CFG_TUD_AUDIO > 1 +#ifndef CFG_TUD_AUDIO_FUNC_2_DESC_LEN +#error You must tell the driver the length of the audio function descriptor including IAD descriptor +#endif +#endif +#if CFG_TUD_AUDIO > 2 +#ifndef CFG_TUD_AUDIO_FUNC_3_DESC_LEN +#error You must tell the driver the length of the audio function descriptor including IAD descriptor +#endif +#endif + +// Size of control buffer used to receive and send control messages via EP0 - has to be big enough to hold your biggest request structure e.g. range requests with multiple intervals defined or cluster descriptors +#ifndef CFG_TUD_AUDIO_FUNC_1_CTRL_BUF_SZ +#error You must define an audio class control request buffer size! +#endif + +#if CFG_TUD_AUDIO > 1 +#ifndef CFG_TUD_AUDIO_FUNC_2_CTRL_BUF_SZ +#error You must define an audio class control request buffer size! +#endif +#endif + +#if CFG_TUD_AUDIO > 2 +#ifndef CFG_TUD_AUDIO_FUNC_3_CTRL_BUF_SZ +#error You must define an audio class control request buffer size! +#endif +#endif + +// End point sizes IN BYTES - Limits: Full Speed <= 1023, High Speed <= 1024 +#ifndef CFG_TUD_AUDIO_ENABLE_EP_IN +#define CFG_TUD_AUDIO_ENABLE_EP_IN 0 // TX +#endif + +#ifndef CFG_TUD_AUDIO_ENABLE_EP_OUT +#define CFG_TUD_AUDIO_ENABLE_EP_OUT 0 // RX +#endif + +// Maximum EP sizes for all alternate AS interface settings - used for checks and buffer allocation +#if CFG_TUD_AUDIO_ENABLE_EP_IN +#ifndef CFG_TUD_AUDIO_FUNC_1_EP_IN_SZ_MAX +#error You must tell the driver the biggest EP IN size! +#endif +#if CFG_TUD_AUDIO > 1 +#ifndef CFG_TUD_AUDIO_FUNC_2_EP_IN_SZ_MAX +#error You must tell the driver the biggest EP IN size! +#endif +#endif +#if CFG_TUD_AUDIO > 2 +#ifndef CFG_TUD_AUDIO_FUNC_3_EP_IN_SZ_MAX +#error You must tell the driver the biggest EP IN size! +#endif +#endif +#endif // CFG_TUD_AUDIO_ENABLE_EP_IN + +#if CFG_TUD_AUDIO_ENABLE_EP_OUT +#ifndef CFG_TUD_AUDIO_FUNC_1_EP_OUT_SZ_MAX +#error You must tell the driver the biggest EP OUT size! +#endif +#if CFG_TUD_AUDIO > 1 +#ifndef CFG_TUD_AUDIO_FUNC_2_EP_OUT_SZ_MAX +#error You must tell the driver the biggest EP OUT size! +#endif +#endif +#if CFG_TUD_AUDIO > 2 +#ifndef CFG_TUD_AUDIO_FUNC_3_EP_OUT_SZ_MAX +#error You must tell the driver the biggest EP OUT size! +#endif +#endif +#endif // CFG_TUD_AUDIO_ENABLE_EP_OUT + +// Software EP FIFO buffer sizes - must be >= max EP SIZEs! +#ifndef CFG_TUD_AUDIO_FUNC_1_EP_IN_SW_BUF_SZ +#define CFG_TUD_AUDIO_FUNC_1_EP_IN_SW_BUF_SZ 0 +#endif +#ifndef CFG_TUD_AUDIO_FUNC_2_EP_IN_SW_BUF_SZ +#define CFG_TUD_AUDIO_FUNC_2_EP_IN_SW_BUF_SZ 0 +#endif +#ifndef CFG_TUD_AUDIO_FUNC_3_EP_IN_SW_BUF_SZ +#define CFG_TUD_AUDIO_FUNC_3_EP_IN_SW_BUF_SZ 0 +#endif + +#ifndef CFG_TUD_AUDIO_FUNC_1_EP_OUT_SW_BUF_SZ +#define CFG_TUD_AUDIO_FUNC_1_EP_OUT_SW_BUF_SZ 0 +#endif +#ifndef CFG_TUD_AUDIO_FUNC_2_EP_OUT_SW_BUF_SZ +#define CFG_TUD_AUDIO_FUNC_2_EP_OUT_SW_BUF_SZ 0 +#endif +#ifndef CFG_TUD_AUDIO_FUNC_3_EP_OUT_SW_BUF_SZ +#define CFG_TUD_AUDIO_FUNC_3_EP_OUT_SW_BUF_SZ 0 +#endif + +#if CFG_TUD_AUDIO_ENABLE_EP_IN +#if CFG_TUD_AUDIO_FUNC_1_EP_IN_SW_BUF_SZ < CFG_TUD_AUDIO_FUNC_1_EP_IN_SZ_MAX +#error EP software buffer size MUST BE at least as big as maximum EP size +#endif + +#if CFG_TUD_AUDIO > 1 +#if CFG_TUD_AUDIO_FUNC_2_EP_IN_SW_BUF_SZ < CFG_TUD_AUDIO_FUNC_2_EP_IN_SZ_MAX +#error EP software buffer size MUST BE at least as big as maximum EP size +#endif +#endif + +#if CFG_TUD_AUDIO > 2 +#if CFG_TUD_AUDIO_FUNC_3_EP_IN_SW_BUF_SZ < CFG_TUD_AUDIO_FUNC_3_EP_IN_SZ_MAX +#error EP software buffer size MUST BE at least as big as maximum EP size +#endif +#endif +#endif + +#if CFG_TUD_AUDIO_ENABLE_EP_OUT +#if CFG_TUD_AUDIO_FUNC_1_EP_OUT_SW_BUF_SZ < CFG_TUD_AUDIO_FUNC_1_EP_OUT_SZ_MAX +#error EP software buffer size MUST BE at least as big as maximum EP size +#endif + +#if CFG_TUD_AUDIO > 1 +#if CFG_TUD_AUDIO_FUNC_2_EP_OUT_SW_BUF_SZ < CFG_TUD_AUDIO_FUNC_2_EP_OUT_SZ_MAX +#error EP software buffer size MUST BE at least as big as maximum EP size +#endif +#endif + +#if CFG_TUD_AUDIO > 2 +#if CFG_TUD_AUDIO_FUNC_3_EP_OUT_SW_BUF_SZ < CFG_TUD_AUDIO_FUNC_3_EP_OUT_SZ_MAX +#error EP software buffer size MUST BE at least as big as maximum EP size +#endif +#endif +#endif + +// (For TYPE-I format only) Flow control is necessary to allow IN ep send correct amount of data, unless it's a virtual device where data is perfectly synchronized to USB clock. +#ifndef CFG_TUD_AUDIO_EP_IN_FLOW_CONTROL +#define CFG_TUD_AUDIO_EP_IN_FLOW_CONTROL 1 +#endif + +// Enable/disable feedback EP (required for asynchronous RX applications) +#ifndef CFG_TUD_AUDIO_ENABLE_FEEDBACK_EP +#define CFG_TUD_AUDIO_ENABLE_FEEDBACK_EP 0 // Feedback - 0 or 1 +#endif + +// Enable/disable conversion from 16.16 to 10.14 format on full-speed devices. See tud_audio_n_fb_set(). +// Can be override by tud_audio_feedback_format_correction_cb() +#ifndef CFG_TUD_AUDIO_ENABLE_FEEDBACK_FORMAT_CORRECTION +#define CFG_TUD_AUDIO_ENABLE_FEEDBACK_FORMAT_CORRECTION 0 // 0 or 1 +#endif + +// Enable/disable interrupt EP (required for notifying host of control changes) +#ifndef CFG_TUD_AUDIO_ENABLE_INTERRUPT_EP +#define CFG_TUD_AUDIO_ENABLE_INTERRUPT_EP 0 // Feedback - 0 or 1 +#endif + +// Audio control interrupt EP - 6 Bytes according to UAC 2 specification (p. 74) +#define CFG_TUD_AUDIO_INTERRUPT_EP_SZ 6 + +#ifdef __cplusplus +extern "C" { +#endif + +/** \addtogroup AUDIO_Serial Serial + * @{ + * \defgroup AUDIO_Serial_Device Device + * @{ */ + +//--------------------------------------------------------------------+ +// Application API (Multiple Interfaces) +// CFG_TUD_AUDIO > 1 +//--------------------------------------------------------------------+ +bool tud_audio_n_mounted(uint8_t func_id); + +#if CFG_TUD_AUDIO_ENABLE_EP_OUT +uint16_t tud_audio_n_available (uint8_t func_id); +uint16_t tud_audio_n_read (uint8_t func_id, void* buffer, uint16_t bufsize); +bool tud_audio_n_clear_ep_out_ff (uint8_t func_id); +tu_fifo_t* tud_audio_n_get_ep_out_ff (uint8_t func_id); +#endif + +#if CFG_TUD_AUDIO_ENABLE_EP_IN +uint16_t tud_audio_n_write (uint8_t func_id, const void * data, uint16_t len); +bool tud_audio_n_clear_ep_in_ff (uint8_t func_id); +tu_fifo_t* tud_audio_n_get_ep_in_ff (uint8_t func_id); +#endif + +#if CFG_TUD_AUDIO_ENABLE_INTERRUPT_EP +bool tud_audio_int_n_write (uint8_t func_id, const audio_interrupt_data_t * data); +#endif + +//--------------------------------------------------------------------+ +// Application API (Interface0) +//--------------------------------------------------------------------+ +static inline bool tud_audio_mounted (void); + +#if CFG_TUD_AUDIO_ENABLE_EP_OUT +static inline uint16_t tud_audio_available (void); +static inline bool tud_audio_clear_ep_out_ff (void); +static inline uint16_t tud_audio_read (void* buffer, uint16_t bufsize); +static inline tu_fifo_t* tud_audio_get_ep_out_ff (void); +#endif + +#if CFG_TUD_AUDIO_ENABLE_EP_IN +static inline uint16_t tud_audio_write (const void * data, uint16_t len); +static inline bool tud_audio_clear_ep_in_ff (void); +static inline tu_fifo_t* tud_audio_get_ep_in_ff (void); +#endif + +// INT CTR API + +#if CFG_TUD_AUDIO_ENABLE_INTERRUPT_EP +static inline bool tud_audio_int_write (const audio_interrupt_data_t * data); +#endif + +// Buffer control EP data and schedule a transmit +// This function is intended to be used if you do not have a persistent buffer or memory location available (e.g. non-local variables) and need to answer onto a +// get request. This function buffers your answer request frame into the control buffer of the corresponding audio driver and schedules a transmit for sending it. +// Since transmission is triggered via interrupts, a persistent memory location is required onto which the buffer pointer in pointing. If you already have such +// available you may directly use 'tud_control_xfer(...)'. In this case data does not need to be copied into an additional buffer and you save some time. +// If the request's wLength is zero, a status packet is sent instead. +bool tud_audio_buffer_and_schedule_control_xfer(uint8_t rhport, tusb_control_request_t const * p_request, void* data, uint16_t len); + +//--------------------------------------------------------------------+ +// Application Callback API +//--------------------------------------------------------------------+ + +#if CFG_TUD_AUDIO_ENABLE_EP_IN +// Invoked in ISR context once an audio packet was sent successfully. +// Normally this function is not needed, since the data transfer should be driven by audio clock (i.e. I2S clock), call tud_audio_write() in I2S receive callback. +bool tud_audio_tx_done_isr(uint8_t rhport, uint16_t n_bytes_sent, uint8_t func_id, uint8_t ep_in, uint8_t cur_alt_setting); +#endif + +#if CFG_TUD_AUDIO_ENABLE_EP_OUT +// Invoked in ISR context once an audio packet was received successfully. +// Normally this function is not needed, since the data transfer should be driven by audio clock (i.e. I2S clock), call tud_audio_read() in I2S transmit callback. +bool tud_audio_rx_done_isr(uint8_t rhport, uint16_t n_bytes_received, uint8_t func_id, uint8_t ep_out, uint8_t cur_alt_setting); +#endif + +#if CFG_TUD_AUDIO_ENABLE_EP_OUT && CFG_TUD_AUDIO_ENABLE_FEEDBACK_EP + +// Note about feedback calculation +// +// Option 1 - AUDIO_FEEDBACK_METHOD_FIFO_COUNT +// Feedback value is calculated within the audio driver by regulating the FIFO level to half fill. +// Advantage: No ISR interrupt is enabled, hence the CPU need not to handle an ISR every 1ms or 125us and thus less CPU load, well tested +// (Windows, Linux, OSX) with a reliable result so far. +// Disadvantage: A FIFO of minimal 4 frames is needed to compensate for jitter, an average delay of 2 frames is introduced. +// +// Option 2 - AUDIO_FEEDBACK_METHOD_FREQUENCY_FIXED / AUDIO_FEEDBACK_METHOD_FREQUENCY_FLOAT +// Feedback value is calculated within the audio driver by use of SOF interrupt. The driver needs information about the master clock f_m from +// which the audio sample frequency f_s is derived, f_s itself, and the cycle count of f_m at time of the SOF interrupt (e.g. by use of a hardware counter). +// See tud_audio_set_fb_params() and tud_audio_feedback_update() +// Advantage: Reduced jitter in the feedback value computation, hence, the receive FIFO can be smaller and thus a smaller delay is possible. +// Disadvantage: higher CPU load due to SOF ISR handling every frame i.e. 1ms or 125us. (The most critical point is the reading of the cycle counter value of f_m. +// It is read from within the SOF ISR - see: audiod_sof() -, hence, the ISR must has a high priority such that no software dependent "random" delay i.e. jitter is introduced). +// Long-term drift could occur since error is accumulated. +// +// Option 3 - manual +// Determined by the user itself and set by use of tud_audio_n_fb_set(). The feedback value may be determined e.g. from some fill status of some FIFO buffer. +// Advantage: No ISR interrupt is enabled, hence the CPU need not to handle an ISR every 1ms or 125us and thus less CPU load. +// Disadvantage: typically a larger FIFO is needed to compensate for jitter (e.g. 6 frames), i.e. a larger delay is introduced. + + +// This function is used to provide data rate feedback from an asynchronous sink. Feedback value will be sent at FB endpoint interval till it's changed. +// +// The feedback format is specified to be 16.16 for HS and 10.14 for FS devices (see Universal Serial Bus Specification Revision 2.0 5.12.4.2). By default, +// the choice of format is left to the caller and feedback argument is sent as-is. If CFG_TUD_AUDIO_ENABLE_FEEDBACK_FORMAT_CORRECTION is set or tud_audio_feedback_format_correction_cb() +// return true, then tinyusb expects 16.16 format and handles the conversion to 10.14 on FS. +// +// Note that due to a bug in its USB Audio 2.0 driver, Windows currently requires 16.16 format for _all_ USB 2.0 devices. On Linux and it seems the +// driver can work with either format. +// +// Feedback value can be determined from within the SOF ISR of the audio driver. This should reduce jitter. If the feature is used, the user can not set the feedback value. +// +// Determine feedback value - The feedback method is described in 5.12.4.2 of the USB 2.0 spec +// Boiled down, the feedback value Ff = n_samples / (micro)frame. +// Since an accuracy of less than 1 Sample / second is desired, at least n_frames = ceil(2^K * f_s / f_m) frames need to be measured, where K = 10 for full speed and K = 13 +// for high speed, f_s is the sampling frequency e.g. 48 kHz and f_m is the cpu clock frequency e.g. 100 MHz (or any other master clock whose clock count is available and locked to f_s) +// The update interval in the (4.10.2.1) Feedback Endpoint Descriptor must be less or equal to 2^(K - P), where P = min( ceil(log2(f_m / f_s)), K) +// feedback = n_cycles / n_frames * f_s / f_m in 16.16 format, where n_cycles are the number of main clock cycles within fb_n_frames +bool tud_audio_n_fb_set(uint8_t func_id, uint32_t feedback); + +// Update feedback value with passed MCLK cycles since last time this update function is called. +// Typically called within tud_audio_sof_isr(). Required tud_audio_feedback_params_cb() is implemented +// This function will also call tud_audio_feedback_set() +// return feedback value in 16.16 for reference (0 for error) +// Example : +// binterval=3 (4ms); FS = 48kHz; MCLK = 12.288MHz +// In 4 SOF MCLK counted 49152 cycles +uint32_t tud_audio_feedback_update(uint8_t func_id, uint32_t cycles); + +enum { + AUDIO_FEEDBACK_METHOD_DISABLED, + AUDIO_FEEDBACK_METHOD_FREQUENCY_FIXED, + AUDIO_FEEDBACK_METHOD_FREQUENCY_FLOAT, + AUDIO_FEEDBACK_METHOD_FREQUENCY_POWER_OF_2, // For driver internal use only + AUDIO_FEEDBACK_METHOD_FIFO_COUNT +}; + +typedef struct { + uint8_t method; + uint32_t sample_freq; // sample frequency in Hz + + union { + struct { + uint32_t mclk_freq; // Main clock frequency in Hz i.e. master clock to which sample clock is based on + }frequency; + + }; +}audio_feedback_params_t; + +// Invoked when needed to set feedback parameters +void tud_audio_feedback_params_cb(uint8_t func_id, uint8_t alt_itf, audio_feedback_params_t* feedback_param); + +// Callback in ISR context, invoked periodically according to feedback endpoint bInterval. +// Could be used to compute and update feedback value, should be placed in RAM if possible +// frame_number : current SOF count +// interval_shift: number of bit shift i.e log2(interval) from Feedback endpoint descriptor +TU_ATTR_FAST_FUNC void tud_audio_feedback_interval_isr(uint8_t func_id, uint32_t frame_number, uint8_t interval_shift); + +// (Full-Speed only) Callback to set feedback format correction is applied or not, +// default to CFG_TUD_AUDIO_ENABLE_FEEDBACK_FORMAT_CORRECTION if not implemented. +bool tud_audio_feedback_format_correction_cb(uint8_t func_id); +#endif // CFG_TUD_AUDIO_ENABLE_EP_OUT && CFG_TUD_AUDIO_ENABLE_FEEDBACK_EP + +#if CFG_TUD_AUDIO_ENABLE_INTERRUPT_EP +void tud_audio_int_done_cb(uint8_t rhport); +#endif + +// Invoked when audio set interface request received +bool tud_audio_set_itf_cb(uint8_t rhport, tusb_control_request_t const * p_request); + +// Invoked when audio set interface request received which closes an EP +bool tud_audio_set_itf_close_ep_cb(uint8_t rhport, tusb_control_request_t const * p_request); + +// backward compatible for typo +#define tud_audio_set_itf_close_EP_cb tud_audio_set_itf_close_ep_cb + +// Invoked when audio class specific set request received for an EP +bool tud_audio_set_req_ep_cb(uint8_t rhport, tusb_control_request_t const * p_request, uint8_t *pBuff); + +// Invoked when audio class specific set request received for an interface +bool tud_audio_set_req_itf_cb(uint8_t rhport, tusb_control_request_t const * p_request, uint8_t *pBuff); + +// Invoked when audio class specific set request received for an entity +bool tud_audio_set_req_entity_cb(uint8_t rhport, tusb_control_request_t const * p_request, uint8_t *pBuff); + +// Invoked when audio class specific get request received for an EP +bool tud_audio_get_req_ep_cb(uint8_t rhport, tusb_control_request_t const * p_request); + +// Invoked when audio class specific get request received for an interface +bool tud_audio_get_req_itf_cb(uint8_t rhport, tusb_control_request_t const * p_request); + +// Invoked when audio class specific get request received for an entity +bool tud_audio_get_req_entity_cb(uint8_t rhport, tusb_control_request_t const * p_request); + +//--------------------------------------------------------------------+ +// Inline Functions +//--------------------------------------------------------------------+ + +TU_ATTR_ALWAYS_INLINE static inline bool tud_audio_mounted(void) { + return tud_audio_n_mounted(0); +} + +#if CFG_TUD_AUDIO_ENABLE_EP_OUT + +TU_ATTR_ALWAYS_INLINE static inline uint16_t tud_audio_available(void) { + return tud_audio_n_available(0); +} + +TU_ATTR_ALWAYS_INLINE static inline uint16_t tud_audio_read(void* buffer, uint16_t bufsize) { + return tud_audio_n_read(0, buffer, bufsize); +} + +TU_ATTR_ALWAYS_INLINE static inline bool tud_audio_clear_ep_out_ff(void) { + return tud_audio_n_clear_ep_out_ff(0); +} + +TU_ATTR_ALWAYS_INLINE static inline tu_fifo_t* tud_audio_get_ep_out_ff(void) { + return tud_audio_n_get_ep_out_ff(0); +} + +#endif + +#if CFG_TUD_AUDIO_ENABLE_EP_IN + +TU_ATTR_ALWAYS_INLINE static inline uint16_t tud_audio_write(const void * data, uint16_t len) { + return tud_audio_n_write(0, data, len); +} + +TU_ATTR_ALWAYS_INLINE static inline bool tud_audio_clear_ep_in_ff(void) { + return tud_audio_n_clear_ep_in_ff(0); +} + +TU_ATTR_ALWAYS_INLINE static inline tu_fifo_t* tud_audio_get_ep_in_ff(void) { + return tud_audio_n_get_ep_in_ff(0); +} + +#endif + +#if CFG_TUD_AUDIO_ENABLE_INTERRUPT_EP +TU_ATTR_ALWAYS_INLINE static inline bool tud_audio_int_write(const audio_interrupt_data_t * data) { + return tud_audio_int_n_write(0, data); +} +#endif + +#if CFG_TUD_AUDIO_ENABLE_EP_OUT && CFG_TUD_AUDIO_ENABLE_FEEDBACK_EP +TU_ATTR_ALWAYS_INLINE static inline bool tud_audio_fb_set(uint32_t feedback) { + return tud_audio_n_fb_set(0, feedback); +} +#endif + +//--------------------------------------------------------------------+ +// Internal Class Driver API +//--------------------------------------------------------------------+ +void audiod_init (void); +bool audiod_deinit (void); +void audiod_reset (uint8_t rhport); +uint16_t audiod_open (uint8_t rhport, tusb_desc_interface_t const * itf_desc, uint16_t max_len); +bool audiod_control_xfer_cb(uint8_t rhport, uint8_t stage, tusb_control_request_t const * request); +bool audiod_xfer_cb (uint8_t rhport, uint8_t edpt_addr, xfer_result_t result, uint32_t xferred_bytes); +bool audiod_xfer_isr (uint8_t rhport, uint8_t edpt_addr, xfer_result_t result, uint32_t xferred_bytes); +void audiod_sof_isr (uint8_t rhport, uint32_t frame_count); + +#ifdef __cplusplus +} +#endif + +#endif /* _TUSB_AUDIO_DEVICE_H_ */ + +/** @} */ +/** @} */ diff --git a/cores/nRF5/TinyUSB/Adafruit_TinyUSB_ArduinoCore/tinyusb/src/class/bth/bth_device.c b/cores/nRF5/TinyUSB/Adafruit_TinyUSB_ArduinoCore/tinyusb/src/class/bth/bth_device.c index 252e20e..3f1529c 100644 --- a/cores/nRF5/TinyUSB/Adafruit_TinyUSB_ArduinoCore/tinyusb/src/class/bth/bth_device.c +++ b/cores/nRF5/TinyUSB/Adafruit_TinyUSB_ArduinoCore/tinyusb/src/class/bth/bth_device.c @@ -26,48 +26,73 @@ #include "tusb_option.h" -#if (TUSB_OPT_DEVICE_ENABLED && CFG_TUD_BTH) +#if (CFG_TUD_ENABLED && CFG_TUD_BTH) //--------------------------------------------------------------------+ // INCLUDE //--------------------------------------------------------------------+ #include "bth_device.h" -#include #include //--------------------------------------------------------------------+ // MACRO CONSTANT TYPEDEF //--------------------------------------------------------------------+ -typedef struct -{ +typedef struct { uint8_t itf_num; uint8_t ep_ev; uint8_t ep_acl_in; + uint16_t ep_acl_in_pkt_sz; uint8_t ep_acl_out; - uint8_t ep_voice[2]; // Not used yet + uint8_t ep_voice[2];// Not used yet uint8_t ep_voice_size[2][CFG_TUD_BTH_ISO_ALT_COUNT]; - // Endpoint Transfer buffer - CFG_TUSB_MEM_ALIGN bt_hci_cmd_t hci_cmd; - CFG_TUSB_MEM_ALIGN uint8_t epout_buf[CFG_TUD_BTH_DATA_EPSIZE]; - + // Previous amount of bytes sent when issuing ZLP + uint32_t prev_xferred_bytes; } btd_interface_t; +typedef struct { + TUD_EPBUF_DEF(epout_buf, CFG_TUD_BTH_DATA_EPSIZE); + TUD_EPBUF_TYPE_DEF(bt_hci_cmd_t, hci_cmd); +} btd_epbuf_t; + //--------------------------------------------------------------------+ // INTERNAL OBJECT & FUNCTION DECLARATION //--------------------------------------------------------------------+ -CFG_TUSB_MEM_SECTION btd_interface_t _btd_itf; +static btd_interface_t _btd_itf; +CFG_TUD_MEM_SECTION static btd_epbuf_t _btd_epbuf; + +static bool bt_tx_data(uint8_t ep, void *data, uint16_t len) { + uint8_t const rhport = 0; -static bool bt_tx_data(uint8_t ep, void *data, uint16_t len) -{ // skip if previous transfer not complete - TU_VERIFY(!usbd_edpt_busy(TUD_OPT_RHPORT, ep)); + TU_VERIFY(!usbd_edpt_busy(rhport, ep)); - TU_ASSERT(usbd_edpt_xfer(TUD_OPT_RHPORT, ep, data, len)); + TU_ASSERT(usbd_edpt_xfer(rhport, ep, data, len)); return true; } +//--------------------------------------------------------------------+ +// Weak stubs: invoked if no strong implementation is available +//--------------------------------------------------------------------+ +TU_ATTR_WEAK void tud_bt_hci_cmd_cb(void *hci_cmd, size_t cmd_len) { + (void) hci_cmd; + (void) cmd_len; +} + +TU_ATTR_WEAK void tud_bt_acl_data_received_cb(void *acl_data, uint16_t data_len) { + (void) acl_data; + (void) data_len; +} + +TU_ATTR_WEAK void tud_bt_event_sent_cb(uint16_t sent_bytes) { + (void) sent_bytes; +} + +TU_ATTR_WEAK void tud_bt_acl_data_sent_cb(uint16_t sent_bytes) { + (void) sent_bytes; +} + //--------------------------------------------------------------------+ // READ API //--------------------------------------------------------------------+ @@ -77,31 +102,30 @@ static bool bt_tx_data(uint8_t ep, void *data, uint16_t len) // WRITE API //--------------------------------------------------------------------+ -bool tud_bt_event_send(void *event, uint16_t event_len) -{ +bool tud_bt_event_send(void *event, uint16_t event_len) { return bt_tx_data(_btd_itf.ep_ev, event, event_len); } -bool tud_bt_acl_data_send(void *event, uint16_t event_len) -{ +bool tud_bt_acl_data_send(void *event, uint16_t event_len) { return bt_tx_data(_btd_itf.ep_acl_in, event, event_len); } //--------------------------------------------------------------------+ // USBD Driver API //--------------------------------------------------------------------+ -void btd_init(void) -{ +void btd_init(void) { tu_memclr(&_btd_itf, sizeof(_btd_itf)); } -void btd_reset(uint8_t rhport) -{ - (void)rhport; +bool btd_deinit(void) { + return true; +} + +void btd_reset(uint8_t rhport) { + (void) rhport; } -uint16_t btd_open(uint8_t rhport, tusb_desc_interface_t const *itf_desc, uint16_t max_len) -{ +uint16_t btd_open(uint8_t rhport, tusb_desc_interface_t const *itf_desc, uint16_t max_len) { tusb_desc_endpoint_t const *desc_ep; uint16_t drv_len = 0; // Size of single alternative of ISO interface @@ -110,143 +134,165 @@ uint16_t btd_open(uint8_t rhport, tusb_desc_interface_t const *itf_desc, uint16_ const uint16_t hci_itf_size = sizeof(tusb_desc_interface_t) + 3 * sizeof(tusb_desc_endpoint_t); // Ensure this is BT Primary Controller TU_VERIFY(TUSB_CLASS_WIRELESS_CONTROLLER == itf_desc->bInterfaceClass && - TUD_BT_APP_SUBCLASS == itf_desc->bInterfaceSubClass && - TUD_BT_PROTOCOL_PRIMARY_CONTROLLER == itf_desc->bInterfaceProtocol, 0); + TUD_BT_APP_SUBCLASS == itf_desc->bInterfaceSubClass && + TUD_BT_PROTOCOL_PRIMARY_CONTROLLER == itf_desc->bInterfaceProtocol, + 0); - // Distinguish interface by number of endpoints, as both interface have same class, subclass and protocol - if (itf_desc->bNumEndpoints == 3 && max_len >= hci_itf_size) - { - _btd_itf.itf_num = itf_desc->bInterfaceNumber; + TU_ASSERT(itf_desc->bNumEndpoints == 3 && max_len >= hci_itf_size); - desc_ep = (tusb_desc_endpoint_t const *) tu_desc_next(itf_desc); + _btd_itf.itf_num = itf_desc->bInterfaceNumber; + + desc_ep = (tusb_desc_endpoint_t const *) tu_desc_next(itf_desc); - TU_ASSERT(TUSB_DESC_ENDPOINT == desc_ep->bDescriptorType && TUSB_XFER_INTERRUPT == desc_ep->bmAttributes.xfer, 0); - TU_ASSERT(usbd_edpt_open(rhport, desc_ep), 0); - _btd_itf.ep_ev = desc_ep->bEndpointAddress; + TU_ASSERT(TUSB_DESC_ENDPOINT == desc_ep->bDescriptorType && TUSB_XFER_INTERRUPT == desc_ep->bmAttributes.xfer, 0); + TU_ASSERT(usbd_edpt_open(rhport, desc_ep), 0); + _btd_itf.ep_ev = desc_ep->bEndpointAddress; - // Open endpoint pair - TU_ASSERT(usbd_open_edpt_pair(rhport, tu_desc_next(desc_ep), 2, TUSB_XFER_BULK, &_btd_itf.ep_acl_out, - &_btd_itf.ep_acl_in), 0); + desc_ep = (tusb_desc_endpoint_t const *) tu_desc_next(desc_ep); - // Prepare for incoming data from host - TU_ASSERT(usbd_edpt_xfer(rhport, _btd_itf.ep_acl_out, _btd_itf.epout_buf, CFG_TUD_BTH_DATA_EPSIZE), 0); + // Open endpoint pair + TU_ASSERT(usbd_open_edpt_pair(rhport, (uint8_t const *) desc_ep, 2, + TUSB_XFER_BULK, &_btd_itf.ep_acl_out, + &_btd_itf.ep_acl_in), + 0); - drv_len = hci_itf_size; + // Save acl in endpoint max packet size + tusb_desc_endpoint_t const *desc_ep_acl_in = desc_ep; + for (size_t p = 0; p < 2; p++) { + if (tu_edpt_dir(desc_ep_acl_in->bEndpointAddress) == TUSB_DIR_IN) { + _btd_itf.ep_acl_in_pkt_sz = tu_edpt_packet_size(desc_ep_acl_in); + break; + } + desc_ep_acl_in = (tusb_desc_endpoint_t const *) tu_desc_next(desc_ep_acl_in); } - else if (itf_desc->bNumEndpoints == 2 && max_len >= iso_alt_itf_size) - { - uint8_t dir; - desc_ep = (tusb_desc_endpoint_t const *)tu_desc_next(itf_desc); + itf_desc = (tusb_desc_interface_t const *) tu_desc_next(tu_desc_next(desc_ep)); + + // Prepare for incoming data from host + TU_ASSERT(usbd_edpt_xfer(rhport, _btd_itf.ep_acl_out, _btd_epbuf.epout_buf, CFG_TUD_BTH_DATA_EPSIZE), 0); + + drv_len = hci_itf_size; + + // Ensure this is still BT Primary Controller + TU_ASSERT(TUSB_CLASS_WIRELESS_CONTROLLER == itf_desc->bInterfaceClass && + TUD_BT_APP_SUBCLASS == itf_desc->bInterfaceSubClass && + TUD_BT_PROTOCOL_PRIMARY_CONTROLLER == itf_desc->bInterfaceProtocol, + 0); + TU_ASSERT(itf_desc->bNumEndpoints == 2 && max_len >= iso_alt_itf_size + drv_len); + + uint8_t dir; + + desc_ep = (tusb_desc_endpoint_t const *) tu_desc_next(itf_desc); + TU_ASSERT(itf_desc->bAlternateSetting < CFG_TUD_BTH_ISO_ALT_COUNT, 0); + TU_ASSERT(desc_ep->bDescriptorType == TUSB_DESC_ENDPOINT, 0); + dir = tu_edpt_dir(desc_ep->bEndpointAddress); + _btd_itf.ep_voice[dir] = desc_ep->bEndpointAddress; + // Store endpoint size for alternative + _btd_itf.ep_voice_size[dir][itf_desc->bAlternateSetting] = (uint8_t) tu_edpt_packet_size(desc_ep); + + desc_ep = (tusb_desc_endpoint_t const *) tu_desc_next(desc_ep); + TU_ASSERT(desc_ep->bDescriptorType == TUSB_DESC_ENDPOINT, 0); + dir = tu_edpt_dir(desc_ep->bEndpointAddress); + _btd_itf.ep_voice[dir] = desc_ep->bEndpointAddress; + // Store endpoint size for alternative + _btd_itf.ep_voice_size[dir][itf_desc->bAlternateSetting] = (uint8_t) tu_edpt_packet_size(desc_ep); + drv_len += iso_alt_itf_size; + + for (int i = 1; i < CFG_TUD_BTH_ISO_ALT_COUNT && drv_len + iso_alt_itf_size <= max_len; ++i) { + // Make sure rest of alternatives matches + itf_desc = (tusb_desc_interface_t const *) tu_desc_next(desc_ep); + if (itf_desc->bDescriptorType != TUSB_DESC_INTERFACE || + TUSB_CLASS_WIRELESS_CONTROLLER != itf_desc->bInterfaceClass || + TUD_BT_APP_SUBCLASS != itf_desc->bInterfaceSubClass || + TUD_BT_PROTOCOL_PRIMARY_CONTROLLER != itf_desc->bInterfaceProtocol) { + // Not an Iso interface instance + break; + } TU_ASSERT(itf_desc->bAlternateSetting < CFG_TUD_BTH_ISO_ALT_COUNT, 0); - TU_ASSERT(desc_ep->bDescriptorType == TUSB_DESC_ENDPOINT, 0); + + desc_ep = (tusb_desc_endpoint_t const *) tu_desc_next(itf_desc); dir = tu_edpt_dir(desc_ep->bEndpointAddress); - _btd_itf.ep_voice[dir] = desc_ep->bEndpointAddress; - // Store endpoint size for alternative - _btd_itf.ep_voice_size[dir][itf_desc->bAlternateSetting] = (uint8_t)desc_ep->wMaxPacketSize.size; + // Verify that alternative endpoint are same as first ones + TU_ASSERT(desc_ep->bDescriptorType == TUSB_DESC_ENDPOINT && + _btd_itf.ep_voice[dir] == desc_ep->bEndpointAddress, + 0); + _btd_itf.ep_voice_size[dir][itf_desc->bAlternateSetting] = (uint8_t) tu_edpt_packet_size(desc_ep); - desc_ep = (tusb_desc_endpoint_t const *)tu_desc_next(desc_ep); - TU_ASSERT(desc_ep->bDescriptorType == TUSB_DESC_ENDPOINT, 0); + desc_ep = (tusb_desc_endpoint_t const *) tu_desc_next(desc_ep); dir = tu_edpt_dir(desc_ep->bEndpointAddress); - _btd_itf.ep_voice[dir] = desc_ep->bEndpointAddress; - // Store endpoint size for alternative - _btd_itf.ep_voice_size[dir][itf_desc->bAlternateSetting] = (uint8_t)desc_ep->wMaxPacketSize.size; + // Verify that alternative endpoint are same as first ones + TU_ASSERT(desc_ep->bDescriptorType == TUSB_DESC_ENDPOINT && + _btd_itf.ep_voice[dir] == desc_ep->bEndpointAddress, + 0); + _btd_itf.ep_voice_size[dir][itf_desc->bAlternateSetting] = (uint8_t) tu_edpt_packet_size(desc_ep); drv_len += iso_alt_itf_size; - - for (int i = 1; i < CFG_TUD_BTH_ISO_ALT_COUNT && drv_len + iso_alt_itf_size <= max_len; ++i) { - // Make sure rest of alternatives matches - itf_desc = (tusb_desc_interface_t const *)tu_desc_next(desc_ep); - if (itf_desc->bDescriptorType != TUSB_DESC_INTERFACE || - TUSB_CLASS_WIRELESS_CONTROLLER != itf_desc->bInterfaceClass || - TUD_BT_APP_SUBCLASS != itf_desc->bInterfaceSubClass || - TUD_BT_PROTOCOL_PRIMARY_CONTROLLER != itf_desc->bInterfaceProtocol) - { - // Not an Iso interface instance - break; - } - TU_ASSERT(itf_desc->bAlternateSetting < CFG_TUD_BTH_ISO_ALT_COUNT, 0); - - desc_ep = (tusb_desc_endpoint_t const *)tu_desc_next(itf_desc); - dir = tu_edpt_dir(desc_ep->bEndpointAddress); - // Verify that alternative endpoint are same as first ones - TU_ASSERT(desc_ep->bDescriptorType == TUSB_DESC_ENDPOINT && - _btd_itf.ep_voice[dir] == desc_ep->bEndpointAddress, 0); - _btd_itf.ep_voice_size[dir][itf_desc->bAlternateSetting] = (uint8_t)desc_ep->wMaxPacketSize.size; - - desc_ep = (tusb_desc_endpoint_t const *)tu_desc_next(desc_ep); - dir = tu_edpt_dir(desc_ep->bEndpointAddress); - // Verify that alternative endpoint are same as first ones - TU_ASSERT(desc_ep->bDescriptorType == TUSB_DESC_ENDPOINT && - _btd_itf.ep_voice[dir] == desc_ep->bEndpointAddress, 0); - _btd_itf.ep_voice_size[dir][itf_desc->bAlternateSetting] = (uint8_t)desc_ep->wMaxPacketSize.size; - drv_len += iso_alt_itf_size; - } } return drv_len; } -bool btd_control_complete(uint8_t rhport, tusb_control_request_t const *request) -{ - (void)rhport; - - // Handle class request only - TU_VERIFY(request->bmRequestType_bit.type == TUSB_REQ_TYPE_CLASS); - - if (tud_bt_hci_cmd_cb) tud_bt_hci_cmd_cb(&_btd_itf.hci_cmd, request->wLength); - - return true; -} +// Invoked when a control transfer occurred on an interface of this class +// Driver response accordingly to the request and the transfer stage (setup/data/ack) +// return false to stall control endpoint (e.g unsupported request) +bool btd_control_xfer_cb(uint8_t rhport, uint8_t stage, tusb_control_request_t const *request) { + (void) rhport; + + if (stage == CONTROL_STAGE_SETUP) { + if (request->bmRequestType_bit.type == TUSB_REQ_TYPE_CLASS && + request->bmRequestType_bit.recipient == TUSB_REQ_RCPT_DEVICE) { + // HCI command packet addressing for single function Primary Controllers + // also compatible with historical mode if enabled + TU_VERIFY((request->bRequest == 0 && request->wValue == 0 && request->wIndex == 0) || + (CFG_TUD_BTH_HISTORICAL_COMPATIBLE && request->bRequest == 0xe0)); + } else if (request->bmRequestType_bit.recipient == TUSB_REQ_RCPT_INTERFACE) { + if (request->bRequest == TUSB_REQ_SET_INTERFACE && _btd_itf.itf_num + 1 == request->wIndex) { + // TODO: Set interface it would involve changing size of endpoint size + } else { + // HCI command packet for Primary Controller function in a composite device + TU_VERIFY(request->bRequest == 0 && request->wValue == 0 && request->wIndex == _btd_itf.itf_num); + } + } else + return false; -bool btd_control_request(uint8_t rhport, tusb_control_request_t const *request) -{ - (void)rhport; + return tud_control_xfer(rhport, request, &_btd_epbuf.hci_cmd, sizeof(bt_hci_cmd_t)); + } else if (stage == CONTROL_STAGE_DATA) { + // Handle class request only + TU_VERIFY(request->bmRequestType_bit.type == TUSB_REQ_TYPE_CLASS); - if (request->bmRequestType_bit.type == TUSB_REQ_TYPE_CLASS && - request->bmRequestType_bit.recipient == TUSB_REQ_RCPT_DEVICE) - { - // HCI command packet addressing for single function Primary Controllers - TU_VERIFY(request->bRequest == 0 && request->wValue == 0 && request->wIndex == 0); + tud_bt_hci_cmd_cb(&_btd_epbuf.hci_cmd, tu_min16(request->wLength, sizeof(bt_hci_cmd_t))); } - else if (request->bmRequestType_bit.recipient == TUSB_REQ_RCPT_INTERFACE) - { - if (request->bRequest == TUSB_REQ_SET_INTERFACE && _btd_itf.itf_num + 1 == request->wIndex) - { - // TODO: Set interface it would involve changing size of endpoint size - } - else - { - // HCI command packet for Primary Controller function in a composite device - TU_VERIFY(request->bRequest == 0 && request->wValue == 0 && request->wIndex == _btd_itf.itf_num); - } - } - else return false; - return tud_control_xfer(rhport, request, &_btd_itf.hci_cmd, request->wLength); + return true; } -bool btd_xfer_cb(uint8_t rhport, uint8_t ep_addr, xfer_result_t result, uint32_t xferred_bytes) -{ - (void)result; - +bool btd_xfer_cb(uint8_t rhport, uint8_t ep_addr, xfer_result_t result, + uint32_t xferred_bytes) { // received new data from host - if (ep_addr == _btd_itf.ep_acl_out) - { - if (tud_bt_acl_data_received_cb) tud_bt_acl_data_received_cb(_btd_itf.epout_buf, xferred_bytes); + if (ep_addr == _btd_itf.ep_acl_out) { + tud_bt_acl_data_received_cb(_btd_epbuf.epout_buf, xferred_bytes); // prepare for next data - TU_ASSERT(usbd_edpt_xfer(rhport, _btd_itf.ep_acl_out, _btd_itf.epout_buf, CFG_TUD_BTH_DATA_EPSIZE)); - } - else if (ep_addr == _btd_itf.ep_ev) - { - if (tud_bt_event_sent_cb) tud_bt_event_sent_cb((uint16_t)xferred_bytes); - } - else if (ep_addr == _btd_itf.ep_acl_in) - { - if (tud_bt_acl_data_sent_cb) tud_bt_acl_data_sent_cb((uint16_t)xferred_bytes); + TU_ASSERT(usbd_edpt_xfer(rhport, _btd_itf.ep_acl_out, _btd_epbuf.epout_buf, CFG_TUD_BTH_DATA_EPSIZE)); + } else if (ep_addr == _btd_itf.ep_ev) { + tud_bt_event_sent_cb((uint16_t) xferred_bytes); + } else if (ep_addr == _btd_itf.ep_acl_in) { + if ((result == XFER_RESULT_SUCCESS) && (xferred_bytes > 0) && + ((xferred_bytes & (_btd_itf.ep_acl_in_pkt_sz - 1)) == 0)) { + // Save number of transferred bytes + _btd_itf.prev_xferred_bytes = xferred_bytes; + + // Send zero-length packet + tud_bt_acl_data_send(NULL, 0); + } else { + if (xferred_bytes == 0) { + xferred_bytes = _btd_itf.prev_xferred_bytes; + _btd_itf.prev_xferred_bytes = 0; + } + tud_bt_acl_data_sent_cb((uint16_t) xferred_bytes); + } } - return TUSB_ERROR_NONE; + return true; } #endif diff --git a/cores/nRF5/TinyUSB/Adafruit_TinyUSB_ArduinoCore/tinyusb/src/class/bth/bth_device.h b/cores/nRF5/TinyUSB/Adafruit_TinyUSB_ArduinoCore/tinyusb/src/class/bth/bth_device.h index 5e54680..68f073b 100644 --- a/cores/nRF5/TinyUSB/Adafruit_TinyUSB_ArduinoCore/tinyusb/src/class/bth/bth_device.h +++ b/cores/nRF5/TinyUSB/Adafruit_TinyUSB_ArduinoCore/tinyusb/src/class/bth/bth_device.h @@ -1,4 +1,4 @@ -/* +/* * The MIT License (MIT) * * Copyright (c) 2020 Jerzy Kasenberg @@ -36,10 +36,17 @@ #ifndef CFG_TUD_BTH_EVENT_EPSIZE #define CFG_TUD_BTH_EVENT_EPSIZE 16 #endif + #ifndef CFG_TUD_BTH_DATA_EPSIZE #define CFG_TUD_BTH_DATA_EPSIZE 64 #endif +// Allow BTH class to work in historically compatibility mode where the bRequest is always 0xe0. +// See Bluetooth Core v5.3, Vol. 4, Part B, Section 2.2 +#ifndef CFG_TUD_BTH_HISTORICAL_COMPATIBLE +#define CFG_TUD_BTH_HISTORICAL_COMPATIBLE 0 +#endif + typedef struct TU_ATTR_PACKED { uint16_t op_code; @@ -60,23 +67,23 @@ typedef struct TU_ATTR_PACKED // Part E, 5.4.1. // Length of the command is from 3 bytes (2 bytes for OpCode, // 1 byte for parameter total length) to 258. -TU_ATTR_WEAK void tud_bt_hci_cmd_cb(void *hci_cmd, size_t cmd_len); +void tud_bt_hci_cmd_cb(void *hci_cmd, size_t cmd_len); // Invoked when ACL data was received over USB from Bluetooth host. // Detailed format is described in Bluetooth core specification Vol 2, // Part E, 5.4.2. // Length is from 4 bytes, (12 bits for Handle, 4 bits for flags // and 16 bits for data total length) to endpoint size. -TU_ATTR_WEAK void tud_bt_acl_data_received_cb(void *acl_data, uint16_t data_len); +void tud_bt_acl_data_received_cb(void *acl_data, uint16_t data_len); // Called when event sent with tud_bt_event_send() was delivered to BT stack. // Controller can release/reuse buffer with Event packet at this point. -TU_ATTR_WEAK void tud_bt_event_sent_cb(uint16_t sent_bytes); +void tud_bt_event_sent_cb(uint16_t sent_bytes); // Called when ACL data that was sent with tud_bt_acl_data_send() // was delivered to BT stack. // Controller can release/reuse buffer with ACL packet at this point. -TU_ATTR_WEAK void tud_bt_acl_data_sent_cb(uint16_t sent_bytes); +void tud_bt_acl_data_sent_cb(uint16_t sent_bytes); // Bluetooth controller calls this function when it wants to send even packet // as described in Bluetooth core specification Vol 2, Part E, 5.4.4. @@ -96,12 +103,12 @@ bool tud_bt_acl_data_send(void *acl_data, uint16_t data_len); //--------------------------------------------------------------------+ // Internal Class Driver API //--------------------------------------------------------------------+ -void btd_init (void); -void btd_reset (uint8_t rhport); -uint16_t btd_open (uint8_t rhport, tusb_desc_interface_t const * itf_desc, uint16_t max_len); -bool btd_control_request (uint8_t rhport, tusb_control_request_t const * request); -bool btd_control_complete (uint8_t rhport, tusb_control_request_t const * request); -bool btd_xfer_cb (uint8_t rhport, uint8_t edpt_addr, xfer_result_t result, uint32_t xferred_bytes); +void btd_init (void); +bool btd_deinit (void); +void btd_reset (uint8_t rhport); +uint16_t btd_open (uint8_t rhport, tusb_desc_interface_t const * itf_desc, uint16_t max_len); +bool btd_control_xfer_cb (uint8_t rhport, uint8_t stage, tusb_control_request_t const *request); +bool btd_xfer_cb (uint8_t rhport, uint8_t edpt_addr, xfer_result_t result, uint32_t xferred_bytes); #ifdef __cplusplus } diff --git a/cores/nRF5/TinyUSB/Adafruit_TinyUSB_ArduinoCore/tinyusb/src/class/cdc/cdc.h b/cores/nRF5/TinyUSB/Adafruit_TinyUSB_ArduinoCore/tinyusb/src/class/cdc/cdc.h index f0488ac..10ba16a 100644 --- a/cores/nRF5/TinyUSB/Adafruit_TinyUSB_ArduinoCore/tinyusb/src/class/cdc/cdc.h +++ b/cores/nRF5/TinyUSB/Adafruit_TinyUSB_ArduinoCore/tinyusb/src/class/cdc/cdc.h @@ -1,4 +1,4 @@ -/* +/* * The MIT License (MIT) * * Copyright (c) 2019 Ha Thach (tinyusb.org) @@ -41,16 +41,6 @@ /** \defgroup ClassDriver_CDC_Common Common Definitions * @{ */ -// TODO remove -/// CDC Pipe ID, used to indicate which pipe the API is addressing to (Notification, Out, In) -typedef enum -{ - CDC_PIPE_NOTIFICATION , ///< Notification pipe - CDC_PIPE_DATA_IN , ///< Data in pipe - CDC_PIPE_DATA_OUT , ///< Data out pipe - CDC_PIPE_ERROR , ///< Invalid Pipe ID -}cdc_pipeid_t; - //--------------------------------------------------------------------+ // CDC Communication Interface Class //--------------------------------------------------------------------+ @@ -58,31 +48,32 @@ typedef enum /// Communication Interface Subclass Codes typedef enum { - CDC_COMM_SUBCLASS_DIRECT_LINE_CONTROL_MODEL = 0x01 , ///< Direct Line Control Model [USBPSTN1.2] - CDC_COMM_SUBCLASS_ABSTRACT_CONTROL_MODEL , ///< Abstract Control Model [USBPSTN1.2] - CDC_COMM_SUBCLASS_TELEPHONE_CONTROL_MODEL , ///< Telephone Control Model [USBPSTN1.2] - CDC_COMM_SUBCLASS_MULTICHANNEL_CONTROL_MODEL , ///< Multi-Channel Control Model [USBISDN1.2] - CDC_COMM_SUBCLASS_CAPI_CONTROL_MODEL , ///< CAPI Control Model [USBISDN1.2] - CDC_COMM_SUBCLASS_ETHERNET_NETWORKING_CONTROL_MODEL , ///< Ethernet Networking Control Model [USBECM1.2] - CDC_COMM_SUBCLASS_ATM_NETWORKING_CONTROL_MODEL , ///< ATM Networking Control Model [USBATM1.2] - CDC_COMM_SUBCLASS_WIRELESS_HANDSET_CONTROL_MODEL , ///< Wireless Handset Control Model [USBWMC1.1] - CDC_COMM_SUBCLASS_DEVICE_MANAGEMENT , ///< Device Management [USBWMC1.1] - CDC_COMM_SUBCLASS_MOBILE_DIRECT_LINE_MODEL , ///< Mobile Direct Line Model [USBWMC1.1] - CDC_COMM_SUBCLASS_OBEX , ///< OBEX [USBWMC1.1] - CDC_COMM_SUBCLASS_ETHERNET_EMULATION_MODEL ///< Ethernet Emulation Model [USBEEM1.0] + CDC_COMM_SUBCLASS_DIRECT_LINE_CONTROL_MODEL = 0x01 , ///< Direct Line Control Model [USBPSTN1.2] + CDC_COMM_SUBCLASS_ABSTRACT_CONTROL_MODEL = 0x02 , ///< Abstract Control Model [USBPSTN1.2] + CDC_COMM_SUBCLASS_TELEPHONE_CONTROL_MODEL = 0x03 , ///< Telephone Control Model [USBPSTN1.2] + CDC_COMM_SUBCLASS_MULTICHANNEL_CONTROL_MODEL = 0x04 , ///< Multi-Channel Control Model [USBISDN1.2] + CDC_COMM_SUBCLASS_CAPI_CONTROL_MODEL = 0x05 , ///< CAPI Control Model [USBISDN1.2] + CDC_COMM_SUBCLASS_ETHERNET_CONTROL_MODEL = 0x06 , ///< Ethernet Networking Control Model [USBECM1.2] + CDC_COMM_SUBCLASS_ATM_NETWORKING_CONTROL_MODEL = 0x07 , ///< ATM Networking Control Model [USBATM1.2] + CDC_COMM_SUBCLASS_WIRELESS_HANDSET_CONTROL_MODEL = 0x08 , ///< Wireless Handset Control Model [USBWMC1.1] + CDC_COMM_SUBCLASS_DEVICE_MANAGEMENT = 0x09 , ///< Device Management [USBWMC1.1] + CDC_COMM_SUBCLASS_MOBILE_DIRECT_LINE_MODEL = 0x0A , ///< Mobile Direct Line Model [USBWMC1.1] + CDC_COMM_SUBCLASS_OBEX = 0x0B , ///< OBEX [USBWMC1.1] + CDC_COMM_SUBCLASS_ETHERNET_EMULATION_MODEL = 0x0C , ///< Ethernet Emulation Model [USBEEM1.0] + CDC_COMM_SUBCLASS_NETWORK_CONTROL_MODEL = 0x0D ///< Network Control Model [USBNCM1.0] } cdc_comm_sublcass_type_t; /// Communication Interface Protocol Codes typedef enum { - CDC_COMM_PROTOCOL_NONE = 0x00 , ///< No specific protocol - CDC_COMM_PROTOCOL_ATCOMMAND , ///< AT Commands: V.250 etc - CDC_COMM_PROTOCOL_ATCOMMAND_PCCA_101 , ///< AT Commands defined by PCCA-101 - CDC_COMM_PROTOCOL_ATCOMMAND_PCCA_101_AND_ANNEXO , ///< AT Commands defined by PCCA-101 & Annex O - CDC_COMM_PROTOCOL_ATCOMMAND_GSM_707 , ///< AT Commands defined by GSM 07.07 - CDC_COMM_PROTOCOL_ATCOMMAND_3GPP_27007 , ///< AT Commands defined by 3GPP 27.007 - CDC_COMM_PROTOCOL_ATCOMMAND_CDMA , ///< AT Commands defined by TIA for CDMA - CDC_COMM_PROTOCOL_ETHERNET_EMULATION_MODEL ///< Ethernet Emulation Model + CDC_COMM_PROTOCOL_NONE = 0x00 , ///< No specific protocol + CDC_COMM_PROTOCOL_ATCOMMAND = 0x01 , ///< AT Commands: V.250 etc + CDC_COMM_PROTOCOL_ATCOMMAND_PCCA_101 = 0x02 , ///< AT Commands defined by PCCA-101 + CDC_COMM_PROTOCOL_ATCOMMAND_PCCA_101_AND_ANNEXO = 0x03 , ///< AT Commands defined by PCCA-101 & Annex O + CDC_COMM_PROTOCOL_ATCOMMAND_GSM_707 = 0x04 , ///< AT Commands defined by GSM 07.07 + CDC_COMM_PROTOCOL_ATCOMMAND_3GPP_27007 = 0x05 , ///< AT Commands defined by 3GPP 27.007 + CDC_COMM_PROTOCOL_ATCOMMAND_CDMA = 0x06 , ///< AT Commands defined by TIA for CDMA + CDC_COMM_PROTOCOL_ETHERNET_EMULATION_MODEL = 0x07 ///< Ethernet Emulation Model } cdc_comm_protocol_type_t; //------------- SubType Descriptor in COMM Functional Descriptor -------------// @@ -114,7 +105,8 @@ typedef enum CDC_FUNC_DESC_COMMAND_SET = 0x16 , ///< Command Set Functional Descriptor CDC_FUNC_DESC_COMMAND_SET_DETAIL = 0x17 , ///< Command Set Detail Functional Descriptor CDC_FUNC_DESC_TELEPHONE_CONTROL_MODEL = 0x18 , ///< Telephone Control Model Functional Descriptor - CDC_FUNC_DESC_OBEX_SERVICE_IDENTIFIER = 0x19 ///< OBEX Service Identifier Functional Descriptor + CDC_FUNC_DESC_OBEX_SERVICE_IDENTIFIER = 0x19 , ///< OBEX Service Identifier Functional Descriptor + CDC_FUNC_DESC_NCM = 0x1A , ///< NCM Functional Descriptor }cdc_func_desc_type_t; //--------------------------------------------------------------------+ @@ -122,7 +114,8 @@ typedef enum //--------------------------------------------------------------------+ // SUBCLASS code of Data Interface is not used and should/must be zero -/// Data Interface Protocol Codes + +// Data Interface Protocol Codes typedef enum{ CDC_DATA_PROTOCOL_ISDN_BRI = 0x30, ///< Physical interface protocol for ISDN BRI CDC_DATA_PROTOCOL_HDLC = 0x31, ///< HDLC @@ -143,11 +136,9 @@ typedef enum{ //--------------------------------------------------------------------+ /// Communication Interface Management Element Request Codes -typedef enum -{ +typedef enum { CDC_REQUEST_SEND_ENCAPSULATED_COMMAND = 0x00, ///< is used to issue a command in the format of the supported control protocol of the Communications Class interface CDC_REQUEST_GET_ENCAPSULATED_RESPONSE = 0x01, ///< is used to request a response in the format of the supported control protocol of the Communications Class interface. - CDC_REQUEST_SET_COMM_FEATURE = 0x02, CDC_REQUEST_GET_COMM_FEATURE = 0x03, CDC_REQUEST_CLEAR_COMM_FEATURE = 0x04, @@ -188,36 +179,73 @@ typedef enum CDC_REQUEST_GET_ATM_VC_STATISTICS = 0x53, CDC_REQUEST_MDLM_SEMANTIC_MODEL = 0x60, -}cdc_management_request_t; +} cdc_management_request_t; + +typedef enum { + CDC_CONTROL_LINE_STATE_DTR = 0x01, + CDC_CONTROL_LINE_STATE_RTS = 0x02, +} cdc_control_line_state_t; + +typedef enum { + CDC_LINE_CODING_STOP_BITS_1 = 0, // 1 bit + CDC_LINE_CODING_STOP_BITS_1_5 = 1, // 1.5 bits + CDC_LINE_CODING_STOP_BITS_2 = 2, // 2 bits +} cdc_line_coding_stopbits_t; + +#define CDC_LINE_CODING_STOP_BITS_TEXT(STOP_BITS) ( \ + STOP_BITS == CDC_LINE_CODING_STOP_BITS_1 ? "1" : \ + STOP_BITS == CDC_LINE_CODING_STOP_BITS_1_5 ? "1.5" : \ + STOP_BITS == CDC_LINE_CODING_STOP_BITS_2 ? "2" : "?" ) + +// TODO Backward compatible for typos. Maybe removed in the future release +#define CDC_LINE_CONDING_STOP_BITS_1 CDC_LINE_CODING_STOP_BITS_1 +#define CDC_LINE_CONDING_STOP_BITS_1_5 CDC_LINE_CODING_STOP_BITS_1_5 +#define CDC_LINE_CONDING_STOP_BITS_2 CDC_LINE_CODING_STOP_BITS_2 + +typedef enum { + CDC_LINE_CODING_PARITY_NONE = 0, + CDC_LINE_CODING_PARITY_ODD = 1, + CDC_LINE_CODING_PARITY_EVEN = 2, + CDC_LINE_CODING_PARITY_MARK = 3, + CDC_LINE_CODING_PARITY_SPACE = 4, +} cdc_line_coding_parity_t; + +#define CDC_LINE_CODING_PARITY_CHAR(PARITY) ( \ + PARITY == CDC_LINE_CODING_PARITY_NONE ? 'N' : \ + PARITY == CDC_LINE_CODING_PARITY_ODD ? 'O' : \ + PARITY == CDC_LINE_CODING_PARITY_EVEN ? 'E' : \ + PARITY == CDC_LINE_CODING_PARITY_MARK ? 'M' : \ + PARITY == CDC_LINE_CODING_PARITY_SPACE ? 'S' : '?' ) //--------------------------------------------------------------------+ -// Management Elemenent Notification (Notification Endpoint) +// Management Element Notification (Notification Endpoint) //--------------------------------------------------------------------+ -/// Communication Interface Management Element Notification Codes -typedef enum -{ - NETWORK_CONNECTION = 0x00, ///< This notification allows the device to notify the host about network connection status. - RESPONSE_AVAILABLE = 0x01, ///< This notification allows the device to notify the hostthat a response is available. This response can be retrieved with a subsequent \ref CDC_REQUEST_GET_ENCAPSULATED_RESPONSE request. - - AUX_JACK_HOOK_STATE = 0x08, - RING_DETECT = 0x09, - - SERIAL_STATE = 0x20, - - CALL_STATE_CHANGE = 0x28, - LINE_STATE_CHANGE = 0x29, - CONNECTION_SPEED_CHANGE = 0x2A, ///< This notification allows the device to inform the host-networking driver that a change in either the upstream or the downstream bit rate of the connection has occurred - MDLM_SEMANTIC_MODEL_NOTIFICATION = 0x40, -}cdc_notification_request_t; +#define CDC_REQ_TYPE_NOTIF 0xA1 ///< Direction IN; Type Class; Recipient Interface + +/// 6.3 Notification Codes +typedef enum { + CDC_NOTIF_NETWORK_CONNECTION = 0x00, // notify the host about network connection status. + CDC_NOTIF_RESPONSE_AVAILABLE = 0x01, // notify the host that a response is available. + CDC_NOTIF_AUX_JACK_HOOK_STATE = 0x08, + CDC_NOTIF_RING_DETECT = 0x09, + CDC_NOTIF_SERIAL_STATE = 0x20, + CDC_NOTIF_CALL_STATE_CHANGE = 0x28, + CDC_NOTIF_LINE_STATE_CHANGE = 0x29, + CDC_NOTIF_CONNECTION_SPEED_CHANGE = 0x2A, // notify the host-networking driver that a change in either the upstream or the downstream bit rate of the connection has occurred + CDC_NOTIF_MDLM_SEMANTIC_MODEL_NOTIFICATION = 0x40, +} cdc_notify_request_t; //--------------------------------------------------------------------+ // Class Specific Functional Descriptor (Communication Interface) //--------------------------------------------------------------------+ +// Start of all packed definitions for compiler without per-type packed +TU_ATTR_PACKED_BEGIN +TU_ATTR_BIT_FIELD_ORDER_BEGIN + /// Header Functional Descriptor (Communication Interface) -typedef struct TU_ATTR_PACKED -{ +typedef struct TU_ATTR_PACKED { uint8_t bLength ; ///< Size of this descriptor in bytes. uint8_t bDescriptorType ; ///< Descriptor Type, must be Class-Specific uint8_t bDescriptorSubType ; ///< Descriptor SubType one of above CDC_FUNC_DESC_ @@ -225,8 +253,7 @@ typedef struct TU_ATTR_PACKED }cdc_desc_func_header_t; /// Union Functional Descriptor (Communication Interface) -typedef struct TU_ATTR_PACKED -{ +typedef struct TU_ATTR_PACKED { uint8_t bLength ; ///< Size of this descriptor in bytes. uint8_t bDescriptorType ; ///< Descriptor Type, must be Class-Specific uint8_t bDescriptorSubType ; ///< Descriptor SubType one of above CDC_FUCN_DESC_ @@ -235,7 +262,7 @@ typedef struct TU_ATTR_PACKED }cdc_desc_func_union_t; #define cdc_desc_func_union_n_t(no_slave)\ - struct TU_ATTR_PACKED { \ + struct TU_ATTR_PACKED { \ uint8_t bLength ;\ uint8_t bDescriptorType ;\ uint8_t bDescriptorSubType ;\ @@ -244,17 +271,16 @@ typedef struct TU_ATTR_PACKED } /// Country Selection Functional Descriptor (Communication Interface) -typedef struct TU_ATTR_PACKED -{ +typedef struct TU_ATTR_PACKED { uint8_t bLength ; ///< Size of this descriptor in bytes. uint8_t bDescriptorType ; ///< Descriptor Type, must be Class-Specific uint8_t bDescriptorSubType ; ///< Descriptor SubType one of above CDC_FUCN_DESC_ uint8_t iCountryCodeRelDate ; ///< Index of a string giving the release date for the implemented ISO 3166 Country Codes. uint16_t wCountryCode ; ///< Country code in the format as defined in [ISO3166], release date as specified inoffset 3 for the first supported country. -}cdc_desc_func_country_selection_t; +} cdc_desc_func_country_selection_t; #define cdc_desc_func_country_selection_n_t(no_country) \ - struct TU_ATTR_PACKED {\ + struct TU_ATTR_PACKED { \ uint8_t bLength ;\ uint8_t bDescriptorType ;\ uint8_t bDescriptorSubType ;\ @@ -268,8 +294,7 @@ typedef struct TU_ATTR_PACKED /// \brief Call Management Functional Descriptor /// \details This functional descriptor describes the processing of calls for the Communications Class interface. -typedef struct TU_ATTR_PACKED -{ +typedef struct TU_ATTR_PACKED { uint8_t bLength ; ///< Size of this descriptor in bytes. uint8_t bDescriptorType ; ///< Descriptor Type, must be Class-Specific uint8_t bDescriptorSubType ; ///< Descriptor SubType one of above CDC_FUCN_DESC_ @@ -277,28 +302,25 @@ typedef struct TU_ATTR_PACKED struct { uint8_t handle_call : 1; ///< 0 - Device sends/receives call management information only over the Communications Class interface. 1 - Device can send/receive call management information over a Data Class interface. uint8_t send_recv_call : 1; ///< 0 - Device does not handle call management itself. 1 - Device handles call management itself. - uint8_t : 0; + uint8_t TU_RESERVED : 6; } bmCapabilities; uint8_t bDataInterface; }cdc_desc_func_call_management_t; - -typedef struct TU_ATTR_PACKED -{ +typedef struct TU_ATTR_PACKED { uint8_t support_comm_request : 1; ///< Device supports the request combination of Set_Comm_Feature, Clear_Comm_Feature, and Get_Comm_Feature. uint8_t support_line_request : 1; ///< Device supports the request combination of Set_Line_Coding, Set_Control_Line_State, Get_Line_Coding, and the notification Serial_State. uint8_t support_send_break : 1; ///< Device supports the request Send_Break uint8_t support_notification_network_connection : 1; ///< Device supports the notification Network_Connection. - uint8_t : 0; + uint8_t TU_RESERVED : 4; }cdc_acm_capability_t; TU_VERIFY_STATIC(sizeof(cdc_acm_capability_t) == 1, "mostly problem with compiler"); -/// \brief Abstract Control Management Functional Descriptor -/// \details This functional descriptor describes the commands supported by by the Communications Class interface with SubClass code of \ref CDC_COMM_SUBCLASS_ABSTRACT_CONTROL_MODEL -typedef struct TU_ATTR_PACKED -{ +/// Abstract Control Management Functional Descriptor +/// This functional descriptor describes the commands supported by by the Communications Class interface with SubClass code of \ref CDC_COMM_SUBCLASS_ABSTRACT_CONTROL_MODEL +typedef struct TU_ATTR_PACKED { uint8_t bLength ; ///< Size of this descriptor in bytes. uint8_t bDescriptorType ; ///< Descriptor Type, must be Class-Specific uint8_t bDescriptorSubType ; ///< Descriptor SubType one of above CDC_FUCN_DESC_ @@ -307,8 +329,7 @@ typedef struct TU_ATTR_PACKED /// \brief Direct Line Management Functional Descriptor /// \details This functional descriptor describes the commands supported by the Communications Class interface with SubClass code of \ref CDC_FUNC_DESC_DIRECT_LINE_MANAGEMENT -typedef struct TU_ATTR_PACKED -{ +typedef struct TU_ATTR_PACKED { uint8_t bLength ; ///< Size of this descriptor in bytes. uint8_t bDescriptorType ; ///< Descriptor Type, must be Class-Specific uint8_t bDescriptorSubType ; ///< Descriptor SubType one of above CDC_FUCN_DESC_ @@ -316,7 +337,7 @@ typedef struct TU_ATTR_PACKED uint8_t require_pulse_setup : 1; ///< Device requires extra Pulse_Setup request during pulse dialing sequence to disengage holding circuit. uint8_t support_aux_request : 1; ///< Device supports the request combination of Set_Aux_Line_State, Ring_Aux_Jack, and notification Aux_Jack_Hook_State. uint8_t support_pulse_request : 1; ///< Device supports the request combination of Pulse_Setup, Send_Pulse, and Set_Pulse_Time. - uint8_t : 0; + uint8_t TU_RESERVED : 5; } bmCapabilities; }cdc_desc_func_direct_line_management_t; @@ -344,7 +365,7 @@ typedef struct TU_ATTR_PACKED uint8_t simple_mode : 1; uint8_t standalone_mode : 1; uint8_t computer_centric_mode : 1; - uint8_t : 0; + uint8_t TU_RESERVED : 5; } bmCapabilities; }cdc_desc_func_telephone_operational_modes_t; @@ -363,20 +384,21 @@ typedef struct TU_ATTR_PACKED uint32_t incoming_distinctive : 1; ///< 0 : Reports only incoming ringing. 1 : Reports incoming distinctive ringing patterns. uint32_t dual_tone_multi_freq : 1; ///< 0 : Cannot report dual tone multi-frequency (DTMF) digits input remotely over the telephone line. 1 : Can report DTMF digits input remotely over the telephone line. uint32_t line_state_change : 1; ///< 0 : Does not support line state change notification. 1 : Does support line state change notification - uint32_t : 0; + uint32_t TU_RESERVED0 : 2; + uint32_t TU_RESERVED1 : 16; + uint32_t TU_RESERVED2 : 8; } bmCapabilities; }cdc_desc_func_telephone_call_state_reporting_capabilities_t; -static inline uint8_t cdc_functional_desc_typeof(uint8_t const * p_desc) -{ +// TODO remove +TU_ATTR_ALWAYS_INLINE static inline uint8_t cdc_functional_desc_typeof(uint8_t const * p_desc) { return p_desc[2]; } //--------------------------------------------------------------------+ // Requests //--------------------------------------------------------------------+ -typedef struct TU_ATTR_PACKED -{ +typedef struct TU_ATTR_PACKED { uint32_t bit_rate; uint8_t stop_bits; ///< 0: 1 stop bit - 1: 1.5 stop bits - 2: 2 stop bits uint8_t parity; ///< 0: None - 1: Odd - 2: Even - 3: Mark - 4: Space @@ -385,16 +407,61 @@ typedef struct TU_ATTR_PACKED TU_VERIFY_STATIC(sizeof(cdc_line_coding_t) == 7, "size is not correct"); -typedef struct TU_ATTR_PACKED -{ - uint16_t dte_is_present : 1; ///< Indicates to DCE if DTE is presentor not. This signal corresponds to V.24 signal 108/2 and RS-232 signal DTR. - uint16_t half_duplex_carrier_control : 1; - uint16_t : 14; +typedef union TU_ATTR_PACKED { + struct TU_ATTR_PACKED { + uint8_t dtr : 1; + uint8_t rts : 1; + uint8_t : 6; + }; + uint8_t value; } cdc_line_control_state_t; -TU_VERIFY_STATIC(sizeof(cdc_line_control_state_t) == 2, "size is not correct"); +TU_VERIFY_STATIC(sizeof(cdc_line_control_state_t) == 1, "size is not correct"); -/** @} */ +//--------------------------------------------------------------------+ +// Notifications +//--------------------------------------------------------------------+ +// PSTN 1.2 section 6.5.4 table 31 +typedef union TU_ATTR_PACKED { + struct TU_ATTR_PACKED { + uint16_t bRxCarrier : 1; // DCD + uint16_t bTxCarrier : 1; // DSR + uint16_t bBreak : 1; // Break Detected + uint16_t bRingSignal : 1; + uint16_t bFraming : 1; + uint16_t bParity : 1; + uint16_t bOverRun : 1; + uint16_t : 9; + }; + struct TU_ATTR_PACKED { + uint16_t dcd : 1; + uint16_t dsr : 1; + uint16_t brk : 1; + uint16_t :13; + }; + uint16_t value; +} cdc_notify_uart_state_t; + +TU_VERIFY_STATIC(sizeof(cdc_notify_uart_state_t) == 2, "size is not correct"); + +// CDC 1.2 section 6.3.3 table 21 +typedef struct TU_ATTR_PACKED { + uint32_t upstream_bitrate; + uint32_t downstream_bitrate; +} cdc_notify_conn_speed_change_t; + +typedef struct TU_ATTR_PACKED { + tusb_control_request_t request; + union { + cdc_notify_uart_state_t serial_state; + cdc_notify_conn_speed_change_t conn_speed_change; + }; +} cdc_notify_msg_t; + +TU_VERIFY_STATIC(sizeof(cdc_notify_msg_t) == 16, "size is not correct"); + +TU_ATTR_PACKED_END // End of all packed definitions +TU_ATTR_BIT_FIELD_ORDER_END #ifdef __cplusplus } diff --git a/cores/nRF5/TinyUSB/Adafruit_TinyUSB_ArduinoCore/tinyusb/src/class/cdc/cdc_device.c b/cores/nRF5/TinyUSB/Adafruit_TinyUSB_ArduinoCore/tinyusb/src/class/cdc/cdc_device.c index e39adc5..f1c4a3b 100644 --- a/cores/nRF5/TinyUSB/Adafruit_TinyUSB_ArduinoCore/tinyusb/src/class/cdc/cdc_device.c +++ b/cores/nRF5/TinyUSB/Adafruit_TinyUSB_ArduinoCore/tinyusb/src/class/cdc/cdc_device.c @@ -1,4 +1,4 @@ -/* +/* * The MIT License (MIT) * * Copyright (c) 2019 Ha Thach (tinyusb.org) @@ -26,27 +26,37 @@ #include "tusb_option.h" -#if (TUSB_OPT_DEVICE_ENABLED && CFG_TUD_CDC) +#if (CFG_TUD_ENABLED && CFG_TUD_CDC) -#include "cdc_device.h" +#include "device/usbd.h" #include "device/usbd_pvt.h" +#include "cdc_device.h" + +// Level where CFG_TUSB_DEBUG must be at least for this driver is logged +#ifndef CFG_TUD_CDC_LOG_LEVEL + #define CFG_TUD_CDC_LOG_LEVEL CFG_TUD_LOG_LEVEL +#endif + +#define TU_LOG_DRV(...) TU_LOG(CFG_TUD_CDC_LOG_LEVEL, __VA_ARGS__) + //--------------------------------------------------------------------+ // MACRO CONSTANT TYPEDEF //--------------------------------------------------------------------+ -typedef struct -{ +#define BULK_PACKET_SIZE (TUD_OPT_HIGH_SPEED ? 512 : 64) + +typedef struct { + uint8_t rhport; uint8_t itf_num; - uint8_t ep_notif; uint8_t ep_in; uint8_t ep_out; - // Bit 0: DTR (Data Terminal Ready), Bit 1: RTS (Request to Send) - uint8_t line_state; + uint8_t ep_notify; + uint8_t line_state; // Bit 0: DTR, Bit 1: RTS /*------------- From this point, data is not cleared by bus reset -------------*/ - char wanted_char; - cdc_line_coding_t line_coding; + char wanted_char; + TU_ATTR_ALIGNED(4) cdc_line_coding_t line_coding; // FIFO tu_fifo_t rx_ff; @@ -55,410 +65,511 @@ typedef struct uint8_t rx_ff_buf[CFG_TUD_CDC_RX_BUFSIZE]; uint8_t tx_ff_buf[CFG_TUD_CDC_TX_BUFSIZE]; -#if CFG_FIFO_MUTEX - osal_mutex_def_t rx_ff_mutex; - osal_mutex_def_t tx_ff_mutex; -#endif + OSAL_MUTEX_DEF(rx_ff_mutex); + OSAL_MUTEX_DEF(tx_ff_mutex); +} cdcd_interface_t; - // Endpoint Transfer buffer - CFG_TUSB_MEM_ALIGN uint8_t epout_buf[CFG_TUD_CDC_EP_BUFSIZE]; - CFG_TUSB_MEM_ALIGN uint8_t epin_buf[CFG_TUD_CDC_EP_BUFSIZE]; +#define ITF_MEM_RESET_SIZE offsetof(cdcd_interface_t, wanted_char) -}cdcd_interface_t; +typedef struct { + TUD_EPBUF_DEF(epout, CFG_TUD_CDC_EP_BUFSIZE); + TUD_EPBUF_DEF(epin, CFG_TUD_CDC_EP_BUFSIZE); -#define ITF_MEM_RESET_SIZE offsetof(cdcd_interface_t, wanted_char) + #if CFG_TUD_CDC_NOTIFY + TUD_EPBUF_TYPE_DEF(cdc_notify_msg_t, epnotify); + #endif +} cdcd_epbuf_t; //--------------------------------------------------------------------+ // INTERNAL OBJECT & FUNCTION DECLARATION //--------------------------------------------------------------------+ -CFG_TUSB_MEM_SECTION static cdcd_interface_t _cdcd_itf[CFG_TUD_CDC]; +static cdcd_interface_t _cdcd_itf[CFG_TUD_CDC]; +CFG_TUD_MEM_SECTION static cdcd_epbuf_t _cdcd_epbuf[CFG_TUD_CDC]; + +static tud_cdc_configure_t _cdcd_cfg = TUD_CDC_CONFIGURE_DEFAULT(); + +static bool _prep_out_transaction(uint8_t itf) { + const uint8_t rhport = 0; + cdcd_interface_t* p_cdc = &_cdcd_itf[itf]; + cdcd_epbuf_t* p_epbuf = &_cdcd_epbuf[itf]; + + // Skip if usb is not ready yet + TU_VERIFY(tud_ready() && p_cdc->ep_out); -static void _prep_out_transaction (cdcd_interface_t* p_cdc) -{ - uint8_t const rhport = TUD_OPT_RHPORT; uint16_t available = tu_fifo_remaining(&p_cdc->rx_ff); // Prepare for incoming data but only allow what we can store in the ring buffer. // TODO Actually we can still carry out the transfer, keeping count of received bytes // and slowly move it to the FIFO when read(). // This pre-check reduces endpoint claiming - TU_VERIFY(available >= sizeof(p_cdc->epout_buf), ); + TU_VERIFY(available >= CFG_TUD_CDC_EP_BUFSIZE); // claim endpoint - TU_VERIFY(usbd_edpt_claim(rhport, p_cdc->ep_out), ); + TU_VERIFY(usbd_edpt_claim(p_cdc->rhport, p_cdc->ep_out)); // fifo can be changed before endpoint is claimed available = tu_fifo_remaining(&p_cdc->rx_ff); - if ( available >= sizeof(p_cdc->epout_buf) ) { - usbd_edpt_xfer(rhport, p_cdc->ep_out, p_cdc->epout_buf, sizeof(p_cdc->epout_buf)); - }else - { + if (available >= CFG_TUD_CDC_EP_BUFSIZE) { + return usbd_edpt_xfer(rhport, p_cdc->ep_out, p_epbuf->epout, CFG_TUD_CDC_EP_BUFSIZE); + } else { // Release endpoint since we don't make any transfer - usbd_edpt_release(rhport, p_cdc->ep_out); + usbd_edpt_release(p_cdc->rhport, p_cdc->ep_out); + return false; } } +//--------------------------------------------------------------------+ +// Weak stubs: invoked if no strong implementation is available +//--------------------------------------------------------------------+ +TU_ATTR_WEAK void tud_cdc_rx_cb(uint8_t itf) { + (void) itf; +} + +TU_ATTR_WEAK void tud_cdc_rx_wanted_cb(uint8_t itf, char wanted_char) { + (void) itf; + (void) wanted_char; +} + +TU_ATTR_WEAK void tud_cdc_tx_complete_cb(uint8_t itf) { + (void) itf; +} + +TU_ATTR_WEAK void tud_cdc_notify_complete_cb(uint8_t itf) { + (void) itf; +} + +TU_ATTR_WEAK void tud_cdc_line_state_cb(uint8_t itf, bool dtr, bool rts) { + (void) itf; + (void) dtr; + (void) rts; +} + +TU_ATTR_WEAK void tud_cdc_line_coding_cb(uint8_t itf, cdc_line_coding_t const* p_line_coding) { + (void) itf; + (void) p_line_coding; +} + +TU_ATTR_WEAK void tud_cdc_send_break_cb(uint8_t itf, uint16_t duration_ms) { + (void) itf; + (void) duration_ms; +} + //--------------------------------------------------------------------+ // APPLICATION API //--------------------------------------------------------------------+ -bool tud_cdc_n_connected(uint8_t itf) -{ +bool tud_cdc_configure(const tud_cdc_configure_t* driver_cfg) { + TU_VERIFY(driver_cfg); + _cdcd_cfg = *driver_cfg; + return true; +} + +bool tud_cdc_n_ready(uint8_t itf) { + return tud_ready() && _cdcd_itf[itf].ep_in != 0 && _cdcd_itf[itf].ep_out != 0; +} + +bool tud_cdc_n_connected(uint8_t itf) { // DTR (bit 0) active is considered as connected return tud_ready() && tu_bit_test(_cdcd_itf[itf].line_state, 0); } -uint8_t tud_cdc_n_get_line_state (uint8_t itf) -{ +uint8_t tud_cdc_n_get_line_state(uint8_t itf) { return _cdcd_itf[itf].line_state; } -void tud_cdc_n_get_line_coding (uint8_t itf, cdc_line_coding_t* coding) -{ +void tud_cdc_n_get_line_coding(uint8_t itf, cdc_line_coding_t* coding) { (*coding) = _cdcd_itf[itf].line_coding; } -void tud_cdc_n_set_wanted_char (uint8_t itf, char wanted) -{ - _cdcd_itf[itf].wanted_char = wanted; +#if CFG_TUD_CDC_NOTIFY +bool tud_cdc_n_notify_uart_state (uint8_t itf, const cdc_notify_uart_state_t *state) { + cdcd_interface_t* p_cdc = &_cdcd_itf[itf]; + cdcd_epbuf_t* p_epbuf = &_cdcd_epbuf[itf]; + TU_VERIFY(tud_ready() && p_cdc->ep_notify != 0); + TU_VERIFY(usbd_edpt_claim(p_cdc->rhport, p_cdc->ep_notify)); + + cdc_notify_msg_t* notify_msg = &p_epbuf->epnotify; + notify_msg->request.bmRequestType = CDC_REQ_TYPE_NOTIF; + notify_msg->request.bRequest = CDC_NOTIF_SERIAL_STATE; + notify_msg->request.wValue = 0; + notify_msg->request.wIndex = p_cdc->itf_num; + notify_msg->request.wLength = sizeof(cdc_notify_uart_state_t); + notify_msg->serial_state = *state; + + return usbd_edpt_xfer(p_cdc->rhport, p_cdc->ep_notify, (uint8_t *)notify_msg, 8 + sizeof(cdc_notify_uart_state_t)); +} + +bool tud_cdc_n_notify_conn_speed_change(uint8_t itf, const cdc_notify_conn_speed_change_t* conn_speed_change) { + cdcd_interface_t* p_cdc = &_cdcd_itf[itf]; + cdcd_epbuf_t* p_epbuf = &_cdcd_epbuf[itf]; + TU_VERIFY(tud_ready() && p_cdc->ep_notify != 0); + TU_VERIFY(usbd_edpt_claim(p_cdc->rhport, p_cdc->ep_notify)); + + cdc_notify_msg_t* notify_msg = &p_epbuf->epnotify; + notify_msg->request.bmRequestType = CDC_REQ_TYPE_NOTIF; + notify_msg->request.bRequest = CDC_NOTIF_CONNECTION_SPEED_CHANGE; + notify_msg->request.wValue = 0; + notify_msg->request.wIndex = p_cdc->itf_num; + notify_msg->request.wLength = sizeof(cdc_notify_conn_speed_change_t); + notify_msg->conn_speed_change = *conn_speed_change; + + return usbd_edpt_xfer(p_cdc->rhport, p_cdc->ep_notify, (uint8_t *)notify_msg, 8 + sizeof(cdc_notify_conn_speed_change_t)); } +#endif +void tud_cdc_n_set_wanted_char(uint8_t itf, char wanted) { + _cdcd_itf[itf].wanted_char = wanted; +} //--------------------------------------------------------------------+ // READ API //--------------------------------------------------------------------+ -uint32_t tud_cdc_n_available(uint8_t itf) -{ +uint32_t tud_cdc_n_available(uint8_t itf) { return tu_fifo_count(&_cdcd_itf[itf].rx_ff); } -uint32_t tud_cdc_n_read(uint8_t itf, void* buffer, uint32_t bufsize) -{ +uint32_t tud_cdc_n_read(uint8_t itf, void* buffer, uint32_t bufsize) { cdcd_interface_t* p_cdc = &_cdcd_itf[itf]; - uint32_t num_read = tu_fifo_read_n(&p_cdc->rx_ff, buffer, bufsize); - _prep_out_transaction(p_cdc); + uint32_t num_read = tu_fifo_read_n(&p_cdc->rx_ff, buffer, (uint16_t) TU_MIN(bufsize, UINT16_MAX)); + _prep_out_transaction(itf); return num_read; } -bool tud_cdc_n_peek(uint8_t itf, int pos, uint8_t* chr) -{ - return tu_fifo_peek_at(&_cdcd_itf[itf].rx_ff, pos, chr); +bool tud_cdc_n_peek(uint8_t itf, uint8_t* chr) { + return tu_fifo_peek(&_cdcd_itf[itf].rx_ff, chr); } -void tud_cdc_n_read_flush (uint8_t itf) -{ +void tud_cdc_n_read_flush(uint8_t itf) { cdcd_interface_t* p_cdc = &_cdcd_itf[itf]; tu_fifo_clear(&p_cdc->rx_ff); - _prep_out_transaction(p_cdc); + _prep_out_transaction(itf); } //--------------------------------------------------------------------+ // WRITE API //--------------------------------------------------------------------+ -uint32_t tud_cdc_n_write(uint8_t itf, void const* buffer, uint32_t bufsize) -{ +uint32_t tud_cdc_n_write(uint8_t itf, const void* buffer, uint32_t bufsize) { cdcd_interface_t* p_cdc = &_cdcd_itf[itf]; - uint16_t ret = tu_fifo_write_n(&p_cdc->tx_ff, buffer, bufsize); - -#if 0 // TODO issue with circuitpython's REPL - // flush if queue more than endpoint size - if ( tu_fifo_count(&p_cdc->tx_ff) >= CFG_TUD_CDC_EP_BUFSIZE ) - { + uint16_t wr_count = tu_fifo_write_n(&p_cdc->tx_ff, buffer, (uint16_t) TU_MIN(bufsize, UINT16_MAX)); + + // flush if queue more than packet size + if (tu_fifo_count(&p_cdc->tx_ff) >= BULK_PACKET_SIZE + #if CFG_TUD_CDC_TX_BUFSIZE < BULK_PACKET_SIZE + || tu_fifo_full(&p_cdc->tx_ff) // check full if fifo size is less than packet size + #endif + ) { tud_cdc_n_write_flush(itf); } -#endif - return ret; + return wr_count; } -uint32_t tud_cdc_n_write_flush (uint8_t itf) -{ +uint32_t tud_cdc_n_write_flush(uint8_t itf) { cdcd_interface_t* p_cdc = &_cdcd_itf[itf]; + cdcd_epbuf_t* p_epbuf = &_cdcd_epbuf[itf]; + TU_VERIFY(tud_ready(), 0); // Skip if usb is not ready yet // No data to send - if ( !tu_fifo_count(&p_cdc->tx_ff) ) return 0; - - uint8_t const rhport = TUD_OPT_RHPORT; + if (!tu_fifo_count(&p_cdc->tx_ff)) { + return 0; + } - // Claim the endpoint - TU_VERIFY( usbd_edpt_claim(rhport, p_cdc->ep_in), 0 ); + TU_VERIFY(usbd_edpt_claim(p_cdc->rhport, p_cdc->ep_in), 0); // Claim the endpoint // Pull data from FIFO - uint16_t const count = tu_fifo_read_n(&p_cdc->tx_ff, p_cdc->epin_buf, sizeof(p_cdc->epin_buf)); + const uint16_t count = tu_fifo_read_n(&p_cdc->tx_ff, p_epbuf->epin, CFG_TUD_CDC_EP_BUFSIZE); - if ( count && tud_cdc_n_connected(itf) ) - { - TU_ASSERT( usbd_edpt_xfer(rhport, p_cdc->ep_in, p_cdc->epin_buf, count), 0 ); + if (count) { + TU_ASSERT(usbd_edpt_xfer(p_cdc->rhport, p_cdc->ep_in, p_epbuf->epin, count), 0); return count; - }else - { + } else { // Release endpoint since we don't make any transfer // Note: data is dropped if terminal is not connected - usbd_edpt_release(rhport, p_cdc->ep_in); + usbd_edpt_release(p_cdc->rhport, p_cdc->ep_in); return 0; } } -uint32_t tud_cdc_n_write_available (uint8_t itf) -{ +uint32_t tud_cdc_n_write_available(uint8_t itf) { return tu_fifo_remaining(&_cdcd_itf[itf].tx_ff); } +bool tud_cdc_n_write_clear(uint8_t itf) { + return tu_fifo_clear(&_cdcd_itf[itf].tx_ff); +} //--------------------------------------------------------------------+ // USBD Driver API //--------------------------------------------------------------------+ -void cdcd_init(void) -{ +void cdcd_init(void) { tu_memclr(_cdcd_itf, sizeof(_cdcd_itf)); - - for(uint8_t i=0; iwanted_char = -1; + p_cdc->wanted_char = (char) -1; // default line coding is : stop bit = 1, parity = none, data bits = 8 - p_cdc->line_coding.bit_rate = 115200; + p_cdc->line_coding.bit_rate = 115200; p_cdc->line_coding.stop_bits = 0; - p_cdc->line_coding.parity = 0; + p_cdc->line_coding.parity = 0; p_cdc->line_coding.data_bits = 8; - // config fifo + // Config RX fifo tu_fifo_config(&p_cdc->rx_ff, p_cdc->rx_ff_buf, TU_ARRAY_SIZE(p_cdc->rx_ff_buf), 1, false); - tu_fifo_config(&p_cdc->tx_ff, p_cdc->tx_ff_buf, TU_ARRAY_SIZE(p_cdc->tx_ff_buf), 1, false); -#if CFG_FIFO_MUTEX - tu_fifo_config_mutex(&p_cdc->rx_ff, osal_mutex_create(&p_cdc->rx_ff_mutex)); - tu_fifo_config_mutex(&p_cdc->tx_ff, osal_mutex_create(&p_cdc->tx_ff_mutex)); -#endif + // TX fifo can be configured to change to overwritable if not connected (DTR bit not set). Without DTR we do not + // know if data is actually polled by terminal. This way the most current data is prioritized. + // Default: is overwritable + tu_fifo_config(&p_cdc->tx_ff, p_cdc->tx_ff_buf, TU_ARRAY_SIZE(p_cdc->tx_ff_buf), 1, _cdcd_cfg.tx_overwritabe_if_not_connected); + + #if OSAL_MUTEX_REQUIRED + osal_mutex_t mutex_rd = osal_mutex_create(&p_cdc->rx_ff_mutex); + osal_mutex_t mutex_wr = osal_mutex_create(&p_cdc->tx_ff_mutex); + TU_ASSERT(mutex_rd != NULL && mutex_wr != NULL, ); + + tu_fifo_config_mutex(&p_cdc->rx_ff, NULL, mutex_rd); + tu_fifo_config_mutex(&p_cdc->tx_ff, mutex_wr, NULL); + #endif } } -void cdcd_reset(uint8_t rhport) -{ +bool cdcd_deinit(void) { + #if OSAL_MUTEX_REQUIRED + for(uint8_t i=0; irx_ff.mutex_rd; + osal_mutex_t mutex_wr = p_cdc->tx_ff.mutex_wr; + + if (mutex_rd) { + osal_mutex_delete(mutex_rd); + tu_fifo_config_mutex(&p_cdc->rx_ff, NULL, NULL); + } + + if (mutex_wr) { + osal_mutex_delete(mutex_wr); + tu_fifo_config_mutex(&p_cdc->tx_ff, NULL, NULL); + } + } + #endif + + return true; +} + +void cdcd_reset(uint8_t rhport) { (void) rhport; - for(uint8_t i=0; irx_ff); + } + if (!_cdcd_cfg.tx_persistent) { + tu_fifo_clear(&p_cdc->tx_ff); + } + tu_fifo_set_overwritable(&p_cdc->tx_ff, _cdcd_cfg.tx_overwritabe_if_not_connected); } } -uint16_t cdcd_open(uint8_t rhport, tusb_desc_interface_t const * itf_desc, uint16_t max_len) -{ +uint16_t cdcd_open(uint8_t rhport, const tusb_desc_interface_t* itf_desc, uint16_t max_len) { // Only support ACM subclass - TU_VERIFY ( TUSB_CLASS_CDC == itf_desc->bInterfaceClass && - CDC_COMM_SUBCLASS_ABSTRACT_CONTROL_MODEL == itf_desc->bInterfaceSubClass, 0); - - // Note: 0xFF can be used with RNDIS - TU_VERIFY(tu_within(CDC_COMM_PROTOCOL_NONE, itf_desc->bInterfaceProtocol, CDC_COMM_PROTOCOL_ATCOMMAND_CDMA), 0); + TU_VERIFY( TUSB_CLASS_CDC == itf_desc->bInterfaceClass && + CDC_COMM_SUBCLASS_ABSTRACT_CONTROL_MODEL == itf_desc->bInterfaceSubClass, 0); // Find available interface - cdcd_interface_t * p_cdc = NULL; - for(uint8_t cdc_id=0; cdc_idep_in == 0) { break; } } - TU_ASSERT(p_cdc, 0); + TU_ASSERT(cdc_id < CFG_TUD_CDC, 0); //------------- Control Interface -------------// + p_cdc->rhport = rhport; p_cdc->itf_num = itf_desc->bInterfaceNumber; uint16_t drv_len = sizeof(tusb_desc_interface_t); - uint8_t const * p_desc = tu_desc_next( itf_desc ); + const uint8_t* p_desc = tu_desc_next(itf_desc); // Communication Functional Descriptors - while ( TUSB_DESC_CS_INTERFACE == tu_desc_type(p_desc) && drv_len <= max_len ) - { + while (TUSB_DESC_CS_INTERFACE == tu_desc_type(p_desc) && drv_len <= max_len) { drv_len += tu_desc_len(p_desc); - p_desc = tu_desc_next(p_desc); + p_desc = tu_desc_next(p_desc); } - if ( TUSB_DESC_ENDPOINT == tu_desc_type(p_desc) ) - { - // notification endpoint if any - TU_ASSERT( usbd_edpt_open(rhport, (tusb_desc_endpoint_t const *) p_desc), 0 ); - - p_cdc->ep_notif = ((tusb_desc_endpoint_t const *) p_desc)->bEndpointAddress; + if (TUSB_DESC_ENDPOINT == tu_desc_type(p_desc)) { + // notification endpoint + const tusb_desc_endpoint_t* desc_ep = (const tusb_desc_endpoint_t*) p_desc; + TU_ASSERT(usbd_edpt_open(rhport, desc_ep), 0); + p_cdc->ep_notify = desc_ep->bEndpointAddress; drv_len += tu_desc_len(p_desc); - p_desc = tu_desc_next(p_desc); + p_desc = tu_desc_next(p_desc); } //------------- Data Interface (if any) -------------// - if ( (TUSB_DESC_INTERFACE == tu_desc_type(p_desc)) && - (TUSB_CLASS_CDC_DATA == ((tusb_desc_interface_t const *) p_desc)->bInterfaceClass) ) - { + if ((TUSB_DESC_INTERFACE == tu_desc_type(p_desc)) && + (TUSB_CLASS_CDC_DATA == ((const tusb_desc_interface_t*) p_desc)->bInterfaceClass)) { // next to endpoint descriptor drv_len += tu_desc_len(p_desc); - p_desc = tu_desc_next(p_desc); + p_desc = tu_desc_next(p_desc); // Open endpoint pair - TU_ASSERT( usbd_open_edpt_pair(rhport, p_desc, 2, TUSB_XFER_BULK, &p_cdc->ep_out, &p_cdc->ep_in), 0 ); + TU_ASSERT(usbd_open_edpt_pair(rhport, p_desc, 2, TUSB_XFER_BULK, &p_cdc->ep_out, &p_cdc->ep_in), 0); - drv_len += 2*sizeof(tusb_desc_endpoint_t); + drv_len += 2 * sizeof(tusb_desc_endpoint_t); } // Prepare for incoming data - _prep_out_transaction(p_cdc); + _prep_out_transaction(cdc_id); return drv_len; } -// Invoked when class request DATA stage is finished. -// return false to stall control endpoint (e.g Host send non-sense DATA) -bool cdcd_control_complete(uint8_t rhport, tusb_control_request_t const * request) -{ - (void) rhport; - - //------------- Class Specific Request -------------// - TU_VERIFY (request->bmRequestType_bit.type == TUSB_REQ_TYPE_CLASS); - - uint8_t itf = 0; - cdcd_interface_t* p_cdc = _cdcd_itf; - - // Identify which interface to use - for ( ; ; itf++, p_cdc++) - { - if (itf >= TU_ARRAY_SIZE(_cdcd_itf)) return false; - - if ( p_cdc->itf_num == request->wIndex ) break; - } - - // Invoke callback - if ( CDC_REQUEST_SET_LINE_CODING == request->bRequest ) - { - if ( tud_cdc_line_coding_cb ) tud_cdc_line_coding_cb(itf, &p_cdc->line_coding); - } - - return true; -} - -// Handle class control request +// Invoked when a control transfer occurred on an interface of this class +// Driver response accordingly to the request and the transfer stage (setup/data/ack) // return false to stall control endpoint (e.g unsupported request) -bool cdcd_control_request(uint8_t rhport, tusb_control_request_t const * request) -{ +bool cdcd_control_xfer_cb(uint8_t rhport, uint8_t stage, const tusb_control_request_t* request) { // Handle class request only TU_VERIFY(request->bmRequestType_bit.type == TUSB_REQ_TYPE_CLASS); - uint8_t itf = 0; - cdcd_interface_t* p_cdc = _cdcd_itf; + uint8_t itf; + cdcd_interface_t* p_cdc; // Identify which interface to use - for ( ; ; itf++, p_cdc++) - { - if (itf >= TU_ARRAY_SIZE(_cdcd_itf)) return false; - - if ( p_cdc->itf_num == request->wIndex ) break; + for (itf = 0; itf < CFG_TUD_CDC; itf++) { + p_cdc = &_cdcd_itf[itf]; + if (p_cdc->itf_num == request->wIndex) { + break; + } } + TU_VERIFY(itf < CFG_TUD_CDC); - switch ( request->bRequest ) - { + switch (request->bRequest) { case CDC_REQUEST_SET_LINE_CODING: - TU_LOG2(" Set Line Coding\r\n"); - tud_control_xfer(rhport, request, &p_cdc->line_coding, sizeof(cdc_line_coding_t)); - break; + if (stage == CONTROL_STAGE_SETUP) { + TU_LOG_DRV(" Set Line Coding\r\n"); + tud_control_xfer(rhport, request, &p_cdc->line_coding, sizeof(cdc_line_coding_t)); + } else if (stage == CONTROL_STAGE_ACK) { + tud_cdc_line_coding_cb(itf, &p_cdc->line_coding); + } + break; case CDC_REQUEST_GET_LINE_CODING: - TU_LOG2(" Get Line Coding\r\n"); - tud_control_xfer(rhport, request, &p_cdc->line_coding, sizeof(cdc_line_coding_t)); - break; + if (stage == CONTROL_STAGE_SETUP) { + TU_LOG_DRV(" Get Line Coding\r\n"); + tud_control_xfer(rhport, request, &p_cdc->line_coding, sizeof(cdc_line_coding_t)); + } + break; case CDC_REQUEST_SET_CONTROL_LINE_STATE: - { - // CDC PSTN v1.2 section 6.3.12 - // Bit 0: Indicates if DTE is present or not. - // This signal corresponds to V.24 signal 108/2 and RS-232 signal DTR (Data Terminal Ready) - // Bit 1: Carrier control for half-duplex modems. - // This signal corresponds to V.24 signal 105 and RS-232 signal RTS (Request to Send) - bool const dtr = tu_bit_test(request->wValue, 0); - bool const rts = tu_bit_test(request->wValue, 1); - - p_cdc->line_state = (uint8_t) request->wValue; + if (stage == CONTROL_STAGE_SETUP) { + tud_control_status(rhport, request); + } else if (stage == CONTROL_STAGE_ACK) { + // CDC PSTN v1.2 section 6.3.12 + // Bit 0: Indicates if DTE is present or not. + // This signal corresponds to V.24 signal 108/2 and RS-232 signal DTR (Data Terminal Ready) + // Bit 1: Carrier control for half-duplex modems. + // This signal corresponds to V.24 signal 105 and RS-232 signal RTS (Request to Send) + bool const dtr = tu_bit_test(request->wValue, 0); + bool const rts = tu_bit_test(request->wValue, 1); + + p_cdc->line_state = (uint8_t) request->wValue; + + // If enabled: fifo overwriting is disabled if DTR bit is set and vice versa + if (_cdcd_cfg.tx_overwritabe_if_not_connected) { + tu_fifo_set_overwritable(&p_cdc->tx_ff, !dtr); + } else { + tu_fifo_set_overwritable(&p_cdc->tx_ff, false); + } - TU_LOG2(" Set Control Line State: DTR = %d, RTS = %d\r\n", dtr, rts); + TU_LOG_DRV(" Set Control Line State: DTR = %d, RTS = %d\r\n", dtr, rts); - tud_control_status(rhport, request); + // Invoke callback + tud_cdc_line_state_cb(itf, dtr, rts); + } + break; - // Invoke callback - if ( tud_cdc_line_state_cb ) tud_cdc_line_state_cb(itf, dtr, rts); - } - break; + case CDC_REQUEST_SEND_BREAK: + if (stage == CONTROL_STAGE_SETUP) { + tud_control_status(rhport, request); + } else if (stage == CONTROL_STAGE_ACK) { + TU_LOG_DRV(" Send Break\r\n"); + tud_cdc_send_break_cb(itf, request->wValue); + } + break; - default: return false; // stall unsupported request + default: + return false; // stall unsupported request } return true; } -bool cdcd_xfer_cb(uint8_t rhport, uint8_t ep_addr, xfer_result_t result, uint32_t xferred_bytes) -{ +bool cdcd_xfer_cb(uint8_t rhport, uint8_t ep_addr, xfer_result_t result, uint32_t xferred_bytes) { (void) result; uint8_t itf; cdcd_interface_t* p_cdc; // Identify which interface to use - for (itf = 0; itf < CFG_TUD_CDC; itf++) - { + for (itf = 0; itf < CFG_TUD_CDC; itf++) { p_cdc = &_cdcd_itf[itf]; - if ( ( ep_addr == p_cdc->ep_out ) || ( ep_addr == p_cdc->ep_in ) ) break; + if ((ep_addr == p_cdc->ep_out) || (ep_addr == p_cdc->ep_in) || (ep_addr == p_cdc->ep_notify)) { + break; + } } TU_ASSERT(itf < CFG_TUD_CDC); + cdcd_epbuf_t* p_epbuf = &_cdcd_epbuf[itf]; // Received new data - if ( ep_addr == p_cdc->ep_out ) - { - // TODO search for wanted char first for better performance - for(uint32_t i=0; irx_ff, &p_cdc->epout_buf[i]); - - // Check for wanted char and invoke callback if needed - if ( tud_cdc_rx_wanted_cb && ( ((signed char) p_cdc->wanted_char) != -1 ) && ( p_cdc->wanted_char == p_cdc->epout_buf[i] ) ) - { - tud_cdc_rx_wanted_cb(itf, p_cdc->wanted_char); + if (ep_addr == p_cdc->ep_out) { + tu_fifo_write_n(&p_cdc->rx_ff, p_epbuf->epout, (uint16_t) xferred_bytes); + + // Check for wanted char and invoke callback if needed + if (((signed char) p_cdc->wanted_char) != -1) { + for (uint32_t i = 0; i < xferred_bytes; i++) { + if ((p_cdc->wanted_char == p_epbuf->epout[i]) && !tu_fifo_empty(&p_cdc->rx_ff)) { + tud_cdc_rx_wanted_cb(itf, p_cdc->wanted_char); + } } } // invoke receive callback (if there is still data) - if (tud_cdc_rx_cb && tu_fifo_count(&p_cdc->rx_ff) ) tud_cdc_rx_cb(itf); + if (!tu_fifo_empty(&p_cdc->rx_ff)) { + tud_cdc_rx_cb(itf); + } // prepare for OUT transaction - _prep_out_transaction(p_cdc); + _prep_out_transaction(itf); } // Data sent to host, we continue to fetch from tx fifo to send. // Note: This will cause incorrect baudrate set in line coding. // Though maybe the baudrate is not really important !!! - if ( ep_addr == p_cdc->ep_in ) - { + if (ep_addr == p_cdc->ep_in) { // invoke transmit callback to possibly refill tx fifo - if ( tud_cdc_tx_complete_cb ) tud_cdc_tx_complete_cb(itf); + tud_cdc_tx_complete_cb(itf); - if ( 0 == tud_cdc_n_write_flush(itf) ) - { + if (0 == tud_cdc_n_write_flush(itf)) { // If there is no data left, a ZLP should be sent if - // xferred_bytes is multiple of EP size and not zero - // FIXME CFG_TUD_CDC_EP_BUFSIZE is not Endpoint packet size - if ( !tu_fifo_count(&p_cdc->tx_ff) && xferred_bytes && (0 == (xferred_bytes % CFG_TUD_CDC_EP_BUFSIZE)) ) - { - if ( usbd_edpt_claim(rhport, p_cdc->ep_in) ) - { - usbd_edpt_xfer(rhport, p_cdc->ep_in, NULL, 0); + // xferred_bytes is multiple of EP Packet size and not zero + if (!tu_fifo_count(&p_cdc->tx_ff) && xferred_bytes && (0 == (xferred_bytes & (BULK_PACKET_SIZE - 1)))) { + if (usbd_edpt_claim(rhport, p_cdc->ep_in)) { + TU_ASSERT(usbd_edpt_xfer(rhport, p_cdc->ep_in, NULL, 0)); } } } } - // nothing to do with notif endpoint for now + // Sent notification to host + if (ep_addr == p_cdc->ep_notify) { + tud_cdc_notify_complete_cb(itf); + } return true; } diff --git a/cores/nRF5/TinyUSB/Adafruit_TinyUSB_ArduinoCore/tinyusb/src/class/cdc/cdc_device.h b/cores/nRF5/TinyUSB/Adafruit_TinyUSB_ArduinoCore/tinyusb/src/class/cdc/cdc_device.h index 3c679c4..9673b98 100644 --- a/cores/nRF5/TinyUSB/Adafruit_TinyUSB_ArduinoCore/tinyusb/src/class/cdc/cdc_device.h +++ b/cores/nRF5/TinyUSB/Adafruit_TinyUSB_ArduinoCore/tinyusb/src/class/cdc/cdc_device.h @@ -1,4 +1,4 @@ -/* +/* * The MIT License (MIT) * * Copyright (c) 2019 Ha Thach (tinyusb.org) @@ -24,16 +24,18 @@ * This file is part of the TinyUSB stack. */ -#ifndef _TUSB_CDC_DEVICE_H_ -#define _TUSB_CDC_DEVICE_H_ +#ifndef TUSB_CDC_DEVICE_H_ +#define TUSB_CDC_DEVICE_H_ -#include "common/tusb_common.h" -#include "device/usbd.h" #include "cdc.h" //--------------------------------------------------------------------+ // Class Driver Configuration //--------------------------------------------------------------------+ +#ifndef CFG_TUD_CDC_NOTIFY + #define CFG_TUD_CDC_NOTIFY 0 +#endif + #if !defined(CFG_TUD_CDC_EP_BUFSIZE) && defined(CFG_TUD_CDC_EPSIZE) #warning CFG_TUD_CDC_EPSIZE is renamed to CFG_TUD_CDC_EP_BUFSIZE, please update to use the new name #define CFG_TUD_CDC_EP_BUFSIZE CFG_TUD_CDC_EPSIZE @@ -47,201 +49,209 @@ extern "C" { #endif -/** \addtogroup CDC_Serial Serial - * @{ - * \defgroup CDC_Serial_Device Device - * @{ */ +//--------------------------------------------------------------------+ +// Driver Configuration +//--------------------------------------------------------------------+ +typedef struct TU_ATTR_PACKED { + uint8_t rx_persistent : 1; // keep rx fifo data even with bus reset or disconnect + uint8_t tx_persistent : 1; // keep tx fifo data even with reset or disconnect + uint8_t tx_overwritabe_if_not_connected : 1; // if not connected, tx fifo can be overwritten +} tud_cdc_configure_t; + +#define TUD_CDC_CONFIGURE_DEFAULT() { \ + .rx_persistent = 0, \ + .tx_persistent = 0, \ + .tx_overwritabe_if_not_connected = 1, \ +} + +// Configure CDC driver behavior +bool tud_cdc_configure(const tud_cdc_configure_t* driver_cfg); + +// Backward compatible +#define tud_cdc_configure_fifo_t tud_cdc_configure_t +#define tud_cdc_configure_fifo tud_cdc_configure //--------------------------------------------------------------------+ -// Application API (Multiple Ports) -// CFG_TUD_CDC > 1 +// Application API (Multiple Ports) i.e. CFG_TUD_CDC > 1 //--------------------------------------------------------------------+ +// Check if interface is ready +bool tud_cdc_n_ready(uint8_t itf); + // Check if terminal is connected to this port -bool tud_cdc_n_connected (uint8_t itf); +bool tud_cdc_n_connected(uint8_t itf); // Get current line state. Bit 0: DTR (Data Terminal Ready), Bit 1: RTS (Request to Send) -uint8_t tud_cdc_n_get_line_state (uint8_t itf); +uint8_t tud_cdc_n_get_line_state(uint8_t itf); // Get current line encoding: bit rate, stop bits parity etc .. -void tud_cdc_n_get_line_coding (uint8_t itf, cdc_line_coding_t* coding); +void tud_cdc_n_get_line_coding(uint8_t itf, cdc_line_coding_t* coding); // Set special character that will trigger tud_cdc_rx_wanted_cb() callback on receiving -void tud_cdc_n_set_wanted_char (uint8_t itf, char wanted); +void tud_cdc_n_set_wanted_char(uint8_t itf, char wanted); // Get the number of bytes available for reading -uint32_t tud_cdc_n_available (uint8_t itf); +uint32_t tud_cdc_n_available(uint8_t itf); // Read received bytes -uint32_t tud_cdc_n_read (uint8_t itf, void* buffer, uint32_t bufsize); +uint32_t tud_cdc_n_read(uint8_t itf, void* buffer, uint32_t bufsize); // Read a byte, return -1 if there is none -static inline -int32_t tud_cdc_n_read_char (uint8_t itf); +TU_ATTR_ALWAYS_INLINE static inline int32_t tud_cdc_n_read_char(uint8_t itf) { + uint8_t ch; + return tud_cdc_n_read(itf, &ch, 1) ? (int32_t) ch : -1; +} // Clear the received FIFO -void tud_cdc_n_read_flush (uint8_t itf); +void tud_cdc_n_read_flush(uint8_t itf); -// Get a byte from FIFO at the specified position without removing it -bool tud_cdc_n_peek (uint8_t itf, int pos, uint8_t* u8); +// Get a byte from FIFO without removing it +bool tud_cdc_n_peek(uint8_t itf, uint8_t* ui8); // Write bytes to TX FIFO, data may remain in the FIFO for a while -uint32_t tud_cdc_n_write (uint8_t itf, void const* buffer, uint32_t bufsize); +uint32_t tud_cdc_n_write(uint8_t itf, void const* buffer, uint32_t bufsize); // Write a byte -static inline -uint32_t tud_cdc_n_write_char (uint8_t itf, char ch); +TU_ATTR_ALWAYS_INLINE static inline uint32_t tud_cdc_n_write_char(uint8_t itf, char ch) { + return tud_cdc_n_write(itf, &ch, 1); +} // Write a null-terminated string -static inline -uint32_t tud_cdc_n_write_str (uint8_t itf, char const* str); +TU_ATTR_ALWAYS_INLINE static inline uint32_t tud_cdc_n_write_str(uint8_t itf, char const* str) { + return tud_cdc_n_write(itf, str, strlen(str)); +} // Force sending data if possible, return number of forced bytes -uint32_t tud_cdc_n_write_flush (uint8_t itf); +uint32_t tud_cdc_n_write_flush(uint8_t itf); // Return the number of bytes (characters) available for writing to TX FIFO buffer in a single n_write operation. -uint32_t tud_cdc_n_write_available (uint8_t itf); +uint32_t tud_cdc_n_write_available(uint8_t itf); -//--------------------------------------------------------------------+ -// Application API (Single Port) -//--------------------------------------------------------------------+ -static inline bool tud_cdc_connected (void); -static inline uint8_t tud_cdc_get_line_state (void); -static inline void tud_cdc_get_line_coding (cdc_line_coding_t* coding); -static inline void tud_cdc_set_wanted_char (char wanted); - -static inline uint32_t tud_cdc_available (void); -static inline int32_t tud_cdc_read_char (void); -static inline uint32_t tud_cdc_read (void* buffer, uint32_t bufsize); -static inline void tud_cdc_read_flush (void); -static inline bool tud_cdc_peek (int pos, uint8_t* u8); - -static inline uint32_t tud_cdc_write_char (char ch); -static inline uint32_t tud_cdc_write (void const* buffer, uint32_t bufsize); -static inline uint32_t tud_cdc_write_str (char const* str); -static inline uint32_t tud_cdc_write_flush (void); -static inline uint32_t tud_cdc_write_available (void); +// Clear the transmit FIFO +bool tud_cdc_n_write_clear(uint8_t itf); -//--------------------------------------------------------------------+ -// Application Callback API (weak is optional) -//--------------------------------------------------------------------+ -// Invoked when received new data -TU_ATTR_WEAK void tud_cdc_rx_cb(uint8_t itf); - -// Invoked when received `wanted_char` -TU_ATTR_WEAK void tud_cdc_rx_wanted_cb(uint8_t itf, char wanted_char); +#if CFG_TUD_CDC_NOTIFY +// Send UART status notification: DCD, DSR etc .. +bool tud_cdc_n_notify_uart_state(uint8_t itf, const cdc_notify_uart_state_t *state); -// Invoked when space becomes available in TX buffer -TU_ATTR_WEAK void tud_cdc_tx_complete_cb(uint8_t itf); +// Send connection speed change notification +bool tud_cdc_n_notify_conn_speed_change(uint8_t itf, const cdc_notify_conn_speed_change_t* conn_speed_change); -// Invoked when line state DTR & RTS are changed via SET_CONTROL_LINE_STATE -TU_ATTR_WEAK void tud_cdc_line_state_cb(uint8_t itf, bool dtr, bool rts); +TU_ATTR_ALWAYS_INLINE static inline bool tud_cdc_notify_uart_state(const cdc_notify_uart_state_t* state) { + return tud_cdc_n_notify_uart_state(0, state); +} -// Invoked when line coding is change via SET_LINE_CODING -TU_ATTR_WEAK void tud_cdc_line_coding_cb(uint8_t itf, cdc_line_coding_t const* p_line_coding); +TU_ATTR_ALWAYS_INLINE static inline bool tud_cdc_notify_conn_speed_change(const cdc_notify_conn_speed_change_t* conn_speed_change) { + return tud_cdc_n_notify_conn_speed_change(0, conn_speed_change); +} +#endif //--------------------------------------------------------------------+ -// Inline Functions +// Application API (Single Port) //--------------------------------------------------------------------+ -static inline int32_t tud_cdc_n_read_char (uint8_t itf) -{ - uint8_t ch; - return tud_cdc_n_read(itf, &ch, 1) ? (int32_t) ch : -1; -} -static inline uint32_t tud_cdc_n_write_char(uint8_t itf, char ch) -{ - return tud_cdc_n_write(itf, &ch, 1); +TU_ATTR_ALWAYS_INLINE static inline bool tud_cdc_ready(void) { + return tud_cdc_n_ready(0); } -static inline uint32_t tud_cdc_n_write_str (uint8_t itf, char const* str) -{ - return tud_cdc_n_write(itf, str, strlen(str)); -} - -static inline bool tud_cdc_connected (void) -{ +TU_ATTR_ALWAYS_INLINE static inline bool tud_cdc_connected(void) { return tud_cdc_n_connected(0); } -static inline uint8_t tud_cdc_get_line_state (void) -{ +TU_ATTR_ALWAYS_INLINE static inline uint8_t tud_cdc_get_line_state(void) { return tud_cdc_n_get_line_state(0); } -static inline void tud_cdc_get_line_coding (cdc_line_coding_t* coding) -{ +TU_ATTR_ALWAYS_INLINE static inline void tud_cdc_get_line_coding(cdc_line_coding_t* coding) { tud_cdc_n_get_line_coding(0, coding); } -static inline void tud_cdc_set_wanted_char (char wanted) -{ +TU_ATTR_ALWAYS_INLINE static inline void tud_cdc_set_wanted_char(char wanted) { tud_cdc_n_set_wanted_char(0, wanted); } -static inline uint32_t tud_cdc_available (void) -{ +TU_ATTR_ALWAYS_INLINE static inline uint32_t tud_cdc_available(void) { return tud_cdc_n_available(0); } -static inline int32_t tud_cdc_read_char (void) -{ +TU_ATTR_ALWAYS_INLINE static inline int32_t tud_cdc_read_char(void) { return tud_cdc_n_read_char(0); } -static inline uint32_t tud_cdc_read (void* buffer, uint32_t bufsize) -{ +TU_ATTR_ALWAYS_INLINE static inline uint32_t tud_cdc_read(void* buffer, uint32_t bufsize) { return tud_cdc_n_read(0, buffer, bufsize); } -static inline void tud_cdc_read_flush (void) -{ +TU_ATTR_ALWAYS_INLINE static inline void tud_cdc_read_flush(void) { tud_cdc_n_read_flush(0); } -static inline bool tud_cdc_peek (int pos, uint8_t* u8) -{ - return tud_cdc_n_peek(0, pos, u8); +TU_ATTR_ALWAYS_INLINE static inline bool tud_cdc_peek(uint8_t* ui8) { + return tud_cdc_n_peek(0, ui8); } -static inline uint32_t tud_cdc_write_char (char ch) -{ +TU_ATTR_ALWAYS_INLINE static inline uint32_t tud_cdc_write_char(char ch) { return tud_cdc_n_write_char(0, ch); } -static inline uint32_t tud_cdc_write (void const* buffer, uint32_t bufsize) -{ +TU_ATTR_ALWAYS_INLINE static inline uint32_t tud_cdc_write(void const* buffer, uint32_t bufsize) { return tud_cdc_n_write(0, buffer, bufsize); } -static inline uint32_t tud_cdc_write_str (char const* str) -{ +TU_ATTR_ALWAYS_INLINE static inline uint32_t tud_cdc_write_str(char const* str) { return tud_cdc_n_write_str(0, str); } -static inline uint32_t tud_cdc_write_flush (void) -{ +TU_ATTR_ALWAYS_INLINE static inline uint32_t tud_cdc_write_flush(void) { return tud_cdc_n_write_flush(0); } -static inline uint32_t tud_cdc_write_available(void) -{ +TU_ATTR_ALWAYS_INLINE static inline uint32_t tud_cdc_write_available(void) { return tud_cdc_n_write_available(0); } -/** @} */ -/** @} */ +TU_ATTR_ALWAYS_INLINE static inline bool tud_cdc_write_clear(void) { + return tud_cdc_n_write_clear(0); +} + +//--------------------------------------------------------------------+ +// Application Callback API +//--------------------------------------------------------------------+ + +// Invoked when received new data +void tud_cdc_rx_cb(uint8_t itf); + +// Invoked when received `wanted_char` +void tud_cdc_rx_wanted_cb(uint8_t itf, char wanted_char); + +// Invoked when a TX is complete and therefore space becomes available in TX buffer +void tud_cdc_tx_complete_cb(uint8_t itf); + +// Invoked when a notification is sent to host +void tud_cdc_notify_complete_cb(uint8_t itf); + +// Invoked when line state DTR & RTS are changed via SET_CONTROL_LINE_STATE +void tud_cdc_line_state_cb(uint8_t itf, bool dtr, bool rts); + +// Invoked when line coding is change via SET_LINE_CODING +void tud_cdc_line_coding_cb(uint8_t itf, cdc_line_coding_t const* p_line_coding); + +// Invoked when received send break +// \param[in] itf interface for which send break was received. +// \param[in] duration_ms the length of time, in milliseconds, of the break signal. If a value of FFFFh, then the +// device will send a break until another SendBreak request is received with value 0000h. +void tud_cdc_send_break_cb(uint8_t itf, uint16_t duration_ms); //--------------------------------------------------------------------+ // INTERNAL USBD-CLASS DRIVER API //--------------------------------------------------------------------+ -void cdcd_init (void); -void cdcd_reset (uint8_t rhport); -uint16_t cdcd_open (uint8_t rhport, tusb_desc_interface_t const * itf_desc, uint16_t max_len); -bool cdcd_control_request (uint8_t rhport, tusb_control_request_t const * request); -bool cdcd_control_complete (uint8_t rhport, tusb_control_request_t const * request); -bool cdcd_xfer_cb (uint8_t rhport, uint8_t ep_addr, xfer_result_t result, uint32_t xferred_bytes); +void cdcd_init (void); +bool cdcd_deinit (void); +void cdcd_reset (uint8_t rhport); +uint16_t cdcd_open (uint8_t rhport, tusb_desc_interface_t const * itf_desc, uint16_t max_len); +bool cdcd_control_xfer_cb (uint8_t rhport, uint8_t stage, tusb_control_request_t const * request); +bool cdcd_xfer_cb (uint8_t rhport, uint8_t ep_addr, xfer_result_t result, uint32_t xferred_bytes); #ifdef __cplusplus } diff --git a/cores/nRF5/TinyUSB/Adafruit_TinyUSB_ArduinoCore/tinyusb/src/class/cdc/cdc_host.c b/cores/nRF5/TinyUSB/Adafruit_TinyUSB_ArduinoCore/tinyusb/src/class/cdc/cdc_host.c new file mode 100644 index 0000000..beef03e --- /dev/null +++ b/cores/nRF5/TinyUSB/Adafruit_TinyUSB_ArduinoCore/tinyusb/src/class/cdc/cdc_host.c @@ -0,0 +1,2540 @@ +/* + * 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. + * + * Contribution + * - Heiko Kuester: add support of CH34x & PL2303, improve support of FTDI & CP210x + */ + +#include "tusb_option.h" + +#if (CFG_TUH_ENABLED && CFG_TUH_CDC) + +#include "host/usbh.h" +#include "host/usbh_pvt.h" + +#include "cdc_host.h" +#include "serial/ftdi_sio.h" +#include "serial/cp210x.h" +#include "serial/ch34x.h" +#include "serial/pl2303.h" + +// Level where CFG_TUSB_DEBUG must be at least for this driver is logged +#ifndef CFG_TUH_CDC_LOG_LEVEL + #define CFG_TUH_CDC_LOG_LEVEL 2 +#endif + +#define TU_LOG_DRV(...) TU_LOG(CFG_TUH_CDC_LOG_LEVEL, __VA_ARGS__) +#define TU_LOG_CDC(_cdc, _format, ...) TU_LOG_DRV("[:%u:%u] CDCh %s " _format "\r\n", _cdc->daddr, _cdc->bInterfaceNumber, \ + serial_drivers[_cdc->serial_drid].name, ##__VA_ARGS__) + +//--------------------------------------------------------------------+ +// Host CDC Interface +//--------------------------------------------------------------------+ + +typedef struct { + uint8_t daddr; + uint8_t bInterfaceNumber; + uint8_t bInterfaceSubClass; + uint8_t bInterfaceProtocol; + + uint8_t ep_notif; + uint8_t serial_drid; // Serial Driver ID + bool mounted; // Enumeration is complete + + struct { + TU_ATTR_ALIGNED(4) cdc_line_coding_t coding; // Baudrate, stop bits, parity, data width + cdc_line_control_state_t control_state; // DTR, RTS + } line, requested_line; + + tuh_xfer_cb_t user_complete_cb; // required since we handle request internally first + + union { + struct { + cdc_acm_capability_t capability; + } acm; + + #if CFG_TUH_CDC_FTDI + ftdi_private_t ftdi; + #endif + + #if CFG_TUH_CDC_PL2303 + pl2303_private_t pl2303; + #endif + }; + + struct { + tu_edpt_stream_t tx; + tu_edpt_stream_t rx; + + uint8_t tx_ff_buf[CFG_TUH_CDC_TX_BUFSIZE]; + uint8_t rx_ff_buf[CFG_TUH_CDC_RX_BUFSIZE]; + } stream; +} cdch_interface_t; + +typedef struct { + TUH_EPBUF_DEF(tx, CFG_TUH_CDC_TX_EPSIZE); + TUH_EPBUF_DEF(rx, CFG_TUH_CDC_RX_EPSIZE); +} cdch_epbuf_t; + +static cdch_interface_t cdch_data[CFG_TUH_CDC]; +CFG_TUH_MEM_SECTION static cdch_epbuf_t cdch_epbuf[CFG_TUH_CDC]; + +//--------------------------------------------------------------------+ +// Serial Driver +//--------------------------------------------------------------------+ + +// General driver +static void cdch_process_set_config(tuh_xfer_t *xfer); +static void cdch_process_line_state_on_enum(tuh_xfer_t *xfer); // invoked after set config is processed +static void cdch_internal_control_complete(tuh_xfer_t *xfer); +static void cdch_set_line_coding_stage1_baudrate_complete(tuh_xfer_t *xfer); +static void cdch_set_line_coding_stage2_data_format_complete(tuh_xfer_t *xfer); + +//------------- ACM prototypes -------------// +static bool acm_open(uint8_t daddr, tusb_desc_interface_t const * itf_desc, uint16_t max_len); +static bool acm_process_set_config(cdch_interface_t *p_cdc, tuh_xfer_t *xfer); +static void acm_internal_control_complete(cdch_interface_t *p_cdc, tuh_xfer_t *xfer); + +static bool acm_set_line_coding(cdch_interface_t * p_cdc, tuh_xfer_cb_t complete_cb, uintptr_t user_data); +static bool acm_set_control_line_state(cdch_interface_t * p_cdc, tuh_xfer_cb_t complete_cb, uintptr_t user_data); + +//------------- FTDI prototypes -------------// +#if CFG_TUH_CDC_FTDI +static uint16_t const ftdi_vid_pid_list[][2] = {CFG_TUH_CDC_FTDI_VID_PID_LIST}; +static bool ftdi_open(uint8_t daddr, const tusb_desc_interface_t * itf_desc, uint16_t max_len); +static bool ftdi_proccess_set_config(cdch_interface_t *p_cdc, tuh_xfer_t *xfer); +static void ftdi_internal_control_complete(cdch_interface_t* p_cdc, tuh_xfer_t *xfer); + +static bool ftdi_set_baudrate(cdch_interface_t * p_cdc, tuh_xfer_cb_t complete_cb, uintptr_t user_data); +static bool ftdi_set_data_format(cdch_interface_t * p_cdc, tuh_xfer_cb_t complete_cb, uintptr_t user_data); +static bool ftdi_set_modem_ctrl(cdch_interface_t * p_cdc, tuh_xfer_cb_t complete_cb, uintptr_t user_data); +#endif + +//------------- CP210X prototypes -------------// +#if CFG_TUH_CDC_CP210X +static uint16_t const cp210x_vid_pid_list[][2] = {CFG_TUH_CDC_CP210X_VID_PID_LIST}; + +static bool cp210x_open(uint8_t daddr, tusb_desc_interface_t const * itf_desc, uint16_t max_len); +static bool cp210x_process_set_config(cdch_interface_t *p_cdc, tuh_xfer_t *xfer); +static void cp210x_internal_control_complete(cdch_interface_t *p_cdc, tuh_xfer_t *xfer); + +static bool cp210x_set_baudrate(cdch_interface_t * p_cdc, tuh_xfer_cb_t complete_cb, uintptr_t user_data); +static bool cp210x_set_data_format(cdch_interface_t * p_cdc, tuh_xfer_cb_t complete_cb, uintptr_t user_data); +static bool cp210x_set_modem_ctrl(cdch_interface_t * p_cdc, tuh_xfer_cb_t complete_cb, uintptr_t user_data); +#endif + +//------------- CH34x prototypes -------------// +#if CFG_TUH_CDC_CH34X +static uint16_t const ch34x_vid_pid_list[][2] = {CFG_TUH_CDC_CH34X_VID_PID_LIST}; + +static bool ch34x_open(uint8_t daddr, tusb_desc_interface_t const * itf_desc, uint16_t max_len); +static bool ch34x_process_set_config(cdch_interface_t *p_cdc, tuh_xfer_t *xfer); +static void ch34x_internal_control_complete(cdch_interface_t *p_cdc, tuh_xfer_t *xfer); + +static bool ch34x_set_baudrate(cdch_interface_t * p_cdc, tuh_xfer_cb_t complete_cb, uintptr_t user_data); +static bool ch34x_set_data_format(cdch_interface_t * p_cdc, tuh_xfer_cb_t complete_cb, uintptr_t user_data); +static bool ch34x_set_modem_ctrl(cdch_interface_t * p_cdc, tuh_xfer_cb_t complete_cb, uintptr_t user_data); +#endif + +//------------- PL2303 prototypes -------------// +#if CFG_TUH_CDC_PL2303 +static uint16_t const pl2303_vid_pid_list[][2] = {CFG_TUH_CDC_PL2303_VID_PID_LIST}; +static const pl2303_type_data_t pl2303_type_data[PL2303_TYPE_COUNT] = {PL2303_TYPE_DATA}; + +static bool pl2303_open(uint8_t daddr, tusb_desc_interface_t const * itf_desc, uint16_t max_len); +static bool pl2303_process_set_config(cdch_interface_t *p_cdc, tuh_xfer_t *xfer); +static void pl2303_internal_control_complete(cdch_interface_t *p_cdc, tuh_xfer_t *xfer); + +static bool pl2303_set_line_coding(cdch_interface_t * p_cdc, tuh_xfer_cb_t complete_cb, uintptr_t user_data); +static bool pl2303_set_modem_ctrl(cdch_interface_t * p_cdc, tuh_xfer_cb_t complete_cb, uintptr_t user_data); +#endif + +//------------- Common -------------// +enum { + SERIAL_DRIVER_ACM = 0, + +#if CFG_TUH_CDC_FTDI + SERIAL_DRIVER_FTDI, +#endif + +#if CFG_TUH_CDC_CP210X + SERIAL_DRIVER_CP210X, +#endif + +#if CFG_TUH_CDC_CH34X + SERIAL_DRIVER_CH34X, +#endif + +#if CFG_TUH_CDC_PL2303 + SERIAL_DRIVER_PL2303, +#endif + + SERIAL_DRIVER_COUNT +}; + +typedef bool (*serial_driver_func_t)(cdch_interface_t * p_cdc, tuh_xfer_cb_t complete_cb, uintptr_t user_data); + +typedef struct { + uint16_t const (*vid_pid_list)[2]; + uint16_t const vid_pid_count; + bool (*const open)(uint8_t daddr, const tusb_desc_interface_t * itf_desc, uint16_t max_len); + bool (*const process_set_config)(cdch_interface_t * p_cdc, tuh_xfer_t * xfer); + void (*const request_complete)(cdch_interface_t * p_cdc, tuh_xfer_t * xfer); // internal request complete handler to update line state + + serial_driver_func_t set_control_line_state, set_baudrate, set_data_format, set_line_coding; + + #if CFG_TUSB_DEBUG && CFG_TUSB_DEBUG >= CFG_TUH_CDC_LOG_LEVEL + const char * name; + #endif +} cdch_serial_driver_t; + +#if CFG_TUSB_DEBUG >= CFG_TUH_CDC_LOG_LEVEL + #define DRIVER_NAME_DECLARE(_str) .name = _str +#else + #define DRIVER_NAME_DECLARE(_str) +#endif + +// Note driver list must be in the same order as SERIAL_DRIVER enum +static const cdch_serial_driver_t serial_drivers[] = { + { + .vid_pid_list = NULL, + .vid_pid_count = 0, + .open = acm_open, + .process_set_config = acm_process_set_config, + .request_complete = acm_internal_control_complete, + .set_control_line_state = acm_set_control_line_state, + .set_baudrate = acm_set_line_coding, + .set_data_format = acm_set_line_coding, + .set_line_coding = acm_set_line_coding, + DRIVER_NAME_DECLARE("ACM") + }, + + #if CFG_TUH_CDC_FTDI + { + .vid_pid_list = ftdi_vid_pid_list, + .vid_pid_count = TU_ARRAY_SIZE(ftdi_vid_pid_list), + .open = ftdi_open, + .process_set_config = ftdi_proccess_set_config, + .request_complete = ftdi_internal_control_complete, + .set_control_line_state = ftdi_set_modem_ctrl, + .set_baudrate = ftdi_set_baudrate, + .set_data_format = ftdi_set_data_format, + .set_line_coding = NULL, // 2 stage set line coding + DRIVER_NAME_DECLARE("FTDI") + }, + #endif + + #if CFG_TUH_CDC_CP210X + { + .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_set_config, + .request_complete = cp210x_internal_control_complete, + .set_control_line_state = cp210x_set_modem_ctrl, + .set_baudrate = cp210x_set_baudrate, + .set_data_format = cp210x_set_data_format, + .set_line_coding = NULL, // 2 stage set line coding + DRIVER_NAME_DECLARE("CP210x") + }, + #endif + + #if CFG_TUH_CDC_CH34X + { + .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_set_config, + .request_complete = ch34x_internal_control_complete, + + .set_control_line_state = ch34x_set_modem_ctrl, + .set_baudrate = ch34x_set_baudrate, + .set_data_format = ch34x_set_data_format, + .set_line_coding = NULL, // 2 stage set line coding + DRIVER_NAME_DECLARE("CH34x") + }, + #endif + + #if CFG_TUH_CDC_PL2303 + { + .vid_pid_list = pl2303_vid_pid_list, + .vid_pid_count = TU_ARRAY_SIZE(pl2303_vid_pid_list), + .open = pl2303_open, + .process_set_config = pl2303_process_set_config, + .request_complete = pl2303_internal_control_complete, + .set_control_line_state = pl2303_set_modem_ctrl, + .set_baudrate = pl2303_set_line_coding, + .set_data_format = pl2303_set_line_coding, + .set_line_coding = pl2303_set_line_coding, + DRIVER_NAME_DECLARE("PL2303") + } + #endif +}; + +TU_VERIFY_STATIC(TU_ARRAY_SIZE(serial_drivers) == SERIAL_DRIVER_COUNT, "Serial driver count mismatch"); + +//--------------------------------------------------------------------+ +// INTERNAL OBJECT & FUNCTION DECLARATION +//--------------------------------------------------------------------+ + +TU_ATTR_ALWAYS_INLINE static inline cdch_interface_t * get_itf(uint8_t idx) { + TU_ASSERT(idx < CFG_TUH_CDC, NULL); + cdch_interface_t * p_cdc = &cdch_data[idx]; + return (p_cdc->daddr != 0) ? p_cdc : NULL; +} + +TU_ATTR_ALWAYS_INLINE static inline uint8_t get_idx_by_ptr(cdch_interface_t* p_cdc) { + return (uint8_t) (p_cdc - cdch_data); +} + +static inline uint8_t get_idx_by_ep_addr(uint8_t daddr, uint8_t ep_addr) { + for(uint8_t i=0; idaddr == daddr) && + (ep_addr == p_cdc->ep_notif || ep_addr == p_cdc->stream.rx.ep_addr || ep_addr == p_cdc->stream.tx.ep_addr)) { + return i; + } + } + + return TUSB_INDEX_INVALID_8; +} + +// determine the interface from the completed transfer +static cdch_interface_t* get_itf_by_xfer(const tuh_xfer_t * xfer) { + TU_VERIFY(xfer->daddr != 0, NULL); + for(uint8_t i=0; idaddr == xfer->daddr) { + switch (p_cdc->serial_drid) { + #if CFG_TUH_CDC_CP210X + case SERIAL_DRIVER_CP210X: + #endif + case SERIAL_DRIVER_ACM: { + // Driver use wIndex for bInterfaceNumber + const uint8_t itf_num = (uint8_t) tu_le16toh(xfer->setup->wIndex); + if (p_cdc->bInterfaceNumber == itf_num) { + return p_cdc; + } + break; + } + + #if CFG_TUH_CDC_FTDI + case SERIAL_DRIVER_FTDI: { + // FTDI uses wIndex for channel number, if channel is 0 then it is the default channel + const uint8_t channel = (uint8_t) tu_le16toh(xfer->setup->wIndex); + if (p_cdc->ftdi.channel == 0 || p_cdc->ftdi.channel == channel) { + return p_cdc; + } + break; + } + #endif + + #if CFG_TUH_CDC_CH34X + case SERIAL_DRIVER_CH34X: + // ch34x has only one interface + return p_cdc; + #endif + + #if CFG_TUH_CDC_PL2303 + case SERIAL_DRIVER_PL2303: + // pl2303 has only one interface + return p_cdc; + #endif + + default: + break; + } + } + } + + return NULL; +} + +static cdch_interface_t * make_new_itf(uint8_t daddr, tusb_desc_interface_t const * itf_desc) { + for(uint8_t i=0; idaddr = daddr; + p_cdc->bInterfaceNumber = itf_desc->bInterfaceNumber; + p_cdc->bInterfaceSubClass = itf_desc->bInterfaceSubClass; + p_cdc->bInterfaceProtocol = itf_desc->bInterfaceProtocol; + p_cdc->line.coding = (cdc_line_coding_t) { 0, 0, 0, 0 }; + p_cdc->line.control_state.value = 0; + return p_cdc; + } + } + + return NULL; +} + +static bool open_ep_stream_pair(cdch_interface_t * p_cdc , tusb_desc_endpoint_t const *desc_ep); + +//--------------------------------------------------------------------+ +// Weak stubs: invoked if no strong implementation is available +//--------------------------------------------------------------------+ +TU_ATTR_WEAK void tuh_cdc_mount_cb(uint8_t idx) { + (void) idx; +} + +TU_ATTR_WEAK void tuh_cdc_umount_cb(uint8_t idx) { + (void) idx; +} + +TU_ATTR_WEAK void tuh_cdc_rx_cb(uint8_t idx) { + (void) idx; +} + +TU_ATTR_WEAK void tuh_cdc_tx_complete_cb(uint8_t idx) { + (void) idx; +} + +//--------------------------------------------------------------------+ +// APPLICATION API +//--------------------------------------------------------------------+ + +uint8_t tuh_cdc_itf_get_index(uint8_t daddr, uint8_t itf_num) { + for (uint8_t i = 0; i < CFG_TUH_CDC; i++) { + const cdch_interface_t * p_cdc = &cdch_data[i]; + if (p_cdc->daddr == daddr && p_cdc->bInterfaceNumber == itf_num) { return i; } + } + return TUSB_INDEX_INVALID_8; +} + +bool tuh_cdc_itf_get_info(uint8_t idx, tuh_itf_info_t * info) { + cdch_interface_t * p_cdc = get_itf(idx); + TU_VERIFY(p_cdc && info); + + info->daddr = p_cdc->daddr; + + // re-construct interface 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; +} + +bool tuh_cdc_mounted(uint8_t idx) { + cdch_interface_t * p_cdc = get_itf(idx); + TU_VERIFY(p_cdc); + return p_cdc->mounted; +} + +bool tuh_cdc_get_control_line_state_local(uint8_t idx, uint16_t* line_state) { + cdch_interface_t * p_cdc = get_itf(idx); + TU_VERIFY(p_cdc); + *line_state = p_cdc->line.control_state.value; + return true; +} + +bool tuh_cdc_get_line_coding_local(uint8_t idx, cdc_line_coding_t * line_coding) { + cdch_interface_t * p_cdc = get_itf(idx); + TU_VERIFY(p_cdc); + *line_coding = p_cdc->line.coding; + return true; +} + +//--------------------------------------------------------------------+ +// Write +//--------------------------------------------------------------------+ + +uint32_t tuh_cdc_write(uint8_t idx, void const * buffer, uint32_t bufsize) { + cdch_interface_t * p_cdc = get_itf(idx); + TU_VERIFY(p_cdc); + return tu_edpt_stream_write(p_cdc->daddr, &p_cdc->stream.tx, buffer, bufsize); +} + +uint32_t tuh_cdc_write_flush(uint8_t idx) { + cdch_interface_t * p_cdc = get_itf(idx); + TU_VERIFY(p_cdc); + return tu_edpt_stream_write_xfer(p_cdc->daddr, &p_cdc->stream.tx); +} + +bool tuh_cdc_write_clear(uint8_t idx) { + cdch_interface_t * p_cdc = get_itf(idx); + TU_VERIFY(p_cdc); + return tu_edpt_stream_clear(&p_cdc->stream.tx); +} + +uint32_t tuh_cdc_write_available(uint8_t idx) { + cdch_interface_t * p_cdc = get_itf(idx); + TU_VERIFY(p_cdc); + return tu_edpt_stream_write_available(p_cdc->daddr, &p_cdc->stream.tx); +} + +//--------------------------------------------------------------------+ +// Read +//--------------------------------------------------------------------+ + +uint32_t tuh_cdc_read (uint8_t idx, void * buffer, uint32_t bufsize) { + cdch_interface_t * p_cdc = get_itf(idx); + TU_VERIFY(p_cdc); + return tu_edpt_stream_read(p_cdc->daddr, &p_cdc->stream.rx, buffer, bufsize); +} + +uint32_t tuh_cdc_read_available(uint8_t idx) { + cdch_interface_t * p_cdc = get_itf(idx); + TU_VERIFY(p_cdc); + return tu_edpt_stream_read_available(&p_cdc->stream.rx); +} + +bool tuh_cdc_peek(uint8_t idx, uint8_t * ch) { + cdch_interface_t * p_cdc = get_itf(idx); + TU_VERIFY(p_cdc); + return tu_edpt_stream_peek(&p_cdc->stream.rx, ch); +} + +bool tuh_cdc_read_clear (uint8_t idx) { + cdch_interface_t * p_cdc = get_itf(idx); + TU_VERIFY(p_cdc); + + bool ret = tu_edpt_stream_clear(&p_cdc->stream.rx); + tu_edpt_stream_read_xfer(p_cdc->daddr, &p_cdc->stream.rx); + return ret; +} + +//--------------------------------------------------------------------+ +// Control Endpoint API +//--------------------------------------------------------------------+ + +bool tuh_cdc_set_control_line_state(uint8_t idx, uint16_t line_state, tuh_xfer_cb_t complete_cb, uintptr_t user_data) { + cdch_interface_t * p_cdc = get_itf(idx); + TU_VERIFY(p_cdc && p_cdc->serial_drid < SERIAL_DRIVER_COUNT); + TU_LOG_CDC(p_cdc, "set control line state dtr = %u rts = %u", p_cdc->requested_line.control_state.dtr, p_cdc->requested_line.control_state.rts); + const cdch_serial_driver_t * driver = &serial_drivers[p_cdc->serial_drid]; + + p_cdc->requested_line.control_state.value = (uint8_t) line_state; + p_cdc->user_complete_cb = complete_cb; + TU_VERIFY(driver->set_control_line_state(p_cdc, complete_cb ? cdch_internal_control_complete : NULL, user_data)); + + if (!complete_cb) { + // blocking, update line state if request was successful + p_cdc->line.control_state.value = (uint8_t) line_state; + } + + return true; +} + +bool tuh_cdc_set_baudrate(uint8_t idx, uint32_t baudrate, tuh_xfer_cb_t complete_cb, uintptr_t user_data) { + cdch_interface_t *p_cdc = get_itf(idx); + TU_VERIFY(p_cdc && p_cdc->serial_drid < SERIAL_DRIVER_COUNT); + TU_LOG_CDC(p_cdc, "set baudrate %lu", baudrate); + const cdch_serial_driver_t *driver = &serial_drivers[p_cdc->serial_drid]; + + p_cdc->requested_line = p_cdc->line; // keep current line coding + p_cdc->requested_line.coding.bit_rate = baudrate; + p_cdc->user_complete_cb = complete_cb; + TU_VERIFY(driver->set_baudrate(p_cdc, complete_cb ? cdch_internal_control_complete : NULL, user_data)); + + if (!complete_cb) { + p_cdc->line.coding.bit_rate = baudrate; + } + + return true; +} + +bool tuh_cdc_set_data_format(uint8_t idx, uint8_t stop_bits, uint8_t parity, uint8_t data_bits, + tuh_xfer_cb_t complete_cb, uintptr_t user_data) { + cdch_interface_t *p_cdc = get_itf(idx); + TU_VERIFY(p_cdc && p_cdc->serial_drid < SERIAL_DRIVER_COUNT); + TU_LOG_CDC(p_cdc, "set data format %u%c%s", + data_bits, CDC_LINE_CODING_PARITY_CHAR(parity), + CDC_LINE_CODING_STOP_BITS_TEXT(stop_bits)); + const cdch_serial_driver_t *driver = &serial_drivers[p_cdc->serial_drid]; + + p_cdc->requested_line = p_cdc->line; // keep current line coding + p_cdc->requested_line.coding.stop_bits = stop_bits; + p_cdc->requested_line.coding.parity = parity; + p_cdc->requested_line.coding.data_bits = data_bits; + + p_cdc->user_complete_cb = complete_cb; + TU_VERIFY(driver->set_data_format(p_cdc, complete_cb ? cdch_internal_control_complete : NULL, user_data)); + + if (!complete_cb) { + // blocking + p_cdc->line.coding.stop_bits = stop_bits; + p_cdc->line.coding.parity = parity; + p_cdc->line.coding.data_bits = data_bits; + } + + return true; +} + +bool tuh_cdc_set_line_coding(uint8_t idx, cdc_line_coding_t const *line_coding, + tuh_xfer_cb_t complete_cb, uintptr_t user_data) { + cdch_interface_t *p_cdc = get_itf(idx); + TU_VERIFY(p_cdc && p_cdc->serial_drid < SERIAL_DRIVER_COUNT); + TU_LOG_CDC(p_cdc, "set line coding %lu %u%c%s", + line_coding->bit_rate, line_coding->data_bits, + CDC_LINE_CODING_PARITY_CHAR(line_coding->parity), + CDC_LINE_CODING_STOP_BITS_TEXT(line_coding->stop_bits)); + cdch_serial_driver_t const *driver = &serial_drivers[p_cdc->serial_drid]; + p_cdc->requested_line.coding = *line_coding; + p_cdc->user_complete_cb = complete_cb; + + if (driver->set_line_coding) { + // driver support set_line_coding request + TU_VERIFY(driver->set_line_coding(p_cdc, complete_cb ? cdch_internal_control_complete : NULL, user_data)); + + if (!complete_cb) { + p_cdc->line.coding = *line_coding; + } + } else { + // driver does not support set_line_coding and need 2 stage to set baudrate and data format separately + if (complete_cb) { + // non-blocking + TU_VERIFY(driver->set_baudrate(p_cdc, cdch_set_line_coding_stage1_baudrate_complete, user_data)); + } else { + // blocking + xfer_result_t result = XFER_RESULT_INVALID; + + TU_VERIFY(driver->set_baudrate(p_cdc, NULL, (uintptr_t) &result)); + if (user_data) { + *((xfer_result_t *) user_data) = result; + } + TU_VERIFY(result == XFER_RESULT_SUCCESS); + p_cdc->line.coding.bit_rate = p_cdc->requested_line.coding.bit_rate; // update baudrate + + result = XFER_RESULT_INVALID; + TU_VERIFY(driver->set_data_format(p_cdc, NULL, (uintptr_t) &result)); + if (user_data) { + *((xfer_result_t *) user_data) = result; + } + TU_VERIFY(result == XFER_RESULT_SUCCESS); + p_cdc->line.coding = p_cdc->requested_line.coding; // update data format + } + } + + return true; +} + +//--------------------------------------------------------------------+ +// CLASS-USBH API +//--------------------------------------------------------------------+ + +bool cdch_init(void) { + TU_LOG_DRV("sizeof(cdch_interface_t) = %u\r\n", sizeof(cdch_interface_t)); + tu_memclr(cdch_data, sizeof(cdch_data)); + for (size_t i = 0; i < CFG_TUH_CDC; i++) { + cdch_interface_t *p_cdc = &cdch_data[i]; + cdch_epbuf_t *epbuf = &cdch_epbuf[i]; + tu_edpt_stream_init(&p_cdc->stream.tx, true, true, false, + p_cdc->stream.tx_ff_buf, CFG_TUH_CDC_TX_BUFSIZE, + epbuf->tx, CFG_TUH_CDC_TX_EPSIZE); + + tu_edpt_stream_init(&p_cdc->stream.rx, true, false, false, + p_cdc->stream.rx_ff_buf, CFG_TUH_CDC_RX_BUFSIZE, + epbuf->rx, CFG_TUH_CDC_RX_EPSIZE); + } + + return true; +} + +bool cdch_deinit(void) { + for (size_t i = 0; i < CFG_TUH_CDC; i++) { + cdch_interface_t *p_cdc = &cdch_data[i]; + tu_edpt_stream_deinit(&p_cdc->stream.tx); + tu_edpt_stream_deinit(&p_cdc->stream.rx); + } + return true; +} + +void cdch_close(uint8_t daddr) { + for (uint8_t idx = 0; idx < CFG_TUH_CDC; idx++) { + cdch_interface_t *p_cdc = &cdch_data[idx]; + if (p_cdc->daddr == daddr) { + TU_LOG_CDC(p_cdc, "close"); + + // Invoke application callback + tuh_cdc_umount_cb(idx); + + p_cdc->daddr = 0; + p_cdc->bInterfaceNumber = 0; + p_cdc->mounted = false; + tu_edpt_stream_close(&p_cdc->stream.tx); + tu_edpt_stream_close(&p_cdc->stream.rx); + } + } +} + +bool cdch_xfer_cb(uint8_t daddr, uint8_t ep_addr, xfer_result_t event, uint32_t xferred_bytes) { + // TODO handle stall response, retry failed transfer ... + TU_VERIFY(event == XFER_RESULT_SUCCESS); + + uint8_t const idx = get_idx_by_ep_addr(daddr, ep_addr); + cdch_interface_t *p_cdc = get_itf(idx); + TU_ASSERT(p_cdc); + + if (ep_addr == p_cdc->stream.tx.ep_addr) { + // invoke tx complete callback to possibly refill tx fifo + tuh_cdc_tx_complete_cb(idx); + + if (0 == tu_edpt_stream_write_xfer(daddr, &p_cdc->stream.tx)) { + // If there is no data left, a ZLP should be sent if: + // - xferred_bytes is multiple of EP Packet size and not zero + tu_edpt_stream_write_zlp_if_needed(daddr, &p_cdc->stream.tx, xferred_bytes); + } + } else if (ep_addr == p_cdc->stream.rx.ep_addr) { + #if CFG_TUH_CDC_FTDI + if (p_cdc->serial_drid == SERIAL_DRIVER_FTDI) { + // FTDI reserve 2 bytes for status + // uint8_t status[2] = {p_cdc->stream.rx.ep_buf[0], p_cdc->stream.rx.ep_buf[1]}; + if (xferred_bytes > 2) { + tu_edpt_stream_read_xfer_complete_with_buf(&p_cdc->stream.rx, p_cdc->stream.rx.ep_buf + 2, xferred_bytes - 2); + + tuh_cdc_rx_cb(idx); // invoke receive callback + } + } else + #endif + { + tu_edpt_stream_read_xfer_complete(&p_cdc->stream.rx, xferred_bytes); + + tuh_cdc_rx_cb(idx); // invoke receive callback + } + + // prepare for next transfer if needed + tu_edpt_stream_read_xfer(daddr, &p_cdc->stream.rx); + } else if (ep_addr == p_cdc->ep_notif) { + // TODO handle notification endpoint + } else { + TU_ASSERT(false); + } + + return true; +} + +//--------------------------------------------------------------------+ +// Enumeration +//--------------------------------------------------------------------+ + +static bool open_ep_stream_pair(cdch_interface_t *p_cdc, tusb_desc_endpoint_t const *desc_ep) { + for (size_t i = 0; i < 2; i++) { + TU_ASSERT(TUSB_DESC_ENDPOINT == desc_ep->bDescriptorType && + TUSB_XFER_BULK == desc_ep->bmAttributes.xfer); + TU_ASSERT(tuh_edpt_open(p_cdc->daddr, desc_ep)); + + if (tu_edpt_dir(desc_ep->bEndpointAddress) == TUSB_DIR_IN) { + tu_edpt_stream_open(&p_cdc->stream.rx, desc_ep); + } else { + tu_edpt_stream_open(&p_cdc->stream.tx, desc_ep); + } + + desc_ep = (tusb_desc_endpoint_t const *) tu_desc_next(desc_ep); + } + + return true; +} + +bool cdch_open(uint8_t rhport, uint8_t daddr, tusb_desc_interface_t const *itf_desc, uint16_t max_len) { + (void) rhport; + // For CDC: only support ACM subclass + // Note: Protocol 0xFF can be RNDIS device + if (TUSB_CLASS_CDC == itf_desc->bInterfaceClass && + CDC_COMM_SUBCLASS_ABSTRACT_CONTROL_MODEL == itf_desc->bInterfaceSubClass) { + return acm_open(daddr, itf_desc, max_len); + } else if (SERIAL_DRIVER_COUNT > 1 && + TUSB_CLASS_VENDOR_SPECIFIC == itf_desc->bInterfaceClass) { + uint16_t vid, pid; + TU_VERIFY(tuh_vid_pid_get(daddr, &vid, &pid)); + + for (size_t dr = 1; dr < SERIAL_DRIVER_COUNT; dr++) { + const cdch_serial_driver_t *driver = &serial_drivers[dr]; + for (size_t i = 0; i < driver->vid_pid_count; i++) { + if (driver->vid_pid_list[i][0] == vid && driver->vid_pid_list[i][1] == pid) { + const bool ret = driver->open(daddr, itf_desc, max_len); + TU_LOG_DRV("[:%u:%u] CDCh %s open %s\r\n", daddr, itf_desc->bInterfaceNumber, driver->name, ret ? "OK" : "FAILED"); + return ret; + } + } + } + } + + return false; +} + +bool cdch_set_config(uint8_t daddr, uint8_t itf_num) { + tusb_control_request_t request; + request.wIndex = tu_htole16((uint16_t) itf_num); + uint8_t const idx = tuh_cdc_itf_get_index(daddr, itf_num); + cdch_interface_t *p_cdc = get_itf(idx); + TU_ASSERT(p_cdc && p_cdc->serial_drid < SERIAL_DRIVER_COUNT); + TU_LOG_CDC(p_cdc, "set config"); + + // fake transfer to kick-off process_set_config() + tuh_xfer_t xfer; + xfer.daddr = daddr; + xfer.result = XFER_RESULT_SUCCESS; + xfer.setup = &request; + xfer.user_data = 0; // initial state 0 + cdch_process_set_config(&xfer); + + return true; +} + +static void set_config_complete(cdch_interface_t *p_cdc, bool success) { + if (success) { + const uint8_t idx = get_idx_by_ptr(p_cdc); + p_cdc->mounted = true; + tuh_cdc_mount_cb(idx); + // Prepare for incoming data + tu_edpt_stream_read_xfer(p_cdc->daddr, &p_cdc->stream.rx); + } else { + // clear the interface entry + p_cdc->daddr = 0; + p_cdc->bInterfaceNumber = 0; + } + + // notify usbh that driver enumeration is complete + const uint8_t itf_offset = (p_cdc->serial_drid == SERIAL_DRIVER_ACM) ? 1 : 0; + usbh_driver_set_config_complete(p_cdc->daddr, p_cdc->bInterfaceNumber + itf_offset); +} + +static void cdch_process_set_config(tuh_xfer_t *xfer) { + cdch_interface_t *p_cdc = get_itf_by_xfer(xfer); + TU_ASSERT(p_cdc && p_cdc->serial_drid < SERIAL_DRIVER_COUNT,); + TU_LOG_DRV(" state = %u\r\n", xfer->user_data); + const cdch_serial_driver_t *driver = &serial_drivers[p_cdc->serial_drid]; + + if (!driver->process_set_config(p_cdc, xfer)) { + set_config_complete(p_cdc, false); + } +} + +static bool set_line_state_on_enum(cdch_interface_t *p_cdc, tuh_xfer_t *xfer) { + enum { + ENUM_SET_LINE_CODING = 0, + ENUM_SET_LINE_CONTROL, + ENUM_SET_LINE_COMPLETE, + }; + const uint8_t idx = get_idx_by_ptr(p_cdc); + const uintptr_t state = xfer->user_data; + + switch (state) { + case ENUM_SET_LINE_CODING: { + #ifdef CFG_TUH_CDC_LINE_CODING_ON_ENUM + #if CFG_TUH_CDC_CH34X + // ch34x already set line coding in serial init + if (p_cdc->serial_drid != SERIAL_DRIVER_CH34X) + #endif + { + const cdc_line_coding_t line_coding = (cdc_line_coding_t) CFG_TUH_CDC_LINE_CODING_ON_ENUM; + TU_ASSERT(tuh_cdc_set_line_coding(idx, &line_coding, + cdch_process_line_state_on_enum, ENUM_SET_LINE_CONTROL)); + break; + } + #endif + TU_ATTR_FALLTHROUGH; + } + + case ENUM_SET_LINE_CONTROL: + #ifdef CFG_TUH_CDC_LINE_CONTROL_ON_ENUM + TU_ASSERT(tuh_cdc_set_control_line_state(idx, CFG_TUH_CDC_LINE_CONTROL_ON_ENUM, + cdch_process_line_state_on_enum, ENUM_SET_LINE_COMPLETE)); + break; + #else + TU_ATTR_FALLTHROUGH; + #endif + + case ENUM_SET_LINE_COMPLETE: + set_config_complete(p_cdc, true); + break; + + default: + return false; + } + + return true; +} + +static void cdch_process_line_state_on_enum(tuh_xfer_t *xfer) { + cdch_interface_t *p_cdc = get_itf_by_xfer(xfer); + TU_ASSERT(p_cdc && p_cdc->serial_drid < SERIAL_DRIVER_COUNT,); + if (xfer->result != XFER_RESULT_SUCCESS || !set_line_state_on_enum(p_cdc, xfer)) { + set_config_complete(p_cdc, false); + } +} + + +static void cdch_internal_control_complete(tuh_xfer_t *xfer) { + cdch_interface_t *p_cdc = get_itf_by_xfer(xfer); + TU_ASSERT(p_cdc && p_cdc->serial_drid < SERIAL_DRIVER_COUNT,); + TU_LOG_DRV(" request result = %u\r\n", xfer->result); + const cdch_serial_driver_t *driver = &serial_drivers[p_cdc->serial_drid]; + driver->request_complete(p_cdc, xfer); + + // Invoke application callback + xfer->complete_cb = p_cdc->user_complete_cb; + if (xfer->complete_cb) { + xfer->complete_cb(xfer); + } +} + +static void cdch_set_line_coding_stage1_baudrate_complete(tuh_xfer_t *xfer) { + cdch_interface_t *p_cdc = get_itf_by_xfer(xfer); + TU_ASSERT(p_cdc && p_cdc->serial_drid < SERIAL_DRIVER_COUNT,); + TU_LOG_DRV(" stage1 set baudrate result = %u\r\n", xfer->result); + const cdch_serial_driver_t *driver = &serial_drivers[p_cdc->serial_drid]; + + if (xfer->result == XFER_RESULT_SUCCESS) { + p_cdc->line.coding.bit_rate = p_cdc->requested_line.coding.bit_rate; // update baudrate + TU_ASSERT(driver->set_data_format(p_cdc, cdch_set_line_coding_stage2_data_format_complete, xfer->user_data),); + } else { + xfer->complete_cb = p_cdc->user_complete_cb; + if (xfer->complete_cb) { + xfer->complete_cb(xfer); + } + } +} + +static void cdch_set_line_coding_stage2_data_format_complete(tuh_xfer_t *xfer) { + cdch_interface_t *p_cdc = get_itf_by_xfer(xfer); + TU_ASSERT(p_cdc && p_cdc->serial_drid < SERIAL_DRIVER_COUNT,); + TU_LOG_DRV(" stage2 set data format result = %u\r\n", xfer->result); + + if (xfer->result == XFER_RESULT_SUCCESS) { + p_cdc->line.coding = p_cdc->requested_line.coding; // update data format + } + + xfer->complete_cb = p_cdc->user_complete_cb; + if (xfer->complete_cb) { + xfer->complete_cb(xfer); + } +} + +//--------------------------------------------------------------------+ +// ACM +//--------------------------------------------------------------------+ + +// internal control complete to update state such as line state, encoding +static void acm_internal_control_complete(cdch_interface_t *p_cdc, tuh_xfer_t *xfer) { + TU_VERIFY (xfer->result == XFER_RESULT_SUCCESS,); + const tusb_control_request_t * setup = xfer->setup; + + switch (setup->bRequest) { + case CDC_REQUEST_SET_CONTROL_LINE_STATE: + p_cdc->line.control_state = p_cdc->requested_line.control_state; + break; + + case CDC_REQUEST_SET_LINE_CODING: + p_cdc->line.coding = p_cdc->requested_line.coding; + break; + + default: + break; + } +} + +static bool acm_set_control_line_state(cdch_interface_t *p_cdc, tuh_xfer_cb_t complete_cb, uintptr_t user_data) { + TU_VERIFY(p_cdc->acm.capability.support_line_request); + + const tusb_control_request_t request = { + .bmRequestType_bit = { + .recipient = TUSB_REQ_RCPT_INTERFACE, + .type = TUSB_REQ_TYPE_CLASS, + .direction = TUSB_DIR_OUT + }, + .bRequest = CDC_REQUEST_SET_CONTROL_LINE_STATE, + .wValue = tu_htole16((uint16_t) p_cdc->requested_line.control_state.value), + .wIndex = tu_htole16((uint16_t) p_cdc->bInterfaceNumber), + .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); +} + +static bool acm_set_line_coding(cdch_interface_t *p_cdc, tuh_xfer_cb_t complete_cb, uintptr_t user_data) { + TU_VERIFY(p_cdc->acm.capability.support_line_request); + TU_VERIFY((p_cdc->requested_line.coding.data_bits >= 5 && p_cdc->requested_line.coding.data_bits <= 8) || + p_cdc->requested_line.coding.data_bits == 16); + + tusb_control_request_t const request = { + .bmRequestType_bit = { + .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((uint16_t) p_cdc->bInterfaceNumber), + .wLength = tu_htole16((uint16_t) sizeof(cdc_line_coding_t)) + }; + + // use usbh enum buf to hold line coding since user line_coding variable does not live long enough + uint8_t *enum_buf = usbh_get_enum_buf(); + memcpy(enum_buf, &p_cdc->requested_line.coding, sizeof(cdc_line_coding_t)); + + 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); +} + +//------------- Enumeration -------------// +enum { + CONFIG_ACM_COMPLETE = 0 +}; + +static bool acm_open(uint8_t daddr, tusb_desc_interface_t const *itf_desc, uint16_t max_len) { + uint8_t const *p_desc_end = ((uint8_t const *) itf_desc) + max_len; + + cdch_interface_t *p_cdc = make_new_itf(daddr, itf_desc); + TU_VERIFY(p_cdc); + + p_cdc->serial_drid = SERIAL_DRIVER_ACM; + + //------------- Control Interface -------------// + uint8_t const *p_desc = tu_desc_next(itf_desc); + + // Communication Functional Descriptors + while ((p_desc < p_desc_end) && (TUSB_DESC_CS_INTERFACE == tu_desc_type(p_desc))) { + if (CDC_FUNC_DESC_ABSTRACT_CONTROL_MANAGEMENT == cdc_functional_desc_typeof(p_desc)) { + // save ACM bmCapabilities + p_cdc->acm.capability = ((cdc_desc_func_acm_t const *) p_desc)->bmCapabilities; + } + + p_desc = tu_desc_next(p_desc); + } + + // Open notification endpoint of control interface if any + if (itf_desc->bNumEndpoints == 1) { + TU_ASSERT(TUSB_DESC_ENDPOINT == tu_desc_type(p_desc)); + tusb_desc_endpoint_t const *desc_ep = (tusb_desc_endpoint_t const *) p_desc; + + TU_ASSERT(tuh_edpt_open(daddr, desc_ep)); + p_cdc->ep_notif = desc_ep->bEndpointAddress; + + p_desc = tu_desc_next(p_desc); + } + + //------------- Data Interface (if any) -------------// + if ((TUSB_DESC_INTERFACE == tu_desc_type(p_desc)) && + (TUSB_CLASS_CDC_DATA == ((tusb_desc_interface_t const *) p_desc)->bInterfaceClass)) { + // next to endpoint descriptor + p_desc = tu_desc_next(p_desc); + + // data endpoints expected to be in pairs + TU_ASSERT(open_ep_stream_pair(p_cdc, (tusb_desc_endpoint_t const *) p_desc)); + } + + return true; +} + +static bool acm_process_set_config(cdch_interface_t *p_cdc, tuh_xfer_t *xfer) { + TU_ASSERT(xfer->result == XFER_RESULT_SUCCESS); + (void) p_cdc; + const uintptr_t state = xfer->user_data; + + switch (state) { + case CONFIG_ACM_COMPLETE: { + xfer->user_data = 0; // kick-off set line state on enum + cdch_process_line_state_on_enum(xfer); + break; + } + + default: + return false; // invalid state + } + + return true; +} + +//--------------------------------------------------------------------+ +// FTDI +//--------------------------------------------------------------------+ +#if CFG_TUH_CDC_FTDI + +static bool ftdi_determine_type(cdch_interface_t *p_cdc); +static uint32_t ftdi_get_divisor(cdch_interface_t *p_cdc); + +//------------- Control Request -------------// + +// set request without data +static bool ftdi_set_request(cdch_interface_t *p_cdc, uint8_t request, uint8_t requesttype, + uint16_t value, uint16_t index, tuh_xfer_cb_t complete_cb, uintptr_t user_data) { + tusb_control_request_t const request_setup = { + .bmRequestType = requesttype, + .bRequest = request, + .wValue = tu_htole16(value), + .wIndex = tu_htole16(index), + .wLength = 0 + }; + + tuh_xfer_t xfer = { + .daddr = p_cdc->daddr, + .ep_addr = 0, + .setup = &request_setup, + .buffer = NULL, + .complete_cb = complete_cb, + .user_data = user_data + }; + + return tuh_control_xfer(&xfer); +} + +#ifdef CFG_TUH_CDC_FTDI_LATENCY +static int8_t ftdi_write_latency_timer(cdch_interface_t * p_cdc, uint16_t latency, + tuh_xfer_cb_t complete_cb, uintptr_t user_data) { + if (p_cdc->ftdi.chip_type == FTDI_SIO /* || p_cdc->ftdi.chip_type == FT232A */ ) + return FTDI_NOT_POSSIBLE; + return ftdi_set_request(p_cdc, FTDI_SIO_SET_LATENCY_TIMER_REQUEST, FTDI_SIO_SET_LATENCY_TIMER_REQUEST_TYPE, + latency, p_cdc->ftdi.channel, complete_cb, user_data) ? FTDI_REQUESTED : FTDI_FAIL; +} +#endif + +static inline bool ftdi_sio_reset(cdch_interface_t *p_cdc, tuh_xfer_cb_t complete_cb, uintptr_t user_data) { + return ftdi_set_request(p_cdc, FTDI_SIO_RESET_REQUEST, FTDI_SIO_RESET_REQUEST_TYPE, FTDI_SIO_RESET_SIO, + p_cdc->ftdi.channel, complete_cb, user_data); +} + + +//------------- Driver API -------------// + +// internal control complete to update state such as line state, line_coding +static void ftdi_internal_control_complete(cdch_interface_t* p_cdc, tuh_xfer_t *xfer) { + TU_VERIFY(xfer->result == XFER_RESULT_SUCCESS,); + const tusb_control_request_t * setup = xfer->setup; + if (xfer->result == XFER_RESULT_SUCCESS) { + if (setup->bRequest == FTDI_SIO_SET_MODEM_CTRL_REQUEST && + setup->bmRequestType == FTDI_SIO_SET_MODEM_CTRL_REQUEST_TYPE ) { + p_cdc->line.control_state = p_cdc->requested_line.control_state; + } + if (setup->bRequest == FTDI_SIO_SET_DATA_REQUEST && + setup->bmRequestType == FTDI_SIO_SET_DATA_REQUEST_TYPE ) { + p_cdc->line.coding.stop_bits = p_cdc->requested_line.coding.stop_bits; + p_cdc->line.coding.parity = p_cdc->requested_line.coding.parity; + p_cdc->line.coding.data_bits = p_cdc->requested_line.coding.data_bits; + } + if (setup->bRequest == FTDI_SIO_SET_BAUDRATE_REQUEST && + setup->bmRequestType == FTDI_SIO_SET_BAUDRATE_REQUEST_TYPE ) { + p_cdc->line.coding.bit_rate = p_cdc->requested_line.coding.bit_rate; + } + } +} + +static bool ftdi_set_data_format(cdch_interface_t *p_cdc, tuh_xfer_cb_t complete_cb, uintptr_t user_data) { + TU_VERIFY(p_cdc->requested_line.coding.data_bits >= 7 && p_cdc->requested_line.coding.data_bits <= 8, 0); + uint16_t value = (uint16_t) ((p_cdc->requested_line.coding.data_bits & 0xfUL) | // data bit quantity is stored in bits 0-3 + (p_cdc->requested_line.coding.parity & 0x7UL) << 8 | // parity is stored in bits 8-10, same coding + (p_cdc->requested_line.coding.stop_bits & 0x3UL) << 11); // stop bits quantity is stored in bits 11-12, same coding + // not each FTDI supports 1.5 stop bits + return ftdi_set_request(p_cdc, FTDI_SIO_SET_DATA_REQUEST, FTDI_SIO_SET_DATA_REQUEST_TYPE, + value, p_cdc->ftdi.channel, complete_cb, user_data); +} + +static bool ftdi_set_baudrate(cdch_interface_t *p_cdc, tuh_xfer_cb_t complete_cb, uintptr_t user_data) { + uint32_t index_value = ftdi_get_divisor(p_cdc); + TU_VERIFY(index_value); + uint16_t value = (uint16_t) index_value; + uint16_t index = (uint16_t) (index_value >> 16); + if (p_cdc->ftdi.channel) { + index = (uint16_t) ((index << 8) | p_cdc->ftdi.channel); + } + + return ftdi_set_request(p_cdc, FTDI_SIO_SET_BAUDRATE_REQUEST, FTDI_SIO_SET_BAUDRATE_REQUEST_TYPE, + value, index, complete_cb, user_data); +} + +static bool ftdi_set_modem_ctrl(cdch_interface_t *p_cdc, tuh_xfer_cb_t complete_cb, uintptr_t user_data) { + uint16_t line_state = (uint16_t) ((p_cdc->requested_line.control_state.dtr ? FTDI_SIO_SET_DTR_HIGH : FTDI_SIO_SET_DTR_LOW) | + (p_cdc->requested_line.control_state.rts ? FTDI_SIO_SET_RTS_HIGH : FTDI_SIO_SET_RTS_LOW)); + return ftdi_set_request(p_cdc, FTDI_SIO_SET_MODEM_CTRL_REQUEST, FTDI_SIO_SET_MODEM_CTRL_REQUEST_TYPE, + line_state, p_cdc->ftdi.channel, complete_cb ? cdch_internal_control_complete : NULL, user_data); +} + +//------------- Enumeration -------------// +enum { + CONFIG_FTDI_DETERMINE_TYPE = 0, + CONFIG_FTDI_WRITE_LATENCY, + CONFIG_FTDI_SIO_RESET, + CONFIG_FTDI_FLOW_CONTROL, + 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); + + p_cdc->serial_drid = SERIAL_DRIVER_FTDI; + + // endpoint pair + tusb_desc_endpoint_t const *desc_ep = (tusb_desc_endpoint_t const *) tu_desc_next(itf_desc); + + /* + * NOTE: Some customers have programmed FT232R/FT245R devices + * with an endpoint size of 0 - not good. + */ + TU_ASSERT(desc_ep->wMaxPacketSize != 0); + + // data endpoints expected to be in pairs + return open_ep_stream_pair(p_cdc, desc_ep); +} + +static bool ftdi_proccess_set_config(cdch_interface_t *p_cdc, tuh_xfer_t *xfer) { + TU_ASSERT(xfer->result == XFER_RESULT_SUCCESS); + const uintptr_t state = xfer->user_data; + switch (state) { + // from here sequence overtaken from Linux Kernel function ftdi_port_probe() + case CONFIG_FTDI_DETERMINE_TYPE: + // determine type + if (p_cdc->bInterfaceNumber == 0) { + TU_ASSERT(ftdi_determine_type(p_cdc)); + } else { + // other interfaces have same type as interface 0 + uint8_t const idx_itf0 = tuh_cdc_itf_get_index(xfer->daddr, 0); + cdch_interface_t const *p_cdc_itf0 = get_itf(idx_itf0); + TU_ASSERT(p_cdc_itf0); + p_cdc->ftdi.chip_type = p_cdc_itf0->ftdi.chip_type; + } + TU_ATTR_FALLTHROUGH; + + case CONFIG_FTDI_WRITE_LATENCY: + #ifdef CFG_TUH_CDC_FTDI_LATENCY + int8_t result = ftdi_write_latency_timer(p_cdc, CFG_TUH_CDC_FTDI_LATENCY, ftdi_process_config, + CONFIG_FTDI_SIO_RESET); + TU_ASSERT(result != FTDI_FAIL); + if (result == FTDI_REQUESTED) { + break; + }// else FTDI_NOT_POSSIBLE => continue directly with next state + #endif + TU_ATTR_FALLTHROUGH; + + // from here sequence overtaken from Linux Kernel function ftdi_open() + case CONFIG_FTDI_SIO_RESET: + TU_ASSERT(ftdi_sio_reset(p_cdc, cdch_process_set_config, CONFIG_FTDI_FLOW_CONTROL)); + break; + + case CONFIG_FTDI_FLOW_CONTROL: + // disable flow control + TU_ASSERT(ftdi_set_request(p_cdc, FTDI_SIO_SET_FLOW_CTRL_REQUEST, FTDI_SIO_SET_FLOW_CTRL_REQUEST_TYPE, FTDI_SIO_DISABLE_FLOW_CTRL, + p_cdc->ftdi.channel, cdch_process_set_config, CONFIG_FTDI_COMPLETE)); + break; + + case CONFIG_FTDI_COMPLETE: { + xfer->user_data = 0; // kick-off set line state on enum + cdch_process_line_state_on_enum(xfer); + break; + } + + default: + return false; + } + + return true; +} + +//------------- Helper -------------// + +static bool ftdi_determine_type(cdch_interface_t *p_cdc) { + tusb_desc_device_t desc_dev; + TU_VERIFY(tuh_descriptor_get_device_local(p_cdc->daddr, &desc_dev)); + uint16_t const version = desc_dev.bcdDevice; + uint8_t const itf_num = p_cdc->bInterfaceNumber; + + p_cdc->ftdi.chip_type = FTDI_UNKNOWN; + + /* Assume Hi-Speed type */ + p_cdc->ftdi.channel = CHANNEL_A + itf_num; + + switch (version) { + case 0x200: + // FT232A not supported to keep it simple (no extra _read_latency_timer()) not testable + // p_cdc->ftdi.chip_type = FT232A; + // p_cdc->ftdi.baud_base = 48000000 / 2; + // p_cdc->ftdi.channel = 0; + // /* + // * FT232B devices have a bug where bcdDevice gets set to 0x200 + // * when iSerialNumber is 0. Assume it is an FT232B in case the + // * latency timer is readable. + // */ + // if (desc->iSerialNumber == 0 && + // _read_latency_timer(port) >= 0) { + // p_cdc->ftdi.chip_type = FTDI_FT232B; + // } + break; + + case 0x400 : p_cdc->ftdi.chip_type = FTDI_FT232B; p_cdc->ftdi.channel = 0; break; + case 0x500 : p_cdc->ftdi.chip_type = FTDI_FT2232C; break; + case 0x600 : p_cdc->ftdi.chip_type = FTDI_FT232R; p_cdc->ftdi.channel = 0; break; + case 0x700 : p_cdc->ftdi.chip_type = FTDI_FT2232H; break; + case 0x800 : p_cdc->ftdi.chip_type = FTDI_FT4232H; break; + case 0x900 : p_cdc->ftdi.chip_type = FTDI_FT232H; break; + case 0x1000: p_cdc->ftdi.chip_type = FTDI_FTX; break; + case 0x2800: p_cdc->ftdi.chip_type = FTDI_FT2233HP; break; + case 0x2900: p_cdc->ftdi.chip_type = FTDI_FT4233HP; break; + case 0x3000: p_cdc->ftdi.chip_type = FTDI_FT2232HP; break; + case 0x3100: p_cdc->ftdi.chip_type = FTDI_FT4232HP; break; + case 0x3200: p_cdc->ftdi.chip_type = FTDI_FT233HP; break; + case 0x3300: p_cdc->ftdi.chip_type = FTDI_FT232HP; break; + case 0x3600: p_cdc->ftdi.chip_type = FTDI_FT4232HA; break; + + default: + if (version < 0x200) { + p_cdc->ftdi.chip_type = FTDI_SIO; + p_cdc->ftdi.channel = 0; + } + break; + } + + #if CFG_TUSB_DEBUG >= CFG_TUH_CDC_LOG_LEVEL + const char * ftdi_chip_name[] = { FTDI_CHIP_NAMES }; + TU_LOG_CDC(p_cdc, "%s detected (bcdDevice = 0x%04x)", + ftdi_chip_name[p_cdc->ftdi.chip_type], version); + #endif + + return (p_cdc->ftdi.chip_type != FTDI_UNKNOWN); +} + +// FT232A not supported +//static uint32_t ftdi_232am_baud_base_to_divisor(uint32_t baud, uint32_t base) +//{ +// uint32_t divisor; +// /* divisor shifted 3 bits to the left */ +// uint32_t divisor3 = DIV_ROUND_CLOSEST(base, 2 * baud); +// if ((divisor3 & 0x7) == 7) +// divisor3++; /* round x.7/8 up to x+1 */ +// divisor = divisor3 >> 3; +// divisor3 &= 0x7; +// if (divisor3 == 1) +// divisor |= 0xc000; /* +0.125 */ +// else if (divisor3 >= 4) +// divisor |= 0x4000; /* +0.5 */ +// else if (divisor3 != 0) +// divisor |= 0x8000; /* +0.25 */ +// else if (divisor == 1) +// divisor = 0; /* special case for maximum baud rate */ +// return divisor; +//} + +// FT232A not supported +//static inline uint32_t ftdi_232am_baud_to_divisor(uint32_t baud) +//{ +// return ftdi_232am_baud_base_to_divisor(baud, (uint32_t) 48000000); +//} + +static uint32_t ftdi_232bm_baud_base_to_divisor(uint32_t baud, uint32_t base) { + uint8_t divfrac[8] = {0, 3, 2, 4, 1, 5, 6, 7}; + uint32_t divisor; + /* divisor shifted 3 bits to the left */ + uint32_t divisor3 = DIV_ROUND_CLOSEST(base, 2 * baud); + divisor = divisor3 >> 3; + divisor |= (uint32_t) divfrac[divisor3 & 0x7] << 14; + /* Deal with special cases for highest baud rates. */ + if (divisor == 1) /* 1.0 */ { + divisor = 0; + } else if (divisor == 0x4001) /* 1.5 */ { + divisor = 1; + } + return divisor; +} + +static inline uint32_t ftdi_232bm_baud_to_divisor(uint32_t baud) { + return ftdi_232bm_baud_base_to_divisor(baud, 48000000); +} + +static uint32_t ftdi_2232h_baud_base_to_divisor(uint32_t baud, uint32_t base) { + static const unsigned char divfrac[8] = {0, 3, 2, 4, 1, 5, 6, 7}; + uint32_t divisor; + uint32_t divisor3; + + /* hi-speed baud rate is 10-bit sampling instead of 16-bit */ + divisor3 = DIV_ROUND_CLOSEST(8 * base, 10 * 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; + } + /* + * Set this bit to turn off a divide by 2.5 on baud rate generator + * This enables baud rates up to 12Mbaud but cannot reach below 1200 + * baud with this bit set + */ + divisor |= 0x00020000; + return divisor; +} + +static inline uint32_t ftdi_2232h_baud_to_divisor(uint32_t baud) { + return ftdi_2232h_baud_base_to_divisor(baud, (uint32_t) 120000000); +} + +static inline uint32_t ftdi_get_divisor(cdch_interface_t *p_cdc) { + uint32_t baud = p_cdc->requested_line.coding.bit_rate; + uint32_t div_value = 0; + TU_VERIFY(baud); + + switch (p_cdc->ftdi.chip_type) { + case FTDI_UNKNOWN: + return 0; + case FTDI_SIO: + switch (baud) { + case 300: div_value = ftdi_sio_b300; break; + case 600: div_value = ftdi_sio_b600; break; + case 1200: div_value = ftdi_sio_b1200; break; + case 2400: div_value = ftdi_sio_b2400; break; + case 4800: div_value = ftdi_sio_b4800; break; + case 9600: div_value = ftdi_sio_b9600; break; + case 19200: div_value = ftdi_sio_b19200; break; + case 38400: div_value = ftdi_sio_b38400; break; + case 57600: div_value = ftdi_sio_b57600; break; + case 115200: div_value = ftdi_sio_b115200; break; + default: + // Baudrate not supported + return 0; + break; + } + break; + // FT232A not supported + // case FT232A: + // if (baud <= 3000000) { + // div_value = ftdi_232am_baud_to_divisor(baud); + // } else { + // // Baud rate too high! + // baud = 9600; + // div_value = ftdi_232am_baud_to_divisor(9600); + // div_okay = false; + // } + // break; + case FTDI_FT232B: + case FTDI_FT2232C: + case FTDI_FT232R: + case FTDI_FTX: + TU_VERIFY(baud <= 3000000); // else Baud rate too high! + div_value = ftdi_232bm_baud_to_divisor(baud); + break; + case FTDI_FT232H: + case FTDI_FT2232H: + case FTDI_FT4232H: + case FTDI_FT4232HA: + case FTDI_FT232HP: + case FTDI_FT233HP: + case FTDI_FT2232HP: + case FTDI_FT2233HP: + case FTDI_FT4232HP: + case FTDI_FT4233HP: + default: + TU_VERIFY(baud <= 12000000); // else Baud rate too high! + if (baud >= 1200) { + div_value = ftdi_2232h_baud_to_divisor(baud); + } else { + div_value = ftdi_232bm_baud_to_divisor(baud); + } + break; + } + + TU_LOG_CDC(p_cdc, "Baudrate divisor = 0x%lu", div_value); + + return div_value; +} + +#endif + +//--------------------------------------------------------------------+ +// CP210x +//--------------------------------------------------------------------+ +#if CFG_TUH_CDC_CP210X + +//------------- Control Request -------------// + +static bool cp210x_set_request(cdch_interface_t * p_cdc, uint8_t command, uint16_t value, + uint8_t * buffer, uint16_t length, tuh_xfer_cb_t complete_cb, uintptr_t user_data) { + tusb_control_request_t const request = { + .bmRequestType_bit = { + .recipient = TUSB_REQ_RCPT_INTERFACE, + .type = TUSB_REQ_TYPE_VENDOR, + .direction = TUSB_DIR_OUT + }, + .bRequest = command, + .wValue = tu_htole16(value), + .wIndex = tu_htole16((uint16_t) p_cdc->bInterfaceNumber), + .wLength = tu_htole16(length) + }; + + // use usbh enum buf since application variable does not live long enough + uint8_t * enum_buf = NULL; + + 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); +} + +TU_ATTR_ALWAYS_INLINE static inline bool cp210x_ifc_enable(cdch_interface_t *p_cdc, uint16_t enabled, tuh_xfer_cb_t complete_cb, uintptr_t user_data) { + return cp210x_set_request(p_cdc, CP210X_IFC_ENABLE, enabled, NULL, 0, complete_cb, user_data); +} + +TU_ATTR_ALWAYS_INLINE static inline bool cp210x_set_mhs(cdch_interface_t *p_cdc, tuh_xfer_cb_t complete_cb, uintptr_t user_data) { + // CP210x has the same bit coding + return cp210x_set_request(p_cdc, CP210X_SET_MHS, + (uint16_t) (CP210X_CONTROL_WRITE_DTR | CP210X_CONTROL_WRITE_RTS | p_cdc->requested_line.control_state.value), + NULL, 0, complete_cb, user_data); +} + +//------------- Driver API -------------// + +// internal control complete to update state such as line state, encoding +static void cp210x_internal_control_complete(cdch_interface_t *p_cdc, tuh_xfer_t *xfer) { + TU_VERIFY(xfer->result == XFER_RESULT_SUCCESS,); + switch (xfer->setup->bRequest) { + case CP210X_SET_MHS: + p_cdc->line.control_state = p_cdc->requested_line.control_state; + break; + + case CP210X_SET_LINE_CTL: + p_cdc->line.coding.stop_bits = p_cdc->requested_line.coding.stop_bits; + p_cdc->line.coding.parity = p_cdc->requested_line.coding.parity; + p_cdc->line.coding.data_bits = p_cdc->requested_line.coding.data_bits; + break; + + case CP210X_SET_BAUDRATE: + p_cdc->line.coding.bit_rate = p_cdc->requested_line.coding.bit_rate; + break; + + default: break; + } +} + +static bool cp210x_set_baudrate(cdch_interface_t *p_cdc, tuh_xfer_cb_t complete_cb, uintptr_t user_data) { + // Not every baud rate is supported. See datasheets and AN205 "CP210x Baud Rate Support" + uint32_t baud_le = tu_htole32(p_cdc->requested_line.coding.bit_rate); + return cp210x_set_request(p_cdc, CP210X_SET_BAUDRATE, 0, (uint8_t *) &baud_le, 4, complete_cb, user_data); +} + +static bool cp210x_set_data_format(cdch_interface_t *p_cdc, tuh_xfer_cb_t complete_cb, uintptr_t user_data) { + TU_VERIFY(p_cdc->requested_line.coding.data_bits >= 5 && p_cdc->requested_line.coding.data_bits <= 8, 0); + uint16_t lcr = (uint16_t) ((p_cdc->requested_line.coding.data_bits & 0xfUL) << 8 | // data bit quantity is stored in bits 8-11 + (p_cdc->requested_line.coding.parity & 0xfUL) << 4 | // parity is stored in bits 4-7, same coding + (p_cdc->requested_line.coding.stop_bits & 0xfUL)); // parity is stored in bits 0-3, same coding + + return cp210x_set_request(p_cdc, CP210X_SET_LINE_CTL, lcr, NULL, 0, complete_cb, user_data); +} + +static bool cp210x_set_modem_ctrl(cdch_interface_t *p_cdc, tuh_xfer_cb_t complete_cb, uintptr_t user_data) { + return cp210x_set_mhs(p_cdc, complete_cb, user_data); +} + +//------------- Enumeration -------------// + +enum { + CONFIG_CP210X_IFC_ENABLE = 0, + 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); + + p_cdc->serial_drid = SERIAL_DRIVER_CP210X; + + // endpoint pair + tusb_desc_endpoint_t const *desc_ep = (tusb_desc_endpoint_t const *) tu_desc_next(itf_desc); + + // data endpoints expected to be in pairs + return open_ep_stream_pair(p_cdc, desc_ep); +} + +static bool cp210x_process_set_config(cdch_interface_t *p_cdc, tuh_xfer_t *xfer) { + TU_ASSERT(xfer->result == XFER_RESULT_SUCCESS); + const uintptr_t state = xfer->user_data; + + switch (state) { + case CONFIG_CP210X_IFC_ENABLE: + TU_ASSERT(cp210x_ifc_enable(p_cdc, CP210X_UART_ENABLE, cdch_process_set_config, CONFIG_CP210X_COMPLETE)); + break; + + case CONFIG_CP210X_COMPLETE: + xfer->user_data = 0;// kick-off set line state on enum + cdch_process_line_state_on_enum(xfer); + break; + + default: + return false; + } + + return true; +} + +#endif + +//--------------------------------------------------------------------+ +// CH34x (CH340 & CH341) +//--------------------------------------------------------------------+ + +#if CFG_TUH_CDC_CH34X + +static uint8_t ch34x_get_lcr(cdch_interface_t *p_cdc); +static uint16_t ch34x_get_divisor_prescaler(cdch_interface_t *p_cdc); + +//------------- Control Request -------------// + +static bool ch34x_set_request(cdch_interface_t *p_cdc, uint8_t direction, uint8_t request, + uint16_t value, uint16_t index, uint8_t *buffer, uint16_t length, + tuh_xfer_cb_t complete_cb, uintptr_t user_data) { + tusb_control_request_t const request_setup = { + .bmRequestType_bit = { + .recipient = TUSB_REQ_RCPT_DEVICE, + .type = TUSB_REQ_TYPE_VENDOR, + .direction = direction & 0x01u + }, + .bRequest = request, + .wValue = tu_htole16(value), + .wIndex = tu_htole16(index), + .wLength = tu_htole16(length) + }; + + // use usbh enum buf since application variable does not live long enough + uint8_t *enum_buf = NULL; + + if (buffer && length > 0) { + enum_buf = usbh_get_enum_buf(); + if (direction == TUSB_DIR_OUT) { + tu_memcpy_s(enum_buf, CFG_TUH_ENUMERATION_BUFSIZE, buffer, length); + } + } + + tuh_xfer_t xfer = { + .daddr = p_cdc->daddr, + .ep_addr = 0, + .setup = &request_setup, + .buffer = enum_buf, + .complete_cb = complete_cb, + .user_data = user_data + }; + + return tuh_control_xfer(&xfer); +} + +TU_ATTR_ALWAYS_INLINE static inline bool ch34x_control_out(cdch_interface_t *p_cdc, uint8_t request, uint16_t value, uint16_t index, + tuh_xfer_cb_t complete_cb, uintptr_t user_data) { + return ch34x_set_request(p_cdc, TUSB_DIR_OUT, request, value, index, NULL, 0, complete_cb, user_data); +} + +TU_ATTR_ALWAYS_INLINE static inline bool ch34x_control_in(cdch_interface_t *p_cdc, uint8_t request, uint16_t value, uint16_t index, + uint8_t *buffer, uint16_t buffersize, tuh_xfer_cb_t complete_cb, uintptr_t user_data) { + return ch34x_set_request(p_cdc, TUSB_DIR_IN, request, value, index, buffer, buffersize, + complete_cb, user_data); +} + +TU_ATTR_ALWAYS_INLINE static inline bool ch34x_write_reg(cdch_interface_t *p_cdc, uint16_t reg, uint16_t reg_value, + tuh_xfer_cb_t complete_cb, uintptr_t user_data) { + return ch34x_control_out(p_cdc, CH34X_REQ_WRITE_REG, reg, reg_value, complete_cb, user_data); +} + +//static bool ch34x_read_reg_request ( cdch_interface_t * p_cdc, uint16_t reg, +// uint8_t *buffer, uint16_t buffersize, tuh_xfer_cb_t complete_cb, uintptr_t user_data ) +//{ +// return ch34x_control_in ( p_cdc, CH34X_REQ_READ_REG, reg, 0, buffer, buffersize, complete_cb, user_data ); +//} + +//------------- Driver API -------------// + +// internal control complete to update state such as line state, encoding +static void ch34x_internal_control_complete(cdch_interface_t *p_cdc, tuh_xfer_t *xfer) { + TU_VERIFY(xfer->result == XFER_RESULT_SUCCESS,); + switch (xfer->setup->bRequest) { + case CH34X_REQ_WRITE_REG: + // register write request + switch (tu_le16toh(xfer->setup->wValue)) { + case CH34X_REG16_DIVISOR_PRESCALER: + // baudrate + p_cdc->line.coding.bit_rate = p_cdc->requested_line.coding.bit_rate; + break; + + case CH32X_REG16_LCR2_LCR: + // data format + p_cdc->line.coding.stop_bits = p_cdc->requested_line.coding.stop_bits; + p_cdc->line.coding.parity = p_cdc->requested_line.coding.parity; + p_cdc->line.coding.data_bits = p_cdc->requested_line.coding.data_bits; + break; + + default: break; + } + break; + + case CH34X_REQ_MODEM_CTRL: + p_cdc->line.control_state = p_cdc->requested_line.control_state; + break; + + default: break; + } +} + +static bool ch34x_set_data_format(cdch_interface_t *p_cdc, tuh_xfer_cb_t complete_cb, uintptr_t user_data) { + const uint8_t lcr = ch34x_get_lcr(p_cdc); + TU_VERIFY(lcr); + return ch34x_write_reg(p_cdc, CH32X_REG16_LCR2_LCR, lcr, complete_cb, user_data); +} + +static bool ch34x_set_baudrate(cdch_interface_t *p_cdc, tuh_xfer_cb_t complete_cb, uintptr_t user_data) { + const uint16_t div_ps = ch34x_get_divisor_prescaler(p_cdc); + TU_VERIFY(div_ps); + return ch34x_write_reg(p_cdc, CH34X_REG16_DIVISOR_PRESCALER, div_ps, complete_cb, user_data); +} + +static bool ch34x_set_modem_ctrl(cdch_interface_t * p_cdc, tuh_xfer_cb_t complete_cb, uintptr_t user_data) { + // CH34x signals are inverted + uint8_t control = ~((p_cdc->requested_line.control_state.rts ? CH34X_BIT_RTS : 0) | + (p_cdc->requested_line.control_state.dtr ? CH34X_BIT_DTR : 0)); + return ch34x_control_out(p_cdc, CH34X_REQ_MODEM_CTRL, control, 0, complete_cb, user_data); +} + +//------------- Enumeration -------------// + +enum { + CONFIG_CH34X_READ_VERSION = 0, + CONFIG_CH34X_SERIAL_INIT, + CONFIG_CH34X_SPECIAL_REG_WRITE, + CONFIG_CH34X_FLOW_CONTROL, + CONFIG_CH34X_COMPLETE +}; + +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); + + cdch_interface_t * p_cdc = make_new_itf(daddr, itf_desc); + TU_VERIFY(p_cdc); + + p_cdc->serial_drid = SERIAL_DRIVER_CH34X; + + tusb_desc_endpoint_t const * desc_ep = (tusb_desc_endpoint_t const *) tu_desc_next(itf_desc); + + // data endpoints expected to be in pairs + TU_ASSERT(open_ep_stream_pair(p_cdc, desc_ep)); + 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; +} + +static bool ch34x_process_set_config(cdch_interface_t *p_cdc, tuh_xfer_t *xfer) { + TU_ASSERT(xfer->result == XFER_RESULT_SUCCESS); + const uintptr_t state = xfer->user_data; + + switch (state) { + case CONFIG_CH34X_READ_VERSION: { + uint8_t* enum_buf = usbh_get_enum_buf(); + TU_ASSERT(ch34x_control_in(p_cdc, CH34X_REQ_READ_VERSION, 0, 0, enum_buf, 2, + cdch_process_set_config, CONFIG_CH34X_SERIAL_INIT)); + break; + } + + case CONFIG_CH34X_SERIAL_INIT: { + // handle version read data, set CH34x line coding (incl. baudrate) + uint8_t const version = xfer->buffer[0]; + TU_LOG_CDC(p_cdc, "Chip Version = 0x%02x", version); + // only versions >= 0x30 are tested, below 0x30 seems having other programming + // see drivers from WCH vendor, Linux kernel and FreeBSD + if (version >= 0x30) { + // init CH34x with line coding + p_cdc->requested_line.coding = (cdc_line_coding_t) CFG_TUH_CDC_LINE_CODING_ON_ENUM_CH34X; + uint16_t const div_ps = ch34x_get_divisor_prescaler(p_cdc); + uint8_t const lcr = ch34x_get_lcr(p_cdc); + TU_ASSERT(div_ps != 0 && lcr != 0); + TU_ASSERT(ch34x_control_out(p_cdc, CH34X_REQ_SERIAL_INIT, tu_u16(lcr, 0x9c), div_ps, + cdch_process_set_config, CONFIG_CH34X_SPECIAL_REG_WRITE)); + } + break; + } + + case CONFIG_CH34X_SPECIAL_REG_WRITE: + // overtake line coding and do special reg write, purpose unknown, overtaken from WCH driver + p_cdc->line.coding = (cdc_line_coding_t) CFG_TUH_CDC_LINE_CODING_ON_ENUM_CH34X; + TU_ASSERT(ch34x_write_reg(p_cdc, TU_U16(CH341_REG_0x0F, CH341_REG_0x2C), 0x0007, + cdch_process_set_config, CONFIG_CH34X_FLOW_CONTROL)); + break; + + case CONFIG_CH34X_FLOW_CONTROL: + // no hardware flow control + TU_ASSERT(ch34x_write_reg(p_cdc, TU_U16(CH341_REG_0x27, CH341_REG_0x27), 0x0000, + cdch_process_set_config, CONFIG_CH34X_COMPLETE)); + break; + + case CONFIG_CH34X_COMPLETE: + xfer->user_data = 0; // kick-off set line state on enum + cdch_process_line_state_on_enum(xfer); + break; + + default: + return false; + } + + return true; +} + +//------------- Helper -------------// + +// calculate divisor and prescaler for baudrate, return it as 16-bit combined value +static uint16_t ch34x_get_divisor_prescaler(cdch_interface_t *p_cdc) { + uint32_t const baval = p_cdc->requested_line.coding.bit_rate; + uint8_t a; + uint8_t b; + uint32_t c; + + TU_VERIFY(baval != 0 && baval <= 2000000, 0); + 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 = (uint8_t) (c / baval); + if (a == 0 || a == 0xFF) { + return 0; + } + if ((c / a - baval) > (baval - c / (a + 1))) { + a++; + } + a = (uint8_t) (256 - a); + break; + } + + // reg divisor = a, reg prescaler = b + // According to linux code we need to set bit 7 of UCHCOM_REG_BPS_PRE, + // otherwise the chip will buffer data. + return (uint16_t) ((uint16_t) a << 8 | 0x80 | b); +} + +// calculate lcr value from data coding +static uint8_t ch34x_get_lcr(cdch_interface_t *p_cdc) { + uint8_t const stop_bits = p_cdc->requested_line.coding.stop_bits; + uint8_t const parity = p_cdc->requested_line.coding.parity; + uint8_t const data_bits = p_cdc->requested_line.coding.data_bits; + + uint8_t lcr = CH34X_LCR_ENABLE_RX | CH34X_LCR_ENABLE_TX; + TU_VERIFY(data_bits >= 5 && data_bits <= 8); + lcr |= (uint8_t) (data_bits - 5); + + switch (parity) { + case CDC_LINE_CODING_PARITY_NONE: + break; + + case CDC_LINE_CODING_PARITY_ODD: + lcr |= CH34X_LCR_ENABLE_PAR; + break; + + case CDC_LINE_CODING_PARITY_EVEN: + lcr |= CH34X_LCR_ENABLE_PAR | CH34X_LCR_PAR_EVEN; + break; + + case CDC_LINE_CODING_PARITY_MARK: + lcr |= CH34X_LCR_ENABLE_PAR | CH34X_LCR_MARK_SPACE; + break; + + case CDC_LINE_CODING_PARITY_SPACE: + lcr |= CH34X_LCR_ENABLE_PAR | CH34X_LCR_MARK_SPACE | CH34X_LCR_PAR_EVEN; + break; + + default: break; + } + + // 1.5 stop bits not supported + TU_VERIFY(stop_bits != CDC_LINE_CODING_STOP_BITS_1_5); + if (stop_bits == CDC_LINE_CODING_STOP_BITS_2) { + lcr |= CH34X_LCR_STOP_BITS_2; + } + + return lcr; +} + +#endif // CFG_TUH_CDC_CH34X + +//--------------------------------------------------------------------+ +// PL2303 +//--------------------------------------------------------------------+ +#if CFG_TUH_CDC_PL2303 + +static pl2303_type_t pl2303_detect_type(cdch_interface_t *p_cdc, uint8_t step); +static bool pl2303_encode_baud_rate(cdch_interface_t *p_cdc, uint8_t buf[PL2303_LINE_CODING_BAUDRATE_BUFSIZE]); + +//------------- Control Request -------------// +static bool pl2303_set_request(cdch_interface_t *p_cdc, uint8_t request, uint8_t requesttype, + uint16_t value, uint16_t index, uint8_t *buffer, uint16_t length, + tuh_xfer_cb_t complete_cb, uintptr_t user_data) { + tusb_control_request_t const request_setup = { + .bmRequestType = requesttype, + .bRequest = request, + .wValue = tu_htole16(value), + .wIndex = tu_htole16(index), + .wLength = tu_htole16(length) + }; + + // use usbh enum buf since application variable does not live long enough + uint8_t *enum_buf = NULL; + + if (buffer && length > 0) { + enum_buf = usbh_get_enum_buf(); + if (request_setup.bmRequestType_bit.direction == TUSB_DIR_OUT) { + tu_memcpy_s(enum_buf, CFG_TUH_ENUMERATION_BUFSIZE, buffer, length); + } + } + + tuh_xfer_t xfer = { + .daddr = p_cdc->daddr, + .ep_addr = 0, + .setup = &request_setup, + .buffer = enum_buf, + .complete_cb = complete_cb, + .user_data = user_data + }; + + return tuh_control_xfer(&xfer); +} + +static bool pl2303_vendor_read(cdch_interface_t *p_cdc, uint16_t value, uint8_t *buf, + tuh_xfer_cb_t complete_cb, uintptr_t user_data) { + uint8_t request = p_cdc->pl2303.type == PL2303_TYPE_HXN ? PL2303_VENDOR_READ_NREQUEST : PL2303_VENDOR_READ_REQUEST; + return pl2303_set_request(p_cdc, request, PL2303_VENDOR_READ_REQUEST_TYPE, value, 0, buf, 1, complete_cb, user_data); +} + +static bool pl2303_vendor_write(cdch_interface_t *p_cdc, uint16_t value, uint16_t index, + tuh_xfer_cb_t complete_cb, uintptr_t user_data) { + uint8_t request = p_cdc->pl2303.type == PL2303_TYPE_HXN ? PL2303_VENDOR_WRITE_NREQUEST : PL2303_VENDOR_WRITE_REQUEST; + return pl2303_set_request(p_cdc, request, PL2303_VENDOR_WRITE_REQUEST_TYPE, value, index, NULL, 0, complete_cb, user_data); +} + +static inline bool pl2303_supports_hx_status(cdch_interface_t *p_cdc, tuh_xfer_cb_t complete_cb, uintptr_t user_data) { + uint8_t buf = 0; + return pl2303_set_request(p_cdc, PL2303_VENDOR_READ_REQUEST, PL2303_VENDOR_READ_REQUEST_TYPE, PL2303_READ_TYPE_HX_STATUS, 0, + &buf, 1, complete_cb, user_data); +} + +//static bool pl2303_get_line_request(cdch_interface_t * p_cdc, uint8_t buf[PL2303_LINE_CODING_BUFSIZE]) { +// return pl2303_set_request(p_cdc, PL2303_GET_LINE_REQUEST, PL2303_GET_LINE_REQUEST_TYPE, 0, 0, buf, PL2303_LINE_CODING_BUFSIZE); +//} + +//static bool pl2303_set_break(cdch_interface_t * p_cdc, bool enable) { +// uint16_t state = enable ? PL2303_BREAK_ON : PL2303_BREAK_OFF; +// return pl2303_set_request(p_cdc, PL2303_BREAK_REQUEST, PL2303_BREAK_REQUEST_TYPE, state, 0, NULL, 0); +//} + +static inline int pl2303_clear_halt(cdch_interface_t *p_cdc, uint8_t endp, tuh_xfer_cb_t complete_cb, uintptr_t user_data) { + /* we don't care if it wasn't halted first. in fact some devices + * (like some ibmcam model 1 units) seem to expect hosts to make + * this request for iso endpoints, which can't halt! + */ + return pl2303_set_request(p_cdc, TUSB_REQ_CLEAR_FEATURE, PL2303_CLEAR_HALT_REQUEST_TYPE, TUSB_REQ_FEATURE_EDPT_HALT, endp, + NULL, 0, complete_cb, user_data); +} + +//------------- Driver API -------------// + +// internal control complete to update state such as line state, encoding +static void pl2303_internal_control_complete(cdch_interface_t *p_cdc, tuh_xfer_t *xfer) { + TU_VERIFY(xfer->result == XFER_RESULT_SUCCESS,); + if (xfer->setup->bRequest == PL2303_SET_LINE_REQUEST && + xfer->setup->bmRequestType == PL2303_SET_LINE_REQUEST_TYPE) { + p_cdc->line.coding = p_cdc->requested_line.coding; + } + if (xfer->setup->bRequest == PL2303_SET_CONTROL_REQUEST && + xfer->setup->bmRequestType == PL2303_SET_CONTROL_REQUEST_TYPE) { + p_cdc->line.control_state = p_cdc->requested_line.control_state; + } +} + +static bool pl2303_set_line_coding(cdch_interface_t *p_cdc, tuh_xfer_cb_t complete_cb, uintptr_t user_data) { + // the caller has to precheck, that the new line coding different than the current, else false returned + uint8_t buf[PL2303_LINE_CODING_BUFSIZE]; + /* + * Some PL2303 are known to lose bytes if you change serial settings + * even to the same values as before. Thus we actually need to filter + * in this specific case. + */ + TU_VERIFY(p_cdc->requested_line.coding.data_bits != p_cdc->line.coding.data_bits || + p_cdc->requested_line.coding.stop_bits != p_cdc->line.coding.stop_bits || + p_cdc->requested_line.coding.parity != p_cdc->line.coding.parity || + p_cdc->requested_line.coding.bit_rate != p_cdc->line.coding.bit_rate ); + + /* For reference buf[6] data bits value */ + TU_VERIFY(p_cdc->requested_line.coding.data_bits >= 5 && p_cdc->requested_line.coding.data_bits <= 8, 0); + buf[6] = p_cdc->requested_line.coding.data_bits; + + /* For reference buf[0]:buf[3] baud rate value */ + TU_VERIFY(pl2303_encode_baud_rate(p_cdc, &buf[0])); + + /* For reference buf[4]=0 is 1 stop bits */ + /* For reference buf[4]=1 is 1.5 stop bits */ + /* For reference buf[4]=2 is 2 stop bits */ + buf[4] = p_cdc->requested_line.coding.stop_bits; // PL2303 has the same coding + + /* For reference buf[5]=0 is none parity */ + /* For reference buf[5]=1 is odd parity */ + /* For reference buf[5]=2 is even parity */ + /* For reference buf[5]=3 is mark parity */ + /* For reference buf[5]=4 is space parity */ + buf[5] = p_cdc->requested_line.coding.parity; // PL2303 has the same coding + + return pl2303_set_request(p_cdc, PL2303_SET_LINE_REQUEST, PL2303_SET_LINE_REQUEST_TYPE, 0, 0, + buf, PL2303_LINE_CODING_BUFSIZE, complete_cb, user_data); +} + +static bool pl2303_set_modem_ctrl(cdch_interface_t *p_cdc, tuh_xfer_cb_t complete_cb, uintptr_t user_data) { + // PL2303 has the same bit coding + return pl2303_set_request(p_cdc, PL2303_SET_CONTROL_REQUEST, PL2303_SET_CONTROL_REQUEST_TYPE, + p_cdc->requested_line.control_state.value, 0, NULL, 0, complete_cb, user_data); +} + +//------------- Enumeration -------------// + +enum { + CONFIG_PL2303_DETECT_TYPE = 0, + CONFIG_PL2303_READ1, + CONFIG_PL2303_WRITE1, + CONFIG_PL2303_READ2, + CONFIG_PL2303_READ3, + CONFIG_PL2303_READ4, + CONFIG_PL2303_WRITE2, + CONFIG_PL2303_READ5, + CONFIG_PL2303_READ6, + CONFIG_PL2303_WRITE3, + CONFIG_PL2303_WRITE4, + CONFIG_PL2303_WRITE5, + CONFIG_PL2303_RESET_ENDP1, + CONFIG_PL2303_RESET_ENDP2, +// CONFIG_PL2303_FLOW_CTRL_READ, +// CONFIG_PL2303_FLOW_CTRL_WRITE, + CONFIG_PL2303_COMPLETE +}; + +static bool pl2303_open(uint8_t daddr, tusb_desc_interface_t const *itf_desc, uint16_t max_len) { + // PL2303 Interface includes 1 vendor interface + 1 interrupt endpoints + 2 bulk + TU_VERIFY(itf_desc->bNumEndpoints == 3); + TU_VERIFY(sizeof(tusb_desc_interface_t) + 3 * sizeof(tusb_desc_endpoint_t) <= max_len); + + cdch_interface_t *p_cdc = make_new_itf(daddr, itf_desc); + TU_VERIFY(p_cdc); + + p_cdc->serial_drid = SERIAL_DRIVER_PL2303; + p_cdc->pl2303.quirks = 0; + p_cdc->pl2303.supports_hx_status = false; + + tusb_desc_endpoint_t const *desc_ep = (tusb_desc_endpoint_t const *) tu_desc_next(itf_desc); + + // Interrupt endpoint: not used for now + TU_ASSERT(TUSB_DESC_ENDPOINT == tu_desc_type(desc_ep) && + TUSB_XFER_INTERRUPT == desc_ep->bmAttributes.xfer); + TU_ASSERT(tuh_edpt_open(daddr, desc_ep)); + p_cdc->ep_notif = desc_ep->bEndpointAddress; + desc_ep += 1; + + // data endpoints expected to be in pairs + TU_ASSERT(open_ep_stream_pair(p_cdc, desc_ep)); + + return true; +} + +static bool pl2303_process_set_config(cdch_interface_t *p_cdc, tuh_xfer_t *xfer) { + // state CONFIG_PL2303_READ1 may have no success due to expected stall by pl2303_supports_hx_status() + const uintptr_t state = xfer->user_data; + TU_ASSERT(xfer->result == XFER_RESULT_SUCCESS || state == CONFIG_PL2303_READ1); + uint8_t* enum_buf = usbh_get_enum_buf(); + pl2303_type_t type; + + switch (state) { + // from here sequence overtaken from Linux Kernel function pl2303_startup() + case CONFIG_PL2303_DETECT_TYPE: + // get type and quirks (step 1) + type = pl2303_detect_type(p_cdc, 1); + TU_ASSERT(type != PL2303_TYPE_UNKNOWN); + if (type == PL2303_TYPE_NEED_SUPPORTS_HX_STATUS) { + TU_ASSERT(pl2303_supports_hx_status(p_cdc, cdch_process_set_config, CONFIG_PL2303_READ1)); + break; + } else { + // no transfer triggered and continue with CONFIG_PL2303_READ1 + TU_ATTR_FALLTHROUGH; + } + + case CONFIG_PL2303_READ1: + // get supports_hx_status, type and quirks (step 2), do special read + // will not be true, if coming directly from previous case + if (xfer->user_data == CONFIG_PL2303_READ1 && xfer->result == XFER_RESULT_SUCCESS) { + p_cdc->pl2303.supports_hx_status = true; + } + type = pl2303_detect_type(p_cdc, 2); // step 2 now with supports_hx_status + TU_ASSERT(type != PL2303_TYPE_UNKNOWN); + TU_LOG_DRV(" PL2303 type detected: %u\r\n", type); + + p_cdc->pl2303.type = type; + p_cdc->pl2303.quirks |= pl2303_type_data[type].quirks; + + // purpose unknown, overtaken from Linux Kernel driver + if (p_cdc->pl2303.type != PL2303_TYPE_HXN) { + TU_ASSERT(pl2303_vendor_read(p_cdc, 0x8484, enum_buf, cdch_process_set_config, CONFIG_PL2303_WRITE1)); + break; + }// else: continue with next step + TU_ATTR_FALLTHROUGH; + + case CONFIG_PL2303_WRITE1: + // purpose unknown, overtaken from Linux Kernel driver + if (p_cdc->pl2303.type != PL2303_TYPE_HXN) { + TU_ASSERT(pl2303_vendor_write(p_cdc, 0x0404, 0, cdch_process_set_config, CONFIG_PL2303_READ2)); + break; + }// else: continue with next step + TU_ATTR_FALLTHROUGH; + + case CONFIG_PL2303_READ2: + // purpose unknown, overtaken from Linux Kernel driver + if (p_cdc->pl2303.type != PL2303_TYPE_HXN) { + TU_ASSERT(pl2303_vendor_read(p_cdc, 0x8484, enum_buf, cdch_process_set_config, CONFIG_PL2303_READ3)); + break; + }// else: continue with next step + TU_ATTR_FALLTHROUGH; + + case CONFIG_PL2303_READ3: + // purpose unknown, overtaken from Linux Kernel driver + if (p_cdc->pl2303.type != PL2303_TYPE_HXN) { + TU_ASSERT(pl2303_vendor_read(p_cdc, 0x8383, enum_buf, cdch_process_set_config, CONFIG_PL2303_READ4)); + break; + }// else: continue with next step + TU_ATTR_FALLTHROUGH; + + case CONFIG_PL2303_READ4: + // purpose unknown, overtaken from Linux Kernel driver + if (p_cdc->pl2303.type != PL2303_TYPE_HXN) { + TU_ASSERT(pl2303_vendor_read(p_cdc, 0x8484, enum_buf, cdch_process_set_config, CONFIG_PL2303_WRITE2)); + break; + }// else: continue with next step + TU_ATTR_FALLTHROUGH; + + case CONFIG_PL2303_WRITE2: + // purpose unknown, overtaken from Linux Kernel driver + if (p_cdc->pl2303.type != PL2303_TYPE_HXN) { + TU_ASSERT(pl2303_vendor_write(p_cdc, 0x0404, 1, cdch_process_set_config, CONFIG_PL2303_READ5)); + break; + }// else: continue with next step + TU_ATTR_FALLTHROUGH; + + case CONFIG_PL2303_READ5: + // purpose unknown, overtaken from Linux Kernel driver + if (p_cdc->pl2303.type != PL2303_TYPE_HXN) { + TU_ASSERT(pl2303_vendor_read(p_cdc, 0x8484, enum_buf, cdch_process_set_config, CONFIG_PL2303_READ6)); + break; + }// else: continue with next step + TU_ATTR_FALLTHROUGH; + + case CONFIG_PL2303_READ6: + // purpose unknown, overtaken from Linux Kernel driver + if (p_cdc->pl2303.type != PL2303_TYPE_HXN) { + TU_ASSERT(pl2303_vendor_read(p_cdc, 0x8383, enum_buf, cdch_process_set_config, CONFIG_PL2303_WRITE3)); + break; + }// else: continue with next step + TU_ATTR_FALLTHROUGH; + + case CONFIG_PL2303_WRITE3: + // purpose unknown, overtaken from Linux Kernel driver + if (p_cdc->pl2303.type != PL2303_TYPE_HXN) { + TU_ASSERT(pl2303_vendor_write(p_cdc, 0, 1, cdch_process_set_config, CONFIG_PL2303_WRITE4)); + break; + }// else: continue with next step + TU_ATTR_FALLTHROUGH; + + case CONFIG_PL2303_WRITE4: + // purpose unknown, overtaken from Linux Kernel driver + if (p_cdc->pl2303.type != PL2303_TYPE_HXN) { + TU_ASSERT(pl2303_vendor_write(p_cdc, 1, 0, cdch_process_set_config, CONFIG_PL2303_WRITE5)); + break; + }// else: continue with next step + TU_ATTR_FALLTHROUGH; + + case CONFIG_PL2303_WRITE5: + // purpose unknown, overtaken from Linux Kernel driver + if (p_cdc->pl2303.type != PL2303_TYPE_HXN) { + uint16_t const windex = (p_cdc->pl2303.quirks & PL2303_QUIRK_LEGACY) ? 0x24 : 0x44; + TU_ASSERT(pl2303_vendor_write(p_cdc, 2, windex, cdch_process_set_config, CONFIG_PL2303_RESET_ENDP1)); + break; + }// else: continue with next step + TU_ATTR_FALLTHROUGH; + + // from here sequence overtaken from Linux Kernel function pl2303_open() + case CONFIG_PL2303_RESET_ENDP1: + // step 1 + if (p_cdc->pl2303.quirks & PL2303_QUIRK_LEGACY) { + TU_ASSERT(pl2303_clear_halt(p_cdc, PL2303_OUT_EP, cdch_process_set_config, CONFIG_PL2303_RESET_ENDP2)); + } else { + /* reset upstream data pipes */ + if (p_cdc->pl2303.type == PL2303_TYPE_HXN) { + TU_ASSERT(pl2303_vendor_write(p_cdc, PL2303_HXN_RESET_REG,// skip CONFIG_PL2303_RESET_ENDP2, no 2nd step + PL2303_HXN_RESET_UPSTREAM_PIPE | PL2303_HXN_RESET_DOWNSTREAM_PIPE, + cdch_process_set_config, CONFIG_PL2303_COMPLETE)); + } else { + pl2303_vendor_write(p_cdc, 8, 0, cdch_process_set_config, CONFIG_PL2303_RESET_ENDP2); + } + } + break; + + case CONFIG_PL2303_RESET_ENDP2: + // step 2 + if (p_cdc->pl2303.quirks & PL2303_QUIRK_LEGACY) { + TU_ASSERT(pl2303_clear_halt(p_cdc, PL2303_IN_EP, cdch_process_set_config, CONFIG_PL2303_COMPLETE)); + } else { + /* reset upstream data pipes */ + if (p_cdc->pl2303.type == PL2303_TYPE_HXN) { + // here nothing to do, only structure of previous step overtaken for better reading and comparison + } else { + TU_ASSERT(pl2303_vendor_write(p_cdc, 9, 0, cdch_process_set_config, CONFIG_PL2303_COMPLETE)); + } + } + break; + + // skipped, because it's not working with each PL230x. flow control can be also set by PL2303 EEPROM Writer Program + // case CONFIG_PL2303_FLOW_CTRL_READ: + // // read flow control register for modify & write back in next step + // if (p_cdc->pl2303.type == PL2303_TYPE_HXN) { + // TU_LOG_P_CDC ( "1\r\n" ); + // TU_ASSERT(pl2303_vendor_read(p_cdc, PL2303_HXN_FLOWCTRL_REG, &buf, + // cdch_process_set_config, CONFIG_PL2303_FLOW_CTRL_WRITE)); + // } else { + // TU_LOG_P_CDC ( "2\r\n" ); + // TU_ASSERT(pl2303_vendor_read(p_cdc, 0, &buf, cdch_process_set_config, CONFIG_PL2303_FLOW_CTRL_WRITE)); + // } + // break; + // + // case CONFIG_PL2303_FLOW_CTRL_WRITE: + // // no flow control + // buf = xfer->buffer[0]; + // if (p_cdc->pl2303.type == PL2303_TYPE_HXN) { + // buf &= (uint8_t) ~PL2303_HXN_FLOWCTRL_MASK; + // buf |= PL2303_HXN_FLOWCTRL_NONE; + // TU_ASSERT(pl2303_vendor_write(p_cdc, PL2303_HXN_FLOWCTRL_REG, buf, + // cdch_process_set_config, CONFIG_PL2303_COMPLETE)); + // } else { + // buf &= (uint8_t) ~PL2303_FLOWCTRL_MASK; + // TU_ASSERT(pl2303_vendor_write(p_cdc, 0, buf, + // cdch_process_set_config, CONFIG_PL2303_COMPLETE)); + // } + // break; + + case CONFIG_PL2303_COMPLETE: + xfer->user_data = 0; // kick-off set line state on enum + cdch_process_line_state_on_enum(xfer); + break; + + default: + return false; + } + + return true; +} + +//------------- Helper -------------// + +static pl2303_type_t pl2303_detect_type(cdch_interface_t *p_cdc, uint8_t step) { + tusb_desc_device_t desc_dev; + TU_VERIFY(tuh_descriptor_get_device_local(p_cdc->daddr, &desc_dev), PL2303_TYPE_UNKNOWN); + + // Legacy PL2303H, variants 0 and 1 (difference unknown). + if (desc_dev.bDeviceClass == 0x02) { + return PL2303_TYPE_H; /* variant 0 */ + } + + if (desc_dev.bMaxPacketSize0 != 0x40) { + if (desc_dev.bDeviceClass == 0x00 || desc_dev.bDeviceClass == 0xff) { + return PL2303_TYPE_H; /* variant 1 */ + } + return PL2303_TYPE_H; /* variant 0 */ + } + + switch (desc_dev.bcdUSB) { + case 0x101: + /* USB 1.0.1? Let's assume they meant 1.1... */ + TU_ATTR_FALLTHROUGH; + case 0x110: + switch (desc_dev.bcdDevice) { + case 0x300: return PL2303_TYPE_HX; + case 0x400: return PL2303_TYPE_HXD; + default: return PL2303_TYPE_HX; + } + break; + + case 0x200: + switch (desc_dev.bcdDevice) { + case 0x100: /* GC */ + case 0x105: + return PL2303_TYPE_HXN; + + case 0x300: /* GT / TA */ + if (step == 1) { + // step 1 trigger pl2303_supports_hx_status() request + return PL2303_TYPE_NEED_SUPPORTS_HX_STATUS; + } else { + // step 2 use supports_hx_status + if (p_cdc->pl2303.supports_hx_status) { + return PL2303_TYPE_TA; + } + } + TU_ATTR_FALLTHROUGH; + case 0x305: + case 0x400: /* GL */ + case 0x405: + return PL2303_TYPE_HXN; + + case 0x500: /* GE / TB */ + if (step == 1) { + // step 1 trigger pl2303_supports_hx_status() request + return PL2303_TYPE_NEED_SUPPORTS_HX_STATUS; + } else { + // step 2 use supports_hx_status + if (p_cdc->pl2303.supports_hx_status) { + return PL2303_TYPE_TB; + } + } + TU_ATTR_FALLTHROUGH; + case 0x505: + case 0x600: /* GS */ + case 0x605: + case 0x700: /* GR */ + case 0x705: + return PL2303_TYPE_HXN; + + default: + break; + } + break; + default: break; + } + + TU_LOG_CDC(p_cdc, "unknown device type bcdUSB = 0x%04x", desc_dev.bcdUSB); + return PL2303_TYPE_UNKNOWN; +} + +/* + * Returns the nearest supported baud rate that can be set directly without + * using divisors. + */ +static uint32_t pl2303_get_supported_baud_rate(uint32_t baud) { + static const uint32_t baud_sup[] = { + 75, 150, 300, 600, 1200, 1800, 2400, 3600, 4800, 7200, 9600, + 14400, 19200, 28800, 38400, 57600, 115200, 230400, 460800, + 614400, 921600, 1228800, 2457600, 3000000, 6000000 + }; + + uint8_t i; + for (i = 0; i < TU_ARRAY_SIZE(baud_sup); ++i) { + if (baud_sup[i] > baud) { + break; + } + } + + if (i == TU_ARRAY_SIZE(baud_sup)) { + baud = baud_sup[i - 1]; + } else if (i > 0 && (baud_sup[i] - baud) > (baud - baud_sup[i - 1])) { + baud = baud_sup[i - 1]; + } else { + baud = baud_sup[i]; + } + + return baud; +} + +/* + * NOTE: If unsupported baud rates are set directly, the PL2303 seems to + * use 9600 baud. + */ +static uint32_t pl2303_encode_baud_rate_direct(uint8_t buf[PL2303_LINE_CODING_BAUDRATE_BUFSIZE], uint32_t baud) { + uint32_t baud_le = tu_htole32(baud); + buf[0] = (uint8_t) ( baud_le & 0xff); + buf[1] = (uint8_t) ((baud_le >> 8) & 0xff); + buf[2] = (uint8_t) ((baud_le >> 16) & 0xff); + buf[3] = (uint8_t) ((baud_le >> 24) & 0xff); + + return baud; +} + +static uint32_t pl2303_encode_baud_rate_divisor(uint8_t buf[PL2303_LINE_CODING_BAUDRATE_BUFSIZE], uint32_t baud) { + uint32_t baseline, mantissa, exponent; + + /* + * Apparently the formula is: + * baudrate = 12M * 32 / (mantissa * 4^exponent) + * where + * mantissa = buf[8:0] + * exponent = buf[11:9] + */ + baseline = 12000000 * 32; + mantissa = baseline / baud; + if (mantissa == 0) + mantissa = 1; /* Avoid dividing by zero if baud > 32 * 12M. */ + exponent = 0; + while (mantissa >= 512) { + if (exponent < 7) { + mantissa >>= 2; /* divide by 4 */ + exponent++; + } else { + /* Exponent is maxed. Trim mantissa and leave. */ + mantissa = 511; + break; + } + } + + buf[3] = 0x80; + buf[2] = 0; + buf[1] = (uint8_t) ((exponent << 1 | mantissa >> 8) & 0xff); + buf[0] = (uint8_t) (mantissa & 0xff); + + /* Calculate and return the exact baud rate. */ + baud = (baseline / mantissa) >> (exponent << 1); + + return baud; +} + +static uint32_t pl2303_encode_baud_rate_divisor_alt(uint8_t buf[PL2303_LINE_CODING_BAUDRATE_BUFSIZE], uint32_t baud) { + uint32_t baseline, mantissa, exponent; + + /* + * Apparently, for the TA version the formula is: + * baudrate = 12M * 32 / (mantissa * 2^exponent) + * where + * mantissa = buf[10:0] + * exponent = buf[15:13 16] + */ + baseline = 12000000 * 32; + mantissa = baseline / baud; + if (mantissa == 0) { + mantissa = 1; /* Avoid dividing by zero if baud > 32 * 12M. */ + } + exponent = 0; + while (mantissa >= 2048) { + if (exponent < 15) { + mantissa >>= 1; /* divide by 2 */ + exponent++; + } else { + /* Exponent is maxed. Trim mantissa and leave. */ + mantissa = 2047; + break; + } + } + + buf[3] = 0x80; + buf[2] = (uint8_t) (exponent & 0x01); + buf[1] = (uint8_t) (((exponent & (uint32_t) ~0x01) << 4 | mantissa >> 8) & 0xff); + buf[0] = (uint8_t) (mantissa & 0xff); + + /* Calculate and return the exact baud rate. */ + baud = (baseline / mantissa) >> exponent; + + return baud; +} + +static bool pl2303_encode_baud_rate(cdch_interface_t *p_cdc, uint8_t buf[PL2303_LINE_CODING_BAUDRATE_BUFSIZE]) { + uint32_t baud = p_cdc->requested_line.coding.bit_rate; + uint32_t baud_sup; + const pl2303_type_data_t* type_data = &pl2303_type_data[p_cdc->pl2303.type]; + + TU_VERIFY(baud && baud <= type_data->max_baud_rate); + /* + * Use direct method for supported baud rates, otherwise use divisors. + * Newer chip types do not support divisor encoding. + */ + if (type_data->no_divisors) { + baud_sup = baud; + } else { + baud_sup = pl2303_get_supported_baud_rate(baud); + } + + if (baud == baud_sup) { + baud = pl2303_encode_baud_rate_direct(buf, baud); + } else if (type_data->alt_divisors) { + baud = pl2303_encode_baud_rate_divisor_alt(buf, baud); + } else { + baud = pl2303_encode_baud_rate_divisor(buf, baud); + } + TU_LOG_CDC(p_cdc, "real baudrate %lu", baud); + + return true; +} + +#endif // CFG_TUH_CDC_PL2303 + +#endif diff --git a/cores/nRF5/TinyUSB/Adafruit_TinyUSB_ArduinoCore/tinyusb/src/class/cdc/cdc_host.h b/cores/nRF5/TinyUSB/Adafruit_TinyUSB_ArduinoCore/tinyusb/src/class/cdc/cdc_host.h new file mode 100644 index 0000000..bf6711d --- /dev/null +++ b/cores/nRF5/TinyUSB/Adafruit_TinyUSB_ArduinoCore/tinyusb/src/class/cdc/cdc_host.h @@ -0,0 +1,258 @@ +/* + * The MIT License (MIT) + * + * Copyright (c) 2019 Ha Thach (tinyusb.org) + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + * This file is part of the TinyUSB stack. + */ + +#ifndef _TUSB_CDC_HOST_H_ +#define _TUSB_CDC_HOST_H_ + +#include "cdc.h" + +#ifdef __cplusplus + extern "C" { +#endif + +//--------------------------------------------------------------------+ +// Class Driver Configuration +//--------------------------------------------------------------------+ + +// RX FIFO size +#ifndef CFG_TUH_CDC_RX_BUFSIZE +#define CFG_TUH_CDC_RX_BUFSIZE TUH_EPSIZE_BULK_MPS +#endif + +// RX Endpoint size +#ifndef CFG_TUH_CDC_RX_EPSIZE +#define CFG_TUH_CDC_RX_EPSIZE TUH_EPSIZE_BULK_MPS +#endif + +// TX FIFO size +#ifndef CFG_TUH_CDC_TX_BUFSIZE +#define CFG_TUH_CDC_TX_BUFSIZE TUH_EPSIZE_BULK_MPS +#endif + +// TX Endpoint size +#ifndef CFG_TUH_CDC_TX_EPSIZE +#define CFG_TUH_CDC_TX_EPSIZE TUH_EPSIZE_BULK_MPS +#endif + +//--------------------------------------------------------------------+ +// Application API +//--------------------------------------------------------------------+ + +// Get Interface index from device address + interface number +// return TUSB_INDEX_INVALID_8 (0xFF) if not found +uint8_t tuh_cdc_itf_get_index(uint8_t daddr, uint8_t itf_num); + +// Get Interface information +// return true if index is correct and interface is currently mounted +bool tuh_cdc_itf_get_info(uint8_t idx, tuh_itf_info_t* info); + +// Check if an interface is mounted +bool tuh_cdc_mounted(uint8_t idx); + +// Get local (cached) line state +// This function should return correct values if tuh_cdc_set_control_line_state() / tuh_cdc_get_control_line_state() +// are invoked previously or CFG_TUH_CDC_LINE_STATE_ON_ENUM is defined. +bool tuh_cdc_get_control_line_state_local(uint8_t idx, uint16_t* line_state); + +// Get current DTR status +TU_ATTR_ALWAYS_INLINE static inline bool tuh_cdc_get_dtr(uint8_t idx) { + uint16_t line_state; + TU_VERIFY(tuh_cdc_get_control_line_state_local(idx, &line_state)); + return (line_state & CDC_CONTROL_LINE_STATE_DTR) != 0; +} + +// Get current RTS status +TU_ATTR_ALWAYS_INLINE static inline bool tuh_cdc_get_rts(uint8_t idx) { + uint16_t line_state; + TU_VERIFY(tuh_cdc_get_control_line_state_local(idx, &line_state)); + return (line_state & CDC_CONTROL_LINE_STATE_RTS) != 0; +} + +// Check if interface is connected (DTR active) +TU_ATTR_ALWAYS_INLINE static inline bool tuh_cdc_connected(uint8_t idx) { + return tuh_cdc_get_dtr(idx); +} + +// Get local (saved/cached) version of line coding. +// This function should return correct values if tuh_cdc_set_line_coding() / tuh_cdc_get_line_coding() +// are invoked previously or CFG_TUH_CDC_LINE_CODING_ON_ENUM is defined. +// NOTE: This function does not make any USB transfer request to device. +bool tuh_cdc_get_line_coding_local(uint8_t idx, cdc_line_coding_t* line_coding); + +#define tuh_cdc_get_local_line_coding tuh_cdc_get_line_coding_local // backward compatibility + +//--------------------------------------------------------------------+ +// Write API +//--------------------------------------------------------------------+ + +// Get the number of bytes available for writing +uint32_t tuh_cdc_write_available(uint8_t idx); + +// Write to cdc interface +uint32_t tuh_cdc_write(uint8_t idx, void const* buffer, uint32_t bufsize); + +// Force sending data if possible, return number of forced bytes +uint32_t tuh_cdc_write_flush(uint8_t idx); + +// Clear the transmit FIFO +bool tuh_cdc_write_clear(uint8_t idx); + +//--------------------------------------------------------------------+ +// Read API +//--------------------------------------------------------------------+ + +// Get the number of bytes available for reading +uint32_t tuh_cdc_read_available(uint8_t idx); + +// Read from cdc interface +uint32_t tuh_cdc_read (uint8_t idx, void* buffer, uint32_t bufsize); + +// Get a byte from RX FIFO without removing it +bool tuh_cdc_peek(uint8_t idx, uint8_t* ch); + +// Clear the received FIFO +bool tuh_cdc_read_clear (uint8_t idx); + +//--------------------------------------------------------------------+ +// Control Request API +// Each Function will make a USB control transfer request to/from device +// - If complete_cb is provided, the function will return immediately and invoke +// the callback when request is complete. +// - If complete_cb is NULL, the function will block until request is complete. +// In this case, user_data should be usb_xfer_result_t* to hold the transfer result. +//--------------------------------------------------------------------+ + +// Request to Set Control Line State: DTR (bit 0), RTS (bit 1) +bool tuh_cdc_set_control_line_state(uint8_t idx, uint16_t line_state, tuh_xfer_cb_t complete_cb, uintptr_t user_data); + +// Request to Set DTR +TU_ATTR_ALWAYS_INLINE static inline bool tuh_cdc_set_dtr(uint8_t idx, bool dtr_state, tuh_xfer_cb_t complete_cb, uintptr_t user_data) { + cdc_line_control_state_t line_state = { .dtr = dtr_state }; + line_state.rts = tuh_cdc_get_rts(idx); + return tuh_cdc_set_control_line_state(idx, line_state.value, complete_cb, user_data); +} + +// Request to Set RTS +TU_ATTR_ALWAYS_INLINE static inline bool tuh_cdc_set_rts(uint8_t idx, bool rts_state, tuh_xfer_cb_t complete_cb, uintptr_t user_data) { + cdc_line_control_state_t line_state = { .rts = rts_state }; + line_state.dtr = tuh_cdc_get_dtr(idx); + return tuh_cdc_set_control_line_state(idx, line_state.value, complete_cb, user_data); +} + +// Request to set baudrate +bool tuh_cdc_set_baudrate(uint8_t idx, uint32_t baudrate, tuh_xfer_cb_t complete_cb, uintptr_t user_data); + +// Request to set data format +bool tuh_cdc_set_data_format(uint8_t idx, uint8_t stop_bits, uint8_t parity, uint8_t data_bits, tuh_xfer_cb_t complete_cb, uintptr_t user_data); + +// Request to Set Line Coding = baudrate + data format +// Note: only implemented by ACM and CH34x, not supported by FTDI and CP210x yet +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); + +// Request to Get Line Coding (ACM only) +// Should only use if tuh_cdc_set_line_coding() / tuh_cdc_get_line_coding() never got invoked and +// CFG_TUH_CDC_LINE_CODING_ON_ENUM is not defined +// bool tuh_cdc_get_line_coding(uint8_t idx, cdc_line_coding_t* coding); + +// Connect by set both DTR, RTS +TU_ATTR_ALWAYS_INLINE static inline bool tuh_cdc_connect(uint8_t idx, tuh_xfer_cb_t complete_cb, uintptr_t user_data) { + return tuh_cdc_set_control_line_state(idx, CDC_CONTROL_LINE_STATE_DTR | CDC_CONTROL_LINE_STATE_RTS, complete_cb, user_data); +} + +// Disconnect by clear both DTR, RTS +TU_ATTR_ALWAYS_INLINE static inline bool tuh_cdc_disconnect(uint8_t idx, tuh_xfer_cb_t complete_cb, uintptr_t user_data) { + return tuh_cdc_set_control_line_state(idx, 0x00, complete_cb, user_data); +} + +//--------------------------------------------------------------------+ +// Control Request Sync API +// Each Function will make a USB control transfer request to/from device the function will block until request is +// complete. The function will return the transfer request result +//--------------------------------------------------------------------+ +TU_ATTR_ALWAYS_INLINE static inline tusb_xfer_result_t tuh_cdc_set_control_line_state_sync(uint8_t idx, uint16_t line_state) { + TU_API_SYNC(tuh_cdc_set_control_line_state, idx, line_state); +} + +TU_ATTR_ALWAYS_INLINE static inline tusb_xfer_result_t tuh_cdc_set_dtr_sync(uint8_t idx, bool dtr_state) { + TU_API_SYNC(tuh_cdc_set_dtr, idx, dtr_state); +} + +TU_ATTR_ALWAYS_INLINE static inline tusb_xfer_result_t tuh_cdc_set_rts_sync(uint8_t idx, bool rts_state) { + TU_API_SYNC(tuh_cdc_set_rts, idx, rts_state); +} + +TU_ATTR_ALWAYS_INLINE static inline tusb_xfer_result_t tuh_cdc_set_baudrate_sync(uint8_t idx, uint32_t baudrate) { + TU_API_SYNC(tuh_cdc_set_baudrate, idx, baudrate); +} + +TU_ATTR_ALWAYS_INLINE static inline tusb_xfer_result_t tuh_cdc_set_data_format_sync(uint8_t idx, uint8_t stop_bits, uint8_t parity, uint8_t data_bits) { + TU_API_SYNC(tuh_cdc_set_data_format, idx, stop_bits, parity, data_bits); +} + +TU_ATTR_ALWAYS_INLINE static inline tusb_xfer_result_t tuh_cdc_set_line_coding_sync(uint8_t idx, cdc_line_coding_t const* line_coding) { + TU_API_SYNC(tuh_cdc_set_line_coding, idx, line_coding); +} + +TU_ATTR_ALWAYS_INLINE static inline tusb_xfer_result_t tuh_cdc_connect_sync(uint8_t idx) { + TU_API_SYNC(tuh_cdc_connect, idx); +} + +TU_ATTR_ALWAYS_INLINE static inline tusb_xfer_result_t tuh_cdc_disconnect_sync(uint8_t idx) { + TU_API_SYNC(tuh_cdc_disconnect, idx); +} + +//--------------------------------------------------------------------+ +// CDC APPLICATION CALLBACKS +//--------------------------------------------------------------------+ + +// Invoked when a device with CDC interface is mounted +// idx is index of cdc interface in the internal pool. +extern void tuh_cdc_mount_cb(uint8_t idx); + +// Invoked when a device with CDC interface is unmounted +extern void tuh_cdc_umount_cb(uint8_t idx); + +// Invoked when received new data +extern void tuh_cdc_rx_cb(uint8_t idx); + +// Invoked when a TX is complete and therefore space becomes available in TX buffer +extern void tuh_cdc_tx_complete_cb(uint8_t idx); + +//--------------------------------------------------------------------+ +// Internal Class Driver API +//--------------------------------------------------------------------+ +bool cdch_init (void); +bool cdch_deinit (void); +bool cdch_open (uint8_t rhport, uint8_t dev_addr, tusb_desc_interface_t const *itf_desc, uint16_t max_len); +bool cdch_set_config (uint8_t dev_addr, uint8_t itf_num); +bool cdch_xfer_cb (uint8_t dev_addr, uint8_t ep_addr, xfer_result_t event, uint32_t xferred_bytes); +void cdch_close (uint8_t dev_addr); + +#ifdef __cplusplus + } +#endif + +#endif /* _TUSB_CDC_HOST_H_ */ diff --git a/cores/nRF5/TinyUSB/Adafruit_TinyUSB_ArduinoCore/tinyusb/src/class/cdc/serial/ch34x.h b/cores/nRF5/TinyUSB/Adafruit_TinyUSB_ArduinoCore/tinyusb/src/class/cdc/serial/ch34x.h new file mode 100644 index 0000000..0e08b0a --- /dev/null +++ b/cores/nRF5/TinyUSB/Adafruit_TinyUSB_ArduinoCore/tinyusb/src/class/cdc/serial/ch34x.h @@ -0,0 +1,84 @@ +/* + * The MIT License (MIT) + * + * Copyright (c) 2023 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_CH34X_H +#define TUSB_CH34X_H + +// There is no official documentation for the CH34x (CH340, CH341) chips. Reference can be found +// - https://github.com/WCHSoftGroup/ch341ser_linux +// - https://github.com/torvalds/linux/blob/master/drivers/usb/serial/ch341.c +// - https://github.com/freebsd/freebsd-src/blob/main/sys/dev/usb/serial/uchcom.c + +// 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 CH34X_REQ_READ_VERSION 0x5F // dec 95 +#define CH34X_REQ_WRITE_REG 0x9A // dec 154 +#define CH34X_REQ_READ_REG 0x95 // dec 149 +#define CH34X_REQ_SERIAL_INIT 0xA1 // dec 161 +#define CH34X_REQ_MODEM_CTRL 0xA4 // dev 164 + +// registers +#define CH34X_REG_BREAK 0x05 +#define CH34X_REG_PRESCALER 0x12 +#define CH34X_REG_DIVISOR 0x13 +#define CH34X_REG_LCR 0x18 +#define CH34X_REG_LCR2 0x25 +#define CH34X_REG_MCR_MSR 0x06 +#define CH34X_REG_MCR_MSR2 0x07 +#define CH34X_NBREAK_BITS 0x01 + +#define CH341_REG_0x0F 0x0F // undocumented register +#define CH341_REG_0x2C 0x2C // undocumented register +#define CH341_REG_0x27 0x27 // hardware flow control (cts/rts) + +#define CH34X_REG16_DIVISOR_PRESCALER TU_U16(CH34X_REG_DIVISOR, CH34X_REG_PRESCALER) +#define CH32X_REG16_LCR2_LCR TU_U16(CH34X_REG_LCR2, CH34X_REG_LCR) + +// modem control bits +#define CH34X_BIT_RTS (1 << 6) +#define CH34X_BIT_DTR (1 << 5) + +// line control bits +#define CH34X_LCR_ENABLE_RX 0x80 +#define CH34X_LCR_ENABLE_TX 0x40 +#define CH34X_LCR_MARK_SPACE 0x20 +#define CH34X_LCR_PAR_EVEN 0x10 +#define CH34X_LCR_ENABLE_PAR 0x08 +#define CH34X_LCR_PAR_MASK 0x38 // all parity bits +#define CH34X_LCR_STOP_BITS_2 0x04 +#define CH34X_LCR_CS8 0x03 +#define CH34X_LCR_CS7 0x02 +#define CH34X_LCR_CS6 0x01 +#define CH34X_LCR_CS5 0x00 +#define CH34X_LCR_CS_MASK 0x03 // all CSx bits + +#endif // TUSB_CH34X_H diff --git a/cores/nRF5/TinyUSB/Adafruit_TinyUSB_ArduinoCore/tinyusb/src/class/cdc/serial/cp210x.h b/cores/nRF5/TinyUSB/Adafruit_TinyUSB_ArduinoCore/tinyusb/src/class/cdc/serial/cp210x.h new file mode 100644 index 0000000..a0eff9e --- /dev/null +++ b/cores/nRF5/TinyUSB/Adafruit_TinyUSB_ArduinoCore/tinyusb/src/class/cdc/serial/cp210x.h @@ -0,0 +1,114 @@ +/* + * The MIT License (MIT) + * + * Copyright (c) 2023 Ha Thach (thach@tinyusb.org) for Adafruit Industries + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#ifndef TUSB_CP210X_H +#define TUSB_CP210X_H + +// Protocol details can be found at AN571: CP210x Virtual COM Port Interface +// https://www.silabs.com/documents/public/application-notes/AN571.pdf + +// parts are overtaken from vendors driver +// https://www.silabs.com/documents/public/software/cp210x-3.1.0.tar.gz + +// Config request codes +#define CP210X_IFC_ENABLE 0x00 +#define CP210X_SET_BAUDDIV 0x01 +#define CP210X_GET_BAUDDIV 0x02 +#define CP210X_SET_LINE_CTL 0x03 // Set parity, data bits, stop bits +#define CP210X_GET_LINE_CTL 0x04 +#define CP210X_SET_BREAK 0x05 +#define CP210X_IMM_CHAR 0x06 +#define CP210X_SET_MHS 0x07 // Set DTR, RTS +#define CP210X_GET_MDMSTS 0x08 // Get modem status (DTR, RTS, CTS, DSR, RI, DCD) +#define CP210X_SET_XON 0x09 +#define CP210X_SET_XOFF 0x0A +#define CP210X_SET_EVENTMASK 0x0B +#define CP210X_GET_EVENTMASK 0x0C +#define CP210X_SET_CHAR 0x0D +#define CP210X_GET_CHARS 0x0E +#define CP210X_GET_PROPS 0x0F +#define CP210X_GET_COMM_STATUS 0x10 +#define CP210X_RESET 0x11 +#define CP210X_PURGE 0x12 +#define CP210X_SET_FLOW 0x13 +#define CP210X_GET_FLOW 0x14 +#define CP210X_EMBED_EVENTS 0x15 +#define CP210X_GET_EVENTSTATE 0x16 +#define CP210X_SET_CHARS 0x19 +#define CP210X_GET_BAUDRATE 0x1D +#define CP210X_SET_BAUDRATE 0x1E +#define CP210X_VENDOR_SPECIFIC 0xFF // GPIO, Recipient must be Device + +// SILABSER_IFC_ENABLE_REQUEST_CODE +#define CP210X_UART_ENABLE 0x0001 +#define CP210X_UART_DISABLE 0x0000 + +// SILABSER_SET_BAUDDIV_REQUEST_CODE +#define CP210X_BAUD_RATE_GEN_FREQ 0x384000 + +// SILABSER_SET_LINE_CTL_REQUEST_CODE +#define CP210X_BITS_DATA_MASK 0x0f00 +#define CP210X_BITS_DATA_5 0x0500 +#define CP210X_BITS_DATA_6 0x0600 +#define CP210X_BITS_DATA_7 0x0700 +#define CP210X_BITS_DATA_8 0x0800 +#define CP210X_BITS_DATA_9 0x0900 + +#define CP210X_BITS_PARITY_MASK 0x00f0 +#define CP210X_BITS_PARITY_NONE 0x0000 +#define CP210X_BITS_PARITY_ODD 0x0010 +#define CP210X_BITS_PARITY_EVEN 0x0020 +#define CP210X_BITS_PARITY_MARK 0x0030 +#define CP210X_BITS_PARITY_SPACE 0x0040 + +#define CP210X_BITS_STOP_MASK 0x000f +#define CP210X_BITS_STOP_1 0x0000 +#define CP210X_BITS_STOP_1_5 0x0001 +#define CP210X_BITS_STOP_2 0x0002 + +// SILABSER_SET_BREAK_REQUEST_CODE +#define CP210X_BREAK_ON 0x0001 +#define CP210X_BREAK_OFF 0x0000 + +// SILABSER_SET_MHS_REQUEST_CODE +#define CP210X_MCR_DTR 0x0001 +#define CP210X_MCR_RTS 0x0002 +#define CP210X_MCR_ALL 0x0003 +#define CP210X_MSR_CTS 0x0010 +#define CP210X_MSR_DSR 0x0020 +#define CP210X_MSR_RING 0x0040 +#define CP210X_MSR_DCD 0x0080 +#define CP210X_MSR_ALL 0x00F0 + +#define CP210X_CONTROL_WRITE_DTR 0x0100UL +#define CP210X_CONTROL_WRITE_RTS 0x0200UL + +#define CP210X_LSR_BREAK 0x0001 +#define CP210X_LSR_FRAMING_ERROR 0x0002 +#define CP210X_LSR_HW_OVERRUN 0x0004 +#define CP210X_LSR_QUEUE_OVERRUN 0x0008 +#define CP210X_LSR_PARITY_ERROR 0x0010 +#define CP210X_LSR_ALL 0x001F + +#endif //TUSB_CP210X_H diff --git a/cores/nRF5/TinyUSB/Adafruit_TinyUSB_ArduinoCore/tinyusb/src/class/cdc/serial/ftdi_sio.h b/cores/nRF5/TinyUSB/Adafruit_TinyUSB_ArduinoCore/tinyusb/src/class/cdc/serial/ftdi_sio.h new file mode 100644 index 0000000..8abf74f --- /dev/null +++ b/cores/nRF5/TinyUSB/Adafruit_TinyUSB_ArduinoCore/tinyusb/src/class/cdc/serial/ftdi_sio.h @@ -0,0 +1,231 @@ +/* + * The MIT License (MIT) + * + * Copyright (c) 2023 Ha Thach (thach@tinyusb.org) for Adafruit Industries + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#ifndef TUSB_FTDI_SIO_H +#define TUSB_FTDI_SIO_H + +#include + +// Commands +#define FTDI_SIO_RESET 0 // Reset the port +#define FTDI_SIO_MODEM_CTRL 1 // Set the modem control register +#define FTDI_SIO_SET_FLOW_CTRL 2 // Set flow control register +#define FTDI_SIO_SET_BAUD_RATE 3 // Set baud rate +#define FTDI_SIO_SET_DATA 4 // Set the data characteristics of the port +#define FTDI_SIO_GET_MODEM_STATUS 5 // Retrieve current value of modem status register +#define FTDI_SIO_SET_EVENT_CHAR 6 // Set the event character +#define FTDI_SIO_SET_ERROR_CHAR 7 // Set the error character +#define FTDI_SIO_SET_LATENCY_TIMER 9 // Set the latency timer +#define FTDI_SIO_GET_LATENCY_TIMER 10 // Get the latency timer +#define FTDI_SIO_SET_BITMODE 11 // Set bitbang mode +#define FTDI_SIO_READ_PINS 12 // Read immediate value of pins +#define FTDI_SIO_READ_EEPROM 0x90 // Read EEPROM + +// Channel indices for FT2232, FT2232H and FT4232H devices +#define CHANNEL_A 1 +#define CHANNEL_B 2 +#define CHANNEL_C 3 +#define CHANNEL_D 4 + +// Port Identifier Table +#define PIT_DEFAULT 0 // SIOA +#define PIT_SIOA 1 // SIOA +// The device this driver is tested with one has only one port +#define PIT_SIOB 2 // SIOB +#define PIT_PARALLEL 3 // Parallel + +// FTDI_SIO_RESET +#define FTDI_SIO_RESET_REQUEST FTDI_SIO_RESET +#define FTDI_SIO_RESET_REQUEST_TYPE 0x40 +#define FTDI_SIO_RESET_SIO 0 +#define FTDI_SIO_RESET_PURGE_RX 1 +#define FTDI_SIO_RESET_PURGE_TX 2 + +// FTDI_SIO_SET_BAUDRATE +#define FTDI_SIO_SET_BAUDRATE_REQUEST_TYPE 0x40 +#define FTDI_SIO_SET_BAUDRATE_REQUEST 3 + +enum ftdi_sio_baudrate { + ftdi_sio_b300 = 0, + ftdi_sio_b600 = 1, + ftdi_sio_b1200 = 2, + ftdi_sio_b2400 = 3, + ftdi_sio_b4800 = 4, + ftdi_sio_b9600 = 5, + ftdi_sio_b19200 = 6, + ftdi_sio_b38400 = 7, + ftdi_sio_b57600 = 8, + ftdi_sio_b115200 = 9 +}; + +// FTDI_SIO_SET_DATA +#define FTDI_SIO_SET_DATA_REQUEST FTDI_SIO_SET_DATA +#define FTDI_SIO_SET_DATA_REQUEST_TYPE 0x40 +#define FTDI_SIO_SET_DATA_PARITY_NONE (0x0 << 8) +#define FTDI_SIO_SET_DATA_PARITY_ODD (0x1 << 8) +#define FTDI_SIO_SET_DATA_PARITY_EVEN (0x2 << 8) +#define FTDI_SIO_SET_DATA_PARITY_MARK (0x3 << 8) +#define FTDI_SIO_SET_DATA_PARITY_SPACE (0x4 << 8) +#define FTDI_SIO_SET_DATA_STOP_BITS_1 (0x0 << 11) // same coding as ACM +#define FTDI_SIO_SET_DATA_STOP_BITS_15 (0x1 << 11) // 1.5 not supported, for future use? +#define FTDI_SIO_SET_DATA_STOP_BITS_2 (0x2 << 11) +#define FTDI_SIO_SET_BREAK (0x1 << 14) + +// FTDI_SIO_MODEM_CTRL +#define FTDI_SIO_SET_MODEM_CTRL_REQUEST_TYPE 0x40 +#define FTDI_SIO_SET_MODEM_CTRL_REQUEST FTDI_SIO_MODEM_CTRL + +#define FTDI_SIO_SET_DTR_MASK 0x1UL +#define FTDI_SIO_SET_DTR_HIGH ((FTDI_SIO_SET_DTR_MASK << 8) | 1UL) +#define FTDI_SIO_SET_DTR_LOW ((FTDI_SIO_SET_DTR_MASK << 8) | 0UL) +#define FTDI_SIO_SET_RTS_MASK 0x2UL +#define FTDI_SIO_SET_RTS_HIGH ((FTDI_SIO_SET_RTS_MASK << 8) | 2UL) +#define FTDI_SIO_SET_RTS_LOW ((FTDI_SIO_SET_RTS_MASK << 8) | 0UL) + +// FTDI_SIO_SET_FLOW_CTRL +#define FTDI_SIO_SET_FLOW_CTRL_REQUEST_TYPE 0x40 +#define FTDI_SIO_SET_FLOW_CTRL_REQUEST FTDI_SIO_SET_FLOW_CTRL +#define FTDI_SIO_DISABLE_FLOW_CTRL 0x0 +#define FTDI_SIO_RTS_CTS_HS (0x1 << 8) +#define FTDI_SIO_DTR_DSR_HS (0x2 << 8) +#define FTDI_SIO_XON_XOFF_HS (0x4 << 8) + +// FTDI_SIO_GET_LATENCY_TIMER +#define FTDI_SIO_GET_LATENCY_TIMER_REQUEST FTDI_SIO_GET_LATENCY_TIMER +#define FTDI_SIO_GET_LATENCY_TIMER_REQUEST_TYPE 0xC0 + +// FTDI_SIO_SET_LATENCY_TIMER +#define FTDI_SIO_SET_LATENCY_TIMER_REQUEST FTDI_SIO_SET_LATENCY_TIMER +#define FTDI_SIO_SET_LATENCY_TIMER_REQUEST_TYPE 0x40 + +// FTDI_SIO_SET_EVENT_CHAR +#define FTDI_SIO_SET_EVENT_CHAR_REQUEST FTDI_SIO_SET_EVENT_CHAR +#define FTDI_SIO_SET_EVENT_CHAR_REQUEST_TYPE 0x40 + +// FTDI_SIO_GET_MODEM_STATUS +#define FTDI_SIO_GET_MODEM_STATUS_REQUEST_TYPE 0xc0 +#define FTDI_SIO_GET_MODEM_STATUS_REQUEST FTDI_SIO_GET_MODEM_STATUS +#define FTDI_SIO_CTS_MASK 0x10 +#define FTDI_SIO_DSR_MASK 0x20 +#define FTDI_SIO_RI_MASK 0x40 +#define FTDI_SIO_RLSD_MASK 0x80 + +// FTDI_SIO_SET_BITMODE +#define FTDI_SIO_SET_BITMODE_REQUEST_TYPE 0x40 +#define FTDI_SIO_SET_BITMODE_REQUEST FTDI_SIO_SET_BITMODE + +// Possible bitmodes for FTDI_SIO_SET_BITMODE_REQUEST +#define FTDI_SIO_BITMODE_RESET 0x00 +#define FTDI_SIO_BITMODE_CBUS 0x20 + +// FTDI_SIO_READ_PINS +#define FTDI_SIO_READ_PINS_REQUEST_TYPE 0xc0 +#define FTDI_SIO_READ_PINS_REQUEST FTDI_SIO_READ_PINS + +// FTDI_SIO_READ_EEPROM +#define FTDI_SIO_READ_EEPROM_REQUEST_TYPE 0xc0 +#define FTDI_SIO_READ_EEPROM_REQUEST FTDI_SIO_READ_EEPROM + +#define FTDI_FTX_CBUS_MUX_GPIO 0x8 +#define FTDI_FT232R_CBUS_MUX_GPIO 0xa + +#define FTDI_RS0_CTS (1 << 4) +#define FTDI_RS0_DSR (1 << 5) +#define FTDI_RS0_RI (1 << 6) +#define FTDI_RS0_RLSD (1 << 7) + +#define FTDI_RS_DR 1 +#define FTDI_RS_OE (1 << 1) +#define FTDI_RS_PE (1 << 2) +#define FTDI_RS_FE (1 << 3) +#define FTDI_RS_BI (1 << 4) +#define FTDI_RS_THRE (1 << 5) +#define FTDI_RS_TEMT (1 << 6) +#define FTDI_RS_FIFO (1 << 7) + +// chip types and names +typedef enum ftdi_chip_type { + FTDI_SIO = 0, +// FTDI_FT232A, + FTDI_FT232B, + FTDI_FT2232C, + FTDI_FT232R, + FTDI_FT232H, + FTDI_FT2232H, + FTDI_FT4232H, + FTDI_FT4232HA, + FTDI_FT232HP, + FTDI_FT233HP, + FTDI_FT2232HP, + FTDI_FT2233HP, + FTDI_FT4232HP, + FTDI_FT4233HP, + FTDI_FTX, + FTDI_UNKNOWN +} ftdi_chip_type_t; + +#define FTDI_CHIP_NAMES \ + [FTDI_SIO] = "SIO", /* the serial part of FT8U100AX */ \ +/* [FTDI_FT232A] = "FT232A", */ \ + [FTDI_FT232B] = "FT232B", \ + [FTDI_FT2232C] = "FT2232C/D", \ + [FTDI_FT232R] = "FT232R", \ + [FTDI_FT232H] = "FT232H", \ + [FTDI_FT2232H] = "FTDI_FT2232H", \ + [FTDI_FT4232H] = "FT4232H", \ + [FTDI_FT4232HA] = "FT4232HA", \ + [FTDI_FT232HP] = "FT232HP", \ + [FTDI_FT233HP] = "FT233HP", \ + [FTDI_FT2232HP] = "FT2232HP", \ + [FTDI_FT2233HP] = "FT2233HP", \ + [FTDI_FT4232HP] = "FT4232HP", \ + [FTDI_FT4233HP] = "FT4233HP", \ + [FTDI_FTX] = "FT-X", \ + [FTDI_UNKNOWN] = "UNKNOWN" + +// private interface data +typedef struct ftdi_private { + ftdi_chip_type_t chip_type; + uint8_t channel; // channel index, or 0 for legacy types +} ftdi_private_t; + +#define FTDI_OK true +#define FTDI_FAIL false +#define FTDI_NOT_POSSIBLE -1 +#define FTDI_REQUESTED -2 + +// division and round function overtaken from math.h +#define DIV_ROUND_CLOSEST(x, divisor)( \ +{ \ + typeof(x) __x = x; \ + typeof(divisor) __d = divisor; \ + (((typeof(x))-1) > 0 || \ + ((typeof(divisor))-1) > 0 || \ + (((__x) > 0) == ((__d) > 0))) ? \ + (((__x) + ((__d) / 2)) / (__d)) : \ + (((__x) - ((__d) / 2)) / (__d)); \ +} \ +) + +#endif //TUSB_FTDI_SIO_H diff --git a/cores/nRF5/TinyUSB/Adafruit_TinyUSB_ArduinoCore/tinyusb/src/class/cdc/serial/pl2303.h b/cores/nRF5/TinyUSB/Adafruit_TinyUSB_ArduinoCore/tinyusb/src/class/cdc/serial/pl2303.h new file mode 100644 index 0000000..63910c7 --- /dev/null +++ b/cores/nRF5/TinyUSB/Adafruit_TinyUSB_ArduinoCore/tinyusb/src/class/cdc/serial/pl2303.h @@ -0,0 +1,159 @@ +/* + * The MIT License (MIT) + * + * Copyright (c) 2024 Heiko Kuester + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + * This file is part of the TinyUSB stack. + */ + +#ifndef TUSB_PL2303_H +#define TUSB_PL2303_H + +#include +#include + +// There is no official documentation for the PL2303 chips. +// Reference can be found +// - https://github.com/torvalds/linux/blob/master/drivers/usb/serial/pl2303.h and +// https://github.com/torvalds/linux/blob/master/drivers/usb/serial/pl2303.c +// - https://github.com/freebsd/freebsd-src/blob/main/sys/dev/usb/serial/uplcom.c + +// quirks +#define PL2303_QUIRK_UART_STATE_IDX0 1 +#define PL2303_QUIRK_LEGACY 2 +#define PL2303_QUIRK_ENDPOINT_HACK 4 + +// requests and bits +#define PL2303_SET_LINE_REQUEST_TYPE 0x21 // class request host to device interface +#define PL2303_SET_LINE_REQUEST 0x20 // dec 32 + +#define PL2303_SET_CONTROL_REQUEST_TYPE 0x21 // class request host to device interface +#define PL2303_SET_CONTROL_REQUEST 0x22 // dec 34 +#define PL2303_CONTROL_DTR 0x01 // dec 1 +#define PL2303_CONTROL_RTS 0x02 // dec 2 + +#define PL2303_BREAK_REQUEST_TYPE 0x21 // class request host to device interface +#define PL2303_BREAK_REQUEST 0x23 // dec 35 +#define PL2303_BREAK_ON 0xffff +#define PL2303_BREAK_OFF 0x0000 + +#define PL2303_GET_LINE_REQUEST_TYPE 0xa1 // class request device to host interface +#define PL2303_GET_LINE_REQUEST 0x21 // dec 33 + +#define PL2303_VENDOR_WRITE_REQUEST_TYPE 0x40 // vendor request host to device interface +#define PL2303_VENDOR_WRITE_REQUEST 0x01 // dec 1 +#define PL2303_VENDOR_WRITE_NREQUEST 0x80 // dec 128 + +#define PL2303_VENDOR_READ_REQUEST_TYPE 0xc0 // vendor request device to host interface +#define PL2303_VENDOR_READ_REQUEST 0x01 // dec 1 +#define PL2303_VENDOR_READ_NREQUEST 0x81 // dec 129 + +#define PL2303_UART_STATE_INDEX 8 +#define PL2303_UART_STATE_MSR_MASK 0x8b +#define PL2303_UART_STATE_TRANSIENT_MASK 0x74 +#define PL2303_UART_DCD 0x01 +#define PL2303_UART_DSR 0x02 +#define PL2303_UART_BREAK_ERROR 0x04 +#define PL2303_UART_RING 0x08 +#define PL2303_UART_FRAME_ERROR 0x10 +#define PL2303_UART_PARITY_ERROR 0x20 +#define PL2303_UART_OVERRUN_ERROR 0x40 +#define PL2303_UART_CTS 0x80 + +#define PL2303_FLOWCTRL_MASK 0xf0 + +#define PL2303_CLEAR_HALT_REQUEST_TYPE 0x02 // standard request host to device endpoint + +// registers via vendor read/write requests +#define PL2303_READ_TYPE_HX_STATUS 0x8080 + +#define PL2303_HXN_RESET_REG 0x07 +#define PL2303_HXN_RESET_UPSTREAM_PIPE 0x02 +#define PL2303_HXN_RESET_DOWNSTREAM_PIPE 0x01 + +#define PL2303_HXN_FLOWCTRL_REG 0x0a +#define PL2303_HXN_FLOWCTRL_MASK 0x1c +#define PL2303_HXN_FLOWCTRL_NONE 0x1c +#define PL2303_HXN_FLOWCTRL_RTS_CTS 0x18 +#define PL2303_HXN_FLOWCTRL_XON_XOFF 0x0c + +// type data +typedef enum pl2303_type { + PL2303_TYPE_H = 0, // 0 + PL2303_TYPE_HX, // 1 + PL2303_TYPE_TA, // 2 + PL2303_TYPE_TB, // 3 + PL2303_TYPE_HXD, // 4 + PL2303_TYPE_HXN, // 5 + PL2303_TYPE_COUNT, + PL2303_TYPE_NEED_SUPPORTS_HX_STATUS, + PL2303_TYPE_UNKNOWN, +} pl2303_type_t; + +typedef struct pl2303_type_data { + uint32_t max_baud_rate; + uint8_t quirks; + uint8_t no_autoxonxoff : 1; + uint8_t no_divisors : 1; + uint8_t alt_divisors : 1; +} pl2303_type_data_t; + +#define PL2303_TYPE_DATA \ + [PL2303_TYPE_H] = { \ + .max_baud_rate = 1228800, .quirks = PL2303_QUIRK_LEGACY, \ + .no_autoxonxoff = 1, .no_divisors = 0, .alt_divisors = 0 \ + }, \ + [PL2303_TYPE_HX] = { \ + .max_baud_rate = 6000000, .quirks = 0, \ + .no_autoxonxoff = 0, .no_divisors = 0, .alt_divisors = 0 \ + }, \ + [PL2303_TYPE_TA] = { \ + .max_baud_rate = 6000000, .quirks = 0, \ + .no_autoxonxoff = 0, .no_divisors = 0, .alt_divisors = 1 \ + }, \ + [PL2303_TYPE_TB] = { \ + .max_baud_rate = 12000000, .quirks = 0, \ + .no_autoxonxoff = 0, .no_divisors = 0, .alt_divisors = 1 \ + }, \ + [PL2303_TYPE_HXD] = { \ + .max_baud_rate = 12000000, .quirks = 0, \ + .no_autoxonxoff = 0, .no_divisors = 0, .alt_divisors = 0 \ + }, \ + [PL2303_TYPE_HXN] = { \ + .max_baud_rate = 12000000, .quirks = 0, \ + .no_autoxonxoff = 0, .no_divisors = 1, .alt_divisors = 0 \ + } + +typedef struct TU_ATTR_PACKED { + pl2303_type_t type; + uint8_t quirks; + bool supports_hx_status; +} pl2303_private_t; + +// buffer sizes for line coding data +#define PL2303_LINE_CODING_BUFSIZE 7 +#define PL2303_LINE_CODING_BAUDRATE_BUFSIZE 4 + +// bulk endpoints +#define PL2303_OUT_EP 0x02 +#define PL2303_IN_EP 0x83 + +#endif // TUSB_PL2303_H diff --git a/cores/nRF5/TinyUSB/Adafruit_TinyUSB_ArduinoCore/tinyusb/src/class/dfu/dfu.h b/cores/nRF5/TinyUSB/Adafruit_TinyUSB_ArduinoCore/tinyusb/src/class/dfu/dfu.h new file mode 100644 index 0000000..114c827 --- /dev/null +++ b/cores/nRF5/TinyUSB/Adafruit_TinyUSB_ArduinoCore/tinyusb/src/class/dfu/dfu.h @@ -0,0 +1,119 @@ +/* + * The MIT License (MIT) + * + * Copyright (c) 2021 XMOS LIMITED + * + * 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_DFU_H_ +#define _TUSB_DFU_H_ + +#include "common/tusb_common.h" + +#ifdef __cplusplus + extern "C" { +#endif + +//--------------------------------------------------------------------+ +// Common Definitions +//--------------------------------------------------------------------+ + +// DFU Protocol +typedef enum +{ + DFU_PROTOCOL_RT = 0x01, + DFU_PROTOCOL_DFU = 0x02, +} dfu_protocol_type_t; + +// DFU Descriptor Type +typedef enum +{ + DFU_DESC_FUNCTIONAL = 0x21, +} dfu_descriptor_type_t; + +// DFU Requests +typedef enum { + DFU_REQUEST_DETACH = 0, + DFU_REQUEST_DNLOAD = 1, + DFU_REQUEST_UPLOAD = 2, + DFU_REQUEST_GETSTATUS = 3, + DFU_REQUEST_CLRSTATUS = 4, + DFU_REQUEST_GETSTATE = 5, + DFU_REQUEST_ABORT = 6, +} dfu_requests_t; + +// DFU States +typedef enum { + APP_IDLE = 0, + APP_DETACH = 1, + DFU_IDLE = 2, + DFU_DNLOAD_SYNC = 3, + DFU_DNBUSY = 4, + DFU_DNLOAD_IDLE = 5, + DFU_MANIFEST_SYNC = 6, + DFU_MANIFEST = 7, + DFU_MANIFEST_WAIT_RESET = 8, + DFU_UPLOAD_IDLE = 9, + DFU_ERROR = 10, +} dfu_state_t; + +// DFU Status +typedef enum { + DFU_STATUS_OK = 0x00, + DFU_STATUS_ERR_TARGET = 0x01, + DFU_STATUS_ERR_FILE = 0x02, + DFU_STATUS_ERR_WRITE = 0x03, + DFU_STATUS_ERR_ERASE = 0x04, + DFU_STATUS_ERR_CHECK_ERASED = 0x05, + DFU_STATUS_ERR_PROG = 0x06, + DFU_STATUS_ERR_VERIFY = 0x07, + DFU_STATUS_ERR_ADDRESS = 0x08, + DFU_STATUS_ERR_NOTDONE = 0x09, + DFU_STATUS_ERR_FIRMWARE = 0x0A, + DFU_STATUS_ERR_VENDOR = 0x0B, + DFU_STATUS_ERR_USBR = 0x0C, + DFU_STATUS_ERR_POR = 0x0D, + DFU_STATUS_ERR_UNKNOWN = 0x0E, + DFU_STATUS_ERR_STALLEDPKT = 0x0F, +} dfu_status_t; + +#define DFU_ATTR_CAN_DOWNLOAD (1u << 0) +#define DFU_ATTR_CAN_UPLOAD (1u << 1) +#define DFU_ATTR_MANIFESTATION_TOLERANT (1u << 2) +#define DFU_ATTR_WILL_DETACH (1u << 3) + +// DFU Status Request Payload +typedef struct TU_ATTR_PACKED +{ + uint8_t bStatus; + uint8_t bwPollTimeout[3]; + uint8_t bState; + uint8_t iString; +} dfu_status_response_t; + +TU_VERIFY_STATIC( sizeof(dfu_status_response_t) == 6, "size is not correct"); + +#ifdef __cplusplus + } +#endif + +#endif /* _TUSB_DFU_H_ */ diff --git a/cores/nRF5/TinyUSB/Adafruit_TinyUSB_ArduinoCore/tinyusb/src/class/dfu/dfu_device.c b/cores/nRF5/TinyUSB/Adafruit_TinyUSB_ArduinoCore/tinyusb/src/class/dfu/dfu_device.c new file mode 100644 index 0000000..0d2b63b --- /dev/null +++ b/cores/nRF5/TinyUSB/Adafruit_TinyUSB_ArduinoCore/tinyusb/src/class/dfu/dfu_device.c @@ -0,0 +1,430 @@ +/* + * The MIT License (MIT) + * + * Copyright (c) 2021 XMOS LIMITED + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + * This file is part of the TinyUSB stack. + */ + +#include "tusb_option.h" + +#if (CFG_TUD_ENABLED && CFG_TUD_DFU) + +#include "device/usbd.h" +#include "device/usbd_pvt.h" + +#include "dfu_device.h" + +//--------------------------------------------------------------------+ +// MACRO CONSTANT TYPEDEF +//--------------------------------------------------------------------+ + +// Level where CFG_TUSB_DEBUG must be at least for this driver is logged +#ifndef CFG_TUD_DFU_LOG_LEVEL + #define CFG_TUD_DFU_LOG_LEVEL CFG_TUD_LOG_LEVEL +#endif + +#define TU_LOG_DRV(...) TU_LOG(CFG_TUD_DFU_LOG_LEVEL, __VA_ARGS__) + +//--------------------------------------------------------------------+ +// INTERNAL OBJECT & FUNCTION DECLARATION +//--------------------------------------------------------------------+ +typedef struct { + uint8_t attrs; + uint8_t alt; + + dfu_state_t state; + dfu_status_t status; + + bool flashing_in_progress; + uint16_t block; + uint16_t length; +} dfu_state_ctx_t; + +// Only a single dfu state is allowed +static dfu_state_ctx_t _dfu_ctx; + +CFG_TUD_MEM_SECTION static struct { + TUD_EPBUF_DEF(transfer_buf, CFG_TUD_DFU_XFER_BUFSIZE); +} _dfu_epbuf; + +static void reset_state(void) { + _dfu_ctx.state = DFU_IDLE; + _dfu_ctx.status = DFU_STATUS_OK; + _dfu_ctx.flashing_in_progress = false; +} + +static bool reply_getstatus(uint8_t rhport, const tusb_control_request_t* request, dfu_state_t state, dfu_status_t status, uint32_t timeout); +static bool process_download_get_status(uint8_t rhport, uint8_t stage, const tusb_control_request_t* request); +static bool process_manifest_get_status(uint8_t rhport, uint8_t stage, const tusb_control_request_t* request); + +//--------------------------------------------------------------------+ +// Weak stubs: invoked if no strong implementation is available +//--------------------------------------------------------------------+ +TU_ATTR_WEAK void tud_dfu_detach_cb(void) { +} + +TU_ATTR_WEAK void tud_dfu_abort_cb(uint8_t alt) { + (void) alt; +} + +TU_ATTR_WEAK uint16_t tud_dfu_upload_cb(uint8_t alt, uint16_t block_num, uint8_t* data, uint16_t length) { + (void) alt; + (void) block_num; + (void) data; + (void) length; + return 0; +} + +//--------------------------------------------------------------------+ +// Debug +//--------------------------------------------------------------------+ +#if CFG_TUSB_DEBUG >= 2 + +tu_static tu_lookup_entry_t const _dfu_request_lookup[] = { + { .key = DFU_REQUEST_DETACH , .data = "DETACH" }, + { .key = DFU_REQUEST_DNLOAD , .data = "DNLOAD" }, + { .key = DFU_REQUEST_UPLOAD , .data = "UPLOAD" }, + { .key = DFU_REQUEST_GETSTATUS, .data = "GETSTATUS" }, + { .key = DFU_REQUEST_CLRSTATUS, .data = "CLRSTATUS" }, + { .key = DFU_REQUEST_GETSTATE , .data = "GETSTATE" }, + { .key = DFU_REQUEST_ABORT , .data = "ABORT" }, +}; + +tu_static tu_lookup_table_t const _dfu_request_table = { + .count = TU_ARRAY_SIZE(_dfu_request_lookup), + .items = _dfu_request_lookup +}; + +tu_static tu_lookup_entry_t const _dfu_state_lookup[] = { + { .key = APP_IDLE , .data = "APP_IDLE" }, + { .key = APP_DETACH , .data = "APP_DETACH" }, + { .key = DFU_IDLE , .data = "IDLE" }, + { .key = DFU_DNLOAD_SYNC , .data = "DNLOAD_SYNC" }, + { .key = DFU_DNBUSY , .data = "DNBUSY" }, + { .key = DFU_DNLOAD_IDLE , .data = "DNLOAD_IDLE" }, + { .key = DFU_MANIFEST_SYNC , .data = "MANIFEST_SYNC" }, + { .key = DFU_MANIFEST , .data = "MANIFEST" }, + { .key = DFU_MANIFEST_WAIT_RESET, .data = "MANIFEST_WAIT_RESET" }, + { .key = DFU_UPLOAD_IDLE , .data = "UPLOAD_IDLE" }, + { .key = DFU_ERROR , .data = "ERROR" }, +}; + +tu_static tu_lookup_table_t const _dfu_state_table = { + .count = TU_ARRAY_SIZE(_dfu_state_lookup), + .items = _dfu_state_lookup +}; + +tu_static tu_lookup_entry_t const _dfu_status_lookup[] = { + { .key = DFU_STATUS_OK , .data = "OK" }, + { .key = DFU_STATUS_ERR_TARGET , .data = "errTARGET" }, + { .key = DFU_STATUS_ERR_FILE , .data = "errFILE" }, + { .key = DFU_STATUS_ERR_WRITE , .data = "errWRITE" }, + { .key = DFU_STATUS_ERR_ERASE , .data = "errERASE" }, + { .key = DFU_STATUS_ERR_CHECK_ERASED , .data = "errCHECK_ERASED" }, + { .key = DFU_STATUS_ERR_PROG , .data = "errPROG" }, + { .key = DFU_STATUS_ERR_VERIFY , .data = "errVERIFY" }, + { .key = DFU_STATUS_ERR_ADDRESS , .data = "errADDRESS" }, + { .key = DFU_STATUS_ERR_NOTDONE , .data = "errNOTDONE" }, + { .key = DFU_STATUS_ERR_FIRMWARE , .data = "errFIRMWARE" }, + { .key = DFU_STATUS_ERR_VENDOR , .data = "errVENDOR" }, + { .key = DFU_STATUS_ERR_USBR , .data = "errUSBR" }, + { .key = DFU_STATUS_ERR_POR , .data = "errPOR" }, + { .key = DFU_STATUS_ERR_UNKNOWN , .data = "errUNKNOWN" }, + { .key = DFU_STATUS_ERR_STALLEDPKT , .data = "errSTALLEDPKT" }, +}; + +tu_static tu_lookup_table_t const _dfu_status_table = { + .count = TU_ARRAY_SIZE(_dfu_status_lookup), + .items = _dfu_status_lookup +}; + +#endif + +//--------------------------------------------------------------------+ +// USBD Driver API +//--------------------------------------------------------------------+ +void dfu_moded_reset(uint8_t rhport) { + (void) rhport; + _dfu_ctx.attrs = 0; + _dfu_ctx.alt = 0; + reset_state(); +} + +void dfu_moded_init(void) { + dfu_moded_reset(0); +} + +bool dfu_moded_deinit(void) { + return true; +} + +uint16_t dfu_moded_open(uint8_t rhport, const tusb_desc_interface_t* itf_desc, uint16_t max_len) { + (void) rhport; + + //------------- Interface (with Alt) descriptor -------------// + const uint8_t itf_num = itf_desc->bInterfaceNumber; + uint8_t alt_count = 0; + + uint16_t drv_len = 0; + TU_VERIFY(itf_desc->bInterfaceSubClass == TUD_DFU_APP_SUBCLASS && itf_desc->bInterfaceProtocol == DFU_PROTOCOL_DFU, 0); + + while(itf_desc->bInterfaceSubClass == TUD_DFU_APP_SUBCLASS && itf_desc->bInterfaceProtocol == DFU_PROTOCOL_DFU) { + TU_ASSERT(max_len > drv_len, 0); + + // Alternate must have the same interface number + TU_ASSERT(itf_desc->bInterfaceNumber == itf_num, 0); + + // Alt should increase by one every time + TU_ASSERT(itf_desc->bAlternateSetting == alt_count, 0); + alt_count++; + + drv_len += tu_desc_len(itf_desc); + itf_desc = (const tusb_desc_interface_t*) tu_desc_next(itf_desc); + } + + //------------- DFU Functional descriptor -------------// + const tusb_desc_dfu_functional_t*func_desc = (const tusb_desc_dfu_functional_t*) itf_desc; + TU_ASSERT(tu_desc_type(func_desc) == TUSB_DESC_FUNCTIONAL, 0); + drv_len += sizeof(tusb_desc_dfu_functional_t); + + _dfu_ctx.attrs = func_desc->bAttributes; + + // CFG_TUD_DFU_XFER_BUFSIZE has to be set to the buffer size used in TUD_DFU_DESCRIPTOR + const uint16_t transfer_size = tu_le16toh( tu_unaligned_read16((const uint8_t*) func_desc + offsetof(tusb_desc_dfu_functional_t, wTransferSize)) ); + TU_ASSERT(transfer_size <= CFG_TUD_DFU_XFER_BUFSIZE, drv_len); + + return drv_len; +} + +// Invoked when a control transfer occurred on an interface of this class +// Driver response accordingly to the request and the transfer stage (setup/data/ack) +// return false to stall control endpoint (e.g unsupported request) +bool dfu_moded_control_xfer_cb(uint8_t rhport, uint8_t stage, const tusb_control_request_t* request) { + TU_VERIFY(request->bmRequestType_bit.recipient == TUSB_REQ_RCPT_INTERFACE); + TU_LOG_DRV(" DFU State : %s, Status: %s\r\n", tu_lookup_find(&_dfu_state_table, _dfu_ctx.state), tu_lookup_find(&_dfu_status_table, _dfu_ctx.status)); + + if (request->bmRequestType_bit.type == TUSB_REQ_TYPE_STANDARD) { + // Standard request include GET/SET_INTERFACE + switch (request->bRequest) { + case TUSB_REQ_SET_INTERFACE: + if (stage == CONTROL_STAGE_SETUP) { + // Switch Alt interface and reset state machine + _dfu_ctx.alt = (uint8_t)request->wValue; + reset_state(); + return tud_control_status(rhport, request); + } + break; + + case TUSB_REQ_GET_INTERFACE: + if (stage == CONTROL_STAGE_SETUP) { + return tud_control_xfer(rhport, request, &_dfu_ctx.alt, 1); + } + break; + + // unsupported request + default: return false; + } + } else if (request->bmRequestType_bit.type == TUSB_REQ_TYPE_CLASS) { + TU_LOG_DRV(" DFU Request: %s\r\n", tu_lookup_find(&_dfu_request_table, request->bRequest)); + + // Class request + switch (request->bRequest) { + case DFU_REQUEST_DETACH: + if (stage == CONTROL_STAGE_SETUP) { + tud_control_status(rhport, request); + } else if (stage == CONTROL_STAGE_ACK) { + tud_dfu_detach_cb(); + } + break; + + case DFU_REQUEST_CLRSTATUS: + if (stage == CONTROL_STAGE_SETUP) { + reset_state(); + tud_control_status(rhport, request); + } + break; + + case DFU_REQUEST_GETSTATE: + if (stage == CONTROL_STAGE_SETUP) { + tud_control_xfer(rhport, request, &_dfu_ctx.state, 1); + } + break; + + case DFU_REQUEST_ABORT: + if (stage == CONTROL_STAGE_SETUP) { + reset_state(); + tud_control_status(rhport, request); + } else if (stage == CONTROL_STAGE_ACK) { + tud_dfu_abort_cb(_dfu_ctx.alt); + } + break; + + case DFU_REQUEST_UPLOAD: + if (stage == CONTROL_STAGE_SETUP) { + TU_VERIFY(_dfu_ctx.attrs & DFU_ATTR_CAN_UPLOAD); + TU_VERIFY(request->wLength <= CFG_TUD_DFU_XFER_BUFSIZE); + + const uint16_t xfer_len = tud_dfu_upload_cb(_dfu_ctx.alt, request->wValue, _dfu_epbuf.transfer_buf, + request->wLength); + + return tud_control_xfer(rhport, request, _dfu_epbuf.transfer_buf, xfer_len); + } + break; + + case DFU_REQUEST_DNLOAD: + if (stage == CONTROL_STAGE_SETUP) { + TU_VERIFY(_dfu_ctx.attrs & DFU_ATTR_CAN_DOWNLOAD); + TU_VERIFY(_dfu_ctx.state == DFU_IDLE || _dfu_ctx.state == DFU_DNLOAD_IDLE); + TU_VERIFY(request->wLength <= CFG_TUD_DFU_XFER_BUFSIZE); + + // set to true for both download and manifest + _dfu_ctx.flashing_in_progress = true; + + // save block and length for flashing + _dfu_ctx.block = request->wValue; + _dfu_ctx.length = request->wLength; + + if (request->wLength) { + // Download with payload -> transition to DOWNLOAD SYNC + _dfu_ctx.state = DFU_DNLOAD_SYNC; + return tud_control_xfer(rhport, request, _dfu_epbuf.transfer_buf, request->wLength); + } else { + // Download is complete -> transition to MANIFEST SYNC + _dfu_ctx.state = DFU_MANIFEST_SYNC; + return tud_control_status(rhport, request); + } + } + break; + + case DFU_REQUEST_GETSTATUS: + switch (_dfu_ctx.state) { + case DFU_DNLOAD_SYNC: + return process_download_get_status(rhport, stage, request); + break; + + case DFU_MANIFEST_SYNC: + return process_manifest_get_status(rhport, stage, request); + break; + + default: + if (stage == CONTROL_STAGE_SETUP) { + return reply_getstatus(rhport, request, _dfu_ctx.state, _dfu_ctx.status, 0); + } + break; + } + break; + + default: return false; // stall unsupported request + } + } else { + return false; // unsupported request + } + + return true; +} + +void tud_dfu_finish_flashing(uint8_t status) { + _dfu_ctx.flashing_in_progress = false; + + if (status == DFU_STATUS_OK) { + if (_dfu_ctx.state == DFU_DNBUSY) { + _dfu_ctx.state = DFU_DNLOAD_SYNC; + } else if (_dfu_ctx.state == DFU_MANIFEST) { + _dfu_ctx.state = (_dfu_ctx.attrs & DFU_ATTR_MANIFESTATION_TOLERANT) + ? DFU_MANIFEST_SYNC + : DFU_MANIFEST_WAIT_RESET; + } + } else { + // failed while flashing, move to dfuError + _dfu_ctx.state = DFU_ERROR; + _dfu_ctx.status = (dfu_status_t)status; + } +} + +static bool process_download_get_status(uint8_t rhport, uint8_t stage, const tusb_control_request_t* request) { + if (stage == CONTROL_STAGE_SETUP) { + // only transition to next state on CONTROL_STAGE_ACK + dfu_state_t next_state; + uint32_t timeout; + + if (_dfu_ctx.flashing_in_progress) { + next_state = DFU_DNBUSY; + timeout = tud_dfu_get_timeout_cb(_dfu_ctx.alt, (uint8_t)next_state); + } else { + next_state = DFU_DNLOAD_IDLE; + timeout = 0; + } + + return reply_getstatus(rhport, request, next_state, _dfu_ctx.status, timeout); + } else if (stage == CONTROL_STAGE_ACK) { + if (_dfu_ctx.flashing_in_progress) { + _dfu_ctx.state = DFU_DNBUSY; + tud_dfu_download_cb(_dfu_ctx.alt, _dfu_ctx.block, _dfu_epbuf.transfer_buf, _dfu_ctx.length); + } else { + _dfu_ctx.state = DFU_DNLOAD_IDLE; + } + } + + return true; +} + +static bool process_manifest_get_status(uint8_t rhport, uint8_t stage, const tusb_control_request_t* request) { + if (stage == CONTROL_STAGE_SETUP) { + // only transition to next state on CONTROL_STAGE_ACK + dfu_state_t next_state; + uint32_t timeout; + + if (_dfu_ctx.flashing_in_progress) { + next_state = DFU_MANIFEST; + timeout = tud_dfu_get_timeout_cb(_dfu_ctx.alt, next_state); + } else { + next_state = DFU_IDLE; + timeout = 0; + } + + return reply_getstatus(rhport, request, next_state, _dfu_ctx.status, timeout); + } else if (stage == CONTROL_STAGE_ACK) { + if (_dfu_ctx.flashing_in_progress) { + _dfu_ctx.state = DFU_MANIFEST; + tud_dfu_manifest_cb(_dfu_ctx.alt); + } else { + _dfu_ctx.state = DFU_IDLE; + } + } + + return true; +} + +static bool reply_getstatus(uint8_t rhport, const tusb_control_request_t* request, dfu_state_t state, + dfu_status_t status, uint32_t timeout) { + dfu_status_response_t resp; + resp.bStatus = (uint8_t)status; + resp.bwPollTimeout[0] = TU_U32_BYTE0(timeout); + resp.bwPollTimeout[1] = TU_U32_BYTE1(timeout); + resp.bwPollTimeout[2] = TU_U32_BYTE2(timeout); + resp.bState = (uint8_t)state; + resp.iString = 0; + + return tud_control_xfer(rhport, request, &resp, sizeof(dfu_status_response_t)); +} + +#endif diff --git a/cores/nRF5/TinyUSB/Adafruit_TinyUSB_ArduinoCore/tinyusb/src/class/dfu/dfu_device.h b/cores/nRF5/TinyUSB/Adafruit_TinyUSB_ArduinoCore/tinyusb/src/class/dfu/dfu_device.h new file mode 100644 index 0000000..e59e61c --- /dev/null +++ b/cores/nRF5/TinyUSB/Adafruit_TinyUSB_ArduinoCore/tinyusb/src/class/dfu/dfu_device.h @@ -0,0 +1,99 @@ +/* + * The MIT License (MIT) + * + * Copyright (c) 2021 XMOS LIMITED + * + * 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_DFU_DEVICE_H_ +#define _TUSB_DFU_DEVICE_H_ + +#include "dfu.h" + +#ifdef __cplusplus + extern "C" { +#endif + +//--------------------------------------------------------------------+ +// Class Driver Default Configure & Validation +//--------------------------------------------------------------------+ + +#if !defined(CFG_TUD_DFU_XFER_BUFSIZE) + #error "CFG_TUD_DFU_XFER_BUFSIZE must be defined, it has to be set to the buffer size used in TUD_DFU_DESCRIPTOR" +#endif + +//--------------------------------------------------------------------+ +// Application API +//--------------------------------------------------------------------+ + +// Must be called when the application is done with flashing started by +// tud_dfu_download_cb() and tud_dfu_manifest_cb(). +// status is DFU_STATUS_OK if successful, any other error status will cause state to enter dfuError +void tud_dfu_finish_flashing(uint8_t status); + +//--------------------------------------------------------------------+ +// Application Callback API (weak is optional) +//--------------------------------------------------------------------+ + +// Note: alt is used as the partition number, in order to support multiple partitions like FLASH, EEPROM, etc. + +// Invoked right before tud_dfu_download_cb() (state=DFU_DNBUSY) or tud_dfu_manifest_cb() (state=DFU_MANIFEST) +// Application return timeout in milliseconds (bwPollTimeout) for the next download/manifest operation. +// During this period, USB host won't try to communicate with us. +uint32_t tud_dfu_get_timeout_cb(uint8_t alt, uint8_t state); + +// Invoked when received DFU_DNLOAD (wLength>0) following by DFU_GETSTATUS (state=DFU_DNBUSY) requests +// This callback could be returned before flashing op is complete (async). +// Once finished flashing, application must call tud_dfu_finish_flashing() +void tud_dfu_download_cb (uint8_t alt, uint16_t block_num, uint8_t const *data, uint16_t length); + +// Invoked when download process is complete, received DFU_DNLOAD (wLength=0) following by DFU_GETSTATUS (state=Manifest) +// Application can do checksum, or actual flashing if buffered entire image previously. +// Once finished flashing, application must call tud_dfu_finish_flashing() +void tud_dfu_manifest_cb(uint8_t alt); + +// Invoked when received DFU_UPLOAD request +// Application must populate data with up to length bytes and +// Return the number of written bytes +uint16_t tud_dfu_upload_cb(uint8_t alt, uint16_t block_num, uint8_t* data, uint16_t length); + +// Invoked when a DFU_DETACH request is received +void tud_dfu_detach_cb(void); + +// Invoked when the Host has terminated a download or upload transfer +void tud_dfu_abort_cb(uint8_t alt); + +//--------------------------------------------------------------------+ +// Internal Class Driver API +//--------------------------------------------------------------------+ +void dfu_moded_init(void); +bool dfu_moded_deinit(void); +void dfu_moded_reset(uint8_t rhport); +uint16_t dfu_moded_open(uint8_t rhport, tusb_desc_interface_t const * itf_desc, uint16_t max_len); +bool dfu_moded_control_xfer_cb(uint8_t rhport, uint8_t stage, tusb_control_request_t const * request); + + +#ifdef __cplusplus + } +#endif + +#endif /* _TUSB_DFU_MODE_DEVICE_H_ */ diff --git a/cores/nRF5/TinyUSB/Adafruit_TinyUSB_ArduinoCore/tinyusb/src/class/dfu/dfu_rt_device.c b/cores/nRF5/TinyUSB/Adafruit_TinyUSB_ArduinoCore/tinyusb/src/class/dfu/dfu_rt_device.c index d4c3ecb..a63c23d 100644 --- a/cores/nRF5/TinyUSB/Adafruit_TinyUSB_ArduinoCore/tinyusb/src/class/dfu/dfu_rt_device.c +++ b/cores/nRF5/TinyUSB/Adafruit_TinyUSB_ArduinoCore/tinyusb/src/class/dfu/dfu_rt_device.c @@ -26,41 +26,39 @@ #include "tusb_option.h" -#if (TUSB_OPT_DEVICE_ENABLED && CFG_TUD_DFU_RT) +#if (CFG_TUD_ENABLED && CFG_TUD_DFU_RUNTIME) -#include "dfu_rt_device.h" +#include "device/usbd.h" #include "device/usbd_pvt.h" +#include "dfu_rt_device.h" + //--------------------------------------------------------------------+ // MACRO CONSTANT TYPEDEF //--------------------------------------------------------------------+ -typedef enum { - DFU_REQUEST_DETACH = 0, - DFU_REQUEST_DNLOAD = 1, - DFU_REQUEST_UPLOAD = 2, - DFU_REQUEST_GETSTATUS = 3, - DFU_REQUEST_CLRSTATUS = 4, - DFU_REQUEST_GETSTATE = 5, - DFU_REQUEST_ABORT = 6, -} dfu_requests_t; - -typedef struct TU_ATTR_PACKED -{ - uint8_t status; - uint8_t poll_timeout[3]; - uint8_t state; - uint8_t istring; -} dfu_status_t; + +// Level where CFG_TUSB_DEBUG must be at least for this driver is logged +#ifndef CFG_TUD_DFU_RUNTIME_LOG_LEVEL + #define CFG_TUD_DFU_RUNTIME_LOG_LEVEL CFG_TUD_LOG_LEVEL +#endif + +#define TU_LOG_DRV(...) TU_LOG(CFG_TUD_DFU_RUNTIME_LOG_LEVEL, __VA_ARGS__) + +//--------------------------------------------------------------------+ +// INTERNAL OBJECT & FUNCTION DECLARATION +//--------------------------------------------------------------------+ //--------------------------------------------------------------------+ // USBD Driver API //--------------------------------------------------------------------+ -void dfu_rtd_init(void) -{ +void dfu_rtd_init(void) { } -void dfu_rtd_reset(uint8_t rhport) -{ +bool dfu_rtd_deinit(void) { + return true; +} + +void dfu_rtd_reset(uint8_t rhport) { (void) rhport; } @@ -70,8 +68,8 @@ uint16_t dfu_rtd_open(uint8_t rhport, tusb_desc_interface_t const * itf_desc, ui (void) max_len; // Ensure this is DFU Runtime - TU_VERIFY(itf_desc->bInterfaceSubClass == TUD_DFU_APP_SUBCLASS && - itf_desc->bInterfaceProtocol == DFU_PROTOCOL_RT, 0); + TU_VERIFY((itf_desc->bInterfaceSubClass == TUD_DFU_APP_SUBCLASS) && + (itf_desc->bInterfaceProtocol == DFU_PROTOCOL_RT), 0); uint8_t const * p_desc = tu_desc_next( itf_desc ); uint16_t drv_len = sizeof(tusb_desc_interface_t); @@ -85,17 +83,14 @@ uint16_t dfu_rtd_open(uint8_t rhport, tusb_desc_interface_t const * itf_desc, ui return drv_len; } -bool dfu_rtd_control_complete(uint8_t rhport, tusb_control_request_t const * request) +// Invoked when a control transfer occurred on an interface of this class +// Driver response accordingly to the request and the transfer stage (setup/data/ack) +// return false to stall control endpoint (e.g unsupported request) +bool dfu_rtd_control_xfer_cb(uint8_t rhport, uint8_t stage, tusb_control_request_t const * request) { - (void) rhport; - (void) request; - - // nothing to do - return true; -} + // nothing to do with DATA or ACK stage + if ( stage != CONTROL_STAGE_SETUP ) return true; -bool dfu_rtd_control_request(uint8_t rhport, tusb_control_request_t const * request) -{ TU_VERIFY(request->bmRequestType_bit.recipient == TUSB_REQ_RCPT_INTERFACE); // dfu-util will try to claim the interface with SET_INTERFACE request before sending DFU request @@ -109,34 +104,34 @@ bool dfu_rtd_control_request(uint8_t rhport, tusb_control_request_t const * requ // Handle class request only from here TU_VERIFY(request->bmRequestType_bit.type == TUSB_REQ_TYPE_CLASS); - switch ( request->bRequest ) + switch (request->bRequest) { case DFU_REQUEST_DETACH: + { + TU_LOG_DRV(" DFU RT Request: DETACH\r\n"); tud_control_status(rhport, request); - tud_dfu_rt_reboot_to_dfu(); + tud_dfu_runtime_reboot_to_dfu_cb(); + } break; case DFU_REQUEST_GETSTATUS: { - // status = OK, poll timeout = 0, state = app idle, istring = 0 - uint8_t status_response[6] = { 0, 0, 0, 0, 0, 0 }; - tud_control_xfer(rhport, request, status_response, sizeof(status_response)); + TU_LOG_DRV(" DFU RT Request: GETSTATUS\r\n"); + dfu_status_response_t resp; + // Status = OK, Poll timeout is ignored during RT, State = APP_IDLE, IString = 0 + TU_VERIFY(tu_memset_s(&resp, sizeof(resp), 0x00, sizeof(resp))==0); + tud_control_xfer(rhport, request, &resp, sizeof(dfu_status_response_t)); } break; - default: return false; // stall unsupported request + default: + { + TU_LOG_DRV(" DFU RT Unexpected Request: %d\r\n", request->bRequest); + return false; // stall unsupported request + } } return true; } -bool dfu_rtd_xfer_cb(uint8_t rhport, uint8_t ep_addr, xfer_result_t result, uint32_t xferred_bytes) -{ - (void) rhport; - (void) ep_addr; - (void) result; - (void) xferred_bytes; - return true; -} - #endif diff --git a/cores/nRF5/TinyUSB/Adafruit_TinyUSB_ArduinoCore/tinyusb/src/class/dfu/dfu_rt_device.h b/cores/nRF5/TinyUSB/Adafruit_TinyUSB_ArduinoCore/tinyusb/src/class/dfu/dfu_rt_device.h index 91ead88..67eb26d 100644 --- a/cores/nRF5/TinyUSB/Adafruit_TinyUSB_ArduinoCore/tinyusb/src/class/dfu/dfu_rt_device.h +++ b/cores/nRF5/TinyUSB/Adafruit_TinyUSB_ArduinoCore/tinyusb/src/class/dfu/dfu_rt_device.h @@ -27,48 +27,26 @@ #ifndef _TUSB_DFU_RT_DEVICE_H_ #define _TUSB_DFU_RT_DEVICE_H_ -#include "common/tusb_common.h" -#include "device/usbd.h" +#include "dfu.h" #ifdef __cplusplus extern "C" { #endif - -//--------------------------------------------------------------------+ -// Common Definitions -//--------------------------------------------------------------------+ - -// DFU Protocol -typedef enum -{ - DFU_PROTOCOL_RT = 1, - DFU_PROTOCOL_DFU = 2, -} dfu_protocol_type_t; - -// DFU Descriptor Type -typedef enum -{ - DFU_DESC_FUNCTIONAL = 0x21, -} dfu_descriptor_type_t; - - //--------------------------------------------------------------------+ // Application Callback API (weak is optional) //--------------------------------------------------------------------+ - -// Invoked when received new data -TU_ATTR_WEAK void tud_dfu_rt_reboot_to_dfu(void); // TODO rename to _cb convention +// Invoked when a DFU_DETACH request is received and bitWillDetach is set +void tud_dfu_runtime_reboot_to_dfu_cb(void); //--------------------------------------------------------------------+ // Internal Class Driver API //--------------------------------------------------------------------+ void dfu_rtd_init(void); +bool dfu_rtd_deinit(void); void dfu_rtd_reset(uint8_t rhport); uint16_t dfu_rtd_open(uint8_t rhport, tusb_desc_interface_t const * itf_desc, uint16_t max_len); -bool dfu_rtd_control_request(uint8_t rhport, tusb_control_request_t const * request); -bool dfu_rtd_control_complete(uint8_t rhport, tusb_control_request_t const * request); -bool dfu_rtd_xfer_cb(uint8_t rhport, uint8_t ep_addr, xfer_result_t event, uint32_t xferred_bytes); +bool dfu_rtd_control_xfer_cb(uint8_t rhport, uint8_t stage, tusb_control_request_t const * request); #ifdef __cplusplus } diff --git a/cores/nRF5/TinyUSB/Adafruit_TinyUSB_ArduinoCore/tinyusb/src/class/hid/hid.h b/cores/nRF5/TinyUSB/Adafruit_TinyUSB_ArduinoCore/tinyusb/src/class/hid/hid.h index 8803e4b..b69f623 100644 --- a/cores/nRF5/TinyUSB/Adafruit_TinyUSB_ArduinoCore/tinyusb/src/class/hid/hid.h +++ b/cores/nRF5/TinyUSB/Adafruit_TinyUSB_ArduinoCore/tinyusb/src/class/hid/hid.h @@ -1,4 +1,4 @@ -/* +/* * The MIT License (MIT) * * Copyright (c) 2019 Ha Thach (tinyusb.org) @@ -43,7 +43,7 @@ /** \defgroup ClassDriver_HID_Common Common Definitions * @{ */ - /// USB HID Descriptor +/// USB HID Descriptor typedef struct TU_ATTR_PACKED { uint8_t bLength; /**< Numeric expression that is the total size of the HID descriptor */ @@ -62,15 +62,15 @@ typedef enum { HID_SUBCLASS_NONE = 0, ///< No Subclass HID_SUBCLASS_BOOT = 1 ///< Boot Interface Subclass -}hid_subclass_type_t; +}hid_subclass_enum_t; -/// HID Protocol +/// HID Interface Protocol typedef enum { - HID_PROTOCOL_NONE = 0, ///< None - HID_PROTOCOL_KEYBOARD = 1, ///< Keyboard - HID_PROTOCOL_MOUSE = 2 ///< Mouse -}hid_protocol_type_t; + HID_ITF_PROTOCOL_NONE = 0, ///< None + HID_ITF_PROTOCOL_KEYBOARD = 1, ///< Keyboard + HID_ITF_PROTOCOL_MOUSE = 2 ///< Mouse +}hid_interface_protocol_enum_t; /// HID Descriptor Type typedef enum @@ -78,7 +78,7 @@ typedef enum HID_DESC_TYPE_HID = 0x21, ///< HID Descriptor HID_DESC_TYPE_REPORT = 0x22, ///< Report Descriptor HID_DESC_TYPE_PHYSICAL = 0x23 ///< Physical Descriptor -}hid_descriptor_type_t; +}hid_descriptor_enum_t; /// HID Request Report Type typedef enum @@ -98,9 +98,9 @@ typedef enum HID_REQ_CONTROL_SET_REPORT = 0x09, ///< Set Report HID_REQ_CONTROL_SET_IDLE = 0x0a, ///< Set Idle HID_REQ_CONTROL_SET_PROTOCOL = 0x0b ///< Set Protocol -}hid_request_type_t; +}hid_request_enum_t; -/// HID Country Code +/// HID Local Code typedef enum { HID_LOCAL_NotSupported = 0 , ///< NotSupported @@ -139,10 +139,151 @@ typedef enum HID_LOCAL_US , ///< US HID_LOCAL_Yugoslavia , ///< Yugoslavia HID_LOCAL_Turkish_F ///< Turkish-F -} hid_country_code_t; +} hid_local_enum_t; + +// HID protocol value used by GetProtocol / SetProtocol +typedef enum +{ + HID_PROTOCOL_BOOT = 0, + HID_PROTOCOL_REPORT = 1 +} hid_protocol_mode_enum_t; /** @} */ +//--------------------------------------------------------------------+ +// GAMEPAD +//--------------------------------------------------------------------+ +/** \addtogroup ClassDriver_HID_Gamepad Gamepad + * @{ */ + +/* From https://www.kernel.org/doc/html/latest/input/gamepad.html + ____________________________ __ + / [__ZL__] [__ZR__] \ | + / [__ TL __] [__ TR __] \ | Front Triggers + __/________________________________\__ __| + / _ \ | + / /\ __ (N) \ | + / || __ |MO| __ _ _ \ | Main Pad + | <===DP===> |SE| |ST| (W) -|- (E) | | + \ || ___ ___ _ / | + /\ \/ / \ / \ (S) /\ __| + / \________ | LS | ____ | RS | ________/ \ | +| / \ \___/ / \ \___/ / \ | | Control Sticks +| / \_____/ \_____/ \ | __| +| / \ | + \_____/ \_____/ + + |________|______| |______|___________| + D-Pad Left Right Action Pad + Stick Stick + + |_____________| + Menu Pad + + Most gamepads have the following features: + - Action-Pad 4 buttons in diamonds-shape (on the right side) NORTH, SOUTH, WEST and EAST. + - D-Pad (Direction-pad) 4 buttons (on the left side) that point up, down, left and right. + - Menu-Pad Different constellations, but most-times 2 buttons: SELECT - START. + - Analog-Sticks provide freely moveable sticks to control directions, Analog-sticks may also + provide a digital button if you press them. + - Triggers are located on the upper-side of the pad in vertical direction. The upper buttons + are normally named Left- and Right-Triggers, the lower buttons Z-Left and Z-Right. + - Rumble Many devices provide force-feedback features. But are mostly just simple rumble motors. + */ + +/// HID Gamepad Protocol Report. +typedef struct TU_ATTR_PACKED +{ + int8_t x; ///< Delta x movement of left analog-stick + int8_t y; ///< Delta y movement of left analog-stick + int8_t z; ///< Delta z movement of right analog-joystick + int8_t rz; ///< Delta Rz movement of right analog-joystick + int8_t rx; ///< Delta Rx movement of analog left trigger + int8_t ry; ///< Delta Ry movement of analog right trigger + uint8_t hat; ///< Buttons mask for currently pressed buttons in the DPad/hat + uint32_t buttons; ///< Buttons mask for currently pressed buttons +}hid_gamepad_report_t; + +/// Standard Gamepad Buttons Bitmap +typedef enum +{ + GAMEPAD_BUTTON_0 = TU_BIT(0), + GAMEPAD_BUTTON_1 = TU_BIT(1), + GAMEPAD_BUTTON_2 = TU_BIT(2), + GAMEPAD_BUTTON_3 = TU_BIT(3), + GAMEPAD_BUTTON_4 = TU_BIT(4), + GAMEPAD_BUTTON_5 = TU_BIT(5), + GAMEPAD_BUTTON_6 = TU_BIT(6), + GAMEPAD_BUTTON_7 = TU_BIT(7), + GAMEPAD_BUTTON_8 = TU_BIT(8), + GAMEPAD_BUTTON_9 = TU_BIT(9), + GAMEPAD_BUTTON_10 = TU_BIT(10), + GAMEPAD_BUTTON_11 = TU_BIT(11), + GAMEPAD_BUTTON_12 = TU_BIT(12), + GAMEPAD_BUTTON_13 = TU_BIT(13), + GAMEPAD_BUTTON_14 = TU_BIT(14), + GAMEPAD_BUTTON_15 = TU_BIT(15), + GAMEPAD_BUTTON_16 = TU_BIT(16), + GAMEPAD_BUTTON_17 = TU_BIT(17), + GAMEPAD_BUTTON_18 = TU_BIT(18), + GAMEPAD_BUTTON_19 = TU_BIT(19), + GAMEPAD_BUTTON_20 = TU_BIT(20), + GAMEPAD_BUTTON_21 = TU_BIT(21), + GAMEPAD_BUTTON_22 = TU_BIT(22), + GAMEPAD_BUTTON_23 = TU_BIT(23), + GAMEPAD_BUTTON_24 = TU_BIT(24), + GAMEPAD_BUTTON_25 = TU_BIT(25), + GAMEPAD_BUTTON_26 = TU_BIT(26), + GAMEPAD_BUTTON_27 = TU_BIT(27), + GAMEPAD_BUTTON_28 = TU_BIT(28), + GAMEPAD_BUTTON_29 = TU_BIT(29), + GAMEPAD_BUTTON_30 = TU_BIT(30), + GAMEPAD_BUTTON_31 = TU_BIT(31), +}hid_gamepad_button_bm_t; + +/// Standard Gamepad Buttons Naming from Linux input event codes +/// https://github.com/torvalds/linux/blob/master/include/uapi/linux/input-event-codes.h +#define GAMEPAD_BUTTON_A GAMEPAD_BUTTON_0 +#define GAMEPAD_BUTTON_SOUTH GAMEPAD_BUTTON_0 + +#define GAMEPAD_BUTTON_B GAMEPAD_BUTTON_1 +#define GAMEPAD_BUTTON_EAST GAMEPAD_BUTTON_1 + +#define GAMEPAD_BUTTON_C GAMEPAD_BUTTON_2 + +#define GAMEPAD_BUTTON_X GAMEPAD_BUTTON_3 +#define GAMEPAD_BUTTON_NORTH GAMEPAD_BUTTON_3 + +#define GAMEPAD_BUTTON_Y GAMEPAD_BUTTON_4 +#define GAMEPAD_BUTTON_WEST GAMEPAD_BUTTON_4 + +#define GAMEPAD_BUTTON_Z GAMEPAD_BUTTON_5 +#define GAMEPAD_BUTTON_TL GAMEPAD_BUTTON_6 +#define GAMEPAD_BUTTON_TR GAMEPAD_BUTTON_7 +#define GAMEPAD_BUTTON_TL2 GAMEPAD_BUTTON_8 +#define GAMEPAD_BUTTON_TR2 GAMEPAD_BUTTON_9 +#define GAMEPAD_BUTTON_SELECT GAMEPAD_BUTTON_10 +#define GAMEPAD_BUTTON_START GAMEPAD_BUTTON_11 +#define GAMEPAD_BUTTON_MODE GAMEPAD_BUTTON_12 +#define GAMEPAD_BUTTON_THUMBL GAMEPAD_BUTTON_13 +#define GAMEPAD_BUTTON_THUMBR GAMEPAD_BUTTON_14 + +/// Standard Gamepad HAT/DPAD Buttons (from Linux input event codes) +typedef enum +{ + GAMEPAD_HAT_CENTERED = 0, ///< DPAD_CENTERED + GAMEPAD_HAT_UP = 1, ///< DPAD_UP + GAMEPAD_HAT_UP_RIGHT = 2, ///< DPAD_UP_RIGHT + GAMEPAD_HAT_RIGHT = 3, ///< DPAD_RIGHT + GAMEPAD_HAT_DOWN_RIGHT = 4, ///< DPAD_DOWN_RIGHT + GAMEPAD_HAT_DOWN = 5, ///< DPAD_DOWN + GAMEPAD_HAT_DOWN_LEFT = 6, ///< DPAD_DOWN_LEFT + GAMEPAD_HAT_LEFT = 7, ///< DPAD_LEFT + GAMEPAD_HAT_UP_LEFT = 8, ///< DPAD_UP_LEFT +}hid_gamepad_hat_t; + +/// @} + //--------------------------------------------------------------------+ // MOUSE //--------------------------------------------------------------------+ @@ -159,6 +300,19 @@ typedef struct TU_ATTR_PACKED int8_t pan; // using AC Pan } hid_mouse_report_t; + +// Absolute Mouse: same as the Standard (relative) Mouse Report but +// with int16_t instead of int8_t for X and Y coordinates. +typedef struct TU_ATTR_PACKED +{ + uint8_t buttons; /**< buttons mask for currently pressed buttons in the mouse. */ + int16_t x; /**< Current x position of the mouse. */ + int16_t y; /**< Current y position of the mouse. */ + int8_t wheel; /**< Current delta wheel movement on the mouse. */ + int8_t pan; // using AC Pan +} hid_abs_mouse_report_t; + + /// Standard Mouse Buttons Bitmap typedef enum { @@ -171,6 +325,29 @@ typedef enum /// @} +//--------------------------------------------------------------------+ +// Digitizer Stylus Pen +//--------------------------------------------------------------------+ +/** \addtogroup ClassDriver_HID_Stylus Stylus + * @{ */ + +// Standard Stylus Pen Report. +typedef struct TU_ATTR_PACKED +{ + uint8_t attr; /**< Attribute mask for describing current status of the stylus pen. */ + uint16_t x; /**< Current x position of the mouse. */ + uint16_t y; /**< Current y position of the mouse. */ +} hid_stylus_report_t; + +// Standard Stylus Pen Attributes Bitmap. +typedef enum +{ + STYLUS_ATTR_TIP_SWITCH = TU_BIT(0), ///< Tip switch + STYLUS_ATTR_IN_RANGE = TU_BIT(1), ///< In-range bit. +} hid_stylus_attr_bm_t; + +/// @} + //--------------------------------------------------------------------+ // Keyboard //--------------------------------------------------------------------+ @@ -212,123 +389,230 @@ typedef enum //--------------------------------------------------------------------+ // HID KEYCODE //--------------------------------------------------------------------+ -#define HID_KEY_NONE 0x00 -#define HID_KEY_A 0x04 -#define HID_KEY_B 0x05 -#define HID_KEY_C 0x06 -#define HID_KEY_D 0x07 -#define HID_KEY_E 0x08 -#define HID_KEY_F 0x09 -#define HID_KEY_G 0x0A -#define HID_KEY_H 0x0B -#define HID_KEY_I 0x0C -#define HID_KEY_J 0x0D -#define HID_KEY_K 0x0E -#define HID_KEY_L 0x0F -#define HID_KEY_M 0x10 -#define HID_KEY_N 0x11 -#define HID_KEY_O 0x12 -#define HID_KEY_P 0x13 -#define HID_KEY_Q 0x14 -#define HID_KEY_R 0x15 -#define HID_KEY_S 0x16 -#define HID_KEY_T 0x17 -#define HID_KEY_U 0x18 -#define HID_KEY_V 0x19 -#define HID_KEY_W 0x1A -#define HID_KEY_X 0x1B -#define HID_KEY_Y 0x1C -#define HID_KEY_Z 0x1D -#define HID_KEY_1 0x1E -#define HID_KEY_2 0x1F -#define HID_KEY_3 0x20 -#define HID_KEY_4 0x21 -#define HID_KEY_5 0x22 -#define HID_KEY_6 0x23 -#define HID_KEY_7 0x24 -#define HID_KEY_8 0x25 -#define HID_KEY_9 0x26 -#define HID_KEY_0 0x27 -#define HID_KEY_RETURN 0x28 -#define HID_KEY_ESCAPE 0x29 -#define HID_KEY_BACKSPACE 0x2A -#define HID_KEY_TAB 0x2B -#define HID_KEY_SPACE 0x2C -#define HID_KEY_MINUS 0x2D -#define HID_KEY_EQUAL 0x2E -#define HID_KEY_BRACKET_LEFT 0x2F -#define HID_KEY_BRACKET_RIGHT 0x30 -#define HID_KEY_BACKSLASH 0x31 -#define HID_KEY_EUROPE_1 0x32 -#define HID_KEY_SEMICOLON 0x33 -#define HID_KEY_APOSTROPHE 0x34 -#define HID_KEY_GRAVE 0x35 -#define HID_KEY_COMMA 0x36 -#define HID_KEY_PERIOD 0x37 -#define HID_KEY_SLASH 0x38 -#define HID_KEY_CAPS_LOCK 0x39 -#define HID_KEY_F1 0x3A -#define HID_KEY_F2 0x3B -#define HID_KEY_F3 0x3C -#define HID_KEY_F4 0x3D -#define HID_KEY_F5 0x3E -#define HID_KEY_F6 0x3F -#define HID_KEY_F7 0x40 -#define HID_KEY_F8 0x41 -#define HID_KEY_F9 0x42 -#define HID_KEY_F10 0x43 -#define HID_KEY_F11 0x44 -#define HID_KEY_F12 0x45 -#define HID_KEY_PRINT_SCREEN 0x46 -#define HID_KEY_SCROLL_LOCK 0x47 -#define HID_KEY_PAUSE 0x48 -#define HID_KEY_INSERT 0x49 -#define HID_KEY_HOME 0x4A -#define HID_KEY_PAGE_UP 0x4B -#define HID_KEY_DELETE 0x4C -#define HID_KEY_END 0x4D -#define HID_KEY_PAGE_DOWN 0x4E -#define HID_KEY_ARROW_RIGHT 0x4F -#define HID_KEY_ARROW_LEFT 0x50 -#define HID_KEY_ARROW_DOWN 0x51 -#define HID_KEY_ARROW_UP 0x52 -#define HID_KEY_NUM_LOCK 0x53 -#define HID_KEY_KEYPAD_DIVIDE 0x54 -#define HID_KEY_KEYPAD_MULTIPLY 0x55 -#define HID_KEY_KEYPAD_SUBTRACT 0x56 -#define HID_KEY_KEYPAD_ADD 0x57 -#define HID_KEY_KEYPAD_ENTER 0x58 -#define HID_KEY_KEYPAD_1 0x59 -#define HID_KEY_KEYPAD_2 0x5A -#define HID_KEY_KEYPAD_3 0x5B -#define HID_KEY_KEYPAD_4 0x5C -#define HID_KEY_KEYPAD_5 0x5D -#define HID_KEY_KEYPAD_6 0x5E -#define HID_KEY_KEYPAD_7 0x5F -#define HID_KEY_KEYPAD_8 0x60 -#define HID_KEY_KEYPAD_9 0x61 -#define HID_KEY_KEYPAD_0 0x62 -#define HID_KEY_KEYPAD_DECIMAL 0x63 -#define HID_KEY_EUROPE_2 0x64 -#define HID_KEY_APPLICATION 0x65 -#define HID_KEY_POWER 0x66 -#define HID_KEY_KEYPAD_EQUAL 0x67 -#define HID_KEY_F13 0x68 -#define HID_KEY_F14 0x69 -#define HID_KEY_F15 0x6A -#define HID_KEY_CONTROL_LEFT 0xE0 -#define HID_KEY_SHIFT_LEFT 0xE1 -#define HID_KEY_ALT_LEFT 0xE2 -#define HID_KEY_GUI_LEFT 0xE3 -#define HID_KEY_CONTROL_RIGHT 0xE4 -#define HID_KEY_SHIFT_RIGHT 0xE5 -#define HID_KEY_ALT_RIGHT 0xE6 -#define HID_KEY_GUI_RIGHT 0xE7 +#define HID_KEY_NONE 0x00 +#define HID_KEY_A 0x04 +#define HID_KEY_B 0x05 +#define HID_KEY_C 0x06 +#define HID_KEY_D 0x07 +#define HID_KEY_E 0x08 +#define HID_KEY_F 0x09 +#define HID_KEY_G 0x0A +#define HID_KEY_H 0x0B +#define HID_KEY_I 0x0C +#define HID_KEY_J 0x0D +#define HID_KEY_K 0x0E +#define HID_KEY_L 0x0F +#define HID_KEY_M 0x10 +#define HID_KEY_N 0x11 +#define HID_KEY_O 0x12 +#define HID_KEY_P 0x13 +#define HID_KEY_Q 0x14 +#define HID_KEY_R 0x15 +#define HID_KEY_S 0x16 +#define HID_KEY_T 0x17 +#define HID_KEY_U 0x18 +#define HID_KEY_V 0x19 +#define HID_KEY_W 0x1A +#define HID_KEY_X 0x1B +#define HID_KEY_Y 0x1C +#define HID_KEY_Z 0x1D +#define HID_KEY_1 0x1E +#define HID_KEY_2 0x1F +#define HID_KEY_3 0x20 +#define HID_KEY_4 0x21 +#define HID_KEY_5 0x22 +#define HID_KEY_6 0x23 +#define HID_KEY_7 0x24 +#define HID_KEY_8 0x25 +#define HID_KEY_9 0x26 +#define HID_KEY_0 0x27 +#define HID_KEY_ENTER 0x28 +#define HID_KEY_ESCAPE 0x29 +#define HID_KEY_BACKSPACE 0x2A +#define HID_KEY_TAB 0x2B +#define HID_KEY_SPACE 0x2C +#define HID_KEY_MINUS 0x2D +#define HID_KEY_EQUAL 0x2E +#define HID_KEY_BRACKET_LEFT 0x2F +#define HID_KEY_BRACKET_RIGHT 0x30 +#define HID_KEY_BACKSLASH 0x31 +#define HID_KEY_EUROPE_1 0x32 +#define HID_KEY_SEMICOLON 0x33 +#define HID_KEY_APOSTROPHE 0x34 +#define HID_KEY_GRAVE 0x35 +#define HID_KEY_COMMA 0x36 +#define HID_KEY_PERIOD 0x37 +#define HID_KEY_SLASH 0x38 +#define HID_KEY_CAPS_LOCK 0x39 +#define HID_KEY_F1 0x3A +#define HID_KEY_F2 0x3B +#define HID_KEY_F3 0x3C +#define HID_KEY_F4 0x3D +#define HID_KEY_F5 0x3E +#define HID_KEY_F6 0x3F +#define HID_KEY_F7 0x40 +#define HID_KEY_F8 0x41 +#define HID_KEY_F9 0x42 +#define HID_KEY_F10 0x43 +#define HID_KEY_F11 0x44 +#define HID_KEY_F12 0x45 +#define HID_KEY_PRINT_SCREEN 0x46 +#define HID_KEY_SCROLL_LOCK 0x47 +#define HID_KEY_PAUSE 0x48 +#define HID_KEY_INSERT 0x49 +#define HID_KEY_HOME 0x4A +#define HID_KEY_PAGE_UP 0x4B +#define HID_KEY_DELETE 0x4C +#define HID_KEY_END 0x4D +#define HID_KEY_PAGE_DOWN 0x4E +#define HID_KEY_ARROW_RIGHT 0x4F +#define HID_KEY_ARROW_LEFT 0x50 +#define HID_KEY_ARROW_DOWN 0x51 +#define HID_KEY_ARROW_UP 0x52 +#define HID_KEY_NUM_LOCK 0x53 +#define HID_KEY_KEYPAD_DIVIDE 0x54 +#define HID_KEY_KEYPAD_MULTIPLY 0x55 +#define HID_KEY_KEYPAD_SUBTRACT 0x56 +#define HID_KEY_KEYPAD_ADD 0x57 +#define HID_KEY_KEYPAD_ENTER 0x58 +#define HID_KEY_KEYPAD_1 0x59 +#define HID_KEY_KEYPAD_2 0x5A +#define HID_KEY_KEYPAD_3 0x5B +#define HID_KEY_KEYPAD_4 0x5C +#define HID_KEY_KEYPAD_5 0x5D +#define HID_KEY_KEYPAD_6 0x5E +#define HID_KEY_KEYPAD_7 0x5F +#define HID_KEY_KEYPAD_8 0x60 +#define HID_KEY_KEYPAD_9 0x61 +#define HID_KEY_KEYPAD_0 0x62 +#define HID_KEY_KEYPAD_DECIMAL 0x63 +#define HID_KEY_EUROPE_2 0x64 +#define HID_KEY_APPLICATION 0x65 +#define HID_KEY_POWER 0x66 +#define HID_KEY_KEYPAD_EQUAL 0x67 +#define HID_KEY_F13 0x68 +#define HID_KEY_F14 0x69 +#define HID_KEY_F15 0x6A +#define HID_KEY_F16 0x6B +#define HID_KEY_F17 0x6C +#define HID_KEY_F18 0x6D +#define HID_KEY_F19 0x6E +#define HID_KEY_F20 0x6F +#define HID_KEY_F21 0x70 +#define HID_KEY_F22 0x71 +#define HID_KEY_F23 0x72 +#define HID_KEY_F24 0x73 +#define HID_KEY_EXECUTE 0x74 +#define HID_KEY_HELP 0x75 +#define HID_KEY_MENU 0x76 +#define HID_KEY_SELECT 0x77 +#define HID_KEY_STOP 0x78 +#define HID_KEY_AGAIN 0x79 +#define HID_KEY_UNDO 0x7A +#define HID_KEY_CUT 0x7B +#define HID_KEY_COPY 0x7C +#define HID_KEY_PASTE 0x7D +#define HID_KEY_FIND 0x7E +#define HID_KEY_MUTE 0x7F +#define HID_KEY_VOLUME_UP 0x80 +#define HID_KEY_VOLUME_DOWN 0x81 +#define HID_KEY_LOCKING_CAPS_LOCK 0x82 +#define HID_KEY_LOCKING_NUM_LOCK 0x83 +#define HID_KEY_LOCKING_SCROLL_LOCK 0x84 +#define HID_KEY_KEYPAD_COMMA 0x85 +#define HID_KEY_KEYPAD_EQUAL_SIGN 0x86 +#define HID_KEY_KANJI1 0x87 +#define HID_KEY_KANJI2 0x88 +#define HID_KEY_KANJI3 0x89 +#define HID_KEY_KANJI4 0x8A +#define HID_KEY_KANJI5 0x8B +#define HID_KEY_KANJI6 0x8C +#define HID_KEY_KANJI7 0x8D +#define HID_KEY_KANJI8 0x8E +#define HID_KEY_KANJI9 0x8F +#define HID_KEY_LANG1 0x90 +#define HID_KEY_LANG2 0x91 +#define HID_KEY_LANG3 0x92 +#define HID_KEY_LANG4 0x93 +#define HID_KEY_LANG5 0x94 +#define HID_KEY_LANG6 0x95 +#define HID_KEY_LANG7 0x96 +#define HID_KEY_LANG8 0x97 +#define HID_KEY_LANG9 0x98 +#define HID_KEY_ALTERNATE_ERASE 0x99 +#define HID_KEY_SYSREQ_ATTENTION 0x9A +#define HID_KEY_CANCEL 0x9B +#define HID_KEY_CLEAR 0x9C +#define HID_KEY_PRIOR 0x9D +#define HID_KEY_RETURN 0x9E +#define HID_KEY_SEPARATOR 0x9F +#define HID_KEY_OUT 0xA0 +#define HID_KEY_OPER 0xA1 +#define HID_KEY_CLEAR_AGAIN 0xA2 +#define HID_KEY_CRSEL_PROPS 0xA3 +#define HID_KEY_EXSEL 0xA4 +// RESERVED 0xA5-AF +#define HID_KEY_KEYPAD_00 0xB0 +#define HID_KEY_KEYPAD_000 0xB1 +#define HID_KEY_THOUSANDS_SEPARATOR 0xB2 +#define HID_KEY_DECIMAL_SEPARATOR 0xB3 +#define HID_KEY_CURRENCY_UNIT 0xB4 +#define HID_KEY_CURRENCY_SUBUNIT 0xB5 +#define HID_KEY_KEYPAD_LEFT_PARENTHESIS 0xB6 +#define HID_KEY_KEYPAD_RIGHT_PARENTHESIS 0xB7 +#define HID_KEY_KEYPAD_LEFT_BRACE 0xB8 +#define HID_KEY_KEYPAD_RIGHT_BRACE 0xB9 +#define HID_KEY_KEYPAD_TAB 0xBA +#define HID_KEY_KEYPAD_BACKSPACE 0xBB +#define HID_KEY_KEYPAD_A 0xBC +#define HID_KEY_KEYPAD_B 0xBD +#define HID_KEY_KEYPAD_C 0xBE +#define HID_KEY_KEYPAD_D 0xBF +#define HID_KEY_KEYPAD_E 0xC0 +#define HID_KEY_KEYPAD_F 0xC1 +#define HID_KEY_KEYPAD_XOR 0xC2 +#define HID_KEY_KEYPAD_CARET 0xC3 +#define HID_KEY_KEYPAD_PERCENT 0xC4 +#define HID_KEY_KEYPAD_LESS_THAN 0xC5 +#define HID_KEY_KEYPAD_GREATER_THAN 0xC6 +#define HID_KEY_KEYPAD_AMPERSAND 0xC7 +#define HID_KEY_KEYPAD_DOUBLE_AMPERSAND 0xC8 +#define HID_KEY_KEYPAD_VERTICAL_BAR 0xC9 +#define HID_KEY_KEYPAD_DOUBLE_VERTICAL_BAR 0xCA +#define HID_KEY_KEYPAD_COLON 0xCB +#define HID_KEY_KEYPAD_HASH 0xCC +#define HID_KEY_KEYPAD_SPACE 0xCD +#define HID_KEY_KEYPAD_AT 0xCE +#define HID_KEY_KEYPAD_EXCLAMATION 0xCF +#define HID_KEY_KEYPAD_MEMORY_STORE 0xD0 +#define HID_KEY_KEYPAD_MEMORY_RECALL 0xD1 +#define HID_KEY_KEYPAD_MEMORY_CLEAR 0xD2 +#define HID_KEY_KEYPAD_MEMORY_ADD 0xD3 +#define HID_KEY_KEYPAD_MEMORY_SUBTRACT 0xD4 +#define HID_KEY_KEYPAD_MEMORY_MULTIPLY 0xD5 +#define HID_KEY_KEYPAD_MEMORY_DIVIDE 0xD6 +#define HID_KEY_KEYPAD_PLUS_MINUS 0xD7 +#define HID_KEY_KEYPAD_CLEAR 0xD8 +#define HID_KEY_KEYPAD_CLEAR_ENTRY 0xD9 +#define HID_KEY_KEYPAD_BINARY 0xDA +#define HID_KEY_KEYPAD_OCTAL 0xDB +#define HID_KEY_KEYPAD_DECIMAL_2 0xDC +#define HID_KEY_KEYPAD_HEXADECIMAL 0xDD +// RESERVED 0xDE-DF +#define HID_KEY_CONTROL_LEFT 0xE0 +#define HID_KEY_SHIFT_LEFT 0xE1 +#define HID_KEY_ALT_LEFT 0xE2 +#define HID_KEY_GUI_LEFT 0xE3 +#define HID_KEY_CONTROL_RIGHT 0xE4 +#define HID_KEY_SHIFT_RIGHT 0xE5 +#define HID_KEY_ALT_RIGHT 0xE6 +#define HID_KEY_GUI_RIGHT 0xE7 //--------------------------------------------------------------------+ // REPORT DESCRIPTOR //--------------------------------------------------------------------+ + //------------- ITEM & TAG -------------// #define HID_REPORT_DATA_0(data) #define HID_REPORT_DATA_1(data) , data @@ -338,18 +622,31 @@ typedef enum #define HID_REPORT_ITEM(data, tag, type, size) \ (((tag) << 4) | ((type) << 2) | (size)) HID_REPORT_DATA_##size(data) -#define RI_TYPE_MAIN 0 -#define RI_TYPE_GLOBAL 1 -#define RI_TYPE_LOCAL 2 +// Report Item Types +enum { + RI_TYPE_MAIN = 0, + RI_TYPE_GLOBAL = 1, + RI_TYPE_LOCAL = 2 +}; + +//------------- Main Items - HID 1.11 section 6.2.2.4 -------------// -//------------- MAIN ITEMS 6.2.2.4 -------------// -#define HID_INPUT(x) HID_REPORT_ITEM(x, 8, RI_TYPE_MAIN, 1) -#define HID_OUTPUT(x) HID_REPORT_ITEM(x, 9, RI_TYPE_MAIN, 1) -#define HID_COLLECTION(x) HID_REPORT_ITEM(x, 10, RI_TYPE_MAIN, 1) -#define HID_FEATURE(x) HID_REPORT_ITEM(x, 11, RI_TYPE_MAIN, 1) -#define HID_COLLECTION_END HID_REPORT_ITEM(x, 12, RI_TYPE_MAIN, 0) +// Report Item Main group +enum { + RI_MAIN_INPUT = 8, + RI_MAIN_OUTPUT = 9, + RI_MAIN_COLLECTION = 10, + RI_MAIN_FEATURE = 11, + RI_MAIN_COLLECTION_END = 12 +}; -//------------- INPUT, OUTPUT, FEATURE 6.2.2.5 -------------// +#define HID_INPUT(x) HID_REPORT_ITEM(x, RI_MAIN_INPUT , RI_TYPE_MAIN, 1) +#define HID_OUTPUT(x) HID_REPORT_ITEM(x, RI_MAIN_OUTPUT , RI_TYPE_MAIN, 1) +#define HID_COLLECTION(x) HID_REPORT_ITEM(x, RI_MAIN_COLLECTION , RI_TYPE_MAIN, 1) +#define HID_FEATURE(x) HID_REPORT_ITEM(x, RI_MAIN_FEATURE , RI_TYPE_MAIN, 1) +#define HID_COLLECTION_END HID_REPORT_ITEM(x, RI_MAIN_COLLECTION_END, RI_TYPE_MAIN, 0) + +//------------- Input, Output, Feature - HID 1.11 section 6.2.2.5 -------------// #define HID_DATA (0<<0) #define HID_CONSTANT (1<<0) @@ -377,7 +674,7 @@ typedef enum #define HID_BITFIELD (0<<8) #define HID_BUFFERED_BYTES (1<<8) -//------------- COLLECTION ITEM 6.2.2.6 -------------// +//------------- Collection Item - HID 1.11 section 6.2.2.6 -------------// enum { HID_COLLECTION_PHYSICAL = 0, HID_COLLECTION_APPLICATION, @@ -388,81 +685,138 @@ enum { HID_COLLECTION_USAGE_MODIFIER }; -//------------- GLOBAL ITEMS 6.2.2.7 -------------// -#define HID_USAGE_PAGE(x) HID_REPORT_ITEM(x, 0, RI_TYPE_GLOBAL, 1) -#define HID_USAGE_PAGE_N(x, n) HID_REPORT_ITEM(x, 0, RI_TYPE_GLOBAL, n) +//------------- Global Items - HID 1.11 section 6.2.2.7 -------------// + +// Report Item Global group +enum { + RI_GLOBAL_USAGE_PAGE = 0, + RI_GLOBAL_LOGICAL_MIN = 1, + RI_GLOBAL_LOGICAL_MAX = 2, + RI_GLOBAL_PHYSICAL_MIN = 3, + RI_GLOBAL_PHYSICAL_MAX = 4, + RI_GLOBAL_UNIT_EXPONENT = 5, + RI_GLOBAL_UNIT = 6, + RI_GLOBAL_REPORT_SIZE = 7, + RI_GLOBAL_REPORT_ID = 8, + RI_GLOBAL_REPORT_COUNT = 9, + RI_GLOBAL_PUSH = 10, + RI_GLOBAL_POP = 11 +}; + +#define HID_USAGE_PAGE(x) HID_REPORT_ITEM(x, RI_GLOBAL_USAGE_PAGE, RI_TYPE_GLOBAL, 1) +#define HID_USAGE_PAGE_N(x, n) HID_REPORT_ITEM(x, RI_GLOBAL_USAGE_PAGE, RI_TYPE_GLOBAL, n) -#define HID_LOGICAL_MIN(x) HID_REPORT_ITEM(x, 1, RI_TYPE_GLOBAL, 1) -#define HID_LOGICAL_MIN_N(x, n) HID_REPORT_ITEM(x, 1, RI_TYPE_GLOBAL, n) +#define HID_LOGICAL_MIN(x) HID_REPORT_ITEM(x, RI_GLOBAL_LOGICAL_MIN, RI_TYPE_GLOBAL, 1) +#define HID_LOGICAL_MIN_N(x, n) HID_REPORT_ITEM(x, RI_GLOBAL_LOGICAL_MIN, RI_TYPE_GLOBAL, n) -#define HID_LOGICAL_MAX(x) HID_REPORT_ITEM(x, 2, RI_TYPE_GLOBAL, 1) -#define HID_LOGICAL_MAX_N(x, n) HID_REPORT_ITEM(x, 2, RI_TYPE_GLOBAL, n) +#define HID_LOGICAL_MAX(x) HID_REPORT_ITEM(x, RI_GLOBAL_LOGICAL_MAX, RI_TYPE_GLOBAL, 1) +#define HID_LOGICAL_MAX_N(x, n) HID_REPORT_ITEM(x, RI_GLOBAL_LOGICAL_MAX, RI_TYPE_GLOBAL, n) -#define HID_PHYSICAL_MIN(x) HID_REPORT_ITEM(x, 3, RI_TYPE_GLOBAL, 1) -#define HID_PHYSICAL_MIN_N(x, n) HID_REPORT_ITEM(x, 3, RI_TYPE_GLOBAL, n) +#define HID_PHYSICAL_MIN(x) HID_REPORT_ITEM(x, RI_GLOBAL_PHYSICAL_MIN, RI_TYPE_GLOBAL, 1) +#define HID_PHYSICAL_MIN_N(x, n) HID_REPORT_ITEM(x, RI_GLOBAL_PHYSICAL_MIN, RI_TYPE_GLOBAL, n) -#define HID_PHYSICAL_MAX(x) HID_REPORT_ITEM(x, 4, RI_TYPE_GLOBAL, 1) -#define HID_PHYSICAL_MAX_N(x, n) HID_REPORT_ITEM(x, 4, RI_TYPE_GLOBAL, n) +#define HID_PHYSICAL_MAX(x) HID_REPORT_ITEM(x, RI_GLOBAL_PHYSICAL_MAX, RI_TYPE_GLOBAL, 1) +#define HID_PHYSICAL_MAX_N(x, n) HID_REPORT_ITEM(x, RI_GLOBAL_PHYSICAL_MAX, RI_TYPE_GLOBAL, n) -#define HID_UNIT_EXPONENT(x) HID_REPORT_ITEM(x, 5, RI_TYPE_GLOBAL, 1) -#define HID_UNIT_EXPONENT_N(x, n) HID_REPORT_ITEM(x, 5, RI_TYPE_GLOBAL, n) +#define HID_UNIT_EXPONENT(x) HID_REPORT_ITEM(x, RI_GLOBAL_UNIT_EXPONENT, RI_TYPE_GLOBAL, 1) +#define HID_UNIT_EXPONENT_N(x, n) HID_REPORT_ITEM(x, RI_GLOBAL_UNIT_EXPONENT, RI_TYPE_GLOBAL, n) -#define HID_UNIT(x) HID_REPORT_ITEM(x, 6, RI_TYPE_GLOBAL, 1) -#define HID_UNIT_N(x, n) HID_REPORT_ITEM(x, 6, RI_TYPE_GLOBAL, n) +#define HID_UNIT(x) HID_REPORT_ITEM(x, RI_GLOBAL_UNIT, RI_TYPE_GLOBAL, 1) +#define HID_UNIT_N(x, n) HID_REPORT_ITEM(x, RI_GLOBAL_UNIT, RI_TYPE_GLOBAL, n) -#define HID_REPORT_SIZE(x) HID_REPORT_ITEM(x, 7, RI_TYPE_GLOBAL, 1) -#define HID_REPORT_SIZE_N(x, n) HID_REPORT_ITEM(x, 7, RI_TYPE_GLOBAL, n) +#define HID_REPORT_SIZE(x) HID_REPORT_ITEM(x, RI_GLOBAL_REPORT_SIZE, RI_TYPE_GLOBAL, 1) +#define HID_REPORT_SIZE_N(x, n) HID_REPORT_ITEM(x, RI_GLOBAL_REPORT_SIZE, RI_TYPE_GLOBAL, n) -#define HID_REPORT_ID(x) HID_REPORT_ITEM(x, 8, RI_TYPE_GLOBAL, 1), -#define HID_REPORT_ID_N(x) HID_REPORT_ITEM(x, 8, RI_TYPE_GLOBAL, n), +#define HID_REPORT_ID(x) HID_REPORT_ITEM(x, RI_GLOBAL_REPORT_ID, RI_TYPE_GLOBAL, 1), +#define HID_REPORT_ID_N(x, n) HID_REPORT_ITEM(x, RI_GLOBAL_REPORT_ID, RI_TYPE_GLOBAL, n), -#define HID_REPORT_COUNT(x) HID_REPORT_ITEM(x, 9, RI_TYPE_GLOBAL, 1) -#define HID_REPORT_COUNT_N(x, n) HID_REPORT_ITEM(x, 9, RI_TYPE_GLOBAL, n) +#define HID_REPORT_COUNT(x) HID_REPORT_ITEM(x, RI_GLOBAL_REPORT_COUNT, RI_TYPE_GLOBAL, 1) +#define HID_REPORT_COUNT_N(x, n) HID_REPORT_ITEM(x, RI_GLOBAL_REPORT_COUNT, RI_TYPE_GLOBAL, n) -#define HID_PUSH HID_REPORT_ITEM(x, 10, RI_TYPE_GLOBAL, 0) -#define HID_POP HID_REPORT_ITEM(x, 11, RI_TYPE_GLOBAL, 0) +#define HID_PUSH HID_REPORT_ITEM(x, RI_GLOBAL_PUSH, RI_TYPE_GLOBAL, 0) +#define HID_POP HID_REPORT_ITEM(x, RI_GLOBAL_POP, RI_TYPE_GLOBAL, 0) //------------- LOCAL ITEMS 6.2.2.8 -------------// -#define HID_USAGE(x) HID_REPORT_ITEM(x, 0, RI_TYPE_LOCAL, 1) -#define HID_USAGE_N(x, n) HID_REPORT_ITEM(x, 0, RI_TYPE_LOCAL, n) -#define HID_USAGE_MIN(x) HID_REPORT_ITEM(x, 1, RI_TYPE_LOCAL, 1) -#define HID_USAGE_MIN_N(x, n) HID_REPORT_ITEM(x, 1, RI_TYPE_LOCAL, n) +enum { + RI_LOCAL_USAGE = 0, + RI_LOCAL_USAGE_MIN = 1, + RI_LOCAL_USAGE_MAX = 2, + RI_LOCAL_DESIGNATOR_INDEX = 3, + RI_LOCAL_DESIGNATOR_MIN = 4, + RI_LOCAL_DESIGNATOR_MAX = 5, + // 6 is reserved + RI_LOCAL_STRING_INDEX = 7, + RI_LOCAL_STRING_MIN = 8, + RI_LOCAL_STRING_MAX = 9, + RI_LOCAL_DELIMITER = 10, +}; + +#define HID_USAGE(x) HID_REPORT_ITEM(x, RI_LOCAL_USAGE, RI_TYPE_LOCAL, 1) +#define HID_USAGE_N(x, n) HID_REPORT_ITEM(x, RI_LOCAL_USAGE, RI_TYPE_LOCAL, n) -#define HID_USAGE_MAX(x) HID_REPORT_ITEM(x, 2, RI_TYPE_LOCAL, 1) -#define HID_USAGE_MAX_N(x, n) HID_REPORT_ITEM(x, 2, RI_TYPE_LOCAL, n) +#define HID_USAGE_MIN(x) HID_REPORT_ITEM(x, RI_LOCAL_USAGE_MIN, RI_TYPE_LOCAL, 1) +#define HID_USAGE_MIN_N(x, n) HID_REPORT_ITEM(x, RI_LOCAL_USAGE_MIN, RI_TYPE_LOCAL, n) + +#define HID_USAGE_MAX(x) HID_REPORT_ITEM(x, RI_LOCAL_USAGE_MAX, RI_TYPE_LOCAL, 1) +#define HID_USAGE_MAX_N(x, n) HID_REPORT_ITEM(x, RI_LOCAL_USAGE_MAX, RI_TYPE_LOCAL, n) //--------------------------------------------------------------------+ // Usage Table +/* Usage Types Data + Sel Selector Array + SV Static Value Constant, Variable, Absolute + SF Static Flag Constant, Variable, Absolute + DV Dynamic Value Constant, Variable, Absolute + DF Dynamic Flag Constant, Variable, Absolute +*/ +/* Usage Types Collection + NAry Named Array Logical + CA Collection Application Application + CL Collection Logical Logical + CP Collection Physical Physical + US Usage Switch Logical + UM Usage Modifier Logical +*/ //--------------------------------------------------------------------+ /// HID Usage Table - Table 1: Usage Page Summary enum { - HID_USAGE_PAGE_DESKTOP = 0x01, - HID_USAGE_PAGE_SIMULATE = 0x02, - HID_USAGE_PAGE_VIRTUAL_REALITY = 0x03, - HID_USAGE_PAGE_SPORT = 0x04, - HID_USAGE_PAGE_GAME = 0x05, - HID_USAGE_PAGE_GENERIC_DEVICE = 0x06, - HID_USAGE_PAGE_KEYBOARD = 0x07, - HID_USAGE_PAGE_LED = 0x08, - HID_USAGE_PAGE_BUTTON = 0x09, - HID_USAGE_PAGE_ORDINAL = 0x0a, - HID_USAGE_PAGE_TELEPHONY = 0x0b, - HID_USAGE_PAGE_CONSUMER = 0x0c, - HID_USAGE_PAGE_DIGITIZER = 0x0d, - HID_USAGE_PAGE_PID = 0x0f, - HID_USAGE_PAGE_UNICODE = 0x10, - HID_USAGE_PAGE_ALPHA_DISPLAY = 0x14, - HID_USAGE_PAGE_MEDICAL = 0x40, - HID_USAGE_PAGE_MONITOR = 0x80, //0x80 - 0x83 - HID_USAGE_PAGE_POWER = 0x84, // 0x084 - 0x87 - HID_USAGE_PAGE_BARCODE_SCANNER = 0x8c, - HID_USAGE_PAGE_SCALE = 0x8d, - HID_USAGE_PAGE_MSR = 0x8e, - HID_USAGE_PAGE_CAMERA = 0x90, - HID_USAGE_PAGE_ARCADE = 0x91, - HID_USAGE_PAGE_VENDOR = 0xFF00 // 0xFF00 - 0xFFFF + HID_USAGE_PAGE_DESKTOP = 0x01, + HID_USAGE_PAGE_SIMULATE = 0x02, + HID_USAGE_PAGE_VIRTUAL_REALITY = 0x03, + HID_USAGE_PAGE_SPORT = 0x04, + HID_USAGE_PAGE_GAME = 0x05, + HID_USAGE_PAGE_GENERIC_DEVICE = 0x06, + HID_USAGE_PAGE_KEYBOARD = 0x07, + HID_USAGE_PAGE_LED = 0x08, + HID_USAGE_PAGE_BUTTON = 0x09, + HID_USAGE_PAGE_ORDINAL = 0x0a, + HID_USAGE_PAGE_TELEPHONY = 0x0b, + HID_USAGE_PAGE_CONSUMER = 0x0c, + HID_USAGE_PAGE_DIGITIZER = 0x0d, + HID_USAGE_PAGE_PID = 0x0f, + HID_USAGE_PAGE_UNICODE = 0x10, + HID_USAGE_PAGE_SOC = 0x11, + HID_USAGE_PAGE_EYE_AND_HEAD_TRACKERS = 0x12, + // 0x13 is reserved + HID_USAGE_PAGE_AUXILIARY_DISPLAY = 0x14, + // 0x15 - 0x1f is reserved + HID_USAGE_PAGE_SENSORS = 0x20, + // 0x21 - 0x3f is reserved + HID_USAGE_PAGE_MEDICAL_INSTRUMENT = 0x40, + HID_USAGE_PAGE_LIGHTING_AND_ILLUMINATION = 0x59, + HID_USAGE_PAGE_MONITOR = 0x80, // 0x80 - 0x83 + HID_USAGE_PAGE_POWER = 0x84, + HID_USAGE_PAGE_BATTERY = 0x85, + // 0x86 - 0x87 is reserved for Power Device + HID_USAGE_PAGE_BARCODE_SCANNER = 0x8c, + HID_USAGE_PAGE_SCALE = 0x8d, + HID_USAGE_PAGE_MSR = 0x8e, + HID_USAGE_PAGE_CAMERA = 0x90, + HID_USAGE_PAGE_ARCADE = 0x91, + HID_USAGE_PAGE_FIDO = 0xF1D0, // FIDO alliance HID usage page + HID_USAGE_PAGE_VENDOR = 0xFF00 // 0xFF00 - 0xFFFF }; /// HID Usage Table - Table 6: Generic Desktop Page @@ -538,19 +892,54 @@ enum { HID_USAGE_DESKTOP_SYSTEM_DISPLAY_LCD_AUTOSCALE = 0xB7 }; - /// HID Usage Table: Consumer Page (0x0C) /// Only contains controls that supported by Windows (whole list is too long) -enum -{ +enum { + HID_USAGE_CONSUMER_UNASSIGNED = 0x0000, + // Generic Control HID_USAGE_CONSUMER_CONTROL = 0x0001, + HID_USAGE_CONSUMER_NUMERIC_KEY_PAD = 0x0002, + HID_USAGE_CONSUMER_PROGRAMMABLE_BUTTONS = 0x0003, + HID_USAGE_CONSUMER_MICROPHONE = 0x0004, + HID_USAGE_CONSUMER_HEADPHONE = 0x0005, + HID_USAGE_CONSUMER_GRAPHIC_EQUALIZER = 0x0006, + // 07-1F Reserved + + HID_USAGE_CONSUMER_PLUS_10 = 0x0020, + HID_USAGE_CONSUMER_PLUS_100 = 0x0021, + HID_USAGE_CONSUMER_AM_PM = 0x0022, + // 23-3F Reserved // Power Control HID_USAGE_CONSUMER_POWER = 0x0030, HID_USAGE_CONSUMER_RESET = 0x0031, HID_USAGE_CONSUMER_SLEEP = 0x0032, + HID_USAGE_CONSUMER_SLEEP_AFTER = 0x0033, + HID_USAGE_CONSUMER_SLEEP_MODE = 0x0034, + HID_USAGE_CONSUMER_ILLUMINATION = 0x0035, + HID_USAGE_CONSUMER_FUNCTION_BUTTONS = 0x0036, + // 37-3F Reserved + HID_USAGE_CONSUMER_MENU = 0x0040, + HID_USAGE_CONSUMER_MENU_PICK = 0x0041, + HID_USAGE_CONSUMER_MENU_UP = 0x0042, + HID_USAGE_CONSUMER_MENU_DOWN = 0x0043, + HID_USAGE_CONSUMER_MENU_LEFT = 0x0044, + HID_USAGE_CONSUMER_MENU_RIGHT = 0x0045, + HID_USAGE_CONSUMER_MENU_ESCAPE = 0x0046, + HID_USAGE_CONSUMER_MENU_VALUE_INCREASE = 0x0047, + HID_USAGE_CONSUMER_MENU_VALUE_DECREASE = 0x0048, + // 49-5F Reserved + HID_USAGE_CONSUMER_DATA_ON_SCREEN = 0x0060, + HID_USAGE_CONSUMER_CLOSED_CAPTION = 0x0061, + HID_USAGE_CONSUMER_CLOSED_CAPTION_SELECT = 0x0062, + HID_USAGE_CONSUMER_VCR_TV = 0x0063, + HID_USAGE_CONSUMER_BROADCAST_MODE = 0x0064, + HID_USAGE_CONSUMER_SNAPSHOT = 0x0065, + HID_USAGE_CONSUMER_STILL = 0x0066, + + // 67-7F Reserved // Screen Brightness HID_USAGE_CONSUMER_BRIGHTNESS_INCREMENT = 0x006F, HID_USAGE_CONSUMER_BRIGHTNESS_DECREMENT = 0x0070, @@ -562,40 +951,851 @@ enum HID_USAGE_CONSUMER_WIRELESS_RADIO_LED = 0x00C7, HID_USAGE_CONSUMER_WIRELESS_RADIO_SLIDER_SWITCH = 0x00C8, + HID_USAGE_CONSUMER_SELECTION = 0x0080, + HID_USAGE_CONSUMER_ASSIGN_SELECTION = 0x0081, + HID_USAGE_CONSUMER_MODE_STEP = 0x0082, + HID_USAGE_CONSUMER_RECALL_LAST = 0x0083, + HID_USAGE_CONSUMER_ENTER_CHANNEL = 0x0084, + HID_USAGE_CONSUMER_ORDER_MOVIE = 0x0085, + HID_USAGE_CONSUMER_CHANNEL = 0x0086, + HID_USAGE_CONSUMER_MEDIA_SELECTION = 0x0087, + HID_USAGE_CONSUMER_MEDIA_SELECT_COMPUTER = 0x0088, + HID_USAGE_CONSUMER_MEDIA_SELECT_TV = 0x0089, + HID_USAGE_CONSUMER_MEDIA_SELECT_WWW = 0x008A, + HID_USAGE_CONSUMER_MEDIA_SELECT_DVD = 0x008B, + HID_USAGE_CONSUMER_MEDIA_SELECT_TELEPHONE = 0x008C, + HID_USAGE_CONSUMER_MEDIA_SELECT_PROGRAM_GUIDE = 0x008D, + HID_USAGE_CONSUMER_MEDIA_SELECT_VIDEO_PHONE = 0x008E, + HID_USAGE_CONSUMER_MEDIA_SELECT_GAMES = 0x008F, + HID_USAGE_CONSUMER_MEDIA_SELECT_MESSAGES = 0x0090, + HID_USAGE_CONSUMER_MEDIA_SELECT_CD = 0x0091, + HID_USAGE_CONSUMER_MEDIA_SELECT_VCR = 0x0092, + HID_USAGE_CONSUMER_MEDIA_SELECT_TUNER = 0x0093, + HID_USAGE_CONSUMER_QUIT = 0x0094, + HID_USAGE_CONSUMER_HELP = 0x0095, + HID_USAGE_CONSUMER_MEDIA_SELECT_TAPE = 0x0096, + HID_USAGE_CONSUMER_MEDIA_SELECT_CABLE = 0x0097, + HID_USAGE_CONSUMER_MEDIA_SELECT_SATELLITE = 0x0098, + HID_USAGE_CONSUMER_MEDIA_SELECT_SECURITY = 0x0099, + HID_USAGE_CONSUMER_MEDIA_SELECT_HOME = 0x009A, + HID_USAGE_CONSUMER_MEDIA_SELECT_CALL = 0x009B, + HID_USAGE_CONSUMER_CHANNEL_INCREMENT = 0x009C, + HID_USAGE_CONSUMER_CHANNEL_DECREMENT = 0x009D, + HID_USAGE_CONSUMER_MEDIA_SELECT_SAP = 0x009E, + // 9F Reserved + HID_USAGE_CONSUMER_VCR_PLUS = 0x00A0, + HID_USAGE_CONSUMER_ONCE = 0x00A1, + HID_USAGE_CONSUMER_DAILY = 0x00A2, + HID_USAGE_CONSUMER_WEEKLY = 0x00A3, + HID_USAGE_CONSUMER_MONTHLY = 0x00A4, + // A5-AF Reserved + + HID_USAGE_CONSUMER_PLAY = 0x00B0, + HID_USAGE_CONSUMER_PAUSE = 0x00B1, + HID_USAGE_CONSUMER_RECORD = 0x00B2, + HID_USAGE_CONSUMER_FAST_FORWARD = 0x00B3, + HID_USAGE_CONSUMER_REWIND = 0x00B4, + HID_USAGE_CONSUMER_SCAN_NEXT_TRACK = 0x00B5, + HID_USAGE_CONSUMER_SCAN_PREVIOUS_TRACK = 0x00B6, + HID_USAGE_CONSUMER_STOP = 0x00B7, + HID_USAGE_CONSUMER_EJECT = 0x00B8, + HID_USAGE_CONSUMER_RANDOM_PLAY = 0x00B9, + HID_USAGE_CONSUMER_SELECT_DISC = 0x00BA, + HID_USAGE_CONSUMER_ENTER_DISC = 0x00BB, + HID_USAGE_CONSUMER_REPEAT = 0x00BC, + HID_USAGE_CONSUMER_TRACKING = 0x00BD, + HID_USAGE_CONSUMER_TRACK_NORMAL = 0x00BE, + HID_USAGE_CONSUMER_SLOW_TRACKING = 0x00BF, + HID_USAGE_CONSUMER_FRAME_FORWARD = 0x00C0, + HID_USAGE_CONSUMER_FRAME_BACK = 0x00C1, + HID_USAGE_CONSUMER_MARK = 0x00C2, + HID_USAGE_CONSUMER_CLEAR_MARK = 0x00C3, + HID_USAGE_CONSUMER_REPEAT_FROM_MARK = 0x00C4, + HID_USAGE_CONSUMER_RETURN_TO_MARK = 0x00C5, + HID_USAGE_CONSUMER_SEARCH_MARK_FORWARD = 0x00C6, + HID_USAGE_CONSUMER_SEARCH_MARK_BACKWARDS = 0x00C7, + HID_USAGE_CONSUMER_COUNTER_RESET = 0x00C8, + HID_USAGE_CONSUMER_SHOW_COUNTER = 0x00C9, + HID_USAGE_CONSUMER_TRACKING_INCREMENT = 0x00CA, + HID_USAGE_CONSUMER_TRACKING_DECREMENT = 0x00CB, + HID_USAGE_CONSUMER_STOP_EJECT = 0x00CC, + + // Media Control HID_USAGE_CONSUMER_PLAY_PAUSE = 0x00CD, - HID_USAGE_CONSUMER_SCAN_NEXT = 0x00B5, - HID_USAGE_CONSUMER_SCAN_PREVIOUS = 0x00B6, - HID_USAGE_CONSUMER_STOP = 0x00B7, + + HID_USAGE_CONSUMER_PLAY_SKIP = 0x00CE, + + // CF-DF Reserved HID_USAGE_CONSUMER_VOLUME = 0x00E0, + HID_USAGE_CONSUMER_BALANCE = 0x00E1, HID_USAGE_CONSUMER_MUTE = 0x00E2, HID_USAGE_CONSUMER_BASS = 0x00E3, HID_USAGE_CONSUMER_TREBLE = 0x00E4, HID_USAGE_CONSUMER_BASS_BOOST = 0x00E5, + HID_USAGE_CONSUMER_SURROUND_MODE = 0x00E6, + HID_USAGE_CONSUMER_LOUDNESS = 0x00E7, + HID_USAGE_CONSUMER_MPX = 0x00E8, HID_USAGE_CONSUMER_VOLUME_INCREMENT = 0x00E9, HID_USAGE_CONSUMER_VOLUME_DECREMENT = 0x00EA, + // EB-EF Reserved + HID_USAGE_CONSUMER_SPEED_SELECT = 0x00F0, + HID_USAGE_CONSUMER_PLAYBACK_SPEED = 0x00F1, + HID_USAGE_CONSUMER_STANDARD_PLAY = 0x00F2, + HID_USAGE_CONSUMER_LONG_PLAY = 0x00F3, + HID_USAGE_CONSUMER_EXTENDED_PLAY = 0x00F4, + HID_USAGE_CONSUMER_SLOW = 0x00F5, + // F6-FF Reserved + HID_USAGE_CONSUMER_FAN_ENABLE = 0x0100, + HID_USAGE_CONSUMER_FAN_SPEED = 0x0101, + HID_USAGE_CONSUMER_LIGHT_ENABLE = 0x0102, + HID_USAGE_CONSUMER_LIGHT_ILLUMINATION_LEVEL = 0x0103, + HID_USAGE_CONSUMER_CLIMATE_CONTROL_ENABLE = 0x0104, + HID_USAGE_CONSUMER_ROOM_TEMPERATURE = 0x0105, + HID_USAGE_CONSUMER_SECURITY_ENABLE = 0x0106, + HID_USAGE_CONSUMER_FIRE_ALARM = 0x0107, + HID_USAGE_CONSUMER_POLICE_ALARM = 0x0108, + HID_USAGE_CONSUMER_PROXIMITY = 0x0109, + HID_USAGE_CONSUMER_MOTION = 0x010A, + HID_USAGE_CONSUMER_DURESS_ALARM = 0x010B, + HID_USAGE_CONSUMER_HOLDUP_ALARM = 0x010C, + HID_USAGE_CONSUMER_MEDICAL_ALARM = 0x010D, + // 10E-14F Reserved + HID_USAGE_CONSUMER_BALANCE_RIGHT = 0x0150, + HID_USAGE_CONSUMER_BALANCE_LEFT = 0x0151, HID_USAGE_CONSUMER_BASS_INCREMENT = 0x0152, HID_USAGE_CONSUMER_BASS_DECREMENT = 0x0153, HID_USAGE_CONSUMER_TREBLE_INCREMENT = 0x0154, HID_USAGE_CONSUMER_TREBLE_DECREMENT = 0x0155, - // Application Launcher + // 156-15F Reserved + HID_USAGE_CONSUMER_SPEAKER_SYSTEM = 0x0160, + HID_USAGE_CONSUMER_CHANNEL_LEFT = 0x0161, + HID_USAGE_CONSUMER_CHANNEL_RIGHT = 0x0162, + HID_USAGE_CONSUMER_CHANNEL_CENTER = 0x0163, + HID_USAGE_CONSUMER_CHANNEL_FRONT = 0x0164, + HID_USAGE_CONSUMER_CHANNEL_CENTER_FRONT = 0x0165, + HID_USAGE_CONSUMER_CHANNEL_SIDE = 0x0166, + HID_USAGE_CONSUMER_CHANNEL_SURROUND = 0x0167, + HID_USAGE_CONSUMER_CHANNEL_LOW_FREQUENCY = 0x0168, + // Enhancement + // CL 15.12.1 + HID_USAGE_CONSUMER_CHANNEL_TOP = 0x0169, + HID_USAGE_CONSUMER_CHANNEL_UNKNOWN = 0x016A, + // 16B-16F Reserved + HID_USAGE_CONSUMER_SUB_CHANNEL = 0x0170, + HID_USAGE_CONSUMER_SUB_CHANNEL_INCREMENT = 0x0171, + HID_USAGE_CONSUMER_SUB_CHANNEL_DECREMENT = 0x0172, + HID_USAGE_CONSUMER_ALTERNATE_AUDIO_INCREMENT = 0x0173, + HID_USAGE_CONSUMER_ALTERNATE_AUDIO_DECREMENT = 0x0174, + // 175-17F Reserved + HID_USAGE_CONSUMER_APPLICATION_LAUNCH_BUTTONS = 0x0180, + HID_USAGE_CONSUMER_AL_LAUNCH_BUTTON_CONFIGURATION = 0x0181, + // Tool + // Sel 15.15 + HID_USAGE_CONSUMER_AL_PROGRAMMABLE_BUTTON = 0x0182, + // Configuration + // Sel 15.15 HID_USAGE_CONSUMER_AL_CONSUMER_CONTROL_CONFIGURATION = 0x0183, + // Configuration + // Sel 15.15 + HID_USAGE_CONSUMER_AL_WORD_PROCESSOR = 0x0184, + HID_USAGE_CONSUMER_AL_TEXT_EDITOR = 0x0185, + HID_USAGE_CONSUMER_AL_SPREADSHEET = 0x0186, + HID_USAGE_CONSUMER_AL_GRAPHICS_EDITOR = 0x0187, + HID_USAGE_CONSUMER_AL_PRESENTATION_APP = 0x0188, + HID_USAGE_CONSUMER_AL_DATABASE_APP = 0x0189, HID_USAGE_CONSUMER_AL_EMAIL_READER = 0x018A, + HID_USAGE_CONSUMER_AL_NEWSREADER = 0x018B, + HID_USAGE_CONSUMER_AL_VOICEMAIL = 0x018C, + HID_USAGE_CONSUMER_AL_CONTACTS_ADDRESS_BOOK = 0x018D, + HID_USAGE_CONSUMER_AL_CALENDAR_SCHEDULE = 0x018E, + HID_USAGE_CONSUMER_AL_TASK_PROJECT_MANAGER = 0x018F, + HID_USAGE_CONSUMER_AL_LOG_JOURNAL_TIMECARD = 0x0190, + HID_USAGE_CONSUMER_AL_CHECKBOOK_FINANCE = 0x0191, HID_USAGE_CONSUMER_AL_CALCULATOR = 0x0192, - HID_USAGE_CONSUMER_AL_LOCAL_BROWSER = 0x0194, - + HID_USAGE_CONSUMER_AL_A_V_CAPTURE_PLAYBACK = 0x0193, + HID_USAGE_CONSUMER_AL_LOCAL_MACHINE_BROWSER = 0x0194, + HID_USAGE_CONSUMER_AL_LAN_WAN_BROWSER = 0x0195, + HID_USAGE_CONSUMER_AL_INTERNET_BROWSER = 0x0196, + HID_USAGE_CONSUMER_AL_REMOTE_NETWORKING_ISP = 0x0197, + // Connect + // Sel 15.15 + HID_USAGE_CONSUMER_AL_NETWORK_CONFERENCE = 0x0198, + HID_USAGE_CONSUMER_AL_NETWORK_CHAT = 0x0199, + HID_USAGE_CONSUMER_AL_TELEPHONY_DIALER = 0x019A, + HID_USAGE_CONSUMER_AL_LOGON = 0x019B, + HID_USAGE_CONSUMER_AL_LOGOFF = 0x019C, + HID_USAGE_CONSUMER_AL_LOGON_LOGOFF = 0x019D, + HID_USAGE_CONSUMER_AL_TERMINAL_LOCK_SCREENSAVER = 0x019E, + HID_USAGE_CONSUMER_AL_CONTROL_PANEL = 0x019F, + HID_USAGE_CONSUMER_AL_COMMAND_LINE_PROCESSOR_RUN = 0x01A0, + HID_USAGE_CONSUMER_AL_PROCESS_TASK_MANAGER = 0x01A1, + HID_USAGE_CONSUMER_AL_SELECT_TASK_APPLICATION = 0x01A2, + HID_USAGE_CONSUMER_AL_NEXT_TASK_APPLICATION = 0x01A3, + HID_USAGE_CONSUMER_AL_PREVIOUS_TASK_APPLICATION = 0x01A4, + HID_USAGE_CONSUMER_AL_PREEMPTIVE_HALT = 0x01A5, + // Task_Application + // Sel 15.15 + HID_USAGE_CONSUMER_AL_INTEGRATED_HELP_CENTER = 0x01A6, + HID_USAGE_CONSUMER_AL_DOCUMENTS = 0x01A7, + HID_USAGE_CONSUMER_AL_THESAURUS = 0x01A8, + HID_USAGE_CONSUMER_AL_DICTIONARY = 0x01A9, + HID_USAGE_CONSUMER_AL_DESKTOP = 0x01AA, + HID_USAGE_CONSUMER_AL_SPELL_CHECK = 0x01AB, + HID_USAGE_CONSUMER_AL_GRAMMAR_CHECK = 0x01AC, + HID_USAGE_CONSUMER_AL_WIRELESS_STATUS = 0x01AD, + HID_USAGE_CONSUMER_AL_KEYBOARD_LAYOUT = 0x01AE, + HID_USAGE_CONSUMER_AL_VIRUS_PROTECTION = 0x01AF, + HID_USAGE_CONSUMER_AL_ENCRYPTION = 0x01B0, + HID_USAGE_CONSUMER_AL_SCREEN_SAVER = 0x01B1, + HID_USAGE_CONSUMER_AL_ALARMS = 0x01B2, + HID_USAGE_CONSUMER_AL_CLOCK = 0x01B3, + HID_USAGE_CONSUMER_AL_FILE_BROWSER = 0x01B4, + HID_USAGE_CONSUMER_AL_POWER_STATUS = 0x01B5, + HID_USAGE_CONSUMER_AL_IMAGE_BROWSER = 0x01B6, + HID_USAGE_CONSUMER_AL_AUDIO_BROWSER = 0x01B7, + HID_USAGE_CONSUMER_AL_MOVIE_BROWSER = 0x01B8, + HID_USAGE_CONSUMER_AL_DIGITAL_RIGHTS_MANAGER = 0x01B9, + HID_USAGE_CONSUMER_AL_DIGITAL_WALLET = 0x01BA, + // 1BB Reserved + HID_USAGE_CONSUMER_AL_INSTANT_MESSAGING = 0x01BC, + HID_USAGE_CONSUMER_AL_OEM_FEATURES_TIPS_TUTORIAL = 0x01BD, + // Browser + // Sel 15.15 + HID_USAGE_CONSUMER_AL_OEM_HELP = 0x01BE, + HID_USAGE_CONSUMER_AL_ONLINE_COMMUNITY = 0x01BF, + HID_USAGE_CONSUMER_AL_ENTERTAINMENT_CONTENT = 0x01C0, + // Browser + // Sel 15.15 + HID_USAGE_CONSUMER_AL_ONLINE_SHOPPING_BROWSER = 0x01C1, + HID_USAGE_CONSUMER_AL_SMARTCARD_INFORMATION_HELP = 0x01C2, + HID_USAGE_CONSUMER_AL_MARKET_MONITOR_FINANCE = 0x01C3, + // Browser + // Sel 15.15 + HID_USAGE_CONSUMER_AL_CUSTOMIZED_CORPORATE_NEWS = 0x01C4, + // Browser + // Sel 15.15 + HID_USAGE_CONSUMER_AL_ONLINE_ACTIVITY_BROWSER = 0x01C5, + HID_USAGE_CONSUMER_AL_RESEARCH_SEARCH_BROWSER = 0x01C6, + HID_USAGE_CONSUMER_AL_AUDIO_PLAYER = 0x01C7, + // 1C8-1FF Reserved + HID_USAGE_CONSUMER_GENERIC_GUI_APPLICATION = 0x0200, + // ' Controls + // ' + HID_USAGE_CONSUMER_AC_NEW = 0x0201, + HID_USAGE_CONSUMER_AC_OPEN = 0x0202, + HID_USAGE_CONSUMER_AC_CLOSE = 0x0203, + HID_USAGE_CONSUMER_AC_EXIT = 0x0204, + HID_USAGE_CONSUMER_AC_MAXIMIZE = 0x0205, + HID_USAGE_CONSUMER_AC_MINIMIZE = 0x0206, + HID_USAGE_CONSUMER_AC_SAVE = 0x0207, + HID_USAGE_CONSUMER_AC_PRINT = 0x0208, + HID_USAGE_CONSUMER_AC_PROPERTIES = 0x0209, + HID_USAGE_CONSUMER_AC_UNDO = 0x021A, + HID_USAGE_CONSUMER_AC_COPY = 0x021B, + HID_USAGE_CONSUMER_AC_CUT = 0x021C, + HID_USAGE_CONSUMER_AC_PASTE = 0x021D, + HID_USAGE_CONSUMER_AC_SELECT_ALL = 0x021E, + HID_USAGE_CONSUMER_AC_FIND = 0x021F, + HID_USAGE_CONSUMER_AC_FIND_AND_REPLACE = 0x0220, // Browser/Explorer Specific HID_USAGE_CONSUMER_AC_SEARCH = 0x0221, + HID_USAGE_CONSUMER_AC_GO_TO = 0x0222, HID_USAGE_CONSUMER_AC_HOME = 0x0223, HID_USAGE_CONSUMER_AC_BACK = 0x0224, HID_USAGE_CONSUMER_AC_FORWARD = 0x0225, HID_USAGE_CONSUMER_AC_STOP = 0x0226, HID_USAGE_CONSUMER_AC_REFRESH = 0x0227, + HID_USAGE_CONSUMER_AC_PREVIOUS_LINK = 0x0228, + HID_USAGE_CONSUMER_AC_NEXT_LINK = 0x0229, HID_USAGE_CONSUMER_AC_BOOKMARKS = 0x022A, - + HID_USAGE_CONSUMER_AC_HISTORY = 0x022B, + HID_USAGE_CONSUMER_AC_SUBSCRIPTIONS = 0x022C, + HID_USAGE_CONSUMER_AC_ZOOM_IN = 0x022D, + HID_USAGE_CONSUMER_AC_ZOOM_OUT = 0x022E, + HID_USAGE_CONSUMER_AC_ZOOM = 0x022F, + HID_USAGE_CONSUMER_AC_FULL_SCREEN_VIEW = 0x0230, + HID_USAGE_CONSUMER_AC_NORMAL_VIEW = 0x0231, + HID_USAGE_CONSUMER_AC_VIEW_TOGGLE = 0x0232, + HID_USAGE_CONSUMER_AC_SCROLL_UP = 0x0233, + HID_USAGE_CONSUMER_AC_SCROLL_DOWN = 0x0234, + HID_USAGE_CONSUMER_AC_SCROLL = 0x0235, + HID_USAGE_CONSUMER_AC_PAN_LEFT = 0x0236, + HID_USAGE_CONSUMER_AC_PAN_RIGHT = 0x0237, // Mouse Horizontal scroll HID_USAGE_CONSUMER_AC_PAN = 0x0238, + HID_USAGE_CONSUMER_AC_NEW_WINDOW = 0x0239, + HID_USAGE_CONSUMER_AC_TILE_HORIZONTALLY = 0x023A, + HID_USAGE_CONSUMER_AC_TILE_VERTICALLY = 0x023B, + HID_USAGE_CONSUMER_AC_FORMAT = 0x023C, + HID_USAGE_CONSUMER_AC_EDIT = 0x023D, + HID_USAGE_CONSUMER_AC_BOLD = 0x023E, + HID_USAGE_CONSUMER_AC_ITALICS = 0x023F, + HID_USAGE_CONSUMER_AC_UNDERLINE = 0x0240, + HID_USAGE_CONSUMER_AC_STRIKETHROUGH = 0x0241, + HID_USAGE_CONSUMER_AC_SUBSCRIPT = 0x0242, + HID_USAGE_CONSUMER_AC_SUPERSCRIPT = 0x0243, + HID_USAGE_CONSUMER_AC_ALL_CAPS = 0x0244, + HID_USAGE_CONSUMER_AC_ROTATE = 0x0245, + HID_USAGE_CONSUMER_AC_RESIZE = 0x0246, + HID_USAGE_CONSUMER_AC_FLIP_HORIZONTAL = 0x0247, + HID_USAGE_CONSUMER_AC_FLIP_VERTICAL = 0x0248, + HID_USAGE_CONSUMER_AC_MIRROR_HORIZONTAL = 0x0249, + HID_USAGE_CONSUMER_AC_MIRROR_VERTICAL = 0x024A, + HID_USAGE_CONSUMER_AC_FONT_SELECT = 0x024B, + HID_USAGE_CONSUMER_AC_FONT_COLOR = 0x024C, + HID_USAGE_CONSUMER_AC_FONT_SIZE = 0x024D, + HID_USAGE_CONSUMER_AC_JUSTIFY_LEFT = 0x024E, + HID_USAGE_CONSUMER_AC_JUSTIFY_CENTER_H = 0x024F, + HID_USAGE_CONSUMER_AC_JUSTIFY_RIGHT = 0x0250, + HID_USAGE_CONSUMER_AC_JUSTIFY_BLOCK_H = 0x0251, + HID_USAGE_CONSUMER_AC_JUSTIFY_TOP = 0x0252, + HID_USAGE_CONSUMER_AC_JUSTIFY_CENTER_V = 0x0253, + HID_USAGE_CONSUMER_AC_JUSTIFY_BOTTOM = 0x0254, + HID_USAGE_CONSUMER_AC_JUSTIFY_BLOCK_V = 0x0255, + HID_USAGE_CONSUMER_AC_INDENT_DECREASE = 0x0256, + HID_USAGE_CONSUMER_AC_INDENT_INCREASE = 0x0257, + HID_USAGE_CONSUMER_AC_NUMBERED_LIST = 0x0258, + HID_USAGE_CONSUMER_AC_RESTART_NUMBERING = 0x0259, + HID_USAGE_CONSUMER_AC_BULLETED_LIST = 0x025A, + HID_USAGE_CONSUMER_AC_PROMOTE = 0x025B, + HID_USAGE_CONSUMER_AC_DEMOTE = 0x025C, + HID_USAGE_CONSUMER_AC_YES = 0x025D, + HID_USAGE_CONSUMER_AC_NO = 0x025E, + HID_USAGE_CONSUMER_AC_CANCEL = 0x025F, + HID_USAGE_CONSUMER_AC_CATALOG = 0x0260, + HID_USAGE_CONSUMER_AC_BUY_CHECKOUT = 0x0261, + HID_USAGE_CONSUMER_AC_ADD_TO_CART = 0x0262, + HID_USAGE_CONSUMER_AC_EXPAND = 0x0263, + HID_USAGE_CONSUMER_AC_EXPAND_ALL = 0x0264, + HID_USAGE_CONSUMER_AC_COLLAPSE = 0x0265, + HID_USAGE_CONSUMER_AC_COLLAPSE_ALL = 0x0266, + HID_USAGE_CONSUMER_AC_PRINT_PREVIEW = 0x0267, + HID_USAGE_CONSUMER_AC_PASTE_SPECIAL = 0x0268, + HID_USAGE_CONSUMER_AC_INSERT_MODE = 0x0269, + HID_USAGE_CONSUMER_AC_DELETE = 0x026A, + HID_USAGE_CONSUMER_AC_LOCK = 0x026B, + HID_USAGE_CONSUMER_AC_UNLOCK = 0x026C, + HID_USAGE_CONSUMER_AC_PROTECT = 0x026D, + HID_USAGE_CONSUMER_AC_UNPROTECT = 0x026E, + HID_USAGE_CONSUMER_AC_ATTACH_COMMENT = 0x026F, + HID_USAGE_CONSUMER_AC_DELETE_COMMENT = 0x0270, + HID_USAGE_CONSUMER_AC_VIEW_COMMENT = 0x0271, + HID_USAGE_CONSUMER_AC_SELECT_WORD = 0x0272, + HID_USAGE_CONSUMER_AC_SELECT_SENTENCE = 0x0273, + HID_USAGE_CONSUMER_AC_SELECT_PARAGRAPH = 0x0274, + HID_USAGE_CONSUMER_AC_SELECT_COLUMN = 0x0275, + HID_USAGE_CONSUMER_AC_SELECT_ROW = 0x0276, + HID_USAGE_CONSUMER_AC_SELECT_TABLE = 0x0277, + HID_USAGE_CONSUMER_AC_SELECT_OBJECT = 0x0278, + HID_USAGE_CONSUMER_AC_REDO_REPEAT = 0x0279, + HID_USAGE_CONSUMER_AC_SORT = 0x027A, + HID_USAGE_CONSUMER_AC_SORT_ASCENDING = 0x027B, + HID_USAGE_CONSUMER_AC_SORT_DESCENDING = 0x027C, + HID_USAGE_CONSUMER_AC_FILTER = 0x027D, + HID_USAGE_CONSUMER_AC_SET_CLOCK = 0x027E, + HID_USAGE_CONSUMER_AC_VIEW_CLOCK = 0x027F, + HID_USAGE_CONSUMER_AC_SELECT_TIME_ZONE = 0x0280, + HID_USAGE_CONSUMER_AC_EDIT_TIME_ZONES = 0x0281, + HID_USAGE_CONSUMER_AC_SET_ALARM = 0x0282, + HID_USAGE_CONSUMER_AC_CLEAR_ALARM = 0x0283, + HID_USAGE_CONSUMER_AC_SNOOZE_ALARM = 0x0284, + HID_USAGE_CONSUMER_AC_RESET_ALARM = 0x0285, + HID_USAGE_CONSUMER_AC_SYNCHRONIZE = 0x0286, + HID_USAGE_CONSUMER_AC_SEND_RECEIVE = 0x0287, + HID_USAGE_CONSUMER_AC_SEND_TO = 0x0288, + HID_USAGE_CONSUMER_AC_REPLY = 0x0289, + HID_USAGE_CONSUMER_AC_REPLY_ALL = 0x028A, + HID_USAGE_CONSUMER_AC_FORWARD_MSG = 0x028B, + HID_USAGE_CONSUMER_AC_SEND = 0x028C, + HID_USAGE_CONSUMER_AC_ATTACH_FILE = 0x028D, + HID_USAGE_CONSUMER_AC_UPLOAD = 0x028E, + HID_USAGE_CONSUMER_AC_DOWNLOAD_SAVE_TARGET_AS = 0x028F, + HID_USAGE_CONSUMER_AC_SET_BORDERS = 0x0290, + HID_USAGE_CONSUMER_AC_INSERT_ROW = 0x0291, + HID_USAGE_CONSUMER_AC_INSERT_COLUMN = 0x0292, + HID_USAGE_CONSUMER_AC_INSERT_FILE = 0x0293, + HID_USAGE_CONSUMER_AC_INSERT_PICTURE = 0x0294, + HID_USAGE_CONSUMER_AC_INSERT_OBJECT = 0x0295, + HID_USAGE_CONSUMER_AC_INSERT_SYMBOL = 0x0296, + HID_USAGE_CONSUMER_AC_SAVE_AND_CLOSE = 0x0297, + HID_USAGE_CONSUMER_AC_RENAME = 0x0298, + HID_USAGE_CONSUMER_AC_MERGE = 0x0299, + HID_USAGE_CONSUMER_AC_SPLIT = 0x029A, + HID_USAGE_CONSUMER_AC_DISRIBUTE_HORIZONTALLY = 0x029B, + HID_USAGE_CONSUMER_AC_DISTRIBUTE_VERTICALLY = 0x029C, + // 29D-FFFF Reserved + +}; + +/// HID Usage Table: Digitizer Page (0x0D) +enum { + HID_USAGE_DIGITIZER_UNDEFINED = 0x00, + HID_USAGE_DIGITIZER_DIGITIZER = 0x01, // CA + HID_USAGE_DIGITIZER_PEN = 0x02, // CA + HID_USAGE_DIGITIZER_LIGHT_PEN = 0x03, // CA + HID_USAGE_DIGITIZER_TOUCH_SCREEN = 0x04, // CA + HID_USAGE_DIGITIZER_TOUCH_PAD = 0x05, // CA + HID_USAGE_DIGITIZER_WHITEBOARD = 0x06, // CA + HID_USAGE_DIGITIZER_COORDINATE_MEASURING_MACHINE = 0x07, // CA + HID_USAGE_DIGITIZER_3D_DIGITIZER = 0x08, // CA + HID_USAGE_DIGITIZER_STEREO_PLOTTER = 0x09, // CA + HID_USAGE_DIGITIZER_ARTICULATED_ARM = 0x0A, // CA + HID_USAGE_DIGITIZER_ARMATURE = 0x0B, // CA + HID_USAGE_DIGITIZER_MULTIPLE_POINT_DIGITIZER = 0x0C, // CA + HID_USAGE_DIGITIZER_FREE_SPACE_WAND = 0x0D, // CA + HID_USAGE_DIGITIZER_DEVICE_CONFIGURATION = 0x0E, // CA + HID_USAGE_DIGITIZER_CAPACITIVE_HEAT_MAP_DIGITIZER = 0x0F, // CA + // Reserved (0x10 - 0x1F) + HID_USAGE_DIGITIZER_STYLUS = 0x20, // CA/CL + HID_USAGE_DIGITIZER_PUCK = 0x21, // CL + HID_USAGE_DIGITIZER_FINGER = 0x22, // CL + HID_USAGE_DIGITIZER_DEVICE_SETTINGS = 0x23, // CL + HID_USAGE_DIGITIZER_CHARACTER_GESTURE = 0x24, // CL + // Reserved (0x25 - 0x2F) + HID_USAGE_DIGITIZER_TIP_PRESSURE = 0x30, // DV + HID_USAGE_DIGITIZER_BARREL_PRESSURE = 0x31, // DV + HID_USAGE_DIGITIZER_IN_RANGE = 0x32, // MC + HID_USAGE_DIGITIZER_TOUCH = 0x33, // MC + HID_USAGE_DIGITIZER_UNTOUCH = 0x34, // OSC + HID_USAGE_DIGITIZER_TAP = 0x35, // OSC + HID_USAGE_DIGITIZER_QUALITY = 0x36, // DV + HID_USAGE_DIGITIZER_DATA_VALID = 0x37, // MC + HID_USAGE_DIGITIZER_TRANSDUCER_INDEX = 0x38, // DV + HID_USAGE_DIGITIZER_TABLET_FUNCTION_KEYS = 0x39, // CL + HID_USAGE_DIGITIZER_PROGRAM_CHANGE_KEYS = 0x3A, // CL + HID_USAGE_DIGITIZER_BATTERY_STRENGTH = 0x3B, // DV + HID_USAGE_DIGITIZER_INVERT = 0x3C, // MC + HID_USAGE_DIGITIZER_X_TILT = 0x3D, // DV + HID_USAGE_DIGITIZER_Y_TILT = 0x3E, // DV + HID_USAGE_DIGITIZER_AZIMUTH = 0x3F, // DV + HID_USAGE_DIGITIZER_ALTITUDE = 0x40, // DV + HID_USAGE_DIGITIZER_TWIST = 0x41, // DV + HID_USAGE_DIGITIZER_TIP_SWITCH = 0x42, // MC + HID_USAGE_DIGITIZER_SECONDARY_TIP_SWITCH = 0x43, // MC + HID_USAGE_DIGITIZER_BARREL_SWITCH = 0x44, // MC + HID_USAGE_DIGITIZER_ERASER = 0x45, // MC + HID_USAGE_DIGITIZER_TABLET_PICK = 0x46, // MC + HID_USAGE_DIGITIZER_TOUCH_VALID = 0x47, // MC + HID_USAGE_DIGITIZER_WIDTH = 0x48, // DV + HID_USAGE_DIGITIZER_HEIGHT = 0x49, // DV + // Reserved (0x4A - 0x50) + HID_USAGE_DIGITIZER_CONTACT_IDENTIFIER = 0x51, // DV + HID_USAGE_DIGITIZER_DEVICE_MODE = 0x52, // DV + HID_USAGE_DIGITIZER_DEVICE_IDENTIFIER = 0x53, // DV/SV + HID_USAGE_DIGITIZER_CONTACT_COUNT = 0x54, // DV + HID_USAGE_DIGITIZER_CONTACT_COUNT_MAXIMUM = 0x55, // SV + HID_USAGE_DIGITIZER_SCAN_TIME = 0x56, // DV + HID_USAGE_DIGITIZER_SURFACE_SWITCH = 0x57, // DF + HID_USAGE_DIGITIZER_BUTTON_SWITCH = 0x58, // DF + HID_USAGE_DIGITIZER_PAD_TYPE = 0x59, // SF + HID_USAGE_DIGITIZER_TRANSDUCER_SERIAL_NUMBER = 0x5B, // SV + HID_USAGE_DIGITIZER_PREFERRED_COLOR = 0x5C, // DV + HID_USAGE_DIGITIZER_PREFERRED_COLOR_LOCKED = 0x5D, // MC + HID_USAGE_DIGITIZER_PREFERRED_LINE_WIDTH = 0x5E, // DV + HID_USAGE_DIGITIZER_PREFERRED_LINE_WIDTH_LOCKED = 0x5F, // MC + HID_USAGE_DIGITIZER_LATENCY_MODE = 0x60, // DF + HID_USAGE_DIGITIZER_GESTURE_CHARACTER_QUALITY = 0x61, // DV + HID_USAGE_DIGITIZER_CHARACTER_GESTURE_DATA_LENGTH = 0x62, // DV + HID_USAGE_DIGITIZER_CHARACTER_GESTURE_DATA = 0x63, // DV + HID_USAGE_DIGITIZER_GESTURE_CHARACTER_ENCODING = 0x64, // NAry + HID_USAGE_DIGITIZER_UTF8_CHARACTER_GESTURE_ENCODING = 0x65, // Sel + HID_USAGE_DIGITIZER_UTF16_LE_CHARACTER_GESTURE_ENCODING = 0x66, // Sel + HID_USAGE_DIGITIZER_UTF16_BE_CHARACTER_GESTURE_ENCODING = 0x67, // Sel + HID_USAGE_DIGITIZER_UTF32_LE_CHARACTER_GESTURE_ENCODING = 0x68, // Sel + HID_USAGE_DIGITIZER_UTF32_BE_CHARACTER_GESTURE_ENCODING = 0x69, // Sel + HID_USAGE_DIGITIZER_CAPACITIVE_HEAT_MAP_VENDOR_ID = 0x6A, // SV + HID_USAGE_DIGITIZER_CAPACITIVE_HEAT_MAP_VERSION = 0x6B, // SV + HID_USAGE_DIGITIZER_CAPACITIVE_HEAT_MAP_FRAME_DATA = 0x6C, // DV + HID_USAGE_DIGITIZER_GESTURE_CHARACTER_ENABLE = 0x6D, // DF + HID_USAGE_DIGITIZER_TRANSDUCER_SERIAL_NUMBER_PART2 = 0x6E, // SV + HID_USAGE_DIGITIZER_NO_PREFERRED_COLOR = 0x6F, // DF + HID_USAGE_DIGITIZER_PREFERRED_LINE_STYLE = 0x70, // NAry + HID_USAGE_DIGITIZER_PREFERRED_LINE_STYLE_LOCKED = 0x71, // MC + HID_USAGE_DIGITIZER_INK = 0x72, // Sel + HID_USAGE_DIGITIZER_PENCIL = 0x73, // Sel + HID_USAGE_DIGITIZER_HIGHLIGHTER = 0x74, // Sel + HID_USAGE_DIGITIZER_CHISEL_MARKER = 0x75, // Sel + HID_USAGE_DIGITIZER_BRUSH = 0x76, // Sel + HID_USAGE_DIGITIZER_NO_PREFERENCE = 0x77, // Sel + // Reserved (0x78 - 0x7F) + HID_USAGE_DIGITIZER_DIGITIZER_DIAGNOSTIC = 0x80, // CL + HID_USAGE_DIGITIZER_DIGITIZER_ERROR = 0x81, // NAry + HID_USAGE_DIGITIZER_ERR_NORMAL_STATUS = 0x82, // Sel + HID_USAGE_DIGITIZER_ERR_TRANSDUCERS_EXCEEDED = 0x83, // Sel + HID_USAGE_DIGITIZER_ERR_FULL_TRANS_FEATURES_UNAVAILABLE = 0x84, // Sel + HID_USAGE_DIGITIZER_ERR_CHARGE_LOW = 0x85, // Sel + // Reserved (0x86 - 0x8F) + HID_USAGE_DIGITIZER_TRANSDUCER_SOFTWARE_INFO = 0x90, // CL + HID_USAGE_DIGITIZER_TRANSDUCER_VENDOR_ID = 0x91, // SV + HID_USAGE_DIGITIZER_TRANSDUCER_PRODUCT_ID = 0x92, // SV + HID_USAGE_DIGITIZER_DEVICE_SUPPORTED_PROTOCOLS = 0x93, // NAry/CL + HID_USAGE_DIGITIZER_TRANSDUCER_SUPPORTED_PROTOCOLS = 0x94, // NAry/CL + HID_USAGE_DIGITIZER_NO_PROTOCOL = 0x95, // Sel + HID_USAGE_DIGITIZER_WACOM_AES_PROTOCOL = 0x96, // Sel + HID_USAGE_DIGITIZER_USI_PROTOCOL = 0x97, // Sel + HID_USAGE_DIGITIZER_MICROSOFT_PEN_PROTOCOL = 0x98, // Sel + // Reserved (0x99 - 0x9F) + HID_USAGE_DIGITIZER_SUPPORTED_REPORT_RATES = 0xA0, // SV/CL + HID_USAGE_DIGITIZER_REPORT_RATE = 0xA1, // DV + HID_USAGE_DIGITIZER_TRANSDUCER_CONNECTED = 0xA2, // SF + HID_USAGE_DIGITIZER_SWITCH_DISABLED = 0xA3, // Sel + HID_USAGE_DIGITIZER_SWITCH_UNIMPLEMENTED = 0xA4, // Sel + HID_USAGE_DIGITIZER_TRANSDUCER_SWITCHES = 0xA5, // CL + HID_USAGE_DIGITIZER_TRANSDUCER_INDEX_SELECTOR = 0xA6, // DV + // Reserved (0xA7 - 0xAF) + HID_USAGE_DIGITIZER_BUTTON_PRESS_THRESHOLD = 0xB0, // DV + + // Reserved (0xB1 - 0xFFFF) +}; + +/// HID Usage Table: Physical Input Device Page (0x0F) +enum { + HID_USAGE_PID_UNDEFINED = 0x00, + HID_USAGE_PID_PHYSICAL_INPUT_DEVICE = 0x01, + HID_USAGE_PID_NORMAL = 0x20, + HID_USAGE_PID_SET_EFFECT_REPORT = 0x21, + HID_USAGE_PID_EFFECT_PARAMETER_BLOCK_INDEX = 0x22, + HID_USAGE_PID_PARAMETER_BLOCK_OFFSET = 0x23, + HID_USAGE_PID_ROM_FLAG = 0x24, + HID_USAGE_PID_EFFECT_TYPE = 0x25, + HID_USAGE_PID_ET_CONSTANTFORCE = 0x26, + HID_USAGE_PID_ET_RAMP = 0x27, + HID_USAGE_PID_ET_CUSTOMFORCE = 0x28, + HID_USAGE_PID_ET_SQUARE = 0x30, + HID_USAGE_PID_ET_SINE = 0x31, + HID_USAGE_PID_ET_TRIANGLE = 0x32, + HID_USAGE_PID_ET_SAWTOOTH_UP = 0x33, + HID_USAGE_PID_ET_SAWTOOTH_DOWN = 0x34, + HID_USAGE_PID_ET_SPRING = 0x40, + HID_USAGE_PID_ET_DAMPER = 0x41, + HID_USAGE_PID_ET_INERTIA = 0x42, + HID_USAGE_PID_ET_FRICTION = 0x43, + HID_USAGE_PID_DURATION = 0x50, + HID_USAGE_PID_SAMPLE_PERIOD = 0x51, + HID_USAGE_PID_GAIN = 0x52, + HID_USAGE_PID_TRIGGER_BUTTON = 0x53, + HID_USAGE_PID_TRIGGER_REPEAT_INTERVAL = 0x54, + HID_USAGE_PID_AXES_ENABLE = 0x55, + HID_USAGE_PID_DIRECTION_ENABLE = 0x56, + HID_USAGE_PID_DIRECTION = 0x57, + HID_USAGE_PID_TYPE_SPECIFIC_BLOCK_OFFSET = 0x58, + HID_USAGE_PID_BLOCK_TYPE = 0x59, + HID_USAGE_PID_SET_ENVELOPE_REPORT = 0x5a, + HID_USAGE_PID_ATTACK_LEVEL = 0x5b, + HID_USAGE_PID_ATTACK_TIME = 0x5c, + HID_USAGE_PID_FADE_LEVEL = 0x5d, + HID_USAGE_PID_FADE_TIME = 0x5e, + HID_USAGE_PID_SET_CONDITION_REPORT = 0x5f, + HID_USAGE_PID_CENTERPOINT_OFFSET = 0x60, + HID_USAGE_PID_POSITIVE_COEFFICIENT = 0x61, + HID_USAGE_PID_NEGATIVE_COEFFICIENT = 0x62, + HID_USAGE_PID_POSITIVE_SATURATION = 0x63, + HID_USAGE_PID_NEGATIVE_SATURATION = 0x64, + HID_USAGE_PID_DEAD_BAND = 0x65, + HID_USAGE_PID_DOWNLOAD_FORCE_SAMPLE = 0x66, + HID_USAGE_PID_ISOCH_CUSTOMFORCE_ENABLE = 0x67, + HID_USAGE_PID_CUSTOMFORCE_DATA_REPORT = 0x68, + HID_USAGE_PID_CUSTOMFORCE_DATA = 0x69, + HID_USAGE_PID_CUSTOMFORCE_VENDOR_DEFINED_DATA = 0x6a, + HID_USAGE_PID_SET_CUSTOMFORCE_REPORT = 0x6b, + HID_USAGE_PID_CUSTOMFORCE_DATA_OFFSET = 0x6c, + HID_USAGE_PID_SAMPLE_COUNT = 0x6d, + HID_USAGE_PID_SET_PERIODIC_REPORT = 0x6e, + HID_USAGE_PID_OFFSET = 0x6f, + HID_USAGE_PID_MAGNITUDE = 0x70, + HID_USAGE_PID_PHASE = 0x71, + HID_USAGE_PID_PERIOD = 0x72, + HID_USAGE_PID_SET_CONSTANTFORCE_REPORT = 0x73, + HID_USAGE_PID_SET_RAMPFORCE_REPORT = 0x74, + HID_USAGE_PID_RAMP_START = 0x75, + HID_USAGE_PID_RAMP_END = 0x76, + HID_USAGE_PID_EFFECT_OPERATION_REPORT = 0x77, + HID_USAGE_PID_EFFECT_OPERATION = 0x78, + HID_USAGE_PID_OP_EFFECT_START = 0x79, + HID_USAGE_PID_OP_EFFECT_START_SOLO = 0x7a, + HID_USAGE_PID_OP_EFFECT_STOP = 0x7b, + HID_USAGE_PID_LOOP_COUNT = 0x7c, + HID_USAGE_PID_DEVICE_GAIN_REPORT = 0x7d, + HID_USAGE_PID_DEVICE_GAIN = 0x7e, + HID_USAGE_PID_PARAMETER_BLOCK_POOLS_REPORT = 0x7f, + HID_USAGE_PID_RAM_POOL_SIZE = 0x80, + HID_USAGE_PID_ROM_POOL_SIZE = 0x81, + HID_USAGE_PID_ROM_EFFECT_BLOCK_COUNT = 0x82, + HID_USAGE_PID_SIMULTANEOUS_EFFECTS_MAX = 0x83, + HID_USAGE_PID_POOL_ALIGNMENT = 0x84, + HID_USAGE_PID_PARAMETER_BLOCK_MOVE_REPORT = 0x85, + HID_USAGE_PID_MOVE_SOURCE = 0x86, + HID_USAGE_PID_MOVE_DESTINATION = 0x87, + HID_USAGE_PID_MOVE_LENGTH = 0x88, + HID_USAGE_PID_EFFECT_PARAMETER_BLOCK_LOAD_REPORT = 0x89, + HID_USAGE_PID_EFFECT_PARAMETER_BLOCK_LOAD_STATUS = 0x8b, + HID_USAGE_PID_BLOCK_LOAD_SUCCESS = 0x8c, + HID_USAGE_PID_BLOCK_LOAD_FULL = 0x8d, + HID_USAGE_PID_BLOCK_LOAD_ERROR = 0x8e, + HID_USAGE_PID_BLOCK_HANDLE = 0x8f, + HID_USAGE_PID_EFFECT_PARAMETER_BLOCK_FREE_REPORT = 0x90, + HID_USAGE_PID_TYPE_SPECIFIC_BLOCK_HANDLE = 0x91, + HID_USAGE_PID_PID_STATE_REPORT = 0x92, + HID_USAGE_PID_EFFECT_PLAYING = 0x94, + HID_USAGE_PID_PID_DEVICE_CONTROL_REPORT = 0x95, + HID_USAGE_PID_PID_DEVICE_CONTROL = 0x96, + HID_USAGE_PID_DC_ENABLE_ACTUATORS = 0x97, + HID_USAGE_PID_DC_DISABLE_ACTUATORS = 0x98, + HID_USAGE_PID_DC_STOP_ALL_EFFECTS = 0x99, + HID_USAGE_PID_DC_RESET = 0x9a, + HID_USAGE_PID_DC_PAUSE = 0x9b, + HID_USAGE_PID_DC_CONTINUE = 0x9c, + HID_USAGE_PID_DEVICE_PAUSED = 0x9f, + HID_USAGE_PID_ACTUATORS_ENABLED = 0xa0, + HID_USAGE_PID_SAFETY_SWITCH = 0xa4, + HID_USAGE_PID_ACTUATOR_OVERRIDE_SWITCH = 0xa5, + HID_USAGE_PID_ACTUATOR_POWER = 0xa6, + HID_USAGE_PID_START_DELAY = 0xa7, + HID_USAGE_PID_PARAMETER_BLOCK_SIZE = 0xa8, + HID_USAGE_PID_DEVICEMANAGED_POOL = 0xa9, + HID_USAGE_PID_SHARED_PARAMETER_BLOCKS = 0xaa, + HID_USAGE_PID_CREATE_NEW_EFFECT_PARAMETER_BLOCK_REPORT = 0xab, + HID_USAGE_PID_RAM_POOL_AVAILABLE = 0xac, +}; + +/// HID Usage Table - Lighting And Illumination Page (0x59) +enum { + HID_USAGE_LIGHTING_LAMP_ARRAY = 0x01, + HID_USAGE_LIGHTING_LAMP_ARRAY_ATTRIBUTES_REPORT = 0x02, + HID_USAGE_LIGHTING_LAMP_COUNT = 0x03, + HID_USAGE_LIGHTING_BOUNDING_BOX_WIDTH_IN_MICROMETERS = 0x04, + HID_USAGE_LIGHTING_BOUNDING_BOX_HEIGHT_IN_MICROMETERS = 0x05, + HID_USAGE_LIGHTING_BOUNDING_BOX_DEPTH_IN_MICROMETERS = 0x06, + HID_USAGE_LIGHTING_LAMP_ARRAY_KIND = 0x07, + HID_USAGE_LIGHTING_MIN_UPDATE_INTERVAL_IN_MICROSECONDS = 0x08, + HID_USAGE_LIGHTING_LAMP_ATTRIBUTES_REQUEST_REPORT = 0x20, + HID_USAGE_LIGHTING_LAMP_ID = 0x21, + HID_USAGE_LIGHTING_LAMP_ATTRIBUTES_RESPONSE_REPORT = 0x22, + HID_USAGE_LIGHTING_POSITION_X_IN_MICROMETERS = 0x23, + HID_USAGE_LIGHTING_POSITION_Y_IN_MICROMETERS = 0x24, + HID_USAGE_LIGHTING_POSITION_Z_IN_MICROMETERS = 0x25, + HID_USAGE_LIGHTING_LAMP_PURPOSES = 0x26, + HID_USAGE_LIGHTING_UPDATE_LATENCY_IN_MICROSECONDS = 0x27, + HID_USAGE_LIGHTING_RED_LEVEL_COUNT = 0x28, + HID_USAGE_LIGHTING_GREEN_LEVEL_COUNT = 0x29, + HID_USAGE_LIGHTING_BLUE_LEVEL_COUNT = 0x2A, + HID_USAGE_LIGHTING_INTENSITY_LEVEL_COUNT = 0x2B, + HID_USAGE_LIGHTING_IS_PROGRAMMABLE = 0x2C, + HID_USAGE_LIGHTING_INPUT_BINDING = 0x2D, + HID_USAGE_LIGHTING_LAMP_MULTI_UPDATE_REPORT = 0x50, + HID_USAGE_LIGHTING_RED_UPDATE_CHANNEL = 0x51, + HID_USAGE_LIGHTING_GREEN_UPDATE_CHANNEL = 0x52, + HID_USAGE_LIGHTING_BLUE_UPDATE_CHANNEL = 0x53, + HID_USAGE_LIGHTING_INTENSITY_UPDATE_CHANNEL = 0x54, + HID_USAGE_LIGHTING_LAMP_UPDATE_FLAGS = 0x55, + HID_USAGE_LIGHTING_LAMP_RANGE_UPDATE_REPORT = 0x60, + HID_USAGE_LIGHTING_LAMP_ID_START = 0x61, + HID_USAGE_LIGHTING_LAMP_ID_END = 0x62, + HID_USAGE_LIGHTING_LAMP_ARRAY_CONTROL_REPORT = 0x70, + HID_USAGE_LIGHTING_AUTONOMOUS_MODE = 0x71, +}; + +/// HID Usage Table: Power Device Page (0x84) +enum { + HID_USAGE_POWER_UNDEFINED = 0x00, + HID_USAGE_POWER_I_NAME = 0x01, + HID_USAGE_POWER_PRESENT_STATUS = 0x02, + HID_USAGE_POWER_CHANGED_STATUS = 0x03, + HID_USAGE_POWER_UPS = 0x04, + HID_USAGE_POWER_POWER_SUPPLY = 0x05, + // 06-0F Reserved + HID_USAGE_POWER_BATTERY_SYSTEM = 0x10, + HID_USAGE_POWER_BATTERY_SYSTEM_ID = 0x11, + HID_USAGE_POWER_BATTERY = 0x12, + HID_USAGE_POWER_BATTERY_ID = 0x13, + HID_USAGE_POWER_CHARGER = 0x14, + HID_USAGE_POWER_CHARGER_ID = 0x15, + HID_USAGE_POWER_POWER_CONVERTER = 0x16, + HID_USAGE_POWER_POWER_CONVERTER_ID = 0x17, + HID_USAGE_POWER_OUTLET_SYSTEM = 0x18, + HID_USAGE_POWER_OUTLET_SYSTEM_ID = 0x19, + HID_USAGE_POWER_INPUT = 0x1A, + HID_USAGE_POWER_INPUT_ID = 0x1B, + HID_USAGE_POWER_OUTPUT = 0x1C, + HID_USAGE_POWER_OUTPUT_ID = 0x1D, + HID_USAGE_POWER_FLOW = 0x1E, + HID_USAGE_POWER_FLOW_ID = 0x1F, + HID_USAGE_POWER_OUTLET = 0x20, + HID_USAGE_POWER_OUTLET_ID = 0x21, + HID_USAGE_POWER_GANG = 0x22, + HID_USAGE_POWER_GANG_ID = 0x23, + HID_USAGE_POWER_POWER_SUMMARY = 0x24, + HID_USAGE_POWER_POWER_SUMMARY_ID = 0x25, + // 26-2F Reserved + HID_USAGE_POWER_VOLTAGE = 0x30, + HID_USAGE_POWER_CURRENT = 0x31, + HID_USAGE_POWER_FREQUENCY = 0x32, + HID_USAGE_POWER_APPARENT_POWER = 0x33, + HID_USAGE_POWER_ACTIVE_POWER = 0x34, + HID_USAGE_POWER_PERCENT_LOAD = 0x35, + HID_USAGE_POWER_TEMPERATURE = 0x36, + HID_USAGE_POWER_HUMIDITY = 0x37, + HID_USAGE_POWER_BAD_COUNT = 0x38, + // 39-3F Reserved + HID_USAGE_POWER_CONFIG_VOLTAGE = 0x40, + HID_USAGE_POWER_CONFIG_CURRENT = 0x41, + HID_USAGE_POWER_CONFIG_FREQUENCY = 0x42, + HID_USAGE_POWER_CONFIG_APPARENT_POWER = 0x43, + HID_USAGE_POWER_CONFIG_ACTIVE_POWER = 0x44, + HID_USAGE_POWER_CONFIG_PERCENT_LOAD = 0x45, + HID_USAGE_POWER_CONFIG_TEMPERATURE = 0x46, + HID_USAGE_POWER_CONFIG_HUMIDITY = 0x47, + // 48-4F Reserved + HID_USAGE_POWER_SWITCH_ON_CONTROL = 0x50, + HID_USAGE_POWER_SWITCH_OFF_CONTROL = 0x51, + HID_USAGE_POWER_TOGGLE_CONTROL = 0x52, + HID_USAGE_POWER_LOW_VOLTAGE_TRANSFER = 0x53, + HID_USAGE_POWER_HIGH_VOLTAGE_TRANSFER = 0x54, + HID_USAGE_POWER_DELAY_BEFORE_REBOOT = 0x55, + HID_USAGE_POWER_DELAY_BEFORE_STARTUP = 0x56, + HID_USAGE_POWER_DELAY_BEFORE_SHUTDOWN = 0x57, + HID_USAGE_POWER_TEST = 0x58, + HID_USAGE_POWER_MODULE_RESET = 0x59, + HID_USAGE_POWER_AUDIBLE_ALARM_CONTROL = 0x5A, + // 5B-5F Reserved + HID_USAGE_POWER_PRESENT = 0x60, + HID_USAGE_POWER_GOOD = 0x61, + HID_USAGE_POWER_INTERNAL_FAILURE = 0x62, + HID_USAGE_POWER_VOLTAGE_OUT_OF_RANGE = 0x63, + HID_USAGE_POWER_FREQUENCY_OUT_OF_RANGE = 0x64, + HID_USAGE_POWER_OVERLOAD = 0x65, + HID_USAGE_POWER_OVER_CHARGED = 0x66, + HID_USAGE_POWER_OVER_TEMPERATURE = 0x67, + HID_USAGE_POWER_SHUTDOWN_REQUESTED = 0x68, + HID_USAGE_POWER_SHUTDOWN_IMMINENT = 0x69, + // 6A Reserved + HID_USAGE_POWER_SWITCH_ON_OFF = 0x6B, + HID_USAGE_POWER_SWITCHABLE = 0x6C, + HID_USAGE_POWER_USED = 0x6D, + HID_USAGE_POWER_BOOST = 0x6E, + HID_USAGE_POWER_BUCK = 0x6F, + HID_USAGE_POWER_INITIALIZED = 0x70, + HID_USAGE_POWER_TESTED = 0x71, + HID_USAGE_POWER_AWAITING_POWER = 0x72, + HID_USAGE_POWER_COMMUNICATION_LOST = 0x73, + // 74-FC Reserved + HID_USAGE_POWER_I_MANUFACTURER = 0xFD, + HID_USAGE_POWER_I_PRODUCT = 0xFE, + HID_USAGE_POWER_I_SERIAL_NUMBER = 0xFF +}; + +/// HID Usage Table: Battery System Page (0x85) +enum { + HID_USAGE_BATTERY_UNDEFINED = 0x00, + HID_USAGE_BATTERY_SMB_BATTERY_MODE = 0x01, + HID_USAGE_BATTERY_SMB_BATTERY_STATUS = 0x02, + HID_USAGE_BATTERY_SMB_ALARM_WARNING = 0x03, + HID_USAGE_BATTERY_SMB_CHARGER_MODE = 0x04, + HID_USAGE_BATTERY_SMB_CHARGER_STATUS = 0x05, + HID_USAGE_BATTERY_SMB_CHARGER_SPEC_INFO = 0x06, + HID_USAGE_BATTERY_SMB_SELECTOR_STATE = 0x07, + HID_USAGE_BATTERY_SMB_SELECTOR_PRESETS = 0x08, + HID_USAGE_BATTERY_SMB_SELECTOR_INFO = 0x09, + // 0A-0F Reserved + HID_USAGE_BATTERY_OPTIONAL_MFG_FUNCTION_1 = 0x10, + HID_USAGE_BATTERY_OPTIONAL_MFG_FUNCTION_2 = 0x11, + HID_USAGE_BATTERY_OPTIONAL_MFG_FUNCTION_3 = 0x12, + HID_USAGE_BATTERY_OPTIONAL_MFG_FUNCTION_4 = 0x13, + HID_USAGE_BATTERY_OPTIONAL_MFG_FUNCTION_5 = 0x14, + HID_USAGE_BATTERY_CONNECTION_TO_SMBUS = 0x15, + HID_USAGE_BATTERY_OUTPUT_CONNECTION = 0x16, + HID_USAGE_BATTERY_CHARGER_CONNECTION = 0x17, + HID_USAGE_BATTERY_BATTERY_INSERTION = 0x18, + HID_USAGE_BATTERY_USE_NEXT = 0x19, + HID_USAGE_BATTERY_OK_TO_USE = 0x1A, + HID_USAGE_BATTERY_BATTERY_SUPPORTED = 0x1B, + HID_USAGE_BATTERY_SELECTOR_REVISION = 0x1C, + HID_USAGE_BATTERY_CHARGING_INDICATOR = 0x1D, + // 1E-27 Reserved + HID_USAGE_BATTERY_MANUFACTURER_ACCESS = 0x28, + HID_USAGE_BATTERY_REMAINING_CAPACITY_LIMIT = 0x29, + HID_USAGE_BATTERY_REMAINING_TIME_LIMIT = 0x2A, + HID_USAGE_BATTERY_AT_RATE = 0x2B, + HID_USAGE_BATTERY_CAPACITY_MODE = 0x2C, + HID_USAGE_BATTERY_BROADCAST_TO_CHARGER = 0x2D, + HID_USAGE_BATTERY_PRIMARY_BATTERY = 0x2E, + HID_USAGE_BATTERY_CHARGE_CONTROLLER = 0x2F, + // 30-3F Reserved + HID_USAGE_BATTERY_TERMINATE_CHARGE = 0x40, + HID_USAGE_BATTERY_TERMINATE_DISCHARGE = 0x41, + HID_USAGE_BATTERY_BELOW_REMAINING_CAPACITY_LIMIT = 0x42, + HID_USAGE_BATTERY_REMAINING_TIME_LIMIT_EXPIRED = 0x43, + HID_USAGE_BATTERY_CHARGING = 0x44, + HID_USAGE_BATTERY_DISCHARGING = 0x45, + HID_USAGE_BATTERY_FULLY_CHARGED = 0x46, + HID_USAGE_BATTERY_FULLY_DISCHARGED = 0x47, + HID_USAGE_BATTERY_CONDITIONING_FLAG = 0x48, + HID_USAGE_BATTERY_AT_RATE_OK = 0x49, + HID_USAGE_BATTERY_SMB_ERROR_CODE = 0x4A, + HID_USAGE_BATTERY_NEED_REPLACEMENT = 0x4B, + // 4C-5F Reserved + HID_USAGE_BATTERY_AT_RATE_TIME_TO_FULL = 0x60, + HID_USAGE_BATTERY_AT_RATE_TIME_TO_EMPTY = 0x61, + HID_USAGE_BATTERY_AVERAGE_CURRENT = 0x62, + HID_USAGE_BATTERY_MAX_ERROR = 0x63, + HID_USAGE_BATTERY_RELATIVE_STATE_OF_CHARGE = 0x64, + HID_USAGE_BATTERY_ABSOLUTE_STATE_OF_CHARGE = 0x65, + HID_USAGE_BATTERY_REMAINING_CAPACITY = 0x66, + HID_USAGE_BATTERY_FULL_CHARGE_CAPACITY = 0x67, + HID_USAGE_BATTERY_RUN_TIME_TO_EMPTY = 0x68, + HID_USAGE_BATTERY_AVERAGE_TIME_TO_EMPTY = 0x69, + HID_USAGE_BATTERY_AVERAGE_TIME_TO_FULL = 0x6A, + HID_USAGE_BATTERY_CYCLE_COUNT = 0x6B, + // 6C-7F Reserved + HID_USAGE_BATTERY_BATT_PACK_MODEL_LEVEL = 0x80, + HID_USAGE_BATTERY_INTERNAL_CHARGE_CONTROLLER = 0x81, + HID_USAGE_BATTERY_PRIMARY_BATTERY_SUPPORT = 0x82, + HID_USAGE_BATTERY_DESIGN_CAPACITY = 0x83, + HID_USAGE_BATTERY_SPECIFICATION_INFO = 0x84, + HID_USAGE_BATTERY_MANUFACTURER_DATE = 0x85, + HID_USAGE_BATTERY_SERIAL_NUMBER = 0x86, + HID_USAGE_BATTERY_I_MANUFACTURER_NAME = 0x87, + HID_USAGE_BATTERY_I_DEVICE_NAME = 0x88, + HID_USAGE_BATTERY_I_DEVICE_CHEMISTRY = 0x89, + HID_USAGE_BATTERY_MANUFACTURER_DATA = 0x8A, + HID_USAGE_BATTERY_RECHARGEABLE = 0x8B, + HID_USAGE_BATTERY_WARNING_CAPACITY_LIMIT = 0x8C, + HID_USAGE_BATTERY_CAPACITY_GRANULARITY_1 = 0x8D, + HID_USAGE_BATTERY_CAPACITY_GRANULARITY_2 = 0x8E, + HID_USAGE_BATTERY_I_OEMINFORMATION = 0x8F, + // 90-BF Reserved + HID_USAGE_BATTERY_INHIBIT_CHARGE = 0xC0, + HID_USAGE_BATTERY_ENABLE_POLLING = 0xC1, + HID_USAGE_BATTERY_RESET_TO_ZERO = 0xC2, + // C3-CF Reserved + HID_USAGE_BATTERY_AC_PRESENT = 0xD0, + HID_USAGE_BATTERY_BATTERY_PRESENT = 0xD1, + HID_USAGE_BATTERY_POWER_FAIL = 0xD2, + HID_USAGE_BATTERY_ALARM_INHIBITED = 0xD3, + HID_USAGE_BATTERY_THERMISTOR_UNDER_RANGE = 0xD4, + HID_USAGE_BATTERY_THERMISTOR_HOT = 0xD5, + HID_USAGE_BATTERY_THERMISTOR_COLD = 0xD6, + HID_USAGE_BATTERY_THERMISTOR_OVER_RANGE = 0xD7, + HID_USAGE_BATTERY_VOLTAGE_OUT_OF_RANGE = 0xD8, + HID_USAGE_BATTERY_CURRENT_OUT_OF_RANGE = 0xD9, + HID_USAGE_BATTERY_CURRENT_NOT_REGULATED = 0xDA, + HID_USAGE_BATTERY_VOLTAGE_NOT_REGULATED = 0xDB, + HID_USAGE_BATTERY_MASTER_MODE = 0xDC, + // DD-EF Reserved + HID_USAGE_BATTERY_CHARGER_SELECTOR_SUPPORT = 0xF0, + HID_USAGE_BATTERY_CHARGER_SPEC = 0xF1, + HID_USAGE_BATTERY_LEVEL_2 = 0xF2, + HID_USAGE_BATTERY_LEVEL_3 = 0xF3 + // F4-FF Reserved +}; + +/// HID Usage Table: FIDO Alliance Page (0xF1D0) +enum { + HID_USAGE_FIDO_U2FHID = 0x01, // U2FHID usage for top-level collection + HID_USAGE_FIDO_DATA_IN = 0x20, // Raw IN data report + HID_USAGE_FIDO_DATA_OUT = 0x21 // Raw OUT data report }; /*-------------------------------------------------------------------- @@ -625,10 +1825,10 @@ enum {0, 0 }, /* 0x07 */ \ {0, HID_KEY_BACKSPACE }, /* 0x08 Backspace */ \ {0, HID_KEY_TAB }, /* 0x09 Tab */ \ - {0, HID_KEY_RETURN }, /* 0x0A Line Feed */ \ + {0, HID_KEY_ENTER }, /* 0x0A Line Feed */ \ {0, 0 }, /* 0x0B */ \ {0, 0 }, /* 0x0C */ \ - {0, HID_KEY_RETURN }, /* 0x0D CR */ \ + {0, HID_KEY_ENTER }, /* 0x0D CR */ \ {0, 0 }, /* 0x0E */ \ {0, 0 }, /* 0x0F */ \ {0, 0 }, /* 0x10 */ \ @@ -860,7 +2060,10 @@ enum {'8' , 0 }, /* 0x60 */ \ {'9' , 0 }, /* 0x61 */ \ {'0' , 0 }, /* 0x62 */ \ - {'0' , 0 }, /* 0x63 */ \ + {'.' , 0 }, /* 0x63 */ \ + {0 , 0 }, /* 0x64 */ \ + {0 , 0 }, /* 0x65 */ \ + {0 , 0 }, /* 0x66 */ \ {'=' , '=' }, /* 0x67 */ \ diff --git a/cores/nRF5/TinyUSB/Adafruit_TinyUSB_ArduinoCore/tinyusb/src/class/hid/hid_device.c b/cores/nRF5/TinyUSB/Adafruit_TinyUSB_ArduinoCore/tinyusb/src/class/hid/hid_device.c index 10267d7..b4f2490 100644 --- a/cores/nRF5/TinyUSB/Adafruit_TinyUSB_ArduinoCore/tinyusb/src/class/hid/hid_device.c +++ b/cores/nRF5/TinyUSB/Adafruit_TinyUSB_ArduinoCore/tinyusb/src/class/hid/hid_device.c @@ -1,4 +1,4 @@ -/* +/* * The MIT License (MIT) * * Copyright (c) 2019 Ha Thach (tinyusb.org) @@ -26,328 +26,394 @@ #include "tusb_option.h" -#if (TUSB_OPT_DEVICE_ENABLED && CFG_TUD_HID) +#if (CFG_TUD_ENABLED && CFG_TUD_HID) //--------------------------------------------------------------------+ // INCLUDE //--------------------------------------------------------------------+ -#include "common/tusb_common.h" -#include "hid_device.h" +#include "device/usbd.h" #include "device/usbd_pvt.h" +#include "hid_device.h" + //--------------------------------------------------------------------+ // MACRO CONSTANT TYPEDEF //--------------------------------------------------------------------+ -typedef struct -{ +typedef struct { uint8_t itf_num; uint8_t ep_in; - uint8_t ep_out; // optional Out endpoint - uint8_t boot_protocol; // Boot mouse or keyboard - bool boot_mode; // default = false (Report) - uint8_t idle_rate; // up to application to handle idle rate - uint16_t report_desc_len; + uint8_t ep_out; // optional Out endpoint + uint8_t itf_protocol; // Boot mouse or keyboard - CFG_TUSB_MEM_ALIGN uint8_t epin_buf[CFG_TUD_HID_EP_BUFSIZE]; - CFG_TUSB_MEM_ALIGN uint8_t epout_buf[CFG_TUD_HID_EP_BUFSIZE]; + uint16_t report_desc_len; + uint8_t protocol_mode; // Boot (0) or Report protocol (1) + uint8_t idle_rate; // up to application to handle idle rate - tusb_hid_descriptor_hid_t const * hid_descriptor; + // TODO save hid descriptor since host can specifically request this after enumeration + // Note: HID descriptor may be not available from application after enumeration + const tusb_hid_descriptor_hid_t*hid_descriptor; } hidd_interface_t; -CFG_TUSB_MEM_SECTION static hidd_interface_t _hidd_itf[CFG_TUD_HID]; +typedef struct { + TUD_EPBUF_DEF(ctrl , CFG_TUD_HID_EP_BUFSIZE); + TUD_EPBUF_DEF(epin , CFG_TUD_HID_EP_BUFSIZE); + TUD_EPBUF_DEF(epout, CFG_TUD_HID_EP_BUFSIZE); +} hidd_epbuf_t; + +static hidd_interface_t _hidd_itf[CFG_TUD_HID]; +CFG_TUD_MEM_SECTION static hidd_epbuf_t _hidd_epbuf[CFG_TUD_HID]; /*------------- Helpers -------------*/ -static inline hidd_interface_t* get_interface_by_itfnum(uint8_t itf_num) -{ - for (uint8_t i=0; i < CFG_TUD_HID; i++ ) - { - if ( itf_num == _hidd_itf[i].itf_num ) return &_hidd_itf[i]; +TU_ATTR_ALWAYS_INLINE static inline uint8_t get_index_by_itfnum(uint8_t itf_num) { + for (uint8_t i = 0; i < CFG_TUD_HID; i++) { + if (itf_num == _hidd_itf[i].itf_num) { + return i; + } } + return 0xFF; +} - return NULL; +//--------------------------------------------------------------------+ +// Weak stubs: invoked if no strong implementation is available +//--------------------------------------------------------------------+ +TU_ATTR_WEAK void tud_hid_set_protocol_cb(uint8_t instance, uint8_t protocol) { + (void) instance; + (void) protocol; +} + +TU_ATTR_WEAK bool tud_hid_set_idle_cb(uint8_t instance, uint8_t idle_rate) { + (void) instance; + (void) idle_rate; + return true; +} + +TU_ATTR_WEAK void tud_hid_report_complete_cb(uint8_t instance, uint8_t const* report, uint16_t len) { + (void) instance; + (void) report; + (void) len; +} + +// Invoked when a transfer wasn't successful +TU_ATTR_WEAK void tud_hid_report_failed_cb(uint8_t instance, hid_report_type_t report_type, uint8_t const* report, uint16_t xferred_bytes) { + (void) instance; + (void) report_type; + (void) report; + (void) xferred_bytes; } //--------------------------------------------------------------------+ // APPLICATION API //--------------------------------------------------------------------+ -bool tud_hid_ready(void) -{ - uint8_t const itf = 0; - uint8_t const ep_in = _hidd_itf[itf].ep_in; - return tud_ready() && (ep_in != 0) && !usbd_edpt_busy(TUD_OPT_RHPORT, ep_in); +bool tud_hid_n_ready(uint8_t instance) { + uint8_t const rhport = 0; + uint8_t const ep_in = _hidd_itf[instance].ep_in; + return tud_ready() && (ep_in != 0) && !usbd_edpt_busy(rhport, ep_in); } -bool tud_hid_report(uint8_t report_id, void const* report, uint8_t len) -{ - uint8_t const rhport = 0; - uint8_t const itf = 0; - hidd_interface_t * p_hid = &_hidd_itf[itf]; +bool tud_hid_n_report(uint8_t instance, uint8_t report_id, void const *report, uint16_t len) { + TU_VERIFY(instance < CFG_TUD_HID); + const uint8_t rhport = 0; + hidd_interface_t *p_hid = &_hidd_itf[instance]; + hidd_epbuf_t *p_epbuf = &_hidd_epbuf[instance]; // claim endpoint - TU_VERIFY( usbd_edpt_claim(rhport, p_hid->ep_in) ); + TU_VERIFY(usbd_edpt_claim(rhport, p_hid->ep_in)); // prepare data - if (report_id) - { - len = tu_min8(len, CFG_TUD_HID_EP_BUFSIZE-1); - - p_hid->epin_buf[0] = report_id; - memcpy(p_hid->epin_buf+1, report, len); + if (report_id) { + p_epbuf->epin[0] = report_id; + TU_VERIFY(0 == tu_memcpy_s(p_epbuf->epin + 1, CFG_TUD_HID_EP_BUFSIZE - 1, report, len)); len++; - }else - { - // If report id = 0, skip ID field - len = tu_min8(len, CFG_TUD_HID_EP_BUFSIZE); - memcpy(p_hid->epin_buf, report, len); + } else { + TU_VERIFY(0 == tu_memcpy_s(p_epbuf->epin, CFG_TUD_HID_EP_BUFSIZE, report, len)); } - return usbd_edpt_xfer(TUD_OPT_RHPORT, p_hid->ep_in, p_hid->epin_buf, len); + return usbd_edpt_xfer(rhport, p_hid->ep_in, p_epbuf->epin, len); } -bool tud_hid_boot_mode(void) -{ - uint8_t itf = 0; - return _hidd_itf[itf].boot_mode; +uint8_t tud_hid_n_interface_protocol(uint8_t instance) { + return _hidd_itf[instance].itf_protocol; } -//--------------------------------------------------------------------+ -// KEYBOARD API -//--------------------------------------------------------------------+ -bool tud_hid_keyboard_report(uint8_t report_id, uint8_t modifier, uint8_t keycode[6]) -{ - hid_keyboard_report_t report; +uint8_t tud_hid_n_get_protocol(uint8_t instance) { + return _hidd_itf[instance].protocol_mode; +} +bool tud_hid_n_keyboard_report(uint8_t instance, uint8_t report_id, uint8_t modifier, const uint8_t keycode[6]) { + hid_keyboard_report_t report; report.modifier = modifier; + report.reserved = 0; - if ( keycode ) - { - memcpy(report.keycode, keycode, 6); - }else - { + if (keycode) { + memcpy(report.keycode, keycode, sizeof(report.keycode)); + } else { tu_memclr(report.keycode, 6); } - return tud_hid_report(report_id, &report, sizeof(report)); + return tud_hid_n_report(instance, report_id, &report, sizeof(report)); } -//--------------------------------------------------------------------+ -// MOUSE APPLICATION API -//--------------------------------------------------------------------+ -bool tud_hid_mouse_report(uint8_t report_id, uint8_t buttons, int8_t x, int8_t y, int8_t vertical, int8_t horizontal) -{ - hid_mouse_report_t report = - { +bool tud_hid_n_mouse_report(uint8_t instance, uint8_t report_id, + uint8_t buttons, int8_t x, int8_t y, int8_t vertical, int8_t horizontal) { + hid_mouse_report_t report = { + .buttons = buttons, + .x = x, + .y = y, + .wheel = vertical, + .pan = horizontal + }; + + return tud_hid_n_report(instance, report_id, &report, sizeof(report)); +} + +bool tud_hid_n_abs_mouse_report(uint8_t instance, uint8_t report_id, + uint8_t buttons, int16_t x, int16_t y, int8_t vertical, int8_t horizontal) { + hid_abs_mouse_report_t report = { .buttons = buttons, - .x = x, - .y = y, - .wheel = vertical, - .pan = horizontal + .x = x, + .y = y, + .wheel = vertical, + .pan = horizontal + }; + return tud_hid_n_report(instance, report_id, &report, sizeof(report)); +} + +bool tud_hid_n_gamepad_report(uint8_t instance, uint8_t report_id, + int8_t x, int8_t y, int8_t z, int8_t rz, int8_t rx, int8_t ry, uint8_t hat, uint32_t buttons) { + hid_gamepad_report_t report = { + .x = x, + .y = y, + .z = z, + .rz = rz, + .rx = rx, + .ry = ry, + .hat = hat, + .buttons = buttons, }; - return tud_hid_report(report_id, &report, sizeof(report)); + return tud_hid_n_report(instance, report_id, &report, sizeof(report)); +} + +bool tud_hid_n_stylus_report(uint8_t instance, uint8_t report_id, uint8_t attrs, uint16_t x, uint16_t y) { + hid_stylus_report_t report = { + .attr = attrs, + .x = x, + .y = y, + }; + + return tud_hid_n_report(instance, report_id, &report, sizeof(report)); } //--------------------------------------------------------------------+ // USBD-CLASS API //--------------------------------------------------------------------+ -void hidd_init(void) -{ - hidd_reset(TUD_OPT_RHPORT); +void hidd_init(void) { + hidd_reset(0); } -void hidd_reset(uint8_t rhport) -{ - (void) rhport; +bool hidd_deinit(void) { + return true; +} + +void hidd_reset(uint8_t rhport) { + (void)rhport; tu_memclr(_hidd_itf, sizeof(_hidd_itf)); } -uint16_t hidd_open(uint8_t rhport, tusb_desc_interface_t const * desc_itf, uint16_t max_len) -{ +uint16_t hidd_open(uint8_t rhport, tusb_desc_interface_t const *desc_itf, uint16_t max_len) { TU_VERIFY(TUSB_CLASS_HID == desc_itf->bInterfaceClass, 0); // len = interface + hid + n*endpoints - uint16_t const drv_len = sizeof(tusb_desc_interface_t) + sizeof(tusb_hid_descriptor_hid_t) + desc_itf->bNumEndpoints*sizeof(tusb_desc_endpoint_t); + uint16_t const drv_len = (uint16_t) (sizeof(tusb_desc_interface_t) + sizeof(tusb_hid_descriptor_hid_t) + + desc_itf->bNumEndpoints * sizeof(tusb_desc_endpoint_t)); TU_ASSERT(max_len >= drv_len, 0); // Find available interface - hidd_interface_t * p_hid = NULL; + hidd_interface_t *p_hid; uint8_t hid_id; - for(hid_id=0; hid_idep_in == 0) { break; } } - TU_ASSERT(p_hid, 0); + TU_ASSERT(hid_id < CFG_TUD_HID, 0); + hidd_epbuf_t *p_epbuf = &_hidd_epbuf[hid_id]; - uint8_t const *p_desc = (uint8_t const *) desc_itf; + uint8_t const *p_desc = (uint8_t const *)desc_itf; //------------- HID descriptor -------------// p_desc = tu_desc_next(p_desc); - p_hid->hid_descriptor = (tusb_hid_descriptor_hid_t const *) p_desc; - TU_ASSERT(HID_DESC_TYPE_HID == p_hid->hid_descriptor->bDescriptorType, 0); + TU_ASSERT(HID_DESC_TYPE_HID == tu_desc_type(p_desc), 0); + p_hid->hid_descriptor = (tusb_hid_descriptor_hid_t const *)p_desc; //------------- Endpoint Descriptor -------------// p_desc = tu_desc_next(p_desc); TU_ASSERT(usbd_open_edpt_pair(rhport, p_desc, desc_itf->bNumEndpoints, TUSB_XFER_INTERRUPT, &p_hid->ep_out, &p_hid->ep_in), 0); - if ( desc_itf->bInterfaceSubClass == HID_SUBCLASS_BOOT ) p_hid->boot_protocol = desc_itf->bInterfaceProtocol; + if (desc_itf->bInterfaceSubClass == HID_SUBCLASS_BOOT) { + p_hid->itf_protocol = desc_itf->bInterfaceProtocol; + } + + p_hid->protocol_mode = HID_PROTOCOL_REPORT; // Per Specs: default is report mode + p_hid->itf_num = desc_itf->bInterfaceNumber; - p_hid->boot_mode = false; // default mode is REPORT - p_hid->itf_num = desc_itf->bInterfaceNumber; - // Use offsetof to avoid pointer to the odd/misaligned address - memcpy(&p_hid->report_desc_len, (uint8_t*) p_hid->hid_descriptor + offsetof(tusb_hid_descriptor_hid_t, wReportLength), 2); + p_hid->report_desc_len = tu_unaligned_read16((uint8_t const *)p_hid->hid_descriptor + offsetof(tusb_hid_descriptor_hid_t, wReportLength)); // Prepare for output endpoint - if (p_hid->ep_out) - { - if ( !usbd_edpt_xfer(rhport, p_hid->ep_out, p_hid->epout_buf, sizeof(p_hid->epout_buf)) ) - { - TU_LOG1_FAILED(); - TU_BREAKPOINT(); - } + if (p_hid->ep_out) { + TU_ASSERT(usbd_edpt_xfer(rhport, p_hid->ep_out, p_epbuf->epout, CFG_TUD_HID_EP_BUFSIZE), drv_len); } return drv_len; } -// Handle class control request +// Invoked when a control transfer occurred on an interface of this class +// Driver response accordingly to the request and the transfer stage (setup/data/ack) // return false to stall control endpoint (e.g unsupported request) -bool hidd_control_request(uint8_t rhport, tusb_control_request_t const * request) -{ +bool hidd_control_xfer_cb(uint8_t rhport, uint8_t stage, tusb_control_request_t const *request) { TU_VERIFY(request->bmRequestType_bit.recipient == TUSB_REQ_RCPT_INTERFACE); - hidd_interface_t* p_hid = get_interface_by_itfnum( (uint8_t) request->wIndex ); - TU_ASSERT(p_hid); + uint8_t const hid_itf = get_index_by_itfnum((uint8_t)request->wIndex); + TU_VERIFY(hid_itf < CFG_TUD_HID); + hidd_interface_t *p_hid = &_hidd_itf[hid_itf]; + hidd_epbuf_t *p_epbuf = &_hidd_epbuf[hid_itf]; - if (request->bmRequestType_bit.type == TUSB_REQ_TYPE_STANDARD) - { + if (request->bmRequestType_bit.type == TUSB_REQ_TYPE_STANDARD) { //------------- STD Request -------------// - uint8_t const desc_type = tu_u16_high(request->wValue); - uint8_t const desc_index = tu_u16_low (request->wValue); - (void) desc_index; - - if (request->bRequest == TUSB_REQ_GET_DESCRIPTOR && desc_type == HID_DESC_TYPE_HID) - { - TU_VERIFY(p_hid->hid_descriptor != NULL); - TU_VERIFY(tud_control_xfer(rhport, request, (void*) p_hid->hid_descriptor, p_hid->hid_descriptor->bLength)); - } - else if (request->bRequest == TUSB_REQ_GET_DESCRIPTOR && desc_type == HID_DESC_TYPE_REPORT) - { - uint8_t const * desc_report = tud_hid_descriptor_report_cb(); - tud_control_xfer(rhport, request, (void*) desc_report, p_hid->report_desc_len); - } - else - { - return false; // stall unsupported request + if (stage == CONTROL_STAGE_SETUP) { + uint8_t const desc_type = tu_u16_high(request->wValue); + // uint8_t const desc_index = tu_u16_low (request->wValue); + + if (request->bRequest == TUSB_REQ_GET_DESCRIPTOR && desc_type == HID_DESC_TYPE_HID) { + TU_VERIFY(p_hid->hid_descriptor); + TU_VERIFY(tud_control_xfer(rhport, request, (void *)(uintptr_t)p_hid->hid_descriptor, p_hid->hid_descriptor->bLength)); + } else if (request->bRequest == TUSB_REQ_GET_DESCRIPTOR && desc_type == HID_DESC_TYPE_REPORT) { + uint8_t const *desc_report = tud_hid_descriptor_report_cb(hid_itf); + tud_control_xfer(rhport, request, (void *)(uintptr_t)desc_report, p_hid->report_desc_len); + } else { + return false; // stall unsupported request + } } - } - else if (request->bmRequestType_bit.type == TUSB_REQ_TYPE_CLASS) - { + } else if (request->bmRequestType_bit.type == TUSB_REQ_TYPE_CLASS) { //------------- Class Specific Request -------------// - switch( request->bRequest ) - { + switch (request->bRequest) { case HID_REQ_CONTROL_GET_REPORT: - { - // wValue = Report Type | Report ID - uint8_t const report_type = tu_u16_high(request->wValue); - uint8_t const report_id = tu_u16_low(request->wValue); + if (stage == CONTROL_STAGE_SETUP) { + uint8_t const report_type = tu_u16_high(request->wValue); + uint8_t const report_id = tu_u16_low(request->wValue); - uint16_t xferlen = tud_hid_get_report_cb(report_id, (hid_report_type_t) report_type, p_hid->epin_buf, request->wLength); - TU_ASSERT( xferlen > 0 ); + uint8_t* report_buf = p_epbuf->ctrl; + uint16_t req_len = tu_min16(request->wLength, CFG_TUD_HID_EP_BUFSIZE); + uint16_t xferlen = 0; - tud_control_xfer(rhport, request, p_hid->epin_buf, xferlen); - } - break; + // If host request a specific Report ID, add ID to as 1 byte of response + if ((report_id != HID_REPORT_TYPE_INVALID) && (req_len > 1)) { + *report_buf++ = report_id; + req_len--; + xferlen++; + } - case HID_REQ_CONTROL_SET_REPORT: - TU_VERIFY(request->wLength <= sizeof(p_hid->epout_buf)); - tud_control_xfer(rhport, request, p_hid->epout_buf, request->wLength); - break; + xferlen += tud_hid_get_report_cb(hid_itf, report_id, (hid_report_type_t) report_type, report_buf, req_len); + TU_ASSERT(xferlen > 0); - case HID_REQ_CONTROL_SET_IDLE: - p_hid->idle_rate = tu_u16_high(request->wValue); - if ( tud_hid_set_idle_cb ) - { - // stall request if callback return false - if ( !tud_hid_set_idle_cb(p_hid->idle_rate) ) return false; + tud_control_xfer(rhport, request, p_epbuf->ctrl, xferlen); } + break; + + case HID_REQ_CONTROL_SET_REPORT: + if (stage == CONTROL_STAGE_SETUP) { + TU_VERIFY(request->wLength <= CFG_TUD_HID_EP_BUFSIZE); + tud_control_xfer(rhport, request, p_epbuf->ctrl, request->wLength); + } else if (stage == CONTROL_STAGE_ACK) { + uint8_t const report_type = tu_u16_high(request->wValue); + uint8_t const report_id = tu_u16_low(request->wValue); + + uint8_t const* report_buf = p_epbuf->ctrl; + uint16_t report_len = tu_min16(request->wLength, CFG_TUD_HID_EP_BUFSIZE); + + // If host request a specific Report ID, extract report ID in buffer before invoking callback + if ((report_id != HID_REPORT_TYPE_INVALID) && (report_len > 1) && (report_id == report_buf[0])) { + report_buf++; + report_len--; + } + + tud_hid_set_report_cb(hid_itf, report_id, (hid_report_type_t) report_type, report_buf, report_len); + } + break; - tud_control_status(rhport, request); - break; + case HID_REQ_CONTROL_SET_IDLE: + if (stage == CONTROL_STAGE_SETUP) { + p_hid->idle_rate = tu_u16_high(request->wValue); + TU_VERIFY(tud_hid_set_idle_cb(hid_itf, p_hid->idle_rate)); // stall if false + tud_control_status(rhport, request); + } + break; case HID_REQ_CONTROL_GET_IDLE: - // TODO idle rate of report - tud_control_xfer(rhport, request, &p_hid->idle_rate, 1); - break; + if (stage == CONTROL_STAGE_SETUP) { + // TODO idle rate of report + tud_control_xfer(rhport, request, &p_hid->idle_rate, 1); + } + break; case HID_REQ_CONTROL_GET_PROTOCOL: - { - uint8_t protocol = (uint8_t)(1-p_hid->boot_mode); // 0 is Boot, 1 is Report protocol - tud_control_xfer(rhport, request, &protocol, 1); - } - break; + if (stage == CONTROL_STAGE_SETUP) { + tud_control_xfer(rhport, request, &p_hid->protocol_mode, 1); + } + break; case HID_REQ_CONTROL_SET_PROTOCOL: - p_hid->boot_mode = 1 - request->wValue; // 0 is Boot, 1 is Report protocol - - if (tud_hid_boot_mode_cb) tud_hid_boot_mode_cb(p_hid->boot_mode); - - tud_control_status(rhport, request); - break; + if (stage == CONTROL_STAGE_SETUP) { + tud_control_status(rhport, request); + } else if (stage == CONTROL_STAGE_ACK) { + p_hid->protocol_mode = (uint8_t) request->wValue; + tud_hid_set_protocol_cb(hid_itf, p_hid->protocol_mode); + } + break; - default: return false; // stall unsupported request + default: + return false; // stall unsupported request } - }else - { + } else { return false; // stall unsupported request } return true; } -// Invoked when class request DATA stage is finished. -// return false to stall control endpoint (e.g Host send non-sense DATA) -bool hidd_control_complete(uint8_t rhport, tusb_control_request_t const * p_request) -{ - (void) rhport; - hidd_interface_t* p_hid = get_interface_by_itfnum( (uint8_t) p_request->wIndex ); - TU_ASSERT(p_hid); - - if (p_request->bmRequestType_bit.type == TUSB_REQ_TYPE_CLASS && - p_request->bRequest == HID_REQ_CONTROL_SET_REPORT) - { - // wValue = Report Type | Report ID - uint8_t const report_type = tu_u16_high(p_request->wValue); - uint8_t const report_id = tu_u16_low(p_request->wValue); - - tud_hid_set_report_cb(report_id, (hid_report_type_t) report_type, p_hid->epout_buf, p_request->wLength); - } +bool hidd_xfer_cb(uint8_t rhport, uint8_t ep_addr, xfer_result_t result, uint32_t xferred_bytes) { + uint8_t instance; + hidd_interface_t *p_hid; - return true; -} - -bool hidd_xfer_cb(uint8_t rhport, uint8_t ep_addr, xfer_result_t result, uint32_t xferred_bytes) -{ - (void) result; - - uint8_t itf = 0; - hidd_interface_t * p_hid = _hidd_itf; - - for ( ; ; itf++, p_hid++) - { - if (itf >= TU_ARRAY_SIZE(_hidd_itf)) return false; - - if ( ep_addr == p_hid->ep_out ) break; + // Identify which interface to use + for (instance = 0; instance < CFG_TUD_HID; instance++) { + p_hid = &_hidd_itf[instance]; + if ((ep_addr == p_hid->ep_out) || (ep_addr == p_hid->ep_in)) { + break; + } } + TU_ASSERT(instance < CFG_TUD_HID); + hidd_epbuf_t *p_epbuf = &_hidd_epbuf[instance]; + + if (ep_addr == p_hid->ep_in) { + // Input report + if (XFER_RESULT_SUCCESS == result) { + tud_hid_report_complete_cb(instance, p_epbuf->epin, (uint16_t) xferred_bytes); + } else { + tud_hid_report_failed_cb(instance, HID_REPORT_TYPE_INPUT, p_epbuf->epin, (uint16_t) xferred_bytes); + } + } else { + // Output report + if (XFER_RESULT_SUCCESS == result) { + tud_hid_set_report_cb(instance, 0, HID_REPORT_TYPE_OUTPUT, p_epbuf->epout, (uint16_t)xferred_bytes); + } else { + tud_hid_report_failed_cb(instance, HID_REPORT_TYPE_OUTPUT, p_epbuf->epout, (uint16_t) xferred_bytes); + } - if (ep_addr == p_hid->ep_out) - { - tud_hid_set_report_cb(0, HID_REPORT_TYPE_INVALID, p_hid->epout_buf, xferred_bytes); - TU_ASSERT(usbd_edpt_xfer(rhport, p_hid->ep_out, p_hid->epout_buf, sizeof(p_hid->epout_buf))); + // prepare for new transfer + TU_ASSERT(usbd_edpt_xfer(rhport, p_hid->ep_out, p_epbuf->epout, CFG_TUD_HID_EP_BUFSIZE)); } return true; diff --git a/cores/nRF5/TinyUSB/Adafruit_TinyUSB_ArduinoCore/tinyusb/src/class/hid/hid_device.h b/cores/nRF5/TinyUSB/Adafruit_TinyUSB_ArduinoCore/tinyusb/src/class/hid/hid_device.h index 1e584e4..fc1dbcb 100644 --- a/cores/nRF5/TinyUSB/Adafruit_TinyUSB_ArduinoCore/tinyusb/src/class/hid/hid_device.h +++ b/cores/nRF5/TinyUSB/Adafruit_TinyUSB_ArduinoCore/tinyusb/src/class/hid/hid_device.h @@ -1,4 +1,4 @@ -/* +/* * The MIT License (MIT) * * Copyright (c) 2019 Ha Thach (tinyusb.org) @@ -24,11 +24,9 @@ * This file is part of the TinyUSB stack. */ -#ifndef _TUSB_HID_DEVICE_H_ -#define _TUSB_HID_DEVICE_H_ +#ifndef TUSB_HID_DEVICE_H_ +#define TUSB_HID_DEVICE_H_ -#include "common/tusb_common.h" -#include "device/usbd.h" #include "hid.h" #ifdef __cplusplus @@ -46,54 +44,116 @@ #endif #ifndef CFG_TUD_HID_EP_BUFSIZE - #define CFG_TUD_HID_EP_BUFSIZE 16 + #define CFG_TUD_HID_EP_BUFSIZE 64 #endif //--------------------------------------------------------------------+ -// Application API +// Application API (Multiple Instances) i.e. CFG_TUD_HID > 1 //--------------------------------------------------------------------+ // Check if the interface is ready to use -bool tud_hid_ready(void); +bool tud_hid_n_ready(uint8_t instance); -// Check if current mode is Boot (true) or Report (false) -bool tud_hid_boot_mode(void); +// Get interface supported protocol (bInterfaceProtocol) check out hid_interface_protocol_enum_t for possible values +uint8_t tud_hid_n_interface_protocol(uint8_t instance); + +// Get current active protocol: HID_PROTOCOL_BOOT (0) or HID_PROTOCOL_REPORT (1) +uint8_t tud_hid_n_get_protocol(uint8_t instance); // Send report to host -bool tud_hid_report(uint8_t report_id, void const* report, uint8_t len); +bool tud_hid_n_report(uint8_t instance, uint8_t report_id, void const* report, uint16_t len); // KEYBOARD: convenient helper to send keyboard report if application // use template layout report as defined by hid_keyboard_report_t -bool tud_hid_keyboard_report(uint8_t report_id, uint8_t modifier, uint8_t keycode[6]); +bool tud_hid_n_keyboard_report(uint8_t instance, uint8_t report_id, uint8_t modifier, const uint8_t keycode[6]); // MOUSE: convenient helper to send mouse report if application // use template layout report as defined by hid_mouse_report_t -bool tud_hid_mouse_report(uint8_t report_id, uint8_t buttons, int8_t x, int8_t y, int8_t vertical, int8_t horizontal); +bool tud_hid_n_mouse_report(uint8_t instance, uint8_t report_id, uint8_t buttons, int8_t x, int8_t y, int8_t vertical, int8_t horizontal); + +// ABSOLUTE MOUSE: convenient helper to send absolute mouse report if application +// use template layout report as defined by hid_abs_mouse_report_t +bool tud_hid_n_abs_mouse_report(uint8_t instance, uint8_t report_id, uint8_t buttons, int16_t x, int16_t y, int8_t vertical, int8_t horizontal); + +// Gamepad: convenient helper to send gamepad report if application +// use template layout report TUD_HID_REPORT_DESC_GAMEPAD +bool tud_hid_n_gamepad_report(uint8_t instance, uint8_t report_id, int8_t x, int8_t y, int8_t z, int8_t rz, int8_t rx, int8_t ry, uint8_t hat, uint32_t buttons); + +// STYLUS PEN: convenient helper to send absolute stylus pen report if application +bool tud_hid_n_stylus_report(uint8_t instance, uint8_t report_id, uint8_t attrs, uint16_t x, uint16_t y); + +//--------------------------------------------------------------------+ +// Application API (Single Port) +//--------------------------------------------------------------------+ +TU_ATTR_ALWAYS_INLINE static inline bool tud_hid_ready(void) { + return tud_hid_n_ready(0); +} + +TU_ATTR_ALWAYS_INLINE static inline uint8_t tud_hid_interface_protocol(void) { + return tud_hid_n_interface_protocol(0); +} + +TU_ATTR_ALWAYS_INLINE static inline uint8_t tud_hid_get_protocol(void) { + return tud_hid_n_get_protocol(0); +} + +TU_ATTR_ALWAYS_INLINE static inline bool tud_hid_report(uint8_t report_id, void const* report, uint16_t len) { + return tud_hid_n_report(0, report_id, report, len); +} + +TU_ATTR_ALWAYS_INLINE static inline bool tud_hid_keyboard_report(uint8_t report_id, uint8_t modifier, const uint8_t keycode[6]) { + return tud_hid_n_keyboard_report(0, report_id, modifier, keycode); +} + +TU_ATTR_ALWAYS_INLINE static inline bool tud_hid_mouse_report(uint8_t report_id, uint8_t buttons, int8_t x, int8_t y, int8_t vertical, int8_t horizontal) { + return tud_hid_n_mouse_report(0, report_id, buttons, x, y, vertical, horizontal); +} + +TU_ATTR_ALWAYS_INLINE static inline bool tud_hid_abs_mouse_report(uint8_t report_id, uint8_t buttons, int16_t x, int16_t y, int8_t vertical, int8_t horizontal) { + return tud_hid_n_abs_mouse_report(0, report_id, buttons, x, y, vertical, horizontal); +} + +TU_ATTR_ALWAYS_INLINE static inline bool tud_hid_gamepad_report(uint8_t report_id, int8_t x, int8_t y, int8_t z, int8_t rz, int8_t rx, int8_t ry, uint8_t hat, uint32_t buttons) { + return tud_hid_n_gamepad_report(0, report_id, x, y, z, rz, rx, ry, hat, buttons); +} + +TU_ATTR_ALWAYS_INLINE static inline bool tud_hid_stylus_report(uint8_t report_id, uint8_t attrs, uint16_t x, uint16_t y) { + return tud_hid_n_stylus_report(0, report_id, attrs, x, y); +} //--------------------------------------------------------------------+ -// Callbacks (Weak is optional) +// Application Callbacks //--------------------------------------------------------------------+ // Invoked when received GET HID REPORT DESCRIPTOR request // Application return pointer to descriptor, whose contents must exist long enough for transfer to complete -uint8_t const * tud_hid_descriptor_report_cb(void); +uint8_t const * tud_hid_descriptor_report_cb(uint8_t instance); // Invoked when received GET_REPORT control request // Application must fill buffer report's content and return its length. // Return zero will cause the stack to STALL request -uint16_t tud_hid_get_report_cb(uint8_t report_id, hid_report_type_t report_type, uint8_t* buffer, uint16_t reqlen); +uint16_t tud_hid_get_report_cb(uint8_t instance, uint8_t report_id, hid_report_type_t report_type, uint8_t* buffer, uint16_t reqlen); // Invoked when received SET_REPORT control request or -// received data on OUT endpoint ( Report ID = 0, Type = 0 ) -void tud_hid_set_report_cb(uint8_t report_id, hid_report_type_t report_type, uint8_t const* buffer, uint16_t bufsize); +// received data on OUT endpoint (Report ID = 0, Type = OUTPUT) +void tud_hid_set_report_cb(uint8_t instance, uint8_t report_id, hid_report_type_t report_type, uint8_t const* buffer, uint16_t bufsize); -// Invoked when received SET_PROTOCOL request ( mode switch Boot <-> Report ) -TU_ATTR_WEAK void tud_hid_boot_mode_cb(uint8_t boot_mode); +// Invoked when received SET_PROTOCOL request +// protocol is either HID_PROTOCOL_BOOT (0) or HID_PROTOCOL_REPORT (1) +void tud_hid_set_protocol_cb(uint8_t instance, uint8_t protocol); // Invoked when received SET_IDLE request. return false will stall the request -// - Idle Rate = 0 : only send report if there is changes, i.e skip duplication +// - Idle Rate = 0 : only send report if there is changes, i.e. skip duplication // - Idle Rate > 0 : skip duplication, but send at least 1 report every idle rate (in unit of 4 ms). -TU_ATTR_WEAK bool tud_hid_set_idle_cb(uint8_t idle_rate); +bool tud_hid_set_idle_cb(uint8_t instance, uint8_t idle_rate); + +// Invoked when sent REPORT successfully to host +// Application can use this to send the next report +// Note: For composite reports, report[0] is report ID +void tud_hid_report_complete_cb(uint8_t instance, uint8_t const* report, uint16_t len); + +// Invoked when a transfer wasn't successful +void tud_hid_report_failed_cb(uint8_t instance, hid_report_type_t report_type, uint8_t const* report, uint16_t xferred_bytes); /* --------------------------------------------------------------------+ * HID Report Descriptor Template @@ -120,7 +180,7 @@ TU_ATTR_WEAK bool tud_hid_set_idle_cb(uint8_t idle_rate); HID_COLLECTION ( HID_COLLECTION_APPLICATION ) ,\ /* Report ID if any */\ __VA_ARGS__ \ - /* 8 bits Modifier Keys (Shfit, Control, Alt) */ \ + /* 8 bits Modifier Keys (Shift, Control, Alt) */ \ HID_USAGE_PAGE ( HID_USAGE_PAGE_KEYBOARD ) ,\ HID_USAGE_MIN ( 224 ) ,\ HID_USAGE_MAX ( 231 ) ,\ @@ -133,16 +193,7 @@ TU_ATTR_WEAK bool tud_hid_set_idle_cb(uint8_t idle_rate); HID_REPORT_COUNT ( 1 ) ,\ HID_REPORT_SIZE ( 8 ) ,\ HID_INPUT ( HID_CONSTANT ) ,\ - /* 6-byte Keycodes */ \ - HID_USAGE_PAGE ( HID_USAGE_PAGE_KEYBOARD ) ,\ - HID_USAGE_MIN ( 0 ) ,\ - HID_USAGE_MAX ( 255 ) ,\ - HID_LOGICAL_MIN ( 0 ) ,\ - HID_LOGICAL_MAX ( 255 ) ,\ - HID_REPORT_COUNT ( 6 ) ,\ - HID_REPORT_SIZE ( 8 ) ,\ - HID_INPUT ( HID_DATA | HID_ARRAY | HID_ABSOLUTE ) ,\ - /* 5-bit LED Indicator Kana | Compose | ScrollLock | CapsLock | NumLock */ \ + /* Output 5-bit LED Indicator Kana | Compose | ScrollLock | CapsLock | NumLock */ \ HID_USAGE_PAGE ( HID_USAGE_PAGE_LED ) ,\ HID_USAGE_MIN ( 1 ) ,\ HID_USAGE_MAX ( 5 ) ,\ @@ -153,6 +204,15 @@ TU_ATTR_WEAK bool tud_hid_set_idle_cb(uint8_t idle_rate); HID_REPORT_COUNT ( 1 ) ,\ HID_REPORT_SIZE ( 3 ) ,\ HID_OUTPUT ( HID_CONSTANT ) ,\ + /* 6-byte Keycodes */ \ + HID_USAGE_PAGE ( HID_USAGE_PAGE_KEYBOARD ) ,\ + HID_USAGE_MIN ( 0 ) ,\ + HID_USAGE_MAX_N ( 255, 2 ) ,\ + HID_LOGICAL_MIN ( 0 ) ,\ + HID_LOGICAL_MAX_N( 255, 2 ) ,\ + HID_REPORT_COUNT ( 6 ) ,\ + HID_REPORT_SIZE ( 8 ) ,\ + HID_INPUT ( HID_DATA | HID_ARRAY | HID_ABSOLUTE ) ,\ HID_COLLECTION_END \ // Mouse Report Descriptor Template @@ -204,6 +264,90 @@ TU_ATTR_WEAK bool tud_hid_set_idle_cb(uint8_t idle_rate); HID_COLLECTION_END , \ HID_COLLECTION_END \ +// Stylus Pen Report Descriptor Template +#define TUD_HID_REPORT_DESC_STYLUS_PEN(...) \ + HID_USAGE_PAGE ( HID_USAGE_PAGE_DIGITIZER ) , \ + HID_USAGE ( HID_USAGE_DIGITIZER_TOUCH_SCREEN ) , \ + HID_COLLECTION ( HID_COLLECTION_APPLICATION ) , \ + /* Report ID if any */\ + __VA_ARGS__ \ + HID_USAGE ( HID_USAGE_DIGITIZER_STYLUS ) , \ + HID_COLLECTION ( HID_COLLECTION_PHYSICAL ) , \ + HID_USAGE_PAGE ( HID_USAGE_DIGITIZER_TIP_SWITCH ) , \ + HID_USAGE_PAGE ( HID_USAGE_DIGITIZER_IN_RANGE ) , \ + HID_LOGICAL_MIN ( 0 ), \ + HID_LOGICAL_MAX ( 1 ), \ + HID_REPORT_SIZE ( 1 ), \ + HID_REPORT_COUNT( 2 ), \ + HID_INPUT ( HID_DATA | HID_VARIABLE | HID_ABSOLUTE ), \ + HID_REPORT_SIZE ( 1 ), \ + HID_REPORT_COUNT( 6 ), \ + HID_INPUT ( HID_CONSTANT | HID_ARRAY | HID_ABSOLUTE), \ + HID_USAGE_PAGE ( HID_USAGE_PAGE_DESKTOP ), \ + HID_PHYSICAL_MAX_N( 0x7fff, 2 ), \ + HID_LOGICAL_MAX_N ( 0x7fff, 2 ), \ + HID_REPORT_SIZE ( 16 ), \ + HID_REPORT_COUNT( 1 ), \ + HID_UNIT_EXPONENT( 0x0f ), \ + HID_UNIT ( HID_VARIABLE | HID_NONLINEAR ), \ + HID_PHYSICAL_MIN( 0 ), \ + HID_PHYSICAL_MAX( 0 ), \ + HID_USAGE ( HID_USAGE_DESKTOP_X ), \ + HID_INPUT ( HID_DATA | HID_VARIABLE | HID_ABSOLUTE ), \ + HID_USAGE ( HID_USAGE_DESKTOP_Y ), \ + HID_INPUT ( HID_DATA | HID_VARIABLE | HID_ABSOLUTE ), \ + HID_COLLECTION_END , \ + HID_COLLECTION_END \ + +// Absolute Mouse Report Descriptor Template +#define TUD_HID_REPORT_DESC_ABSMOUSE(...) \ + HID_USAGE_PAGE ( HID_USAGE_PAGE_DESKTOP ) ,\ + HID_USAGE ( HID_USAGE_DESKTOP_MOUSE ) ,\ + HID_COLLECTION ( HID_COLLECTION_APPLICATION ) ,\ + /* Report ID if any */\ + __VA_ARGS__ \ + HID_USAGE ( HID_USAGE_DESKTOP_POINTER ) ,\ + HID_COLLECTION ( HID_COLLECTION_PHYSICAL ) ,\ + HID_USAGE_PAGE ( HID_USAGE_PAGE_BUTTON ) ,\ + HID_USAGE_MIN ( 1 ) ,\ + HID_USAGE_MAX ( 5 ) ,\ + HID_LOGICAL_MIN ( 0 ) ,\ + HID_LOGICAL_MAX ( 1 ) ,\ + /* Left, Right, Middle, Backward, Forward buttons */ \ + HID_REPORT_COUNT( 5 ) ,\ + HID_REPORT_SIZE ( 1 ) ,\ + HID_INPUT ( HID_DATA | HID_VARIABLE | HID_ABSOLUTE ) ,\ + /* 3 bit padding */ \ + HID_REPORT_COUNT( 1 ) ,\ + HID_REPORT_SIZE ( 3 ) ,\ + HID_INPUT ( HID_CONSTANT ) ,\ + HID_USAGE_PAGE ( HID_USAGE_PAGE_DESKTOP ) ,\ + /* X, Y absolute position [0, 32767] */ \ + HID_USAGE ( HID_USAGE_DESKTOP_X ) ,\ + HID_USAGE ( HID_USAGE_DESKTOP_Y ) ,\ + HID_LOGICAL_MIN ( 0x00 ) ,\ + HID_LOGICAL_MAX_N( 0x7FFF, 2 ) ,\ + HID_REPORT_SIZE ( 16 ) ,\ + HID_REPORT_COUNT ( 2 ) ,\ + HID_INPUT ( HID_DATA | HID_VARIABLE | HID_ABSOLUTE ) ,\ + /* Vertical wheel scroll [-127, 127] */ \ + HID_USAGE ( HID_USAGE_DESKTOP_WHEEL ) ,\ + HID_LOGICAL_MIN ( 0x81 ) ,\ + HID_LOGICAL_MAX ( 0x7f ) ,\ + HID_REPORT_COUNT( 1 ) ,\ + HID_REPORT_SIZE ( 8 ) ,\ + HID_INPUT ( HID_DATA | HID_VARIABLE | HID_RELATIVE ) ,\ + HID_USAGE_PAGE ( HID_USAGE_PAGE_CONSUMER ), \ + /* Horizontal wheel scroll [-127, 127] */ \ + HID_USAGE_N ( HID_USAGE_CONSUMER_AC_PAN, 2 ), \ + HID_LOGICAL_MIN ( 0x81 ), \ + HID_LOGICAL_MAX ( 0x7f ), \ + HID_REPORT_COUNT( 1 ), \ + HID_REPORT_SIZE ( 8 ), \ + HID_INPUT ( HID_DATA | HID_VARIABLE | HID_RELATIVE ), \ + HID_COLLECTION_END , \ + HID_COLLECTION_END \ + // Consumer Control Report Descriptor Template #define TUD_HID_REPORT_DESC_CONSUMER(...) \ HID_USAGE_PAGE ( HID_USAGE_PAGE_CONSUMER ) ,\ @@ -224,7 +368,7 @@ TU_ATTR_WEAK bool tud_hid_set_idle_cb(uint8_t idle_rate); * 0x00 - do nothing * 0x01 - Power Off * 0x02 - Standby - * 0x04 - Wake Host + * 0x03 - Wake Host */ #define TUD_HID_REPORT_DESC_SYSTEM_CONTROL(...) \ HID_USAGE_PAGE ( HID_USAGE_PAGE_DESKTOP ) ,\ @@ -237,8 +381,8 @@ TU_ATTR_WEAK bool tud_hid_set_idle_cb(uint8_t idle_rate); HID_LOGICAL_MAX ( 3 ) ,\ HID_REPORT_COUNT ( 1 ) ,\ HID_REPORT_SIZE ( 2 ) ,\ - HID_USAGE ( HID_USAGE_DESKTOP_SYSTEM_SLEEP ) ,\ HID_USAGE ( HID_USAGE_DESKTOP_SYSTEM_POWER_DOWN ) ,\ + HID_USAGE ( HID_USAGE_DESKTOP_SYSTEM_SLEEP ) ,\ HID_USAGE ( HID_USAGE_DESKTOP_SYSTEM_WAKE_UP ) ,\ HID_INPUT ( HID_DATA | HID_ARRAY | HID_ABSOLUTE ) ,\ /* 6 bit padding */ \ @@ -248,34 +392,71 @@ TU_ATTR_WEAK bool tud_hid_set_idle_cb(uint8_t idle_rate); HID_COLLECTION_END \ // Gamepad Report Descriptor Template -// with 16 buttons and 2 joysticks with following layout -// | Button Map (2 bytes) | X | Y | Z | Rz +// with 32 buttons, 2 joysticks and 1 hat/dpad with following layout +// | X | Y | Z | Rz | Rx | Ry (1 byte each) | hat/DPAD (1 byte) | Button Map (4 bytes) | #define TUD_HID_REPORT_DESC_GAMEPAD(...) \ - HID_USAGE_PAGE ( HID_USAGE_PAGE_DESKTOP ) ,\ - HID_USAGE ( HID_USAGE_DESKTOP_GAMEPAD ) ,\ - HID_COLLECTION ( HID_COLLECTION_APPLICATION ) ,\ + HID_USAGE_PAGE ( HID_USAGE_PAGE_DESKTOP ) ,\ + HID_USAGE ( HID_USAGE_DESKTOP_GAMEPAD ) ,\ + HID_COLLECTION ( HID_COLLECTION_APPLICATION ) ,\ /* Report ID if any */\ __VA_ARGS__ \ - /* 16 bit Button Map */ \ - HID_USAGE_PAGE ( HID_USAGE_PAGE_BUTTON ) ,\ - HID_USAGE_MIN ( 1 ) ,\ - HID_USAGE_MAX ( 16 ) ,\ - HID_LOGICAL_MIN ( 0 ) ,\ - HID_LOGICAL_MAX ( 1 ) ,\ - HID_REPORT_COUNT ( 16 ) ,\ - HID_REPORT_SIZE ( 1 ) ,\ - HID_INPUT ( HID_DATA | HID_VARIABLE | HID_ABSOLUTE ) ,\ - /* X, Y, Z, Rz (min -127, max 127 ) */ \ - HID_USAGE_PAGE ( HID_USAGE_PAGE_DESKTOP ) ,\ - HID_LOGICAL_MIN ( 0x81 ) ,\ - HID_LOGICAL_MAX ( 0x7f ) ,\ - HID_USAGE ( HID_USAGE_DESKTOP_X ) ,\ - HID_USAGE ( HID_USAGE_DESKTOP_Y ) ,\ - HID_USAGE ( HID_USAGE_DESKTOP_Z ) ,\ - HID_USAGE ( HID_USAGE_DESKTOP_RZ ) ,\ - HID_REPORT_COUNT ( 4 ) ,\ - HID_REPORT_SIZE ( 8 ) ,\ - HID_INPUT ( HID_DATA | HID_VARIABLE | HID_ABSOLUTE ) ,\ + /* 8 bit X, Y, Z, Rz, Rx, Ry (min -127, max 127 ) */ \ + HID_USAGE_PAGE ( HID_USAGE_PAGE_DESKTOP ) ,\ + HID_USAGE ( HID_USAGE_DESKTOP_X ) ,\ + HID_USAGE ( HID_USAGE_DESKTOP_Y ) ,\ + HID_USAGE ( HID_USAGE_DESKTOP_Z ) ,\ + HID_USAGE ( HID_USAGE_DESKTOP_RZ ) ,\ + HID_USAGE ( HID_USAGE_DESKTOP_RX ) ,\ + HID_USAGE ( HID_USAGE_DESKTOP_RY ) ,\ + HID_LOGICAL_MIN ( 0x81 ) ,\ + HID_LOGICAL_MAX ( 0x7f ) ,\ + HID_REPORT_COUNT ( 6 ) ,\ + HID_REPORT_SIZE ( 8 ) ,\ + HID_INPUT ( HID_DATA | HID_VARIABLE | HID_ABSOLUTE ) ,\ + /* 8 bit DPad/Hat Button Map */ \ + HID_USAGE_PAGE ( HID_USAGE_PAGE_DESKTOP ) ,\ + HID_USAGE ( HID_USAGE_DESKTOP_HAT_SWITCH ) ,\ + HID_LOGICAL_MIN ( 1 ) ,\ + HID_LOGICAL_MAX ( 8 ) ,\ + HID_PHYSICAL_MIN ( 0 ) ,\ + HID_PHYSICAL_MAX_N ( 315, 2 ) ,\ + HID_REPORT_COUNT ( 1 ) ,\ + HID_REPORT_SIZE ( 8 ) ,\ + HID_INPUT ( HID_DATA | HID_VARIABLE | HID_ABSOLUTE ) ,\ + /* 32 bit Button Map */ \ + HID_USAGE_PAGE ( HID_USAGE_PAGE_BUTTON ) ,\ + HID_USAGE_MIN ( 1 ) ,\ + HID_USAGE_MAX ( 32 ) ,\ + HID_LOGICAL_MIN ( 0 ) ,\ + HID_LOGICAL_MAX ( 1 ) ,\ + HID_REPORT_COUNT ( 32 ) ,\ + HID_REPORT_SIZE ( 1 ) ,\ + HID_INPUT ( HID_DATA | HID_VARIABLE | HID_ABSOLUTE ) ,\ + HID_COLLECTION_END \ + +// FIDO U2F Authenticator Descriptor Template +// - 1st parameter is report size, which is 64 bytes maximum in U2F +// - 2nd parameter is HID_REPORT_ID(n) (optional) +#define TUD_HID_REPORT_DESC_FIDO_U2F(report_size, ...) \ + HID_USAGE_PAGE_N ( HID_USAGE_PAGE_FIDO, 2 ) ,\ + HID_USAGE ( HID_USAGE_FIDO_U2FHID ) ,\ + HID_COLLECTION ( HID_COLLECTION_APPLICATION ) ,\ + /* Report ID if any */ \ + __VA_ARGS__ \ + /* Usage Data In */ \ + HID_USAGE ( HID_USAGE_FIDO_DATA_IN ) ,\ + HID_LOGICAL_MIN ( 0 ) ,\ + HID_LOGICAL_MAX_N ( 0xff, 2 ) ,\ + HID_REPORT_SIZE ( 8 ) ,\ + HID_REPORT_COUNT ( report_size ) ,\ + HID_INPUT ( HID_DATA | HID_VARIABLE | HID_ABSOLUTE ) ,\ + /* Usage Data Out */ \ + HID_USAGE ( HID_USAGE_FIDO_DATA_OUT ) ,\ + HID_LOGICAL_MIN ( 0 ) ,\ + HID_LOGICAL_MAX_N ( 0xff, 2 ) ,\ + HID_REPORT_SIZE ( 8 ) ,\ + HID_REPORT_COUNT ( report_size ) ,\ + HID_OUTPUT ( HID_DATA | HID_VARIABLE | HID_ABSOLUTE ) ,\ HID_COLLECTION_END \ // HID Generic Input & Output @@ -290,32 +471,203 @@ TU_ATTR_WEAK bool tud_hid_set_idle_cb(uint8_t idle_rate); /* Input */ \ HID_USAGE ( 0x02 ),\ HID_LOGICAL_MIN ( 0x00 ),\ - HID_LOGICAL_MAX ( 0xff ),\ + HID_LOGICAL_MAX_N ( 0xff, 2 ),\ HID_REPORT_SIZE ( 8 ),\ HID_REPORT_COUNT( report_size ),\ HID_INPUT ( HID_DATA | HID_VARIABLE | HID_ABSOLUTE ),\ /* Output */ \ HID_USAGE ( 0x03 ),\ HID_LOGICAL_MIN ( 0x00 ),\ - HID_LOGICAL_MAX ( 0xff ),\ + HID_LOGICAL_MAX_N ( 0xff, 2 ),\ HID_REPORT_SIZE ( 8 ),\ HID_REPORT_COUNT( report_size ),\ HID_OUTPUT ( HID_DATA | HID_VARIABLE | HID_ABSOLUTE ),\ HID_COLLECTION_END \ +// HID Lighting and Illumination Report Descriptor Template +// - 1st parameter is report id (required) +// Creates 6 report ids for lighting HID usages in the following order: +// report_id+0: HID_USAGE_LIGHTING_LAMP_ARRAY_ATTRIBUTES_REPORT +// report_id+1: HID_USAGE_LIGHTING_LAMP_ATTRIBUTES_REQUEST_REPORT +// report_id+2: HID_USAGE_LIGHTING_LAMP_ATTRIBUTES_RESPONSE_REPORT +// report_id+3: HID_USAGE_LIGHTING_LAMP_MULTI_UPDATE_REPORT +// report_id+4: HID_USAGE_LIGHTING_LAMP_RANGE_UPDATE_REPORT +// report_id+5: HID_USAGE_LIGHTING_LAMP_ARRAY_CONTROL_REPORT +#define TUD_HID_REPORT_DESC_LIGHTING(report_id) \ + HID_USAGE_PAGE ( HID_USAGE_PAGE_LIGHTING_AND_ILLUMINATION ),\ + HID_USAGE ( HID_USAGE_LIGHTING_LAMP_ARRAY ),\ + HID_COLLECTION ( HID_COLLECTION_APPLICATION ),\ + /* Lamp Array Attributes Report */ \ + HID_REPORT_ID (report_id ) \ + HID_USAGE ( HID_USAGE_LIGHTING_LAMP_ARRAY_ATTRIBUTES_REPORT ),\ + HID_COLLECTION ( HID_COLLECTION_LOGICAL ),\ + HID_USAGE ( HID_USAGE_LIGHTING_LAMP_COUNT ),\ + HID_LOGICAL_MIN ( 0 ),\ + HID_LOGICAL_MAX_N ( 65535, 3 ),\ + HID_REPORT_SIZE ( 16 ),\ + HID_REPORT_COUNT ( 1 ),\ + HID_FEATURE ( HID_CONSTANT | HID_VARIABLE | HID_ABSOLUTE ),\ + HID_USAGE ( HID_USAGE_LIGHTING_BOUNDING_BOX_WIDTH_IN_MICROMETERS ),\ + HID_USAGE ( HID_USAGE_LIGHTING_BOUNDING_BOX_HEIGHT_IN_MICROMETERS ),\ + HID_USAGE ( HID_USAGE_LIGHTING_BOUNDING_BOX_DEPTH_IN_MICROMETERS ),\ + HID_USAGE ( HID_USAGE_LIGHTING_LAMP_ARRAY_KIND ),\ + HID_USAGE ( HID_USAGE_LIGHTING_MIN_UPDATE_INTERVAL_IN_MICROSECONDS ),\ + HID_LOGICAL_MIN ( 0 ),\ + HID_LOGICAL_MAX_N ( 2147483647, 3 ),\ + HID_REPORT_SIZE ( 32 ),\ + HID_REPORT_COUNT ( 5 ),\ + HID_FEATURE ( HID_CONSTANT | HID_VARIABLE | HID_ABSOLUTE ),\ + HID_COLLECTION_END ,\ + /* Lamp Attributes Request Report */ \ + HID_REPORT_ID ( report_id + 1 ) \ + HID_USAGE ( HID_USAGE_LIGHTING_LAMP_ATTRIBUTES_REQUEST_REPORT ),\ + HID_COLLECTION ( HID_COLLECTION_LOGICAL ),\ + HID_USAGE ( HID_USAGE_LIGHTING_LAMP_ID ),\ + HID_LOGICAL_MIN ( 0 ),\ + HID_LOGICAL_MAX_N ( 65535, 3 ),\ + HID_REPORT_SIZE ( 16 ),\ + HID_REPORT_COUNT ( 1 ),\ + HID_FEATURE ( HID_DATA | HID_VARIABLE | HID_ABSOLUTE ),\ + HID_COLLECTION_END ,\ + /* Lamp Attributes Response Report */ \ + HID_REPORT_ID ( report_id + 2 ) \ + HID_USAGE ( HID_USAGE_LIGHTING_LAMP_ATTRIBUTES_RESPONSE_REPORT ),\ + HID_COLLECTION ( HID_COLLECTION_LOGICAL ),\ + HID_USAGE ( HID_USAGE_LIGHTING_LAMP_ID ),\ + HID_LOGICAL_MIN ( 0 ),\ + HID_LOGICAL_MAX_N ( 65535, 3 ),\ + HID_REPORT_SIZE ( 16 ),\ + HID_REPORT_COUNT ( 1 ),\ + HID_FEATURE ( HID_DATA | HID_VARIABLE | HID_ABSOLUTE ),\ + HID_USAGE ( HID_USAGE_LIGHTING_POSITION_X_IN_MICROMETERS ),\ + HID_USAGE ( HID_USAGE_LIGHTING_POSITION_Y_IN_MICROMETERS ),\ + HID_USAGE ( HID_USAGE_LIGHTING_POSITION_Z_IN_MICROMETERS ),\ + HID_USAGE ( HID_USAGE_LIGHTING_UPDATE_LATENCY_IN_MICROSECONDS ),\ + HID_USAGE ( HID_USAGE_LIGHTING_LAMP_PURPOSES ),\ + HID_LOGICAL_MIN ( 0 ),\ + HID_LOGICAL_MAX_N ( 2147483647, 3 ),\ + HID_REPORT_SIZE ( 32 ),\ + HID_REPORT_COUNT ( 5 ),\ + HID_FEATURE ( HID_DATA | HID_VARIABLE | HID_ABSOLUTE ),\ + HID_USAGE ( HID_USAGE_LIGHTING_RED_LEVEL_COUNT ),\ + HID_USAGE ( HID_USAGE_LIGHTING_GREEN_LEVEL_COUNT ),\ + HID_USAGE ( HID_USAGE_LIGHTING_BLUE_LEVEL_COUNT ),\ + HID_USAGE ( HID_USAGE_LIGHTING_INTENSITY_LEVEL_COUNT ),\ + HID_USAGE ( HID_USAGE_LIGHTING_IS_PROGRAMMABLE ),\ + HID_USAGE ( HID_USAGE_LIGHTING_INPUT_BINDING ),\ + HID_LOGICAL_MIN ( 0 ),\ + HID_LOGICAL_MAX_N ( 255, 2 ),\ + HID_REPORT_SIZE ( 8 ),\ + HID_REPORT_COUNT ( 6 ),\ + HID_FEATURE ( HID_DATA | HID_VARIABLE | HID_ABSOLUTE ),\ + HID_COLLECTION_END ,\ + /* Lamp Multi-Update Report */ \ + HID_REPORT_ID ( report_id + 3 ) \ + HID_USAGE ( HID_USAGE_LIGHTING_LAMP_MULTI_UPDATE_REPORT ),\ + HID_COLLECTION ( HID_COLLECTION_LOGICAL ),\ + HID_USAGE ( HID_USAGE_LIGHTING_LAMP_COUNT ),\ + HID_USAGE ( HID_USAGE_LIGHTING_LAMP_UPDATE_FLAGS ),\ + HID_LOGICAL_MIN ( 0 ),\ + HID_LOGICAL_MAX ( 8 ),\ + HID_REPORT_SIZE ( 8 ),\ + HID_REPORT_COUNT ( 2 ),\ + HID_FEATURE ( HID_DATA | HID_VARIABLE | HID_ABSOLUTE ),\ + HID_USAGE ( HID_USAGE_LIGHTING_LAMP_ID ),\ + HID_LOGICAL_MIN ( 0 ),\ + HID_LOGICAL_MAX_N ( 65535, 3 ),\ + HID_REPORT_SIZE ( 16 ),\ + HID_REPORT_COUNT ( 8 ),\ + HID_FEATURE ( HID_DATA | HID_VARIABLE | HID_ABSOLUTE ),\ + HID_USAGE ( HID_USAGE_LIGHTING_RED_UPDATE_CHANNEL ),\ + HID_USAGE ( HID_USAGE_LIGHTING_GREEN_UPDATE_CHANNEL ),\ + HID_USAGE ( HID_USAGE_LIGHTING_BLUE_UPDATE_CHANNEL ),\ + HID_USAGE ( HID_USAGE_LIGHTING_INTENSITY_UPDATE_CHANNEL ),\ + HID_USAGE ( HID_USAGE_LIGHTING_RED_UPDATE_CHANNEL ),\ + HID_USAGE ( HID_USAGE_LIGHTING_GREEN_UPDATE_CHANNEL ),\ + HID_USAGE ( HID_USAGE_LIGHTING_BLUE_UPDATE_CHANNEL ),\ + HID_USAGE ( HID_USAGE_LIGHTING_INTENSITY_UPDATE_CHANNEL ),\ + HID_USAGE ( HID_USAGE_LIGHTING_RED_UPDATE_CHANNEL ),\ + HID_USAGE ( HID_USAGE_LIGHTING_GREEN_UPDATE_CHANNEL ),\ + HID_USAGE ( HID_USAGE_LIGHTING_BLUE_UPDATE_CHANNEL ),\ + HID_USAGE ( HID_USAGE_LIGHTING_INTENSITY_UPDATE_CHANNEL ),\ + HID_USAGE ( HID_USAGE_LIGHTING_RED_UPDATE_CHANNEL ),\ + HID_USAGE ( HID_USAGE_LIGHTING_GREEN_UPDATE_CHANNEL ),\ + HID_USAGE ( HID_USAGE_LIGHTING_BLUE_UPDATE_CHANNEL ),\ + HID_USAGE ( HID_USAGE_LIGHTING_INTENSITY_UPDATE_CHANNEL ),\ + HID_USAGE ( HID_USAGE_LIGHTING_RED_UPDATE_CHANNEL ),\ + HID_USAGE ( HID_USAGE_LIGHTING_GREEN_UPDATE_CHANNEL ),\ + HID_USAGE ( HID_USAGE_LIGHTING_BLUE_UPDATE_CHANNEL ),\ + HID_USAGE ( HID_USAGE_LIGHTING_INTENSITY_UPDATE_CHANNEL ),\ + HID_USAGE ( HID_USAGE_LIGHTING_RED_UPDATE_CHANNEL ),\ + HID_USAGE ( HID_USAGE_LIGHTING_GREEN_UPDATE_CHANNEL ),\ + HID_USAGE ( HID_USAGE_LIGHTING_BLUE_UPDATE_CHANNEL ),\ + HID_USAGE ( HID_USAGE_LIGHTING_INTENSITY_UPDATE_CHANNEL ),\ + HID_USAGE ( HID_USAGE_LIGHTING_RED_UPDATE_CHANNEL ),\ + HID_USAGE ( HID_USAGE_LIGHTING_GREEN_UPDATE_CHANNEL ),\ + HID_USAGE ( HID_USAGE_LIGHTING_BLUE_UPDATE_CHANNEL ),\ + HID_USAGE ( HID_USAGE_LIGHTING_INTENSITY_UPDATE_CHANNEL ),\ + HID_USAGE ( HID_USAGE_LIGHTING_RED_UPDATE_CHANNEL ),\ + HID_USAGE ( HID_USAGE_LIGHTING_GREEN_UPDATE_CHANNEL ),\ + HID_USAGE ( HID_USAGE_LIGHTING_BLUE_UPDATE_CHANNEL ),\ + HID_USAGE ( HID_USAGE_LIGHTING_INTENSITY_UPDATE_CHANNEL ),\ + HID_LOGICAL_MIN ( 0 ),\ + HID_LOGICAL_MAX_N ( 255, 2 ),\ + HID_REPORT_SIZE ( 8 ),\ + HID_REPORT_COUNT ( 32 ),\ + HID_FEATURE ( HID_DATA | HID_VARIABLE | HID_ABSOLUTE ),\ + HID_COLLECTION_END ,\ + /* Lamp Range Update Report */ \ + HID_REPORT_ID ( report_id + 4 ) \ + HID_USAGE ( HID_USAGE_LIGHTING_LAMP_RANGE_UPDATE_REPORT ),\ + HID_COLLECTION ( HID_COLLECTION_LOGICAL ),\ + HID_USAGE ( HID_USAGE_LIGHTING_LAMP_UPDATE_FLAGS ),\ + HID_LOGICAL_MIN ( 0 ),\ + HID_LOGICAL_MAX ( 8 ),\ + HID_REPORT_SIZE ( 8 ),\ + HID_REPORT_COUNT ( 1 ),\ + HID_FEATURE ( HID_DATA | HID_VARIABLE | HID_ABSOLUTE ),\ + HID_USAGE ( HID_USAGE_LIGHTING_LAMP_ID_START ),\ + HID_USAGE ( HID_USAGE_LIGHTING_LAMP_ID_END ),\ + HID_LOGICAL_MIN ( 0 ),\ + HID_LOGICAL_MAX_N ( 65535, 3 ),\ + HID_REPORT_SIZE ( 16 ),\ + HID_REPORT_COUNT ( 2 ),\ + HID_FEATURE ( HID_DATA | HID_VARIABLE | HID_ABSOLUTE ),\ + HID_USAGE ( HID_USAGE_LIGHTING_RED_UPDATE_CHANNEL ),\ + HID_USAGE ( HID_USAGE_LIGHTING_GREEN_UPDATE_CHANNEL ),\ + HID_USAGE ( HID_USAGE_LIGHTING_BLUE_UPDATE_CHANNEL ),\ + HID_USAGE ( HID_USAGE_LIGHTING_INTENSITY_UPDATE_CHANNEL ),\ + HID_LOGICAL_MIN ( 0 ),\ + HID_LOGICAL_MAX_N ( 255, 2 ),\ + HID_REPORT_SIZE ( 8 ),\ + HID_REPORT_COUNT ( 4 ),\ + HID_FEATURE ( HID_DATA | HID_VARIABLE | HID_ABSOLUTE ),\ + HID_COLLECTION_END ,\ + /* Lamp Array Control Report */ \ + HID_REPORT_ID ( report_id + 5 ) \ + HID_USAGE ( HID_USAGE_LIGHTING_LAMP_ARRAY_CONTROL_REPORT ),\ + HID_COLLECTION ( HID_COLLECTION_LOGICAL ),\ + HID_USAGE ( HID_USAGE_LIGHTING_AUTONOMOUS_MODE ),\ + HID_LOGICAL_MIN ( 0 ),\ + HID_LOGICAL_MAX ( 1 ),\ + HID_REPORT_SIZE ( 8 ),\ + HID_REPORT_COUNT ( 1 ),\ + HID_FEATURE ( HID_DATA | HID_VARIABLE | HID_ABSOLUTE ),\ + HID_COLLECTION_END ,\ + HID_COLLECTION_END \ + //--------------------------------------------------------------------+ // Internal Class Driver API //--------------------------------------------------------------------+ -void hidd_init (void); -void hidd_reset (uint8_t rhport); -uint16_t hidd_open (uint8_t rhport, tusb_desc_interface_t const * itf_desc, uint16_t max_len); -bool hidd_control_request (uint8_t rhport, tusb_control_request_t const * request); -bool hidd_control_complete (uint8_t rhport, tusb_control_request_t const * request); -bool hidd_xfer_cb (uint8_t rhport, uint8_t ep_addr, xfer_result_t event, uint32_t xferred_bytes); +void hidd_init (void); +bool hidd_deinit (void); +void hidd_reset (uint8_t rhport); +uint16_t hidd_open (uint8_t rhport, tusb_desc_interface_t const * itf_desc, uint16_t max_len); +bool hidd_control_xfer_cb (uint8_t rhport, uint8_t stage, tusb_control_request_t const * request); +bool hidd_xfer_cb (uint8_t rhport, uint8_t ep_addr, xfer_result_t event, uint32_t xferred_bytes); #ifdef __cplusplus } #endif -#endif /* _TUSB_HID_DEVICE_H_ */ - +#endif diff --git a/cores/nRF5/TinyUSB/Adafruit_TinyUSB_ArduinoCore/tinyusb/src/class/hid/hid_host.c b/cores/nRF5/TinyUSB/Adafruit_TinyUSB_ArduinoCore/tinyusb/src/class/hid/hid_host.c new file mode 100644 index 0000000..da776d0 --- /dev/null +++ b/cores/nRF5/TinyUSB/Adafruit_TinyUSB_ArduinoCore/tinyusb/src/class/hid/hid_host.c @@ -0,0 +1,804 @@ +/* + * 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. + */ + +#include "tusb_option.h" + +#if (CFG_TUH_ENABLED && CFG_TUH_HID) + +#include "host/usbh.h" +#include "host/usbh_pvt.h" + +#include "hid_host.h" + +// Level where CFG_TUSB_DEBUG must be at least for this driver is logged +#ifndef CFG_TUH_HID_LOG_LEVEL + #define CFG_TUH_HID_LOG_LEVEL CFG_TUH_LOG_LEVEL +#endif + +#define TU_LOG_DRV(...) TU_LOG(CFG_TUH_HID_LOG_LEVEL, __VA_ARGS__) + +//--------------------------------------------------------------------+ +// MACRO CONSTANT TYPEDEF +//--------------------------------------------------------------------+ +typedef struct { + uint8_t daddr; + uint8_t itf_num; + uint8_t ep_in; + uint8_t ep_out; + + bool mounted; // Enumeration is complete + uint8_t itf_protocol; // None, Keyboard, Mouse + uint8_t protocol_mode; // Boot (0) or Report protocol (1) + + uint8_t report_desc_type; + uint16_t report_desc_len; + + uint16_t epin_size; + uint16_t epout_size; +} hidh_interface_t; + +typedef struct { + TUH_EPBUF_DEF(epin, CFG_TUH_HID_EPIN_BUFSIZE); + TUH_EPBUF_DEF(epout, CFG_TUH_HID_EPOUT_BUFSIZE); +} hidh_epbuf_t; + +static hidh_interface_t _hidh_itf[CFG_TUH_HID]; +CFG_TUH_MEM_SECTION static hidh_epbuf_t _hidh_epbuf[CFG_TUH_HID]; + +static uint8_t _hidh_default_protocol = HID_PROTOCOL_BOOT; + +//--------------------------------------------------------------------+ +// Weak stubs: invoked if no strong implementation is available +//--------------------------------------------------------------------+ +TU_ATTR_WEAK void tuh_hid_mount_cb(uint8_t dev_addr, uint8_t idx, uint8_t const* report_desc, uint16_t desc_len) { + (void) dev_addr; + (void) idx; + (void) report_desc; + (void) desc_len; +} + +TU_ATTR_WEAK void tuh_hid_umount_cb(uint8_t dev_addr, uint8_t idx) { + (void) dev_addr; + (void) idx; +} + +TU_ATTR_WEAK void tuh_hid_report_sent_cb(uint8_t dev_addr, uint8_t idx, uint8_t const* report, uint16_t len) { + (void) dev_addr; + (void) idx; + (void) report; + (void) len; +} + +TU_ATTR_WEAK void tuh_hid_get_report_complete_cb(uint8_t dev_addr, uint8_t idx, uint8_t report_id, uint8_t report_type, uint16_t len) { + (void) dev_addr; + (void) idx; + (void) report_id; + (void) report_type; + (void) len; +} + +TU_ATTR_WEAK void tuh_hid_set_report_complete_cb(uint8_t dev_addr, uint8_t idx, uint8_t report_id, uint8_t report_type, uint16_t len) { + (void) dev_addr; + (void) idx; + (void) report_id; + (void) report_type; + (void) len; +} + +TU_ATTR_WEAK void tuh_hid_set_protocol_complete_cb(uint8_t dev_addr, uint8_t idx, uint8_t protocol) { + (void) dev_addr; + (void) idx; + (void) protocol; +} + +//--------------------------------------------------------------------+ +// Helper +//--------------------------------------------------------------------+ +TU_ATTR_ALWAYS_INLINE static inline hidh_interface_t* get_hid_itf(uint8_t daddr, uint8_t idx) { + TU_ASSERT(daddr > 0 && idx < CFG_TUH_HID, NULL); + hidh_interface_t* p_hid = &_hidh_itf[idx]; + return (p_hid->daddr == daddr) ? p_hid : NULL; +} + +TU_ATTR_ALWAYS_INLINE static inline hidh_epbuf_t* get_hid_epbuf(uint8_t idx) { + return &_hidh_epbuf[idx]; +} + +// Get instance ID by endpoint address +static uint8_t get_idx_by_epaddr(uint8_t daddr, uint8_t ep_addr) { + for (uint8_t idx = 0; idx < CFG_TUH_HID; idx++) { + hidh_interface_t const* p_hid = &_hidh_itf[idx]; + if (p_hid->daddr == daddr && + (p_hid->ep_in == ep_addr || p_hid->ep_out == ep_addr)) { + return idx; + } + } + return TUSB_INDEX_INVALID_8; +} + +static hidh_interface_t* find_new_itf(void) { + for (uint8_t i = 0; i < CFG_TUH_HID; i++) { + if (_hidh_itf[i].daddr == 0) return &_hidh_itf[i]; + } + return NULL; +} + +//--------------------------------------------------------------------+ +// Interface API +//--------------------------------------------------------------------+ +uint8_t tuh_hid_itf_get_count(uint8_t daddr) { + uint8_t count = 0; + for (uint8_t i = 0; i < CFG_TUH_HID; i++) { + if (_hidh_itf[i].daddr == daddr) count++; + } + return count; +} + +uint8_t tuh_hid_itf_get_total_count(void) { + uint8_t count = 0; + for (uint8_t i = 0; i < CFG_TUH_HID; i++) { + if (_hidh_itf[i].daddr != 0) count++; + } + return count; +} + +bool tuh_hid_mounted(uint8_t daddr, uint8_t idx) { + hidh_interface_t* p_hid = get_hid_itf(daddr, idx); + TU_VERIFY(p_hid); + return p_hid->mounted; +} + +bool tuh_hid_itf_get_info(uint8_t daddr, uint8_t idx, tuh_itf_info_t* info) { + hidh_interface_t* p_hid = get_hid_itf(daddr, idx); + TU_VERIFY(p_hid && info); + + info->daddr = daddr; + + // re-construct descriptor + tusb_desc_interface_t* desc = &info->desc; + desc->bLength = sizeof(tusb_desc_interface_t); + desc->bDescriptorType = TUSB_DESC_INTERFACE; + + desc->bInterfaceNumber = p_hid->itf_num; + desc->bAlternateSetting = 0; + desc->bNumEndpoints = (uint8_t) ((p_hid->ep_in ? 1u : 0u) + (p_hid->ep_out ? 1u : 0u)); + desc->bInterfaceClass = TUSB_CLASS_HID; + desc->bInterfaceSubClass = (p_hid->itf_protocol ? HID_SUBCLASS_BOOT : HID_SUBCLASS_NONE); + desc->bInterfaceProtocol = p_hid->itf_protocol; + desc->iInterface = 0; // not used yet + + return true; +} + +uint8_t tuh_hid_itf_get_index(uint8_t daddr, uint8_t itf_num) { + for (uint8_t idx = 0; idx < CFG_TUH_HID; idx++) { + hidh_interface_t const* p_hid = &_hidh_itf[idx]; + if (p_hid->daddr == daddr && p_hid->itf_num == itf_num) return idx; + } + + return TUSB_INDEX_INVALID_8; +} + +uint8_t tuh_hid_interface_protocol(uint8_t daddr, uint8_t idx) { + hidh_interface_t* p_hid = get_hid_itf(daddr, idx); + return p_hid ? p_hid->itf_protocol : 0; +} + +//--------------------------------------------------------------------+ +// Control Endpoint API +//--------------------------------------------------------------------+ +uint8_t tuh_hid_get_protocol(uint8_t daddr, uint8_t idx) { + hidh_interface_t* p_hid = get_hid_itf(daddr, idx); + return p_hid ? p_hid->protocol_mode : 0; +} + +static void set_protocol_complete(tuh_xfer_t* xfer) { + uint8_t const itf_num = (uint8_t) tu_le16toh(xfer->setup->wIndex); + uint8_t const daddr = xfer->daddr; + uint8_t const idx = tuh_hid_itf_get_index(daddr, itf_num); + + hidh_interface_t* p_hid = get_hid_itf(daddr, idx); + TU_VERIFY(p_hid,); + + if (XFER_RESULT_SUCCESS == xfer->result) { + p_hid->protocol_mode = (uint8_t) tu_le16toh(xfer->setup->wValue); + } + + tuh_hid_set_protocol_complete_cb(daddr, idx, p_hid->protocol_mode); +} + +void tuh_hid_set_default_protocol(uint8_t protocol) { + _hidh_default_protocol = protocol; +} + +static bool _hidh_set_protocol(uint8_t daddr, uint8_t itf_num, uint8_t protocol, + tuh_xfer_cb_t complete_cb, uintptr_t user_data) { + TU_LOG_DRV("HID Set Protocol = %d\r\n", protocol); + + tusb_control_request_t const request = { + .bmRequestType_bit = { + .recipient = TUSB_REQ_RCPT_INTERFACE, + .type = TUSB_REQ_TYPE_CLASS, + .direction = TUSB_DIR_OUT + }, + .bRequest = HID_REQ_CONTROL_SET_PROTOCOL, + .wValue = protocol, + .wIndex = itf_num, + .wLength = 0 + }; + + tuh_xfer_t xfer = { + .daddr = daddr, + .ep_addr = 0, + .setup = &request, + .buffer = NULL, + .complete_cb = complete_cb, + .user_data = user_data + }; + + return tuh_control_xfer(&xfer); +} + +bool tuh_hid_set_protocol(uint8_t daddr, uint8_t idx, uint8_t protocol) { + hidh_interface_t* p_hid = get_hid_itf(daddr, idx); + TU_VERIFY(p_hid && p_hid->itf_protocol != HID_ITF_PROTOCOL_NONE); + + return _hidh_set_protocol(daddr, p_hid->itf_num, protocol, set_protocol_complete, 0); +} + +static void get_report_complete(tuh_xfer_t* xfer) { + TU_LOG_DRV("HID Get Report complete\r\n"); + + uint8_t const itf_num = (uint8_t) tu_le16toh(xfer->setup->wIndex); + uint8_t const idx = tuh_hid_itf_get_index(xfer->daddr, itf_num); + + uint8_t const report_type = tu_u16_high(xfer->setup->wValue); + uint8_t const report_id = tu_u16_low(xfer->setup->wValue); + + tuh_hid_get_report_complete_cb(xfer->daddr, idx, report_id, report_type, + (xfer->result == XFER_RESULT_SUCCESS) ? xfer->setup->wLength : 0); +} + +bool tuh_hid_get_report(uint8_t daddr, uint8_t idx, uint8_t report_id, uint8_t report_type, void* report, uint16_t len) { + hidh_interface_t* p_hid = get_hid_itf(daddr, idx); + TU_VERIFY(p_hid); + TU_LOG_DRV("HID Get Report: id = %u, type = %u, len = %u\r\n", report_id, report_type, len); + + tusb_control_request_t const request = { + .bmRequestType_bit = { + .recipient = TUSB_REQ_RCPT_INTERFACE, + .type = TUSB_REQ_TYPE_CLASS, + .direction = TUSB_DIR_IN + }, + .bRequest = HID_REQ_CONTROL_GET_REPORT, + .wValue = tu_htole16(tu_u16(report_type, report_id)), + .wIndex = tu_htole16((uint16_t) p_hid->itf_num), + .wLength = len + }; + + tuh_xfer_t xfer = { + .daddr = daddr, + .ep_addr = 0, + .setup = &request, + .buffer = report, + .complete_cb = get_report_complete, + .user_data = 0 + }; + + return tuh_control_xfer(&xfer); +} + +static void set_report_complete(tuh_xfer_t* xfer) { + TU_LOG_DRV("HID Set Report complete\r\n"); + + uint8_t const itf_num = (uint8_t) tu_le16toh(xfer->setup->wIndex); + uint8_t const idx = tuh_hid_itf_get_index(xfer->daddr, itf_num); + + uint8_t const report_type = tu_u16_high(xfer->setup->wValue); + uint8_t const report_id = tu_u16_low(xfer->setup->wValue); + + tuh_hid_set_report_complete_cb(xfer->daddr, idx, report_id, report_type, + (xfer->result == XFER_RESULT_SUCCESS) ? xfer->setup->wLength : 0); +} + +bool tuh_hid_set_report(uint8_t daddr, uint8_t idx, uint8_t report_id, uint8_t report_type, void* report, uint16_t len) { + hidh_interface_t* p_hid = get_hid_itf(daddr, idx); + TU_VERIFY(p_hid); + TU_LOG_DRV("HID Set Report: id = %u, type = %u, len = %u\r\n", report_id, report_type, len); + + tusb_control_request_t const request = { + .bmRequestType_bit = { + .recipient = TUSB_REQ_RCPT_INTERFACE, + .type = TUSB_REQ_TYPE_CLASS, + .direction = TUSB_DIR_OUT + }, + .bRequest = HID_REQ_CONTROL_SET_REPORT, + .wValue = tu_htole16(tu_u16(report_type, report_id)), + .wIndex = tu_htole16((uint16_t) p_hid->itf_num), + .wLength = len + }; + + tuh_xfer_t xfer = { + .daddr = daddr, + .ep_addr = 0, + .setup = &request, + .buffer = report, + .complete_cb = set_report_complete, + .user_data = 0 + }; + + return tuh_control_xfer(&xfer); +} + +static bool _hidh_set_idle(uint8_t daddr, uint8_t itf_num, uint16_t idle_rate, + tuh_xfer_cb_t complete_cb, uintptr_t user_data) { + // SET IDLE request, device can stall if not support this request + TU_LOG_DRV("HID Set Idle \r\n"); + + tusb_control_request_t const request = { + .bmRequestType_bit = { + .recipient = TUSB_REQ_RCPT_INTERFACE, + .type = TUSB_REQ_TYPE_CLASS, + .direction = TUSB_DIR_OUT + }, + .bRequest = HID_REQ_CONTROL_SET_IDLE, + .wValue = tu_htole16(idle_rate), + .wIndex = tu_htole16((uint16_t) itf_num), + .wLength = 0 + }; + + tuh_xfer_t xfer = { + .daddr = daddr, + .ep_addr = 0, + .setup = &request, + .buffer = NULL, + .complete_cb = complete_cb, + .user_data = user_data + }; + + return tuh_control_xfer(&xfer); +} + +//--------------------------------------------------------------------+ +// Interrupt Endpoint API +//--------------------------------------------------------------------+ + +// Check if HID interface is ready to receive report +bool tuh_hid_receive_ready(uint8_t dev_addr, uint8_t idx) { + hidh_interface_t* p_hid = get_hid_itf(dev_addr, idx); + TU_VERIFY(p_hid); + return !usbh_edpt_busy(dev_addr, p_hid->ep_in); +} + +bool tuh_hid_receive_report(uint8_t daddr, uint8_t idx) { + hidh_interface_t* p_hid = get_hid_itf(daddr, idx); + TU_VERIFY(p_hid); + hidh_epbuf_t* epbuf = get_hid_epbuf(idx); + + // claim endpoint + TU_VERIFY(usbh_edpt_claim(daddr, p_hid->ep_in)); + + if (!usbh_edpt_xfer(daddr, p_hid->ep_in, epbuf->epin, p_hid->epin_size)) { + usbh_edpt_release(daddr, p_hid->ep_in); + return false; + } + + return true; +} +bool tuh_hid_receive_abort(uint8_t dev_addr, uint8_t idx) { + hidh_interface_t* p_hid = get_hid_itf(dev_addr, idx); + TU_VERIFY(p_hid); + return tuh_edpt_abort_xfer(dev_addr, p_hid->ep_in); +} + +bool tuh_hid_send_ready(uint8_t dev_addr, uint8_t idx) { + hidh_interface_t* p_hid = get_hid_itf(dev_addr, idx); + TU_VERIFY(p_hid); + return !usbh_edpt_busy(dev_addr, p_hid->ep_out); +} + +bool tuh_hid_send_report(uint8_t daddr, uint8_t idx, uint8_t report_id, const void* report, uint16_t len) { + TU_LOG_DRV("HID Send Report %d\r\n", report_id); + + hidh_interface_t* p_hid = get_hid_itf(daddr, idx); + TU_VERIFY(p_hid); + hidh_epbuf_t* epbuf = get_hid_epbuf(idx); + + if (p_hid->ep_out == 0) { + // This HID does not have an out endpoint (other than control) + return false; + } else if (len > CFG_TUH_HID_EPOUT_BUFSIZE || + (report_id != 0 && len > (CFG_TUH_HID_EPOUT_BUFSIZE - 1))) { + // ep_out buffer is not large enough to hold contents + return false; + } + + // claim endpoint + TU_VERIFY(usbh_edpt_claim(daddr, p_hid->ep_out)); + + if (report_id == 0) { + // No report ID in transmission + memcpy(&epbuf->epout[0], report, len); + } else { + epbuf->epout[0] = report_id; + memcpy(&epbuf->epout[1], report, len); + ++len; // 1 more byte for report_id + } + + TU_LOG3_MEM(epbuf->epout, len, 2); + + if (!usbh_edpt_xfer(daddr, p_hid->ep_out, epbuf->epout, len)) { + usbh_edpt_release(daddr, p_hid->ep_out); + return false; + } + + return true; +} + +//--------------------------------------------------------------------+ +// USBH API +//--------------------------------------------------------------------+ +bool hidh_init(void) { + TU_LOG_DRV("sizeof(hidh_interface_t) = %u\r\n", sizeof(hidh_interface_t)); + tu_memclr(_hidh_itf, sizeof(_hidh_itf)); + return true; +} + +bool hidh_deinit(void) { + return true; +} + +bool hidh_xfer_cb(uint8_t daddr, uint8_t ep_addr, xfer_result_t result, uint32_t xferred_bytes) { + (void) result; + + uint8_t const dir = tu_edpt_dir(ep_addr); + uint8_t const idx = get_idx_by_epaddr(daddr, ep_addr); + + hidh_interface_t* p_hid = get_hid_itf(daddr, idx); + TU_VERIFY(p_hid); + hidh_epbuf_t* epbuf = get_hid_epbuf(idx); + + if (dir == TUSB_DIR_IN) { + TU_LOG_DRV(" [idx=%u] Get Report callback\r\n", idx); + TU_LOG3_MEM(epbuf->epin, xferred_bytes, 2); + tuh_hid_report_received_cb(daddr, idx, epbuf->epin, (uint16_t) xferred_bytes); + } else { + tuh_hid_report_sent_cb(daddr, idx, epbuf->epout, (uint16_t) xferred_bytes); + } + + return true; +} + +void hidh_close(uint8_t daddr) { + for (uint8_t i = 0; i < CFG_TUH_HID; i++) { + hidh_interface_t* p_hid = &_hidh_itf[i]; + if (p_hid->daddr == daddr) { + TU_LOG_DRV(" HIDh close addr = %u index = %u\r\n", daddr, i); + tuh_hid_umount_cb(daddr, i); + tu_memclr(p_hid, sizeof(hidh_interface_t)); + } + } +} + +//--------------------------------------------------------------------+ +// Enumeration +//--------------------------------------------------------------------+ + +bool hidh_open(uint8_t rhport, uint8_t daddr, tusb_desc_interface_t const* desc_itf, uint16_t max_len) { + (void) rhport; + (void) max_len; + + TU_VERIFY(TUSB_CLASS_HID == desc_itf->bInterfaceClass); + TU_LOG_DRV("[%u] HID opening Interface %u\r\n", daddr, desc_itf->bInterfaceNumber); + + // len = interface + hid + n*endpoints + uint16_t const drv_len = (uint16_t) (sizeof(tusb_desc_interface_t) + sizeof(tusb_hid_descriptor_hid_t) + + desc_itf->bNumEndpoints * sizeof(tusb_desc_endpoint_t)); + TU_ASSERT(max_len >= drv_len); + uint8_t const* p_desc = (uint8_t const*) desc_itf; + + //------------- HID descriptor -------------// + p_desc = tu_desc_next(p_desc); + tusb_hid_descriptor_hid_t const* desc_hid = (tusb_hid_descriptor_hid_t const*) p_desc; + TU_ASSERT(HID_DESC_TYPE_HID == desc_hid->bDescriptorType); + + hidh_interface_t* p_hid = find_new_itf(); + TU_ASSERT(p_hid); // not enough interface, try to increase CFG_TUH_HID + p_hid->daddr = daddr; + + //------------- Endpoint Descriptors -------------// + p_desc = tu_desc_next(p_desc); + tusb_desc_endpoint_t const* desc_ep = (tusb_desc_endpoint_t const*) p_desc; + + for (int i = 0; i < desc_itf->bNumEndpoints; i++) { + TU_ASSERT(TUSB_DESC_ENDPOINT == desc_ep->bDescriptorType); + TU_ASSERT(tuh_edpt_open(daddr, desc_ep)); + + if (tu_edpt_dir(desc_ep->bEndpointAddress) == TUSB_DIR_IN) { + p_hid->ep_in = desc_ep->bEndpointAddress; + p_hid->epin_size = tu_edpt_packet_size(desc_ep); + } else { + p_hid->ep_out = desc_ep->bEndpointAddress; + p_hid->epout_size = tu_edpt_packet_size(desc_ep); + } + + p_desc = tu_desc_next(p_desc); + desc_ep = (tusb_desc_endpoint_t const*) p_desc; + } + + p_hid->itf_num = desc_itf->bInterfaceNumber; + + // Assume bNumDescriptors = 1 + p_hid->report_desc_type = desc_hid->bReportType; + // Use offsetof to avoid pointer to the odd/misaligned address + p_hid->report_desc_len = tu_unaligned_read16((uint8_t const*)desc_hid + offsetof(tusb_hid_descriptor_hid_t, wReportLength)); + + // Per HID Specs: default is Report protocol, though we will force Boot protocol when set_config + p_hid->protocol_mode = _hidh_default_protocol; + if (HID_SUBCLASS_BOOT == desc_itf->bInterfaceSubClass) { + p_hid->itf_protocol = desc_itf->bInterfaceProtocol; + } + + return true; +} + +//--------------------------------------------------------------------+ +// Set Configure +//--------------------------------------------------------------------+ + +enum { + CONFG_SET_IDLE, + CONFIG_SET_PROTOCOL, + CONFIG_GET_REPORT_DESC, + CONFIG_COMPLETE +}; + +static void config_driver_mount_complete(uint8_t daddr, uint8_t idx, uint8_t const* desc_report, uint16_t desc_len); +static void process_set_config(tuh_xfer_t* xfer); + +bool hidh_set_config(uint8_t daddr, uint8_t itf_num) { + tusb_control_request_t request; + request.wIndex = tu_htole16((uint16_t) itf_num); + + tuh_xfer_t xfer; + xfer.daddr = daddr; + xfer.result = XFER_RESULT_SUCCESS; + xfer.setup = &request; + xfer.user_data = CONFG_SET_IDLE; + + // fake request to kick-off the set config process + process_set_config(&xfer); + + return true; +} + +static void process_set_config(tuh_xfer_t* xfer) { + // Stall is a valid response for SET_IDLE, sometime SET_PROTOCOL as well + // therefore we could ignore its result + if (!(xfer->setup->bRequest == HID_REQ_CONTROL_SET_IDLE || + xfer->setup->bRequest == HID_REQ_CONTROL_SET_PROTOCOL)) { + TU_ASSERT(xfer->result == XFER_RESULT_SUCCESS,); + } + + uintptr_t const state = xfer->user_data; + uint8_t const itf_num = (uint8_t) tu_le16toh(xfer->setup->wIndex); + uint8_t const daddr = xfer->daddr; + + uint8_t const idx = tuh_hid_itf_get_index(daddr, itf_num); + hidh_interface_t* p_hid = get_hid_itf(daddr, idx); + TU_VERIFY(p_hid,); + + switch (state) { + case CONFG_SET_IDLE: { + // Idle rate = 0 mean only report when there is changes + const uint16_t idle_rate = 0; + const uintptr_t next_state = (p_hid->itf_protocol != HID_ITF_PROTOCOL_NONE) + ? CONFIG_SET_PROTOCOL : CONFIG_GET_REPORT_DESC; + _hidh_set_idle(daddr, itf_num, idle_rate, process_set_config, next_state); + break; + } + + case CONFIG_SET_PROTOCOL: + _hidh_set_protocol(daddr, p_hid->itf_num, _hidh_default_protocol, process_set_config, CONFIG_GET_REPORT_DESC); + break; + + case CONFIG_GET_REPORT_DESC: + // Get Report Descriptor if possible + // using usbh enumeration buffer since report descriptor can be very long + if (p_hid->report_desc_len > CFG_TUH_ENUMERATION_BUFSIZE) { + TU_LOG_DRV("HID Skip Report Descriptor since it is too large %u bytes\r\n", p_hid->report_desc_len); + + // Driver is mounted without report descriptor + config_driver_mount_complete(daddr, idx, NULL, 0); + } else { + tuh_descriptor_get_hid_report(daddr, itf_num, p_hid->report_desc_type, 0, + usbh_get_enum_buf(), p_hid->report_desc_len, + process_set_config, CONFIG_COMPLETE); + } + break; + + case CONFIG_COMPLETE: { + uint8_t const* desc_report = usbh_get_enum_buf(); + uint16_t const desc_len = tu_le16toh(xfer->setup->wLength); + + config_driver_mount_complete(daddr, idx, desc_report, desc_len); + break; + } + + default: + break; + } +} + +static void config_driver_mount_complete(uint8_t daddr, uint8_t idx, uint8_t const* desc_report, uint16_t desc_len) { + hidh_interface_t* p_hid = get_hid_itf(daddr, idx); + TU_VERIFY(p_hid,); + p_hid->mounted = true; + + // enumeration is complete + tuh_hid_mount_cb(daddr, idx, desc_report, desc_len); + + // notify usbh that driver enumeration is complete + usbh_driver_set_config_complete(daddr, p_hid->itf_num); +} + +//--------------------------------------------------------------------+ +// Report Descriptor Parser +//--------------------------------------------------------------------+ + +uint8_t tuh_hid_parse_report_descriptor(tuh_hid_report_info_t* report_info_arr, uint8_t arr_count, + uint8_t const* desc_report, uint16_t desc_len) { + // Report Item 6.2.2.2 USB HID 1.11 + union TU_ATTR_PACKED { + uint8_t byte; + struct TU_ATTR_PACKED { + uint8_t size : 2; + uint8_t type : 2; + uint8_t tag : 4; + }; + } header; + + tu_memclr(report_info_arr, arr_count * sizeof(tuh_hid_report_info_t)); + + uint8_t report_num = 0; + tuh_hid_report_info_t* info = report_info_arr; + + // current parsed report count & size from descriptor +// uint8_t ri_report_count = 0; +// uint8_t ri_report_size = 0; + + uint8_t ri_collection_depth = 0; + while (desc_len && report_num < arr_count) { + header.byte = *desc_report++; + desc_len--; + + uint8_t const tag = header.tag; + uint8_t const type = header.type; + uint8_t size = header.size; + if (size == 3) { + size = 4; // HID 1.11 6.2.2.2 3 is 4 bytes + } + + uint8_t const data8 = (size > 0) ? desc_report[0] : 0; + + TU_LOG(3, "tag = %d, type = %d, size = %d, data = ", tag, type, size); + for (uint32_t i = 0; i < size; i++) { + TU_LOG(3, "%02X ", desc_report[i]); + } + TU_LOG(3, "\r\n"); + + switch (type) { + case RI_TYPE_MAIN: + switch (tag) { + case RI_MAIN_INPUT: break; + case RI_MAIN_OUTPUT: break; + case RI_MAIN_FEATURE: break; + case RI_MAIN_COLLECTION: + ri_collection_depth++; + break; + + case RI_MAIN_COLLECTION_END: + ri_collection_depth--; + if (ri_collection_depth == 0) { + info++; + report_num++; + } + break; + + default:break; + } + break; + + case RI_TYPE_GLOBAL: + switch (tag) { + case RI_GLOBAL_USAGE_PAGE: + // only take in account the "usage page" before REPORT ID + if (ri_collection_depth == 0) memcpy(&info->usage_page, desc_report, size); + break; + + case RI_GLOBAL_LOGICAL_MIN: break; + case RI_GLOBAL_LOGICAL_MAX: break; + case RI_GLOBAL_PHYSICAL_MIN: break; + case RI_GLOBAL_PHYSICAL_MAX: break; + + case RI_GLOBAL_REPORT_ID: + info->report_id = data8; + break; + + case RI_GLOBAL_REPORT_SIZE: +// ri_report_size = data8; + break; + + case RI_GLOBAL_REPORT_COUNT: +// ri_report_count = data8; + break; + + case RI_GLOBAL_UNIT_EXPONENT: break; + case RI_GLOBAL_UNIT: break; + case RI_GLOBAL_PUSH: break; + case RI_GLOBAL_POP: break; + + default: break; + } + break; + + case RI_TYPE_LOCAL: + switch (tag) { + case RI_LOCAL_USAGE: + // only take in account the "usage" before starting REPORT ID + if (ri_collection_depth == 0) info->usage = data8; + break; + + case RI_LOCAL_USAGE_MIN: break; + case RI_LOCAL_USAGE_MAX: break; + case RI_LOCAL_DESIGNATOR_INDEX: break; + case RI_LOCAL_DESIGNATOR_MIN: break; + case RI_LOCAL_DESIGNATOR_MAX: break; + case RI_LOCAL_STRING_INDEX: break; + case RI_LOCAL_STRING_MIN: break; + case RI_LOCAL_STRING_MAX: break; + case RI_LOCAL_DELIMITER: break; + default: break; + } + break; + + // error + default: break; + } + + desc_report += size; + desc_len -= size; + } + + for (uint8_t i = 0; i < report_num; i++) { + info = report_info_arr + i; + TU_LOG_DRV("%u: id = %u, usage_page = %u, usage = %u\r\n", i, info->report_id, info->usage_page, info->usage); + } + + return report_num; +} + +#endif diff --git a/cores/nRF5/TinyUSB/Adafruit_TinyUSB_ArduinoCore/tinyusb/src/class/hid/hid_host.h b/cores/nRF5/TinyUSB/Adafruit_TinyUSB_ArduinoCore/tinyusb/src/class/hid/hid_host.h new file mode 100644 index 0000000..032827a --- /dev/null +++ b/cores/nRF5/TinyUSB/Adafruit_TinyUSB_ArduinoCore/tinyusb/src/class/hid/hid_host.h @@ -0,0 +1,185 @@ +/* + * The MIT License (MIT) + * + * Copyright (c) 2019 Ha Thach (tinyusb.org) + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + * This file is part of the TinyUSB stack. + */ + +#ifndef _TUSB_HID_HOST_H_ +#define _TUSB_HID_HOST_H_ + +#include "hid.h" + +#ifdef __cplusplus +extern "C" { +#endif + +//--------------------------------------------------------------------+ +// Class Driver Configuration +//--------------------------------------------------------------------+ + +// TODO Highspeed interrupt can be up to 512 bytes +#ifndef CFG_TUH_HID_EPIN_BUFSIZE +#define CFG_TUH_HID_EPIN_BUFSIZE 64 +#endif + +#ifndef CFG_TUH_HID_EPOUT_BUFSIZE +#define CFG_TUH_HID_EPOUT_BUFSIZE 64 +#endif + + +typedef struct { + uint8_t report_id; + uint8_t usage; + uint16_t usage_page; + + // TODO still use the endpoint size for now +// uint8_t in_len; // length of IN report +// uint8_t out_len; // length of OUT report +} tuh_hid_report_info_t; + +//--------------------------------------------------------------------+ +// Interface API +//--------------------------------------------------------------------+ + +// Get the total number of mounted HID interfaces of a device +uint8_t tuh_hid_itf_get_count(uint8_t dev_addr); + +// Get all mounted interfaces across devices +uint8_t tuh_hid_itf_get_total_count(void); + +// backward compatible rename +#define tuh_hid_instance_count tuh_hid_itf_get_count + +// Get Interface information +bool tuh_hid_itf_get_info(uint8_t daddr, uint8_t idx, tuh_itf_info_t* itf_info); + +// Get Interface index from device address + interface number +// return TUSB_INDEX_INVALID_8 (0xFF) if not found +uint8_t tuh_hid_itf_get_index(uint8_t daddr, uint8_t itf_num); + +// Get interface supported protocol (bInterfaceProtocol) check out hid_interface_protocol_enum_t for possible values +uint8_t tuh_hid_interface_protocol(uint8_t dev_addr, uint8_t idx); + +// Check if HID interface is mounted +bool tuh_hid_mounted(uint8_t dev_addr, uint8_t idx); + +// Parse report descriptor into array of report_info struct and return number of reports. +// For complicated report, application should write its own parser. +TU_ATTR_UNUSED uint8_t tuh_hid_parse_report_descriptor(tuh_hid_report_info_t* reports_info_arr, uint8_t arr_count, + uint8_t const* desc_report, uint16_t desc_len); + +//--------------------------------------------------------------------+ +// Control Endpoint API +//--------------------------------------------------------------------+ + +// Get current protocol: HID_PROTOCOL_BOOT (0) or HID_PROTOCOL_REPORT (1) +// Note: Device will be initialized in Boot protocol for simplicity. +// Application can use set_protocol() to switch back to Report protocol. +uint8_t tuh_hid_get_protocol(uint8_t dev_addr, uint8_t idx); + +// Device by default is enumerated in Boot protocol for simplicity. Application +// can use this to modify the default protocol for next enumeration. +void tuh_hid_set_default_protocol(uint8_t protocol); + +// Set protocol to HID_PROTOCOL_BOOT (0) or HID_PROTOCOL_REPORT (1) +// This function is only supported by Boot interface (tuh_n_hid_interface_protocol() != NONE) +bool tuh_hid_set_protocol(uint8_t dev_addr, uint8_t idx, uint8_t protocol); + +// Get Report using control endpoint +// report_type is either Input, Output or Feature, (value from hid_report_type_t) +bool tuh_hid_get_report(uint8_t dev_addr, uint8_t idx, uint8_t report_id, uint8_t report_type, void* report, uint16_t len); + +// Set Report using control endpoint +// report_type is either Input, Output or Feature, (value from hid_report_type_t) +bool tuh_hid_set_report(uint8_t dev_addr, uint8_t idx, uint8_t report_id, uint8_t report_type, + void* report, uint16_t len); + +//--------------------------------------------------------------------+ +// Interrupt Endpoint API +//--------------------------------------------------------------------+ + +// Check if HID interface is ready to receive report +bool tuh_hid_receive_ready(uint8_t dev_addr, uint8_t idx); + +// Try to receive next report on Interrupt Endpoint. Immediately return +// - true If succeeded, tuh_hid_report_received_cb() callback will be invoked when report is available +// - false if failed to queue the transfer e.g endpoint is busy +bool tuh_hid_receive_report(uint8_t dev_addr, uint8_t idx); + +// Abort receiving report on Interrupt Endpoint +bool tuh_hid_receive_abort(uint8_t dev_addr, uint8_t idx); + +// Check if HID interface is ready to send report +bool tuh_hid_send_ready(uint8_t dev_addr, uint8_t idx); + +// Send report using interrupt endpoint +// If report_id > 0 (composite), it will be sent as 1st byte, then report contents. Otherwise only report content is sent. +bool tuh_hid_send_report(uint8_t dev_addr, uint8_t idx, uint8_t report_id, const void* report, uint16_t len); + +//--------------------------------------------------------------------+ +// Callbacks (Weak is optional) +//--------------------------------------------------------------------+ + +// Invoked when device with hid interface is mounted +// Report descriptor is also available for use. tuh_hid_parse_report_descriptor() +// can be used to parse common/simple enough descriptor. +// Note: if report descriptor length > CFG_TUH_ENUMERATION_BUFSIZE, it will be skipped +// therefore report_desc = NULL, desc_len = 0 +void tuh_hid_mount_cb(uint8_t dev_addr, uint8_t idx, uint8_t const* report_desc, uint16_t desc_len); + +// Invoked when device with hid interface is un-mounted +void tuh_hid_umount_cb(uint8_t dev_addr, uint8_t idx); + +// Invoked when received report from device via interrupt endpoint +// Note: if there is report ID (composite), it is 1st byte of report +void tuh_hid_report_received_cb(uint8_t dev_addr, uint8_t idx, uint8_t const* report, uint16_t len); + +// Invoked when sent report to device successfully via interrupt endpoint +void tuh_hid_report_sent_cb(uint8_t dev_addr, uint8_t idx, uint8_t const* report, uint16_t len); + +// Invoked when Get Report to device via either control endpoint +// len = 0 indicate there is error in the transfer e.g stalled response +void tuh_hid_get_report_complete_cb(uint8_t dev_addr, uint8_t idx, uint8_t report_id, uint8_t report_type, uint16_t len); + +// Invoked when Sent Report to device via either control endpoint +// len = 0 indicate there is error in the transfer e.g stalled response +void tuh_hid_set_report_complete_cb(uint8_t dev_addr, uint8_t idx, uint8_t report_id, uint8_t report_type, uint16_t len); + +// Invoked when Set Protocol request is complete +void tuh_hid_set_protocol_complete_cb(uint8_t dev_addr, uint8_t idx, uint8_t protocol); + +//--------------------------------------------------------------------+ +// Internal Class Driver API +//--------------------------------------------------------------------+ +bool hidh_init(void); +bool hidh_deinit(void); +bool hidh_open(uint8_t rhport, uint8_t dev_addr, tusb_desc_interface_t const* desc_itf, uint16_t max_len); +bool hidh_set_config(uint8_t dev_addr, uint8_t itf_num); +bool hidh_xfer_cb(uint8_t dev_addr, uint8_t ep_addr, xfer_result_t result, uint32_t xferred_bytes); +void hidh_close(uint8_t dev_addr); + +#ifdef __cplusplus +} +#endif + +#endif /* _TUSB_HID_HOST_H_ */ diff --git a/cores/nRF5/TinyUSB/Adafruit_TinyUSB_ArduinoCore/tinyusb/src/class/midi/midi.h b/cores/nRF5/TinyUSB/Adafruit_TinyUSB_ArduinoCore/tinyusb/src/class/midi/midi.h index f758b6e..cd67640 100644 --- a/cores/nRF5/TinyUSB/Adafruit_TinyUSB_ArduinoCore/tinyusb/src/class/midi/midi.h +++ b/cores/nRF5/TinyUSB/Adafruit_TinyUSB_ArduinoCore/tinyusb/src/class/midi/midi.h @@ -1,4 +1,4 @@ -/* +/* * The MIT License (MIT) * * Copyright (c) 2019 Ha Thach (tinyusb.org) @@ -24,13 +24,8 @@ * This file is part of the TinyUSB stack. */ -/** \ingroup group_class - * \defgroup ClassDriver_CDC Communication Device Class (CDC) - * Currently only Abstract Control Model subclass is supported - * @{ */ - -#ifndef _TUSB_MIDI_H__ -#define _TUSB_MIDI_H__ +#ifndef TUSB_MIDI_H_ +#define TUSB_MIDI_H_ #include "common/tusb_common.h" @@ -39,102 +34,121 @@ #endif //--------------------------------------------------------------------+ -// Class Specific Descriptor +// Constants //--------------------------------------------------------------------+ +enum { + MIDI_VERSION_1_0 = 0x0100, + MIDI_VERSION_2_0 = 0x0200, +}; -typedef enum -{ +typedef enum { MIDI_CS_INTERFACE_HEADER = 0x01, MIDI_CS_INTERFACE_IN_JACK = 0x02, MIDI_CS_INTERFACE_OUT_JACK = 0x03, MIDI_CS_INTERFACE_ELEMENT = 0x04, } midi_cs_interface_subtype_t; -typedef enum -{ - MIDI_CS_ENDPOINT_GENERAL = 0x01 +typedef enum { + MIDI_CS_ENDPOINT_GENERAL = 0x01, + MIDI_CS_ENDPOINT_GENERAL_2_0 = 0x02, } midi_cs_endpoint_subtype_t; -typedef enum -{ +typedef enum { MIDI_JACK_EMBEDDED = 0x01, MIDI_JACK_EXTERNAL = 0x02 } midi_jack_type_t; +typedef enum { + MIDI_CIN_MISC = 0, + MIDI_CIN_CABLE_EVENT = 1, + MIDI_CIN_SYSCOM_2BYTE = 2, // 2 byte system common message e.g MTC, SongSelect + MIDI_CIN_SYSCOM_3BYTE = 3, // 3 byte system common message e.g SPP + MIDI_CIN_SYSEX_START = 4, // SysEx starts or continue + MIDI_CIN_SYSEX_END_1BYTE = 5, // SysEx ends with 1 data, or 1 byte system common message + MIDI_CIN_SYSEX_END_2BYTE = 6, // SysEx ends with 2 data + MIDI_CIN_SYSEX_END_3BYTE = 7, // SysEx ends with 3 data + MIDI_CIN_NOTE_OFF = 8, + MIDI_CIN_NOTE_ON = 9, + MIDI_CIN_POLY_KEYPRESS = 10, + MIDI_CIN_CONTROL_CHANGE = 11, + MIDI_CIN_PROGRAM_CHANGE = 12, + MIDI_CIN_CHANNEL_PRESSURE = 13, + MIDI_CIN_PITCH_BEND_CHANGE = 14, + MIDI_CIN_1BYTE_DATA = 15 +} midi_code_index_number_t; + +// MIDI 1.0 status byte +enum { + //------------- System Exclusive -------------// + MIDI_STATUS_SYSEX_START = 0xF0, + MIDI_STATUS_SYSEX_END = 0xF7, + + //------------- System Common -------------// + MIDI_STATUS_SYSCOM_TIME_CODE_QUARTER_FRAME = 0xF1, + MIDI_STATUS_SYSCOM_SONG_POSITION_POINTER = 0xF2, + MIDI_STATUS_SYSCOM_SONG_SELECT = 0xF3, + // F4, F5 is undefined + MIDI_STATUS_SYSCOM_TUNE_REQUEST = 0xF6, + + //------------- System RealTime -------------// + MIDI_STATUS_SYSREAL_TIMING_CLOCK = 0xF8, + // 0xF9 is undefined + MIDI_STATUS_SYSREAL_START = 0xFA, + MIDI_STATUS_SYSREAL_CONTINUE = 0xFB, + MIDI_STATUS_SYSREAL_STOP = 0xFC, + // 0xFD is undefined + MIDI_STATUS_SYSREAL_ACTIVE_SENSING = 0xFE, + MIDI_STATUS_SYSREAL_SYSTEM_RESET = 0xFF, +}; + +enum { + MIDI_MAX_DATA_VAL = 0x7F, +}; + +//--------------------------------------------------------------------+ +// Class Specific Descriptor +//--------------------------------------------------------------------+ + /// MIDI Interface Header Descriptor -typedef struct TU_ATTR_PACKED -{ - uint8_t bLength ; ///< Size of this descriptor in bytes. - uint8_t bDescriptorType ; ///< Descriptor Type, must be Class-Specific - uint8_t bDescriptorSubType ; ///< Descriptor SubType - uint16_t bcdMSC ; ///< MidiStreaming SubClass release number in Binary-Coded Decimal - uint16_t wTotalLength ; +typedef struct TU_ATTR_PACKED { + uint8_t bLength; ///< Size of this descriptor in bytes. + uint8_t bDescriptorType; ///< must be TUSB_DESC_CS_INTERFACE + uint8_t bDescriptorSubType;///< Descriptor SubType + uint16_t bcdMSC; ///< MidiStreaming SubClass release number in Binary-Coded Decimal + uint16_t wTotalLength; } midi_desc_header_t; +TU_VERIFY_STATIC(sizeof(midi_desc_header_t) == 7, "size is not correct"); /// MIDI In Jack Descriptor -typedef struct TU_ATTR_PACKED -{ - uint8_t bLength ; ///< Size of this descriptor in bytes. - uint8_t bDescriptorType ; ///< Descriptor Type, must be Class-Specific - uint8_t bDescriptorSubType ; ///< Descriptor SubType - uint8_t bJackType ; ///< Embedded or External - uint8_t bJackID ; ///< Unique ID for MIDI IN Jack - uint8_t iJack ; ///< string descriptor +typedef struct TU_ATTR_PACKED { + uint8_t bLength; ///< Size of this descriptor in bytes. + uint8_t bDescriptorType; ///< Descriptor Type, must be Class-Specific + uint8_t bDescriptorSubType;///< Descriptor SubType + uint8_t bJackType; ///< Embedded or External + uint8_t bJackID; ///< Unique ID for MIDI IN Jack + uint8_t iJack; ///< string descriptor } midi_desc_in_jack_t; +TU_VERIFY_STATIC(sizeof(midi_desc_in_jack_t) == 6, "size is not correct"); - -/// MIDI Out Jack Descriptor with single pin -typedef struct TU_ATTR_PACKED -{ - uint8_t bLength ; ///< Size of this descriptor in bytes. - uint8_t bDescriptorType ; ///< Descriptor Type, must be Class-Specific - uint8_t bDescriptorSubType ; ///< Descriptor SubType - uint8_t bJackType ; ///< Embedded or External - uint8_t bJackID ; ///< Unique ID for MIDI IN Jack - uint8_t bNrInputPins; - - uint8_t baSourceID; - uint8_t baSourcePin; - - uint8_t iJack ; ///< string descriptor -} midi_desc_out_jack_t ; - -/// MIDI Out Jack Descriptor with multiple pins +/// MIDI Out Jack Descriptor with multiple input pins #define midi_desc_out_jack_n_t(input_num) \ - struct TU_ATTR_PACKED { \ - uint8_t bLength ; \ - uint8_t bDescriptorType ; \ - uint8_t bDescriptorSubType ; \ - uint8_t bJackType ; \ - uint8_t bJackID ; \ - uint8_t bNrInputPins ; \ - struct TU_ATTR_PACKED { \ - uint8_t baSourceID; \ - uint8_t baSourcePin; \ - } pins[input_num]; \ - uint8_t iJack ; \ + struct TU_ATTR_PACKED { \ + uint8_t bLength; \ + uint8_t bDescriptorType; \ + uint8_t bDescriptorSubType; \ + uint8_t bJackType; \ + uint8_t bJackID; \ + uint8_t bNrInputPins; \ + struct TU_ATTR_PACKED { \ + uint8_t baSourceID; \ + uint8_t baSourcePin; \ + } input[input_num]; \ + uint8_t iJack; \ } -/// MIDI Element Descriptor -typedef struct TU_ATTR_PACKED -{ - uint8_t bLength ; ///< Size of this descriptor in bytes. - uint8_t bDescriptorType ; ///< Descriptor Type, must be Class-Specific - uint8_t bDescriptorSubType ; ///< Descriptor SubType - uint8_t bElementID; - - uint8_t bNrInputPins; - uint8_t baSourceID; - uint8_t baSourcePin; - - uint8_t bNrOutputPins; - uint8_t bInTerminalLink; - uint8_t bOutTerminalLink; - uint8_t bElCapsSize; - - uint16_t bmElementCaps; - uint8_t iElement; -} midi_desc_element_t; +typedef midi_desc_out_jack_n_t(1) midi_desc_out_jack_1in_t; // 1 input +typedef midi_desc_out_jack_1in_t midi_desc_out_jack_t; // backward compatible +TU_VERIFY_STATIC(sizeof(midi_desc_out_jack_1in_t) == 7 + 2 * 1, "size is not correct"); /// MIDI Element Descriptor with multiple pins #define midi_desc_element_n_t(input_num) \ @@ -156,12 +170,32 @@ typedef struct TU_ATTR_PACKED uint8_t iElement; \ } -/** @} */ +// This descriptor follows the standard bulk data endpoint descriptor +#define midi_desc_cs_endpoint_n_t(jack_num) \ + struct TU_ATTR_PACKED { \ + uint8_t bLength; \ + uint8_t bDescriptorType; \ + uint8_t bDescriptorSubType; \ + uint8_t bNumEmbMIDIJack; \ + uint8_t baAssocJackID[jack_num]; \ + } + +typedef midi_desc_cs_endpoint_n_t() midi_desc_cs_endpoint_t; // empty/flexible jack list +typedef midi_desc_cs_endpoint_n_t(1) midi_desc_cs_endpoint_1jack_t; + +TU_VERIFY_STATIC(sizeof(midi_desc_cs_endpoint_1jack_t) == 4+1, "size is not correct"); + +//--------------------------------------------------------------------+ +// For Internal Driver Use +//--------------------------------------------------------------------+ +typedef struct { + uint8_t buffer[4]; + uint8_t index; + uint8_t total; +} midi_driver_stream_t; #ifdef __cplusplus } #endif #endif - -/** @} */ diff --git a/cores/nRF5/TinyUSB/Adafruit_TinyUSB_ArduinoCore/tinyusb/src/class/midi/midi_device.c b/cores/nRF5/TinyUSB/Adafruit_TinyUSB_ArduinoCore/tinyusb/src/class/midi/midi_device.c index 00b1bde..7dac7c4 100644 --- a/cores/nRF5/TinyUSB/Adafruit_TinyUSB_ArduinoCore/tinyusb/src/class/midi/midi_device.c +++ b/cores/nRF5/TinyUSB/Adafruit_TinyUSB_ArduinoCore/tinyusb/src/class/midi/midi_device.c @@ -1,4 +1,4 @@ -/* +/* * The MIT License (MIT) * * Copyright (c) 2019 Ha Thach (tinyusb.org) @@ -26,24 +26,30 @@ #include "tusb_option.h" -#if (TUSB_OPT_DEVICE_ENABLED && CFG_TUD_MIDI) +#if (CFG_TUD_ENABLED && CFG_TUD_MIDI) //--------------------------------------------------------------------+ // INCLUDE //--------------------------------------------------------------------+ -#include "midi_device.h" -#include "class/audio/audio.h" +#include "device/usbd.h" #include "device/usbd_pvt.h" +#include "midi_device.h" + //--------------------------------------------------------------------+ // MACRO CONSTANT TYPEDEF //--------------------------------------------------------------------+ -typedef struct -{ +typedef struct { uint8_t itf_num; uint8_t ep_in; uint8_t ep_out; + // For Stream read()/write() API + // Messages are always 4 bytes long, queue them for reading and writing so the + // callers can use the Stream interface with single-byte read/write calls. + midi_driver_stream_t stream_write; + midi_driver_stream_t stream_read; + /*------------- From this point, data is not cleared by bus reset -------------*/ // FIFO tu_fifo_t rx_ff; @@ -55,209 +61,312 @@ typedef struct osal_mutex_def_t rx_ff_mutex; osal_mutex_def_t tx_ff_mutex; #endif - - // Messages are always 4 bytes long, queue them for reading and writing so the - // callers can use the Stream interface with single-byte read/write calls. - uint8_t write_buffer[4]; - uint8_t write_buffer_length; - uint8_t write_target_length; - - uint8_t read_buffer[4]; - uint8_t read_buffer_length; - uint8_t read_target_length; - - // Endpoint Transfer buffer - CFG_TUSB_MEM_ALIGN uint8_t epout_buf[CFG_TUD_MIDI_EP_BUFSIZE]; - CFG_TUSB_MEM_ALIGN uint8_t epin_buf[CFG_TUD_MIDI_EP_BUFSIZE]; - } midid_interface_t; #define ITF_MEM_RESET_SIZE offsetof(midid_interface_t, rx_ff) +// Endpoint Transfer buffer +CFG_TUD_MEM_SECTION static struct { + TUD_EPBUF_DEF(epin, CFG_TUD_MIDI_EP_BUFSIZE); + TUD_EPBUF_DEF(epout, CFG_TUD_MIDI_EP_BUFSIZE); +} _midid_epbuf[CFG_TUD_MIDI]; + //--------------------------------------------------------------------+ // INTERNAL OBJECT & FUNCTION DECLARATION //--------------------------------------------------------------------+ -CFG_TUSB_MEM_SECTION midid_interface_t _midid_itf[CFG_TUD_MIDI]; +static midid_interface_t _midid_itf[CFG_TUD_MIDI]; -bool tud_midi_n_mounted (uint8_t itf) -{ +bool tud_midi_n_mounted (uint8_t itf) { midid_interface_t* midi = &_midid_itf[itf]; return midi->ep_in && midi->ep_out; } +static void _prep_out_transaction(uint8_t idx) { + const uint8_t rhport = 0; + midid_interface_t* p_midi = &_midid_itf[idx]; + uint16_t available = tu_fifo_remaining(&p_midi->rx_ff); + + // Prepare for incoming data but only allow what we can store in the ring buffer. + // TODO Actually we can still carry out the transfer, keeping count of received bytes + // and slowly move it to the FIFO when read(). + // This pre-check reduces endpoint claiming + TU_VERIFY(available >= CFG_TUD_MIDI_EP_BUFSIZE, ); + + // claim endpoint + TU_VERIFY(usbd_edpt_claim(rhport, p_midi->ep_out), ); + + // fifo can be changed before endpoint is claimed + available = tu_fifo_remaining(&p_midi->rx_ff); + + if ( available >= CFG_TUD_MIDI_EP_BUFSIZE ) { + usbd_edpt_xfer(rhport, p_midi->ep_out, _midid_epbuf[idx].epout, CFG_TUD_MIDI_EP_BUFSIZE); + }else + { + // Release endpoint since we don't make any transfer + usbd_edpt_release(rhport, p_midi->ep_out); + } +} + + +//--------------------------------------------------------------------+ +// Weak stubs: invoked if no strong implementation is available +//--------------------------------------------------------------------+ +TU_ATTR_WEAK void tud_midi_rx_cb(uint8_t itf) { + (void) itf; +} + //--------------------------------------------------------------------+ // READ API //--------------------------------------------------------------------+ -uint32_t tud_midi_n_available(uint8_t itf, uint8_t jack_id) +uint32_t tud_midi_n_available(uint8_t itf, uint8_t cable_num) { - (void) jack_id; - return tu_fifo_count(&_midid_itf[itf].rx_ff); + (void) cable_num; + + midid_interface_t* midi = &_midid_itf[itf]; + const midi_driver_stream_t* stream = &midi->stream_read; + + // when using with packet API stream total & index are both zero + return tu_fifo_count(&midi->rx_ff) + (uint8_t) (stream->total - stream->index); } -uint32_t tud_midi_n_read(uint8_t itf, uint8_t jack_id, void* buffer, uint32_t bufsize) +uint32_t tud_midi_n_stream_read(uint8_t itf, uint8_t cable_num, void* buffer, uint32_t bufsize) { - (void) jack_id; + (void) cable_num; + TU_VERIFY(bufsize, 0); + + uint8_t* buf8 = (uint8_t*) buffer; + midid_interface_t* midi = &_midid_itf[itf]; + midi_driver_stream_t* stream = &midi->stream_read; + + uint32_t total_read = 0; + while( bufsize ) + { + // Get new packet from fifo, then set packet expected bytes + if ( stream->total == 0 ) + { + // return if there is no more data from fifo + if ( !tud_midi_n_packet_read(itf, stream->buffer) ) return total_read; - // Fill empty buffer - if (midi->read_buffer_length == 0) { - if (!tud_midi_n_receive(itf, midi->read_buffer)) return 0; - - uint8_t code_index = midi->read_buffer[0] & 0x0f; - // We always copy over the first byte. - uint8_t count = 1; - // Ignore subsequent bytes based on the code. - if (code_index != 0x5 && code_index != 0xf) { - count = 2; - if (code_index != 0x2 && code_index != 0x6 && code_index != 0xc && code_index != 0xd) { - count = 3; + uint8_t const code_index = stream->buffer[0] & 0x0f; + + // MIDI 1.0 Table 4-1: Code Index Number Classifications + switch(code_index) + { + case MIDI_CIN_MISC: + case MIDI_CIN_CABLE_EVENT: + // These are reserved and unused, possibly issue somewhere, skip this packet + return 0; + break; + + case MIDI_CIN_SYSEX_END_1BYTE: + case MIDI_CIN_1BYTE_DATA: + stream->total = 1; + break; + + case MIDI_CIN_SYSCOM_2BYTE : + case MIDI_CIN_SYSEX_END_2BYTE : + case MIDI_CIN_PROGRAM_CHANGE : + case MIDI_CIN_CHANNEL_PRESSURE : + stream->total = 2; + break; + + default: + stream->total = 3; + break; } } - midi->read_buffer_length = count; - } + // Copy data up to bufsize + uint8_t const count = (uint8_t) tu_min32(stream->total - stream->index, bufsize); - uint32_t n = midi->read_buffer_length - midi->read_target_length; - if (bufsize < n) n = bufsize; + // Skip the header (1st byte) in the buffer + TU_VERIFY(0 == tu_memcpy_s(buf8, bufsize, stream->buffer + 1 + stream->index, count)); - // Skip the header in the buffer - memcpy(buffer, midi->read_buffer + 1 + midi->read_target_length, n); - midi->read_target_length += n; + total_read += count; + stream->index += count; + buf8 += count; + bufsize -= count; - if (midi->read_target_length == midi->read_buffer_length) { - midi->read_buffer_length = 0; - midi->read_target_length = 0; + // complete current event packet, reset stream + if ( stream->total == stream->index ) + { + stream->index = 0; + stream->total = 0; + } } - return n; + return total_read; } -void tud_midi_n_read_flush (uint8_t itf, uint8_t jack_id) +bool tud_midi_n_packet_read (uint8_t itf, uint8_t packet[4]) { - (void) jack_id; - tu_fifo_clear(&_midid_itf[itf].rx_ff); -} - -bool tud_midi_n_receive (uint8_t itf, uint8_t packet[4]) -{ - return tu_fifo_read_n(&_midid_itf[itf].rx_ff, packet, 4); -} + midid_interface_t* midi = &_midid_itf[itf]; + TU_VERIFY(midi->ep_out); -void midi_rx_done_cb(midid_interface_t* midi, uint8_t const* buffer, uint32_t bufsize) { - tu_fifo_write_n(&midi->rx_ff, buffer, bufsize); + const uint32_t num_read = tu_fifo_read_n(&midi->rx_ff, packet, 4); + _prep_out_transaction(itf); + return (num_read == 4); } //--------------------------------------------------------------------+ // WRITE API //--------------------------------------------------------------------+ -static uint32_t write_flush(midid_interface_t* midi) -{ - // No data to send - if ( !tu_fifo_count(&midi->tx_ff) ) return 0; +static uint32_t write_flush(uint8_t idx) { + midid_interface_t* midi = &_midid_itf[idx]; + + if (!tu_fifo_count(&midi->tx_ff)) { + return 0; // No data to send + } - uint8_t const rhport = TUD_OPT_RHPORT; + const uint8_t rhport = 0; // skip if previous transfer not complete TU_VERIFY( usbd_edpt_claim(rhport, midi->ep_in), 0 ); - uint16_t count = tu_fifo_read_n(&midi->tx_ff, midi->epin_buf, CFG_TUD_MIDI_EP_BUFSIZE); - if (count > 0) - { - TU_ASSERT( usbd_edpt_xfer(rhport, midi->ep_in, midi->epin_buf, count), 0 ); + uint16_t count = tu_fifo_read_n(&midi->tx_ff, _midid_epbuf[idx].epin, CFG_TUD_MIDI_EP_BUFSIZE); + + if (count) { + TU_ASSERT( usbd_edpt_xfer(rhport, midi->ep_in, _midid_epbuf[idx].epin, count), 0 ); return count; - }else - { + }else { // Release endpoint since we don't make any transfer usbd_edpt_release(rhport, midi->ep_in); return 0; } } -uint32_t tud_midi_n_write(uint8_t itf, uint8_t jack_id, uint8_t const* buffer, uint32_t bufsize) +uint32_t tud_midi_n_stream_write(uint8_t itf, uint8_t cable_num, const uint8_t* buffer, uint32_t bufsize) { midid_interface_t* midi = &_midid_itf[itf]; - if (midi->itf_num == 0) { - return 0; - } + TU_VERIFY(midi->ep_in, 0); + + midi_driver_stream_t* stream = &midi->stream_write; uint32_t i = 0; - while (i < bufsize) { - uint8_t data = buffer[i]; - if (midi->write_buffer_length == 0) { - uint8_t msg = data >> 4; - midi->write_buffer[1] = data; - midi->write_buffer_length = 2; - // Check to see if we're still in a SysEx transmit. - if (midi->write_buffer[0] == 0x4) { - if (data == 0xf7) { - midi->write_buffer[0] = 0x5; - } else { - midi->write_buffer_length = 4; - } - } else if ((msg >= 0x8 && msg <= 0xB) || msg == 0xE) { - midi->write_buffer[0] = jack_id << 4 | msg; - midi->write_target_length = 4; - } else if (msg == 0xf) { - if (data == 0xf0) { - midi->write_buffer[0] = 0x4; - midi->write_target_length = 4; - } else if (data == 0xf1 || data == 0xf3) { - midi->write_buffer[0] = 0x2; - midi->write_target_length = 3; - } else if (data == 0xf2) { - midi->write_buffer[0] = 0x3; - midi->write_target_length = 4; - } else { - midi->write_buffer[0] = 0x5; - midi->write_target_length = 2; - } - } else { - // Pack individual bytes if we don't support packing them into words. - midi->write_buffer[0] = jack_id << 4 | 0xf; - midi->write_buffer[2] = 0; - midi->write_buffer[3] = 0; - midi->write_buffer_length = 2; - midi->write_target_length = 2; + while ( (i < bufsize) && (tu_fifo_remaining(&midi->tx_ff) >= 4) ) + { + const uint8_t data = buffer[i]; + i++; + + if ( stream->index == 0 ) + { + //------------- New event packet -------------// + const uint8_t msg = data >> 4; + + stream->index = 2; + stream->buffer[1] = data; + + // Check to see if we're still in a SysEx transmit. + if ( ((stream->buffer[0]) & 0xF) == MIDI_CIN_SYSEX_START ) + { + if ( data == MIDI_STATUS_SYSEX_END ) + { + stream->buffer[0] = (uint8_t) ((cable_num << 4) | MIDI_CIN_SYSEX_END_1BYTE); + stream->total = 2; } - } else { - midi->write_buffer[midi->write_buffer_length] = data; - midi->write_buffer_length += 1; - // See if this byte ends a SysEx. - if (midi->write_buffer[0] == 0x4 && data == 0xf7) { - midi->write_buffer[0] = 0x4 + (midi->write_buffer_length - 1); - midi->write_target_length = midi->write_buffer_length; + else + { + stream->total = 4; + } + } + else if ( (msg >= 0x8 && msg <= 0xB) || msg == 0xE ) + { + // Channel Voice Messages + stream->buffer[0] = (uint8_t) ((cable_num << 4) | msg); + stream->total = 4; + } + else if ( msg == 0xC || msg == 0xD) + { + // Channel Voice Messages, two-byte variants (Program Change and Channel Pressure) + stream->buffer[0] = (uint8_t) ((cable_num << 4) | msg); + stream->total = 3; + } + else if ( msg == 0xf ) + { + // System message + if ( data == MIDI_STATUS_SYSEX_START ) + { + stream->buffer[0] = MIDI_CIN_SYSEX_START; + stream->total = 4; } + else if ( data == MIDI_STATUS_SYSCOM_TIME_CODE_QUARTER_FRAME || data == MIDI_STATUS_SYSCOM_SONG_SELECT ) + { + stream->buffer[0] = MIDI_CIN_SYSCOM_2BYTE; + stream->total = 3; + } + else if ( data == MIDI_STATUS_SYSCOM_SONG_POSITION_POINTER ) + { + stream->buffer[0] = MIDI_CIN_SYSCOM_3BYTE; + stream->total = 4; + } + else + { + stream->buffer[0] = MIDI_CIN_SYSEX_END_1BYTE; + stream->total = 2; + } + stream->buffer[0] |= (uint8_t)(cable_num << 4); + } + else + { + // Pack individual bytes if we don't support packing them into words. + stream->buffer[0] = (uint8_t) (cable_num << 4 | 0xf); + stream->buffer[2] = 0; + stream->buffer[3] = 0; + stream->index = 2; + stream->total = 2; + } } + else + { + //------------- On-going (buffering) packet -------------// - if (midi->write_buffer_length == midi->write_target_length) { - uint16_t written = tu_fifo_write_n(&midi->tx_ff, midi->write_buffer, 4); - if (written < 4) { - TU_ASSERT( written == 0 ); - break; - } - midi->write_buffer_length = 0; + TU_ASSERT(stream->index < 4, i); + stream->buffer[stream->index] = data; + stream->index++; + + // See if this byte ends a SysEx. + if ( (stream->buffer[0] & 0xF) == MIDI_CIN_SYSEX_START && data == MIDI_STATUS_SYSEX_END ) + { + stream->buffer[0] = (uint8_t) ((cable_num << 4) | (MIDI_CIN_SYSEX_START + (stream->index - 1))); + stream->total = stream->index; + } + } + + // Send out packet + if ( stream->index == stream->total ) + { + // zeroes unused bytes + for (uint8_t idx = stream->total; idx < 4; idx++) { + stream->buffer[idx] = 0; + } + + const uint16_t count = tu_fifo_write_n(&midi->tx_ff, stream->buffer, 4); + + // complete current event packet, reset stream + stream->index = stream->total = 0; + + // FIFO overflown, since we already check fifo remaining. It is probably race condition + TU_ASSERT(count == 4, i); } - i++; } - write_flush(midi); + write_flush(itf); return i; } -bool tud_midi_n_send (uint8_t itf, uint8_t const packet[4]) -{ +bool tud_midi_n_packet_write (uint8_t itf, const uint8_t packet[4]) { midid_interface_t* midi = &_midid_itf[itf]; - if (midi->itf_num == 0) { - return 0; - } + TU_VERIFY(midi->ep_in); - if (tu_fifo_remaining(&midi->tx_ff) < 4) + if (tu_fifo_remaining(&midi->tx_ff) < 4) { return false; + } tu_fifo_write_n(&midi->tx_ff, packet, 4); - write_flush(midi); + write_flush(itf); return true; } @@ -265,25 +374,49 @@ bool tud_midi_n_send (uint8_t itf, uint8_t const packet[4]) //--------------------------------------------------------------------+ // USBD Driver API //--------------------------------------------------------------------+ -void midid_init(void) -{ +void midid_init(void) { tu_memclr(_midid_itf, sizeof(_midid_itf)); - for(uint8_t i=0; irx_ff, midi->rx_ff_buf, CFG_TUD_MIDI_RX_BUFSIZE, 1, true); - tu_fifo_config(&midi->tx_ff, midi->tx_ff_buf, CFG_TUD_MIDI_TX_BUFSIZE, 1, true); + tu_fifo_config(&midi->rx_ff, midi->rx_ff_buf, CFG_TUD_MIDI_RX_BUFSIZE, 1, false); // true, true + tu_fifo_config(&midi->tx_ff, midi->tx_ff_buf, CFG_TUD_MIDI_TX_BUFSIZE, 1, false); // OBVS. #if CFG_FIFO_MUTEX - tu_fifo_config_mutex(&midi->rx_ff, osal_mutex_create(&midi->rx_ff_mutex)); - tu_fifo_config_mutex(&midi->tx_ff, osal_mutex_create(&midi->tx_ff_mutex)); + osal_mutex_t mutex_rd = osal_mutex_create(&midi->rx_ff_mutex); + osal_mutex_t mutex_wr = osal_mutex_create(&midi->tx_ff_mutex); + TU_ASSERT(mutex_wr != NULL && mutex_wr != NULL, ); + + tu_fifo_config_mutex(&midi->rx_ff, NULL, mutex_rd); + tu_fifo_config_mutex(&midi->tx_ff, mutex_wr, NULL); #endif } } +bool midid_deinit(void) { + #if CFG_FIFO_MUTEX + for(uint8_t i=0; irx_ff.mutex_rd; + osal_mutex_t mutex_wr = midi->tx_ff.mutex_wr; + + if (mutex_rd) { + osal_mutex_delete(mutex_rd); + tu_fifo_config_mutex(&midi->rx_ff, NULL, NULL); + } + + if (mutex_wr) { + osal_mutex_delete(mutex_wr); + tu_fifo_config_mutex(&midi->tx_ff, NULL, NULL); + } + } + #endif + + return true; +} + void midid_reset(uint8_t rhport) { (void) rhport; @@ -297,43 +430,44 @@ void midid_reset(uint8_t rhport) } } -uint16_t midid_open(uint8_t rhport, tusb_desc_interface_t const * desc_itf, uint16_t max_len) -{ - // 1st Interface is Audio Control v1 - TU_VERIFY(TUSB_CLASS_AUDIO == desc_itf->bInterfaceClass && - AUDIO_SUBCLASS_CONTROL == desc_itf->bInterfaceSubClass && - AUDIO_PROTOCOL_V1 == desc_itf->bInterfaceProtocol, 0); - - uint16_t drv_len = tu_desc_len(desc_itf); - uint8_t const * p_desc = tu_desc_next(desc_itf); - - // Skip Class Specific descriptors - while ( TUSB_DESC_CS_INTERFACE == tu_desc_type(p_desc) && drv_len <= max_len ) - { - drv_len += tu_desc_len(p_desc); - p_desc = tu_desc_next(p_desc); +uint16_t midid_open(uint8_t rhport, const tusb_desc_interface_t* desc_itf, uint16_t max_len) { + uint16_t drv_len = 0; + uint8_t const * p_desc = (uint8_t const *)desc_itf; + + // 1st Interface is Audio Control v1 (optional) + if (TUSB_CLASS_AUDIO == desc_itf->bInterfaceClass && + AUDIO_SUBCLASS_CONTROL == desc_itf->bInterfaceSubClass && + AUDIO_FUNC_PROTOCOL_CODE_UNDEF == desc_itf->bInterfaceProtocol) { + drv_len = tu_desc_len(desc_itf); + p_desc = tu_desc_next(desc_itf); + // Skip Class Specific descriptors + while (TUSB_DESC_CS_INTERFACE == tu_desc_type(p_desc) && drv_len <= max_len) { + drv_len += tu_desc_len(p_desc); + p_desc = tu_desc_next(p_desc); + } } // 2nd Interface is MIDI Streaming TU_VERIFY(TUSB_DESC_INTERFACE == tu_desc_type(p_desc), 0); - tusb_desc_interface_t const * desc_midi = (tusb_desc_interface_t const *) p_desc; + const tusb_desc_interface_t* desc_midi = (const tusb_desc_interface_t*) p_desc; - TU_VERIFY(TUSB_CLASS_AUDIO == desc_midi->bInterfaceClass && - AUDIO_SUBCLASS_MIDI_STREAMING == desc_midi->bInterfaceSubClass && - AUDIO_PROTOCOL_V1 == desc_midi->bInterfaceProtocol, 0); + TU_VERIFY(TUSB_CLASS_AUDIO == desc_midi->bInterfaceClass && + AUDIO_SUBCLASS_MIDI_STREAMING == desc_midi->bInterfaceSubClass && + AUDIO_FUNC_PROTOCOL_CODE_UNDEF == desc_midi->bInterfaceProtocol, 0); // Find available interface midid_interface_t * p_midi = NULL; - for(uint8_t i=0; iitf_num = desc_midi->bInterfaceNumber; + (void) p_midi->itf_num; // next descriptor drv_len += tu_desc_len(p_desc); @@ -345,8 +479,8 @@ uint16_t midid_open(uint8_t rhport, tusb_desc_interface_t const * desc_itf, uint { if ( TUSB_DESC_ENDPOINT == tu_desc_type(p_desc) ) { - TU_ASSERT(usbd_edpt_open(rhport, (tusb_desc_endpoint_t const *) p_desc), 0); - uint8_t ep_addr = ((tusb_desc_endpoint_t const *) p_desc)->bEndpointAddress; + TU_ASSERT(usbd_edpt_open(rhport, (const tusb_desc_endpoint_t*) p_desc), 0); + uint8_t ep_addr = ((const tusb_desc_endpoint_t*) p_desc)->bEndpointAddress; if (tu_edpt_dir(ep_addr) == TUSB_DIR_IN) { @@ -355,6 +489,7 @@ uint16_t midid_open(uint8_t rhport, tusb_desc_interface_t const * desc_itf, uint p_midi->ep_out = ep_addr; } + // Class Specific MIDI Stream endpoint descriptor drv_len += tu_desc_len(p_desc); p_desc = tu_desc_next(p_desc); @@ -366,69 +501,53 @@ uint16_t midid_open(uint8_t rhport, tusb_desc_interface_t const * desc_itf, uint } // Prepare for incoming data - if ( !usbd_edpt_xfer(rhport, p_midi->ep_out, p_midi->epout_buf, CFG_TUD_MIDI_EP_BUFSIZE) ) - { - TU_LOG1_FAILED(); - TU_BREAKPOINT(); - } + _prep_out_transaction(idx); return drv_len; } -bool midid_control_complete(uint8_t rhport, tusb_control_request_t const * p_request) -{ - (void) rhport; - (void) p_request; - return true; -} - -bool midid_control_request(uint8_t rhport, tusb_control_request_t const * p_request) -{ - (void) rhport; - (void) p_request; - - // driver doesn't support any request yet - return false; +// Invoked when a control transfer occurred on an interface of this class +// Driver response accordingly to the request and the transfer stage (setup/data/ack) +// return false to stall control endpoint (e.g unsupported request) +bool midid_control_xfer_cb(uint8_t rhport, uint8_t stage, const tusb_control_request_t* request) { + (void) rhport; (void) stage; (void) request; + return false; // driver doesn't support any request yet } bool midid_xfer_cb(uint8_t rhport, uint8_t ep_addr, xfer_result_t result, uint32_t xferred_bytes) { (void) result; + (void) rhport; - uint8_t itf; + uint8_t idx; midid_interface_t* p_midi; // Identify which interface to use - for (itf = 0; itf < CFG_TUD_MIDI; itf++) - { - p_midi = &_midid_itf[itf]; - if ( ( ep_addr == p_midi->ep_out ) || ( ep_addr == p_midi->ep_in ) ) break; + for (idx = 0; idx < CFG_TUD_MIDI; idx++) { + p_midi = &_midid_itf[idx]; + if ((ep_addr == p_midi->ep_out) || (ep_addr == p_midi->ep_in)) { + break; + } } - TU_ASSERT(itf < CFG_TUD_MIDI); + TU_ASSERT(idx < CFG_TUD_MIDI); // receive new data - if ( ep_addr == p_midi->ep_out ) - { - tu_fifo_write_n(&p_midi->rx_ff, p_midi->epout_buf, xferred_bytes); + if (ep_addr == p_midi->ep_out) { + tu_fifo_write_n(&p_midi->rx_ff, _midid_epbuf[idx].epout, (uint16_t)xferred_bytes); // invoke receive callback if available - if (tud_midi_rx_cb) tud_midi_rx_cb(itf); + tud_midi_rx_cb(idx); // prepare for next // TODO for now ep_out is not used by public API therefore there is no race condition, // and does not need to claim like ep_in - TU_ASSERT(usbd_edpt_xfer(rhport, p_midi->ep_out, p_midi->epout_buf, CFG_TUD_MIDI_EP_BUFSIZE), false); - } - else if ( ep_addr == p_midi->ep_in ) - { - if (0 == write_flush(p_midi)) - { + _prep_out_transaction(idx); + } else if (ep_addr == p_midi->ep_in) { + if (0 == write_flush(idx)) { // If there is no data left, a ZLP should be sent if // xferred_bytes is multiple of EP size and not zero - if ( !tu_fifo_count(&p_midi->tx_ff) && xferred_bytes && (0 == (xferred_bytes % CFG_TUD_MIDI_EP_BUFSIZE)) ) - { - if ( usbd_edpt_claim(rhport, p_midi->ep_in) ) - { + if (!tu_fifo_count(&p_midi->tx_ff) && xferred_bytes && (0 == (xferred_bytes % CFG_TUD_MIDI_EP_BUFSIZE))) { + if (usbd_edpt_claim(rhport, p_midi->ep_in)) { usbd_edpt_xfer(rhport, p_midi->ep_in, NULL, 0); } } diff --git a/cores/nRF5/TinyUSB/Adafruit_TinyUSB_ArduinoCore/tinyusb/src/class/midi/midi_device.h b/cores/nRF5/TinyUSB/Adafruit_TinyUSB_ArduinoCore/tinyusb/src/class/midi/midi_device.h index b8fb55c..c2c6e98 100644 --- a/cores/nRF5/TinyUSB/Adafruit_TinyUSB_ArduinoCore/tinyusb/src/class/midi/midi_device.h +++ b/cores/nRF5/TinyUSB/Adafruit_TinyUSB_ArduinoCore/tinyusb/src/class/midi/midi_device.h @@ -1,4 +1,4 @@ -/* +/* * The MIT License (MIT) * * Copyright (c) 2019 Ha Thach (tinyusb.org) @@ -27,9 +27,6 @@ #ifndef _TUSB_MIDI_DEVICE_H_ #define _TUSB_MIDI_DEVICE_H_ -#include "common/tusb_common.h" -#include "device/usbd.h" - #include "class/audio/audio.h" #include "midi.h" @@ -59,95 +56,113 @@ // Application API (Multiple Interfaces) // CFG_TUD_MIDI > 1 //--------------------------------------------------------------------+ -bool tud_midi_n_mounted (uint8_t itf); -uint32_t tud_midi_n_available (uint8_t itf, uint8_t jack_id); -uint32_t tud_midi_n_read (uint8_t itf, uint8_t jack_id, void* buffer, uint32_t bufsize); -void tud_midi_n_read_flush (uint8_t itf, uint8_t jack_id); -uint32_t tud_midi_n_write (uint8_t itf, uint8_t jack_id, uint8_t const* buffer, uint32_t bufsize); -static inline -uint32_t tud_midi_n_write24 (uint8_t itf, uint8_t jack_id, uint8_t b1, uint8_t b2, uint8_t b3); +// Check if midi interface is mounted +bool tud_midi_n_mounted (uint8_t itf); + +// Get the number of bytes available for reading +uint32_t tud_midi_n_available (uint8_t itf, uint8_t cable_num); + +// Read byte stream (legacy) +uint32_t tud_midi_n_stream_read (uint8_t itf, uint8_t cable_num, void* buffer, uint32_t bufsize); + +// Write byte Stream (legacy) +uint32_t tud_midi_n_stream_write (uint8_t itf, uint8_t cable_num, uint8_t const* buffer, uint32_t bufsize); -bool tud_midi_n_receive (uint8_t itf, uint8_t packet[4]); -bool tud_midi_n_send (uint8_t itf, uint8_t const packet[4]); +// Read event packet (4 bytes) +bool tud_midi_n_packet_read (uint8_t itf, uint8_t packet[4]); + +// Write event packet (4 bytes) +bool tud_midi_n_packet_write (uint8_t itf, uint8_t const packet[4]); //--------------------------------------------------------------------+ // Application API (Single Interface) //--------------------------------------------------------------------+ -static inline bool tud_midi_mounted (void); -static inline uint32_t tud_midi_available (void); -static inline uint32_t tud_midi_read (void* buffer, uint32_t bufsize); -static inline void tud_midi_read_flush (void); -static inline uint32_t tud_midi_write (uint8_t jack_id, uint8_t const* buffer, uint32_t bufsize); -static inline uint32_t tudi_midi_write24 (uint8_t jack_id, uint8_t b1, uint8_t b2, uint8_t b3); -static inline bool tud_midi_receive (uint8_t packet[4]); -static inline bool tud_midi_send (uint8_t const packet[4]); +static inline bool tud_midi_mounted (void); +static inline uint32_t tud_midi_available (void); -//--------------------------------------------------------------------+ -// Application Callback API (weak is optional) -//--------------------------------------------------------------------+ -TU_ATTR_WEAK void tud_midi_rx_cb(uint8_t itf); +static inline uint32_t tud_midi_stream_read (void* buffer, uint32_t bufsize); +static inline uint32_t tud_midi_stream_write (uint8_t cable_num, uint8_t const* buffer, uint32_t bufsize); -//--------------------------------------------------------------------+ -// Inline Functions -//--------------------------------------------------------------------+ +static inline bool tud_midi_packet_read (uint8_t packet[4]); +static inline bool tud_midi_packet_write (uint8_t const packet[4]); -static inline uint32_t tud_midi_n_write24 (uint8_t itf, uint8_t jack_id, uint8_t b1, uint8_t b2, uint8_t b3) +//------------- Deprecated API name -------------// +// TODO remove after 0.10.0 release + +TU_ATTR_DEPRECATED("tud_midi_read() is renamed to tud_midi_stream_read()") +static inline uint32_t tud_midi_read (void* buffer, uint32_t bufsize) { - uint8_t msg[3] = { b1, b2, b3 }; - return tud_midi_n_write(itf, jack_id, msg, 3); + return tud_midi_stream_read(buffer, bufsize); } -static inline bool tud_midi_mounted (void) +TU_ATTR_DEPRECATED("tud_midi_write() is renamed to tud_midi_stream_write()") +static inline uint32_t tud_midi_write(uint8_t cable_num, uint8_t const* buffer, uint32_t bufsize) { - return tud_midi_n_mounted(0); + return tud_midi_stream_write(cable_num, buffer, bufsize); } -static inline uint32_t tud_midi_available (void) + +TU_ATTR_DEPRECATED("tud_midi_send() is renamed to tud_midi_packet_write()") +static inline bool tud_midi_send(uint8_t packet[4]) { - return tud_midi_n_available(0, 0); + return tud_midi_packet_write(packet); } -static inline uint32_t tud_midi_read (void* buffer, uint32_t bufsize) +TU_ATTR_DEPRECATED("tud_midi_receive() is renamed to tud_midi_packet_read()") +static inline bool tud_midi_receive(uint8_t packet[4]) +{ + return tud_midi_packet_read(packet); +} + +//--------------------------------------------------------------------+ +// Application Callback API (optional) +//--------------------------------------------------------------------+ +void tud_midi_rx_cb(uint8_t itf); + +//--------------------------------------------------------------------+ +// Inline Functions +//--------------------------------------------------------------------+ + +static inline bool tud_midi_mounted (void) { - return tud_midi_n_read(0, 0, buffer, bufsize); + return tud_midi_n_mounted(0); } -static inline void tud_midi_read_flush (void) +static inline uint32_t tud_midi_available (void) { - tud_midi_n_read_flush(0, 0); + return tud_midi_n_available(0, 0); } -static inline uint32_t tud_midi_write (uint8_t jack_id, uint8_t const* buffer, uint32_t bufsize) +static inline uint32_t tud_midi_stream_read (void* buffer, uint32_t bufsize) { - return tud_midi_n_write(0, jack_id, buffer, bufsize); + return tud_midi_n_stream_read(0, 0, buffer, bufsize); } -static inline uint32_t tudi_midi_write24 (uint8_t jack_id, uint8_t b1, uint8_t b2, uint8_t b3) +static inline uint32_t tud_midi_stream_write (uint8_t cable_num, uint8_t const* buffer, uint32_t bufsize) { - uint8_t msg[3] = { b1, b2, b3 }; - return tud_midi_write(jack_id, msg, 3); + return tud_midi_n_stream_write(0, cable_num, buffer, bufsize); } -static inline bool tud_midi_receive (uint8_t packet[4]) +static inline bool tud_midi_packet_read (uint8_t packet[4]) { - return tud_midi_n_receive(0, packet); + return tud_midi_n_packet_read(0, packet); } -static inline bool tud_midi_send (uint8_t const packet[4]) +static inline bool tud_midi_packet_write (uint8_t const packet[4]) { - return tud_midi_n_send(0, packet); + return tud_midi_n_packet_write(0, packet); } //--------------------------------------------------------------------+ // Internal Class Driver API //--------------------------------------------------------------------+ -void midid_init (void); -void midid_reset (uint8_t rhport); -uint16_t midid_open (uint8_t rhport, tusb_desc_interface_t const * itf_desc, uint16_t max_len); -bool midid_control_request (uint8_t rhport, tusb_control_request_t const * request); -bool midid_control_complete (uint8_t rhport, tusb_control_request_t const * request); -bool midid_xfer_cb (uint8_t rhport, uint8_t edpt_addr, xfer_result_t result, uint32_t xferred_bytes); +void midid_init (void); +bool midid_deinit (void); +void midid_reset (uint8_t rhport); +uint16_t midid_open (uint8_t rhport, tusb_desc_interface_t const * itf_desc, uint16_t max_len); +bool midid_control_xfer_cb (uint8_t rhport, uint8_t stage, tusb_control_request_t const * request); +bool midid_xfer_cb (uint8_t rhport, uint8_t edpt_addr, xfer_result_t result, uint32_t xferred_bytes); #ifdef __cplusplus } diff --git a/cores/nRF5/TinyUSB/Adafruit_TinyUSB_ArduinoCore/tinyusb/src/class/midi/midi_host.c b/cores/nRF5/TinyUSB/Adafruit_TinyUSB_ArduinoCore/tinyusb/src/class/midi/midi_host.c new file mode 100644 index 0000000..e6ace31 --- /dev/null +++ b/cores/nRF5/TinyUSB/Adafruit_TinyUSB_ArduinoCore/tinyusb/src/class/midi/midi_host.c @@ -0,0 +1,649 @@ +/* + * 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. + */ + +#include "tusb_option.h" + +#if (CFG_TUH_ENABLED && CFG_TUH_MIDI) + +#include "host/usbh.h" +#include "host/usbh_pvt.h" + +#include "midi_host.h" + +// Level where CFG_TUSB_DEBUG must be at least for this driver is logged +#ifndef CFG_TUH_MIDI_LOG_LEVEL + #define CFG_TUH_MIDI_LOG_LEVEL CFG_TUH_LOG_LEVEL +#endif + +#define TU_LOG_DRV(...) TU_LOG(CFG_TUH_MIDI_LOG_LEVEL, __VA_ARGS__) + +//--------------------------------------------------------------------+ +// Weak stubs: invoked if no strong implementation is available +//--------------------------------------------------------------------+ +TU_ATTR_WEAK void tuh_midi_descriptor_cb(uint8_t idx, const tuh_midi_descriptor_cb_t * desc_cb_data) { (void) idx; (void) desc_cb_data; } +TU_ATTR_WEAK void tuh_midi_mount_cb(uint8_t idx, const tuh_midi_mount_cb_t* mount_cb_data) { (void) idx; (void) mount_cb_data; } +TU_ATTR_WEAK void tuh_midi_umount_cb(uint8_t idx) { (void) idx; } +TU_ATTR_WEAK void tuh_midi_rx_cb(uint8_t idx, uint32_t xferred_bytes) { (void) idx; (void) xferred_bytes; } +TU_ATTR_WEAK void tuh_midi_tx_cb(uint8_t idx, uint32_t xferred_bytes) { (void) idx; (void) xferred_bytes; } + +//--------------------------------------------------------------------+ +// MACRO CONSTANT TYPEDEF +//--------------------------------------------------------------------+ + +typedef struct { + uint8_t daddr; + uint8_t bInterfaceNumber; // interface number of MIDI streaming + uint8_t iInterface; + uint8_t itf_count; // number of interface including Audio Control + MIDI streaming + + uint8_t ep_in; // IN endpoint address + uint8_t ep_out; // OUT endpoint address + + uint8_t rx_cable_count; // IN endpoint CS descriptor bNumEmbMIDIJack value + uint8_t tx_cable_count; // OUT endpoint CS descriptor bNumEmbMIDIJack value + + #if CFG_TUH_MIDI_STREAM_API + // For Stream read()/write() API + // Messages are always 4 bytes long, queue them for reading and writing so the + // callers can use the Stream interface with single-byte read/write calls. + midi_driver_stream_t stream_write; + midi_driver_stream_t stream_read; + #endif + + // Endpoint stream + struct { + tu_edpt_stream_t tx; + tu_edpt_stream_t rx; + + uint8_t rx_ff_buf[CFG_TUH_MIDI_RX_BUFSIZE]; + uint8_t tx_ff_buf[CFG_TUH_MIDI_TX_BUFSIZE]; + } ep_stream; + + bool mounted; +}midih_interface_t; + +typedef struct { + TUH_EPBUF_DEF(tx, TUH_EPSIZE_BULK_MPS); + TUH_EPBUF_DEF(rx, TUH_EPSIZE_BULK_MPS); +} midih_epbuf_t; + +static midih_interface_t _midi_host[CFG_TUH_MIDI]; +CFG_TUH_MEM_SECTION static midih_epbuf_t _midi_epbuf[CFG_TUH_MIDI]; + +//--------------------------------------------------------------------+ +// Helper +//--------------------------------------------------------------------+ +TU_ATTR_ALWAYS_INLINE static inline uint8_t find_new_midi_index(void) { + for (uint8_t idx = 0; idx < CFG_TUH_MIDI; idx++) { + if (_midi_host[idx].daddr == 0) { + return idx; + } + } + return TUSB_INDEX_INVALID_8; +} + +static inline uint8_t get_idx_by_ep_addr(uint8_t daddr, uint8_t ep_addr) { + for (uint8_t idx = 0; idx < CFG_TUH_MIDI; idx++) { + const midih_interface_t *p_midi = &_midi_host[idx]; + if ((p_midi->daddr == daddr) && + (ep_addr == p_midi->ep_stream.rx.ep_addr || ep_addr == p_midi->ep_stream.tx.ep_addr)) { + return idx; + } + } + return TUSB_INDEX_INVALID_8; +} + +//--------------------------------------------------------------------+ +// USBH API +//--------------------------------------------------------------------+ +bool midih_init(void) { + tu_memclr(&_midi_host, sizeof(_midi_host)); + for (int inst = 0; inst < CFG_TUH_MIDI; inst++) { + midih_interface_t *p_midi_host = &_midi_host[inst]; + tu_edpt_stream_init(&p_midi_host->ep_stream.rx, true, false, false, + p_midi_host->ep_stream.rx_ff_buf, CFG_TUH_MIDI_RX_BUFSIZE, _midi_epbuf->rx, TUH_EPSIZE_BULK_MPS); + tu_edpt_stream_init(&p_midi_host->ep_stream.tx, true, true, false, + p_midi_host->ep_stream.tx_ff_buf, CFG_TUH_MIDI_TX_BUFSIZE, _midi_epbuf->tx, TUH_EPSIZE_BULK_MPS); + } + return true; +} + +bool midih_deinit(void) { + for (size_t i = 0; i < CFG_TUH_MIDI; i++) { + midih_interface_t* p_midi = &_midi_host[i]; + tu_edpt_stream_deinit(&p_midi->ep_stream.rx); + tu_edpt_stream_deinit(&p_midi->ep_stream.tx); + } + return true; +} + +void midih_close(uint8_t daddr) { + for (uint8_t idx = 0; idx < CFG_TUH_MIDI; idx++) { + midih_interface_t* p_midi = &_midi_host[idx]; + if (p_midi->daddr == daddr) { + TU_LOG_DRV(" MIDI close addr = %u index = %u\r\n", daddr, idx); + tuh_midi_umount_cb(idx); + + p_midi->ep_in = 0; + p_midi->ep_out = 0; + p_midi->bInterfaceNumber = 0; + p_midi->rx_cable_count = 0; + p_midi->tx_cable_count = 0; + p_midi->daddr = 0; + p_midi->mounted = false; +#if CFG_TUH_MIDI_STREAM_API + tu_memclr(&p_midi->stream_read, sizeof(p_midi->stream_read)); + tu_memclr(&p_midi->stream_write, sizeof(p_midi->stream_write)); +#endif + tu_edpt_stream_close(&p_midi->ep_stream.rx); + tu_edpt_stream_close(&p_midi->ep_stream.tx); + } + } +} + +bool midih_xfer_cb(uint8_t dev_addr, uint8_t ep_addr, xfer_result_t result, uint32_t xferred_bytes) { + (void) result; + const uint8_t idx = get_idx_by_ep_addr(dev_addr, ep_addr); + TU_VERIFY(idx < CFG_TUH_MIDI); + midih_interface_t *p_midi = &_midi_host[idx]; + + if (ep_addr == p_midi->ep_stream.rx.ep_addr) { + // receive new data, put it into FIFO and invoke callback if available + // Note: some devices send back all zero packets even if there is no data ready + if (xferred_bytes && !tu_mem_is_zero(p_midi->ep_stream.rx.ep_buf, xferred_bytes)) { + tu_edpt_stream_read_xfer_complete(&p_midi->ep_stream.rx, xferred_bytes); + tuh_midi_rx_cb(idx, xferred_bytes); + } + + tu_edpt_stream_read_xfer(dev_addr, &p_midi->ep_stream.rx); // prepare for next transfer + } else if (ep_addr == p_midi->ep_stream.tx.ep_addr) { + tuh_midi_tx_cb(idx, xferred_bytes); + + if (0 == tu_edpt_stream_write_xfer(dev_addr, &p_midi->ep_stream.tx)) { + // If there is no data left, a ZLP should be sent if + // xferred_bytes is multiple of EP size and not zero + tu_edpt_stream_write_zlp_if_needed(dev_addr, &p_midi->ep_stream.tx, xferred_bytes); + } + } + + return true; +} + +//--------------------------------------------------------------------+ +// Enumeration +//--------------------------------------------------------------------+ +bool midih_open(uint8_t rhport, uint8_t dev_addr, tusb_desc_interface_t const *desc_itf, uint16_t max_len) { + (void) rhport; + + TU_VERIFY(TUSB_CLASS_AUDIO == desc_itf->bInterfaceClass); + const uint8_t *p_end = ((const uint8_t *) desc_itf) + max_len; + const uint8_t *p_desc = (const uint8_t *) desc_itf; + + const uint8_t idx = find_new_midi_index(); + TU_VERIFY(idx < CFG_TUH_MIDI); + midih_interface_t *p_midi = &_midi_host[idx]; + p_midi->itf_count = 0; + + tuh_midi_descriptor_cb_t desc_cb = { 0 }; + desc_cb.jack_num = 0; + + // There can be just a MIDI or an Audio + MIDI interface + // - If there is Audio Control Interface + Audio Header descriptor, then skip it. + // - If there is an Audio Control Interface + Audio Streaming Interface, then ignore the Audio Streaming Interface. + // Future: + // Note that if this driver is used with an USB Audio Streaming host driver, + // then call that driver first. If the MIDI interface comes before the + // audio streaming interface, then the audio driver will have to call this + // driver after parsing the audio control interface and then resume parsing + // the streaming audio interface. + if (AUDIO_SUBCLASS_CONTROL == desc_itf->bInterfaceSubClass) { + TU_VERIFY(max_len > 2*sizeof(tusb_desc_interface_t) + sizeof(audio_desc_cs_ac_interface_t)); + + p_desc = tu_desc_next(p_desc); + TU_VERIFY(tu_desc_type(p_desc) == TUSB_DESC_CS_INTERFACE && + tu_desc_subtype(p_desc) == AUDIO_CS_AC_INTERFACE_HEADER); + desc_cb.desc_audio_control = desc_itf; + + p_desc = tu_desc_next(p_desc); + desc_itf = (const tusb_desc_interface_t *)p_desc; + p_midi->itf_count = 1; + // skip non-interface and non-midi streaming descriptors + while (tu_desc_in_bounds(p_desc, p_end) && + (desc_itf->bDescriptorType != TUSB_DESC_INTERFACE || (desc_itf->bInterfaceClass == TUSB_CLASS_AUDIO && desc_itf->bInterfaceSubClass != AUDIO_SUBCLASS_MIDI_STREAMING))) { + if (desc_itf->bDescriptorType == TUSB_DESC_INTERFACE && desc_itf->bAlternateSetting == 0) { + p_midi->itf_count++; + } + p_desc = tu_desc_next(p_desc); + desc_itf = (tusb_desc_interface_t const *)p_desc; + } + TU_VERIFY(p_desc < p_end); // TODO: If MIDI interface comes after Audio Streaming, then max_len did not include the MIDI interface descriptor + TU_VERIFY(TUSB_CLASS_AUDIO == desc_itf->bInterfaceClass); + } + TU_VERIFY(AUDIO_SUBCLASS_MIDI_STREAMING == desc_itf->bInterfaceSubClass); + + TU_LOG_DRV("MIDI opening Interface %u (addr = %u)\r\n", desc_itf->bInterfaceNumber, dev_addr); + p_midi->bInterfaceNumber = desc_itf->bInterfaceNumber; + p_midi->iInterface = desc_itf->iInterface; + p_midi->itf_count++; + desc_cb.desc_midi = desc_itf; + + p_desc = tu_desc_next(p_desc); // next to CS Header + + bool found_new_interface = false; + while (tu_desc_in_bounds(p_desc, p_end) && !found_new_interface) { + switch (tu_desc_type(p_desc)) { + case TUSB_DESC_INTERFACE: + found_new_interface = true; + break; + + case TUSB_DESC_CS_INTERFACE: + switch (tu_desc_subtype(p_desc)) { + case MIDI_CS_INTERFACE_HEADER: + TU_LOG_DRV(" Interface Header descriptor\r\n"); + desc_cb.desc_header = p_desc; + break; + + case MIDI_CS_INTERFACE_IN_JACK: + case MIDI_CS_INTERFACE_OUT_JACK: { + TU_LOG_DRV(" Jack %s %s descriptor \r\n", + tu_desc_subtype(p_desc) == MIDI_CS_INTERFACE_IN_JACK ? "IN" : "OUT", + p_desc[3] == MIDI_JACK_EXTERNAL ? "External" : "Embedded"); + if (desc_cb.jack_num < TU_ARRAY_SIZE(desc_cb.desc_jack)) { + desc_cb.desc_jack[desc_cb.jack_num++] = p_desc; + } + break; + } + + case MIDI_CS_INTERFACE_ELEMENT: + TU_LOG_DRV(" Element descriptor\r\n"); + desc_cb.desc_element = p_desc; + break; + + default: + TU_LOG_DRV(" Unknown CS Interface sub-type %u\r\n", tu_desc_subtype(p_desc)); + break; + } + break; + + case TUSB_DESC_ENDPOINT: { + const tusb_desc_endpoint_t *p_ep = (const tusb_desc_endpoint_t *) p_desc; + p_desc = tu_desc_next(p_desc); // next to CS endpoint + TU_VERIFY(p_desc < p_end && tu_desc_next(p_desc) <= p_end); + const midi_desc_cs_endpoint_t *p_csep = (const midi_desc_cs_endpoint_t *) p_desc; + + TU_LOG_DRV(" Endpoint and CS_Endpoint descriptor %02x\r\n", p_ep->bEndpointAddress); + if (tu_edpt_dir(p_ep->bEndpointAddress) == TUSB_DIR_OUT) { + p_midi->ep_out = p_ep->bEndpointAddress; + p_midi->tx_cable_count = p_csep->bNumEmbMIDIJack; + desc_cb.desc_epout = p_ep; + + TU_ASSERT(tuh_edpt_open(dev_addr, p_ep)); + tu_edpt_stream_open(&p_midi->ep_stream.tx, p_ep); + } else { + p_midi->ep_in = p_ep->bEndpointAddress; + p_midi->rx_cable_count = p_csep->bNumEmbMIDIJack; + desc_cb.desc_epin = p_ep; + + TU_ASSERT(tuh_edpt_open(dev_addr, p_ep)); + tu_edpt_stream_open(&p_midi->ep_stream.rx, p_ep); + } + break; + } + + default: break; // skip unknown descriptor + } + p_desc = tu_desc_next(p_desc); + } + desc_cb.desc_midi_total_len = (uint16_t) ((uintptr_t)p_desc - (uintptr_t) desc_itf); + + p_midi->daddr = dev_addr; + tuh_midi_descriptor_cb(idx, &desc_cb); + + return true; +} + +bool midih_set_config(uint8_t dev_addr, uint8_t itf_num) { + uint8_t idx = tuh_midi_itf_get_index(dev_addr, itf_num); + TU_ASSERT(idx < CFG_TUH_MIDI); + midih_interface_t *p_midi = &_midi_host[idx]; + p_midi->mounted = true; + + const tuh_midi_mount_cb_t mount_cb_data = { + .daddr = dev_addr, + .bInterfaceNumber = itf_num, + .rx_cable_count = p_midi->rx_cable_count, + .tx_cable_count = p_midi->tx_cable_count, + }; + tuh_midi_mount_cb(idx, &mount_cb_data); + + tu_edpt_stream_read_xfer(dev_addr, &p_midi->ep_stream.rx); // prepare for incoming data + + // No special config things to do for MIDI + usbh_driver_set_config_complete(dev_addr, p_midi->bInterfaceNumber); + return true; +} + +//--------------------------------------------------------------------+ +// API +//--------------------------------------------------------------------+ +bool tuh_midi_mounted(uint8_t idx) { + TU_VERIFY(idx < CFG_TUH_MIDI); + midih_interface_t *p_midi = &_midi_host[idx]; + return p_midi->mounted; +} + +uint8_t tuh_midi_itf_get_index(uint8_t daddr, uint8_t itf_num) { + for (uint8_t idx = 0; idx < CFG_TUH_MIDI; idx++) { + const midih_interface_t *p_midi = &_midi_host[idx]; + if (p_midi->daddr == daddr && + (p_midi->bInterfaceNumber == itf_num || + p_midi->bInterfaceNumber == (uint8_t) (itf_num + p_midi->itf_count - 1))) { + return idx; + } + } + return TUSB_INDEX_INVALID_8; +} + +bool tuh_midi_itf_get_info(uint8_t idx, tuh_itf_info_t* info) { + midih_interface_t* p_midi = &_midi_host[idx]; + TU_VERIFY(p_midi && info); + + info->daddr = p_midi->daddr; + + // re-construct descriptor + tusb_desc_interface_t* desc = &info->desc; + desc->bLength = sizeof(tusb_desc_interface_t); + desc->bDescriptorType = TUSB_DESC_INTERFACE; + + desc->bInterfaceNumber = p_midi->bInterfaceNumber; + desc->bAlternateSetting = 0; + desc->bNumEndpoints = (uint8_t)((p_midi->ep_in != 0 ? 1:0) + (p_midi->ep_out != 0 ? 1:0)); + desc->bInterfaceClass = TUSB_CLASS_AUDIO; + desc->bInterfaceSubClass = AUDIO_SUBCLASS_MIDI_STREAMING; + desc->bInterfaceProtocol = 0; + desc->iInterface = p_midi->iInterface; + + return true; +} + +uint8_t tuh_midi_get_tx_cable_count (uint8_t idx) { + TU_VERIFY(idx < CFG_TUH_MIDI); + midih_interface_t *p_midi = &_midi_host[idx]; + TU_VERIFY(p_midi->ep_stream.tx.ep_addr != 0, 0); + return p_midi->tx_cable_count; +} + +uint8_t tuh_midi_get_rx_cable_count (uint8_t idx) { + TU_VERIFY(idx < CFG_TUH_MIDI); + midih_interface_t *p_midi = &_midi_host[idx]; + TU_VERIFY(p_midi->ep_stream.rx.ep_addr != 0, 0); + return p_midi->rx_cable_count; +} + +uint32_t tuh_midi_read_available(uint8_t idx) { + TU_VERIFY(idx < CFG_TUH_MIDI); + midih_interface_t *p_midi = &_midi_host[idx]; + return tu_edpt_stream_read_available(&p_midi->ep_stream.rx); +} + +uint32_t tuh_midi_write_flush(uint8_t idx) { + TU_VERIFY(idx < CFG_TUH_MIDI); + midih_interface_t *p_midi = &_midi_host[idx]; + return tu_edpt_stream_write_xfer(p_midi->daddr, &p_midi->ep_stream.tx); +} + +//--------------------------------------------------------------------+ +// Packet API +//--------------------------------------------------------------------+ +uint32_t tuh_midi_packet_read_n(uint8_t idx, uint8_t* buffer, uint32_t bufsize) { + TU_VERIFY(idx < CFG_TUH_MIDI && buffer && bufsize > 0, 0); + midih_interface_t *p_midi = &_midi_host[idx]; + + uint32_t count4 = tu_min32(bufsize, tu_edpt_stream_read_available(&p_midi->ep_stream.rx)); + count4 = tu_align4(count4); // round down to multiple of 4 + TU_VERIFY(count4 > 0, 0); + return tu_edpt_stream_read(p_midi->daddr, &p_midi->ep_stream.rx, buffer, count4); +} + +uint32_t tuh_midi_packet_write_n(uint8_t idx, const uint8_t* buffer, uint32_t bufsize) { + TU_VERIFY(idx < CFG_TUH_MIDI && buffer && bufsize > 0, 0); + midih_interface_t *p_midi = &_midi_host[idx]; + + const uint32_t bufsize4 = tu_align4(bufsize); + TU_VERIFY(bufsize4 > 0, 0); + return tu_edpt_stream_write(p_midi->daddr, &p_midi->ep_stream.tx, buffer, bufsize4); +} + +//--------------------------------------------------------------------+ +// Stream API +//--------------------------------------------------------------------+ +#if CFG_TUH_MIDI_STREAM_API +uint32_t tuh_midi_stream_write(uint8_t idx, uint8_t cable_num, uint8_t const *buffer, uint32_t bufsize) { + TU_VERIFY(idx < CFG_TUH_MIDI && buffer && bufsize > 0); + midih_interface_t *p_midi = &_midi_host[idx]; + TU_VERIFY(cable_num < p_midi->tx_cable_count); + midi_driver_stream_t *stream = &p_midi->stream_write; + + uint32_t byte_count = 0; + while ((byte_count < bufsize) && (tu_edpt_stream_write_available(p_midi->daddr, &p_midi->ep_stream.tx) >= 4)) { + uint8_t const data = buffer[byte_count]; + byte_count++; + if (data >= MIDI_STATUS_SYSREAL_TIMING_CLOCK) { + // real-time messages need to be sent right away + midi_driver_stream_t streamrt; + streamrt.buffer[0] = MIDI_CIN_SYSEX_END_1BYTE; + streamrt.buffer[1] = data; + streamrt.index = 2; + streamrt.total = 2; + uint32_t const count = tu_edpt_stream_write(p_midi->daddr, &p_midi->ep_stream.tx, streamrt.buffer, 4); + TU_ASSERT(count == 4, byte_count); // Check FIFO overflown, since we already check fifo remaining. It is probably race condition + } else if (stream->index == 0) { + //------------- New event packet -------------// + + uint8_t const msg = data >> 4; + + stream->index = 2; + stream->buffer[1] = data; + + // Check to see if we're still in a SysEx transmit. + if (stream->buffer[0] == MIDI_CIN_SYSEX_START) { + if (data == MIDI_STATUS_SYSEX_END) { + stream->buffer[0] = MIDI_CIN_SYSEX_END_1BYTE; + stream->total = 2; + } else { + stream->total = 4; + } + } else if ((msg >= 0x8 && msg <= 0xB) || msg == 0xE) { + // Channel Voice Messages + stream->buffer[0] = (uint8_t) ((cable_num << 4) | msg); + stream->total = 4; + } else if (msg == 0xC || msg == 0xD) { + // Channel Voice Messages, two-byte variants (Program Change and Channel Pressure) + stream->buffer[0] = (uint8_t) ((cable_num << 4) | msg); + stream->total = 3; + } else if (msg == 0xf) { + // System message + if (data == MIDI_STATUS_SYSEX_START) { + stream->buffer[0] = MIDI_CIN_SYSEX_START; + stream->total = 4; + } else if (data == MIDI_STATUS_SYSCOM_TIME_CODE_QUARTER_FRAME || data == MIDI_STATUS_SYSCOM_SONG_SELECT) { + stream->buffer[0] = MIDI_CIN_SYSCOM_2BYTE; + stream->total = 3; + } else if (data == MIDI_STATUS_SYSCOM_SONG_POSITION_POINTER) { + stream->buffer[0] = MIDI_CIN_SYSCOM_3BYTE; + stream->total = 4; + } else { + stream->buffer[0] = MIDI_CIN_SYSEX_END_1BYTE; + stream->total = 2; + } + } else { + // Pack individual bytes if we don't support packing them into words. + stream->buffer[0] = (uint8_t) (cable_num << 4 | 0xf); + stream->buffer[2] = 0; + stream->buffer[3] = 0; + stream->index = 2; + stream->total = 2; + } + } else { + //------------- On-going (buffering) packet -------------// + TU_ASSERT(stream->index < 4, byte_count); + stream->buffer[stream->index] = data; + stream->index++; + // See if this byte ends a SysEx. + if (stream->buffer[0] == MIDI_CIN_SYSEX_START && data == MIDI_STATUS_SYSEX_END) { + stream->buffer[0] = MIDI_CIN_SYSEX_START + (stream->index - 1); + stream->total = stream->index; + } + } + + // Send out packet + if (stream->index >= 2 && stream->index == stream->total) { + // zeroes unused bytes + for (uint8_t i = stream->total; i < 4; i++) { + stream->buffer[i] = 0; + } + TU_LOG3_MEM(stream->buffer, 4, 2); + + const uint32_t count = tu_edpt_stream_write(p_midi->daddr, &p_midi->ep_stream.tx, stream->buffer, 4); + + // complete current event packet, reset stream + stream->index = 0; + stream->total = 0; + + // FIFO overflown, since we already check fifo remaining. It is probably race condition + TU_ASSERT(count == 4, byte_count); + } + } + return byte_count; +} + +uint32_t tuh_midi_stream_read(uint8_t idx, uint8_t *p_cable_num, uint8_t *p_buffer, uint16_t bufsize) { + TU_VERIFY(idx < CFG_TUH_MIDI && p_cable_num && p_buffer && bufsize > 0); + midih_interface_t *p_midi = &_midi_host[idx]; + uint32_t bytes_buffered = 0; + uint8_t one_byte; + if (!tu_edpt_stream_peek(&p_midi->ep_stream.rx, &one_byte)) { + return 0; + } + *p_cable_num = (one_byte >> 4) & 0xf; + uint32_t nread = tu_edpt_stream_read(p_midi->daddr, &p_midi->ep_stream.rx, p_midi->stream_read.buffer, 4); + static uint16_t cable_sysex_in_progress;// bit i is set if received MIDI_STATUS_SYSEX_START but not MIDI_STATUS_SYSEX_END + while (nread == 4 && bytes_buffered < bufsize) { + *p_cable_num = (p_midi->stream_read.buffer[0] >> 4) & 0x0f; + uint8_t bytes_to_add_to_stream = 0; + if (*p_cable_num < p_midi->rx_cable_count) { + // ignore the CIN field; too many devices out there encode this wrong + uint8_t status = p_midi->stream_read.buffer[1]; + uint16_t cable_mask = (uint16_t) (1 << *p_cable_num); + if (status <= MIDI_MAX_DATA_VAL || status == MIDI_STATUS_SYSEX_START) { + if (status == MIDI_STATUS_SYSEX_START) { + cable_sysex_in_progress |= cable_mask; + } + // only add the packet if a sysex message is in progress + if (cable_sysex_in_progress & cable_mask) { + ++bytes_to_add_to_stream; + for (uint8_t i = 2; i < 4; i++) { + if (p_midi->stream_read.buffer[i] <= MIDI_MAX_DATA_VAL) { + ++bytes_to_add_to_stream; + } else if (p_midi->stream_read.buffer[i] == MIDI_STATUS_SYSEX_END) { + ++bytes_to_add_to_stream; + cable_sysex_in_progress &= (uint16_t) ~cable_mask; + i = 4;// force the loop to exit; I hate break statements in loops + } + } + } + else { + // bad packet discard + nread = tu_edpt_stream_read(p_midi->daddr, &p_midi->ep_stream.rx, p_midi->stream_read.buffer, 4); + continue; + } + } else if (status < MIDI_STATUS_SYSEX_START) { + // then it is a channel message either three bytes or two + uint8_t fake_cin = (status & 0xf0) >> 4; + switch (fake_cin) { + case MIDI_CIN_NOTE_OFF: + case MIDI_CIN_NOTE_ON: + case MIDI_CIN_POLY_KEYPRESS: + case MIDI_CIN_CONTROL_CHANGE: + case MIDI_CIN_PITCH_BEND_CHANGE: + bytes_to_add_to_stream = 3; + break; + case MIDI_CIN_PROGRAM_CHANGE: + case MIDI_CIN_CHANNEL_PRESSURE: + bytes_to_add_to_stream = 2; + break; + default: + break;// Should not get this + } + cable_sysex_in_progress &= (uint16_t) ~cable_mask; + } else if (status < MIDI_STATUS_SYSREAL_TIMING_CLOCK) { + switch (status) { + case MIDI_STATUS_SYSCOM_TIME_CODE_QUARTER_FRAME: + case MIDI_STATUS_SYSCOM_SONG_SELECT: + bytes_to_add_to_stream = 2; + break; + case MIDI_STATUS_SYSCOM_SONG_POSITION_POINTER: + bytes_to_add_to_stream = 3; + break; + case MIDI_STATUS_SYSCOM_TUNE_REQUEST: + case MIDI_STATUS_SYSEX_END: + bytes_to_add_to_stream = 1; + break; + default: + break; + } + cable_sysex_in_progress &= (uint16_t) ~cable_mask; + } else { + // Real-time message: can be inserted into a sysex message, + // so do don't clear cable_sysex_in_progress bit + bytes_to_add_to_stream = 1; + } + } + else { + // bad packet discard + nread = tu_edpt_stream_read(p_midi->daddr, &p_midi->ep_stream.rx, p_midi->stream_read.buffer, 4); + continue; + } + + for (uint8_t i = 1; i <= bytes_to_add_to_stream; i++) { + *p_buffer++ = p_midi->stream_read.buffer[i]; + } + bytes_buffered += bytes_to_add_to_stream; + nread = 0; + if (tu_edpt_stream_peek(&p_midi->ep_stream.rx, &one_byte)) { + uint8_t new_cable = (one_byte >> 4) & 0xf; + if (new_cable == *p_cable_num) { + // still on the same cable. Continue reading the stream + nread = tu_edpt_stream_read(p_midi->daddr, &p_midi->ep_stream.rx, p_midi->stream_read.buffer, 4); + } + } + } + + return bytes_buffered; +} +#endif + +#endif diff --git a/cores/nRF5/TinyUSB/Adafruit_TinyUSB_ArduinoCore/tinyusb/src/class/midi/midi_host.h b/cores/nRF5/TinyUSB/Adafruit_TinyUSB_ArduinoCore/tinyusb/src/class/midi/midi_host.h new file mode 100644 index 0000000..06554a0 --- /dev/null +++ b/cores/nRF5/TinyUSB/Adafruit_TinyUSB_ArduinoCore/tinyusb/src/class/midi/midi_host.h @@ -0,0 +1,193 @@ +/* + * The MIT License (MIT) + * + * Copyright (c) 2019 Ha Thach (tinyusb.org) + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + * This file is part of the TinyUSB stack. + */ + +#ifndef TUSB_MIDI_HOST_H_ +#define TUSB_MIDI_HOST_H_ + +#include "class/audio/audio.h" +#include "midi.h" + +#ifdef __cplusplus + extern "C" { +#endif + +//--------------------------------------------------------------------+ +// Class Driver Configuration +//--------------------------------------------------------------------+ +#ifndef CFG_TUH_MIDI_RX_BUFSIZE +#define CFG_TUH_MIDI_RX_BUFSIZE TUH_EPSIZE_BULK_MPS +#endif + +#ifndef CFG_TUH_MIDI_TX_BUFSIZE +#define CFG_TUH_MIDI_TX_BUFSIZE TUH_EPSIZE_BULK_MPS +#endif + +#ifndef CFG_TUH_MIDI_EP_BUFSIZE +#define CFG_TUH_MIDI_EP_BUFSIZE TUH_EPSIZE_BULK_MPS +#endif + +// Enable the MIDI stream read/write API. Some library can work with raw USB MIDI packet +// Disable this can save driver footprint. +#ifndef CFG_TUH_MIDI_STREAM_API +#define CFG_TUH_MIDI_STREAM_API 1 +#endif + +//--------------------------------------------------------------------+ +// Application Types +//--------------------------------------------------------------------+ +typedef struct { + const tusb_desc_interface_t* desc_audio_control; + const tusb_desc_interface_t* desc_midi; // start of whole midi interface descriptor + uint16_t desc_midi_total_len; + + const uint8_t* desc_header; + const uint8_t* desc_element; + const tusb_desc_endpoint_t* desc_epin; // endpoint IN descriptor, CS_ENDPOINT is right after + const tusb_desc_endpoint_t* desc_epout; // endpoint OUT descriptor, CS_ENDPOINT is right after + + uint8_t jack_num; + const uint8_t* desc_jack[32]; // list of jack descriptors (embedded + external) +} tuh_midi_descriptor_cb_t; + +typedef struct { + uint8_t daddr; + uint8_t bInterfaceNumber; // interface number of MIDI streaming + uint8_t rx_cable_count; + uint8_t tx_cable_count; +} tuh_midi_mount_cb_t; + +//--------------------------------------------------------------------+ +// Application API +//--------------------------------------------------------------------+ + +// Check if MIDI interface is mounted +bool tuh_midi_mounted(uint8_t idx); + +// Get Interface index from device address + interface number +// return TUSB_INDEX_INVALID_8 (0xFF) if not found +uint8_t tuh_midi_itf_get_index(uint8_t daddr, uint8_t itf_num); + +// Get Interface information +// return true if index is correct and interface is currently mounted +bool tuh_midi_itf_get_info(uint8_t idx, tuh_itf_info_t* info); + +// return the number of virtual midi cables on the device's IN endpoint +uint8_t tuh_midi_get_rx_cable_count(uint8_t idx); + +// return the number of virtual midi cables on the device's OUT endpoint +uint8_t tuh_midi_get_tx_cable_count(uint8_t idx); + +// return the raw number of bytes available. +// Note: this is related but not the same as number of stream bytes available. +uint32_t tuh_midi_read_available(uint8_t idx); + +// Send any queued packets to the device if the host hardware is able to do it +// Returns the number of bytes flushed to the host hardware or 0 if +// the host hardware is busy or there is nothing in queue to send. +uint32_t tuh_midi_write_flush(uint8_t idx); + +//--------------------------------------------------------------------+ +// Packet API +//--------------------------------------------------------------------+ + +// Read all available MIDI packets from the connected device +// Return number of bytes read (always multiple of 4) +uint32_t tuh_midi_packet_read_n(uint8_t idx, uint8_t* buffer, uint32_t bufsize); + +// Read a raw MIDI packet from the connected device +// Return true if a packet was returned +TU_ATTR_ALWAYS_INLINE static inline +bool tuh_midi_packet_read (uint8_t idx, uint8_t packet[4]) { + return 4 == tuh_midi_packet_read_n(idx, packet, 4); +} + +// Write all 4-byte packets, data is locally buffered and only transferred when buffered bytes +// reach the endpoint packet size or tuh_midi_write_flush() is called +uint32_t tuh_midi_packet_write_n(uint8_t idx, const uint8_t* buffer, uint32_t bufsize); + +// Write a 4-bytes packet to the device. +// Returns true if the packet was successfully queued. +TU_ATTR_ALWAYS_INLINE static inline +bool tuh_midi_packet_write (uint8_t idx, uint8_t const packet[4]) { + return 4 == tuh_midi_packet_write_n(idx, packet, 4); +} + +//--------------------------------------------------------------------+ +// Stream API +//--------------------------------------------------------------------+ +#if CFG_TUH_MIDI_STREAM_API + +// Queue a message to the device using stream API. data is locally buffered and only transferred when buffered bytes +// reach the endpoint packet size or tuh_midi_write_flush() is called +// Returns number of bytes was successfully queued. +uint32_t tuh_midi_stream_write(uint8_t idx, uint8_t cable_num, uint8_t const *p_buffer, uint32_t bufsize); + +// Get the MIDI stream from the device. Set the value pointed +// to by p_cable_num to the MIDI cable number intended to receive it. +// The MIDI stream will be stored in the buffer pointed to by p_buffer. +// Return the number of bytes added to the buffer. +// Note that this function ignores the CIN field of the MIDI packet +// because a number of commercial devices out there do not encode +// it properly. +uint32_t tuh_midi_stream_read(uint8_t idx, uint8_t *p_cable_num, uint8_t *p_buffer, uint16_t bufsize); + +#endif + +//--------------------------------------------------------------------+ +// Callbacks (Weak is optional) +//--------------------------------------------------------------------+ + +// Invoked when MIDI interface is detected in enumeration. Application can copy/parse descriptor if needed. +// Note: may be fired before tuh_midi_mount_cb(), therefore midi interface is not mounted/ready. +void tuh_midi_descriptor_cb(uint8_t idx, const tuh_midi_descriptor_cb_t * desc_cb_data); + +// Invoked when device with MIDI interface is mounted. +void tuh_midi_mount_cb(uint8_t idx, const tuh_midi_mount_cb_t* mount_cb_data); + +// Invoked when device with MIDI interface is un-mounted +void tuh_midi_umount_cb(uint8_t idx); + +// Invoked when received new data +void tuh_midi_rx_cb(uint8_t idx, uint32_t xferred_bytes); + +// Invoked when a TX is complete and therefore space becomes available in TX buffer +void tuh_midi_tx_cb(uint8_t idx, uint32_t xferred_bytes); + +//--------------------------------------------------------------------+ +// Internal Class Driver API +//--------------------------------------------------------------------+ +bool midih_init (void); +bool midih_deinit (void); +bool midih_open (uint8_t rhport, uint8_t dev_addr, tusb_desc_interface_t const *desc_itf, uint16_t max_len); +bool midih_set_config (uint8_t dev_addr, uint8_t itf_num); +bool midih_xfer_cb (uint8_t dev_addr, uint8_t ep_addr, xfer_result_t result, uint32_t xferred_bytes); +void midih_close (uint8_t daddr); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/cores/nRF5/TinyUSB/Adafruit_TinyUSB_ArduinoCore/tinyusb/src/class/msc/msc.h b/cores/nRF5/TinyUSB/Adafruit_TinyUSB_ArduinoCore/tinyusb/src/class/msc/msc.h index cba8066..b2b44ea 100644 --- a/cores/nRF5/TinyUSB/Adafruit_TinyUSB_ArduinoCore/tinyusb/src/class/msc/msc.h +++ b/cores/nRF5/TinyUSB/Adafruit_TinyUSB_ArduinoCore/tinyusb/src/class/msc/msc.h @@ -1,4 +1,4 @@ -/* +/* * The MIT License (MIT) * * Copyright (c) 2019 Ha Thach (tinyusb.org) @@ -24,13 +24,6 @@ * This file is part of the TinyUSB stack. */ -/** \ingroup group_class - * \defgroup ClassDriver_MSC MassStorage (MSC) - * @{ */ - -/** \defgroup ClassDriver_MSC_Common Common Definitions - * @{ */ - #ifndef _TUSB_MSC_H_ #define _TUSB_MSC_H_ @@ -60,7 +53,7 @@ enum { }; /// \brief MassStorage Protocol. -/// \details CBI only approved to use with full-speed floopy disk & should not used with highspeed or device other than floopy +/// \details CBI only approved to use with full-speed floppy disk & should not used with highspeed or device other than floppy typedef enum { MSC_PROTOCOL_CBI = 0 , ///< Control/Bulk/Interrupt protocol (with command completion interrupt) @@ -104,7 +97,7 @@ typedef struct TU_ATTR_PACKED { uint32_t signature ; ///< Signature that helps identify this data packet as a CSW. The signature field shall contain the value 53425355h (little endian), indicating CSW. uint32_t tag ; ///< The device shall set this field to the value received in the dCBWTag of the associated CBW. - uint32_t data_residue ; ///< For Data-Out the device shall report in the dCSWDataResiduethe difference between the amount of data expected as stated in the dCBWDataTransferLength, and the actual amount of data processed by the device. For Data-In the device shall report in the dCSWDataResiduethe difference between the amount of data expected as stated in the dCBWDataTransferLengthand the actual amount of relevant data sent by the device + uint32_t data_residue ; ///< For Data-Out the device shall report in the dCSWDataResidue the difference between the amount of data expected as stated in the dCBWDataTransferLength, and the actual amount of data processed by the device. For Data-In the device shall report in the dCSWDataResiduethe difference between the amount of data expected as stated in the dCBWDataTransferLengthand the actual amount of relevant data sent by the device uint8_t status ; ///< indicates the success or failure of the command. Values from \ref msc_csw_status_t }msc_csw_t; @@ -115,8 +108,7 @@ TU_VERIFY_STATIC(sizeof(msc_csw_t) == 13, "size is not correct"); //--------------------------------------------------------------------+ /// SCSI Command Operation Code -typedef enum -{ +typedef enum { SCSI_CMD_TEST_UNIT_READY = 0x00, ///< The SCSI Test Unit Ready command is used to determine if a device is ready to transfer data (read/write), i.e. if a disk has spun up, if a tape is loaded and ready etc. The device does not perform a self-test operation. SCSI_CMD_INQUIRY = 0x12, ///< The SCSI Inquiry command is used to obtain basic information from a target device. SCSI_CMD_MODE_SELECT_6 = 0x15, ///< provides a means for the application client to specify medium, logical unit, or peripheral device parameters to the device server. Device servers that implement the MODE SELECT(6) command shall also implement the MODE SENSE(6) command. Application clients should issue MODE SENSE(6) prior to each MODE SELECT(6) to determine supported mode pages, page lengths, and other parameters. @@ -127,14 +119,13 @@ typedef enum SCSI_CMD_REQUEST_SENSE = 0x03, ///< The SCSI Request Sense command is part of the SCSI computer protocol standard. This command is used to obtain sense data -- status/error information -- from a target device. SCSI_CMD_READ_FORMAT_CAPACITY = 0x23, ///< The command allows the Host to request a list of the possible format capacities for an installed writable media. This command also has the capability to report the writable capacity for a media when it is installed SCSI_CMD_READ_10 = 0x28, ///< The READ (10) command requests that the device server read the specified logical block(s) and transfer them to the data-in buffer. - SCSI_CMD_WRITE_10 = 0x2A, ///< The WRITE (10) command requests thatthe device server transfer the specified logical block(s) from the data-out buffer and write them. + SCSI_CMD_WRITE_10 = 0x2A, ///< The WRITE (10) command requests that the device server transfer the specified logical block(s) from the data-out buffer and write them. }scsi_cmd_type_t; /// SCSI Sense Key -typedef enum -{ +typedef enum { SCSI_SENSE_NONE = 0x00, ///< no specific Sense Key. This would be the case for a successful command - SCSI_SENSE_RECOVERED_ERROR = 0x01, ///< ndicates the last command completed successfully with some recovery action performed by the disc drive. + SCSI_SENSE_RECOVERED_ERROR = 0x01, ///< Indicates the last command completed successfully with some recovery action performed by the disc drive. SCSI_SENSE_NOT_READY = 0x02, ///< Indicates the logical unit addressed cannot be accessed. SCSI_SENSE_MEDIUM_ERROR = 0x03, ///< Indicates the command terminated with a non-recovered error condition. SCSI_SENSE_HARDWARE_ERROR = 0x04, ///< Indicates the disc drive detected a nonrecoverable hardware failure while performing the command or during a self test. @@ -145,9 +136,30 @@ typedef enum SCSI_SENSE_ABORTED_COMMAND = 0x0b, ///< Indicates the disc drive aborted the command. SCSI_SENSE_EQUAL = 0x0c, ///< Indicates a SEARCH DATA command has satisfied an equal comparison. SCSI_SENSE_VOLUME_OVERFLOW = 0x0d, ///< Indicates a buffered peripheral device has reached the end of medium partition and data remains in the buffer that has not been written to the medium. - SCSI_SENSE_MISCOMPARE = 0x0e ///< ndicates that the source data did not match the data read from the medium. + SCSI_SENSE_MISCOMPARE = 0x0e ///< Indicates that the source data did not match the data read from the medium. }scsi_sense_key_type_t; + +typedef enum { + SCSI_PDT_DIRECT_ACCESS = 0x0, + SCSI_PDT_SEQUENTIAL_ACCESS = 0x1, + SCSI_PDT_PRINTER = 0x2, + SCSI_PDT_PROCESSOR = 0x3, + SCSI_PDT_WRITE_ONCE = 0x4, + SCSI_PDT_CD_DVD = 0x5, + SCSI_PDT_SCANNER = 0x6, + SCSI_PDT_OPTICAL_DEVICE = 0x7, + SCSI_PDT_MEDIUM_CHANGER = 0x8, + SCSI_PDT_COMMUNICATIONS = 0x9, // obsolete + SCSI_PDT_RAID = 0x0c, + SCSI_PDT_ENCLOSURE_SERVICES = 0x0d, + SCSI_PDT_SIMPLIFIED_DIRECT_ACCESS = 0x0e, + SCSI_PDT_OPTICAL_CARD_READER = 0x0f, + SCSI_PDT_BRIDGE = 0x10, ///< Bridge device, e.g. USB to SCSI bridge + SCSI_PDT_OBJECT_BASED_STORAGE = 0x11, ///< Object-based storage device + SCSI_PDT_AUTOMATION_DRIVE_INTERFACE = 0x12, ///< Automation/Drive Interface (ADI) device +} scsi_peripheral_device_type_t; + //--------------------------------------------------------------------+ // SCSI Primary Command (SPC-4) //--------------------------------------------------------------------+ @@ -255,7 +267,7 @@ typedef struct TU_ATTR_PACKED uint8_t : 3; uint8_t disable_block_descriptor : 1; - uint8_t : 0; + uint8_t : 4; uint8_t page_code : 6; uint8_t page_control : 2; @@ -387,6 +399,3 @@ TU_VERIFY_STATIC(sizeof(scsi_write10_t) == 10, "size is not correct"); #endif #endif /* _TUSB_MSC_H_ */ - -/// @} -/// @} diff --git a/cores/nRF5/TinyUSB/Adafruit_TinyUSB_ArduinoCore/tinyusb/src/class/msc/msc_device.c b/cores/nRF5/TinyUSB/Adafruit_TinyUSB_ArduinoCore/tinyusb/src/class/msc/msc_device.c index f3f2e53..b0eafd5 100644 --- a/cores/nRF5/TinyUSB/Adafruit_TinyUSB_ArduinoCore/tinyusb/src/class/msc/msc_device.c +++ b/cores/nRF5/TinyUSB/Adafruit_TinyUSB_ArduinoCore/tinyusb/src/class/msc/msc_device.c @@ -1,4 +1,4 @@ -/* +/* * The MIT License (MIT) * * Copyright (c) 2019 Ha Thach (tinyusb.org) @@ -26,94 +26,262 @@ #include "tusb_option.h" -#if (TUSB_OPT_DEVICE_ENABLED && CFG_TUD_MSC) +#if (CFG_TUD_ENABLED && CFG_TUD_MSC) -#include "common/tusb_common.h" -#include "msc_device.h" -#include "device/usbd_pvt.h" #include "device/dcd.h" // for faking dcd_event_xfer_complete +#include "device/usbd.h" +#include "device/usbd_pvt.h" + +#include "msc_device.h" + +// Level where CFG_TUSB_DEBUG must be at least for this driver is logged +#ifndef CFG_TUD_MSC_LOG_LEVEL + #define CFG_TUD_MSC_LOG_LEVEL CFG_TUD_LOG_LEVEL +#endif + +#define TU_LOG_DRV(...) TU_LOG(CFG_TUD_MSC_LOG_LEVEL, __VA_ARGS__) + +//--------------------------------------------------------------------+ +// Weak stubs: invoked if no strong implementation is available +//--------------------------------------------------------------------+ +TU_ATTR_WEAK void tud_msc_inquiry_cb(uint8_t lun, uint8_t vendor_id[8], uint8_t product_id[16], uint8_t product_rev[4]) { + (void) lun; (void) vendor_id; (void) product_id; (void) product_rev; +} +TU_ATTR_WEAK uint32_t tud_msc_inquiry2_cb(uint8_t lun, scsi_inquiry_resp_t *inquiry_resp, uint32_t bufsize) { + (void) lun; (void) inquiry_resp; (void) bufsize; + return 0; +} //--------------------------------------------------------------------+ // MACRO CONSTANT TYPEDEF //--------------------------------------------------------------------+ -enum -{ +enum { MSC_STAGE_CMD = 0, MSC_STAGE_DATA, MSC_STAGE_STATUS, - MSC_STAGE_STATUS_SENT + MSC_STAGE_STATUS_SENT, + MSC_STAGE_NEED_RESET, }; -typedef struct -{ - // TODO optimize alignment - CFG_TUSB_MEM_ALIGN msc_cbw_t cbw; - CFG_TUSB_MEM_ALIGN msc_csw_t csw; +typedef struct { + TU_ATTR_ALIGNED(4) msc_cbw_t cbw; // 31 bytes + uint8_t rhport; + TU_ATTR_ALIGNED(4) msc_csw_t csw; // 13 bytes uint8_t itf_num; uint8_t ep_in; uint8_t ep_out; - // Bulk Only Transfer (BOT) Protocol - uint8_t stage; - uint32_t total_len; + uint32_t total_len; // byte to be transferred, can be smaller than total_bytes in cbw uint32_t xferred_len; // numbered of bytes transferred so far in the Data Stage - // Sense Response Data + // Bulk Only Transfer (BOT) Protocol + uint8_t stage; + + // SCSI Sense Response Data uint8_t sense_key; uint8_t add_sense_code; uint8_t add_sense_qualifier; + + uint8_t pending_io; // pending async IO }mscd_interface_t; -CFG_TUSB_MEM_SECTION CFG_TUSB_MEM_ALIGN static mscd_interface_t _mscd_itf; -CFG_TUSB_MEM_SECTION CFG_TUSB_MEM_ALIGN static uint8_t _mscd_buf[CFG_TUD_MSC_EP_BUFSIZE]; +static mscd_interface_t _mscd_itf; + +CFG_TUD_MEM_SECTION static struct { + TUD_EPBUF_DEF(buf, CFG_TUD_MSC_EP_BUFSIZE); +} _mscd_epbuf; //--------------------------------------------------------------------+ // INTERNAL OBJECT & FUNCTION DECLARATION //--------------------------------------------------------------------+ -static void proc_read10_cmd(uint8_t rhport, mscd_interface_t* p_msc); -static void proc_write10_cmd(uint8_t rhport, mscd_interface_t* p_msc); - -static inline uint32_t rdwr10_get_lba(uint8_t const command[]) -{ - // read10 & write10 has the same format - scsi_write10_t* p_rdwr10 = (scsi_write10_t*) command; +static int32_t proc_builtin_scsi(uint8_t lun, uint8_t const scsi_cmd[16], uint8_t* buffer, uint32_t bufsize); +static void proc_read10_cmd(mscd_interface_t* p_msc); +static void proc_read_io_data(mscd_interface_t* p_msc, int32_t nbytes); +static void proc_write10_cmd(mscd_interface_t* p_msc); +static void proc_write10_host_data(mscd_interface_t* p_msc, uint32_t xferred_bytes); +static void proc_write_io_data(mscd_interface_t* p_msc, uint32_t xferred_bytes, int32_t nbytes); +static bool proc_stage_status(mscd_interface_t* p_msc); + +TU_ATTR_ALWAYS_INLINE static inline bool is_data_in(uint8_t dir) { + return tu_bit_test(dir, 7); +} - // copy first to prevent mis-aligned access - uint32_t lba; - // use offsetof to avoid pointer to the odd/misaligned address - memcpy(&lba, (uint8_t*) p_rdwr10 + offsetof(scsi_write10_t, lba), 4); +static inline bool send_csw(mscd_interface_t* p_msc) { + // Data residue is always = host expect - actual transferred + uint8_t rhport = p_msc->rhport; + p_msc->csw.data_residue = p_msc->cbw.total_bytes - p_msc->xferred_len; + p_msc->stage = MSC_STAGE_STATUS_SENT; + memcpy(_mscd_epbuf.buf, &p_msc->csw, sizeof(msc_csw_t)); + return usbd_edpt_xfer(rhport, p_msc->ep_in , _mscd_epbuf.buf, sizeof(msc_csw_t)); +} - // lba is in Big Endian format - return tu_ntohl(lba); +static inline bool prepare_cbw(mscd_interface_t* p_msc) { + uint8_t rhport = p_msc->rhport; + p_msc->stage = MSC_STAGE_CMD; + return usbd_edpt_xfer(rhport, p_msc->ep_out, _mscd_epbuf.buf, sizeof(msc_cbw_t)); } -static inline uint16_t rdwr10_get_blockcount(uint8_t const command[]) -{ - // read10 & write10 has the same format - scsi_write10_t* p_rdwr10 = (scsi_write10_t*) command; +static void fail_scsi_op(mscd_interface_t* p_msc, uint8_t status) { + msc_cbw_t const * p_cbw = &p_msc->cbw; + msc_csw_t * p_csw = &p_msc->csw; + uint8_t rhport = p_msc->rhport; + + p_csw->status = status; + p_csw->data_residue = p_msc->cbw.total_bytes - p_msc->xferred_len; + p_msc->stage = MSC_STAGE_STATUS; + + // failed but sense key is not set: default to Illegal Request + if (p_msc->sense_key == 0) { + tud_msc_set_sense(p_cbw->lun, SCSI_SENSE_ILLEGAL_REQUEST, 0x20, 0x00); + } - // copy first to prevent mis-aligned access - uint16_t block_count; - // use offsetof to avoid pointer to the odd/misaligned address - memcpy(&block_count, (uint8_t*) p_rdwr10 + offsetof(scsi_write10_t, block_count), 2); + // If there is data stage and not yet complete, stall it + if (p_cbw->total_bytes && p_csw->data_residue) { + if (is_data_in(p_cbw->dir)) { + usbd_edpt_stall(rhport, p_msc->ep_in); + } else { + usbd_edpt_stall(rhport, p_msc->ep_out); + } + } +} + +static inline uint32_t rdwr10_get_lba(uint8_t const command[]) { + // use offsetof to avoid pointer to the odd/unaligned address + const uint32_t lba = tu_unaligned_read32(command + offsetof(scsi_write10_t, lba)); + return tu_ntohl(lba); // lba is in Big Endian +} +static inline uint16_t rdwr10_get_blockcount(msc_cbw_t const* cbw) { + uint16_t const block_count = tu_unaligned_read16(cbw->command + offsetof(scsi_write10_t, block_count)); return tu_ntohs(block_count); } +static inline uint16_t rdwr10_get_blocksize(msc_cbw_t const* cbw) { + // first extract block count in the command + uint16_t const block_count = rdwr10_get_blockcount(cbw); + if (block_count == 0) { + return 0; // invalid block count + } + return (uint16_t) (cbw->total_bytes / block_count); +} + +static uint8_t rdwr10_validate_cmd(msc_cbw_t const* cbw) { + uint8_t status = MSC_CSW_STATUS_PASSED; + uint16_t const block_count = rdwr10_get_blockcount(cbw); + + if (cbw->total_bytes == 0) { + if (block_count) { + TU_LOG_DRV(" SCSI case 2 (Hn < Di) or case 3 (Hn < Do) \r\n"); + status = MSC_CSW_STATUS_PHASE_ERROR; + } else { + // no data transfer, only exist in complaint test suite + } + } else { + if (SCSI_CMD_READ_10 == cbw->command[0] && !is_data_in(cbw->dir)) { + TU_LOG_DRV(" SCSI case 10 (Ho <> Di)\r\n"); + status = MSC_CSW_STATUS_PHASE_ERROR; + } else if (SCSI_CMD_WRITE_10 == cbw->command[0] && is_data_in(cbw->dir)) { + TU_LOG_DRV(" SCSI case 8 (Hi <> Do)\r\n"); + status = MSC_CSW_STATUS_PHASE_ERROR; + } else if (0 == block_count) { + TU_LOG_DRV(" SCSI case 4 Hi > Dn (READ10) or case 9 Ho > Dn (WRITE10) \r\n"); + status = MSC_CSW_STATUS_FAILED; + } else if (cbw->total_bytes / block_count == 0) { + TU_LOG_DRV(" Computed block size = 0. SCSI case 7 Hi < Di (READ10) or case 13 Ho < Do (WRIT10)\r\n"); + status = MSC_CSW_STATUS_PHASE_ERROR; + } + } + + return status; +} + +static bool proc_stage_status(mscd_interface_t *p_msc) { + uint8_t rhport = p_msc->rhport; + msc_cbw_t const *p_cbw = &p_msc->cbw; + + // skip status if epin is currently stalled, will do it when received Clear Stall request + if (!usbd_edpt_stalled(rhport, p_msc->ep_in)) { + if ((p_cbw->total_bytes > p_msc->xferred_len) && is_data_in(p_cbw->dir)) { + // 6.7 The 13 Cases: case 5 (Hi > Di): STALL before status + // TU_LOG_DRV(" SCSI case 5 (Hi > Di): %lu > %lu\r\n", p_cbw->total_bytes, p_msc->xferred_len); + usbd_edpt_stall(rhport, p_msc->ep_in); + } else { + TU_ASSERT(send_csw(p_msc)); + } + } + + #if TU_CHECK_MCU(OPT_MCU_CXD56) + // WORKAROUND: cxd56 has its own nuttx usb stack which does not forward Set/ClearFeature(Endpoint) to DCD. + // There is no way for us to know when EP is un-stall, therefore we will unconditionally un-stall here and + // hope everything will work + if (usbd_edpt_stalled(rhport, p_msc->ep_in)) { + usbd_edpt_clear_stall(rhport, p_msc->ep_in); + send_csw(p_msc); + } + #endif + return true; +} + +//--------------------------------------------------------------------+ +// Weak stubs: invoked if no strong implementation is available +//--------------------------------------------------------------------+ +TU_ATTR_WEAK void tud_msc_read10_complete_cb(uint8_t lun) { + (void) lun; +} + +TU_ATTR_WEAK void tud_msc_write10_complete_cb(uint8_t lun) { + (void) lun; +} + +TU_ATTR_WEAK void tud_msc_scsi_complete_cb(uint8_t lun, uint8_t const scsi_cmd[16]) { + (void) lun; + (void) scsi_cmd; +} + +TU_ATTR_WEAK uint8_t tud_msc_get_maxlun_cb(void) { + return 1; +} + +TU_ATTR_WEAK bool tud_msc_start_stop_cb(uint8_t lun, uint8_t power_condition, bool start, bool load_eject) { + (void) lun; + (void) power_condition; + (void) start; + (void) load_eject; + return true; +} + +TU_ATTR_WEAK bool tud_msc_prevent_allow_medium_removal_cb(uint8_t lun, uint8_t prohibit_removal, uint8_t control) { + (void) lun; + (void) prohibit_removal; + (void) control; + return true; +} + +TU_ATTR_WEAK int32_t tud_msc_request_sense_cb(uint8_t lun, void* buffer, uint16_t bufsize) { + (void) lun; + (void) buffer; + (void) bufsize; + return sizeof(scsi_sense_fixed_resp_t); +} + +TU_ATTR_WEAK bool tud_msc_is_writable_cb(uint8_t lun) { + (void) lun; + return true; +} + //--------------------------------------------------------------------+ // Debug //--------------------------------------------------------------------+ -#if CFG_TUSB_DEBUG >= 2 +#if CFG_TUSB_DEBUG >= CFG_TUD_MSC_LOG_LEVEL -static tu_lookup_entry_t const _msc_scsi_cmd_lookup[] = -{ +TU_ATTR_UNUSED tu_static tu_lookup_entry_t const _msc_scsi_cmd_lookup[] = { { .key = SCSI_CMD_TEST_UNIT_READY , .data = "Test Unit Ready" }, { .key = SCSI_CMD_INQUIRY , .data = "Inquiry" }, { .key = SCSI_CMD_MODE_SELECT_6 , .data = "Mode_Select 6" }, { .key = SCSI_CMD_MODE_SENSE_6 , .data = "Mode_Sense 6" }, { .key = SCSI_CMD_START_STOP_UNIT , .data = "Start Stop Unit" }, - { .key = SCSI_CMD_PREVENT_ALLOW_MEDIUM_REMOVAL , .data = "Prevent Allow Medium Removal" }, + { .key = SCSI_CMD_PREVENT_ALLOW_MEDIUM_REMOVAL , .data = "Prevent/Allow Medium Removal" }, { .key = SCSI_CMD_READ_CAPACITY_10 , .data = "Read Capacity10" }, { .key = SCSI_CMD_REQUEST_SENSE , .data = "Request Sense" }, { .key = SCSI_CMD_READ_FORMAT_CAPACITY , .data = "Read Format Capacity" }, @@ -121,8 +289,7 @@ static tu_lookup_entry_t const _msc_scsi_cmd_lookup[] = { .key = SCSI_CMD_WRITE_10 , .data = "Write10" } }; -static tu_lookup_table_t const _msc_scsi_cmd_table = -{ +TU_ATTR_UNUSED tu_static tu_lookup_table_t const _msc_scsi_cmd_table = { .count = TU_ARRAY_SIZE(_msc_scsi_cmd_lookup), .items = _msc_scsi_cmd_lookup }; @@ -132,86 +299,163 @@ static tu_lookup_table_t const _msc_scsi_cmd_table = //--------------------------------------------------------------------+ // APPLICATION API //--------------------------------------------------------------------+ -bool tud_msc_set_sense(uint8_t lun, uint8_t sense_key, uint8_t add_sense_code, uint8_t add_sense_qualifier) -{ +bool tud_msc_set_sense(uint8_t lun, uint8_t sense_key, uint8_t add_sense_code, uint8_t add_sense_qualifier) { (void) lun; - _mscd_itf.sense_key = sense_key; _mscd_itf.add_sense_code = add_sense_code; _mscd_itf.add_sense_qualifier = add_sense_qualifier; + return true; +} + +TU_ATTR_ALWAYS_INLINE static inline void set_sense_medium_not_present(uint8_t lun) { + // default sense is NOT READY, MEDIUM NOT PRESENT + tud_msc_set_sense(lun, SCSI_SENSE_NOT_READY, 0x3A, 0x00); +} + +static void proc_async_io_done(void *bytes_io) { + mscd_interface_t *p_msc = &_mscd_itf; + TU_VERIFY(p_msc->pending_io, ); + const int32_t nbytes = (int32_t) (intptr_t) bytes_io; + const uint8_t cmd = p_msc->cbw.command[0]; + p_msc->pending_io = 0; + switch (cmd) { + case SCSI_CMD_READ_10: + proc_read_io_data(p_msc, nbytes); + break; + + case SCSI_CMD_WRITE_10: + proc_write_io_data(p_msc, (uint32_t) nbytes, nbytes); + break; + + default: break; + } + + // send status if stage is transitioned to STATUS + if (p_msc->stage == MSC_STAGE_STATUS) { + proc_stage_status(p_msc); + } +} + +bool tud_msc_async_io_done(int32_t bytes_io, bool in_isr) { + // Precheck to avoid queueing multiple RW done callback + TU_VERIFY(_mscd_itf.pending_io); + if (bytes_io == 0) { + bytes_io = TUD_MSC_RET_ERROR; // 0 is treated as error, no reason to call this with BUSY here + } + usbd_defer_func(proc_async_io_done, (void *) (intptr_t) bytes_io, in_isr); return true; } //--------------------------------------------------------------------+ // USBD Driver API //--------------------------------------------------------------------+ -void mscd_init(void) -{ +void mscd_init(void) { + TU_LOG_INT(CFG_TUD_MSC_LOG_LEVEL, sizeof(mscd_interface_t)); tu_memclr(&_mscd_itf, sizeof(mscd_interface_t)); } -void mscd_reset(uint8_t rhport) -{ +bool mscd_deinit(void) { + return true; // nothing to do +} + +void mscd_reset(uint8_t rhport) { (void) rhport; tu_memclr(&_mscd_itf, sizeof(mscd_interface_t)); } -uint16_t mscd_open(uint8_t rhport, tusb_desc_interface_t const * itf_desc, uint16_t max_len) -{ +uint16_t mscd_open(uint8_t rhport, tusb_desc_interface_t const * itf_desc, uint16_t max_len) { // only support SCSI's BOT protocol TU_VERIFY(TUSB_CLASS_MSC == itf_desc->bInterfaceClass && MSC_SUBCLASS_SCSI == itf_desc->bInterfaceSubClass && MSC_PROTOCOL_BOT == itf_desc->bInterfaceProtocol, 0); - - // msc driver length is fixed uint16_t const drv_len = sizeof(tusb_desc_interface_t) + 2*sizeof(tusb_desc_endpoint_t); - - // Max length mus be at least 1 interface + 2 endpoints - TU_ASSERT(max_len >= drv_len, 0); + TU_ASSERT(max_len >= drv_len, 0); // Max length must be at least 1 interface + 2 endpoints mscd_interface_t * p_msc = &_mscd_itf; p_msc->itf_num = itf_desc->bInterfaceNumber; + p_msc->rhport = rhport; // Open endpoint pair - TU_ASSERT( usbd_open_edpt_pair(rhport, tu_desc_next(itf_desc), 2, TUSB_XFER_BULK, &p_msc->ep_out, &p_msc->ep_in), 0 ); + TU_ASSERT(usbd_open_edpt_pair(rhport, tu_desc_next(itf_desc), 2, TUSB_XFER_BULK, &p_msc->ep_out, &p_msc->ep_in), 0); // Prepare for Command Block Wrapper - if ( !usbd_edpt_xfer(rhport, p_msc->ep_out, (uint8_t*) &p_msc->cbw, sizeof(msc_cbw_t)) ) - { - TU_LOG1_FAILED(); - TU_BREAKPOINT(); - } + TU_ASSERT(prepare_cbw(p_msc), drv_len); return drv_len; } -// Handle class control request +static void proc_bot_reset(mscd_interface_t* p_msc) { + p_msc->stage = MSC_STAGE_CMD; + p_msc->total_len = 0; + p_msc->xferred_len = 0; + p_msc->sense_key = 0; + p_msc->add_sense_code = 0; + p_msc->add_sense_qualifier = 0; +} + +// Invoked when a control transfer occurred on an interface of this class +// Driver response accordingly to the request and the transfer stage (setup/data/ack) // return false to stall control endpoint (e.g unsupported request) -bool mscd_control_request(uint8_t rhport, tusb_control_request_t const * p_request) -{ - // Handle class request only - TU_VERIFY(p_request->bmRequestType_bit.type == TUSB_REQ_TYPE_CLASS); +bool mscd_control_xfer_cb(uint8_t rhport, uint8_t stage, tusb_control_request_t const * request) { + if (stage != CONTROL_STAGE_SETUP) { + return true; // nothing to do with DATA & ACK stage + } - switch ( p_request->bRequest ) - { + mscd_interface_t* p_msc = &_mscd_itf; + + // Clear Endpoint Feature (stall) for recovery + if ( TUSB_REQ_TYPE_STANDARD == request->bmRequestType_bit.type && + TUSB_REQ_RCPT_ENDPOINT == request->bmRequestType_bit.recipient && + TUSB_REQ_CLEAR_FEATURE == request->bRequest && + TUSB_REQ_FEATURE_EDPT_HALT == request->wValue ) { + uint8_t const ep_addr = tu_u16_low(request->wIndex); + + if (p_msc->stage == MSC_STAGE_NEED_RESET) { + // reset recovery is required to recover from this stage + // Clear Stall request cannot resolve this -> continue to stall endpoint + usbd_edpt_stall(rhport, ep_addr); + } else { + if (ep_addr == p_msc->ep_in) { + if (p_msc->stage == MSC_STAGE_STATUS) { + // resume sending SCSI status if we are in this stage previously before stalled + TU_ASSERT(send_csw(p_msc)); + } + } else if (ep_addr == p_msc->ep_out) { + if (p_msc->stage == MSC_STAGE_CMD) { + // part of reset recovery (probably due to invalid CBW) -> prepare for new command + // Note: skip if already queued previously + if (usbd_edpt_ready(rhport, p_msc->ep_out)) { + TU_ASSERT(prepare_cbw(p_msc)); + } + } + } + } + + return true; + } + + // From this point only handle class request only + TU_VERIFY(request->bmRequestType_bit.type == TUSB_REQ_TYPE_CLASS); + + switch ( request->bRequest ) { case MSC_REQ_RESET: - // TODO: Actually reset interface. - tud_control_status(rhport, p_request); + TU_LOG_DRV(" MSC BOT Reset\r\n"); + TU_VERIFY(request->wValue == 0 && request->wLength == 0); + proc_bot_reset(p_msc); // driver state reset + tud_control_status(rhport, request); break; - case MSC_REQ_GET_MAX_LUN: - { - uint8_t maxlun = 1; - if (tud_msc_get_maxlun_cb) maxlun = tud_msc_get_maxlun_cb(); - TU_VERIFY(maxlun); - - // MAX LUN is minus 1 by specs - maxlun--; + case MSC_REQ_GET_MAX_LUN: { + TU_LOG_DRV(" MSC Get Max Lun\r\n"); + TU_VERIFY(request->wValue == 0 && request->wLength == 1); - tud_control_xfer(rhport, p_request, &maxlun, 1); + uint8_t maxlun = tud_msc_get_maxlun_cb(); + TU_VERIFY(maxlun); + maxlun--; // MAX LUN is minus 1 by specs + tud_control_xfer(rhport, request, &maxlun, 1); + break; } - break; default: return false; // stall unsupported request } @@ -219,93 +463,292 @@ bool mscd_control_request(uint8_t rhport, tusb_control_request_t const * p_reque return true; } -// Invoked when class request DATA stage is finished. -// return false to stall control endpoint (e.g Host send non-sense DATA) -bool mscd_control_complete(uint8_t rhport, tusb_control_request_t const * request) -{ - (void) rhport; - (void) request; +bool mscd_xfer_cb(uint8_t rhport, uint8_t ep_addr, xfer_result_t event, uint32_t xferred_bytes) { + (void) event; + + mscd_interface_t* p_msc = &_mscd_itf; + msc_cbw_t * p_cbw = &p_msc->cbw; + msc_csw_t * p_csw = &p_msc->csw; + + switch (p_msc->stage) { + case MSC_STAGE_CMD: { + //------------- new CBW received -------------// + // Complete IN while waiting for CMD is usually Status of previous SCSI op, ignore it + if (ep_addr != p_msc->ep_out) { + return true; + } + + const uint32_t signature = tu_le32toh(tu_unaligned_read32(_mscd_epbuf.buf)); + + if (!(xferred_bytes == sizeof(msc_cbw_t) && signature == MSC_CBW_SIGNATURE)) { + // BOT 6.6.1 If CBW is not valid stall both endpoints until reset recovery + TU_LOG_DRV(" SCSI CBW is not valid\r\n"); + p_msc->stage = MSC_STAGE_NEED_RESET; + usbd_edpt_stall(rhport, p_msc->ep_in); + usbd_edpt_stall(rhport, p_msc->ep_out); + return false; + } + + memcpy(p_cbw, _mscd_epbuf.buf, sizeof(msc_cbw_t)); + + TU_LOG_DRV(" SCSI Command [Lun%u]: %s\r\n", p_cbw->lun, tu_lookup_find(&_msc_scsi_cmd_table, p_cbw->command[0])); + // TU_LOG_MEM(CFG_TUD_MSC_LOG_LEVEL, p_cbw, xferred_bytes, 2); + + p_csw->signature = MSC_CSW_SIGNATURE; + p_csw->tag = p_cbw->tag; + p_csw->data_residue = 0; + p_csw->status = MSC_CSW_STATUS_PASSED; + + /*------------- Parse command and prepare DATA -------------*/ + p_msc->stage = MSC_STAGE_DATA; + p_msc->total_len = p_cbw->total_bytes; + p_msc->xferred_len = 0; + + // Read10 or Write10 + if ((SCSI_CMD_READ_10 == p_cbw->command[0]) || (SCSI_CMD_WRITE_10 == p_cbw->command[0])) { + uint8_t const status = rdwr10_validate_cmd(p_cbw); + + if (status != MSC_CSW_STATUS_PASSED) { + fail_scsi_op(p_msc, status); + } else if (p_cbw->total_bytes) { + if (SCSI_CMD_READ_10 == p_cbw->command[0]) { + proc_read10_cmd(p_msc); + } else { + proc_write10_cmd(p_msc); + } + } else { + // no data transfer, only exist in complaint test suite + p_msc->stage = MSC_STAGE_STATUS; + } + } else { + // For other SCSI commands + // 1. OUT : queue transfer (invoke app callback after done) + // 2. IN & Zero: Process if is built-in, else Invoke app callback. Skip DATA if zero length + if ((p_cbw->total_bytes > 0) && !is_data_in(p_cbw->dir)) { + if (p_cbw->total_bytes > CFG_TUD_MSC_EP_BUFSIZE) { + TU_LOG_DRV(" SCSI reject non READ10/WRITE10 with large data\r\n"); + fail_scsi_op(p_msc, MSC_CSW_STATUS_FAILED); + } else { + // Didn't check for case 9 (Ho > Dn), which requires examining scsi command first + // but it is OK to just receive data then responded with failed status + TU_ASSERT(usbd_edpt_xfer(rhport, p_msc->ep_out, _mscd_epbuf.buf, (uint16_t) p_msc->total_len)); + } + } else { + // First process if it is a built-in commands + int32_t resplen = proc_builtin_scsi(p_cbw->lun, p_cbw->command, _mscd_epbuf.buf, CFG_TUD_MSC_EP_BUFSIZE); + + // Invoke user callback if not built-in + if ((resplen < 0) && (p_msc->sense_key == 0)) { + resplen = tud_msc_scsi_cb(p_cbw->lun, p_cbw->command, _mscd_epbuf.buf, (uint16_t)p_msc->total_len); + } + + if (resplen < 0) { + // unsupported command + TU_LOG_DRV(" SCSI unsupported or failed command\r\n"); + fail_scsi_op(p_msc, MSC_CSW_STATUS_FAILED); + } else if (resplen == 0) { + if (p_cbw->total_bytes) { + // 6.7 The 13 Cases: case 4 (Hi > Dn) + // TU_LOG_DRV(" SCSI case 4 (Hi > Dn): %lu\r\n", p_cbw->total_bytes); + fail_scsi_op(p_msc, MSC_CSW_STATUS_FAILED); + } else { + // case 1 Hn = Dn: all good + p_msc->stage = MSC_STAGE_STATUS; + } + } else { + if (p_cbw->total_bytes == 0) { + // 6.7 The 13 Cases: case 2 (Hn < Di) + // TU_LOG_DRV(" SCSI case 2 (Hn < Di): %lu\r\n", p_cbw->total_bytes); + fail_scsi_op(p_msc, MSC_CSW_STATUS_FAILED); + } else { + // cannot return more than host expect + p_msc->total_len = tu_min32((uint32_t)resplen, p_cbw->total_bytes); + TU_ASSERT(usbd_edpt_xfer(rhport, p_msc->ep_in, _mscd_epbuf.buf, (uint16_t) p_msc->total_len)); + } + } + } + } + break; + } + + case MSC_STAGE_DATA: + TU_LOG_DRV(" SCSI Data [Lun%u]\r\n", p_cbw->lun); + TU_ASSERT(xferred_bytes <= CFG_TUD_MSC_EP_BUFSIZE); // sanity check to avoid buffer overflow + // TU_LOG_MEM(CFG_TUD_MSC_LOG_LEVEL, _mscd_epbuf.buf, xferred_bytes, 2); + + if (SCSI_CMD_READ_10 == p_cbw->command[0]) { + p_msc->xferred_len += xferred_bytes; + + if ( p_msc->xferred_len >= p_msc->total_len ) { + // Data Stage is complete + p_msc->stage = MSC_STAGE_STATUS; + }else { + proc_read10_cmd(p_msc); + } + } else if (SCSI_CMD_WRITE_10 == p_cbw->command[0]) { + proc_write10_host_data(p_msc, xferred_bytes); + } else { + p_msc->xferred_len += xferred_bytes; + + // OUT transfer, invoke callback if needed + if ( !is_data_in(p_cbw->dir) ) { + int32_t cb_result = tud_msc_scsi_cb(p_cbw->lun, p_cbw->command, _mscd_epbuf.buf, (uint16_t) p_msc->total_len); + + if ( cb_result < 0 ) { + // unsupported command + TU_LOG_DRV(" SCSI unsupported command\r\n"); + fail_scsi_op(p_msc, MSC_CSW_STATUS_FAILED); + }else { + // TODO haven't implement this scenario any further yet + } + } + + if ( p_msc->xferred_len >= p_msc->total_len ) { + // Data Stage is complete + p_msc->stage = MSC_STAGE_STATUS; + } else { + // This scenario with command that take more than one transfer is already rejected at Command stage + TU_BREAKPOINT(); + } + } + break; + + case MSC_STAGE_STATUS: + // processed immediately after this switch, supposedly to be empty + break; + + case MSC_STAGE_STATUS_SENT: + // Status phase is complete + if ((ep_addr == p_msc->ep_in) && (xferred_bytes == sizeof(msc_csw_t))) { + TU_LOG_DRV(" SCSI Status [Lun%u] = %u\r\n", p_cbw->lun, p_csw->status); + // TU_LOG_MEM(CFG_TUD_MSC_LOG_LEVEL, p_csw, xferred_bytes, 2); + + // Invoke complete callback if defined + // Note: There is racing issue with samd51 + qspi flash testing with arduino + // if complete_cb() is invoked after queuing the status. + switch (p_cbw->command[0]) { + case SCSI_CMD_READ_10: + tud_msc_read10_complete_cb(p_cbw->lun); + break; + + case SCSI_CMD_WRITE_10: + tud_msc_write10_complete_cb(p_cbw->lun); + break; + + default: + tud_msc_scsi_complete_cb(p_cbw->lun, p_cbw->command); + break; + } + + TU_ASSERT(prepare_cbw(p_msc)); + } else { + // Any xfer ended here is considered unknown error, ignore it + TU_LOG1(" Warning expect SCSI Status but received unknown data\r\n"); + } + break; + + default: break; + } + + if (p_msc->stage == MSC_STAGE_STATUS) { + TU_ASSERT(proc_stage_status(p_msc)); + } - // nothing to do return true; } +/*------------------------------------------------------------------*/ +/* SCSI Command Process + *------------------------------------------------------------------*/ + // return response's length (copied to buffer). Negative if it is not an built-in command or indicate Failed status (CSW) // In case of a failed status, sense key must be set for reason of failure -int32_t proc_builtin_scsi(uint8_t lun, uint8_t const scsi_cmd[16], uint8_t* buffer, uint32_t bufsize) -{ - (void) bufsize; // TODO refractor later +static int32_t proc_builtin_scsi(uint8_t lun, uint8_t const scsi_cmd[16], uint8_t* buffer, uint32_t bufsize) { + (void)bufsize; // TODO refractor later int32_t resplen; - switch ( scsi_cmd[0] ) - { + mscd_interface_t* p_msc = &_mscd_itf; + + switch (scsi_cmd[0]) { case SCSI_CMD_TEST_UNIT_READY: resplen = 0; - if ( !tud_msc_test_unit_ready_cb(lun) ) - { + if (!tud_msc_test_unit_ready_cb(lun)) { // Failed status response - resplen = - 1; + resplen = -1; - // If sense key is not set by callback, default to Logical Unit Not Ready, Cause Not Reportable - if ( _mscd_itf.sense_key == 0 ) tud_msc_set_sense(lun, SCSI_SENSE_NOT_READY, 0x04, 0x00); + // set default sense if not set by callback + if (p_msc->sense_key == 0) { + set_sense_medium_not_present(lun); + } } - break; + break; case SCSI_CMD_START_STOP_UNIT: resplen = 0; - if (tud_msc_start_stop_cb) - { - scsi_start_stop_unit_t const * start_stop = (scsi_start_stop_unit_t const *) scsi_cmd; - if ( !tud_msc_start_stop_cb(lun, start_stop->power_condition, start_stop->start, start_stop->load_eject) ) - { - // Failed status response - resplen = - 1; + scsi_start_stop_unit_t const* start_stop = (scsi_start_stop_unit_t const*)scsi_cmd; + if (!tud_msc_start_stop_cb(lun, start_stop->power_condition, start_stop->start, start_stop->load_eject)) { + // Failed status response + resplen = -1; - // If sense key is not set by callback, default to Logical Unit Not Ready, Cause Not Reportable - if ( _mscd_itf.sense_key == 0 ) tud_msc_set_sense(lun, SCSI_SENSE_NOT_READY, 0x04, 0x00); + // set default sense if not set by callback + if (p_msc->sense_key == 0) { + set_sense_medium_not_present(lun); } } - break; + break; + + case SCSI_CMD_PREVENT_ALLOW_MEDIUM_REMOVAL: + resplen = 0; - case SCSI_CMD_READ_CAPACITY_10: - { + scsi_prevent_allow_medium_removal_t const* prevent_allow = (scsi_prevent_allow_medium_removal_t const*)scsi_cmd; + if (!tud_msc_prevent_allow_medium_removal_cb(lun, prevent_allow->prohibit_removal, prevent_allow->control)) { + // Failed status response + resplen = -1; + + // set default sense if not set by callback + if (p_msc->sense_key == 0) { + set_sense_medium_not_present(lun); + } + } + break; + + + case SCSI_CMD_READ_CAPACITY_10: { uint32_t block_count; uint32_t block_size; uint16_t block_size_u16; tud_msc_capacity_cb(lun, &block_count, &block_size_u16); - block_size = (uint32_t) block_size_u16; + block_size = (uint32_t)block_size_u16; // Invalid block size/count from callback, possibly unit is not ready // stall this request, set sense key to NOT READY - if (block_count == 0 || block_size == 0) - { + if (block_count == 0 || block_size == 0) { resplen = -1; - // If sense key is not set by callback, default to Logical Unit Not Ready, Cause Not Reportable - if ( _mscd_itf.sense_key == 0 ) tud_msc_set_sense(lun, SCSI_SENSE_NOT_READY, 0x04, 0x00); - }else - { + // set default sense if not set by callback + if (p_msc->sense_key == 0) { + set_sense_medium_not_present(lun); + } + } else { scsi_read_capacity10_resp_t read_capa10; read_capa10.last_lba = tu_htonl(block_count-1); read_capa10.block_size = tu_htonl(block_size); resplen = sizeof(read_capa10); - memcpy(buffer, &read_capa10, resplen); + TU_VERIFY(0 == tu_memcpy_s(buffer, bufsize, &read_capa10, (size_t) resplen)); } } break; - case SCSI_CMD_READ_FORMAT_CAPACITY: - { - scsi_read_format_capacity_data_t read_fmt_capa = - { - .list_length = 8, - .block_num = 0, - .descriptor_type = 2, // formatted media - .block_size_u16 = 0 + case SCSI_CMD_READ_FORMAT_CAPACITY: { + scsi_read_format_capacity_data_t read_fmt_capa = { + .list_length = 8, + .block_num = 0, + .descriptor_type = 2, // formatted media + .block_size_u16 = 0 }; uint32_t block_count; @@ -315,400 +758,200 @@ int32_t proc_builtin_scsi(uint8_t lun, uint8_t const scsi_cmd[16], uint8_t* buff // Invalid block size/count from callback, possibly unit is not ready // stall this request, set sense key to NOT READY - if (block_count == 0 || block_size == 0) - { + if (block_count == 0 || block_size == 0) { resplen = -1; - // If sense key is not set by callback, default to Logical Unit Not Ready, Cause Not Reportable - if ( _mscd_itf.sense_key == 0 ) tud_msc_set_sense(lun, SCSI_SENSE_NOT_READY, 0x04, 0x00); - }else - { + // set default sense if not set by callback + if (p_msc->sense_key == 0) { + set_sense_medium_not_present(lun); + } + } else { read_fmt_capa.block_num = tu_htonl(block_count); read_fmt_capa.block_size_u16 = tu_htons(block_size); resplen = sizeof(read_fmt_capa); - memcpy(buffer, &read_fmt_capa, resplen); + TU_VERIFY(0 == tu_memcpy_s(buffer, bufsize, &read_fmt_capa, (size_t) resplen)); } } break; - case SCSI_CMD_INQUIRY: - { - scsi_inquiry_resp_t inquiry_rsp = - { - .is_removable = 1, - .version = 2, - .response_data_format = 2, - }; - - // vendor_id, product_id, product_rev is space padded string - memset(inquiry_rsp.vendor_id , ' ', sizeof(inquiry_rsp.vendor_id)); - memset(inquiry_rsp.product_id , ' ', sizeof(inquiry_rsp.product_id)); - memset(inquiry_rsp.product_rev, ' ', sizeof(inquiry_rsp.product_rev)); - - tud_msc_inquiry_cb(lun, inquiry_rsp.vendor_id, inquiry_rsp.product_id, inquiry_rsp.product_rev); - - resplen = sizeof(inquiry_rsp); - memcpy(buffer, &inquiry_rsp, resplen); + case SCSI_CMD_INQUIRY: { + scsi_inquiry_resp_t *inquiry_rsp = (scsi_inquiry_resp_t *) buffer; + tu_memclr(inquiry_rsp, sizeof(scsi_inquiry_resp_t)); + inquiry_rsp->is_removable = 1; + inquiry_rsp->version = 2; + inquiry_rsp->response_data_format = 2; + inquiry_rsp->additional_length = sizeof(scsi_inquiry_resp_t) - 5; + + resplen = (int32_t) tud_msc_inquiry2_cb(lun, inquiry_rsp, bufsize); + if (resplen == 0) { + // stub callback with no response, use v1 callback + tud_msc_inquiry_cb(lun, inquiry_rsp->vendor_id, inquiry_rsp->product_id, inquiry_rsp->product_rev); + resplen = sizeof(scsi_inquiry_resp_t); + } } break; - case SCSI_CMD_MODE_SENSE_6: - { - scsi_mode_sense6_resp_t mode_resp = - { - .data_len = 3, - .medium_type = 0, - .write_protected = false, - .reserved = 0, - .block_descriptor_len = 0 // no block descriptor are included + case SCSI_CMD_MODE_SENSE_6: { + scsi_mode_sense6_resp_t mode_resp = { + .data_len = 3, + .medium_type = 0, + .write_protected = false, + .reserved = 0, + .block_descriptor_len = 0 // no block descriptor are included }; - bool writable = true; - if (tud_msc_is_writable_cb) { - writable = tud_msc_is_writable_cb(lun); - } + bool writable = tud_msc_is_writable_cb(lun); + mode_resp.write_protected = !writable; resplen = sizeof(mode_resp); - memcpy(buffer, &mode_resp, resplen); + TU_VERIFY(0 == tu_memcpy_s(buffer, bufsize, &mode_resp, (size_t) resplen)); } break; - case SCSI_CMD_REQUEST_SENSE: - { - scsi_sense_fixed_resp_t sense_rsp = - { - .response_code = 0x70, - .valid = 1 + case SCSI_CMD_REQUEST_SENSE: { + scsi_sense_fixed_resp_t sense_rsp = { + .response_code = 0x70, // current, fixed format + .valid = 1 }; sense_rsp.add_sense_len = sizeof(scsi_sense_fixed_resp_t) - 8; - - sense_rsp.sense_key = _mscd_itf.sense_key; - sense_rsp.add_sense_code = _mscd_itf.add_sense_code; - sense_rsp.add_sense_qualifier = _mscd_itf.add_sense_qualifier; + sense_rsp.sense_key = (uint8_t)(p_msc->sense_key & 0x0F); + sense_rsp.add_sense_code = p_msc->add_sense_code; + sense_rsp.add_sense_qualifier = p_msc->add_sense_qualifier; resplen = sizeof(sense_rsp); - memcpy(buffer, &sense_rsp, resplen); + TU_VERIFY(0 == tu_memcpy_s(buffer, bufsize, &sense_rsp, (size_t) resplen)); + + // request sense callback could overwrite the sense data + resplen = tud_msc_request_sense_cb(lun, buffer, (uint16_t)bufsize); // Clear sense data after copy tud_msc_set_sense(lun, 0, 0, 0); } break; - default: resplen = -1; break; + default: resplen = -1; + break; } return resplen; } -bool mscd_xfer_cb(uint8_t rhport, uint8_t ep_addr, xfer_result_t event, uint32_t xferred_bytes) -{ - mscd_interface_t* p_msc = &_mscd_itf; - msc_cbw_t const * p_cbw = &p_msc->cbw; - msc_csw_t * p_csw = &p_msc->csw; - - switch (p_msc->stage) - { - case MSC_STAGE_CMD: - //------------- new CBW received -------------// - // Complete IN while waiting for CMD is usually Status of previous SCSI op, ignore it - if(ep_addr != p_msc->ep_out) return true; - - TU_ASSERT( event == XFER_RESULT_SUCCESS && - xferred_bytes == sizeof(msc_cbw_t) && p_cbw->signature == MSC_CBW_SIGNATURE ); - - TU_LOG2(" SCSI Command: %s\r\n", tu_lookup_find(&_msc_scsi_cmd_table, p_cbw->command[0])); - // TU_LOG2_MEM(p_cbw, xferred_bytes, 2); - - p_csw->signature = MSC_CSW_SIGNATURE; - p_csw->tag = p_cbw->tag; - p_csw->data_residue = 0; - - /*------------- Parse command and prepare DATA -------------*/ - p_msc->stage = MSC_STAGE_DATA; - p_msc->total_len = p_cbw->total_bytes; - p_msc->xferred_len = 0; - - if (SCSI_CMD_READ_10 == p_cbw->command[0]) - { - proc_read10_cmd(rhport, p_msc); - } - else if (SCSI_CMD_WRITE_10 == p_cbw->command[0]) - { - proc_write10_cmd(rhport, p_msc); - } - else - { - // For other SCSI commands - // 1. OUT : queue transfer (invoke app callback after done) - // 2. IN & Zero: Process if is built-in, else Invoke app callback. Skip DATA if zero length - if ( (p_cbw->total_bytes > 0 ) && !tu_bit_test(p_cbw->dir, 7) ) - { - // queue transfer - TU_ASSERT( usbd_edpt_xfer(rhport, p_msc->ep_out, _mscd_buf, p_msc->total_len) ); - }else - { - int32_t resplen; - - // First process if it is a built-in commands - resplen = proc_builtin_scsi(p_cbw->lun, p_cbw->command, _mscd_buf, sizeof(_mscd_buf)); - - // Not built-in, invoke user callback - if ( (resplen < 0) && (p_msc->sense_key == 0) ) - { - resplen = tud_msc_scsi_cb(p_cbw->lun, p_cbw->command, _mscd_buf, p_msc->total_len); - } - - if ( resplen < 0 ) - { - p_msc->total_len = 0; - p_csw->status = MSC_CSW_STATUS_FAILED; - p_msc->stage = MSC_STAGE_STATUS; - - // failed but senskey is not set: default to Illegal Request - if ( p_msc->sense_key == 0 ) tud_msc_set_sense(p_cbw->lun, SCSI_SENSE_ILLEGAL_REQUEST, 0x20, 0x00); - - // Stall bulk In if needed - if (p_cbw->total_bytes) usbd_edpt_stall(rhport, p_msc->ep_in); - } - else - { - p_msc->total_len = (uint32_t) resplen; - p_csw->status = MSC_CSW_STATUS_PASSED; - - if (p_msc->total_len) - { - TU_ASSERT( p_cbw->total_bytes >= p_msc->total_len ); // cannot return more than host expect - TU_ASSERT( usbd_edpt_xfer(rhport, p_msc->ep_in, _mscd_buf, p_msc->total_len) ); - }else - { - p_msc->stage = MSC_STAGE_STATUS; - } - } - } - } - break; - - case MSC_STAGE_DATA: - TU_LOG2(" SCSI Data\r\n"); - //TU_LOG2_MEM(_mscd_buf, xferred_bytes, 2); - - // OUT transfer, invoke callback if needed - if ( !tu_bit_test(p_cbw->dir, 7) ) - { - if ( SCSI_CMD_WRITE_10 != p_cbw->command[0] ) - { - int32_t cb_result = tud_msc_scsi_cb(p_cbw->lun, p_cbw->command, _mscd_buf, p_msc->total_len); - - if ( cb_result < 0 ) - { - p_csw->status = MSC_CSW_STATUS_FAILED; - tud_msc_set_sense(p_cbw->lun, SCSI_SENSE_ILLEGAL_REQUEST, 0x20, 0x00); // Sense = Invalid Command Operation - }else - { - p_csw->status = MSC_CSW_STATUS_PASSED; - } - } - else - { - uint16_t const block_sz = p_cbw->total_bytes / rdwr10_get_blockcount(p_cbw->command); - - // Adjust lba with transferred bytes - uint32_t const lba = rdwr10_get_lba(p_cbw->command) + (p_msc->xferred_len / block_sz); - - // Application can consume smaller bytes - int32_t nbytes = tud_msc_write10_cb(p_cbw->lun, lba, p_msc->xferred_len % block_sz, _mscd_buf, xferred_bytes); - - if ( nbytes < 0 ) - { - // negative means error -> skip to status phase, status in CSW set to failed - p_csw->data_residue = p_cbw->total_bytes - p_msc->xferred_len; - p_csw->status = MSC_CSW_STATUS_FAILED; - p_msc->stage = MSC_STAGE_STATUS; - - tud_msc_set_sense(p_cbw->lun, SCSI_SENSE_ILLEGAL_REQUEST, 0x20, 0x00); // Sense = Invalid Command Operation - break; - }else - { - // Application consume less than what we got (including zero) - if ( nbytes < (int32_t) xferred_bytes ) - { - if ( nbytes > 0 ) - { - p_msc->xferred_len += nbytes; - memmove(_mscd_buf, _mscd_buf+nbytes, xferred_bytes-nbytes); - } - - // simulate an transfer complete with adjusted parameters --> this driver callback will fired again - dcd_event_xfer_complete(rhport, p_msc->ep_out, xferred_bytes-nbytes, XFER_RESULT_SUCCESS, false); - - return true; // skip the rest - } - else - { - // Application consume all bytes in our buffer. Nothing to do, process with normal flow - } - } - } - } - - // Accumulate data so far - p_msc->xferred_len += xferred_bytes; - - if ( p_msc->xferred_len >= p_msc->total_len ) - { - // Data Stage is complete - p_msc->stage = MSC_STAGE_STATUS; - } - else - { - // READ10 & WRITE10 Can be executed with large bulk of data e.g write 8K bytes (several flash write) - // We break it into multiple smaller command whose data size is up to CFG_TUD_MSC_EP_BUFSIZE - if (SCSI_CMD_READ_10 == p_cbw->command[0]) - { - proc_read10_cmd(rhport, p_msc); - } - else if (SCSI_CMD_WRITE_10 == p_cbw->command[0]) - { - proc_write10_cmd(rhport, p_msc); - }else - { - // No other command take more than one transfer yet -> unlikely error - TU_BREAKPOINT(); - } - } - break; - - case MSC_STAGE_STATUS: - // processed immediately after this switch, supposedly to be empty - break; - - case MSC_STAGE_STATUS_SENT: - // Wait for the Status phase to complete - if( (ep_addr == p_msc->ep_in) && (xferred_bytes == sizeof(msc_csw_t)) ) - { - TU_LOG2(" SCSI Status: %u\r\n", p_csw->status); - // TU_LOG2_MEM(p_csw, xferred_bytes, 2); - - // Move to default CMD stage - p_msc->stage = MSC_STAGE_CMD; +static void proc_read10_cmd(mscd_interface_t* p_msc) { + msc_cbw_t const* p_cbw = &p_msc->cbw; + uint16_t const block_sz = rdwr10_get_blocksize(p_cbw); // already verified non-zero + // Adjust lba & offset with transferred bytes + uint32_t const lba = rdwr10_get_lba(p_cbw->command) + (p_msc->xferred_len / block_sz); + uint32_t const offset = p_msc->xferred_len % block_sz; - // Queue for the next CBW - TU_ASSERT( usbd_edpt_xfer(rhport, p_msc->ep_out, (uint8_t*) &p_msc->cbw, sizeof(msc_cbw_t)) ); - } - break; + // remaining bytes capped at class buffer + int32_t nbytes = (int32_t)tu_min32(CFG_TUD_MSC_EP_BUFSIZE, p_cbw->total_bytes - p_msc->xferred_len); - default : break; + p_msc->pending_io = 1; + nbytes = tud_msc_read10_cb(p_cbw->lun, lba, offset, _mscd_epbuf.buf, (uint32_t)nbytes); + if (nbytes != TUD_MSC_RET_ASYNC) { + p_msc->pending_io = 0; + proc_read_io_data(p_msc, nbytes); } +} - if ( p_msc->stage == MSC_STAGE_STATUS ) - { - // Either endpoints is stalled, need to wait until it is cleared by host - if ( usbd_edpt_stalled(rhport, p_msc->ep_in) || usbd_edpt_stalled(rhport, p_msc->ep_out) ) - { - // simulate an transfer complete with adjusted parameters --> this driver callback will fired again - // and response with status phase after halted endpoints are cleared. - // note: use ep_out to prevent confusing with STATUS complete - dcd_event_xfer_complete(rhport, p_msc->ep_out, 0, XFER_RESULT_SUCCESS, false); - } - else - { - // Invoke complete callback if defined - // Note: There is racing issue with samd51 + qspi flash testing with arduino - // if complete_cb() is invoked after queuing the status. - switch(p_cbw->command[0]) - { - case SCSI_CMD_READ_10: - if ( tud_msc_read10_complete_cb ) tud_msc_read10_complete_cb(p_cbw->lun); - break; - - case SCSI_CMD_WRITE_10: - if ( tud_msc_write10_complete_cb ) tud_msc_write10_complete_cb(p_cbw->lun); +static void proc_read_io_data(mscd_interface_t* p_msc, int32_t nbytes) { + const uint8_t rhport = p_msc->rhport; + if (nbytes > 0) { + TU_ASSERT(usbd_edpt_xfer(rhport, p_msc->ep_in, _mscd_epbuf.buf, (uint16_t) nbytes),); + } else { + // nbytes is status + switch (nbytes) { + case TUD_MSC_RET_ERROR: + // error -> endpoint is stalled & status in CSW set to failed + TU_LOG_DRV(" IO read() failed\r\n"); + set_sense_medium_not_present(p_msc->cbw.lun); + fail_scsi_op(p_msc, MSC_CSW_STATUS_FAILED); break; - default: - if ( tud_msc_scsi_complete_cb ) tud_msc_scsi_complete_cb(p_cbw->lun, p_cbw->command); + case TUD_MSC_RET_BUSY: + // not ready yet -> fake a transfer complete so that this driver callback will fire again + dcd_event_xfer_complete(rhport, p_msc->ep_in, 0, XFER_RESULT_SUCCESS, false); break; - } - // Move to Status Sent stage - p_msc->stage = MSC_STAGE_STATUS_SENT; - - // Send SCSI Status - TU_ASSERT(usbd_edpt_xfer(rhport, p_msc->ep_in , (uint8_t*) &p_msc->csw, sizeof(msc_csw_t))); + default: break; } } - - return true; } -/*------------------------------------------------------------------*/ -/* SCSI Command Process - *------------------------------------------------------------------*/ -static void proc_read10_cmd(uint8_t rhport, mscd_interface_t* p_msc) -{ - msc_cbw_t const * p_cbw = &p_msc->cbw; - msc_csw_t * p_csw = &p_msc->csw; - - uint16_t const block_cnt = rdwr10_get_blockcount(p_cbw->command); - TU_ASSERT(block_cnt, ); // prevent div by zero - - uint16_t const block_sz = p_cbw->total_bytes / block_cnt; - TU_ASSERT(block_sz, ); // prevent div by zero +static void proc_write10_cmd(mscd_interface_t* p_msc) { + msc_cbw_t const* p_cbw = &p_msc->cbw; + bool writable = tud_msc_is_writable_cb(p_cbw->lun); - // Adjust lba with transferred bytes - uint32_t const lba = rdwr10_get_lba(p_cbw->command) + (p_msc->xferred_len / block_sz); + if (!writable) { + // Not writable, complete this SCSI op with error + // Sense = Write protected + tud_msc_set_sense(p_cbw->lun, SCSI_SENSE_DATA_PROTECT, 0x27, 0x00); + fail_scsi_op(p_msc, MSC_CSW_STATUS_FAILED); + return; + } // remaining bytes capped at class buffer - int32_t nbytes = (int32_t) tu_min32(sizeof(_mscd_buf), p_cbw->total_bytes-p_msc->xferred_len); + uint16_t nbytes = (uint16_t)tu_min32(CFG_TUD_MSC_EP_BUFSIZE, p_cbw->total_bytes - p_msc->xferred_len); + // Write10 callback will be called later when usb transfer complete + TU_ASSERT(usbd_edpt_xfer(p_msc->rhport, p_msc->ep_out, _mscd_epbuf.buf, nbytes),); +} - // Application can consume smaller bytes - nbytes = tud_msc_read10_cb(p_cbw->lun, lba, p_msc->xferred_len % block_sz, _mscd_buf, (uint32_t) nbytes); +// process new data arrived from WRITE10 +static void proc_write10_host_data(mscd_interface_t* p_msc, uint32_t xferred_bytes) { + msc_cbw_t const* p_cbw = &p_msc->cbw; + uint16_t const block_sz = rdwr10_get_blocksize(p_cbw); // already verified non-zero - if ( nbytes < 0 ) - { - // negative means error -> pipe is stalled & status in CSW set to failed - p_csw->data_residue = p_cbw->total_bytes - p_msc->xferred_len; - p_csw->status = MSC_CSW_STATUS_FAILED; + // Adjust lba & offset with transferred bytes + uint32_t const lba = rdwr10_get_lba(p_cbw->command) + (p_msc->xferred_len / block_sz); + uint32_t const offset = p_msc->xferred_len % block_sz; - tud_msc_set_sense(p_cbw->lun, SCSI_SENSE_ILLEGAL_REQUEST, 0x20, 0x00); // Sense = Invalid Command Operation - usbd_edpt_stall(rhport, p_msc->ep_in); - } - else if ( nbytes == 0 ) - { - // zero means not ready -> simulate an transfer complete so that this driver callback will fired again - dcd_event_xfer_complete(rhport, p_msc->ep_in, 0, XFER_RESULT_SUCCESS, false); - } - else - { - TU_ASSERT( usbd_edpt_xfer(rhport, p_msc->ep_in, _mscd_buf, nbytes), ); + p_msc->pending_io = 1; + int32_t nbytes = tud_msc_write10_cb(p_cbw->lun, lba, offset, _mscd_epbuf.buf, xferred_bytes); + if (nbytes != TUD_MSC_RET_ASYNC) { + p_msc->pending_io = 0; + proc_write_io_data(p_msc, xferred_bytes, nbytes); } } -static void proc_write10_cmd(uint8_t rhport, mscd_interface_t* p_msc) -{ - msc_cbw_t const * p_cbw = &p_msc->cbw; - bool writable = true; - if (tud_msc_is_writable_cb) { - writable = tud_msc_is_writable_cb(p_cbw->lun); - } - if (!writable) { - msc_csw_t* p_csw = &p_msc->csw; - p_csw->data_residue = p_cbw->total_bytes; - p_csw->status = MSC_CSW_STATUS_FAILED; +static void proc_write_io_data(mscd_interface_t* p_msc, uint32_t xferred_bytes, int32_t nbytes) { + if (nbytes < 0) { + // nbytes is status + switch (nbytes) { + case TUD_MSC_RET_ERROR: + // IO error -> failed this scsi op + TU_LOG_DRV(" IO write() failed\r\n"); + set_sense_medium_not_present(p_msc->cbw.lun); + fail_scsi_op(p_msc, MSC_CSW_STATUS_FAILED); + break; - tud_msc_set_sense(p_cbw->lun, SCSI_SENSE_DATA_PROTECT, 0x27, 0x00); // Sense = Write protected - usbd_edpt_stall(rhport, p_msc->ep_out); - return; - } + default: break; + } + } else { + if ((uint32_t)nbytes < xferred_bytes) { + // Application consume less than what we got including TUD_MSC_RET_BUSY (0) + const uint32_t left_over = xferred_bytes - (uint32_t)nbytes; + if (nbytes > 0) { + memmove(_mscd_epbuf.buf, _mscd_epbuf.buf + nbytes, left_over); + } - // remaining bytes capped at class buffer - int32_t nbytes = (int32_t) tu_min32(sizeof(_mscd_buf), p_cbw->total_bytes-p_msc->xferred_len); + // fake a transfer complete with adjusted parameters --> callback will be invoked with adjusted parameters + dcd_event_xfer_complete(p_msc->rhport, p_msc->ep_out, left_over, XFER_RESULT_SUCCESS, false); + } else { + // Application consume all bytes in our buffer + p_msc->xferred_len += xferred_bytes; - // Write10 callback will be called later when usb transfer complete - TU_ASSERT( usbd_edpt_xfer(rhport, p_msc->ep_out, _mscd_buf, nbytes), ); + if (p_msc->xferred_len >= p_msc->total_len) { + // Data Stage is complete + p_msc->stage = MSC_STAGE_STATUS; + } else { + // prepare to receive more data from host + proc_write10_cmd(p_msc); + } + } + } } #endif diff --git a/cores/nRF5/TinyUSB/Adafruit_TinyUSB_ArduinoCore/tinyusb/src/class/msc/msc_device.h b/cores/nRF5/TinyUSB/Adafruit_TinyUSB_ArduinoCore/tinyusb/src/class/msc/msc_device.h index 3aa93ff..7d898e9 100644 --- a/cores/nRF5/TinyUSB/Adafruit_TinyUSB_ArduinoCore/tinyusb/src/class/msc/msc_device.h +++ b/cores/nRF5/TinyUSB/Adafruit_TinyUSB_ArduinoCore/tinyusb/src/class/msc/msc_device.h @@ -1,4 +1,4 @@ -/* +/* * The MIT License (MIT) * * Copyright (c) 2019 Ha Thach (tinyusb.org) @@ -28,7 +28,6 @@ #define _TUSB_MSC_DEVICE_H_ #include "common/tusb_common.h" -#include "device/usbd.h" #include "msc.h" #ifdef __cplusplus @@ -49,61 +48,57 @@ #error CFG_TUD_MSC_EP_BUFSIZE must be defined, value of a block size should work well, the more the better #endif +// Return value of callback functions +enum { + TUD_MSC_RET_BUSY = 0, // Busy, e.g disk I/O is not ready + TUD_MSC_RET_ERROR = -1, + TUD_MSC_RET_ASYNC = -2, // Asynchronous IO +}; + TU_VERIFY_STATIC(CFG_TUD_MSC_EP_BUFSIZE < UINT16_MAX, "Size is not correct"); -/** \addtogroup ClassDriver_MSC - * @{ - * \defgroup MSC_Device Device - * @{ */ +//--------------------------------------------------------------------+ +// Application API +//--------------------------------------------------------------------+ +// Set SCSI sense response bool tud_msc_set_sense(uint8_t lun, uint8_t sense_key, uint8_t add_sense_code, uint8_t add_sense_qualifier); +// Called by Application once asynchronous I/O operation is done +// bytes_io is number of bytes in I/O op, typically the bufsize in read/write_cb() or +// TUD_MSC_RET_ERROR (-1) for error. Note TUD_MSC_RET_BUSY (0) will be treated as error as well. +bool tud_msc_async_io_done(int32_t bytes_io, bool in_isr); + //--------------------------------------------------------------------+ // Application Callbacks (WEAK is optional) //--------------------------------------------------------------------+ -/** - * Invoked when received \ref SCSI_CMD_READ_10 command - * \param[in] lun Logical unit number - * \param[in] lba Logical Block Address to be read - * \param[in] offset Byte offset from LBA - * \param[out] buffer Buffer which application need to update with the response data. - * \param[in] bufsize Requested bytes - * - * \return Number of byte read, if it is less than requested bytes by \a \b bufsize. Tinyusb will transfer - * this amount first and invoked this again for remaining data. - * - * \retval zero Indicate application is not ready yet to response e.g disk I/O is not complete. - * tinyusb will invoke this callback with the same parameters again some time later. - * - * \retval negative Indicate error e.g reading disk I/O. tinyusb will \b STALL the corresponding - * endpoint and return failed status in command status wrapper phase. - */ +/* + Invoked when received SCSI READ10/WRITE10 command + - Address = lba * BLOCK_SIZE + offset + - offset is only needed if CFG_TUD_MSC_EP_BUFSIZE is smaller than BLOCK_SIZE. + - Application fill the buffer (up to bufsize) with address contents and return number of bytes read or status. + - 0 < ret < bufsize: These bytes are transferred first and callback will be invoked again for remaining data. + - TUD_MSC_RET_BUSY + Application is buys e.g disk I/O not ready. Callback will be invoked again with the same parameters later on. + - TUD_MSC_RET_ERROR + error such as invalid address. This request will be STALLed and scsi command will be failed + - TUD_MSC_RET_ASYNC + Data I/O will be done asynchronously in a background task. Application should return immediately. + tud_msc_async_io_done() must be called once IO/ is done to signal completion. +*/ int32_t tud_msc_read10_cb (uint8_t lun, uint32_t lba, uint32_t offset, void* buffer, uint32_t bufsize); - -/** - * Invoked when received \ref SCSI_CMD_WRITE_10 command - * \param[in] lun Logical unit number - * \param[in] lba Logical Block Address to be write - * \param[in] offset Byte offset from LBA - * \param[out] buffer Buffer which holds written data. - * \param[in] bufsize Requested bytes - * - * \return Number of byte written, if it is less than requested bytes by \a \b bufsize. Tinyusb will proceed with - * other work and invoked this again with adjusted parameters. - * - * \retval zero Indicate application is not ready yet e.g disk I/O is not complete. - * Tinyusb will invoke this callback with the same parameters again some time later. - * - * \retval negative Indicate error writing disk I/O. Tinyusb will \b STALL the corresponding - * endpoint and return failed status in command status wrapper phase. - */ int32_t tud_msc_write10_cb (uint8_t lun, uint32_t lba, uint32_t offset, uint8_t* buffer, uint32_t bufsize); -// Invoked when received SCSI_CMD_INQUIRY +// Invoked when received SCSI_CMD_INQUIRY, v1, application should use v2 if possible // Application fill vendor id, product id and revision with string up to 8, 16, 4 characters respectively void tud_msc_inquiry_cb(uint8_t lun, uint8_t vendor_id[8], uint8_t product_id[16], uint8_t product_rev[4]); +// Invoked when received SCSI_CMD_INQUIRY, v2 with full inquiry response +// Some inquiry_resp's fields are already filled with default values, application can update them +// Return length of inquiry response, typically sizeof(scsi_inquiry_resp_t) (36 bytes), can be longer if included vendor data. +uint32_t tud_msc_inquiry2_cb(uint8_t lun, scsi_inquiry_resp_t *inquiry_resp, uint32_t bufsize); + // Invoked when received Test Unit Ready command. // return true allowing host to read/write this LUN e.g SD card inserted bool tud_msc_test_unit_ready_cb(uint8_t lun); @@ -133,37 +128,40 @@ int32_t tud_msc_scsi_cb (uint8_t lun, uint8_t const scsi_cmd[16], void* buffer, /*------------- Optional callbacks -------------*/ // Invoked when received GET_MAX_LUN request, required for multiple LUNs implementation -TU_ATTR_WEAK uint8_t tud_msc_get_maxlun_cb(void); +uint8_t tud_msc_get_maxlun_cb(void); // Invoked when received Start Stop Unit command // - Start = 0 : stopped power mode, if load_eject = 1 : unload disk storage // - Start = 1 : active mode, if load_eject = 1 : load disk storage -TU_ATTR_WEAK bool tud_msc_start_stop_cb(uint8_t lun, uint8_t power_condition, bool start, bool load_eject); +bool tud_msc_start_stop_cb(uint8_t lun, uint8_t power_condition, bool start, bool load_eject); + +//Invoked when we receive the Prevent / Allow Medium Removal command +bool tud_msc_prevent_allow_medium_removal_cb(uint8_t lun, uint8_t prohibit_removal, uint8_t control); + +// Invoked when received REQUEST_SENSE +int32_t tud_msc_request_sense_cb(uint8_t lun, void* buffer, uint16_t bufsize); // Invoked when Read10 command is complete -TU_ATTR_WEAK void tud_msc_read10_complete_cb(uint8_t lun); +void tud_msc_read10_complete_cb(uint8_t lun); // Invoke when Write10 command is complete, can be used to flush flash caching -TU_ATTR_WEAK void tud_msc_write10_complete_cb(uint8_t lun); +void tud_msc_write10_complete_cb(uint8_t lun); // Invoked when command in tud_msc_scsi_cb is complete -TU_ATTR_WEAK void tud_msc_scsi_complete_cb(uint8_t lun, uint8_t const scsi_cmd[16]); - -// Hook to make a mass storage device read-only. TODO remove -TU_ATTR_WEAK bool tud_msc_is_writable_cb(uint8_t lun); +void tud_msc_scsi_complete_cb(uint8_t lun, uint8_t const scsi_cmd[16]); -/** @} */ -/** @} */ +// Invoked to check if device is writable as part of SCSI WRITE10 +bool tud_msc_is_writable_cb(uint8_t lun); //--------------------------------------------------------------------+ // Internal Class Driver API //--------------------------------------------------------------------+ -void mscd_init (void); -void mscd_reset (uint8_t rhport); -uint16_t mscd_open (uint8_t rhport, tusb_desc_interface_t const * itf_desc, uint16_t max_len); -bool mscd_control_request (uint8_t rhport, tusb_control_request_t const * p_request); -bool mscd_control_complete (uint8_t rhport, tusb_control_request_t const * p_request); -bool mscd_xfer_cb (uint8_t rhport, uint8_t ep_addr, xfer_result_t event, uint32_t xferred_bytes); +void mscd_init (void); +bool mscd_deinit (void); +void mscd_reset (uint8_t rhport); +uint16_t mscd_open (uint8_t rhport, tusb_desc_interface_t const * itf_desc, uint16_t max_len); +bool mscd_control_xfer_cb (uint8_t rhport, uint8_t stage, tusb_control_request_t const * p_request); +bool mscd_xfer_cb (uint8_t rhport, uint8_t ep_addr, xfer_result_t event, uint32_t xferred_bytes); #ifdef __cplusplus } diff --git a/cores/nRF5/TinyUSB/Adafruit_TinyUSB_ArduinoCore/tinyusb/src/class/msc/msc_host.c b/cores/nRF5/TinyUSB/Adafruit_TinyUSB_ArduinoCore/tinyusb/src/class/msc/msc_host.c new file mode 100644 index 0000000..eb69ae4 --- /dev/null +++ b/cores/nRF5/TinyUSB/Adafruit_TinyUSB_ArduinoCore/tinyusb/src/class/msc/msc_host.c @@ -0,0 +1,517 @@ +/* + * 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. + */ + +#include "tusb_option.h" + +#if CFG_TUH_ENABLED && CFG_TUH_MSC + +#include "host/usbh.h" +#include "host/usbh_pvt.h" + +#include "msc_host.h" + +// Level where CFG_TUSB_DEBUG must be at least for this driver is logged +#ifndef CFG_TUH_MSC_LOG_LEVEL + #define CFG_TUH_MSC_LOG_LEVEL CFG_TUH_LOG_LEVEL +#endif + +#define TU_LOG_DRV(...) TU_LOG(CFG_TUH_MSC_LOG_LEVEL, __VA_ARGS__) + +//--------------------------------------------------------------------+ +// MACRO CONSTANT TYPEDEF +//--------------------------------------------------------------------+ +enum { + MSC_STAGE_IDLE = 0, + MSC_STAGE_CMD, + MSC_STAGE_DATA, + MSC_STAGE_STATUS, +}; + +typedef struct { + uint8_t itf_num; + uint8_t ep_in; + uint8_t ep_out; + uint8_t max_lun; + + volatile bool configured; // Receive SET_CONFIGURE + volatile bool mounted; // Enumeration is complete + + // SCSI command data + uint8_t stage; + void* buffer; + tuh_msc_complete_cb_t complete_cb; + uintptr_t complete_arg; + + struct { + uint32_t block_size; + uint32_t block_count; + } capacity[CFG_TUH_MSC_MAXLUN]; +} msch_interface_t; + +typedef struct { + TUH_EPBUF_TYPE_DEF(msc_cbw_t, cbw); + TUH_EPBUF_TYPE_DEF(msc_csw_t, csw); +} msch_epbuf_t; + +static msch_interface_t _msch_itf[CFG_TUH_DEVICE_MAX]; +CFG_TUH_MEM_SECTION static msch_epbuf_t _msch_epbuf[CFG_TUH_DEVICE_MAX]; + +TU_ATTR_ALWAYS_INLINE static inline msch_interface_t* get_itf(uint8_t daddr) { + return &_msch_itf[daddr - 1]; +} + +TU_ATTR_ALWAYS_INLINE static inline msch_epbuf_t* get_epbuf(uint8_t daddr) { + return &_msch_epbuf[daddr - 1]; +} + +//--------------------------------------------------------------------+ +// Weak stubs: invoked if no strong implementation is available +//--------------------------------------------------------------------+ +TU_ATTR_WEAK void tuh_msc_mount_cb(uint8_t dev_addr) { + (void) dev_addr; +} + +TU_ATTR_WEAK void tuh_msc_umount_cb(uint8_t dev_addr) { + (void) dev_addr; +} + +//--------------------------------------------------------------------+ +// PUBLIC API +//--------------------------------------------------------------------+ +uint8_t tuh_msc_get_maxlun(uint8_t dev_addr) { + msch_interface_t* p_msc = get_itf(dev_addr); + return p_msc->max_lun; +} + +uint32_t tuh_msc_get_block_count(uint8_t dev_addr, uint8_t lun) { + msch_interface_t* p_msc = get_itf(dev_addr); + return p_msc->capacity[lun].block_count; +} + +uint32_t tuh_msc_get_block_size(uint8_t dev_addr, uint8_t lun) { + msch_interface_t* p_msc = get_itf(dev_addr); + return p_msc->capacity[lun].block_size; +} + +bool tuh_msc_mounted(uint8_t dev_addr) { + msch_interface_t* p_msc = get_itf(dev_addr); + return p_msc->mounted; +} + +bool tuh_msc_ready(uint8_t dev_addr) { + msch_interface_t* p_msc = get_itf(dev_addr); + return p_msc->mounted && !usbh_edpt_busy(dev_addr, p_msc->ep_in) && !usbh_edpt_busy(dev_addr, p_msc->ep_out); +} + +//--------------------------------------------------------------------+ +// PUBLIC API: SCSI COMMAND +//--------------------------------------------------------------------+ +static inline void cbw_init(msc_cbw_t* cbw, uint8_t lun) { + tu_memclr(cbw, sizeof(msc_cbw_t)); + cbw->signature = MSC_CBW_SIGNATURE; + cbw->tag = 0x54555342; // TUSB + cbw->lun = lun; +} + +bool tuh_msc_scsi_command(uint8_t daddr, msc_cbw_t const* cbw, void* data, + tuh_msc_complete_cb_t complete_cb, uintptr_t arg) { + msch_interface_t* p_msc = get_itf(daddr); + TU_VERIFY(p_msc->configured); + + // claim endpoint + TU_VERIFY(usbh_edpt_claim(daddr, p_msc->ep_out)); + msch_epbuf_t* epbuf = get_epbuf(daddr); + + epbuf->cbw = *cbw; + p_msc->buffer = data; + p_msc->complete_cb = complete_cb; + p_msc->complete_arg = arg; + p_msc->stage = MSC_STAGE_CMD; + + if (!usbh_edpt_xfer(daddr, p_msc->ep_out, (uint8_t*) &epbuf->cbw, sizeof(msc_cbw_t))) { + usbh_edpt_release(daddr, p_msc->ep_out); + return false; + } + + return true; +} + +bool tuh_msc_read_capacity(uint8_t dev_addr, uint8_t lun, scsi_read_capacity10_resp_t* response, + tuh_msc_complete_cb_t complete_cb, uintptr_t arg) { + msch_interface_t* p_msc = get_itf(dev_addr); + TU_VERIFY(p_msc->configured); + + msc_cbw_t cbw; + cbw_init(&cbw, lun); + + cbw.total_bytes = sizeof(scsi_read_capacity10_resp_t); + cbw.dir = TUSB_DIR_IN_MASK; + cbw.cmd_len = sizeof(scsi_read_capacity10_t); + cbw.command[0] = SCSI_CMD_READ_CAPACITY_10; + + return tuh_msc_scsi_command(dev_addr, &cbw, response, complete_cb, arg); +} + +bool tuh_msc_inquiry(uint8_t dev_addr, uint8_t lun, scsi_inquiry_resp_t* response, + tuh_msc_complete_cb_t complete_cb, uintptr_t arg) { + msch_interface_t* p_msc = get_itf(dev_addr); + TU_VERIFY(p_msc->mounted); + + msc_cbw_t cbw; + cbw_init(&cbw, lun); + + cbw.total_bytes = sizeof(scsi_inquiry_resp_t); + cbw.dir = TUSB_DIR_IN_MASK; + cbw.cmd_len = sizeof(scsi_inquiry_t); + + scsi_inquiry_t const cmd_inquiry = { + .cmd_code = SCSI_CMD_INQUIRY, + .alloc_length = sizeof(scsi_inquiry_resp_t) + }; + memcpy(cbw.command, &cmd_inquiry, cbw.cmd_len); + + return tuh_msc_scsi_command(dev_addr, &cbw, response, complete_cb, arg); +} + +bool tuh_msc_test_unit_ready(uint8_t dev_addr, uint8_t lun, tuh_msc_complete_cb_t complete_cb, uintptr_t arg) { + msch_interface_t* p_msc = get_itf(dev_addr); + TU_VERIFY(p_msc->configured); + + msc_cbw_t cbw; + cbw_init(&cbw, lun); + + cbw.total_bytes = 0; + cbw.dir = TUSB_DIR_OUT; + cbw.cmd_len = sizeof(scsi_test_unit_ready_t); + cbw.command[0] = SCSI_CMD_TEST_UNIT_READY; + cbw.command[1] = lun; // according to wiki TODO need verification + + return tuh_msc_scsi_command(dev_addr, &cbw, NULL, complete_cb, arg); +} + +bool tuh_msc_request_sense(uint8_t dev_addr, uint8_t lun, void* response, + tuh_msc_complete_cb_t complete_cb, uintptr_t arg) { + msc_cbw_t cbw; + cbw_init(&cbw, lun); + + cbw.total_bytes = 18; // TODO sense response + cbw.dir = TUSB_DIR_IN_MASK; + cbw.cmd_len = sizeof(scsi_request_sense_t); + + scsi_request_sense_t const cmd_request_sense = { + .cmd_code = SCSI_CMD_REQUEST_SENSE, + .alloc_length = 18 + }; + memcpy(cbw.command, &cmd_request_sense, cbw.cmd_len); + + return tuh_msc_scsi_command(dev_addr, &cbw, response, complete_cb, arg); +} + +bool tuh_msc_read10(uint8_t dev_addr, uint8_t lun, void* buffer, uint32_t lba, uint16_t block_count, + tuh_msc_complete_cb_t complete_cb, uintptr_t arg) { + msch_interface_t* p_msc = get_itf(dev_addr); + TU_VERIFY(p_msc->mounted); + + msc_cbw_t cbw; + cbw_init(&cbw, lun); + + cbw.total_bytes = block_count * p_msc->capacity[lun].block_size; + cbw.dir = TUSB_DIR_IN_MASK; + cbw.cmd_len = sizeof(scsi_read10_t); + + scsi_read10_t const cmd_read10 = { + .cmd_code = SCSI_CMD_READ_10, + .lba = tu_htonl(lba), + .block_count = tu_htons(block_count) + }; + memcpy(cbw.command, &cmd_read10, cbw.cmd_len); + + return tuh_msc_scsi_command(dev_addr, &cbw, buffer, complete_cb, arg); +} + +bool tuh_msc_write10(uint8_t dev_addr, uint8_t lun, void const* buffer, uint32_t lba, uint16_t block_count, + tuh_msc_complete_cb_t complete_cb, uintptr_t arg) { + msch_interface_t* p_msc = get_itf(dev_addr); + TU_VERIFY(p_msc->mounted); + + msc_cbw_t cbw; + cbw_init(&cbw, lun); + + cbw.total_bytes = block_count * p_msc->capacity[lun].block_size; + cbw.dir = TUSB_DIR_OUT; + cbw.cmd_len = sizeof(scsi_write10_t); + + scsi_write10_t const cmd_write10 = { + .cmd_code = SCSI_CMD_WRITE_10, + .lba = tu_htonl(lba), + .block_count = tu_htons(block_count) + }; + memcpy(cbw.command, &cmd_write10, cbw.cmd_len); + + return tuh_msc_scsi_command(dev_addr, &cbw, (void*) (uintptr_t) buffer, complete_cb, arg); +} + +#if 0 +// MSC interface Reset (not used now) +bool tuh_msc_reset(uint8_t dev_addr) { + tusb_control_request_t const new_request = { + .bmRequestType_bit = { + .recipient = TUSB_REQ_RCPT_INTERFACE, + .type = TUSB_REQ_TYPE_CLASS, + .direction = TUSB_DIR_OUT + }, + .bRequest = MSC_REQ_RESET, + .wValue = 0, + .wIndex = p_msc->itf_num, + .wLength = 0 + }; + TU_ASSERT( usbh_control_xfer( dev_addr, &new_request, NULL ) ); +} +#endif + +//--------------------------------------------------------------------+ +// CLASS-USBH API +//--------------------------------------------------------------------+ +bool msch_init(void) { + TU_LOG_DRV("sizeof(msch_interface_t) = %u\r\n", sizeof(msch_interface_t)); + TU_LOG_DRV("sizeof(msch_epbuf_t) = %u\r\n", sizeof(msch_epbuf_t)); + tu_memclr(_msch_itf, sizeof(_msch_itf)); + return true; +} + +bool msch_deinit(void) { + return true; +} + +void msch_close(uint8_t dev_addr) { + TU_VERIFY(dev_addr <= CFG_TUH_DEVICE_MAX,); + msch_interface_t* p_msc = get_itf(dev_addr); + TU_VERIFY(p_msc->configured,); + + TU_LOG_DRV(" MSCh close addr = %d\r\n", dev_addr); + + // invoke Application Callback + if (p_msc->mounted) { + tuh_msc_umount_cb(dev_addr); + } + + tu_memclr(p_msc, sizeof(msch_interface_t)); +} + +bool msch_xfer_cb(uint8_t dev_addr, uint8_t ep_addr, xfer_result_t event, uint32_t xferred_bytes) { + msch_interface_t* p_msc = get_itf(dev_addr); + msch_epbuf_t* epbuf = get_epbuf(dev_addr); + msc_cbw_t const * cbw = &epbuf->cbw; + msc_csw_t * csw = &epbuf->csw; + + switch (p_msc->stage) { + case MSC_STAGE_CMD: + // Must be Command Block + TU_ASSERT(ep_addr == p_msc->ep_out && event == XFER_RESULT_SUCCESS && xferred_bytes == sizeof(msc_cbw_t)); + if (cbw->total_bytes && p_msc->buffer) { + // Data stage if any + p_msc->stage = MSC_STAGE_DATA; + uint8_t const ep_data = (cbw->dir & TUSB_DIR_IN_MASK) ? p_msc->ep_in : p_msc->ep_out; + TU_ASSERT(usbh_edpt_xfer(dev_addr, ep_data, p_msc->buffer, (uint16_t) cbw->total_bytes)); + break; + } + + TU_ATTR_FALLTHROUGH; // fallthrough to status stage + + case MSC_STAGE_DATA: + // Status stage + p_msc->stage = MSC_STAGE_STATUS; + TU_ASSERT(usbh_edpt_xfer(dev_addr, p_msc->ep_in, (uint8_t*) csw, (uint16_t) sizeof(msc_csw_t))); + break; + + case MSC_STAGE_STATUS: + // SCSI op is complete + p_msc->stage = MSC_STAGE_IDLE; + + if (p_msc->complete_cb) { + tuh_msc_complete_data_t const cb_data = { + .cbw = cbw, + .csw = csw, + .scsi_data = p_msc->buffer, + .user_arg = p_msc->complete_arg + }; + p_msc->complete_cb(dev_addr, &cb_data); + } + break; + + // unknown state + default: + break; + } + + return true; +} + +//--------------------------------------------------------------------+ +// MSC Enumeration +//--------------------------------------------------------------------+ +static void config_get_maxlun_complete(tuh_xfer_t* xfer); +static bool config_test_unit_ready_complete(uint8_t dev_addr, tuh_msc_complete_data_t const* cb_data); +static bool config_request_sense_complete(uint8_t dev_addr, tuh_msc_complete_data_t const* cb_data); +static bool config_read_capacity_complete(uint8_t dev_addr, tuh_msc_complete_data_t const* cb_data); + +bool msch_open(uint8_t rhport, uint8_t dev_addr, tusb_desc_interface_t const* desc_itf, uint16_t max_len) { + (void) rhport; + TU_VERIFY (MSC_SUBCLASS_SCSI == desc_itf->bInterfaceSubClass && + MSC_PROTOCOL_BOT == desc_itf->bInterfaceProtocol); + + // msc driver length is fixed + uint16_t const drv_len = (uint16_t) (sizeof(tusb_desc_interface_t) + + desc_itf->bNumEndpoints * sizeof(tusb_desc_endpoint_t)); + TU_ASSERT(drv_len <= max_len); + + msch_interface_t* p_msc = get_itf(dev_addr); + tusb_desc_endpoint_t const* ep_desc = (tusb_desc_endpoint_t const*) tu_desc_next(desc_itf); + + for (uint32_t i = 0; i < 2; i++) { + TU_ASSERT(TUSB_DESC_ENDPOINT == ep_desc->bDescriptorType && TUSB_XFER_BULK == ep_desc->bmAttributes.xfer); + TU_ASSERT(tuh_edpt_open(dev_addr, ep_desc)); + + if (TUSB_DIR_IN == tu_edpt_dir(ep_desc->bEndpointAddress)) { + p_msc->ep_in = ep_desc->bEndpointAddress; + } else { + p_msc->ep_out = ep_desc->bEndpointAddress; + } + + ep_desc = (tusb_desc_endpoint_t const*) tu_desc_next(ep_desc); + } + + p_msc->itf_num = desc_itf->bInterfaceNumber; + + return true; +} + +bool msch_set_config(uint8_t daddr, uint8_t itf_num) { + msch_interface_t* p_msc = get_itf(daddr); + TU_ASSERT(p_msc->itf_num == itf_num); + p_msc->configured = true; + + //------------- Get Max Lun -------------// + TU_LOG_DRV("MSC Get Max Lun\r\n"); + tusb_control_request_t const request = { + .bmRequestType_bit = { + .recipient = TUSB_REQ_RCPT_INTERFACE, + .type = TUSB_REQ_TYPE_CLASS, + .direction = TUSB_DIR_IN + }, + .bRequest = MSC_REQ_GET_MAX_LUN, + .wValue = 0, + .wIndex = itf_num, + .wLength = 1 + }; + + uint8_t* enum_buf = usbh_get_enum_buf(); + tuh_xfer_t xfer = { + .daddr = daddr, + .ep_addr = 0, + .setup = &request, + .buffer = enum_buf, + .complete_cb = config_get_maxlun_complete, + .user_data = 0 + }; + TU_ASSERT(tuh_control_xfer(&xfer)); + + return true; +} + +static void config_get_maxlun_complete(tuh_xfer_t* xfer) { + uint8_t const daddr = xfer->daddr; + msch_interface_t* p_msc = get_itf(daddr); + + // MAXLUN's response is minus 1 by specs, STALL means 1 + if (XFER_RESULT_SUCCESS == xfer->result) { + uint8_t* enum_buf = usbh_get_enum_buf(); + p_msc->max_lun = enum_buf[0] + 1; + } else { + p_msc->max_lun = 1; + } + + TU_LOG_DRV(" Max LUN = %u\r\n", p_msc->max_lun); + + // TODO multiple LUN support + TU_LOG_DRV("SCSI Test Unit Ready\r\n"); + uint8_t const lun = 0; + tuh_msc_test_unit_ready(daddr, lun, config_test_unit_ready_complete, 0); +} + +static bool config_test_unit_ready_complete(uint8_t dev_addr, tuh_msc_complete_data_t const* cb_data) { + msc_cbw_t const* cbw = cb_data->cbw; + msc_csw_t const* csw = cb_data->csw; + uint8_t* enum_buf = usbh_get_enum_buf(); + + if (csw->status == 0) { + // Unit is ready, read its capacity + TU_LOG_DRV("SCSI Read Capacity\r\n"); + tuh_msc_read_capacity(dev_addr, cbw->lun, (scsi_read_capacity10_resp_t*) (uintptr_t) enum_buf, + config_read_capacity_complete, 0); + } else { + // Note: During enumeration, some device fails Test Unit Ready and require a few retries + // with Request Sense to start working !! + // TODO limit number of retries + TU_LOG_DRV("SCSI Request Sense\r\n"); + TU_ASSERT(tuh_msc_request_sense(dev_addr, cbw->lun, enum_buf, config_request_sense_complete, 0)); + } + + return true; +} + +static bool config_request_sense_complete(uint8_t dev_addr, tuh_msc_complete_data_t const* cb_data) { + msc_cbw_t const* cbw = cb_data->cbw; + msc_csw_t const* csw = cb_data->csw; + + TU_ASSERT(csw->status == 0); + TU_ASSERT(tuh_msc_test_unit_ready(dev_addr, cbw->lun, config_test_unit_ready_complete, 0)); + return true; +} + +static bool config_read_capacity_complete(uint8_t dev_addr, tuh_msc_complete_data_t const* cb_data) { + msc_cbw_t const* cbw = cb_data->cbw; + msc_csw_t const* csw = cb_data->csw; + TU_ASSERT(csw->status == 0); + msch_interface_t* p_msc = get_itf(dev_addr); + uint8_t* enum_buf = usbh_get_enum_buf(); + + // Capacity response field: Block size and Last LBA are both Big-Endian + scsi_read_capacity10_resp_t* resp = (scsi_read_capacity10_resp_t*) (uintptr_t) enum_buf; + p_msc->capacity[cbw->lun].block_count = tu_ntohl(resp->last_lba) + 1; + p_msc->capacity[cbw->lun].block_size = tu_ntohl(resp->block_size); + + // Mark enumeration is complete + p_msc->mounted = true; + tuh_msc_mount_cb(dev_addr); + + // notify usbh that driver enumeration is complete + usbh_driver_set_config_complete(dev_addr, p_msc->itf_num); + + return true; +} + +#endif diff --git a/cores/nRF5/TinyUSB/Adafruit_TinyUSB_ArduinoCore/tinyusb/src/class/msc/msc_host.h b/cores/nRF5/TinyUSB/Adafruit_TinyUSB_ArduinoCore/tinyusb/src/class/msc/msc_host.h new file mode 100644 index 0000000..b5fd555 --- /dev/null +++ b/cores/nRF5/TinyUSB/Adafruit_TinyUSB_ArduinoCore/tinyusb/src/class/msc/msc_host.h @@ -0,0 +1,132 @@ +/* + * The MIT License (MIT) + * + * Copyright (c) 2019 Ha Thach (tinyusb.org) + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + * This file is part of the TinyUSB stack. + */ + +#ifndef TUSB_MSC_HOST_H_ +#define TUSB_MSC_HOST_H_ + +#include "msc.h" + +#ifdef __cplusplus + extern "C" { +#endif + +//--------------------------------------------------------------------+ +// Class Driver Configuration +//--------------------------------------------------------------------+ + +#ifndef CFG_TUH_MSC_MAXLUN +#define CFG_TUH_MSC_MAXLUN 4 +#endif + +typedef struct { + msc_cbw_t const* cbw; // SCSI command + msc_csw_t const* csw; // SCSI status + void* scsi_data; // SCSI Data + uintptr_t user_arg; // user argument +}tuh_msc_complete_data_t; + +typedef bool (*tuh_msc_complete_cb_t)(uint8_t dev_addr, tuh_msc_complete_data_t const* cb_data); + +//--------------------------------------------------------------------+ +// Application API +//--------------------------------------------------------------------+ + +// Check if device supports MassStorage interface. +// This function true after tuh_msc_mounted_cb() and false after tuh_msc_unmounted_cb() +bool tuh_msc_mounted(uint8_t dev_addr); + +// Check if the interface is currently ready or busy transferring data +bool tuh_msc_ready(uint8_t dev_addr); + +// Get Max Lun +uint8_t tuh_msc_get_maxlun(uint8_t dev_addr); + +// Get number of block +uint32_t tuh_msc_get_block_count(uint8_t dev_addr, uint8_t lun); + +// Get block size in bytes +uint32_t tuh_msc_get_block_size(uint8_t dev_addr, uint8_t lun); + +// Perform a full SCSI command (cbw, data, csw) in non-blocking manner. +// Complete callback is invoked when SCSI op is complete. +// return true if success, false if there is already pending operation. +// NOTE: buffer must be accessible by USB/DMA controller, aligned correctly and multiple of cache line if enabled +bool tuh_msc_scsi_command(uint8_t daddr, msc_cbw_t const* cbw, void* data, tuh_msc_complete_cb_t complete_cb, uintptr_t arg); + +// Perform SCSI Inquiry command +// Complete callback is invoked when SCSI op is complete. +// NOTE: response must be accessible by USB/DMA controller, aligned correctly and multiple of cache line if enabled +bool tuh_msc_inquiry(uint8_t dev_addr, uint8_t lun, scsi_inquiry_resp_t* response, tuh_msc_complete_cb_t complete_cb, uintptr_t arg); + +// Perform SCSI Test Unit Ready command +// Complete callback is invoked when SCSI op is complete. +bool tuh_msc_test_unit_ready(uint8_t dev_addr, uint8_t lun, tuh_msc_complete_cb_t complete_cb, uintptr_t arg); + +// Perform SCSI Request Sense 10 command +// Complete callback is invoked when SCSI op is complete. +// NOTE: response must be accessible by USB/DMA controller, aligned correctly and multiple of cache line if enabled +bool tuh_msc_request_sense(uint8_t dev_addr, uint8_t lun, void *response, tuh_msc_complete_cb_t complete_cb, uintptr_t arg); + +// Perform SCSI Read 10 command. Read n blocks starting from LBA to buffer +// Complete callback is invoked when SCSI op is complete. +// NOTE: buffer must be accessible by USB/DMA controller, aligned correctly and multiple of cache line if enabled +bool tuh_msc_read10(uint8_t dev_addr, uint8_t lun, void * buffer, uint32_t lba, uint16_t block_count, tuh_msc_complete_cb_t complete_cb, uintptr_t arg); + +// Perform SCSI Write 10 command. Write n blocks starting from LBA to device +// Complete callback is invoked when SCSI op is complete. +// NOTE: buffer must be accessible by USB/DMA controller, aligned correctly and multiple of cache line if enabled +bool tuh_msc_write10(uint8_t dev_addr, uint8_t lun, void const * buffer, uint32_t lba, uint16_t block_count, tuh_msc_complete_cb_t complete_cb, uintptr_t arg); + +// Perform SCSI Read Capacity 10 command +// Complete callback is invoked when SCSI op is complete. +// Note: during enumeration, host stack already carried out this request. Application can retrieve capacity by +// simply call tuh_msc_get_block_count() and tuh_msc_get_block_size() +bool tuh_msc_read_capacity(uint8_t dev_addr, uint8_t lun, scsi_read_capacity10_resp_t* response, tuh_msc_complete_cb_t complete_cb, uintptr_t arg); + +//------------- Application Callback -------------// + +// Invoked when a device with MassStorage interface is mounted +void tuh_msc_mount_cb(uint8_t dev_addr); + +// Invoked when a device with MassStorage interface is unmounted +void tuh_msc_umount_cb(uint8_t dev_addr); + +//--------------------------------------------------------------------+ +// Internal Class Driver API +//--------------------------------------------------------------------+ + +bool msch_init (void); +bool msch_deinit (void); +bool msch_open (uint8_t rhport, uint8_t dev_addr, tusb_desc_interface_t const *desc_itf, uint16_t max_len); +bool msch_set_config (uint8_t daddr, uint8_t itf_num); +void msch_close (uint8_t dev_addr); +bool msch_xfer_cb (uint8_t dev_addr, uint8_t ep_addr, xfer_result_t event, uint32_t xferred_bytes); + +#ifdef __cplusplus + } +#endif + +#endif diff --git a/cores/nRF5/TinyUSB/Adafruit_TinyUSB_ArduinoCore/tinyusb/src/class/net/ecm_rndis_device.c b/cores/nRF5/TinyUSB/Adafruit_TinyUSB_ArduinoCore/tinyusb/src/class/net/ecm_rndis_device.c new file mode 100644 index 0000000..299eb97 --- /dev/null +++ b/cores/nRF5/TinyUSB/Adafruit_TinyUSB_ArduinoCore/tinyusb/src/class/net/ecm_rndis_device.c @@ -0,0 +1,432 @@ +/* + * The MIT License (MIT) + * + * Copyright (c) 2020 Peter Lawrence + * 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. + */ + +#include "tusb_option.h" + +#if ( CFG_TUD_ENABLED && CFG_TUD_ECM_RNDIS ) + +#include "device/usbd.h" +#include "device/usbd_pvt.h" + +#include "net_device.h" +#include "rndis_protocol.h" + +extern void rndis_class_set_handler(uint8_t *data, int size); /* found in ./misc/networking/rndis_reports.c */ + +#define CFG_TUD_NET_PACKET_PREFIX_LEN sizeof(rndis_data_packet_t) +#define CFG_TUD_NET_PACKET_SUFFIX_LEN 0 + +#define NETD_PACKET_SIZE (CFG_TUD_NET_PACKET_PREFIX_LEN + CFG_TUD_NET_MTU + CFG_TUD_NET_PACKET_PREFIX_LEN) +#define NETD_CONTROL_SIZE 120 + +//--------------------------------------------------------------------+ +// MACRO CONSTANT TYPEDEF +//--------------------------------------------------------------------+ +typedef struct { + uint8_t itf_num; // Index number of Management Interface, +1 for Data Interface + uint8_t itf_data_alt; // Alternate setting of Data Interface. 0 : inactive, 1 : active + + uint8_t ep_notif; + uint8_t ep_in; + uint8_t ep_out; + + bool ecm_mode; + + // Endpoint descriptor use to open/close when receiving SetInterface + // TODO since configuration descriptor may not be long-lived memory, we should + // keep a copy of endpoint attribute instead + uint8_t const * ecm_desc_epdata; +} netd_interface_t; + +typedef struct ecm_notify_struct { + tusb_control_request_t header; + uint32_t downlink, uplink; +} ecm_notify_t; + +typedef struct { + TUD_EPBUF_DEF(rx, NETD_PACKET_SIZE); + TUD_EPBUF_DEF(tx, NETD_PACKET_SIZE); + + TUD_EPBUF_DEF(notify, sizeof(ecm_notify_t)); + TUD_EPBUF_DEF(ctrl, NETD_CONTROL_SIZE); +} netd_epbuf_t; + +//--------------------------------------------------------------------+ +// INTERNAL OBJECT & FUNCTION DECLARATION +//--------------------------------------------------------------------+ +static netd_interface_t _netd_itf; +CFG_TUD_MEM_SECTION static netd_epbuf_t _netd_epbuf; +static bool can_xmit; +static bool ecm_link_is_up = true; // Store link state for ECM mode + +void tud_network_recv_renew(void) { + usbd_edpt_xfer(0, _netd_itf.ep_out, _netd_epbuf.rx, NETD_PACKET_SIZE); +} + +static void do_in_xfer(uint8_t *buf, uint16_t len) { + can_xmit = false; + usbd_edpt_xfer(0, _netd_itf.ep_in, buf, len); +} + +void netd_report(uint8_t *buf, uint16_t len) { + const uint8_t rhport = 0; + len = tu_min16(len, sizeof(ecm_notify_t)); + + if (!usbd_edpt_claim(rhport, _netd_itf.ep_notif)) { + TU_LOG1("ECM: Failed to claim notification endpoint\n"); + return; + } + + memcpy(_netd_epbuf.notify, buf, len); + usbd_edpt_xfer(rhport, _netd_itf.ep_notif, _netd_epbuf.notify, len); +} + +//--------------------------------------------------------------------+ +// USBD Driver API +//--------------------------------------------------------------------+ +void netd_init(void) { + tu_memclr(&_netd_itf, sizeof(_netd_itf)); +} + +bool netd_deinit(void) { + return true; +} + +void netd_reset(uint8_t rhport) { + (void) rhport; + netd_init(); +} + +uint16_t netd_open(uint8_t rhport, tusb_desc_interface_t const * itf_desc, uint16_t max_len) { + bool const is_rndis = (TUD_RNDIS_ITF_CLASS == itf_desc->bInterfaceClass && + TUD_RNDIS_ITF_SUBCLASS == itf_desc->bInterfaceSubClass && + TUD_RNDIS_ITF_PROTOCOL == itf_desc->bInterfaceProtocol); + + bool const is_ecm = (TUSB_CLASS_CDC == itf_desc->bInterfaceClass && + CDC_COMM_SUBCLASS_ETHERNET_CONTROL_MODEL == itf_desc->bInterfaceSubClass && + 0x00 == itf_desc->bInterfaceProtocol); + + TU_VERIFY(is_rndis || is_ecm, 0); + + // confirm interface hasn't already been allocated + TU_ASSERT(0 == _netd_itf.ep_notif, 0); + + // sanity check the descriptor + _netd_itf.ecm_mode = is_ecm; + + //------------- Management Interface -------------// + _netd_itf.itf_num = itf_desc->bInterfaceNumber; + + uint16_t drv_len = sizeof(tusb_desc_interface_t); + uint8_t const * p_desc = tu_desc_next( itf_desc ); + + // Communication Functional Descriptors + while (TUSB_DESC_CS_INTERFACE == tu_desc_type(p_desc) && drv_len <= max_len) { + drv_len += tu_desc_len(p_desc); + p_desc = tu_desc_next(p_desc); + } + + // notification endpoint (if any) + if (TUSB_DESC_ENDPOINT == tu_desc_type(p_desc)) { + TU_ASSERT(usbd_edpt_open(rhport, (tusb_desc_endpoint_t const *) p_desc), 0); + + _netd_itf.ep_notif = ((tusb_desc_endpoint_t const*)p_desc)->bEndpointAddress; + + drv_len += tu_desc_len(p_desc); + p_desc = tu_desc_next(p_desc); + } + + //------------- Data Interface -------------// + // - RNDIS Data followed immediately by a pair of endpoints + // - CDC-ECM data interface has 2 alternate settings + // - 0 : zero endpoints for inactive (default) + // - 1 : IN & OUT endpoints for active networking + TU_ASSERT(TUSB_DESC_INTERFACE == tu_desc_type(p_desc), 0); + + do { + tusb_desc_interface_t const * data_itf_desc = (tusb_desc_interface_t const *) p_desc; + TU_ASSERT(TUSB_CLASS_CDC_DATA == data_itf_desc->bInterfaceClass, 0); + + drv_len += tu_desc_len(p_desc); + p_desc = tu_desc_next(p_desc); + } while (_netd_itf.ecm_mode && (TUSB_DESC_INTERFACE == tu_desc_type(p_desc)) && (drv_len <= max_len)); + + // Pair of endpoints + TU_ASSERT(TUSB_DESC_ENDPOINT == tu_desc_type(p_desc), 0); + + if (_netd_itf.ecm_mode) { + // ECM by default is in-active, save the endpoint attribute + // to open later when received setInterface + _netd_itf.ecm_desc_epdata = p_desc; + } else { + // Open endpoint pair for RNDIS + TU_ASSERT(usbd_open_edpt_pair(rhport, p_desc, 2, TUSB_XFER_BULK, &_netd_itf.ep_out, &_netd_itf.ep_in), 0); + + // we are ready to transmit a packet + can_xmit = true; + + // prepare for incoming packets + tud_network_recv_renew(); + } + + drv_len += 2*sizeof(tusb_desc_endpoint_t); + + return drv_len; +} + +static void ecm_report(bool nc) { + ecm_notify_t ecm_notify_nc = { + .header = { + .bmRequestType = 0xA1, + .bRequest = 0, /* NETWORK_CONNECTION aka NetworkConnection */ + .wValue = ecm_link_is_up ? 1 : 0, /* Use current link state */ + .wLength = 0, + }, + }; + + const ecm_notify_t ecm_notify_csc = { + .header = { + .bmRequestType = 0xA1, + .bRequest = 0x2A, /* CONNECTION_SPEED_CHANGE aka ConnectionSpeedChange */ + .wLength = 8, + }, + .downlink = 9728000, + .uplink = 9728000, + }; + + ecm_notify_t notify = (nc) ? ecm_notify_nc : ecm_notify_csc; + notify.header.wIndex = _netd_itf.itf_num; + netd_report((uint8_t *)¬ify, (nc) ? sizeof(notify.header) : sizeof(notify)); +} + +// Invoked when a control transfer occurred on an interface of this class +// Driver response accordingly to the request and the transfer stage (setup/data/ack) +// return false to stall control endpoint (e.g unsupported request) +bool netd_control_xfer_cb (uint8_t rhport, uint8_t stage, tusb_control_request_t const * request) { + if (stage == CONTROL_STAGE_SETUP) { + switch (request->bmRequestType_bit.type) { + case TUSB_REQ_TYPE_STANDARD: + switch (request->bRequest) { + case TUSB_REQ_GET_INTERFACE: { + uint8_t const req_itfnum = (uint8_t)request->wIndex; + TU_VERIFY(_netd_itf.itf_num+1 == req_itfnum); + + tud_control_xfer(rhport, request, &_netd_itf.itf_data_alt, 1); + } + break; + + case TUSB_REQ_SET_INTERFACE: { + uint8_t const req_itfnum = (uint8_t)request->wIndex; + uint8_t const req_alt = (uint8_t)request->wValue; + + // Only valid for Data Interface with Alternate is either 0 or 1 + TU_VERIFY(_netd_itf.itf_num+1 == req_itfnum && req_alt < 2); + + // ACM-ECM only: qequest to enable/disable network activities + TU_VERIFY(_netd_itf.ecm_mode); + + _netd_itf.itf_data_alt = req_alt; + + if (_netd_itf.itf_data_alt) { + // TODO since we don't actually close endpoint + // hack here to not re-open it + if (_netd_itf.ep_in == 0 && _netd_itf.ep_out == 0) { + TU_ASSERT(_netd_itf.ecm_desc_epdata); + TU_ASSERT( + usbd_open_edpt_pair(rhport, _netd_itf.ecm_desc_epdata, 2, TUSB_XFER_BULK, &_netd_itf.ep_out, & + _netd_itf.ep_in)); + + // TODO should be merge with RNDIS's after endpoint opened + // Also should have opposite callback for application to disable network !! + can_xmit = true; // we are ready to transmit a packet + tud_network_recv_renew(); // prepare for incoming packets + } + } else { + // TODO close the endpoint pair + // For now pretend that we did, this should have no harm since host won't try to + // communicate with the endpoints again + // _netd_itf.ep_in = _netd_itf.ep_out = 0 + } + + tud_control_status(rhport, request); + } + break; + + // unsupported request + default: return false; + } + break; + + case TUSB_REQ_TYPE_CLASS: + TU_VERIFY(_netd_itf.itf_num == request->wIndex); + + if (_netd_itf.ecm_mode) { + /* the only required CDC-ECM Management Element Request is SetEthernetPacketFilter */ + if (0x43 /* SET_ETHERNET_PACKET_FILTER */ == request->bRequest) { + tud_control_xfer(rhport, request, NULL, 0); + // Only send connection notification if link is up + if (ecm_link_is_up) { + ecm_report(true); + } + } + } else { + if (request->bmRequestType_bit.direction == TUSB_DIR_IN) { + rndis_generic_msg_t* rndis_msg = (rndis_generic_msg_t*)((void*)_netd_epbuf.ctrl); + uint32_t msglen = tu_le32toh(rndis_msg->MessageLength); + TU_ASSERT(msglen <= NETD_CONTROL_SIZE); + tud_control_xfer(rhport, request, _netd_epbuf.ctrl, (uint16_t)msglen); + } else { + tud_control_xfer(rhport, request, _netd_epbuf.ctrl, NETD_CONTROL_SIZE); + } + } + break; + + // unsupported request + default: return false; + } + } else if (stage == CONTROL_STAGE_DATA) { + // Handle RNDIS class control OUT only + if (request->bmRequestType_bit.type == TUSB_REQ_TYPE_CLASS && + request->bmRequestType_bit.direction == TUSB_DIR_OUT && + _netd_itf.itf_num == request->wIndex) { + if (!_netd_itf.ecm_mode) { + rndis_class_set_handler(_netd_epbuf.ctrl, request->wLength); + } + } + } + + return true; +} + +static void handle_incoming_packet(uint32_t len) { + uint8_t* pnt = _netd_epbuf.rx; + uint32_t size = 0; + + if (_netd_itf.ecm_mode) { + size = len; + } else { + rndis_data_packet_t* r = (rndis_data_packet_t*)((void*)pnt); + if (len >= sizeof(rndis_data_packet_t)) { + if ((r->MessageType == REMOTE_NDIS_PACKET_MSG) && (r->MessageLength <= len)) { + if ((r->DataOffset + offsetof(rndis_data_packet_t, DataOffset) + r->DataLength) <= len) { + pnt = &_netd_epbuf.rx[r->DataOffset + offsetof(rndis_data_packet_t, DataOffset)]; + size = r->DataLength; + } + } + } + } + + if (!tud_network_recv_cb(pnt, (uint16_t)size)) { + /* if a buffer was never handled by user code, we must renew on the user's behalf */ + tud_network_recv_renew(); + } +} + +bool netd_xfer_cb(uint8_t rhport, uint8_t ep_addr, xfer_result_t result, uint32_t xferred_bytes) { + (void)rhport; + (void)result; + + /* new packet received */ + if (ep_addr == _netd_itf.ep_out) { + handle_incoming_packet(xferred_bytes); + } + + /* data transmission finished */ + if (ep_addr == _netd_itf.ep_in) { + /* TinyUSB requires the class driver to implement ZLP (since ZLP usage is class-specific) */ + + if (xferred_bytes && (0 == (xferred_bytes % CFG_TUD_NET_ENDPOINT_SIZE))) { + do_in_xfer(NULL, 0); /* a ZLP is needed */ + } else { + /* we're finally finished */ + can_xmit = true; + } + } + + if (_netd_itf.ecm_mode && (ep_addr == _netd_itf.ep_notif)) { + // Notification transfer complete - endpoint is now free + // Don't automatically send speed change notification after link state changes + } + + return true; +} + +bool tud_network_can_xmit(uint16_t size) { + (void)size; + return can_xmit; +} + +void tud_network_xmit(void *ref, uint16_t arg) { + if (!can_xmit) { + return; + } + + uint16_t len = (_netd_itf.ecm_mode) ? 0 : CFG_TUD_NET_PACKET_PREFIX_LEN; + uint8_t* data = _netd_epbuf.tx + len; + + len += tud_network_xmit_cb(data, ref, arg); + + if (!_netd_itf.ecm_mode) { + rndis_data_packet_t *hdr = (rndis_data_packet_t *) ((void*) _netd_epbuf.tx); + memset(hdr, 0, sizeof(rndis_data_packet_t)); + hdr->MessageType = REMOTE_NDIS_PACKET_MSG; + hdr->MessageLength = len; + hdr->DataOffset = sizeof(rndis_data_packet_t) - offsetof(rndis_data_packet_t, DataOffset); + hdr->DataLength = len - sizeof(rndis_data_packet_t); + } + + do_in_xfer(_netd_epbuf.tx, len); +} + +// Set the network link state (up/down) and notify the host +void tud_network_link_state(uint8_t rhport, bool is_up) { + (void)rhport; + + if (_netd_itf.ecm_mode) { + ecm_link_is_up = is_up; + + // For ECM mode, send network connection notification only + // Don't trigger speed change notification for link state changes + ecm_notify_t notify = { + .header = { + .bmRequestType = 0xA1, + .bRequest = 0, /* NETWORK_CONNECTION */ + .wValue = is_up ? 1 : 0, /* 0 = disconnected, 1 = connected */ + .wLength = 0, + }, + }; + notify.header.wIndex = _netd_itf.itf_num; + netd_report((uint8_t *)¬ify, sizeof(notify.header)); + } else { + // For RNDIS mode, we would need to implement RNDIS status indication + // This is more complex and requires RNDIS_INDICATE_STATUS_MSG + // For now, RNDIS doesn't support dynamic link state changes + (void)is_up; + } +} + +#endif diff --git a/cores/nRF5/TinyUSB/Adafruit_TinyUSB_ArduinoCore/tinyusb/src/class/net/ncm.h b/cores/nRF5/TinyUSB/Adafruit_TinyUSB_ArduinoCore/tinyusb/src/class/net/ncm.h new file mode 100644 index 0000000..0245a87 --- /dev/null +++ b/cores/nRF5/TinyUSB/Adafruit_TinyUSB_ArduinoCore/tinyusb/src/class/net/ncm.h @@ -0,0 +1,164 @@ +/* + * The MIT License (MIT) + * + * Copyright (c) 2021, Ha Thach (tinyusb.org) + * Copyright (c) 2024, Hardy Griech + * + * 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_NCM_H_ +#define _TUSB_NCM_H_ + +#include "common/tusb_common.h" + +// NTB buffers size for reception side, must be >> MTU to avoid TCP retransmission (driver issue ?) +// Linux use 2048 as minimal size +#ifndef CFG_TUD_NCM_OUT_NTB_MAX_SIZE + #define CFG_TUD_NCM_OUT_NTB_MAX_SIZE 3200 +#endif + +// NTB buffers size for reception side, must be > MTU +// Linux use 2048 as minimal size +#ifndef CFG_TUD_NCM_IN_NTB_MAX_SIZE + #define CFG_TUD_NCM_IN_NTB_MAX_SIZE 3200 +#endif + +// Number of NTB buffers for reception side +// Depending on the configuration, this parameter could be increased with the cost of additional RAM requirements +// On Full-Speed (RP2040) : +// 1 - good performance +// 2 - up to 30% more performance with iperf with small packets +// >2 - no performance gain +// On High-Speed (STM32F7) : +// No performance gain +#ifndef CFG_TUD_NCM_OUT_NTB_N + #define CFG_TUD_NCM_OUT_NTB_N 1 +#endif + +// Number of NTB buffers for transmission side +// Depending on the configuration, this parameter could be increased with the cost of additional RAM requirements +// On Full-Speed (RP2040) : +// 1 - good performance but SystemView shows lost events (on load test) +// 2 - up to 50% more performance with iperf with small packets, "tud_network_can_xmit: request blocked" +// happens from time to time with SystemView +// 3 - "tud_network_can_xmit: request blocked" never happens +// >3 - no performance gain +// On High-Speed (STM32F7) : +// No performance gain +#ifndef CFG_TUD_NCM_IN_NTB_N + #define CFG_TUD_NCM_IN_NTB_N 1 +#endif + +// How many datagrams it is allowed to put into an NTB for transmission side +#ifndef CFG_TUD_NCM_IN_MAX_DATAGRAMS_PER_NTB + #define CFG_TUD_NCM_IN_MAX_DATAGRAMS_PER_NTB 8 +#endif + +// This tells the host how many datagrams it is allowed to put into an NTB +#ifndef CFG_TUD_NCM_OUT_MAX_DATAGRAMS_PER_NTB + #define CFG_TUD_NCM_OUT_MAX_DATAGRAMS_PER_NTB 6 +#endif + +// Table 6.2 Class-Specific Request Codes for Network Control Model subclass +typedef enum +{ + NCM_SET_ETHERNET_MULTICAST_FILTERS = 0x40, + NCM_SET_ETHERNET_POWER_MANAGEMENT_PATTERN_FILTER = 0x41, + NCM_GET_ETHERNET_POWER_MANAGEMENT_PATTERN_FILTER = 0x42, + NCM_SET_ETHERNET_PACKET_FILTER = 0x43, + NCM_GET_ETHERNET_STATISTIC = 0x44, + NCM_GET_NTB_PARAMETERS = 0x80, + NCM_GET_NET_ADDRESS = 0x81, + NCM_SET_NET_ADDRESS = 0x82, + NCM_GET_NTB_FORMAT = 0x83, + NCM_SET_NTB_FORMAT = 0x84, + NCM_GET_NTB_INPUT_SIZE = 0x85, + NCM_SET_NTB_INPUT_SIZE = 0x86, + NCM_GET_MAX_DATAGRAM_SIZE = 0x87, + NCM_SET_MAX_DATAGRAM_SIZE = 0x88, + NCM_GET_CRC_MODE = 0x89, + NCM_SET_CRC_MODE = 0x8A, +} ncm_request_code_t; + +#define NTH16_SIGNATURE 0x484D434E +#define NDP16_SIGNATURE_NCM0 0x304D434E +#define NDP16_SIGNATURE_NCM1 0x314D434E + +typedef struct TU_ATTR_PACKED { + uint16_t wLength; + uint16_t bmNtbFormatsSupported; + uint32_t dwNtbInMaxSize; + uint16_t wNdbInDivisor; + uint16_t wNdbInPayloadRemainder; + uint16_t wNdbInAlignment; + uint16_t wReserved; + uint32_t dwNtbOutMaxSize; + uint16_t wNdbOutDivisor; + uint16_t wNdbOutPayloadRemainder; + uint16_t wNdbOutAlignment; + uint16_t wNtbOutMaxDatagrams; +} ntb_parameters_t; + +typedef struct TU_ATTR_PACKED { + uint32_t dwSignature; + uint16_t wHeaderLength; + uint16_t wSequence; + uint16_t wBlockLength; + uint16_t wNdpIndex; +} nth16_t; + +typedef struct TU_ATTR_PACKED { + uint16_t wDatagramIndex; + uint16_t wDatagramLength; +} ndp16_datagram_t; + +typedef struct TU_ATTR_PACKED { + uint32_t dwSignature; + uint16_t wLength; + uint16_t wNextNdpIndex; + //ndp16_datagram_t datagram[]; +} ndp16_t; + +typedef union TU_ATTR_PACKED { + struct { + nth16_t nth; + ndp16_t ndp; + ndp16_datagram_t ndp_datagram[CFG_TUD_NCM_IN_MAX_DATAGRAMS_PER_NTB + 1]; + }; + uint8_t data[CFG_TUD_NCM_IN_NTB_MAX_SIZE]; +} xmit_ntb_t; + +typedef union TU_ATTR_PACKED { + struct { + nth16_t nth; + // only the header is at a guaranteed position + }; + uint8_t data[CFG_TUD_NCM_OUT_NTB_MAX_SIZE]; +} recv_ntb_t; + +typedef struct { + tusb_control_request_t header; + uint32_t downlink; + uint32_t uplink; +} ncm_notify_t; + +#endif diff --git a/cores/nRF5/TinyUSB/Adafruit_TinyUSB_ArduinoCore/tinyusb/src/class/net/ncm_device.c b/cores/nRF5/TinyUSB/Adafruit_TinyUSB_ArduinoCore/tinyusb/src/class/net/ncm_device.c new file mode 100644 index 0000000..02833c5 --- /dev/null +++ b/cores/nRF5/TinyUSB/Adafruit_TinyUSB_ArduinoCore/tinyusb/src/class/net/ncm_device.c @@ -0,0 +1,978 @@ +/* + * The MIT License (MIT) + * + * Copyright (c) 2019 Ha Thach (tinyusb.org) + * Copyright (c) 2024 Hardy Griech + * Copyright (c) 2020 Jacob Berg Potter + * Copyright (c) 2020 Peter Lawrence + * + * 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. + */ + +/** + * Small Glossary (from the spec) + * -------------- + * Datagram - A collection of bytes forming a single item of information, passed as a unit from source to destination. + * NCM - Network Control Model + * NDP - NCM Datagram Pointer: NTB structure that delineates Datagrams (typically Ethernet frames) within an NTB + * NTB - NCM Transfer Block: a data structure for efficient USB encapsulation of one or more datagrams + * Each NTB is designed to be a single USB transfer + * NTH - NTB Header: a data structure at the front of each NTB, which provides the information needed to validate + * the NTB and begin decoding + * + * Some explanations + * ----------------- + * - rhport is the USB port of the device, in most cases "0" + * - itf_data_alt if != 0 -> data xmit/recv are allowed (see spec) + * - ep_in IN endpoints take data from the device intended to go in to the host (the device transmits) + * - ep_out OUT endpoints send data out of the host to the device (the device receives) + */ + +#include "tusb_option.h" + +#if (CFG_TUD_ENABLED && CFG_TUD_NCM) + +#include +#include +#include + +#include "device/usbd.h" +#include "device/usbd_pvt.h" + +#include "ncm.h" +#include "net_device.h" + +// Level where CFG_TUSB_DEBUG must be at least for this driver is logged +#ifndef CFG_TUD_NCM_LOG_LEVEL + #define CFG_TUD_NCM_LOG_LEVEL CFG_TUD_LOG_LEVEL +#endif + +#define TU_LOG_DRV(...) TU_LOG(CFG_TUD_NCM_LOG_LEVEL, __VA_ARGS__) + +// Alignment must be 4 +#define TUD_NCM_ALIGNMENT 4 +// calculate alignment of xmit datagrams within an NTB +#define XMIT_ALIGN_OFFSET(x) ((TUD_NCM_ALIGNMENT - ((x) & (TUD_NCM_ALIGNMENT - 1))) & (TUD_NCM_ALIGNMENT - 1)) + +//----------------------------------------------------------------------------- +// +// Module global things +// +#define XMIT_NTB_N CFG_TUD_NCM_IN_NTB_N +#define RECV_NTB_N CFG_TUD_NCM_OUT_NTB_N + +typedef struct { + // general + uint8_t ep_in; // endpoint for outgoing datagrams (naming is a little bit confusing) + uint8_t ep_out; // endpoint for incoming datagrams (naming is a little bit confusing) + uint8_t ep_notif; // endpoint for notifications + uint8_t itf_num; // interface number + uint8_t itf_data_alt; // ==0 -> no endpoints, i.e. no network traffic, ==1 -> normal operation with two endpoints (spec, chapter 5.3) + uint8_t rhport; // storage of \a rhport because some callbacks are done without it + + // recv handling + recv_ntb_t *recv_free_ntb[RECV_NTB_N]; // free list of recv NTBs + recv_ntb_t *recv_ready_ntb[RECV_NTB_N]; // NTBs waiting for transmission to glue logic + recv_ntb_t *recv_tinyusb_ntb; // buffer for the running transfer TinyUSB -> driver + recv_ntb_t *recv_glue_ntb; // buffer for the running transfer driver -> glue logic + uint16_t recv_glue_ntb_datagram_ndx; // index into \a recv_glue_ntb_datagram + + // xmit handling + xmit_ntb_t *xmit_free_ntb[XMIT_NTB_N]; // free list of xmit NTBs + xmit_ntb_t *xmit_ready_ntb[XMIT_NTB_N]; // NTBs waiting for transmission to TinyUSB + xmit_ntb_t *xmit_tinyusb_ntb; // buffer for the running transfer driver -> TinyUSB + xmit_ntb_t *xmit_glue_ntb; // buffer for the running transfer glue logic -> driver + uint16_t xmit_sequence; // NTB sequence counter + uint16_t xmit_glue_ntb_datagram_ndx; // index into \a xmit_glue_ntb_datagram + + // notification handling + enum { + NOTIFICATION_SPEED, + NOTIFICATION_CONNECTED, + NOTIFICATION_DONE + } notification_xmit_state; // state of notification transmission + bool notification_xmit_is_running; // notification is currently transmitted + bool link_is_up; // current link state + + // misc + bool tud_network_recv_renew_active; // tud_network_recv_renew() is active (avoid recursive invocations) + bool tud_network_recv_renew_process_again; // tud_network_recv_renew() should process again +} ncm_interface_t; + +typedef struct { + struct { + TUD_EPBUF_TYPE_DEF(recv_ntb_t, ntb); + } recv[RECV_NTB_N]; + + struct { + TUD_EPBUF_TYPE_DEF(xmit_ntb_t, ntb); + } xmit[XMIT_NTB_N]; + + TUD_EPBUF_TYPE_DEF(ncm_notify_t, epnotif); +} ncm_epbuf_t; + +static ncm_interface_t ncm_interface; +CFG_TUD_MEM_SECTION static ncm_epbuf_t ncm_epbuf; + +/** + * This is the NTB parameter structure + * + * \attention + * We are lucky, that byte order is correct + */ +TU_ATTR_ALIGNED(4) static const ntb_parameters_t ntb_parameters = { + .wLength = sizeof(ntb_parameters_t), + .bmNtbFormatsSupported = 0x01,// 16-bit NTB supported + .dwNtbInMaxSize = CFG_TUD_NCM_IN_NTB_MAX_SIZE, + .wNdbInDivisor = 1, + .wNdbInPayloadRemainder = 0, + .wNdbInAlignment = TUD_NCM_ALIGNMENT, + .wReserved = 0, + .dwNtbOutMaxSize = CFG_TUD_NCM_OUT_NTB_MAX_SIZE, + .wNdbOutDivisor = 1, + .wNdbOutPayloadRemainder = 0, + .wNdbOutAlignment = TUD_NCM_ALIGNMENT, + .wNtbOutMaxDatagrams = CFG_TUD_NCM_OUT_MAX_DATAGRAMS_PER_NTB, +}; + +// Some confusing remarks about wNtbOutMaxDatagrams... +// ==1 -> SystemView packets/s goes up to 2000 and events are lost during startup +// ==0 -> SystemView runs fine, iperf shows in wireshark a lot of error +// ==6 -> SystemView runs fine, iperf also +// >6 -> iperf starts to show errors +// -> 6 seems to be the best value. Why? Don't know, perhaps only on my system? +// +// iperf: for MSS in 100 200 400 800 1200 1450 1500; do iperf -c 192.168.14.1 -e -i 1 -M $MSS -l 8192 -P 1; sleep 2; done +// sysview: SYSTICKS_PER_SEC=35000, IDLE_US=1000, PRINT_MOD=1000 +// + +//----------------------------------------------------------------------------- +// +// everything about notifications +// + +/** + * Transmit next notification to the host (if appropriate). + * Notifications are transferred to the host once during connection setup. + */ +static void notification_xmit(uint8_t rhport, bool force_next) { + TU_LOG_DRV("notification_xmit(%d, %d) - %d %d\n", force_next, rhport, ncm_interface.notification_xmit_state, ncm_interface.notification_xmit_is_running); + + if (!force_next && ncm_interface.notification_xmit_is_running) { + return; + } + + if (ncm_interface.notification_xmit_state == NOTIFICATION_SPEED) { + TU_LOG_DRV(" NOTIFICATION_SPEED\n"); + ncm_notify_t notify_speed_change = { + .header = { + .bmRequestType_bit = { + .recipient = TUSB_REQ_RCPT_INTERFACE, + .type = TUSB_REQ_TYPE_CLASS, + .direction = TUSB_DIR_IN + }, + .bRequest = CDC_NOTIF_CONNECTION_SPEED_CHANGE, + .wValue = 0, + .wIndex = ncm_interface.itf_num, + .wLength = 8 + } + }; + if (tud_speed_get() == TUSB_SPEED_HIGH) { + notify_speed_change.downlink = 480000000; + notify_speed_change.uplink = 480000000; + } else { + notify_speed_change.downlink = 12000000; + notify_speed_change.uplink = 12000000; + } + + uint16_t notif_len = sizeof(notify_speed_change.header) + notify_speed_change.header.wLength; + ncm_epbuf.epnotif = notify_speed_change; + usbd_edpt_xfer(rhport, ncm_interface.ep_notif, (uint8_t*) &ncm_epbuf.epnotif, notif_len); + + ncm_interface.notification_xmit_state = NOTIFICATION_CONNECTED; + ncm_interface.notification_xmit_is_running = true; + } else if (ncm_interface.notification_xmit_state == NOTIFICATION_CONNECTED) { + TU_LOG_DRV(" NOTIFICATION_CONNECTED\n"); + ncm_notify_t notify_connected = { + .header = { + .bmRequestType_bit = { + .recipient = TUSB_REQ_RCPT_INTERFACE, + .type = TUSB_REQ_TYPE_CLASS, + .direction = TUSB_DIR_IN + }, + .bRequest = CDC_NOTIF_NETWORK_CONNECTION, + .wValue = ncm_interface.link_is_up ? 1 : 0, /* Dynamic link state */ + .wIndex = ncm_interface.itf_num, + .wLength = 0, + }, + }; + + uint16_t notif_len = sizeof(notify_connected.header) + notify_connected.header.wLength; + ncm_epbuf.epnotif = notify_connected; + usbd_edpt_xfer(rhport, ncm_interface.ep_notif, (uint8_t *) &ncm_epbuf.epnotif, notif_len); + + ncm_interface.notification_xmit_state = NOTIFICATION_DONE; + ncm_interface.notification_xmit_is_running = true; + } else { + TU_LOG_DRV(" NOTIFICATION_FINISHED\n"); + ncm_interface.notification_xmit_is_running = false; + } +} // notification_xmit + +//----------------------------------------------------------------------------- +// +// everything about packet transmission (driver -> TinyUSB) +// + +/** + * Put NTB into the transmitter free list. + */ +static void xmit_put_ntb_into_free_list(xmit_ntb_t *free_ntb) { + TU_LOG_DRV("xmit_put_ntb_into_free_list() - %p\n", ncm_interface.xmit_tinyusb_ntb); + + if (free_ntb == NULL) { // can happen due to ZLPs + return; + } + + for (int i = 0; i < XMIT_NTB_N; ++i) { + if (ncm_interface.xmit_free_ntb[i] == NULL) { + ncm_interface.xmit_free_ntb[i] = free_ntb; + return; + } + } + TU_LOG_DRV("(EE) xmit_put_ntb_into_free_list - no entry in free list\n");// this should not happen +} // xmit_put_ntb_into_free_list + +/** + * Get an NTB from the free list + */ +static xmit_ntb_t *xmit_get_free_ntb(void) { + TU_LOG_DRV("xmit_get_free_ntb()\n"); + + for (int i = 0; i < XMIT_NTB_N; ++i) { + if (ncm_interface.xmit_free_ntb[i] != NULL) { + xmit_ntb_t *free = ncm_interface.xmit_free_ntb[i]; + ncm_interface.xmit_free_ntb[i] = NULL; + return free; + } + } + return NULL; +} // xmit_get_free_ntb + +/** + * Put a filled NTB into the ready list + */ +static void xmit_put_ntb_into_ready_list(xmit_ntb_t *ready_ntb) { + TU_LOG_DRV("xmit_put_ntb_into_ready_list(%p) %d\n", ready_ntb, ready_ntb->nth.wBlockLength); + + for (int i = 0; i < XMIT_NTB_N; ++i) { + if (ncm_interface.xmit_ready_ntb[i] == NULL) { + ncm_interface.xmit_ready_ntb[i] = ready_ntb; + return; + } + } + TU_LOG_DRV("(EE) xmit_put_ntb_into_ready_list: ready list full\n");// this should not happen +} // xmit_put_ntb_into_ready_list + +/** + * Get the next NTB from the ready list (and remove it from the list). + * If the ready list is empty, return NULL. + */ +static xmit_ntb_t *xmit_get_next_ready_ntb(void) { + xmit_ntb_t *r = NULL; + + r = ncm_interface.xmit_ready_ntb[0]; + memmove(ncm_interface.xmit_ready_ntb + 0, ncm_interface.xmit_ready_ntb + 1, sizeof(ncm_interface.xmit_ready_ntb) - sizeof(ncm_interface.xmit_ready_ntb[0])); + ncm_interface.xmit_ready_ntb[XMIT_NTB_N - 1] = NULL; + + TU_LOG_DRV("recv_get_next_ready_ntb: %p\n", r); + return r; +} // xmit_get_next_ready_ntb + +/** + * Transmit a ZLP if required + * + * \note + * Insertion of the ZLPs is a little bit different then described in the spec. + * But the below implementation actually works. Don't know if this is a spec + * or TinyUSB issue. + * + * \pre + * This must be called from netd_xfer_cb() so that ep_in is ready + */ +static bool xmit_insert_required_zlp(uint8_t rhport, uint32_t xferred_bytes) { + TU_LOG_DRV("xmit_insert_required_zlp(%d,%ld)\n", rhport, xferred_bytes); + + if (xferred_bytes == 0 || xferred_bytes % CFG_TUD_NET_ENDPOINT_SIZE != 0) { + return false; + } + + TU_ASSERT(ncm_interface.itf_data_alt == 1, false); + TU_ASSERT(!usbd_edpt_busy(rhport, ncm_interface.ep_in), false); + + TU_LOG_DRV("xmit_insert_required_zlp! (%u)\n", (unsigned) xferred_bytes); + + // start transmission of the ZLP + usbd_edpt_xfer(rhport, ncm_interface.ep_in, NULL, 0); + + return true; +} // xmit_insert_required_zlp + +/** + * Start transmission if it there is a waiting packet and if can be done from interface side. + */ +static void xmit_start_if_possible(uint8_t rhport) { + TU_LOG_DRV("xmit_start_if_possible()\n"); + + if (ncm_interface.xmit_tinyusb_ntb != NULL) { + TU_LOG_DRV(" !xmit_start_if_possible 1\n"); + return; + } + if (ncm_interface.itf_data_alt != 1) { + TU_LOG_DRV("(EE) !xmit_start_if_possible 2\n"); + return; + } + if (usbd_edpt_busy(rhport, ncm_interface.ep_in)) { + TU_LOG_DRV(" !xmit_start_if_possible 3\n"); + return; + } + + ncm_interface.xmit_tinyusb_ntb = xmit_get_next_ready_ntb(); + if (ncm_interface.xmit_tinyusb_ntb == NULL) { + if (ncm_interface.xmit_glue_ntb == NULL || ncm_interface.xmit_glue_ntb_datagram_ndx == 0) { + // -> really nothing is waiting + return; + } + ncm_interface.xmit_tinyusb_ntb = ncm_interface.xmit_glue_ntb; + ncm_interface.xmit_glue_ntb = NULL; + } + + #if CFG_TUD_NCM_LOG_LEVEL >= 3 + { + uint16_t len = ncm_interface.xmit_tinyusb_ntb->nth.wBlockLength; + TU_LOG_BUF(3, ncm_interface.xmit_tinyusb_ntb->data[i], len); + } + #endif + + if (ncm_interface.xmit_glue_ntb_datagram_ndx != 1) { + TU_LOG_DRV(">> %d %d\n", ncm_interface.xmit_tinyusb_ntb->nth.wBlockLength, ncm_interface.xmit_glue_ntb_datagram_ndx); + } + + // Kick off an endpoint transfer + usbd_edpt_xfer(0, ncm_interface.ep_in, ncm_interface.xmit_tinyusb_ntb->data, ncm_interface.xmit_tinyusb_ntb->nth.wBlockLength); +} // xmit_start_if_possible + +/** + * check if a new datagram fits into the current NTB + */ +static bool xmit_requested_datagram_fits_into_current_ntb(uint16_t datagram_size) { + TU_LOG_DRV("xmit_requested_datagram_fits_into_current_ntb(%d) - %p %p\n", datagram_size, ncm_interface.xmit_tinyusb_ntb, ncm_interface.xmit_glue_ntb); + + if (ncm_interface.xmit_glue_ntb == NULL) { + return false; + } + if (ncm_interface.xmit_glue_ntb_datagram_ndx >= CFG_TUD_NCM_IN_MAX_DATAGRAMS_PER_NTB) { + return false; + } + if (ncm_interface.xmit_glue_ntb->nth.wBlockLength + datagram_size + XMIT_ALIGN_OFFSET(datagram_size) > CFG_TUD_NCM_IN_NTB_MAX_SIZE) { + return false; + } + return true; +} // xmit_requested_datagram_fits_into_current_ntb + +/** + * Setup an NTB for the glue logic + */ +static bool xmit_setup_next_glue_ntb(void) { + TU_LOG_DRV("xmit_setup_next_glue_ntb - %p\n", ncm_interface.xmit_glue_ntb); + + if (ncm_interface.xmit_glue_ntb != NULL) { + // put NTB into waiting list (the new datagram did not fit in) + xmit_put_ntb_into_ready_list(ncm_interface.xmit_glue_ntb); + } + + ncm_interface.xmit_glue_ntb = xmit_get_free_ntb();// get next buffer (if any) + if (ncm_interface.xmit_glue_ntb == NULL) { + TU_LOG_DRV(" xmit_setup_next_glue_ntb - nothing free\n");// should happen rarely + return false; + } + + ncm_interface.xmit_glue_ntb_datagram_ndx = 0; + + xmit_ntb_t *ntb = ncm_interface.xmit_glue_ntb; + + // Fill in NTB header + ntb->nth.dwSignature = NTH16_SIGNATURE; + ntb->nth.wHeaderLength = sizeof(ntb->nth); + ntb->nth.wSequence = ncm_interface.xmit_sequence++; + ntb->nth.wBlockLength = sizeof(ntb->nth) + sizeof(ntb->ndp) + sizeof(ntb->ndp_datagram); + ntb->nth.wNdpIndex = sizeof(ntb->nth); + + // Fill in NDP16 header and terminator + ntb->ndp.dwSignature = NDP16_SIGNATURE_NCM0; + ntb->ndp.wLength = sizeof(ntb->ndp) + sizeof(ntb->ndp_datagram); + ntb->ndp.wNextNdpIndex = 0; + + memset(ntb->ndp_datagram, 0, sizeof(ntb->ndp_datagram)); + return true; +} // xmit_setup_next_glue_ntb + +//----------------------------------------------------------------------------- +// +// all the recv_*() stuff (TinyUSB -> driver -> glue logic) +// + +/** + * Return pointer to an available receive buffer or NULL. + * Returned buffer (if any) has the size \a CFG_TUD_NCM_OUT_NTB_MAX_SIZE. + */ +static recv_ntb_t *recv_get_free_ntb(void) { + TU_LOG_DRV("recv_get_free_ntb()\n"); + + for (int i = 0; i < RECV_NTB_N; ++i) { + if (ncm_interface.recv_free_ntb[i] != NULL) { + recv_ntb_t *free = ncm_interface.recv_free_ntb[i]; + ncm_interface.recv_free_ntb[i] = NULL; + return free; + } + } + return NULL; +} // recv_get_free_ntb + +/** + * Get the next NTB from the ready list (and remove it from the list). + * If the ready list is empty, return NULL. + */ +static recv_ntb_t *recv_get_next_ready_ntb(void) { + recv_ntb_t *r = NULL; + + r = ncm_interface.recv_ready_ntb[0]; + memmove(ncm_interface.recv_ready_ntb + 0, ncm_interface.recv_ready_ntb + 1, sizeof(ncm_interface.recv_ready_ntb) - sizeof(ncm_interface.recv_ready_ntb[0])); + ncm_interface.recv_ready_ntb[RECV_NTB_N - 1] = NULL; + + TU_LOG_DRV("recv_get_next_ready_ntb: %p\n", r); + return r; +} // recv_get_next_ready_ntb + +/** + * Put NTB into the receiver free list. + */ +static void recv_put_ntb_into_free_list(recv_ntb_t *free_ntb) { + TU_LOG_DRV("recv_put_ntb_into_free_list(%p)\n", free_ntb); + + for (int i = 0; i < RECV_NTB_N; ++i) { + if (ncm_interface.recv_free_ntb[i] == NULL) { + ncm_interface.recv_free_ntb[i] = free_ntb; + return; + } + } + TU_LOG_DRV("(EE) recv_put_ntb_into_free_list - no entry in free list\n");// this should not happen +} // recv_put_ntb_into_free_list + +/** + * \a ready_ntb holds a validated NTB, + * put this buffer into the waiting list. + */ +static void recv_put_ntb_into_ready_list(recv_ntb_t *ready_ntb) { + TU_LOG_DRV("recv_put_ntb_into_ready_list(%p) %d\n", ready_ntb, ready_ntb->nth.wBlockLength); + + for (int i = 0; i < RECV_NTB_N; ++i) { + if (ncm_interface.recv_ready_ntb[i] == NULL) { + ncm_interface.recv_ready_ntb[i] = ready_ntb; + return; + } + } + TU_LOG_DRV("(EE) recv_put_ntb_into_ready_list: ready list full\n");// this should not happen +} // recv_put_ntb_into_ready_list + +/** + * If possible, start a new reception TinyUSB -> driver. + */ +static void recv_try_to_start_new_reception(uint8_t rhport) { + TU_LOG_DRV("recv_try_to_start_new_reception(%d)\n", rhport); + + if (ncm_interface.itf_data_alt != 1) { + return; + } + if (ncm_interface.recv_tinyusb_ntb != NULL) { + return; + } + if (usbd_edpt_busy(rhport, ncm_interface.ep_out)) { + return; + } + + ncm_interface.recv_tinyusb_ntb = recv_get_free_ntb(); + if (ncm_interface.recv_tinyusb_ntb == NULL) { + return; + } + + // initiate transfer + TU_LOG_DRV(" start reception\n"); + bool r = usbd_edpt_xfer(rhport, ncm_interface.ep_out, ncm_interface.recv_tinyusb_ntb->data, CFG_TUD_NCM_OUT_NTB_MAX_SIZE); + if (!r) { + recv_put_ntb_into_free_list(ncm_interface.recv_tinyusb_ntb); + ncm_interface.recv_tinyusb_ntb = NULL; + } +} // recv_try_to_start_new_reception + +/** + * Validate incoming datagram. + * \return true if valid + * + * \note + * \a ndp16->wNextNdpIndex != 0 is not supported + */ +static bool recv_validate_datagram(const recv_ntb_t *ntb, uint32_t len) { + const nth16_t *nth16 = &(ntb->nth); + + TU_LOG_DRV("recv_validate_datagram(%p, %d)\n", ntb, (int) len); + + // check header + if (nth16->wHeaderLength != sizeof(nth16_t)) { + TU_LOG_DRV("(EE) ill nth16 length: %d\n", nth16->wHeaderLength); + return false; + } + if (nth16->dwSignature != NTH16_SIGNATURE) { + TU_LOG_DRV("(EE) ill signature: 0x%08x\n", (unsigned) nth16->dwSignature); + return false; + } + if (len < sizeof(nth16_t) + sizeof(ndp16_t) + 2 * sizeof(ndp16_datagram_t)) { + TU_LOG_DRV("(EE) ill min len: %lu\n", len); + return false; + } + if (nth16->wBlockLength > len) { + TU_LOG_DRV("(EE) ill block length: %d > %lu\n", nth16->wBlockLength, len); + return false; + } + if (nth16->wBlockLength > CFG_TUD_NCM_OUT_NTB_MAX_SIZE) { + TU_LOG_DRV("(EE) ill block length2: %d > %d\n", nth16->wBlockLength, CFG_TUD_NCM_OUT_NTB_MAX_SIZE); + return false; + } + if (nth16->wNdpIndex < sizeof(nth16) || nth16->wNdpIndex > len - (sizeof(ndp16_t) + 2 * sizeof(ndp16_datagram_t))) { + TU_LOG_DRV("(EE) ill position of first ndp: %d (%lu)\n", nth16->wNdpIndex, len); + return false; + } + + // check (first) NDP(16) + const ndp16_t *ndp16 = (const ndp16_t *) (ntb->data + nth16->wNdpIndex); + + if (ndp16->wLength < sizeof(ndp16_t) + 2 * sizeof(ndp16_datagram_t)) { + TU_LOG_DRV("(EE) ill ndp16 length: %d\n", ndp16->wLength); + return false; + } + if (ndp16->dwSignature != NDP16_SIGNATURE_NCM0 && ndp16->dwSignature != NDP16_SIGNATURE_NCM1) { + TU_LOG_DRV("(EE) ill signature: 0x%08x\n", (unsigned) ndp16->dwSignature); + return false; + } + if (ndp16->wNextNdpIndex != 0) { + TU_LOG_DRV("(EE) cannot handle wNextNdpIndex!=0 (%d)\n", ndp16->wNextNdpIndex); + return false; + } + + const ndp16_datagram_t *ndp16_datagram = (const ndp16_datagram_t *) (ntb->data + nth16->wNdpIndex + sizeof(ndp16_t)); + int ndx = 0; + uint16_t max_ndx = (uint16_t) ((ndp16->wLength - sizeof(ndp16_t)) / sizeof(ndp16_datagram_t)); + + if (max_ndx > 2) { // number of datagrams in NTB > 1 + TU_LOG_DRV("<< %d (%d)\n", max_ndx - 1, ntb->nth.wBlockLength); + } + if (ndp16_datagram[max_ndx - 1].wDatagramIndex != 0 || ndp16_datagram[max_ndx - 1].wDatagramLength != 0) { + TU_LOG_DRV(" max_ndx != 0\n"); + return false; + } + while (ndp16_datagram[ndx].wDatagramIndex != 0 && ndp16_datagram[ndx].wDatagramLength != 0) { + TU_LOG_DRV(" << %d %d\n", ndp16_datagram[ndx].wDatagramIndex, ndp16_datagram[ndx].wDatagramLength); + if (ndp16_datagram[ndx].wDatagramIndex > len) { + TU_LOG_DRV("(EE) ill start of datagram[%d]: %d (%lu)\n", ndx, ndp16_datagram[ndx].wDatagramIndex, len); + return false; + } + if (ndp16_datagram[ndx].wDatagramIndex + ndp16_datagram[ndx].wDatagramLength > len) { + TU_LOG_DRV("(EE) ill end of datagram[%d]: %d (%lu)\n", ndx, ndp16_datagram[ndx].wDatagramIndex + ndp16_datagram[ndx].wDatagramLength, len); + return false; + } + ++ndx; + } + + #if CFG_TUD_NCM_LOG_LEVEL >= 3 + TU_LOG_BUF(3, ntb->data[i], len); + #endif + + // -> ntb contains a valid packet structure + // ok... I did not check for garbage within the datagram indices... + return true; +} // recv_validate_datagram + +/** + * Transfer the next (pending) datagram to the glue logic and return receive buffer if empty. + */ +static void recv_transfer_datagram_to_glue_logic(void) { + TU_LOG_DRV("recv_transfer_datagram_to_glue_logic()\n"); + + if (ncm_interface.recv_glue_ntb == NULL) { + ncm_interface.recv_glue_ntb = recv_get_next_ready_ntb(); + TU_LOG_DRV(" new buffer for glue logic: %p\n", ncm_interface.recv_glue_ntb); + ncm_interface.recv_glue_ntb_datagram_ndx = 0; + } + + if (ncm_interface.recv_glue_ntb != NULL) { + const ndp16_datagram_t *ndp16_datagram = (ndp16_datagram_t *) (ncm_interface.recv_glue_ntb->data + ncm_interface.recv_glue_ntb->nth.wNdpIndex + sizeof(ndp16_t)); + + if (ndp16_datagram[ncm_interface.recv_glue_ntb_datagram_ndx].wDatagramIndex == 0) { + TU_LOG_DRV("(EE) SOMETHING WENT WRONG 1\n"); + } else if (ndp16_datagram[ncm_interface.recv_glue_ntb_datagram_ndx].wDatagramLength == 0) { + TU_LOG_DRV("(EE) SOMETHING WENT WRONG 2\n"); + } else { + uint16_t datagramIndex = ndp16_datagram[ncm_interface.recv_glue_ntb_datagram_ndx].wDatagramIndex; + uint16_t datagramLength = ndp16_datagram[ncm_interface.recv_glue_ntb_datagram_ndx].wDatagramLength; + + TU_LOG_DRV(" recv[%d] - %d %d\n", ncm_interface.recv_glue_ntb_datagram_ndx, datagramIndex, datagramLength); + if (tud_network_recv_cb(ncm_interface.recv_glue_ntb->data + datagramIndex, datagramLength)) { + // send datagram successfully to glue logic + TU_LOG_DRV(" OK\n"); + datagramIndex = ndp16_datagram[ncm_interface.recv_glue_ntb_datagram_ndx + 1].wDatagramIndex; + datagramLength = ndp16_datagram[ncm_interface.recv_glue_ntb_datagram_ndx + 1].wDatagramLength; + + if (datagramIndex != 0 && datagramLength != 0) { + // -> next datagram + ++ncm_interface.recv_glue_ntb_datagram_ndx; + } else { + // end of datagrams reached + recv_put_ntb_into_free_list(ncm_interface.recv_glue_ntb); + ncm_interface.recv_glue_ntb = NULL; + } + } + } + } +} // recv_transfer_datagram_to_glue_logic + +//----------------------------------------------------------------------------- +// +// all the tud_network_*() stuff (glue logic -> driver) +// + +/** + * Check if the glue logic is allowed to call tud_network_xmit(). + * This function also fetches a next buffer if required, so that tud_network_xmit() is ready for copy + * and transmission operation. + */ +bool tud_network_can_xmit(uint16_t size) { + TU_LOG_DRV("tud_network_can_xmit(%d)\n", size); + + TU_ASSERT(size <= CFG_TUD_NCM_IN_NTB_MAX_SIZE - (sizeof(nth16_t) + sizeof(ndp16_t) + 2 * sizeof(ndp16_datagram_t)), false); + + if (xmit_requested_datagram_fits_into_current_ntb(size) || xmit_setup_next_glue_ntb()) { + // -> everything is fine + return true; + } + xmit_start_if_possible(ncm_interface.rhport); + TU_LOG_DRV("(II) tud_network_can_xmit: request blocked\n");// could happen if all xmit buffers are full (but should happen rarely) + return false; +} // tud_network_can_xmit + +/** + * Put a datagram into a waiting NTB. + * If currently no transmission is started, then initiate transmission. + */ +void tud_network_xmit(void *ref, uint16_t arg) { + TU_LOG_DRV("tud_network_xmit(%p, %d)\n", ref, arg); + + if (ncm_interface.xmit_glue_ntb == NULL) { + TU_LOG_DRV("(EE) tud_network_xmit: no buffer\n");// must not happen (really) + return; + } + + xmit_ntb_t *ntb = ncm_interface.xmit_glue_ntb; + + // copy new datagram to the end of the current NTB + uint16_t size = tud_network_xmit_cb(ntb->data + ntb->nth.wBlockLength, ref, arg); + + // correct NTB internals + ntb->ndp_datagram[ncm_interface.xmit_glue_ntb_datagram_ndx].wDatagramIndex = ntb->nth.wBlockLength; + ntb->ndp_datagram[ncm_interface.xmit_glue_ntb_datagram_ndx].wDatagramLength = size; + ncm_interface.xmit_glue_ntb_datagram_ndx += 1; + + ntb->nth.wBlockLength += (uint16_t) (size + XMIT_ALIGN_OFFSET(size)); + + if (ntb->nth.wBlockLength > CFG_TUD_NCM_IN_NTB_MAX_SIZE) { + TU_LOG_DRV("(EE) tud_network_xmit: buffer overflow\n"); // must not happen (really) + return; + } + + xmit_start_if_possible(ncm_interface.rhport); +} // tud_network_xmit + +/** + * Keep the receive logic busy and transfer pending packets to the glue logic. + * Avoid recursive calls due to wrong expectations of the net glue logic, + * see https://github.com/hathach/tinyusb/issues/2711 + */ +void tud_network_recv_renew(void) { + TU_LOG_DRV("tud_network_recv_renew()\n"); + + ncm_interface.tud_network_recv_renew_process_again = true; + + if (ncm_interface.tud_network_recv_renew_active) { + TU_LOG_DRV("Re-entrant into tud_network_recv_renew, will process later\n"); + return; + } + + while (ncm_interface.tud_network_recv_renew_process_again) { + ncm_interface.tud_network_recv_renew_process_again = false; + + // If the current function is called within recv_transfer_datagram_to_glue_logic, + // tud_network_recv_renew_process_again will become true, and the loop will run again + // Otherwise the loop will not run again + ncm_interface.tud_network_recv_renew_active = true; + recv_transfer_datagram_to_glue_logic(); + ncm_interface.tud_network_recv_renew_active = false; + } + recv_try_to_start_new_reception(ncm_interface.rhport); +} // tud_network_recv_renew + +/** + * Same as tud_network_recv_renew() but knows \a rhport + */ +static void tud_network_recv_renew_r(uint8_t rhport) { + TU_LOG_DRV("tud_network_recv_renew_r(%d)\n", rhport); + + ncm_interface.rhport = rhport; + tud_network_recv_renew(); +} // tud_network_recv_renew + +/** + * Set the link state and send notification to host + */ +void tud_network_link_state(uint8_t rhport, bool is_up) { + TU_LOG_DRV("tud_network_link_state(%d, %d)\n", rhport, is_up); + + if (ncm_interface.link_is_up == is_up) { + // No change in link state + return; + } + + ncm_interface.link_is_up = is_up; + + // Only send notification if we have an active data interface + if (ncm_interface.itf_data_alt != 1) { + TU_LOG_DRV(" link state notification skipped (interface not active)\n"); + return; + } + + // Reset notification state to send link state update + ncm_interface.notification_xmit_state = NOTIFICATION_CONNECTED; + + // Trigger notification transmission + notification_xmit(rhport, false); +} + +//----------------------------------------------------------------------------- +// +// all the netd_*() stuff (interface TinyUSB -> driver) +// +/** + * Initialize the driver data structures. + * Might be called several times. + */ +void netd_init(void) { + TU_LOG_DRV("netd_init()\n"); + + memset(&ncm_interface, 0, sizeof(ncm_interface)); + + for (int i = 0; i < XMIT_NTB_N; ++i) { + ncm_interface.xmit_free_ntb[i] = &ncm_epbuf.xmit[i].ntb; + } + for (int i = 0; i < RECV_NTB_N; ++i) { + ncm_interface.recv_free_ntb[i] = &ncm_epbuf.recv[i].ntb; + } + // Default link state - can be configured via CFG_TUD_NCM_DEFAULT_LINK_UP + #ifdef CFG_TUD_NCM_DEFAULT_LINK_UP + ncm_interface.link_is_up = CFG_TUD_NCM_DEFAULT_LINK_UP; + #else + ncm_interface.link_is_up = true; // Default to link up if not set. + #endif +} // netd_init + +/** + * Deinit driver + */ +bool netd_deinit(void) { + return true; +} + +/** + * Resets the port. + * In this driver this is the same as netd_init() + */ +void netd_reset(uint8_t rhport) { + (void) rhport; + + netd_init(); +} // netd_reset + +/** + * Open the USB interface. + * - parse the USB descriptor \a TUD_CDC_NCM_DESCRIPTOR for itfnum and endpoints + * - a specific order of elements in the descriptor is tested. + * + * \note + * Actually all of the information could be read directly from \a itf_desc, because the + * structure and the values are well known. But we do it this way. + * + * \post + * - \a itf_num set + * - \a ep_notif, \a ep_in and \a ep_out are set + * - USB interface is open + */ +uint16_t netd_open(uint8_t rhport, tusb_desc_interface_t const *itf_desc, uint16_t max_len) { + TU_ASSERT(ncm_interface.ep_notif == 0, 0);// assure that the interface is only opened once + + ncm_interface.itf_num = itf_desc->bInterfaceNumber;// management interface + + // skip the two first entries and the following TUSB_DESC_CS_INTERFACE entries + uint16_t drv_len = sizeof(tusb_desc_interface_t); + uint8_t const *p_desc = tu_desc_next(itf_desc); + while (tu_desc_type(p_desc) == TUSB_DESC_CS_INTERFACE && drv_len <= max_len) { + drv_len += tu_desc_len(p_desc); + p_desc = tu_desc_next(p_desc); + } + + // get notification endpoint + TU_ASSERT(tu_desc_type(p_desc) == TUSB_DESC_ENDPOINT, 0); + TU_ASSERT(usbd_edpt_open(rhport, (tusb_desc_endpoint_t const *) p_desc), 0); + ncm_interface.ep_notif = ((tusb_desc_endpoint_t const *) p_desc)->bEndpointAddress; + drv_len += tu_desc_len(p_desc); + p_desc = tu_desc_next(p_desc); + + // skip the following TUSB_DESC_INTERFACE entries (which must be TUSB_CLASS_CDC_DATA) + while (tu_desc_type(p_desc) == TUSB_DESC_INTERFACE && drv_len <= max_len) { + tusb_desc_interface_t const *data_itf_desc = (tusb_desc_interface_t const *) p_desc; + TU_ASSERT(data_itf_desc->bInterfaceClass == TUSB_CLASS_CDC_DATA, 0); + + drv_len += tu_desc_len(p_desc); + p_desc = tu_desc_next(p_desc); + } + + // a TUSB_DESC_ENDPOINT (actually two) must follow, open these endpoints + TU_ASSERT(tu_desc_type(p_desc) == TUSB_DESC_ENDPOINT, 0); + TU_ASSERT(usbd_open_edpt_pair(rhport, p_desc, 2, TUSB_XFER_BULK, &ncm_interface.ep_out, &ncm_interface.ep_in)); + drv_len += 2 * sizeof(tusb_desc_endpoint_t); + + return drv_len; +} // netd_open + +/** + * Handle TinyUSB requests to process transfer events. + */ +bool netd_xfer_cb(uint8_t rhport, uint8_t ep_addr, xfer_result_t result, uint32_t xferred_bytes) { + (void) result; + + if (ep_addr == ncm_interface.ep_out) { + // new NTB received + // - make the NTB valid + // - if ready transfer datagrams to the glue logic for further processing + // - if there is a free receive buffer, initiate reception + if (!recv_validate_datagram(ncm_interface.recv_tinyusb_ntb, xferred_bytes)) { + // verification failed: ignore NTB and return it to free + TU_LOG_DRV("Invalid datatagram. Ignoring NTB\n"); + recv_put_ntb_into_free_list(ncm_interface.recv_tinyusb_ntb); + } else { + // packet ok -> put it into ready list + recv_put_ntb_into_ready_list(ncm_interface.recv_tinyusb_ntb); + } + ncm_interface.recv_tinyusb_ntb = NULL; + tud_network_recv_renew_r(rhport); + } else if (ep_addr == ncm_interface.ep_in) { + // transmission of an NTB finished + // - free the transmitted NTB buffer + // - insert ZLPs when necessary + // - if there is another transmit NTB waiting, try to start transmission + xmit_put_ntb_into_free_list(ncm_interface.xmit_tinyusb_ntb); + ncm_interface.xmit_tinyusb_ntb = NULL; + if (!xmit_insert_required_zlp(rhport, xferred_bytes)) { + xmit_start_if_possible(rhport); + } + } else if (ep_addr == ncm_interface.ep_notif) { + // next transfer on notification channel + notification_xmit(rhport, true); + } + + return true; +} // netd_xfer_cb + +/** + * Respond to TinyUSB control requests. + * At startup transmission of notification packets are done here. + */ +bool netd_control_xfer_cb(uint8_t rhport, uint8_t stage, tusb_control_request_t const *request) { + if (stage != CONTROL_STAGE_SETUP) { + return true; + } + + switch (request->bmRequestType_bit.type) { + case TUSB_REQ_TYPE_STANDARD: + + switch (request->bRequest) { + case TUSB_REQ_GET_INTERFACE: { + TU_VERIFY(ncm_interface.itf_num + 1 == request->wIndex, false); + + tud_control_xfer(rhport, request, &ncm_interface.itf_data_alt, 1); + } break; + + case TUSB_REQ_SET_INTERFACE: { + TU_VERIFY(ncm_interface.itf_num + 1 == request->wIndex && request->wValue < 2, false); + + ncm_interface.itf_data_alt = (uint8_t) request->wValue; + + if (ncm_interface.itf_data_alt == 1) { + tud_network_recv_renew_r(rhport); + notification_xmit(rhport, false); + } + tud_control_status(rhport, request); + } break; + + // unsupported request + default: + return false; + } + break; + + case TUSB_REQ_TYPE_CLASS: + TU_VERIFY(ncm_interface.itf_num == request->wIndex, false); + switch (request->bRequest) { + case NCM_GET_NTB_PARAMETERS: { + // transfer NTB parameters to host. + tud_control_xfer(rhport, request, (void *) (uintptr_t) &ntb_parameters, sizeof(ntb_parameters)); + } break; + + // unsupported request + default: + return false; + } + break; + // unsupported request + default: + return false; + } + + return true; +} // netd_control_xfer_cb + +#endif // ( CFG_TUD_ENABLED && CFG_TUD_NCM ) diff --git a/cores/nRF5/TinyUSB/Adafruit_TinyUSB_ArduinoCore/tinyusb/src/class/net/net_device.c b/cores/nRF5/TinyUSB/Adafruit_TinyUSB_ArduinoCore/tinyusb/src/class/net/net_device.c deleted file mode 100644 index 1d7f9ce..0000000 --- a/cores/nRF5/TinyUSB/Adafruit_TinyUSB_ArduinoCore/tinyusb/src/class/net/net_device.c +++ /dev/null @@ -1,464 +0,0 @@ -/* - * The MIT License (MIT) - * - * Copyright (c) 2020 Peter Lawrence - * 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. - */ - -#include "tusb_option.h" - -#if ( TUSB_OPT_DEVICE_ENABLED && CFG_TUD_NET ) - -#include "net_device.h" -#include "device/usbd_pvt.h" -#include "rndis_protocol.h" - -void rndis_class_set_handler(uint8_t *data, int size); /* found in ./misc/networking/rndis_reports.c */ - -//--------------------------------------------------------------------+ -// MACRO CONSTANT TYPEDEF -//--------------------------------------------------------------------+ -typedef struct -{ - uint8_t itf_num; // Index number of Management Interface, +1 for Data Interface - uint8_t itf_data_alt; // Alternate setting of Data Interface. 0 : inactive, 1 : active - - uint8_t ep_notif; - uint8_t ep_in; - uint8_t ep_out; - - bool ecm_mode; - - // Endpoint descriptor use to open/close when receving SetInterface - // TODO since configuration descriptor may not be long-lived memory, we should - // keep a copy of endpoint attribute instead - uint8_t const * ecm_desc_epdata; - -} netd_interface_t; - -#define CFG_TUD_NET_PACKET_PREFIX_LEN sizeof(rndis_data_packet_t) -#define CFG_TUD_NET_PACKET_SUFFIX_LEN 0 - -CFG_TUSB_MEM_SECTION CFG_TUSB_MEM_ALIGN static uint8_t received[CFG_TUD_NET_PACKET_PREFIX_LEN + CFG_TUD_NET_MTU + CFG_TUD_NET_PACKET_PREFIX_LEN]; -CFG_TUSB_MEM_SECTION CFG_TUSB_MEM_ALIGN static uint8_t transmitted[CFG_TUD_NET_PACKET_PREFIX_LEN + CFG_TUD_NET_MTU + CFG_TUD_NET_PACKET_PREFIX_LEN]; - -struct ecm_notify_struct -{ - tusb_control_request_t header; - uint32_t downlink, uplink; -}; - -static const struct ecm_notify_struct ecm_notify_nc = -{ - .header = { - .bmRequestType = 0xA1, - .bRequest = 0 /* NETWORK_CONNECTION aka NetworkConnection */, - .wValue = 1 /* Connected */, - .wLength = 0, - }, -}; - -static const struct ecm_notify_struct ecm_notify_csc = -{ - .header = { - .bmRequestType = 0xA1, - .bRequest = 0x2A /* CONNECTION_SPEED_CHANGE aka ConnectionSpeedChange */, - .wLength = 8, - }, - .downlink = 9728000, - .uplink = 9728000, -}; - -// TODO remove CFG_TUSB_MEM_SECTION, control internal buffer is already in this special section -CFG_TUSB_MEM_SECTION CFG_TUSB_MEM_ALIGN static union -{ - uint8_t rndis_buf[120]; - struct ecm_notify_struct ecm_buf; -} notify; - -//--------------------------------------------------------------------+ -// INTERNAL OBJECT & FUNCTION DECLARATION -//--------------------------------------------------------------------+ -// TODO remove CFG_TUSB_MEM_SECTION -CFG_TUSB_MEM_SECTION static netd_interface_t _netd_itf; - -static bool can_xmit; - -void tud_network_recv_renew(void) -{ - usbd_edpt_xfer(TUD_OPT_RHPORT, _netd_itf.ep_out, received, sizeof(received)); -} - -static void do_in_xfer(uint8_t *buf, uint16_t len) -{ - can_xmit = false; - usbd_edpt_xfer(TUD_OPT_RHPORT, _netd_itf.ep_in, buf, len); -} - -void netd_report(uint8_t *buf, uint16_t len) -{ - usbd_edpt_xfer(TUD_OPT_RHPORT, _netd_itf.ep_notif, buf, len); -} - -//--------------------------------------------------------------------+ -// USBD Driver API -//--------------------------------------------------------------------+ -void netd_init(void) -{ - tu_memclr(&_netd_itf, sizeof(_netd_itf)); -} - -void netd_reset(uint8_t rhport) -{ - (void) rhport; - - netd_init(); -} - -uint16_t netd_open(uint8_t rhport, tusb_desc_interface_t const * itf_desc, uint16_t max_len) -{ - bool const is_rndis = (TUD_RNDIS_ITF_CLASS == itf_desc->bInterfaceClass && - TUD_RNDIS_ITF_SUBCLASS == itf_desc->bInterfaceSubClass && - TUD_RNDIS_ITF_PROTOCOL == itf_desc->bInterfaceProtocol); - - bool const is_ecm = (TUSB_CLASS_CDC == itf_desc->bInterfaceClass && - CDC_COMM_SUBCLASS_ETHERNET_NETWORKING_CONTROL_MODEL == itf_desc->bInterfaceSubClass && - 0x00 == itf_desc->bInterfaceProtocol); - - TU_VERIFY(is_rndis || is_ecm, 0); - - // confirm interface hasn't already been allocated - TU_ASSERT(0 == _netd_itf.ep_notif, 0); - - // sanity check the descriptor - _netd_itf.ecm_mode = is_ecm; - - //------------- Management Interface -------------// - _netd_itf.itf_num = itf_desc->bInterfaceNumber; - - uint16_t drv_len = sizeof(tusb_desc_interface_t); - uint8_t const * p_desc = tu_desc_next( itf_desc ); - - // Communication Functional Descriptors - while ( TUSB_DESC_CS_INTERFACE == tu_desc_type(p_desc) && drv_len <= max_len ) - { - drv_len += tu_desc_len(p_desc); - p_desc = tu_desc_next(p_desc); - } - - // notification endpoint (if any) - if ( TUSB_DESC_ENDPOINT == tu_desc_type(p_desc) ) - { - TU_ASSERT( usbd_edpt_open(rhport, (tusb_desc_endpoint_t const *) p_desc), 0 ); - - _netd_itf.ep_notif = ((tusb_desc_endpoint_t const *) p_desc)->bEndpointAddress; - - drv_len += tu_desc_len(p_desc); - p_desc = tu_desc_next(p_desc); - } - - //------------- Data Interface -------------// - // - RNDIS Data followed immediately by a pair of endpoints - // - CDC-ECM data interface has 2 alternate settings - // - 0 : zero endpoints for inactive (default) - // - 1 : IN & OUT endpoints for active networking - TU_ASSERT(TUSB_DESC_INTERFACE == tu_desc_type(p_desc), 0); - - do - { - tusb_desc_interface_t const * data_itf_desc = (tusb_desc_interface_t const *) p_desc; - TU_ASSERT(TUSB_CLASS_CDC_DATA == data_itf_desc->bInterfaceClass, 0); - - drv_len += tu_desc_len(p_desc); - p_desc = tu_desc_next(p_desc); - }while( _netd_itf.ecm_mode && (TUSB_DESC_INTERFACE == tu_desc_type(p_desc)) && (drv_len <= max_len) ); - - // Pair of endpoints - TU_ASSERT(TUSB_DESC_ENDPOINT == tu_desc_type(p_desc), 0); - - if ( _netd_itf.ecm_mode ) - { - // ECM by default is in-active, save the endpoint attribute - // to open later when received setInterface - _netd_itf.ecm_desc_epdata = p_desc; - }else - { - // Open endpoint pair for RNDIS - TU_ASSERT( usbd_open_edpt_pair(rhport, p_desc, 2, TUSB_XFER_BULK, &_netd_itf.ep_out, &_netd_itf.ep_in), 0 ); - - tud_network_init_cb(); - - // we are ready to transmit a packet - can_xmit = true; - - // prepare for incoming packets - tud_network_recv_renew(); - } - - drv_len += 2*sizeof(tusb_desc_endpoint_t); - - return drv_len; -} - -// Invoked when class request DATA stage is finished. -// return false to stall control endpoint (e.g Host send nonsense DATA) -bool netd_control_complete(uint8_t rhport, tusb_control_request_t const * request) -{ - (void) rhport; - - // Handle RNDIS class control OUT only - if (request->bmRequestType_bit.type == TUSB_REQ_TYPE_CLASS && - request->bmRequestType_bit.direction == TUSB_DIR_OUT && - _netd_itf.itf_num == request->wIndex) - { - if ( !_netd_itf.ecm_mode ) - { - rndis_class_set_handler(notify.rndis_buf, request->wLength); - } - } - - return true; -} - -static void ecm_report(bool nc) -{ - notify.ecm_buf = (nc) ? ecm_notify_nc : ecm_notify_csc; - notify.ecm_buf.header.wIndex = _netd_itf.itf_num; - netd_report((uint8_t *)¬ify.ecm_buf, (nc) ? sizeof(notify.ecm_buf.header) : sizeof(notify.ecm_buf)); -} - -// Handle class control request -// return false to stall control endpoint (e.g unsupported request) -bool netd_control_request(uint8_t rhport, tusb_control_request_t const * request) -{ - switch ( request->bmRequestType_bit.type ) - { - case TUSB_REQ_TYPE_STANDARD: - switch ( request->bRequest ) - { - case TUSB_REQ_GET_INTERFACE: - { - uint8_t const req_itfnum = (uint8_t) request->wIndex; - TU_VERIFY(_netd_itf.itf_num+1 == req_itfnum); - - tud_control_xfer(rhport, request, &_netd_itf.itf_data_alt, 1); - } - break; - - case TUSB_REQ_SET_INTERFACE: - { - uint8_t const req_itfnum = (uint8_t) request->wIndex; - uint8_t const req_alt = (uint8_t) request->wValue; - - // Only valid for Data Interface with Alternate is either 0 or 1 - TU_VERIFY(_netd_itf.itf_num+1 == req_itfnum && req_alt < 2); - - // ACM-ECM only: qequest to enable/disable network activities - TU_VERIFY(_netd_itf.ecm_mode); - - _netd_itf.itf_data_alt = req_alt; - - if ( _netd_itf.itf_data_alt ) - { - // TODO since we don't actually close endpoint - // hack here to not re-open it - if ( _netd_itf.ep_in == 0 && _netd_itf.ep_out == 0 ) - { - TU_ASSERT(_netd_itf.ecm_desc_epdata); - TU_ASSERT( usbd_open_edpt_pair(rhport, _netd_itf.ecm_desc_epdata, 2, TUSB_XFER_BULK, &_netd_itf.ep_out, &_netd_itf.ep_in) ); - - // TODO should be merge with RNDIS's after endpoint opened - // Also should have opposite callback for application to disable network !! - tud_network_init_cb(); - can_xmit = true; // we are ready to transmit a packet - tud_network_recv_renew(); // prepare for incoming packets - } - }else - { - // TODO close the endpoint pair - // For now pretend that we did, this should have no harm since host won't try to - // communicate with the endpoints again - // _netd_itf.ep_in = _netd_itf.ep_out = 0 - } - - tud_control_status(rhport, request); - } - break; - - // unsupported request - default: return false; - } - break; - - case TUSB_REQ_TYPE_CLASS: - TU_VERIFY (_netd_itf.itf_num == request->wIndex); - - if (_netd_itf.ecm_mode) - { - /* the only required CDC-ECM Management Element Request is SetEthernetPacketFilter */ - if (0x43 /* SET_ETHERNET_PACKET_FILTER */ == request->bRequest) - { - tud_control_xfer(rhport, request, NULL, 0); - ecm_report(true); - } - } - else - { - if (request->bmRequestType_bit.direction == TUSB_DIR_IN) - { - rndis_generic_msg_t *rndis_msg = (rndis_generic_msg_t *) ((void*) notify.rndis_buf); - uint32_t msglen = tu_le32toh(rndis_msg->MessageLength); - TU_ASSERT(msglen <= sizeof(notify.rndis_buf)); - tud_control_xfer(rhport, request, notify.rndis_buf, msglen); - } - else - { - tud_control_xfer(rhport, request, notify.rndis_buf, sizeof(notify.rndis_buf)); - } - } - break; - - // unsupported request - default: return false; - } - - return true; -} - -static void handle_incoming_packet(uint32_t len) -{ - uint8_t *pnt = received; - uint32_t size = 0; - - if (_netd_itf.ecm_mode) - { - size = len; - } - else - { - rndis_data_packet_t *r = (rndis_data_packet_t *) ((void*) pnt); - if (len >= sizeof(rndis_data_packet_t)) - if ( (r->MessageType == REMOTE_NDIS_PACKET_MSG) && (r->MessageLength <= len)) - if ( (r->DataOffset + offsetof(rndis_data_packet_t, DataOffset) + r->DataLength) <= len) - { - pnt = &received[r->DataOffset + offsetof(rndis_data_packet_t, DataOffset)]; - size = r->DataLength; - } - } - - bool accepted = false; - - if (size) - { - struct pbuf *p = pbuf_alloc(PBUF_RAW, size, PBUF_POOL); - - if (p) - { - memcpy(p->payload, pnt, size); - p->len = size; - accepted = tud_network_recv_cb(p); - - if (!accepted) pbuf_free(p); - } - } - - if (!accepted) - { - /* if a buffer was never handled by user code, we must renew on the user's behalf */ - tud_network_recv_renew(); - } -} - -bool netd_xfer_cb(uint8_t rhport, uint8_t ep_addr, xfer_result_t result, uint32_t xferred_bytes) -{ - (void) rhport; - (void) result; - - /* new packet received */ - if ( ep_addr == _netd_itf.ep_out ) - { - handle_incoming_packet(xferred_bytes); - } - - /* data transmission finished */ - if ( ep_addr == _netd_itf.ep_in ) - { - /* TinyUSB requires the class driver to implement ZLP (since ZLP usage is class-specific) */ - - if ( xferred_bytes && (0 == (xferred_bytes % CFG_TUD_NET_ENDPOINT_SIZE)) ) - { - do_in_xfer(NULL, 0); /* a ZLP is needed */ - } - else - { - /* we're finally finished */ - can_xmit = true; - } - } - - if ( _netd_itf.ecm_mode && (ep_addr == _netd_itf.ep_notif) ) - { - if (sizeof(notify.ecm_buf.header) == xferred_bytes) ecm_report(false); - } - - return true; -} - -bool tud_network_can_xmit(void) -{ - return can_xmit; -} - -void tud_network_xmit(struct pbuf *p) -{ - struct pbuf *q; - uint8_t *data; - uint16_t len; - - if (!can_xmit) - return; - - len = (_netd_itf.ecm_mode) ? 0 : CFG_TUD_NET_PACKET_PREFIX_LEN; - data = transmitted + len; - - for(q = p; q != NULL; q = q->next) - { - memcpy(data, (char *)q->payload, q->len); - data += q->len; - len += q->len; - } - - if (!_netd_itf.ecm_mode) - { - rndis_data_packet_t *hdr = (rndis_data_packet_t *) ((void*) transmitted); - memset(hdr, 0, sizeof(rndis_data_packet_t)); - hdr->MessageType = REMOTE_NDIS_PACKET_MSG; - hdr->MessageLength = len; - hdr->DataOffset = sizeof(rndis_data_packet_t) - offsetof(rndis_data_packet_t, DataOffset); - hdr->DataLength = len - sizeof(rndis_data_packet_t); - } - - do_in_xfer(transmitted, len); -} - -#endif diff --git a/cores/nRF5/TinyUSB/Adafruit_TinyUSB_ArduinoCore/tinyusb/src/class/net/net_device.h b/cores/nRF5/TinyUSB/Adafruit_TinyUSB_ArduinoCore/tinyusb/src/class/net/net_device.h index 0175ea5..fff2623 100644 --- a/cores/nRF5/TinyUSB/Adafruit_TinyUSB_ArduinoCore/tinyusb/src/class/net/net_device.h +++ b/cores/nRF5/TinyUSB/Adafruit_TinyUSB_ArduinoCore/tinyusb/src/class/net/net_device.h @@ -1,4 +1,4 @@ -/* +/* * The MIT License (MIT) * * Copyright (c) 2020 Peter Lawrence @@ -28,19 +28,28 @@ #ifndef _TUSB_NET_DEVICE_H_ #define _TUSB_NET_DEVICE_H_ -#include "common/tusb_common.h" -#include "device/usbd.h" +#include #include "class/cdc/cdc.h" -// TODO should not include external files -#include "lwip/pbuf.h" -#include "netif/ethernet.h" +#if CFG_TUD_ECM_RNDIS && CFG_TUD_NCM +#error "Cannot enable both ECM_RNDIS and NCM network drivers" +#endif /* declared here, NOT in usb_descriptors.c, so that the driver can intelligently ZLP as needed */ #define CFG_TUD_NET_ENDPOINT_SIZE (TUD_OPT_HIGH_SPEED ? 512 : 64) -/* Maximum Tranmission Unit (in bytes) of the network, including Ethernet header */ -#define CFG_TUD_NET_MTU (1500 + SIZEOF_ETH_HDR) +/* Maximum Transmission Unit (in bytes) of the network, including Ethernet header */ +#ifndef CFG_TUD_NET_MTU +#define CFG_TUD_NET_MTU 1514 +#endif + + +// Table 4.3 Data Class Interface Protocol Codes +typedef enum +{ + NCM_DATA_PROTOCOL_NETWORK_TRANSFER_BLOCK = 0x01 +} ncm_data_interface_protocol_code_t; + #ifdef __cplusplus extern "C" { @@ -50,35 +59,49 @@ // Application API //--------------------------------------------------------------------+ -// client must provide this: initialize any network state back to the beginning -void tud_network_init_cb(void); +// indicate to network driver that client has finished with the packet provided to network_recv_cb() +void tud_network_recv_renew(void); + +// poll network driver for its ability to accept another packet to transmit +bool tud_network_can_xmit(uint16_t size); + +// if network_can_xmit() returns true, network_xmit() can be called once +void tud_network_xmit(void *ref, uint16_t arg); + +//--------------------------------------------------------------------+ +// Application Callbacks (WEAK is optional) +//--------------------------------------------------------------------+ // client must provide this: return false if the packet buffer was not accepted -bool tud_network_recv_cb(struct pbuf *p); +bool tud_network_recv_cb(const uint8_t *src, uint16_t size); + +// client must provide this: copy from network stack packet pointer to dst +uint16_t tud_network_xmit_cb(uint8_t *dst, void *ref, uint16_t arg); + +//------------- ECM/RNDIS -------------// + +// client must provide this: initialize any network state back to the beginning +void tud_network_init_cb(void); // client must provide this: 48-bit MAC address // TODO removed later since it is not part of tinyusb stack -extern const uint8_t tud_network_mac_address[6]; - -// indicate to network driver that client has finished with the packet provided to network_recv_cb() -void tud_network_recv_renew(void); +extern uint8_t tud_network_mac_address[6]; -// poll network driver for its ability to accept another packet to transmit -bool tud_network_can_xmit(void); +//------------- NCM -------------// -// if network_can_xmit() returns true, network_xmit() can be called once -void tud_network_xmit(struct pbuf *p); +// Set the network link state (up/down) and notify the host +void tud_network_link_state(uint8_t rhport, bool is_up); //--------------------------------------------------------------------+ // INTERNAL USBD-CLASS DRIVER API //--------------------------------------------------------------------+ -void netd_init (void); -void netd_reset (uint8_t rhport); -uint16_t netd_open (uint8_t rhport, tusb_desc_interface_t const * itf_desc, uint16_t max_len); -bool netd_control_request (uint8_t rhport, tusb_control_request_t const * request); -bool netd_control_complete (uint8_t rhport, tusb_control_request_t const * request); -bool netd_xfer_cb (uint8_t rhport, uint8_t ep_addr, xfer_result_t result, uint32_t xferred_bytes); -void netd_report (uint8_t *buf, uint16_t len); +void netd_init (void); +bool netd_deinit (void); +void netd_reset (uint8_t rhport); +uint16_t netd_open (uint8_t rhport, tusb_desc_interface_t const * itf_desc, uint16_t max_len); +bool netd_control_xfer_cb (uint8_t rhport, uint8_t stage, tusb_control_request_t const * request); +bool netd_xfer_cb (uint8_t rhport, uint8_t ep_addr, xfer_result_t result, uint32_t xferred_bytes); +void netd_report (uint8_t *buf, uint16_t len); #ifdef __cplusplus } diff --git a/cores/nRF5/TinyUSB/Adafruit_TinyUSB_ArduinoCore/tinyusb/src/class/usbtmc/usbtmc.h b/cores/nRF5/TinyUSB/Adafruit_TinyUSB_ArduinoCore/tinyusb/src/class/usbtmc/usbtmc.h index 7d7005c..327de08 100644 --- a/cores/nRF5/TinyUSB/Adafruit_TinyUSB_ArduinoCore/tinyusb/src/class/usbtmc/usbtmc.h +++ b/cores/nRF5/TinyUSB/Adafruit_TinyUSB_ArduinoCore/tinyusb/src/class/usbtmc/usbtmc.h @@ -158,7 +158,7 @@ enum { USBTMC_BULK_IN_ERR_DATA_TOO_SHORT = 4u, USBTMC_BULK_IN_ERR_DATA_TOO_LONG = 5u, }; -// bult-in halt errors +// built-in halt errors enum { USBTMC_BULK_IN_ERR = 1u, ///< receives a USBTMC command message that expects a response while a /// Bulk-IN transfer is in progress @@ -183,13 +183,33 @@ typedef enum { } usmtmc_request_type_enum; +typedef enum { + // The last and first valid bNotify1 for use by the USBTMC class specification. + USBTMC_bNOTIFY1_USBTMC_FIRST = 0x00, + USBTMC_bNOTIFY1_USBTMC_LAST = 0x3F, + + // The last and first valid bNotify1 for use by vendors. + USBTMC_bNOTIFY1_VENDOR_SPECIFIC_FIRST = 0x40, + USBTMC_bNOTIFY1_VENDOR_SPECIFIC_LAST = 0x7F, + + // The last and first valid bNotify1 for use by USBTMC subclass specifications. + USBTMC_bNOTIFY1_SUBCLASS_FIRST = 0x80, + USBTMC_bNOTIFY1_SUBCLASS_LAST = 0xFF, + + // From the USB488 Subclass Specification, Section 3.4. + USB488_bNOTIFY1_SRQ = 0x81, +} usbtmc_int_in_payload_format; + typedef enum { USBTMC_STATUS_SUCCESS = 0x01, USBTMC_STATUS_PENDING = 0x02, USBTMC_STATUS_FAILED = 0x80, USBTMC_STATUS_TRANSFER_NOT_IN_PROGRESS = 0x81, USBTMC_STATUS_SPLIT_NOT_IN_PROGRESS = 0x82, - USBTMC_STATUS_SPLIT_IN_PROGRESS = 0x83 + USBTMC_STATUS_SPLIT_IN_PROGRESS = 0x83, + + /****** USBTMC 488 *************/ + USB488_STATUS_INTERRUPT_IN_BUSY = 0x20 } usbtmc_status_enum; /************************************************************ @@ -259,14 +279,14 @@ typedef struct TU_ATTR_PACKED struct TU_ATTR_PACKED { - unsigned int listenOnly :1; - unsigned int talkOnly :1; - unsigned int supportsIndicatorPulse :1; + uint8_t listenOnly :1; + uint8_t talkOnly :1; + uint8_t supportsIndicatorPulse :1; } bmIntfcCapabilities; struct TU_ATTR_PACKED { - unsigned int canEndBulkInOnTermChar :1; + uint8_t canEndBulkInOnTermChar :1; } bmDevCapabilities; uint8_t _reserved2[6]; @@ -274,17 +294,17 @@ typedef struct TU_ATTR_PACKED struct TU_ATTR_PACKED { - unsigned int is488_2 :1; - unsigned int supportsREN_GTL_LLO :1; - unsigned int supportsTrigger :1; + uint8_t supportsTrigger :1; + uint8_t supportsREN_GTL_LLO :1; + uint8_t is488_2 :1; } bmIntfcCapabilities488; struct TU_ATTR_PACKED { - unsigned int SCPI :1; - unsigned int SR1 :1; - unsigned int RL1 :1; - unsigned int DT1 :1; + uint8_t DT1 :1; + uint8_t RL1 :1; + uint8_t SR1 :1; + uint8_t SCPI :1; } bmDevCapabilities488; uint8_t _reserved3[8]; } usbtmc_response_capabilities_488_t; @@ -300,6 +320,14 @@ typedef struct TU_ATTR_PACKED TU_VERIFY_STATIC(sizeof(usbtmc_read_stb_rsp_488_t) == 3u, "struct wrong length"); +typedef struct TU_ATTR_PACKED +{ + uint8_t bNotify1; // Must be USB488_bNOTIFY1_SRQ + uint8_t StatusByte; +} usbtmc_srq_interrupt_488_t; + +TU_VERIFY_STATIC(sizeof(usbtmc_srq_interrupt_488_t) == 2u, "struct wrong length"); + typedef struct TU_ATTR_PACKED { struct TU_ATTR_PACKED @@ -313,4 +341,3 @@ typedef struct TU_ATTR_PACKED TU_VERIFY_STATIC(sizeof(usbtmc_read_stb_interrupt_488_t) == 2u, "struct wrong length"); #endif - diff --git a/cores/nRF5/TinyUSB/Adafruit_TinyUSB_ArduinoCore/tinyusb/src/class/usbtmc/usbtmc_device.c b/cores/nRF5/TinyUSB/Adafruit_TinyUSB_ArduinoCore/tinyusb/src/class/usbtmc/usbtmc_device.c index bc5f23f..3f6bedd 100644 --- a/cores/nRF5/TinyUSB/Adafruit_TinyUSB_ArduinoCore/tinyusb/src/class/usbtmc/usbtmc_device.c +++ b/cores/nRF5/TinyUSB/Adafruit_TinyUSB_ArduinoCore/tinyusb/src/class/usbtmc/usbtmc_device.c @@ -1,10 +1,3 @@ -/* - * usbtmc.c - * - * Created on: Sep 9, 2019 - * Author: nconrad - */ - /* * The MIT License (MIT) * @@ -52,7 +45,7 @@ */ //Limitations: -// "vendor-specific" commands are not handled. +// "vendor-specific" commands are handled similar to normal messages, except that the MsgID is changed to "vendor-specific". // Dealing with "termchar" must be handled by the application layer, // though additional error checking is does in this module. // talkOnly and listenOnly are NOT supported. They're not permitted @@ -71,24 +64,26 @@ // USBTMC 3.2.2 error conditions not strictly followed // No local lock-out, REN, or GTL. // Clear message available status byte at the correct time? (488 4.3.1.3) - +// Ability to defer status byte transmission +// Transmission of status byte in response to USB488 SRQ condition #include "tusb_option.h" -#if (TUSB_OPT_DEVICE_ENABLED && CFG_TUD_USBTMC) +#if (CFG_TUD_ENABLED && CFG_TUD_USBTMC) -#include -#include "usbtmc.h" -#include "usbtmc_device.h" #include "device/usbd.h" -#include "osal/osal.h" - -// FIXME: I shouldn't need to include _pvt headers, but it is necessary for usbd_edpt_xfer, _stall, and _busy #include "device/usbd_pvt.h" -#ifdef xDEBUG -#include "uart_util.h" -static char logMsg[150]; +#include "usbtmc_device.h" + +// Buffer size must be an exact multiple of the max packet size for both +// bulk (up to 64 bytes for FS, 512 bytes for HS). In addation, this driver +// imposes a minimum buffer size of 32 bytes. +#define USBTMCD_BUFFER_SIZE (TUD_OPT_HIGH_SPEED ? 512 : 64) + +// Interrupt endpoint buffer size, default to 2 bytes as USB488 specification. +#ifndef CFG_TUD_USBTMC_INT_EP_SIZE + #define CFG_TUD_USBTMC_INT_EP_SIZE 2 #endif /* @@ -96,27 +91,26 @@ static char logMsg[150]; * consistent with USBTMC. */ -typedef enum -{ - STATE_CLOSED, // Endpoints have not yet been opened since USB reset - STATE_NAK, // Bulk-out endpoint is in NAK state. - STATE_IDLE, // Bulk-out endpoint is waiting for CMD. - STATE_RCV, // Bulk-out is receiving DEV_DEP message +typedef enum { + STATE_CLOSED,// Endpoints have not yet been opened since USB reset + STATE_NAK, // Bulk-out endpoint is in NAK state. + STATE_IDLE, // Bulk-out endpoint is waiting for CMD. + STATE_RCV, // Bulk-out is receiving DEV_DEP message STATE_TX_REQUESTED, STATE_TX_INITIATED, STATE_TX_SHORTED, STATE_CLEARING, STATE_ABORTING_BULK_IN, - STATE_ABORTING_BULK_IN_SHORTED, // aborting, and short packet has been queued for transmission - STATE_ABORTING_BULK_IN_ABORTED, // aborting, and short packet has been transmitted + STATE_ABORTING_BULK_IN_SHORTED,// aborting, and short packet has been queued for transmission + STATE_ABORTING_BULK_IN_ABORTED,// aborting, and short packet has been transmitted STATE_ABORTING_BULK_OUT, STATE_NUM_STATES } usbtmcd_state_enum; #if (CFG_TUD_USBTMC_ENABLE_488) - typedef usbtmc_response_capabilities_488_t usbtmc_capabilities_specific_t; +typedef usbtmc_response_capabilities_488_t usbtmc_capabilities_specific_t; #else - typedef usbtmc_response_capabilities_t usbtmc_capabilities_specific_t; +typedef usbtmc_response_capabilities_t usbtmc_capabilities_specific_t; #endif @@ -129,58 +123,87 @@ typedef struct uint8_t ep_bulk_in; uint8_t ep_bulk_out; uint8_t ep_int_in; - // IN buffer is only used for first packet, not the remainder - // in order to deal with prepending header - uint8_t ep_bulk_in_buf[USBTMCD_MAX_PACKET_SIZE]; - // OUT buffer receives one packet at a time - uint8_t ep_bulk_out_buf[USBTMCD_MAX_PACKET_SIZE]; - uint32_t transfer_size_remaining; // also used for requested length for bulk IN. - uint32_t transfer_size_sent; // To keep track of data bytes that have been queued in FIFO (not header bytes) + uint32_t ep_bulk_in_wMaxPacketSize; + uint32_t ep_bulk_out_wMaxPacketSize; + uint32_t transfer_size_remaining;// also used for requested length for bulk IN. + uint32_t transfer_size_sent; // To keep track of data bytes that have been queued in FIFO (not header bytes) - uint8_t lastBulkOutTag; // used for aborts (mostly) + uint8_t lastBulkOutTag;// used for aborts (mostly) uint8_t lastBulkInTag; // used for aborts (mostly) - uint8_t const * devInBuffer; // pointer to application-layer used for transmissions + uint8_t const *devInBuffer;// pointer to application-layer used for transmissions - usbtmc_capabilities_specific_t const * capabilities; + usbtmc_capabilities_specific_t const *capabilities; } usbtmc_interface_state_t; -static usbtmc_interface_state_t usbtmc_state = -{ +typedef struct { + // IN buffer is only used for first packet, not the remainder in order to deal with prepending header + TUD_EPBUF_DEF(epin, USBTMCD_BUFFER_SIZE); + + // OUT buffer receives one packet at a time + TUD_EPBUF_DEF(epout, USBTMCD_BUFFER_SIZE); + + // Buffer int msg + TUD_EPBUF_DEF(epnotif, CFG_TUD_USBTMC_INT_EP_SIZE); +} usbtmc_epbuf_t; + +static usbtmc_interface_state_t usbtmc_state = { .itf_id = 0xFF, }; -// We need all headers to fit in a single packet in this implementation. -TU_VERIFY_STATIC(USBTMCD_MAX_PACKET_SIZE >= 32u,"USBTMC dev EP packet size too small"); -TU_VERIFY_STATIC( - (sizeof(usbtmc_state.ep_bulk_in_buf) % USBTMCD_MAX_PACKET_SIZE) == 0, - "packet buffer must be a multiple of the packet size"); +CFG_TUD_MEM_SECTION static usbtmc_epbuf_t usbtmc_epbuf; + +// We need all headers to fit in a single packet in this implementation, 32 bytes will fit all standard USBTMC headers +TU_VERIFY_STATIC(USBTMCD_BUFFER_SIZE >= 32u, "USBTMC dev buffer size too small"); static bool handle_devMsgOutStart(uint8_t rhport, void *data, size_t len); static bool handle_devMsgOut(uint8_t rhport, void *data, size_t len, size_t packetLen); -static uint8_t termChar; -static uint8_t termCharRequested = false; +// USBTMC Device Callbacks weak implementations +TU_ATTR_WEAK bool tud_usbtmc_notification_complete_cb(void) { + return true; +} + +TU_ATTR_WEAK bool tud_usbtmc_indicator_pulse_cb(tusb_control_request_t const * msg, uint8_t *tmcResult) { + (void) msg; + (void) tmcResult; + return true; +} + +#if (CFG_TUD_USBTMC_ENABLE_488) +TU_ATTR_WEAK bool tud_usbtmc_msg_trigger_cb(usbtmc_msg_generic_t* msg) { + (void) msg; + return true; +} +#endif + +#ifndef NDEBUG +tu_static uint8_t termChar; +#endif + +tu_static uint8_t termCharRequested = false; -osal_mutex_def_t usbtmcLockBuffer; -static osal_mutex_t usbtmcLock; +tu_static bool usbtmcVendorSpecificRequested = false; + +#if OSAL_MUTEX_REQUIRED +static OSAL_MUTEX_DEF(usbtmcLockBuffer); +#endif +osal_mutex_t usbtmcLock; // Our own private lock, mostly for the state variable. -#define criticalEnter() do {osal_mutex_lock(usbtmcLock,OSAL_TIMEOUT_WAIT_FOREVER); } while (0) -#define criticalLeave() do {osal_mutex_unlock(usbtmcLock); } while (0) +#define criticalEnter() \ + do { (void) osal_mutex_lock(usbtmcLock, OSAL_TIMEOUT_WAIT_FOREVER); } while (0) +#define criticalLeave() \ + do { (void) osal_mutex_unlock(usbtmcLock); } while (0) -bool atomicChangeState(usbtmcd_state_enum expectedState, usbtmcd_state_enum newState) -{ +static bool atomicChangeState(usbtmcd_state_enum expectedState, usbtmcd_state_enum newState) { bool ret = true; criticalEnter(); usbtmcd_state_enum oldState = usbtmc_state.state; - if (oldState == expectedState) - { + if (oldState == expectedState) { usbtmc_state.state = newState; - } - else - { + } else { ret = false; } criticalLeave(); @@ -195,84 +218,98 @@ bool atomicChangeState(usbtmcd_state_enum expectedState, usbtmcd_state_enum newS // We can't just send the whole thing at once because we need to concatanate the // header with the data. bool tud_usbtmc_transmit_dev_msg_data( - const void * data, size_t len, + const void *data, size_t len, bool endOfMessage, - bool usingTermChar) -{ - const unsigned int txBufLen = sizeof(usbtmc_state.ep_bulk_in_buf); + bool usingTermChar) { + const unsigned int txBufLen = USBTMCD_BUFFER_SIZE; #ifndef NDEBUG TU_ASSERT(len > 0u); TU_ASSERT(len <= usbtmc_state.transfer_size_remaining); TU_ASSERT(usbtmc_state.transfer_size_sent == 0u); - if(usingTermChar) - { + if (usingTermChar) { TU_ASSERT(usbtmc_state.capabilities->bmDevCapabilities.canEndBulkInOnTermChar); TU_ASSERT(termCharRequested); - TU_ASSERT(((uint8_t*)data)[len-1u] == termChar); + TU_ASSERT(((uint8_t const *) data)[len - 1u] == termChar); } #endif TU_VERIFY(usbtmc_state.state == STATE_TX_REQUESTED); - usbtmc_msg_dev_dep_msg_in_header_t *hdr = (usbtmc_msg_dev_dep_msg_in_header_t*)usbtmc_state.ep_bulk_in_buf; + usbtmc_msg_dev_dep_msg_in_header_t *hdr = (usbtmc_msg_dev_dep_msg_in_header_t *) usbtmc_epbuf.epin; tu_varclr(hdr); - hdr->header.MsgID = USBTMC_MSGID_DEV_DEP_MSG_IN; + if (usbtmcVendorSpecificRequested) { + hdr->header.MsgID = USBTMC_MSGID_VENDOR_SPECIFIC_IN; + } else { + hdr->header.MsgID = USBTMC_MSGID_DEV_DEP_MSG_IN; + } hdr->header.bTag = usbtmc_state.lastBulkInTag; - hdr->header.bTagInverse = (uint8_t)~(usbtmc_state.lastBulkInTag); + hdr->header.bTagInverse = (uint8_t) ~(usbtmc_state.lastBulkInTag); hdr->TransferSize = len; hdr->bmTransferAttributes.EOM = endOfMessage; hdr->bmTransferAttributes.UsingTermChar = usingTermChar; // Copy in the header const size_t headerLen = sizeof(*hdr); - const size_t dataLen = ((headerLen + hdr->TransferSize) <= txBufLen) ? - len : (txBufLen - headerLen); + const size_t dataLen = ((headerLen + hdr->TransferSize) <= txBufLen) ? len : (txBufLen - headerLen); const size_t packetLen = headerLen + dataLen; - memcpy((uint8_t*)(usbtmc_state.ep_bulk_in_buf) + headerLen, data, dataLen); + memcpy((uint8_t *) (usbtmc_epbuf.epin) + headerLen, data, dataLen); usbtmc_state.transfer_size_remaining = len - dataLen; usbtmc_state.transfer_size_sent = dataLen; - usbtmc_state.devInBuffer = (uint8_t*)data + (dataLen); + usbtmc_state.devInBuffer = (uint8_t const *) data + (dataLen); bool stateChanged = atomicChangeState(STATE_TX_REQUESTED, (packetLen >= txBufLen) ? STATE_TX_INITIATED : STATE_TX_SHORTED); TU_VERIFY(stateChanged); - TU_VERIFY(usbd_edpt_xfer(usbtmc_state.rhport, usbtmc_state.ep_bulk_in, usbtmc_state.ep_bulk_in_buf, (uint16_t)packetLen)); + TU_VERIFY(usbd_edpt_xfer(usbtmc_state.rhport, usbtmc_state.ep_bulk_in, usbtmc_epbuf.epin, (uint16_t) packetLen)); return true; } -void usbtmcd_init_cb(void) -{ +bool tud_usbtmc_transmit_notification_data(const void *data, size_t len) { +#ifndef NDEBUG + TU_ASSERT(len > 0); + TU_ASSERT(usbtmc_state.ep_int_in != 0); +#endif + TU_VERIFY(usbd_edpt_busy(usbtmc_state.rhport, usbtmc_state.ep_int_in)); + + TU_VERIFY(tu_memcpy_s(usbtmc_epbuf.epnotif, CFG_TUD_USBTMC_INT_EP_SIZE, data, len) == 0); + TU_VERIFY(usbd_edpt_xfer(usbtmc_state.rhport, usbtmc_state.ep_int_in, usbtmc_epbuf.epnotif, (uint16_t) len)); + return true; +} + +void usbtmcd_init_cb(void) { usbtmc_state.capabilities = tud_usbtmc_get_capabilities_cb(); #ifndef NDEBUG -# if CFG_TUD_USBTMC_ENABLE_488 - if(usbtmc_state.capabilities->bmIntfcCapabilities488.supportsTrigger) - TU_ASSERT(&tud_usbtmc_msg_trigger_cb != NULL,); - // Per USB488 spec: table 8 - TU_ASSERT(!usbtmc_state.capabilities->bmIntfcCapabilities.listenOnly,); - TU_ASSERT(!usbtmc_state.capabilities->bmIntfcCapabilities.talkOnly,); -# endif - if(usbtmc_state.capabilities->bmIntfcCapabilities.supportsIndicatorPulse) - TU_ASSERT(&tud_usbtmc_indicator_pulse_cb != NULL,); + #if CFG_TUD_USBTMC_ENABLE_488 + // Per USB488 spec: table 8 + TU_ASSERT(!usbtmc_state.capabilities->bmIntfcCapabilities.listenOnly, ); + TU_ASSERT(!usbtmc_state.capabilities->bmIntfcCapabilities.talkOnly, ); + #endif #endif - usbtmcLock = osal_mutex_create(&usbtmcLockBuffer); + usbtmcLock = osal_mutex_create(&usbtmcLockBuffer); } -uint16_t usbtmcd_open_cb(uint8_t rhport, tusb_desc_interface_t const * itf_desc, uint16_t max_len) -{ - (void)rhport; +bool usbtmcd_deinit(void) { +#if OSAL_MUTEX_REQUIRED + osal_mutex_delete(usbtmcLock); +#endif + return true; +} + +uint16_t usbtmcd_open_cb(uint8_t rhport, tusb_desc_interface_t const *itf_desc, uint16_t max_len) { + (void) rhport; uint16_t drv_len; - uint8_t const * p_desc; + uint8_t const *p_desc; uint8_t found_endpoints = 0; - TU_VERIFY(itf_desc->bInterfaceClass == TUD_USBTMC_APP_CLASS , 0); + TU_VERIFY(itf_desc->bInterfaceClass == TUD_USBTMC_APP_CLASS, 0); TU_VERIFY(itf_desc->bInterfaceSubClass == TUD_USBTMC_APP_SUBCLASS, 0); #ifndef NDEBUG // Only 2 or 3 endpoints are allowed for USBTMC. - TU_ASSERT((itf_desc->bNumEndpoints == 2) || (itf_desc->bNumEndpoints ==3), 0); + TU_ASSERT((itf_desc->bNumEndpoints == 2) || (itf_desc->bNumEndpoints == 3), 0); #endif TU_ASSERT(usbtmc_state.state == STATE_CLOSED, 0); @@ -284,19 +321,19 @@ uint16_t usbtmcd_open_cb(uint8_t rhport, tusb_desc_interface_t const * itf_desc, usbtmc_state.itf_id = itf_desc->bInterfaceNumber; usbtmc_state.rhport = rhport; - while (found_endpoints < itf_desc->bNumEndpoints && drv_len <= max_len) - { - if ( TUSB_DESC_ENDPOINT == p_desc[DESC_OFFSET_TYPE]) - { - tusb_desc_endpoint_t const *ep_desc = (tusb_desc_endpoint_t const *)p_desc; - switch(ep_desc->bmAttributes.xfer) { + while (found_endpoints < itf_desc->bNumEndpoints && drv_len <= max_len) { + if (TUSB_DESC_ENDPOINT == p_desc[DESC_OFFSET_TYPE]) { + tusb_desc_endpoint_t const *ep_desc = (tusb_desc_endpoint_t const *) p_desc; + switch (ep_desc->bmAttributes.xfer) { case TUSB_XFER_BULK: - TU_ASSERT(ep_desc->wMaxPacketSize.size == USBTMCD_MAX_PACKET_SIZE, 0); - if (tu_edpt_dir(ep_desc->bEndpointAddress) == TUSB_DIR_IN) - { + // Ensure buffer is an exact multiple of the maxPacketSize + TU_ASSERT((USBTMCD_BUFFER_SIZE % tu_edpt_packet_size(ep_desc)) == 0, 0); + if (tu_edpt_dir(ep_desc->bEndpointAddress) == TUSB_DIR_IN) { usbtmc_state.ep_bulk_in = ep_desc->bEndpointAddress; + usbtmc_state.ep_bulk_in_wMaxPacketSize = tu_edpt_packet_size(ep_desc); } else { usbtmc_state.ep_bulk_out = ep_desc->bEndpointAddress; + usbtmc_state.ep_bulk_out_wMaxPacketSize = tu_edpt_packet_size(ep_desc); } break; @@ -310,33 +347,29 @@ uint16_t usbtmcd_open_cb(uint8_t rhport, tusb_desc_interface_t const * itf_desc, default: TU_ASSERT(false, 0); } - TU_ASSERT( usbd_edpt_open(rhport, ep_desc), 0); + TU_ASSERT(usbd_edpt_open(rhport, ep_desc), 0); found_endpoints++; } drv_len += tu_desc_len(p_desc); - p_desc = tu_desc_next(p_desc); + p_desc = tu_desc_next(p_desc); } - // bulk endpoints are required, but interrupt IN is optional +// bulk endpoints are required, but interrupt IN is optional #ifndef NDEBUG - TU_ASSERT(usbtmc_state.ep_bulk_in != 0, 0); + TU_ASSERT(usbtmc_state.ep_bulk_in != 0, 0); TU_ASSERT(usbtmc_state.ep_bulk_out != 0, 0); - if (itf_desc->bNumEndpoints == 2) - { + if (itf_desc->bNumEndpoints == 2) { TU_ASSERT(usbtmc_state.ep_int_in == 0, 0); - } - else if (itf_desc->bNumEndpoints == 3) - { + } else if (itf_desc->bNumEndpoints == 3) { TU_ASSERT(usbtmc_state.ep_int_in != 0, 0); } -#if (CFG_TUD_USBTMC_ENABLE_488) - if(usbtmc_state.capabilities->bmIntfcCapabilities488.is488_2 || - usbtmc_state.capabilities->bmDevCapabilities488.SR1) - { + #if (CFG_TUD_USBTMC_ENABLE_488) + if (usbtmc_state.capabilities->bmIntfcCapabilities488.is488_2 || + usbtmc_state.capabilities->bmDevCapabilities488.SR1) { TU_ASSERT(usbtmc_state.ep_int_in != 0, 0); } -#endif + #endif #endif atomicChangeState(STATE_CLOSED, STATE_NAK); tud_usbtmc_open_cb(itf_desc->iInterface); @@ -349,30 +382,27 @@ uint16_t usbtmcd_open_cb(uint8_t rhport, tusb_desc_interface_t const * itf_desc, // processing a command (such as a clear). Returns true if it was // in the NAK state and successfully transitioned to the ACK wait // state. -bool tud_usbtmc_start_bus_read() -{ +bool tud_usbtmc_start_bus_read(void) { usbtmcd_state_enum oldState = usbtmc_state.state; - switch(oldState) - { - // These may transition to IDLE - case STATE_NAK: - case STATE_ABORTING_BULK_IN_ABORTED: - TU_VERIFY(atomicChangeState(oldState, STATE_IDLE)); - break; - // When receiving, let it remain receiving - case STATE_RCV: - break; - default: - TU_VERIFY(false); + switch (oldState) { + // These may transition to IDLE + case STATE_NAK: + case STATE_ABORTING_BULK_IN_ABORTED: + TU_VERIFY(atomicChangeState(oldState, STATE_IDLE)); + break; + // When receiving, let it remain receiving + case STATE_RCV: + break; + default: + return false; } - TU_VERIFY(usbd_edpt_xfer(usbtmc_state.rhport, usbtmc_state.ep_bulk_out, usbtmc_state.ep_bulk_out_buf, 64)); + TU_VERIFY(usbd_edpt_xfer(usbtmc_state.rhport, usbtmc_state.ep_bulk_out, usbtmc_epbuf.epout, (uint16_t) usbtmc_state.ep_bulk_out_wMaxPacketSize)); return true; } -void usbtmcd_reset_cb(uint8_t rhport) -{ - (void)rhport; - usbtmc_capabilities_specific_t const * capabilities = tud_usbtmc_get_capabilities_cb(); +void usbtmcd_reset_cb(uint8_t rhport) { + (void) rhport; + usbtmc_capabilities_specific_t const *capabilities = tud_usbtmc_get_capabilities_cb(); criticalEnter(); tu_varclr(&usbtmc_state); @@ -381,35 +411,32 @@ void usbtmcd_reset_cb(uint8_t rhport) criticalLeave(); } -static bool handle_devMsgOutStart(uint8_t rhport, void *data, size_t len) -{ - (void)rhport; +static bool handle_devMsgOutStart(uint8_t rhport, void *data, size_t len) { + (void) rhport; // return true upon failure, as we can assume error is being handled elsewhere. TU_VERIFY(atomicChangeState(STATE_IDLE, STATE_RCV), true); usbtmc_state.transfer_size_sent = 0u; // must be a header, should have been confirmed before calling here. - usbtmc_msg_request_dev_dep_out *msg = (usbtmc_msg_request_dev_dep_out*)data; + usbtmc_msg_request_dev_dep_out *msg = (usbtmc_msg_request_dev_dep_out *) data; usbtmc_state.transfer_size_remaining = msg->TransferSize; TU_VERIFY(tud_usbtmc_msgBulkOut_start_cb(msg)); - TU_VERIFY(handle_devMsgOut(rhport, (uint8_t*)data + sizeof(*msg), len - sizeof(*msg), len)); + TU_VERIFY(handle_devMsgOut(rhport, (uint8_t *) data + sizeof(*msg), len - sizeof(*msg), len)); usbtmc_state.lastBulkOutTag = msg->header.bTag; return true; } -static bool handle_devMsgOut(uint8_t rhport, void *data, size_t len, size_t packetLen) -{ - (void)rhport; +static bool handle_devMsgOut(uint8_t rhport, void *data, size_t len, size_t packetLen) { + (void) rhport; // return true upon failure, as we can assume error is being handled elsewhere. - TU_VERIFY(usbtmc_state.state == STATE_RCV,true); + TU_VERIFY(usbtmc_state.state == STATE_RCV, true); - bool shortPacket = (packetLen < USBTMCD_MAX_PACKET_SIZE); + bool shortPacket = (packetLen < usbtmc_state.ep_bulk_out_wMaxPacketSize); // Packet is to be considered complete when we get enough data or at a short packet. bool atEnd = false; - if(len >= usbtmc_state.transfer_size_remaining || shortPacket) - { + if (len >= usbtmc_state.transfer_size_remaining || shortPacket) { atEnd = true; TU_VERIFY(atomicChangeState(STATE_RCV, STATE_NAK)); } @@ -420,8 +447,7 @@ static bool handle_devMsgOut(uint8_t rhport, void *data, size_t len, size_t pack usbtmc_state.transfer_size_sent += len; // App may (should?) call the wait_for_bus() command at this point - if(!tud_usbtmc_msg_data_cb(data, len, atEnd)) - { + if (!tud_usbtmc_msg_data_cb(data, len, atEnd)) { // TODO: Go to an error state upon failure other than just stalling the EP? return false; } @@ -430,10 +456,9 @@ static bool handle_devMsgOut(uint8_t rhport, void *data, size_t len, size_t pack return true; } -static bool handle_devMsgIn(void *data, size_t len) -{ +static bool handle_devMsgIn(void *data, size_t len) { TU_VERIFY(len == sizeof(usbtmc_msg_request_dev_dep_in)); - usbtmc_msg_request_dev_dep_in *msg = (usbtmc_msg_request_dev_dep_in*)data; + usbtmc_msg_request_dev_dep_in *msg = (usbtmc_msg_request_dev_dep_in *) data; bool stateChanged = atomicChangeState(STATE_IDLE, STATE_TX_REQUESTED); TU_VERIFY(stateChanged); usbtmc_state.lastBulkInTag = msg->header.bTag; @@ -441,306 +466,300 @@ static bool handle_devMsgIn(void *data, size_t len) usbtmc_state.transfer_size_sent = 0u; termCharRequested = msg->bmTransferAttributes.TermCharEnabled; + +#ifndef NDEBUG termChar = msg->TermChar; +#endif - if(termCharRequested) + if (termCharRequested) TU_VERIFY(usbtmc_state.capabilities->bmDevCapabilities.canEndBulkInOnTermChar); TU_VERIFY(tud_usbtmc_msgBulkIn_request_cb(msg)); return true; } -bool usbtmcd_xfer_cb(uint8_t rhport, uint8_t ep_addr, xfer_result_t result, uint32_t xferred_bytes) -{ +bool usbtmcd_xfer_cb(uint8_t rhport, uint8_t ep_addr, xfer_result_t result, uint32_t xferred_bytes) { TU_VERIFY(result == XFER_RESULT_SUCCESS); //uart_tx_str_sync("TMC XFER CB\r\n"); - if(usbtmc_state.state == STATE_CLEARING) { + if (usbtmc_state.state == STATE_CLEARING) { return true; /* I think we can ignore everything here */ } - if(ep_addr == usbtmc_state.ep_bulk_out) - { + if (ep_addr == usbtmc_state.ep_bulk_out) { usbtmc_msg_generic_t *msg = NULL; - switch(usbtmc_state.state) - { - case STATE_IDLE: - TU_VERIFY(xferred_bytes >= sizeof(usbtmc_msg_generic_t)); - msg = (usbtmc_msg_generic_t*)(usbtmc_state.ep_bulk_out_buf); - uint8_t invInvTag = (uint8_t)~(msg->header.bTagInverse); - TU_VERIFY(msg->header.bTag == invInvTag); - TU_VERIFY(msg->header.bTag != 0x00); - - switch(msg->header.MsgID) { - case USBTMC_MSGID_DEV_DEP_MSG_OUT: - if(!handle_devMsgOutStart(rhport, msg, xferred_bytes)) - { - usbd_edpt_stall(rhport, usbtmc_state.ep_bulk_out); - TU_VERIFY(false); - } - break; - - case USBTMC_MSGID_DEV_DEP_MSG_IN: - TU_VERIFY(handle_devMsgIn(msg, xferred_bytes)); - break; + switch (usbtmc_state.state) { + case STATE_IDLE: { + TU_VERIFY(xferred_bytes >= sizeof(usbtmc_msg_generic_t)); + msg = (usbtmc_msg_generic_t *) (usbtmc_epbuf.epout); + uint8_t invInvTag = (uint8_t) ~(msg->header.bTagInverse); + TU_VERIFY(msg->header.bTag == invInvTag); + TU_VERIFY(msg->header.bTag != 0x00); + + switch (msg->header.MsgID) { + case USBTMC_MSGID_DEV_DEP_MSG_OUT: + usbtmcVendorSpecificRequested = false; + if (!handle_devMsgOutStart(rhport, msg, xferred_bytes)) { + usbd_edpt_stall(rhport, usbtmc_state.ep_bulk_out); + return false; + } + break; + + case USBTMC_MSGID_DEV_DEP_MSG_IN: + usbtmcVendorSpecificRequested = false; + TU_VERIFY(handle_devMsgIn(msg, xferred_bytes)); + break; #if (CFG_TUD_USBTMC_ENABLE_488) - case USBTMC_MSGID_USB488_TRIGGER: - // Spec says we halt the EP if we didn't declare we support it. - TU_VERIFY(usbtmc_state.capabilities->bmIntfcCapabilities488.supportsTrigger); - TU_VERIFY(tud_usbtmc_msg_trigger_cb(msg)); + case USBTMC_MSGID_USB488_TRIGGER: + // Spec says we halt the EP if we didn't declare we support it. + TU_VERIFY(usbtmc_state.capabilities->bmIntfcCapabilities488.supportsTrigger); + TU_VERIFY(tud_usbtmc_msg_trigger_cb(msg)); - break; + break; #endif - case USBTMC_MSGID_VENDOR_SPECIFIC_MSG_OUT: - case USBTMC_MSGID_VENDOR_SPECIFIC_IN: - default: - usbd_edpt_stall(rhport, usbtmc_state.ep_bulk_out); - TU_VERIFY(false); - return false; - } - return true; - - case STATE_RCV: - if(!handle_devMsgOut(rhport, usbtmc_state.ep_bulk_out_buf, xferred_bytes, xferred_bytes)) - { - usbd_edpt_stall(rhport, usbtmc_state.ep_bulk_out); - TU_VERIFY(false); + case USBTMC_MSGID_VENDOR_SPECIFIC_MSG_OUT: + usbtmcVendorSpecificRequested = true; + if (!handle_devMsgOutStart(rhport, msg, xferred_bytes)) { + usbd_edpt_stall(rhport, usbtmc_state.ep_bulk_out); + return false; + } + break; + + case USBTMC_MSGID_VENDOR_SPECIFIC_IN: + usbtmcVendorSpecificRequested = true; + TU_VERIFY(handle_devMsgIn(msg, xferred_bytes)); + break; + + default: + usbd_edpt_stall(rhport, usbtmc_state.ep_bulk_out); + return false; + } + return true; } - return true; + case STATE_RCV: + if (!handle_devMsgOut(rhport, usbtmc_epbuf.epout, xferred_bytes, xferred_bytes)) { + usbd_edpt_stall(rhport, usbtmc_state.ep_bulk_out); + return false; + } + return true; - case STATE_ABORTING_BULK_OUT: - TU_VERIFY(false); - return false; // Should be stalled by now, shouldn't have received a packet. - case STATE_TX_REQUESTED: - case STATE_TX_INITIATED: - case STATE_ABORTING_BULK_IN: - case STATE_ABORTING_BULK_IN_SHORTED: - case STATE_ABORTING_BULK_IN_ABORTED: - default: + case STATE_ABORTING_BULK_OUT: + // Should be stalled by now, shouldn't have received a packet. + return false; - TU_VERIFY(false); + case STATE_TX_REQUESTED: + case STATE_TX_INITIATED: + case STATE_ABORTING_BULK_IN: + case STATE_ABORTING_BULK_IN_SHORTED: + case STATE_ABORTING_BULK_IN_ABORTED: + default: + return false; } - } - else if(ep_addr == usbtmc_state.ep_bulk_in) - { - switch(usbtmc_state.state) { - case STATE_TX_SHORTED: - TU_VERIFY(atomicChangeState(STATE_TX_SHORTED, STATE_NAK)); - TU_VERIFY(tud_usbtmc_msgBulkIn_complete_cb()); - break; + } else if (ep_addr == usbtmc_state.ep_bulk_in) { + switch (usbtmc_state.state) { + case STATE_TX_SHORTED: + TU_VERIFY(atomicChangeState(STATE_TX_SHORTED, STATE_NAK)); + TU_VERIFY(tud_usbtmc_msgBulkIn_complete_cb()); + break; - case STATE_TX_INITIATED: - if(usbtmc_state.transfer_size_remaining >=sizeof(usbtmc_state.ep_bulk_in_buf)) - { - // FIXME! This removes const below! - TU_VERIFY( usbd_edpt_xfer(rhport, usbtmc_state.ep_bulk_in, - (void*)usbtmc_state.devInBuffer,sizeof(usbtmc_state.ep_bulk_in_buf))); - usbtmc_state.devInBuffer += sizeof(usbtmc_state.ep_bulk_in_buf); - usbtmc_state.transfer_size_remaining -= sizeof(usbtmc_state.ep_bulk_in_buf); - usbtmc_state.transfer_size_sent += sizeof(usbtmc_state.ep_bulk_in_buf); - } - else // last packet - { - size_t packetLen = usbtmc_state.transfer_size_remaining; - memcpy(usbtmc_state.ep_bulk_in_buf, usbtmc_state.devInBuffer, usbtmc_state.transfer_size_remaining); - usbtmc_state.transfer_size_sent += sizeof(usbtmc_state.transfer_size_remaining); - usbtmc_state.transfer_size_remaining = 0; - usbtmc_state.devInBuffer = NULL; - TU_VERIFY( usbd_edpt_xfer(rhport, usbtmc_state.ep_bulk_in, usbtmc_state.ep_bulk_in_buf,(uint16_t)packetLen)); - if(((packetLen % USBTMCD_MAX_PACKET_SIZE) != 0) || (packetLen == 0 )) + case STATE_TX_INITIATED: + if (usbtmc_state.transfer_size_remaining >= USBTMCD_BUFFER_SIZE) { + // Copy buffer to ensure alignment correctness + memcpy(usbtmc_epbuf.epin, usbtmc_state.devInBuffer, USBTMCD_BUFFER_SIZE); + TU_VERIFY(usbd_edpt_xfer(rhport, usbtmc_state.ep_bulk_in, usbtmc_epbuf.epin, USBTMCD_BUFFER_SIZE)); + usbtmc_state.devInBuffer += USBTMCD_BUFFER_SIZE; + usbtmc_state.transfer_size_remaining -= USBTMCD_BUFFER_SIZE; + usbtmc_state.transfer_size_sent += USBTMCD_BUFFER_SIZE; + } else// last packet { - usbtmc_state.state = STATE_TX_SHORTED; + size_t packetLen = usbtmc_state.transfer_size_remaining; + memcpy(usbtmc_epbuf.epin, usbtmc_state.devInBuffer, usbtmc_state.transfer_size_remaining); + usbtmc_state.transfer_size_sent += packetLen; + usbtmc_state.transfer_size_remaining = 0; + usbtmc_state.devInBuffer = NULL; + TU_VERIFY(usbd_edpt_xfer(rhport, usbtmc_state.ep_bulk_in, usbtmc_epbuf.epin, (uint16_t) packetLen)); + if (((packetLen % usbtmc_state.ep_bulk_in_wMaxPacketSize) != 0) || (packetLen == 0)) { + usbtmc_state.state = STATE_TX_SHORTED; + } } - } - return true; - case STATE_ABORTING_BULK_IN: - // need to send short packet (ZLP?) - TU_VERIFY( usbd_edpt_xfer(rhport, usbtmc_state.ep_bulk_in, usbtmc_state.ep_bulk_in_buf,(uint16_t)0u)); - usbtmc_state.state = STATE_ABORTING_BULK_IN_SHORTED; - return true; - case STATE_ABORTING_BULK_IN_SHORTED: - /* Done. :)*/ - usbtmc_state.state = STATE_ABORTING_BULK_IN_ABORTED; - return true; - default: - TU_ASSERT(false); - return false; + return true; + + case STATE_ABORTING_BULK_IN: + // need to send short packet (ZLP?) + TU_VERIFY(usbd_edpt_xfer(rhport, usbtmc_state.ep_bulk_in, usbtmc_epbuf.epin, (uint16_t) 0u)); + usbtmc_state.state = STATE_ABORTING_BULK_IN_SHORTED; + return true; + + case STATE_ABORTING_BULK_IN_SHORTED: + /* Done. :)*/ + usbtmc_state.state = STATE_ABORTING_BULK_IN_ABORTED; + return true; + + default: + TU_ASSERT(false); } - } - else if (ep_addr == usbtmc_state.ep_int_in) { - // Good? + } else if (ep_addr == usbtmc_state.ep_int_in) { + TU_VERIFY(tud_usbtmc_notification_complete_cb()); return true; } return false; } -bool usbtmcd_control_request_cb(uint8_t rhport, tusb_control_request_t const * request) { +// Invoked when a control transfer occurred on an interface of this class +// Driver response accordingly to the request and the transfer stage (setup/data/ack) +// return false to stall control endpoint (e.g unsupported request) +bool usbtmcd_control_xfer_cb(uint8_t rhport, uint8_t stage, tusb_control_request_t const *request) { + // nothing to do with DATA and ACK stage + if (stage != CONTROL_STAGE_SETUP) return true; uint8_t tmcStatusCode = USBTMC_STATUS_FAILED; #if (CFG_TUD_USBTMC_ENABLE_488) uint8_t bTag; #endif - if((request->bmRequestType_bit.type == TUSB_REQ_TYPE_STANDARD) && + if ((request->bmRequestType_bit.type == TUSB_REQ_TYPE_STANDARD) && (request->bmRequestType_bit.recipient == TUSB_REQ_RCPT_ENDPOINT) && (request->bRequest == TUSB_REQ_CLEAR_FEATURE) && - (request->wValue == TUSB_REQ_FEATURE_EDPT_HALT)) - { + (request->wValue == TUSB_REQ_FEATURE_EDPT_HALT)) { uint32_t ep_addr = (request->wIndex); - if(ep_addr == usbtmc_state.ep_bulk_out) - { + // At this point, a transfer MAY be in progress. Based on USB spec, when clearing bulk EP HALT, + // the EP transfer buffer needs to be cleared and DTOG needs to be reset, even if + // the EP is not halted. The only USBD API interface to do this is to stall and then un-stall the EP. + if (ep_addr == usbtmc_state.ep_bulk_out) { criticalEnter(); - usbtmc_state.state = STATE_NAK; // USBD core has placed EP in NAK state for us + usbd_edpt_stall(rhport, (uint8_t) ep_addr); + usbd_edpt_clear_stall(rhport, (uint8_t) ep_addr); + usbtmc_state.state = STATE_NAK;// USBD core has placed EP in NAK state for us criticalLeave(); tud_usbtmc_bulkOut_clearFeature_cb(); - } - else if (ep_addr == usbtmc_state.ep_bulk_in) - { + } else if (ep_addr == usbtmc_state.ep_bulk_in) { + usbd_edpt_stall(rhport, (uint8_t) ep_addr); + usbd_edpt_clear_stall(rhport, (uint8_t) ep_addr); tud_usbtmc_bulkIn_clearFeature_cb(); - } - else - { + } else if ((usbtmc_state.ep_int_in != 0) && (ep_addr == usbtmc_state.ep_int_in)) { + // Clearing interrupt in EP + usbd_edpt_stall(rhport, (uint8_t) ep_addr); + usbd_edpt_clear_stall(rhport, (uint8_t) ep_addr); + } else { return false; } return true; } // Otherwise, we only handle class requests. - if(request->bmRequestType_bit.type != TUSB_REQ_TYPE_CLASS) - { + if (request->bmRequestType_bit.type != TUSB_REQ_TYPE_CLASS) { return false; } // Verification that we own the interface is unneeded since it's been routed to us specifically. - switch(request->bRequest) - { - // USBTMC required requests - case USBTMC_bREQUEST_INITIATE_ABORT_BULK_OUT: - { - usbtmc_initiate_abort_rsp_t rsp = { - .bTag = usbtmc_state.lastBulkOutTag, - }; - TU_VERIFY(request->bmRequestType == 0xA2); // in,class,interface - TU_VERIFY(request->wLength == sizeof(rsp)); - TU_VERIFY(request->wIndex == usbtmc_state.ep_bulk_out); - - // wValue is the requested bTag to abort - if(usbtmc_state.state != STATE_RCV) - { - rsp.USBTMC_status = USBTMC_STATUS_FAILED; + switch (request->bRequest) { + // USBTMC required requests + case USBTMC_bREQUEST_INITIATE_ABORT_BULK_OUT: { + usbtmc_initiate_abort_rsp_t rsp = { + .bTag = usbtmc_state.lastBulkOutTag, + }; + TU_VERIFY(request->bmRequestType == 0xA2);// in,class,interface + TU_VERIFY(request->wLength == sizeof(rsp)); + TU_VERIFY(request->wIndex == usbtmc_state.ep_bulk_out); + + // wValue is the requested bTag to abort + if (usbtmc_state.state != STATE_RCV) { + rsp.USBTMC_status = USBTMC_STATUS_FAILED; + } else if (usbtmc_state.lastBulkOutTag == (request->wValue & 0x7Fu)) { + rsp.USBTMC_status = USBTMC_STATUS_TRANSFER_NOT_IN_PROGRESS; + } else { + rsp.USBTMC_status = USBTMC_STATUS_SUCCESS; + // Check if we've queued a short packet + criticalEnter(); + usbtmc_state.state = STATE_ABORTING_BULK_OUT; + criticalLeave(); + TU_VERIFY(tud_usbtmc_initiate_abort_bulk_out_cb(&(rsp.USBTMC_status))); + usbd_edpt_stall(rhport, usbtmc_state.ep_bulk_out); + } + TU_VERIFY(tud_control_xfer(rhport, request, (void *) &rsp, sizeof(rsp))); + return true; } - else if(usbtmc_state.lastBulkOutTag == (request->wValue & 0x7Fu)) - { - rsp.USBTMC_status = USBTMC_STATUS_TRANSFER_NOT_IN_PROGRESS; + + case USBTMC_bREQUEST_CHECK_ABORT_BULK_OUT_STATUS: { + usbtmc_check_abort_bulk_rsp_t rsp = { + .USBTMC_status = USBTMC_STATUS_SUCCESS, + .NBYTES_RXD_TXD = usbtmc_state.transfer_size_sent}; + TU_VERIFY(request->bmRequestType == 0xA2);// in,class,EP + TU_VERIFY(request->wLength == sizeof(rsp)); + TU_VERIFY(request->wIndex == usbtmc_state.ep_bulk_out); + TU_VERIFY(tud_usbtmc_check_abort_bulk_out_cb(&rsp)); + TU_VERIFY(tud_control_xfer(rhport, request, (void *) &rsp, sizeof(rsp))); + return true; } - else - { - rsp.USBTMC_status = USBTMC_STATUS_SUCCESS; - // Check if we've queued a short packet - criticalEnter(); - usbtmc_state.state = STATE_ABORTING_BULK_OUT; - criticalLeave(); - TU_VERIFY(tud_usbtmc_initiate_abort_bulk_out_cb(&(rsp.USBTMC_status))); - usbd_edpt_stall(rhport, usbtmc_state.ep_bulk_out); + + case USBTMC_bREQUEST_INITIATE_ABORT_BULK_IN: { + usbtmc_initiate_abort_rsp_t rsp = { + .bTag = usbtmc_state.lastBulkInTag, + }; + TU_VERIFY(request->bmRequestType == 0xA2);// in,class,interface + TU_VERIFY(request->wLength == sizeof(rsp)); + TU_VERIFY(request->wIndex == usbtmc_state.ep_bulk_in); + // wValue is the requested bTag to abort + if ((usbtmc_state.state == STATE_TX_REQUESTED || usbtmc_state.state == STATE_TX_INITIATED) && + usbtmc_state.lastBulkInTag == (request->wValue & 0x7Fu)) { + rsp.USBTMC_status = USBTMC_STATUS_SUCCESS; + usbtmc_state.transfer_size_remaining = 0u; + // Check if we've queued a short packet + criticalEnter(); + usbtmc_state.state = ((usbtmc_state.transfer_size_sent % usbtmc_state.ep_bulk_in_wMaxPacketSize) == 0) ? STATE_ABORTING_BULK_IN : STATE_ABORTING_BULK_IN_SHORTED; + criticalLeave(); + if (usbtmc_state.transfer_size_sent == 0) { + // Send short packet, nothing is in the buffer yet + TU_VERIFY(usbd_edpt_xfer(rhport, usbtmc_state.ep_bulk_in, usbtmc_epbuf.epin, (uint16_t) 0u)); + usbtmc_state.state = STATE_ABORTING_BULK_IN_SHORTED; + } + TU_VERIFY(tud_usbtmc_initiate_abort_bulk_in_cb(&(rsp.USBTMC_status))); + } else if ((usbtmc_state.state == STATE_TX_REQUESTED || usbtmc_state.state == STATE_TX_INITIATED)) {// FIXME: Unsure how to check if the OUT endpoint fifo is non-empty.... + rsp.USBTMC_status = USBTMC_STATUS_TRANSFER_NOT_IN_PROGRESS; + } else { + rsp.USBTMC_status = USBTMC_STATUS_FAILED; + } + TU_VERIFY(tud_control_xfer(rhport, request, (void *) &rsp, sizeof(rsp))); + return true; } - TU_VERIFY(tud_control_xfer(rhport, request, (void*)&rsp,sizeof(rsp))); - return true; - } - case USBTMC_bREQUEST_CHECK_ABORT_BULK_OUT_STATUS: - { - usbtmc_check_abort_bulk_rsp_t rsp = { - .USBTMC_status = USBTMC_STATUS_SUCCESS, - .NBYTES_RXD_TXD = usbtmc_state.transfer_size_sent - }; - TU_VERIFY(request->bmRequestType == 0xA2); // in,class,EP - TU_VERIFY(request->wLength == sizeof(rsp)); - TU_VERIFY(request->wIndex == usbtmc_state.ep_bulk_out); - TU_VERIFY(tud_usbtmc_check_abort_bulk_out_cb(&rsp)); - TU_VERIFY(tud_control_xfer(rhport, request, (void*)&rsp,sizeof(rsp))); - return true; - } + case USBTMC_bREQUEST_CHECK_ABORT_BULK_IN_STATUS: { + TU_VERIFY(request->bmRequestType == 0xA2);// in,class,EP + TU_VERIFY(request->wLength == 8u); - case USBTMC_bREQUEST_INITIATE_ABORT_BULK_IN: - { - usbtmc_initiate_abort_rsp_t rsp = { - .bTag = usbtmc_state.lastBulkInTag, - }; - TU_VERIFY(request->bmRequestType == 0xA2); // in,class,interface - TU_VERIFY(request->wLength == sizeof(rsp)); - TU_VERIFY(request->wIndex == usbtmc_state.ep_bulk_in); - // wValue is the requested bTag to abort - if((usbtmc_state.state == STATE_TX_REQUESTED || usbtmc_state.state == STATE_TX_INITIATED) && - usbtmc_state.lastBulkInTag == (request->wValue & 0x7Fu)) - { - rsp.USBTMC_status = USBTMC_STATUS_SUCCESS; - usbtmc_state.transfer_size_remaining = 0u; - // Check if we've queued a short packet + usbtmc_check_abort_bulk_rsp_t rsp = + { + .USBTMC_status = USBTMC_STATUS_FAILED, + .bmAbortBulkIn = + { + .BulkInFifoBytes = (usbtmc_state.state != STATE_ABORTING_BULK_IN_ABORTED)}, + .NBYTES_RXD_TXD = usbtmc_state.transfer_size_sent, + }; + TU_VERIFY(tud_usbtmc_check_abort_bulk_in_cb(&rsp)); criticalEnter(); - usbtmc_state.state = ((usbtmc_state.transfer_size_sent % USBTMCD_MAX_PACKET_SIZE) == 0) ? - STATE_ABORTING_BULK_IN : STATE_ABORTING_BULK_IN_SHORTED; - criticalLeave(); - if(usbtmc_state.transfer_size_sent == 0) - { - // Send short packet, nothing is in the buffer yet - TU_VERIFY( usbd_edpt_xfer(rhport, usbtmc_state.ep_bulk_in, usbtmc_state.ep_bulk_in_buf,(uint16_t)0u)); - usbtmc_state.state = STATE_ABORTING_BULK_IN_SHORTED; + switch (usbtmc_state.state) { + case STATE_ABORTING_BULK_IN_ABORTED: + rsp.USBTMC_status = USBTMC_STATUS_SUCCESS; + usbtmc_state.state = STATE_IDLE; + break; + case STATE_ABORTING_BULK_IN: + case STATE_ABORTING_BULK_OUT: + rsp.USBTMC_status = USBTMC_STATUS_PENDING; + break; + default: + break; } - TU_VERIFY(tud_usbtmc_initiate_abort_bulk_in_cb(&(rsp.USBTMC_status))); - } - else if((usbtmc_state.state == STATE_TX_REQUESTED || usbtmc_state.state == STATE_TX_INITIATED)) - { // FIXME: Unsure how to check if the OUT endpoint fifo is non-empty.... - rsp.USBTMC_status = USBTMC_STATUS_TRANSFER_NOT_IN_PROGRESS; - } - else - { - rsp.USBTMC_status = USBTMC_STATUS_FAILED; - } - TU_VERIFY(tud_control_xfer(rhport, request, (void*)&rsp,sizeof(rsp))); - return true; - } - - case USBTMC_bREQUEST_CHECK_ABORT_BULK_IN_STATUS: - { - TU_VERIFY(request->bmRequestType == 0xA2); // in,class,EP - TU_VERIFY(request->wLength == 8u); + criticalLeave(); + TU_VERIFY(tud_control_xfer(rhport, request, (void *) &rsp, sizeof(rsp))); - usbtmc_check_abort_bulk_rsp_t rsp = - { - .USBTMC_status = USBTMC_STATUS_FAILED, - .bmAbortBulkIn = - { - .BulkInFifoBytes = (usbtmc_state.state != STATE_ABORTING_BULK_IN_ABORTED) - }, - .NBYTES_RXD_TXD = usbtmc_state.transfer_size_sent, - }; - TU_VERIFY(tud_usbtmc_check_abort_bulk_in_cb(&rsp)); - criticalEnter(); - switch(usbtmc_state.state) - { - case STATE_ABORTING_BULK_IN_ABORTED: - rsp.USBTMC_status = USBTMC_STATUS_SUCCESS; - usbtmc_state.state = STATE_IDLE; - break; - case STATE_ABORTING_BULK_IN: - case STATE_ABORTING_BULK_OUT: - rsp.USBTMC_status = USBTMC_STATUS_PENDING; - break; - default: - break; + return true; } - criticalLeave(); - TU_VERIFY(tud_control_xfer(rhport, request, (void*)&rsp,sizeof(rsp))); - - return true; - } - case USBTMC_bREQUEST_INITIATE_CLEAR: - { - TU_VERIFY(request->bmRequestType == 0xA1); // in,class,interface + case USBTMC_bREQUEST_INITIATE_CLEAR: { + TU_VERIFY(request->bmRequestType == 0xA1);// in,class,interface TU_VERIFY(request->wLength == sizeof(tmcStatusCode)); // After receiving an INITIATE_CLEAR request, the device must Halt the Bulk-OUT endpoint, queue the // control endpoint response shown in Table 31, and clear all input buffers and output buffers. @@ -750,118 +769,98 @@ bool usbtmcd_control_request_cb(uint8_t rhport, tusb_control_request_t const * r usbtmc_state.state = STATE_CLEARING; criticalLeave(); TU_VERIFY(tud_usbtmc_initiate_clear_cb(&tmcStatusCode)); - TU_VERIFY(tud_control_xfer(rhport, request, (void*)&tmcStatusCode,sizeof(tmcStatusCode))); + TU_VERIFY(tud_control_xfer(rhport, request, (void *) &tmcStatusCode, sizeof(tmcStatusCode))); return true; } - case USBTMC_bREQUEST_CHECK_CLEAR_STATUS: - { - TU_VERIFY(request->bmRequestType == 0xA1); // in,class,interface + case USBTMC_bREQUEST_CHECK_CLEAR_STATUS: { + TU_VERIFY(request->bmRequestType == 0xA1);// in,class,interface usbtmc_get_clear_status_rsp_t clearStatusRsp = {0}; TU_VERIFY(request->wLength == sizeof(clearStatusRsp)); - if(usbd_edpt_busy(rhport, usbtmc_state.ep_bulk_in)) - { + if (usbd_edpt_busy(rhport, usbtmc_state.ep_bulk_in)) { // Stuff stuck in TX buffer? clearStatusRsp.bmClear.BulkInFifoBytes = 1; clearStatusRsp.USBTMC_status = USBTMC_STATUS_PENDING; - } - else - { + } else { // Let app check if it's clear TU_VERIFY(tud_usbtmc_check_clear_cb(&clearStatusRsp)); } - if(clearStatusRsp.USBTMC_status == USBTMC_STATUS_SUCCESS) - { + if (clearStatusRsp.USBTMC_status == USBTMC_STATUS_SUCCESS) { criticalEnter(); usbtmc_state.state = STATE_IDLE; criticalLeave(); } - TU_VERIFY(tud_control_xfer(rhport, request, (void*)&clearStatusRsp,sizeof(clearStatusRsp))); + TU_VERIFY(tud_control_xfer(rhport, request, (void *) &clearStatusRsp, sizeof(clearStatusRsp))); return true; } - case USBTMC_bREQUEST_GET_CAPABILITIES: - { - TU_VERIFY(request->bmRequestType == 0xA1); // in,class,interface + case USBTMC_bREQUEST_GET_CAPABILITIES: { + TU_VERIFY(request->bmRequestType == 0xA1);// in,class,interface TU_VERIFY(request->wLength == sizeof(*(usbtmc_state.capabilities))); - TU_VERIFY(tud_control_xfer(rhport, request, (void*)usbtmc_state.capabilities, sizeof(*usbtmc_state.capabilities))); + TU_VERIFY(tud_control_xfer(rhport, request, (void *) (uintptr_t) usbtmc_state.capabilities, sizeof(*usbtmc_state.capabilities))); return true; } - // USBTMC Optional Requests + // USBTMC Optional Requests - case USBTMC_bREQUEST_INDICATOR_PULSE: // Optional + case USBTMC_bREQUEST_INDICATOR_PULSE:// Optional { - TU_VERIFY(request->bmRequestType == 0xA1); // in,class,interface + TU_VERIFY(request->bmRequestType == 0xA1);// in,class,interface TU_VERIFY(request->wLength == sizeof(tmcStatusCode)); TU_VERIFY(usbtmc_state.capabilities->bmIntfcCapabilities.supportsIndicatorPulse); TU_VERIFY(tud_usbtmc_indicator_pulse_cb(request, &tmcStatusCode)); - TU_VERIFY(tud_control_xfer(rhport, request, (void*)&tmcStatusCode, sizeof(tmcStatusCode))); + TU_VERIFY(tud_control_xfer(rhport, request, (void *) &tmcStatusCode, sizeof(tmcStatusCode))); return true; } #if (CFG_TUD_USBTMC_ENABLE_488) - // USB488 required requests - case USB488_bREQUEST_READ_STATUS_BYTE: - { + // USB488 required requests + case USB488_bREQUEST_READ_STATUS_BYTE: { usbtmc_read_stb_rsp_488_t rsp; TU_VERIFY(request->bmRequestType == 0xA1); // in,class,interface - TU_VERIFY(request->wLength == sizeof(rsp)); // in,class,interface + TU_VERIFY(request->wLength == sizeof(rsp));// in,class,interface bTag = request->wValue & 0x7F; TU_VERIFY(request->bmRequestType == 0xA1); - TU_VERIFY((request->wValue & (~0x7F)) == 0u); // Other bits are required to be zero + TU_VERIFY((request->wValue & (~0x7F)) == 0u);// Other bits are required to be zero (USB488v1.0 Table 11) TU_VERIFY(bTag >= 0x02 && bTag <= 127); TU_VERIFY(request->wIndex == usbtmc_state.itf_id); TU_VERIFY(request->wLength == 0x0003); - rsp.bTag = (uint8_t)bTag; - if(usbtmc_state.ep_int_in != 0) - { - rsp.USBTMC_status = USBTMC_STATUS_SUCCESS; - rsp.statusByte = 0x00; // Use interrupt endpoint, instead. - - usbtmc_read_stb_interrupt_488_t intMsg = - { - .bNotify1 = { - .one = 1, - .bTag = bTag & 0x7Fu, - }, - .StatusByte = tud_usbtmc_get_stb_cb(&(rsp.USBTMC_status)) - }; - usbd_edpt_xfer(rhport, usbtmc_state.ep_int_in, (void*)&intMsg, sizeof(intMsg)); - } - else - { + rsp.bTag = (uint8_t) bTag; + if (usbtmc_state.ep_int_in != 0) { + rsp.statusByte = 0x00;// Use interrupt endpoint, instead. Must be 0x00 (USB488v1.0 4.3.1.2) + if (usbd_edpt_busy(rhport, usbtmc_state.ep_int_in)) { + rsp.USBTMC_status = USB488_STATUS_INTERRUPT_IN_BUSY; + } else { + rsp.USBTMC_status = USBTMC_STATUS_SUCCESS; + usbtmc_read_stb_interrupt_488_t intMsg = + { + .bNotify1 = { + .one = 1, + .bTag = bTag & 0x7Fu, + }, + .StatusByte = tud_usbtmc_get_stb_cb(&(rsp.USBTMC_status))}; + // Must be queued before control request response sent (USB488v1.0 4.3.1.2) + usbd_edpt_xfer(rhport, usbtmc_state.ep_int_in, (void *) &intMsg, sizeof(intMsg)); + } + } else { rsp.statusByte = tud_usbtmc_get_stb_cb(&(rsp.USBTMC_status)); } - TU_VERIFY(tud_control_xfer(rhport, request, (void*)&rsp, sizeof(rsp))); + TU_VERIFY(tud_control_xfer(rhport, request, (void *) &rsp, sizeof(rsp))); return true; } - // USB488 optional requests - case USB488_bREQUEST_REN_CONTROL: - case USB488_bREQUEST_GO_TO_LOCAL: - case USB488_bREQUEST_LOCAL_LOCKOUT: - { - TU_VERIFY(request->bmRequestType == 0xA1); // in,class,interface - TU_VERIFY(false); + // USB488 optional requests + case USB488_bREQUEST_REN_CONTROL: + case USB488_bREQUEST_GO_TO_LOCAL: + case USB488_bREQUEST_LOCAL_LOCKOUT: { + TU_VERIFY(request->bmRequestType == 0xA1);// in,class,interface return false; } #endif - default: - TU_VERIFY(false); - return false; + default: + return false; } - TU_VERIFY(false); -} - -bool usbtmcd_control_complete_cb(uint8_t rhport, tusb_control_request_t const * request) -{ - (void)rhport; - //------------- Class Specific Request -------------// - TU_ASSERT (request->bmRequestType_bit.type == TUSB_REQ_TYPE_CLASS); - - return true; } #endif /* CFG_TUD_TSMC */ diff --git a/cores/nRF5/TinyUSB/Adafruit_TinyUSB_ArduinoCore/tinyusb/src/class/usbtmc/usbtmc_device.h b/cores/nRF5/TinyUSB/Adafruit_TinyUSB_ArduinoCore/tinyusb/src/class/usbtmc/usbtmc_device.h index a6b5e4c..235f127 100644 --- a/cores/nRF5/TinyUSB/Adafruit_TinyUSB_ArduinoCore/tinyusb/src/class/usbtmc/usbtmc_device.h +++ b/cores/nRF5/TinyUSB/Adafruit_TinyUSB_ArduinoCore/tinyusb/src/class/usbtmc/usbtmc_device.h @@ -1,9 +1,3 @@ -/* - * usbtmc_device.h - * - * Created on: Sep 10, 2019 - * Author: nconrad - */ /* * The MIT License (MIT) * @@ -41,12 +35,8 @@ #define CFG_TUD_USBTMC_ENABLE_488 (1) #endif -// USB spec says that full-speed must be 8,16,32, or 64. -// However, this driver implementation requires it to be >=32 -#define USBTMCD_MAX_PACKET_SIZE (64u) - /*********************************************** - * Functions to be implemeted by the class implementation + * Functions to be implemented by the class implementation */ // In order to proceed, app must call call tud_usbtmc_start_bus_read(rhport) during or soon after: @@ -83,41 +73,45 @@ bool tud_usbtmc_check_abort_bulk_in_cb(usbtmc_check_abort_bulk_rsp_t *rsp); bool tud_usbtmc_check_abort_bulk_out_cb(usbtmc_check_abort_bulk_rsp_t *rsp); bool tud_usbtmc_check_clear_cb(usbtmc_get_clear_status_rsp_t *rsp); +// The interrupt-IN endpoint buffer was transmitted to the host. Use +// tud_usbtmc_transmit_notification_data to send another notification. +bool tud_usbtmc_notification_complete_cb(void); + // Indicator pulse should be 0.5 to 1.0 seconds long -TU_ATTR_WEAK bool tud_usbtmc_indicator_pulse_cb(tusb_control_request_t const * msg, uint8_t *tmcResult); +bool tud_usbtmc_indicator_pulse_cb(tusb_control_request_t const * msg, uint8_t *tmcResult); #if (CFG_TUD_USBTMC_ENABLE_488) uint8_t tud_usbtmc_get_stb_cb(uint8_t *tmcResult); -TU_ATTR_WEAK bool tud_usbtmc_msg_trigger_cb(usbtmc_msg_generic_t* msg); -//TU_ATTR_WEAK bool tud_usbtmc_app_go_to_local_cb(); +bool tud_usbtmc_msg_trigger_cb(usbtmc_msg_generic_t* msg); #endif -/******************************************* - * Called from app - * - * We keep a reference to the buffer, so it MUST not change until the app is - * notified that the transfer is complete. - ******************************************/ - +// Called from app +// +// We keep a reference to the buffer, so it MUST not change until the app is +// notified that the transfer is complete. bool tud_usbtmc_transmit_dev_msg_data( const void * data, size_t len, bool endOfMessage, bool usingTermChar); +// Buffers a notification to be sent to the host. The data starts +// with the bNotify1 field, see the USBTMC Specification, Table 13. +// +// If the previous notification data has not yet been sent, this +// returns false. +// +// Requires an interrupt endpoint in the interface. +bool tud_usbtmc_transmit_notification_data(const void * data, size_t len); + bool tud_usbtmc_start_bus_read(void); /* "callbacks" from USB device core */ +void usbtmcd_init_cb(void); +bool usbtmcd_deinit(void); uint16_t usbtmcd_open_cb(uint8_t rhport, tusb_desc_interface_t const * itf_desc, uint16_t max_len); void usbtmcd_reset_cb(uint8_t rhport); bool usbtmcd_xfer_cb(uint8_t rhport, uint8_t ep_addr, xfer_result_t result, uint32_t xferred_bytes); -bool usbtmcd_control_request_cb(uint8_t rhport, tusb_control_request_t const * request); -bool usbtmcd_control_complete_cb(uint8_t rhport, tusb_control_request_t const * request); -void usbtmcd_init_cb(void); - -/************************************************************ - * USBTMC Descriptor Templates - *************************************************************/ - +bool usbtmcd_control_xfer_cb(uint8_t rhport, uint8_t stage, tusb_control_request_t const * request); #endif /* CLASS_USBTMC_USBTMC_DEVICE_H_ */ diff --git a/cores/nRF5/TinyUSB/Adafruit_TinyUSB_ArduinoCore/tinyusb/src/class/vendor/vendor_device.c b/cores/nRF5/TinyUSB/Adafruit_TinyUSB_ArduinoCore/tinyusb/src/class/vendor/vendor_device.c index 3fcea89..27724b1 100644 --- a/cores/nRF5/TinyUSB/Adafruit_TinyUSB_ArduinoCore/tinyusb/src/class/vendor/vendor_device.c +++ b/cores/nRF5/TinyUSB/Adafruit_TinyUSB_ArduinoCore/tinyusb/src/class/vendor/vendor_device.c @@ -1,4 +1,4 @@ -/* +/* * The MIT License (MIT) * * Copyright (c) 2019 Ha Thach (tinyusb.org) @@ -26,209 +26,267 @@ #include "tusb_option.h" -#if (TUSB_OPT_DEVICE_ENABLED && CFG_TUD_VENDOR) +#if (CFG_TUD_ENABLED && CFG_TUD_VENDOR) -#include "vendor_device.h" +#include "device/usbd.h" #include "device/usbd_pvt.h" +#include "vendor_device.h" + //--------------------------------------------------------------------+ // MACRO CONSTANT TYPEDEF //--------------------------------------------------------------------+ -typedef struct -{ +typedef struct { uint8_t itf_num; - uint8_t ep_in; - uint8_t ep_out; /*------------- From this point, data is not cleared by bus reset -------------*/ - tu_fifo_t rx_ff; - tu_fifo_t tx_ff; - - uint8_t rx_ff_buf[CFG_TUD_VENDOR_RX_BUFSIZE]; - uint8_t tx_ff_buf[CFG_TUD_VENDOR_TX_BUFSIZE]; - -#if CFG_FIFO_MUTEX - osal_mutex_def_t rx_ff_mutex; - osal_mutex_def_t tx_ff_mutex; -#endif + struct { + tu_edpt_stream_t stream; + #if CFG_TUD_VENDOR_TX_BUFSIZE > 0 + uint8_t ff_buf[CFG_TUD_VENDOR_TX_BUFSIZE]; + #endif + } tx; + + struct { + tu_edpt_stream_t stream; + #if CFG_TUD_VENDOR_RX_BUFSIZE > 0 + uint8_t ff_buf[CFG_TUD_VENDOR_RX_BUFSIZE]; + #endif + } rx; - // Endpoint Transfer buffer - CFG_TUSB_MEM_ALIGN uint8_t epout_buf[CFG_TUD_VENDOR_EPSIZE]; - CFG_TUSB_MEM_ALIGN uint8_t epin_buf[CFG_TUD_VENDOR_EPSIZE]; } vendord_interface_t; -CFG_TUSB_MEM_SECTION static vendord_interface_t _vendord_itf[CFG_TUD_VENDOR]; +#define ITF_MEM_RESET_SIZE (offsetof(vendord_interface_t, itf_num) + sizeof(((vendord_interface_t *)0)->itf_num)) -#define ITF_MEM_RESET_SIZE offsetof(vendord_interface_t, rx_ff) +static vendord_interface_t _vendord_itf[CFG_TUD_VENDOR]; +typedef struct { + TUD_EPBUF_DEF(epout, CFG_TUD_VENDOR_EPSIZE); + TUD_EPBUF_DEF(epin, CFG_TUD_VENDOR_EPSIZE); +} vendord_epbuf_t; -bool tud_vendor_n_mounted (uint8_t itf) -{ - return _vendord_itf[itf].ep_in && _vendord_itf[itf].ep_out; +CFG_TUD_MEM_SECTION static vendord_epbuf_t _vendord_epbuf[CFG_TUD_VENDOR]; + +//--------------------------------------------------------------------+ +// Weak stubs: invoked if no strong implementation is available +//--------------------------------------------------------------------+ +TU_ATTR_WEAK void tud_vendor_rx_cb(uint8_t itf, uint8_t const* buffer, uint16_t bufsize) { + (void) itf; + (void) buffer; + (void) bufsize; } -uint32_t tud_vendor_n_available (uint8_t itf) -{ - return tu_fifo_count(&_vendord_itf[itf].rx_ff); +TU_ATTR_WEAK void tud_vendor_tx_cb(uint8_t itf, uint32_t sent_bytes) { + (void) itf; + (void) sent_bytes; } -bool tud_vendor_n_peek(uint8_t itf, int pos, uint8_t* u8) -{ - return tu_fifo_peek_at(&_vendord_itf[itf].rx_ff, pos, u8); +//-------------------------------------------------------------------- +// Application API +//-------------------------------------------------------------------- + +bool tud_vendor_n_mounted(uint8_t itf) { + TU_VERIFY(itf < CFG_TUD_VENDOR); + vendord_interface_t* p_itf = &_vendord_itf[itf]; + return p_itf->rx.stream.ep_addr || p_itf->tx.stream.ep_addr; } //--------------------------------------------------------------------+ // Read API //--------------------------------------------------------------------+ -static void _prep_out_transaction (vendord_interface_t* p_itf) -{ - // skip if previous transfer not complete - if ( usbd_edpt_busy(TUD_OPT_RHPORT, p_itf->ep_out) ) return; - - // Prepare for incoming data but only allow what we can store in the ring buffer. - uint16_t max_read = tu_fifo_remaining(&p_itf->rx_ff); - if ( max_read >= CFG_TUD_VENDOR_EPSIZE ) - { - usbd_edpt_xfer(TUD_OPT_RHPORT, p_itf->ep_out, p_itf->epout_buf, CFG_TUD_VENDOR_EPSIZE); - } +uint32_t tud_vendor_n_available(uint8_t itf) { + TU_VERIFY(itf < CFG_TUD_VENDOR, 0); + vendord_interface_t* p_itf = &_vendord_itf[itf]; + + return tu_edpt_stream_read_available(&p_itf->rx.stream); +} + +bool tud_vendor_n_peek(uint8_t itf, uint8_t* u8) { + TU_VERIFY(itf < CFG_TUD_VENDOR); + vendord_interface_t* p_itf = &_vendord_itf[itf]; + + return tu_edpt_stream_peek(&p_itf->rx.stream, u8); } -uint32_t tud_vendor_n_read (uint8_t itf, void* buffer, uint32_t bufsize) -{ +uint32_t tud_vendor_n_read (uint8_t itf, void* buffer, uint32_t bufsize) { + TU_VERIFY(itf < CFG_TUD_VENDOR, 0); vendord_interface_t* p_itf = &_vendord_itf[itf]; - uint32_t num_read = tu_fifo_read_n(&p_itf->rx_ff, buffer, bufsize); - _prep_out_transaction(p_itf); - return num_read; + const uint8_t rhport = 0; + + return tu_edpt_stream_read(rhport, &p_itf->rx.stream, buffer, bufsize); +} + +void tud_vendor_n_read_flush (uint8_t itf) { + TU_VERIFY(itf < CFG_TUD_VENDOR, ); + vendord_interface_t* p_itf = &_vendord_itf[itf]; + const uint8_t rhport = 0; + + tu_edpt_stream_clear(&p_itf->rx.stream); + tu_edpt_stream_read_xfer(rhport, &p_itf->rx.stream); } //--------------------------------------------------------------------+ // Write API //--------------------------------------------------------------------+ -static bool maybe_transmit(vendord_interface_t* p_itf) -{ - // skip if previous transfer not complete - TU_VERIFY( !usbd_edpt_busy(TUD_OPT_RHPORT, p_itf->ep_in) ); - - uint16_t count = tu_fifo_read_n(&p_itf->tx_ff, p_itf->epin_buf, CFG_TUD_VENDOR_EPSIZE); - if (count > 0) - { - TU_ASSERT( usbd_edpt_xfer(TUD_OPT_RHPORT, p_itf->ep_in, p_itf->epin_buf, count) ); - } - return true; +uint32_t tud_vendor_n_write (uint8_t itf, const void* buffer, uint32_t bufsize) { + TU_VERIFY(itf < CFG_TUD_VENDOR, 0); + vendord_interface_t* p_itf = &_vendord_itf[itf]; + const uint8_t rhport = 0; + + return tu_edpt_stream_write(rhport, &p_itf->tx.stream, buffer, (uint16_t) bufsize); } -uint32_t tud_vendor_n_write (uint8_t itf, void const* buffer, uint32_t bufsize) -{ +uint32_t tud_vendor_n_write_flush (uint8_t itf) { + TU_VERIFY(itf < CFG_TUD_VENDOR, 0); vendord_interface_t* p_itf = &_vendord_itf[itf]; - uint16_t ret = tu_fifo_write_n(&p_itf->tx_ff, buffer, bufsize); - maybe_transmit(p_itf); - return ret; + const uint8_t rhport = 0; + + return tu_edpt_stream_write_xfer(rhport, &p_itf->tx.stream); } -uint32_t tud_vendor_n_write_available (uint8_t itf) -{ - return tu_fifo_remaining(&_vendord_itf[itf].tx_ff); +uint32_t tud_vendor_n_write_available (uint8_t itf) { + TU_VERIFY(itf < CFG_TUD_VENDOR, 0); + vendord_interface_t* p_itf = &_vendord_itf[itf]; + const uint8_t rhport = 0; + + return tu_edpt_stream_write_available(rhport, &p_itf->tx.stream); } //--------------------------------------------------------------------+ // USBD Driver API //--------------------------------------------------------------------+ -void vendord_init(void) -{ +void vendord_init(void) { tu_memclr(_vendord_itf, sizeof(_vendord_itf)); - for(uint8_t i=0; i 0 + p_itf->rx.ff_buf; + #else + NULL; + #endif + + tu_edpt_stream_init(&p_itf->rx.stream, false, false, false, + rx_ff_buf, CFG_TUD_VENDOR_RX_BUFSIZE, + p_epbuf->epout, CFG_TUD_VENDOR_EPSIZE); + + uint8_t* tx_ff_buf = + #if CFG_TUD_VENDOR_TX_BUFSIZE > 0 + p_itf->tx.ff_buf; + #else + NULL; + #endif + + tu_edpt_stream_init(&p_itf->tx.stream, false, true, false, + tx_ff_buf, CFG_TUD_VENDOR_TX_BUFSIZE, + p_epbuf->epin, CFG_TUD_VENDOR_EPSIZE); + } +} - // config fifo - tu_fifo_config(&p_itf->rx_ff, p_itf->rx_ff_buf, CFG_TUD_VENDOR_RX_BUFSIZE, 1, false); - tu_fifo_config(&p_itf->tx_ff, p_itf->tx_ff_buf, CFG_TUD_VENDOR_TX_BUFSIZE, 1, false); - -#if CFG_FIFO_MUTEX - tu_fifo_config_mutex(&p_itf->rx_ff, osal_mutex_create(&p_itf->rx_ff_mutex)); - tu_fifo_config_mutex(&p_itf->tx_ff, osal_mutex_create(&p_itf->tx_ff_mutex)); -#endif +bool vendord_deinit(void) { + for(uint8_t i=0; irx.stream); + tu_edpt_stream_deinit(&p_itf->tx.stream); } + return true; } -void vendord_reset(uint8_t rhport) -{ +void vendord_reset(uint8_t rhport) { (void) rhport; - for(uint8_t i=0; irx_ff); - tu_fifo_clear(&p_itf->tx_ff); + tu_edpt_stream_clear(&p_itf->rx.stream); + tu_edpt_stream_clear(&p_itf->tx.stream); + tu_edpt_stream_close(&p_itf->rx.stream); + tu_edpt_stream_close(&p_itf->tx.stream); } } -uint16_t vendord_open(uint8_t rhport, tusb_desc_interface_t const * itf_desc, uint16_t max_len) -{ - TU_VERIFY(TUSB_CLASS_VENDOR_SPECIFIC == itf_desc->bInterfaceClass, 0); - - uint16_t const drv_len = sizeof(tusb_desc_interface_t) + itf_desc->bNumEndpoints*sizeof(tusb_desc_endpoint_t); - TU_VERIFY(max_len >= drv_len, 0); +uint16_t vendord_open(uint8_t rhport, const tusb_desc_interface_t* desc_itf, uint16_t max_len) { + TU_VERIFY(TUSB_CLASS_VENDOR_SPECIFIC == desc_itf->bInterfaceClass, 0); + const uint8_t* desc_end = (const uint8_t*)desc_itf + max_len; + const uint8_t* p_desc = tu_desc_next(desc_itf); // Find available interface vendord_interface_t* p_vendor = NULL; - for(uint8_t i=0; iep_out, &p_vendor->ep_in), 0); - - p_vendor->itf_num = itf_desc->bInterfaceNumber; + p_vendor->itf_num = desc_itf->bInterfaceNumber; + while (tu_desc_in_bounds(p_desc, desc_end)) { + const uint8_t desc_type = tu_desc_type(p_desc); + if (desc_type == TUSB_DESC_INTERFACE || desc_type == TUSB_DESC_INTERFACE_ASSOCIATION) { + break; // end of this interface + } else if (desc_type == TUSB_DESC_ENDPOINT) { + const tusb_desc_endpoint_t* desc_ep = (const tusb_desc_endpoint_t*) p_desc; + TU_ASSERT(usbd_edpt_open(rhport, desc_ep)); + + // open endpoint stream, skip if already opened + if (tu_edpt_dir(desc_ep->bEndpointAddress) == TUSB_DIR_IN) { + if (p_vendor->tx.stream.ep_addr == 0) { + tu_edpt_stream_open(&p_vendor->tx.stream, desc_ep); + tud_vendor_n_write_flush(itf); + } + } else { + if (p_vendor->rx.stream.ep_addr == 0) { + tu_edpt_stream_open(&p_vendor->rx.stream, desc_ep); + TU_ASSERT(tu_edpt_stream_read_xfer(rhport, &p_vendor->rx.stream) > 0, 0); // prepare for incoming data + } + } + } - // Prepare for incoming data - if ( !usbd_edpt_xfer(rhport, p_vendor->ep_out, p_vendor->epout_buf, sizeof(p_vendor->epout_buf)) ) - { - TU_LOG1_FAILED(); - TU_BREAKPOINT(); + p_desc = tu_desc_next(p_desc); } - return drv_len; + return (uint16_t) ((uintptr_t) p_desc - (uintptr_t) desc_itf); } -bool vendord_xfer_cb(uint8_t rhport, uint8_t ep_addr, xfer_result_t result, uint32_t xferred_bytes) -{ - (void) rhport; +bool vendord_xfer_cb(uint8_t rhport, uint8_t ep_addr, xfer_result_t result, uint32_t xferred_bytes) { (void) result; - uint8_t itf = 0; - vendord_interface_t* p_itf = _vendord_itf; - - for ( ; ; itf++, p_itf++) - { - if (itf >= TU_ARRAY_SIZE(_vendord_itf)) return false; + uint8_t itf; + vendord_interface_t* p_vendor; - if ( ( ep_addr == p_itf->ep_out ) || ( ep_addr == p_itf->ep_in ) ) break; + for (itf = 0; itf < CFG_TUD_VENDOR; itf++) { + p_vendor = &_vendord_itf[itf]; + if ((ep_addr == p_vendor->rx.stream.ep_addr) || (ep_addr == p_vendor->tx.stream.ep_addr)) { + break; + } } + TU_VERIFY(itf < CFG_TUD_VENDOR); + vendord_epbuf_t* p_epbuf = &_vendord_epbuf[itf]; - if ( ep_addr == p_itf->ep_out ) - { - // Receive new data - tu_fifo_write_n(&p_itf->rx_ff, p_itf->epout_buf, xferred_bytes); + if ( ep_addr == p_vendor->rx.stream.ep_addr ) { + // Received new data: put into stream's fifo + tu_edpt_stream_read_xfer_complete(&p_vendor->rx.stream, xferred_bytes); // Invoked callback if any - if (tud_vendor_rx_cb) tud_vendor_rx_cb(itf); - - _prep_out_transaction(p_itf); - } - else if ( ep_addr == p_itf->ep_in ) - { - // Send complete, try to send more if possible - maybe_transmit(p_itf); + tud_vendor_rx_cb(itf, p_epbuf->epout, (uint16_t) xferred_bytes); + + tu_edpt_stream_read_xfer(rhport, &p_vendor->rx.stream); + } else if ( ep_addr == p_vendor->tx.stream.ep_addr ) { + // Send complete + tud_vendor_tx_cb(itf, (uint16_t) xferred_bytes); + + #if CFG_TUD_VENDOR_TX_BUFSIZE > 0 + // try to send more if possible + if ( 0 == tu_edpt_stream_write_xfer(rhport, &p_vendor->tx.stream) ) { + // If there is no data left, a ZLP should be sent if xferred_bytes is multiple of EP Packet size and not zero + tu_edpt_stream_write_zlp_if_needed(rhport, &p_vendor->tx.stream, xferred_bytes); + } + #endif } return true; diff --git a/cores/nRF5/TinyUSB/Adafruit_TinyUSB_ArduinoCore/tinyusb/src/class/vendor/vendor_device.h b/cores/nRF5/TinyUSB/Adafruit_TinyUSB_ArduinoCore/tinyusb/src/class/vendor/vendor_device.h index a4235bf..5fe4fc9 100644 --- a/cores/nRF5/TinyUSB/Adafruit_TinyUSB_ArduinoCore/tinyusb/src/class/vendor/vendor_device.h +++ b/cores/nRF5/TinyUSB/Adafruit_TinyUSB_ArduinoCore/tinyusb/src/class/vendor/vendor_device.h @@ -1,4 +1,4 @@ -/* +/* * The MIT License (MIT) * * Copyright (c) 2019 Ha Thach (tinyusb.org) @@ -28,97 +28,111 @@ #define _TUSB_VENDOR_DEVICE_H_ #include "common/tusb_common.h" -#include "device/usbd.h" #ifndef CFG_TUD_VENDOR_EPSIZE #define CFG_TUD_VENDOR_EPSIZE 64 #endif +// RX FIFO can be disabled by setting this value to 0 +#ifndef CFG_TUD_VENDOR_RX_BUFSIZE +#define CFG_TUD_VENDOR_RX_BUFSIZE 64 +#endif + +// TX FIFO can be disabled by setting this value to 0 +#ifndef CFG_TUD_VENDOR_TX_BUFSIZE +#define CFG_TUD_VENDOR_TX_BUFSIZE 64 +#endif + #ifdef __cplusplus extern "C" { #endif //--------------------------------------------------------------------+ -// Application API (Multiple Interfaces) +// Application API (Multiple Interfaces) i.e CFG_TUD_VENDOR > 1 //--------------------------------------------------------------------+ bool tud_vendor_n_mounted (uint8_t itf); - uint32_t tud_vendor_n_available (uint8_t itf); uint32_t tud_vendor_n_read (uint8_t itf, void* buffer, uint32_t bufsize); -bool tud_vendor_n_peek (uint8_t itf, int pos, uint8_t* u8); +bool tud_vendor_n_peek (uint8_t itf, uint8_t* ui8); +void tud_vendor_n_read_flush (uint8_t itf); uint32_t tud_vendor_n_write (uint8_t itf, void const* buffer, uint32_t bufsize); +uint32_t tud_vendor_n_write_flush (uint8_t itf); uint32_t tud_vendor_n_write_available (uint8_t itf); -static inline -uint32_t tud_vendor_n_write_str (uint8_t itf, char const* str); +TU_ATTR_ALWAYS_INLINE static inline uint32_t tud_vendor_n_write_str (uint8_t itf, char const* str); -//--------------------------------------------------------------------+ -// Application API (Single Port) -//--------------------------------------------------------------------+ -static inline bool tud_vendor_mounted (void); -static inline uint32_t tud_vendor_available (void); -static inline uint32_t tud_vendor_read (void* buffer, uint32_t bufsize); -static inline bool tud_vendor_peek (int pos, uint8_t* u8); -static inline uint32_t tud_vendor_write (void const* buffer, uint32_t bufsize); -static inline uint32_t tud_vendor_write_str (char const* str); -static inline uint32_t tud_vendor_write_available (void); +// backward compatible +#define tud_vendor_n_flush(itf) tud_vendor_n_write_flush(itf) //--------------------------------------------------------------------+ -// Application Callback API (weak is optional) +// Application API (Single Port) i.e CFG_TUD_VENDOR = 1 //--------------------------------------------------------------------+ -// Invoked when received new data -TU_ATTR_WEAK void tud_vendor_rx_cb(uint8_t itf); +TU_ATTR_ALWAYS_INLINE static inline uint32_t tud_vendor_n_write_str(uint8_t itf, char const* str) { + return tud_vendor_n_write(itf, str, strlen(str)); +} -//--------------------------------------------------------------------+ -// Inline Functions -//--------------------------------------------------------------------+ +TU_ATTR_ALWAYS_INLINE static inline bool tud_vendor_mounted(void) { + return tud_vendor_n_mounted(0); +} -static inline uint32_t tud_vendor_n_write_str (uint8_t itf, char const* str) -{ - return tud_vendor_n_write(itf, str, strlen(str)); +TU_ATTR_ALWAYS_INLINE static inline uint32_t tud_vendor_available(void) { + return tud_vendor_n_available(0); } -static inline bool tud_vendor_mounted (void) -{ - return tud_vendor_n_mounted(0); +TU_ATTR_ALWAYS_INLINE static inline uint32_t tud_vendor_read(void* buffer, uint32_t bufsize) { + return tud_vendor_n_read(0, buffer, bufsize); } -static inline uint32_t tud_vendor_available (void) -{ - return tud_vendor_n_available(0); +TU_ATTR_ALWAYS_INLINE static inline bool tud_vendor_peek(uint8_t* ui8) { + return tud_vendor_n_peek(0, ui8); } -static inline uint32_t tud_vendor_read (void* buffer, uint32_t bufsize) -{ - return tud_vendor_n_read(0, buffer, bufsize); +TU_ATTR_ALWAYS_INLINE static inline void tud_vendor_read_flush(void) { + tud_vendor_n_read_flush(0); } -static inline bool tud_vendor_peek (int pos, uint8_t* u8) -{ - return tud_vendor_n_peek(0, pos, u8); +TU_ATTR_ALWAYS_INLINE static inline uint32_t tud_vendor_write(void const* buffer, uint32_t bufsize) { + return tud_vendor_n_write(0, buffer, bufsize); } -static inline uint32_t tud_vendor_write (void const* buffer, uint32_t bufsize) -{ - return tud_vendor_n_write(0, buffer, bufsize); +TU_ATTR_ALWAYS_INLINE static inline uint32_t tud_vendor_write_str(char const* str) { + return tud_vendor_n_write_str(0, str); } -static inline uint32_t tud_vendor_write_str (char const* str) -{ - return tud_vendor_n_write_str(0, str); +TU_ATTR_ALWAYS_INLINE static inline uint32_t tud_vendor_write_flush(void) { + return tud_vendor_n_write_flush(0); } -static inline uint32_t tud_vendor_write_available (void) -{ - return tud_vendor_n_write_available(0); +#if CFG_TUD_VENDOR_TX_BUFSIZE > 0 +TU_ATTR_ALWAYS_INLINE static inline uint32_t tud_vendor_write_available(void) { + return tud_vendor_n_write_available(0); } +#endif + +// backward compatible +#define tud_vendor_flush() tud_vendor_write_flush() + +//--------------------------------------------------------------------+ +// Application Callback API (weak is optional) +//--------------------------------------------------------------------+ + +// Invoked when received new data +void tud_vendor_rx_cb(uint8_t itf, uint8_t const* buffer, uint16_t bufsize); +// Invoked when last rx transfer finished +void tud_vendor_tx_cb(uint8_t itf, uint32_t sent_bytes); + +//--------------------------------------------------------------------+ +// Inline Functions +//--------------------------------------------------------------------+ + //--------------------------------------------------------------------+ // Internal Class Driver API //--------------------------------------------------------------------+ void vendord_init(void); +bool vendord_deinit(void); void vendord_reset(uint8_t rhport); uint16_t vendord_open(uint8_t rhport, tusb_desc_interface_t const * itf_desc, uint16_t max_len); bool vendord_xfer_cb(uint8_t rhport, uint8_t ep_addr, xfer_result_t event, uint32_t xferred_bytes); diff --git a/cores/nRF5/TinyUSB/Adafruit_TinyUSB_ArduinoCore/tinyusb/src/class/video/video.h b/cores/nRF5/TinyUSB/Adafruit_TinyUSB_ArduinoCore/tinyusb/src/class/video/video.h new file mode 100644 index 0000000..f348e18 --- /dev/null +++ b/cores/nRF5/TinyUSB/Adafruit_TinyUSB_ArduinoCore/tinyusb/src/class/video/video.h @@ -0,0 +1,697 @@ +/* + * The MIT License (MIT) + * + * Copyright (c) 2021 Koji KITAYAMA + * + * 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_VIDEO_H_ +#define TUSB_VIDEO_H_ + +#include "common/tusb_common.h" + +enum { + VIDEO_BCD_1_50 = 0x0150, +}; + +// Table 3-19 Color Matching Descriptor +typedef enum { + VIDEO_COLOR_PRIMARIES_UNDEFINED = 0x00, + VIDEO_COLOR_PRIMARIES_BT709, // sRGB (default) + VIDEO_COLOR_PRIMARIES_BT470_2M, + VIDEO_COLOR_PRIMARIES_BT470_2BG, + VIDEO_COLOR_PRIMARIES_SMPTE170M, + VIDEO_COLOR_PRIMARIES_SMPTE240M, +} video_color_primaries_t; + +// Table 3-19 Color Matching Descriptor +typedef enum { + VIDEO_COLOR_XFER_CH_UNDEFINED = 0x00, + VIDEO_COLOR_XFER_CH_BT709, // default + VIDEO_COLOR_XFER_CH_BT470_2M, + VIDEO_COLOR_XFER_CH_BT470_2BG, + VIDEO_COLOR_XFER_CH_SMPTE170M, + VIDEO_COLOR_XFER_CH_SMPTE240M, + VIDEO_COLOR_XFER_CH_LINEAR, + VIDEO_COLOR_XFER_CH_SRGB, +} video_color_transfer_characteristics_t; + +// Table 3-19 Color Matching Descriptor +typedef enum { + VIDEO_COLOR_COEF_UNDEFINED = 0x00, + VIDEO_COLOR_COEF_BT709, + VIDEO_COLOR_COEF_FCC, + VIDEO_COLOR_COEF_BT470_2BG, + VIDEO_COLOR_COEF_SMPTE170M, // BT.601 default + VIDEO_COLOR_COEF_SMPTE240M, +} video_color_matrix_coefficients_t; + +/* 4.2.1.2 Request Error Code Control */ +typedef enum { + VIDEO_ERROR_NONE = 0, /* The request succeeded. */ + VIDEO_ERROR_NOT_READY, + VIDEO_ERROR_WRONG_STATE, + VIDEO_ERROR_POWER, + VIDEO_ERROR_OUT_OF_RANGE, + VIDEO_ERROR_INVALID_UNIT, + VIDEO_ERROR_INVALID_CONTROL, + VIDEO_ERROR_INVALID_REQUEST, + VIDEO_ERROR_INVALID_VALUE_WITHIN_RANGE, + VIDEO_ERROR_UNKNOWN = 0xFF, +} video_error_code_t; + +/* A.2 Interface Subclass */ +typedef enum { + VIDEO_SUBCLASS_UNDEFINED = 0x00, + VIDEO_SUBCLASS_CONTROL, + VIDEO_SUBCLASS_STREAMING, + VIDEO_SUBCLASS_INTERFACE_COLLECTION, +} video_subclass_type_t; + +/* A.3 Interface Protocol */ +typedef enum { + VIDEO_ITF_PROTOCOL_UNDEFINED = 0x00, + VIDEO_ITF_PROTOCOL_15, +} video_interface_protocol_code_t; + +/* A.5 Class-Specific VideoControl Interface Descriptor Subtypes */ +typedef enum { + VIDEO_CS_ITF_VC_UNDEFINED = 0x00, + VIDEO_CS_ITF_VC_HEADER, + VIDEO_CS_ITF_VC_INPUT_TERMINAL, + VIDEO_CS_ITF_VC_OUTPUT_TERMINAL, + VIDEO_CS_ITF_VC_SELECTOR_UNIT, + VIDEO_CS_ITF_VC_PROCESSING_UNIT, + VIDEO_CS_ITF_VC_EXTENSION_UNIT, + VIDEO_CS_ITF_VC_ENCODING_UNIT, + VIDEO_CS_ITF_VC_MAX, +} video_cs_vc_interface_subtype_t; + +/* A.6 Class-Specific VideoStreaming Interface Descriptor Subtypes */ +typedef enum { + VIDEO_CS_ITF_VS_UNDEFINED = 0x00, + VIDEO_CS_ITF_VS_INPUT_HEADER = 0x01, + VIDEO_CS_ITF_VS_OUTPUT_HEADER = 0x02, + VIDEO_CS_ITF_VS_STILL_IMAGE_FRAME = 0x03, + VIDEO_CS_ITF_VS_FORMAT_UNCOMPRESSED = 0x04, + VIDEO_CS_ITF_VS_FRAME_UNCOMPRESSED = 0x05, + VIDEO_CS_ITF_VS_FORMAT_MJPEG = 0x06, + VIDEO_CS_ITF_VS_FRAME_MJPEG = 0x07, + VIDEO_CS_ITF_VS_FORMAT_MPEG2TS = 0x0A, + VIDEO_CS_ITF_VS_FORMAT_DV = 0x0C, + VIDEO_CS_ITF_VS_COLORFORMAT = 0x0D, + VIDEO_CS_ITF_VS_FORMAT_FRAME_BASED = 0x10, + VIDEO_CS_ITF_VS_FRAME_FRAME_BASED = 0x11, + VIDEO_CS_ITF_VS_FORMAT_STREAM_BASED = 0x12, + VIDEO_CS_ITF_VS_FORMAT_H264 = 0x13, + VIDEO_CS_ITF_VS_FRAME_H264 = 0x14, + VIDEO_CS_ITF_VS_FORMAT_H264_SIMULCAST = 0x15, + VIDEO_CS_ITF_VS_FORMAT_VP8 = 0x16, + VIDEO_CS_ITF_VS_FRAME_VP8 = 0x17, + VIDEO_CS_ITF_VS_FORMAT_VP8_SIMULCAST = 0x18, +} video_cs_vs_interface_subtype_t; + +/* A.7. Class-Specific Endpoint Descriptor Subtypes */ +typedef enum { + VIDEO_CS_EP_UNDEFINED = 0x00, + VIDEO_CS_EP_GENERAL, + VIDEO_CS_EP_ENDPOINT, + VIDEO_CS_EP_INTERRUPT +} video_cs_ep_subtype_t; + +/* A.8 Class-Specific Request Codes */ +typedef enum { + VIDEO_REQUEST_UNDEFINED = 0x00, + VIDEO_REQUEST_SET_CUR = 0x01, + VIDEO_REQUEST_SET_CUR_ALL = 0x11, + VIDEO_REQUEST_GET_CUR = 0x81, + VIDEO_REQUEST_GET_MIN = 0x82, + VIDEO_REQUEST_GET_MAX = 0x83, + VIDEO_REQUEST_GET_RES = 0x84, + VIDEO_REQUEST_GET_LEN = 0x85, + VIDEO_REQUEST_GET_INFO = 0x86, + VIDEO_REQUEST_GET_DEF = 0x87, + VIDEO_REQUEST_GET_CUR_ALL = 0x91, + VIDEO_REQUEST_GET_MIN_ALL = 0x92, + VIDEO_REQUEST_GET_MAX_ALL = 0x93, + VIDEO_REQUEST_GET_RES_ALL = 0x94, + VIDEO_REQUEST_GET_DEF_ALL = 0x97 +} video_control_request_t; + +/* A.9.1 VideoControl Interface Control Selectors */ +typedef enum { + VIDEO_VC_CTL_UNDEFINED = 0x00, + VIDEO_VC_CTL_VIDEO_POWER_MODE, // 0x01 + VIDEO_VC_CTL_REQUEST_ERROR_CODE, // 0x02 +} video_interface_control_selector_t; + +/* A.9.8 VideoStreaming Interface Control Selectors */ +typedef enum { + VIDEO_VS_CTL_UNDEFINED = 0x00, + VIDEO_VS_CTL_PROBE, // 0x01 + VIDEO_VS_CTL_COMMIT, // 0x02 + VIDEO_VS_CTL_STILL_PROBE, // 0x03 + VIDEO_VS_CTL_STILL_COMMIT, // 0x04 + VIDEO_VS_CTL_STILL_IMAGE_TRIGGER, // 0x05 + VIDEO_VS_CTL_STREAM_ERROR_CODE, // 0x06 + VIDEO_VS_CTL_GENERATE_KEY_FRAME, // 0x07 + VIDEO_VS_CTL_UPDATE_FRAME_SEGMENT, // 0x08 + VIDEO_VS_CTL_SYNCH_DELAY_CONTROL, // 0x09 + +} video_interface_streaming_selector_t; + +/* B. Terminal Types */ +typedef enum { + // Terminal + VIDEO_TT_VENDOR_SPECIFIC = 0x0100, + VIDEO_TT_STREAMING = 0x0101, + + // Input + VIDEO_ITT_VENDOR_SPECIFIC = 0x0200, + VIDEO_ITT_CAMERA = 0x0201, + VIDEO_ITT_MEDIA_TRANSPORT_INPUT = 0x0202, + + // Output + VIDEO_OTT_VENDOR_SPECIFIC = 0x0300, + VIDEO_OTT_DISPLAY = 0x0301, + VIDEO_OTT_MEDIA_TRANSPORT_OUTPUT = 0x0302, + + // External + VIDEO_ETT_VENDOR_SPEIFIC = 0x0400, + VIDEO_ETT_COMPOSITE_CONNECTOR = 0x0401, + VIDEO_ETT_SVIDEO_CONNECTOR = 0x0402, + VIDEO_ETT_COMPONENT_CONNECTOR = 0x0403, +} video_terminal_type_t; + +//--------------------------------------------------------------------+ +// Video Control (VC) Descriptors +//--------------------------------------------------------------------+ + +/* 2.3.4.2 */ +#define tusb_desc_video_control_header_nitf_t(_nitf) \ + struct TU_ATTR_PACKED { \ + uint8_t bLength; \ + uint8_t bDescriptorType; \ + uint8_t bDescriptorSubType; \ + uint16_t bcdUVC; \ + uint16_t wTotalLength; \ + uint32_t dwClockFrequency; /* deprecated */ \ + uint8_t bInCollection; \ + uint8_t baInterfaceNr[_nitf]; \ + } + +typedef tusb_desc_video_control_header_nitf_t() tusb_desc_video_control_header_t; +typedef tusb_desc_video_control_header_nitf_t(1) tusb_desc_video_control_header_1itf_t; +typedef tusb_desc_video_control_header_nitf_t(2) tusb_desc_video_control_header_2itf_t; +typedef tusb_desc_video_control_header_nitf_t(3) tusb_desc_video_control_header_3itf_t; +typedef tusb_desc_video_control_header_nitf_t(4) tusb_desc_video_control_header_4itf_t; + +typedef struct TU_ATTR_PACKED { + uint8_t bLength; + uint8_t bDescriptorType; + uint8_t bDescriptorSubType; + uint8_t bTerminalID; + uint16_t wTerminalType; + uint8_t bAssocTerminal; + uint8_t iTerminal; +} tusb_desc_video_control_input_terminal_t; + +TU_VERIFY_STATIC(sizeof(tusb_desc_video_control_input_terminal_t) == 8, "size is not correct"); + +typedef struct TU_ATTR_PACKED { + uint8_t bLength; + uint8_t bDescriptorType; + uint8_t bDescriptorSubType; + uint8_t bTerminalID; + uint16_t wTerminalType; + uint8_t bAssocTerminal; + uint8_t bSourceID; + uint8_t iTerminal; +} tusb_desc_video_control_output_terminal_t; + +TU_VERIFY_STATIC(sizeof(tusb_desc_video_control_output_terminal_t) == 9, "size is not correct"); + +typedef struct TU_ATTR_PACKED { + uint8_t bLength; + uint8_t bDescriptorType; + uint8_t bDescriptorSubType; + uint8_t bTerminalID; + uint16_t wTerminalType; + uint8_t bAssocTerminal; + uint8_t iTerminal; + + uint16_t wObjectiveFocalLengthMin; + uint16_t wObjectiveFocalLengthMax; + uint16_t wOcularFocalLength; + uint8_t bControlSize; + uint8_t bmControls[3]; +} tusb_desc_video_control_camera_terminal_t; + +TU_VERIFY_STATIC(sizeof(tusb_desc_video_control_camera_terminal_t) == 18, "size is not correct"); + +//--------------------------------------------------------------------+ +// Video Streaming (VS) Descriptors +//--------------------------------------------------------------------+ + +/* 3.9.2.1 */ +#define tusb_desc_video_streaming_input_header_nbyte_t(_nb) \ + struct TU_ATTR_PACKED { \ + uint8_t bLength; \ + uint8_t bDescriptorType; \ + uint8_t bDescriptorSubType; \ + uint8_t bNumFormats; /* Number of video payload Format descriptors for this interface */ \ + uint16_t wTotalLength; \ + uint8_t bEndpointAddress; \ + uint8_t bmInfo; /* Bit 0: dynamic format change supported */ \ + uint8_t bTerminalLink; \ + uint8_t bStillCaptureMethod; \ + uint8_t bTriggerSupport; /* Hardware trigger supported */ \ + uint8_t bTriggerUsage; \ + uint8_t bControlSize; /* sizeof of each control item */ \ + uint8_t bmaControls[_nb]; \ + } + +typedef tusb_desc_video_streaming_input_header_nbyte_t() tusb_desc_video_streaming_input_header_t; +typedef tusb_desc_video_streaming_input_header_nbyte_t(1) tusb_desc_video_streaming_input_header_1byte_t; +typedef tusb_desc_video_streaming_input_header_nbyte_t(2) tusb_desc_video_streaming_input_header_2byte_t; +typedef tusb_desc_video_streaming_input_header_nbyte_t(3) tusb_desc_video_streaming_input_header_3byte_t; +typedef tusb_desc_video_streaming_input_header_nbyte_t(4) tusb_desc_video_streaming_input_header_4byte_t; + +/* 3.9.2.2 */ +typedef struct TU_ATTR_PACKED { + uint8_t bLength; + uint8_t bDescriptorType; + uint8_t bDescriptorSubType; + uint8_t bNumFormats; + uint16_t wTotalLength; + uint8_t bEndpointAddress; + uint8_t bTerminalLink; + uint8_t bControlSize; + uint8_t bmaControls[]; +} tusb_desc_video_streaming_output_header_t; + +typedef struct TU_ATTR_PACKED { + uint8_t bLength; + uint8_t bDescriptorType; + uint8_t bDescriptorSubType; + uint8_t bNumFormats; + uint16_t wTotalLength; + uint8_t bEndpointAddress; + union { + struct { + uint8_t bmInfo; + uint8_t bTerminalLink; + uint8_t bStillCaptureMethod; + uint8_t bTriggerSupport; + uint8_t bTriggerUsage; + uint8_t bControlSize; + uint8_t bmaControls[]; + } input; + struct { + uint8_t bEndpointAddress; + uint8_t bTerminalLink; + uint8_t bControlSize; + uint8_t bmaControls[]; + } output; + }; +} tusb_desc_video_streaming_inout_header_t; + +// 3.9.2.6 Color Matching Descriptor +typedef struct TU_ATTR_PACKED { + uint8_t bLength; + uint8_t bDescriptorType; + uint8_t bDescriptorSubType; + uint8_t bColorPrimaries; + uint8_t bTransferCharacteristics; + uint8_t bMatrixCoefficients; +} tusb_desc_video_streaming_color_matching_t; + +TU_VERIFY_STATIC(sizeof(tusb_desc_video_streaming_color_matching_t) == 6, "size is not correct"); + +//--------------------------------------------------------------------+ +// Format and Frame Descriptor +// Note: bFormatIndex & bFrameIndex are 1-based index +//--------------------------------------------------------------------+ + +//------------- Uncompressed -------------// +// Uncompressed payload specs: 3.1.1 format descriptor +typedef struct TU_ATTR_PACKED { + uint8_t bLength; + uint8_t bDescriptorType; + uint8_t bDescriptorSubType; + uint8_t bFormatIndex; + uint8_t bNumFrameDescriptors; // Number of frame descriptors for this format + uint8_t guidFormat[16]; + uint8_t bBitsPerPixel; + uint8_t bDefaultFrameIndex; + uint8_t bAspectRatioX; + uint8_t bAspectRatioY; + uint8_t bmInterlaceFlags; + uint8_t bCopyProtect; +} tusb_desc_video_format_uncompressed_t; + +TU_VERIFY_STATIC(sizeof(tusb_desc_video_format_uncompressed_t) == 27, "size is not correct"); + +// Uncompressed payload specs: 3.1.2 frame descriptor +#define tusb_desc_video_frame_uncompressed_nint_t(_nint) \ + struct TU_ATTR_PACKED { \ + uint8_t bLength; \ + uint8_t bDescriptorType; \ + uint8_t bDescriptorSubType; \ + uint8_t bFrameIndex; \ + uint8_t bmCapabilities; \ + uint16_t wWidth; \ + uint16_t wHeight; \ + uint32_t dwMinBitRate; \ + uint32_t dwMaxBitRate; \ + uint32_t dwMaxVideoFrameBufferSize; /* deprecated in 1.5 */ \ + uint32_t dwDefaultFrameInterval; /* 100ns unit */\ + uint8_t bFrameIntervalType; \ + uint32_t dwFrameInterval[_nint]; \ + } + +typedef tusb_desc_video_frame_uncompressed_nint_t() tusb_desc_video_frame_uncompressed_t; +typedef tusb_desc_video_frame_uncompressed_nint_t(1) tusb_desc_video_frame_uncompressed_1int_t; +typedef tusb_desc_video_frame_uncompressed_nint_t(2) tusb_desc_video_frame_uncompressed_2int_t; +typedef tusb_desc_video_frame_uncompressed_nint_t(3) tusb_desc_video_frame_uncompressed_3int_t; +typedef tusb_desc_video_frame_uncompressed_nint_t(4) tusb_desc_video_frame_uncompressed_4int_t; + +// continuous = 3 intervals: min, max, step +typedef tusb_desc_video_frame_uncompressed_3int_t tusb_desc_video_frame_uncompressed_continuous_t; + +TU_VERIFY_STATIC(sizeof(tusb_desc_video_frame_uncompressed_continuous_t) == 38, "size is not correct"); + +//------------- MJPEG -------------// +// MJPEG payload specs: 3.1.1 format descriptor +typedef struct TU_ATTR_PACKED { + uint8_t bLength; + uint8_t bDescriptorType; + uint8_t bDescriptorSubType; + uint8_t bFormatIndex; + uint8_t bNumFrameDescriptors; + uint8_t bmFlags; // Bit 0: fixed size samples (1 = yes) + uint8_t bDefaultFrameIndex; + uint8_t bAspectRatioX; + uint8_t bAspectRatioY; + uint8_t bmInterlaceFlags; + uint8_t bCopyProtect; +} tusb_desc_video_format_mjpeg_t; + +TU_VERIFY_STATIC(sizeof(tusb_desc_video_format_mjpeg_t) == 11, "size is not correct"); + +// MJPEG payload specs: 3.1.2 frame descriptor (same as uncompressed) +typedef tusb_desc_video_frame_uncompressed_t tusb_desc_video_frame_mjpeg_t; +typedef tusb_desc_video_frame_uncompressed_1int_t tusb_desc_video_frame_mjpeg_1int_t; +typedef tusb_desc_video_frame_uncompressed_2int_t tusb_desc_video_frame_mjpeg_2int_t; +typedef tusb_desc_video_frame_uncompressed_3int_t tusb_desc_video_frame_mjpeg_3int_t; +typedef tusb_desc_video_frame_uncompressed_4int_t tusb_desc_video_frame_mjpeg_4int_t; + +// continuous = 3 intervals: min, max, step +typedef tusb_desc_video_frame_mjpeg_3int_t tusb_desc_video_frame_mjpeg_continuous_t; + +//------------- DV -------------// +// DV payload specs: 3.1.1 +typedef struct TU_ATTR_PACKED { + uint8_t bLength; + uint8_t bDescriptorType; + uint8_t bDescriptorSubType; + uint8_t bFormatIndex; + uint32_t dwMaxVideoFrameBufferSize; /* deprecated */ + uint8_t bFormatType; +} tusb_desc_video_format_dv_t; + +// Frame Based payload specs: 3.1.1 +typedef struct TU_ATTR_PACKED { + uint8_t bLength; + uint8_t bDescriptorType; + uint8_t bDescriptorSubType; + uint8_t bFormatIndex; + uint8_t bNumFrameDescriptors; + uint8_t guidFormat[16]; + uint8_t bBitsPerPixel; + uint8_t bDefaultFrameIndex; + uint8_t bAspectRatioX; + uint8_t bAspectRatioY; + uint8_t bmInterlaceFlags; + uint8_t bCopyProtect; + uint8_t bVaribaleSize; +} tusb_desc_video_format_framebased_t; + +typedef struct TU_ATTR_PACKED { + uint8_t bLength; + uint8_t bDescriptorType; + uint8_t bDescriptorSubType; + uint8_t bFrameIndex; + uint8_t bmCapabilities; + uint16_t wWidth; + uint16_t wHeight; + uint32_t dwMinBitRate; + uint32_t dwMaxBitRate; + uint32_t dwDefaultFrameInterval; + uint8_t bFrameIntervalType; + uint32_t dwBytesPerLine; + uint32_t dwFrameInterval[]; +} tusb_desc_video_frame_framebased_t; + +//--------------------------------------------------------------------+ +// Requests +//--------------------------------------------------------------------+ + +/* 2.4.3.3 */ +typedef struct TU_ATTR_PACKED { + uint8_t bHeaderLength; + union { + uint8_t bmHeaderInfo; + struct { + uint8_t FrameID: 1; + uint8_t EndOfFrame: 1; + uint8_t PresentationTime: 1; + uint8_t SourceClockReference: 1; + uint8_t PayloadSpecific: 1; + uint8_t StillImage: 1; + uint8_t Error: 1; + uint8_t EndOfHeader: 1; + }; + }; +} tusb_video_payload_header_t; + +/* 4.3.1.1 */ +typedef struct TU_ATTR_PACKED { + union { + uint8_t bmHint; + struct TU_ATTR_PACKED { + uint16_t dwFrameInterval: 1; + uint16_t wKeyFrameRatel : 1; + uint16_t wPFrameRate : 1; + uint16_t wCompQuality : 1; + uint16_t wCompWindowSize: 1; + uint16_t : 0; + } Hint; + }; + uint8_t bFormatIndex; + uint8_t bFrameIndex; + uint32_t dwFrameInterval; + uint16_t wKeyFrameRate; + uint16_t wPFrameRate; + uint16_t wCompQuality; + uint16_t wCompWindowSize; + uint16_t wDelay; + uint32_t dwMaxVideoFrameSize; + uint32_t dwMaxPayloadTransferSize; + uint32_t dwClockFrequency; + union { + uint8_t bmFramingInfo; + struct TU_ATTR_PACKED { + uint8_t FrameID : 1; + uint8_t EndOfFrame: 1; + uint8_t EndOfSlice: 1; + uint8_t : 0; + } FramingInfo; + }; + uint8_t bPreferedVersion; + uint8_t bMinVersion; + uint8_t bMaxVersion; + uint8_t bUsage; + uint8_t bBitDepthLuma; + uint8_t bmSettings; + uint8_t bMaxNumberOfRefFramesPlus1; + uint16_t bmRateControlModes; + uint64_t bmLayoutPerStream; +} video_probe_and_commit_control_t; + +TU_VERIFY_STATIC( sizeof(video_probe_and_commit_control_t) == 48, "size is not correct"); + +#define TUD_VIDEO_DESC_IAD_LEN 8 +#define TUD_VIDEO_DESC_STD_VC_LEN 9 +#define TUD_VIDEO_DESC_CS_VC_LEN 12 +#define TUD_VIDEO_DESC_INPUT_TERM_LEN 8 +#define TUD_VIDEO_DESC_OUTPUT_TERM_LEN 9 +#define TUD_VIDEO_DESC_CAMERA_TERM_LEN 18 +#define TUD_VIDEO_DESC_STD_VS_LEN 9 +#define TUD_VIDEO_DESC_CS_VS_IN_LEN 13 +#define TUD_VIDEO_DESC_CS_VS_OUT_LEN 9 +#define TUD_VIDEO_DESC_CS_VS_FMT_UNCOMPR_LEN 27 +#define TUD_VIDEO_DESC_CS_VS_FMT_MJPEG_LEN 11 +#define TUD_VIDEO_DESC_CS_VS_FMT_FRAME_BASED_LEN 28 +#define TUD_VIDEO_DESC_CS_VS_FRM_UNCOMPR_CONT_LEN 38 +#define TUD_VIDEO_DESC_CS_VS_FRM_UNCOMPR_DISC_LEN 26 +#define TUD_VIDEO_DESC_CS_VS_FRM_MJPEG_CONT_LEN 38 +#define TUD_VIDEO_DESC_CS_VS_FRM_MJPEG_DISC_LEN 26 +#define TUD_VIDEO_DESC_CS_VS_FRM_FRAME_BASED_CONT_LEN 38 +#define TUD_VIDEO_DESC_CS_VS_FRM_FRAME_BASED_DISC_LEN 26 +#define TUD_VIDEO_DESC_CS_VS_COLOR_MATCHING_LEN 6 + +/* 2.2 compression formats */ +#define TUD_VIDEO_GUID_YUY2 0x59,0x55,0x59,0x32,0x00,0x00,0x10,0x00,0x80,0x00,0x00,0xAA,0x00,0x38,0x9B,0x71 +#define TUD_VIDEO_GUID_NV12 0x4E,0x56,0x31,0x32,0x00,0x00,0x10,0x00,0x80,0x00,0x00,0xAA,0x00,0x38,0x9B,0x71 +#define TUD_VIDEO_GUID_M420 0x4D,0x34,0x32,0x30,0x00,0x00,0x10,0x00,0x80,0x00,0x00,0xAA,0x00,0x38,0x9B,0x71 +#define TUD_VIDEO_GUID_I420 0x49,0x34,0x32,0x30,0x00,0x00,0x10,0x00,0x80,0x00,0x00,0xAA,0x00,0x38,0x9B,0x71 +#define TUD_VIDEO_GUID_H264 0x48,0x32,0x36,0x34,0x00,0x00,0x10,0x00,0x80,0x00,0x00,0xAA,0x00,0x38,0x9B,0x71 + +#define TUD_VIDEO_DESC_IAD(_firstitf, _nitfs, _stridx) \ + TUD_VIDEO_DESC_IAD_LEN, TUSB_DESC_INTERFACE_ASSOCIATION, \ + _firstitf, _nitfs, TUSB_CLASS_VIDEO, VIDEO_SUBCLASS_INTERFACE_COLLECTION, \ + VIDEO_ITF_PROTOCOL_UNDEFINED, _stridx + +#define TUD_VIDEO_DESC_STD_VC(_itfnum, _nEPs, _stridx) \ + TUD_VIDEO_DESC_STD_VC_LEN, TUSB_DESC_INTERFACE, _itfnum, /* fixed to zero */ 0x00, \ + _nEPs, TUSB_CLASS_VIDEO, VIDEO_SUBCLASS_CONTROL, VIDEO_ITF_PROTOCOL_15, _stridx + +/* 3.7.2 */ +#define TUD_VIDEO_DESC_CS_VC(_bcdUVC, _totallen, _clkfreq, ...) \ + TUD_VIDEO_DESC_CS_VC_LEN + (TU_ARGS_NUM(__VA_ARGS__)), TUSB_DESC_CS_INTERFACE, VIDEO_CS_ITF_VC_HEADER, \ + U16_TO_U8S_LE(_bcdUVC), U16_TO_U8S_LE((_totallen) + TUD_VIDEO_DESC_CS_VC_LEN + (TU_ARGS_NUM(__VA_ARGS__))), \ + U32_TO_U8S_LE(_clkfreq), TU_ARGS_NUM(__VA_ARGS__), __VA_ARGS__ + +/* 3.7.2.1 */ +#define TUD_VIDEO_DESC_INPUT_TERM(_tid, _tt, _at, _stridx) \ + TUD_VIDEO_DESC_INPUT_TERM_LEN, TUSB_DESC_CS_INTERFACE, VIDEO_CS_ITF_VC_INPUT_TERMINAL, \ + _tid, U16_TO_U8S_LE(_tt), _at, _stridx + +/* 3.7.2.2 */ +#define TUD_VIDEO_DESC_OUTPUT_TERM(_tid, _tt, _at, _srcid, _stridx) \ + TUD_VIDEO_DESC_OUTPUT_TERM_LEN, TUSB_DESC_CS_INTERFACE, VIDEO_CS_ITF_VC_OUTPUT_TERMINAL, \ + _tid, U16_TO_U8S_LE(_tt), _at, _srcid, _stridx + +/* 3.7.2.3 */ +#define TUD_VIDEO_DESC_CAMERA_TERM(_tid, _at, _stridx, _focal_min, _focal_max, _focal, _ctls) \ + TUD_VIDEO_DESC_CAMERA_TERM_LEN, TUSB_DESC_CS_INTERFACE, VIDEO_CS_ITF_VC_INPUT_TERMINAL, \ + _tid, U16_TO_U8S_LE(VIDEO_ITT_CAMERA), _at, _stridx, \ + U16_TO_U8S_LE(_focal_min), U16_TO_U8S_LE(_focal_max), U16_TO_U8S_LE(_focal), 3, \ + TU_U32_BYTE0(_ctls), TU_U32_BYTE1(_ctls), TU_U32_BYTE2(_ctls) + +/* 3.9.1 */ +#define TUD_VIDEO_DESC_STD_VS(_itfnum, _alt, _epn, _stridx) \ + TUD_VIDEO_DESC_STD_VS_LEN, TUSB_DESC_INTERFACE, _itfnum, _alt, \ + _epn, TUSB_CLASS_VIDEO, VIDEO_SUBCLASS_STREAMING, VIDEO_ITF_PROTOCOL_15, _stridx + +/* 3.9.2.1 */ +#define TUD_VIDEO_DESC_CS_VS_INPUT(_numfmt, _totallen, _ep, _inf, _termlnk, _sticaptmeth, _trgspt, _trgusg, ...) \ + TUD_VIDEO_DESC_CS_VS_IN_LEN + (_numfmt) * (TU_ARGS_NUM(__VA_ARGS__)), TUSB_DESC_CS_INTERFACE, \ + VIDEO_CS_ITF_VS_INPUT_HEADER, _numfmt, \ + U16_TO_U8S_LE((_totallen) + TUD_VIDEO_DESC_CS_VS_IN_LEN + (_numfmt) * (TU_ARGS_NUM(__VA_ARGS__))), \ + _ep, _inf, _termlnk, _sticaptmeth, _trgspt, _trgusg, (TU_ARGS_NUM(__VA_ARGS__)), __VA_ARGS__ + +/* 3.9.2.2 */ +#define TUD_VIDEO_DESC_CS_VS_OUTPUT(_numfmt, _totallen, _ep, _inf, _termlnk, ...) \ + TUD_VIDEO_DESC_CS_VS_OUT_LEN + (_numfmt) * (TU_ARGS_NUM(__VA_ARGS__)), TUSB_DESC_CS_INTERFACE, \ + VIDEO_CS_ITF_VS_OUTPUT_HEADER, _numfmt, \ + U16_TO_U8S_LE((_totallen) + TUD_VIDEO_DESC_CS_VS_OUT_LEN + (_numfmt) * (TU_ARGS_NUM(__VA_ARGS__))), \ + _ep, _inf, _termlnk, (TU_ARGS_NUM(__VA_ARGS__)), __VA_ARGS__ + +/* Uncompressed 3.1.1 */ +#define TUD_VIDEO_GUID(_g0,_g1,_g2,_g3,_g4,_g5,_g6,_g7,_g8,_g9,_g10,_g11,_g12,_g13,_g14,_g15) _g0,_g1,_g2,_g3,_g4,_g5,_g6,_g7,_g8,_g9,_g10,_g11,_g12,_g13,_g14,_g15 + +#define TUD_VIDEO_DESC_CS_VS_FMT_UNCOMPR(_fmtidx, _numfrmdesc, \ + _guid, _bitsperpix, _frmidx, _asrx, _asry, _interlace, _cp) \ + TUD_VIDEO_DESC_CS_VS_FMT_UNCOMPR_LEN, TUSB_DESC_CS_INTERFACE, VIDEO_CS_ITF_VS_FORMAT_UNCOMPRESSED, \ + _fmtidx, _numfrmdesc, TUD_VIDEO_GUID(_guid), \ + _bitsperpix, _frmidx, _asrx, _asry, _interlace, _cp + +/* Uncompressed 3.1.2 Table 3-3 */ +#define TUD_VIDEO_DESC_CS_VS_FRM_UNCOMPR_CONT(_frmidx, _cap, _width, _height, _minbr, _maxbr, _maxfrmbufsz, _frminterval, _minfrminterval, _maxfrminterval, _frmintervalstep) \ + TUD_VIDEO_DESC_CS_VS_FRM_UNCOMPR_CONT_LEN, TUSB_DESC_CS_INTERFACE, VIDEO_CS_ITF_VS_FRAME_UNCOMPRESSED, \ + _frmidx, _cap, U16_TO_U8S_LE(_width), U16_TO_U8S_LE(_height), U32_TO_U8S_LE(_minbr), U32_TO_U8S_LE(_maxbr), \ + U32_TO_U8S_LE(_maxfrmbufsz), U32_TO_U8S_LE(_frminterval), 0, \ + U32_TO_U8S_LE(_minfrminterval), U32_TO_U8S_LE(_maxfrminterval), U32_TO_U8S_LE(_frmintervalstep) + +/* Uncompressed 3.1.2 Table 3-4 */ +#define TUD_VIDEO_DESC_CS_VS_FRM_UNCOMPR_DISC(_frmidx, _cap, _width, _height, _minbr, _maxbr, _maxfrmbufsz, _frminterval, ...) \ + TUD_VIDEO_DESC_CS_VS_FRM_UNCOMPR_DISC_LEN + (TU_ARGS_NUM(__VA_ARGS__)) * 4, \ + TUSB_DESC_CS_INTERFACE, VIDEO_CS_ITF_VS_FRAME_UNCOMPRESSED, \ + _frmidx, _cap, U16_TO_U8S_LE(_width), U16_TO_U8S_LE(_height), U32_TO_U8S_LE(_minbr), U32_TO_U8S_LE(_maxbr), \ + U32_TO_U8S_LE(_maxfrmbufsz), U32_TO_U8S_LE(_frminterval), (TU_ARGS_NUM(__VA_ARGS__)), __VA_ARGS__ + +/* Motion-JPEG 3.1.1 Table 3-1 */ +#define TUD_VIDEO_DESC_CS_VS_FMT_MJPEG(_fmtidx, _numfrmdesc, _fixed_sz, _frmidx, _asrx, _asry, _interlace, _cp) \ + TUD_VIDEO_DESC_CS_VS_FMT_MJPEG_LEN, TUSB_DESC_CS_INTERFACE, VIDEO_CS_ITF_VS_FORMAT_MJPEG, \ + _fmtidx, _numfrmdesc, _fixed_sz, _frmidx, _asrx, _asry, _interlace, _cp + +/* Motion-JPEG 3.1.1 Table 3-2 and 3-3 */ +#define TUD_VIDEO_DESC_CS_VS_FRM_MJPEG_CONT(_frmidx, _cap, _width, _height, _minbr, _maxbr, _maxfrmbufsz, _frminterval, _minfrminterval, _maxfrminterval, _frmintervalstep) \ + TUD_VIDEO_DESC_CS_VS_FRM_MJPEG_CONT_LEN, TUSB_DESC_CS_INTERFACE, VIDEO_CS_ITF_VS_FRAME_MJPEG, \ + _frmidx, _cap, U16_TO_U8S_LE(_width), U16_TO_U8S_LE(_height), U32_TO_U8S_LE(_minbr), U32_TO_U8S_LE(_maxbr), \ + U32_TO_U8S_LE(_maxfrmbufsz), U32_TO_U8S_LE(_frminterval), 0, \ + U32_TO_U8S_LE(_minfrminterval), U32_TO_U8S_LE(_maxfrminterval), U32_TO_U8S_LE(_frmintervalstep) + +/* Motion-JPEG 3.1.1 Table 3-2 and 3-4 */ +#define TUD_VIDEO_DESC_CS_VS_FRM_MJPEG_DISC(_frmidx, _cap, _width, _height, _minbr, _maxbr, _maxfrmbufsz, _frminterval, ...) \ + TUD_VIDEO_DESC_CS_VS_FRM_MJPEG_DISC_LEN + (TU_ARGS_NUM(__VA_ARGS__)) * 4, \ + TUSB_DESC_CS_INTERFACE, VIDEO_CS_ITF_VS_FRAME_MJPEG, \ + _frmidx, _cap, U16_TO_U8S_LE(_width), U16_TO_U8S_LE(_height), U32_TO_U8S_LE(_minbr), U32_TO_U8S_LE(_maxbr), \ + U32_TO_U8S_LE(_maxfrmbufsz), U32_TO_U8S_LE(_frminterval), (TU_ARGS_NUM(__VA_ARGS__)), __VA_ARGS__ + +/* Motion-Frame-Based 3.1.1 Table 3-1 */ +#define TUD_VIDEO_DESC_CS_VS_FMT_FRAME_BASED(_fmtidx, _numfrmdesc, _guid, _bitsperpix, _frmidx, _asrx, _asry, _interlace, _cp, _variablesize) \ + TUD_VIDEO_DESC_CS_VS_FMT_FRAME_BASED_LEN, TUSB_DESC_CS_INTERFACE, VIDEO_CS_ITF_VS_FORMAT_FRAME_BASED, \ + _fmtidx, _numfrmdesc, TUD_VIDEO_GUID(_guid), _bitsperpix, _frmidx, _asrx, _asry, _interlace, _cp, _variablesize + +/* Motion-Frame-Based 3.1.1 Table 3-2 and 3-3 */ +#define TUD_VIDEO_DESC_CS_VS_FRM_FRAME_BASED_CONT(_frmidx, _cap, _width, _height, _minbr, _maxbr, _frminterval, _bytesperline, _minfrminterval, _maxfrminterval, _frmintervalstep) \ + TUD_VIDEO_DESC_CS_VS_FRM_FRAME_BASED_CONT_LEN, TUSB_DESC_CS_INTERFACE, VIDEO_CS_ITF_VS_FRAME_FRAME_BASED, \ + _frmidx, _cap, U16_TO_U8S_LE(_width), U16_TO_U8S_LE(_height), U32_TO_U8S_LE(_minbr), U32_TO_U8S_LE(_maxbr), \ + U32_TO_U8S_LE(_frminterval), 0, U32_TO_U8S_LE(_bytesperline), \ + U32_TO_U8S_LE(_minfrminterval), U32_TO_U8S_LE(_maxfrminterval), U32_TO_U8S_LE(_frmintervalstep) + +/* Motion-Frame-Based 3.1.1 Table 3-2 and 3-4 */ +#define TUD_VIDEO_DESC_CS_VS_FRM_FRAME_BASED_DISC(_frmidx, _cap, _width, _height, _minbr, _maxbr, _frminterval, _bytesperline, ...) \ + TUD_VIDEO_DESC_CS_VS_FRM_FRAME_BASED_DISC_LEN + (TU_ARGS_NUM(__VA_ARGS__)) * 4, \ + TUSB_DESC_CS_INTERFACE, VIDEO_CS_ITF_VS_FRAME_FRAME_BASED, \ + _frmidx, _cap, U16_TO_U8S_LE(_width), U16_TO_U8S_LE(_height), U32_TO_U8S_LE(_minbr), U32_TO_U8S_LE(_maxbr), \ +U32_TO_U8S_LE(_frminterval), U32_TO_U8S_LE(_bytesperline), (TU_ARGS_NUM(__VA_ARGS__)), __VA_ARGS__ + +/* 3.9.2.6 */ +#define TUD_VIDEO_DESC_CS_VS_COLOR_MATCHING(_color, _trns, _mat) \ + TUD_VIDEO_DESC_CS_VS_COLOR_MATCHING_LEN, \ + TUSB_DESC_CS_INTERFACE, VIDEO_CS_ITF_VS_COLORFORMAT, \ + _color, _trns, _mat + +/* 3.10.1.1 */ +#define TUD_VIDEO_DESC_EP_ISO(_ep, _epsize, _ep_interval) \ + 7, TUSB_DESC_ENDPOINT, _ep, (uint8_t) (TUSB_XFER_ISOCHRONOUS | TUSB_ISO_EP_ATT_ASYNCHRONOUS),\ + U16_TO_U8S_LE(_epsize), _ep_interval + +/* 3.10.1.2 */ +#define TUD_VIDEO_DESC_EP_BULK(_ep, _epsize, _ep_interval) \ + 7, TUSB_DESC_ENDPOINT, _ep, TUSB_XFER_BULK, U16_TO_U8S_LE(_epsize), _ep_interval + +#endif diff --git a/cores/nRF5/TinyUSB/Adafruit_TinyUSB_ArduinoCore/tinyusb/src/class/video/video_device.c b/cores/nRF5/TinyUSB/Adafruit_TinyUSB_ArduinoCore/tinyusb/src/class/video/video_device.c new file mode 100644 index 0000000..5c00cc3 --- /dev/null +++ b/cores/nRF5/TinyUSB/Adafruit_TinyUSB_ArduinoCore/tinyusb/src/class/video/video_device.c @@ -0,0 +1,1446 @@ +/* + * The MIT License (MIT) + * + * Copyright (c) 2021 Koji KITAYAMA + * 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. + */ + +#include "tusb_option.h" + +#if (CFG_TUD_ENABLED && CFG_TUD_VIDEO && CFG_TUD_VIDEO_STREAMING) + +#include "device/usbd.h" +#include "device/usbd_pvt.h" + +#include "video_device.h" + +// Level where CFG_TUSB_DEBUG must be at least for this driver is logged +#ifndef CFG_TUD_VIDEO_LOG_LEVEL + #define CFG_TUD_VIDEO_LOG_LEVEL CFG_TUD_LOG_LEVEL +#endif + +#define TU_LOG_DRV(...) TU_LOG(CFG_TUD_VIDEO_LOG_LEVEL, __VA_ARGS__) + +//--------------------------------------------------------------------+ +// MACRO CONSTANT TYPEDEF +//--------------------------------------------------------------------+ +#define VS_STATE_PROBING 0 /* Configuration in progress */ +#define VS_STATE_COMMITTED 1 /* Ready for streaming or Streaming via bulk endpoint */ +#define VS_STATE_STREAMING 2 /* Streaming via isochronous endpoint */ + +typedef struct { + tusb_desc_interface_t std; + tusb_desc_video_control_header_t ctl; +} tusb_desc_vc_itf_t; + +typedef struct { + tusb_desc_interface_t std; + tusb_desc_video_streaming_inout_header_t stm; +} tusb_desc_vs_itf_t; + +typedef union { + tusb_desc_video_control_header_t ctl; + tusb_desc_video_streaming_inout_header_t stm; +} tusb_desc_video_itf_hdr_t; + +typedef struct TU_ATTR_PACKED { + uint8_t bLength; + uint8_t bDescriptorType; + uint8_t bDescriptorSubtype; + uint8_t bEntityId; +} tusb_desc_cs_video_entity_itf_t; + +typedef union { + struct TU_ATTR_PACKED { + uint8_t bLength; + uint8_t bDescriptorType; + uint8_t bDescriptorSubType; + uint8_t bFormatIndex; + uint8_t bNumFrameDescriptors; + }; + tusb_desc_video_format_uncompressed_t uncompressed; + tusb_desc_video_format_mjpeg_t mjpeg; + tusb_desc_video_format_framebased_t frame_based; +} tusb_desc_cs_video_fmt_t; + +typedef union { + struct TU_ATTR_PACKED { + uint8_t bLength; + uint8_t bDescriptorType; + uint8_t bDescriptorSubType; + uint8_t bFrameIndex; + uint8_t bmCapabilities; + uint16_t wWidth; + uint16_t wHeight; + }; + tusb_desc_video_frame_uncompressed_t uncompressed; + tusb_desc_video_frame_mjpeg_t mjpeg; + tusb_desc_video_frame_framebased_t frame_based; +} tusb_desc_cs_video_frm_t; + +/* video streaming interface */ +typedef struct TU_ATTR_PACKED { + uint8_t index_vc; /* index of bound video control interface */ + uint8_t index_vs; /* index from the video control interface */ + struct { + uint16_t beg; /* Offset of the begging of video streaming interface descriptor */ + uint16_t end; /* Offset of the end of video streaming interface descriptor */ + uint16_t cur; /* Offset of the current settings */ + uint16_t ep[2]; /* Offset of endpoint descriptors. 0: streaming, 1: still capture */ + } desc; + uint8_t *buffer; /* frame buffer. assume linear buffer. no support for stride access */ + uint32_t bufsize; /* frame buffer size */ + uint32_t offset; /* offset for the next payload transfer */ + uint32_t max_payload_transfer_size; + uint8_t error_code;/* error code */ + uint8_t state; /* 0:probing 1:committed 2:streaming */ + + video_probe_and_commit_control_t probe_commit_payload; /* Probe and Commit control */ +} videod_streaming_interface_t; + +typedef struct { + TUD_EPBUF_DEF(buf, CFG_TUD_VIDEO_STREAMING_EP_BUFSIZE); +} videod_streaming_epbuf_t; + +/* video control interface */ +typedef struct TU_ATTR_PACKED { + const uint8_t*beg; /* The head of the first video control interface descriptor */ + uint16_t len; /* Byte length of the descriptors */ + uint16_t cur; /* offset for current video control interface */ + uint8_t stm[CFG_TUD_VIDEO_STREAMING]; /* Indices of streaming interface */ + uint8_t error_code; /* error code */ + uint8_t power_mode; +} videod_interface_t; + +//--------------------------------------------------------------------+ +// INTERNAL OBJECT & FUNCTION DECLARATION +//--------------------------------------------------------------------+ +static videod_interface_t _videod_itf[CFG_TUD_VIDEO]; + +static videod_streaming_interface_t _videod_streaming_itf[CFG_TUD_VIDEO_STREAMING]; +CFG_TUD_MEM_SECTION static videod_streaming_epbuf_t _videod_streaming_epbuf[CFG_TUD_VIDEO_STREAMING]; + +static uint8_t const _cap_get = 0x1u; /* support for GET */ +static uint8_t const _cap_get_set = 0x3u; /* support for GET and SET */ + +//--------------------------------------------------------------------+ +// Debug +//--------------------------------------------------------------------+ +#if CFG_TUSB_DEBUG >= CFG_TUD_VIDEO_LOG_LEVEL + +static tu_lookup_entry_t const tu_lookup_video_request[] = { + {.key = VIDEO_REQUEST_UNDEFINED, .data = "Undefined"}, + {.key = VIDEO_REQUEST_SET_CUR, .data = "SetCur"}, + {.key = VIDEO_REQUEST_SET_CUR_ALL, .data = "SetCurAll"}, + {.key = VIDEO_REQUEST_GET_CUR, .data = "GetCur"}, + {.key = VIDEO_REQUEST_GET_MIN, .data = "GetMin"}, + {.key = VIDEO_REQUEST_GET_MAX, .data = "GetMax"}, + {.key = VIDEO_REQUEST_GET_RES, .data = "GetRes"}, + {.key = VIDEO_REQUEST_GET_LEN, .data = "GetLen"}, + {.key = VIDEO_REQUEST_GET_INFO, .data = "GetInfo"}, + {.key = VIDEO_REQUEST_GET_DEF, .data = "GetDef"}, + {.key = VIDEO_REQUEST_GET_CUR_ALL, .data = "GetCurAll"}, + {.key = VIDEO_REQUEST_GET_MIN_ALL, .data = "GetMinAll"}, + {.key = VIDEO_REQUEST_GET_MAX_ALL, .data = "GetMaxAll"}, + {.key = VIDEO_REQUEST_GET_RES_ALL, .data = "GetResAll"}, + {.key = VIDEO_REQUEST_GET_DEF_ALL, .data = "GetDefAll"}, +}; + +static tu_lookup_table_t const tu_table_video_request = { + .count = TU_ARRAY_SIZE(tu_lookup_video_request), + .items = tu_lookup_video_request +}; + +static char const* const tu_str_video_vc_control_selector[] = { + "Undefined", + "Video Power Mode", + "Request Error Code", +}; + +static char const* const tu_str_video_vs_control_selector[] = { + "Undefined", + "Probe", + "Commit", + "Still Probe", + "Still Commit", + "Still Image Trigger", + "Stream Error Code", + "Generate Key Frame", + "Update Frame Segment", + "Sync Delay", +}; + +#endif + +//--------------------------------------------------------------------+ +// Weak stubs: invoked if no strong implementation is available +//--------------------------------------------------------------------+ +TU_ATTR_WEAK void tud_video_frame_xfer_complete_cb(uint_fast8_t ctl_idx, uint_fast8_t stm_idx) { + (void) ctl_idx; + (void) stm_idx; +} + +TU_ATTR_WEAK int tud_video_power_mode_cb(uint_fast8_t ctl_idx, uint8_t power_mod) { + (void) ctl_idx; + (void) power_mod; + return VIDEO_ERROR_NONE; +} + +TU_ATTR_WEAK int tud_video_commit_cb(uint_fast8_t ctl_idx, uint_fast8_t stm_idx, + video_probe_and_commit_control_t const *parameters) { + (void) ctl_idx; + (void) stm_idx; + (void) parameters; + return VIDEO_ERROR_NONE; +} + +//--------------------------------------------------------------------+ +// +//--------------------------------------------------------------------+ + +/** Get interface number from the interface descriptor + * + * @param[in] desc interface descriptor + * + * @return bInterfaceNumber */ +static inline uint8_t _desc_itfnum(void const *desc) { + return ((uint8_t const*)desc)[2]; +} + +/** Get endpoint address from the endpoint descriptor + * + * @param[in] desc endpoint descriptor + * + * @return bEndpointAddress */ +static inline uint8_t _desc_ep_addr(void const *desc) { + return ((uint8_t const*)desc)[2]; +} + +/** Get instance of streaming interface + * + * @param[in] ctl_idx instance number of video control + * @param[in] stm_idx index number of streaming interface + * + * @return instance */ +static videod_streaming_interface_t* _get_instance_streaming(uint_fast8_t ctl_idx, uint_fast8_t stm_idx) { + videod_interface_t *ctl = &_videod_itf[ctl_idx]; + if (!ctl->beg) return NULL; + videod_streaming_interface_t *stm = &_videod_streaming_itf[ctl->stm[stm_idx]]; + if (!stm->desc.beg) return NULL; + return stm; +} + +static tusb_desc_vc_itf_t const* _get_desc_vc(videod_interface_t const *self) { + return (tusb_desc_vc_itf_t const *)(self->beg + self->cur); +} + +static tusb_desc_vs_itf_t const* _get_desc_vs(videod_streaming_interface_t const *self) { + if (!self->desc.cur) return NULL; + uint8_t const *desc = _videod_itf[self->index_vc].beg; + return (tusb_desc_vs_itf_t const*)(desc + self->desc.cur); +} + +/** Find the first descriptor of a given type + * + * @param[in] beg The head of descriptor byte array. + * @param[in] end The tail of descriptor byte array. + * @param[in] desc_type The target descriptor type. + * + * @return The pointer for interface descriptor. + * @retval end did not found interface descriptor */ +static void const* _find_desc(void const *beg, void const *end, uint_fast8_t desc_type) { + void const *cur = beg; + while ((cur < end) && (desc_type != tu_desc_type(cur))) { + cur = tu_desc_next(cur); + } + return cur; +} + +/** Find the first descriptor of two given types + * + * @param[in] beg The head of descriptor byte array. + * @param[in] end The tail of descriptor byte array. + * @param[in] desc_type_0 The first target descriptor type. + * @param[in] desc_type_1 The second target descriptor type. + * + * @return The pointer for interface descriptor. + * @retval end did not found interface descriptor */ +static void const* _find_desc_2_type(void const *beg, void const *end, uint_fast8_t desc_type_0, uint_fast8_t desc_type_1) +{ + void const *cur = beg; + while ((cur < end) && (desc_type_0 != tu_desc_type(cur)) && (desc_type_1 != tu_desc_type(cur))) { + cur = tu_desc_next(cur); + } + return cur; +} + +/** Find the first descriptor specified by the arguments + * + * @param[in] beg The head of descriptor byte array. + * @param[in] end The tail of descriptor byte array. + * @param[in] desc_type The target descriptor type + * @param[in] element_0 The target element following the desc_type + * @param[in] element_1 The target element following the element_0 + * + * @return The pointer for interface descriptor. + * @retval end did not found interface descriptor */ +static void const* _find_desc_3(void const *beg, void const *end, + uint_fast8_t desc_type, + uint_fast8_t element_0, + uint_fast8_t element_1) { + for (void const *cur = beg; cur < end; cur = _find_desc(cur, end, desc_type)) { + uint8_t const *p = (uint8_t const *)cur; + if ((p[2] == element_0) && (p[3] == element_1)) { + return cur; + } + cur = tu_desc_next(cur); + } + return end; +} + +/** Return the next interface descriptor which has another interface number. + * If there are multiple VC interfaces, there will be an IAD descriptor before + * the next interface descriptor. Check both the IAD descriptor and the interface + * descriptor. + * 3.1 Descriptor Layout Overview + * + * @param[in] beg The head of descriptor byte array. + * @param[in] end The tail of descriptor byte array. + * + * @return The pointer for interface descriptor. + * @retval end did not found interface descriptor */ +static void const* _next_desc_itf(void const *beg, void const *end) { + void const *cur = beg; + uint_fast8_t itfnum = ((tusb_desc_interface_t const*)cur)->bInterfaceNumber; + while ((cur < end) && + (itfnum == ((tusb_desc_interface_t const*)cur)->bInterfaceNumber)) { + cur = _find_desc_2_type(tu_desc_next(cur), end, TUSB_DESC_INTERFACE, TUSB_DESC_INTERFACE_ASSOCIATION); + } + return cur; +} + +/** Find the first interface descriptor with the specified interface number and alternate setting number. + * + * @param[in] beg The head of descriptor byte array. + * @param[in] end The tail of descriptor byte array. + * @param[in] itfnum The target interface number. + * @param[in] altnum The target alternate setting number. + * + * @return The pointer for interface descriptor. + * @retval end did not found interface descriptor */ +static inline uint8_t const* _find_desc_itf(void const *beg, void const *end, uint_fast8_t itfnum, uint_fast8_t altnum) +{ + return (uint8_t const*) _find_desc_3(beg, end, TUSB_DESC_INTERFACE, itfnum, altnum); +} + +/** Find the first endpoint descriptor belonging to the current interface descriptor. + * + * The search range is from `beg` to `end` or the next interface descriptor. + * + * @param[in] beg The head of descriptor byte array. + * @param[in] end The tail of descriptor byte array. + * + * @return The pointer for endpoint descriptor. + * @retval end did not found endpoint descriptor */ +static void const* _find_desc_ep(void const *beg, void const *end) +{ + for (void const *cur = beg; cur < end; cur = tu_desc_next(cur)) { + uint_fast8_t desc_type = tu_desc_type(cur); + if (TUSB_DESC_ENDPOINT == desc_type) return cur; + if (TUSB_DESC_INTERFACE == desc_type) break; + } + return end; +} + +/** Return the end of the video control descriptor. */ +static inline void const* _end_of_control_descriptor(void const *desc) +{ + tusb_desc_vc_itf_t const *vc = (tusb_desc_vc_itf_t const *)desc; + return ((uint8_t const*) desc) + vc->std.bLength + tu_le16toh(vc->ctl.wTotalLength); +} + +/** Find the first entity descriptor with the entity ID + * specified by the argument belonging to the current video control descriptor. + * + * @param[in] desc The video control interface descriptor. + * @param[in] entityid The target entity id. + * + * @return The pointer for interface descriptor. + * @retval end did not found interface descriptor */ +static void const* _find_desc_entity(void const *desc, uint_fast8_t entityid) +{ + void const *end = _end_of_control_descriptor(desc); + for (void const *cur = desc; cur < end; cur = _find_desc(cur, end, TUSB_DESC_CS_INTERFACE)) { + tusb_desc_cs_video_entity_itf_t const *itf = (tusb_desc_cs_video_entity_itf_t const *)cur; + if ((VIDEO_CS_ITF_VC_INPUT_TERMINAL <= itf->bDescriptorSubtype + && itf->bDescriptorSubtype < VIDEO_CS_ITF_VC_MAX) + && itf->bEntityId == entityid) { + return itf; + } + cur = tu_desc_next(cur); + } + return end; +} + +/** Return the end of the video streaming descriptor. */ +static inline void const* _end_of_streaming_descriptor(void const *desc) +{ + tusb_desc_vs_itf_t const *vs = (tusb_desc_vs_itf_t const *)desc; + return ((uint8_t const*) desc) + vs->std.bLength + tu_le16toh(vs->stm.wTotalLength); +} + +/** Find the first format descriptor with the specified format number. */ +static inline void const *_find_desc_format(void const *beg, void const *end, uint_fast8_t fmtnum) +{ + for (void const *cur = beg; cur < end; cur = _find_desc(cur, end, TUSB_DESC_CS_INTERFACE)) { + uint8_t const *p = (uint8_t const *)cur; + uint_fast8_t fmt = p[2]; + if ((fmt == VIDEO_CS_ITF_VS_FORMAT_UNCOMPRESSED || + fmt == VIDEO_CS_ITF_VS_FORMAT_MJPEG || + fmt == VIDEO_CS_ITF_VS_FORMAT_DV || + fmt == VIDEO_CS_ITF_VS_FORMAT_FRAME_BASED) && + fmtnum == p[3]) { + return cur; + } + cur = tu_desc_next(cur); + } + return end; +} + +/** Find the first frame descriptor with the specified format number. */ +static inline void const *_find_desc_frame(void const *beg, void const *end, uint_fast8_t frmnum) +{ + for (void const *cur = beg; cur < end; cur = _find_desc(cur, end, TUSB_DESC_CS_INTERFACE)) { + uint8_t const *p = (uint8_t const *)cur; + uint_fast8_t frm = p[2]; + if ((frm == VIDEO_CS_ITF_VS_FRAME_UNCOMPRESSED || + frm == VIDEO_CS_ITF_VS_FRAME_MJPEG || + frm == VIDEO_CS_ITF_VS_FRAME_FRAME_BASED) && + frmnum == p[3]) { + return cur; + } + cur = tu_desc_next(cur); + } + return end; +} + +/** Set uniquely determined values to variables that have not been set + * + * @param[in,out] param Target */ +static bool _update_streaming_parameters(videod_streaming_interface_t const *stm, + video_probe_and_commit_control_t *param) +{ + tusb_desc_vs_itf_t const *vs = _get_desc_vs(stm); + uint_fast8_t fmtnum = param->bFormatIndex; + TU_ASSERT(vs && fmtnum <= vs->stm.bNumFormats); + if (!fmtnum) { + if (1 < vs->stm.bNumFormats) return true; /* Need to negotiate all variables. */ + fmtnum = 1; + param->bFormatIndex = 1; + } + + /* Set the parameters determined by the format */ + param->wKeyFrameRate = 1; + param->wPFrameRate = 0; + param->wCompWindowSize = 1; /* GOP size? */ + param->wDelay = 0; /* milliseconds */ + param->dwClockFrequency = 27000000; /* same as MPEG-2 system time clock */ + param->bmFramingInfo = 0x3; /* enables FrameID and EndOfFrame */ + param->bPreferedVersion = 1; + param->bMinVersion = 1; + param->bMaxVersion = 1; + param->bUsage = 0; + param->bBitDepthLuma = 8; + + void const *end = _end_of_streaming_descriptor(vs); + tusb_desc_cs_video_fmt_t const *fmt = _find_desc_format(tu_desc_next(vs), end, fmtnum); + TU_ASSERT(fmt != end); + + switch (fmt->bDescriptorSubType) { + case VIDEO_CS_ITF_VS_FORMAT_UNCOMPRESSED: + param->wCompQuality = 1; /* 1 to 10000 */ + break; + + case VIDEO_CS_ITF_VS_FORMAT_MJPEG: + break; + + case VIDEO_CS_ITF_VS_FORMAT_FRAME_BASED: + break; + + default: return false; + } + + uint_fast8_t frmnum = param->bFrameIndex; + TU_ASSERT(frmnum <= fmt->bNumFrameDescriptors); + if (!frmnum) { + if (1 < fmt->bNumFrameDescriptors) return true; + frmnum = 1; + param->bFrameIndex = 1; + } + tusb_desc_cs_video_frm_t const *frm = _find_desc_frame(tu_desc_next(fmt), end, frmnum); + TU_ASSERT(frm != end); + + /* Set the parameters determined by the frame */ + uint_fast32_t frame_size = param->dwMaxVideoFrameSize; + if (!frame_size) { + switch (fmt->bDescriptorSubType) { + case VIDEO_CS_ITF_VS_FORMAT_UNCOMPRESSED: + frame_size = (uint_fast32_t)frm->wWidth * frm->wHeight * fmt->uncompressed.bBitsPerPixel / 8; + break; + + case VIDEO_CS_ITF_VS_FORMAT_MJPEG: + frame_size = (uint_fast32_t)frm->wWidth * frm->wHeight * 16 / 8; /* YUV422 */ + break; + + case VIDEO_CS_ITF_VS_FORMAT_FRAME_BASED: + frame_size = (uint_fast32_t)frm->wWidth * frm->wHeight * 16 / 8; /* YUV422 */ + break; + + default: break; + } + param->dwMaxVideoFrameSize = frame_size; + } + + uint_fast32_t interval = param->dwFrameInterval; + if (!interval) { + if ((1 < frm->uncompressed.bFrameIntervalType) || + ((0 == frm->uncompressed.bFrameIntervalType) && + (frm->uncompressed.dwFrameInterval[1] != frm->uncompressed.dwFrameInterval[0]))) { + return true; + } + interval = frm->uncompressed.dwFrameInterval[0]; + param->dwFrameInterval = interval; + } + uint_fast32_t interval_ms = interval / 10000; + TU_ASSERT(interval_ms); + uint_fast32_t payload_size = (frame_size + interval_ms - 1) / interval_ms + 2; + if (CFG_TUD_VIDEO_STREAMING_EP_BUFSIZE < payload_size) { + payload_size = CFG_TUD_VIDEO_STREAMING_EP_BUFSIZE; + } + param->dwMaxPayloadTransferSize = payload_size; + return true; +} + +/** Set the minimum, maximum, default values or resolutions to variables which need to negotiate with the host + * + * @param[in] request GET_MAX, GET_MIN, GET_RES or GET_DEF + * @param[in,out] param Target + */ +static bool _negotiate_streaming_parameters(videod_streaming_interface_t const *stm, uint_fast8_t request, + video_probe_and_commit_control_t *param) +{ + uint_fast8_t const fmtnum = param->bFormatIndex; + if (!fmtnum) { + switch (request) { + case VIDEO_REQUEST_GET_MAX: + if (_get_desc_vs(stm)) + param->bFormatIndex = _get_desc_vs(stm)->stm.bNumFormats; + break; + + case VIDEO_REQUEST_GET_MIN: + case VIDEO_REQUEST_GET_DEF: + param->bFormatIndex = 1; + break; + + default: return false; + } + /* Set the parameters determined by the format */ + param->wKeyFrameRate = 1; + param->wPFrameRate = 0; + param->wCompQuality = 1; /* 1 to 10000 */ + param->wCompWindowSize = 1; /* GOP size? */ + param->wDelay = 0; /* milliseconds */ + param->dwClockFrequency = 27000000; /* same as MPEG-2 system time clock */ + param->bmFramingInfo = 0x3; /* enables FrameID and EndOfFrame */ + param->bPreferedVersion = 1; + param->bMinVersion = 1; + param->bMaxVersion = 1; + param->bUsage = 0; + param->bBitDepthLuma = 8; + return true; + } + + uint_fast8_t frmnum = param->bFrameIndex; + if (!frmnum) { + tusb_desc_vs_itf_t const *vs = _get_desc_vs(stm); + TU_ASSERT(vs); + void const *end = _end_of_streaming_descriptor(vs); + tusb_desc_cs_video_fmt_t const *fmt = _find_desc_format(tu_desc_next(vs), end, fmtnum); + switch (request) { + case VIDEO_REQUEST_GET_MAX: + frmnum = fmt->bNumFrameDescriptors; + break; + + case VIDEO_REQUEST_GET_MIN: + frmnum = 1; + break; + + case VIDEO_REQUEST_GET_DEF: + switch (fmt->bDescriptorSubType) { + case VIDEO_CS_ITF_VS_FORMAT_UNCOMPRESSED: + frmnum = fmt->uncompressed.bDefaultFrameIndex; + break; + + case VIDEO_CS_ITF_VS_FORMAT_MJPEG: + frmnum = fmt->mjpeg.bDefaultFrameIndex; + break; + + case VIDEO_CS_ITF_VS_FORMAT_FRAME_BASED: + frmnum = fmt->frame_based.bDefaultFrameIndex; + break; + + default: return false; + } + break; + default: return false; + } + param->bFrameIndex = (uint8_t)frmnum; + /* Set the parameters determined by the frame */ + tusb_desc_cs_video_frm_t const *frm = _find_desc_frame(tu_desc_next(fmt), end, frmnum); + uint_fast32_t frame_size; + switch (fmt->bDescriptorSubType) { + case VIDEO_CS_ITF_VS_FORMAT_UNCOMPRESSED: + frame_size = (uint_fast32_t)frm->wWidth * frm->wHeight * fmt->uncompressed.bBitsPerPixel / 8; + break; + + case VIDEO_CS_ITF_VS_FORMAT_MJPEG: + frame_size = (uint_fast32_t)frm->wWidth * frm->wHeight * 16 / 8; /* YUV422 */ + break; + + case VIDEO_CS_ITF_VS_FORMAT_FRAME_BASED: + frame_size = (uint_fast32_t)frm->wWidth * frm->wHeight * 16 / 8; /* YUV422 */ + break; + + default: return false; + } + param->dwMaxVideoFrameSize = frame_size; + return true; + } + + if (!param->dwFrameInterval) { + tusb_desc_vs_itf_t const *vs = _get_desc_vs(stm); + TU_ASSERT(vs); + void const *end = _end_of_streaming_descriptor(vs); + tusb_desc_cs_video_fmt_t const *fmt = _find_desc_format(tu_desc_next(vs), end, fmtnum); + tusb_desc_cs_video_frm_t const *frm = _find_desc_frame(tu_desc_next(fmt), end, frmnum); + + uint_fast32_t interval, interval_ms; + switch (request) { + case VIDEO_REQUEST_GET_MAX: { + uint_fast32_t min_interval, max_interval; + uint_fast8_t num_intervals = frm->uncompressed.bFrameIntervalType; + max_interval = num_intervals ? frm->uncompressed.dwFrameInterval[num_intervals - 1]: frm->uncompressed.dwFrameInterval[1]; + min_interval = frm->uncompressed.dwFrameInterval[0]; + interval = max_interval; + interval_ms = min_interval / 10000; + break; + } + + case VIDEO_REQUEST_GET_MIN: { + uint_fast32_t min_interval, max_interval; + uint_fast8_t num_intervals = frm->uncompressed.bFrameIntervalType; + max_interval = num_intervals ? frm->uncompressed.dwFrameInterval[num_intervals - 1]: frm->uncompressed.dwFrameInterval[1]; + min_interval = frm->uncompressed.dwFrameInterval[0]; + interval = min_interval; + interval_ms = max_interval / 10000; + break; + } + + case VIDEO_REQUEST_GET_DEF: + interval = frm->uncompressed.dwDefaultFrameInterval; + interval_ms = interval / 10000; + break; + + case VIDEO_REQUEST_GET_RES: { + uint_fast8_t num_intervals = frm->uncompressed.bFrameIntervalType; + if (num_intervals) { + interval = 0; + interval_ms = 0; + } else { + interval = frm->uncompressed.dwFrameInterval[2]; + interval_ms = interval / 10000; + } + break; + } + + default: return false; + } + param->dwFrameInterval = interval; + if (!interval) { + param->dwMaxPayloadTransferSize = 0; + } else { + uint_fast32_t frame_size = param->dwMaxVideoFrameSize; + uint_fast32_t payload_size; + if (!interval_ms) { + payload_size = frame_size + 2; + } else { + payload_size = (frame_size + interval_ms - 1) / interval_ms + 2; + } + if (CFG_TUD_VIDEO_STREAMING_EP_BUFSIZE < payload_size) { + payload_size = CFG_TUD_VIDEO_STREAMING_EP_BUFSIZE; + } + param->dwMaxPayloadTransferSize = payload_size; + } + return true; + } + return true; +} + +/** Close current video control interface. + * + * @param[in,out] self Video control interface context. + * @param[in] altnum The target alternate setting number. */ +static bool _close_vc_itf(uint8_t rhport, videod_interface_t *self) +{ + tusb_desc_vc_itf_t const *vc = _get_desc_vc(self); + + /* The next descriptor after the class-specific VC interface header descriptor. */ + void const *cur = (uint8_t const*)vc + vc->std.bLength + vc->ctl.bLength; + + /* The end of the video control interface descriptor. */ + void const *end = _end_of_control_descriptor(vc); + if (vc->std.bNumEndpoints) { + /* Find the notification endpoint descriptor. */ + cur = _find_desc(cur, end, TUSB_DESC_ENDPOINT); + TU_ASSERT(cur < end); + tusb_desc_endpoint_t const *notif = (tusb_desc_endpoint_t const *)cur; + usbd_edpt_close(rhport, notif->bEndpointAddress); + } + self->cur = 0; + return true; +} + +/** Set the alternate setting to own video control interface. + * + * @param[in,out] self Video control interface context. + * @param[in] altnum The target alternate setting number. */ +static bool _open_vc_itf(uint8_t rhport, videod_interface_t *self, uint_fast8_t altnum) +{ + TU_LOG_DRV(" open VC %d\r\n", altnum); + uint8_t const *beg = self->beg; + uint8_t const *end = beg + self->len; + + /* The first descriptor is a video control interface descriptor. */ + uint8_t const *cur = _find_desc_itf(beg, end, _desc_itfnum(beg), altnum); + TU_LOG_DRV(" cur %" PRId32 "\r\n", (int32_t) (cur - beg)); + TU_VERIFY(cur < end); + + tusb_desc_vc_itf_t const *vc = (tusb_desc_vc_itf_t const *)cur; + TU_LOG_DRV(" bInCollection %d\r\n", vc->ctl.bInCollection); + /* Support for up to 2 streaming interfaces only. */ + TU_ASSERT(vc->ctl.bInCollection <= CFG_TUD_VIDEO_STREAMING); + + /* Update to point the end of the video control interface descriptor. */ + end = _end_of_control_descriptor(cur); + + /* Advance to the next descriptor after the class-specific VC interface header descriptor. */ + cur += vc->std.bLength + vc->ctl.bLength; + TU_LOG_DRV(" bNumEndpoints %d\r\n", vc->std.bNumEndpoints); + /* Open the notification endpoint if it exist. */ + if (vc->std.bNumEndpoints) { + /* Support for 1 endpoint only. */ + TU_VERIFY(1 == vc->std.bNumEndpoints); + /* Find the notification endpoint descriptor. */ + cur = _find_desc(cur, end, TUSB_DESC_ENDPOINT); + TU_VERIFY(cur < end); + tusb_desc_endpoint_t const *notif = (tusb_desc_endpoint_t const *)cur; + /* Open the notification endpoint */ + TU_ASSERT(usbd_edpt_open(rhport, notif)); + } + self->cur = (uint16_t) ((uint8_t const*)vc - beg); + return true; +} + +static bool _init_vs_configuration(videod_streaming_interface_t *stm) { + /* initialize streaming settings */ + stm->state = VS_STATE_PROBING; + stm->max_payload_transfer_size = 0; + video_probe_and_commit_control_t *param = &stm->probe_commit_payload; + tu_memclr(param, sizeof(*param)); + return _update_streaming_parameters(stm, param); +} + +/** Set the alternate setting to own video streaming interface. + * + * @param[in,out] stm Streaming interface context. + * @param[in] altnum The target alternate setting number. */ +static bool _open_vs_itf(uint8_t rhport, videod_streaming_interface_t *stm, uint_fast8_t altnum) +{ + uint_fast8_t i; + TU_LOG_DRV(" reopen VS %d\r\n", altnum); + uint8_t const *desc = _videod_itf[stm->index_vc].beg; + +#ifndef TUP_DCD_EDPT_ISO_ALLOC + /* Close endpoints of previous settings. */ + for (i = 0; i < TU_ARRAY_SIZE(stm->desc.ep); ++i) { + uint_fast16_t ofs_ep = stm->desc.ep[i]; + if (!ofs_ep) break; + tusb_desc_endpoint_t const *ep = (tusb_desc_endpoint_t const*)(desc + ofs_ep); + /* Only ISO endpoints needs to be closed */ + if(ep->bmAttributes.xfer == TUSB_XFER_ISOCHRONOUS) { + stm->desc.ep[i] = 0; + usbd_edpt_close(rhport, ep->bEndpointAddress); + TU_LOG_DRV(" close EP%02x\r\n", ep->bEndpointAddress); + } + } +#endif + + /* clear transfer management information */ + stm->buffer = NULL; + stm->bufsize = 0; + stm->offset = 0; + + /* Find a alternate interface */ + uint8_t const *beg = desc + stm->desc.beg; + uint8_t const *end = desc + stm->desc.end; + uint8_t const *cur = _find_desc_itf(beg, end, _desc_itfnum(beg), altnum); + TU_VERIFY(cur < end); + + uint_fast8_t numeps = ((tusb_desc_interface_t const *)cur)->bNumEndpoints; + TU_ASSERT(numeps <= TU_ARRAY_SIZE(stm->desc.ep)); + stm->desc.cur = (uint16_t)(cur - desc); /* Save the offset of the new settings */ + if (!altnum && (VS_STATE_COMMITTED != stm->state)) { + TU_VERIFY(_init_vs_configuration(stm)); + } + /* Open bulk or isochronous endpoints of the new settings. */ + for (i = 0, cur = tu_desc_next(cur); i < numeps; ++i, cur = tu_desc_next(cur)) { + cur = _find_desc_ep(cur, end); + TU_ASSERT(cur < end); + tusb_desc_endpoint_t const *ep = (tusb_desc_endpoint_t const*)cur; + uint_fast32_t max_size = stm->max_payload_transfer_size; + if (altnum && (TUSB_XFER_ISOCHRONOUS == ep->bmAttributes.xfer)) { + /* FS must be less than or equal to max packet size */ + TU_VERIFY (tu_edpt_packet_size(ep) >= max_size); +#ifdef TUP_DCD_EDPT_ISO_ALLOC + usbd_edpt_iso_activate(rhport, ep); +#else + TU_ASSERT(usbd_edpt_open(rhport, ep)); +#endif + } else { + TU_VERIFY(TUSB_XFER_BULK == ep->bmAttributes.xfer); + TU_ASSERT(usbd_edpt_open(rhport, ep)); + } + stm->desc.ep[i] = (uint16_t) (cur - desc); + TU_LOG_DRV(" open EP%02x\r\n", _desc_ep_addr(cur)); + } + if (altnum) { + stm->state = VS_STATE_STREAMING; + } + TU_LOG_DRV(" done\r\n"); + return true; +} + +/** Prepare the next packet payload. */ +static uint_fast16_t _prepare_in_payload(videod_streaming_interface_t *stm, uint8_t* ep_buf) { + uint_fast16_t remaining = stm->bufsize - stm->offset; + uint_fast16_t hdr_len = ep_buf[0]; + uint_fast16_t pkt_len = stm->max_payload_transfer_size; + if (hdr_len + remaining < pkt_len) { + pkt_len = hdr_len + remaining; + } + TU_ASSERT(pkt_len >= hdr_len); + uint_fast16_t data_len = pkt_len - hdr_len; + memcpy(&ep_buf[hdr_len], stm->buffer + stm->offset, data_len); + stm->offset += data_len; + remaining -= data_len; + if (!remaining) { + tusb_video_payload_header_t *hdr = (tusb_video_payload_header_t*) ep_buf; + hdr->EndOfFrame = 1; + } + return hdr_len + data_len; +} + +/** Handle a standard request to the video control interface. */ +static int handle_video_ctl_std_req(uint8_t rhport, uint8_t stage, + tusb_control_request_t const *request, + uint_fast8_t ctl_idx) +{ + TU_LOG_DRV("\r\n"); + switch (request->bRequest) { + case TUSB_REQ_GET_INTERFACE: + if (stage == CONTROL_STAGE_SETUP) + { + TU_VERIFY(1 == request->wLength, VIDEO_ERROR_UNKNOWN); + tusb_desc_vc_itf_t const *vc = _get_desc_vc(&_videod_itf[ctl_idx]); + TU_VERIFY(vc, VIDEO_ERROR_UNKNOWN); + + uint8_t alt_num = vc->std.bAlternateSetting; + + TU_VERIFY(tud_control_xfer(rhport, request, &alt_num, sizeof(alt_num)), VIDEO_ERROR_UNKNOWN); + } + return VIDEO_ERROR_NONE; + + case TUSB_REQ_SET_INTERFACE: + if (stage == CONTROL_STAGE_SETUP) + { + TU_VERIFY(0 == request->wLength, VIDEO_ERROR_UNKNOWN); + TU_VERIFY(_close_vc_itf(rhport, &_videod_itf[ctl_idx]), VIDEO_ERROR_UNKNOWN); + TU_VERIFY(_open_vc_itf(rhport, &_videod_itf[ctl_idx], request->wValue), VIDEO_ERROR_UNKNOWN); + tud_control_status(rhport, request); + } + return VIDEO_ERROR_NONE; + + default: /* Unknown/Unsupported request */ + TU_BREAKPOINT(); + return VIDEO_ERROR_INVALID_REQUEST; + } +} + +static int handle_video_ctl_cs_req(uint8_t rhport, uint8_t stage, + tusb_control_request_t const *request, + uint_fast8_t ctl_idx) +{ + videod_interface_t *self = &_videod_itf[ctl_idx]; + + /* 4.2.1 Interface Control Request */ + uint8_t const ctrl_sel = TU_U16_HIGH(request->wValue); + TU_LOG_DRV("%s_Control(%s)\r\n", tu_str_video_vc_control_selector[ctrl_sel], tu_lookup_find(&tu_table_video_request, request->bRequest)); + + switch (ctrl_sel) { + case VIDEO_VC_CTL_VIDEO_POWER_MODE: + switch (request->bRequest) { + case VIDEO_REQUEST_SET_CUR: + if (stage == CONTROL_STAGE_SETUP) { + TU_VERIFY(1 == request->wLength, VIDEO_ERROR_UNKNOWN); + TU_VERIFY(tud_control_xfer(rhport, request, &self->power_mode, sizeof(self->power_mode)), VIDEO_ERROR_UNKNOWN); + } else if (stage == CONTROL_STAGE_DATA) { + return tud_video_power_mode_cb(ctl_idx, self->power_mode); + } + return VIDEO_ERROR_NONE; + + case VIDEO_REQUEST_GET_CUR: + if (stage == CONTROL_STAGE_SETUP) + { + TU_VERIFY(1 == request->wLength, VIDEO_ERROR_UNKNOWN); + TU_VERIFY(tud_control_xfer(rhport, request, &self->power_mode, sizeof(self->power_mode)), VIDEO_ERROR_UNKNOWN); + } + return VIDEO_ERROR_NONE; + + case VIDEO_REQUEST_GET_INFO: + if (stage == CONTROL_STAGE_SETUP) + { + TU_VERIFY(1 == request->wLength, VIDEO_ERROR_UNKNOWN); + TU_VERIFY(tud_control_xfer(rhport, request, (uint8_t*)(uintptr_t) &_cap_get_set, sizeof(_cap_get_set)), VIDEO_ERROR_UNKNOWN); + } + return VIDEO_ERROR_NONE; + + default: break; + } + break; + + case VIDEO_VC_CTL_REQUEST_ERROR_CODE: + switch (request->bRequest) { + case VIDEO_REQUEST_GET_CUR: + if (stage == CONTROL_STAGE_SETUP) + { + TU_VERIFY(tud_control_xfer(rhport, request, &self->error_code, sizeof(uint8_t)), VIDEO_ERROR_UNKNOWN); + } + return VIDEO_ERROR_NONE; + + case VIDEO_REQUEST_GET_INFO: + if (stage == CONTROL_STAGE_SETUP) + { + TU_VERIFY(tud_control_xfer(rhport, request, (uint8_t*)(uintptr_t) &_cap_get, sizeof(_cap_get)), VIDEO_ERROR_UNKNOWN); + } + return VIDEO_ERROR_NONE; + + default: break; + } + break; + + default: break; + } + + /* Unknown/Unsupported request */ + TU_BREAKPOINT(); + return VIDEO_ERROR_INVALID_REQUEST; +} + +static int handle_video_ctl_req(uint8_t rhport, uint8_t stage, + tusb_control_request_t const *request, + uint_fast8_t ctl_idx) +{ + switch (request->bmRequestType_bit.type) { + case TUSB_REQ_TYPE_STANDARD: + return handle_video_ctl_std_req(rhport, stage, request, ctl_idx); + + case TUSB_REQ_TYPE_CLASS: { + uint_fast8_t entity_id = TU_U16_HIGH(request->wIndex); + if (!entity_id) { + return handle_video_ctl_cs_req(rhport, stage, request, ctl_idx); + } else { + TU_VERIFY(_find_desc_entity(_get_desc_vc(&_videod_itf[ctl_idx]), entity_id), VIDEO_ERROR_INVALID_REQUEST); + return VIDEO_ERROR_NONE; + } + } + + default: + return VIDEO_ERROR_INVALID_REQUEST; + } +} + +static int handle_video_stm_std_req(uint8_t rhport, uint8_t stage, + tusb_control_request_t const *request, + uint_fast8_t stm_idx) +{ + TU_LOG_DRV("\r\n"); + videod_streaming_interface_t *self = &_videod_streaming_itf[stm_idx]; + switch (request->bRequest) { + case TUSB_REQ_GET_INTERFACE: + if (stage == CONTROL_STAGE_SETUP) + { + TU_VERIFY(1 == request->wLength, VIDEO_ERROR_UNKNOWN); + tusb_desc_vs_itf_t const *vs = _get_desc_vs(self); + TU_VERIFY(vs, VIDEO_ERROR_UNKNOWN); + uint8_t alt_num = vs->std.bAlternateSetting; + + TU_VERIFY(tud_control_xfer(rhport, request, &alt_num, sizeof(alt_num)), VIDEO_ERROR_UNKNOWN); + } + return VIDEO_ERROR_NONE; + + case TUSB_REQ_SET_INTERFACE: + if (stage == CONTROL_STAGE_SETUP) { + TU_VERIFY(_open_vs_itf(rhport, self, request->wValue), VIDEO_ERROR_UNKNOWN); + tud_control_status(rhport, request); + } + return VIDEO_ERROR_NONE; + + default: /* Unknown/Unsupported request */ + TU_BREAKPOINT(); + return VIDEO_ERROR_INVALID_REQUEST; + } +} + +static int handle_video_stm_cs_req(uint8_t rhport, uint8_t stage, + tusb_control_request_t const *request, + uint_fast8_t stm_idx) { + (void)rhport; + videod_streaming_interface_t *stm = &_videod_streaming_itf[stm_idx]; + videod_streaming_epbuf_t *stm_epbuf = &_videod_streaming_epbuf[stm_idx]; + + uint8_t const ctrl_sel = TU_U16_HIGH(request->wValue); + TU_LOG_DRV("%s_Control(%s)\r\n", tu_str_video_vs_control_selector[ctrl_sel], tu_lookup_find(&tu_table_video_request, request->bRequest)); + + /* 4.2.1 Interface Control Request */ + switch (ctrl_sel) { + case VIDEO_VS_CTL_STREAM_ERROR_CODE: + switch (request->bRequest) { + case VIDEO_REQUEST_GET_CUR: + if (stage == CONTROL_STAGE_SETUP) { + /* TODO */ + TU_VERIFY(tud_control_xfer(rhport, request, &stm->error_code, sizeof(uint8_t)), VIDEO_ERROR_UNKNOWN); + } + return VIDEO_ERROR_NONE; + + case VIDEO_REQUEST_GET_INFO: + if (stage == CONTROL_STAGE_SETUP) { + TU_VERIFY(tud_control_xfer(rhport, request, (uint8_t*)(uintptr_t) &_cap_get, sizeof(_cap_get)), VIDEO_ERROR_UNKNOWN); + } + return VIDEO_ERROR_NONE; + + default: break; + } + break; + + case VIDEO_VS_CTL_PROBE: + if (stm->state != VS_STATE_PROBING) { + stm->state = VS_STATE_PROBING; + } + + switch (request->bRequest) { + case VIDEO_REQUEST_SET_CUR: + if (stage == CONTROL_STAGE_SETUP) { + TU_VERIFY(tud_control_xfer(rhport, request, &stm->probe_commit_payload, sizeof(video_probe_and_commit_control_t)), + VIDEO_ERROR_UNKNOWN); + } else if (stage == CONTROL_STAGE_DATA) { + TU_VERIFY(_update_streaming_parameters(stm, &stm->probe_commit_payload), + VIDEO_ERROR_INVALID_VALUE_WITHIN_RANGE); + } + return VIDEO_ERROR_NONE; + + case VIDEO_REQUEST_GET_CUR: + if (stage == CONTROL_STAGE_SETUP) { + TU_VERIFY(request->wLength, VIDEO_ERROR_UNKNOWN); + TU_VERIFY(tud_control_xfer(rhport, request, &stm->probe_commit_payload, sizeof(video_probe_and_commit_control_t)), VIDEO_ERROR_UNKNOWN); + } + return VIDEO_ERROR_NONE; + + case VIDEO_REQUEST_GET_MIN: + case VIDEO_REQUEST_GET_MAX: + case VIDEO_REQUEST_GET_RES: + case VIDEO_REQUEST_GET_DEF: + if (stage == CONTROL_STAGE_SETUP) { + TU_VERIFY(request->wLength, VIDEO_ERROR_UNKNOWN); + video_probe_and_commit_control_t tmp = stm->probe_commit_payload; + TU_VERIFY(_negotiate_streaming_parameters(stm, request->bRequest, &tmp), VIDEO_ERROR_INVALID_VALUE_WITHIN_RANGE); + TU_VERIFY(tud_control_xfer(rhport, request, &tmp, sizeof(tmp)), VIDEO_ERROR_UNKNOWN); + } + return VIDEO_ERROR_NONE; + + case VIDEO_REQUEST_GET_LEN: + if (stage == CONTROL_STAGE_SETUP) { + TU_VERIFY(2 == request->wLength, VIDEO_ERROR_UNKNOWN); + uint16_t len = sizeof(video_probe_and_commit_control_t); + TU_VERIFY(tud_control_xfer(rhport, request, (uint8_t*)&len, sizeof(len)), VIDEO_ERROR_UNKNOWN); + } + return VIDEO_ERROR_NONE; + + case VIDEO_REQUEST_GET_INFO: + if (stage == CONTROL_STAGE_SETUP) { + TU_VERIFY(1 == request->wLength, VIDEO_ERROR_UNKNOWN); + TU_VERIFY(tud_control_xfer(rhport, request, (uint8_t*)(uintptr_t)&_cap_get_set, sizeof(_cap_get_set)), VIDEO_ERROR_UNKNOWN); + } + return VIDEO_ERROR_NONE; + + default: break; + } + break; + + case VIDEO_VS_CTL_COMMIT: + switch (request->bRequest) { + case VIDEO_REQUEST_SET_CUR: + if (stage == CONTROL_STAGE_SETUP) { + TU_VERIFY(tud_control_xfer(rhport, request, &stm->probe_commit_payload, sizeof(video_probe_and_commit_control_t)), VIDEO_ERROR_UNKNOWN); + } else if (stage == CONTROL_STAGE_DATA) { + video_probe_and_commit_control_t *param = &stm->probe_commit_payload; + TU_VERIFY(_update_streaming_parameters(stm, param), VIDEO_ERROR_INVALID_VALUE_WITHIN_RANGE); + /* Set the negotiated value */ + stm->max_payload_transfer_size = param->dwMaxPayloadTransferSize; + int ret = tud_video_commit_cb(stm->index_vc, stm->index_vs, param); + if (VIDEO_ERROR_NONE == ret) { + stm->state = VS_STATE_COMMITTED; + stm->buffer = NULL; + stm->bufsize = 0; + stm->offset = 0; + /* initialize payload header */ + tusb_video_payload_header_t *hdr = (tusb_video_payload_header_t*)stm_epbuf->buf; + hdr->bHeaderLength = sizeof(*hdr); + hdr->bmHeaderInfo = 0; + } + } + return VIDEO_ERROR_NONE; + + case VIDEO_REQUEST_GET_CUR: + if (stage == CONTROL_STAGE_SETUP) { + TU_VERIFY(request->wLength, VIDEO_ERROR_UNKNOWN); + TU_VERIFY(tud_control_xfer(rhport, request, &stm->probe_commit_payload, sizeof(video_probe_and_commit_control_t)), VIDEO_ERROR_UNKNOWN); + } + return VIDEO_ERROR_NONE; + + case VIDEO_REQUEST_GET_LEN: + if (stage == CONTROL_STAGE_SETUP) { + TU_VERIFY(2 == request->wLength, VIDEO_ERROR_UNKNOWN); + uint16_t len = sizeof(video_probe_and_commit_control_t); + TU_VERIFY(tud_control_xfer(rhport, request, (uint8_t*)&len, sizeof(len)), VIDEO_ERROR_UNKNOWN); + } + return VIDEO_ERROR_NONE; + + case VIDEO_REQUEST_GET_INFO: + if (stage == CONTROL_STAGE_SETUP) { + TU_VERIFY(1 == request->wLength, VIDEO_ERROR_UNKNOWN); + TU_VERIFY(tud_control_xfer(rhport, request, (uint8_t*)(uintptr_t) &_cap_get_set, sizeof(_cap_get_set)), VIDEO_ERROR_UNKNOWN); + } + return VIDEO_ERROR_NONE; + + default: break; + } + break; + + case VIDEO_VS_CTL_STILL_PROBE: + case VIDEO_VS_CTL_STILL_COMMIT: + case VIDEO_VS_CTL_STILL_IMAGE_TRIGGER: + case VIDEO_VS_CTL_GENERATE_KEY_FRAME: + case VIDEO_VS_CTL_UPDATE_FRAME_SEGMENT: + case VIDEO_VS_CTL_SYNCH_DELAY_CONTROL: + /* TODO */ + break; + + default: break; + } + + /* Unknown/Unsupported request */ + TU_BREAKPOINT(); + return VIDEO_ERROR_INVALID_REQUEST; +} + +static int handle_video_stm_req(uint8_t rhport, uint8_t stage, + tusb_control_request_t const *request, + uint_fast8_t stm_idx) +{ + switch (request->bmRequestType_bit.type) { + case TUSB_REQ_TYPE_STANDARD: + return handle_video_stm_std_req(rhport, stage, request, stm_idx); + + case TUSB_REQ_TYPE_CLASS: + if (TU_U16_HIGH(request->wIndex)) return VIDEO_ERROR_INVALID_REQUEST; + return handle_video_stm_cs_req(rhport, stage, request, stm_idx); + + default: return VIDEO_ERROR_INVALID_REQUEST; + } +} + +//--------------------------------------------------------------------+ +// APPLICATION API +//--------------------------------------------------------------------+ + +bool tud_video_n_connected(uint_fast8_t ctl_idx) +{ + TU_ASSERT(ctl_idx < CFG_TUD_VIDEO); + videod_streaming_interface_t *stm = _get_instance_streaming(ctl_idx, 0); + if (stm) return true; + return false; +} + +bool tud_video_n_streaming(uint_fast8_t ctl_idx, uint_fast8_t stm_idx) +{ + TU_ASSERT(ctl_idx < CFG_TUD_VIDEO); + TU_ASSERT(stm_idx < CFG_TUD_VIDEO_STREAMING); + videod_streaming_interface_t *stm = _get_instance_streaming(ctl_idx, stm_idx); + if (!stm || !stm->desc.ep[0]) return false; + if (stm->state == VS_STATE_PROBING) return false; + +#ifdef TUP_DCD_EDPT_ISO_ALLOC + uint8_t const *desc = _videod_itf[stm->index_vc].beg; + uint_fast16_t ofs_ep = stm->desc.ep[0]; + tusb_desc_endpoint_t const *ep = (tusb_desc_endpoint_t const*)(desc + ofs_ep); + if (ep->bmAttributes.xfer == TUSB_XFER_ISOCHRONOUS) { + if (stm->state == VS_STATE_COMMITTED) return false; + } +#endif + + return true; +} + +bool tud_video_n_frame_xfer(uint_fast8_t ctl_idx, uint_fast8_t stm_idx, void *buffer, size_t bufsize) { + TU_ASSERT(ctl_idx < CFG_TUD_VIDEO); + TU_ASSERT(stm_idx < CFG_TUD_VIDEO_STREAMING); + + if (!buffer || !bufsize) return false; + videod_streaming_interface_t *stm = _get_instance_streaming(ctl_idx, stm_idx); + videod_streaming_epbuf_t *stm_epbuf = &_videod_streaming_epbuf[ctl_idx]; + + if (!stm || !stm->desc.ep[0] || stm->buffer) return false; + if (stm->state == VS_STATE_PROBING) return false; + + /* Find EP address */ + uint8_t const *desc = _videod_itf[stm->index_vc].beg; + uint8_t ep_addr = 0; + for (uint_fast8_t i = 0; i < CFG_TUD_VIDEO_STREAMING; ++i) { + uint_fast16_t ofs_ep = stm->desc.ep[i]; + if (!ofs_ep) continue; + ep_addr = _desc_ep_addr(desc + ofs_ep); + break; + } + if (!ep_addr) return false; + + TU_VERIFY( usbd_edpt_claim(0, ep_addr) ); + /* update the packet header */ + tusb_video_payload_header_t *hdr = (tusb_video_payload_header_t*)stm_epbuf->buf; + hdr->FrameID ^= 1; + hdr->EndOfFrame = 0; + /* update the packet data */ + stm->buffer = (uint8_t*)buffer; + stm->bufsize = bufsize; + uint_fast16_t pkt_len = _prepare_in_payload(stm, stm_epbuf->buf); + TU_ASSERT( usbd_edpt_xfer(0, ep_addr, stm_epbuf->buf, (uint16_t) pkt_len), 0); + return true; +} + +//--------------------------------------------------------------------+ +// USBD Driver API +//--------------------------------------------------------------------+ +void videod_init(void) { + for (uint_fast8_t i = 0; i < CFG_TUD_VIDEO; ++i) { + videod_interface_t* ctl = &_videod_itf[i]; + tu_memclr(ctl, sizeof(*ctl)); + } + for (uint_fast8_t i = 0; i < CFG_TUD_VIDEO_STREAMING; ++i) { + videod_streaming_interface_t *stm = &_videod_streaming_itf[i]; + tu_memclr(stm, sizeof(videod_streaming_interface_t)); + } +} + +bool videod_deinit(void) { + return true; +} + +void videod_reset(uint8_t rhport) { + (void) rhport; + for (uint_fast8_t i = 0; i < CFG_TUD_VIDEO; ++i) { + videod_interface_t* ctl = &_videod_itf[i]; + tu_memclr(ctl, sizeof(*ctl)); + } + for (uint_fast8_t i = 0; i < CFG_TUD_VIDEO_STREAMING; ++i) { + videod_streaming_interface_t *stm = &_videod_streaming_itf[i]; + tu_memclr(stm, sizeof(videod_streaming_interface_t)); + } +} + +uint16_t videod_open(uint8_t rhport, tusb_desc_interface_t const * itf_desc, uint16_t max_len) { + TU_VERIFY((TUSB_CLASS_VIDEO == itf_desc->bInterfaceClass) && + (VIDEO_SUBCLASS_CONTROL == itf_desc->bInterfaceSubClass) && + (VIDEO_ITF_PROTOCOL_15 == itf_desc->bInterfaceProtocol), 0); + + /* Find available interface */ + videod_interface_t *self = NULL; + uint8_t ctl_idx; + for (ctl_idx = 0; ctl_idx < CFG_TUD_VIDEO; ++ctl_idx) { + if (_videod_itf[ctl_idx].beg) continue; + self = &_videod_itf[ctl_idx]; + break; + } + TU_ASSERT(ctl_idx < CFG_TUD_VIDEO, 0); + + uint8_t const *end = (uint8_t const*)itf_desc + max_len; + self->beg = (uint8_t const*) itf_desc; + self->len = max_len; + + /*------------- Video Control Interface -------------*/ + TU_VERIFY(_open_vc_itf(rhport, self, 0), 0); + tusb_desc_vc_itf_t const *vc = _get_desc_vc(self); + uint_fast8_t bInCollection = vc->ctl.bInCollection; + + /* Find the end of the video interface descriptor */ + void const *cur = _next_desc_itf(itf_desc, end); + for (uint8_t stm_idx = 0; stm_idx < bInCollection; ++stm_idx) { + videod_streaming_interface_t *stm = NULL; + /* find free streaming interface handle */ + for (uint8_t i = 0; i < CFG_TUD_VIDEO_STREAMING; ++i) { + if (_videod_streaming_itf[i].desc.beg) continue; + stm = &_videod_streaming_itf[i]; + self->stm[stm_idx] = i; + break; + } + TU_ASSERT(stm, 0); + stm->index_vc = ctl_idx; + stm->index_vs = stm_idx; + stm->desc.beg = (uint16_t) ((uintptr_t)cur - (uintptr_t)itf_desc); + cur = _next_desc_itf(cur, end); + stm->desc.end = (uint16_t) ((uintptr_t)cur - (uintptr_t)itf_desc); + stm->state = VS_STATE_PROBING; +#ifdef TUP_DCD_EDPT_ISO_ALLOC + /* Allocate ISO endpoints */ + uint16_t ep_size = 0; + uint8_t ep_addr = 0; + uint8_t const *p_desc = (uint8_t const*)itf_desc + stm->desc.beg; + uint8_t const *p_desc_end = (uint8_t const*)itf_desc + stm->desc.end; + while (p_desc < p_desc_end) { + if (tu_desc_type(p_desc) == TUSB_DESC_ENDPOINT) { + tusb_desc_endpoint_t const *desc_ep = (tusb_desc_endpoint_t const *) p_desc; + if (desc_ep->bmAttributes.xfer == TUSB_XFER_ISOCHRONOUS) { + ep_addr = desc_ep->bEndpointAddress; + ep_size = TU_MAX(tu_edpt_packet_size(desc_ep), ep_size); + } + } + p_desc = tu_desc_next(p_desc); + } + if(ep_addr > 0 && ep_size > 0) usbd_edpt_iso_alloc(rhport, ep_addr, ep_size); +#endif + if (0 == stm_idx && 1 == bInCollection) { + /* If there is only one streaming interface and no alternate settings, + * host may not issue set_interface so open the streaming interface here. */ + uint8_t const *sbeg = (uint8_t const*)itf_desc + stm->desc.beg; + uint8_t const *send = (uint8_t const*)itf_desc + stm->desc.end; + if (send == _find_desc_itf(sbeg, send, _desc_itfnum(sbeg), 1)) { + TU_VERIFY(_open_vs_itf(rhport, stm, 0), 0); + } + } + } + self->len = (uint16_t) ((uintptr_t)cur - (uintptr_t)itf_desc); + return (uint16_t) ((uintptr_t)cur - (uintptr_t)itf_desc); +} + +// Invoked when a control transfer occurred on an interface of this class +// Driver response accordingly to the request and the transfer stage (setup/data/ack) +// return false to stall control endpoint (e.g unsupported request) +bool videod_control_xfer_cb(uint8_t rhport, uint8_t stage, tusb_control_request_t const * request) { + int err; + TU_VERIFY(request->bmRequestType_bit.recipient == TUSB_REQ_RCPT_INTERFACE); + uint_fast8_t itfnum = tu_u16_low(request->wIndex); + /* Identify which control interface to use */ + uint_fast8_t itf; + for (itf = 0; itf < CFG_TUD_VIDEO; ++itf) { + void const *desc = _videod_itf[itf].beg; + if (!desc) continue; + if (itfnum == _desc_itfnum(desc)) break; + } + + if (itf < CFG_TUD_VIDEO) { + TU_LOG_DRV(" VC[%d]: ", itf); + err = handle_video_ctl_req(rhport, stage, request, itf); + _videod_itf[itf].error_code = (uint8_t)err; + if (err) return false; + return true; + } + + /* Identify which streaming interface to use */ + for (itf = 0; itf < CFG_TUD_VIDEO_STREAMING; ++itf) { + videod_streaming_interface_t *stm = &_videod_streaming_itf[itf]; + if (!stm->desc.beg) continue; + uint8_t const *desc = _videod_itf[stm->index_vc].beg; + if (itfnum == _desc_itfnum(desc + stm->desc.beg)) break; + } + + if (itf < CFG_TUD_VIDEO_STREAMING) { + TU_LOG_DRV(" VS[%d]: ", itf); + err = handle_video_stm_req(rhport, stage, request, itf); + _videod_streaming_itf[itf].error_code = (uint8_t)err; + if (err) return false; + return true; + } + return false; +} + +bool videod_xfer_cb(uint8_t rhport, uint8_t ep_addr, xfer_result_t result, uint32_t xferred_bytes) { + (void)result; (void)xferred_bytes; + + /* find streaming handle */ + uint_fast8_t itf; + videod_interface_t *ctl; + videod_streaming_interface_t *stm; + for (itf = 0; itf < CFG_TUD_VIDEO_STREAMING; ++itf) { + stm = &_videod_streaming_itf[itf]; + uint_fast16_t const ep_ofs = stm->desc.ep[0]; + if (!ep_ofs) continue; + ctl = &_videod_itf[stm->index_vc]; + uint8_t const *desc = ctl->beg; + if (ep_addr == _desc_ep_addr(desc + ep_ofs)) break; + } + TU_ASSERT(itf < CFG_TUD_VIDEO_STREAMING); + videod_streaming_epbuf_t *stm_epbuf = &_videod_streaming_epbuf[itf]; + + if (stm->offset < stm->bufsize) { + /* Claim the endpoint */ + TU_VERIFY( usbd_edpt_claim(rhport, ep_addr), 0); + uint_fast16_t pkt_len = _prepare_in_payload(stm, stm_epbuf->buf); + TU_ASSERT( usbd_edpt_xfer(rhport, ep_addr, stm_epbuf->buf, (uint16_t) pkt_len), 0); + } else { + stm->buffer = NULL; + stm->bufsize = 0; + stm->offset = 0; + tud_video_frame_xfer_complete_cb(stm->index_vc, stm->index_vs); + } + return true; +} + +#endif diff --git a/cores/nRF5/TinyUSB/Adafruit_TinyUSB_ArduinoCore/tinyusb/src/class/video/video_device.h b/cores/nRF5/TinyUSB/Adafruit_TinyUSB_ArduinoCore/tinyusb/src/class/video/video_device.h new file mode 100644 index 0000000..2b41c3b --- /dev/null +++ b/cores/nRF5/TinyUSB/Adafruit_TinyUSB_ArduinoCore/tinyusb/src/class/video/video_device.h @@ -0,0 +1,100 @@ +/* + * The MIT License (MIT) + * + * Copyright (c) 2019 Ha Thach (tinyusb.org) + * Copyright (c) 2021 Koji KITAYAMA + * + * 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_VIDEO_DEVICE_H_ +#define TUSB_VIDEO_DEVICE_H_ + +#include "common/tusb_common.h" +#include "video.h" + +#ifdef __cplusplus +extern "C" { +#endif + +//--------------------------------------------------------------------+ +// Application API (Multiple Ports) +// CFG_TUD_VIDEO > 1 +//--------------------------------------------------------------------+ + +bool tud_video_n_connected(uint_fast8_t ctl_idx); + +/** Return true if streaming + * + * @param[in] ctl_idx Destination control interface index + * @param[in] stm_idx Destination streaming interface index */ +bool tud_video_n_streaming(uint_fast8_t ctl_idx, uint_fast8_t stm_idx); + +/** Transfer a frame + * + * @param[in] ctl_idx Destination control interface index + * @param[in] stm_idx Destination streaming interface index + * @param[in] buffer Frame buffer. The caller must not use this buffer until the operation is completed. + * @param[in] bufsize Byte size of the frame buffer */ +bool tud_video_n_frame_xfer(uint_fast8_t ctl_idx, uint_fast8_t stm_idx, void *buffer, size_t bufsize); + +/*------------- Optional callbacks -------------*/ +/** Invoked when compeletion of a frame transfer + * + * @param[in] ctl_idx Destination control interface index + * @param[in] stm_idx Destination streaming interface index */ +void tud_video_frame_xfer_complete_cb(uint_fast8_t ctl_idx, uint_fast8_t stm_idx); + +//--------------------------------------------------------------------+ +// Application Callback API (weak is optional) +//--------------------------------------------------------------------+ + +/** Invoked when SET_POWER_MODE request received + * + * @param[in] ctl_idx Destination control interface index + * @param[in] stm_idx Destination streaming interface index + * @return video_error_code_t */ +int tud_video_power_mode_cb(uint_fast8_t ctl_idx, uint8_t power_mod); + +/** Invoked when VS_COMMIT_CONTROL(SET_CUR) request received + * + * @param[in] ctl_idx Destination control interface index + * @param[in] stm_idx Destination streaming interface index + * @param[in] parameters Video streaming parameters + * @return video_error_code_t */ +int tud_video_commit_cb(uint_fast8_t ctl_idx, uint_fast8_t stm_idx, + video_probe_and_commit_control_t const *parameters); + +//--------------------------------------------------------------------+ +// INTERNAL USBD-CLASS DRIVER API +//--------------------------------------------------------------------+ +void videod_init (void); +bool videod_deinit (void); +void videod_reset (uint8_t rhport); +uint16_t videod_open (uint8_t rhport, tusb_desc_interface_t const * itf_desc, uint16_t max_len); +bool videod_control_xfer_cb(uint8_t rhport, uint8_t stage, tusb_control_request_t const * request); +bool videod_xfer_cb (uint8_t rhport, uint8_t ep_addr, xfer_result_t result, uint32_t xferred_bytes); + +#ifdef __cplusplus + } +#endif + +#endif diff --git a/cores/nRF5/TinyUSB/Adafruit_TinyUSB_ArduinoCore/tinyusb/src/common/sys_queue.h b/cores/nRF5/TinyUSB/Adafruit_TinyUSB_ArduinoCore/tinyusb/src/common/sys_queue.h deleted file mode 100644 index 443f01b..0000000 --- a/cores/nRF5/TinyUSB/Adafruit_TinyUSB_ArduinoCore/tinyusb/src/common/sys_queue.h +++ /dev/null @@ -1,871 +0,0 @@ -/*- - * SPDX-License-Identifier: BSD-3-Clause - * - * Copyright (c) 1991, 1993 - * The Regents of the University of California. All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions - * are met: - * 1. Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * 2. Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - * 3. Neither the name of the University nor the names of its contributors - * may be used to endorse or promote products derived from this software - * without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND - * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE - * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE - * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE - * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL - * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS - * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) - * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT - * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY - * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF - * SUCH DAMAGE. - * - * @(#)queue.h 8.5 (Berkeley) 8/20/94 - * $FreeBSD$ - */ - -#ifndef _SYS_QUEUE_H_ -#define _SYS_QUEUE_H_ - -#include - -/* - * This file defines four types of data structures: singly-linked lists, - * singly-linked tail queues, lists and tail queues. - * - * A singly-linked list is headed by a single forward pointer. The elements - * are singly linked for minimum space and pointer manipulation overhead at - * the expense of O(n) removal for arbitrary elements. New elements can be - * added to the list after an existing element or at the head of the list. - * Elements being removed from the head of the list should use the explicit - * macro for this purpose for optimum efficiency. A singly-linked list may - * only be traversed in the forward direction. Singly-linked lists are ideal - * for applications with large datasets and few or no removals or for - * implementing a LIFO queue. - * - * A singly-linked tail queue is headed by a pair of pointers, one to the - * head of the list and the other to the tail of the list. The elements are - * singly linked for minimum space and pointer manipulation overhead at the - * expense of O(n) removal for arbitrary elements. New elements can be added - * to the list after an existing element, at the head of the list, or at the - * end of the list. Elements being removed from the head of the tail queue - * should use the explicit macro for this purpose for optimum efficiency. - * A singly-linked tail queue may only be traversed in the forward direction. - * Singly-linked tail queues are ideal for applications with large datasets - * and few or no removals or for implementing a FIFO queue. - * - * A list is headed by a single forward pointer (or an array of forward - * pointers for a hash table header). The elements are doubly linked - * so that an arbitrary element can be removed without a need to - * traverse the list. New elements can be added to the list before - * or after an existing element or at the head of the list. A list - * may be traversed in either direction. - * - * A tail queue is headed by a pair of pointers, one to the head of the - * list and the other to the tail of the list. The elements are doubly - * linked so that an arbitrary element can be removed without a need to - * traverse the list. New elements can be added to the list before or - * after an existing element, at the head of the list, or at the end of - * the list. A tail queue may be traversed in either direction. - * - * For details on the use of these macros, see the queue(3) manual page. - * - * Below is a summary of implemented functions where: - * + means the macro is available - * - means the macro is not available - * s means the macro is available but is slow (runs in O(n) time) - * - * SLIST LIST STAILQ TAILQ - * _HEAD + + + + - * _CLASS_HEAD + + + + - * _HEAD_INITIALIZER + + + + - * _ENTRY + + + + - * _CLASS_ENTRY + + + + - * _INIT + + + + - * _EMPTY + + + + - * _FIRST + + + + - * _NEXT + + + + - * _PREV - + - + - * _LAST - - + + - * _LAST_FAST - - - + - * _FOREACH + + + + - * _FOREACH_FROM + + + + - * _FOREACH_SAFE + + + + - * _FOREACH_FROM_SAFE + + + + - * _FOREACH_REVERSE - - - + - * _FOREACH_REVERSE_FROM - - - + - * _FOREACH_REVERSE_SAFE - - - + - * _FOREACH_REVERSE_FROM_SAFE - - - + - * _INSERT_HEAD + + + + - * _INSERT_BEFORE - + - + - * _INSERT_AFTER + + + + - * _INSERT_TAIL - - + + - * _CONCAT s s + + - * _REMOVE_AFTER + - + - - * _REMOVE_HEAD + - + - - * _REMOVE s + s + - * _SWAP + + + + - * - */ -#ifdef QUEUE_MACRO_DEBUG -#warn Use QUEUE_MACRO_DEBUG_TRACE and/or QUEUE_MACRO_DEBUG_TRASH -#define QUEUE_MACRO_DEBUG_TRACE -#define QUEUE_MACRO_DEBUG_TRASH -#endif - -#ifdef QUEUE_MACRO_DEBUG_TRACE -/* Store the last 2 places the queue element or head was altered */ -struct qm_trace { - unsigned long lastline; - unsigned long prevline; - const char *lastfile; - const char *prevfile; -}; - -#define TRACEBUF struct qm_trace trace; -#define TRACEBUF_INITIALIZER { __LINE__, 0, __FILE__, NULL } , - -#define QMD_TRACE_HEAD(head) do { \ - (head)->trace.prevline = (head)->trace.lastline; \ - (head)->trace.prevfile = (head)->trace.lastfile; \ - (head)->trace.lastline = __LINE__; \ - (head)->trace.lastfile = __FILE__; \ -} while (0) - -#define QMD_TRACE_ELEM(elem) do { \ - (elem)->trace.prevline = (elem)->trace.lastline; \ - (elem)->trace.prevfile = (elem)->trace.lastfile; \ - (elem)->trace.lastline = __LINE__; \ - (elem)->trace.lastfile = __FILE__; \ -} while (0) - -#else /* !QUEUE_MACRO_DEBUG_TRACE */ -#define QMD_TRACE_ELEM(elem) -#define QMD_TRACE_HEAD(head) -#define TRACEBUF -#define TRACEBUF_INITIALIZER -#endif /* QUEUE_MACRO_DEBUG_TRACE */ - -#ifdef QUEUE_MACRO_DEBUG_TRASH -#define TRASHIT(x) do {(x) = (void *)-1;} while (0) -#define QMD_IS_TRASHED(x) ((x) == (void *)(intptr_t)-1) -#else /* !QUEUE_MACRO_DEBUG_TRASH */ -#define TRASHIT(x) -#define QMD_IS_TRASHED(x) 0 -#endif /* QUEUE_MACRO_DEBUG_TRASH */ - -#if defined(QUEUE_MACRO_DEBUG_TRACE) || defined(QUEUE_MACRO_DEBUG_TRASH) -#define QMD_SAVELINK(name, link) void **name = (void *)&(link) -#else /* !QUEUE_MACRO_DEBUG_TRACE && !QUEUE_MACRO_DEBUG_TRASH */ -#define QMD_SAVELINK(name, link) -#endif /* QUEUE_MACRO_DEBUG_TRACE || QUEUE_MACRO_DEBUG_TRASH */ - -#ifdef __cplusplus -/* - * In C++ there can be structure lists and class lists: - */ -#define QUEUE_TYPEOF(type) type -#else -#define QUEUE_TYPEOF(type) struct type -#endif - -/* - * Singly-linked List declarations. - */ -#define SLIST_HEAD(name, type) \ -struct name { \ - struct type *slh_first; /* first element */ \ -} - -#define SLIST_CLASS_HEAD(name, type) \ -struct name { \ - class type *slh_first; /* first element */ \ -} - -#define SLIST_HEAD_INITIALIZER(head) \ - { NULL } - -#define SLIST_ENTRY(type) \ -struct { \ - struct type *sle_next; /* next element */ \ -} - -#define SLIST_CLASS_ENTRY(type) \ -struct { \ - class type *sle_next; /* next element */ \ -} - -/* - * Singly-linked List functions. - */ -#if (defined(_KERNEL) && defined(INVARIANTS)) -#define QMD_SLIST_CHECK_PREVPTR(prevp, elm) do { \ - if (*(prevp) != (elm)) \ - panic("Bad prevptr *(%p) == %p != %p", \ - (prevp), *(prevp), (elm)); \ -} while (0) -#else -#define QMD_SLIST_CHECK_PREVPTR(prevp, elm) -#endif - -#define SLIST_CONCAT(head1, head2, type, field) do { \ - QUEUE_TYPEOF(type) *curelm = SLIST_FIRST(head1); \ - if (curelm == NULL) { \ - if ((SLIST_FIRST(head1) = SLIST_FIRST(head2)) != NULL) \ - SLIST_INIT(head2); \ - } else if (SLIST_FIRST(head2) != NULL) { \ - while (SLIST_NEXT(curelm, field) != NULL) \ - curelm = SLIST_NEXT(curelm, field); \ - SLIST_NEXT(curelm, field) = SLIST_FIRST(head2); \ - SLIST_INIT(head2); \ - } \ -} while (0) - -#define SLIST_EMPTY(head) ((head)->slh_first == NULL) - -#define SLIST_FIRST(head) ((head)->slh_first) - -#define SLIST_FOREACH(var, head, field) \ - for ((var) = SLIST_FIRST((head)); \ - (var); \ - (var) = SLIST_NEXT((var), field)) - -#define SLIST_FOREACH_FROM(var, head, field) \ - for ((var) = ((var) ? (var) : SLIST_FIRST((head))); \ - (var); \ - (var) = SLIST_NEXT((var), field)) - -#define SLIST_FOREACH_SAFE(var, head, field, tvar) \ - for ((var) = SLIST_FIRST((head)); \ - (var) && ((tvar) = SLIST_NEXT((var), field), 1); \ - (var) = (tvar)) - -#define SLIST_FOREACH_FROM_SAFE(var, head, field, tvar) \ - for ((var) = ((var) ? (var) : SLIST_FIRST((head))); \ - (var) && ((tvar) = SLIST_NEXT((var), field), 1); \ - (var) = (tvar)) - -#define SLIST_FOREACH_PREVPTR(var, varp, head, field) \ - for ((varp) = &SLIST_FIRST((head)); \ - ((var) = *(varp)) != NULL; \ - (varp) = &SLIST_NEXT((var), field)) - -#define SLIST_INIT(head) do { \ - SLIST_FIRST((head)) = NULL; \ -} while (0) - -#define SLIST_INSERT_AFTER(slistelm, elm, field) do { \ - SLIST_NEXT((elm), field) = SLIST_NEXT((slistelm), field); \ - SLIST_NEXT((slistelm), field) = (elm); \ -} while (0) - -#define SLIST_INSERT_HEAD(head, elm, field) do { \ - SLIST_NEXT((elm), field) = SLIST_FIRST((head)); \ - SLIST_FIRST((head)) = (elm); \ -} while (0) - -#define SLIST_NEXT(elm, field) ((elm)->field.sle_next) - -#define SLIST_REMOVE(head, elm, type, field) do { \ - QMD_SAVELINK(oldnext, (elm)->field.sle_next); \ - if (SLIST_FIRST((head)) == (elm)) { \ - SLIST_REMOVE_HEAD((head), field); \ - } \ - else { \ - QUEUE_TYPEOF(type) *curelm = SLIST_FIRST(head); \ - while (SLIST_NEXT(curelm, field) != (elm)) \ - curelm = SLIST_NEXT(curelm, field); \ - SLIST_REMOVE_AFTER(curelm, field); \ - } \ - TRASHIT(*oldnext); \ -} while (0) - -#define SLIST_REMOVE_AFTER(elm, field) do { \ - SLIST_NEXT(elm, field) = \ - SLIST_NEXT(SLIST_NEXT(elm, field), field); \ -} while (0) - -#define SLIST_REMOVE_HEAD(head, field) do { \ - SLIST_FIRST((head)) = SLIST_NEXT(SLIST_FIRST((head)), field); \ -} while (0) - -#define SLIST_REMOVE_PREVPTR(prevp, elm, field) do { \ - QMD_SLIST_CHECK_PREVPTR(prevp, elm); \ - *(prevp) = SLIST_NEXT(elm, field); \ - TRASHIT((elm)->field.sle_next); \ -} while (0) - -#define SLIST_SWAP(head1, head2, type) do { \ - QUEUE_TYPEOF(type) *swap_first = SLIST_FIRST(head1); \ - SLIST_FIRST(head1) = SLIST_FIRST(head2); \ - SLIST_FIRST(head2) = swap_first; \ -} while (0) - -/* - * Singly-linked Tail queue declarations. - */ -#define STAILQ_HEAD(name, type) \ -struct name { \ - struct type *stqh_first;/* first element */ \ - struct type **stqh_last;/* addr of last next element */ \ -} - -#define STAILQ_CLASS_HEAD(name, type) \ -struct name { \ - class type *stqh_first; /* first element */ \ - class type **stqh_last; /* addr of last next element */ \ -} - -#define STAILQ_HEAD_INITIALIZER(head) \ - { NULL, &(head).stqh_first } - -#define STAILQ_ENTRY(type) \ -struct { \ - struct type *stqe_next; /* next element */ \ -} - -#define STAILQ_CLASS_ENTRY(type) \ -struct { \ - class type *stqe_next; /* next element */ \ -} - -/* - * Singly-linked Tail queue functions. - */ -#define STAILQ_CONCAT(head1, head2) do { \ - if (!STAILQ_EMPTY((head2))) { \ - *(head1)->stqh_last = (head2)->stqh_first; \ - (head1)->stqh_last = (head2)->stqh_last; \ - STAILQ_INIT((head2)); \ - } \ -} while (0) - -#define STAILQ_EMPTY(head) ((head)->stqh_first == NULL) - -#define STAILQ_FIRST(head) ((head)->stqh_first) - -#define STAILQ_FOREACH(var, head, field) \ - for((var) = STAILQ_FIRST((head)); \ - (var); \ - (var) = STAILQ_NEXT((var), field)) - -#define STAILQ_FOREACH_FROM(var, head, field) \ - for ((var) = ((var) ? (var) : STAILQ_FIRST((head))); \ - (var); \ - (var) = STAILQ_NEXT((var), field)) - -#define STAILQ_FOREACH_SAFE(var, head, field, tvar) \ - for ((var) = STAILQ_FIRST((head)); \ - (var) && ((tvar) = STAILQ_NEXT((var), field), 1); \ - (var) = (tvar)) - -#define STAILQ_FOREACH_FROM_SAFE(var, head, field, tvar) \ - for ((var) = ((var) ? (var) : STAILQ_FIRST((head))); \ - (var) && ((tvar) = STAILQ_NEXT((var), field), 1); \ - (var) = (tvar)) - -#define STAILQ_INIT(head) do { \ - STAILQ_FIRST((head)) = NULL; \ - (head)->stqh_last = &STAILQ_FIRST((head)); \ -} while (0) - -#define STAILQ_INSERT_AFTER(head, tqelm, elm, field) do { \ - if ((STAILQ_NEXT((elm), field) = STAILQ_NEXT((tqelm), field)) == NULL)\ - (head)->stqh_last = &STAILQ_NEXT((elm), field); \ - STAILQ_NEXT((tqelm), field) = (elm); \ -} while (0) - -#define STAILQ_INSERT_HEAD(head, elm, field) do { \ - if ((STAILQ_NEXT((elm), field) = STAILQ_FIRST((head))) == NULL) \ - (head)->stqh_last = &STAILQ_NEXT((elm), field); \ - STAILQ_FIRST((head)) = (elm); \ -} while (0) - -#define STAILQ_INSERT_TAIL(head, elm, field) do { \ - STAILQ_NEXT((elm), field) = NULL; \ - *(head)->stqh_last = (elm); \ - (head)->stqh_last = &STAILQ_NEXT((elm), field); \ -} while (0) - -#define STAILQ_LAST(head, type, field) \ - (STAILQ_EMPTY((head)) ? NULL : \ - __containerof((head)->stqh_last, \ - QUEUE_TYPEOF(type), field.stqe_next)) - -#define STAILQ_NEXT(elm, field) ((elm)->field.stqe_next) - -#define STAILQ_REMOVE(head, elm, type, field) do { \ - QMD_SAVELINK(oldnext, (elm)->field.stqe_next); \ - if (STAILQ_FIRST((head)) == (elm)) { \ - STAILQ_REMOVE_HEAD((head), field); \ - } \ - else { \ - QUEUE_TYPEOF(type) *curelm = STAILQ_FIRST(head); \ - while (STAILQ_NEXT(curelm, field) != (elm)) \ - curelm = STAILQ_NEXT(curelm, field); \ - STAILQ_REMOVE_AFTER(head, curelm, field); \ - } \ - TRASHIT(*oldnext); \ -} while (0) - -#define STAILQ_REMOVE_AFTER(head, elm, field) do { \ - if ((STAILQ_NEXT(elm, field) = \ - STAILQ_NEXT(STAILQ_NEXT(elm, field), field)) == NULL) \ - (head)->stqh_last = &STAILQ_NEXT((elm), field); \ -} while (0) - -#define STAILQ_REMOVE_HEAD(head, field) do { \ - if ((STAILQ_FIRST((head)) = \ - STAILQ_NEXT(STAILQ_FIRST((head)), field)) == NULL) \ - (head)->stqh_last = &STAILQ_FIRST((head)); \ -} while (0) - -#define STAILQ_SWAP(head1, head2, type) do { \ - QUEUE_TYPEOF(type) *swap_first = STAILQ_FIRST(head1); \ - QUEUE_TYPEOF(type) **swap_last = (head1)->stqh_last; \ - STAILQ_FIRST(head1) = STAILQ_FIRST(head2); \ - (head1)->stqh_last = (head2)->stqh_last; \ - STAILQ_FIRST(head2) = swap_first; \ - (head2)->stqh_last = swap_last; \ - if (STAILQ_EMPTY(head1)) \ - (head1)->stqh_last = &STAILQ_FIRST(head1); \ - if (STAILQ_EMPTY(head2)) \ - (head2)->stqh_last = &STAILQ_FIRST(head2); \ -} while (0) - - -/* - * List declarations. - */ -#define LIST_HEAD(name, type) \ -struct name { \ - struct type *lh_first; /* first element */ \ -} - -#define LIST_CLASS_HEAD(name, type) \ -struct name { \ - class type *lh_first; /* first element */ \ -} - -#define LIST_HEAD_INITIALIZER(head) \ - { NULL } - -#define LIST_ENTRY(type) \ -struct { \ - struct type *le_next; /* next element */ \ - struct type **le_prev; /* address of previous next element */ \ -} - -#define LIST_CLASS_ENTRY(type) \ -struct { \ - class type *le_next; /* next element */ \ - class type **le_prev; /* address of previous next element */ \ -} - -/* - * List functions. - */ - -#if (defined(_KERNEL) && defined(INVARIANTS)) -/* - * QMD_LIST_CHECK_HEAD(LIST_HEAD *head, LIST_ENTRY NAME) - * - * If the list is non-empty, validates that the first element of the list - * points back at 'head.' - */ -#define QMD_LIST_CHECK_HEAD(head, field) do { \ - if (LIST_FIRST((head)) != NULL && \ - LIST_FIRST((head))->field.le_prev != \ - &LIST_FIRST((head))) \ - panic("Bad list head %p first->prev != head", (head)); \ -} while (0) - -/* - * QMD_LIST_CHECK_NEXT(TYPE *elm, LIST_ENTRY NAME) - * - * If an element follows 'elm' in the list, validates that the next element - * points back at 'elm.' - */ -#define QMD_LIST_CHECK_NEXT(elm, field) do { \ - if (LIST_NEXT((elm), field) != NULL && \ - LIST_NEXT((elm), field)->field.le_prev != \ - &((elm)->field.le_next)) \ - panic("Bad link elm %p next->prev != elm", (elm)); \ -} while (0) - -/* - * QMD_LIST_CHECK_PREV(TYPE *elm, LIST_ENTRY NAME) - * - * Validates that the previous element (or head of the list) points to 'elm.' - */ -#define QMD_LIST_CHECK_PREV(elm, field) do { \ - if (*(elm)->field.le_prev != (elm)) \ - panic("Bad link elm %p prev->next != elm", (elm)); \ -} while (0) -#else -#define QMD_LIST_CHECK_HEAD(head, field) -#define QMD_LIST_CHECK_NEXT(elm, field) -#define QMD_LIST_CHECK_PREV(elm, field) -#endif /* (_KERNEL && INVARIANTS) */ - -#define LIST_CONCAT(head1, head2, type, field) do { \ - QUEUE_TYPEOF(type) *curelm = LIST_FIRST(head1); \ - if (curelm == NULL) { \ - if ((LIST_FIRST(head1) = LIST_FIRST(head2)) != NULL) { \ - LIST_FIRST(head2)->field.le_prev = \ - &LIST_FIRST((head1)); \ - LIST_INIT(head2); \ - } \ - } else if (LIST_FIRST(head2) != NULL) { \ - while (LIST_NEXT(curelm, field) != NULL) \ - curelm = LIST_NEXT(curelm, field); \ - LIST_NEXT(curelm, field) = LIST_FIRST(head2); \ - LIST_FIRST(head2)->field.le_prev = &LIST_NEXT(curelm, field); \ - LIST_INIT(head2); \ - } \ -} while (0) - -#define LIST_EMPTY(head) ((head)->lh_first == NULL) - -#define LIST_FIRST(head) ((head)->lh_first) - -#define LIST_FOREACH(var, head, field) \ - for ((var) = LIST_FIRST((head)); \ - (var); \ - (var) = LIST_NEXT((var), field)) - -#define LIST_FOREACH_FROM(var, head, field) \ - for ((var) = ((var) ? (var) : LIST_FIRST((head))); \ - (var); \ - (var) = LIST_NEXT((var), field)) - -#define LIST_FOREACH_SAFE(var, head, field, tvar) \ - for ((var) = LIST_FIRST((head)); \ - (var) && ((tvar) = LIST_NEXT((var), field), 1); \ - (var) = (tvar)) - -#define LIST_FOREACH_FROM_SAFE(var, head, field, tvar) \ - for ((var) = ((var) ? (var) : LIST_FIRST((head))); \ - (var) && ((tvar) = LIST_NEXT((var), field), 1); \ - (var) = (tvar)) - -#define LIST_INIT(head) do { \ - LIST_FIRST((head)) = NULL; \ -} while (0) - -#define LIST_INSERT_AFTER(listelm, elm, field) do { \ - QMD_LIST_CHECK_NEXT(listelm, field); \ - if ((LIST_NEXT((elm), field) = LIST_NEXT((listelm), field)) != NULL)\ - LIST_NEXT((listelm), field)->field.le_prev = \ - &LIST_NEXT((elm), field); \ - LIST_NEXT((listelm), field) = (elm); \ - (elm)->field.le_prev = &LIST_NEXT((listelm), field); \ -} while (0) - -#define LIST_INSERT_BEFORE(listelm, elm, field) do { \ - QMD_LIST_CHECK_PREV(listelm, field); \ - (elm)->field.le_prev = (listelm)->field.le_prev; \ - LIST_NEXT((elm), field) = (listelm); \ - *(listelm)->field.le_prev = (elm); \ - (listelm)->field.le_prev = &LIST_NEXT((elm), field); \ -} while (0) - -#define LIST_INSERT_HEAD(head, elm, field) do { \ - QMD_LIST_CHECK_HEAD((head), field); \ - if ((LIST_NEXT((elm), field) = LIST_FIRST((head))) != NULL) \ - LIST_FIRST((head))->field.le_prev = &LIST_NEXT((elm), field);\ - LIST_FIRST((head)) = (elm); \ - (elm)->field.le_prev = &LIST_FIRST((head)); \ -} while (0) - -#define LIST_NEXT(elm, field) ((elm)->field.le_next) - -#define LIST_PREV(elm, head, type, field) \ - ((elm)->field.le_prev == &LIST_FIRST((head)) ? NULL : \ - __containerof((elm)->field.le_prev, \ - QUEUE_TYPEOF(type), field.le_next)) - -#define LIST_REMOVE(elm, field) do { \ - QMD_SAVELINK(oldnext, (elm)->field.le_next); \ - QMD_SAVELINK(oldprev, (elm)->field.le_prev); \ - QMD_LIST_CHECK_NEXT(elm, field); \ - QMD_LIST_CHECK_PREV(elm, field); \ - if (LIST_NEXT((elm), field) != NULL) \ - LIST_NEXT((elm), field)->field.le_prev = \ - (elm)->field.le_prev; \ - *(elm)->field.le_prev = LIST_NEXT((elm), field); \ - TRASHIT(*oldnext); \ - TRASHIT(*oldprev); \ -} while (0) - -#define LIST_SWAP(head1, head2, type, field) do { \ - QUEUE_TYPEOF(type) *swap_tmp = LIST_FIRST(head1); \ - LIST_FIRST((head1)) = LIST_FIRST((head2)); \ - LIST_FIRST((head2)) = swap_tmp; \ - if ((swap_tmp = LIST_FIRST((head1))) != NULL) \ - swap_tmp->field.le_prev = &LIST_FIRST((head1)); \ - if ((swap_tmp = LIST_FIRST((head2))) != NULL) \ - swap_tmp->field.le_prev = &LIST_FIRST((head2)); \ -} while (0) - -/* - * Tail queue declarations. - */ -#define TAILQ_HEAD(name, type) \ -struct name { \ - struct type *tqh_first; /* first element */ \ - struct type **tqh_last; /* addr of last next element */ \ - TRACEBUF \ -} - -#define TAILQ_CLASS_HEAD(name, type) \ -struct name { \ - class type *tqh_first; /* first element */ \ - class type **tqh_last; /* addr of last next element */ \ - TRACEBUF \ -} - -#define TAILQ_HEAD_INITIALIZER(head) \ - { NULL, &(head).tqh_first, TRACEBUF_INITIALIZER } - -#define TAILQ_ENTRY(type) \ -struct { \ - struct type *tqe_next; /* next element */ \ - struct type **tqe_prev; /* address of previous next element */ \ - TRACEBUF \ -} - -#define TAILQ_CLASS_ENTRY(type) \ -struct { \ - class type *tqe_next; /* next element */ \ - class type **tqe_prev; /* address of previous next element */ \ - TRACEBUF \ -} - -/* - * Tail queue functions. - */ -#if (defined(_KERNEL) && defined(INVARIANTS)) -/* - * QMD_TAILQ_CHECK_HEAD(TAILQ_HEAD *head, TAILQ_ENTRY NAME) - * - * If the tailq is non-empty, validates that the first element of the tailq - * points back at 'head.' - */ -#define QMD_TAILQ_CHECK_HEAD(head, field) do { \ - if (!TAILQ_EMPTY(head) && \ - TAILQ_FIRST((head))->field.tqe_prev != \ - &TAILQ_FIRST((head))) \ - panic("Bad tailq head %p first->prev != head", (head)); \ -} while (0) - -/* - * QMD_TAILQ_CHECK_TAIL(TAILQ_HEAD *head, TAILQ_ENTRY NAME) - * - * Validates that the tail of the tailq is a pointer to pointer to NULL. - */ -#define QMD_TAILQ_CHECK_TAIL(head, field) do { \ - if (*(head)->tqh_last != NULL) \ - panic("Bad tailq NEXT(%p->tqh_last) != NULL", (head)); \ -} while (0) - -/* - * QMD_TAILQ_CHECK_NEXT(TYPE *elm, TAILQ_ENTRY NAME) - * - * If an element follows 'elm' in the tailq, validates that the next element - * points back at 'elm.' - */ -#define QMD_TAILQ_CHECK_NEXT(elm, field) do { \ - if (TAILQ_NEXT((elm), field) != NULL && \ - TAILQ_NEXT((elm), field)->field.tqe_prev != \ - &((elm)->field.tqe_next)) \ - panic("Bad link elm %p next->prev != elm", (elm)); \ -} while (0) - -/* - * QMD_TAILQ_CHECK_PREV(TYPE *elm, TAILQ_ENTRY NAME) - * - * Validates that the previous element (or head of the tailq) points to 'elm.' - */ -#define QMD_TAILQ_CHECK_PREV(elm, field) do { \ - if (*(elm)->field.tqe_prev != (elm)) \ - panic("Bad link elm %p prev->next != elm", (elm)); \ -} while (0) -#else -#define QMD_TAILQ_CHECK_HEAD(head, field) -#define QMD_TAILQ_CHECK_TAIL(head, headname) -#define QMD_TAILQ_CHECK_NEXT(elm, field) -#define QMD_TAILQ_CHECK_PREV(elm, field) -#endif /* (_KERNEL && INVARIANTS) */ - -#define TAILQ_CONCAT(head1, head2, field) do { \ - if (!TAILQ_EMPTY(head2)) { \ - *(head1)->tqh_last = (head2)->tqh_first; \ - (head2)->tqh_first->field.tqe_prev = (head1)->tqh_last; \ - (head1)->tqh_last = (head2)->tqh_last; \ - TAILQ_INIT((head2)); \ - QMD_TRACE_HEAD(head1); \ - QMD_TRACE_HEAD(head2); \ - } \ -} while (0) - -#define TAILQ_EMPTY(head) ((head)->tqh_first == NULL) - -#define TAILQ_FIRST(head) ((head)->tqh_first) - -#define TAILQ_FOREACH(var, head, field) \ - for ((var) = TAILQ_FIRST((head)); \ - (var); \ - (var) = TAILQ_NEXT((var), field)) - -#define TAILQ_FOREACH_FROM(var, head, field) \ - for ((var) = ((var) ? (var) : TAILQ_FIRST((head))); \ - (var); \ - (var) = TAILQ_NEXT((var), field)) - -#define TAILQ_FOREACH_SAFE(var, head, field, tvar) \ - for ((var) = TAILQ_FIRST((head)); \ - (var) && ((tvar) = TAILQ_NEXT((var), field), 1); \ - (var) = (tvar)) - -#define TAILQ_FOREACH_FROM_SAFE(var, head, field, tvar) \ - for ((var) = ((var) ? (var) : TAILQ_FIRST((head))); \ - (var) && ((tvar) = TAILQ_NEXT((var), field), 1); \ - (var) = (tvar)) - -#define TAILQ_FOREACH_REVERSE(var, head, headname, field) \ - for ((var) = TAILQ_LAST((head), headname); \ - (var); \ - (var) = TAILQ_PREV((var), headname, field)) - -#define TAILQ_FOREACH_REVERSE_FROM(var, head, headname, field) \ - for ((var) = ((var) ? (var) : TAILQ_LAST((head), headname)); \ - (var); \ - (var) = TAILQ_PREV((var), headname, field)) - -#define TAILQ_FOREACH_REVERSE_SAFE(var, head, headname, field, tvar) \ - for ((var) = TAILQ_LAST((head), headname); \ - (var) && ((tvar) = TAILQ_PREV((var), headname, field), 1); \ - (var) = (tvar)) - -#define TAILQ_FOREACH_REVERSE_FROM_SAFE(var, head, headname, field, tvar) \ - for ((var) = ((var) ? (var) : TAILQ_LAST((head), headname)); \ - (var) && ((tvar) = TAILQ_PREV((var), headname, field), 1); \ - (var) = (tvar)) - -#define TAILQ_INIT(head) do { \ - TAILQ_FIRST((head)) = NULL; \ - (head)->tqh_last = &TAILQ_FIRST((head)); \ - QMD_TRACE_HEAD(head); \ -} while (0) - -#define TAILQ_INSERT_AFTER(head, listelm, elm, field) do { \ - QMD_TAILQ_CHECK_NEXT(listelm, field); \ - if ((TAILQ_NEXT((elm), field) = TAILQ_NEXT((listelm), field)) != NULL)\ - TAILQ_NEXT((elm), field)->field.tqe_prev = \ - &TAILQ_NEXT((elm), field); \ - else { \ - (head)->tqh_last = &TAILQ_NEXT((elm), field); \ - QMD_TRACE_HEAD(head); \ - } \ - TAILQ_NEXT((listelm), field) = (elm); \ - (elm)->field.tqe_prev = &TAILQ_NEXT((listelm), field); \ - QMD_TRACE_ELEM(&(elm)->field); \ - QMD_TRACE_ELEM(&(listelm)->field); \ -} while (0) - -#define TAILQ_INSERT_BEFORE(listelm, elm, field) do { \ - QMD_TAILQ_CHECK_PREV(listelm, field); \ - (elm)->field.tqe_prev = (listelm)->field.tqe_prev; \ - TAILQ_NEXT((elm), field) = (listelm); \ - *(listelm)->field.tqe_prev = (elm); \ - (listelm)->field.tqe_prev = &TAILQ_NEXT((elm), field); \ - QMD_TRACE_ELEM(&(elm)->field); \ - QMD_TRACE_ELEM(&(listelm)->field); \ -} while (0) - -#define TAILQ_INSERT_HEAD(head, elm, field) do { \ - QMD_TAILQ_CHECK_HEAD(head, field); \ - if ((TAILQ_NEXT((elm), field) = TAILQ_FIRST((head))) != NULL) \ - TAILQ_FIRST((head))->field.tqe_prev = \ - &TAILQ_NEXT((elm), field); \ - else \ - (head)->tqh_last = &TAILQ_NEXT((elm), field); \ - TAILQ_FIRST((head)) = (elm); \ - (elm)->field.tqe_prev = &TAILQ_FIRST((head)); \ - QMD_TRACE_HEAD(head); \ - QMD_TRACE_ELEM(&(elm)->field); \ -} while (0) - -#define TAILQ_INSERT_TAIL(head, elm, field) do { \ - QMD_TAILQ_CHECK_TAIL(head, field); \ - TAILQ_NEXT((elm), field) = NULL; \ - (elm)->field.tqe_prev = (head)->tqh_last; \ - *(head)->tqh_last = (elm); \ - (head)->tqh_last = &TAILQ_NEXT((elm), field); \ - QMD_TRACE_HEAD(head); \ - QMD_TRACE_ELEM(&(elm)->field); \ -} while (0) - -#define TAILQ_LAST(head, headname) \ - (*(((struct headname *)((head)->tqh_last))->tqh_last)) - -/* - * The FAST function is fast in that it causes no data access other - * then the access to the head. The standard LAST function above - * will cause a data access of both the element you want and - * the previous element. FAST is very useful for instances when - * you may want to prefetch the last data element. - */ -#define TAILQ_LAST_FAST(head, type, field) \ - (TAILQ_EMPTY(head) ? NULL : __containerof((head)->tqh_last, QUEUE_TYPEOF(type), field.tqe_next)) - -#define TAILQ_NEXT(elm, field) ((elm)->field.tqe_next) - -#define TAILQ_PREV(elm, headname, field) \ - (*(((struct headname *)((elm)->field.tqe_prev))->tqh_last)) - -#define TAILQ_REMOVE(head, elm, field) do { \ - QMD_SAVELINK(oldnext, (elm)->field.tqe_next); \ - QMD_SAVELINK(oldprev, (elm)->field.tqe_prev); \ - QMD_TAILQ_CHECK_NEXT(elm, field); \ - QMD_TAILQ_CHECK_PREV(elm, field); \ - if ((TAILQ_NEXT((elm), field)) != NULL) \ - TAILQ_NEXT((elm), field)->field.tqe_prev = \ - (elm)->field.tqe_prev; \ - else { \ - (head)->tqh_last = (elm)->field.tqe_prev; \ - QMD_TRACE_HEAD(head); \ - } \ - *(elm)->field.tqe_prev = TAILQ_NEXT((elm), field); \ - TRASHIT(*oldnext); \ - TRASHIT(*oldprev); \ - QMD_TRACE_ELEM(&(elm)->field); \ -} while (0) - -#define TAILQ_SWAP(head1, head2, type, field) do { \ - QUEUE_TYPEOF(type) *swap_first = (head1)->tqh_first; \ - QUEUE_TYPEOF(type) **swap_last = (head1)->tqh_last; \ - (head1)->tqh_first = (head2)->tqh_first; \ - (head1)->tqh_last = (head2)->tqh_last; \ - (head2)->tqh_first = swap_first; \ - (head2)->tqh_last = swap_last; \ - if ((swap_first = (head1)->tqh_first) != NULL) \ - swap_first->field.tqe_prev = &(head1)->tqh_first; \ - else \ - (head1)->tqh_last = &(head1)->tqh_first; \ - if ((swap_first = (head2)->tqh_first) != NULL) \ - swap_first->field.tqe_prev = &(head2)->tqh_first; \ - else \ - (head2)->tqh_last = &(head2)->tqh_first; \ -} while (0) - -#endif /* !_SYS_QUEUE_H_ */ diff --git a/cores/nRF5/TinyUSB/Adafruit_TinyUSB_ArduinoCore/tinyusb/src/common/tusb_common.h b/cores/nRF5/TinyUSB/Adafruit_TinyUSB_ArduinoCore/tinyusb/src/common/tusb_common.h index d95c0ff..2b095e2 100644 --- a/cores/nRF5/TinyUSB/Adafruit_TinyUSB_ArduinoCore/tinyusb/src/common/tusb_common.h +++ b/cores/nRF5/TinyUSB/Adafruit_TinyUSB_ArduinoCore/tinyusb/src/common/tusb_common.h @@ -1,4 +1,4 @@ -/* +/* * The MIT License (MIT) * * Copyright (c) 2019 Ha Thach (tinyusb.org) @@ -24,10 +24,6 @@ * This file is part of the TinyUSB stack. */ -/** \ingroup Group_Common - * \defgroup Group_CommonH common.h - * @{ */ - #ifndef _TUSB_COMMON_H_ #define _TUSB_COMMON_H_ @@ -39,23 +35,29 @@ // Macros Helper //--------------------------------------------------------------------+ #define TU_ARRAY_SIZE(_arr) ( sizeof(_arr) / sizeof(_arr[0]) ) +#define TU_FIELD_SIZE(_type, _field) (sizeof(((_type *)0)->_field)) #define TU_MIN(_x, _y) ( ( (_x) < (_y) ) ? (_x) : (_y) ) #define TU_MAX(_x, _y) ( ( (_x) > (_y) ) ? (_x) : (_y) ) +#define TU_DIV_CEIL(n, d) (((n) + (d) - 1) / (d)) + +#define TU_U16(_high, _low) ((uint16_t) (((_high) << 8) | (_low))) +#define TU_U16_HIGH(_u16) ((uint8_t) (((_u16) >> 8) & 0x00ff)) +#define TU_U16_LOW(_u16) ((uint8_t) ((_u16) & 0x00ff)) +#define U16_TO_U8S_BE(_u16) TU_U16_HIGH(_u16), TU_U16_LOW(_u16) +#define U16_TO_U8S_LE(_u16) TU_U16_LOW(_u16), TU_U16_HIGH(_u16) -#define TU_U16_HIGH(u16) ((uint8_t) (((u16) >> 8) & 0x00ff)) -#define TU_U16_LOW(u16) ((uint8_t) ((u16) & 0x00ff)) -#define U16_TO_U8S_BE(u16) TU_U16_HIGH(u16), TU_U16_LOW(u16) -#define U16_TO_U8S_LE(u16) TU_U16_LOW(u16), TU_U16_HIGH(u16) +#define TU_U32_BYTE3(_u32) ((uint8_t) ((((uint32_t) _u32) >> 24) & 0x000000ff)) // MSB +#define TU_U32_BYTE2(_u32) ((uint8_t) ((((uint32_t) _u32) >> 16) & 0x000000ff)) +#define TU_U32_BYTE1(_u32) ((uint8_t) ((((uint32_t) _u32) >> 8) & 0x000000ff)) +#define TU_U32_BYTE0(_u32) ((uint8_t) (((uint32_t) _u32) & 0x000000ff)) // LSB -#define U32_B1_U8(u32) ((uint8_t) (((u32) >> 24) & 0x000000ff)) // MSB -#define U32_B2_U8(u32) ((uint8_t) (((u32) >> 16) & 0x000000ff)) -#define U32_B3_U8(u32) ((uint8_t) (((u32) >> 8) & 0x000000ff)) -#define U32_B4_U8(u32) ((uint8_t) ((u32) & 0x000000ff)) // LSB +#define U32_TO_U8S_BE(_u32) TU_U32_BYTE3(_u32), TU_U32_BYTE2(_u32), TU_U32_BYTE1(_u32), TU_U32_BYTE0(_u32) +#define U32_TO_U8S_LE(_u32) TU_U32_BYTE0(_u32), TU_U32_BYTE1(_u32), TU_U32_BYTE2(_u32), TU_U32_BYTE3(_u32) -#define U32_TO_U8S_BE(u32) U32_B1_U8(u32), U32_B2_U8(u32), U32_B3_U8(u32), U32_B4_U8(u32) -#define U32_TO_U8S_LE(u32) U32_B4_U8(u32), U32_B3_U8(u32), U32_B2_U8(u32), U32_B1_U8(u32) +#define TU_BIT(n) (1UL << (n)) -#define TU_BIT(n) (1U << (n)) +// Generate a mask with bit from high (31) to low (0) set, e.g TU_GENMASK(3, 0) = 0b1111 +#define TU_GENMASK(h, l) ( (UINT32_MAX << (l)) & (UINT32_MAX >> (31 - (h))) ) //--------------------------------------------------------------------+ // Includes @@ -64,6 +66,7 @@ // Standard Headers #include #include +#include #include #include #include @@ -72,106 +75,231 @@ #include "tusb_option.h" #include "tusb_compiler.h" #include "tusb_verify.h" -#include "tusb_error.h" // TODO remove -#include "tusb_timeout.h" #include "tusb_types.h" +#include "tusb_debug.h" + +//--------------------------------------------------------------------+ +// API implemented by application if needed +// TODO move to a more obvious place/file +//--------------------------------------------------------------------+ + +// Get current milliseconds, required by some port/configuration without RTOS +extern uint32_t tusb_time_millis_api(void); + +// Delay in milliseconds, use tusb_time_millis_api() by default. required by some port/configuration with no RTOS +extern void tusb_time_delay_ms_api(uint32_t ms); + +// flush data cache +extern void tusb_app_dcache_flush(uintptr_t addr, uint32_t data_size); + +// invalidate data cache +extern void tusb_app_dcache_invalidate(uintptr_t addr, uint32_t data_size); + +// Optional physical <-> virtual address translation +extern void* tusb_app_virt_to_phys(void *virt_addr); +extern void* tusb_app_phys_to_virt(void *phys_addr); //--------------------------------------------------------------------+ -// Inline Functions +// Internal Inline Functions //--------------------------------------------------------------------+ + +//------------- Mem -------------// #define tu_memclr(buffer, size) memset((buffer), 0, (size)) #define tu_varclr(_var) tu_memclr(_var, sizeof(*(_var))) -static inline uint32_t tu_u32(uint8_t b1, uint8_t b2, uint8_t b3, uint8_t b4) -{ - return ( ((uint32_t) b1) << 24) + ( ((uint32_t) b2) << 16) + ( ((uint32_t) b3) << 8) + b4; +// This is a backport of memset_s from c11 +TU_ATTR_ALWAYS_INLINE static inline int tu_memset_s(void *dest, size_t destsz, int ch, size_t count) { + // TODO may check if desst and src is not NULL + if ( count > destsz ) { + return -1; + } + memset(dest, ch, count); + return 0; } -static inline uint16_t tu_u16(uint8_t high, uint8_t low) -{ - return (uint16_t)((((uint16_t) high) << 8) + low); +// This is a backport of memcpy_s from c11 +TU_ATTR_ALWAYS_INLINE static inline int tu_memcpy_s(void *dest, size_t destsz, const void *src, size_t count) { + // TODO may check if desst and src is not NULL + if ( count > destsz ) { + return -1; + } + memcpy(dest, src, count); + return 0; +} + +TU_ATTR_ALWAYS_INLINE static inline bool tu_mem_is_zero(const void *buffer, size_t size) { + const uint8_t* buf8 = (const uint8_t*) buffer; + for (size_t i = 0; i < size; i++) { + if (buf8[i] != 0) { return false; } + } + return true; +} + +TU_ATTR_ALWAYS_INLINE static inline bool tu_mem_is_ff(const void *buffer, size_t size) { + const uint8_t* buf8 = (const uint8_t*) buffer; + for (size_t i = 0; i < size; i++) { + if (buf8[i] != 0xff) { return false; } + } + return true; } -static inline uint8_t tu_u16_high(uint16_t u16) { return (uint8_t) (((uint16_t) (u16 >> 8)) & 0x00ff); } -static inline uint8_t tu_u16_low (uint16_t u16) { return (uint8_t) (u16 & 0x00ff); } -// Min -static inline uint8_t tu_min8 (uint8_t x, uint8_t y ) { return (x < y) ? x : y; } -static inline uint16_t tu_min16 (uint16_t x, uint16_t y) { return (x < y) ? x : y; } -static inline uint32_t tu_min32 (uint32_t x, uint32_t y) { return (x < y) ? x : y; } +//------------- Bytes -------------// +TU_ATTR_ALWAYS_INLINE static inline uint32_t tu_u32(uint8_t b3, uint8_t b2, uint8_t b1, uint8_t b0) { + return (((uint32_t)b3) << 24) | (((uint32_t)b2) << 16) | (((uint32_t)b1) << 8) | b0; +} -// Max -static inline uint8_t tu_max8 (uint8_t x, uint8_t y ) { return (x > y) ? x : y; } -static inline uint16_t tu_max16 (uint16_t x, uint16_t y) { return (x > y) ? x : y; } -static inline uint32_t tu_max32 (uint32_t x, uint32_t y) { return (x > y) ? x : y; } +TU_ATTR_ALWAYS_INLINE static inline uint32_t tu_u32_from_u16(uint16_t high, uint16_t low) { + return (((uint32_t)high) << 16) | low; +} -// Align -static inline uint32_t tu_align(uint32_t value, uint32_t alignment) -{ +TU_ATTR_ALWAYS_INLINE static inline uint16_t tu_u16(uint8_t high, uint8_t low) { + return (uint16_t)((((uint16_t)high) << 8) | low); +} + +TU_ATTR_ALWAYS_INLINE static inline uint8_t tu_u32_byte3(uint32_t ui32) { return TU_U32_BYTE3(ui32); } +TU_ATTR_ALWAYS_INLINE static inline uint8_t tu_u32_byte2(uint32_t ui32) { return TU_U32_BYTE2(ui32); } +TU_ATTR_ALWAYS_INLINE static inline uint8_t tu_u32_byte1(uint32_t ui32) { return TU_U32_BYTE1(ui32); } +TU_ATTR_ALWAYS_INLINE static inline uint8_t tu_u32_byte0(uint32_t ui32) { return TU_U32_BYTE0(ui32); } + +TU_ATTR_ALWAYS_INLINE static inline uint16_t tu_u32_high16(uint32_t ui32) { return (uint16_t) (ui32 >> 16); } +TU_ATTR_ALWAYS_INLINE static inline uint16_t tu_u32_low16 (uint32_t ui32) { return (uint16_t) (ui32 & 0x0000ffffu); } + +TU_ATTR_ALWAYS_INLINE static inline uint8_t tu_u16_high(uint16_t ui16) { return TU_U16_HIGH(ui16); } +TU_ATTR_ALWAYS_INLINE static inline uint8_t tu_u16_low (uint16_t ui16) { return TU_U16_LOW(ui16); } + +//------------- Bits -------------// +TU_ATTR_ALWAYS_INLINE static inline uint32_t tu_bit_set (uint32_t value, uint8_t pos) { return value | TU_BIT(pos); } +TU_ATTR_ALWAYS_INLINE static inline uint32_t tu_bit_clear(uint32_t value, uint8_t pos) { return value & (~TU_BIT(pos)); } +TU_ATTR_ALWAYS_INLINE static inline bool tu_bit_test (uint32_t value, uint8_t pos) { return (value & TU_BIT(pos)) ? true : false; } + +//------------- Min -------------// +TU_ATTR_ALWAYS_INLINE static inline uint8_t tu_min8 (uint8_t x, uint8_t y ) { return (x < y) ? x : y; } +TU_ATTR_ALWAYS_INLINE static inline uint16_t tu_min16 (uint16_t x, uint16_t y) { return (x < y) ? x : y; } +TU_ATTR_ALWAYS_INLINE static inline uint32_t tu_min32 (uint32_t x, uint32_t y) { return (x < y) ? x : y; } + +//------------- Max -------------// +TU_ATTR_ALWAYS_INLINE static inline uint8_t tu_max8 (uint8_t x, uint8_t y ) { return (x > y) ? x : y; } +TU_ATTR_ALWAYS_INLINE static inline uint16_t tu_max16 (uint16_t x, uint16_t y) { return (x > y) ? x : y; } +TU_ATTR_ALWAYS_INLINE static inline uint32_t tu_max32 (uint32_t x, uint32_t y) { return (x > y) ? x : y; } + +//------------- Align -------------// +TU_ATTR_ALWAYS_INLINE static inline uint32_t tu_align(uint32_t value, uint32_t alignment) { return value & ((uint32_t) ~(alignment-1)); } -static inline uint32_t tu_align32 (uint32_t value) { return (value & 0xFFFFFFE0UL); } -static inline uint32_t tu_align16 (uint32_t value) { return (value & 0xFFFFFFF0UL); } -static inline uint32_t tu_align4k (uint32_t value) { return (value & 0xFFFFF000UL); } -static inline uint32_t tu_offset4k(uint32_t value) { return (value & 0xFFFUL); } +TU_ATTR_ALWAYS_INLINE static inline uint32_t tu_align4 (uint32_t value) { return (value & 0xFFFFFFFCUL); } +TU_ATTR_ALWAYS_INLINE static inline uint32_t tu_align8 (uint32_t value) { return (value & 0xFFFFFFF8UL); } +TU_ATTR_ALWAYS_INLINE static inline uint32_t tu_align16 (uint32_t value) { return (value & 0xFFFFFFF0UL); } +TU_ATTR_ALWAYS_INLINE static inline uint32_t tu_align32 (uint32_t value) { return (value & 0xFFFFFFE0UL); } +TU_ATTR_ALWAYS_INLINE static inline uint32_t tu_align4k (uint32_t value) { return (value & 0xFFFFF000UL); } +TU_ATTR_ALWAYS_INLINE static inline uint32_t tu_offset4k(uint32_t value) { return (value & 0xFFFUL); } + +TU_ATTR_ALWAYS_INLINE static inline bool tu_is_aligned32(uint32_t value) { return (value & 0x1FUL) == 0; } +TU_ATTR_ALWAYS_INLINE static inline bool tu_is_aligned64(uint64_t value) { return (value & 0x3FUL) == 0; } //------------- Mathematics -------------// -static inline uint32_t tu_abs(int32_t value) { return (uint32_t)((value < 0) ? (-value) : value); } +TU_ATTR_ALWAYS_INLINE static inline uint32_t tu_div_ceil(uint32_t v, uint32_t d) { return TU_DIV_CEIL(v, d); } +TU_ATTR_ALWAYS_INLINE static inline uint32_t tu_round_up(uint32_t v, uint32_t f) { return tu_div_ceil(v, f) * f; } -/// inclusive range checking -static inline bool tu_within(uint32_t lower, uint32_t value, uint32_t upper) +// log2 of a value is its MSB's position +// TODO use clz TODO remove +TU_ATTR_ALWAYS_INLINE static inline uint8_t tu_log2(uint32_t value) { + uint8_t result = 0; + while (value >>= 1) { result++; } + return result; +} + +//static inline uint8_t tu_log2(uint32_t value) +//{ +// return sizeof(uint32_t) * CHAR_BIT - __builtin_clz(x) - 1; +//} + +TU_ATTR_ALWAYS_INLINE static inline bool tu_is_power_of_two(uint32_t value) { + return (value != 0) && ((value & (value - 1)) == 0); +} + +//------------- Unaligned Access -------------// +#if TUP_ARCH_STRICT_ALIGN + +// Rely on compiler to generate correct code for unaligned access +typedef struct { uint16_t val; } TU_ATTR_PACKED tu_unaligned_uint16_t; +typedef struct { uint32_t val; } TU_ATTR_PACKED tu_unaligned_uint32_t; + +TU_ATTR_ALWAYS_INLINE static inline uint32_t tu_unaligned_read32(const void *mem) { + tu_unaligned_uint32_t const *ua32 = (tu_unaligned_uint32_t const *) mem; + return ua32->val; +} + +TU_ATTR_ALWAYS_INLINE static inline void tu_unaligned_write32(void *mem, uint32_t value) { + tu_unaligned_uint32_t *ua32 = (tu_unaligned_uint32_t *) mem; + ua32->val = value; +} + +TU_ATTR_ALWAYS_INLINE static inline uint16_t tu_unaligned_read16(const void *mem) { + tu_unaligned_uint16_t const *ua16 = (tu_unaligned_uint16_t const *) mem; + return ua16->val; +} + +TU_ATTR_ALWAYS_INLINE static inline void tu_unaligned_write16(void *mem, uint16_t value) { + tu_unaligned_uint16_t *ua16 = (tu_unaligned_uint16_t *) mem; + ua16->val = value; +} + +#elif TUP_MCU_STRICT_ALIGN + +// MCU such as LPC_IP3511 Highspeed cannot access unaligned memory on USB_RAM although it is ARM M4. +// We have to manually pick up bytes since tu_unaligned_uint32_t will still generate unaligned code +// NOTE: volatile cast to memory to prevent compiler to optimize and generate unaligned code +// TODO Big Endian may need minor changes +TU_ATTR_ALWAYS_INLINE static inline uint32_t tu_unaligned_read32(const void* mem) { - return (lower <= value) && (value <= upper); + volatile uint8_t const* buf8 = (uint8_t const*) mem; + return tu_u32(buf8[3], buf8[2], buf8[1], buf8[0]); } -// log2 of a value is its MSB's position -// TODO use clz TODO remove -static inline uint8_t tu_log2(uint32_t value) +TU_ATTR_ALWAYS_INLINE static inline void tu_unaligned_write32(void* mem, uint32_t value) { - uint8_t result = 0; + volatile uint8_t* buf8 = (uint8_t*) mem; + buf8[0] = tu_u32_byte0(value); + buf8[1] = tu_u32_byte1(value); + buf8[2] = tu_u32_byte2(value); + buf8[3] = tu_u32_byte3(value); +} - while (value >>= 1) - { - result++; - } - return result; +TU_ATTR_ALWAYS_INLINE static inline uint16_t tu_unaligned_read16(const void* mem) +{ + volatile uint8_t const* buf8 = (uint8_t const*) mem; + return tu_u16(buf8[1], buf8[0]); +} + +TU_ATTR_ALWAYS_INLINE static inline void tu_unaligned_write16(void* mem, uint16_t value) +{ + volatile uint8_t* buf8 = (uint8_t*) mem; + buf8[0] = tu_u16_low(value); + buf8[1] = tu_u16_high(value); +} + + +#else + +// MCU that could access unaligned memory natively +TU_ATTR_ALWAYS_INLINE static inline uint32_t tu_unaligned_read32(const void *mem) { + return *((uint32_t const *) mem); +} + +TU_ATTR_ALWAYS_INLINE static inline uint16_t tu_unaligned_read16(const void *mem) { + return *((uint16_t const *) mem); +} + +TU_ATTR_ALWAYS_INLINE static inline void tu_unaligned_write32(void *mem, uint32_t value) { + *((uint32_t *) mem) = value; +} + +TU_ATTR_ALWAYS_INLINE static inline void tu_unaligned_write16(void *mem, uint16_t value) { + *((uint16_t *) mem) = value; } -// Bit -static inline uint32_t tu_bit_set (uint32_t value, uint8_t pos) { return value | TU_BIT(pos); } -static inline uint32_t tu_bit_clear(uint32_t value, uint8_t pos) { return value & (~TU_BIT(pos)); } -static inline bool tu_bit_test (uint32_t value, uint8_t pos) { return (value & TU_BIT(pos)) ? true : false; } - -/*------------------------------------------------------------------*/ -/* Count number of arguments of __VA_ARGS__ - * - reference https://groups.google.com/forum/#!topic/comp.std.c/d-6Mj5Lko_s - * - _GET_NTH_ARG() takes args >= N (64) but only expand to Nth one (64th) - * - _RSEQ_N() is reverse sequential to N to add padding to have - * Nth position is the same as the number of arguments - * - ##__VA_ARGS__ is used to deal with 0 paramerter (swallows comma) - *------------------------------------------------------------------*/ -#ifndef TU_ARGS_NUM - -#define TU_ARGS_NUM(...) NARG_(_0, ##__VA_ARGS__,_RSEQ_N()) - -#define NARG_(...) _GET_NTH_ARG(__VA_ARGS__) -#define _GET_NTH_ARG( \ - _1, _2, _3, _4, _5, _6, _7, _8, _9,_10, \ - _11,_12,_13,_14,_15,_16,_17,_18,_19,_20, \ - _21,_22,_23,_24,_25,_26,_27,_28,_29,_30, \ - _31,_32,_33,_34,_35,_36,_37,_38,_39,_40, \ - _41,_42,_43,_44,_45,_46,_47,_48,_49,_50, \ - _51,_52,_53,_54,_55,_56,_57,_58,_59,_60, \ - _61,_62,_63,N,...) N -#define _RSEQ_N() \ - 62,61,60, \ - 59,58,57,56,55,54,53,52,51,50, \ - 49,48,47,46,45,44,43,42,41,40, \ - 39,38,37,36,35,34,33,32,31,30, \ - 29,28,27,26,25,24,23,22,21,20, \ - 19,18,17,16,15,14,13,12,11,10, \ - 9,8,7,6,5,4,3,2,1,0 #endif // To be removed @@ -204,97 +332,45 @@ static inline bool tu_bit_test (uint32_t value, uint8_t pos) { return (value #endif //--------------------------------------------------------------------+ -// Debug Function +// Descriptor helper //--------------------------------------------------------------------+ -// CFG_TUSB_DEBUG for debugging -// 0 : no debug -// 1 : print when there is error -// 2 : print out log -#if CFG_TUSB_DEBUG - -void tu_print_mem(void const *buf, uint16_t count, uint8_t indent); - -#ifdef CFG_TUSB_DEBUG_PRINTF - extern int CFG_TUSB_DEBUG_PRINTF(const char *format, ...); - #define tu_printf CFG_TUSB_DEBUG_PRINTF -#else - #define tu_printf printf -#endif - -static inline -void tu_print_var(uint8_t const* buf, uint32_t bufsize) -{ - for(uint32_t i=0; i 1 - #define TU_LOG2 TU_LOG1 - #define TU_LOG2_MEM TU_LOG1_MEM - #define TU_LOG2_VAR TU_LOG1_VAR - #define TU_LOG2_INT TU_LOG1_INT - #define TU_LOG2_HEX TU_LOG1_HEX - #define TU_LOG2_LOCATION() TU_LOG1_LOCATION() -#endif - - -typedef struct -{ - uint32_t key; - const char* data; -} tu_lookup_entry_t; +// get descriptor length +TU_ATTR_ALWAYS_INLINE static inline uint8_t tu_desc_len(void const* desc) { + return ((uint8_t const*) desc)[DESC_OFFSET_LEN]; +} -typedef struct -{ - uint16_t count; - tu_lookup_entry_t const* items; -} tu_lookup_table_t; +// get descriptor type +TU_ATTR_ALWAYS_INLINE static inline uint8_t tu_desc_type(void const* desc) { + return ((uint8_t const*) desc)[DESC_OFFSET_TYPE]; +} -static inline const char* tu_lookup_find(tu_lookup_table_t const* p_table, uint32_t key) -{ - for(uint16_t i=0; icount; i++) - { - if (p_table->items[i].key == key) return p_table->items[i].data; - } +// get descriptor subtype +TU_ATTR_ALWAYS_INLINE static inline uint8_t tu_desc_subtype(void const* desc) { + return ((uint8_t const*) desc)[DESC_OFFSET_SUBTYPE]; +} - return NULL; +TU_ATTR_ALWAYS_INLINE static inline uint8_t tu_desc_in_bounds(uint8_t const* p_desc, uint8_t const* desc_end) { + return (p_desc < desc_end) && (tu_desc_next(p_desc) <= desc_end); } -#endif // CFG_TUSB_DEBUG +// find descriptor that match byte1 (type) +uint8_t const * tu_desc_find(uint8_t const* desc, uint8_t const* end, uint8_t byte1); -#ifndef TU_LOG1 - #define TU_LOG1(...) - #define TU_LOG1_MEM(...) - #define TU_LOG1_VAR(...) - #define TU_LOG1_INT(...) - #define TU_LOG1_HEX(...) - #define TU_LOG1_LOCATION() - #define TU_LOG1_FAILED() -#endif +// find descriptor that match byte1 (type) and byte2 +uint8_t const * tu_desc_find2(uint8_t const* desc, uint8_t const* end, uint8_t byte1, uint8_t byte2); -#ifndef TU_LOG2 - #define TU_LOG2(...) - #define TU_LOG2_MEM(...) - #define TU_LOG2_VAR(...) - #define TU_LOG2_INT(...) - #define TU_LOG2_HEX(...) - #define TU_LOG2_LOCATION() -#endif +// find descriptor that match byte1 (type) and byte2 +uint8_t const * tu_desc_find3(uint8_t const* desc, uint8_t const* end, uint8_t byte1, uint8_t byte2, uint8_t byte3); #ifdef __cplusplus } #endif #endif /* _TUSB_COMMON_H_ */ - -/** @} */ diff --git a/cores/nRF5/TinyUSB/Adafruit_TinyUSB_ArduinoCore/tinyusb/src/common/tusb_compiler.h b/cores/nRF5/TinyUSB/Adafruit_TinyUSB_ArduinoCore/tinyusb/src/common/tusb_compiler.h index 6f6aa3c..9b33a6f 100644 --- a/cores/nRF5/TinyUSB/Adafruit_TinyUSB_ArduinoCore/tinyusb/src/common/tusb_compiler.h +++ b/cores/nRF5/TinyUSB/Adafruit_TinyUSB_ArduinoCore/tinyusb/src/common/tusb_compiler.h @@ -1,4 +1,4 @@ -/* +/* * The MIT License (MIT) * * Copyright (c) 2019 Ha Thach (tinyusb.org) @@ -32,10 +32,17 @@ #ifndef _TUSB_COMPILER_H_ #define _TUSB_COMPILER_H_ -#define TU_STRING(x) #x ///< stringify without expand -#define TU_XSTRING(x) TU_STRING(x) ///< expand then stringify -#define TU_STRCAT(a, b) a##b ///< concat without expand -#define TU_XSTRCAT(a, b) TU_STRCAT(a, b) ///< expand then concat +#define TU_TOKEN(x) x +#define TU_STRING(x) #x ///< stringify without expand +#define TU_XSTRING(x) TU_STRING(x) ///< expand then stringify + +#define TU_STRCAT(a, b) a##b ///< concat without expand +#define TU_STRCAT3(a, b, c) a##b##c ///< concat without expand + +#define TU_XSTRCAT(a, b) TU_STRCAT(a, b) ///< expand then concat +#define TU_XSTRCAT3(a, b, c) TU_STRCAT3(a, b, c) ///< expand then concat 3 tokens + +#define TU_INCLUDE_PATH(_dir,_file) TU_XSTRING( TU_TOKEN(_dir)TU_TOKEN(_file) ) #if defined __COUNTER__ && __COUNTER__ != __COUNTER__ #define _TU_COUNTER_ __COUNTER__ @@ -44,31 +51,114 @@ #endif // Compile-time Assert -#if __STDC_VERSION__ >= 201112L +#if defined (__cplusplus) && __cplusplus >= 201103L + #define TU_VERIFY_STATIC static_assert +#elif defined (__STDC_VERSION__) && __STDC_VERSION__ >= 201112L #define TU_VERIFY_STATIC _Static_assert +#elif defined(__CCRX__) + #define TU_VERIFY_STATIC(const_expr, _mess) typedef char TU_XSTRCAT(_verify_static_, _TU_COUNTER_)[(const_expr) ? 1 : 0]; #else #define TU_VERIFY_STATIC(const_expr, _mess) enum { TU_XSTRCAT(_verify_static_, _TU_COUNTER_) = 1/(!!(const_expr)) } #endif +/* --------------------- Fuzzing types -------------------------------------- */ +#ifdef _FUZZ + #define tu_static static __thread +#else + #define tu_static static +#endif + // for declaration of reserved field, make use of _TU_COUNTER_ #define TU_RESERVED TU_XSTRCAT(reserved, _TU_COUNTER_) #define TU_LITTLE_ENDIAN (0x12u) #define TU_BIG_ENDIAN (0x21u) +/*------------------------------------------------------------------*/ +/* Count number of arguments of __VA_ARGS__ + * - reference https://stackoverflow.com/questions/2124339/c-preprocessor-va-args-number-of-arguments + * - _GET_NTH_ARG() takes args >= N (64) but only expand to Nth one (64th) + * - _RSEQ_N() is reverse sequential to N to add padding to have + * Nth position is the same as the number of arguments + * - ##__VA_ARGS__ is used to deal with 0 paramerter (swallows comma) + *------------------------------------------------------------------*/ +#if !defined(__CCRX__) +#define TU_ARGS_NUM(...) _TU_NARG(_0, ##__VA_ARGS__, _RSEQ_N()) +#else +#define TU_ARGS_NUM(...) _TU_NARG(_0, __VA_ARGS__, _RSEQ_N()) +#endif + +#define _TU_NARG(...) _GET_NTH_ARG(__VA_ARGS__) +#define _GET_NTH_ARG( \ + _1, _2, _3, _4, _5, _6, _7, _8, _9,_10, \ + _11,_12,_13,_14,_15,_16,_17,_18,_19,_20, \ + _21,_22,_23,_24,_25,_26,_27,_28,_29,_30, \ + _31,_32,_33,_34,_35,_36,_37,_38,_39,_40, \ + _41,_42,_43,_44,_45,_46,_47,_48,_49,_50, \ + _51,_52,_53,_54,_55,_56,_57,_58,_59,_60, \ + _61,_62,_63,N,...) N +#define _RSEQ_N() \ + 62,61,60, \ + 59,58,57,56,55,54,53,52,51,50, \ + 49,48,47,46,45,44,43,42,41,40, \ + 39,38,37,36,35,34,33,32,31,30, \ + 29,28,27,26,25,24,23,22,21,20, \ + 19,18,17,16,15,14,13,12,11,10, \ + 9,8,7,6,5,4,3,2,1,0 + +// Apply an macro X to each of the arguments with an separated of choice +#define TU_ARGS_APPLY(_X, _s, ...) TU_XSTRCAT(_TU_ARGS_APPLY_, TU_ARGS_NUM(__VA_ARGS__))(_X, _s, __VA_ARGS__) + +#define _TU_ARGS_APPLY_1(_X, _s, _a1) _X(_a1) +#define _TU_ARGS_APPLY_2(_X, _s, _a1, _a2) _X(_a1) _s _X(_a2) +#define _TU_ARGS_APPLY_3(_X, _s, _a1, _a2, _a3) _X(_a1) _s _TU_ARGS_APPLY_2(_X, _s, _a2, _a3) +#define _TU_ARGS_APPLY_4(_X, _s, _a1, _a2, _a3, _a4) _X(_a1) _s _TU_ARGS_APPLY_3(_X, _s, _a2, _a3, _a4) +#define _TU_ARGS_APPLY_5(_X, _s, _a1, _a2, _a3, _a4, _a5) _X(_a1) _s _TU_ARGS_APPLY_4(_X, _s, _a2, _a3, _a4, _a5) +#define _TU_ARGS_APPLY_6(_X, _s, _a1, _a2, _a3, _a4, _a5, _a6) _X(_a1) _s _TU_ARGS_APPLY_5(_X, _s, _a2, _a3, _a4, _a5, _a6) +#define _TU_ARGS_APPLY_7(_X, _s, _a1, _a2, _a3, _a4, _a5, _a6, _a7) _X(_a1) _s _TU_ARGS_APPLY_6(_X, _s, _a2, _a3, _a4, _a5, _a6, _a7) +#define _TU_ARGS_APPLY_8(_X, _s, _a1, _a2, _a3, _a4, _a5, _a6, _a7, _a8) _X(_a1) _s _TU_ARGS_APPLY_7(_X, _s, _a2, _a3, _a4, _a5, _a6, _a7, _a8) + +//--------------------------------------------------------------------+ +// Macro for function default arguments +//--------------------------------------------------------------------+ +#define TU_GET_3RD_ARG(arg1, arg2, arg3, ...) arg3 + +// function expand with number of arguments +#define TU_FUNC_OPTIONAL_ARG(func, ...) TU_XSTRCAT(func##_arg, TU_ARGS_NUM(__VA_ARGS__))(__VA_ARGS__) + //--------------------------------------------------------------------+ // Compiler porting with Attribute and Endian //--------------------------------------------------------------------+ + +// TODO refactor since __attribute__ is supported across many compiler #if defined(__GNUC__) #define TU_ATTR_ALIGNED(Bytes) __attribute__ ((aligned(Bytes))) #define TU_ATTR_SECTION(sec_name) __attribute__ ((section(#sec_name))) #define TU_ATTR_PACKED __attribute__ ((packed)) - #define TU_ATTR_PREPACKED #define TU_ATTR_WEAK __attribute__ ((weak)) + // #define TU_ATTR_WEAK_ALIAS(f) __attribute__ ((weak, alias(#f))) + #ifndef TU_ATTR_ALWAYS_INLINE // allow to override for debug + #define TU_ATTR_ALWAYS_INLINE __attribute__ ((always_inline)) + #endif #define TU_ATTR_DEPRECATED(mess) __attribute__ ((deprecated(mess))) // warn if function with this attribute is used #define TU_ATTR_UNUSED __attribute__ ((unused)) // Function/Variable is meant to be possibly unused #define TU_ATTR_USED __attribute__ ((used)) // Function/Variable is meant to be used + #define TU_ATTR_PACKED_BEGIN + #define TU_ATTR_PACKED_END + #define TU_ATTR_BIT_FIELD_ORDER_BEGIN + #define TU_ATTR_BIT_FIELD_ORDER_END + + #if __GNUC__ < 5 + #define TU_ATTR_FALLTHROUGH do {} while (0) /* fallthrough */ + #else + #if __has_attribute(__fallthrough__) + #define TU_ATTR_FALLTHROUGH __attribute__((fallthrough)) + #else + #define TU_ATTR_FALLTHROUGH do {} while (0) /* fallthrough */ + #endif + #endif + // Endian conversion use well-known host to network (big endian) naming #if __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__ #define TU_BYTE_ORDER TU_LITTLE_ENDIAN @@ -76,18 +166,40 @@ #define TU_BYTE_ORDER TU_BIG_ENDIAN #endif - #define TU_BSWAP16(u16) (__builtin_bswap16(u16)) - #define TU_BSWAP32(u32) (__builtin_bswap32(u32)) + // Unfortunately XC16 doesn't provide builtins for 32bit endian conversion + #if defined(__XC16) + #define TU_BSWAP16(u16) (__builtin_swap(u16)) + #define TU_BSWAP32(u32) ((((u32) & 0xff000000) >> 24) | \ + (((u32) & 0x00ff0000) >> 8) | \ + (((u32) & 0x0000ff00) << 8) | \ + (((u32) & 0x000000ff) << 24)) + #else + #define TU_BSWAP16(u16) (__builtin_bswap16(u16)) + #define TU_BSWAP32(u32) (__builtin_bswap32(u32)) + #endif + + #ifndef __ARMCC_VERSION + // List of obsolete callback function that is renamed and should not be defined. + // Put it here since only gcc support this pragma + #pragma GCC poison tud_vendor_control_request_cb + #endif #elif defined(__TI_COMPILER_VERSION__) #define TU_ATTR_ALIGNED(Bytes) __attribute__ ((aligned(Bytes))) #define TU_ATTR_SECTION(sec_name) __attribute__ ((section(#sec_name))) #define TU_ATTR_PACKED __attribute__ ((packed)) - #define TU_ATTR_PREPACKED #define TU_ATTR_WEAK __attribute__ ((weak)) + // #define TU_ATTR_WEAK_ALIAS(f) __attribute__ ((weak, alias(#f))) + #define TU_ATTR_ALWAYS_INLINE __attribute__ ((always_inline)) #define TU_ATTR_DEPRECATED(mess) __attribute__ ((deprecated(mess))) // warn if function with this attribute is used #define TU_ATTR_UNUSED __attribute__ ((unused)) // Function/Variable is meant to be possibly unused #define TU_ATTR_USED __attribute__ ((used)) + #define TU_ATTR_FALLTHROUGH __attribute__((fallthrough)) + + #define TU_ATTR_PACKED_BEGIN + #define TU_ATTR_PACKED_END + #define TU_ATTR_BIT_FIELD_ORDER_BEGIN + #define TU_ATTR_BIT_FIELD_ORDER_END // __BYTE_ORDER is defined in the TI ARM compiler, but not MSP430 (which is little endian) #if ((__BYTE_ORDER__) == (__ORDER_LITTLE_ENDIAN__)) || defined(__MSP430__) @@ -100,14 +212,24 @@ #define TU_BSWAP32(u32) (__builtin_bswap32(u32)) #elif defined(__ICCARM__) + #include #define TU_ATTR_ALIGNED(Bytes) __attribute__ ((aligned(Bytes))) #define TU_ATTR_SECTION(sec_name) __attribute__ ((section(#sec_name))) #define TU_ATTR_PACKED __attribute__ ((packed)) - #define TU_ATTR_PREPACKED #define TU_ATTR_WEAK __attribute__ ((weak)) + // #define TU_ATTR_WEAK_ALIAS(f) __attribute__ ((weak, alias(#f))) + #ifndef TU_ATTR_ALWAYS_INLINE // allow to override for debug + #define TU_ATTR_ALWAYS_INLINE __attribute__ ((always_inline)) + #endif #define TU_ATTR_DEPRECATED(mess) __attribute__ ((deprecated(mess))) // warn if function with this attribute is used #define TU_ATTR_UNUSED __attribute__ ((unused)) // Function/Variable is meant to be possibly unused #define TU_ATTR_USED __attribute__ ((used)) // Function/Variable is meant to be used + #define TU_ATTR_FALLTHROUGH do {} while (0) /* fallthrough */ + + #define TU_ATTR_PACKED_BEGIN + #define TU_ATTR_PACKED_END + #define TU_ATTR_BIT_FIELD_ORDER_BEGIN + #define TU_ATTR_BIT_FIELD_ORDER_END // Endian conversion use well-known host to network (big endian) naming #if __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__ @@ -118,10 +240,39 @@ #define TU_BSWAP16(u16) (__iar_builtin_REV16(u16)) #define TU_BSWAP32(u32) (__iar_builtin_REV(u32)) -#else + +#elif defined(__CCRX__) + #define TU_ATTR_ALIGNED(Bytes) + #define TU_ATTR_SECTION(sec_name) + #define TU_ATTR_PACKED + #define TU_ATTR_WEAK + // #define TU_ATTR_WEAK_ALIAS(f) + #define TU_ATTR_ALWAYS_INLINE + #define TU_ATTR_DEPRECATED(mess) + #define TU_ATTR_UNUSED + #define TU_ATTR_USED + #define TU_ATTR_FALLTHROUGH do {} while (0) /* fallthrough */ + + #define TU_ATTR_PACKED_BEGIN _Pragma("pack") + #define TU_ATTR_PACKED_END _Pragma("packoption") + #define TU_ATTR_BIT_FIELD_ORDER_BEGIN _Pragma("bit_order right") + #define TU_ATTR_BIT_FIELD_ORDER_END _Pragma("bit_order") + + // Endian conversion use well-known host to network (big endian) naming + #if defined(__LIT) + #define TU_BYTE_ORDER TU_LITTLE_ENDIAN + #else + #define TU_BYTE_ORDER TU_BIG_ENDIAN + #endif + + #define TU_BSWAP16(u16) ((unsigned short)_builtin_revw((unsigned long)u16)) + #define TU_BSWAP32(u32) (_builtin_revl(u32)) + +#else #error "Compiler attribute porting is required" #endif + #if (TU_BYTE_ORDER == TU_LITTLE_ENDIAN) #define tu_htons(u16) (TU_BSWAP16(u16)) @@ -144,11 +295,11 @@ #define tu_htonl(u32) (u32) #define tu_ntohl(u32) (u32) - #define tu_htole16(u16) (tu_bswap16(u16)) - #define tu_le16toh(u16) (tu_bswap16(u16)) + #define tu_htole16(u16) (TU_BSWAP16(u16)) + #define tu_le16toh(u16) (TU_BSWAP16(u16)) - #define tu_htole32(u32) (tu_bswap32(u32)) - #define tu_le32toh(u32) (tu_bswap32(u32)) + #define tu_htole32(u32) (TU_BSWAP32(u32)) + #define tu_le32toh(u32) (TU_BSWAP32(u32)) #else #error Byte order is undefined diff --git a/cores/nRF5/TinyUSB/Adafruit_TinyUSB_ArduinoCore/tinyusb/src/common/tusb_debug.h b/cores/nRF5/TinyUSB/Adafruit_TinyUSB_ArduinoCore/tinyusb/src/common/tusb_debug.h new file mode 100644 index 0000000..1d0c6f1 --- /dev/null +++ b/cores/nRF5/TinyUSB/Adafruit_TinyUSB_ArduinoCore/tinyusb/src/common/tusb_debug.h @@ -0,0 +1,169 @@ +/* + * The MIT License (MIT) + * + * Copyright (c) 2022, Ha Thach (tinyusb.org) + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + * This file is part of the TinyUSB stack. + */ + +#ifndef _TUSB_DEBUG_H_ +#define _TUSB_DEBUG_H_ + +#ifdef __cplusplus + extern "C" { +#endif + +//--------------------------------------------------------------------+ +// Debug +//--------------------------------------------------------------------+ + +// CFG_TUSB_DEBUG for debugging +// 0 : no debug +// 1 : print error +// 2 : print warning +// 3 : print info +#if CFG_TUSB_DEBUG + +// Enum to String for debugging purposes +#if CFG_TUSB_DEBUG >= CFG_TUH_LOG_LEVEL || CFG_TUSB_DEBUG >= CFG_TUD_LOG_LEVEL +extern char const* const tu_str_speed[]; +extern char const* const tu_str_std_request[]; +extern char const* const tu_str_xfer_result[]; +#endif + +void tu_print_mem(void const *buf, uint32_t count, uint8_t indent); + +#ifdef CFG_TUSB_DEBUG_PRINTF + extern int CFG_TUSB_DEBUG_PRINTF(const char *format, ...); + #define tu_printf CFG_TUSB_DEBUG_PRINTF +#else + #define tu_printf printf +#endif + +static inline void tu_print_buf(uint8_t const* buf, uint32_t bufsize) { + for(uint32_t i=0; i= 2 + #define TU_LOG2 TU_LOG1 + #define TU_LOG2_MEM TU_LOG1_MEM + #define TU_LOG2_BUF TU_LOG1_BUF + #define TU_LOG2_INT TU_LOG1_INT + #define TU_LOG2_HEX TU_LOG1_HEX +#endif + +// Log Level 3: Info +#if CFG_TUSB_DEBUG >= 3 + #define TU_LOG3 TU_LOG1 + #define TU_LOG3_MEM TU_LOG1_MEM + #define TU_LOG3_BUF TU_LOG1_BUF + #define TU_LOG3_INT TU_LOG1_INT + #define TU_LOG3_HEX TU_LOG1_HEX +#endif + +typedef struct { + uint32_t key; + const char* data; +} tu_lookup_entry_t; + +typedef struct { + uint16_t count; + tu_lookup_entry_t const* items; +} tu_lookup_table_t; + +static inline const char* tu_lookup_find(tu_lookup_table_t const* p_table, uint32_t key) { + for(uint16_t i=0; icount; i++) { + if (p_table->items[i].key == key) { return p_table->items[i].data; } + } + + // not found return the key value in hex + static char not_found[11]; + snprintf(not_found, sizeof(not_found), "0x%08lX", (unsigned long) key); + return not_found; +} + +#endif // CFG_TUSB_DEBUG + +#ifndef TU_LOG + #define TU_LOG(n, ...) + #define TU_LOG_MEM(n, ...) + #define TU_LOG_BUF(n, ...) + #define TU_LOG_INT(n, ...) + #define TU_LOG_HEX(n, ...) + #define TU_LOG_LOCATION() + #define TU_LOG_FAILED() +#endif + +// TODO replace all TU_LOGn with TU_LOG(n) + +#define TU_LOG0(...) +#define TU_LOG0_MEM(...) +#define TU_LOG0_BUF(...) +#define TU_LOG0_INT(...) +#define TU_LOG0_HEX(...) + +#ifndef TU_LOG1 + #define TU_LOG1(...) + #define TU_LOG1_MEM(...) + #define TU_LOG1_BUF(...) + #define TU_LOG1_INT(...) + #define TU_LOG1_HEX(...) +#endif + +#ifndef TU_LOG2 + #define TU_LOG2(...) + #define TU_LOG2_MEM(...) + #define TU_LOG2_BUF(...) + #define TU_LOG2_INT(...) + #define TU_LOG2_HEX(...) +#endif + +#ifndef TU_LOG3 + #define TU_LOG3(...) + #define TU_LOG3_MEM(...) + #define TU_LOG3_BUF(...) + #define TU_LOG3_INT(...) + #define TU_LOG3_HEX(...) +#endif + +#ifdef __cplusplus + } +#endif + +#endif /* _TUSB_DEBUG_H_ */ diff --git a/cores/nRF5/TinyUSB/Adafruit_TinyUSB_ArduinoCore/tinyusb/src/common/tusb_error.h b/cores/nRF5/TinyUSB/Adafruit_TinyUSB_ArduinoCore/tinyusb/src/common/tusb_error.h deleted file mode 100644 index f600c4a..0000000 --- a/cores/nRF5/TinyUSB/Adafruit_TinyUSB_ArduinoCore/tinyusb/src/common/tusb_error.h +++ /dev/null @@ -1,74 +0,0 @@ -/* - * 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. - */ - -/** \ingroup Group_Common - * \defgroup Group_Error Error Codes - * @{ */ - -#ifndef _TUSB_ERRORS_H_ -#define _TUSB_ERRORS_H_ - -#include "tusb_option.h" - -#ifdef __cplusplus - extern "C" { -#endif - -#define ERROR_ENUM(x) x, -#define ERROR_STRING(x) #x, - -#define ERROR_TABLE(ENTRY) \ - ENTRY(TUSB_ERROR_NONE )\ - ENTRY(TUSB_ERROR_INVALID_PARA )\ - ENTRY(TUSB_ERROR_DEVICE_NOT_READY )\ - ENTRY(TUSB_ERROR_INTERFACE_IS_BUSY )\ - ENTRY(TUSB_ERROR_HCD_OPEN_PIPE_FAILED )\ - ENTRY(TUSB_ERROR_OSAL_TIMEOUT )\ - ENTRY(TUSB_ERROR_CDCH_DEVICE_NOT_MOUNTED )\ - ENTRY(TUSB_ERROR_MSCH_DEVICE_NOT_MOUNTED )\ - ENTRY(TUSB_ERROR_NOT_SUPPORTED )\ - ENTRY(TUSB_ERROR_NOT_ENOUGH_MEMORY )\ - ENTRY(TUSB_ERROR_FAILED )\ - -/// \brief Error Code returned -typedef enum -{ - ERROR_TABLE(ERROR_ENUM) - TUSB_ERROR_COUNT -}tusb_error_t; - -#if CFG_TUSB_DEBUG -/// Enum to String for debugging purposes. Only available if \ref CFG_TUSB_DEBUG > 0 -extern char const* const tusb_strerr[TUSB_ERROR_COUNT]; -#endif - -#ifdef __cplusplus - } -#endif - -#endif /* _TUSB_ERRORS_H_ */ - -/** @} */ diff --git a/cores/nRF5/TinyUSB/Adafruit_TinyUSB_ArduinoCore/tinyusb/src/common/tusb_fifo.c b/cores/nRF5/TinyUSB/Adafruit_TinyUSB_ArduinoCore/tinyusb/src/common/tusb_fifo.c index 6ab158c..f767955 100644 --- a/cores/nRF5/TinyUSB/Adafruit_TinyUSB_ArduinoCore/tinyusb/src/common/tusb_fifo.c +++ b/cores/nRF5/TinyUSB/Adafruit_TinyUSB_ArduinoCore/tinyusb/src/common/tusb_fifo.c @@ -2,6 +2,7 @@ * The MIT License (MIT) * * Copyright (c) 2019 Ha Thach (tinyusb.org) + * Copyright (c) 2020 Reinhard Panhuber - rework to unmasked pointers * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -24,95 +25,667 @@ * This file is part of the TinyUSB stack. */ -#include - #include "osal/osal.h" #include "tusb_fifo.h" -// implement mutex lock and unlock -#if CFG_FIFO_MUTEX +#define TU_FIFO_DBG 0 + +// Suppress IAR warning +// Warning[Pa082]: undefined behavior: the order of volatile accesses is undefined in this statement +#if defined(__ICCARM__) +#pragma diag_suppress = Pa082 +#endif + +#if OSAL_MUTEX_REQUIRED + +TU_ATTR_ALWAYS_INLINE static inline void _ff_lock(osal_mutex_t mutex) +{ + if (mutex) osal_mutex_lock(mutex, OSAL_TIMEOUT_WAIT_FOREVER); +} + +TU_ATTR_ALWAYS_INLINE static inline void _ff_unlock(osal_mutex_t mutex) +{ + if (mutex) osal_mutex_unlock(mutex); +} + +#else + +#define _ff_lock(_mutex) +#define _ff_unlock(_mutex) + +#endif -static void tu_fifo_lock(tu_fifo_t *f) +/** \enum tu_fifo_copy_mode_t + * \brief Write modes intended to allow special read and write functions to be able to + * copy data to and from USB hardware FIFOs as needed for e.g. STM32s and others + */ +typedef enum { - if (f->mutex) + TU_FIFO_COPY_INC, ///< Copy from/to an increasing source/destination address - default mode +#ifdef TUP_MEM_CONST_ADDR + TU_FIFO_COPY_CST_FULL_WORDS, ///< Copy from/to a constant source/destination address - required for e.g. STM32 to write into USB hardware FIFO +#endif +} tu_fifo_copy_mode_t; + +bool tu_fifo_config(tu_fifo_t *f, void* buffer, uint16_t depth, uint16_t item_size, bool overwritable) +{ + // Limit index space to 2*depth - this allows for a fast "modulo" calculation + // but limits the maximum depth to 2^16/2 = 2^15 and buffer overflows are detectable + // only if overflow happens once (important for unsupervised DMA applications) + if (depth > 0x8000) return false; + + _ff_lock(f->mutex_wr); + _ff_lock(f->mutex_rd); + + f->buffer = (uint8_t*) buffer; + f->depth = depth; + f->item_size = (uint16_t) (item_size & 0x7FFF); + f->overwritable = overwritable; + f->rd_idx = 0; + f->wr_idx = 0; + + _ff_unlock(f->mutex_wr); + _ff_unlock(f->mutex_rd); + + return true; +} + +//--------------------------------------------------------------------+ +// Pull & Push +//--------------------------------------------------------------------+ + +#ifdef TUP_MEM_CONST_ADDR +// Intended to be used to read from hardware USB FIFO in e.g. STM32 where all data is read from a constant address +// Code adapted from dcd_synopsys.c +// TODO generalize with configurable 1 byte or 4 byte each read +static void _ff_push_const_addr(uint8_t * ff_buf, const void * app_buf, uint16_t len) +{ + volatile const uint32_t * reg_rx = (volatile const uint32_t *) app_buf; + + // Reading full available 32 bit words from const app address + uint16_t full_words = len >> 2; + while(full_words--) { - osal_mutex_lock(f->mutex, OSAL_TIMEOUT_WAIT_FOREVER); + tu_unaligned_write32(ff_buf, *reg_rx); + ff_buf += 4; + } + + // Read the remaining 1-3 bytes from const app address + uint8_t const bytes_rem = len & 0x03; + if ( bytes_rem ) + { + uint32_t tmp32 = *reg_rx; + memcpy(ff_buf, &tmp32, bytes_rem); } } -static void tu_fifo_unlock(tu_fifo_t *f) +// Intended to be used to write to hardware USB FIFO in e.g. STM32 +// where all data is written to a constant address in full word copies +static void _ff_pull_const_addr(void * app_buf, const uint8_t * ff_buf, uint16_t len) { - if (f->mutex) + volatile uint32_t * reg_tx = (volatile uint32_t *) app_buf; + + // Write full available 32 bit words to const address + uint16_t full_words = len >> 2; + while(full_words--) + { + *reg_tx = tu_unaligned_read32(ff_buf); + ff_buf += 4; + } + + // Write the remaining 1-3 bytes into const address + uint8_t const bytes_rem = len & 0x03; + if ( bytes_rem ) { - osal_mutex_unlock(f->mutex); + uint32_t tmp32 = 0; + memcpy(&tmp32, ff_buf, bytes_rem); + + *reg_tx = tmp32; } } +#endif -#else +// send one item to fifo WITHOUT updating write pointer +static inline void _ff_push(tu_fifo_t* f, void const * app_buf, uint16_t rel) +{ + memcpy(f->buffer + (rel * f->item_size), app_buf, f->item_size); +} -#define tu_fifo_lock(_ff) -#define tu_fifo_unlock(_ff) +// send n items to fifo WITHOUT updating write pointer +static void _ff_push_n(tu_fifo_t* f, void const * app_buf, uint16_t n, uint16_t wr_ptr, tu_fifo_copy_mode_t copy_mode) +{ + uint16_t const lin_count = f->depth - wr_ptr; + uint16_t const wrap_count = n - lin_count; + + uint16_t lin_bytes = lin_count * f->item_size; + uint16_t wrap_bytes = wrap_count * f->item_size; + + // current buffer of fifo + uint8_t* ff_buf = f->buffer + (wr_ptr * f->item_size); + switch (copy_mode) + { + case TU_FIFO_COPY_INC: + if(n <= lin_count) + { + // Linear only + memcpy(ff_buf, app_buf, n*f->item_size); + } + else + { + // Wrap around + + // Write data to linear part of buffer + memcpy(ff_buf, app_buf, lin_bytes); + + // Write data wrapped around + // TU_ASSERT(nWrap_bytes <= f->depth, ); + memcpy(f->buffer, ((uint8_t const*) app_buf) + lin_bytes, wrap_bytes); + } + break; +#ifdef TUP_MEM_CONST_ADDR + case TU_FIFO_COPY_CST_FULL_WORDS: + // Intended for hardware buffers from which it can be read word by word only + if(n <= lin_count) + { + // Linear only + _ff_push_const_addr(ff_buf, app_buf, n*f->item_size); + } + else + { + // Wrap around case + + // Write full words to linear part of buffer + uint16_t nLin_4n_bytes = lin_bytes & 0xFFFC; + _ff_push_const_addr(ff_buf, app_buf, nLin_4n_bytes); + ff_buf += nLin_4n_bytes; + + // There could be odd 1-3 bytes before the wrap-around boundary + uint8_t rem = lin_bytes & 0x03; + if (rem > 0) + { + volatile const uint32_t * rx_fifo = (volatile const uint32_t *) app_buf; + + uint8_t remrem = (uint8_t) tu_min16(wrap_bytes, 4-rem); + wrap_bytes -= remrem; + + uint32_t tmp32 = *rx_fifo; + uint8_t * src_u8 = ((uint8_t *) &tmp32); + + // Write 1-3 bytes before wrapped boundary + while(rem--) *ff_buf++ = *src_u8++; + + // Read more bytes to beginning to complete a word + ff_buf = f->buffer; + while(remrem--) *ff_buf++ = *src_u8++; + } + else + { + ff_buf = f->buffer; // wrap around to beginning + } + + // Write data wrapped part + if (wrap_bytes > 0) _ff_push_const_addr(ff_buf, app_buf, wrap_bytes); + } + break; #endif + default: break; + } +} -bool tu_fifo_config(tu_fifo_t *f, void* buffer, uint16_t depth, uint16_t item_size, bool overwritable) +// get one item from fifo WITHOUT updating read pointer +static inline void _ff_pull(tu_fifo_t* f, void * app_buf, uint16_t rel) { - tu_fifo_lock(f); + memcpy(app_buf, f->buffer + (rel * f->item_size), f->item_size); +} - f->buffer = (uint8_t*) buffer; - f->depth = depth; - f->item_size = item_size; - f->overwritable = overwritable; +// get n items from fifo WITHOUT updating read pointer +static void _ff_pull_n(tu_fifo_t* f, void* app_buf, uint16_t n, uint16_t rd_ptr, tu_fifo_copy_mode_t copy_mode) +{ + uint16_t const lin_count = f->depth - rd_ptr; + uint16_t const wrap_count = n - lin_count; // only used if wrapped - f->rd_idx = f->wr_idx = f->count = 0; + uint16_t lin_bytes = lin_count * f->item_size; + uint16_t wrap_bytes = wrap_count * f->item_size; - tu_fifo_unlock(f); + // current buffer of fifo + uint8_t* ff_buf = f->buffer + (rd_ptr * f->item_size); - return true; + switch (copy_mode) + { + case TU_FIFO_COPY_INC: + if ( n <= lin_count ) + { + // Linear only + memcpy(app_buf, ff_buf, n*f->item_size); + } + else + { + // Wrap around + + // Read data from linear part of buffer + memcpy(app_buf, ff_buf, lin_bytes); + + // Read data wrapped part + memcpy((uint8_t*) app_buf + lin_bytes, f->buffer, wrap_bytes); + } + break; +#ifdef TUP_MEM_CONST_ADDR + case TU_FIFO_COPY_CST_FULL_WORDS: + if ( n <= lin_count ) + { + // Linear only + _ff_pull_const_addr(app_buf, ff_buf, n*f->item_size); + } + else + { + // Wrap around case + + // Read full words from linear part of buffer + uint16_t lin_4n_bytes = lin_bytes & 0xFFFC; + _ff_pull_const_addr(app_buf, ff_buf, lin_4n_bytes); + ff_buf += lin_4n_bytes; + + // There could be odd 1-3 bytes before the wrap-around boundary + uint8_t rem = lin_bytes & 0x03; + if (rem > 0) + { + volatile uint32_t * reg_tx = (volatile uint32_t *) app_buf; + + uint8_t remrem = (uint8_t) tu_min16(wrap_bytes, 4-rem); + wrap_bytes -= remrem; + + uint32_t tmp32=0; + uint8_t * dst_u8 = (uint8_t *)&tmp32; + + // Read 1-3 bytes before wrapped boundary + while(rem--) *dst_u8++ = *ff_buf++; + + // Read more bytes from beginning to complete a word + ff_buf = f->buffer; + while(remrem--) *dst_u8++ = *ff_buf++; + + *reg_tx = tmp32; + } + else + { + ff_buf = f->buffer; // wrap around to beginning + } + + // Read data wrapped part + if (wrap_bytes > 0) _ff_pull_const_addr(app_buf, ff_buf, wrap_bytes); + } + break; +#endif + default: break; + } +} + +//--------------------------------------------------------------------+ +// Helper +//--------------------------------------------------------------------+ + +// return only the index difference and as such can be used to determine an overflow i.e overflowable count +TU_ATTR_ALWAYS_INLINE static inline +uint16_t _ff_count(uint16_t depth, uint16_t wr_idx, uint16_t rd_idx) +{ + // In case we have non-power of two depth we need a further modification + if (wr_idx >= rd_idx) + { + return (uint16_t) (wr_idx - rd_idx); + } else + { + return (uint16_t) (2*depth - (rd_idx - wr_idx)); + } } -static inline uint16_t _ff_mod(uint16_t idx, uint16_t depth) +// return remaining slot in fifo +TU_ATTR_ALWAYS_INLINE static inline +uint16_t _ff_remaining(uint16_t depth, uint16_t wr_idx, uint16_t rd_idx) { - return (idx < depth) ? idx : (idx-depth); + uint16_t const count = _ff_count(depth, wr_idx, rd_idx); + return (depth > count) ? (depth - count) : 0; } -// retrieve data from fifo -static inline void _ff_pull(tu_fifo_t* f, void * buffer, uint16_t n) +//--------------------------------------------------------------------+ +// Index Helper +//--------------------------------------------------------------------+ + +// Advance an absolute index +// "absolute" index is only in the range of [0..2*depth) +static uint16_t advance_index(uint16_t depth, uint16_t idx, uint16_t offset) { - memcpy(buffer, - f->buffer + (f->rd_idx * f->item_size), - f->item_size*n); + // We limit the index space of p such that a correct wrap around happens + // Check for a wrap around or if we are in unused index space - This has to be checked first!! + // We are exploiting the wrap around to the correct index + uint16_t new_idx = (uint16_t) (idx + offset); + if ( (idx > new_idx) || (new_idx >= 2*depth) ) + { + uint16_t const non_used_index_space = (uint16_t) (UINT16_MAX - (2*depth-1)); + new_idx = (uint16_t) (new_idx + non_used_index_space); + } + + return new_idx; +} + +#if 0 // not used but +// Backward an absolute index +static uint16_t backward_index(uint16_t depth, uint16_t idx, uint16_t offset) +{ + // We limit the index space of p such that a correct wrap around happens + // Check for a wrap around or if we are in unused index space - This has to be checked first!! + // We are exploiting the wrap around to the correct index + uint16_t new_idx = (uint16_t) (idx - offset); + if ( (idx < new_idx) || (new_idx >= 2*depth) ) + { + uint16_t const non_used_index_space = (uint16_t) (UINT16_MAX - (2*depth-1)); + new_idx = (uint16_t) (new_idx - non_used_index_space); + } + + return new_idx; +} +#endif + +// index to pointer, simply an modulo with minus. +TU_ATTR_ALWAYS_INLINE static inline +uint16_t idx2ptr(uint16_t depth, uint16_t idx) +{ + // Only run at most 3 times since index is limit in the range of [0..2*depth) + while ( idx >= depth ) idx -= depth; + return idx; +} + +// Works on local copies of w +// When an overwritable fifo is overflowed, rd_idx will be re-index so that it forms +// an full fifo i.e _ff_count() = depth +TU_ATTR_ALWAYS_INLINE static inline +uint16_t _ff_correct_read_index(tu_fifo_t* f, uint16_t wr_idx) +{ + uint16_t rd_idx; + if ( wr_idx >= f->depth ) + { + rd_idx = wr_idx - f->depth; + }else + { + rd_idx = wr_idx + f->depth; + } + + f->rd_idx = rd_idx; - f->rd_idx = _ff_mod(f->rd_idx + n, f->depth); - f->count -= n; + return rd_idx; } -// send data to fifo -static inline void _ff_push(tu_fifo_t* f, void const * data, uint16_t n) +// Works on local copies of w and r +// Must be protected by mutexes since in case of an overflow read pointer gets modified +static bool _tu_fifo_peek(tu_fifo_t* f, void * p_buffer, uint16_t wr_idx, uint16_t rd_idx) { - memcpy(f->buffer + (f->wr_idx * f->item_size), - data, - f->item_size*n); + uint16_t cnt = _ff_count(f->depth, wr_idx, rd_idx); - f->wr_idx = _ff_mod(f->wr_idx + n, f->depth); + // nothing to peek + if ( cnt == 0 ) return false; - if (tu_fifo_full(f)) + // Check overflow and correct if required + if ( cnt > f->depth ) { - f->rd_idx = f->wr_idx; // keep the full state (rd == wr && count = depth) + rd_idx = _ff_correct_read_index(f, wr_idx); + } + + uint16_t rd_ptr = idx2ptr(f->depth, rd_idx); + + // Peek data + _ff_pull(f, p_buffer, rd_ptr); + + return true; +} + +// Works on local copies of w and r +// Must be protected by mutexes since in case of an overflow read pointer gets modified +static uint16_t _tu_fifo_peek_n(tu_fifo_t* f, void * p_buffer, uint16_t n, uint16_t wr_idx, uint16_t rd_idx, tu_fifo_copy_mode_t copy_mode) +{ + uint16_t cnt = _ff_count(f->depth, wr_idx, rd_idx); + + // nothing to peek + if ( cnt == 0 ) return 0; + + // Check overflow and correct if required + if ( cnt > f->depth ) + { + rd_idx = _ff_correct_read_index(f, wr_idx); + cnt = f->depth; + } + + // Check if we can read something at and after offset - if too less is available we read what remains + if ( cnt < n ) n = cnt; + + uint16_t rd_ptr = idx2ptr(f->depth, rd_idx); + + // Peek data + _ff_pull_n(f, p_buffer, n, rd_ptr, copy_mode); + + return n; +} + +static uint16_t _tu_fifo_write_n(tu_fifo_t* f, const void * data, uint16_t n, tu_fifo_copy_mode_t copy_mode) +{ + if ( n == 0 ) return 0; + + _ff_lock(f->mutex_wr); + + uint16_t wr_idx = f->wr_idx; + uint16_t rd_idx = f->rd_idx; + + uint8_t const* buf8 = (uint8_t const*) data; + + TU_LOG(TU_FIFO_DBG, "rd = %3u, wr = %3u, count = %3u, remain = %3u, n = %3u: ", + rd_idx, wr_idx, _ff_count(f->depth, wr_idx, rd_idx), _ff_remaining(f->depth, wr_idx, rd_idx), n); + + if ( !f->overwritable ) + { + // limit up to full + uint16_t const remain = _ff_remaining(f->depth, wr_idx, rd_idx); + n = tu_min16(n, remain); } else { - f->count += n; + // In over-writable mode, fifo_write() is allowed even when fifo is full. In such case, + // oldest data in fifo i.e at read pointer data will be overwritten + // Note: we can modify read buffer contents but we must not modify the read index itself within a write function! + // Since it would end up in a race condition with read functions! + if ( n >= f->depth ) + { + // Only copy last part + if ( copy_mode == TU_FIFO_COPY_INC ) + { + buf8 += (n - f->depth) * f->item_size; + }else + { + // TODO should read from hw fifo to discard data, however reading an odd number could + // accidentally discard data. + } + + n = f->depth; + + // We start writing at the read pointer's position since we fill the whole buffer + wr_idx = rd_idx; + } + else + { + uint16_t const overflowable_count = _ff_count(f->depth, wr_idx, rd_idx); + if (overflowable_count + n >= 2*f->depth) + { + // Double overflowed + // Index is bigger than the allowed range [0,2*depth) + // re-position write index to have a full fifo after pushed + wr_idx = advance_index(f->depth, rd_idx, f->depth - n); + + // TODO we should also shift out n bytes from read index since we avoid changing rd index !! + // However memmove() is expensive due to actual copying + wrapping consideration. + // Also race condition could happen anyway if read() is invoke while moving result in corrupted memory + // currently deliberately not implemented --> result in incorrect data read back + }else + { + // normal + single overflowed: + // Index is in the range of [0,2*depth) and thus detect and recoverable. Recovering is handled in read() + // Therefore we just increase write index + // we will correct (re-position) read index later on in fifo_read() function + } + } } + + if (n) + { + uint16_t wr_ptr = idx2ptr(f->depth, wr_idx); + + TU_LOG(TU_FIFO_DBG, "actual_n = %u, wr_ptr = %u", n, wr_ptr); + + // Write data + _ff_push_n(f, buf8, n, wr_ptr, copy_mode); + + // Advance index + f->wr_idx = advance_index(f->depth, wr_idx, n); + + TU_LOG(TU_FIFO_DBG, "\tnew_wr = %u\r\n", f->wr_idx); + } + + _ff_unlock(f->mutex_wr); + + return n; +} + +static uint16_t _tu_fifo_read_n(tu_fifo_t* f, void * buffer, uint16_t n, tu_fifo_copy_mode_t copy_mode) +{ + _ff_lock(f->mutex_rd); + + // Peek the data + // f->rd_idx might get modified in case of an overflow so we can not use a local variable + n = _tu_fifo_peek_n(f, buffer, n, f->wr_idx, f->rd_idx, copy_mode); + + // Advance read pointer + f->rd_idx = advance_index(f->depth, f->rd_idx, n); + + _ff_unlock(f->mutex_rd); + return n; +} + +//--------------------------------------------------------------------+ +// Application API +//--------------------------------------------------------------------+ + +/******************************************************************************/ +/*! + @brief Get number of items in FIFO. + + As this function only reads the read and write pointers once, this function is + reentrant and thus thread and ISR save without any mutexes. In case an + overflow occurred, this function return f.depth at maximum. Overflows are + checked and corrected for in the read functions! + + @param[in] f + Pointer to the FIFO buffer to manipulate + + @returns Number of items in FIFO + */ +/******************************************************************************/ +uint16_t tu_fifo_count(tu_fifo_t* f) +{ + return tu_min16(_ff_count(f->depth, f->wr_idx, f->rd_idx), f->depth); +} + +/******************************************************************************/ +/*! + @brief Check if FIFO is empty. + + As this function only reads the read and write pointers once, this function is + reentrant and thus thread and ISR save without any mutexes. + + @param[in] f + Pointer to the FIFO buffer to manipulate + + @returns Number of items in FIFO + */ +/******************************************************************************/ +bool tu_fifo_empty(tu_fifo_t* f) +{ + return f->wr_idx == f->rd_idx; +} + +/******************************************************************************/ +/*! + @brief Check if FIFO is full. + + As this function only reads the read and write pointers once, this function is + reentrant and thus thread and ISR save without any mutexes. + + @param[in] f + Pointer to the FIFO buffer to manipulate + + @returns Number of items in FIFO + */ +/******************************************************************************/ +bool tu_fifo_full(tu_fifo_t* f) +{ + return _ff_count(f->depth, f->wr_idx, f->rd_idx) >= f->depth; +} + +/******************************************************************************/ +/*! + @brief Get remaining space in FIFO. + + As this function only reads the read and write pointers once, this function is + reentrant and thus thread and ISR save without any mutexes. + + @param[in] f + Pointer to the FIFO buffer to manipulate + + @returns Number of items in FIFO + */ +/******************************************************************************/ +uint16_t tu_fifo_remaining(tu_fifo_t* f) +{ + return _ff_remaining(f->depth, f->wr_idx, f->rd_idx); +} + +/******************************************************************************/ +/*! + @brief Check if overflow happened. + + BE AWARE - THIS FUNCTION MIGHT NOT GIVE A CORRECT ANSWERE IN CASE WRITE POINTER "OVERFLOWS" + Only one overflow is allowed for this function to work e.g. if depth = 100, you must not + write more than 2*depth-1 items in one rush without updating write pointer. Otherwise + write pointer wraps and your pointer states are messed up. This can only happen if you + use DMAs, write functions do not allow such an error. Avoid such nasty things! + + All reading functions (read, peek) check for overflows and correct read pointer on their own such + that latest items are read. + If required (e.g. for DMA use) you can also correct the read pointer by + tu_fifo_correct_read_pointer(). + + @param[in] f + Pointer to the FIFO buffer to manipulate + + @returns True if overflow happened + */ +/******************************************************************************/ +bool tu_fifo_overflowed(tu_fifo_t* f) +{ + return _ff_count(f->depth, f->wr_idx, f->rd_idx) > f->depth; +} + +// Only use in case tu_fifo_overflow() returned true! +void tu_fifo_correct_read_pointer(tu_fifo_t* f) +{ + _ff_lock(f->mutex_rd); + _ff_correct_read_index(f, f->wr_idx); + _ff_unlock(f->mutex_rd); } /******************************************************************************/ /*! - @brief Read one element out of the RX buffer. + @brief Read one element out of the buffer. This function will return the element located at the array index of the - read pointer, and then increment the read pointer index. If the read - pointer exceeds the maximum buffer size, it will roll over to zero. + read pointer, and then increment the read pointer index. + This function checks for an overflow and corrects read pointer if required. @param[in] f Pointer to the FIFO buffer to manipulate @@ -120,105 +693,118 @@ static inline void _ff_push(tu_fifo_t* f, void const * data, uint16_t n) Pointer to the place holder for data read from the buffer @returns TRUE if the queue is not empty -*/ + */ /******************************************************************************/ bool tu_fifo_read(tu_fifo_t* f, void * buffer) { - if( tu_fifo_empty(f) ) return false; - - tu_fifo_lock(f); + _ff_lock(f->mutex_rd); - _ff_pull(f, buffer, 1); + // Peek the data + // f->rd_idx might get modified in case of an overflow so we can not use a local variable + bool ret = _tu_fifo_peek(f, buffer, f->wr_idx, f->rd_idx); - tu_fifo_unlock(f); + // Advance pointer + f->rd_idx = advance_index(f->depth, f->rd_idx, ret); - return true; + _ff_unlock(f->mutex_rd); + return ret; } /******************************************************************************/ /*! @brief This function will read n elements from the array index specified by - the read pointer and increment the read index. If the read index - exceeds the max buffer size, then it will roll over to zero. + the read pointer and increment the read index. + This function checks for an overflow and corrects read pointer if required. @param[in] f Pointer to the FIFO buffer to manipulate @param[in] buffer The pointer to data location - @param[in] count + @param[in] n Number of element that buffer can afford @returns number of items read from the FIFO -*/ + */ /******************************************************************************/ -uint16_t tu_fifo_read_n (tu_fifo_t* f, void * buffer, uint16_t count) +uint16_t tu_fifo_read_n(tu_fifo_t* f, void * buffer, uint16_t n) { - if(tu_fifo_empty(f)) return 0; - - tu_fifo_lock(f); - - // Limit up to fifo's count - if(count > f->count) count = f->count; - - if(count + f->rd_idx <= f->depth) - { - _ff_pull(f, buffer, count); - } - else - { - uint16_t const part1 = f->depth - f->rd_idx; - - // Part 1: from rd_idx to end - _ff_pull(f, buffer, part1); - buffer = ((uint8_t*) buffer) + part1*f->item_size; + return _tu_fifo_read_n(f, buffer, n, TU_FIFO_COPY_INC); +} - // Part 2: start to remaining - _ff_pull(f, buffer, count-part1); - } +#ifdef TUP_MEM_CONST_ADDR +/******************************************************************************/ +/*! + @brief This function will read n elements from the array index specified by + the read pointer and increment the read index. + This function checks for an overflow and corrects read pointer if required. + The dest address will not be incremented which is useful for writing to registers. - tu_fifo_unlock(f); + @param[in] f + Pointer to the FIFO buffer to manipulate + @param[in] buffer + The pointer to data location + @param[in] n + Number of element that buffer can afford - return count; + @returns number of items read from the FIFO + */ +/******************************************************************************/ +uint16_t tu_fifo_read_n_const_addr_full_words(tu_fifo_t* f, void * buffer, uint16_t n) +{ + return _tu_fifo_read_n(f, buffer, n, TU_FIFO_COPY_CST_FULL_WORDS); } +#endif /******************************************************************************/ /*! - @brief Read one item without removing it from the FIFO + @brief Read one item without removing it from the FIFO. + This function checks for an overflow and corrects read pointer if required. @param[in] f Pointer to the FIFO buffer to manipulate - @param[in] pos - Position to read from in the FIFO buffer @param[in] p_buffer Pointer to the place holder for data read from the buffer @returns TRUE if the queue is not empty -*/ + */ /******************************************************************************/ -bool tu_fifo_peek_at(tu_fifo_t* f, uint16_t pos, void * p_buffer) +bool tu_fifo_peek(tu_fifo_t* f, void * p_buffer) { - if ( pos >= f->count ) return false; - - tu_fifo_lock(f); + _ff_lock(f->mutex_rd); + bool ret = _tu_fifo_peek(f, p_buffer, f->wr_idx, f->rd_idx); + _ff_unlock(f->mutex_rd); + return ret; +} - // rd_idx is pos=0 - uint16_t index = _ff_mod(f->rd_idx + pos, f->depth); - memcpy(p_buffer, - f->buffer + (index * f->item_size), - f->item_size); +/******************************************************************************/ +/*! + @brief Read n items without removing it from the FIFO + This function checks for an overflow and corrects read pointer if required. - tu_fifo_unlock(f); + @param[in] f + Pointer to the FIFO buffer to manipulate + @param[in] p_buffer + Pointer to the place holder for data read from the buffer + @param[in] n + Number of items to peek - return true; + @returns Number of bytes written to p_buffer + */ +/******************************************************************************/ +uint16_t tu_fifo_peek_n(tu_fifo_t* f, void * p_buffer, uint16_t n) +{ + _ff_lock(f->mutex_rd); + uint16_t ret = _tu_fifo_peek_n(f, p_buffer, n, f->wr_idx, f->rd_idx, TU_FIFO_COPY_INC); + _ff_unlock(f->mutex_rd); + return ret; } /******************************************************************************/ /*! - @brief Write one element into the RX buffer. + @brief Write one element into the buffer. This function will write one element into the array index specified by - the write pointer and increment the write index. If the write index - exceeds the max buffer size, then it will roll over to zero. + the write pointer and increment the write index. @param[in] f Pointer to the FIFO buffer to manipulate @@ -227,26 +813,40 @@ bool tu_fifo_peek_at(tu_fifo_t* f, uint16_t pos, void * p_buffer) @returns TRUE if the data was written to the FIFO (overwrittable FIFO will always return TRUE) -*/ + */ /******************************************************************************/ -bool tu_fifo_write (tu_fifo_t* f, const void * data) +bool tu_fifo_write(tu_fifo_t* f, const void * data) { - if ( tu_fifo_full(f) && !f->overwritable ) return false; + _ff_lock(f->mutex_wr); - tu_fifo_lock(f); + bool ret; + uint16_t const wr_idx = f->wr_idx; - _ff_push(f, data, 1); + if ( tu_fifo_full(f) && !f->overwritable ) + { + ret = false; + }else + { + uint16_t wr_ptr = idx2ptr(f->depth, wr_idx); - tu_fifo_unlock(f); + // Write data + _ff_push(f, data, wr_ptr); - return true; + // Advance pointer + f->wr_idx = advance_index(f->depth, wr_idx, 1); + + ret = true; + } + + _ff_unlock(f->mutex_wr); + + return ret; } /******************************************************************************/ /*! @brief This function will write n elements into the array index specified by - the write pointer and increment the write index. If the write index - exceeds the max buffer size, then it will roll over to zero. + the write pointer and increment the write index. @param[in] f Pointer to the FIFO buffer to manipulate @@ -255,67 +855,240 @@ bool tu_fifo_write (tu_fifo_t* f, const void * data) @param[in] count Number of element @return Number of written elements -*/ + */ /******************************************************************************/ -uint16_t tu_fifo_write_n (tu_fifo_t* f, const void * data, uint16_t count) +uint16_t tu_fifo_write_n(tu_fifo_t* f, const void * data, uint16_t n) { - if ( count == 0 ) return 0; + return _tu_fifo_write_n(f, data, n, TU_FIFO_COPY_INC); +} - tu_fifo_lock(f); +#ifdef TUP_MEM_CONST_ADDR +/******************************************************************************/ +/*! + @brief This function will write n elements into the array index specified by + the write pointer and increment the write index. The source address will + not be incremented which is useful for reading from registers. - uint8_t const* buf8 = (uint8_t const*) data; + @param[in] f + Pointer to the FIFO buffer to manipulate + @param[in] data + The pointer to data to add to the FIFO + @param[in] count + Number of element + @return Number of written elements + */ +/******************************************************************************/ +uint16_t tu_fifo_write_n_const_addr_full_words(tu_fifo_t* f, const void * data, uint16_t n) +{ + return _tu_fifo_write_n(f, data, n, TU_FIFO_COPY_CST_FULL_WORDS); +} +#endif + +/******************************************************************************/ +/*! + @brief Clear the fifo read and write pointers + + @param[in] f + Pointer to the FIFO buffer to manipulate + */ +/******************************************************************************/ +bool tu_fifo_clear(tu_fifo_t *f) +{ + _ff_lock(f->mutex_wr); + _ff_lock(f->mutex_rd); + + f->rd_idx = 0; + f->wr_idx = 0; + + _ff_unlock(f->mutex_wr); + _ff_unlock(f->mutex_rd); + return true; +} + +/******************************************************************************/ +/*! + @brief Change the fifo mode to overwritable or not overwritable + + @param[in] f + Pointer to the FIFO buffer to manipulate + @param[in] overwritable + Overwritable mode the fifo is set to + */ +/******************************************************************************/ +bool tu_fifo_set_overwritable(tu_fifo_t *f, bool overwritable) { + if (f->overwritable == overwritable) { + return true; + } + + _ff_lock(f->mutex_wr); + _ff_lock(f->mutex_rd); + + f->overwritable = overwritable; + + _ff_unlock(f->mutex_wr); + _ff_unlock(f->mutex_rd); + + return true; +} + +/******************************************************************************/ +/*! + @brief Advance write pointer - intended to be used in combination with DMA. + It is possible to fill the FIFO by use of a DMA in circular mode. Within + DMA ISRs you may update the write pointer to be able to read from the FIFO. + As long as the DMA is the only process writing into the FIFO this is safe + to use. + + USE WITH CARE - WE DO NOT CONDUCT SAFETY CHECKS HERE! + + @param[in] f + Pointer to the FIFO buffer to manipulate + @param[in] n + Number of items the write pointer moves forward + */ +/******************************************************************************/ +void tu_fifo_advance_write_pointer(tu_fifo_t *f, uint16_t n) +{ + f->wr_idx = advance_index(f->depth, f->wr_idx, n); +} + +/******************************************************************************/ +/*! + @brief Advance read pointer - intended to be used in combination with DMA. + It is possible to read from the FIFO by use of a DMA in linear mode. Within + DMA ISRs you may update the read pointer to be able to again write into the + FIFO. As long as the DMA is the only process reading from the FIFO this is + safe to use. + + USE WITH CARE - WE DO NOT CONDUCT SAFETY CHECKS HERE! + + @param[in] f + Pointer to the FIFO buffer to manipulate + @param[in] n + Number of items the read pointer moves forward + */ +/******************************************************************************/ +void tu_fifo_advance_read_pointer(tu_fifo_t *f, uint16_t n) +{ + f->rd_idx = advance_index(f->depth, f->rd_idx, n); +} + +/******************************************************************************/ +/*! + @brief Get read info + + Returns the length and pointer from which bytes can be read in a linear manner. + This is of major interest for DMA transmissions. If returned length is zero the + corresponding pointer is invalid. + The read pointer does NOT get advanced, use tu_fifo_advance_read_pointer() to + do so! + @param[in] f + Pointer to FIFO + @param[out] *info + Pointer to struct which holds the desired infos + */ +/******************************************************************************/ +void tu_fifo_get_read_info(tu_fifo_t *f, tu_fifo_buffer_info_t *info) +{ + // Operate on temporary values in case they change in between + uint16_t wr_idx = f->wr_idx; + uint16_t rd_idx = f->rd_idx; + + uint16_t cnt = _ff_count(f->depth, wr_idx, rd_idx); - if (!f->overwritable) + // Check overflow and correct if required - may happen in case a DMA wrote too fast + if (cnt > f->depth) { - // Not overwritable limit up to full - count = tu_min16(count, tu_fifo_remaining(f)); + _ff_lock(f->mutex_rd); + rd_idx = _ff_correct_read_index(f, wr_idx); + _ff_unlock(f->mutex_rd); + + cnt = f->depth; } - else if (count > f->depth) + + // Check if fifo is empty + if (cnt == 0) { - // Only copy last part - buf8 = buf8 + (count - f->depth) * f->item_size; - count = f->depth; - f->wr_idx = 0; - f->rd_idx = 0; - f->count = 0; + info->len_lin = 0; + info->len_wrap = 0; + info->ptr_lin = NULL; + info->ptr_wrap = NULL; + return; } - if (count + f->wr_idx <= f->depth ) + // Get relative pointers + uint16_t wr_ptr = idx2ptr(f->depth, wr_idx); + uint16_t rd_ptr = idx2ptr(f->depth, rd_idx); + + // Copy pointer to buffer to start reading from + info->ptr_lin = &f->buffer[rd_ptr]; + + // Check if there is a wrap around necessary + if (wr_ptr > rd_ptr) { - _ff_push(f, buf8, count); + // Non wrapping case + info->len_lin = cnt; + + info->len_wrap = 0; + info->ptr_wrap = NULL; } else { - uint16_t const part1 = f->depth - f->wr_idx; - - // Part 1: from wr_idx to end - _ff_push(f, buf8, part1); - buf8 += part1*f->item_size; + info->len_lin = f->depth - rd_ptr; // Also the case if FIFO was full - // Part 2: start to remaining - _ff_push(f, buf8, count-part1); + info->len_wrap = cnt - info->len_lin; + info->ptr_wrap = f->buffer; } - - tu_fifo_unlock(f); - - return count; } /******************************************************************************/ /*! - @brief Clear the fifo read and write pointers and set length to zero - - @param[in] f - Pointer to the FIFO buffer to manipulate -*/ + @brief Get linear write info + + Returns the length and pointer to which bytes can be written into FIFO in a linear manner. + This is of major interest for DMA transmissions not using circular mode. If a returned length is zero the + corresponding pointer is invalid. The returned lengths summed up are the currently free space in the FIFO. + The write pointer does NOT get advanced, use tu_fifo_advance_write_pointer() to do so! + TAKE CARE TO NOT OVERFLOW THE BUFFER MORE THAN TWO TIMES THE FIFO DEPTH - IT CAN NOT RECOVERE OTHERWISE! + @param[in] f + Pointer to FIFO + @param[out] *info + Pointer to struct which holds the desired infos + */ /******************************************************************************/ -bool tu_fifo_clear(tu_fifo_t *f) +void tu_fifo_get_write_info(tu_fifo_t *f, tu_fifo_buffer_info_t *info) { - tu_fifo_lock(f); + uint16_t wr_idx = f->wr_idx; + uint16_t rd_idx = f->rd_idx; + uint16_t remain = _ff_remaining(f->depth, wr_idx, rd_idx); - f->rd_idx = f->wr_idx = f->count = 0; + if (remain == 0) + { + info->len_lin = 0; + info->len_wrap = 0; + info->ptr_lin = NULL; + info->ptr_wrap = NULL; + return; + } - tu_fifo_unlock(f); + // Get relative pointers + uint16_t wr_ptr = idx2ptr(f->depth, wr_idx); + uint16_t rd_ptr = idx2ptr(f->depth, rd_idx); - return true; + // Copy pointer to buffer to start writing to + info->ptr_lin = &f->buffer[wr_ptr]; + + if (wr_ptr < rd_ptr) + { + // Non wrapping case + info->len_lin = rd_ptr-wr_ptr; + info->len_wrap = 0; + info->ptr_wrap = NULL; + } + else + { + info->len_lin = f->depth - wr_ptr; + info->len_wrap = remain - info->len_lin; // Remaining length - n already was limited to remain or FIFO depth + info->ptr_wrap = f->buffer; // Always start of buffer + } } diff --git a/cores/nRF5/TinyUSB/Adafruit_TinyUSB_ArduinoCore/tinyusb/src/common/tusb_fifo.h b/cores/nRF5/TinyUSB/Adafruit_TinyUSB_ArduinoCore/tinyusb/src/common/tusb_fifo.h index fb0c896..879acda 100644 --- a/cores/nRF5/TinyUSB/Adafruit_TinyUSB_ArduinoCore/tinyusb/src/common/tusb_fifo.h +++ b/cores/nRF5/TinyUSB/Adafruit_TinyUSB_ArduinoCore/tinyusb/src/common/tusb_fifo.h @@ -2,6 +2,7 @@ * The MIT License (MIT) * * Copyright (c) 2019 Ha Thach (tinyusb.org) + * Copyright (c) 2020 Reinhard Panhuber - rework to unmasked pointers * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -24,108 +25,175 @@ * This file is part of the TinyUSB stack. */ -/** \ingroup Group_Common - * \defgroup group_fifo fifo - * @{ */ - #ifndef _TUSB_FIFO_H_ #define _TUSB_FIFO_H_ -// mutex is only needed for RTOS -// for OS None, we don't get preempted -#define CFG_FIFO_MUTEX (CFG_TUSB_OS != OPT_OS_NONE) - -#include -#include - #ifdef __cplusplus - extern "C" { +extern "C" { #endif -#if CFG_FIFO_MUTEX -#define tu_fifo_mutex_t osal_mutex_t -#endif +// Due to the use of unmasked pointers, this FIFO does not suffer from losing +// one item slice. Furthermore, write and read operations are completely +// decoupled as write and read functions do not modify a common state. Henceforth, +// writing or reading from the FIFO within an ISR is safe as long as no other +// process (thread or ISR) interferes. +// Also, this FIFO is ready to be used in combination with a DMA as the write and +// read pointers can be updated from within a DMA ISR. Overflows are detectable +// within a certain number (see tu_fifo_overflow()). + +#include "common/tusb_common.h" +#include "osal/osal.h" +// mutex is only needed for RTOS +// for OS None, we don't get preempted +#define CFG_FIFO_MUTEX OSAL_MUTEX_REQUIRED -/** \struct tu_fifo_t - * \brief Simple Circular FIFO +/* Write/Read index is always in the range of: + * 0 .. 2*depth-1 + * The extra window allow us to determine the fifo state of empty or full with only 2 indices + * Following are examples with depth = 3 + * + * - empty: W = R + * | + * ------------------------- + * | 0 | RW| 2 | 3 | 4 | 5 | + * + * - full 1: W > R + * | + * ------------------------- + * | 0 | R | 2 | 3 | W | 5 | + * + * - full 2: W < R + * | + * ------------------------- + * | 0 | 1 | W | 3 | 4 | R | + * + * - Number of items in the fifo can be determined in either cases: + * - case W >= R: Count = W - R + * - case W < R: Count = 2*depth - (R - W) + * + * In non-overwritable mode, computed Count (in above 2 cases) is at most equal to depth. + * However, in over-writable mode, write index can be repeatedly increased and count can be + * temporarily larger than depth (overflowed condition) e.g + * + * - Overflowed 1: write(3), write(1) + * In this case we will adjust Read index when read()/peek() is called so that count = depth. + * | + * ------------------------- + * | R | 1 | 2 | 3 | W | 5 | + * + * - Double Overflowed i.e index is out of allowed range [0,2*depth) + * This occurs when we continue to write after 1st overflowed to 2nd overflowed. e.g: + * write(3), write(1), write(2) + * This must be prevented since it will cause unrecoverable state, in above example + * if not handled the fifo will be empty instead of continue-to-be full. Since we must not modify + * read index in write() function, which cause race condition. We will re-position write index so that + * after data is written it is a full fifo i.e W = depth - R + * + * re-position W = 1 before write(2) + * Note: we should also move data from mem[3] to read index as well, but deliberately skipped here + * since it is an expensive operation !!! + * | + * ------------------------- + * | R | W | 2 | 3 | 4 | 5 | + * + * perform write(2), result is still a full fifo. + * + * | + * ------------------------- + * | R | 1 | 2 | W | 4 | 5 | */ -typedef struct -{ - uint8_t* buffer ; ///< buffer pointer - uint16_t depth ; ///< max items - uint16_t item_size ; ///< size of each item - bool overwritable ; - - volatile uint16_t count ; ///< number of items in queue - volatile uint16_t wr_idx ; ///< write pointer - volatile uint16_t rd_idx ; ///< read pointer - -#if CFG_FIFO_MUTEX - tu_fifo_mutex_t mutex; +typedef struct { + uint8_t* buffer ; // buffer pointer + uint16_t depth ; // max items + + struct TU_ATTR_PACKED { + uint16_t item_size : 15; // size of each item + bool overwritable : 1 ; // ovwerwritable when full + }; + + volatile uint16_t wr_idx ; // write index + volatile uint16_t rd_idx ; // read index + +#if OSAL_MUTEX_REQUIRED + osal_mutex_t mutex_wr; + osal_mutex_t mutex_rd; #endif } tu_fifo_t; -#define TU_FIFO_DEF(_name, _depth, _type, _overwritable) \ - uint8_t _name##_buf[_depth*sizeof(_type)]; \ - tu_fifo_t _name = { \ - .buffer = _name##_buf, \ - .depth = _depth, \ - .item_size = sizeof(_type), \ - .overwritable = _overwritable, \ - } +typedef struct { + uint16_t len_lin ; ///< linear length in item size + uint16_t len_wrap ; ///< wrapped length in item size + void * ptr_lin ; ///< linear part start pointer + void * ptr_wrap ; ///< wrapped part start pointer +} tu_fifo_buffer_info_t; + +#define TU_FIFO_INIT(_buffer, _depth, _type, _overwritable){\ + .buffer = _buffer, \ + .depth = _depth, \ + .item_size = sizeof(_type), \ + .overwritable = _overwritable, \ +} +#define TU_FIFO_DEF(_name, _depth, _type, _overwritable) \ + uint8_t _name##_buf[_depth*sizeof(_type)]; \ + tu_fifo_t _name = TU_FIFO_INIT(_name##_buf, _depth, _type, _overwritable) + +bool tu_fifo_set_overwritable(tu_fifo_t *f, bool overwritable); bool tu_fifo_clear(tu_fifo_t *f); bool tu_fifo_config(tu_fifo_t *f, void* buffer, uint16_t depth, uint16_t item_size, bool overwritable); -#if CFG_FIFO_MUTEX -static inline void tu_fifo_config_mutex(tu_fifo_t *f, tu_fifo_mutex_t mutex_hdl) -{ - f->mutex = mutex_hdl; +#if OSAL_MUTEX_REQUIRED +TU_ATTR_ALWAYS_INLINE static inline +void tu_fifo_config_mutex(tu_fifo_t *f, osal_mutex_t wr_mutex, osal_mutex_t rd_mutex) { + f->mutex_wr = wr_mutex; + f->mutex_rd = rd_mutex; } +#else +#define tu_fifo_config_mutex(_f, _wr_mutex, _rd_mutex) #endif -bool tu_fifo_write (tu_fifo_t* f, void const * p_data); -uint16_t tu_fifo_write_n (tu_fifo_t* f, void const * p_data, uint16_t count); - -bool tu_fifo_read (tu_fifo_t* f, void * p_buffer); -uint16_t tu_fifo_read_n (tu_fifo_t* f, void * p_buffer, uint16_t count); - -bool tu_fifo_peek_at (tu_fifo_t* f, uint16_t pos, void * p_buffer); +bool tu_fifo_write (tu_fifo_t* f, void const * data); +uint16_t tu_fifo_write_n (tu_fifo_t* f, void const * data, uint16_t n); +#ifdef TUP_MEM_CONST_ADDR +uint16_t tu_fifo_write_n_const_addr_full_words (tu_fifo_t* f, const void * data, uint16_t n); +#endif -static inline bool tu_fifo_peek(tu_fifo_t* f, void * p_buffer) -{ - return tu_fifo_peek_at(f, 0, p_buffer); -} +bool tu_fifo_read (tu_fifo_t* f, void * buffer); +uint16_t tu_fifo_read_n (tu_fifo_t* f, void * buffer, uint16_t n); +#ifdef TUP_MEM_CONST_ADDR +uint16_t tu_fifo_read_n_const_addr_full_words (tu_fifo_t* f, void * buffer, uint16_t n); +#endif -static inline bool tu_fifo_empty(tu_fifo_t* f) -{ - return (f->count == 0); -} +bool tu_fifo_peek (tu_fifo_t* f, void * p_buffer); +uint16_t tu_fifo_peek_n (tu_fifo_t* f, void * p_buffer, uint16_t n); -static inline bool tu_fifo_full(tu_fifo_t* f) -{ - return (f->count == f->depth); -} +uint16_t tu_fifo_count (tu_fifo_t* f); +uint16_t tu_fifo_remaining (tu_fifo_t* f); +bool tu_fifo_empty (tu_fifo_t* f); +bool tu_fifo_full (tu_fifo_t* f); +bool tu_fifo_overflowed (tu_fifo_t* f); +void tu_fifo_correct_read_pointer (tu_fifo_t* f); -static inline uint16_t tu_fifo_count(tu_fifo_t* f) -{ - return f->count; +TU_ATTR_ALWAYS_INLINE static inline +uint16_t tu_fifo_depth(tu_fifo_t* f) { + return f->depth; } -static inline uint16_t tu_fifo_remaining(tu_fifo_t* f) -{ - return f->depth - f->count; -} +// Pointer modifications intended to be used in combinations with DMAs. +// USE WITH CARE - NO SAFETY CHECKS CONDUCTED HERE! NOT MUTEX PROTECTED! +void tu_fifo_advance_write_pointer(tu_fifo_t *f, uint16_t n); +void tu_fifo_advance_read_pointer (tu_fifo_t *f, uint16_t n); -static inline uint16_t tu_fifo_depth(tu_fifo_t* f) -{ - return f->depth; -} +// If you want to read/write from/to the FIFO by use of a DMA, you may need to conduct two copies +// to handle a possible wrapping part. These functions deliver a pointer to start +// reading/writing from/to and a valid linear length along which no wrap occurs. +void tu_fifo_get_read_info (tu_fifo_t *f, tu_fifo_buffer_info_t *info); +void tu_fifo_get_write_info(tu_fifo_t *f, tu_fifo_buffer_info_t *info); #ifdef __cplusplus - } +} #endif #endif /* _TUSB_FIFO_H_ */ diff --git a/cores/nRF5/TinyUSB/Adafruit_TinyUSB_ArduinoCore/tinyusb/src/common/tusb_mcu.h b/cores/nRF5/TinyUSB/Adafruit_TinyUSB_ArduinoCore/tinyusb/src/common/tusb_mcu.h new file mode 100644 index 0000000..1c11df1 --- /dev/null +++ b/cores/nRF5/TinyUSB/Adafruit_TinyUSB_ArduinoCore/tinyusb/src/common/tusb_mcu.h @@ -0,0 +1,669 @@ +/* + * The MIT License (MIT) + * + * Copyright (c) 2021, Ha Thach (tinyusb.org) + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + * This file is part of the TinyUSB stack. + */ + +#ifndef TUSB_MCU_H_ +#define TUSB_MCU_H_ + +//--------------------------------------------------------------------+ +// Port/Platform Specific +// TUP stand for TinyUSB Port/Platform (can be renamed) +//--------------------------------------------------------------------+ + +//------------- Unaligned Memory Access -------------// + +#ifdef __ARM_ARCH + // ARM Architecture set __ARM_FEATURE_UNALIGNED to 1 for mcu supports unaligned access + #if defined(__ARM_FEATURE_UNALIGNED) && __ARM_FEATURE_UNALIGNED == 1 + #define TUP_ARCH_STRICT_ALIGN 0 + #else + #define TUP_ARCH_STRICT_ALIGN 1 + #endif +#else + // TODO default to strict align for others + // Should investigate other architecture such as risv, xtensa, mips for optimal setting + #define TUP_ARCH_STRICT_ALIGN 1 +#endif + +/* USB Controller Attributes for Device, Host or MCU (both) + * - ENDPOINT_MAX: max (logical) number of endpoint + * - ENDPOINT_EXCLUSIVE_NUMBER: endpoint number with different direction IN and OUT aren't allowed, + * e.g EP1 OUT & EP1 IN cannot exist together + * - RHPORT_HIGHSPEED: support highspeed with on-chip PHY + */ + +//--------------------------------------------------------------------+ +// NXP +//--------------------------------------------------------------------+ +#if TU_CHECK_MCU(OPT_MCU_LPC11UXX, OPT_MCU_LPC13XX, OPT_MCU_LPC15XX) + #define TUP_USBIP_IP3511 + #define TUP_DCD_ENDPOINT_MAX 5 + +#elif TU_CHECK_MCU(OPT_MCU_LPC175X_6X, OPT_MCU_LPC177X_8X, OPT_MCU_LPC40XX) + #define TUP_DCD_ENDPOINT_MAX 16 + #define TUP_USBIP_OHCI + #define TUP_OHCI_RHPORTS 2 + +#elif TU_CHECK_MCU(OPT_MCU_LPC51UXX) + #define TUP_USBIP_IP3511 + #define TUP_DCD_ENDPOINT_MAX 5 + +#elif TU_CHECK_MCU(OPT_MCU_LPC54) + // TODO USB0 has 5, USB1 has 6 + #define TUP_USBIP_IP3511 + #define TUP_DCD_ENDPOINT_MAX 6 + +#elif TU_CHECK_MCU(OPT_MCU_LPC55) + // TODO USB0 has 5, USB1 has 6 + #define TUP_USBIP_IP3511 + #define TUP_DCD_ENDPOINT_MAX 6 + +#elif TU_CHECK_MCU(OPT_MCU_LPC18XX, OPT_MCU_LPC43XX) + // USB0 has 6 with HS PHY, USB1 has 4 only FS + #define TUP_USBIP_CHIPIDEA_HS + #define TUP_USBIP_EHCI + + #define TUP_DCD_ENDPOINT_MAX 6 + #define TUP_RHPORT_HIGHSPEED 1 + +#elif TU_CHECK_MCU(OPT_MCU_MCXN9) + // USB0 is chipidea FS + #define TUP_USBIP_CHIPIDEA_FS + #define TUP_USBIP_CHIPIDEA_FS_MCX + + // USB1 is chipidea HS + #define TUP_USBIP_CHIPIDEA_HS + #define TUP_USBIP_EHCI + + #define TUP_DCD_ENDPOINT_MAX 8 + #define TUP_RHPORT_HIGHSPEED 1 + +#elif TU_CHECK_MCU(OPT_MCU_MCXA15) + // USB0 is chipidea FS + #define TUP_USBIP_CHIPIDEA_FS + #define TUP_USBIP_CHIPIDEA_FS_MCX + + #define TUP_DCD_ENDPOINT_MAX 16 + +#elif TU_CHECK_MCU(OPT_MCU_MIMXRT1XXX) + #include "fsl_device_registers.h" + + #define TUP_USBIP_CHIPIDEA_HS + #define TUP_USBIP_EHCI + + #define TUP_DCD_ENDPOINT_MAX 8 + #define TUP_RHPORT_HIGHSPEED 1 + + #if __CORTEX_M == 7 + #define CFG_TUD_MEM_DCACHE_ENABLE_DEFAULT 1 + #define CFG_TUH_MEM_DCACHE_ENABLE_DEFAULT 1 + #define CFG_TUSB_MEM_DCACHE_LINE_SIZE_DEFAULT 32 + #endif + +#elif TU_CHECK_MCU(OPT_MCU_KINETIS_KL, OPT_MCU_KINETIS_K32L, OPT_MCU_KINETIS_K) + #define TUP_USBIP_CHIPIDEA_FS + #define TUP_USBIP_CHIPIDEA_FS_KINETIS + #define TUP_DCD_ENDPOINT_MAX 16 + +#elif TU_CHECK_MCU(OPT_MCU_MM32F327X) + #define TUP_DCD_ENDPOINT_MAX 16 + +//--------------------------------------------------------------------+ +// Nordic +//--------------------------------------------------------------------+ +#elif TU_CHECK_MCU(OPT_MCU_NRF5X) + // 8 CBI + 1 ISO + #define TUP_DCD_ENDPOINT_MAX 9 + +//--------------------------------------------------------------------+ +// Microchip +//--------------------------------------------------------------------+ +#elif TU_CHECK_MCU(OPT_MCU_SAMD21, OPT_MCU_SAMD51, OPT_MCU_SAME5X) || \ + TU_CHECK_MCU(OPT_MCU_SAMD11, OPT_MCU_SAML21, OPT_MCU_SAML22) + #define TUP_DCD_ENDPOINT_MAX 8 + +#elif TU_CHECK_MCU(OPT_MCU_SAMG) + #define TUP_DCD_ENDPOINT_MAX 6 + #define TUD_ENDPOINT_ONE_DIRECTION_ONLY + +#elif TU_CHECK_MCU(OPT_MCU_SAMX7X) + #define TUP_DCD_ENDPOINT_MAX 10 + #define TUP_RHPORT_HIGHSPEED 1 + #define TUD_ENDPOINT_ONE_DIRECTION_ONLY + +#elif TU_CHECK_MCU(OPT_MCU_PIC32MZ) + #define TUP_DCD_ENDPOINT_MAX 8 + #define TUD_ENDPOINT_ONE_DIRECTION_ONLY + +#elif TU_CHECK_MCU(OPT_MCU_PIC32MX, OPT_MCU_PIC32MM, OPT_MCU_PIC32MK) || \ + TU_CHECK_MCU(OPT_MCU_PIC24, OPT_MCU_DSPIC33) + #define TUP_DCD_ENDPOINT_MAX 16 + #define TUD_ENDPOINT_ONE_DIRECTION_ONLY + +//--------------------------------------------------------------------+ +// ST +//--------------------------------------------------------------------+ +#elif TU_CHECK_MCU(OPT_MCU_STM32F0) + #define TUP_USBIP_FSDEV + #define TUP_USBIP_FSDEV_STM32 + #define TUP_DCD_ENDPOINT_MAX 8 + +#elif TU_CHECK_MCU(OPT_MCU_STM32F1) + // - F102, F103 use fsdev + // - F105, F107 use dwc2 + #if defined (STM32F105x8) || defined (STM32F105xB) || defined (STM32F105xC) || \ + defined (STM32F107xB) || defined (STM32F107xC) + #define TUP_USBIP_DWC2 + #define TUP_USBIP_DWC2_STM32 + #define CFG_TUH_DWC2_DMA_ENABLE_DEFAULT 0 + + #define TUP_DCD_ENDPOINT_MAX 4 + #elif defined(STM32F102x6) || defined(STM32F102xB) || \ + defined(STM32F103x6) || defined(STM32F103xB) || defined(STM32F103xE) || defined(STM32F103xG) + #define TUP_USBIP_FSDEV + #define TUP_USBIP_FSDEV_STM32 + #define TUP_DCD_ENDPOINT_MAX 8 + #else + #error "Unsupported STM32F1 mcu" + #endif + +#elif TU_CHECK_MCU(OPT_MCU_STM32F2) + #define TUP_USBIP_DWC2 + #define TUP_USBIP_DWC2_STM32 + + // FS has 4 ep, HS has 5 ep + #define TUP_DCD_ENDPOINT_MAX 6 + +#elif TU_CHECK_MCU(OPT_MCU_STM32F3) + #define TUP_USBIP_FSDEV + #define TUP_USBIP_FSDEV_STM32 + #define TUP_DCD_ENDPOINT_MAX 8 + +#elif TU_CHECK_MCU(OPT_MCU_STM32F4) + #define TUP_USBIP_DWC2 + #define TUP_USBIP_DWC2_STM32 + + // For most mcu, FS has 4, HS has 6. TODO 446/469/479 HS has 9 + #define TUP_DCD_ENDPOINT_MAX 6 + +#elif TU_CHECK_MCU(OPT_MCU_STM32F7) + #define TUP_USBIP_DWC2 + #define TUP_USBIP_DWC2_STM32 + + // FS has 6, HS has 9 + #define TUP_DCD_ENDPOINT_MAX 9 + + // MCU with on-chip HS Phy + #if defined(STM32F723xx) || defined(STM32F730xx) || defined(STM32F733xx) + #define TUP_RHPORT_HIGHSPEED 1 // Port0: FS, Port1: HS + #endif + + // Enable dcache if DMA is enabled + #define CFG_TUD_MEM_DCACHE_ENABLE_DEFAULT CFG_TUD_DWC2_DMA_ENABLE + #define CFG_TUH_MEM_DCACHE_ENABLE_DEFAULT CFG_TUH_DWC2_DMA_ENABLE + #define CFG_TUSB_MEM_DCACHE_LINE_SIZE_DEFAULT 32 + +#elif TU_CHECK_MCU(OPT_MCU_STM32H7) + #include "stm32h7xx.h" + #define TUP_USBIP_DWC2 + #define TUP_USBIP_DWC2_STM32 + + #define TUP_DCD_ENDPOINT_MAX 9 + + #if __CORTEX_M == 7 + // Enable dcache if DMA is enabled + #define CFG_TUD_MEM_DCACHE_ENABLE_DEFAULT CFG_TUD_DWC2_DMA_ENABLE + #define CFG_TUH_MEM_DCACHE_ENABLE_DEFAULT CFG_TUH_DWC2_DMA_ENABLE + #define CFG_TUSB_MEM_DCACHE_LINE_SIZE_DEFAULT 32 + #endif + +#elif TU_CHECK_MCU(OPT_MCU_STM32H5) + #define TUP_USBIP_FSDEV + #define TUP_USBIP_FSDEV_STM32 + #define TUP_DCD_ENDPOINT_MAX 8 + +#elif TU_CHECK_MCU(OPT_MCU_STM32G4) + // Device controller + #define TUP_USBIP_FSDEV + #define TUP_USBIP_FSDEV_STM32 + + // TypeC controller + #define TUP_USBIP_TYPEC_STM32 + #define TUP_DCD_ENDPOINT_MAX 8 + #define TUP_TYPEC_RHPORTS_NUM 1 + +#elif TU_CHECK_MCU(OPT_MCU_STM32G0) + #define TUP_USBIP_FSDEV + #define TUP_USBIP_FSDEV_STM32 + #define TUP_DCD_ENDPOINT_MAX 8 + +#elif TU_CHECK_MCU(OPT_MCU_STM32C0) + #define TUP_USBIP_FSDEV + #define TUP_USBIP_FSDEV_STM32 + #define TUP_DCD_ENDPOINT_MAX 8 + +#elif TU_CHECK_MCU(OPT_MCU_STM32L0, OPT_MCU_STM32L1) + #define TUP_USBIP_FSDEV + #define TUP_USBIP_FSDEV_STM32 + #define TUP_DCD_ENDPOINT_MAX 8 + +#elif TU_CHECK_MCU(OPT_MCU_STM32L4) + // - L4x2, L4x3 use fsdev + // - L4x4, L4x6, L4x7, L4x9 use dwc2 + #if defined (STM32L475xx) || defined (STM32L476xx) || \ + defined (STM32L485xx) || defined (STM32L486xx) || defined (STM32L496xx) || \ + defined (STM32L4A6xx) || defined (STM32L4P5xx) || defined (STM32L4Q5xx) || \ + defined (STM32L4R5xx) || defined (STM32L4R7xx) || defined (STM32L4R9xx) || \ + defined (STM32L4S5xx) || defined (STM32L4S7xx) || defined (STM32L4S9xx) + #define TUP_USBIP_DWC2 + #define TUP_USBIP_DWC2_STM32 + + #define TUP_DCD_ENDPOINT_MAX 6 + #elif defined(STM32L412xx) || defined(STM32L422xx) || defined(STM32L432xx) || defined(STM32L433xx) || \ + defined(STM32L442xx) || defined(STM32L443xx) || defined(STM32L452xx) || defined(STM32L462xx) + #define TUP_USBIP_FSDEV + #define TUP_USBIP_FSDEV_STM32 + #define TUP_DCD_ENDPOINT_MAX 8 + #else + #error "Unsupported STM32L4 mcu" + #endif + +#elif TU_CHECK_MCU(OPT_MCU_STM32WB) + #define TUP_USBIP_FSDEV + #define TUP_USBIP_FSDEV_STM32 + #define TUP_DCD_ENDPOINT_MAX 8 + +#elif TU_CHECK_MCU(OPT_MCU_STM32WBA) + #define TUP_USBIP_DWC2 + #define TUP_USBIP_DWC2_STM32 + #define TUP_DCD_ENDPOINT_MAX 9 + #define TUP_RHPORT_HIGHSPEED 1 + +#elif TU_CHECK_MCU(OPT_MCU_STM32U5) + #if defined (STM32U535xx) || defined (STM32U545xx) + #define TUP_USBIP_FSDEV + #define TUP_USBIP_FSDEV_STM32 + #define TUP_DCD_ENDPOINT_MAX 8 + + #else + #define TUP_USBIP_DWC2 + #define TUP_USBIP_DWC2_STM32 + + // U59x/5Ax/5Fx/5Gx are highspeed with built-in HS PHY + #if defined(STM32U595xx) || defined(STM32U599xx) || defined(STM32U5A5xx) || defined(STM32U5A9xx) || \ + defined(STM32U5F7xx) || defined(STM32U5F9xx) || defined(STM32U5G7xx) || defined(STM32U5G9xx) + #define TUP_DCD_ENDPOINT_MAX 9 + #define TUP_RHPORT_HIGHSPEED 1 + #else + #define TUP_DCD_ENDPOINT_MAX 6 + #endif + #endif + +#elif TU_CHECK_MCU(OPT_MCU_STM32L5) + #define TUP_USBIP_FSDEV + #define TUP_USBIP_FSDEV_STM32 + #define TUP_DCD_ENDPOINT_MAX 8 + +#elif TU_CHECK_MCU(OPT_MCU_STM32U0) + #define TUP_USBIP_FSDEV + #define TUP_USBIP_FSDEV_STM32 + #define TUP_DCD_ENDPOINT_MAX 8 + +#elif TU_CHECK_MCU(OPT_MCU_STM32H7RS, OPT_MCU_STM32N6) + #define TUP_USBIP_DWC2 + #define TUP_USBIP_DWC2_STM32 + + // FS has 6, HS has 9 + #define TUP_DCD_ENDPOINT_MAX 9 + + // MCU with on-chip HS Phy + #define TUP_RHPORT_HIGHSPEED 1 + + // Enable dcache if DMA is enabled + #define CFG_TUD_MEM_DCACHE_ENABLE_DEFAULT CFG_TUD_DWC2_DMA_ENABLE + #define CFG_TUH_MEM_DCACHE_ENABLE_DEFAULT CFG_TUH_DWC2_DMA_ENABLE + #define CFG_TUSB_MEM_DCACHE_LINE_SIZE_DEFAULT 32 + +//--------------------------------------------------------------------+ +// Sony +//--------------------------------------------------------------------+ +#elif TU_CHECK_MCU(OPT_MCU_CXD56) + #define TUP_DCD_ENDPOINT_MAX 7 + #define TUP_RHPORT_HIGHSPEED 1 + #define TUD_ENDPOINT_ONE_DIRECTION_ONLY + +//--------------------------------------------------------------------+ +// TI +//--------------------------------------------------------------------+ +#elif TU_CHECK_MCU(OPT_MCU_MSP430x5xx) + #define TUP_DCD_ENDPOINT_MAX 8 + +#elif TU_CHECK_MCU(OPT_MCU_MSP432E4, OPT_MCU_TM4C123, OPT_MCU_TM4C129) + #define TUP_USBIP_MUSB + #define TUP_USBIP_MUSB_TI + #define TUP_DCD_ENDPOINT_MAX 8 + +//--------------------------------------------------------------------+ +// ValentyUSB (Litex) +//--------------------------------------------------------------------+ +#elif TU_CHECK_MCU(OPT_MCU_VALENTYUSB_EPTRI) + #define TUP_DCD_ENDPOINT_MAX 16 + +//--------------------------------------------------------------------+ +// Nuvoton +//--------------------------------------------------------------------+ +#elif TU_CHECK_MCU(OPT_MCU_NUC121, OPT_MCU_NUC126) + #define TUP_DCD_ENDPOINT_MAX 8 + +#elif TU_CHECK_MCU(OPT_MCU_NUC120) + #define TUP_DCD_ENDPOINT_MAX 6 + +#elif TU_CHECK_MCU(OPT_MCU_NUC505) + #define TUP_DCD_ENDPOINT_MAX 12 + #define TUP_RHPORT_HIGHSPEED 1 + +//--------------------------------------------------------------------+ +// Espressif +//--------------------------------------------------------------------+ +#elif TU_CHECK_MCU(OPT_MCU_ESP32S2, OPT_MCU_ESP32S3, OPT_MCU_ESP32H4) + #define TUP_USBIP_DWC2 + #define TUP_USBIP_DWC2_ESP32 + #define TUP_DCD_ENDPOINT_MAX 7 // only 5 TX FIFO for endpoint IN + #define CFG_TUSB_OS_INC_PATH_DEFAULT freertos/ + + #if CFG_TUSB_MCU == OPT_MCU_ESP32S3 + #define TUP_MCU_MULTIPLE_CORE 1 + #endif + + // Disable slave if DMA is enabled + #define CFG_TUD_DWC2_SLAVE_ENABLE_DEFAULT !CFG_TUD_DWC2_DMA_ENABLE + #define CFG_TUH_DWC2_SLAVE_ENABLE_DEFAULT !CFG_TUH_DWC2_DMA_ENABLE + +#elif TU_CHECK_MCU(OPT_MCU_ESP32P4) + #define TUP_USBIP_DWC2 + #define TUP_USBIP_DWC2_ESP32 + #define TUP_RHPORT_HIGHSPEED 1 // port0 FS, port1 HS + #define TUP_DCD_ENDPOINT_MAX 16 // FS 7 ep, HS 16 ep + + #define CFG_TUSB_OS_INC_PATH_DEFAULT freertos/ + + #define TUP_MCU_MULTIPLE_CORE 1 + + // Disable slave if DMA is enabled + #define CFG_TUD_DWC2_SLAVE_ENABLE_DEFAULT !CFG_TUD_DWC2_DMA_ENABLE + #define CFG_TUH_DWC2_SLAVE_ENABLE_DEFAULT !CFG_TUH_DWC2_DMA_ENABLE + + // Enable dcache if DMA is enabled + #define CFG_TUD_MEM_DCACHE_ENABLE_DEFAULT CFG_TUD_DWC2_DMA_ENABLE + #define CFG_TUH_MEM_DCACHE_ENABLE_DEFAULT CFG_TUH_DWC2_DMA_ENABLE + #define CFG_TUSB_MEM_DCACHE_LINE_SIZE_DEFAULT 64 + +#elif TU_CHECK_MCU(OPT_MCU_ESP32, OPT_MCU_ESP32C2, OPT_MCU_ESP32C3, OPT_MCU_ESP32C5, OPT_MCU_ESP32C6, OPT_MCU_ESP32C61, OPT_MCU_ESP32H2) + #if (CFG_TUD_ENABLED || !(defined(CFG_TUH_MAX3421) && CFG_TUH_MAX3421)) + #error "MCUs are only supported with CFG_TUH_MAX3421 enabled" + #endif + + #define TUP_DCD_ENDPOINT_MAX 0 + #define CFG_TUSB_OS_INC_PATH_DEFAULT freertos/ + +//--------------------------------------------------------------------+ +// Dialog +//--------------------------------------------------------------------+ +#elif TU_CHECK_MCU(OPT_MCU_DA1469X) + #define TUP_DCD_ENDPOINT_MAX 4 + +//--------------------------------------------------------------------+ +// Raspberry Pi +//--------------------------------------------------------------------+ +#elif TU_CHECK_MCU(OPT_MCU_RP2040) + #define TUP_DCD_EDPT_ISO_ALLOC + #define TUP_DCD_ENDPOINT_MAX 16 + #define TUP_MCU_MULTIPLE_CORE 1 + + #define TU_ATTR_FAST_FUNC __not_in_flash("tinyusb") + +//--------------------------------------------------------------------+ +// Silabs +//--------------------------------------------------------------------+ +#elif TU_CHECK_MCU(OPT_MCU_EFM32GG) + #define TUP_USBIP_DWC2 + #define TUP_DCD_ENDPOINT_MAX 7 + +//--------------------------------------------------------------------+ +// Renesas +//--------------------------------------------------------------------+ +#elif TU_CHECK_MCU(OPT_MCU_RX63X, OPT_MCU_RX65X, OPT_MCU_RX72N, OPT_MCU_RAXXX) + #define TUP_USBIP_RUSB2 + #define TUP_DCD_ENDPOINT_MAX 10 + +//--------------------------------------------------------------------+ +// GigaDevice +//--------------------------------------------------------------------+ +#elif TU_CHECK_MCU(OPT_MCU_GD32VF103) + #define TUP_USBIP_DWC2 + #define TUP_DCD_ENDPOINT_MAX 4 + +//--------------------------------------------------------------------+ +// Broadcom +//--------------------------------------------------------------------+ +#elif TU_CHECK_MCU(OPT_MCU_BCM2711, OPT_MCU_BCM2835, OPT_MCU_BCM2837) + #define TUP_USBIP_DWC2 + #define TUP_DCD_ENDPOINT_MAX 8 + #define TUP_RHPORT_HIGHSPEED 1 + +//--------------------------------------------------------------------+ +// Infineon +//--------------------------------------------------------------------+ +#elif TU_CHECK_MCU(OPT_MCU_XMC4000) + #define TUP_USBIP_DWC2 + #define TUP_DCD_ENDPOINT_MAX 8 + +//--------------------------------------------------------------------+ +// BridgeTek +//--------------------------------------------------------------------+ +#elif TU_CHECK_MCU(OPT_MCU_FT90X) + #define TUP_DCD_ENDPOINT_MAX 8 + #define TUP_RHPORT_HIGHSPEED 1 + #define TUD_ENDPOINT_ONE_DIRECTION_ONLY + +#elif TU_CHECK_MCU(OPT_MCU_FT93X) + #define TUP_DCD_ENDPOINT_MAX 16 + #define TUP_RHPORT_HIGHSPEED 1 + #define TUD_ENDPOINT_ONE_DIRECTION_ONLY + +//--------------------------------------------------------------------+ +// Allwinner +//--------------------------------------------------------------------+ +#elif TU_CHECK_MCU(OPT_MCU_F1C100S) + #define TUP_DCD_ENDPOINT_MAX 4 + +//--------------------------------------------------------------------+ +// WCH +//--------------------------------------------------------------------+ +#elif TU_CHECK_MCU(OPT_MCU_CH32F20X) + #define TUP_USBIP_WCH_USBHS + #define TUP_USBIP_WCH_USBFS + + #if !defined(CFG_TUD_WCH_USBIP_USBFS) + #define CFG_TUD_WCH_USBIP_USBFS 0 + #endif + + #if !defined(CFG_TUD_WCH_USBIP_USBHS) + #define CFG_TUD_WCH_USBIP_USBHS (CFG_TUD_WCH_USBIP_USBFS ? 0 : 1) + #endif + + #define TUP_RHPORT_HIGHSPEED CFG_TUD_WCH_USBIP_USBHS + #define TUP_DCD_ENDPOINT_MAX (CFG_TUD_WCH_USBIP_USBHS ? 16 : 8) + +#elif TU_CHECK_MCU(OPT_MCU_CH32V103) + #define TUP_USBIP_WCH_USBFS + + #if !defined(CFG_TUD_WCH_USBIP_USBFS) + #define CFG_TUD_WCH_USBIP_USBFS 1 + #endif + + #define TUP_DCD_ENDPOINT_MAX 8 + +#elif TU_CHECK_MCU(OPT_MCU_CH32V20X) + // v20x support both port0 FSDEV (USBD) and port1 USBFS + #define TUP_USBIP_WCH_USBFS + + #ifndef CFG_TUH_WCH_USBIP_USBFS + #define CFG_TUH_WCH_USBIP_USBFS 1 + #endif + + #define TUP_USBIP_FSDEV + #define TUP_USBIP_FSDEV_CH32 + + // default to FSDEV for device + #if !defined(CFG_TUD_WCH_USBIP_USBFS) + #define CFG_TUD_WCH_USBIP_USBFS 0 + #endif + + #if !defined(CFG_TUD_WCH_USBIP_FSDEV) + #define CFG_TUD_WCH_USBIP_FSDEV (CFG_TUD_WCH_USBIP_USBFS ? 0 : 1) + #endif + + #define TUP_DCD_ENDPOINT_MAX 8 + +#elif TU_CHECK_MCU(OPT_MCU_CH32V307) + // v307 support both FS and HS, default to HS + #define TUP_USBIP_WCH_USBHS + #define TUP_USBIP_WCH_USBFS + + #if !defined(CFG_TUD_WCH_USBIP_USBFS) + #define CFG_TUD_WCH_USBIP_USBFS 0 + #endif + + #if !defined(CFG_TUD_WCH_USBIP_USBHS) + #define CFG_TUD_WCH_USBIP_USBHS (CFG_TUD_WCH_USBIP_USBFS ? 0 : 1) + #endif + + #define TUP_RHPORT_HIGHSPEED CFG_TUD_WCH_USBIP_USBHS + #define TUP_DCD_ENDPOINT_MAX (CFG_TUD_WCH_USBIP_USBHS ? 16 : 8) + +//--------------------------------------------------------------------+ +// Analog Devices +//--------------------------------------------------------------------+ +#elif TU_CHECK_MCU(OPT_MCU_MAX32650, OPT_MCU_MAX32666, OPT_MCU_MAX32690, OPT_MCU_MAX78002) + #define TUP_USBIP_MUSB + #define TUP_USBIP_MUSB_ADI + #define TUP_DCD_ENDPOINT_MAX 12 + #define TUP_RHPORT_HIGHSPEED 1 + #define TUD_ENDPOINT_ONE_DIRECTION_ONLY + +//--------------------------------------------------------------------+ +// ArteryTek +//--------------------------------------------------------------------+ +#elif TU_CHECK_MCU(OPT_MCU_AT32F403A_407) + #define TUP_USBIP_FSDEV + #define TUP_USBIP_FSDEV_AT32 + #define TUP_DCD_ENDPOINT_MAX 8 + +#elif TU_CHECK_MCU(OPT_MCU_AT32F413) + #define TUP_USBIP_FSDEV + #define TUP_USBIP_FSDEV_AT32 + #define TUP_DCD_ENDPOINT_MAX 8 + +#elif TU_CHECK_MCU(OPT_MCU_AT32F415) + #define TUP_USBIP_DWC2 + #define TUP_USBIP_DWC2_AT32 + #define TUP_DCD_ENDPOINT_MAX 4 + +#elif TU_CHECK_MCU(OPT_MCU_AT32F435_437) + #define TUP_USBIP_DWC2 + #define TUP_USBIP_DWC2_AT32 + #define TUP_DCD_ENDPOINT_MAX 8 + +#elif TU_CHECK_MCU(OPT_MCU_AT32F423) + #define TUP_USBIP_DWC2 + #define TUP_USBIP_DWC2_AT32 + #define TUP_DCD_ENDPOINT_MAX 8 + +#elif TU_CHECK_MCU(OPT_MCU_AT32F402_405) + #define TUP_USBIP_DWC2 + #define TUP_USBIP_DWC2_AT32 + #define TUP_DCD_ENDPOINT_MAX 8 + + // AT32F405xx has on-chip HS PHY + #if defined(AT32F405CBT7) || defined(AT32F405CBU7) || \ + defined(AT32F405CCT7) || defined(AT32F405CCU7) || \ + defined(AT32F405KBU7_4) || defined(AT32F405KCU7_4) || \ + defined(AT32F405RBT7_7) || defined(AT32F405RBT7) || \ + defined(AT32F405RCT7_7) || defined(AT32F405RCT7) + #define TUP_RHPORT_HIGHSPEED 1 // Port0: FS, Port1: HS + #endif + +#elif TU_CHECK_MCU(OPT_MCU_AT32F425) + #define TUP_USBIP_DWC2 + #define TUP_USBIP_DWC2_AT32 + #define TUP_DCD_ENDPOINT_MAX 8 + +#endif + +//--------------------------------------------------------------------+ +// External USB controller +//--------------------------------------------------------------------+ + +#if defined(CFG_TUH_MAX3421) && CFG_TUH_MAX3421 + #ifndef CFG_TUH_MAX3421_ENDPOINT_TOTAL + #define CFG_TUH_MAX3421_ENDPOINT_TOTAL (8 + 4*(CFG_TUH_DEVICE_MAX-1)) + #endif +#endif + + +//--------------------------------------------------------------------+ +// Default Values +//--------------------------------------------------------------------+ + +#ifndef TUP_MCU_MULTIPLE_CORE +#define TUP_MCU_MULTIPLE_CORE 0 +#endif + +#if !defined(TUP_DCD_ENDPOINT_MAX) && defined(CFG_TUD_ENABLED) && CFG_TUD_ENABLED + #warning "TUP_DCD_ENDPOINT_MAX is not defined for this MCU, default to 8" + #define TUP_DCD_ENDPOINT_MAX 8 +#endif + +// Default to fullspeed if not defined +#ifndef TUP_RHPORT_HIGHSPEED + #define TUP_RHPORT_HIGHSPEED 0 +#endif + +// fast function, normally mean placing function in SRAM +#ifndef TU_ATTR_FAST_FUNC + #define TU_ATTR_FAST_FUNC +#endif + +// USBIP that support ISO alloc & activate API +#if defined(TUP_USBIP_DWC2) || defined(TUP_USBIP_FSDEV) || defined(TUP_USBIP_MUSB) + #define TUP_DCD_EDPT_ISO_ALLOC +#endif + +#if defined(TUP_USBIP_DWC2) // && CFG_TUD_DWC2_DMA_ENABLE == 0 + #define TUP_MEM_CONST_ADDR +#endif + +#endif diff --git a/cores/nRF5/TinyUSB/Adafruit_TinyUSB_ArduinoCore/tinyusb/src/common/tusb_private.h b/cores/nRF5/TinyUSB/Adafruit_TinyUSB_ArduinoCore/tinyusb/src/common/tusb_private.h new file mode 100644 index 0000000..31aca8a --- /dev/null +++ b/cores/nRF5/TinyUSB/Adafruit_TinyUSB_ArduinoCore/tinyusb/src/common/tusb_private.h @@ -0,0 +1,172 @@ +/* + * The MIT License (MIT) + * + * Copyright (c) 2022, Ha Thach (tinyusb.org) + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + * This file is part of the TinyUSB stack. + */ + +#ifndef TUSB_PRIVATE_H_ +#define TUSB_PRIVATE_H_ + +// Internal Helper used by Host and Device Stack + +#ifdef __cplusplus + extern "C" { +#endif + +#define TUP_USBIP_CONTROLLER_NUM 2 +extern tusb_role_t _tusb_rhport_role[TUP_USBIP_CONTROLLER_NUM]; + +//--------------------------------------------------------------------+ +// Endpoint +//--------------------------------------------------------------------+ + +typedef struct TU_ATTR_PACKED { + volatile uint8_t busy : 1; + volatile uint8_t stalled : 1; + volatile uint8_t claimed : 1; +}tu_edpt_state_t; + +typedef struct { + struct TU_ATTR_PACKED { + uint8_t is_host : 1; // 1: host, 0: device + uint8_t is_mps512 : 1; // 1: 512, 0: 64 since stream is used for Bulk only + }; + uint8_t ep_addr; + uint16_t ep_bufsize; + + uint8_t* ep_buf; // TODO xfer_fifo can skip this buffer + tu_fifo_t ff; + + // mutex: read if rx, otherwise write + OSAL_MUTEX_DEF(ff_mutexdef); + +}tu_edpt_stream_t; + +//--------------------------------------------------------------------+ +// Endpoint +//--------------------------------------------------------------------+ + +// Check if endpoint descriptor is valid per USB specs +bool tu_edpt_validate(tusb_desc_endpoint_t const * desc_ep, tusb_speed_t speed, bool is_host); + +// Bind all endpoint of a interface descriptor to class driver +void tu_edpt_bind_driver(uint8_t ep2drv[][2], tusb_desc_interface_t const* p_desc, uint16_t desc_len, uint8_t driver_id); + +// Calculate total length of n interfaces (depending on IAD) +uint16_t tu_desc_get_interface_total_len(tusb_desc_interface_t const* desc_itf, uint8_t itf_count, uint16_t max_len); + +// Claim an endpoint with provided mutex +bool tu_edpt_claim(tu_edpt_state_t* ep_state, osal_mutex_t mutex); + +// Release an endpoint with provided mutex +bool tu_edpt_release(tu_edpt_state_t* ep_state, osal_mutex_t mutex); + +//--------------------------------------------------------------------+ +// Endpoint Stream +//--------------------------------------------------------------------+ + +// Init an endpoint stream +bool tu_edpt_stream_init(tu_edpt_stream_t* s, bool is_host, bool is_tx, bool overwritable, + void* ff_buf, uint16_t ff_bufsize, uint8_t* ep_buf, uint16_t ep_bufsize); + +// Deinit an endpoint stream +bool tu_edpt_stream_deinit(tu_edpt_stream_t* s); + +// Open an stream for an endpoint +TU_ATTR_ALWAYS_INLINE static inline +void tu_edpt_stream_open(tu_edpt_stream_t* s, tusb_desc_endpoint_t const *desc_ep) { + tu_fifo_clear(&s->ff); + s->ep_addr = desc_ep->bEndpointAddress; + s->is_mps512 = (tu_edpt_packet_size(desc_ep) == 512) ? 1 : 0; +} + +TU_ATTR_ALWAYS_INLINE static inline +void tu_edpt_stream_close(tu_edpt_stream_t* s) { + s->ep_addr = 0; +} + +// Clear fifo +TU_ATTR_ALWAYS_INLINE static inline +bool tu_edpt_stream_clear(tu_edpt_stream_t* s) { + return tu_fifo_clear(&s->ff); +} + +//--------------------------------------------------------------------+ +// Stream Write +//--------------------------------------------------------------------+ + +// Write to stream +uint32_t tu_edpt_stream_write(uint8_t hwid, tu_edpt_stream_t* s, void const *buffer, uint32_t bufsize); + +// Start an usb transfer if endpoint is not busy +uint32_t tu_edpt_stream_write_xfer(uint8_t hwid, tu_edpt_stream_t* s); + +// Start an zero-length packet if needed +bool tu_edpt_stream_write_zlp_if_needed(uint8_t hwid, tu_edpt_stream_t* s, uint32_t last_xferred_bytes); + +// Get the number of bytes available for writing to FIFO +// Note: if no fifo, return endpoint size if not busy, 0 otherwise +uint32_t tu_edpt_stream_write_available(uint8_t hwid, tu_edpt_stream_t* s); + +//--------------------------------------------------------------------+ +// Stream Read +//--------------------------------------------------------------------+ + +// Read from stream +uint32_t tu_edpt_stream_read(uint8_t hwid, tu_edpt_stream_t* s, void* buffer, uint32_t bufsize); + +// Start an usb transfer if endpoint is not busy +uint32_t tu_edpt_stream_read_xfer(uint8_t hwid, tu_edpt_stream_t* s); + +// Complete read transfer by writing EP -> FIFO. Must be called in the transfer complete callback +TU_ATTR_ALWAYS_INLINE static inline +void tu_edpt_stream_read_xfer_complete(tu_edpt_stream_t* s, uint32_t xferred_bytes) { + if (tu_fifo_depth(&s->ff)) { + tu_fifo_write_n(&s->ff, s->ep_buf, (uint16_t) xferred_bytes); + } +} + +// Complete read transfer with provided buffer +TU_ATTR_ALWAYS_INLINE static inline +void tu_edpt_stream_read_xfer_complete_with_buf(tu_edpt_stream_t* s, const void * buf, uint32_t xferred_bytes) { + if (tu_fifo_depth(&s->ff)) { + tu_fifo_write_n(&s->ff, buf, (uint16_t) xferred_bytes); + } +} + +// Get the number of bytes available for reading +TU_ATTR_ALWAYS_INLINE static inline +uint32_t tu_edpt_stream_read_available(tu_edpt_stream_t* s) { + return (uint32_t) tu_fifo_count(&s->ff); +} + +TU_ATTR_ALWAYS_INLINE static inline +bool tu_edpt_stream_peek(tu_edpt_stream_t* s, uint8_t* ch) { + return tu_fifo_peek(&s->ff, ch); +} + +#ifdef __cplusplus + } +#endif + +#endif diff --git a/cores/nRF5/TinyUSB/Adafruit_TinyUSB_ArduinoCore/tinyusb/src/common/tusb_timeout.h b/cores/nRF5/TinyUSB/Adafruit_TinyUSB_ArduinoCore/tinyusb/src/common/tusb_timeout.h deleted file mode 100644 index ce53955..0000000 --- a/cores/nRF5/TinyUSB/Adafruit_TinyUSB_ArduinoCore/tinyusb/src/common/tusb_timeout.h +++ /dev/null @@ -1,80 +0,0 @@ -/* - * 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. - */ - -/** \ingroup Group_Common Common Files - * \defgroup Group_TimeoutTimer timeout timer - * @{ */ - -#ifndef _TUSB_TIMEOUT_H_ -#define _TUSB_TIMEOUT_H_ - -#include -#include - -#ifdef __cplusplus -extern "C" { -#endif - -typedef struct { - uint32_t start; - uint32_t interval; -}tu_timeout_t; - -#if 0 - -extern uint32_t tusb_hal_millis(void); - -static inline void tu_timeout_set(tu_timeout_t* tt, uint32_t msec) -{ - tt->interval = msec; - tt->start = tusb_hal_millis(); -} - -static inline bool tu_timeout_expired(tu_timeout_t* tt) -{ - return ( tusb_hal_millis() - tt->start ) >= tt->interval; -} - -// For used with periodic event to prevent drift -static inline void tu_timeout_reset(tu_timeout_t* tt) -{ - tt->start += tt->interval; -} - -static inline void tu_timeout_restart(tu_timeout_t* tt) -{ - tt->start = tusb_hal_millis(); -} - -#endif - -#ifdef __cplusplus - } -#endif - -#endif /* _TUSB_TIMEOUT_H_ */ - -/** @} */ diff --git a/cores/nRF5/TinyUSB/Adafruit_TinyUSB_ArduinoCore/tinyusb/src/common/tusb_types.h b/cores/nRF5/TinyUSB/Adafruit_TinyUSB_ArduinoCore/tinyusb/src/common/tusb_types.h index 70d0db9..b3ef1e9 100644 --- a/cores/nRF5/TinyUSB/Adafruit_TinyUSB_ArduinoCore/tinyusb/src/common/tusb_types.h +++ b/cores/nRF5/TinyUSB/Adafruit_TinyUSB_ArduinoCore/tinyusb/src/common/tusb_types.h @@ -1,4 +1,4 @@ -/* +/* * The MIT License (MIT) * * Copyright (c) 2019 Ha Thach (tinyusb.org) @@ -24,12 +24,8 @@ * This file is part of the TinyUSB stack. */ -/** \ingroup group_usb_definitions - * \defgroup USBDef_Type USB Types - * @{ */ - -#ifndef _TUSB_TYPES_H_ -#define _TUSB_TYPES_H_ +#ifndef TUSB_TYPES_H_ +#define TUSB_TYPES_H_ #include #include @@ -39,38 +35,98 @@ extern "C" { #endif +//------------- Device DCache declaration -------------// +#define TUD_EPBUF_DCACHE_SIZE(_size) (CFG_TUD_MEM_DCACHE_ENABLE ? \ + (TU_DIV_CEIL(_size, CFG_TUD_MEM_DCACHE_LINE_SIZE) * CFG_TUD_MEM_DCACHE_LINE_SIZE) : (_size)) + +// Declare an endpoint buffer with uint8_t[size] +#define TUD_EPBUF_DEF(_name, _size) \ + union { \ + CFG_TUD_MEM_ALIGN uint8_t _name[_size]; \ + TU_ATTR_ALIGNED(CFG_TUD_MEM_DCACHE_ENABLE ? CFG_TUD_MEM_DCACHE_LINE_SIZE : 1) uint8_t _name##_dcache_padding[TUD_EPBUF_DCACHE_SIZE(_size)]; \ + } + +// Declare an endpoint buffer with a type +#define TUD_EPBUF_TYPE_DEF(_type, _name) \ + union { \ + CFG_TUD_MEM_ALIGN _type _name; \ + TU_ATTR_ALIGNED(CFG_TUD_MEM_DCACHE_ENABLE ? CFG_TUD_MEM_DCACHE_LINE_SIZE : 1) uint8_t _name##_dcache_padding[TUD_EPBUF_DCACHE_SIZE(sizeof(_type))]; \ + } + +//------------- Host DCache declaration -------------// +#define TUH_EPBUF_DCACHE_SIZE(_size) (CFG_TUH_MEM_DCACHE_ENABLE ? \ + (TU_DIV_CEIL(_size, CFG_TUH_MEM_DCACHE_LINE_SIZE) * CFG_TUH_MEM_DCACHE_LINE_SIZE) : (_size)) + +// Declare an endpoint buffer with uint8_t[size] +#define TUH_EPBUF_DEF(_name, _size) \ + union { \ + CFG_TUH_MEM_ALIGN uint8_t _name[_size]; \ + TU_ATTR_ALIGNED(CFG_TUH_MEM_DCACHE_ENABLE ? CFG_TUH_MEM_DCACHE_LINE_SIZE : 1) uint8_t _name##_dcache_padding[TUH_EPBUF_DCACHE_SIZE(_size)]; \ + } + +// Declare an endpoint buffer with a type +#define TUH_EPBUF_TYPE_DEF(_type, _name) \ + union { \ + CFG_TUH_MEM_ALIGN _type _name; \ + TU_ATTR_ALIGNED(CFG_TUH_MEM_DCACHE_ENABLE ? CFG_TUH_MEM_DCACHE_LINE_SIZE : 1) uint8_t _name##_dcache_padding[TUH_EPBUF_DCACHE_SIZE(sizeof(_type))]; \ + } + + /*------------------------------------------------------------------*/ /* CONSTANTS *------------------------------------------------------------------*/ +typedef enum { + TUSB_ROLE_INVALID = 0, + TUSB_ROLE_DEVICE = 0x1, + TUSB_ROLE_HOST = 0x2, +} tusb_role_t; + /// defined base on EHCI specs value for Endpoint Speed -typedef enum -{ +typedef enum { TUSB_SPEED_FULL = 0, - TUSB_SPEED_LOW , - TUSB_SPEED_HIGH -}tusb_speed_t; + TUSB_SPEED_LOW = 1, + TUSB_SPEED_HIGH = 2, + TUSB_SPEED_AUTO = 0xaa, + TUSB_SPEED_INVALID = 0xff, +} tusb_speed_t; /// defined base on USB Specs Endpoint's bmAttributes -typedef enum -{ - TUSB_XFER_CONTROL = 0 , - TUSB_XFER_ISOCHRONOUS , - TUSB_XFER_BULK , - TUSB_XFER_INTERRUPT -}tusb_xfer_type_t; - -typedef enum -{ +typedef enum { + TUSB_XFER_CONTROL = 0, + TUSB_XFER_ISOCHRONOUS = 1, + TUSB_XFER_BULK = 2, + TUSB_XFER_INTERRUPT = 3 +} tusb_xfer_type_t; + +typedef enum { TUSB_DIR_OUT = 0, TUSB_DIR_IN = 1, TUSB_DIR_IN_MASK = 0x80 -}tusb_dir_t; +} tusb_dir_t; + +enum { + TUSB_EPSIZE_BULK_FS = 64, + TUSB_EPSIZE_BULK_HS = 512, + + TUSB_EPSIZE_ISO_FS_MAX = 1023, + TUSB_EPSIZE_ISO_HS_MAX = 1024, +}; + +/// Isochronous Endpoint Attributes +typedef enum { + TUSB_ISO_EP_ATT_NO_SYNC = 0x00, + TUSB_ISO_EP_ATT_ASYNCHRONOUS = 0x04, + TUSB_ISO_EP_ATT_ADAPTIVE = 0x08, + TUSB_ISO_EP_ATT_SYNCHRONOUS = 0x0C, + TUSB_ISO_EP_ATT_DATA = 0x00, ///< Data End Point + TUSB_ISO_EP_ATT_EXPLICIT_FB = 0x10, ///< Feedback End Point + TUSB_ISO_EP_ATT_IMPLICIT_FB = 0x20, ///< Data endpoint that also serves as an implicit feedback +} tusb_iso_ep_attribute_t; /// USB Descriptor Types -typedef enum -{ +typedef enum { TUSB_DESC_DEVICE = 0x01, TUSB_DESC_CONFIGURATION = 0x02, TUSB_DESC_STRING = 0x03, @@ -97,10 +153,9 @@ typedef enum TUSB_DESC_SUPERSPEED_ENDPOINT_COMPANION = 0x30, TUSB_DESC_SUPERSPEED_ISO_ENDPOINT_COMPANION = 0x31 -}tusb_desc_type_t; +} tusb_desc_type_t; -typedef enum -{ +typedef enum { TUSB_REQ_GET_STATUS = 0 , TUSB_REQ_CLEAR_FEATURE = 1 , TUSB_REQ_RESERVED = 2 , @@ -114,25 +169,22 @@ typedef enum TUSB_REQ_GET_INTERFACE = 10 , TUSB_REQ_SET_INTERFACE = 11 , TUSB_REQ_SYNCH_FRAME = 12 -}tusb_request_code_t; +} tusb_request_code_t; -typedef enum -{ +typedef enum { TUSB_REQ_FEATURE_EDPT_HALT = 0, TUSB_REQ_FEATURE_REMOTE_WAKEUP = 1, TUSB_REQ_FEATURE_TEST_MODE = 2 -}tusb_request_feature_selector_t; +} tusb_request_feature_selector_t; -typedef enum -{ +typedef enum { TUSB_REQ_TYPE_STANDARD = 0, TUSB_REQ_TYPE_CLASS, TUSB_REQ_TYPE_VENDOR, TUSB_REQ_TYPE_INVALID } tusb_request_type_t; -typedef enum -{ +typedef enum { TUSB_REQ_RCPT_DEVICE =0, TUSB_REQ_RCPT_INTERFACE, TUSB_REQ_RCPT_ENDPOINT, @@ -140,8 +192,7 @@ typedef enum } tusb_request_recipient_t; // https://www.usb.org/defined-class-codes -typedef enum -{ +typedef enum { TUSB_CLASS_UNSPECIFIED = 0 , TUSB_CLASS_AUDIO = 1 , TUSB_CLASS_CDC = 2 , @@ -165,26 +216,23 @@ typedef enum TUSB_CLASS_MISC = 0xEF , TUSB_CLASS_APPLICATION_SPECIFIC = 0xFE , TUSB_CLASS_VENDOR_SPECIFIC = 0xFF -}tusb_class_code_t; +} tusb_class_code_t; typedef enum { MISC_SUBCLASS_COMMON = 2 }misc_subclass_type_t; -typedef enum -{ +typedef enum { MISC_PROTOCOL_IAD = 1 -}misc_protocol_type_t; +} misc_protocol_type_t; -typedef enum -{ +typedef enum { APP_SUBCLASS_USBTMC = 0x03, APP_SUBCLASS_DFU_RUNTIME = 0x01 } app_subclass_type_t; -typedef enum -{ +typedef enum { DEVICE_CAPABILITY_WIRELESS_USB = 0x01, DEVICE_CAPABILITY_USB20_EXTENSION = 0x02, DEVICE_CAPABILITY_SUPERSPEED_USB = 0x03, @@ -201,44 +249,49 @@ typedef enum DEVICE_CAPABILITY_AUTHENTICATION = 0x0E, DEVICE_CAPABILITY_BILLBOARD_EX = 0x0F, DEVICE_CAPABILITY_CONFIGURATION_SUMMARY = 0x10 -}device_capability_type_t; +} device_capability_type_t; enum { - TUSB_DESC_CONFIG_ATT_REMOTE_WAKEUP = TU_BIT(5), - TUSB_DESC_CONFIG_ATT_SELF_POWERED = TU_BIT(6), + TUSB_DESC_CONFIG_ATT_REMOTE_WAKEUP = 1u << 5, + TUSB_DESC_CONFIG_ATT_SELF_POWERED = 1u << 6, }; #define TUSB_DESC_CONFIG_POWER_MA(x) ((x)/2) -/// Device State TODO remove -typedef enum -{ - TUSB_DEVICE_STATE_UNPLUG = 0 , - TUSB_DEVICE_STATE_CONFIGURED , - TUSB_DEVICE_STATE_SUSPENDED , -}tusb_device_state_t; +// USB 2.0 Spec Table 9-7: Test Mode Selectors +typedef enum { + TUSB_FEATURE_TEST_J = 1, + TUSB_FEATURE_TEST_K = 2, + TUSB_FEATURE_TEST_SE0_NAK = 3, + TUSB_FEATURE_TEST_PACKET = 4, + TUSB_FEATURE_TEST_FORCE_ENABLE = 5, +} tusb_feature_test_mode_t; -typedef enum -{ - XFER_RESULT_SUCCESS, +//--------------------------------------------------------------------+ +// +//--------------------------------------------------------------------+ +typedef enum { + XFER_RESULT_SUCCESS = 0, XFER_RESULT_FAILED, XFER_RESULT_STALLED, -}xfer_result_t; + XFER_RESULT_TIMEOUT, + XFER_RESULT_INVALID +} xfer_result_t; -enum // TODO remove -{ +#define tusb_xfer_result_t xfer_result_t + +// TODO remove +enum { DESC_OFFSET_LEN = 0, - DESC_OFFSET_TYPE = 1 + DESC_OFFSET_TYPE = 1, + DESC_OFFSET_SUBTYPE = 2 }; -enum -{ +enum { INTERFACE_INVALID_NUMBER = 0xff }; - -typedef enum -{ +typedef enum { MS_OS_20_SET_HEADER_DESCRIPTOR = 0x00, MS_OS_20_SUBSET_HEADER_CONFIGURATION = 0x01, MS_OS_20_SUBSET_HEADER_FUNCTION = 0x02, @@ -250,20 +303,42 @@ typedef enum MS_OS_20_FEATURE_VENDOR_REVISION = 0x08 } microsoft_os_20_type_t; +enum { + CONTROL_STAGE_IDLE = 0, + CONTROL_STAGE_SETUP, // 1 + CONTROL_STAGE_DATA, // 2 + CONTROL_STAGE_ACK // 3 +}; + +enum { + TUSB_INDEX_INVALID_8 = 0xFFu +}; + +//--------------------------------------------------------------------+ +// +//--------------------------------------------------------------------+ +typedef struct { + tusb_role_t role; + tusb_speed_t speed; +} tusb_rhport_init_t; + //--------------------------------------------------------------------+ // USB Descriptors //--------------------------------------------------------------------+ +// Start of all packed definitions for compiler without per-type packed +TU_ATTR_PACKED_BEGIN +TU_ATTR_BIT_FIELD_ORDER_BEGIN + /// USB Device Descriptor -typedef struct TU_ATTR_PACKED -{ +typedef struct TU_ATTR_PACKED { uint8_t bLength ; ///< Size of this descriptor in bytes. uint8_t bDescriptorType ; ///< DEVICE Descriptor Type. - uint16_t bcdUSB ; ///< BUSB Specification Release Number in Binary-Coded Decimal (i.e., 2.10 is 210H). This field identifies the release of the USB Specification with which the device and its descriptors are compliant. + uint16_t bcdUSB ; ///< BUSB Specification Release Number in Binary-Coded Decimal (i.e., 2.10 is 210H). - uint8_t bDeviceClass ; ///< Class code (assigned by the USB-IF). \li If this field is reset to zero, each interface within a configuration specifies its own class information and the various interfaces operate independently. \li If this field is set to a value between 1 and FEH, the device supports different class specifications on different interfaces and the interfaces may not operate independently. This value identifies the class definition used for the aggregate interfaces. \li If this field is set to FFH, the device class is vendor-specific. - uint8_t bDeviceSubClass ; ///< Subclass code (assigned by the USB-IF). These codes are qualified by the value of the bDeviceClass field. \li If the bDeviceClass field is reset to zero, this field must also be reset to zero. \li If the bDeviceClass field is not set to FFH, all values are reserved for assignment by the USB-IF. - uint8_t bDeviceProtocol ; ///< Protocol code (assigned by the USB-IF). These codes are qualified by the value of the bDeviceClass and the bDeviceSubClass fields. If a device supports class-specific protocols on a device basis as opposed to an interface basis, this code identifies the protocols that the device uses as defined by the specification of the device class. \li If this field is reset to zero, the device does not use class-specific protocols on a device basis. However, it may use classspecific protocols on an interface basis. \li If this field is set to FFH, the device uses a vendor-specific protocol on a device basis. + uint8_t bDeviceClass ; ///< Class code (assigned by the USB-IF). + uint8_t bDeviceSubClass ; ///< Subclass code (assigned by the USB-IF). + uint8_t bDeviceProtocol ; ///< Protocol code (assigned by the USB-IF). uint8_t bMaxPacketSize0 ; ///< Maximum packet size for endpoint zero (only 8, 16, 32, or 64 are valid). For HS devices is fixed to 64. uint16_t idVendor ; ///< Vendor ID (assigned by the USB-IF). @@ -272,22 +347,23 @@ typedef struct TU_ATTR_PACKED uint8_t iManufacturer ; ///< Index of string descriptor describing manufacturer. uint8_t iProduct ; ///< Index of string descriptor describing product. uint8_t iSerialNumber ; ///< Index of string descriptor describing the device's serial number. - uint8_t bNumConfigurations ; ///< Number of possible configurations. } tusb_desc_device_t; +TU_VERIFY_STATIC( sizeof(tusb_desc_device_t) == 18, "size is not correct"); + // USB Binary Device Object Store (BOS) Descriptor -typedef struct TU_ATTR_PACKED -{ +typedef struct TU_ATTR_PACKED { uint8_t bLength ; ///< Size of this descriptor in bytes uint8_t bDescriptorType ; ///< CONFIGURATION Descriptor Type uint16_t wTotalLength ; ///< Total length of data returned for this descriptor uint8_t bNumDeviceCaps ; ///< Number of device capability descriptors in the BOS } tusb_desc_bos_t; +TU_VERIFY_STATIC( sizeof(tusb_desc_bos_t) == 5, "size is not correct"); + /// USB Configuration Descriptor -typedef struct TU_ATTR_PACKED -{ +typedef struct TU_ATTR_PACKED { uint8_t bLength ; ///< Size of this descriptor in bytes uint8_t bDescriptorType ; ///< CONFIGURATION Descriptor Type uint16_t wTotalLength ; ///< Total length of data returned for this configuration. Includes the combined length of all descriptors (configuration, interface, endpoint, and class- or vendor-specific) returned for this configuration. @@ -299,9 +375,10 @@ typedef struct TU_ATTR_PACKED uint8_t bMaxPower ; ///< Maximum power consumption of the USB device from the bus in this specific configuration when the device is fully operational. Expressed in 2 mA units (i.e., 50 = 100 mA). } tusb_desc_configuration_t; +TU_VERIFY_STATIC( sizeof(tusb_desc_configuration_t) == 9, "size is not correct"); + /// USB Interface Descriptor -typedef struct TU_ATTR_PACKED -{ +typedef struct TU_ATTR_PACKED { uint8_t bLength ; ///< Size of this descriptor in bytes uint8_t bDescriptorType ; ///< INTERFACE Descriptor Type @@ -314,47 +391,43 @@ typedef struct TU_ATTR_PACKED uint8_t iInterface ; ///< Index of string descriptor describing this interface } tusb_desc_interface_t; +TU_VERIFY_STATIC( sizeof(tusb_desc_interface_t) == 9, "size is not correct"); + /// USB Endpoint Descriptor -typedef struct TU_ATTR_PACKED -{ - uint8_t bLength ; ///< Size of this descriptor in bytes - uint8_t bDescriptorType ; ///< ENDPOINT Descriptor Type +typedef struct TU_ATTR_PACKED { + uint8_t bLength ; // Size of this descriptor in bytes + uint8_t bDescriptorType ; // ENDPOINT Descriptor Type - uint8_t bEndpointAddress ; ///< The address of the endpoint on the USB device described by this descriptor. The address is encoded as follows: \n Bit 3...0: The endpoint number \n Bit 6...4: Reserved, reset to zero \n Bit 7: Direction, ignored for control endpoints 0 = OUT endpoint 1 = IN endpoint. + uint8_t bEndpointAddress ; // The address of the endpoint struct TU_ATTR_PACKED { - uint8_t xfer : 2; - uint8_t sync : 2; - uint8_t usage : 2; + uint8_t xfer : 2; // Control, ISO, Bulk, Interrupt + uint8_t sync : 2; // None, Asynchronous, Adaptive, Synchronous + uint8_t usage : 2; // Data, Feedback, Implicit feedback uint8_t : 2; - } bmAttributes ; ///< This field describes the endpoint's attributes when it is configured using the bConfigurationValue. \n Bits 1..0: Transfer Type \n- 00 = Control \n- 01 = Isochronous \n- 10 = Bulk \n- 11 = Interrupt \n If not an isochronous endpoint, bits 5..2 are reserved and must be set to zero. If isochronous, they are defined as follows: \n Bits 3..2: Synchronization Type \n- 00 = No Synchronization \n- 01 = Asynchronous \n- 10 = Adaptive \n- 11 = Synchronous \n Bits 5..4: Usage Type \n- 00 = Data endpoint \n- 01 = Feedback endpoint \n- 10 = Implicit feedback Data endpoint \n- 11 = Reserved \n Refer to Chapter 5 of USB 2.0 specification for more information. \n All other bits are reserved and must be reset to zero. Reserved bits must be ignored by the host. - - struct TU_ATTR_PACKED { - uint16_t size : 11; ///< Maximum packet size this endpoint is capable of sending or receiving when this configuration is selected. \n For isochronous endpoints, this value is used to reserve the bus time in the schedule, required for the per-(micro)frame data payloads. The pipe may, on an ongoing basis, actually use less bandwidth than that reserved. The device reports, if necessary, the actual bandwidth used via its normal, non-USB defined mechanisms. \n For all endpoints, bits 10..0 specify the maximum packet size (in bytes). \n For high-speed isochronous and interrupt endpoints: \n Bits 12..11 specify the number of additional transaction opportunities per microframe: \n- 00 = None (1 transaction per microframe) \n- 01 = 1 additional (2 per microframe) \n- 10 = 2 additional (3 per microframe) \n- 11 = Reserved \n Bits 15..13 are reserved and must be set to zero. - uint16_t hs_period_mult : 2; - uint16_t : 0; - }wMaxPacketSize; + } bmAttributes; - uint8_t bInterval ; ///< Interval for polling endpoint for data transfers. Expressed in frames or microframes depending on the device operating speed (i.e., either 1 millisecond or 125 us units). \n- For full-/high-speed isochronous endpoints, this value must be in the range from 1 to 16. The bInterval value is used as the exponent for a \f$ 2^(bInterval-1) \f$ value; e.g., a bInterval of 4 means a period of 8 (\f$ 2^(4-1) \f$). \n- For full-/low-speed interrupt endpoints, the value of this field may be from 1 to 255. \n- For high-speed interrupt endpoints, the bInterval value is used as the exponent for a \f$ 2^(bInterval-1) \f$ value; e.g., a bInterval of 4 means a period of 8 (\f$ 2^(4-1) \f$) . This value must be from 1 to 16. \n- For high-speed bulk/control OUT endpoints, the bInterval must specify the maximum NAK rate of the endpoint. A value of 0 indicates the endpoint never NAKs. Other values indicate at most 1 NAK each bInterval number of microframes. This value must be in the range from 0 to 255. \n Refer to Chapter 5 of USB 2.0 specification for more information. + uint16_t wMaxPacketSize ; // Bit 10..0 : max packet size, bit 12..11 additional transaction per highspeed micro-frame + uint8_t bInterval ; // Polling interval, in frames or microframes depending on the operating speed } tusb_desc_endpoint_t; +TU_VERIFY_STATIC( sizeof(tusb_desc_endpoint_t) == 7, "size is not correct"); + /// USB Other Speed Configuration Descriptor -typedef struct TU_ATTR_PACKED -{ +typedef struct TU_ATTR_PACKED { uint8_t bLength ; ///< Size of descriptor uint8_t bDescriptorType ; ///< Other_speed_Configuration Type uint16_t wTotalLength ; ///< Total length of data returned uint8_t bNumInterfaces ; ///< Number of interfaces supported by this speed configuration uint8_t bConfigurationValue ; ///< Value to use to select configuration - uint8_t IConfiguration ; ///< Index of string descriptor + uint8_t iConfiguration ; ///< Index of string descriptor uint8_t bmAttributes ; ///< Same as Configuration descriptor uint8_t bMaxPower ; ///< Same as Configuration descriptor } tusb_desc_other_speed_t; /// USB Device Qualifier Descriptor -typedef struct TU_ATTR_PACKED -{ +typedef struct TU_ATTR_PACKED { uint8_t bLength ; ///< Size of descriptor uint8_t bDescriptorType ; ///< Device Qualifier Type uint16_t bcdUSB ; ///< USB specification version number (e.g., 0200H for V2.00) @@ -362,14 +435,16 @@ typedef struct TU_ATTR_PACKED uint8_t bDeviceClass ; ///< Class Code uint8_t bDeviceSubClass ; ///< SubClass Code uint8_t bDeviceProtocol ; ///< Protocol Code + uint8_t bMaxPacketSize0 ; ///< Maximum packet size for other speed uint8_t bNumConfigurations ; ///< Number of Other-speed Configurations uint8_t bReserved ; ///< Reserved for future use, must be zero } tusb_desc_device_qualifier_t; +TU_VERIFY_STATIC( sizeof(tusb_desc_device_qualifier_t) == 10, "size is not correct"); + /// USB Interface Association Descriptor (IAD ECN) -typedef struct TU_ATTR_PACKED -{ +typedef struct TU_ATTR_PACKED { uint8_t bLength ; ///< Size of descriptor uint8_t bDescriptorType ; ///< Other_speed_Configuration Type @@ -383,17 +458,17 @@ typedef struct TU_ATTR_PACKED uint8_t iFunction ; ///< Index of the string descriptor describing the interface association. } tusb_desc_interface_assoc_t; +TU_VERIFY_STATIC( sizeof(tusb_desc_interface_assoc_t) == 8, "size is not correct"); + // USB String Descriptor -typedef struct TU_ATTR_PACKED -{ +typedef struct TU_ATTR_PACKED { uint8_t bLength ; ///< Size of this descriptor in bytes uint8_t bDescriptorType ; ///< Descriptor Type - uint16_t unicode_string[]; + uint16_t utf16le[]; } tusb_desc_string_t; // USB Binary Device Object Store (BOS) -typedef struct TU_ATTR_PACKED -{ +typedef struct TU_ATTR_PACKED { uint8_t bLength; uint8_t bDescriptorType ; uint8_t bDevCapabilityType; @@ -402,19 +477,41 @@ typedef struct TU_ATTR_PACKED uint8_t CapabilityData[]; } tusb_desc_bos_platform_t; -// USB WebuSB URL Descriptor -typedef struct TU_ATTR_PACKED -{ +// USB WebUSB URL Descriptor +typedef struct TU_ATTR_PACKED { uint8_t bLength; uint8_t bDescriptorType; uint8_t bScheme; char url[]; } tusb_desc_webusb_url_t; -/*------------------------------------------------------------------*/ -/* Types - *------------------------------------------------------------------*/ -typedef struct TU_ATTR_PACKED{ +// DFU Functional Descriptor +typedef struct TU_ATTR_PACKED { + uint8_t bLength; + uint8_t bDescriptorType; + + union { + struct TU_ATTR_PACKED { + uint8_t bitCanDnload : 1; + uint8_t bitCanUpload : 1; + uint8_t bitManifestationTolerant : 1; + uint8_t bitWillDetach : 1; + uint8_t reserved : 4; + } bmAttributes; + + uint8_t bAttributes; + }; + + uint16_t wDetachTimeOut; + uint16_t wTransferSize; + uint16_t bcdDFUVersion; +} tusb_desc_dfu_functional_t; + +//--------------------------------------------------------------------+ +// +//--------------------------------------------------------------------+ + +typedef struct TU_ATTR_PACKED { union { struct TU_ATTR_PACKED { uint8_t recipient : 5; ///< Recipient type tusb_request_recipient_t. @@ -431,58 +528,42 @@ typedef struct TU_ATTR_PACKED{ uint16_t wLength; } tusb_control_request_t; -TU_VERIFY_STATIC( sizeof(tusb_control_request_t) == 8, "mostly compiler option issue"); +TU_VERIFY_STATIC( sizeof(tusb_control_request_t) == 8, "size is not correct"); -// TODO move to somewhere suitable -static inline uint8_t bm_request_type(uint8_t direction, uint8_t type, uint8_t recipient) -{ - return ((uint8_t) (direction << 7)) | ((uint8_t) (type << 5)) | (recipient); -} +TU_ATTR_PACKED_END // End of all packed definitions +TU_ATTR_BIT_FIELD_ORDER_END //--------------------------------------------------------------------+ // Endpoint helper //--------------------------------------------------------------------+ // Get direction from Endpoint address -static inline tusb_dir_t tu_edpt_dir(uint8_t addr) -{ +TU_ATTR_ALWAYS_INLINE static inline tusb_dir_t tu_edpt_dir(uint8_t addr) { return (addr & TUSB_DIR_IN_MASK) ? TUSB_DIR_IN : TUSB_DIR_OUT; } // Get Endpoint number from address -static inline uint8_t tu_edpt_number(uint8_t addr) -{ +TU_ATTR_ALWAYS_INLINE static inline uint8_t tu_edpt_number(uint8_t addr) { return (uint8_t)(addr & (~TUSB_DIR_IN_MASK)); } -static inline uint8_t tu_edpt_addr(uint8_t num, uint8_t dir) -{ +TU_ATTR_ALWAYS_INLINE static inline uint8_t tu_edpt_addr(uint8_t num, uint8_t dir) { return (uint8_t)(num | (dir ? TUSB_DIR_IN_MASK : 0)); } -//--------------------------------------------------------------------+ -// Descriptor helper -//--------------------------------------------------------------------+ -static inline uint8_t const * tu_desc_next(void const* desc) -{ - uint8_t const* desc8 = (uint8_t const*) desc; - return desc8 + desc8[DESC_OFFSET_LEN]; +TU_ATTR_ALWAYS_INLINE static inline uint16_t tu_edpt_packet_size(tusb_desc_endpoint_t const* desc_ep) { + return tu_le16toh(desc_ep->wMaxPacketSize) & 0x7FF; } -static inline uint8_t tu_desc_type(void const* desc) -{ - return ((uint8_t const*) desc)[DESC_OFFSET_TYPE]; -} - -static inline uint8_t tu_desc_len(void const* desc) -{ - return ((uint8_t const*) desc)[DESC_OFFSET_LEN]; +#if CFG_TUSB_DEBUG +TU_ATTR_ALWAYS_INLINE static inline const char *tu_edpt_type_str(tusb_xfer_type_t t) { + tu_static const char *str[] = {"control", "isochronous", "bulk", "interrupt"}; + return str[t]; } +#endif #ifdef __cplusplus } #endif -#endif /* _TUSB_TYPES_H_ */ - -/** @} */ +#endif // TUSB_TYPES_H_ diff --git a/cores/nRF5/TinyUSB/Adafruit_TinyUSB_ArduinoCore/tinyusb/src/common/tusb_verify.h b/cores/nRF5/TinyUSB/Adafruit_TinyUSB_ArduinoCore/tinyusb/src/common/tusb_verify.h index 406f5e6..db91a73 100644 --- a/cores/nRF5/TinyUSB/Adafruit_TinyUSB_ArduinoCore/tinyusb/src/common/tusb_verify.h +++ b/cores/nRF5/TinyUSB/Adafruit_TinyUSB_ArduinoCore/tinyusb/src/common/tusb_verify.h @@ -1,4 +1,4 @@ -/* +/* * The MIT License (MIT) * * Copyright (c) 2019 Ha Thach (tinyusb.org) @@ -36,34 +36,28 @@ * as C++ for the sake of code simplicity. Beware of a headache macro * manipulation that you are told to stay away. * - * * This contains macros for both VERIFY and ASSERT: - * + * * VERIFY: Used when there is an error condition which is not the * fault of the MCU. For example, bounds checking on data * sent to the micro over USB should use this function. * Another example is checking for buffer overflows, where * returning from the active function causes a NAK. - * + * * ASSERT: Used for error conditions that are caused by MCU firmware * bugs. This is used to discover bugs in the code more * quickly. One example would be adding assertions in library * function calls to confirm a function's (untainted) * parameters are valid. * - * - * The difference in behaviour is that ASSERT triggers a breakpoint while + * The difference in behavior is that ASSERT triggers a breakpoint while * verify does not. * - * #define TU_VERIFY(cond) if(cond) return false; - * #define TU_VERIFY(cond,ret) if(cond) return ret; - * - * #define TU_VERIFY_HDLR(cond,handler) if(cond) {handler; return false;} - * #define TU_VERIFY_HDLR(cond,ret,handler) if(cond) {handler; return ret;} + * #define TU_VERIFY(cond) if (!cond) return false; + * #define TU_VERIFY(cond,ret) if (!cond) return ret; * - * #define TU_ASSERT(cond) if(cond) {_MESS_FAILED(); TU_BREAKPOINT(), return false;} - * #define TU_ASSERT(cond,ret) if(cond) {_MESS_FAILED(); TU_BREAKPOINT(), return ret;} - * + * #define TU_ASSERT(cond) if (!cond) {TU_MESS_FAILED(); TU_BREAKPOINT(), return false;} + * #define TU_ASSERT(cond,ret) if (!cond) {TU_MESS_FAILED(); TU_BREAKPOINT(), return ret;} *------------------------------------------------------------------*/ #ifdef __cplusplus @@ -76,62 +70,43 @@ #if CFG_TUSB_DEBUG #include - #define _MESS_ERR(_err) printf("%s %d: failed, error = %s\r\n", __func__, __LINE__, tusb_strerr[_err]) - #define _MESS_FAILED() printf("%s %d: assert failed\r\n", __func__, __LINE__) + #define TU_MESS_FAILED() tu_printf("%s %d: ASSERT FAILED\r\n", __func__, __LINE__) #else - #define _MESS_ERR(_err) do {} while (0) - #define _MESS_FAILED() do {} while (0) + #define TU_MESS_FAILED() do {} while (0) #endif -// Halt CPU (breakpoint) when hitting error, only apply for Cortex M3, M4, M7 -#if defined(__ARM_ARCH_7M__) || defined (__ARM_ARCH_7EM__) - #define TU_BREAKPOINT() do \ - { \ +// Halt CPU (breakpoint) when hitting error, only apply for Cortex M3, M4, M7, M33. M55 +#if defined(__ARM_ARCH_7M__) || defined (__ARM_ARCH_7EM__) || defined(__ARM_ARCH_8M_MAIN__) || defined(__ARM_ARCH_8_1M_MAIN__) || \ + defined(__ARM7M__) || defined (__ARM7EM__) || defined(__ARM8M_MAINLINE__) || defined(__ARM8EM_MAINLINE__) + #define TU_BREAKPOINT() do { \ volatile uint32_t* ARM_CM_DHCSR = ((volatile uint32_t*) 0xE000EDF0UL); /* Cortex M CoreDebug->DHCSR */ \ if ( (*ARM_CM_DHCSR) & 1UL ) __asm("BKPT #0\n"); /* Only halt mcu if debugger is attached */ \ } while(0) -#elif defined(__riscv) +#elif defined(__riscv) && !TUSB_MCU_VENDOR_ESPRESSIF #define TU_BREAKPOINT() do { __asm("ebreak\n"); } while(0) +#elif defined(_mips) + #define TU_BREAKPOINT() do { __asm("sdbbp 0"); } while (0) + #else #define TU_BREAKPOINT() do {} while (0) #endif -/*------------------------------------------------------------------*/ -/* Macro Generator - *------------------------------------------------------------------*/ - -// Helper to implement optional parameter for TU_VERIFY Macro family -#define GET_3RD_ARG(arg1, arg2, arg3, ...) arg3 -#define GET_4TH_ARG(arg1, arg2, arg3, arg4, ...) arg4 - -/*------------- Generator for TU_VERIFY and TU_VERIFY_HDLR -------------*/ -#define TU_VERIFY_DEFINE(_cond, _handler, _ret) do \ -{ \ - if ( !(_cond) ) { _handler; return _ret; } \ -} while(0) - /*------------------------------------------------------------------*/ /* TU_VERIFY * - TU_VERIFY_1ARGS : return false if failed * - TU_VERIFY_2ARGS : return provided value if failed *------------------------------------------------------------------*/ -#define TU_VERIFY_1ARGS(_cond) TU_VERIFY_DEFINE(_cond, , false) -#define TU_VERIFY_2ARGS(_cond, _ret) TU_VERIFY_DEFINE(_cond, , _ret) - -#define TU_VERIFY(...) GET_3RD_ARG(__VA_ARGS__, TU_VERIFY_2ARGS, TU_VERIFY_1ARGS, UNUSED)(__VA_ARGS__) - +#define TU_VERIFY_DEFINE(_cond, _ret) \ + do { \ + if ( !(_cond) ) { return _ret; } \ + } while(0) -/*------------------------------------------------------------------*/ -/* TU_VERIFY WITH HANDLER - * - TU_VERIFY_HDLR_2ARGS : execute handler, return false if failed - * - TU_VERIFY_HDLR_3ARGS : execute handler, return provided error if failed - *------------------------------------------------------------------*/ -#define TU_VERIFY_HDLR_2ARGS(_cond, _handler) TU_VERIFY_DEFINE(_cond, _handler, false) -#define TU_VERIFY_HDLR_3ARGS(_cond, _handler, _ret) TU_VERIFY_DEFINE(_cond, _handler, _ret) +#define TU_VERIFY_1ARGS(_cond) TU_VERIFY_DEFINE(_cond, false) +#define TU_VERIFY_2ARGS(_cond, _ret) TU_VERIFY_DEFINE(_cond, _ret) -#define TU_VERIFY_HDLR(...) GET_4TH_ARG(__VA_ARGS__, TU_VERIFY_HDLR_3ARGS, TU_VERIFY_HDLR_2ARGS,UNUSED)(__VA_ARGS__) +#define TU_VERIFY(...) TU_GET_3RD_ARG(__VA_ARGS__, TU_VERIFY_2ARGS, TU_VERIFY_1ARGS, _dummy)(__VA_ARGS__) /*------------------------------------------------------------------*/ /* ASSERT @@ -139,41 +114,20 @@ * - 1 arg : return false if failed * - 2 arg : return error if failed *------------------------------------------------------------------*/ -#define ASSERT_1ARGS(_cond) TU_VERIFY_DEFINE(_cond, _MESS_FAILED(); TU_BREAKPOINT(), false) -#define ASSERT_2ARGS(_cond, _ret) TU_VERIFY_DEFINE(_cond, _MESS_FAILED(); TU_BREAKPOINT(), _ret) - -#define TU_ASSERT(...) GET_3RD_ARG(__VA_ARGS__, ASSERT_2ARGS, ASSERT_1ARGS,UNUSED)(__VA_ARGS__) - -// TODO remove TU_ASSERT_ERR() later - -/*------------- Generator for TU_VERIFY_ERR and TU_VERIFY_ERR_HDLR -------------*/ -#define TU_VERIFY_ERR_DEF2(_error, _handler) do \ -{ \ - uint32_t _err = (uint32_t)(_error); \ - if ( 0 != _err ) { _MESS_ERR(_err); _handler; return _err; } \ -} while(0) - -#define TU_VERIFY_ERR_DEF3(_error, _handler, _ret) do \ -{ \ - uint32_t _err = (uint32_t)(_error); \ - if ( 0 != _err ) { _MESS_ERR(_err); _handler; return _ret; } \ -} while(0) - -/*------------------------------------------------------------------*/ -/* ASSERT Error - * basically TU_VERIFY Error with TU_BREAKPOINT() as handler - *------------------------------------------------------------------*/ -#define ASERT_ERR_1ARGS(_error) TU_VERIFY_ERR_DEF2(_error, TU_BREAKPOINT()) -#define ASERT_ERR_2ARGS(_error, _ret) TU_VERIFY_ERR_DEF3(_error, TU_BREAKPOINT(), _ret) +#define TU_ASSERT_DEFINE(_cond, _ret) \ + do { \ + if ( !(_cond) ) { TU_MESS_FAILED(); TU_BREAKPOINT(); return _ret; } \ + } while(0) -#define TU_ASSERT_ERR(...) GET_3RD_ARG(__VA_ARGS__, ASERT_ERR_2ARGS, ASERT_ERR_1ARGS,UNUSED)(__VA_ARGS__) +#define TU_ASSERT_1ARGS(_cond) TU_ASSERT_DEFINE(_cond, false) +#define TU_ASSERT_2ARGS(_cond, _ret) TU_ASSERT_DEFINE(_cond, _ret) -/*------------------------------------------------------------------*/ -/* ASSERT HDLR - *------------------------------------------------------------------*/ +#ifndef TU_ASSERT +#define TU_ASSERT(...) TU_GET_3RD_ARG(__VA_ARGS__, TU_ASSERT_2ARGS, TU_ASSERT_1ARGS, _dummy)(__VA_ARGS__) +#endif #ifdef __cplusplus } #endif -#endif /* TUSB_VERIFY_H_ */ +#endif diff --git a/cores/nRF5/TinyUSB/Adafruit_TinyUSB_ArduinoCore/tinyusb/src/device/dcd.h b/cores/nRF5/TinyUSB/Adafruit_TinyUSB_ArduinoCore/tinyusb/src/device/dcd.h index 776f782..400f62b 100644 --- a/cores/nRF5/TinyUSB/Adafruit_TinyUSB_ArduinoCore/tinyusb/src/device/dcd.h +++ b/cores/nRF5/TinyUSB/Adafruit_TinyUSB_ArduinoCore/tinyusb/src/device/dcd.h @@ -1,4 +1,4 @@ -/* +/* * The MIT License (MIT) * * Copyright (c) 2019 Ha Thach (tinyusb.org) @@ -24,49 +24,49 @@ * This file is part of the TinyUSB stack. */ -/** \ingroup group_usbd - * \defgroup group_dcd Device Controller Driver (DCD) - * @{ */ - -#ifndef _TUSB_DCD_H_ -#define _TUSB_DCD_H_ +#ifndef TUSB_DCD_H_ +#define TUSB_DCD_H_ #include "common/tusb_common.h" +#include "osal/osal.h" +#include "common/tusb_fifo.h" #ifdef __cplusplus extern "C" { #endif -typedef enum -{ - DCD_EVENT_INVALID = 0, - DCD_EVENT_BUS_RESET, - DCD_EVENT_UNPLUGGED, - DCD_EVENT_SOF, - DCD_EVENT_SUSPEND, // TODO LPM Sleep L1 support - DCD_EVENT_RESUME, - - DCD_EVENT_SETUP_RECEIVED, - DCD_EVENT_XFER_COMPLETE, - - // Not an DCD event, just a convenient way to defer ISR function - USBD_EVENT_FUNC_CALL, +//--------------------------------------------------------------------+ +// MACRO CONSTANT TYPEDEF PROTYPES +//--------------------------------------------------------------------+ +typedef enum { + DCD_EVENT_INVALID = 0, // 0 + DCD_EVENT_BUS_RESET, // 1 + DCD_EVENT_UNPLUGGED, // 2 + DCD_EVENT_SOF, // 3 + DCD_EVENT_SUSPEND, // 4 TODO LPM Sleep L1 support + DCD_EVENT_RESUME, // 5 + DCD_EVENT_SETUP_RECEIVED, // 6 + DCD_EVENT_XFER_COMPLETE, // 7 + USBD_EVENT_FUNC_CALL, // 8 Not an DCD event, just a convenient way to defer ISR function DCD_EVENT_COUNT } dcd_eventid_t; -typedef struct TU_ATTR_ALIGNED(4) -{ +typedef struct TU_ATTR_ALIGNED(4) { uint8_t rhport; uint8_t event_id; - union - { + union { // BUS RESET struct { tusb_speed_t speed; } bus_reset; + // SOF + struct { + uint32_t frame_count; + }sof; + // SETUP_RECEIVED tusb_control_request_t setup_received; @@ -77,7 +77,7 @@ typedef struct TU_ATTR_ALIGNED(4) uint32_t len; }xfer_complete; - // USBD_EVENT_FUNC_CALL + // FUNC_CALL struct { void (*func) (void*); void* param; @@ -87,12 +87,31 @@ typedef struct TU_ATTR_ALIGNED(4) //TU_VERIFY_STATIC(sizeof(dcd_event_t) <= 12, "size is not correct"); +//--------------------------------------------------------------------+ +// Memory API +//--------------------------------------------------------------------+ + +// clean/flush data cache: write cache -> memory. +// Required before an DMA TX transfer to make sure data is in memory +bool dcd_dcache_clean(const void* addr, uint32_t data_size); + +// invalidate data cache: mark cache as invalid, next read will read from memory +// Required BOTH before and after an DMA RX transfer +bool dcd_dcache_invalidate(const void* addr, uint32_t data_size); + +// clean and invalidate data cache +// Required before an DMA transfer where memory is both read/write by DMA +bool dcd_dcache_clean_invalidate(const void* addr, uint32_t data_size); + //--------------------------------------------------------------------+ // Controller API //--------------------------------------------------------------------+ // Initialize controller to device mode -void dcd_init (uint8_t rhport); +bool dcd_init(uint8_t rhport, const tusb_rhport_init_t* rh_init); + +// Deinitialize controller, unset device mode. +bool dcd_deinit(uint8_t rhport); // Interrupt Handler void dcd_int_handler(uint8_t rhport); @@ -110,58 +129,116 @@ void dcd_set_address(uint8_t rhport, uint8_t dev_addr); void dcd_remote_wakeup(uint8_t rhport); // Connect by enabling internal pull-up resistor on D+/D- -void dcd_connect(uint8_t rhport) TU_ATTR_WEAK; +void dcd_connect(uint8_t rhport); // Disconnect by disabling internal pull-up resistor on D+/D- -void dcd_disconnect(uint8_t rhport) TU_ATTR_WEAK; +void dcd_disconnect(uint8_t rhport); + +// Enable/Disable Start-of-frame interrupt. Default is disabled +void dcd_sof_enable(uint8_t rhport, bool en); +#if CFG_TUD_TEST_MODE +// Put device into a test mode (needs power cycle to quit) +void dcd_enter_test_mode(uint8_t rhport, tusb_feature_test_mode_t test_selector); +#endif //--------------------------------------------------------------------+ // Endpoint API //--------------------------------------------------------------------+ // Invoked when a control transfer's status stage is complete. // May help DCD to prepare for next control transfer, this API is optional. -void dcd_edpt0_status_complete(uint8_t rhport, tusb_control_request_t const * request) TU_ATTR_WEAK; +void dcd_edpt0_status_complete(uint8_t rhport, tusb_control_request_t const * request); // Configure endpoint's registers according to descriptor -bool dcd_edpt_open (uint8_t rhport, tusb_desc_endpoint_t const * p_endpoint_desc); +bool dcd_edpt_open (uint8_t rhport, tusb_desc_endpoint_t const * desc_ep); -// Close an endpoint. -// Since it is weak, caller must TU_ASSERT this function's existence before calling it. -void dcd_edpt_close (uint8_t rhport, uint8_t ep_addr) TU_ATTR_WEAK; +// Close all non-control endpoints, cancel all pending transfers if any. +// Invoked when switching from a non-zero Configuration by SET_CONFIGURE therefore +// required for multiple configuration support. +void dcd_edpt_close_all (uint8_t rhport); // Submit a transfer, When complete dcd_event_xfer_complete() is invoked to notify the stack -bool dcd_edpt_xfer (uint8_t rhport, uint8_t ep_addr, uint8_t * buffer, uint16_t total_bytes); +bool dcd_edpt_xfer (uint8_t rhport, uint8_t ep_addr, uint8_t * buffer, uint16_t total_bytes); -// Stall endpoint -void dcd_edpt_stall (uint8_t rhport, uint8_t ep_addr); +// Submit an transfer using fifo, When complete dcd_event_xfer_complete() is invoked to notify the stack +// This API is optional, may be useful for register-based for transferring data. +bool dcd_edpt_xfer_fifo (uint8_t rhport, uint8_t ep_addr, tu_fifo_t * ff, uint16_t total_bytes); + +// Stall endpoint, any queuing transfer should be removed from endpoint +void dcd_edpt_stall (uint8_t rhport, uint8_t ep_addr); // clear stall, data toggle is also reset to DATA0 -void dcd_edpt_clear_stall (uint8_t rhport, uint8_t ep_addr); +// This API never calls with control endpoints, since it is auto cleared when receiving setup packet +void dcd_edpt_clear_stall (uint8_t rhport, uint8_t ep_addr); + +#ifdef TUP_DCD_EDPT_ISO_ALLOC +// Allocate packet buffer used by ISO endpoints +// Some MCU need manual packet buffer allocation, we allocate the largest size to avoid clustering +bool dcd_edpt_iso_alloc(uint8_t rhport, uint8_t ep_addr, uint16_t largest_packet_size); + +// Configure and enable an ISO endpoint according to descriptor +bool dcd_edpt_iso_activate(uint8_t rhport, tusb_desc_endpoint_t const * desc_ep); + +#else +// Close an endpoint. +void dcd_edpt_close(uint8_t rhport, uint8_t ep_addr); + +#endif //--------------------------------------------------------------------+ -// Event API (Implemented by device stack) +// Event API (implemented by stack) //--------------------------------------------------------------------+ // Called by DCD to notify device stack extern void dcd_event_handler(dcd_event_t const * event, bool in_isr); // helper to send bus signal event -extern void dcd_event_bus_signal (uint8_t rhport, dcd_eventid_t eid, bool in_isr); +TU_ATTR_ALWAYS_INLINE static inline void dcd_event_bus_signal (uint8_t rhport, dcd_eventid_t eid, bool in_isr) { + dcd_event_t event; + event.rhport = rhport; + event.event_id = eid; + dcd_event_handler(&event, in_isr); +} // helper to send bus reset event -extern void dcd_event_bus_reset (uint8_t rhport, tusb_speed_t speed, bool in_isr); +TU_ATTR_ALWAYS_INLINE static inline void dcd_event_bus_reset (uint8_t rhport, tusb_speed_t speed, bool in_isr) { + dcd_event_t event; + event.rhport = rhport; + event.event_id = DCD_EVENT_BUS_RESET; + event.bus_reset.speed = speed; + dcd_event_handler(&event, in_isr); +} // helper to send setup received -extern void dcd_event_setup_received(uint8_t rhport, uint8_t const * setup, bool in_isr); +TU_ATTR_ALWAYS_INLINE static inline void dcd_event_setup_received(uint8_t rhport, uint8_t const * setup, bool in_isr) { + dcd_event_t event; + event.rhport = rhport; + event.event_id = DCD_EVENT_SETUP_RECEIVED; + memcpy(&event.setup_received, setup, sizeof(tusb_control_request_t)); + dcd_event_handler(&event, in_isr); +} // helper to send transfer complete event -extern void dcd_event_xfer_complete (uint8_t rhport, uint8_t ep_addr, uint32_t xferred_bytes, uint8_t result, bool in_isr); +TU_ATTR_ALWAYS_INLINE static inline void dcd_event_xfer_complete (uint8_t rhport, uint8_t ep_addr, uint32_t xferred_bytes, uint8_t result, bool in_isr) { + dcd_event_t event; + event.rhport = rhport; + event.event_id = DCD_EVENT_XFER_COMPLETE; + event.xfer_complete.ep_addr = ep_addr; + event.xfer_complete.len = xferred_bytes; + event.xfer_complete.result = result; + dcd_event_handler(&event, in_isr); +} + +TU_ATTR_ALWAYS_INLINE static inline void dcd_event_sof(uint8_t rhport, uint32_t frame_count, bool in_isr) { + dcd_event_t event; + event.rhport = rhport; + event.event_id = DCD_EVENT_SOF; + event.sof.frame_count = frame_count; + dcd_event_handler(&event, in_isr); +} #ifdef __cplusplus } #endif -#endif /* _TUSB_DCD_H_ */ - -/// @} +#endif diff --git a/cores/nRF5/TinyUSB/Adafruit_TinyUSB_ArduinoCore/tinyusb/src/device/usbd.c b/cores/nRF5/TinyUSB/Adafruit_TinyUSB_ArduinoCore/tinyusb/src/device/usbd.c index d1e984a..9d74b8c 100644 --- a/cores/nRF5/TinyUSB/Adafruit_TinyUSB_ArduinoCore/tinyusb/src/device/usbd.c +++ b/cores/nRF5/TinyUSB/Adafruit_TinyUSB_ArduinoCore/tinyusb/src/device/usbd.c @@ -1,4 +1,4 @@ -/* +/* * The MIT License (MIT) * * Copyright (c) 2019 Ha Thach (tinyusb.org) @@ -26,284 +26,422 @@ #include "tusb_option.h" -#if TUSB_OPT_DEVICE_ENABLED +#if CFG_TUD_ENABLED +#include "device/dcd.h" #include "tusb.h" -#include "usbd.h" +#include "common/tusb_private.h" + +#include "device/usbd.h" #include "device/usbd_pvt.h" -#include "dcd.h" +//--------------------------------------------------------------------+ +// USBD Configuration +//--------------------------------------------------------------------+ #ifndef CFG_TUD_TASK_QUEUE_SZ -#define CFG_TUD_TASK_QUEUE_SZ 16 + #define CFG_TUD_TASK_QUEUE_SZ 16 #endif +//--------------------------------------------------------------------+ +// Weak stubs: invoked if no strong implementation is available +//--------------------------------------------------------------------+ +TU_ATTR_WEAK void tud_event_hook_cb(uint8_t rhport, uint32_t eventid, bool in_isr) { + (void) rhport; (void) eventid; (void) in_isr; +} + +TU_ATTR_WEAK void tud_sof_cb(uint32_t frame_count) { + (void) frame_count; +} + +// TU_ATTR_WEAK uint8_t const* tud_descriptor_bos_cb(void) { +// return NULL; +// } + +TU_ATTR_WEAK uint8_t const* tud_descriptor_device_qualifier_cb(void) { + return NULL; +} + +TU_ATTR_WEAK uint8_t const* tud_descriptor_other_speed_configuration_cb(uint8_t index) { + (void) index; + return NULL; +} + +TU_ATTR_WEAK void tud_mount_cb(void) { +} + +TU_ATTR_WEAK void tud_umount_cb(void) { +} + +TU_ATTR_WEAK void tud_suspend_cb(bool remote_wakeup_en) { + (void) remote_wakeup_en; +} + +TU_ATTR_WEAK void tud_resume_cb(void) { +} + +// TU_ATTR_WEAK bool tud_vendor_control_xfer_cb(uint8_t rhport, uint8_t stage, tusb_control_request_t const* request) { +// (void) rhport; (void) stage; (void) request; +// return false; +// } + +TU_ATTR_WEAK bool dcd_deinit(uint8_t rhport) { + (void) rhport; + return false; +} + +TU_ATTR_WEAK void dcd_connect(uint8_t rhport) { + (void) rhport; +} + +TU_ATTR_WEAK void dcd_disconnect(uint8_t rhport) { + (void) rhport; +} + +TU_ATTR_WEAK bool dcd_dcache_clean(const void* addr, uint32_t data_size) { + (void) addr; (void) data_size; + return true; +} + +TU_ATTR_WEAK bool dcd_dcache_invalidate(const void* addr, uint32_t data_size) { + (void) addr; (void) data_size; + return true; +} + +TU_ATTR_WEAK bool dcd_dcache_clean_invalidate(const void* addr, uint32_t data_size) { + (void) addr; (void) data_size; + return true; +} + //--------------------------------------------------------------------+ // Device Data //--------------------------------------------------------------------+ -typedef struct -{ - struct TU_ATTR_PACKED - { + +// Invalid driver ID in itf2drv[] ep2drv[][] mapping +enum { DRVID_INVALID = 0xFFu }; + +typedef struct { + struct TU_ATTR_PACKED { volatile uint8_t connected : 1; volatile uint8_t addressed : 1; - volatile uint8_t configured : 1; volatile uint8_t suspended : 1; uint8_t remote_wakeup_en : 1; // enable/disable by host uint8_t remote_wakeup_support : 1; // configuration descriptor's attribute uint8_t self_powered : 1; // configuration descriptor's attribute }; - + volatile uint8_t cfg_num; // current active configuration (0x00 is not configured) uint8_t speed; + volatile uint8_t sof_consumer; - uint8_t itf2drv[16]; // map interface number to driver (0xff is invalid) - uint8_t ep2drv[8][2]; // map endpoint to driver ( 0xff is invalid ) - - struct TU_ATTR_PACKED - { - volatile bool busy : 1; - volatile bool stalled : 1; - volatile bool claimed : 1; + uint8_t itf2drv[CFG_TUD_INTERFACE_MAX]; // map interface number to driver (0xff is invalid) + uint8_t ep2drv[CFG_TUD_ENDPPOINT_MAX][2]; // map endpoint to driver ( 0xff is invalid ), can use only 4-bit each - // TODO merge ep2drv here, 4-bit should be sufficient - }ep_status[8][2]; + tu_edpt_state_t ep_status[CFG_TUD_ENDPPOINT_MAX][2]; }usbd_device_t; -static usbd_device_t _usbd_dev; - -// Invalid driver ID in itf2drv[] ep2drv[][] mapping -enum { DRVID_INVALID = 0xFFu }; +tu_static usbd_device_t _usbd_dev; +static volatile uint8_t _usbd_queued_setup; //--------------------------------------------------------------------+ // Class Driver //--------------------------------------------------------------------+ -#if CFG_TUSB_DEBUG >= 2 - #define DRIVER_NAME(_name) .name = _name, +#if CFG_TUSB_DEBUG >= CFG_TUD_LOG_LEVEL + #define DRIVER_NAME(_name) _name #else - #define DRIVER_NAME(_name) + #define DRIVER_NAME(_name) NULL #endif // Built-in class drivers -static usbd_class_driver_t const _usbd_driver[] = -{ - #if CFG_TUD_CDC - { - DRIVER_NAME("CDC") - .init = cdcd_init, - .reset = cdcd_reset, - .open = cdcd_open, - .control_request = cdcd_control_request, - .control_complete = cdcd_control_complete, - .xfer_cb = cdcd_xfer_cb, - .sof = NULL - }, - #endif - - #if CFG_TUD_MSC - { - DRIVER_NAME("MSC") - .init = mscd_init, - .reset = mscd_reset, - .open = mscd_open, - .control_request = mscd_control_request, - .control_complete = mscd_control_complete, - .xfer_cb = mscd_xfer_cb, - .sof = NULL - }, - #endif - - #if CFG_TUD_HID - { - DRIVER_NAME("HID") - .init = hidd_init, - .reset = hidd_reset, - .open = hidd_open, - .control_request = hidd_control_request, - .control_complete = hidd_control_complete, - .xfer_cb = hidd_xfer_cb, - .sof = NULL - }, - #endif - - #if CFG_TUD_MIDI - { - DRIVER_NAME("MIDI") - .init = midid_init, - .open = midid_open, - .reset = midid_reset, - .control_request = midid_control_request, - .control_complete = midid_control_complete, - .xfer_cb = midid_xfer_cb, - .sof = NULL - }, - #endif - - #if CFG_TUD_VENDOR - { - DRIVER_NAME("VENDOR") - .init = vendord_init, - .reset = vendord_reset, - .open = vendord_open, - .control_request = tud_vendor_control_request_cb, - .control_complete = tud_vendor_control_complete_cb, - .xfer_cb = vendord_xfer_cb, - .sof = NULL - }, - #endif - - #if CFG_TUD_USBTMC - { - DRIVER_NAME("TMC") - .init = usbtmcd_init_cb, - .reset = usbtmcd_reset_cb, - .open = usbtmcd_open_cb, - .control_request = usbtmcd_control_request_cb, - .control_complete = usbtmcd_control_complete_cb, - .xfer_cb = usbtmcd_xfer_cb, - .sof = NULL - }, - #endif - - #if CFG_TUD_DFU_RT - { - DRIVER_NAME("DFU-RT") - .init = dfu_rtd_init, - .reset = dfu_rtd_reset, - .open = dfu_rtd_open, - .control_request = dfu_rtd_control_request, - .control_complete = dfu_rtd_control_complete, - .xfer_cb = dfu_rtd_xfer_cb, - .sof = NULL - }, - #endif - - #if CFG_TUD_NET - { - DRIVER_NAME("NET") - .init = netd_init, - .reset = netd_reset, - .open = netd_open, - .control_request = netd_control_request, - .control_complete = netd_control_complete, - .xfer_cb = netd_xfer_cb, - .sof = NULL, - }, - #endif - - #if CFG_TUD_BTH - { - DRIVER_NAME("BTH") - .init = btd_init, - .reset = btd_reset, - .open = btd_open, - .control_request = btd_control_request, - .control_complete = btd_control_complete, - .xfer_cb = btd_xfer_cb, - .sof = NULL - }, - #endif +tu_static usbd_class_driver_t const _usbd_driver[] = { + #if CFG_TUD_CDC + { + .name = DRIVER_NAME("CDC"), + .init = cdcd_init, + .deinit = cdcd_deinit, + .reset = cdcd_reset, + .open = cdcd_open, + .control_xfer_cb = cdcd_control_xfer_cb, + .xfer_cb = cdcd_xfer_cb, + .xfer_isr = NULL, + .sof = NULL + }, + #endif + + #if CFG_TUD_MSC + { + .name = DRIVER_NAME("MSC"), + .init = mscd_init, + .deinit = NULL, + .reset = mscd_reset, + .open = mscd_open, + .control_xfer_cb = mscd_control_xfer_cb, + .xfer_cb = mscd_xfer_cb, + .xfer_isr = NULL, + .sof = NULL + }, + #endif + + #if CFG_TUD_HID + { + .name = DRIVER_NAME("HID"), + .init = hidd_init, + .deinit = hidd_deinit, + .reset = hidd_reset, + .open = hidd_open, + .control_xfer_cb = hidd_control_xfer_cb, + .xfer_cb = hidd_xfer_cb, + .xfer_isr = NULL, + .sof = NULL + }, + #endif + + #if CFG_TUD_AUDIO + { + .name = DRIVER_NAME("AUDIO"), + .init = audiod_init, + .deinit = audiod_deinit, + .reset = audiod_reset, + .open = audiod_open, + .control_xfer_cb = audiod_control_xfer_cb, + .xfer_cb = audiod_xfer_cb, + .xfer_isr = audiod_xfer_isr, + .sof = audiod_sof_isr + }, + #endif + + #if CFG_TUD_VIDEO + { + .name = DRIVER_NAME("VIDEO"), + .init = videod_init, + .deinit = videod_deinit, + .reset = videod_reset, + .open = videod_open, + .control_xfer_cb = videod_control_xfer_cb, + .xfer_cb = videod_xfer_cb, + .xfer_isr = NULL, + .sof = NULL + }, + #endif + + #if CFG_TUD_MIDI + { + .name = DRIVER_NAME("MIDI"), + .init = midid_init, + .deinit = midid_deinit, + .open = midid_open, + .reset = midid_reset, + .control_xfer_cb = midid_control_xfer_cb, + .xfer_cb = midid_xfer_cb, + .xfer_isr = NULL, + .sof = NULL + }, + #endif + + #if CFG_TUD_VENDOR + { + .name = DRIVER_NAME("VENDOR"), + .init = vendord_init, + .deinit = vendord_deinit, + .reset = vendord_reset, + .open = vendord_open, + .control_xfer_cb = tud_vendor_control_xfer_cb, + .xfer_cb = vendord_xfer_cb, + .xfer_isr = NULL, + .sof = NULL + }, + #endif + + #if CFG_TUD_USBTMC + { + .name = DRIVER_NAME("TMC"), + .init = usbtmcd_init_cb, + .deinit = usbtmcd_deinit, + .reset = usbtmcd_reset_cb, + .open = usbtmcd_open_cb, + .control_xfer_cb = usbtmcd_control_xfer_cb, + .xfer_cb = usbtmcd_xfer_cb, + .xfer_isr = NULL, + .sof = NULL + }, + #endif + + #if CFG_TUD_DFU_RUNTIME + { + .name = DRIVER_NAME("DFU-RUNTIME"), + .init = dfu_rtd_init, + .deinit = dfu_rtd_deinit, + .reset = dfu_rtd_reset, + .open = dfu_rtd_open, + .control_xfer_cb = dfu_rtd_control_xfer_cb, + .xfer_cb = NULL, + .xfer_isr = NULL, + .sof = NULL + }, + #endif + + #if CFG_TUD_DFU + { + .name = DRIVER_NAME("DFU"), + .init = dfu_moded_init, + .deinit = dfu_moded_deinit, + .reset = dfu_moded_reset, + .open = dfu_moded_open, + .control_xfer_cb = dfu_moded_control_xfer_cb, + .xfer_cb = NULL, + .xfer_isr = NULL, + .sof = NULL + }, + #endif + + #if CFG_TUD_ECM_RNDIS || CFG_TUD_NCM + { + .name = DRIVER_NAME("NET"), + .init = netd_init, + .deinit = netd_deinit, + .reset = netd_reset, + .open = netd_open, + .control_xfer_cb = netd_control_xfer_cb, + .xfer_cb = netd_xfer_cb, + .xfer_isr = NULL, + .sof = NULL, + }, + #endif + + #if CFG_TUD_BTH + { + .name = DRIVER_NAME("BTH"), + .init = btd_init, + .deinit = btd_deinit, + .reset = btd_reset, + .open = btd_open, + .control_xfer_cb = btd_control_xfer_cb, + .xfer_cb = btd_xfer_cb, + .xfer_isr = NULL, + .sof = NULL + }, + #endif + + #if CFG_TUD_MTP + { + .name = DRIVER_NAME("MTP"), + .init = mtpd_init, + .deinit = mtpd_deinit, + .reset = mtpd_reset, + .open = mtpd_open, + .control_xfer_cb = mtpd_control_xfer_cb, + .xfer_cb = mtpd_xfer_cb, + .xfer_isr = NULL, + .sof = NULL + }, + #endif }; enum { BUILTIN_DRIVER_COUNT = TU_ARRAY_SIZE(_usbd_driver) }; // Additional class drivers implemented by application -static usbd_class_driver_t const * _app_driver = NULL; -static uint8_t _app_driver_count = 0; +tu_static usbd_class_driver_t const * _app_driver = NULL; +tu_static uint8_t _app_driver_count = 0; + +#define TOTAL_DRIVER_COUNT ((uint8_t) (_app_driver_count + BUILTIN_DRIVER_COUNT)) // virtually joins built-in and application drivers together. // Application is positioned first to allow overwriting built-in ones. -static inline usbd_class_driver_t const * get_driver(uint8_t drvid) -{ - // Application drivers - if ( usbd_app_driver_get_cb ) - { - if ( drvid < _app_driver_count ) return &_app_driver[drvid]; - drvid -= _app_driver_count; +TU_ATTR_ALWAYS_INLINE static inline usbd_class_driver_t const * get_driver(uint8_t drvid) { + usbd_class_driver_t const *driver = NULL; + if (drvid < _app_driver_count) { + // Application drivers + driver = &_app_driver[drvid]; + } else if (drvid < TOTAL_DRIVER_COUNT && BUILTIN_DRIVER_COUNT > 0) { + driver = &_usbd_driver[drvid - _app_driver_count]; } - - // Built-in drivers - if (drvid < BUILTIN_DRIVER_COUNT) return &_usbd_driver[drvid]; - - return NULL; + return driver; } -#define TOTAL_DRIVER_COUNT (_app_driver_count + BUILTIN_DRIVER_COUNT) - //--------------------------------------------------------------------+ // DCD Event //--------------------------------------------------------------------+ +enum { RHPORT_INVALID = 0xFFu }; +tu_static uint8_t _usbd_rhport = RHPORT_INVALID; -// Event queue -// OPT_MODE_DEVICE is used by OS NONE for mutex (disable usb isr) -OSAL_QUEUE_DEF(OPT_MODE_DEVICE, _usbd_qdef, CFG_TUD_TASK_QUEUE_SZ, dcd_event_t); +static OSAL_SPINLOCK_DEF(_usbd_spin, usbd_int_set); + +// Event queue: usbd_int_set() is used as mutex in OS NONE config +OSAL_QUEUE_DEF(usbd_int_set, _usbd_qdef, CFG_TUD_TASK_QUEUE_SZ, dcd_event_t); static osal_queue_t _usbd_q; -// Mutex for claiming endpoint, only needed when using with preempted RTOS -#if CFG_TUSB_OS != OPT_OS_NONE -static osal_mutex_def_t _ubsd_mutexdef; -static osal_mutex_t _usbd_mutex; +// Mutex for claiming endpoint +#if OSAL_MUTEX_REQUIRED + static osal_mutex_def_t _ubsd_mutexdef; + static osal_mutex_t _usbd_mutex; +#else + #define _usbd_mutex NULL #endif +TU_ATTR_ALWAYS_INLINE static inline bool queue_event(dcd_event_t const * event, bool in_isr) { + TU_ASSERT(osal_queue_send(_usbd_q, event, in_isr)); + tud_event_hook_cb(event->rhport, event->event_id, in_isr); + return true; +} //--------------------------------------------------------------------+ // Prototypes //--------------------------------------------------------------------+ -static void mark_interface_endpoint(uint8_t ep2drv[8][2], uint8_t const* p_desc, uint16_t desc_len, uint8_t driver_id); static bool process_control_request(uint8_t rhport, tusb_control_request_t const * p_request); static bool process_set_config(uint8_t rhport, uint8_t cfg_num); static bool process_get_descriptor(uint8_t rhport, tusb_control_request_t const * p_request); +#if CFG_TUD_TEST_MODE +static bool process_test_mode_cb(uint8_t rhport, uint8_t stage, tusb_control_request_t const * request) { + TU_VERIFY(CONTROL_STAGE_ACK == stage); + uint8_t const selector = tu_u16_high(request->wIndex); + TU_LOG_USBD(" Enter Test Mode (test selector index: %d)\r\n", selector); + dcd_enter_test_mode(rhport, (tusb_feature_test_mode_t) selector); + return true; +} +#endif + // from usbd_control.c void usbd_control_reset(void); void usbd_control_set_request(tusb_control_request_t const *request); -void usbd_control_set_complete_callback( bool (*fp) (uint8_t, tusb_control_request_t const * ) ); +void usbd_control_set_complete_callback( usbd_control_xfer_cb_t fp ); bool usbd_control_xfer_cb (uint8_t rhport, uint8_t ep_addr, xfer_result_t event, uint32_t xferred_bytes); +//--------------------------------------------------------------------+ +// Weak stubs: invoked if no strong implementation is available +//--------------------------------------------------------------------+ +TU_ATTR_WEAK usbd_class_driver_t const* usbd_app_driver_get_cb(uint8_t* driver_count) { + *driver_count = 0; + return NULL; +} + +TU_ATTR_WEAK bool dcd_edpt_xfer_fifo(uint8_t rhport, uint8_t ep_addr, tu_fifo_t * ff, uint16_t total_bytes) { + (void) rhport; (void) ep_addr; (void) ff; (void) total_bytes; + return false; +} //--------------------------------------------------------------------+ // Debug //--------------------------------------------------------------------+ -#if CFG_TUSB_DEBUG >= 2 -static char const* const _usbd_event_str[DCD_EVENT_COUNT] = -{ - "Invalid" , - "Bus Reset" , - "Unplugged" , - "SOF" , - "Suspend" , - "Resume" , - "Setup Received" , - "Xfer Complete" , - "Func Call" -}; - -static char const* const _tusb_std_request_str[] = -{ - "Get Status" , - "Clear Feature" , - "Reserved" , - "Set Feature" , - "Reserved" , - "Set Address" , - "Get Descriptor" , - "Set Descriptor" , - "Get Configuration" , - "Set Configuration" , - "Get Interface" , - "Set Interface" , - "Synch Frame" +#if CFG_TUSB_DEBUG >= CFG_TUD_LOG_LEVEL +tu_static char const* const _usbd_event_str[DCD_EVENT_COUNT] = { + "Invalid", + "Bus Reset", + "Unplugged", + "SOF", + "Suspend", + "Resume", + "Setup Received", + "Xfer Complete", + "Func Call" }; // for usbd_control to print the name of control complete driver -void usbd_driver_print_control_complete_name(bool (*control_complete) (uint8_t, tusb_control_request_t const * )) -{ - for (uint8_t i = 0; i < TOTAL_DRIVER_COUNT; i++) - { - usbd_class_driver_t const * driver = get_driver(i); - if ( driver->control_complete == control_complete ) - { - TU_LOG2(" %s control complete\r\n", driver->name); +void usbd_driver_print_control_complete_name(usbd_control_xfer_cb_t callback) { + for (uint8_t i = 0; i < TOTAL_DRIVER_COUNT; i++) { + usbd_class_driver_t const* driver = get_driver(i); + if (driver && driver->control_xfer_cb == callback) { + TU_LOG_USBD("%s control complete\r\n", driver->name); return; } } @@ -314,53 +452,86 @@ void usbd_driver_print_control_complete_name(bool (*control_complete) (uint8_t, //--------------------------------------------------------------------+ // Application API //--------------------------------------------------------------------+ -tusb_speed_t tud_speed_get(void) -{ +tusb_speed_t tud_speed_get(void) { return (tusb_speed_t) _usbd_dev.speed; } -bool tud_mounted(void) -{ - return _usbd_dev.configured; +bool tud_connected(void) { + return _usbd_dev.connected; } -bool tud_suspended(void) -{ +bool tud_mounted(void) { + return _usbd_dev.cfg_num ? true : false; +} + +bool tud_suspended(void) { return _usbd_dev.suspended; } -bool tud_remote_wakeup(void) -{ +bool tud_remote_wakeup(void) { // only wake up host if this feature is supported and enabled and we are suspended - TU_VERIFY (_usbd_dev.suspended && _usbd_dev.remote_wakeup_support && _usbd_dev.remote_wakeup_en ); - dcd_remote_wakeup(TUD_OPT_RHPORT); + TU_VERIFY (_usbd_dev.suspended && _usbd_dev.remote_wakeup_support && _usbd_dev.remote_wakeup_en); + dcd_remote_wakeup(_usbd_rhport); return true; } -bool tud_disconnect(void) -{ - TU_VERIFY(dcd_disconnect); - dcd_disconnect(TUD_OPT_RHPORT); +bool tud_disconnect(void) { + dcd_disconnect(_usbd_rhport); return true; } -bool tud_connect(void) -{ - TU_VERIFY(dcd_connect); - dcd_connect(TUD_OPT_RHPORT); +bool tud_connect(void) { + dcd_connect(_usbd_rhport); return true; } +void tud_sof_cb_enable(bool en) { + usbd_sof_enable(_usbd_rhport, SOF_CONSUMER_USER, en); +} + //--------------------------------------------------------------------+ // USBD Task //--------------------------------------------------------------------+ -bool tud_init (void) -{ - TU_LOG2("USBD init\r\n"); +bool tud_inited(void) { + return _usbd_rhport != RHPORT_INVALID; +} + +bool tud_rhport_init(uint8_t rhport, const tusb_rhport_init_t* rh_init) { + if (tud_inited()) { + return true; // skip if already initialized + } + TU_ASSERT(rh_init); +#if CFG_TUSB_DEBUG >= CFG_TUD_LOG_LEVEL + char const* speed_str = 0; + switch (rh_init->speed) { + case TUSB_SPEED_HIGH: + speed_str = "High"; + break; + case TUSB_SPEED_FULL: + speed_str = "Full"; + break; + case TUSB_SPEED_LOW: + speed_str = "Low"; + break; + case TUSB_SPEED_AUTO: + speed_str = "Auto"; + break; + default: + break; + } + TU_LOG_USBD("USBD init on controller %u, speed = %s\r\n", rhport, speed_str); + TU_LOG_INT(CFG_TUD_LOG_LEVEL, sizeof(usbd_device_t)); + TU_LOG_INT(CFG_TUD_LOG_LEVEL, sizeof(dcd_event_t)); + TU_LOG_INT(CFG_TUD_LOG_LEVEL, sizeof(tu_fifo_t)); + TU_LOG_INT(CFG_TUD_LOG_LEVEL, sizeof(tu_edpt_stream_t)); +#endif tu_varclr(&_usbd_dev); + _usbd_queued_setup = 0; + + osal_spin_init(&_usbd_spin); -#if CFG_TUSB_OS != OPT_OS_NONE +#if OSAL_MUTEX_REQUIRED // Init device mutex _usbd_mutex = osal_mutex_create(&_ubsd_mutexdef); TU_ASSERT(_usbd_mutex); @@ -371,46 +542,81 @@ bool tud_init (void) TU_ASSERT(_usbd_q); // Get application driver if available - if ( usbd_app_driver_get_cb ) - { - _app_driver = usbd_app_driver_get_cb(&_app_driver_count); - } + _app_driver = usbd_app_driver_get_cb(&_app_driver_count); + TU_ASSERT(_app_driver_count + BUILTIN_DRIVER_COUNT <= UINT8_MAX); // Init class drivers - for (uint8_t i = 0; i < TOTAL_DRIVER_COUNT; i++) - { - usbd_class_driver_t const * driver = get_driver(i); - TU_LOG2("%s init\r\n", driver->name); + for (uint8_t i = 0; i < TOTAL_DRIVER_COUNT; i++) { + usbd_class_driver_t const* driver = get_driver(i); + TU_ASSERT(driver && driver->init); + TU_LOG_USBD("%s init\r\n", driver->name); driver->init(); } + _usbd_rhport = rhport; + // Init device controller driver - dcd_init(TUD_OPT_RHPORT); - dcd_int_enable(TUD_OPT_RHPORT); + TU_ASSERT(dcd_init(rhport, rh_init)); + dcd_int_enable(rhport); return true; } -static void usbd_reset(uint8_t rhport) -{ - tu_varclr(&_usbd_dev); +bool tud_deinit(uint8_t rhport) { + if (!tud_inited()) { + return true; // skip if not initialized + } - memset(_usbd_dev.itf2drv, DRVID_INVALID, sizeof(_usbd_dev.itf2drv)); // invalid mapping - memset(_usbd_dev.ep2drv , DRVID_INVALID, sizeof(_usbd_dev.ep2drv )); // invalid mapping + TU_LOG_USBD("USBD deinit on controller %u\r\n", rhport); - usbd_control_reset(); + // Deinit device controller driver + dcd_int_disable(rhport); + dcd_disconnect(rhport); + dcd_deinit(rhport); - for ( uint8_t i = 0; i < TOTAL_DRIVER_COUNT; i++ ) - { - get_driver(i)->reset(rhport); + // Deinit class drivers + for (uint8_t i = 0; i < TOTAL_DRIVER_COUNT; i++) { + usbd_class_driver_t const* driver = get_driver(i); + if(driver && driver->deinit) { + TU_LOG_USBD("%s deinit\r\n", driver->name); + driver->deinit(); + } } + + // Deinit device queue & task + osal_queue_delete(_usbd_q); + _usbd_q = NULL; + +#if OSAL_MUTEX_REQUIRED + // TODO make sure there is no task waiting on this mutex + osal_mutex_delete(_usbd_mutex); + _usbd_mutex = NULL; +#endif + + _usbd_rhport = RHPORT_INVALID; + + return true; } -bool tud_task_event_ready(void) -{ - // Skip if stack is not initialized - if ( !tusb_inited() ) return false; +static void configuration_reset(uint8_t rhport) { + for (uint8_t i = 0; i < TOTAL_DRIVER_COUNT; i++) { + usbd_class_driver_t const* driver = get_driver(i); + TU_ASSERT(driver,); + driver->reset(rhport); + } + + tu_varclr(&_usbd_dev); + memset(_usbd_dev.itf2drv, DRVID_INVALID, sizeof(_usbd_dev.itf2drv)); // invalid mapping + memset(_usbd_dev.ep2drv, DRVID_INVALID, sizeof(_usbd_dev.ep2drv)); // invalid mapping +} + +static void usbd_reset(uint8_t rhport) { + configuration_reset(rhport); + usbd_control_reset(); +} +bool tud_task_event_ready(void) { + TU_VERIFY(tud_inited()); // Skip if stack is not initialized return !osal_queue_empty(_usbd_q); } @@ -418,126 +624,141 @@ bool tud_task_event_ready(void) * This top level thread manages all device controller event and delegates events to class-specific drivers. * This should be called periodically within the mainloop or rtos thread. * - @code - int main(void) - { + int main(void) { application_init(); - tusb_init(); + tusb_init(0, TUSB_ROLE_DEVICE); - while(1) // the mainloop - { + while(1) { // the mainloop application_code(); tud_task(); // tinyusb device task } } - @endcode */ -void tud_task (void) -{ +void tud_task_ext(uint32_t timeout_ms, bool in_isr) { + (void) in_isr; // not implemented yet + // Skip if stack is not initialized - if ( !tusb_inited() ) return; + if (!tud_inited()) return; // Loop until there is no more events in the queue - while (1) - { + while (1) { dcd_event_t event; + if (!osal_queue_receive(_usbd_q, &event, timeout_ms)) return; - if ( !osal_queue_receive(_usbd_q, &event) ) return; - -#if CFG_TUSB_DEBUG >= 2 - if (event.event_id == DCD_EVENT_SETUP_RECEIVED) TU_LOG2("\r\n"); // extra line for setup - TU_LOG2("USBD %s ", event.event_id < DCD_EVENT_COUNT ? _usbd_event_str[event.event_id] : "CORRUPTED"); +#if CFG_TUSB_DEBUG >= CFG_TUD_LOG_LEVEL + if (event.event_id == DCD_EVENT_SETUP_RECEIVED) TU_LOG_USBD("\r\n"); // extra line for setup + TU_LOG_USBD("USBD %s ", event.event_id < DCD_EVENT_COUNT ? _usbd_event_str[event.event_id] : "CORRUPTED"); #endif - switch ( event.event_id ) - { + switch (event.event_id) { case DCD_EVENT_BUS_RESET: - TU_LOG2("\r\n"); + TU_LOG_USBD(": %s Speed\r\n", tu_str_speed[event.bus_reset.speed]); usbd_reset(event.rhport); _usbd_dev.speed = event.bus_reset.speed; - break; + break; case DCD_EVENT_UNPLUGGED: - TU_LOG2("\r\n"); + TU_LOG_USBD("\r\n"); usbd_reset(event.rhport); - - // invoke callback - if (tud_umount_cb) tud_umount_cb(); - break; + tud_umount_cb(); + break; case DCD_EVENT_SETUP_RECEIVED: - TU_LOG2_VAR(&event.setup_received); - TU_LOG2("\r\n"); + TU_ASSERT(_usbd_queued_setup > 0,); + _usbd_queued_setup--; + TU_LOG_BUF(CFG_TUD_LOG_LEVEL, &event.setup_received, 8); + if (_usbd_queued_setup) { + TU_LOG_USBD(" Skipped since there is other SETUP in queue\r\n"); + break; + } // Mark as connected after receiving 1st setup packet. // But it is easier to set it every time instead of wasting time to check then set _usbd_dev.connected = 1; + // mark both in & out control as free + _usbd_dev.ep_status[0][TUSB_DIR_OUT].busy = 0; + _usbd_dev.ep_status[0][TUSB_DIR_OUT].claimed = 0; + _usbd_dev.ep_status[0][TUSB_DIR_IN].busy = 0; + _usbd_dev.ep_status[0][TUSB_DIR_IN].claimed = 0; + // Process control request - if ( !process_control_request(event.rhport, &event.setup_received) ) - { - TU_LOG2(" Stall EP0\r\n"); + if (!process_control_request(event.rhport, &event.setup_received)) { + TU_LOG_USBD(" Stall EP0\r\n"); // Failed -> stall both control endpoint IN and OUT dcd_edpt_stall(event.rhport, 0); dcd_edpt_stall(event.rhport, 0 | TUSB_DIR_IN_MASK); } - break; + break; - case DCD_EVENT_XFER_COMPLETE: - { + case DCD_EVENT_XFER_COMPLETE: { // Invoke the class callback associated with the endpoint address uint8_t const ep_addr = event.xfer_complete.ep_addr; - uint8_t const epnum = tu_edpt_number(ep_addr); - uint8_t const ep_dir = tu_edpt_dir(ep_addr); + uint8_t const epnum = tu_edpt_number(ep_addr); + uint8_t const ep_dir = tu_edpt_dir(ep_addr); - TU_LOG2("on EP %02X with %u bytes\r\n", ep_addr, (unsigned int) event.xfer_complete.len); + TU_LOG_USBD("on EP %02X with %u bytes\r\n", ep_addr, (unsigned int) event.xfer_complete.len); - _usbd_dev.ep_status[epnum][ep_dir].busy = false; + _usbd_dev.ep_status[epnum][ep_dir].busy = 0; _usbd_dev.ep_status[epnum][ep_dir].claimed = 0; - if ( 0 == epnum ) - { - usbd_control_xfer_cb(event.rhport, ep_addr, (xfer_result_t)event.xfer_complete.result, event.xfer_complete.len); - } - else - { - usbd_class_driver_t const * driver = get_driver( _usbd_dev.ep2drv[epnum][ep_dir] ); - TU_ASSERT(driver, ); + if (0 == epnum) { + usbd_control_xfer_cb(event.rhport, ep_addr, (xfer_result_t) event.xfer_complete.result, + event.xfer_complete.len); + } else { + usbd_class_driver_t const* driver = get_driver(_usbd_dev.ep2drv[epnum][ep_dir]); + TU_ASSERT(driver,); - TU_LOG2(" %s xfer callback\r\n", driver->name); - driver->xfer_cb(event.rhport, ep_addr, event.xfer_complete.result, event.xfer_complete.len); + TU_LOG_USBD(" %s xfer callback\r\n", driver->name); + driver->xfer_cb(event.rhport, ep_addr, (xfer_result_t) event.xfer_complete.result, event.xfer_complete.len); } + break; } - break; case DCD_EVENT_SUSPEND: - TU_LOG2("\r\n"); - if (tud_suspend_cb) tud_suspend_cb(_usbd_dev.remote_wakeup_en); - break; + // NOTE: When plugging/unplugging device, the D+/D- state are unstable and + // can accidentally meet the SUSPEND condition ( Bus Idle for 3ms ), which result in a series of event + // e.g suspend -> resume -> unplug/plug. Skip suspend/resume if not connected + if (_usbd_dev.connected) { + TU_LOG_USBD(": Remote Wakeup = %u\r\n", _usbd_dev.remote_wakeup_en); + tud_suspend_cb(_usbd_dev.remote_wakeup_en); + } else { + TU_LOG_USBD(" Skipped\r\n"); + } + break; case DCD_EVENT_RESUME: - TU_LOG2("\r\n"); - if (tud_resume_cb) tud_resume_cb(); - break; - - case DCD_EVENT_SOF: - TU_LOG2("\r\n"); - for ( uint8_t i = 0; i < TOTAL_DRIVER_COUNT; i++ ) - { - usbd_class_driver_t const * driver = get_driver(i); - if ( driver->sof ) driver->sof(event.rhport); + if (_usbd_dev.connected) { + TU_LOG_USBD("\r\n"); + tud_resume_cb(); + } else { + TU_LOG_USBD(" Skipped\r\n"); } - break; + break; case USBD_EVENT_FUNC_CALL: - TU_LOG2("\r\n"); - if ( event.func_call.func ) event.func_call.func(event.func_call.param); + TU_LOG_USBD("\r\n"); + if (event.func_call.func) { + event.func_call.func(event.func_call.param); + } + break; + + case DCD_EVENT_SOF: + if (tu_bit_test(_usbd_dev.sof_consumer, SOF_CONSUMER_USER)) { + TU_LOG_USBD("\r\n"); + tud_sof_cb(event.sof.frame_count); + } break; default: TU_BREAKPOINT(); - break; + break; } + +#if CFG_TUSB_OS != OPT_OS_NONE && CFG_TUSB_OS != OPT_OS_PICO + // return if there is no more events, for application to run other background + if (osal_queue_empty(_usbd_q)) { return; } +#endif } } @@ -546,44 +767,35 @@ void tud_task (void) //--------------------------------------------------------------------+ // Helper to invoke class driver control request handler -static bool invoke_class_control(uint8_t rhport, usbd_class_driver_t const * driver, tusb_control_request_t const * request) -{ - usbd_control_set_complete_callback(driver->control_complete); - TU_LOG2(" %s control request\r\n", driver->name); - return driver->control_request(rhport, request); +static bool invoke_class_control(uint8_t rhport, usbd_class_driver_t const * driver, tusb_control_request_t const * request) { + usbd_control_set_complete_callback(driver->control_xfer_cb); + TU_LOG_USBD(" %s control request\r\n", driver->name); + return driver->control_xfer_cb(rhport, CONTROL_STAGE_SETUP, request); } // This handles the actual request and its response. -// return false will cause its caller to stall control endpoint -static bool process_control_request(uint8_t rhport, tusb_control_request_t const * p_request) -{ +// Returns false if unable to complete the request, causing caller to stall control endpoints. +static bool process_control_request(uint8_t rhport, tusb_control_request_t const * p_request) { usbd_control_set_complete_callback(NULL); - TU_ASSERT(p_request->bmRequestType_bit.type < TUSB_REQ_TYPE_INVALID); // Vendor request - if ( p_request->bmRequestType_bit.type == TUSB_REQ_TYPE_VENDOR ) - { - TU_VERIFY(tud_vendor_control_request_cb); - - if (tud_vendor_control_complete_cb) usbd_control_set_complete_callback(tud_vendor_control_complete_cb); - return tud_vendor_control_request_cb(rhport, p_request); + if ( p_request->bmRequestType_bit.type == TUSB_REQ_TYPE_VENDOR ) { + usbd_control_set_complete_callback(tud_vendor_control_xfer_cb); + return tud_vendor_control_xfer_cb(rhport, CONTROL_STAGE_SETUP, p_request); } -#if CFG_TUSB_DEBUG >= 2 - if (TUSB_REQ_TYPE_STANDARD == p_request->bmRequestType_bit.type && p_request->bRequest <= TUSB_REQ_SYNCH_FRAME) - { - TU_LOG2(" %s", _tusb_std_request_str[p_request->bRequest]); - if (TUSB_REQ_GET_DESCRIPTOR != p_request->bRequest) TU_LOG2("\r\n"); +#if CFG_TUSB_DEBUG >= CFG_TUD_LOG_LEVEL + if (TUSB_REQ_TYPE_STANDARD == p_request->bmRequestType_bit.type && p_request->bRequest <= TUSB_REQ_SYNCH_FRAME) { + TU_LOG_USBD(" %s", tu_str_std_request[p_request->bRequest]); + if (TUSB_REQ_GET_DESCRIPTOR != p_request->bRequest) TU_LOG_USBD("\r\n"); } #endif - switch ( p_request->bmRequestType_bit.recipient ) - { + switch ( p_request->bmRequestType_bit.recipient ) { //------------- Device Requests e.g in enumeration -------------// case TUSB_REQ_RCPT_DEVICE: - if ( TUSB_REQ_TYPE_CLASS == p_request->bmRequestType_bit.type ) - { + if ( TUSB_REQ_TYPE_CLASS == p_request->bmRequestType_bit.type ) { uint8_t const itf = tu_u16_low(p_request->wIndex); TU_VERIFY(itf < TU_ARRAY_SIZE(_usbd_dev.itf2drv)); @@ -593,15 +805,14 @@ static bool process_control_request(uint8_t rhport, tusb_control_request_t const // forward to class driver: "non-STD request to Interface" return invoke_class_control(rhport, driver, p_request); } - if ( TUSB_REQ_TYPE_STANDARD != p_request->bmRequestType_bit.type ) - { - // Non standard request is not supported + + if ( TUSB_REQ_TYPE_STANDARD != p_request->bmRequestType_bit.type ) { + // Non-standard request is not supported TU_BREAKPOINT(); return false; } - switch ( p_request->bRequest ) - { + switch ( p_request->bRequest ) { case TUSB_REQ_SET_ADDRESS: // Depending on mcu, status phase could be sent either before or after changing device address, // or even require stack to not response with status at all @@ -612,19 +823,50 @@ static bool process_control_request(uint8_t rhport, tusb_control_request_t const _usbd_dev.addressed = 1; break; - case TUSB_REQ_GET_CONFIGURATION: - { - uint8_t cfgnum = _usbd_dev.configured ? 1 : 0; - tud_control_xfer(rhport, p_request, &cfgnum, 1); + case TUSB_REQ_GET_CONFIGURATION: { + uint8_t cfg_num = _usbd_dev.cfg_num; + tud_control_xfer(rhport, p_request, &cfg_num, 1); } break; - case TUSB_REQ_SET_CONFIGURATION: - { + case TUSB_REQ_SET_CONFIGURATION: { uint8_t const cfg_num = (uint8_t) p_request->wValue; - if ( !_usbd_dev.configured && cfg_num ) TU_ASSERT( process_set_config(rhport, cfg_num) ); - _usbd_dev.configured = cfg_num ? 1 : 0; + // Only process if new configure is different + if (_usbd_dev.cfg_num != cfg_num) { + if ( _usbd_dev.cfg_num ) { + // already configured: need to clear all endpoints and driver first + TU_LOG_USBD(" Clear current Configuration (%u) before switching\r\n", _usbd_dev.cfg_num); + + // disable SOF + dcd_sof_enable(rhport, false); + + // close all non-control endpoints, cancel all pending transfers if any + dcd_edpt_close_all(rhport); + + // close all drivers and current configured state except bus speed + uint8_t const speed = _usbd_dev.speed; + configuration_reset(rhport); + + _usbd_dev.speed = speed; // restore speed + } + + _usbd_dev.cfg_num = cfg_num; + + // Handle the new configuration and execute the corresponding callback + if ( cfg_num ) { + // switch to new configuration if not zero + if (!process_set_config(rhport, cfg_num)) { + TU_MESS_FAILED(); + TU_BREAKPOINT(); + _usbd_dev.cfg_num = 0; + return false; + } + tud_mount_cb(); + } else { + tud_umount_cb(); + } + } tud_control_status(rhport, p_request); } @@ -635,32 +877,52 @@ static bool process_control_request(uint8_t rhport, tusb_control_request_t const break; case TUSB_REQ_SET_FEATURE: - // Only support remote wakeup for device feature - TU_VERIFY(TUSB_REQ_FEATURE_REMOTE_WAKEUP == p_request->wValue); - - // Host may enable remote wake up before suspending especially HID device - _usbd_dev.remote_wakeup_en = true; - tud_control_status(rhport, p_request); + switch(p_request->wValue) { + case TUSB_REQ_FEATURE_REMOTE_WAKEUP: + TU_LOG_USBD(" Enable Remote Wakeup\r\n"); + // Host may enable remote wake up before suspending especially HID device + _usbd_dev.remote_wakeup_en = true; + tud_control_status(rhport, p_request); + break; + + #if CFG_TUD_TEST_MODE + case TUSB_REQ_FEATURE_TEST_MODE: { + // Only handle the test mode if supported and valid + TU_VERIFY(0 == tu_u16_low(p_request->wIndex)); + + uint8_t const selector = tu_u16_high(p_request->wIndex); + TU_VERIFY(TUSB_FEATURE_TEST_J <= selector && selector <= TUSB_FEATURE_TEST_FORCE_ENABLE); + + usbd_control_set_complete_callback(process_test_mode_cb); + tud_control_status(rhport, p_request); + break; + } + #endif /* CFG_TUD_TEST_MODE */ + + // Stall unsupported feature selector + default: return false; + } break; case TUSB_REQ_CLEAR_FEATURE: // Only support remote wakeup for device feature TU_VERIFY(TUSB_REQ_FEATURE_REMOTE_WAKEUP == p_request->wValue); + TU_LOG_USBD(" Disable Remote Wakeup\r\n"); + // Host may disable remote wake up after resuming _usbd_dev.remote_wakeup_en = false; tud_control_status(rhport, p_request); break; - case TUSB_REQ_GET_STATUS: - { + case TUSB_REQ_GET_STATUS: { // Device status bit mask // - Bit 0: Self Powered // - Bit 1: Remote Wakeup enabled - uint16_t status = (_usbd_dev.self_powered ? 1 : 0) | (_usbd_dev.remote_wakeup_en ? 2 : 0); + uint16_t status = (uint16_t) ((_usbd_dev.self_powered ? 1u : 0u) | (_usbd_dev.remote_wakeup_en ? 2u : 0u)); tud_control_xfer(rhport, p_request, &status, 2); + break; } - break; // Unknown/Unsupported request default: TU_BREAKPOINT(); return false; @@ -668,8 +930,7 @@ static bool process_control_request(uint8_t rhport, tusb_control_request_t const break; //------------- Class/Interface Specific Request -------------// - case TUSB_REQ_RCPT_INTERFACE: - { + case TUSB_REQ_RCPT_INTERFACE: { uint8_t const itf = tu_u16_low(p_request->wIndex); TU_VERIFY(itf < TU_ARRAY_SIZE(_usbd_dev.itf2drv)); @@ -678,85 +939,91 @@ static bool process_control_request(uint8_t rhport, tusb_control_request_t const // all requests to Interface (STD or Class) is forwarded to class driver. // notable requests are: GET HID REPORT DESCRIPTOR, SET_INTERFACE, GET_INTERFACE - if ( !invoke_class_control(rhport, driver, p_request) ) - { - // For GET_INTERFACE, it is mandatory to respond even if the class - // driver doesn't use alternate settings. - TU_VERIFY( TUSB_REQ_TYPE_STANDARD == p_request->bmRequestType_bit.type && - TUSB_REQ_GET_INTERFACE == p_request->bRequest); + if ( !invoke_class_control(rhport, driver, p_request) ) { + // For GET_INTERFACE and SET_INTERFACE, it is mandatory to respond even if the class + // driver doesn't use alternate settings or implement this + TU_VERIFY(TUSB_REQ_TYPE_STANDARD == p_request->bmRequestType_bit.type); + + switch(p_request->bRequest) { + case TUSB_REQ_GET_INTERFACE: + case TUSB_REQ_SET_INTERFACE: + // Clear complete callback if driver set since it can also stall the request. + usbd_control_set_complete_callback(NULL); + + if (TUSB_REQ_GET_INTERFACE == p_request->bRequest) { + uint8_t alternate = 0; + tud_control_xfer(rhport, p_request, &alternate, 1); + }else { + tud_control_status(rhport, p_request); + } + break; - uint8_t alternate = 0; - tud_control_xfer(rhport, p_request, &alternate, 1); + default: return false; + } } + break; } - break; //------------- Endpoint Request -------------// - case TUSB_REQ_RCPT_ENDPOINT: - { + case TUSB_REQ_RCPT_ENDPOINT: { uint8_t const ep_addr = tu_u16_low(p_request->wIndex); uint8_t const ep_num = tu_edpt_number(ep_addr); uint8_t const ep_dir = tu_edpt_dir(ep_addr); TU_ASSERT(ep_num < TU_ARRAY_SIZE(_usbd_dev.ep2drv) ); + usbd_class_driver_t const * driver = get_driver(_usbd_dev.ep2drv[ep_num][ep_dir]); - bool ret = false; - - // Handle STD request to endpoint - if ( TUSB_REQ_TYPE_STANDARD == p_request->bmRequestType_bit.type ) - { - // force return true for standard request - ret = true; - - switch ( p_request->bRequest ) - { - case TUSB_REQ_GET_STATUS: - { + if ( TUSB_REQ_TYPE_STANDARD != p_request->bmRequestType_bit.type ) { + // Forward class request to its driver + TU_VERIFY(driver); + return invoke_class_control(rhport, driver, p_request); + } else { + // Handle STD request to endpoint + switch ( p_request->bRequest ) { + case TUSB_REQ_GET_STATUS: { uint16_t status = usbd_edpt_stalled(rhport, ep_addr) ? 0x0001 : 0x0000; tud_control_xfer(rhport, p_request, &status, 2); } break; case TUSB_REQ_CLEAR_FEATURE: - if ( TUSB_REQ_FEATURE_EDPT_HALT == p_request->wValue ) usbd_edpt_clear_stall(rhport, ep_addr); - tud_control_status(rhport, p_request); - break; - - case TUSB_REQ_SET_FEATURE: - if ( TUSB_REQ_FEATURE_EDPT_HALT == p_request->wValue ) usbd_edpt_stall(rhport, ep_addr); - tud_control_status(rhport, p_request); + case TUSB_REQ_SET_FEATURE: { + if ( TUSB_REQ_FEATURE_EDPT_HALT == p_request->wValue ) { + if ( TUSB_REQ_CLEAR_FEATURE == p_request->bRequest ) { + usbd_edpt_clear_stall(rhport, ep_addr); + }else { + usbd_edpt_stall(rhport, ep_addr); + } + } + + if (driver) { + // Some classes such as USBTMC needs to clear/re-init its buffer when receiving CLEAR_FEATURE request + // We will also forward std request targeted endpoint to class drivers as well + + // STD request must always be ACKed regardless of driver returned value + // Also clear complete callback if driver set since it can also stall the request. + (void) invoke_class_control(rhport, driver, p_request); + usbd_control_set_complete_callback(NULL); + + // skip ZLP status if driver already did that + if ( !_usbd_dev.ep_status[0][TUSB_DIR_IN].busy ) tud_control_status(rhport, p_request); + } + } break; // Unknown/Unsupported request - default: TU_BREAKPOINT(); return false; + default: + TU_BREAKPOINT(); + return false; } } - - usbd_class_driver_t const * driver = get_driver(_usbd_dev.ep2drv[ep_num][ep_dir]); - - if (driver) - { - // Some classes such as USBTMC needs to clear/re-init its buffer when receiving CLEAR_FEATURE request - // We will forward all request targeted endpoint to class drivers after - // - For class-type requests: driver is fully responsible to reply to host - // - For std-type requests : driver init/re-init internal variable/buffer only, and - // must not call tud_control_status(), driver's return value will have no effect. - // EP state has already affected (stalled/cleared) - if ( invoke_class_control(rhport, driver, p_request) ) ret = true; - } - - if ( TUSB_REQ_TYPE_STANDARD == p_request->bmRequestType_bit.type ) - { - // Set complete callback = NULL since it can also stall the request. - usbd_control_set_complete_callback(NULL); - } - - return ret; } break; // Unknown recipient - default: TU_BREAKPOINT(); return false; + default: + TU_BREAKPOINT(); + return false; } return true; @@ -766,100 +1033,103 @@ static bool process_control_request(uint8_t rhport, tusb_control_request_t const // This function parse configuration descriptor & open drivers accordingly static bool process_set_config(uint8_t rhport, uint8_t cfg_num) { - tusb_desc_configuration_t const * desc_cfg = (tusb_desc_configuration_t const *) tud_descriptor_configuration_cb(cfg_num-1); // index is cfg_num-1 + // index is cfg_num-1 + tusb_desc_configuration_t const * desc_cfg = (tusb_desc_configuration_t const *) tud_descriptor_configuration_cb(cfg_num-1); TU_ASSERT(desc_cfg != NULL && desc_cfg->bDescriptorType == TUSB_DESC_CONFIGURATION); // Parse configuration descriptor - _usbd_dev.remote_wakeup_support = (desc_cfg->bmAttributes & TUSB_DESC_CONFIG_ATT_REMOTE_WAKEUP) ? 1 : 0; - _usbd_dev.self_powered = (desc_cfg->bmAttributes & TUSB_DESC_CONFIG_ATT_SELF_POWERED) ? 1 : 0; + _usbd_dev.remote_wakeup_support = (desc_cfg->bmAttributes & TUSB_DESC_CONFIG_ATT_REMOTE_WAKEUP) ? 1u : 0u; + _usbd_dev.self_powered = (desc_cfg->bmAttributes & TUSB_DESC_CONFIG_ATT_SELF_POWERED ) ? 1u : 0u; // Parse interface descriptor uint8_t const * p_desc = ((uint8_t const*) desc_cfg) + sizeof(tusb_desc_configuration_t); - uint8_t const * desc_end = ((uint8_t const*) desc_cfg) + desc_cfg->wTotalLength; + uint8_t const * desc_end = ((uint8_t const*) desc_cfg) + tu_le16toh(desc_cfg->wTotalLength); while( p_desc < desc_end ) { - tusb_desc_interface_assoc_t const * desc_itf_assoc = NULL; + uint8_t assoc_itf_count = 1; // Class will always starts with Interface Association (if any) and then Interface descriptor if ( TUSB_DESC_INTERFACE_ASSOCIATION == tu_desc_type(p_desc) ) { - desc_itf_assoc = (tusb_desc_interface_assoc_t const *) p_desc; + tusb_desc_interface_assoc_t const * desc_iad = (tusb_desc_interface_assoc_t const *) p_desc; + assoc_itf_count = desc_iad->bInterfaceCount; + p_desc = tu_desc_next(p_desc); // next to Interface + + // IAD's first interface number and class should match with opened interface + //TU_ASSERT(desc_iad->bFirstInterface == desc_itf->bInterfaceNumber && + // desc_iad->bFunctionClass == desc_itf->bInterfaceClass); } TU_ASSERT( TUSB_DESC_INTERFACE == tu_desc_type(p_desc) ); - tusb_desc_interface_t const * desc_itf = (tusb_desc_interface_t const*) p_desc; - uint16_t const remaining_len = desc_end-p_desc; + // Find driver for this interface + uint16_t const remaining_len = (uint16_t) (desc_end-p_desc); uint8_t drv_id; for (drv_id = 0; drv_id < TOTAL_DRIVER_COUNT; drv_id++) { usbd_class_driver_t const *driver = get_driver(drv_id); + TU_ASSERT(driver); uint16_t const drv_len = driver->open(rhport, desc_itf, remaining_len); - if ( drv_len > 0 ) + if ( (sizeof(tusb_desc_interface_t) <= drv_len) && (drv_len <= remaining_len) ) { - // Open successfully, check if length is correct - TU_ASSERT( sizeof(tusb_desc_interface_t) <= drv_len && drv_len <= remaining_len); + // Open successfully + TU_LOG_USBD(" %s opened\r\n", driver->name); - // Interface number must not be used already - TU_ASSERT(DRVID_INVALID == _usbd_dev.itf2drv[desc_itf->bInterfaceNumber]); + // Some drivers use 2 or more interfaces but may not have IAD e.g MIDI (always) or + // BTH (even CDC) with class in device descriptor (single interface) + if ( assoc_itf_count == 1) + { + #if CFG_TUD_CDC + if ( driver->open == cdcd_open ) assoc_itf_count = 2; + #endif + + #if CFG_TUD_MIDI + if (driver->open == midid_open) { + // If there is a class-compliant Audio Control Class, then 2 interfaces. Otherwise, only one + if (TUSB_CLASS_AUDIO == desc_itf->bInterfaceClass && + AUDIO_SUBCLASS_CONTROL == desc_itf->bInterfaceSubClass && + AUDIO_FUNC_PROTOCOL_CODE_UNDEF == desc_itf->bInterfaceProtocol) { + assoc_itf_count = 2; + } + } + #endif - TU_LOG2(" %s opened\r\n", driver->name); - _usbd_dev.itf2drv[desc_itf->bInterfaceNumber] = drv_id; + #if CFG_TUD_BTH && CFG_TUD_BTH_ISO_ALT_COUNT + if ( driver->open == btd_open ) assoc_itf_count = 2; + #endif + } - // If IAD exist, assign all interfaces to the same driver - if (desc_itf_assoc) + // bind (associated) interfaces to found driver + for(uint8_t i=0; ibFirstInterface == desc_itf->bInterfaceNumber && - desc_itf_assoc->bFunctionClass == desc_itf->bInterfaceClass); + uint8_t const itf_num = desc_itf->bInterfaceNumber+i; - for(uint8_t i=1; ibInterfaceCount; i++) - { - _usbd_dev.itf2drv[desc_itf->bInterfaceNumber+i] = drv_id; - } + // Interface number must not be used already + TU_ASSERT(DRVID_INVALID == _usbd_dev.itf2drv[itf_num]); + _usbd_dev.itf2drv[itf_num] = drv_id; } - mark_interface_endpoint(_usbd_dev.ep2drv, p_desc, drv_len, drv_id); // TODO refactor + // bind all endpoints to found driver + tu_edpt_bind_driver(_usbd_dev.ep2drv, desc_itf, drv_len, drv_id); - p_desc += drv_len; // next interface + // next Interface + p_desc += drv_len; - break; + break; // exit driver find loop } } - // Failed if cannot find supported driver + // Failed if there is no supported drivers TU_ASSERT(drv_id < TOTAL_DRIVER_COUNT); } - // invoke callback - if (tud_mount_cb) tud_mount_cb(); - return true; } -// Helper marking endpoint of interface belongs to class driver -static void mark_interface_endpoint(uint8_t ep2drv[8][2], uint8_t const* p_desc, uint16_t desc_len, uint8_t driver_id) -{ - uint16_t len = 0; - - while( len < desc_len ) - { - if ( TUSB_DESC_ENDPOINT == tu_desc_type(p_desc) ) - { - uint8_t const ep_addr = ((tusb_desc_endpoint_t const*) p_desc)->bEndpointAddress; - - ep2drv[tu_edpt_number(ep_addr)][tu_edpt_dir(ep_addr)] = driver_id; - } - - len = (uint16_t)(len + tu_desc_len(p_desc)); - p_desc = tu_desc_next(p_desc); - } -} - // return descriptor's buffer and update desc_len static bool process_get_descriptor(uint8_t rhport, tusb_control_request_t const * p_request) { @@ -868,94 +1138,84 @@ static bool process_get_descriptor(uint8_t rhport, tusb_control_request_t const switch(desc_type) { - case TUSB_DESC_DEVICE: - { - TU_LOG2(" Device\r\n"); + case TUSB_DESC_DEVICE: { + TU_LOG_USBD(" Device\r\n"); - uint16_t len = sizeof(tusb_desc_device_t); + void* desc_device = (void*) (uintptr_t) tud_descriptor_device_cb(); + TU_ASSERT(desc_device); - // Only send up to EP0 Packet Size if not addressed + // Only response with exactly 1 Packet if: not addressed and host requested more data than device descriptor has. // This only happens with the very first get device descriptor and EP0 size = 8 or 16. - if ((CFG_TUD_ENDPOINT0_SIZE < sizeof(tusb_desc_device_t)) && !_usbd_dev.addressed) - { - len = CFG_TUD_ENDPOINT0_SIZE; - + if ((CFG_TUD_ENDPOINT0_SIZE < sizeof(tusb_desc_device_t)) && !_usbd_dev.addressed && + ((tusb_control_request_t const*) p_request)->wLength > sizeof(tusb_desc_device_t)) { // Hack here: we modify the request length to prevent usbd_control response with zlp - ((tusb_control_request_t*) p_request)->wLength = CFG_TUD_ENDPOINT0_SIZE; - } + // since we are responding with 1 packet & less data than wLength. + tusb_control_request_t mod_request = *p_request; + mod_request.wLength = CFG_TUD_ENDPOINT0_SIZE; - return tud_control_xfer(rhport, p_request, (void*) tud_descriptor_device_cb(), len); + return tud_control_xfer(rhport, &mod_request, desc_device, CFG_TUD_ENDPOINT0_SIZE); + }else { + return tud_control_xfer(rhport, p_request, desc_device, sizeof(tusb_desc_device_t)); + } } - break; + // break; // unreachable - case TUSB_DESC_BOS: - { - TU_LOG2(" BOS\r\n"); + case TUSB_DESC_BOS: { + TU_LOG_USBD(" BOS\r\n"); // requested by host if USB > 2.0 ( i.e 2.1 or 3.x ) - if (!tud_descriptor_bos_cb) return false; + uintptr_t desc_bos = (uintptr_t) tud_descriptor_bos_cb(); + TU_VERIFY(desc_bos); - tusb_desc_bos_t const* desc_bos = (tusb_desc_bos_t const*) tud_descriptor_bos_cb(); - - uint16_t total_len; // Use offsetof to avoid pointer to the odd/misaligned address - memcpy(&total_len, (uint8_t*) desc_bos + offsetof(tusb_desc_bos_t, wTotalLength), 2); + uint16_t const total_len = tu_le16toh( tu_unaligned_read16((const void*) (desc_bos + offsetof(tusb_desc_bos_t, wTotalLength))) ); return tud_control_xfer(rhport, p_request, (void*) desc_bos, total_len); } - break; + // break; // unreachable case TUSB_DESC_CONFIGURATION: - { - TU_LOG2(" Configuration[%u]\r\n", desc_index); - - tusb_desc_configuration_t const* desc_config = (tusb_desc_configuration_t const*) tud_descriptor_configuration_cb(desc_index); - TU_ASSERT(desc_config); + case TUSB_DESC_OTHER_SPEED_CONFIG: { + uintptr_t desc_config; + + if ( desc_type == TUSB_DESC_CONFIGURATION ) { + TU_LOG_USBD(" Configuration[%u]\r\n", desc_index); + desc_config = (uintptr_t) tud_descriptor_configuration_cb(desc_index); + TU_ASSERT(desc_config); + }else { + // Host only request this after getting Device Qualifier descriptor + TU_LOG_USBD(" Other Speed Configuration\r\n"); + desc_config = (uintptr_t) tud_descriptor_other_speed_configuration_cb(desc_index); + TU_VERIFY(desc_config); + } - uint16_t total_len; // Use offsetof to avoid pointer to the odd/misaligned address - memcpy(&total_len, (uint8_t*) desc_config + offsetof(tusb_desc_configuration_t, wTotalLength), 2); + uint16_t const total_len = tu_le16toh( tu_unaligned_read16((const void*) (desc_config + offsetof(tusb_desc_configuration_t, wTotalLength))) ); return tud_control_xfer(rhport, p_request, (void*) desc_config, total_len); } - break; + // break; // unreachable case TUSB_DESC_STRING: - TU_LOG2(" String[%u]\r\n", desc_index); + { + TU_LOG_USBD(" String[%u]\r\n", desc_index); // String Descriptor always uses the desc set from user - uint8_t const* desc_str = (uint8_t const*) tud_descriptor_string_cb(desc_index, p_request->wIndex); + uint8_t const* desc_str = (uint8_t const*) tud_descriptor_string_cb(desc_index, tu_le16toh(p_request->wIndex)); TU_VERIFY(desc_str); // first byte of descriptor is its size - return tud_control_xfer(rhport, p_request, (void*) desc_str, desc_str[0]); - break; - - case TUSB_DESC_DEVICE_QUALIFIER: - TU_LOG2(" Device Qualifier\r\n"); - - // Host sends this request to ask why our device with USB BCD from 2.0 - // but is running at Full/Low Speed. If not highspeed capable stall this request, - // otherwise return the descriptor that could work in highspeed mode - if ( tud_descriptor_device_qualifier_cb ) - { - uint8_t const* desc_qualifier = tud_descriptor_device_qualifier_cb(); - TU_ASSERT(desc_qualifier); - - // first byte of descriptor is its size - return tud_control_xfer(rhport, p_request, (void*) desc_qualifier, desc_qualifier[0]); - }else - { - return false; - } - break; - - case TUSB_DESC_OTHER_SPEED_CONFIG: - TU_LOG2(" Other Speed Configuration\r\n"); + return tud_control_xfer(rhport, p_request, (void*) (uintptr_t) desc_str, tu_desc_len(desc_str)); + } + // break; // unreachable - // After Device Qualifier descriptor is received host will ask for this descriptor - return false; // not supported - break; + case TUSB_DESC_DEVICE_QUALIFIER: { + TU_LOG_USBD(" Device Qualifier\r\n"); + uint8_t const* desc_qualifier = tud_descriptor_device_qualifier_cb(); + TU_VERIFY(desc_qualifier); + return tud_control_xfer(rhport, p_request, (void*) (uintptr_t) desc_qualifier, tu_desc_len(desc_qualifier)); + } + // break; // unreachable default: return false; } @@ -964,84 +1224,120 @@ static bool process_get_descriptor(uint8_t rhport, tusb_control_request_t const //--------------------------------------------------------------------+ // DCD Event Handler //--------------------------------------------------------------------+ -void dcd_event_handler(dcd_event_t const * event, bool in_isr) -{ - switch (event->event_id) - { +TU_ATTR_FAST_FUNC void dcd_event_handler(dcd_event_t const* event, bool in_isr) { + bool send = false; + switch (event->event_id) { case DCD_EVENT_UNPLUGGED: - _usbd_dev.connected = 0; - _usbd_dev.addressed = 0; - _usbd_dev.configured = 0; - _usbd_dev.suspended = 0; - osal_queue_send(_usbd_q, event, in_isr); - break; - - case DCD_EVENT_SOF: - return; // skip SOF event for now - break; + _usbd_dev.connected = 0; + _usbd_dev.addressed = 0; + _usbd_dev.cfg_num = 0; + _usbd_dev.suspended = 0; + send = true; + break; case DCD_EVENT_SUSPEND: - // NOTE: When plugging/unplugging device, the D+/D- state are unstable and can accidentally meet the - // SUSPEND condition ( Idle for 3ms ). Some MCUs such as SAMD doesn't distinguish suspend vs disconnect as well. - // We will skip handling SUSPEND/RESUME event if not currently connected - if ( _usbd_dev.connected ) - { + // NOTE: When plugging/unplugging device, the D+/D- state are unstable and + // can accidentally meet the SUSPEND condition ( Bus Idle for 3ms ). + // In addition, some MCUs such as SAMD or boards that haven no VBUS detection cannot distinguish + // suspended vs disconnected. We will skip handling SUSPEND/RESUME event if not currently connected + if (_usbd_dev.connected) { _usbd_dev.suspended = 1; - osal_queue_send(_usbd_q, event, in_isr); + send = true; } - break; + break; case DCD_EVENT_RESUME: // skip event if not connected (especially required for SAMD) - if ( _usbd_dev.connected ) - { + if (_usbd_dev.connected) { _usbd_dev.suspended = 0; - osal_queue_send(_usbd_q, event, in_isr); + send = true; } - break; + break; - default: - osal_queue_send(_usbd_q, event, in_isr); - break; - } -} + case DCD_EVENT_SOF: + // SOF driver handler in ISR context + for (uint8_t i = 0; i < TOTAL_DRIVER_COUNT; i++) { + usbd_class_driver_t const* driver = get_driver(i); + if (driver && driver->sof) { + driver->sof(event->rhport, event->sof.frame_count); + } + } -void dcd_event_bus_signal (uint8_t rhport, dcd_eventid_t eid, bool in_isr) -{ - dcd_event_t event = { .rhport = rhport, .event_id = eid }; - dcd_event_handler(&event, in_isr); -} + // Some MCUs after running dcd_remote_wakeup() does not have way to detect the end of remote wakeup + // which last 1-15 ms. DCD can use SOF as a clear indicator that bus is back to operational + if (_usbd_dev.suspended) { + _usbd_dev.suspended = 0; -void dcd_event_bus_reset (uint8_t rhport, tusb_speed_t speed, bool in_isr) -{ - dcd_event_t event = { .rhport = rhport, .event_id = DCD_EVENT_BUS_RESET }; - event.bus_reset.speed = speed; - dcd_event_handler(&event, in_isr); -} + dcd_event_t const event_resume = {.rhport = event->rhport, .event_id = DCD_EVENT_RESUME}; + queue_event(&event_resume, in_isr); + } -void dcd_event_setup_received(uint8_t rhport, uint8_t const * setup, bool in_isr) -{ - dcd_event_t event = { .rhport = rhport, .event_id = DCD_EVENT_SETUP_RECEIVED }; - memcpy(&event.setup_received, setup, 8); + if (tu_bit_test(_usbd_dev.sof_consumer, SOF_CONSUMER_USER)) { + dcd_event_t const event_sof = {.rhport = event->rhport, .event_id = DCD_EVENT_SOF, .sof.frame_count = event->sof.frame_count}; + queue_event(&event_sof, in_isr); + } + break; - dcd_event_handler(&event, in_isr); -} + case DCD_EVENT_SETUP_RECEIVED: + _usbd_queued_setup++; + send = true; + break; -void dcd_event_xfer_complete (uint8_t rhport, uint8_t ep_addr, uint32_t xferred_bytes, uint8_t result, bool in_isr) -{ - dcd_event_t event = { .rhport = rhport, .event_id = DCD_EVENT_XFER_COMPLETE }; + case DCD_EVENT_XFER_COMPLETE: { + // Invoke the class callback associated with the endpoint address + uint8_t const ep_addr = event->xfer_complete.ep_addr; + uint8_t const epnum = tu_edpt_number(ep_addr); + uint8_t const ep_dir = tu_edpt_dir(ep_addr); + + send = true; + if(epnum > 0) { + usbd_class_driver_t const* driver = get_driver(_usbd_dev.ep2drv[epnum][ep_dir]); + + if (driver && driver->xfer_isr) { + _usbd_dev.ep_status[epnum][ep_dir].busy = 0; + _usbd_dev.ep_status[epnum][ep_dir].claimed = 0; - event.xfer_complete.ep_addr = ep_addr; - event.xfer_complete.len = xferred_bytes; - event.xfer_complete.result = result; + send = !driver->xfer_isr(event->rhport, ep_addr, (xfer_result_t) event->xfer_complete.result, event->xfer_complete.len); + + // xfer_isr() is deferred to xfer_cb(), revert busy/claimed status + if (send) { + _usbd_dev.ep_status[epnum][ep_dir].busy = 1; + _usbd_dev.ep_status[epnum][ep_dir].claimed = 1; + } + } + } + break; + } - dcd_event_handler(&event, in_isr); + default: + send = true; + break; + } + + if (send) { + queue_event(event, in_isr); + } } //--------------------------------------------------------------------+ -// Helper +// USBD API For Class Driver //--------------------------------------------------------------------+ +void usbd_int_set(bool enabled) { + if (enabled) { + dcd_int_enable(_usbd_rhport); + } else { + dcd_int_disable(_usbd_rhport); + } +} + +void usbd_spin_lock(bool in_isr) { + osal_spin_lock(&_usbd_spin, in_isr); +} +void usbd_spin_unlock(bool in_isr) { + osal_spin_unlock(&_usbd_spin, in_isr); +} + // Parse consecutive endpoint descriptors (IN & OUT) bool usbd_open_edpt_pair(uint8_t rhport, uint8_t const* p_desc, uint8_t ep_count, uint8_t xfer_type, uint8_t* ep_out, uint8_t* ep_in) { @@ -1067,167 +1363,236 @@ bool usbd_open_edpt_pair(uint8_t rhport, uint8_t const* p_desc, uint8_t ep_count } // Helper to defer an isr function -void usbd_defer_func(osal_task_func_t func, void* param, bool in_isr) -{ - dcd_event_t event = - { +void usbd_defer_func(osal_task_func_t func, void* param, bool in_isr) { + dcd_event_t event = { .rhport = 0, .event_id = USBD_EVENT_FUNC_CALL, }; - event.func_call.func = func; event.func_call.param = param; - dcd_event_handler(&event, in_isr); + queue_event(&event, in_isr); } //--------------------------------------------------------------------+ // USBD Endpoint API //--------------------------------------------------------------------+ -bool usbd_edpt_open(uint8_t rhport, tusb_desc_endpoint_t const * desc_ep) -{ - TU_LOG2(" Open EP %02X with Size = %u\r\n", desc_ep->bEndpointAddress, desc_ep->wMaxPacketSize.size); +bool usbd_edpt_open(uint8_t rhport, tusb_desc_endpoint_t const* desc_ep) { + rhport = _usbd_rhport; + + TU_ASSERT(tu_edpt_number(desc_ep->bEndpointAddress) < CFG_TUD_ENDPPOINT_MAX); + TU_ASSERT(tu_edpt_validate(desc_ep, (tusb_speed_t) _usbd_dev.speed, false)); return dcd_edpt_open(rhport, desc_ep); } -bool usbd_edpt_claim(uint8_t rhport, uint8_t ep_addr) -{ +bool usbd_edpt_claim(uint8_t rhport, uint8_t ep_addr) { (void) rhport; - uint8_t const epnum = tu_edpt_number(ep_addr); - uint8_t const dir = tu_edpt_dir(ep_addr); + // TODO add this check later, also make sure we don't starve an out endpoint while suspending + // TU_VERIFY(tud_ready()); -#if CFG_TUSB_OS != OPT_OS_NONE - // pre-check to help reducing mutex lock - TU_VERIFY((_usbd_dev.ep_status[epnum][dir].busy == 0) && (_usbd_dev.ep_status[epnum][dir].claimed == 0)); + uint8_t const epnum = tu_edpt_number(ep_addr); + uint8_t const dir = tu_edpt_dir(ep_addr); + tu_edpt_state_t* ep_state = &_usbd_dev.ep_status[epnum][dir]; - osal_mutex_lock(_usbd_mutex, OSAL_TIMEOUT_WAIT_FOREVER); -#endif + return tu_edpt_claim(ep_state, _usbd_mutex); +} - // can only claim the endpoint if it is not busy and not claimed yet. - bool const ret = (_usbd_dev.ep_status[epnum][dir].busy == 0) && (_usbd_dev.ep_status[epnum][dir].claimed == 0); - if (ret) - { - _usbd_dev.ep_status[epnum][dir].claimed = 1; - } +bool usbd_edpt_release(uint8_t rhport, uint8_t ep_addr) { + (void) rhport; -#if CFG_TUSB_OS != OPT_OS_NONE - osal_mutex_unlock(_usbd_mutex); -#endif + uint8_t const epnum = tu_edpt_number(ep_addr); + uint8_t const dir = tu_edpt_dir(ep_addr); + tu_edpt_state_t* ep_state = &_usbd_dev.ep_status[epnum][dir]; - return ret; + return tu_edpt_release(ep_state, _usbd_mutex); } -bool usbd_edpt_release(uint8_t rhport, uint8_t ep_addr) -{ - (void) rhport; +bool usbd_edpt_xfer(uint8_t rhport, uint8_t ep_addr, uint8_t* buffer, uint16_t total_bytes) { + rhport = _usbd_rhport; uint8_t const epnum = tu_edpt_number(ep_addr); - uint8_t const dir = tu_edpt_dir(ep_addr); + uint8_t const dir = tu_edpt_dir(ep_addr); -#if CFG_TUSB_OS != OPT_OS_NONE - osal_mutex_lock(_usbd_mutex, OSAL_TIMEOUT_WAIT_FOREVER); -#endif + // TODO skip ready() check for now since enumeration also use this API + // TU_VERIFY(tud_ready()); - // can only release the endpoint if it is claimed and not busy - bool const ret = (_usbd_dev.ep_status[epnum][dir].busy == 0) && (_usbd_dev.ep_status[epnum][dir].claimed == 1); - if (ret) - { - _usbd_dev.ep_status[epnum][dir].claimed = 0; + TU_LOG_USBD(" Queue EP %02X with %u bytes ...\r\n", ep_addr, total_bytes); +#if CFG_TUD_LOG_LEVEL >= 3 + if(dir == TUSB_DIR_IN) { + TU_LOG_MEM(CFG_TUD_LOG_LEVEL, buffer, total_bytes, 2); } - -#if CFG_TUSB_OS != OPT_OS_NONE - osal_mutex_unlock(_usbd_mutex); #endif - return ret; + // Attempt to transfer on a busy endpoint, sound like an race condition ! + TU_ASSERT(_usbd_dev.ep_status[epnum][dir].busy == 0); + + // Set busy first since the actual transfer can be complete before dcd_edpt_xfer() + // could return and USBD task can preempt and clear the busy + _usbd_dev.ep_status[epnum][dir].busy = 1; + + if (dcd_edpt_xfer(rhport, ep_addr, buffer, total_bytes)) { + return true; + } else { + // DCD error, mark endpoint as ready to allow next transfer + _usbd_dev.ep_status[epnum][dir].busy = 0; + _usbd_dev.ep_status[epnum][dir].claimed = 0; + TU_LOG_USBD("FAILED\r\n"); + TU_BREAKPOINT(); + return false; + } } -bool usbd_edpt_xfer(uint8_t rhport, uint8_t ep_addr, uint8_t * buffer, uint16_t total_bytes) -{ +// The number of bytes has to be given explicitly to allow more flexible control of how many +// bytes should be written and second to keep the return value free to give back a boolean +// success message. If total_bytes is too big, the FIFO will copy only what is available +// into the USB buffer! +bool usbd_edpt_xfer_fifo(uint8_t rhport, uint8_t ep_addr, tu_fifo_t* ff, uint16_t total_bytes) { + rhport = _usbd_rhport; + uint8_t const epnum = tu_edpt_number(ep_addr); - uint8_t const dir = tu_edpt_dir(ep_addr); + uint8_t const dir = tu_edpt_dir(ep_addr); - TU_LOG2(" Queue EP %02X with %u bytes ... ", ep_addr, total_bytes); + TU_LOG_USBD(" Queue ISO EP %02X with %u bytes ... ", ep_addr, total_bytes); // Attempt to transfer on a busy endpoint, sound like an race condition ! TU_ASSERT(_usbd_dev.ep_status[epnum][dir].busy == 0); // Set busy first since the actual transfer can be complete before dcd_edpt_xfer() could return // and usbd task can preempt and clear the busy - _usbd_dev.ep_status[epnum][dir].busy = true; + _usbd_dev.ep_status[epnum][dir].busy = 1; - if ( dcd_edpt_xfer(rhport, ep_addr, buffer, total_bytes) ) - { - TU_LOG2("OK\r\n"); + if (dcd_edpt_xfer_fifo(rhport, ep_addr, ff, total_bytes)) { + TU_LOG_USBD("OK\r\n"); return true; - }else - { + } else { // DCD error, mark endpoint as ready to allow next transfer - _usbd_dev.ep_status[epnum][dir].busy = false; + _usbd_dev.ep_status[epnum][dir].busy = 0; _usbd_dev.ep_status[epnum][dir].claimed = 0; - TU_LOG2("failed\r\n"); + TU_LOG_USBD("failed\r\n"); TU_BREAKPOINT(); return false; } } -bool usbd_edpt_busy(uint8_t rhport, uint8_t ep_addr) -{ +bool usbd_edpt_busy(uint8_t rhport, uint8_t ep_addr) { (void) rhport; uint8_t const epnum = tu_edpt_number(ep_addr); - uint8_t const dir = tu_edpt_dir(ep_addr); + uint8_t const dir = tu_edpt_dir(ep_addr); return _usbd_dev.ep_status[epnum][dir].busy; } -void usbd_edpt_stall(uint8_t rhport, uint8_t ep_addr) -{ +void usbd_edpt_stall(uint8_t rhport, uint8_t ep_addr) { + rhport = _usbd_rhport; + uint8_t const epnum = tu_edpt_number(ep_addr); - uint8_t const dir = tu_edpt_dir(ep_addr); + uint8_t const dir = tu_edpt_dir(ep_addr); + // only stalled if currently cleared + TU_LOG_USBD(" Stall EP %02X\r\n", ep_addr); dcd_edpt_stall(rhport, ep_addr); - _usbd_dev.ep_status[epnum][dir].stalled = true; - _usbd_dev.ep_status[epnum][dir].busy = true; + _usbd_dev.ep_status[epnum][dir].stalled = 1; + _usbd_dev.ep_status[epnum][dir].busy = 1; } -void usbd_edpt_clear_stall(uint8_t rhport, uint8_t ep_addr) -{ +void usbd_edpt_clear_stall(uint8_t rhport, uint8_t ep_addr) { + rhport = _usbd_rhport; + uint8_t const epnum = tu_edpt_number(ep_addr); - uint8_t const dir = tu_edpt_dir(ep_addr); + uint8_t const dir = tu_edpt_dir(ep_addr); + // only clear if currently stalled + TU_LOG_USBD(" Clear Stall EP %02X\r\n", ep_addr); dcd_edpt_clear_stall(rhport, ep_addr); - _usbd_dev.ep_status[epnum][dir].stalled = false; - _usbd_dev.ep_status[epnum][dir].busy = false; + _usbd_dev.ep_status[epnum][dir].stalled = 0; + _usbd_dev.ep_status[epnum][dir].busy = 0; } -bool usbd_edpt_stalled(uint8_t rhport, uint8_t ep_addr) -{ +bool usbd_edpt_stalled(uint8_t rhport, uint8_t ep_addr) { (void) rhport; uint8_t const epnum = tu_edpt_number(ep_addr); - uint8_t const dir = tu_edpt_dir(ep_addr); + uint8_t const dir = tu_edpt_dir(ep_addr); return _usbd_dev.ep_status[epnum][dir].stalled; } /** * usbd_edpt_close will disable an endpoint. - * * In progress transfers on this EP may be delivered after this call. - * */ -void usbd_edpt_close(uint8_t rhport, uint8_t ep_addr) -{ - TU_ASSERT(dcd_edpt_close, /**/); - TU_LOG2(" CLOSING Endpoint: 0x%02X\r\n", ep_addr); +void usbd_edpt_close(uint8_t rhport, uint8_t ep_addr) { +#ifdef TUP_DCD_EDPT_ISO_ALLOC + (void) rhport; (void) ep_addr; + // ISO alloc/activate Should be used instead +#else + rhport = _usbd_rhport; + + TU_LOG_USBD(" CLOSING Endpoint: 0x%02X\r\n", ep_addr); + + uint8_t const epnum = tu_edpt_number(ep_addr); + uint8_t const dir = tu_edpt_dir(ep_addr); dcd_edpt_close(rhport, ep_addr); + _usbd_dev.ep_status[epnum][dir].stalled = 0; + _usbd_dev.ep_status[epnum][dir].busy = 0; + _usbd_dev.ep_status[epnum][dir].claimed = 0; +#endif return; } +void usbd_sof_enable(uint8_t rhport, sof_consumer_t consumer, bool en) { + rhport = _usbd_rhport; + + uint8_t consumer_old = _usbd_dev.sof_consumer; + // Keep track how many class instances need the SOF interrupt + if (en) { + _usbd_dev.sof_consumer |= (uint8_t)(1 << consumer); + } else { + _usbd_dev.sof_consumer &= (uint8_t)(~(1 << consumer)); + } + + // Test logically unequal + if(!_usbd_dev.sof_consumer != !consumer_old) { + dcd_sof_enable(rhport, _usbd_dev.sof_consumer); + } +} + +bool usbd_edpt_iso_alloc(uint8_t rhport, uint8_t ep_addr, uint16_t largest_packet_size) { +#ifdef TUP_DCD_EDPT_ISO_ALLOC + rhport = _usbd_rhport; + + TU_ASSERT(tu_edpt_number(ep_addr) < CFG_TUD_ENDPPOINT_MAX); + return dcd_edpt_iso_alloc(rhport, ep_addr, largest_packet_size); +#else + (void) rhport; (void) ep_addr; (void) largest_packet_size; + return false; +#endif +} + +bool usbd_edpt_iso_activate(uint8_t rhport, tusb_desc_endpoint_t const* desc_ep) { +#ifdef TUP_DCD_EDPT_ISO_ALLOC + rhport = _usbd_rhport; + + uint8_t const epnum = tu_edpt_number(desc_ep->bEndpointAddress); + uint8_t const dir = tu_edpt_dir(desc_ep->bEndpointAddress); + + TU_ASSERT(epnum < CFG_TUD_ENDPPOINT_MAX); + TU_ASSERT(tu_edpt_validate(desc_ep, (tusb_speed_t) _usbd_dev.speed, false)); + + _usbd_dev.ep_status[epnum][dir].stalled = 0; + _usbd_dev.ep_status[epnum][dir].busy = 0; + _usbd_dev.ep_status[epnum][dir].claimed = 0; + return dcd_edpt_iso_activate(rhport, desc_ep); +#else + (void) rhport; (void) desc_ep; + return false; +#endif +} + #endif diff --git a/cores/nRF5/TinyUSB/Adafruit_TinyUSB_ArduinoCore/tinyusb/src/device/usbd.h b/cores/nRF5/TinyUSB/Adafruit_TinyUSB_ArduinoCore/tinyusb/src/device/usbd.h index 5338be1..1820a54 100644 --- a/cores/nRF5/TinyUSB/Adafruit_TinyUSB_ArduinoCore/tinyusb/src/device/usbd.h +++ b/cores/nRF5/TinyUSB/Adafruit_TinyUSB_ArduinoCore/tinyusb/src/device/usbd.h @@ -1,4 +1,4 @@ -/* +/* * The MIT License (MIT) * * Copyright (c) 2019 Ha Thach (tinyusb.org) @@ -24,40 +24,68 @@ * This file is part of the TinyUSB stack. */ -/** \ingroup group_usbd - * @{ */ - #ifndef _TUSB_USBD_H_ #define _TUSB_USBD_H_ +#include "common/tusb_common.h" + #ifdef __cplusplus - extern "C" { +extern "C" { #endif -#include "common/tusb_common.h" - //--------------------------------------------------------------------+ // Application API //--------------------------------------------------------------------+ -// Init device stack -// Note: when using with RTOS, this should be called after scheduler/kernel is started. -// Otherwise it could cause kernel issue since USB IRQ handler does use RTOS queue API. -bool tud_init (void); +// New API to replace tud_init() to init device stack on specific roothub port +bool tud_rhport_init(uint8_t rhport, const tusb_rhport_init_t* rh_init); + +// Init device stack on roothub port +#if TUSB_VERSION_NUMBER > 2000 // 0.20.0 +TU_ATTR_DEPRECATED("Please use tusb_init(rhport, rh_init) instead") +#endif +TU_ATTR_ALWAYS_INLINE static inline bool tud_init (uint8_t rhport) { + const tusb_rhport_init_t rh_init = { + .role = TUSB_ROLE_DEVICE, + .speed = TUD_OPT_HIGH_SPEED ? TUSB_SPEED_HIGH : TUSB_SPEED_FULL + }; + return tud_rhport_init(rhport, &rh_init); +} + +// Deinit device stack on roothub port +bool tud_deinit(uint8_t rhport); + +// Check if device stack is already initialized +bool tud_inited(void); + +// Task function should be called in main/rtos loop, extended version of tud_task() +// - timeout_ms: millisecond to wait, zero = no wait, 0xFFFFFFFF = wait forever +// - in_isr: if function is called in ISR +void tud_task_ext(uint32_t timeout_ms, bool in_isr); // Task function should be called in main/rtos loop -void tud_task (void); +TU_ATTR_ALWAYS_INLINE static inline +void tud_task (void) { + tud_task_ext(UINT32_MAX, false); +} -// Check if there is pending events need proccessing by tud_task() +// Check if there is pending events need processing by tud_task() bool tud_task_event_ready(void); -// Interrupt handler, name alias to DCD +#ifndef TUSB_DCD_H_ extern void dcd_int_handler(uint8_t rhport); +#endif + +// Interrupt handler, name alias to DCD #define tud_int_handler dcd_int_handler // Get current bus speed tusb_speed_t tud_speed_get(void); +// Check if device is connected (may not mounted/configured yet) +// True if just got out of Bus Reset and received the very first data from host +bool tud_connected(void); + // Check if device is connected and configured bool tud_mounted(void); @@ -65,8 +93,8 @@ bool tud_mounted(void); bool tud_suspended(void); // Check if device is ready to transfer -static inline bool tud_ready(void) -{ +TU_ATTR_ALWAYS_INLINE static inline +bool tud_ready(void) { return tud_mounted() && !tud_suspended(); } @@ -81,6 +109,9 @@ bool tud_disconnect(void); // Return false on unsupported MCUs bool tud_connect(void); +// Enable or disable the Start Of Frame callback support +void tud_sof_cb_enable(bool en); + // Carry out Data and Status stage of control transfer // - If len = 0, it is equivalent to sending status only // - If len > wLength : it will be truncated @@ -90,17 +121,13 @@ bool tud_control_xfer(uint8_t rhport, tusb_control_request_t const * request, vo bool tud_control_status(uint8_t rhport, tusb_control_request_t const * request); //--------------------------------------------------------------------+ -// Application Callbacks (WEAK is optional) +// Application Callbacks //--------------------------------------------------------------------+ // Invoked when received GET DEVICE DESCRIPTOR request // Application return pointer to descriptor uint8_t const * tud_descriptor_device_cb(void); -// Invoked when received GET BOS DESCRIPTOR request -// Application return pointer to descriptor -TU_ATTR_WEAK uint8_t const * tud_descriptor_bos_cb(void); - // Invoked when received GET CONFIGURATION DESCRIPTOR request // Application return pointer to descriptor, whose contents must exist long enough for transfer to complete uint8_t const * tud_descriptor_configuration_cb(uint8_t index); @@ -109,29 +136,42 @@ uint8_t const * tud_descriptor_configuration_cb(uint8_t index); // Application return pointer to descriptor, whose contents must exist long enough for transfer to complete uint16_t const* tud_descriptor_string_cb(uint8_t index, uint16_t langid); +// Invoked when received GET BOS DESCRIPTOR request +// Application return pointer to descriptor +TU_ATTR_WEAK uint8_t const * tud_descriptor_bos_cb(void); + // Invoked when received GET DEVICE QUALIFIER DESCRIPTOR request +// Application return pointer to descriptor, whose contents must exist long enough for transfer to complete. +// device_qualifier descriptor describes information about a high-speed capable device that would +// change if the device were operating at the other speed. If not highspeed capable stall this request. +uint8_t const* tud_descriptor_device_qualifier_cb(void); + +// Invoked when received GET OTHER SEED CONFIGURATION DESCRIPTOR request // Application return pointer to descriptor, whose contents must exist long enough for transfer to complete -TU_ATTR_WEAK uint8_t const* tud_descriptor_device_qualifier_cb(void); +// Configuration descriptor in the other speed e.g if high speed then this is for full speed and vice versa +uint8_t const* tud_descriptor_other_speed_configuration_cb(uint8_t index); // Invoked when device is mounted (configured) -TU_ATTR_WEAK void tud_mount_cb(void); +void tud_mount_cb(void); // Invoked when device is unmounted -TU_ATTR_WEAK void tud_umount_cb(void); +void tud_umount_cb(void); // Invoked when usb bus is suspended // Within 7ms, device must draw an average of current less than 2.5 mA from bus -TU_ATTR_WEAK void tud_suspend_cb(bool remote_wakeup_en); +void tud_suspend_cb(bool remote_wakeup_en); // Invoked when usb bus is resumed -TU_ATTR_WEAK void tud_resume_cb(void); +void tud_resume_cb(void); -// Invoked when received control request with VENDOR TYPE -TU_ATTR_WEAK bool tud_vendor_control_request_cb(uint8_t rhport, tusb_control_request_t const * request); +// Invoked when there is a new usb event, which need to be processed by tud_task()/tud_task_ext() +void tud_event_hook_cb(uint8_t rhport, uint32_t eventid, bool in_isr); -// Invoked when vendor control request is complete -TU_ATTR_WEAK bool tud_vendor_control_complete_cb(uint8_t rhport, tusb_control_request_t const * request); +// Invoked when a new (micro) frame started +void tud_sof_cb(uint32_t frame_count); +// Invoked when received control request with VENDOR TYPE +TU_ATTR_WEAK bool tud_vendor_control_xfer_cb(uint8_t rhport, uint8_t stage, tusb_control_request_t const * request); //--------------------------------------------------------------------+ // Binary Device Object Store (BOS) Descriptor Templates @@ -168,21 +208,22 @@ TU_ATTR_WEAK bool tud_vendor_control_complete_cb(uint8_t rhport, tusb_control_re TUD_BOS_PLATFORM_DESCRIPTOR(TUD_BOS_MS_OS_20_UUID, U32_TO_U8S_LE(0x06030000), U16_TO_U8S_LE(_desc_set_len), _vendor_code, 0) #define TUD_BOS_MS_OS_20_UUID \ - 0xDF, 0x60, 0xDD, 0xD8, 0x89, 0x45, 0xC7, 0x4C, \ + 0xDF, 0x60, 0xDD, 0xD8, 0x89, 0x45, 0xC7, 0x4C, \ 0x9C, 0xD2, 0x65, 0x9D, 0x9E, 0x64, 0x8A, 0x9F //--------------------------------------------------------------------+ -// Configuration & Interface Descriptor Templates +// Configuration Descriptor Templates //--------------------------------------------------------------------+ -//------------- Configuration -------------// #define TUD_CONFIG_DESC_LEN (9) // Config number, interface count, string index, total length, attribute, power in mA #define TUD_CONFIG_DESCRIPTOR(config_num, _itfcount, _stridx, _total_len, _attribute, _power_ma) \ 9, TUSB_DESC_CONFIGURATION, U16_TO_U8S_LE(_total_len), _itfcount, config_num, _stridx, TU_BIT(7) | _attribute, (_power_ma)/2 -//------------- CDC -------------// +//--------------------------------------------------------------------+ +// CDC Descriptor Templates +//--------------------------------------------------------------------+ // Length of template descriptor: 66 bytes #define TUD_CDC_DESC_LEN (8+9+5+5+4+5+7+9+7+7) @@ -198,12 +239,12 @@ TU_ATTR_WEAK bool tud_vendor_control_complete_cb(uint8_t rhport, tusb_control_re 5, TUSB_DESC_CS_INTERFACE, CDC_FUNC_DESC_HEADER, U16_TO_U8S_LE(0x0120),\ /* CDC Call */\ 5, TUSB_DESC_CS_INTERFACE, CDC_FUNC_DESC_CALL_MANAGEMENT, 0, (uint8_t)((_itfnum) + 1),\ - /* CDC ACM: support line request */\ - 4, TUSB_DESC_CS_INTERFACE, CDC_FUNC_DESC_ABSTRACT_CONTROL_MANAGEMENT, 2,\ + /* CDC ACM: support line request + send break */\ + 4, TUSB_DESC_CS_INTERFACE, CDC_FUNC_DESC_ABSTRACT_CONTROL_MANAGEMENT, 6,\ /* CDC Union */\ 5, TUSB_DESC_CS_INTERFACE, CDC_FUNC_DESC_UNION, _itfnum, (uint8_t)((_itfnum) + 1),\ /* Endpoint Notification */\ - 7, TUSB_DESC_ENDPOINT, _ep_notif, TUSB_XFER_INTERRUPT, U16_TO_U8S_LE(_ep_notif_size), 16,\ + 7, TUSB_DESC_ENDPOINT, _ep_notif, TUSB_XFER_INTERRUPT, U16_TO_U8S_LE(_ep_notif_size), 1,\ /* CDC Data Interface */\ 9, TUSB_DESC_INTERFACE, (uint8_t)((_itfnum)+1), 0, 2, TUSB_CLASS_CDC_DATA, 0, 0, 0,\ /* Endpoint Out */\ @@ -211,7 +252,9 @@ TU_ATTR_WEAK bool tud_vendor_control_complete_cb(uint8_t rhport, tusb_control_re /* Endpoint In */\ 7, TUSB_DESC_ENDPOINT, _epin, TUSB_XFER_BULK, U16_TO_U8S_LE(_epsize), 0 -//------------- MSC -------------// +//--------------------------------------------------------------------+ +// MSC Descriptor Templates +//--------------------------------------------------------------------+ // Length of template descriptor: 23 bytes #define TUD_MSC_DESC_LEN (9 + 7 + 7) @@ -225,7 +268,29 @@ TU_ATTR_WEAK bool tud_vendor_control_complete_cb(uint8_t rhport, tusb_control_re /* Endpoint In */\ 7, TUSB_DESC_ENDPOINT, _epin, TUSB_XFER_BULK, U16_TO_U8S_LE(_epsize), 0 -//------------- HID -------------// + +//--------------------------------------------------------------------+ +// MTP Descriptor Templates +//--------------------------------------------------------------------+ + +// Length of template descriptor: 30 bytes +#define TUD_MTP_DESC_LEN (9 + 7 + 7 + 7) + +// Interface number, string index, EP event, EP event size, EP event polling, EP Out & EP In address, EP size +#define TUD_MTP_DESCRIPTOR(_itfnum, _stridx, _ep_evt, _ep_evt_size, _ep_evt_polling_interval, _epout, _epin, _epsize) \ + /* Interface */\ + 9, TUSB_DESC_INTERFACE, _itfnum, 0, 3, TUSB_CLASS_IMAGE, MTP_SUBCLASS_STILL_IMAGE, MTP_PROTOCOL_PIMA_15470, _stridx,\ + /* Endpoint Interrupt */\ + 7, TUSB_DESC_ENDPOINT, _ep_evt, TUSB_XFER_INTERRUPT, U16_TO_U8S_LE(_ep_evt_size), _ep_evt_polling_interval,\ + /* Endpoint Out */\ + 7, TUSB_DESC_ENDPOINT, _epout, TUSB_XFER_BULK, U16_TO_U8S_LE(_epsize), 0,\ + /* Endpoint In */\ + 7, TUSB_DESC_ENDPOINT, _epin, TUSB_XFER_BULK, U16_TO_U8S_LE(_epsize), 0 + + +//--------------------------------------------------------------------+ +// HID Descriptor Templates +//--------------------------------------------------------------------+ // Length of template descriptor: 25 bytes #define TUD_HID_DESC_LEN (9 + 9 + 7) @@ -234,7 +299,7 @@ TU_ATTR_WEAK bool tud_vendor_control_complete_cb(uint8_t rhport, tusb_control_re // Interface number, string index, protocol, report descriptor len, EP In address, size & polling interval #define TUD_HID_DESCRIPTOR(_itfnum, _stridx, _boot_protocol, _report_desc_len, _epin, _epsize, _ep_interval) \ /* Interface */\ - 9, TUSB_DESC_INTERFACE, _itfnum, 0, 1, TUSB_CLASS_HID, (uint8_t)((_boot_protocol) ? HID_SUBCLASS_BOOT : 0), _boot_protocol, _stridx,\ + 9, TUSB_DESC_INTERFACE, _itfnum, 0, 1, TUSB_CLASS_HID, (uint8_t)((_boot_protocol) ? (uint8_t)HID_SUBCLASS_BOOT : 0), _boot_protocol, _stridx,\ /* HID descriptor */\ 9, HID_DESC_TYPE_HID, U16_TO_U8S_LE(0x0111), 0, 1, HID_DESC_TYPE_REPORT, U16_TO_U8S_LE(_report_desc_len),\ /* Endpoint In */\ @@ -247,7 +312,7 @@ TU_ATTR_WEAK bool tud_vendor_control_complete_cb(uint8_t rhport, tusb_control_re // Interface number, string index, protocol, report descriptor len, EP OUT & IN address, size & polling interval #define TUD_HID_INOUT_DESCRIPTOR(_itfnum, _stridx, _boot_protocol, _report_desc_len, _epout, _epin, _epsize, _ep_interval) \ /* Interface */\ - 9, TUSB_DESC_INTERFACE, _itfnum, 0, 2, TUSB_CLASS_HID, (uint8_t)((_boot_protocol) ? HID_SUBCLASS_BOOT : 0), _boot_protocol, _stridx,\ + 9, TUSB_DESC_INTERFACE, _itfnum, 0, 2, TUSB_CLASS_HID, (uint8_t)((_boot_protocol) ? (uint8_t)HID_SUBCLASS_BOOT : 0), _boot_protocol, _stridx,\ /* HID descriptor */\ 9, HID_DESC_TYPE_HID, U16_TO_U8S_LE(0x0111), 0, 1, HID_DESC_TYPE_REPORT, U16_TO_U8S_LE(_report_desc_len),\ /* Endpoint Out */\ @@ -255,18 +320,21 @@ TU_ATTR_WEAK bool tud_vendor_control_complete_cb(uint8_t rhport, tusb_control_re /* Endpoint In */\ 7, TUSB_DESC_ENDPOINT, _epin, TUSB_XFER_INTERRUPT, U16_TO_U8S_LE(_epsize), _ep_interval -//------------- MIDI -------------// +//--------------------------------------------------------------------+ +// MIDI Descriptor Templates +// Note: MIDI v1.0 is based on Audio v1.0 +//--------------------------------------------------------------------+ #define TUD_MIDI_DESC_HEAD_LEN (9 + 9 + 9 + 7) #define TUD_MIDI_DESC_HEAD(_itfnum, _stridx, _numcables) \ /* Audio Control (AC) Interface */\ - 9, TUSB_DESC_INTERFACE, _itfnum, 0, 0, TUSB_CLASS_AUDIO, AUDIO_SUBCLASS_CONTROL, AUDIO_PROTOCOL_V1, _stridx,\ + 9, TUSB_DESC_INTERFACE, _itfnum, 0, 0, TUSB_CLASS_AUDIO, AUDIO_SUBCLASS_CONTROL, AUDIO_FUNC_PROTOCOL_CODE_UNDEF, _stridx,\ /* AC Header */\ - 9, TUSB_DESC_CS_INTERFACE, AUDIO_CS_INTERFACE_HEADER, U16_TO_U8S_LE(0x0100), U16_TO_U8S_LE(0x0009), 1, (uint8_t)((_itfnum) + 1),\ + 9, TUSB_DESC_CS_INTERFACE, AUDIO_CS_AC_INTERFACE_HEADER, U16_TO_U8S_LE(0x0100), U16_TO_U8S_LE(0x0009), 1, (uint8_t)((_itfnum) + 1),\ /* MIDI Streaming (MS) Interface */\ - 9, TUSB_DESC_INTERFACE, (uint8_t)((_itfnum) + 1), 0, 2, TUSB_CLASS_AUDIO, AUDIO_SUBCLASS_MIDI_STREAMING, AUDIO_PROTOCOL_V1, 0,\ + 9, TUSB_DESC_INTERFACE, (uint8_t)((_itfnum) + 1), 0, 2, TUSB_CLASS_AUDIO, AUDIO_SUBCLASS_MIDI_STREAMING, AUDIO_FUNC_PROTOCOL_CODE_UNDEF, 0,\ /* MS Header */\ - 7, TUSB_DESC_CS_INTERFACE, MIDI_CS_INTERFACE_HEADER, U16_TO_U8S_LE(0x0100), U16_TO_U8S_LE(7 + (_numcables) * TUD_MIDI_DESC_JACK_LEN) + 7, TUSB_DESC_CS_INTERFACE, MIDI_CS_INTERFACE_HEADER, U16_TO_U8S_LE(0x0100), U16_TO_U8S_LE(7 + (_numcables) * TUD_MIDI_DESC_JACK_LEN + 2 * TUD_MIDI_DESC_EP_LEN(_numcables)) #define TUD_MIDI_JACKID_IN_EMB(_cablenum) \ (uint8_t)(((_cablenum) - 1) * 4 + 1) @@ -281,20 +349,22 @@ TU_ATTR_WEAK bool tud_vendor_control_complete_cb(uint8_t rhport, tusb_control_re (uint8_t)(((_cablenum) - 1) * 4 + 4) #define TUD_MIDI_DESC_JACK_LEN (6 + 6 + 9 + 9) -#define TUD_MIDI_DESC_JACK(_cablenum) \ +#define TUD_MIDI_DESC_JACK_DESC(_cablenum, _stridx) \ /* MS In Jack (Embedded) */\ - 6, TUSB_DESC_CS_INTERFACE, MIDI_CS_INTERFACE_IN_JACK, MIDI_JACK_EMBEDDED, TUD_MIDI_JACKID_IN_EMB(_cablenum), 0,\ + 6, TUSB_DESC_CS_INTERFACE, MIDI_CS_INTERFACE_IN_JACK, MIDI_JACK_EMBEDDED, TUD_MIDI_JACKID_IN_EMB(_cablenum), _stridx,\ /* MS In Jack (External) */\ - 6, TUSB_DESC_CS_INTERFACE, MIDI_CS_INTERFACE_IN_JACK, MIDI_JACK_EXTERNAL, TUD_MIDI_JACKID_IN_EXT(_cablenum), 0,\ + 6, TUSB_DESC_CS_INTERFACE, MIDI_CS_INTERFACE_IN_JACK, MIDI_JACK_EXTERNAL, TUD_MIDI_JACKID_IN_EXT(_cablenum), _stridx,\ /* MS Out Jack (Embedded), connected to In Jack External */\ - 9, TUSB_DESC_CS_INTERFACE, MIDI_CS_INTERFACE_OUT_JACK, MIDI_JACK_EMBEDDED, TUD_MIDI_JACKID_OUT_EMB(_cablenum), 1, TUD_MIDI_JACKID_IN_EXT(_cablenum), 1, 0,\ + 9, TUSB_DESC_CS_INTERFACE, MIDI_CS_INTERFACE_OUT_JACK, MIDI_JACK_EMBEDDED, TUD_MIDI_JACKID_OUT_EMB(_cablenum), 1, TUD_MIDI_JACKID_IN_EXT(_cablenum), 1, _stridx,\ /* MS Out Jack (External), connected to In Jack Embedded */\ - 9, TUSB_DESC_CS_INTERFACE, MIDI_CS_INTERFACE_OUT_JACK, MIDI_JACK_EXTERNAL, TUD_MIDI_JACKID_OUT_EXT(_cablenum), 1, TUD_MIDI_JACKID_IN_EMB(_cablenum), 1, 0 + 9, TUSB_DESC_CS_INTERFACE, MIDI_CS_INTERFACE_OUT_JACK, MIDI_JACK_EXTERNAL, TUD_MIDI_JACKID_OUT_EXT(_cablenum), 1, TUD_MIDI_JACKID_IN_EMB(_cablenum), 1, _stridx + +#define TUD_MIDI_DESC_JACK(_cablenum) TUD_MIDI_DESC_JACK_DESC(_cablenum, 0) -#define TUD_MIDI_DESC_EP_LEN(_numcables) (7 + 4 + (_numcables)) +#define TUD_MIDI_DESC_EP_LEN(_numcables) (9 + 4 + (_numcables)) #define TUD_MIDI_DESC_EP(_epout, _epsize, _numcables) \ - /* Endpoint */\ - 7, TUSB_DESC_ENDPOINT, _epout, TUSB_XFER_BULK, U16_TO_U8S_LE(_epsize), 0,\ + /* Endpoint: Note Audio v1.0's endpoint has 9 bytes instead of 7 */\ + 9, TUSB_DESC_ENDPOINT, _epout, TUSB_XFER_BULK, U16_TO_U8S_LE(_epsize), 0, 0, 0, \ /* MS Endpoint (connected to embedded jack) */\ (uint8_t)(4 + (_numcables)), TUSB_DESC_CS_ENDPOINT, MIDI_CS_ENDPOINT_GENERAL, _numcables @@ -306,14 +376,255 @@ TU_ATTR_WEAK bool tud_vendor_control_complete_cb(uint8_t rhport, tusb_control_re // - 1 Embedded Jack out connected to 1 External Jack In #define TUD_MIDI_DESCRIPTOR(_itfnum, _stridx, _epout, _epin, _epsize) \ TUD_MIDI_DESC_HEAD(_itfnum, _stridx, 1),\ - TUD_MIDI_DESC_JACK(1),\ + TUD_MIDI_DESC_JACK_DESC(1, 0),\ TUD_MIDI_DESC_EP(_epout, _epsize, 1),\ TUD_MIDI_JACKID_IN_EMB(1),\ TUD_MIDI_DESC_EP(_epin, _epsize, 1),\ TUD_MIDI_JACKID_OUT_EMB(1) +//--------------------------------------------------------------------+ +// Audio v2.0 Descriptor Templates +//--------------------------------------------------------------------+ + +/* Standard Interface Association Descriptor (IAD) */ +#define TUD_AUDIO_DESC_IAD_LEN 8 +#define TUD_AUDIO_DESC_IAD(_firstitf, _nitfs, _stridx) \ + TUD_AUDIO_DESC_IAD_LEN, TUSB_DESC_INTERFACE_ASSOCIATION, _firstitf, _nitfs, TUSB_CLASS_AUDIO, AUDIO_FUNCTION_SUBCLASS_UNDEFINED, AUDIO_FUNC_PROTOCOL_CODE_V2, _stridx + +/* Standard AC Interface Descriptor(4.7.1) */ +#define TUD_AUDIO_DESC_STD_AC_LEN 9 +#define TUD_AUDIO_DESC_STD_AC(_itfnum, _nEPs, _stridx) /* _nEPs is 0 or 1 */\ + TUD_AUDIO_DESC_STD_AC_LEN, TUSB_DESC_INTERFACE, _itfnum, /* fixed to zero */ 0x00, _nEPs, TUSB_CLASS_AUDIO, AUDIO_SUBCLASS_CONTROL, AUDIO_INT_PROTOCOL_CODE_V2, _stridx + +/* Class-Specific AC Interface Header Descriptor(4.7.2) */ +#define TUD_AUDIO_DESC_CS_AC_LEN 9 +#define TUD_AUDIO_DESC_CS_AC(_bcdADC, _category, _totallen, _ctrl) /* _bcdADC : Audio Device Class Specification Release Number in Binary-Coded Decimal, _category : see audio_function_t, _totallen : Total number of bytes returned for the class-specific AudioControl interface i.e. Clock Source, Unit and Terminal descriptors - Do not include TUD_AUDIO_DESC_CS_AC_LEN, we already do this here*/ \ + TUD_AUDIO_DESC_CS_AC_LEN, TUSB_DESC_CS_INTERFACE, AUDIO_CS_AC_INTERFACE_HEADER, U16_TO_U8S_LE(_bcdADC), _category, U16_TO_U8S_LE(_totallen + TUD_AUDIO_DESC_CS_AC_LEN), _ctrl + +/* Clock Source Descriptor(4.7.2.1) */ +#define TUD_AUDIO_DESC_CLK_SRC_LEN 8 +#define TUD_AUDIO_DESC_CLK_SRC(_clkid, _attr, _ctrl, _assocTerm, _stridx) \ + TUD_AUDIO_DESC_CLK_SRC_LEN, TUSB_DESC_CS_INTERFACE, AUDIO_CS_AC_INTERFACE_CLOCK_SOURCE, _clkid, _attr, _ctrl, _assocTerm, _stridx + +/* Input Terminal Descriptor(4.7.2.4) */ +#define TUD_AUDIO_DESC_INPUT_TERM_LEN 17 +#define TUD_AUDIO_DESC_INPUT_TERM(_termid, _termtype, _assocTerm, _clkid, _nchannelslogical, _channelcfg, _idxchannelnames, _ctrl, _stridx) \ + TUD_AUDIO_DESC_INPUT_TERM_LEN, TUSB_DESC_CS_INTERFACE, AUDIO_CS_AC_INTERFACE_INPUT_TERMINAL, _termid, U16_TO_U8S_LE(_termtype), _assocTerm, _clkid, _nchannelslogical, U32_TO_U8S_LE(_channelcfg), _idxchannelnames, U16_TO_U8S_LE(_ctrl), _stridx + +/* Output Terminal Descriptor(4.7.2.5) */ +#define TUD_AUDIO_DESC_OUTPUT_TERM_LEN 12 +#define TUD_AUDIO_DESC_OUTPUT_TERM(_termid, _termtype, _assocTerm, _srcid, _clkid, _ctrl, _stridx) \ + TUD_AUDIO_DESC_OUTPUT_TERM_LEN, TUSB_DESC_CS_INTERFACE, AUDIO_CS_AC_INTERFACE_OUTPUT_TERMINAL, _termid, U16_TO_U8S_LE(_termtype), _assocTerm, _srcid, _clkid, U16_TO_U8S_LE(_ctrl), _stridx + +/* Feature Unit Descriptor(4.7.2.8) */ +// 1 - Channel +#define TUD_AUDIO_DESC_FEATURE_UNIT_ONE_CHANNEL_LEN 6+(1+1)*4 +#define TUD_AUDIO_DESC_FEATURE_UNIT_ONE_CHANNEL(_unitid, _srcid, _ctrlch0master, _ctrlch1, _stridx) \ + TUD_AUDIO_DESC_FEATURE_UNIT_ONE_CHANNEL_LEN, TUSB_DESC_CS_INTERFACE, AUDIO_CS_AC_INTERFACE_FEATURE_UNIT, _unitid, _srcid, U32_TO_U8S_LE(_ctrlch0master), U32_TO_U8S_LE(_ctrlch1), _stridx + +// 2 - Channels +#define TUD_AUDIO_DESC_FEATURE_UNIT_TWO_CHANNEL_LEN (6+(2+1)*4) +#define TUD_AUDIO_DESC_FEATURE_UNIT_TWO_CHANNEL(_unitid, _srcid, _ctrlch0master, _ctrlch1, _ctrlch2, _stridx) \ + TUD_AUDIO_DESC_FEATURE_UNIT_TWO_CHANNEL_LEN, TUSB_DESC_CS_INTERFACE, AUDIO_CS_AC_INTERFACE_FEATURE_UNIT, _unitid, _srcid, U32_TO_U8S_LE(_ctrlch0master), U32_TO_U8S_LE(_ctrlch1), U32_TO_U8S_LE(_ctrlch2), _stridx +// 4 - Channels +#define TUD_AUDIO_DESC_FEATURE_UNIT_FOUR_CHANNEL_LEN (6+(4+1)*4) +#define TUD_AUDIO_DESC_FEATURE_UNIT_FOUR_CHANNEL(_unitid, _srcid, _ctrlch0master, _ctrlch1, _ctrlch2, _ctrlch3, _ctrlch4, _stridx) \ + TUD_AUDIO_DESC_FEATURE_UNIT_FOUR_CHANNEL_LEN, TUSB_DESC_CS_INTERFACE, AUDIO_CS_AC_INTERFACE_FEATURE_UNIT, _unitid, _srcid, U32_TO_U8S_LE(_ctrlch0master), U32_TO_U8S_LE(_ctrlch1), U32_TO_U8S_LE(_ctrlch2), U32_TO_U8S_LE(_ctrlch3), U32_TO_U8S_LE(_ctrlch4), _stridx + +// For more channels, add definitions here + +/* Standard AC Interrupt Endpoint Descriptor(4.8.2.1) */ +#define TUD_AUDIO_DESC_STD_AC_INT_EP_LEN 7 +#define TUD_AUDIO_DESC_STD_AC_INT_EP(_ep, _interval) \ + TUD_AUDIO_DESC_STD_AC_INT_EP_LEN, TUSB_DESC_ENDPOINT, _ep, TUSB_XFER_INTERRUPT, U16_TO_U8S_LE(6), _interval + +/* Standard AS Interface Descriptor(4.9.1) */ +#define TUD_AUDIO_DESC_STD_AS_INT_LEN 9 +#define TUD_AUDIO_DESC_STD_AS_INT(_itfnum, _altset, _nEPs, _stridx) \ + TUD_AUDIO_DESC_STD_AS_INT_LEN, TUSB_DESC_INTERFACE, _itfnum, _altset, _nEPs, TUSB_CLASS_AUDIO, AUDIO_SUBCLASS_STREAMING, AUDIO_INT_PROTOCOL_CODE_V2, _stridx + +/* Class-Specific AS Interface Descriptor(4.9.2) */ +#define TUD_AUDIO_DESC_CS_AS_INT_LEN 16 +#define TUD_AUDIO_DESC_CS_AS_INT(_termid, _ctrl, _formattype, _formats, _nchannelsphysical, _channelcfg, _stridx) \ + TUD_AUDIO_DESC_CS_AS_INT_LEN, TUSB_DESC_CS_INTERFACE, AUDIO_CS_AS_INTERFACE_AS_GENERAL, _termid, _ctrl, _formattype, U32_TO_U8S_LE(_formats), _nchannelsphysical, U32_TO_U8S_LE(_channelcfg), _stridx + +/* Type I Format Type Descriptor(2.3.1.6 - Audio Formats) */ +#define TUD_AUDIO_DESC_TYPE_I_FORMAT_LEN 6 +#define TUD_AUDIO_DESC_TYPE_I_FORMAT(_subslotsize, _bitresolution) /* _subslotsize is number of bytes per sample (i.e. subslot) and can be 1,2,3, or 4 */\ + TUD_AUDIO_DESC_TYPE_I_FORMAT_LEN, TUSB_DESC_CS_INTERFACE, AUDIO_CS_AS_INTERFACE_FORMAT_TYPE, AUDIO_FORMAT_TYPE_I, _subslotsize, _bitresolution + +/* Standard AS Isochronous Audio Data Endpoint Descriptor(4.10.1.1) */ +#define TUD_AUDIO_DESC_STD_AS_ISO_EP_LEN 7 +#define TUD_AUDIO_DESC_STD_AS_ISO_EP(_ep, _attr, _maxEPsize, _interval) \ + TUD_AUDIO_DESC_STD_AS_ISO_EP_LEN, TUSB_DESC_ENDPOINT, _ep, _attr, U16_TO_U8S_LE(_maxEPsize), _interval + +/* Class-Specific AS Isochronous Audio Data Endpoint Descriptor(4.10.1.2) */ +#define TUD_AUDIO_DESC_CS_AS_ISO_EP_LEN 8 +#define TUD_AUDIO_DESC_CS_AS_ISO_EP(_attr, _ctrl, _lockdelayunit, _lockdelay) \ + TUD_AUDIO_DESC_CS_AS_ISO_EP_LEN, TUSB_DESC_CS_ENDPOINT, AUDIO_CS_EP_SUBTYPE_GENERAL, _attr, _ctrl, _lockdelayunit, U16_TO_U8S_LE(_lockdelay) + +/* Standard AS Isochronous Feedback Endpoint Descriptor(4.10.2.1) */ +#define TUD_AUDIO_DESC_STD_AS_ISO_FB_EP_LEN 7 +#define TUD_AUDIO_DESC_STD_AS_ISO_FB_EP(_ep, _epsize, _interval) \ + TUD_AUDIO_DESC_STD_AS_ISO_FB_EP_LEN, TUSB_DESC_ENDPOINT, _ep, (uint8_t) ((uint8_t)TUSB_XFER_ISOCHRONOUS | (uint8_t)TUSB_ISO_EP_ATT_NO_SYNC | (uint8_t)TUSB_ISO_EP_ATT_EXPLICIT_FB), U16_TO_U8S_LE(_epsize), _interval + +// AUDIO simple descriptor (UAC2) for 1 microphone input +// - 1 Input Terminal, 1 Feature Unit (Mute and Volume Control), 1 Output Terminal, 1 Clock Source + +#define TUD_AUDIO_MIC_ONE_CH_DESC_LEN (TUD_AUDIO_DESC_IAD_LEN\ + + TUD_AUDIO_DESC_STD_AC_LEN\ + + TUD_AUDIO_DESC_CS_AC_LEN\ + + TUD_AUDIO_DESC_CLK_SRC_LEN\ + + TUD_AUDIO_DESC_INPUT_TERM_LEN\ + + TUD_AUDIO_DESC_OUTPUT_TERM_LEN\ + + TUD_AUDIO_DESC_FEATURE_UNIT_ONE_CHANNEL_LEN\ + + TUD_AUDIO_DESC_STD_AS_INT_LEN\ + + TUD_AUDIO_DESC_STD_AS_INT_LEN\ + + TUD_AUDIO_DESC_CS_AS_INT_LEN\ + + TUD_AUDIO_DESC_TYPE_I_FORMAT_LEN\ + + TUD_AUDIO_DESC_STD_AS_ISO_EP_LEN\ + + TUD_AUDIO_DESC_CS_AS_ISO_EP_LEN) + +#define TUD_AUDIO_MIC_ONE_CH_DESC_N_AS_INT 1 // Number of AS interfaces + +#define TUD_AUDIO_MIC_ONE_CH_DESCRIPTOR(_itfnum, _stridx, _nBytesPerSample, _nBitsUsedPerSample, _epin, _epsize) \ + /* Standard Interface Association Descriptor (IAD) */\ + TUD_AUDIO_DESC_IAD(/*_firstitf*/ _itfnum, /*_nitfs*/ 0x02, /*_stridx*/ 0x00),\ + /* Standard AC Interface Descriptor(4.7.1) */\ + TUD_AUDIO_DESC_STD_AC(/*_itfnum*/ _itfnum, /*_nEPs*/ 0x00, /*_stridx*/ _stridx),\ + /* Class-Specific AC Interface Header Descriptor(4.7.2) */\ + TUD_AUDIO_DESC_CS_AC(/*_bcdADC*/ 0x0200, /*_category*/ AUDIO_FUNC_MICROPHONE, /*_totallen*/ TUD_AUDIO_DESC_CLK_SRC_LEN+TUD_AUDIO_DESC_INPUT_TERM_LEN+TUD_AUDIO_DESC_OUTPUT_TERM_LEN+TUD_AUDIO_DESC_FEATURE_UNIT_ONE_CHANNEL_LEN, /*_ctrl*/ AUDIO_CS_AS_INTERFACE_CTRL_LATENCY_POS),\ + /* Clock Source Descriptor(4.7.2.1) */\ + TUD_AUDIO_DESC_CLK_SRC(/*_clkid*/ 0x04, /*_attr*/ AUDIO_CLOCK_SOURCE_ATT_INT_FIX_CLK, /*_ctrl*/ (AUDIO_CTRL_R << AUDIO_CLOCK_SOURCE_CTRL_CLK_FRQ_POS), /*_assocTerm*/ 0x01, /*_stridx*/ 0x00),\ + /* Input Terminal Descriptor(4.7.2.4) */\ + TUD_AUDIO_DESC_INPUT_TERM(/*_termid*/ 0x01, /*_termtype*/ AUDIO_TERM_TYPE_IN_GENERIC_MIC, /*_assocTerm*/ 0x03, /*_clkid*/ 0x04, /*_nchannelslogical*/ 0x01, /*_channelcfg*/ AUDIO_CHANNEL_CONFIG_NON_PREDEFINED, /*_idxchannelnames*/ 0x00, /*_ctrl*/ AUDIO_CTRL_R << AUDIO_IN_TERM_CTRL_CONNECTOR_POS, /*_stridx*/ 0x00),\ + /* Output Terminal Descriptor(4.7.2.5) */\ + TUD_AUDIO_DESC_OUTPUT_TERM(/*_termid*/ 0x03, /*_termtype*/ AUDIO_TERM_TYPE_USB_STREAMING, /*_assocTerm*/ 0x01, /*_srcid*/ 0x02, /*_clkid*/ 0x04, /*_ctrl*/ 0x0000, /*_stridx*/ 0x00),\ + /* Feature Unit Descriptor(4.7.2.8) */\ + TUD_AUDIO_DESC_FEATURE_UNIT_ONE_CHANNEL(/*_unitid*/ 0x02, /*_srcid*/ 0x01, /*_ctrlch0master*/ AUDIO_CTRL_RW << AUDIO_FEATURE_UNIT_CTRL_MUTE_POS | AUDIO_CTRL_RW << AUDIO_FEATURE_UNIT_CTRL_VOLUME_POS, /*_ctrlch1*/ AUDIO_CTRL_RW << AUDIO_FEATURE_UNIT_CTRL_MUTE_POS | AUDIO_CTRL_RW << AUDIO_FEATURE_UNIT_CTRL_VOLUME_POS, /*_stridx*/ 0x00),\ + /* Standard AS Interface Descriptor(4.9.1) */\ + /* Interface 1, Alternate 0 - default alternate setting with 0 bandwidth */\ + TUD_AUDIO_DESC_STD_AS_INT(/*_itfnum*/ (uint8_t)((_itfnum)+1), /*_altset*/ 0x00, /*_nEPs*/ 0x00, /*_stridx*/ 0x00),\ + /* Standard AS Interface Descriptor(4.9.1) */\ + /* Interface 1, Alternate 1 - alternate interface for data streaming */\ + TUD_AUDIO_DESC_STD_AS_INT(/*_itfnum*/ (uint8_t)((_itfnum)+1), /*_altset*/ 0x01, /*_nEPs*/ 0x01, /*_stridx*/ 0x00),\ + /* Class-Specific AS Interface Descriptor(4.9.2) */\ + TUD_AUDIO_DESC_CS_AS_INT(/*_termid*/ 0x03, /*_ctrl*/ AUDIO_CTRL_NONE, /*_formattype*/ AUDIO_FORMAT_TYPE_I, /*_formats*/ AUDIO_DATA_FORMAT_TYPE_I_PCM, /*_nchannelsphysical*/ 0x01, /*_channelcfg*/ AUDIO_CHANNEL_CONFIG_NON_PREDEFINED, /*_stridx*/ 0x00),\ + /* Type I Format Type Descriptor(2.3.1.6 - Audio Formats) */\ + TUD_AUDIO_DESC_TYPE_I_FORMAT(_nBytesPerSample, _nBitsUsedPerSample),\ + /* Standard AS Isochronous Audio Data Endpoint Descriptor(4.10.1.1) */\ + TUD_AUDIO_DESC_STD_AS_ISO_EP(/*_ep*/ _epin, /*_attr*/ (uint8_t) ((uint8_t)TUSB_XFER_ISOCHRONOUS | (uint8_t)TUSB_ISO_EP_ATT_ASYNCHRONOUS | (uint8_t)TUSB_ISO_EP_ATT_DATA), /*_maxEPsize*/ _epsize, /*_interval*/ 0x01),\ + /* Class-Specific AS Isochronous Audio Data Endpoint Descriptor(4.10.1.2) */\ + TUD_AUDIO_DESC_CS_AS_ISO_EP(/*_attr*/ AUDIO_CS_AS_ISO_DATA_EP_ATT_NON_MAX_PACKETS_OK, /*_ctrl*/ AUDIO_CTRL_NONE, /*_lockdelayunit*/ AUDIO_CS_AS_ISO_DATA_EP_LOCK_DELAY_UNIT_UNDEFINED, /*_lockdelay*/ 0x0000) + +// AUDIO simple descriptor (UAC2) for 4 microphone input +// - 1 Input Terminal, 1 Feature Unit (Mute and Volume Control), 1 Output Terminal, 1 Clock Source + +#define TUD_AUDIO_MIC_FOUR_CH_DESC_LEN (TUD_AUDIO_DESC_IAD_LEN\ + + TUD_AUDIO_DESC_STD_AC_LEN\ + + TUD_AUDIO_DESC_CS_AC_LEN\ + + TUD_AUDIO_DESC_CLK_SRC_LEN\ + + TUD_AUDIO_DESC_INPUT_TERM_LEN\ + + TUD_AUDIO_DESC_OUTPUT_TERM_LEN\ + + TUD_AUDIO_DESC_FEATURE_UNIT_FOUR_CHANNEL_LEN\ + + TUD_AUDIO_DESC_STD_AS_INT_LEN\ + + TUD_AUDIO_DESC_STD_AS_INT_LEN\ + + TUD_AUDIO_DESC_CS_AS_INT_LEN\ + + TUD_AUDIO_DESC_TYPE_I_FORMAT_LEN\ + + TUD_AUDIO_DESC_STD_AS_ISO_EP_LEN\ + + TUD_AUDIO_DESC_CS_AS_ISO_EP_LEN) + +#define TUD_AUDIO_MIC_FOUR_CH_DESC_N_AS_INT 1 // Number of AS interfaces + +#define TUD_AUDIO_MIC_FOUR_CH_DESCRIPTOR(_itfnum, _stridx, _nBytesPerSample, _nBitsUsedPerSample, _epin, _epsize) \ + /* Standard Interface Association Descriptor (IAD) */\ + TUD_AUDIO_DESC_IAD(/*_firstitf*/ _itfnum, /*_nitfs*/ 0x02, /*_stridx*/ 0x00),\ + /* Standard AC Interface Descriptor(4.7.1) */\ + TUD_AUDIO_DESC_STD_AC(/*_itfnum*/ _itfnum, /*_nEPs*/ 0x00, /*_stridx*/ _stridx),\ + /* Class-Specific AC Interface Header Descriptor(4.7.2) */\ + TUD_AUDIO_DESC_CS_AC(/*_bcdADC*/ 0x0200, /*_category*/ AUDIO_FUNC_MICROPHONE, /*_totallen*/ TUD_AUDIO_DESC_CLK_SRC_LEN+TUD_AUDIO_DESC_INPUT_TERM_LEN+TUD_AUDIO_DESC_OUTPUT_TERM_LEN+TUD_AUDIO_DESC_FEATURE_UNIT_FOUR_CHANNEL_LEN, /*_ctrl*/ AUDIO_CS_AS_INTERFACE_CTRL_LATENCY_POS),\ + /* Clock Source Descriptor(4.7.2.1) */\ + TUD_AUDIO_DESC_CLK_SRC(/*_clkid*/ 0x04, /*_attr*/ AUDIO_CLOCK_SOURCE_ATT_INT_FIX_CLK, /*_ctrl*/ (AUDIO_CTRL_R << AUDIO_CLOCK_SOURCE_CTRL_CLK_FRQ_POS), /*_assocTerm*/ 0x01, /*_stridx*/ 0x00),\ + /* Input Terminal Descriptor(4.7.2.4) */\ + TUD_AUDIO_DESC_INPUT_TERM(/*_termid*/ 0x01, /*_termtype*/ AUDIO_TERM_TYPE_IN_GENERIC_MIC, /*_assocTerm*/ 0x03, /*_clkid*/ 0x04, /*_nchannelslogical*/ 0x04, /*_channelcfg*/ AUDIO_CHANNEL_CONFIG_NON_PREDEFINED, /*_idxchannelnames*/ 0x00, /*_ctrl*/ AUDIO_CTRL_R << AUDIO_IN_TERM_CTRL_CONNECTOR_POS, /*_stridx*/ 0x00),\ + /* Output Terminal Descriptor(4.7.2.5) */\ + TUD_AUDIO_DESC_OUTPUT_TERM(/*_termid*/ 0x03, /*_termtype*/ AUDIO_TERM_TYPE_USB_STREAMING, /*_assocTerm*/ 0x01, /*_srcid*/ 0x02, /*_clkid*/ 0x04, /*_ctrl*/ 0x0000, /*_stridx*/ 0x00),\ + /* Feature Unit Descriptor(4.7.2.8) */\ + TUD_AUDIO_DESC_FEATURE_UNIT_FOUR_CHANNEL(/*_unitid*/ 0x02, /*_srcid*/ 0x01, /*_ctrlch0master*/ AUDIO_CTRL_RW << AUDIO_FEATURE_UNIT_CTRL_MUTE_POS | AUDIO_CTRL_RW << AUDIO_FEATURE_UNIT_CTRL_VOLUME_POS, /*_ctrlch1*/ AUDIO_CTRL_RW << AUDIO_FEATURE_UNIT_CTRL_MUTE_POS | AUDIO_CTRL_RW << AUDIO_FEATURE_UNIT_CTRL_VOLUME_POS, /*_ctrlch2*/ AUDIO_CTRL_RW << AUDIO_FEATURE_UNIT_CTRL_MUTE_POS | AUDIO_CTRL_RW << AUDIO_FEATURE_UNIT_CTRL_VOLUME_POS, /*_ctrlch3*/ AUDIO_CTRL_RW << AUDIO_FEATURE_UNIT_CTRL_MUTE_POS | AUDIO_CTRL_RW << AUDIO_FEATURE_UNIT_CTRL_VOLUME_POS, /*_ctrlch4*/ AUDIO_CTRL_RW << AUDIO_FEATURE_UNIT_CTRL_MUTE_POS | AUDIO_CTRL_RW << AUDIO_FEATURE_UNIT_CTRL_VOLUME_POS, /*_stridx*/ 0x00),\ + /* Standard AS Interface Descriptor(4.9.1) */\ + /* Interface 1, Alternate 0 - default alternate setting with 0 bandwidth */\ + TUD_AUDIO_DESC_STD_AS_INT(/*_itfnum*/ (uint8_t)((_itfnum)+1), /*_altset*/ 0x00, /*_nEPs*/ 0x00, /*_stridx*/ 0x00),\ + /* Standard AS Interface Descriptor(4.9.1) */\ + /* Interface 1, Alternate 1 - alternate interface for data streaming */\ + TUD_AUDIO_DESC_STD_AS_INT(/*_itfnum*/ (uint8_t)((_itfnum)+1), /*_altset*/ 0x01, /*_nEPs*/ 0x01, /*_stridx*/ 0x00),\ + /* Class-Specific AS Interface Descriptor(4.9.2) */\ + TUD_AUDIO_DESC_CS_AS_INT(/*_termid*/ 0x03, /*_ctrl*/ AUDIO_CTRL_NONE, /*_formattype*/ AUDIO_FORMAT_TYPE_I, /*_formats*/ AUDIO_DATA_FORMAT_TYPE_I_PCM, /*_nchannelsphysical*/ 0x04, /*_channelcfg*/ AUDIO_CHANNEL_CONFIG_NON_PREDEFINED, /*_stridx*/ 0x00),\ + /* Type I Format Type Descriptor(2.3.1.6 - Audio Formats) */\ + TUD_AUDIO_DESC_TYPE_I_FORMAT(_nBytesPerSample, _nBitsUsedPerSample),\ + /* Standard AS Isochronous Audio Data Endpoint Descriptor(4.10.1.1) */\ + TUD_AUDIO_DESC_STD_AS_ISO_EP(/*_ep*/ _epin, /*_attr*/ (uint8_t) ((uint8_t)TUSB_XFER_ISOCHRONOUS | (uint8_t)TUSB_ISO_EP_ATT_ASYNCHRONOUS | (uint8_t)TUSB_ISO_EP_ATT_DATA), /*_maxEPsize*/ _epsize, /*_interval*/ 0x01),\ + /* Class-Specific AS Isochronous Audio Data Endpoint Descriptor(4.10.1.2) */\ + TUD_AUDIO_DESC_CS_AS_ISO_EP(/*_attr*/ AUDIO_CS_AS_ISO_DATA_EP_ATT_NON_MAX_PACKETS_OK, /*_ctrl*/ AUDIO_CTRL_NONE, /*_lockdelayunit*/ AUDIO_CS_AS_ISO_DATA_EP_LOCK_DELAY_UNIT_UNDEFINED, /*_lockdelay*/ 0x0000) + +// AUDIO simple descriptor (UAC2) for mono speaker +// - 1 Input Terminal, 2 Feature Unit (Mute and Volume Control), 3 Output Terminal, 4 Clock Source + +#define TUD_AUDIO_SPEAKER_MONO_FB_DESC_LEN (TUD_AUDIO_DESC_IAD_LEN\ + + TUD_AUDIO_DESC_STD_AC_LEN\ + + TUD_AUDIO_DESC_CS_AC_LEN\ + + TUD_AUDIO_DESC_CLK_SRC_LEN\ + + TUD_AUDIO_DESC_INPUT_TERM_LEN\ + + TUD_AUDIO_DESC_OUTPUT_TERM_LEN\ + + TUD_AUDIO_DESC_FEATURE_UNIT_ONE_CHANNEL_LEN\ + + TUD_AUDIO_DESC_STD_AS_INT_LEN\ + + TUD_AUDIO_DESC_STD_AS_INT_LEN\ + + TUD_AUDIO_DESC_CS_AS_INT_LEN\ + + TUD_AUDIO_DESC_TYPE_I_FORMAT_LEN\ + + TUD_AUDIO_DESC_STD_AS_ISO_EP_LEN\ + + TUD_AUDIO_DESC_CS_AS_ISO_EP_LEN\ + + TUD_AUDIO_DESC_STD_AS_ISO_FB_EP_LEN) + +#define TUD_AUDIO_SPEAKER_MONO_FB_DESCRIPTOR(_itfnum, _stridx, _nBytesPerSample, _nBitsUsedPerSample, _epout, _epoutsize, _epfb, _epfbsize) \ + /* Standard Interface Association Descriptor (IAD) */\ + TUD_AUDIO_DESC_IAD(/*_firstitf*/ _itfnum, /*_nitfs*/ 0x02, /*_stridx*/ 0x00),\ + /* Standard AC Interface Descriptor(4.7.1) */\ + TUD_AUDIO_DESC_STD_AC(/*_itfnum*/ _itfnum, /*_nEPs*/ 0x00, /*_stridx*/ _stridx),\ + /* Class-Specific AC Interface Header Descriptor(4.7.2) */\ + TUD_AUDIO_DESC_CS_AC(/*_bcdADC*/ 0x0200, /*_category*/ AUDIO_FUNC_DESKTOP_SPEAKER, /*_totallen*/ TUD_AUDIO_DESC_CLK_SRC_LEN+TUD_AUDIO_DESC_INPUT_TERM_LEN+TUD_AUDIO_DESC_OUTPUT_TERM_LEN+TUD_AUDIO_DESC_FEATURE_UNIT_ONE_CHANNEL_LEN, /*_ctrl*/ AUDIO_CS_AS_INTERFACE_CTRL_LATENCY_POS),\ + /* Clock Source Descriptor(4.7.2.1) */\ + TUD_AUDIO_DESC_CLK_SRC(/*_clkid*/ 0x04, /*_attr*/ AUDIO_CLOCK_SOURCE_ATT_INT_FIX_CLK, /*_ctrl*/ (AUDIO_CTRL_R << AUDIO_CLOCK_SOURCE_CTRL_CLK_FRQ_POS), /*_assocTerm*/ 0x01, /*_stridx*/ 0x00),\ + /* Input Terminal Descriptor(4.7.2.4) */\ + TUD_AUDIO_DESC_INPUT_TERM(/*_termid*/ 0x01, /*_termtype*/ AUDIO_TERM_TYPE_USB_STREAMING, /*_assocTerm*/ 0x00, /*_clkid*/ 0x04, /*_nchannelslogical*/ 0x01, /*_channelcfg*/ AUDIO_CHANNEL_CONFIG_NON_PREDEFINED, /*_idxchannelnames*/ 0x00, /*_ctrl*/ 0 * (AUDIO_CTRL_R << AUDIO_IN_TERM_CTRL_CONNECTOR_POS), /*_stridx*/ 0x00),\ + /* Output Terminal Descriptor(4.7.2.5) */\ + TUD_AUDIO_DESC_OUTPUT_TERM(/*_termid*/ 0x03, /*_termtype*/ AUDIO_TERM_TYPE_OUT_DESKTOP_SPEAKER, /*_assocTerm*/ 0x01, /*_srcid*/ 0x02, /*_clkid*/ 0x04, /*_ctrl*/ 0x0000, /*_stridx*/ 0x00),\ + /* Feature Unit Descriptor(4.7.2.8) */\ + TUD_AUDIO_DESC_FEATURE_UNIT_ONE_CHANNEL(/*_unitid*/ 0x02, /*_srcid*/ 0x01, /*_ctrlch0master*/ AUDIO_CTRL_RW << AUDIO_FEATURE_UNIT_CTRL_MUTE_POS | AUDIO_CTRL_RW << AUDIO_FEATURE_UNIT_CTRL_VOLUME_POS, /*_ctrlch1*/ AUDIO_CTRL_RW << AUDIO_FEATURE_UNIT_CTRL_MUTE_POS | AUDIO_CTRL_RW << AUDIO_FEATURE_UNIT_CTRL_VOLUME_POS, /*_stridx*/ 0x00),\ + /* Standard AS Interface Descriptor(4.9.1) */\ + /* Interface 1, Alternate 0 - default alternate setting with 0 bandwidth */\ + TUD_AUDIO_DESC_STD_AS_INT(/*_itfnum*/ (uint8_t)((_itfnum) + 1), /*_altset*/ 0x00, /*_nEPs*/ 0x00, /*_stridx*/ 0x00),\ + /* Standard AS Interface Descriptor(4.9.1) */\ + /* Interface 1, Alternate 1 - alternate interface for data streaming */\ + TUD_AUDIO_DESC_STD_AS_INT(/*_itfnum*/ (uint8_t)((_itfnum) + 1), /*_altset*/ 0x01, /*_nEPs*/ 0x02, /*_stridx*/ 0x00),\ + /* Class-Specific AS Interface Descriptor(4.9.2) */\ + TUD_AUDIO_DESC_CS_AS_INT(/*_termid*/ 0x01, /*_ctrl*/ AUDIO_CTRL_NONE, /*_formattype*/ AUDIO_FORMAT_TYPE_I, /*_formats*/ AUDIO_DATA_FORMAT_TYPE_I_PCM, /*_nchannelsphysical*/ 0x01, /*_channelcfg*/ AUDIO_CHANNEL_CONFIG_NON_PREDEFINED, /*_stridx*/ 0x00),\ + /* Type I Format Type Descriptor(2.3.1.6 - Audio Formats) */\ + TUD_AUDIO_DESC_TYPE_I_FORMAT(_nBytesPerSample, _nBitsUsedPerSample),\ + /* Standard AS Isochronous Audio Data Endpoint Descriptor(4.10.1.1) */\ + TUD_AUDIO_DESC_STD_AS_ISO_EP(/*_ep*/ _epout, /*_attr*/ (uint8_t) ((uint8_t)TUSB_XFER_ISOCHRONOUS | (uint8_t)TUSB_ISO_EP_ATT_ASYNCHRONOUS | (uint8_t)TUSB_ISO_EP_ATT_DATA), /*_maxEPsize*/ _epoutsize, /*_interval*/ 0x01),\ + /* Class-Specific AS Isochronous Audio Data Endpoint Descriptor(4.10.1.2) */\ + TUD_AUDIO_DESC_CS_AS_ISO_EP(/*_attr*/ AUDIO_CS_AS_ISO_DATA_EP_ATT_NON_MAX_PACKETS_OK, /*_ctrl*/ AUDIO_CTRL_NONE, /*_lockdelayunit*/ AUDIO_CS_AS_ISO_DATA_EP_LOCK_DELAY_UNIT_UNDEFINED, /*_lockdelay*/ 0x0000),\ + /* Standard AS Isochronous Feedback Endpoint Descriptor(4.10.2.1) */\ + TUD_AUDIO_DESC_STD_AS_ISO_FB_EP(/*_ep*/ _epfb, /*_epsize*/ _epfbsize, /*_interval*/ 1) + +// Calculate wMaxPacketSize of Endpoints +#define TUD_AUDIO_EP_SIZE(_maxFrequency, _nBytesPerSample, _nChannels) \ + ((((_maxFrequency + (TUD_OPT_HIGH_SPEED ? 7999 : 999)) / (TUD_OPT_HIGH_SPEED ? 8000 : 1000)) + 1) * _nBytesPerSample * _nChannels) + + +//--------------------------------------------------------------------+ +// USBTMC/USB488 Descriptor Templates +//--------------------------------------------------------------------+ -//------------- TUD_USBTMC/USB488 -------------// #define TUD_USBTMC_APP_CLASS (TUSB_CLASS_APPLICATION_SPECIFIC) #define TUD_USBTMC_APP_SUBCLASS 0x03u @@ -339,12 +650,14 @@ TU_ATTR_WEAK bool tud_vendor_control_complete_cb(uint8_t rhport, tusb_control_re /* optional interrupt endpoint */ \ // _int_pollingInterval : for LS/FS, expressed in frames (1ms each). 16 may be a good number? #define TUD_USBTMC_INT_DESCRIPTOR(_ep_interrupt, _ep_interrupt_size, _int_pollingInterval ) \ - 7, TUSB_DESC_ENDPOINT, _ep_interrupt, TUSB_XFER_INTERRUPT, U16_TO_U8S_LE(_ep_interrupt_size), 0x16 + 7, TUSB_DESC_ENDPOINT, _ep_interrupt, TUSB_XFER_INTERRUPT, U16_TO_U8S_LE(_ep_interrupt_size), _int_pollingInterval #define TUD_USBTMC_INT_DESCRIPTOR_LEN (7u) +//--------------------------------------------------------------------+ +// Vendor Descriptor Templates +//--------------------------------------------------------------------+ -//------------- Vendor -------------// #define TUD_VENDOR_DESC_LEN (9+7+7) // Interface number, string index, EP Out & IN address, EP size @@ -356,9 +669,12 @@ TU_ATTR_WEAK bool tud_vendor_control_complete_cb(uint8_t rhport, tusb_control_re /* Endpoint In */\ 7, TUSB_DESC_ENDPOINT, _epin, TUSB_XFER_BULK, U16_TO_U8S_LE(_epsize), 0 -//------------- DFU Runtime -------------// +//--------------------------------------------------------------------+ +// DFU Runtime Descriptor Templates +//--------------------------------------------------------------------+ + #define TUD_DFU_APP_CLASS (TUSB_CLASS_APPLICATION_SPECIFIC) -#define TUD_DFU_APP_SUBCLASS 0x01u +#define TUD_DFU_APP_SUBCLASS (APP_SUBCLASS_DFU_RUNTIME) // Length of template descriptr: 18 bytes #define TUD_DFU_RT_DESC_LEN (9 + 9) @@ -371,8 +687,58 @@ TU_ATTR_WEAK bool tud_vendor_control_complete_cb(uint8_t rhport, tusb_control_re /* Function */ \ 9, DFU_DESC_FUNCTIONAL, _attr, U16_TO_U8S_LE(_timeout), U16_TO_U8S_LE(_xfer_size), U16_TO_U8S_LE(0x0101) +//--------------------------------------------------------------------+ +// DFU Descriptor Templates +//--------------------------------------------------------------------+ + +// Length of template descriptor: 9 bytes + number of alternatives * 9 +#define TUD_DFU_DESC_LEN(_alt_count) (9 + (_alt_count) * 9) + +// Interface number, Alternate count, starting string index, attributes, detach timeout, transfer size +// Note: Alternate count must be numeric or macro, string index is increased by one for each Alt interface +#define TUD_DFU_DESCRIPTOR(_itfnum, _alt_count, _stridx, _attr, _timeout, _xfer_size) \ + TU_XSTRCAT(_TUD_DFU_ALT_,_alt_count)(_itfnum, 0, _stridx), \ + /* Function */ \ + 9, DFU_DESC_FUNCTIONAL, _attr, U16_TO_U8S_LE(_timeout), U16_TO_U8S_LE(_xfer_size), U16_TO_U8S_LE(0x0101) + +#define _TUD_DFU_ALT(_itfnum, _alt, _stridx) \ + /* Interface */ \ + 9, TUSB_DESC_INTERFACE, _itfnum, _alt, 0, TUD_DFU_APP_CLASS, TUD_DFU_APP_SUBCLASS, DFU_PROTOCOL_DFU, _stridx + +#define _TUD_DFU_ALT_1(_itfnum, _alt_count, _stridx) \ + _TUD_DFU_ALT(_itfnum, _alt_count, _stridx) + +#define _TUD_DFU_ALT_2(_itfnum, _alt_count, _stridx) \ + _TUD_DFU_ALT(_itfnum, _alt_count, _stridx), \ + _TUD_DFU_ALT_1(_itfnum, _alt_count+1, _stridx+1) + +#define _TUD_DFU_ALT_3(_itfnum, _alt_count, _stridx) \ + _TUD_DFU_ALT(_itfnum, _alt_count, _stridx), \ + _TUD_DFU_ALT_2(_itfnum, _alt_count+1, _stridx+1) + +#define _TUD_DFU_ALT_4(_itfnum, _alt_count, _stridx) \ + _TUD_DFU_ALT(_itfnum, _alt_count, _stridx), \ + _TUD_DFU_ALT_3(_itfnum, _alt_count+1, _stridx+1) -//------------- CDC-ECM -------------// +#define _TUD_DFU_ALT_5(_itfnum, _alt_count, _stridx) \ + _TUD_DFU_ALT(_itfnum, _alt_count, _stridx), \ + _TUD_DFU_ALT_4(_itfnum, _alt_count+1, _stridx+1) + +#define _TUD_DFU_ALT_6(_itfnum, _alt_count, _stridx) \ + _TUD_DFU_ALT(_itfnum, _alt_count, _stridx), \ + _TUD_DFU_ALT_5(_itfnum, _alt_count+1, _stridx+1) + +#define _TUD_DFU_ALT_7(_itfnum, _alt_count, _stridx) \ + _TUD_DFU_ALT(_itfnum, _alt_count, _stridx), \ + _TUD_DFU_ALT_6(_itfnum, _alt_count+1, _stridx+1) + +#define _TUD_DFU_ALT_8(_itfnum, _alt_count, _stridx) \ + _TUD_DFU_ALT(_itfnum, _alt_count, _stridx), \ + _TUD_DFU_ALT_7(_itfnum, _alt_count+1, _stridx+1) + +//--------------------------------------------------------------------+ +// CDC-ECM Descriptor Templates +//--------------------------------------------------------------------+ // Length of template descriptor: 71 bytes #define TUD_CDC_ECM_DESC_LEN (8+9+5+5+13+7+9+9+7+7) @@ -381,9 +747,9 @@ TU_ATTR_WEAK bool tud_vendor_control_complete_cb(uint8_t rhport, tusb_control_re // Interface number, description string index, MAC address string index, EP notification address and size, EP data address (out, in), and size, max segment size. #define TUD_CDC_ECM_DESCRIPTOR(_itfnum, _desc_stridx, _mac_stridx, _ep_notif, _ep_notif_size, _epout, _epin, _epsize, _maxsegmentsize) \ /* Interface Association */\ - 8, TUSB_DESC_INTERFACE_ASSOCIATION, _itfnum, 2, TUSB_CLASS_CDC, CDC_COMM_SUBCLASS_ETHERNET_NETWORKING_CONTROL_MODEL, 0, 0,\ + 8, TUSB_DESC_INTERFACE_ASSOCIATION, _itfnum, 2, TUSB_CLASS_CDC, CDC_COMM_SUBCLASS_ETHERNET_CONTROL_MODEL, 0, 0,\ /* CDC Control Interface */\ - 9, TUSB_DESC_INTERFACE, _itfnum, 0, 1, TUSB_CLASS_CDC, CDC_COMM_SUBCLASS_ETHERNET_NETWORKING_CONTROL_MODEL, 0, _desc_stridx,\ + 9, TUSB_DESC_INTERFACE, _itfnum, 0, 1, TUSB_CLASS_CDC, CDC_COMM_SUBCLASS_ETHERNET_CONTROL_MODEL, 0, _desc_stridx,\ /* CDC-ECM Header */\ 5, TUSB_DESC_CS_INTERFACE, CDC_FUNC_DESC_HEADER, U16_TO_U8S_LE(0x0120),\ /* CDC-ECM Union */\ @@ -401,19 +767,20 @@ TU_ATTR_WEAK bool tud_vendor_control_complete_cb(uint8_t rhport, tusb_control_re /* Endpoint Out */\ 7, TUSB_DESC_ENDPOINT, _epout, TUSB_XFER_BULK, U16_TO_U8S_LE(_epsize), 0 - -//------------- RNDIS -------------// +//--------------------------------------------------------------------+ +// RNDIS Descriptor Templates +//--------------------------------------------------------------------+ #if 0 - /* Windows XP */ - #define TUD_RNDIS_ITF_CLASS TUSB_CLASS_CDC - #define TUD_RNDIS_ITF_SUBCLASS CDC_COMM_SUBCLASS_ABSTRACT_CONTROL_MODEL - #define TUD_RNDIS_ITF_PROTOCOL 0xFF /* CDC_COMM_PROTOCOL_MICROSOFT_RNDIS */ +/* Windows XP */ +#define TUD_RNDIS_ITF_CLASS TUSB_CLASS_CDC +#define TUD_RNDIS_ITF_SUBCLASS CDC_COMM_SUBCLASS_ABSTRACT_CONTROL_MODEL +#define TUD_RNDIS_ITF_PROTOCOL 0xFF /* CDC_COMM_PROTOCOL_MICROSOFT_RNDIS */ #else - /* Windows 7+ */ - #define TUD_RNDIS_ITF_CLASS TUSB_CLASS_WIRELESS_CONTROLLER - #define TUD_RNDIS_ITF_SUBCLASS 0x01 - #define TUD_RNDIS_ITF_PROTOCOL 0x03 +/* Windows 7+ */ +#define TUD_RNDIS_ITF_CLASS TUSB_CLASS_WIRELESS_CONTROLLER +#define TUD_RNDIS_ITF_SUBCLASS 0x01 +#define TUD_RNDIS_ITF_PROTOCOL 0x03 #endif // Length of template descriptor: 66 bytes @@ -443,22 +810,21 @@ TU_ATTR_WEAK bool tud_vendor_control_complete_cb(uint8_t rhport, tusb_control_re /* Endpoint Out */\ 7, TUSB_DESC_ENDPOINT, _epout, TUSB_XFER_BULK, U16_TO_U8S_LE(_epsize), 0 -//------------- BT Radio -------------// +//--------------------------------------------------------------------+ +// Bluetooth Radio Descriptor Templates +//--------------------------------------------------------------------+ + #define TUD_BT_APP_CLASS (TUSB_CLASS_WIRELESS_CONTROLLER) #define TUD_BT_APP_SUBCLASS 0x01 #define TUD_BT_PROTOCOL_PRIMARY_CONTROLLER 0x01 #define TUD_BT_PROTOCOL_AMP_CONTROLLER 0x02 -#ifndef CFG_TUD_BTH_ISO_ALT_COUNT -#define CFG_TUD_BTH_ISO_ALT_COUNT 0 -#endif - -// Length of template descriptor: 30 bytes + number of ISO alternatives * 23 -#define TUD_BTH_DESC_LEN (9 + 7 + 7 + 7 + (CFG_TUD_BTH_ISO_ALT_COUNT) * (9 + 7 + 7)) +// Length of template descriptor: 38 bytes + number of ISO alternatives * 23 +#define TUD_BTH_DESC_LEN (8 + 9 + 7 + 7 + 7 + (CFG_TUD_BTH_ISO_ALT_COUNT) * (9 + 7 + 7)) /* Primary Interface */ #define TUD_BTH_PRI_ITF(_itfnum, _stridx, _ep_evt, _ep_evt_size, _ep_evt_interval, _ep_in, _ep_out, _ep_size) \ - 9, TUSB_DESC_INTERFACE, _itfnum, _stridx, 3, TUD_BT_APP_CLASS, TUD_BT_APP_SUBCLASS, TUD_BT_PROTOCOL_PRIMARY_CONTROLLER, 0, \ + 9, TUSB_DESC_INTERFACE, _itfnum, 0, 3, TUD_BT_APP_CLASS, TUD_BT_APP_SUBCLASS, TUD_BT_PROTOCOL_PRIMARY_CONTROLLER, _stridx, \ /* Endpoint In for events */ \ 7, TUSB_DESC_ENDPOINT, _ep_evt, TUSB_XFER_INTERRUPT, U16_TO_U8S_LE(_ep_evt_size), _ep_evt_interval, \ /* Endpoint In for ACL data */ \ @@ -479,27 +845,63 @@ TU_ATTR_WEAK bool tud_vendor_control_complete_cb(uint8_t rhport, tusb_control_re #define TUD_BTH_ISO_ITF_0(_itfnum, ...) #define TUD_BTH_ISO_ITF_1(_itfnum, _ep_in, _ep_out, ...) TUD_BTH_ISO_ITF(_itfnum, (CFG_TUD_BTH_ISO_ALT_COUNT) - 1, _ep_in, _ep_out, _FIRST(__VA_ARGS__)) #define TUD_BTH_ISO_ITF_2(_itfnum, _ep_in, _ep_out, ...) TUD_BTH_ISO_ITF(_itfnum, (CFG_TUD_BTH_ISO_ALT_COUNT) - 2, _ep_in, _ep_out, _FIRST(__VA_ARGS__)) \ - TUD_BTH_ISO_ITF_1(_itfnum, _ep_in, _ep_out, _REST(__VA_ARGS__)) + TUD_BTH_ISO_ITF_1(_itfnum, _ep_in, _ep_out, _REST(__VA_ARGS__)) #define TUD_BTH_ISO_ITF_3(_itfnum, _ep_in, _ep_out, ...) TUD_BTH_ISO_ITF(_itfnum, (CFG_TUD_BTH_ISO_ALT_COUNT) - 3, _ep_in, _ep_out, _FIRST(__VA_ARGS__)) \ - TUD_BTH_ISO_ITF_2(_itfnum, _ep_in, _ep_out, _REST(__VA_ARGS__)) + TUD_BTH_ISO_ITF_2(_itfnum, _ep_in, _ep_out, _REST(__VA_ARGS__)) #define TUD_BTH_ISO_ITF_4(_itfnum, _ep_in, _ep_out, ...) TUD_BTH_ISO_ITF(_itfnum, (CFG_TUD_BTH_ISO_ALT_COUNT) - 4, _ep_in, _ep_out, _FIRST(__VA_ARGS__)) \ - TUD_BTH_ISO_ITF_3(_itfnum, _ep_in, _ep_out, _REST(__VA_ARGS__)) + TUD_BTH_ISO_ITF_3(_itfnum, _ep_in, _ep_out, _REST(__VA_ARGS__)) #define TUD_BTH_ISO_ITF_5(_itfnum, _ep_in, _ep_out, ...) TUD_BTH_ISO_ITF(_itfnum, (CFG_TUD_BTH_ISO_ALT_COUNT) - 5, _ep_in, _ep_out, _FIRST(__VA_ARGS__)) \ - TUD_BTH_ISO_ITF_4(_itfnum, _ep_in, _ep_out, _REST(__VA_ARGS__)) + TUD_BTH_ISO_ITF_4(_itfnum, _ep_in, _ep_out, _REST(__VA_ARGS__)) #define TUD_BTH_ISO_ITF_6(_itfnum, _ep_in, _ep_out, ...) TUD_BTH_ISO_ITF(_itfnum, (CFG_TUD_BTH_ISO_ALT_COUNT) - 6, _ep_in, _ep_out, _FIRST(__VA_ARGS__)) \ - TUD_BTH_ISO_ITF_5(_itfnum, _ep_in, _ep_out, _REST(__VA_ARGS__)) + TUD_BTH_ISO_ITF_5(_itfnum, _ep_in, _ep_out, _REST(__VA_ARGS__)) #define TUD_BTH_ISO_ITFS(_itfnum, _ep_in, _ep_out, ...) \ - TU_XSTRCAT(TUD_BTH_ISO_ITF_, CFG_TUD_BTH_ISO_ALT_COUNT)(_itfnum, _ep_in, _ep_out, __VA_ARGS__) + TU_XSTRCAT(TUD_BTH_ISO_ITF_, CFG_TUD_BTH_ISO_ALT_COUNT)(_itfnum, _ep_in, _ep_out, __VA_ARGS__) // BT Primary controller descriptor // Interface number, string index, attributes, event endpoint, event endpoint size, interval, data in, data out, data endpoint size, iso endpoint sizes +// TODO BTH should also use IAD like CDC for composite device #define TUD_BTH_DESCRIPTOR(_itfnum, _stridx, _ep_evt, _ep_evt_size, _ep_evt_interval, _ep_in, _ep_out, _ep_size,...) \ + /* Interface Associate */\ + 8, TUSB_DESC_INTERFACE_ASSOCIATION, _itfnum, 2, TUD_BT_APP_CLASS, TUD_BT_APP_SUBCLASS, TUD_BT_PROTOCOL_PRIMARY_CONTROLLER, 0,\ TUD_BTH_PRI_ITF(_itfnum, _stridx, _ep_evt, _ep_evt_size, _ep_evt_interval, _ep_in, _ep_out, _ep_size) \ TUD_BTH_ISO_ITFS(_itfnum + 1, _ep_in + 1, _ep_out + 1, __VA_ARGS__) +//--------------------------------------------------------------------+ +// CDC-NCM Descriptor Templates +//--------------------------------------------------------------------+ + +// Length of template descriptor +#define TUD_CDC_NCM_DESC_LEN (8+9+5+5+13+6+7+9+9+7+7) + +// CDC-ECM Descriptor Template +// Interface number, description string index, MAC address string index, EP notification address and size, EP data address (out, in), and size, max segment size. +#define TUD_CDC_NCM_DESCRIPTOR(_itfnum, _desc_stridx, _mac_stridx, _ep_notif, _ep_notif_size, _epout, _epin, _epsize, _maxsegmentsize) \ + /* Interface Association */\ + 8, TUSB_DESC_INTERFACE_ASSOCIATION, _itfnum, 2, TUSB_CLASS_CDC, CDC_COMM_SUBCLASS_NETWORK_CONTROL_MODEL, 0, 0,\ + /* CDC Control Interface */\ + 9, TUSB_DESC_INTERFACE, _itfnum, 0, 1, TUSB_CLASS_CDC, CDC_COMM_SUBCLASS_NETWORK_CONTROL_MODEL, 0, _desc_stridx,\ + /* CDC-NCM Header */\ + 5, TUSB_DESC_CS_INTERFACE, CDC_FUNC_DESC_HEADER, U16_TO_U8S_LE(0x0110),\ + /* CDC-NCM Union */\ + 5, TUSB_DESC_CS_INTERFACE, CDC_FUNC_DESC_UNION, _itfnum, (uint8_t)((_itfnum) + 1),\ + /* CDC-NCM Functional Descriptor */\ + 13, TUSB_DESC_CS_INTERFACE, CDC_FUNC_DESC_ETHERNET_NETWORKING, _mac_stridx, 0, 0, 0, 0, U16_TO_U8S_LE(_maxsegmentsize), U16_TO_U8S_LE(0), 0, \ + /* CDC-NCM Functional Descriptor */\ + 6, TUSB_DESC_CS_INTERFACE, CDC_FUNC_DESC_NCM, U16_TO_U8S_LE(0x0100), 0, \ + /* Endpoint Notification */\ + 7, TUSB_DESC_ENDPOINT, _ep_notif, TUSB_XFER_INTERRUPT, U16_TO_U8S_LE(_ep_notif_size), 50,\ + /* CDC Data Interface (default inactive) */\ + 9, TUSB_DESC_INTERFACE, (uint8_t)((_itfnum)+1), 0, 0, TUSB_CLASS_CDC_DATA, 0, NCM_DATA_PROTOCOL_NETWORK_TRANSFER_BLOCK, 0,\ + /* CDC Data Interface (alternative active) */\ + 9, TUSB_DESC_INTERFACE, (uint8_t)((_itfnum)+1), 1, 2, TUSB_CLASS_CDC_DATA, 0, NCM_DATA_PROTOCOL_NETWORK_TRANSFER_BLOCK, 0,\ + /* Endpoint In */\ + 7, TUSB_DESC_ENDPOINT, _epin, TUSB_XFER_BULK, U16_TO_U8S_LE(_epsize), 0,\ + /* Endpoint Out */\ + 7, TUSB_DESC_ENDPOINT, _epout, TUSB_XFER_BULK, U16_TO_U8S_LE(_epsize), 0 + #ifdef __cplusplus - } +} #endif #endif /* _TUSB_USBD_H_ */ diff --git a/cores/nRF5/TinyUSB/Adafruit_TinyUSB_ArduinoCore/tinyusb/src/device/usbd_control.c b/cores/nRF5/TinyUSB/Adafruit_TinyUSB_ArduinoCore/tinyusb/src/device/usbd_control.c index db0eb70..c9700fd 100644 --- a/cores/nRF5/TinyUSB/Adafruit_TinyUSB_ArduinoCore/tinyusb/src/device/usbd_control.c +++ b/cores/nRF5/TinyUSB/Adafruit_TinyUSB_ArduinoCore/tinyusb/src/device/usbd_control.c @@ -26,111 +26,96 @@ #include "tusb_option.h" -#if TUSB_OPT_DEVICE_ENABLED +#if CFG_TUD_ENABLED +#include "dcd.h" #include "tusb.h" #include "device/usbd_pvt.h" -#include "dcd.h" -#if CFG_TUSB_DEBUG >= 2 -extern void usbd_driver_print_control_complete_name(bool (*control_complete) (uint8_t, tusb_control_request_t const *)); -#endif +//--------------------------------------------------------------------+ +// Callback weak stubs (called if application does not provide) +//--------------------------------------------------------------------+ +TU_ATTR_WEAK void dcd_edpt0_status_complete(uint8_t rhport, const tusb_control_request_t* request) { + (void) rhport; + (void) request; +} -enum -{ +//--------------------------------------------------------------------+ +// MACRO CONSTANT TYPEDEF +//--------------------------------------------------------------------+ + +enum { EDPT_CTRL_OUT = 0x00, - EDPT_CTRL_IN = 0x80 + EDPT_CTRL_IN = 0x80 }; -typedef struct -{ +typedef struct { tusb_control_request_t request; - uint8_t* buffer; uint16_t data_len; uint16_t total_xferred; - - bool (*complete_cb) (uint8_t, tusb_control_request_t const *); + usbd_control_xfer_cb_t complete_cb; } usbd_control_xfer_t; static usbd_control_xfer_t _ctrl_xfer; -CFG_TUSB_MEM_SECTION CFG_TUSB_MEM_ALIGN -static uint8_t _usbd_ctrl_buf[CFG_TUD_ENDPOINT0_SIZE]; +CFG_TUD_MEM_SECTION static struct { + TUD_EPBUF_DEF(buf, CFG_TUD_ENDPOINT0_SIZE); +} _ctrl_epbuf; //--------------------------------------------------------------------+ // Application API //--------------------------------------------------------------------+ // Queue ZLP status transaction -static inline bool _status_stage_xact(uint8_t rhport, tusb_control_request_t const * request) -{ +static inline bool status_stage_xact(uint8_t rhport, const tusb_control_request_t* request) { // Opposite to endpoint in Data Phase - uint8_t const ep_addr = request->bmRequestType_bit.direction ? EDPT_CTRL_OUT : EDPT_CTRL_IN; - - TU_LOG2(" Queue EP %02X with zlp Status\r\n", ep_addr); - - // status direction is reversed to one in the setup packet - // Note: Status must always be DATA1 - return dcd_edpt_xfer(rhport, ep_addr, NULL, 0); + const uint8_t ep_addr = request->bmRequestType_bit.direction ? EDPT_CTRL_OUT : EDPT_CTRL_IN; + return usbd_edpt_xfer(rhport, ep_addr, NULL, 0); } // Status phase -bool tud_control_status(uint8_t rhport, tusb_control_request_t const * request) -{ - _ctrl_xfer.request = (*request); - _ctrl_xfer.buffer = NULL; +bool tud_control_status(uint8_t rhport, const tusb_control_request_t* request) { + _ctrl_xfer.request = (*request); + _ctrl_xfer.buffer = NULL; _ctrl_xfer.total_xferred = 0; - _ctrl_xfer.data_len = 0; + _ctrl_xfer.data_len = 0; - return _status_stage_xact(rhport, request); + return status_stage_xact(rhport, request); } // Queue a transaction in Data Stage // Each transaction has up to Endpoint0's max packet size. // This function can also transfer an zero-length packet -static bool _data_stage_xact(uint8_t rhport) -{ - uint16_t const xact_len = tu_min16(_ctrl_xfer.data_len - _ctrl_xfer.total_xferred, CFG_TUD_ENDPOINT0_SIZE); - +static bool data_stage_xact(uint8_t rhport) { + const uint16_t xact_len = tu_min16(_ctrl_xfer.data_len - _ctrl_xfer.total_xferred, CFG_TUD_ENDPOINT0_SIZE); uint8_t ep_addr = EDPT_CTRL_OUT; - if ( _ctrl_xfer.request.bmRequestType_bit.direction == TUSB_DIR_IN ) - { + if (_ctrl_xfer.request.bmRequestType_bit.direction == TUSB_DIR_IN) { ep_addr = EDPT_CTRL_IN; - if ( xact_len ) memcpy(_usbd_ctrl_buf, _ctrl_xfer.buffer, xact_len); + if (xact_len) { + TU_VERIFY(0 == tu_memcpy_s(_ctrl_epbuf.buf, CFG_TUD_ENDPOINT0_SIZE, _ctrl_xfer.buffer, xact_len)); + } } - TU_LOG2(" Queue EP %02X with %u bytes\r\n", ep_addr, xact_len); - - return dcd_edpt_xfer(rhport, ep_addr, xact_len ? _usbd_ctrl_buf : NULL, xact_len); + return usbd_edpt_xfer(rhport, ep_addr, xact_len ? _ctrl_epbuf.buf : NULL, xact_len); } // Transmit data to/from the control endpoint. // If the request's wLength is zero, a status packet is sent instead. -bool tud_control_xfer(uint8_t rhport, tusb_control_request_t const * request, void* buffer, uint16_t len) -{ - _ctrl_xfer.request = (*request); - _ctrl_xfer.buffer = (uint8_t*) buffer; +bool tud_control_xfer(uint8_t rhport, const tusb_control_request_t* request, void* buffer, uint16_t len) { + _ctrl_xfer.request = (*request); + _ctrl_xfer.buffer = (uint8_t*) buffer; _ctrl_xfer.total_xferred = 0U; - _ctrl_xfer.data_len = tu_min16(len, request->wLength); - - if (request->wLength > 0U) - { - if(_ctrl_xfer.data_len > 0U) - { + _ctrl_xfer.data_len = tu_min16(len, request->wLength); + + if (request->wLength > 0U) { + if (_ctrl_xfer.data_len > 0U) { TU_ASSERT(buffer); } - -// TU_LOG2(" Control total data length is %u bytes\r\n", _ctrl_xfer.data_len); - - // Data stage - TU_ASSERT( _data_stage_xact(rhport) ); - } - else - { - // Status stage - TU_ASSERT( _status_stage_xact(rhport, request) ); + TU_ASSERT(data_stage_xact(rhport)); + } else { + TU_ASSERT(status_stage_xact(rhport, request)); } return true; @@ -139,84 +124,85 @@ bool tud_control_xfer(uint8_t rhport, tusb_control_request_t const * request, vo //--------------------------------------------------------------------+ // USBD API //--------------------------------------------------------------------+ +void usbd_control_reset(void); +void usbd_control_set_request(const tusb_control_request_t* request); +void usbd_control_set_complete_callback(usbd_control_xfer_cb_t fp); +bool usbd_control_xfer_cb(uint8_t rhport, uint8_t ep_addr, xfer_result_t result, uint32_t xferred_bytes); -void usbd_control_reset(void) -{ +void usbd_control_reset(void) { tu_varclr(&_ctrl_xfer); } -// TODO may find a better way -void usbd_control_set_complete_callback( bool (*fp) (uint8_t, tusb_control_request_t const * ) ) -{ +// Set complete callback +void usbd_control_set_complete_callback(usbd_control_xfer_cb_t fp) { _ctrl_xfer.complete_cb = fp; } -// useful for dcd_set_address where DCD is responsible for status response -void usbd_control_set_request(tusb_control_request_t const *request) -{ - _ctrl_xfer.request = (*request); - _ctrl_xfer.buffer = NULL; +// for dcd_set_address where DCD is responsible for status response +void usbd_control_set_request(const tusb_control_request_t* request) { + _ctrl_xfer.request = (*request); + _ctrl_xfer.buffer = NULL; _ctrl_xfer.total_xferred = 0; - _ctrl_xfer.data_len = 0; + _ctrl_xfer.data_len = 0; } // callback when a transaction complete on // - DATA stage of control endpoint or // - Status stage -bool usbd_control_xfer_cb (uint8_t rhport, uint8_t ep_addr, xfer_result_t result, uint32_t xferred_bytes) -{ +bool usbd_control_xfer_cb(uint8_t rhport, uint8_t ep_addr, xfer_result_t result, uint32_t xferred_bytes) { (void) result; // Endpoint Address is opposite to direction bit, this is Status Stage complete event - if ( tu_edpt_dir(ep_addr) != _ctrl_xfer.request.bmRequestType_bit.direction ) - { + if (tu_edpt_dir(ep_addr) != _ctrl_xfer.request.bmRequestType_bit.direction) { TU_ASSERT(0 == xferred_bytes); - if (dcd_edpt0_status_complete) dcd_edpt0_status_complete(rhport, &_ctrl_xfer.request); + + // invoke optional dcd hook if available + dcd_edpt0_status_complete(rhport, &_ctrl_xfer.request); + + if (_ctrl_xfer.complete_cb) { + // TODO refactor with usbd_driver_print_control_complete_name + _ctrl_xfer.complete_cb(rhport, CONTROL_STAGE_ACK, &_ctrl_xfer.request); + } + return true; } - if ( _ctrl_xfer.request.bmRequestType_bit.direction == TUSB_DIR_OUT ) - { + if (_ctrl_xfer.request.bmRequestType_bit.direction == TUSB_DIR_OUT) { TU_VERIFY(_ctrl_xfer.buffer); - memcpy(_ctrl_xfer.buffer, _usbd_ctrl_buf, xferred_bytes); + memcpy(_ctrl_xfer.buffer, _ctrl_epbuf.buf, xferred_bytes); + TU_LOG_MEM(CFG_TUD_LOG_LEVEL, _ctrl_xfer.buffer, xferred_bytes, 2); } - _ctrl_xfer.total_xferred += xferred_bytes; + _ctrl_xfer.total_xferred += (uint16_t) xferred_bytes; _ctrl_xfer.buffer += xferred_bytes; // Data Stage is complete when all request's length are transferred or // a short packet is sent including zero-length packet. - if ( (_ctrl_xfer.request.wLength == _ctrl_xfer.total_xferred) || (xferred_bytes < CFG_TUD_ENDPOINT0_SIZE) ) - { + if ((_ctrl_xfer.request.wLength == _ctrl_xfer.total_xferred) || + (xferred_bytes < CFG_TUD_ENDPOINT0_SIZE)) { // DATA stage is complete bool is_ok = true; // invoke complete callback if set // callback can still stall control in status phase e.g out data does not make sense - if ( _ctrl_xfer.complete_cb ) - { - #if CFG_TUSB_DEBUG >= 2 + if (_ctrl_xfer.complete_cb) { + #if CFG_TUSB_DEBUG >= CFG_TUD_LOG_LEVEL usbd_driver_print_control_complete_name(_ctrl_xfer.complete_cb); #endif - is_ok = _ctrl_xfer.complete_cb(rhport, &_ctrl_xfer.request); + is_ok = _ctrl_xfer.complete_cb(rhport, CONTROL_STAGE_DATA, &_ctrl_xfer.request); } - if ( is_ok ) - { - // Send status - TU_ASSERT( _status_stage_xact(rhport, &_ctrl_xfer.request) ); - }else - { + if (is_ok) { + TU_ASSERT(status_stage_xact(rhport, &_ctrl_xfer.request)); + } else { // Stall both IN and OUT control endpoint dcd_edpt_stall(rhport, EDPT_CTRL_OUT); dcd_edpt_stall(rhport, EDPT_CTRL_IN); } - } - else - { + } else { // More data to transfer - TU_ASSERT( _data_stage_xact(rhport) ); + TU_ASSERT(data_stage_xact(rhport)); } return true; diff --git a/cores/nRF5/TinyUSB/Adafruit_TinyUSB_ArduinoCore/tinyusb/src/device/usbd_pvt.h b/cores/nRF5/TinyUSB/Adafruit_TinyUSB_ArduinoCore/tinyusb/src/device/usbd_pvt.h index 09b2855..a688cf4 100644 --- a/cores/nRF5/TinyUSB/Adafruit_TinyUSB_ArduinoCore/tinyusb/src/device/usbd_pvt.h +++ b/cores/nRF5/TinyUSB/Adafruit_TinyUSB_ArduinoCore/tinyusb/src/device/usbd_pvt.h @@ -1,4 +1,4 @@ -/* +/* * The MIT License (MIT) * * Copyright (c) 2019 Ha Thach (tinyusb.org) @@ -23,42 +23,58 @@ * * This file is part of the TinyUSB stack. */ -#ifndef USBD_PVT_H_ -#define USBD_PVT_H_ +#ifndef TUSB_USBD_PVT_H_ +#define TUSB_USBD_PVT_H_ #include "osal/osal.h" #include "common/tusb_fifo.h" +#include "common/tusb_private.h" #ifdef __cplusplus extern "C" { #endif +#define TU_LOG_USBD(...) TU_LOG(CFG_TUD_LOG_LEVEL, __VA_ARGS__) + //--------------------------------------------------------------------+ -// Class Drivers +// MACRO CONSTANT TYPEDEF PROTYPES //--------------------------------------------------------------------+ -typedef struct -{ - #if CFG_TUSB_DEBUG >= 2 - char const* name; - #endif +typedef enum { + SOF_CONSUMER_USER = 0, + SOF_CONSUMER_AUDIO, +} sof_consumer_t; + +//--------------------------------------------------------------------+ +// Class Driver API +//--------------------------------------------------------------------+ +typedef struct { + char const* name; void (* init ) (void); + bool (* deinit ) (void); void (* reset ) (uint8_t rhport); uint16_t (* open ) (uint8_t rhport, tusb_desc_interface_t const * desc_intf, uint16_t max_len); - bool (* control_request ) (uint8_t rhport, tusb_control_request_t const * request); - bool (* control_complete ) (uint8_t rhport, tusb_control_request_t const * request); - bool (* xfer_cb ) (uint8_t rhport, uint8_t ep_addr, xfer_result_t event, uint32_t xferred_bytes); - void (* sof ) (uint8_t rhport); /* optional */ + bool (* control_xfer_cb ) (uint8_t rhport, uint8_t stage, tusb_control_request_t const * request); + bool (* xfer_cb ) (uint8_t rhport, uint8_t ep_addr, xfer_result_t result, uint32_t xferred_bytes); + bool (* xfer_isr ) (uint8_t rhport, uint8_t ep_addr, xfer_result_t result, uint32_t xferred_bytes); // optional, return false to defer to xfer_cb() + void (* sof ) (uint8_t rhport, uint32_t frame_count); // optional } usbd_class_driver_t; // Invoked when initializing device stack to get additional class drivers. -// Can optionally implemented by application to extend/overwrite class driver support. +// Can be implemented by application to extend/overwrite class driver support. // Note: The drivers array must be accessible at all time when stack is active -usbd_class_driver_t const* usbd_app_driver_get_cb(uint8_t* driver_count) TU_ATTR_WEAK; +usbd_class_driver_t const* usbd_app_driver_get_cb(uint8_t* driver_count); + +typedef bool (*usbd_control_xfer_cb_t)(uint8_t rhport, uint8_t stage, tusb_control_request_t const * request); + +void usbd_int_set(bool enabled); +void usbd_spin_lock(bool in_isr); +void usbd_spin_unlock(bool in_isr); //--------------------------------------------------------------------+ // USBD Endpoint API +// Note: rhport should be 0 since device stack only support 1 rhport for now //--------------------------------------------------------------------+ // Open an endpoint @@ -70,14 +86,17 @@ void usbd_edpt_close(uint8_t rhport, uint8_t ep_addr); // Submit a usb transfer bool usbd_edpt_xfer(uint8_t rhport, uint8_t ep_addr, uint8_t * buffer, uint16_t total_bytes); +// Submit a usb ISO transfer by use of a FIFO (ring buffer) - all bytes in FIFO get transmitted +bool usbd_edpt_xfer_fifo(uint8_t rhport, uint8_t ep_addr, tu_fifo_t * ff, uint16_t total_bytes); + // Claim an endpoint before submitting a transfer. // If caller does not make any transfer, it must release endpoint for others. bool usbd_edpt_claim(uint8_t rhport, uint8_t ep_addr); -// Release an endpoint without submitting a transfer +// Release claimed endpoint without submitting a transfer bool usbd_edpt_release(uint8_t rhport, uint8_t ep_addr); -// Check if endpoint transferring is complete +// Check if endpoint is busy transferring bool usbd_edpt_busy(uint8_t rhport, uint8_t ep_addr); // Stall endpoint @@ -89,22 +108,35 @@ void usbd_edpt_clear_stall(uint8_t rhport, uint8_t ep_addr); // Check if endpoint is stalled bool usbd_edpt_stalled(uint8_t rhport, uint8_t ep_addr); -static inline -bool usbd_edpt_ready(uint8_t rhport, uint8_t ep_addr) -{ +// Allocate packet buffer used by ISO endpoints +bool usbd_edpt_iso_alloc(uint8_t rhport, uint8_t ep_addr, uint16_t largest_packet_size); + +// Configure and enable an ISO endpoint according to descriptor +bool usbd_edpt_iso_activate(uint8_t rhport, tusb_desc_endpoint_t const * p_endpoint_desc); + +// Check if endpoint is ready (not busy and not stalled) +TU_ATTR_ALWAYS_INLINE static inline +bool usbd_edpt_ready(uint8_t rhport, uint8_t ep_addr) { return !usbd_edpt_busy(rhport, ep_addr) && !usbd_edpt_stalled(rhport, ep_addr); } +// Enable SOF interrupt +void usbd_sof_enable(uint8_t rhport, sof_consumer_t consumer, bool en); + /*------------------------------------------------------------------*/ /* Helper *------------------------------------------------------------------*/ bool usbd_open_edpt_pair(uint8_t rhport, uint8_t const* p_desc, uint8_t ep_count, uint8_t xfer_type, uint8_t* ep_out, uint8_t* ep_in); -void usbd_defer_func( osal_task_func_t func, void* param, bool in_isr ); +void usbd_defer_func(osal_task_func_t func, void *param, bool in_isr); +#if CFG_TUSB_DEBUG >= CFG_TUD_LOG_LEVEL +void usbd_driver_print_control_complete_name(usbd_control_xfer_cb_t callback); +#endif + #ifdef __cplusplus } #endif -#endif /* USBD_PVT_H_ */ +#endif diff --git a/cores/nRF5/TinyUSB/Adafruit_TinyUSB_ArduinoCore/tinyusb/src/host/hcd.h b/cores/nRF5/TinyUSB/Adafruit_TinyUSB_ArduinoCore/tinyusb/src/host/hcd.h new file mode 100644 index 0000000..d3551bf --- /dev/null +++ b/cores/nRF5/TinyUSB/Adafruit_TinyUSB_ArduinoCore/tinyusb/src/host/hcd.h @@ -0,0 +1,228 @@ +/* + * The MIT License (MIT) + * + * Copyright (c) 2019 Ha Thach (tinyusb.org) + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + * This file is part of the TinyUSB stack. + */ + +#ifndef _TUSB_HCD_H_ +#define _TUSB_HCD_H_ + +#include "common/tusb_common.h" +#include "osal/osal.h" +#include "common/tusb_fifo.h" + +#ifdef __cplusplus + extern "C" { +#endif + +//--------------------------------------------------------------------+ +// Configuration +//--------------------------------------------------------------------+ + +// Max number of endpoints pair per device +// TODO optimize memory usage +#ifndef CFG_TUH_ENDPOINT_MAX + #define CFG_TUH_ENDPOINT_MAX 16 +// #ifdef TUP_HCD_ENDPOINT_MAX +// #define CFG_TUH_ENDPPOINT_MAX TUP_HCD_ENDPOINT_MAX +// #else +// #define +// #endif +#endif + +//--------------------------------------------------------------------+ +// MACRO CONSTANT TYPEDEF +//--------------------------------------------------------------------+ +typedef enum { + HCD_EVENT_DEVICE_ATTACH, + HCD_EVENT_DEVICE_REMOVE, + HCD_EVENT_XFER_COMPLETE, + + USBH_EVENT_FUNC_CALL, // Not an HCD event + HCD_EVENT_COUNT +} hcd_eventid_t; + +typedef struct { + uint8_t rhport; + uint8_t event_id; + uint8_t dev_addr; + + union { + // Attach, Remove + struct { + uint8_t hub_addr; + uint8_t hub_port; + uint8_t speed; + } connection; + + // XFER_COMPLETE + struct { + uint8_t ep_addr; + uint8_t result; + uint32_t len; + } xfer_complete; + + // FUNC_CALL + struct { + void (*func) (void*); + void* param; + }func_call; + }; +} hcd_event_t; + +//--------------------------------------------------------------------+ +// Memory API +//--------------------------------------------------------------------+ + +// clean/flush data cache: write cache -> memory. +// Required before an DMA TX transfer to make sure data is in memory +bool hcd_dcache_clean(void const* addr, uint32_t data_size); + +// invalidate data cache: mark cache as invalid, next read will read from memory +// Required BOTH before and after an DMA RX transfer +bool hcd_dcache_invalidate(void const* addr, uint32_t data_size); + +// clean and invalidate data cache +// Required before an DMA transfer where memory is both read/write by DMA +bool hcd_dcache_clean_invalidate(void const* addr, uint32_t data_size); + +//--------------------------------------------------------------------+ +// Controller API +//--------------------------------------------------------------------+ + +// optional hcd configuration, called by tuh_configure() +bool hcd_configure(uint8_t rhport, uint32_t cfg_id, const void* cfg_param); + +// Initialize controller to host mode +bool hcd_init(uint8_t rhport, const tusb_rhport_init_t* rh_init); + +// De-initialize controller +bool hcd_deinit(uint8_t rhport); + +// Interrupt Handler +void hcd_int_handler(uint8_t rhport, bool in_isr); + +// Enable USB interrupt +void hcd_int_enable (uint8_t rhport); + +// Disable USB interrupt +void hcd_int_disable(uint8_t rhport); + +// Get frame number (1ms) +uint32_t hcd_frame_number(uint8_t rhport); + +//--------------------------------------------------------------------+ +// Port API +//--------------------------------------------------------------------+ + +// Get the current connect status of roothub port +bool hcd_port_connect_status(uint8_t rhport); + +// Reset USB bus on the port. Return immediately, bus reset sequence may not be complete. +// Some port would require hcd_port_reset_end() to be invoked after 10ms to complete the reset sequence. +void hcd_port_reset(uint8_t rhport); + +// Complete bus reset sequence, may be required by some controllers +void hcd_port_reset_end(uint8_t rhport); + +// Get port link speed +tusb_speed_t hcd_port_speed_get(uint8_t rhport); + +// HCD closes all opened endpoints belong to this device +void hcd_device_close(uint8_t rhport, uint8_t dev_addr); + +//--------------------------------------------------------------------+ +// Endpoints API +//--------------------------------------------------------------------+ + +// Open an endpoint +// return true if successfully opened or endpoint is currently opened +bool hcd_edpt_open(uint8_t rhport, uint8_t daddr, tusb_desc_endpoint_t const * ep_desc); + +// Close an endpoint +bool hcd_edpt_close(uint8_t rhport, uint8_t daddr, uint8_t ep_addr); + +// Submit a transfer, when complete hcd_event_xfer_complete() must be invoked +bool hcd_edpt_xfer(uint8_t rhport, uint8_t daddr, uint8_t ep_addr, uint8_t * buffer, uint16_t buflen); + +// Abort a queued transfer. Note: it can only abort transfer that has not been started +// Return true if a queued transfer is aborted, false if there is no transfer to abort +bool hcd_edpt_abort_xfer(uint8_t rhport, uint8_t dev_addr, uint8_t ep_addr); + +// Submit a special transfer to send 8-byte Setup Packet, when complete hcd_event_xfer_complete() must be invoked +bool hcd_setup_send(uint8_t rhport, uint8_t daddr, uint8_t const setup_packet[8]); + +// clear stall, data toggle is also reset to DATA0 +bool hcd_edpt_clear_stall(uint8_t rhport, uint8_t dev_addr, uint8_t ep_addr); + +//--------------------------------------------------------------------+ +// USBH implemented API +//--------------------------------------------------------------------+ + +// Called by HCD to notify stack +extern void hcd_event_handler(hcd_event_t const* event, bool in_isr); + +// Helper to send device attach event +TU_ATTR_ALWAYS_INLINE static inline +void hcd_event_device_attach(uint8_t rhport, bool in_isr) { + hcd_event_t event; + event.rhport = rhport; + event.event_id = HCD_EVENT_DEVICE_ATTACH; + event.connection.hub_addr = 0; + event.connection.hub_port = 0; + + hcd_event_handler(&event, in_isr); +} + +// Helper to send device removal event +TU_ATTR_ALWAYS_INLINE static inline +void hcd_event_device_remove(uint8_t rhport, bool in_isr) { + hcd_event_t event; + event.rhport = rhport; + event.event_id = HCD_EVENT_DEVICE_REMOVE; + event.connection.hub_addr = 0; + event.connection.hub_port = 0; + + hcd_event_handler(&event, in_isr); +} + +// Helper to send USB transfer event +TU_ATTR_ALWAYS_INLINE static inline +void hcd_event_xfer_complete(uint8_t dev_addr, uint8_t ep_addr, uint32_t xferred_bytes, xfer_result_t result, bool in_isr) { + hcd_event_t event = { + .rhport = 0, // TODO correct rhport + .event_id = HCD_EVENT_XFER_COMPLETE, + .dev_addr = dev_addr, + }; + event.xfer_complete.ep_addr = ep_addr; + event.xfer_complete.result = result; + event.xfer_complete.len = xferred_bytes; + + hcd_event_handler(&event, in_isr); +} + +#ifdef __cplusplus + } +#endif + +#endif diff --git a/cores/nRF5/TinyUSB/Adafruit_TinyUSB_ArduinoCore/tinyusb/src/host/hub.c b/cores/nRF5/TinyUSB/Adafruit_TinyUSB_ArduinoCore/tinyusb/src/host/hub.c new file mode 100644 index 0000000..0b172a5 --- /dev/null +++ b/cores/nRF5/TinyUSB/Adafruit_TinyUSB_ArduinoCore/tinyusb/src/host/hub.c @@ -0,0 +1,478 @@ +/* + * 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. + */ + +#include "tusb_option.h" + +#if (CFG_TUH_ENABLED && CFG_TUH_HUB) + +#include "hcd.h" +#include "usbh.h" +#include "usbh_pvt.h" +#include "hub.h" + +// Debug level, TUSB_CFG_DEBUG must be at least this level for debug message +#define HUB_DEBUG 2 +#define TU_LOG_DRV(...) TU_LOG(HUB_DEBUG, __VA_ARGS__) + +//--------------------------------------------------------------------+ +// MACRO CONSTANT TYPEDEF +//--------------------------------------------------------------------+ +typedef struct { + uint8_t itf_num; + uint8_t ep_in; + + // from hub descriptor + uint8_t bNbrPorts; + uint8_t bPwrOn2PwrGood_2ms; // port power on to good, in 2ms unit + // uint16_t wHubCharacteristics; + + hub_port_status_response_t port_status; +} hub_interface_t; + +typedef struct { + TUH_EPBUF_DEF(status_change, 4); // interrupt endpoint + TUH_EPBUF_DEF(ctrl_buf, CFG_TUH_HUB_BUFSIZE); +} hub_epbuf_t; + +static tuh_xfer_cb_t user_complete_cb = NULL; +static hub_interface_t hub_itfs[CFG_TUH_HUB]; +CFG_TUH_MEM_SECTION static hub_epbuf_t hub_epbufs[CFG_TUH_HUB]; + + +TU_ATTR_ALWAYS_INLINE static inline hub_interface_t* get_hub_itf(uint8_t daddr) { + return &hub_itfs[daddr-1-CFG_TUH_DEVICE_MAX]; +} + +TU_ATTR_ALWAYS_INLINE static inline hub_epbuf_t* get_hub_epbuf(uint8_t daddr) { + return &hub_epbufs[daddr-1-CFG_TUH_DEVICE_MAX]; +} + +#if CFG_TUSB_DEBUG >= HUB_DEBUG +static char const* const _hub_feature_str[] = { + [HUB_FEATURE_PORT_CONNECTION ] = "PORT_CONNECTION", + [HUB_FEATURE_PORT_ENABLE ] = "PORT_ENABLE", + [HUB_FEATURE_PORT_SUSPEND ] = "PORT_SUSPEND", + [HUB_FEATURE_PORT_OVER_CURRENT ] = "PORT_OVER_CURRENT", + [HUB_FEATURE_PORT_RESET ] = "PORT_RESET", + [HUB_FEATURE_PORT_POWER ] = "PORT_POWER", + [HUB_FEATURE_PORT_LOW_SPEED ] = "PORT_LOW_SPEED", + [HUB_FEATURE_PORT_CONNECTION_CHANGE ] = "PORT_CONNECTION_CHANGE", + [HUB_FEATURE_PORT_ENABLE_CHANGE ] = "PORT_ENABLE_CHANGE", + [HUB_FEATURE_PORT_SUSPEND_CHANGE ] = "PORT_SUSPEND_CHANGE", + [HUB_FEATURE_PORT_OVER_CURRENT_CHANGE ] = "PORT_OVER_CURRENT_CHANGE", + [HUB_FEATURE_PORT_RESET_CHANGE ] = "PORT_RESET_CHANGE", + [HUB_FEATURE_PORT_TEST ] = "PORT_TEST", + [HUB_FEATURE_PORT_INDICATOR ] = "PORT_INDICATOR", +}; +#endif + +//--------------------------------------------------------------------+ +// HUB +//--------------------------------------------------------------------+ +bool hub_port_clear_feature(uint8_t hub_addr, uint8_t hub_port, uint8_t feature, + tuh_xfer_cb_t complete_cb, uintptr_t user_data) { + tusb_control_request_t const request = { + .bmRequestType_bit = { + .recipient = (hub_port == 0) ? TUSB_REQ_RCPT_DEVICE : TUSB_REQ_RCPT_OTHER, + .type = TUSB_REQ_TYPE_CLASS, + .direction = TUSB_DIR_OUT + }, + .bRequest = HUB_REQUEST_CLEAR_FEATURE, + .wValue = feature, + .wIndex = hub_port, + .wLength = 0 + }; + + tuh_xfer_t xfer = { + .daddr = hub_addr, + .ep_addr = 0, + .setup = &request, + .buffer = NULL, + .complete_cb = complete_cb, + .user_data = user_data + }; + + TU_LOG_DRV("HUB Clear Feature: %s, addr = %u port = %u\r\n", _hub_feature_str[feature], hub_addr, hub_port); + TU_ASSERT(tuh_control_xfer(&xfer)); + return true; +} + +bool hub_port_set_feature(uint8_t hub_addr, uint8_t hub_port, uint8_t feature, + tuh_xfer_cb_t complete_cb, uintptr_t user_data) { + tusb_control_request_t const request = { + .bmRequestType_bit = { + .recipient = (hub_port == 0) ? TUSB_REQ_RCPT_DEVICE : TUSB_REQ_RCPT_OTHER, + .type = TUSB_REQ_TYPE_CLASS, + .direction = TUSB_DIR_OUT + }, + .bRequest = HUB_REQUEST_SET_FEATURE, + .wValue = feature, + .wIndex = hub_port, + .wLength = 0 + }; + + tuh_xfer_t xfer = { + .daddr = hub_addr, + .ep_addr = 0, + .setup = &request, + .buffer = NULL, + .complete_cb = complete_cb, + .user_data = user_data + }; + + TU_LOG_DRV("HUB Set Feature: %s, addr = %u port = %u\r\n", _hub_feature_str[feature], hub_addr, hub_port); + TU_ASSERT(tuh_control_xfer(&xfer)); + return true; +} + +static void port_get_status_complete (tuh_xfer_t* xfer) { + if (xfer->result == XFER_RESULT_SUCCESS) { + hub_interface_t* p_hub = get_hub_itf(xfer->daddr); + p_hub->port_status = *((const hub_port_status_response_t *) (uintptr_t) xfer->buffer); + } + + xfer->complete_cb = user_complete_cb; + user_complete_cb = NULL; + if (xfer->complete_cb) { + xfer->complete_cb(xfer); + } +} + +bool hub_port_get_status(uint8_t hub_addr, uint8_t hub_port, void* resp, + tuh_xfer_cb_t complete_cb, uintptr_t user_data) { + tusb_control_request_t const request = { + .bmRequestType_bit = { + .recipient = (hub_port == 0) ? TUSB_REQ_RCPT_DEVICE : TUSB_REQ_RCPT_OTHER, + .type = TUSB_REQ_TYPE_CLASS, + .direction = TUSB_DIR_IN + }, + .bRequest = HUB_REQUEST_GET_STATUS, + .wValue = 0, + .wIndex = hub_port, + .wLength = tu_htole16(4) + }; + + tuh_xfer_t xfer = { + .daddr = hub_addr, + .ep_addr = 0, + .setup = &request, + .buffer = resp, + .complete_cb = complete_cb, + .user_data = user_data + }; + + if (hub_port != 0) { + // intercept complete callback to save port status, ignore resp + hub_epbuf_t* p_epbuf = get_hub_epbuf(hub_addr); + xfer.complete_cb = port_get_status_complete; + xfer.buffer = p_epbuf->ctrl_buf; + user_complete_cb = complete_cb; + } else { + user_complete_cb = NULL; + } + + TU_LOG_DRV("HUB Get Port Status: addr = %u port = %u\r\n", hub_addr, hub_port); + TU_VERIFY(tuh_control_xfer(&xfer)); + return true; +} + +bool hub_port_get_status_local(uint8_t hub_addr, uint8_t hub_port, hub_port_status_response_t* resp) { + (void) hub_port; + hub_interface_t* p_hub = get_hub_itf(hub_addr); + *resp = p_hub->port_status; + return true; +} + +//--------------------------------------------------------------------+ +// CLASS-USBH API (don't require to verify parameters) +//--------------------------------------------------------------------+ +bool hub_init(void) { + tu_memclr(hub_itfs, sizeof(hub_itfs)); + return true; +} + +bool hub_deinit(void) { + return true; +} + +bool hub_open(uint8_t rhport, uint8_t dev_addr, tusb_desc_interface_t const *itf_desc, uint16_t max_len) { + (void) rhport; + + TU_VERIFY(TUSB_CLASS_HUB == itf_desc->bInterfaceClass && + 0 == itf_desc->bInterfaceSubClass); + TU_VERIFY(itf_desc->bInterfaceProtocol <= 1); // not support multiple TT yet + + uint16_t const drv_len = sizeof(tusb_desc_interface_t) + sizeof(tusb_desc_endpoint_t); + TU_ASSERT(drv_len <= max_len); + + // Interrupt Status endpoint + tusb_desc_endpoint_t const *desc_ep = (tusb_desc_endpoint_t const *) tu_desc_next(itf_desc); + TU_ASSERT(TUSB_DESC_ENDPOINT == desc_ep->bDescriptorType && + TUSB_XFER_INTERRUPT == desc_ep->bmAttributes.xfer, 0); + TU_ASSERT(tuh_edpt_open(dev_addr, desc_ep)); + + hub_interface_t* p_hub = get_hub_itf(dev_addr); + p_hub->itf_num = itf_desc->bInterfaceNumber; + p_hub->ep_in = desc_ep->bEndpointAddress; + + return true; +} + +void hub_close(uint8_t dev_addr) { + TU_VERIFY(dev_addr > CFG_TUH_DEVICE_MAX, ); + hub_interface_t* p_hub = get_hub_itf(dev_addr); + + if (p_hub->ep_in) { + TU_LOG_DRV(" HUB close addr = %d\r\n", dev_addr); + tu_memclr(p_hub, sizeof( hub_interface_t)); + } +} + +bool hub_edpt_status_xfer(uint8_t daddr) { + hub_interface_t* p_hub = get_hub_itf(daddr); + hub_epbuf_t* p_epbuf = get_hub_epbuf(daddr); + + TU_VERIFY(usbh_edpt_claim(daddr, p_hub->ep_in)); + if (!usbh_edpt_xfer(daddr, p_hub->ep_in, p_epbuf->status_change, 1)) { + usbh_edpt_release(daddr, p_hub->ep_in); + return false; + } + + return true; +} + +//--------------------------------------------------------------------+ +// Set Configure +//--------------------------------------------------------------------+ +static void config_set_port_power (tuh_xfer_t* xfer); +static void config_port_power_complete (tuh_xfer_t* xfer); + +bool hub_set_config(uint8_t daddr, uint8_t itf_num) { + hub_interface_t* p_hub = get_hub_itf(daddr); + TU_ASSERT(itf_num == p_hub->itf_num); + hub_epbuf_t* p_epbuf = get_hub_epbuf(daddr); + + // Get Hub Descriptor + tusb_control_request_t const request = { + .bmRequestType_bit = { + .recipient = TUSB_REQ_RCPT_DEVICE, + .type = TUSB_REQ_TYPE_CLASS, + .direction = TUSB_DIR_IN + }, + .bRequest = HUB_REQUEST_GET_DESCRIPTOR, + .wValue = 0, + .wIndex = 0, + .wLength = sizeof(hub_desc_cs_t) + }; + + tuh_xfer_t xfer = { + .daddr = daddr, + .ep_addr = 0, + .setup = &request, + .buffer = p_epbuf->ctrl_buf, + .complete_cb = config_set_port_power, + .user_data = 0 + }; + + TU_ASSERT(tuh_control_xfer(&xfer)); + return true; +} + +static void config_set_port_power (tuh_xfer_t* xfer) { + TU_ASSERT(XFER_RESULT_SUCCESS == xfer->result, ); + + uint8_t const daddr = xfer->daddr; + hub_interface_t* p_hub = get_hub_itf(daddr); + hub_epbuf_t* p_epbuf = get_hub_epbuf(daddr); + + // only use number of ports in hub descriptor + hub_desc_cs_t const* desc_hub = (hub_desc_cs_t const*) p_epbuf->ctrl_buf; + p_hub->bNbrPorts = desc_hub->bNbrPorts; + p_hub->bPwrOn2PwrGood_2ms = desc_hub->bPwrOn2PwrGood; + + // May need to GET_STATUS + + // Set Port Power to be able to detect connection, starting with port 1 + uint8_t const hub_port = 1; + hub_port_set_feature(daddr, hub_port, HUB_FEATURE_PORT_POWER, config_port_power_complete, 0); +} + +static void config_port_power_complete (tuh_xfer_t* xfer) { + TU_ASSERT(XFER_RESULT_SUCCESS == xfer->result, ); + + uint8_t const daddr = xfer->daddr; + hub_interface_t* p_hub = get_hub_itf(daddr); + + if (xfer->setup->wIndex == p_hub->bNbrPorts) { + // All ports are power -> queue notification status endpoint and + // complete the SET CONFIGURATION + if (!hub_edpt_status_xfer(daddr)) { + TU_MESS_FAILED(); + TU_BREAKPOINT(); + } + usbh_driver_set_config_complete(daddr, p_hub->itf_num); + } else { + // power next port + uint8_t const hub_port = (uint8_t) (xfer->setup->wIndex + 1); + hub_port_set_feature(daddr, hub_port, HUB_FEATURE_PORT_POWER, config_port_power_complete, 0); + } +} + +//--------------------------------------------------------------------+ +// Connection Changes +//--------------------------------------------------------------------+ +enum { + STATE_IDLE = 0, + STATE_HUB_STATUS, + STATE_CLEAR_CHANGE, + STATE_CHECK_CONN, + STATE_COMPLETE +}; + +static void process_new_status(tuh_xfer_t* xfer); + +// callback as response of interrupt endpoint polling +bool hub_xfer_cb(uint8_t daddr, uint8_t ep_addr, xfer_result_t result, uint32_t xferred_bytes) { + (void) xferred_bytes; + (void) ep_addr; + + bool processed = false; // true if new status is processed + + if (result == XFER_RESULT_SUCCESS) { + hub_interface_t* p_hub = get_hub_itf(daddr); + hub_epbuf_t *p_epbuf = get_hub_epbuf(daddr); + const uint8_t status_change = p_epbuf->status_change[0]; + TU_LOG_DRV(" Hub Status Change = 0x%02X\r\n", status_change); + + if (status_change == 0) { + // The status change event was neither for the hub, nor for any of its ports. + // This shouldn't happen, but it does with some devices. Re-Initiate the interrupt poll. + processed = false; + } else if (tu_bit_test(status_change, 0)) { + // Hub bit 0 is for the hub device events + processed = hub_get_status(daddr, p_epbuf->ctrl_buf, process_new_status, STATE_HUB_STATUS); + } else { + // Hub bits 1 to n are hub port events + for (uint8_t port=1; port <= p_hub->bNbrPorts; port++) { + if (tu_bit_test(status_change, port)) { + processed = hub_port_get_status(daddr, port, NULL, process_new_status, STATE_CLEAR_CHANGE); + break; // after completely processed one port, we will re-queue the status poll and handle next one + } + } + } + } + + // If new status event is processed: next status pool is queued by usbh.c after handled this request + // Otherwise re-queue the status poll here + if (!processed) { + TU_ASSERT(hub_edpt_status_xfer(daddr)); + } + + return true; +} + +static void process_new_status(tuh_xfer_t* xfer) { + const uint8_t daddr = xfer->daddr; + + if (xfer->result != XFER_RESULT_SUCCESS) { + TU_ASSERT(hub_edpt_status_xfer(daddr),); + return; + } + + const uint8_t port_num = (uint8_t) tu_le16toh(xfer->setup->wIndex); + hub_interface_t *p_hub = get_hub_itf(daddr); + const uintptr_t state = xfer->user_data; + bool processed = false; // true if new status is processed + + switch (state) { + case STATE_HUB_STATUS: { + hub_status_response_t hub_status = *((const hub_status_response_t *) (uintptr_t) xfer->buffer); + TU_LOG_DRV("HUB Got hub status, addr = %u, status = %04x\r\n", daddr, hub_status.change.value); + if (hub_status.change.local_power_source) { + TU_LOG_DRV(" Local Power Change\r\n"); + processed = hub_clear_feature(daddr, HUB_FEATURE_HUB_LOCAL_POWER_CHANGE, + process_new_status, STATE_COMPLETE); + } else if (hub_status.change.over_current) { + TU_LOG_DRV(" Over Current\r\n"); + processed = hub_clear_feature(daddr, HUB_FEATURE_HUB_OVER_CURRENT_CHANGE, + process_new_status, STATE_COMPLETE); + } + break; + } + + case STATE_CLEAR_CHANGE: + // Get port status complete --> clear change + if (p_hub->port_status.change.connection) { + // Connection change + // Port is powered and enabled + //TU_VERIFY(port_status.status_current.port_power && port_status.status_current.port_enable, ); + + // Acknowledge Port Connection Change + processed = hub_port_clear_feature(daddr, port_num, HUB_FEATURE_PORT_CONNECTION_CHANGE, + process_new_status, STATE_CHECK_CONN); + } else if (p_hub->port_status.change.port_enable) { + processed = hub_port_clear_feature(daddr, port_num, HUB_FEATURE_PORT_ENABLE_CHANGE, + process_new_status, STATE_COMPLETE); + } else if (p_hub->port_status.change.suspend) { + processed = hub_port_clear_feature(daddr, port_num, HUB_FEATURE_PORT_SUSPEND_CHANGE, + process_new_status, STATE_COMPLETE); + } else if (p_hub->port_status.change.over_current) { + processed = hub_port_clear_feature(daddr, port_num, HUB_FEATURE_PORT_OVER_CURRENT_CHANGE, + process_new_status, STATE_COMPLETE); + } else if (p_hub->port_status.change.reset) { + processed = hub_port_clear_feature(daddr, port_num, HUB_FEATURE_PORT_RESET_CHANGE, + process_new_status, STATE_COMPLETE); + } + break; + + case STATE_CHECK_CONN: { + const hcd_event_t event = { + .rhport = usbh_get_rhport(daddr), + .event_id = p_hub->port_status.status.connection ? HCD_EVENT_DEVICE_ATTACH : HCD_EVENT_DEVICE_REMOVE, + .connection = { + .hub_addr = daddr, + .hub_port = port_num + } + }; + hcd_event_handler(&event, false); + // skip status for attach event, usbh will do it after handled this enumeration + processed = (event.event_id == HCD_EVENT_DEVICE_ATTACH); + break; + } + + case STATE_COMPLETE: + default: + processed = false; // complete this status, queue next status + break; + + } + + if (!processed) { + TU_ASSERT(hub_edpt_status_xfer(daddr),); + } +} + +#endif diff --git a/cores/nRF5/TinyUSB/Adafruit_TinyUSB_ArduinoCore/tinyusb/src/host/hub.h b/cores/nRF5/TinyUSB/Adafruit_TinyUSB_ArduinoCore/tinyusb/src/host/hub.h new file mode 100644 index 0000000..3587f0e --- /dev/null +++ b/cores/nRF5/TinyUSB/Adafruit_TinyUSB_ArduinoCore/tinyusb/src/host/hub.h @@ -0,0 +1,220 @@ +/* + * The MIT License (MIT) + * + * Copyright (c) 2019 Ha Thach (tinyusb.org) + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + * This file is part of the TinyUSB stack. + */ + +#ifndef TUSB_HUB_H_ +#define TUSB_HUB_H_ + +#include "common/tusb_common.h" + +#ifdef __cplusplus + extern "C" { +#endif + +//--------------------------------------------------------------------+ +// Configuration +//--------------------------------------------------------------------+ + +#ifndef CFG_TUH_HUB_BUFSIZE + #define CFG_TUH_HUB_BUFSIZE 12 +#endif + +//--------------------------------------------------------------------+ +// +//--------------------------------------------------------------------+ +enum { + HUB_REQUEST_GET_STATUS = 0 , + HUB_REQUEST_CLEAR_FEATURE = 1 , + // 2 is reserved + HUB_REQUEST_SET_FEATURE = 3 , + // 4-5 are reserved + HUB_REQUEST_GET_DESCRIPTOR = 6 , + HUB_REQUEST_SET_DESCRIPTOR = 7 , + HUB_REQUEST_CLEAR_TT_BUFFER = 8 , + HUB_REQUEST_RESET_TT = 9 , + HUB_REQUEST_GET_TT_STATE = 10 , + HUB_REQUEST_STOP_TT = 11 +}; + +enum { + HUB_FEATURE_HUB_LOCAL_POWER_CHANGE = 0, + HUB_FEATURE_HUB_OVER_CURRENT_CHANGE +}; + +enum{ + HUB_FEATURE_PORT_CONNECTION = 0, + HUB_FEATURE_PORT_ENABLE = 1, + HUB_FEATURE_PORT_SUSPEND = 2, + HUB_FEATURE_PORT_OVER_CURRENT = 3, + HUB_FEATURE_PORT_RESET = 4, + // 5-7 are reserved + HUB_FEATURE_PORT_POWER = 8, + HUB_FEATURE_PORT_LOW_SPEED = 9, + // 10-15 are reserved + HUB_FEATURE_PORT_CONNECTION_CHANGE = 16, + HUB_FEATURE_PORT_ENABLE_CHANGE = 17, + HUB_FEATURE_PORT_SUSPEND_CHANGE = 18, + HUB_FEATURE_PORT_OVER_CURRENT_CHANGE = 19, + HUB_FEATURE_PORT_RESET_CHANGE = 20, + HUB_FEATURE_PORT_TEST = 21, + HUB_FEATURE_PORT_INDICATOR = 22 +}; + +enum { + HUB_CHARS_POWER_GANGED_SWITCHING = 0, + HUB_CHARS_POWER_INDIVIDUAL_SWITCHING = 1, +}; + +enum { + HUB_CHARS_OVER_CURRENT_GLOBAL = 0, + HUB_CHARS_OVER_CURRENT_INDIVIDUAL = 1, +}; + +typedef struct TU_ATTR_PACKED{ + uint8_t bLength ; ///< Size of descriptor + uint8_t bDescriptorType ; ///< Other_speed_Configuration Type + uint8_t bNbrPorts; + uint16_t wHubCharacteristics; + uint8_t bPwrOn2PwrGood; + uint8_t bHubContrCurrent; + uint8_t DeviceRemovable; // bitmap each bit for a port (from bit1) + uint8_t PortPwrCtrlMask; // just for compatibility, should be 0xff +} hub_desc_cs_t; +TU_VERIFY_STATIC(sizeof(hub_desc_cs_t) == 9, "size is not correct"); +TU_VERIFY_STATIC(CFG_TUH_HUB_BUFSIZE >= sizeof(hub_desc_cs_t), "buffer is not big enough"); + +typedef struct TU_ATTR_PACKED { + struct TU_ATTR_PACKED { + uint8_t logical_power_switching_mode : 2; // [0..1] gannged or individual power switching + uint8_t compound_device : 1; // [2] hub is part of compound device + uint8_t over_current_protect_mode : 2; // [3..4] global or individual port over-current protection + uint8_t tt_think_time : 2; // [5..6] TT think time + uint8_t port_indicator_supported : 1; // [7] port indicator supported + }; + uint8_t rsv1; +} hub_characteristics_t; +TU_VERIFY_STATIC(sizeof(hub_characteristics_t) == 2, "size is not correct"); + +// data in response of HUB_REQUEST_GET_STATUS, wIndex = 0 (hub) +typedef struct { + union{ + struct TU_ATTR_PACKED { + uint16_t local_power_source : 1; + uint16_t over_current : 1; + uint16_t : 14; + }; + + uint16_t value; + } status, change; +} hub_status_response_t; +TU_VERIFY_STATIC( sizeof(hub_status_response_t) == 4, "size is not correct"); + +// data in response of HUB_REQUEST_GET_STATUS, wIndex = Port num +typedef struct { + union TU_ATTR_PACKED { + struct TU_ATTR_PACKED { + // Bit 0-4 are for change & status + uint16_t connection : 1; // [0] 0 = no device, 1 = device connected + uint16_t port_enable : 1; // [1] port is enabled + uint16_t suspend : 1; // [2] + uint16_t over_current : 1; // [3] over-current exists + uint16_t reset : 1; // [4] 0 = no reset, 1 = resetting + + // From Bit 5 are for status only + uint16_t rsv5_7 : 3; // [5..7] reserved + uint16_t port_power : 1; // [8] 0 = port is off, 1 = port is on + uint16_t low_speed : 1; // [9] low speed device attached + uint16_t high_speed : 1; // [10] high speed device attached + uint16_t port_test_mode : 1; // [11] port in test mode + uint16_t port_indicator_control : 1; // [12] 0: default color, 1: indicator is software controlled + uint16_t TU_RESERVED : 3; // [13..15] reserved + }; + + uint16_t value; + } status, change; +} hub_port_status_response_t; +TU_VERIFY_STATIC( sizeof(hub_port_status_response_t) == 4, "size is not correct"); + +//--------------------------------------------------------------------+ +// HUB API +//--------------------------------------------------------------------+ + +// Clear port feature +bool hub_port_clear_feature(uint8_t hub_addr, uint8_t hub_port, uint8_t feature, + tuh_xfer_cb_t complete_cb, uintptr_t user_data); + +// Set port feature +bool hub_port_set_feature(uint8_t hub_addr, uint8_t hub_port, uint8_t feature, + tuh_xfer_cb_t complete_cb, uintptr_t user_data); + +// Get port status +// If hub_port != 0, resp is ignored. hub_port_get_status_local() can be used to retrieve the status +bool hub_port_get_status(uint8_t hub_addr, uint8_t hub_port, void *resp, + tuh_xfer_cb_t complete_cb, uintptr_t user_data); + +// Get port status from local cache. This does not send a request to the device +bool hub_port_get_status_local(uint8_t hub_addr, uint8_t hub_port, hub_port_status_response_t* resp); + +// Get status from Interrupt endpoint +bool hub_edpt_status_xfer(uint8_t daddr); + +// Reset a port +TU_ATTR_ALWAYS_INLINE static inline +bool hub_port_reset(uint8_t hub_addr, uint8_t hub_port, tuh_xfer_cb_t complete_cb, uintptr_t user_data) { + return hub_port_set_feature(hub_addr, hub_port, HUB_FEATURE_PORT_RESET, complete_cb, user_data); +} + +// Clear Port Reset Change +TU_ATTR_ALWAYS_INLINE static inline +bool hub_port_clear_reset_change(uint8_t hub_addr, uint8_t hub_port, tuh_xfer_cb_t complete_cb, uintptr_t user_data) { + return hub_port_clear_feature(hub_addr, hub_port, HUB_FEATURE_PORT_RESET_CHANGE, complete_cb, user_data); +} + +// Get Hub status (port = 0) +TU_ATTR_ALWAYS_INLINE static inline +bool hub_get_status(uint8_t hub_addr, void* resp, tuh_xfer_cb_t complete_cb, uintptr_t user_data) { + return hub_port_get_status(hub_addr, 0, resp, complete_cb, user_data); +} + +// Clear Hub feature +TU_ATTR_ALWAYS_INLINE static inline +bool hub_clear_feature(uint8_t hub_addr, uint8_t feature, tuh_xfer_cb_t complete_cb, uintptr_t user_data) { + return hub_port_clear_feature(hub_addr, 0, feature, complete_cb, user_data); +} +//--------------------------------------------------------------------+ +// Internal Class Driver API +//--------------------------------------------------------------------+ +bool hub_init (void); +bool hub_deinit (void); +bool hub_open (uint8_t rhport, uint8_t dev_addr, tusb_desc_interface_t const *itf_desc, uint16_t max_len); +bool hub_set_config (uint8_t daddr, uint8_t itf_num); +bool hub_xfer_cb (uint8_t daddr, uint8_t ep_addr, xfer_result_t event, uint32_t xferred_bytes); +void hub_close (uint8_t dev_addr); + +#ifdef __cplusplus + } +#endif + +#endif diff --git a/cores/nRF5/TinyUSB/Adafruit_TinyUSB_ArduinoCore/tinyusb/src/host/usbh.c b/cores/nRF5/TinyUSB/Adafruit_TinyUSB_ArduinoCore/tinyusb/src/host/usbh.c new file mode 100644 index 0000000..6bafde3 --- /dev/null +++ b/cores/nRF5/TinyUSB/Adafruit_TinyUSB_ArduinoCore/tinyusb/src/host/usbh.c @@ -0,0 +1,1939 @@ +/* + * 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. + */ + +#include "tusb_option.h" + +#if CFG_TUH_ENABLED + +#include "hcd.h" +#include "tusb.h" +#include "usbh_pvt.h" +#include "hub.h" + +//--------------------------------------------------------------------+ +// Configuration +//--------------------------------------------------------------------+ +#ifndef CFG_TUH_TASK_QUEUE_SZ + #define CFG_TUH_TASK_QUEUE_SZ 16 +#endif + +#ifndef CFG_TUH_INTERFACE_MAX + #define CFG_TUH_INTERFACE_MAX 8 +#endif + +enum { + USBH_CONTROL_RETRY_MAX = 3, +}; + +//--------------------------------------------------------------------+ +// Weak stubs: invoked if no strong implementation is available +//--------------------------------------------------------------------+ +TU_ATTR_WEAK bool hcd_deinit(uint8_t rhport) { + (void) rhport; return false; +} + +TU_ATTR_WEAK bool hcd_configure(uint8_t rhport, uint32_t cfg_id, const void* cfg_param) { + (void) rhport; (void) cfg_id; (void) cfg_param; + return false; +} + +TU_ATTR_WEAK void tuh_enum_descriptor_device_cb(uint8_t daddr, const tusb_desc_device_t *desc_device) { + (void) daddr; (void) desc_device; +} + +TU_ATTR_WEAK bool tuh_enum_descriptor_configuration_cb(uint8_t daddr, uint8_t cfg_index, const tusb_desc_configuration_t *desc_config) { + (void) daddr; (void) cfg_index; (void) desc_config; + return true; +} + +TU_ATTR_WEAK void tuh_event_hook_cb(uint8_t rhport, uint32_t eventid, bool in_isr) { + (void) rhport; (void) eventid; (void) in_isr; +} + +TU_ATTR_WEAK bool hcd_dcache_clean(const void* addr, uint32_t data_size) { + (void) addr; (void) data_size; + return false; +} + +TU_ATTR_WEAK bool hcd_dcache_invalidate(const void* addr, uint32_t data_size) { + (void) addr; (void) data_size; + return false; +} + +TU_ATTR_WEAK bool hcd_dcache_clean_invalidate(const void* addr, uint32_t data_size) { + (void) addr; (void) data_size; + return false; +} + +TU_ATTR_WEAK usbh_class_driver_t const* usbh_app_driver_get_cb(uint8_t* driver_count) { + *driver_count = 0; + return NULL; +} + +TU_ATTR_WEAK void tuh_mount_cb(uint8_t daddr) { + (void) daddr; +} + +TU_ATTR_WEAK void tuh_umount_cb(uint8_t daddr) { + (void) daddr; +} + +//--------------------------------------------------------------------+ +// Data Structure +//--------------------------------------------------------------------+ +typedef struct { + tuh_bus_info_t bus_info; + + // Device Descriptor + uint16_t bcdUSB; + uint8_t bDeviceClass; + uint8_t bDeviceSubClass; + uint8_t bDeviceProtocol; + uint8_t bMaxPacketSize0; + uint16_t idVendor; + uint16_t idProduct; + uint16_t bcdDevice; + uint8_t iManufacturer; + uint8_t iProduct; + uint8_t iSerialNumber; + uint8_t bNumConfigurations; + + // Device State + struct TU_ATTR_PACKED { + volatile uint8_t connected : 1; // After 1st transfer + volatile uint8_t addressed : 1; // After SET_ADDR + volatile uint8_t configured : 1; // After SET_CONFIG and all drivers are configured + volatile uint8_t suspended : 1; // Bus suspended + // volatile uint8_t removing : 1; // Physically disconnected, waiting to be processed by usbh + }; + + // Endpoint & Interface + uint8_t itf2drv[CFG_TUH_INTERFACE_MAX]; // map interface number to driver (0xff is invalid) + uint8_t ep2drv[CFG_TUH_ENDPOINT_MAX][2]; // map endpoint to driver ( 0xff is invalid ), can use only 4-bit each + + tu_edpt_state_t ep_status[CFG_TUH_ENDPOINT_MAX][2]; + +#if CFG_TUH_API_EDPT_XFER + // TODO array can be CFG_TUH_ENDPOINT_MAX-1 + struct { + tuh_xfer_cb_t complete_cb; + uintptr_t user_data; + }ep_callback[CFG_TUH_ENDPOINT_MAX][2]; +#endif + +} usbh_device_t; + +// sum of end device + hub +#define TOTAL_DEVICES (CFG_TUH_DEVICE_MAX + CFG_TUH_HUB) + +// all devices excluding zero-address +// hub address start from CFG_TUH_DEVICE_MAX+1 +// TODO: hub can has its own simpler struct to save memory +static usbh_device_t _usbh_devices[TOTAL_DEVICES]; + +// Mutex for claiming endpoint +#if OSAL_MUTEX_REQUIRED +static osal_mutex_def_t _usbh_mutexdef; +static osal_mutex_t _usbh_mutex; +#else +#define _usbh_mutex NULL +#endif + +// Spinlock for interrupt handler +static OSAL_SPINLOCK_DEF(_usbh_spin, usbh_int_set); + +// Event queue: usbh_int_set() is used as mutex in OS NONE config +OSAL_QUEUE_DEF(usbh_int_set, _usbh_qdef, CFG_TUH_TASK_QUEUE_SZ, hcd_event_t); +static osal_queue_t _usbh_q; + +// Control transfers: since most controllers do not support multiple control transfers +// on multiple devices concurrently and control transfers are not used much except for +// enumeration, we will only execute control transfers one at a time. +typedef struct { + uint8_t* buffer; + tuh_xfer_cb_t complete_cb; + uintptr_t user_data; + + volatile uint8_t stage; + uint8_t daddr; + volatile uint16_t actual_len; + uint8_t failed_count; +} usbh_ctrl_xfer_info_t; + +typedef struct { + uint8_t controller_id; // controller ID + uint8_t enumerating_daddr; // device address of the device being enumerated + uint8_t attach_debouncing_bm; // bitmask for roothub port attach debouncing + tuh_bus_info_t dev0_bus; // bus info for dev0 in enumeration + usbh_ctrl_xfer_info_t ctrl_xfer_info; // control transfer +} usbh_data_t; + +static usbh_data_t _usbh_data = { + .controller_id = TUSB_INDEX_INVALID_8, +}; + +typedef struct { + TUH_EPBUF_TYPE_DEF(tusb_control_request_t, request); + TUH_EPBUF_DEF(ctrl, CFG_TUH_ENUMERATION_BUFSIZE); +} usbh_epbuf_t; +CFG_TUH_MEM_SECTION static usbh_epbuf_t _usbh_epbuf; + +//--------------------------------------------------------------------+ +// Class Driver +//--------------------------------------------------------------------+ +#if CFG_TUSB_DEBUG >= CFG_TUH_LOG_LEVEL + #define DRIVER_NAME(_name) _name +#else + #define DRIVER_NAME(_name) NULL +#endif + +static usbh_class_driver_t const usbh_class_drivers[] = { + #if CFG_TUH_CDC + { + .name = DRIVER_NAME("CDC"), + .init = cdch_init, + .deinit = cdch_deinit, + .open = cdch_open, + .set_config = cdch_set_config, + .xfer_cb = cdch_xfer_cb, + .close = cdch_close + }, + #endif + + #if CFG_TUH_MSC + { + .name = DRIVER_NAME("MSC"), + .init = msch_init, + .deinit = msch_deinit, + .open = msch_open, + .set_config = msch_set_config, + .xfer_cb = msch_xfer_cb, + .close = msch_close + }, + #endif + + #if CFG_TUH_HID + { + .name = DRIVER_NAME("HID"), + .init = hidh_init, + .deinit = hidh_deinit, + .open = hidh_open, + .set_config = hidh_set_config, + .xfer_cb = hidh_xfer_cb, + .close = hidh_close + }, + #endif + + #if CFG_TUH_MIDI + { + .name = DRIVER_NAME("MIDI"), + .init = midih_init, + .deinit = midih_deinit, + .open = midih_open, + .set_config = midih_set_config, + .xfer_cb = midih_xfer_cb, + .close = midih_close + }, + #endif + + #if CFG_TUH_HUB + { + .name = DRIVER_NAME("HUB"), + .init = hub_init, + .deinit = hub_deinit, + .open = hub_open, + .set_config = hub_set_config, + .xfer_cb = hub_xfer_cb, + .close = hub_close + }, + #endif + + #if CFG_TUH_VENDOR + { + .name = DRIVER_NAME("VENDOR"), + .init = cush_init, + .deinit = cush_deinit, + .open = cush_open, + .set_config = cush_set_config, + .xfer_cb = cush_isr, + .close = cush_close + } + #endif +}; + +enum { BUILTIN_DRIVER_COUNT = TU_ARRAY_SIZE(usbh_class_drivers) }; + +// Additional class drivers implemented by application +static usbh_class_driver_t const * _app_driver = NULL; +static uint8_t _app_driver_count = 0; + +#define TOTAL_DRIVER_COUNT (_app_driver_count + BUILTIN_DRIVER_COUNT) + +static inline usbh_class_driver_t const *get_driver(uint8_t drv_id) { + usbh_class_driver_t const *driver = NULL; + + if ( drv_id < _app_driver_count ) { + driver = &_app_driver[drv_id]; + } else if ( drv_id < TOTAL_DRIVER_COUNT && BUILTIN_DRIVER_COUNT > 0) { + driver = &usbh_class_drivers[drv_id - _app_driver_count]; + } + + return driver; +} + +//--------------------------------------------------------------------+ +// Function Inline and Prototypes +//--------------------------------------------------------------------+ +static bool enum_new_device(hcd_event_t* event); +static void process_removed_device(uint8_t rhport, uint8_t hub_addr, uint8_t hub_port); +static bool usbh_edpt_control_open(uint8_t dev_addr, uint8_t max_packet_size); +static bool usbh_control_xfer_cb (uint8_t daddr, uint8_t ep_addr, xfer_result_t result, uint32_t xferred_bytes); + +TU_ATTR_ALWAYS_INLINE static inline usbh_device_t* get_device(uint8_t dev_addr) { + TU_VERIFY(dev_addr > 0 && dev_addr <= TOTAL_DEVICES, NULL); + return &_usbh_devices[dev_addr-1]; +} + +TU_ATTR_ALWAYS_INLINE static inline bool is_hub_addr(uint8_t daddr) { + return (CFG_TUH_HUB > 0) && (daddr > CFG_TUH_DEVICE_MAX); +} + +TU_ATTR_ALWAYS_INLINE static inline bool queue_event(hcd_event_t const * event, bool in_isr) { + TU_ASSERT(osal_queue_send(_usbh_q, event, in_isr)); + tuh_event_hook_cb(event->rhport, event->event_id, in_isr); + return true; +} + +TU_ATTR_ALWAYS_INLINE static inline void _control_set_xfer_stage(uint8_t stage) { + if (_usbh_data.ctrl_xfer_info.stage != stage) { + (void) osal_mutex_lock(_usbh_mutex, OSAL_TIMEOUT_WAIT_FOREVER); + _usbh_data.ctrl_xfer_info.stage = stage; + (void) osal_mutex_unlock(_usbh_mutex); + } +} + +TU_ATTR_ALWAYS_INLINE static inline bool usbh_setup_send(uint8_t daddr, const uint8_t setup_packet[8]) { + const uint8_t rhport = usbh_get_rhport(daddr); + const bool ret = hcd_setup_send(rhport, daddr, setup_packet); + if (!ret) { + _control_set_xfer_stage(CONTROL_STAGE_IDLE); + } + return ret; +} + +TU_ATTR_ALWAYS_INLINE static inline void usbh_device_close(uint8_t rhport, uint8_t daddr) { + hcd_device_close(rhport, daddr); + + // abort any ongoing control transfer + if (daddr == _usbh_data.ctrl_xfer_info.daddr) { + _control_set_xfer_stage(CONTROL_STAGE_IDLE); + } + + // invalidate if enumerating + if (daddr == _usbh_data.enumerating_daddr) { + _usbh_data.enumerating_daddr = TUSB_INDEX_INVALID_8; + } +} + +//--------------------------------------------------------------------+ +// Device API +//--------------------------------------------------------------------+ +bool tuh_mounted(uint8_t dev_addr) { + usbh_device_t *dev = get_device(dev_addr); + TU_VERIFY(dev); + return dev->configured; +} + +bool tuh_connected(uint8_t daddr) { + if (daddr == 0) { + return _usbh_data.enumerating_daddr == 0; + } else { + const usbh_device_t* dev = get_device(daddr); + return dev && dev->connected; + } +} + +bool tuh_vid_pid_get(uint8_t dev_addr, uint16_t *vid, uint16_t *pid) { + *vid = *pid = 0; + + usbh_device_t const *dev = get_device(dev_addr); + TU_VERIFY(dev && dev->addressed && dev->idVendor != 0); + + *vid = dev->idVendor; + *pid = dev->idProduct; + + return true; +} + +bool tuh_descriptor_get_device_local(uint8_t daddr, tusb_desc_device_t* desc_device) { + usbh_device_t *dev = get_device(daddr); + TU_VERIFY(dev && desc_device); + + desc_device->bLength = sizeof(tusb_desc_device_t); + desc_device->bDescriptorType = TUSB_DESC_DEVICE; + desc_device->bcdUSB = dev->bcdUSB; + desc_device->bDeviceClass = dev->bDeviceClass; + desc_device->bDeviceSubClass = dev->bDeviceSubClass; + desc_device->bDeviceProtocol = dev->bDeviceProtocol; + desc_device->bMaxPacketSize0 = dev->bMaxPacketSize0; + desc_device->idVendor = dev->idVendor; + desc_device->idProduct = dev->idProduct; + desc_device->bcdDevice = dev->bcdDevice; + desc_device->iManufacturer = dev->iManufacturer; + desc_device->iProduct = dev->iProduct; + desc_device->iSerialNumber = dev->iSerialNumber; + desc_device->bNumConfigurations = dev->bNumConfigurations; + + return true; +} + +tusb_speed_t tuh_speed_get(uint8_t daddr) { + tuh_bus_info_t bus_info; + tuh_bus_info_get(daddr, &bus_info); + return (tusb_speed_t)bus_info.speed; +} + +bool tuh_rhport_is_active(uint8_t rhport) { + return _usbh_data.controller_id == rhport; +} + +bool tuh_rhport_reset_bus(uint8_t rhport, bool active) { + TU_VERIFY(tuh_rhport_is_active(rhport)); + if (active) { + hcd_port_reset(rhport); + } else { + hcd_port_reset_end(rhport); + } + return true; +} + +//--------------------------------------------------------------------+ +// PUBLIC API (Parameter Verification is required) +//--------------------------------------------------------------------+ +bool tuh_configure(uint8_t rhport, uint32_t cfg_id, const void *cfg_param) { + return hcd_configure(rhport, cfg_id, cfg_param); +} + +static void clear_device(usbh_device_t* dev) { + tu_memclr(dev, sizeof(usbh_device_t)); + memset(dev->itf2drv, TUSB_INDEX_INVALID_8, sizeof(dev->itf2drv)); // invalid mapping + memset(dev->ep2drv , TUSB_INDEX_INVALID_8, sizeof(dev->ep2drv )); // invalid mapping +} + +bool tuh_inited(void) { + return _usbh_data.controller_id != TUSB_INDEX_INVALID_8; +} + +bool tuh_rhport_init(uint8_t rhport, const tusb_rhport_init_t* rh_init) { + if (tuh_rhport_is_active(rhport)) { + return true; // skip if already initialized + } +#if CFG_TUSB_DEBUG >= CFG_TUH_LOG_LEVEL + char const* speed_str = 0; + switch (rh_init->speed) { + case TUSB_SPEED_HIGH: + speed_str = "High"; + break; + case TUSB_SPEED_FULL: + speed_str = "Full"; + break; + case TUSB_SPEED_LOW: + speed_str = "Low"; + break; + case TUSB_SPEED_AUTO: + speed_str = "Auto"; + break; + default: + break; + } + TU_LOG_USBH("USBH init on controller %u, speed = %s\r\n", rhport, speed_str); +#endif + + // Init host stack if not already + if (!tuh_inited()) { + TU_LOG_INT_USBH(sizeof(usbh_data_t)); + TU_LOG_INT_USBH(sizeof(usbh_device_t)); + TU_LOG_INT_USBH(sizeof(hcd_event_t)); + TU_LOG_INT_USBH(sizeof(tuh_xfer_t)); + TU_LOG_INT_USBH(sizeof(tu_fifo_t)); + TU_LOG_INT_USBH(sizeof(tu_edpt_stream_t)); + + osal_spin_init(&_usbh_spin); + + // Event queue + _usbh_q = osal_queue_create(&_usbh_qdef); + TU_ASSERT(_usbh_q != NULL); + +#if OSAL_MUTEX_REQUIRED + // Init mutex + _usbh_mutex = osal_mutex_create(&_usbh_mutexdef); + TU_ASSERT(_usbh_mutex); +#endif + + // Get application driver if available + _app_driver = usbh_app_driver_get_cb(&_app_driver_count); + + // Device + tu_memclr(_usbh_devices, sizeof(_usbh_devices)); + tu_memclr(&_usbh_data, sizeof(_usbh_data)); + + _usbh_data.controller_id = TUSB_INDEX_INVALID_8; + _usbh_data.enumerating_daddr = TUSB_INDEX_INVALID_8; + + for (uint8_t i = 0; i < TOTAL_DEVICES; i++) { + clear_device(&_usbh_devices[i]); + } + + // Class drivers + for (uint8_t drv_id = 0; drv_id < TOTAL_DRIVER_COUNT; drv_id++) { + usbh_class_driver_t const* driver = get_driver(drv_id); + if (driver) { + TU_LOG_USBH("%s init\r\n", driver->name); + driver->init(); + } + } + } + + // Init host controller + _usbh_data.controller_id = rhport; + TU_ASSERT(hcd_init(rhport, rh_init)); + hcd_int_enable(rhport); + + return true; +} + +bool tuh_deinit(uint8_t rhport) { + if (!tuh_rhport_is_active(rhport)) { + return true; + } + + // deinit host controller + hcd_int_disable(rhport); + hcd_deinit(rhport); + _usbh_data.controller_id = TUSB_INDEX_INVALID_8; + + // "unplug" all devices on this rhport (hub_addr = 0, hub_port = 0) + process_removed_device(rhport, 0, 0); + + // deinit host stack if no controller is active + if (!tuh_inited()) { + // Class drivers + for (uint8_t drv_id = 0; drv_id < TOTAL_DRIVER_COUNT; drv_id++) { + usbh_class_driver_t const* driver = get_driver(drv_id); + if (driver && driver->deinit) { + TU_LOG_USBH("%s deinit\r\n", driver->name); + driver->deinit(); + } + } + + osal_queue_delete(_usbh_q); + _usbh_q = NULL; + + #if OSAL_MUTEX_REQUIRED + // TODO make sure there is no task waiting on this mutex + osal_mutex_delete(_usbh_mutex); + _usbh_mutex = NULL; + #endif + } + + return true; +} + +bool tuh_task_event_ready(void) { + if (!tuh_inited()) { + return false; // Skip if stack is not initialized + } + return !osal_queue_empty(_usbh_q); +} + +/* USB Host Driver task + * This top level thread manages all host controller event and delegates events to class-specific drivers. + * This should be called periodically within the mainloop or rtos thread. + * + @code + int main(void) { + application_init(); + tusb_init(0, TUSB_ROLE_HOST); + + while(1) { // the mainloop + application_code(); + tuh_task(); // tinyusb host task + } + } + @endcode + */ +void tuh_task_ext(uint32_t timeout_ms, bool in_isr) { + (void) in_isr; // not implemented yet + + // Skip if stack is not initialized + if (!tuh_inited()) { + return; + } + + // Loop until there is no more events in the queue + while (1) { + hcd_event_t event; + if (!osal_queue_receive(_usbh_q, &event, timeout_ms)) { return; } + + switch (event.event_id) { + case HCD_EVENT_DEVICE_ATTACH: + // due to the shared control buffer, we must fully complete enumerating one device first. + // TODO better to have an separated queue for newly attached devices + if (_usbh_data.enumerating_daddr == TUSB_INDEX_INVALID_8) { + // New device attached and we are ready + TU_LOG_USBH("[%u:] USBH Device Attach\r\n", event.rhport); + _usbh_data.enumerating_daddr = 0; // enumerate new device with address 0 + enum_new_device(&event); + } else { + // currently enumerating another device + TU_LOG_USBH("[%u:] USBH Defer Attach until current enumeration complete\r\n", event.rhport); + const bool is_empty = osal_queue_empty(_usbh_q); + queue_event(&event, in_isr); + if (is_empty) { + return; // Exit if this is the only event in the queue, otherwise we loop forever + } + } + break; + + case HCD_EVENT_DEVICE_REMOVE: + TU_LOG_USBH("[%u:%u:%u] USBH DEVICE REMOVED\r\n", event.rhport, event.connection.hub_addr, event.connection.hub_port); + if (_usbh_data.enumerating_daddr == 0 && + event.rhport == _usbh_data.dev0_bus.rhport && + event.connection.hub_addr == _usbh_data.dev0_bus.hub_addr && + event.connection.hub_port == _usbh_data.dev0_bus.hub_port) { + // dev0 is unplugged while enumerating (not yet assigned an address) + usbh_device_close(_usbh_data.dev0_bus.rhport, 0); + } else { + process_removed_device(event.rhport, event.connection.hub_addr, event.connection.hub_port); + } + break; + + case HCD_EVENT_XFER_COMPLETE: { + uint8_t const ep_addr = event.xfer_complete.ep_addr; + uint8_t const epnum = tu_edpt_number(ep_addr); + uint8_t const ep_dir = (uint8_t) tu_edpt_dir(ep_addr); + + TU_LOG_USBH("[:%u] on EP %02X with %u bytes: %s\r\n", + event.dev_addr, ep_addr, (unsigned int) event.xfer_complete.len, tu_str_xfer_result[event.xfer_complete.result]); + + if (event.dev_addr == 0) { + // device 0 only has control endpoint + TU_ASSERT(epnum == 0,); + usbh_control_xfer_cb(event.dev_addr, ep_addr, (xfer_result_t) event.xfer_complete.result, event.xfer_complete.len); + } else { + usbh_device_t* dev = get_device(event.dev_addr); + TU_VERIFY(dev && dev->connected,); + + dev->ep_status[epnum][ep_dir].busy = 0; + dev->ep_status[epnum][ep_dir].claimed = 0; + + if (0 == epnum) { + usbh_control_xfer_cb(event.dev_addr, ep_addr, (xfer_result_t) event.xfer_complete.result, event.xfer_complete.len); + } else { + // Prefer application callback over built-in one if available. This occurs when tuh_edpt_xfer() is used + // with enabled driver e.g HID endpoint + #if CFG_TUH_API_EDPT_XFER + tuh_xfer_cb_t const complete_cb = dev->ep_callback[epnum][ep_dir].complete_cb; + if ( complete_cb ) { + // re-construct xfer info + tuh_xfer_t xfer = { + .daddr = event.dev_addr, + .ep_addr = ep_addr, + .result = (xfer_result_t)event.xfer_complete.result, + .actual_len = event.xfer_complete.len, + .buflen = 0, // not available + .buffer = NULL, // not available + .complete_cb = complete_cb, + .user_data = dev->ep_callback[epnum][ep_dir].user_data + }; + complete_cb(&xfer); + }else + #endif + { + uint8_t drv_id = dev->ep2drv[epnum][ep_dir]; + usbh_class_driver_t const* driver = get_driver(drv_id); + if (driver) { + TU_LOG_USBH(" %s xfer callback\r\n", driver->name); + driver->xfer_cb(event.dev_addr, ep_addr, (xfer_result_t) event.xfer_complete.result, + event.xfer_complete.len); + } else { + // no driver/callback responsible for this transfer + TU_ASSERT(false,); + } + } + } + } + break; + } + + case USBH_EVENT_FUNC_CALL: + if (event.func_call.func) event.func_call.func(event.func_call.param); + break; + + default: + break; + } + +#if CFG_TUSB_OS != OPT_OS_NONE && CFG_TUSB_OS != OPT_OS_PICO + // return if there is no more events, for application to run other background + if (osal_queue_empty(_usbh_q)) return; +#endif + } +} + +//--------------------------------------------------------------------+ +// Control transfer +//--------------------------------------------------------------------+ + +static void _control_blocking_complete_cb(tuh_xfer_t* xfer) { + // update result + *((xfer_result_t*) xfer->user_data) = xfer->result; +} + +// TODO timeout_ms is not supported yet +bool tuh_control_xfer (tuh_xfer_t* xfer) { + TU_VERIFY(xfer->ep_addr == 0 && xfer->setup); // EP0 with setup packet + const uint8_t daddr = xfer->daddr; + TU_VERIFY(tuh_connected(daddr)); + + usbh_ctrl_xfer_info_t* ctrl_info = &_usbh_data.ctrl_xfer_info; + + TU_VERIFY(ctrl_info->stage == CONTROL_STAGE_IDLE); // pre-check to help reducing mutex lock + (void) osal_mutex_lock(_usbh_mutex, OSAL_TIMEOUT_WAIT_FOREVER); + bool const is_idle = (ctrl_info->stage == CONTROL_STAGE_IDLE); + if (is_idle) { + ctrl_info->stage = CONTROL_STAGE_SETUP; + ctrl_info->daddr = daddr; + ctrl_info->actual_len = 0; + ctrl_info->failed_count = 0; + + ctrl_info->buffer = xfer->buffer; + ctrl_info->complete_cb = xfer->complete_cb; + ctrl_info->user_data = xfer->user_data; + _usbh_epbuf.request = (*xfer->setup); + } + (void) osal_mutex_unlock(_usbh_mutex); + + TU_VERIFY(is_idle); + TU_LOG_USBH("[%u:%u] %s: ", usbh_get_rhport(daddr), daddr, + (xfer->setup->bmRequestType_bit.type == TUSB_REQ_TYPE_STANDARD && xfer->setup->bRequest <= TUSB_REQ_SYNCH_FRAME) ? + tu_str_std_request[xfer->setup->bRequest] : "Class Request"); + TU_LOG_BUF_USBH(xfer->setup, 8); + + if (xfer->complete_cb) { + TU_ASSERT(usbh_setup_send(daddr, (uint8_t const *) &_usbh_epbuf.request)); + }else { + // blocking if complete callback is not provided + // change callback to internal blocking, and result as user argument + volatile xfer_result_t result = XFER_RESULT_INVALID; + + // use user_data to point to xfer_result_t + ctrl_info->user_data = (uintptr_t) &result; + ctrl_info->complete_cb = _control_blocking_complete_cb; + + TU_ASSERT(usbh_setup_send(daddr, (uint8_t const *) &_usbh_epbuf.request)); + + while (result == XFER_RESULT_INVALID) { + // Note: this can be called within an callback ie. part of tuh_task() + // therefore event with RTOS tuh_task() still need to be invoked + if (tuh_task_event_ready()) { + tuh_task(); + } + // TODO probably some timeout to prevent hanged + } + + // update transfer result, user_data is expected to point to xfer_result_t + if (xfer->user_data != 0) { + *((xfer_result_t*) xfer->user_data) = result; + } + xfer->result = result; + xfer->actual_len = ctrl_info->actual_len; + } + + return true; +} + +static void _control_xfer_complete(uint8_t daddr, xfer_result_t result) { + TU_LOG_USBH("\r\n"); + usbh_ctrl_xfer_info_t* ctrl_info = &_usbh_data.ctrl_xfer_info; + + // duplicate xfer since user can execute control transfer within callback + tusb_control_request_t const request = _usbh_epbuf.request; + tuh_xfer_t xfer_temp = { + .daddr = daddr, + .ep_addr = 0, + .result = result, + .setup = &request, + .actual_len = (uint32_t) ctrl_info->actual_len, + .buffer = ctrl_info->buffer, + .complete_cb = ctrl_info->complete_cb, + .user_data = ctrl_info->user_data + }; + + _control_set_xfer_stage(CONTROL_STAGE_IDLE); + + if (xfer_temp.complete_cb) { + xfer_temp.complete_cb(&xfer_temp); + } +} + +static bool usbh_control_xfer_cb (uint8_t daddr, uint8_t ep_addr, xfer_result_t result, uint32_t xferred_bytes) { + (void) ep_addr; + + const uint8_t rhport = usbh_get_rhport(daddr); + tusb_control_request_t const * request = &_usbh_epbuf.request; + usbh_ctrl_xfer_info_t* ctrl_info = &_usbh_data.ctrl_xfer_info; + + switch (result) { + case XFER_RESULT_STALLED: + TU_LOG_USBH("[%u:%u] Control STALLED, xferred_bytes = %" PRIu32 "\r\n", rhport, daddr, xferred_bytes); + TU_LOG_BUF_USBH(request, 8); + _control_xfer_complete(daddr, result); + break; + + case XFER_RESULT_FAILED: + if (tuh_connected(daddr) && ctrl_info->failed_count < USBH_CONTROL_RETRY_MAX) { + TU_LOG_USBH("[%u:%u] Control FAILED %u/%u, retrying\r\n", rhport, daddr, ctrl_info->failed_count+1, USBH_CONTROL_RETRY_MAX); + (void) osal_mutex_lock(_usbh_mutex, OSAL_TIMEOUT_WAIT_FOREVER); + ctrl_info->stage = CONTROL_STAGE_SETUP; + ctrl_info->failed_count++; + ctrl_info->actual_len = 0; // reset actual_len + (void) osal_mutex_unlock(_usbh_mutex); + + TU_ASSERT(usbh_setup_send(daddr, (uint8_t const *) request)); + } else { + TU_LOG_USBH("[%u:%u] Control FAILED, xferred_bytes = %" PRIu32 "\r\n", rhport, daddr, xferred_bytes); + TU_LOG_BUF_USBH(request, 8); + _control_xfer_complete(daddr, result); + } + break; + + case XFER_RESULT_SUCCESS: + switch(ctrl_info->stage) { + case CONTROL_STAGE_SETUP: + if (request->wLength) { + // DATA stage: initial data toggle is always 1 + _control_set_xfer_stage(CONTROL_STAGE_DATA); + const uint8_t ep_data = tu_edpt_addr(0, request->bmRequestType_bit.direction); + TU_ASSERT(hcd_edpt_xfer(rhport, daddr, ep_data, ctrl_info->buffer, request->wLength)); + return true; + } + TU_ATTR_FALLTHROUGH; + + case CONTROL_STAGE_DATA: { + if (request->wLength) { + TU_LOG_USBH("[%u:%u] Control data:\r\n", rhport, daddr); + TU_LOG_MEM_USBH(ctrl_info->buffer, xferred_bytes, 2); + } + ctrl_info->actual_len = (uint16_t) xferred_bytes; + + // ACK stage: toggle is always 1 + _control_set_xfer_stage(CONTROL_STAGE_ACK); + const uint8_t ep_status = tu_edpt_addr(0, 1 - request->bmRequestType_bit.direction); + TU_ASSERT(hcd_edpt_xfer(rhport, daddr, ep_status, NULL, 0)); + break; + } + + case CONTROL_STAGE_ACK: { + // Abort all pending transfers if SET_CONFIGURATION request + // NOTE: should we force closing all non-control endpoints in the future? + if (request->bRequest == TUSB_REQ_SET_CONFIGURATION && request->bmRequestType == 0x00) { + for(uint8_t epnum=1; epnumdaddr; + uint8_t const ep_addr = xfer->ep_addr; + + TU_VERIFY(daddr && ep_addr); + TU_VERIFY(usbh_edpt_claim(daddr, ep_addr)); + + if (!usbh_edpt_xfer_with_callback(daddr, ep_addr, xfer->buffer, (uint16_t) xfer->buflen, + xfer->complete_cb, xfer->user_data)) { + usbh_edpt_release(daddr, ep_addr); + return false; + } + + return true; +} + +bool tuh_edpt_abort_xfer(uint8_t daddr, uint8_t ep_addr) { + TU_LOG_USBH("[%u] Aborted transfer on EP %02X\r\n", daddr, ep_addr); + const uint8_t epnum = tu_edpt_number(ep_addr); + const uint8_t dir = tu_edpt_dir(ep_addr); + + if (epnum == 0) { + // Also include dev0 for aborting enumerating + const uint8_t rhport = usbh_get_rhport(daddr); + + // control transfer: only 1 control at a time, check if we are aborting the current one + const usbh_ctrl_xfer_info_t* ctrl_info = &_usbh_data.ctrl_xfer_info; + TU_VERIFY(daddr == ctrl_info->daddr && ctrl_info->stage != CONTROL_STAGE_IDLE); + hcd_edpt_abort_xfer(rhport, daddr, ep_addr); + _control_set_xfer_stage(CONTROL_STAGE_IDLE); // reset control transfer state to idle + } else { + usbh_device_t* dev = get_device(daddr); + TU_VERIFY(dev); + + TU_VERIFY(dev->ep_status[epnum][dir].busy); // non-control skip if not busy + // abort then mark as ready and release endpoint + hcd_edpt_abort_xfer(dev->bus_info.rhport, daddr, ep_addr); + dev->ep_status[epnum][dir].busy = false; + tu_edpt_release(&dev->ep_status[epnum][dir], _usbh_mutex); + } + + return true; +} + +//--------------------------------------------------------------------+ +// USBH API For Class Driver +//--------------------------------------------------------------------+ + +uint8_t usbh_get_rhport(uint8_t daddr) { + tuh_bus_info_t bus_info; + tuh_bus_info_get(daddr, &bus_info); + return bus_info.rhport; +} + +uint8_t *usbh_get_enum_buf(void) { + return _usbh_epbuf.ctrl; +} + +void usbh_int_set(bool enabled) { + // TODO all host controller if multiple are used since they shared the same event queue + if (enabled) { + hcd_int_enable(_usbh_data.controller_id); + } else { + hcd_int_disable(_usbh_data.controller_id); + } +} + +void usbh_spin_lock(bool in_isr) { + osal_spin_lock(&_usbh_spin, in_isr); +} + +void usbh_spin_unlock(bool in_isr) { + osal_spin_unlock(&_usbh_spin, in_isr); +} + +void usbh_defer_func(osal_task_func_t func, void *param, bool in_isr) { + hcd_event_t event = { 0 }; + event.event_id = USBH_EVENT_FUNC_CALL; + event.func_call.func = func; + event.func_call.param = param; + queue_event(&event, in_isr); +} + +//--------------------------------------------------------------------+ +// Endpoint API +//--------------------------------------------------------------------+ + +// Claim an endpoint for transfer +bool usbh_edpt_claim(uint8_t dev_addr, uint8_t ep_addr) { + // Note: addr0 only use tuh_control_xfer + usbh_device_t* dev = get_device(dev_addr); + TU_ASSERT(dev && dev->connected); + + uint8_t const epnum = tu_edpt_number(ep_addr); + uint8_t const dir = tu_edpt_dir(ep_addr); + + TU_VERIFY(tu_edpt_claim(&dev->ep_status[epnum][dir], _usbh_mutex)); + TU_LOG_USBH("[%u] Claimed EP 0x%02x\r\n", dev_addr, ep_addr); + + return true; +} + +// Release an claimed endpoint due to failed transfer attempt +bool usbh_edpt_release(uint8_t dev_addr, uint8_t ep_addr) { + // Note: addr0 only use tuh_control_xfer + usbh_device_t* dev = get_device(dev_addr); + TU_VERIFY(dev && dev->connected); + + uint8_t const epnum = tu_edpt_number(ep_addr); + uint8_t const dir = tu_edpt_dir(ep_addr); + + TU_VERIFY(tu_edpt_release(&dev->ep_status[epnum][dir], _usbh_mutex)); + TU_LOG_USBH("[%u] Released EP 0x%02x\r\n", dev_addr, ep_addr); + + return true; +} + +// Submit an transfer +bool usbh_edpt_xfer_with_callback(uint8_t dev_addr, uint8_t ep_addr, uint8_t* buffer, uint16_t total_bytes, + tuh_xfer_cb_t complete_cb, uintptr_t user_data) { + (void) complete_cb; + (void) user_data; + + usbh_device_t* dev = get_device(dev_addr); + TU_VERIFY(dev); + + uint8_t const epnum = tu_edpt_number(ep_addr); + uint8_t const dir = tu_edpt_dir(ep_addr); + tu_edpt_state_t* ep_state = &dev->ep_status[epnum][dir]; + + TU_LOG_USBH(" Queue EP %02X with %u bytes ... \r\n", ep_addr, total_bytes); + + // Attempt to transfer on a busy endpoint, sound like an race condition ! + TU_ASSERT(ep_state->busy == 0); + + // Set busy first since the actual transfer can be complete before hcd_edpt_xfer() + // could return and USBH task can preempt and clear the busy + ep_state->busy = 1; + +#if CFG_TUH_API_EDPT_XFER + dev->ep_callback[epnum][dir].complete_cb = complete_cb; + dev->ep_callback[epnum][dir].user_data = user_data; +#endif + + if (hcd_edpt_xfer(dev->bus_info.rhport, dev_addr, ep_addr, buffer, total_bytes)) { + TU_LOG_USBH("OK\r\n"); + return true; + } else { + // HCD error, mark endpoint as ready to allow next transfer + ep_state->busy = 0; + ep_state->claimed = 0; + TU_LOG1("Failed\r\n"); +// TU_BREAKPOINT(); + return false; + } +} + +static bool usbh_edpt_control_open(uint8_t dev_addr, uint8_t max_packet_size) { + TU_LOG_USBH("[%u:%u] Open EP0 with Size = %u\r\n", usbh_get_rhport(dev_addr), dev_addr, max_packet_size); + tusb_desc_endpoint_t ep0_desc = { + .bLength = sizeof(tusb_desc_endpoint_t), + .bDescriptorType = TUSB_DESC_ENDPOINT, + .bEndpointAddress = 0, + .bmAttributes = { .xfer = TUSB_XFER_CONTROL }, + .wMaxPacketSize = max_packet_size, + .bInterval = 0 + }; + + return hcd_edpt_open(usbh_get_rhport(dev_addr), dev_addr, &ep0_desc); +} + +bool tuh_edpt_open(uint8_t dev_addr, tusb_desc_endpoint_t const* desc_ep) { + TU_ASSERT(tu_edpt_validate(desc_ep, tuh_speed_get(dev_addr), true)); + return hcd_edpt_open(usbh_get_rhport(dev_addr), dev_addr, desc_ep); +} + +bool tuh_edpt_close(uint8_t daddr, uint8_t ep_addr) { + TU_VERIFY(0 != tu_edpt_number(ep_addr)); // cannot close EP0 + tuh_edpt_abort_xfer(daddr, ep_addr); // abort any pending transfer + return hcd_edpt_close(usbh_get_rhport(daddr), daddr, ep_addr); +} + +bool usbh_edpt_busy(uint8_t dev_addr, uint8_t ep_addr) { + usbh_device_t* dev = get_device(dev_addr); + TU_VERIFY(dev); + + uint8_t const epnum = tu_edpt_number(ep_addr); + uint8_t const dir = tu_edpt_dir(ep_addr); + + return dev->ep_status[epnum][dir].busy; +} + +//--------------------------------------------------------------------+ +// HCD Event Handler +//--------------------------------------------------------------------+ + +bool tuh_bus_info_get(uint8_t daddr, tuh_bus_info_t* bus_info) { + usbh_device_t const* dev = get_device(daddr); + if (dev) { + *bus_info = dev->bus_info; + } else { + *bus_info = _usbh_data.dev0_bus; + } + return true; +} + +TU_ATTR_FAST_FUNC void hcd_event_handler(hcd_event_t const* event, bool in_isr) { + switch (event->event_id) { + case HCD_EVENT_DEVICE_ATTACH: + case HCD_EVENT_DEVICE_REMOVE: + // Attach debouncing on roothub: skip attach/remove while debouncing delay + if (event->connection.hub_addr == 0) { + if (tu_bit_test(_usbh_data.attach_debouncing_bm, event->rhport)) { + return; + } + + if (event->event_id == HCD_EVENT_DEVICE_ATTACH) { + // No debouncing, set flag if attach event + _usbh_data.attach_debouncing_bm |= TU_BIT(event->rhport); + } + } + break; + + default: break; + } + + queue_event(event, in_isr); +} + +//--------------------------------------------------------------------+ +// Descriptors Async +//--------------------------------------------------------------------+ + +// generic helper to get a descriptor +// if blocking, user_data is pointed to xfer_result +TU_ATTR_ALWAYS_INLINE static inline +bool _get_descriptor(uint8_t daddr, uint8_t type, uint8_t index, uint16_t language_id, void* buffer, uint16_t len, + tuh_xfer_cb_t complete_cb, uintptr_t user_data) { + tusb_control_request_t const request = { + .bmRequestType_bit = { + .recipient = TUSB_REQ_RCPT_DEVICE, + .type = TUSB_REQ_TYPE_STANDARD, + .direction = TUSB_DIR_IN + }, + .bRequest = TUSB_REQ_GET_DESCRIPTOR, + .wValue = tu_htole16( TU_U16(type, index) ), + .wIndex = tu_htole16(language_id), + .wLength = tu_htole16(len) + }; + tuh_xfer_t xfer = { + .daddr = daddr, + .ep_addr = 0, + .setup = &request, + .buffer = buffer, + .complete_cb = complete_cb, + .user_data = user_data + }; + + return tuh_control_xfer(&xfer); +} + +bool tuh_descriptor_get(uint8_t daddr, uint8_t type, uint8_t index, void* buffer, uint16_t len, + tuh_xfer_cb_t complete_cb, uintptr_t user_data) { + return _get_descriptor(daddr, type, index, 0x0000, buffer, len, complete_cb, user_data); +} + +bool tuh_descriptor_get_device(uint8_t daddr, void* buffer, uint16_t len, + tuh_xfer_cb_t complete_cb, uintptr_t user_data) { + len = tu_min16(len, sizeof(tusb_desc_device_t)); + return tuh_descriptor_get(daddr, TUSB_DESC_DEVICE, 0, buffer, len, complete_cb, user_data); +} + +bool tuh_descriptor_get_configuration(uint8_t daddr, uint8_t index, void* buffer, uint16_t len, + tuh_xfer_cb_t complete_cb, uintptr_t user_data) { + return tuh_descriptor_get(daddr, TUSB_DESC_CONFIGURATION, index, buffer, len, complete_cb, user_data); +} + +//------------- String Descriptor -------------// +bool tuh_descriptor_get_string(uint8_t daddr, uint8_t index, uint16_t language_id, void* buffer, uint16_t len, + tuh_xfer_cb_t complete_cb, uintptr_t user_data) { + return _get_descriptor(daddr, TUSB_DESC_STRING, index, language_id, buffer, len, complete_cb, user_data); +} + +// Get manufacturer string descriptor +bool tuh_descriptor_get_manufacturer_string(uint8_t daddr, uint16_t language_id, void* buffer, uint16_t len, + tuh_xfer_cb_t complete_cb, uintptr_t user_data) +{ + usbh_device_t const* dev = get_device(daddr); + TU_VERIFY(dev && dev->iManufacturer); + return tuh_descriptor_get_string(daddr, dev->iManufacturer, language_id, buffer, len, complete_cb, user_data); +} + +// Get product string descriptor +bool tuh_descriptor_get_product_string(uint8_t daddr, uint16_t language_id, void* buffer, uint16_t len, + tuh_xfer_cb_t complete_cb, uintptr_t user_data) { + usbh_device_t const* dev = get_device(daddr); + TU_VERIFY(dev && dev->iProduct); + return tuh_descriptor_get_string(daddr, dev->iProduct, language_id, buffer, len, complete_cb, user_data); +} + +// Get serial string descriptor +bool tuh_descriptor_get_serial_string(uint8_t daddr, uint16_t language_id, void* buffer, uint16_t len, + tuh_xfer_cb_t complete_cb, uintptr_t user_data) { + usbh_device_t const* dev = get_device(daddr); + TU_VERIFY(dev && dev->iSerialNumber); + return tuh_descriptor_get_string(daddr, dev->iSerialNumber, language_id, buffer, len, complete_cb, user_data); +} + +// Get HID report descriptor +// if blocking, user_data is pointed to xfer_result +bool tuh_descriptor_get_hid_report(uint8_t daddr, uint8_t itf_num, uint8_t desc_type, uint8_t index, void* buffer, uint16_t len, + tuh_xfer_cb_t complete_cb, uintptr_t user_data) { + TU_LOG_USBH("HID Get Report Descriptor\r\n"); + tusb_control_request_t const request = { + .bmRequestType_bit = { + .recipient = TUSB_REQ_RCPT_INTERFACE, + .type = TUSB_REQ_TYPE_STANDARD, + .direction = TUSB_DIR_IN + }, + .bRequest = TUSB_REQ_GET_DESCRIPTOR, + .wValue = tu_htole16(TU_U16(desc_type, index)), + .wIndex = tu_htole16((uint16_t) itf_num), + .wLength = len + }; + tuh_xfer_t xfer = { + .daddr = daddr, + .ep_addr = 0, + .setup = &request, + .buffer = buffer, + .complete_cb = complete_cb, + .user_data = user_data + }; + + return tuh_control_xfer(&xfer); +} + +bool tuh_address_set(uint8_t daddr, uint8_t new_addr, + tuh_xfer_cb_t complete_cb, uintptr_t user_data) { + TU_LOG_USBH("Set Address = %d\r\n", new_addr); + const tusb_control_request_t request = { + .bmRequestType_bit = { + .recipient = TUSB_REQ_RCPT_DEVICE, + .type = TUSB_REQ_TYPE_STANDARD, + .direction = TUSB_DIR_OUT + }, + .bRequest = TUSB_REQ_SET_ADDRESS, + .wValue = tu_htole16(new_addr), + .wIndex = 0, + .wLength = 0 + }; + tuh_xfer_t xfer = { + .daddr = daddr, + .ep_addr = 0, + .setup = &request, + .buffer = NULL, + .complete_cb = complete_cb, + .user_data = user_data + }; + + TU_ASSERT(tuh_control_xfer(&xfer)); + return true; +} + +bool tuh_configuration_set(uint8_t daddr, uint8_t config_num, + tuh_xfer_cb_t complete_cb, uintptr_t user_data) { + TU_LOG_USBH("Set Configuration = %d\r\n", config_num); + tusb_control_request_t const request = { + .bmRequestType_bit = { + .recipient = TUSB_REQ_RCPT_DEVICE, + .type = TUSB_REQ_TYPE_STANDARD, + .direction = TUSB_DIR_OUT + }, + .bRequest = TUSB_REQ_SET_CONFIGURATION, + .wValue = tu_htole16(config_num), + .wIndex = 0, + .wLength = 0 + }; + tuh_xfer_t xfer = { + .daddr = daddr, + .ep_addr = 0, + .setup = &request, + .buffer = NULL, + .complete_cb = complete_cb, + .user_data = user_data + }; + + return tuh_control_xfer(&xfer); +} + +bool tuh_interface_set(uint8_t daddr, uint8_t itf_num, uint8_t itf_alt, + tuh_xfer_cb_t complete_cb, uintptr_t user_data) { + TU_LOG_USBH("Set Interface %u Alternate %u\r\n", itf_num, itf_alt); + tusb_control_request_t const request = { + .bmRequestType_bit = { + .recipient = TUSB_REQ_RCPT_INTERFACE, + .type = TUSB_REQ_TYPE_STANDARD, + .direction = TUSB_DIR_OUT + }, + .bRequest = TUSB_REQ_SET_INTERFACE, + .wValue = tu_htole16(itf_alt), + .wIndex = tu_htole16(itf_num), + .wLength = 0 + }; + tuh_xfer_t xfer = { + .daddr = daddr, + .ep_addr = 0, + .setup = &request, + .buffer = NULL, + .complete_cb = complete_cb, + .user_data = user_data + }; + + return tuh_control_xfer(&xfer); +} + +//--------------------------------------------------------------------+ +// Detaching +//--------------------------------------------------------------------+ +// a device unplugged from rhport:hub_addr:hub_port +static void process_removed_device(uint8_t rhport, uint8_t hub_addr, uint8_t hub_port) { + // Find the all devices (star-network) under port that is unplugged + #if CFG_TUH_HUB + uint8_t removing_hubs[CFG_TUH_HUB] = { 0 }; + #endif + + do { + for (uint8_t dev_id = 0; dev_id < TOTAL_DEVICES; dev_id++) { + usbh_device_t* dev = &_usbh_devices[dev_id]; + uint8_t const daddr = dev_id + 1; + + // hub_addr = 0 means roothub, hub_port = 0 means all devices of downstream hub + if (dev->bus_info.rhport == rhport && dev->connected && + (hub_addr == 0 || dev->bus_info.hub_addr == hub_addr) && + (hub_port == 0 || dev->bus_info.hub_port == hub_port)) { + TU_LOG_USBH("[%u:%u:%u] unplugged address = %u\r\n", rhport, hub_addr, hub_port, daddr); + + #if CFG_TUH_HUB + if (is_hub_addr(daddr)) { + TU_LOG_USBH(" is a HUB device %u\r\n", daddr); + removing_hubs[dev_id - CFG_TUH_DEVICE_MAX] = 1; + } else + #endif + { + // Invoke callback before closing driver (maybe call it later ?) + tuh_umount_cb(daddr); + } + + // Close class driver + for (uint8_t drv_id = 0; drv_id < TOTAL_DRIVER_COUNT; drv_id++) { + usbh_class_driver_t const* driver = get_driver(drv_id); + if (driver) { + driver->close(daddr); + } + } + + usbh_device_close(rhport, daddr); + clear_device(dev); + } + } + +#if CFG_TUH_HUB + // if a hub is removed, we need to remove all of its downstream devices + if (tu_mem_is_zero(removing_hubs, CFG_TUH_HUB)) { + break; + } + + // find a marked hub to process + for (uint8_t h_id = 0; h_id < CFG_TUH_HUB; h_id++) { + if (removing_hubs[h_id]) { + removing_hubs[h_id] = 0; + + // update hub_addr and hub_port for next loop + hub_addr = h_id + 1 + CFG_TUH_DEVICE_MAX; + hub_port = 0; + break; + } + } +#else + break; +#endif + + } while(1); +} + +//--------------------------------------------------------------------+ +// Enumeration Process +// is a lengthy process with a series of control transfer to configure newly attached device. +// NOTE: due to the shared control buffer, we must complete enumerating +// one device before enumerating another one. +//--------------------------------------------------------------------+ +enum { // USB 2.0 specs 7.1.7 for timing + ENUM_DEBOUNCING_DELAY_MS = 150, // T(ATTDB) minimum 100 ms for stable connection + ENUM_RESET_ROOT_DELAY_MS = 50, // T(DRSTr) minimum 50 ms for reset from root port + ENUM_RESET_HUB_DELAY_MS = 20, // T(DRST) 10-20 ms for hub reset + ENUM_RESET_RECOVERY_DELAY_MS = 10, // T(RSTRCY) minimum 10 ms for reset recovery + ENUM_SET_ADDRESS_RECOVERY_DELAY_MS = 2, // USB 2.0 Spec 9.2.6.3 min is 2 ms +}; + +enum { + ENUM_IDLE, + ENUM_HUB_RERSET, + ENUM_HUB_GET_STATUS_AFTER_RESET, + ENUM_HUB_CLEAR_RESET, + ENUM_HUB_CLEAR_RESET_COMPLETE, + + ENUM_ADDR0_DEVICE_DESC, + ENUM_SET_ADDR, + ENUM_GET_DEVICE_DESC, + ENUM_GET_STRING_LANGUAGE_ID_LEN, + ENUM_GET_STRING_LANGUAGE_ID, + ENUM_GET_STRING_MANUFACTURER_LEN, + ENUM_GET_STRING_MANUFACTURER, + ENUM_GET_STRING_PRODUCT_LEN, + ENUM_GET_STRING_PRODUCT, + ENUM_GET_STRING_SERIAL_LEN, + ENUM_GET_STRING_SERIAL, + ENUM_GET_9BYTE_CONFIG_DESC, + ENUM_GET_FULL_CONFIG_DESC, + ENUM_SET_CONFIG, + ENUM_CONFIG_DRIVER +}; + +static uint8_t enum_get_new_address(bool is_hub); +static bool enum_parse_configuration_desc (uint8_t dev_addr, tusb_desc_configuration_t const* desc_cfg); +static void enum_full_complete(void); +static void process_enumeration(tuh_xfer_t* xfer); + +// start a new enumeration process +static bool enum_new_device(hcd_event_t* event) { + tuh_bus_info_t* dev0_bus = &_usbh_data.dev0_bus; + dev0_bus->rhport = event->rhport; + dev0_bus->hub_addr = event->connection.hub_addr; + dev0_bus->hub_port = event->connection.hub_port; + + // wait until device connection is stable TODO non blocking + tusb_time_delay_ms_api(ENUM_DEBOUNCING_DELAY_MS); + + // clear roothub debouncing delay + if (dev0_bus->hub_addr == 0) { + _usbh_data.attach_debouncing_bm &= (uint8_t) ~TU_BIT(dev0_bus->rhport); + } + + if (dev0_bus->hub_addr == 0) { + // connected directly to roothub + // USB bus not active and frame number is not available yet. + // need to depend on tusb_time_millis_api() TODO non blocking + + if (!hcd_port_connect_status(dev0_bus->rhport)) { + TU_LOG_USBH("Device unplugged while debouncing\r\n"); + enum_full_complete(); + return true; + } + + // reset device + hcd_port_reset(dev0_bus->rhport); + tusb_time_delay_ms_api(ENUM_RESET_ROOT_DELAY_MS); + hcd_port_reset_end(dev0_bus->rhport); + + if (!hcd_port_connect_status(dev0_bus->rhport)) { + // device unplugged while delaying + enum_full_complete(); + return true; + } + + dev0_bus->speed = hcd_port_speed_get(dev0_bus->rhport); + TU_LOG_USBH("%s Speed\r\n", tu_str_speed[dev0_bus->speed]); + + // fake transfer to kick-off the enumeration process + tuh_xfer_t xfer; + xfer.daddr = 0; + xfer.result = XFER_RESULT_SUCCESS; + xfer.user_data = ENUM_ADDR0_DEVICE_DESC; + process_enumeration(&xfer); + } + #if CFG_TUH_HUB + else { + // connected via hub + TU_VERIFY(dev0_bus->hub_port != 0); + TU_ASSERT(hub_port_get_status(dev0_bus->hub_addr, dev0_bus->hub_port, NULL, + process_enumeration, ENUM_HUB_RERSET)); + } + #endif // hub + + return true; +} + +// process device enumeration +static void process_enumeration(tuh_xfer_t* xfer) { + // Retry a few times while enumerating since device can be unstable when starting up + static uint8_t failed_count = 0; + if (XFER_RESULT_FAILED == xfer->result) { + enum { + ATTEMPT_COUNT_MAX = 3, + ATTEMPT_DELAY_MS = 100 + }; + + // retry if not reaching max attempt + failed_count++; + bool retry = (_usbh_data.enumerating_daddr != TUSB_INDEX_INVALID_8) && (failed_count < ATTEMPT_COUNT_MAX); + if (retry) { + tusb_time_delay_ms_api(ATTEMPT_DELAY_MS); // delay a bit + TU_LOG_USBH("Enumeration attempt %u/%u\r\n", failed_count+1, ATTEMPT_COUNT_MAX); + retry = tuh_control_xfer(xfer); + } + + if (!retry) { + enum_full_complete(); // complete as failed + } + return; + } + failed_count = 0; + + uint8_t const daddr = xfer->daddr; + uintptr_t const state = xfer->user_data; + usbh_device_t* dev = get_device(daddr); + tuh_bus_info_t* dev0_bus = &_usbh_data.dev0_bus; + if (daddr > 0) { + TU_ASSERT(dev,); + } + uint16_t langid = 0x0409; // default is English + + switch (state) { + #if CFG_TUH_HUB + case ENUM_HUB_RERSET: { + hub_port_status_response_t port_status; + hub_port_get_status_local(dev0_bus->hub_addr, dev0_bus->hub_port, &port_status); + + if (!port_status.status.connection) { + TU_LOG_USBH("Device unplugged from hub while debouncing\r\n"); + enum_full_complete(); + return; + } + + TU_ASSERT(hub_port_reset(dev0_bus->hub_addr, dev0_bus->hub_port, process_enumeration, ENUM_HUB_GET_STATUS_AFTER_RESET),); + break; + } + + case ENUM_HUB_GET_STATUS_AFTER_RESET: { + tusb_time_delay_ms_api(ENUM_RESET_HUB_DELAY_MS); // wait for reset to take effect + + // get status to check for reset change + TU_ASSERT(hub_port_get_status(dev0_bus->hub_addr, dev0_bus->hub_port, NULL, process_enumeration, ENUM_HUB_CLEAR_RESET),); + break; + } + + case ENUM_HUB_CLEAR_RESET: { + hub_port_status_response_t port_status; + hub_port_get_status_local(dev0_bus->hub_addr, dev0_bus->hub_port, &port_status); + + if (port_status.change.reset) { + // Acknowledge Port Reset Change + TU_ASSERT(hub_port_clear_reset_change(dev0_bus->hub_addr, dev0_bus->hub_port, process_enumeration, ENUM_HUB_CLEAR_RESET_COMPLETE),); + } else { + // maybe retry if reset change not set but we need timeout to prevent infinite loop + // TU_ASSERT(hub_port_get_status(dev0_bus->hub_addr, dev0_bus->hub_port, NULL, process_enumeration, ENUM_HUB_CLEAR_RESET_COMPLETE),); + } + + break; + } + + case ENUM_HUB_CLEAR_RESET_COMPLETE: { + hub_port_status_response_t port_status; + hub_port_get_status_local(dev0_bus->hub_addr, dev0_bus->hub_port, &port_status); + + if (!port_status.status.connection) { + TU_LOG_USBH("Device unplugged from hub (not addressed yet)\r\n"); + enum_full_complete(); + return; + } + + dev0_bus->speed = (port_status.status.high_speed) ? TUSB_SPEED_HIGH : + (port_status.status.low_speed) ? TUSB_SPEED_LOW : TUSB_SPEED_FULL; + + TU_ATTR_FALLTHROUGH; + } + #endif + + case ENUM_ADDR0_DEVICE_DESC: { + tusb_time_delay_ms_api(ENUM_RESET_RECOVERY_DELAY_MS); // reset recovery + + // TODO probably doesn't need to open/close each enumeration + uint8_t const addr0 = 0; + TU_ASSERT(usbh_edpt_control_open(addr0, 8),); + + // Get first 8 bytes of device descriptor for control endpoint size + TU_LOG_USBH("Get 8 byte of Device Descriptor\r\n"); + TU_ASSERT(tuh_descriptor_get_device(addr0, _usbh_epbuf.ctrl, 8, + process_enumeration, ENUM_SET_ADDR),); + break; + } + + case ENUM_SET_ADDR: { + // Due to physical debouncing, some devices can cause multiple attaches (actually reset) without detach event + // Force remove currently mounted with the same bus info (rhport, hub addr, hub port) if exists + process_removed_device(dev0_bus->rhport, dev0_bus->hub_addr, dev0_bus->hub_port); + + const tusb_desc_device_t *desc_device = (const tusb_desc_device_t *) _usbh_epbuf.ctrl; + const uint8_t new_addr = enum_get_new_address(desc_device->bDeviceClass == TUSB_CLASS_HUB); + TU_ASSERT(new_addr != 0,); + + usbh_device_t* new_dev = get_device(new_addr); + new_dev->bus_info = *dev0_bus; + new_dev->connected = 1; + new_dev->bMaxPacketSize0 = desc_device->bMaxPacketSize0; + + TU_ASSERT(tuh_address_set(0, new_addr, process_enumeration, ENUM_GET_DEVICE_DESC),); + break; + } + + case ENUM_GET_DEVICE_DESC: { + tusb_time_delay_ms_api(ENUM_SET_ADDRESS_RECOVERY_DELAY_MS); // set address recovery + + const uint8_t new_addr = (uint8_t) tu_le16toh(xfer->setup->wValue); + usbh_device_t* new_dev = get_device(new_addr); + TU_ASSERT(new_dev,); + new_dev->addressed = 1; + _usbh_data.enumerating_daddr = new_addr; + + usbh_device_close(dev0_bus->rhport, 0); // close dev0 + + TU_ASSERT(usbh_edpt_control_open(new_addr, new_dev->bMaxPacketSize0),); // open new control endpoint + + TU_LOG_USBH("Get Device Descriptor\r\n"); + TU_ASSERT(tuh_descriptor_get_device(new_addr, _usbh_epbuf.ctrl, sizeof(tusb_desc_device_t), + process_enumeration, ENUM_GET_STRING_LANGUAGE_ID_LEN),); + break; + } + + // For string descriptor (langid, manufacturer, product, serila): always get the first 2 bytes + // to determine the length first. otherwise, some device may have buffer overflow. + case ENUM_GET_STRING_LANGUAGE_ID_LEN: { + // save the received device descriptor + tusb_desc_device_t const *desc_device = (tusb_desc_device_t const *) _usbh_epbuf.ctrl; + dev->bcdUSB = desc_device->bcdUSB; + dev->bDeviceClass = desc_device->bDeviceClass; + dev->bDeviceSubClass = desc_device->bDeviceSubClass; + dev->bDeviceProtocol = desc_device->bDeviceProtocol; + dev->bMaxPacketSize0 = desc_device->bMaxPacketSize0; + dev->idVendor = desc_device->idVendor; + dev->idProduct = desc_device->idProduct; + dev->bcdDevice = desc_device->bcdDevice; + dev->iManufacturer = desc_device->iManufacturer; + dev->iProduct = desc_device->iProduct; + dev->iSerialNumber = desc_device->iSerialNumber; + dev->bNumConfigurations = desc_device->bNumConfigurations; + + tuh_enum_descriptor_device_cb(daddr, desc_device); // callback + tuh_descriptor_get_string_langid(daddr, _usbh_epbuf.ctrl, 2, + process_enumeration, ENUM_GET_STRING_LANGUAGE_ID); + break; + } + + case ENUM_GET_STRING_LANGUAGE_ID: { + const uint8_t str_len = xfer->buffer[0]; + tuh_descriptor_get_string_langid(daddr, _usbh_epbuf.ctrl, str_len, + process_enumeration, ENUM_GET_STRING_MANUFACTURER_LEN); + break; + } + + case ENUM_GET_STRING_MANUFACTURER_LEN: { + const tusb_desc_string_t* desc_langid = (const tusb_desc_string_t *) _usbh_epbuf.ctrl; + if (desc_langid->bLength >= 4) { + langid = tu_le16toh(desc_langid->utf16le[0]); // previous request is langid + } + if (dev->iManufacturer != 0) { + tuh_descriptor_get_string(daddr, dev->iManufacturer, langid, _usbh_epbuf.ctrl, 2, + process_enumeration, ENUM_GET_STRING_MANUFACTURER); + break; + }else { + TU_ATTR_FALLTHROUGH; + } + } + + case ENUM_GET_STRING_MANUFACTURER: { + if (dev->iManufacturer != 0) { + langid = tu_le16toh(xfer->setup->wIndex); // langid from length's request + const uint8_t str_len = xfer->buffer[0]; + tuh_descriptor_get_string(daddr, dev->iManufacturer, langid, _usbh_epbuf.ctrl, str_len, + process_enumeration, ENUM_GET_STRING_PRODUCT_LEN); + break; + } else { + TU_ATTR_FALLTHROUGH; + } + } + + case ENUM_GET_STRING_PRODUCT_LEN: + if (dev->iProduct != 0) { + if (state == ENUM_GET_STRING_PRODUCT_LEN) { + langid = tu_le16toh(xfer->setup->wIndex); // get langid from previous setup packet if not fall through + } + tuh_descriptor_get_string(daddr, dev->iProduct, langid, _usbh_epbuf.ctrl, 2, + process_enumeration, ENUM_GET_STRING_PRODUCT); + break; + } else { + TU_ATTR_FALLTHROUGH; + } + + case ENUM_GET_STRING_PRODUCT: { + if (dev->iProduct != 0) { + langid = tu_le16toh(xfer->setup->wIndex); // langid from length's request + const uint8_t str_len = xfer->buffer[0]; + tuh_descriptor_get_string(daddr, dev->iProduct, langid, _usbh_epbuf.ctrl, str_len, + process_enumeration, ENUM_GET_STRING_SERIAL_LEN); + break; + } else { + TU_ATTR_FALLTHROUGH; + } + } + + case ENUM_GET_STRING_SERIAL_LEN: + if (dev->iSerialNumber != 0) { + if (state == ENUM_GET_STRING_SERIAL_LEN) { + langid = tu_le16toh(xfer->setup->wIndex); // get langid from previous setup packet if not fall through + } + tuh_descriptor_get_string(daddr, dev->iSerialNumber, langid, _usbh_epbuf.ctrl, 2, + process_enumeration, ENUM_GET_STRING_SERIAL); + break; + } else { + TU_ATTR_FALLTHROUGH; + } + + case ENUM_GET_STRING_SERIAL: { + if (dev->iSerialNumber != 0) { + langid = tu_le16toh(xfer->setup->wIndex); // langid from length's request + const uint8_t str_len = xfer->buffer[0]; + tuh_descriptor_get_string(daddr, dev->iSerialNumber, langid, _usbh_epbuf.ctrl, str_len, + process_enumeration, ENUM_GET_9BYTE_CONFIG_DESC); + break; + } else { + TU_ATTR_FALLTHROUGH; + } + } + + case ENUM_GET_9BYTE_CONFIG_DESC: { + // Get 9-byte for total length + uint8_t const config_idx = 0; + TU_LOG_USBH("Get Configuration[%u] Descriptor (9 bytes)\r\n", config_idx); + TU_ASSERT(tuh_descriptor_get_configuration(daddr, config_idx, _usbh_epbuf.ctrl, 9, + process_enumeration, ENUM_GET_FULL_CONFIG_DESC),); + break; + } + + case ENUM_GET_FULL_CONFIG_DESC: { + uint8_t const* desc_config = _usbh_epbuf.ctrl; + + // Use offsetof to avoid pointer to the odd/misaligned address + uint16_t const total_len = tu_le16toh(tu_unaligned_read16(desc_config + offsetof(tusb_desc_configuration_t, wTotalLength))); + + // TODO not enough buffer to hold configuration descriptor + TU_ASSERT(total_len <= CFG_TUH_ENUMERATION_BUFSIZE,); + + // Get full configuration descriptor + uint8_t const config_idx = (uint8_t) tu_le16toh(xfer->setup->wIndex); + TU_LOG_USBH("Get Configuration[%u] Descriptor\r\n", config_idx); + TU_ASSERT(tuh_descriptor_get_configuration(daddr, config_idx, _usbh_epbuf.ctrl, total_len, + process_enumeration, ENUM_SET_CONFIG),); + break; + } + + case ENUM_SET_CONFIG: { + uint8_t config_idx = (uint8_t) tu_le16toh(xfer->setup->wIndex); + if (tuh_enum_descriptor_configuration_cb(daddr, config_idx, (const tusb_desc_configuration_t*) _usbh_epbuf.ctrl)) { + TU_ASSERT(tuh_configuration_set(daddr, config_idx+1, process_enumeration, ENUM_CONFIG_DRIVER),); + } else { + config_idx++; + TU_ASSERT(config_idx < dev->bNumConfigurations,); + TU_LOG_USBH("Get Configuration[%u] Descriptor (9 bytes)\r\n", config_idx); + TU_ASSERT(tuh_descriptor_get_configuration(daddr, config_idx, _usbh_epbuf.ctrl, 9, + process_enumeration, ENUM_GET_FULL_CONFIG_DESC),); + } + break; + } + + case ENUM_CONFIG_DRIVER: { + TU_LOG_USBH("Device configured\r\n"); + dev->configured = 1; + + // Parse configuration & set up drivers + // driver_open() must not make any usb transfer + TU_ASSERT(enum_parse_configuration_desc(daddr, (tusb_desc_configuration_t*) _usbh_epbuf.ctrl),); + + // Start the Set Configuration process for interfaces (itf = TUSB_INDEX_INVALID_8) + // Since driver can perform control transfer within its set_config, this is done asynchronously. + // The process continue with next interface when class driver complete its sequence with usbh_driver_set_config_complete() + // TODO use separated API instead of using TUSB_INDEX_INVALID_8 + usbh_driver_set_config_complete(daddr, TUSB_INDEX_INVALID_8); + break; + } + + default: + enum_full_complete(); // stop enumeration if unknown state + break; + } +} + +static uint8_t enum_get_new_address(bool is_hub) { + uint8_t start; + uint8_t end; + + if ( is_hub ) { + start = CFG_TUH_DEVICE_MAX; + end = start + CFG_TUH_HUB; + }else { + start = 0; + end = start + CFG_TUH_DEVICE_MAX; + } + + for (uint8_t idx = start; idx < end; idx++) { + if (!_usbh_devices[idx].connected) { + return (idx + 1); + } + } + + return 0; // invalid address +} + +static bool enum_parse_configuration_desc(uint8_t dev_addr, tusb_desc_configuration_t const* desc_cfg) { + usbh_device_t* dev = get_device(dev_addr); + uint16_t const total_len = tu_le16toh(desc_cfg->wTotalLength); + uint8_t const* desc_end = ((uint8_t const*) desc_cfg) + total_len; + uint8_t const* p_desc = tu_desc_next(desc_cfg); + + TU_LOG_USBH("Parsing Configuration descriptor (wTotalLength = %u)\r\n", total_len); + + // parse each interfaces + while( p_desc < desc_end ) { + if ( 0 == tu_desc_len(p_desc) ) { + // A zero length descriptor indicates that the device is off spec (e.g. wrong wTotalLength). + // Parsed interfaces should still be usable + TU_LOG_USBH("Encountered a zero-length descriptor after %" PRIu32 " bytes\r\n", (uint32_t)p_desc - (uint32_t)desc_cfg); + break; + } + + uint8_t assoc_itf_count = 1; + + // Class will always starts with Interface Association (if any) and then Interface descriptor + if ( TUSB_DESC_INTERFACE_ASSOCIATION == tu_desc_type(p_desc) ) { + tusb_desc_interface_assoc_t const * desc_iad = (tusb_desc_interface_assoc_t const *) p_desc; + assoc_itf_count = desc_iad->bInterfaceCount; + + p_desc = tu_desc_next(p_desc); // next to Interface + + // IAD's first interface number and class should match with opened interface + //TU_ASSERT(desc_iad->bFirstInterface == desc_itf->bInterfaceNumber && + // desc_iad->bFunctionClass == desc_itf->bInterfaceClass); + } + + TU_ASSERT( TUSB_DESC_INTERFACE == tu_desc_type(p_desc) ); + tusb_desc_interface_t const* desc_itf = (tusb_desc_interface_t const*) p_desc; + +#if CFG_TUH_MIDI + // MIDI has 2 interfaces (Audio Control v1 + MIDIStreaming) but does not have IAD + // manually force associated count = 2 + if (1 == assoc_itf_count && + TUSB_CLASS_AUDIO == desc_itf->bInterfaceClass && + AUDIO_SUBCLASS_CONTROL == desc_itf->bInterfaceSubClass && + AUDIO_FUNC_PROTOCOL_CODE_UNDEF == desc_itf->bInterfaceProtocol) { + assoc_itf_count = 2; + } +#endif + +#if CFG_TUH_CDC + // Some legacy CDC device does not use IAD but rather use device class as hint to combine 2 interfaces + // manually force associated count = 2 + if (1 == assoc_itf_count && + TUSB_CLASS_CDC == desc_itf->bInterfaceClass && + CDC_COMM_SUBCLASS_ABSTRACT_CONTROL_MODEL == desc_itf->bInterfaceSubClass) { + assoc_itf_count = 2; + } +#endif + + uint16_t const drv_len = tu_desc_get_interface_total_len(desc_itf, assoc_itf_count, (uint16_t) (desc_end-p_desc)); + TU_ASSERT(drv_len >= sizeof(tusb_desc_interface_t)); + + // Find driver for this interface + for (uint8_t drv_id = 0; drv_id < TOTAL_DRIVER_COUNT; drv_id++) { + usbh_class_driver_t const * driver = get_driver(drv_id); + if (driver && driver->open(dev->bus_info.rhport, dev_addr, desc_itf, drv_len) ) { + // open successfully + TU_LOG_USBH(" %s opened\r\n", driver->name); + + // bind (associated) interfaces to found driver + for(uint8_t i=0; ibInterfaceNumber+i; + + // Interface number must not be used already + TU_ASSERT( TUSB_INDEX_INVALID_8 == dev->itf2drv[itf_num] ); + dev->itf2drv[itf_num] = drv_id; + } + + // bind all endpoints to found driver + tu_edpt_bind_driver(dev->ep2drv, desc_itf, drv_len, drv_id); + + break; // exit driver find loop + } + + if (drv_id == TOTAL_DRIVER_COUNT - 1) { + TU_LOG_USBH("[%u:%u] Interface %u: class = %u subclass = %u protocol = %u is not supported\r\n", + dev->bus_info.rhport, dev_addr, desc_itf->bInterfaceNumber, desc_itf->bInterfaceClass, desc_itf->bInterfaceSubClass, desc_itf->bInterfaceProtocol); + } + } + + // next Interface or IAD descriptor + p_desc += drv_len; + } + + return true; +} + +void usbh_driver_set_config_complete(uint8_t dev_addr, uint8_t itf_num) { + usbh_device_t* dev = get_device(dev_addr); + + for(itf_num++; itf_num < CFG_TUH_INTERFACE_MAX; itf_num++) { + // continue with next valid interface + // IAD binding interface such as CDCs should return itf_num + 1 when complete + // with usbh_driver_set_config_complete() + uint8_t const drv_id = dev->itf2drv[itf_num]; + usbh_class_driver_t const * driver = get_driver(drv_id); + if (driver) { + TU_LOG_USBH("%s set config: itf = %u\r\n", driver->name, itf_num); + driver->set_config(dev_addr, itf_num); + break; + } + } + + // all interface are configured + if (itf_num == CFG_TUH_INTERFACE_MAX) { + enum_full_complete(); + + if (is_hub_addr(dev_addr)) { + TU_LOG_USBH("HUB address = %u is mounted\r\n", dev_addr); + }else { + // Invoke callback if available + tuh_mount_cb(dev_addr); + } + } +} + +static void enum_full_complete(void) { + // mark enumeration as complete + _usbh_data.enumerating_daddr = TUSB_INDEX_INVALID_8; + +#if CFG_TUH_HUB + if (_usbh_data.dev0_bus.hub_addr != 0) { + hub_edpt_status_xfer(_usbh_data.dev0_bus.hub_addr); // get next hub status + } +#endif + +} + +#endif diff --git a/cores/nRF5/TinyUSB/Adafruit_TinyUSB_ArduinoCore/tinyusb/src/host/usbh.h b/cores/nRF5/TinyUSB/Adafruit_TinyUSB_ArduinoCore/tinyusb/src/host/usbh.h new file mode 100644 index 0000000..8d48bf9 --- /dev/null +++ b/cores/nRF5/TinyUSB/Adafruit_TinyUSB_ArduinoCore/tinyusb/src/host/usbh.h @@ -0,0 +1,393 @@ +/* + * The MIT License (MIT) + * + * Copyright (c) 2019 Ha Thach (tinyusb.org) + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + * This file is part of the TinyUSB stack. + */ + +#ifndef _TUSB_USBH_H_ +#define _TUSB_USBH_H_ + +#ifdef __cplusplus + extern "C" { +#endif + +#include "common/tusb_common.h" + +#if CFG_TUH_MAX3421 +#include "portable/analog/max3421/hcd_max3421.h" +#endif + +//--------------------------------------------------------------------+ +// MACRO CONSTANT TYPEDEF +//--------------------------------------------------------------------+ + +// Endpoint Bulk size depending on host mx speed +#define TUH_EPSIZE_BULK_MPS (TUH_OPT_HIGH_SPEED ? TUSB_EPSIZE_BULK_HS : TUSB_EPSIZE_BULK_FS) + +// forward declaration +struct tuh_xfer_s; +typedef struct tuh_xfer_s tuh_xfer_t; +typedef void (*tuh_xfer_cb_t)(tuh_xfer_t* xfer); + +// Note1: layout and order of this will be changed in near future +// it is advised to initialize it using member name +// Note2: not all field is available/meaningful in callback, +// some info is not saved by usbh to save SRAM +struct tuh_xfer_s { + uint8_t daddr; + uint8_t ep_addr; + uint8_t TU_RESERVED; // reserved + xfer_result_t result; + + uint32_t actual_len; // excluding setup packet + + union { + tusb_control_request_t const* setup; // setup packet pointer if control transfer + uint32_t buflen; // expected length if not control transfer (not available in callback) + }; + + uint8_t* buffer; // not available in callback if not control transfer + tuh_xfer_cb_t complete_cb; + uintptr_t user_data; + + // uint32_t timeout_ms; // place holder, not supported yet +}; + +// Subject to change +typedef struct { + uint8_t daddr; + tusb_desc_interface_t desc; +} tuh_itf_info_t; + +typedef struct { + uint8_t rhport; + uint8_t hub_addr; + uint8_t hub_port; + uint8_t speed; +} tuh_bus_info_t; + +// backward compatibility for hcd_devtree_info_t, maybe removed in the future +#define hcd_devtree_info_t tuh_bus_info_t +#define hcd_devtree_get_info(_daddr, _bus_info) tuh_bus_info_get(_daddr, _bus_info) + +// ConfigID for tuh_configure() +enum { + TUH_CFGID_INVALID = 0, + TUH_CFGID_RPI_PIO_USB_CONFIGURATION = 100, // cfg_param: pio_usb_configuration_t + TUH_CFGID_MAX3421 = 200, +}; + +typedef struct { + uint8_t max_nak; // max NAK per endpoint per frame to save CPU/SPI bus usage + uint8_t cpuctl; // R16: CPU Control Register + uint8_t pinctl; // R17: Pin Control Register. FDUPSPI bit is ignored +} tuh_configure_max3421_t; + +typedef union { + // For TUH_CFGID_RPI_PIO_USB_CONFIGURATION use pio_usb_configuration_t + + tuh_configure_max3421_t max3421; +} tuh_configure_param_t; + +//--------------------------------------------------------------------+ +// APPLICATION CALLBACK +//--------------------------------------------------------------------+ + +// Invoked when enumeration get device descriptor +// Device is not ready to communicate yet, application can copy the descriptor if needed +void tuh_enum_descriptor_device_cb(uint8_t daddr, const tusb_desc_device_t *desc_device); + +// Invoked when enumeration get configuration descriptor +// For multi-configuration device return false to skip, true to proceed with this configuration (may not be implemented yet) +// Device is not ready to communicate yet, application can copy the descriptor if needed +bool tuh_enum_descriptor_configuration_cb(uint8_t daddr, uint8_t cfg_index, const tusb_desc_configuration_t *desc_config); + +// Invoked when a device is mounted (configured) +void tuh_mount_cb (uint8_t daddr); + +// Invoked when a device failed to mount during enumeration process +// void tuh_mount_failed_cb (uint8_t daddr); + +// Invoked when a device is unmounted (detached) +void tuh_umount_cb(uint8_t daddr); + +// Invoked when there is a new usb event, which need to be processed by tuh_task()/tuh_task_ext() +void tuh_event_hook_cb(uint8_t rhport, uint32_t eventid, bool in_isr); + +//--------------------------------------------------------------------+ +// APPLICATION API +//--------------------------------------------------------------------+ + +// Configure host stack behavior with dynamic or port-specific parameters. +// Should be called before tuh_init() +// - cfg_id : configure ID (TBD) +// - cfg_param: configure data, structure depends on the ID +bool tuh_configure(uint8_t rhport, uint32_t cfg_id, const void* cfg_param); + +// New API to replace tuh_init() to init host stack on specific roothub port +bool tuh_rhport_init(uint8_t rhport, const tusb_rhport_init_t* rh_init); + +// Init host stack +#if TUSB_VERSION_NUMBER > 2000 // 0.20.0 +TU_ATTR_DEPRECATED("Please use tusb_init(rhport, rh_init) instead") +#endif +TU_ATTR_ALWAYS_INLINE static inline bool tuh_init(uint8_t rhport) { + const tusb_rhport_init_t rh_init = { + .role = TUSB_ROLE_HOST, + .speed = TUH_OPT_HIGH_SPEED ? TUSB_SPEED_HIGH : TUSB_SPEED_FULL, + }; + return tuh_rhport_init(rhport, &rh_init); +} + +// Deinit host stack on rhport +bool tuh_deinit(uint8_t rhport); + +// Check if host stack is already initialized with any roothub ports +// To check if an rhport is initialized, use tuh_rhport_is_active() +bool tuh_inited(void); + +// Task function should be called in main/rtos loop, extended version of tuh_task() +// - timeout_ms: millisecond to wait, zero = no wait, 0xFFFFFFFF = wait forever +// - in_isr: if function is called in ISR +void tuh_task_ext(uint32_t timeout_ms, bool in_isr); + +// Task function should be called in main/rtos loop +TU_ATTR_ALWAYS_INLINE static inline void tuh_task(void) { + tuh_task_ext(UINT32_MAX, false); +} + +// Check if there is pending events need processing by tuh_task() +bool tuh_task_event_ready(void); + +#ifndef _TUSB_HCD_H_ +extern void hcd_int_handler(uint8_t rhport, bool in_isr); +#endif + +// Interrupt handler alias to HCD with in_isr as optional parameter +#define _tuh_int_handler_arg0() TU_VERIFY_STATIC(false, "tuh_int_handler() must have 1 or 2 arguments") +#define _tuh_int_handler_arg1(_rhport) hcd_int_handler(_rhport, true) +#define _tuh_int_handler_arg2(_rhport, _in_isr) hcd_int_handler(_rhport, _in_isr) + +// 1st argument is rhport (mandatory), 2nd argument in_isr (optional) +#define tuh_int_handler(...) TU_FUNC_OPTIONAL_ARG(_tuh_int_handler, __VA_ARGS__) + +// Check if roothub port is initialized and active as a host +bool tuh_rhport_is_active(uint8_t rhport); + +// Assert/de-assert Bus Reset signal to roothub port. USB specs: it should last 10-50ms +bool tuh_rhport_reset_bus(uint8_t rhport, bool active); + +//--------------------------------------------------------------------+ +// Device API +//--------------------------------------------------------------------+ + +// Get VID/PID of device +bool tuh_vid_pid_get(uint8_t daddr, uint16_t* vid, uint16_t* pid); + +// Get local (cached) device descriptor once device is enumerated +bool tuh_descriptor_get_device_local(uint8_t daddr, tusb_desc_device_t* desc_device); + +// Get speed of device +tusb_speed_t tuh_speed_get(uint8_t daddr); + +// Check if device is connected and configured +bool tuh_mounted(uint8_t daddr); + +// Check if device is connected which mean device has at least 1 successful transfer +// Note: It may not be addressed/configured/mounted yet +bool tuh_connected(uint8_t daddr); + +// Check if device is suspended +TU_ATTR_ALWAYS_INLINE static inline bool tuh_suspended(uint8_t daddr) { + // TODO implement suspend & resume on host + (void) daddr; + return false; +} + +// Check if device is ready to communicate with +TU_ATTR_ALWAYS_INLINE static inline bool tuh_ready(uint8_t daddr) { + return tuh_mounted(daddr) && !tuh_suspended(daddr); +} + +// Get bus information of device +bool tuh_bus_info_get(uint8_t daddr, tuh_bus_info_t* bus_info); + +//--------------------------------------------------------------------+ +// Transfer API +// Each Function will make a USB transfer request to device. If +// - complete_cb != NULL, the function will return immediately and invoke the callback when request is complete. +// - complete_cb == NULL, the function will block until request is complete. +// In this case, user_data should be tusb_xfer_result_t* to hold the transfer result. +//--------------------------------------------------------------------+ + +// Helper to make Sync API from async one +#define TU_API_SYNC(_async_api, ...) \ + xfer_result_t result = XFER_RESULT_INVALID;\ + TU_VERIFY(_async_api(__VA_ARGS__, NULL, (uintptr_t) &result), XFER_RESULT_TIMEOUT); \ + return result + +// Submit a control transfer +// - async: complete callback invoked when finished. +// - sync : blocking if complete callback is NULL. +bool tuh_control_xfer(tuh_xfer_t* xfer); + +// Submit a bulk/interrupt transfer +// - async: complete callback invoked when finished. +// - sync : blocking if complete callback is NULL. +bool tuh_edpt_xfer(tuh_xfer_t* xfer); + +// Open a non-control endpoint +bool tuh_edpt_open(uint8_t daddr, tusb_desc_endpoint_t const * desc_ep); + +// Close a non-control endpoint, it will abort any pending transfer +bool tuh_edpt_close(uint8_t daddr, uint8_t ep_addr); + +// Abort a queued transfer. Note: it can only abort transfer that has not been started +// Return true if a queued transfer is aborted, false if there is no transfer to abort +bool tuh_edpt_abort_xfer(uint8_t daddr, uint8_t ep_addr); + +// Set Address (control transfer) +bool tuh_address_set(uint8_t daddr, uint8_t new_addr, + tuh_xfer_cb_t complete_cb, uintptr_t user_data); + +// Set Configuration (control transfer) +// config_num = 0 will un-configure device. Note: config_num = config_descriptor_index + 1 +// true on success, false if there is on-going control transfer or incorrect parameters +// if complete_cb == NULL i.e blocking, user_data should be pointed to xfer_reuslt_t* +bool tuh_configuration_set(uint8_t daddr, uint8_t config_num, + tuh_xfer_cb_t complete_cb, uintptr_t user_data); + +// Set Interface (control transfer) +// true on success, false if there is on-going control transfer or incorrect parameters +// if complete_cb == NULL i.e blocking, user_data should be pointed to xfer_reuslt_t* +bool tuh_interface_set(uint8_t daddr, uint8_t itf_num, uint8_t itf_alt, + tuh_xfer_cb_t complete_cb, uintptr_t user_data); + +//--------------------------------------------------------------------+ +// Descriptors Asynchronous (non-blocking) +//--------------------------------------------------------------------+ + +// Get an descriptor (control transfer) +// true on success, false if there is on-going control transfer or incorrect parameters +bool tuh_descriptor_get(uint8_t daddr, uint8_t type, uint8_t index, void* buffer, uint16_t len, + tuh_xfer_cb_t complete_cb, uintptr_t user_data); + +// Get device descriptor (control transfer) +// true on success, false if there is on-going control transfer or incorrect parameters +bool tuh_descriptor_get_device(uint8_t daddr, void* buffer, uint16_t len, + tuh_xfer_cb_t complete_cb, uintptr_t user_data); + +// Get configuration descriptor (control transfer) +// true on success, false if there is on-going control transfer or incorrect parameters +bool tuh_descriptor_get_configuration(uint8_t daddr, uint8_t index, void* buffer, uint16_t len, + tuh_xfer_cb_t complete_cb, uintptr_t user_data); + +// Get HID report descriptor (control transfer) +// true on success, false if there is on-going control transfer or incorrect parameters +bool tuh_descriptor_get_hid_report(uint8_t daddr, uint8_t itf_num, uint8_t desc_type, uint8_t index, void* buffer, uint16_t len, + tuh_xfer_cb_t complete_cb, uintptr_t user_data); + +// Get string descriptor (control transfer) +// true on success, false if there is on-going control transfer or incorrect parameters +// Blocking if complete callback is NULL, in this case 'user_data' must contain xfer_result_t variable +bool tuh_descriptor_get_string(uint8_t daddr, uint8_t index, uint16_t language_id, void* buffer, uint16_t len, + tuh_xfer_cb_t complete_cb, uintptr_t user_data); + +// Get language id string descriptor (control transfer) +TU_ATTR_ALWAYS_INLINE static inline +bool tuh_descriptor_get_string_langid(uint8_t daddr, void* buffer, uint16_t len, + tuh_xfer_cb_t complete_cb, uintptr_t user_data) { + return tuh_descriptor_get_string(daddr, 0, 0, buffer, len, complete_cb, user_data); +} + +// Get manufacturer string descriptor (control transfer) +// true on success, false if there is on-going control transfer or incorrect parameters +bool tuh_descriptor_get_manufacturer_string(uint8_t daddr, uint16_t language_id, void* buffer, uint16_t len, + tuh_xfer_cb_t complete_cb, uintptr_t user_data); + +// Get product string descriptor (control transfer) +// true on success, false if there is on-going control transfer or incorrect parameters +bool tuh_descriptor_get_product_string(uint8_t daddr, uint16_t language_id, void* buffer, uint16_t len, + tuh_xfer_cb_t complete_cb, uintptr_t user_data); + +// Get serial string descriptor (control transfer) +// true on success, false if there is on-going control transfer or incorrect parameters +bool tuh_descriptor_get_serial_string(uint8_t daddr, uint16_t language_id, void* buffer, uint16_t len, + tuh_xfer_cb_t complete_cb, uintptr_t user_data); + +//--------------------------------------------------------------------+ +// Descriptors Synchronous (blocking) +// Sync API which is blocking until transfer is complete. +// return transfer result +//--------------------------------------------------------------------+ + +// Sync version of tuh_descriptor_get() +TU_ATTR_ALWAYS_INLINE static inline tusb_xfer_result_t tuh_descriptor_get_sync(uint8_t daddr, uint8_t type, uint8_t index, void* buffer, uint16_t len) { + TU_API_SYNC(tuh_descriptor_get, daddr, type, index, buffer, len); +} + +// Sync version of tuh_descriptor_get_device() +TU_ATTR_ALWAYS_INLINE static inline tusb_xfer_result_t tuh_descriptor_get_device_sync(uint8_t daddr, void* buffer, uint16_t len) { + TU_API_SYNC(tuh_descriptor_get_device, daddr, buffer, len); +} + +// Sync version of tuh_descriptor_get_configuration() +TU_ATTR_ALWAYS_INLINE static inline tusb_xfer_result_t tuh_descriptor_get_configuration_sync(uint8_t daddr, uint8_t index, void* buffer, uint16_t len) { + TU_API_SYNC(tuh_descriptor_get_configuration, daddr, index, buffer, len); +} + +// Sync version of tuh_descriptor_get_hid_report() +TU_ATTR_ALWAYS_INLINE static inline tusb_xfer_result_t tuh_descriptor_get_hid_report_sync(uint8_t daddr, uint8_t itf_num, uint8_t desc_type, uint8_t index, void* buffer, uint16_t len) { + TU_API_SYNC(tuh_descriptor_get_hid_report, daddr, itf_num, desc_type, index, buffer, len); +} + +// Sync version of tuh_descriptor_get_string() +TU_ATTR_ALWAYS_INLINE static inline tusb_xfer_result_t tuh_descriptor_get_string_sync(uint8_t daddr, uint8_t index, uint16_t language_id, void* buffer, uint16_t len) { + TU_API_SYNC(tuh_descriptor_get_string, daddr, index, language_id, buffer, len); +} + +// Sync version of tuh_descriptor_get_string_langid() +TU_ATTR_ALWAYS_INLINE static inline tusb_xfer_result_t tuh_descriptor_get_string_langid_sync(uint8_t daddr, void* buffer, uint16_t len) { + return tuh_descriptor_get_string_sync(daddr, 0, 0, buffer, len); +} + +// Sync version of tuh_descriptor_get_manufacturer_string() +TU_ATTR_ALWAYS_INLINE static inline tusb_xfer_result_t tuh_descriptor_get_manufacturer_string_sync(uint8_t daddr, uint16_t language_id, void* buffer, uint16_t len) { + TU_API_SYNC(tuh_descriptor_get_manufacturer_string, daddr, language_id, buffer, len); +} + +// Sync version of tuh_descriptor_get_product_string() +TU_ATTR_ALWAYS_INLINE static inline tusb_xfer_result_t tuh_descriptor_get_product_string_sync(uint8_t daddr, uint16_t language_id, void* buffer, uint16_t len) { + TU_API_SYNC(tuh_descriptor_get_product_string, daddr, language_id, buffer, len); +} + +// Sync version of tuh_descriptor_get_serial_string() +TU_ATTR_ALWAYS_INLINE static inline tusb_xfer_result_t tuh_descriptor_get_serial_string_sync(uint8_t daddr, uint16_t language_id, void* buffer, uint16_t len) { + TU_API_SYNC(tuh_descriptor_get_serial_string, daddr, language_id, buffer, len); +} + +#ifdef __cplusplus + } +#endif + +#endif diff --git a/cores/nRF5/TinyUSB/Adafruit_TinyUSB_ArduinoCore/tinyusb/src/host/usbh_pvt.h b/cores/nRF5/TinyUSB/Adafruit_TinyUSB_ArduinoCore/tinyusb/src/host/usbh_pvt.h new file mode 100644 index 0000000..9d91e52 --- /dev/null +++ b/cores/nRF5/TinyUSB/Adafruit_TinyUSB_ArduinoCore/tinyusb/src/host/usbh_pvt.h @@ -0,0 +1,104 @@ +/* + * The MIT License (MIT) + * + * Copyright (c) 2021, Ha Thach (tinyusb.org) + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + * This file is part of the TinyUSB stack. + */ + +#ifndef _TUSB_USBH_PVT_H_ +#define _TUSB_USBH_PVT_H_ + +#include "osal/osal.h" +#include "common/tusb_fifo.h" +#include "common/tusb_private.h" + +#ifdef __cplusplus + extern "C" { +#endif + +#define TU_LOG_USBH(...) TU_LOG(CFG_TUH_LOG_LEVEL, __VA_ARGS__) +#define TU_LOG_MEM_USBH(...) TU_LOG_MEM(CFG_TUH_LOG_LEVEL, __VA_ARGS__) +#define TU_LOG_BUF_USBH(...) TU_LOG_BUF(CFG_TUH_LOG_LEVEL, __VA_ARGS__) +#define TU_LOG_INT_USBH(...) TU_LOG_INT(CFG_TUH_LOG_LEVEL, __VA_ARGS__) +#define TU_LOG_HEX_USBH(...) TU_LOG_HEX(CFG_TUH_LOG_LEVEL, __VA_ARGS__) + +//--------------------------------------------------------------------+ +// Class Driver API +//--------------------------------------------------------------------+ + +typedef struct { + char const* name; + bool (* const init )(void); + bool (* const deinit )(void); + bool (* const open )(uint8_t rhport, uint8_t dev_addr, tusb_desc_interface_t const * itf_desc, uint16_t max_len); + bool (* const set_config )(uint8_t dev_addr, uint8_t itf_num); + bool (* const xfer_cb )(uint8_t dev_addr, uint8_t ep_addr, xfer_result_t result, uint32_t xferred_bytes); + void (* const close )(uint8_t dev_addr); +} usbh_class_driver_t; + +// Invoked when initializing host stack to get additional class drivers. +// Can be implemented by application to extend/overwrite class driver support. +// Note: The drivers array must be accessible at all time when stack is active +usbh_class_driver_t const* usbh_app_driver_get_cb(uint8_t* driver_count); + +// Call by class driver to tell USBH that it has complete the enumeration +void usbh_driver_set_config_complete(uint8_t dev_addr, uint8_t itf_num); + +uint8_t usbh_get_rhport(uint8_t daddr); + +uint8_t* usbh_get_enum_buf(void); + +void usbh_int_set(bool enabled); + +void usbh_defer_func(osal_task_func_t func, void *param, bool in_isr); + +void usbh_spin_lock(bool in_isr); +void usbh_spin_unlock(bool in_isr); + +//--------------------------------------------------------------------+ +// USBH Endpoint API +//--------------------------------------------------------------------+ + +// Submit a usb transfer with callback support, require CFG_TUH_API_EDPT_XFER +bool usbh_edpt_xfer_with_callback(uint8_t dev_addr, uint8_t ep_addr, uint8_t * buffer, uint16_t total_bytes, + tuh_xfer_cb_t complete_cb, uintptr_t user_data); + +TU_ATTR_ALWAYS_INLINE static inline +bool usbh_edpt_xfer(uint8_t dev_addr, uint8_t ep_addr, uint8_t * buffer, uint16_t total_bytes) { + return usbh_edpt_xfer_with_callback(dev_addr, ep_addr, buffer, total_bytes, NULL, 0); +} + +// Claim an endpoint before submitting a transfer. +// If caller does not make any transfer, it must release endpoint for others. +bool usbh_edpt_claim(uint8_t dev_addr, uint8_t ep_addr); + +// Release claimed endpoint without submitting a transfer +bool usbh_edpt_release(uint8_t dev_addr, uint8_t ep_addr); + +// Check if endpoint transferring is complete +bool usbh_edpt_busy(uint8_t dev_addr, uint8_t ep_addr); + +#ifdef __cplusplus + } +#endif + +#endif diff --git a/cores/nRF5/TinyUSB/Adafruit_TinyUSB_ArduinoCore/tinyusb/src/osal/osal.h b/cores/nRF5/TinyUSB/Adafruit_TinyUSB_ArduinoCore/tinyusb/src/osal/osal.h index b5057ff..a332804 100644 --- a/cores/nRF5/TinyUSB/Adafruit_TinyUSB_ArduinoCore/tinyusb/src/osal/osal.h +++ b/cores/nRF5/TinyUSB/Adafruit_TinyUSB_ArduinoCore/tinyusb/src/osal/osal.h @@ -1,4 +1,4 @@ -/* +/* * The MIT License (MIT) * * Copyright (c) 2019 Ha Thach (tinyusb.org) @@ -31,28 +31,40 @@ extern "C" { #endif -/** \addtogroup group_osal - * @{ */ - #include "common/tusb_common.h" -// Return immediately -#define OSAL_TIMEOUT_NOTIMEOUT (0) -// Default timeout -#define OSAL_TIMEOUT_NORMAL (10) -// Wait forever -#define OSAL_TIMEOUT_WAIT_FOREVER (UINT32_MAX) +typedef void (*osal_task_func_t)( void * ); +// Timeout +#define OSAL_TIMEOUT_NOTIMEOUT (0) // Return immediately +#define OSAL_TIMEOUT_NORMAL (10) // Default timeout +#define OSAL_TIMEOUT_WAIT_FOREVER (UINT32_MAX) // Wait forever #define OSAL_TIMEOUT_CONTROL_XFER OSAL_TIMEOUT_WAIT_FOREVER -typedef void (*osal_task_func_t)( void * ); +// Mutex is required when using a preempted RTOS or MCU has multiple cores +#if (CFG_TUSB_OS == OPT_OS_NONE) && !TUP_MCU_MULTIPLE_CORE + #define OSAL_MUTEX_REQUIRED 0 + #define OSAL_MUTEX_DEF(_name) uint8_t :0 +#else + #define OSAL_MUTEX_REQUIRED 1 + #define OSAL_MUTEX_DEF(_name) osal_mutex_def_t _name +#endif +// OS thin implementation #if CFG_TUSB_OS == OPT_OS_NONE #include "osal_none.h" #elif CFG_TUSB_OS == OPT_OS_FREERTOS #include "osal_freertos.h" #elif CFG_TUSB_OS == OPT_OS_MYNEWT #include "osal_mynewt.h" +#elif CFG_TUSB_OS == OPT_OS_PICO + #include "osal_pico.h" +#elif CFG_TUSB_OS == OPT_OS_RTTHREAD + #include "osal_rtthread.h" +#elif CFG_TUSB_OS == OPT_OS_RTX4 + #include "osal_rtx4.h" +#elif CFG_TUSB_OS == OPT_OS_ZEPHYR + #include "osal_zephyr.h" #elif CFG_TUSB_OS == OPT_OS_CUSTOM #include "tusb_os_custom.h" // implemented by application #else @@ -61,41 +73,33 @@ typedef void (*osal_task_func_t)( void * ); //--------------------------------------------------------------------+ // OSAL Porting API +// Should be implemented as static inline function in osal_port.h header +/* + void osal_spin_init(osal_spinlock_t *ctx); + void osal_spin_lock(osal_spinlock_t *ctx, bool in_isr) + void osal_spin_unlock(osal_spinlock_t *ctx, bool in_isr); + + osal_semaphore_t osal_semaphore_create(osal_semaphore_def_t* semdef); + bool osal_semaphore_delete(osal_semaphore_t semd_hdl); + bool osal_semaphore_post(osal_semaphore_t sem_hdl, bool in_isr); + bool osal_semaphore_wait(osal_semaphore_t sem_hdl, uint32_t msec); + void osal_semaphore_reset(osal_semaphore_t sem_hdl); // TODO removed + + osal_mutex_t osal_mutex_create(osal_mutex_def_t* mdef); + bool osal_mutex_delete(osal_mutex_t mutex_hdl) + bool osal_mutex_lock (osal_mutex_t sem_hdl, uint32_t msec); + bool osal_mutex_unlock(osal_mutex_t mutex_hdl); + + osal_queue_t osal_queue_create(osal_queue_def_t* qdef); + bool osal_queue_delete(osal_queue_t qhdl); + bool osal_queue_receive(osal_queue_t qhdl, void* data, uint32_t msec); + bool osal_queue_send(osal_queue_t qhdl, void const * data, bool in_isr); + bool osal_queue_empty(osal_queue_t qhdl); +*/ //--------------------------------------------------------------------+ -//static inline void osal_task_delay(uint32_t msec); - -//------------- Semaphore -------------// -static inline osal_semaphore_t osal_semaphore_create(osal_semaphore_def_t* semdef); -static inline bool osal_semaphore_post(osal_semaphore_t sem_hdl, bool in_isr); -static inline bool osal_semaphore_wait(osal_semaphore_t sem_hdl, uint32_t msec); - -static inline void osal_semaphore_reset(osal_semaphore_t sem_hdl); // TODO removed - -//------------- Mutex -------------// -static inline osal_mutex_t osal_mutex_create(osal_mutex_def_t* mdef); -static inline bool osal_mutex_lock (osal_mutex_t sem_hdl, uint32_t msec); -static inline bool osal_mutex_unlock(osal_mutex_t mutex_hdl); - -//------------- Queue -------------// -static inline osal_queue_t osal_queue_create(osal_queue_def_t* qdef); -static inline bool osal_queue_receive(osal_queue_t qhdl, void* data); -static inline bool osal_queue_send(osal_queue_t qhdl, void const * data, bool in_isr); -static inline bool osal_queue_empty(osal_queue_t qhdl); - -#if 0 // TODO remove subtask related macros later -// Sub Task -#define OSAL_SUBTASK_BEGIN -#define OSAL_SUBTASK_END return TUSB_ERROR_NONE; - -#define STASK_RETURN(_error) return _error; -#define STASK_INVOKE(_subtask, _status) (_status) = _subtask -#define STASK_ASSERT(_cond) TU_VERIFY(_cond, TUSB_ERROR_OSAL_TASK_FAILED) -#endif #ifdef __cplusplus } #endif -/** @} */ - #endif /* _TUSB_OSAL_H_ */ diff --git a/cores/nRF5/TinyUSB/Adafruit_TinyUSB_ArduinoCore/tinyusb/src/osal/osal_freertos.h b/cores/nRF5/TinyUSB/Adafruit_TinyUSB_ArduinoCore/tinyusb/src/osal/osal_freertos.h index 004bc8a..bde5ec0 100644 --- a/cores/nRF5/TinyUSB/Adafruit_TinyUSB_ArduinoCore/tinyusb/src/osal/osal_freertos.h +++ b/cores/nRF5/TinyUSB/Adafruit_TinyUSB_ArduinoCore/tinyusb/src/osal/osal_freertos.h @@ -1,4 +1,4 @@ -/* +/* * The MIT License (MIT) * * Copyright (c) 2019 Ha Thach (tinyusb.org) @@ -24,149 +24,249 @@ * This file is part of the TinyUSB stack. */ -#ifndef _TUSB_OSAL_FREERTOS_H_ -#define _TUSB_OSAL_FREERTOS_H_ +#ifndef TUSB_OSAL_FREERTOS_H_ +#define TUSB_OSAL_FREERTOS_H_ // FreeRTOS Headers -#include "freertos/FreeRTOS.h" -#include "freertos/semphr.h" -#include "freertos/queue.h" -#include "freertos/task.h" +#include TU_INCLUDE_PATH(CFG_TUSB_OS_INC_PATH,FreeRTOS.h) +#include TU_INCLUDE_PATH(CFG_TUSB_OS_INC_PATH,semphr.h) +#include TU_INCLUDE_PATH(CFG_TUSB_OS_INC_PATH,queue.h) +#include TU_INCLUDE_PATH(CFG_TUSB_OS_INC_PATH,task.h) #ifdef __cplusplus extern "C" { #endif +//--------------------------------------------------------------------+ +// MACRO CONSTANT TYPEDEF PROTYPES +//--------------------------------------------------------------------+ + +#if configSUPPORT_STATIC_ALLOCATION +typedef StaticSemaphore_t osal_semaphore_def_t; +typedef StaticSemaphore_t osal_mutex_def_t; +#else + +// not used therefore defined to the smallest possible type to save space +typedef uint8_t osal_semaphore_def_t; +typedef uint8_t osal_mutex_def_t; +#endif + +typedef SemaphoreHandle_t osal_semaphore_t; +typedef SemaphoreHandle_t osal_mutex_t; +typedef QueueHandle_t osal_queue_t; + +typedef struct { + uint16_t depth; + uint16_t item_sz; + void* buf; + +#if defined(configQUEUE_REGISTRY_SIZE) && (configQUEUE_REGISTRY_SIZE>0) + char const* name; +#endif + +#if configSUPPORT_STATIC_ALLOCATION + StaticQueue_t sq; +#endif +} osal_queue_def_t; + +#if defined(configQUEUE_REGISTRY_SIZE) && (configQUEUE_REGISTRY_SIZE>0) + #define _OSAL_Q_NAME(_name) .name = #_name +#else + #define _OSAL_Q_NAME(_name) +#endif + +// _int_set is not used with an RTOS +#define OSAL_QUEUE_DEF(_int_set, _name, _depth, _type) \ + static _type _name##_##buf[_depth];\ + osal_queue_def_t _name = { .depth = _depth, .item_sz = sizeof(_type), .buf = _name##_##buf, _OSAL_Q_NAME(_name) } + //--------------------------------------------------------------------+ // TASK API //--------------------------------------------------------------------+ -static inline void osal_task_delay(uint32_t msec) -{ - vTaskDelay( pdMS_TO_TICKS(msec) ); +TU_ATTR_ALWAYS_INLINE static inline uint32_t _osal_ms2tick(uint32_t msec) { + if (msec == OSAL_TIMEOUT_WAIT_FOREVER) { return portMAX_DELAY; } + if (msec == 0) { return 0; } + + uint32_t ticks = pdMS_TO_TICKS(msec); + + // If configTICK_RATE_HZ is less than 1000 and 1 tick > 1 ms, we still need to delay at least 1 tick + if (ticks == 0) { ticks = 1; } + + return ticks; +} + +TU_ATTR_ALWAYS_INLINE static inline void osal_task_delay(uint32_t msec) { + vTaskDelay(pdMS_TO_TICKS(msec)); } //--------------------------------------------------------------------+ -// Semaphore API +// Spinlock API //--------------------------------------------------------------------+ -typedef StaticSemaphore_t osal_semaphore_def_t; -typedef SemaphoreHandle_t osal_semaphore_t; +#define OSAL_SPINLOCK_DEF(_name, _int_set) \ + osal_spinlock_t _name -static inline osal_semaphore_t osal_semaphore_create(osal_semaphore_def_t* semdef) -{ - return xSemaphoreCreateBinaryStatic(semdef); +#if TUSB_MCU_VENDOR_ESPRESSIF +// Espressif critical take spinlock as argument and does not use in_isr +typedef portMUX_TYPE osal_spinlock_t; + +TU_ATTR_ALWAYS_INLINE static inline void osal_spin_init(osal_spinlock_t *ctx) { + spinlock_initialize(ctx); } -static inline bool osal_semaphore_post(osal_semaphore_t sem_hdl, bool in_isr) -{ - if ( !in_isr ) - { - return xSemaphoreGive(sem_hdl) != 0; +TU_ATTR_ALWAYS_INLINE static inline void osal_spin_lock(osal_spinlock_t *ctx, bool in_isr) { + if (!TUP_MCU_MULTIPLE_CORE && in_isr) { + return; // single core MCU does not need to lock in ISR } - else - { - BaseType_t xHigherPriorityTaskWoken; - BaseType_t res = xSemaphoreGiveFromISR(sem_hdl, &xHigherPriorityTaskWoken); + portENTER_CRITICAL(ctx); +} + +TU_ATTR_ALWAYS_INLINE static inline void osal_spin_unlock(osal_spinlock_t *ctx, bool in_isr) { + if (!TUP_MCU_MULTIPLE_CORE && in_isr) { + return; // single core MCU does not need to lock in ISR + } + portEXIT_CRITICAL(ctx); +} -#if CFG_TUSB_MCU == OPT_MCU_ESP32S2 - if ( xHigherPriorityTaskWoken ) portYIELD_FROM_ISR(); #else - portYIELD_FROM_ISR(xHigherPriorityTaskWoken); + +typedef UBaseType_t osal_spinlock_t; + +TU_ATTR_ALWAYS_INLINE static inline void osal_spin_init(osal_spinlock_t *ctx) { + (void) ctx; +} + +TU_ATTR_ALWAYS_INLINE static inline void osal_spin_lock(osal_spinlock_t *ctx, bool in_isr) { + if (in_isr) { + if (!TUP_MCU_MULTIPLE_CORE) { + (void) ctx; + return; // single core MCU does not need to lock in ISR + } + *ctx = taskENTER_CRITICAL_FROM_ISR(); + } else { + taskENTER_CRITICAL(); + } +} + +TU_ATTR_ALWAYS_INLINE static inline void osal_spin_unlock(osal_spinlock_t *ctx, bool in_isr) { + if (in_isr) { + if (!TUP_MCU_MULTIPLE_CORE) { + (void) ctx; + return; // single core MCU does not need to lock in ISR + } + taskEXIT_CRITICAL_FROM_ISR(*ctx); + } else { + taskEXIT_CRITICAL(); + } +} + #endif +//--------------------------------------------------------------------+ +// Semaphore API +//--------------------------------------------------------------------+ +TU_ATTR_ALWAYS_INLINE static inline osal_semaphore_t osal_semaphore_create(osal_semaphore_def_t *semdef) { +#if configSUPPORT_STATIC_ALLOCATION + return xSemaphoreCreateBinaryStatic(semdef); +#else + (void) semdef; + return xSemaphoreCreateBinary(); +#endif +} + +TU_ATTR_ALWAYS_INLINE static inline bool osal_semaphore_delete(osal_semaphore_t semd_hdl) { + vSemaphoreDelete(semd_hdl); + return true; +} + +TU_ATTR_ALWAYS_INLINE static inline bool osal_semaphore_post(osal_semaphore_t sem_hdl, bool in_isr) { + if (!in_isr) { + return xSemaphoreGive(sem_hdl) != 0; + } else { + BaseType_t xHigherPriorityTaskWoken = pdFALSE; + BaseType_t res = xSemaphoreGiveFromISR(sem_hdl, &xHigherPriorityTaskWoken); + portYIELD_FROM_ISR(xHigherPriorityTaskWoken); return res != 0; } } -static inline bool osal_semaphore_wait (osal_semaphore_t sem_hdl, uint32_t msec) -{ - uint32_t const ticks = (msec == OSAL_TIMEOUT_WAIT_FOREVER) ? portMAX_DELAY : pdMS_TO_TICKS(msec); - return xSemaphoreTake(sem_hdl, ticks); +TU_ATTR_ALWAYS_INLINE static inline bool osal_semaphore_wait(osal_semaphore_t sem_hdl, uint32_t msec) { + return xSemaphoreTake(sem_hdl, _osal_ms2tick(msec)); } -static inline void osal_semaphore_reset(osal_semaphore_t const sem_hdl) -{ +TU_ATTR_ALWAYS_INLINE static inline void osal_semaphore_reset(osal_semaphore_t const sem_hdl) { xQueueReset(sem_hdl); } //--------------------------------------------------------------------+ // MUTEX API (priority inheritance) //--------------------------------------------------------------------+ -typedef StaticSemaphore_t osal_mutex_def_t; -typedef SemaphoreHandle_t osal_mutex_t; - -static inline osal_mutex_t osal_mutex_create(osal_mutex_def_t* mdef) -{ +TU_ATTR_ALWAYS_INLINE static inline osal_mutex_t osal_mutex_create(osal_mutex_def_t *mdef) { +#if configSUPPORT_STATIC_ALLOCATION return xSemaphoreCreateMutexStatic(mdef); +#else + (void) mdef; + return xSemaphoreCreateMutex(); +#endif } -static inline bool osal_mutex_lock (osal_mutex_t mutex_hdl, uint32_t msec) -{ +TU_ATTR_ALWAYS_INLINE static inline bool osal_mutex_delete(osal_mutex_t mutex_hdl) { + vSemaphoreDelete(mutex_hdl); + return true; +} + +TU_ATTR_ALWAYS_INLINE static inline bool osal_mutex_lock(osal_mutex_t mutex_hdl, uint32_t msec) { return osal_semaphore_wait(mutex_hdl, msec); } -static inline bool osal_mutex_unlock(osal_mutex_t mutex_hdl) -{ +TU_ATTR_ALWAYS_INLINE static inline bool osal_mutex_unlock(osal_mutex_t mutex_hdl) { return xSemaphoreGive(mutex_hdl); } //--------------------------------------------------------------------+ // QUEUE API //--------------------------------------------------------------------+ +TU_ATTR_ALWAYS_INLINE static inline osal_queue_t osal_queue_create(osal_queue_def_t* qdef) { + osal_queue_t q; -// role device/host is used by OS NONE for mutex (disable usb isr) only -#define OSAL_QUEUE_DEF(_role, _name, _depth, _type) \ - static _type _name##_##buf[_depth];\ - osal_queue_def_t _name = { .depth = _depth, .item_sz = sizeof(_type), .buf = _name##_##buf }; - -typedef struct -{ - uint16_t depth; - uint16_t item_sz; - void* buf; +#if configSUPPORT_STATIC_ALLOCATION + q = xQueueCreateStatic(qdef->depth, qdef->item_sz, (uint8_t*) qdef->buf, &qdef->sq); +#else + q = xQueueCreate(qdef->depth, qdef->item_sz); +#endif - StaticQueue_t sq; -}osal_queue_def_t; +#if defined(configQUEUE_REGISTRY_SIZE) && (configQUEUE_REGISTRY_SIZE>0) + vQueueAddToRegistry(q, qdef->name); +#endif -typedef QueueHandle_t osal_queue_t; + return q; +} -static inline osal_queue_t osal_queue_create(osal_queue_def_t* qdef) -{ - return xQueueCreateStatic(qdef->depth, qdef->item_sz, (uint8_t*) qdef->buf, &qdef->sq); +TU_ATTR_ALWAYS_INLINE static inline bool osal_queue_delete(osal_queue_t qhdl) { + vQueueDelete(qhdl); + return true; } -static inline bool osal_queue_receive(osal_queue_t qhdl, void* data) -{ - return xQueueReceive(qhdl, data, portMAX_DELAY); +TU_ATTR_ALWAYS_INLINE static inline bool osal_queue_receive(osal_queue_t qhdl, void* data, uint32_t msec) { + return xQueueReceive(qhdl, data, _osal_ms2tick(msec)); } -static inline bool osal_queue_send(osal_queue_t qhdl, void const * data, bool in_isr) -{ - if ( !in_isr ) - { +TU_ATTR_ALWAYS_INLINE static inline bool osal_queue_send(osal_queue_t qhdl, void const *data, bool in_isr) { + if (!in_isr) { return xQueueSendToBack(qhdl, data, OSAL_TIMEOUT_WAIT_FOREVER) != 0; - } - else - { - BaseType_t xHigherPriorityTaskWoken; + } else { + BaseType_t xHigherPriorityTaskWoken = pdFALSE; BaseType_t res = xQueueSendToBackFromISR(qhdl, data, &xHigherPriorityTaskWoken); - -#if CFG_TUSB_MCU == OPT_MCU_ESP32S2 - if ( xHigherPriorityTaskWoken ) portYIELD_FROM_ISR(); -#else portYIELD_FROM_ISR(xHigherPriorityTaskWoken); -#endif - return res != 0; } } -static inline bool osal_queue_empty(osal_queue_t qhdl) -{ +TU_ATTR_ALWAYS_INLINE static inline bool osal_queue_empty(osal_queue_t qhdl) { return uxQueueMessagesWaiting(qhdl) == 0; } #ifdef __cplusplus - } +} #endif -#endif /* _TUSB_OSAL_FREERTOS_H_ */ +#endif diff --git a/cores/nRF5/TinyUSB/Adafruit_TinyUSB_ArduinoCore/tinyusb/src/osal/osal_mynewt.h b/cores/nRF5/TinyUSB/Adafruit_TinyUSB_ArduinoCore/tinyusb/src/osal/osal_mynewt.h deleted file mode 100644 index 6882329..0000000 --- a/cores/nRF5/TinyUSB/Adafruit_TinyUSB_ArduinoCore/tinyusb/src/osal/osal_mynewt.h +++ /dev/null @@ -1,174 +0,0 @@ -/* - * The MIT License (MIT) - * - * Copyright (c) 2019 Ha Thach (tinyusb.org) - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - * - * This file is part of the TinyUSB stack. - */ - -#ifndef OSAL_MYNEWT_H_ -#define OSAL_MYNEWT_H_ - -#include "os/os.h" - -#ifdef __cplusplus - extern "C" { -#endif - -//--------------------------------------------------------------------+ -// TASK API -//--------------------------------------------------------------------+ -static inline void osal_task_delay(uint32_t msec) -{ - os_time_delay( os_time_ms_to_ticks32(msec) ); -} - -//--------------------------------------------------------------------+ -// Semaphore API -//--------------------------------------------------------------------+ -typedef struct os_sem osal_semaphore_def_t; -typedef struct os_sem* osal_semaphore_t; - -static inline osal_semaphore_t osal_semaphore_create(osal_semaphore_def_t* semdef) -{ - return (os_sem_init(semdef, 0) == OS_OK) ? (osal_semaphore_t) semdef : NULL; -} - -static inline bool osal_semaphore_post(osal_semaphore_t sem_hdl, bool in_isr) -{ - (void) in_isr; - return os_sem_release(sem_hdl) == OS_OK; -} - -static inline bool osal_semaphore_wait(osal_semaphore_t sem_hdl, uint32_t msec) -{ - uint32_t const ticks = (msec == OSAL_TIMEOUT_WAIT_FOREVER) ? OS_TIMEOUT_NEVER : os_time_ms_to_ticks32(msec); - return os_sem_pend(sem_hdl, ticks) == OS_OK; -} - -static inline void osal_semaphore_reset(osal_semaphore_t sem_hdl) -{ - // TODO implement later -} - -//--------------------------------------------------------------------+ -// MUTEX API (priority inheritance) -//--------------------------------------------------------------------+ -typedef struct os_mutex osal_mutex_def_t; -typedef struct os_mutex* osal_mutex_t; - -static inline osal_mutex_t osal_mutex_create(osal_mutex_def_t* mdef) -{ - return (os_mutex_init(mdef) == OS_OK) ? (osal_mutex_t) mdef : NULL; -} - -static inline bool osal_mutex_lock(osal_mutex_t mutex_hdl, uint32_t msec) -{ - uint32_t const ticks = (msec == OSAL_TIMEOUT_WAIT_FOREVER) ? OS_TIMEOUT_NEVER : os_time_ms_to_ticks32(msec); - return os_mutex_pend(mutex_hdl, ticks) == OS_OK; -} - -static inline bool osal_mutex_unlock(osal_mutex_t mutex_hdl) -{ - return os_mutex_release(mutex_hdl) == OS_OK; -} - -//--------------------------------------------------------------------+ -// QUEUE API -//--------------------------------------------------------------------+ - -// role device/host is used by OS NONE for mutex (disable usb isr) only -#define OSAL_QUEUE_DEF(_role, _name, _depth, _type) \ - static _type _name##_##buf[_depth];\ - static struct os_event _name##_##evbuf[_depth];\ - osal_queue_def_t _name = { .depth = _depth, .item_sz = sizeof(_type), .buf = _name##_##buf, .evbuf = _name##_##evbuf};\ - -typedef struct -{ - uint16_t depth; - uint16_t item_sz; - void* buf; - void* evbuf; - - struct os_mempool mpool; - struct os_mempool epool; - - struct os_eventq evq; -}osal_queue_def_t; - -typedef osal_queue_def_t* osal_queue_t; - -static inline osal_queue_t osal_queue_create(osal_queue_def_t* qdef) -{ - if ( OS_OK != os_mempool_init(&qdef->mpool, qdef->depth, qdef->item_sz, qdef->buf, "usbd queue") ) return NULL; - if ( OS_OK != os_mempool_init(&qdef->epool, qdef->depth, sizeof(struct os_event), qdef->evbuf, "usbd evqueue") ) return NULL; - - os_eventq_init(&qdef->evq); - return (osal_queue_t) qdef; -} - -static inline bool osal_queue_receive(osal_queue_t qhdl, void* data) -{ - struct os_event* ev; - ev = os_eventq_get(&qhdl->evq); - - memcpy(data, ev->ev_arg, qhdl->item_sz); // copy message - os_memblock_put(&qhdl->mpool, ev->ev_arg); // put back mem block - os_memblock_put(&qhdl->epool, ev); // put back ev block - - return true; -} - -static inline bool osal_queue_send(osal_queue_t qhdl, void const * data, bool in_isr) -{ - (void) in_isr; - - // get a block from mem pool for data - void* ptr = os_memblock_get(&qhdl->mpool); - if (!ptr) return false; - memcpy(ptr, data, qhdl->item_sz); - - // get a block from event pool to put into queue - struct os_event* ev = (struct os_event*) os_memblock_get(&qhdl->epool); - if (!ev) - { - os_memblock_put(&qhdl->mpool, ptr); - return false; - } - tu_memclr(ev, sizeof(struct os_event)); - ev->ev_arg = ptr; - - os_eventq_put(&qhdl->evq, ev); - - return true; -} - -static inline bool osal_queue_empty(osal_queue_t qhdl) -{ - return STAILQ_EMPTY(&qhdl->evq.evq_list); -} - - -#ifdef __cplusplus - } -#endif - -#endif /* OSAL_MYNEWT_H_ */ diff --git a/cores/nRF5/TinyUSB/Adafruit_TinyUSB_ArduinoCore/tinyusb/src/osal/osal_none.h b/cores/nRF5/TinyUSB/Adafruit_TinyUSB_ArduinoCore/tinyusb/src/osal/osal_none.h deleted file mode 100644 index 4a5843f..0000000 --- a/cores/nRF5/TinyUSB/Adafruit_TinyUSB_ArduinoCore/tinyusb/src/osal/osal_none.h +++ /dev/null @@ -1,216 +0,0 @@ -/* - * The MIT License (MIT) - * - * Copyright (c) 2019 Ha Thach (tinyusb.org) - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - * - * This file is part of the TinyUSB stack. - */ - -#ifndef _TUSB_OSAL_NONE_H_ -#define _TUSB_OSAL_NONE_H_ - -#ifdef __cplusplus - extern "C" { -#endif - -//--------------------------------------------------------------------+ -// TASK API -//--------------------------------------------------------------------+ -//static inline void osal_task_delay(uint32_t msec) -//{ -// (void) msec; -// // TODO only used by Host stack, will implement using SOF -// -//// uint32_t start = tusb_hal_millis(); -//// while ( ( tusb_hal_millis() - start ) < msec ) {} -//} - -//--------------------------------------------------------------------+ -// Binary Semaphore API -//--------------------------------------------------------------------+ -typedef struct -{ - volatile uint16_t count; -}osal_semaphore_def_t; - -typedef osal_semaphore_def_t* osal_semaphore_t; - -static inline osal_semaphore_t osal_semaphore_create(osal_semaphore_def_t* semdef) -{ - semdef->count = 0; - return semdef; -} - -static inline bool osal_semaphore_post(osal_semaphore_t sem_hdl, bool in_isr) -{ - (void) in_isr; - sem_hdl->count++; - return true; -} - -// TODO blocking for now -static inline bool osal_semaphore_wait (osal_semaphore_t sem_hdl, uint32_t msec) -{ - (void) msec; - - while (sem_hdl->count == 0) { } - sem_hdl->count--; - - return true; -} - -static inline void osal_semaphore_reset(osal_semaphore_t sem_hdl) -{ - sem_hdl->count = 0; -} - -//--------------------------------------------------------------------+ -// MUTEX API -// Within tinyusb, mutex is never used in ISR context -//--------------------------------------------------------------------+ -typedef osal_semaphore_def_t osal_mutex_def_t; -typedef osal_semaphore_t osal_mutex_t; - -static inline osal_mutex_t osal_mutex_create(osal_mutex_def_t* mdef) -{ - mdef->count = 1; - return mdef; -} - -static inline bool osal_mutex_lock (osal_mutex_t mutex_hdl, uint32_t msec) -{ - return osal_semaphore_wait(mutex_hdl, msec); -} - -static inline bool osal_mutex_unlock(osal_mutex_t mutex_hdl) -{ - return osal_semaphore_post(mutex_hdl, false); -} - -//--------------------------------------------------------------------+ -// QUEUE API -//--------------------------------------------------------------------+ -#include "common/tusb_fifo.h" - -// extern to avoid including dcd.h and hcd.h -#if TUSB_OPT_DEVICE_ENABLED -extern void dcd_int_disable(uint8_t rhport); -extern void dcd_int_enable(uint8_t rhport); -#endif - -#if TUSB_OPT_HOST_ENABLED -extern void hcd_int_disable(uint8_t rhport); -extern void hcd_int_enable(uint8_t rhport); -#endif - -typedef struct -{ - uint8_t role; // device or host - tu_fifo_t ff; -}osal_queue_def_t; - -typedef osal_queue_def_t* osal_queue_t; - -// role device/host is used by OS NONE for mutex (disable usb isr) only -#define OSAL_QUEUE_DEF(_role, _name, _depth, _type) \ - uint8_t _name##_buf[_depth*sizeof(_type)]; \ - osal_queue_def_t _name = { \ - .role = _role, \ - .ff = { \ - .buffer = _name##_buf, \ - .depth = _depth, \ - .item_size = sizeof(_type), \ - .overwritable = false, \ - }\ - } - -// lock queue by disable USB interrupt -static inline void _osal_q_lock(osal_queue_t qhdl) -{ - (void) qhdl; - -#if TUSB_OPT_DEVICE_ENABLED - if (qhdl->role == OPT_MODE_DEVICE) dcd_int_disable(TUD_OPT_RHPORT); -#endif - -#if TUSB_OPT_HOST_ENABLED - if (qhdl->role == OPT_MODE_HOST) hcd_int_disable(TUH_OPT_RHPORT); -#endif -} - -// unlock queue -static inline void _osal_q_unlock(osal_queue_t qhdl) -{ - (void) qhdl; - -#if TUSB_OPT_DEVICE_ENABLED - if (qhdl->role == OPT_MODE_DEVICE) dcd_int_enable(TUD_OPT_RHPORT); -#endif - -#if TUSB_OPT_HOST_ENABLED - if (qhdl->role == OPT_MODE_HOST) hcd_int_enable(TUH_OPT_RHPORT); -#endif -} - -static inline osal_queue_t osal_queue_create(osal_queue_def_t* qdef) -{ - tu_fifo_clear(&qdef->ff); - return (osal_queue_t) qdef; -} - -static inline bool osal_queue_receive(osal_queue_t qhdl, void* data) -{ - _osal_q_lock(qhdl); - bool success = tu_fifo_read(&qhdl->ff, data); - _osal_q_unlock(qhdl); - - return success; -} - -static inline bool osal_queue_send(osal_queue_t qhdl, void const * data, bool in_isr) -{ - if (!in_isr) { - _osal_q_lock(qhdl); - } - - bool success = tu_fifo_write(&qhdl->ff, data); - - if (!in_isr) { - _osal_q_unlock(qhdl); - } - - TU_ASSERT(success); - - return success; -} - -static inline bool osal_queue_empty(osal_queue_t qhdl) -{ - // Skip queue lock/unlock since this function is primarily called - // with interrupt disabled before going into low power mode - return tu_fifo_empty(&qhdl->ff); -} - -#ifdef __cplusplus - } -#endif - -#endif /* _TUSB_OSAL_NONE_H_ */ diff --git a/cores/nRF5/TinyUSB/Adafruit_TinyUSB_ArduinoCore/tinyusb/src/portable/analog/max3421/hcd_max3421.c b/cores/nRF5/TinyUSB/Adafruit_TinyUSB_ArduinoCore/tinyusb/src/portable/analog/max3421/hcd_max3421.c new file mode 100644 index 0000000..971dbd6 --- /dev/null +++ b/cores/nRF5/TinyUSB/Adafruit_TinyUSB_ArduinoCore/tinyusb/src/portable/analog/max3421/hcd_max3421.c @@ -0,0 +1,1107 @@ +/* + * The MIT License (MIT) + * + * Copyright (c) 2023 Ha Thach (tinyusb.org) + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + * This file is part of the TinyUSB stack. + */ + +#include "tusb_option.h" + +#if CFG_TUH_ENABLED && defined(CFG_TUH_MAX3421) && CFG_TUH_MAX3421 + +#include "host/hcd.h" +#include "host/usbh.h" +#include "host/usbh_pvt.h" + +//--------------------------------------------------------------------+ +// +//--------------------------------------------------------------------+ + +// Command format is +// Reg [7:3] | 0 [2] | Dir [1] | Ack [0] + +enum { + CMDBYTE_WRITE = 0x02, +}; + +enum { + RCVVFIFO_ADDR = 1u << 3, // 0x08 + SNDFIFO_ADDR = 2u << 3, // 0x10 + SUDFIFO_ADDR = 4u << 3, // 0x20 + RCVBC_ADDR = 6u << 3, // 0x30 + SNDBC_ADDR = 7u << 3, // 0x38 + USBIRQ_ADDR = 13u << 3, // 0x68 + USBIEN_ADDR = 14u << 3, // 0x70 + USBCTL_ADDR = 15u << 3, // 0x78 + CPUCTL_ADDR = 16u << 3, // 0x80 + PINCTL_ADDR = 17u << 3, // 0x88 + REVISION_ADDR = 18u << 3, // 0x90 + // 19 is not used + IOPINS1_ADDR = 20u << 3, // 0xA0 + IOPINS2_ADDR = 21u << 3, // 0xA8 + GPINIRQ_ADDR = 22u << 3, // 0xB0 + GPINIEN_ADDR = 23u << 3, // 0xB8 + GPINPOL_ADDR = 24u << 3, // 0xC0 + HIRQ_ADDR = 25u << 3, // 0xC8 + HIEN_ADDR = 26u << 3, // 0xD0 + MODE_ADDR = 27u << 3, // 0xD8 + PERADDR_ADDR = 28u << 3, // 0xE0 + HCTL_ADDR = 29u << 3, // 0xE8 + HXFR_ADDR = 30u << 3, // 0xF0 + HRSL_ADDR = 31u << 3, // 0xF8 +}; + +enum { + USBIRQ_OSCOK_IRQ = 1u << 0, + USBIRQ_NOVBUS_IRQ = 1u << 5, + USBIRQ_VBUS_IRQ = 1u << 6, +}; + +enum { + USBCTL_PWRDOWN = 1u << 4, + USBCTL_CHIPRES = 1u << 5, +}; + +enum { + CPUCTL_IE = 1u << 0, + CPUCTL_PULSEWID0 = 1u << 6, + CPUCTL_PULSEWID1 = 1u << 7, +}; + +enum { + PINCTL_GPXA = 1u << 0, + PINCTL_GPXB = 1u << 1, + PINCTL_POSINT = 1u << 2, + PINCTL_INTLEVEL = 1u << 3, + PINCTL_FDUPSPI = 1u << 4, +}; + +enum { + HIRQ_BUSEVENT_IRQ = 1u << 0, + HIRQ_RWU_IRQ = 1u << 1, + HIRQ_RCVDAV_IRQ = 1u << 2, + HIRQ_SNDBAV_IRQ = 1u << 3, + HIRQ_SUSDN_IRQ = 1u << 4, + HIRQ_CONDET_IRQ = 1u << 5, + HIRQ_FRAME_IRQ = 1u << 6, + HIRQ_HXFRDN_IRQ = 1u << 7, +}; + +enum { + MODE_HOST = 1u << 0, + MODE_LOWSPEED = 1u << 1, + MODE_HUBPRE = 1u << 2, + MODE_SOFKAENAB = 1u << 3, + MODE_SEPIRQ = 1u << 4, + MODE_DELAYISO = 1u << 5, + MODE_DMPULLDN = 1u << 6, + MODE_DPPULLDN = 1u << 7, +}; + +enum { + HCTL_BUSRST = 1u << 0, + HCTL_FRMRST = 1u << 1, + HCTL_SAMPLEBUS = 1u << 2, + HCTL_SIGRSM = 1u << 3, + HCTL_RCVTOG0 = 1u << 4, + HCTL_RCVTOG1 = 1u << 5, + HCTL_SNDTOG0 = 1u << 6, + HCTL_SNDTOG1 = 1u << 7, +}; + +enum { + HXFR_EPNUM_MASK = 0x0f, + HXFR_SETUP = 1u << 4, + HXFR_OUT_NIN = 1u << 5, + HXFR_ISO = 1u << 6, + HXFR_HS = 1u << 7, +}; + +enum { + HRSL_RESULT_MASK = 0x0f, + HRSL_RCVTOGRD = 1u << 4, + HRSL_SNDTOGRD = 1u << 5, + HRSL_KSTATUS = 1u << 6, + HRSL_JSTATUS = 1u << 7, +}; + +enum { + HRSL_SUCCESS = 0, + HRSL_BUSY, + HRSL_BAD_REQ, + HRSL_UNDEF, + HRSL_NAK, + HRSL_STALL, + HRSL_TOG_ERR, + HRSL_WRONG_PID, + HRSL_BAD_BYTECOUNT, + HRSL_PID_ERR, + HRSL_PKT_ERR, + HRSL_CRC_ERR, + HRSL_K_ERR, + HRSL_J_ERR, + HRSL_TIMEOUT, + HRSL_BABBLE, +}; + +enum { + DEFAULT_HIEN = HIRQ_CONDET_IRQ | HIRQ_FRAME_IRQ | HIRQ_HXFRDN_IRQ | HIRQ_RCVDAV_IRQ +}; + +enum { + MAX_NAK_DEFAULT = 1 // Number of NAK per endpoint per usb frame to save CPU/SPI bus usage +}; + +enum { + EP_STATE_IDLE = 0, + EP_STATE_COMPLETE = 1, + EP_STATE_ABORTING = 2, + EP_STATE_ATTEMPT_1 = 3, // Number of attempts to transfer in a frame. Incremented after each NAK + EP_STATE_ATTEMPT_MAX = 15 +}; + +//--------------------------------------------------------------------+ +// +//--------------------------------------------------------------------+ + +typedef struct TU_ATTR_PACKED { + uint8_t ep_num : 4; + uint8_t is_setup : 1; + uint8_t is_out : 1; + uint8_t is_iso : 1; +} hxfr_bm_t; + +TU_VERIFY_STATIC(sizeof(hxfr_bm_t) == 1, "size is not correct"); + +typedef struct { + uint8_t daddr; + + union { + hxfr_bm_t hxfr_bm; + uint8_t hxfr; + }; + + struct TU_ATTR_PACKED { + uint8_t state : 4; + uint8_t data_toggle : 1; + uint16_t packet_size : 11; + }; + + uint16_t total_len; + uint16_t xferred_len; + uint8_t* buf; +} max3421_ep_t; + +TU_VERIFY_STATIC(sizeof(max3421_ep_t) == 12, "size is not correct"); + +typedef struct { + volatile uint16_t frame_count; + + // cached register + uint8_t sndbc; + uint8_t hirq; + uint8_t hien; + uint8_t mode; + uint8_t peraddr; + union { + hxfr_bm_t hxfr_bm; + uint8_t hxfr; + }; + + // owner of data in SNDFIFO, for retrying NAKed without re-writing to FIFO + struct { + uint8_t daddr; + uint8_t hxfr; + }sndfifo_owner; + + bool busy_lock; // busy transferring + +#if OSAL_MUTEX_REQUIRED + OSAL_MUTEX_DEF(spi_mutexdef); + osal_mutex_t spi_mutex; +#endif + + max3421_ep_t ep[CFG_TUH_MAX3421_ENDPOINT_TOTAL]; // [0] is reserved for addr0 +} max3421_data_t; + +static max3421_data_t _hcd_data; + +// max NAK before giving up in a frame. 0 means infinite NAKs +static tuh_configure_max3421_t _tuh_cfg = { + .max_nak = MAX_NAK_DEFAULT, + .cpuctl = 0, // default: INT pulse width = 10.6 us + .pinctl = 0, // default: negative edge interrupt +}; + +//--------------------------------------------------------------------+ +// SPI Commands and Helper +//--------------------------------------------------------------------+ + +#define reg_read tuh_max3421_reg_read +#define reg_write tuh_max3421_reg_write + +static void max3421_spi_lock(uint8_t rhport, bool in_isr) { + // disable interrupt and mutex lock (for pre-emptive RTOS) if not in_isr + if (!in_isr) { + (void) osal_mutex_lock(_hcd_data.spi_mutex, OSAL_TIMEOUT_WAIT_FOREVER); + tuh_max3421_int_api(rhport, false); + } + + // assert CS + tuh_max3421_spi_cs_api(rhport, true); +} + +static void max3421_spi_unlock(uint8_t rhport, bool in_isr) { + // de-assert CS + tuh_max3421_spi_cs_api(rhport, false); + + // mutex unlock and re-enable interrupt + if (!in_isr) { + tuh_max3421_int_api(rhport, true); + (void) osal_mutex_unlock(_hcd_data.spi_mutex); + } +} + +uint8_t tuh_max3421_reg_read(uint8_t rhport, uint8_t reg, bool in_isr) { + uint8_t tx_buf[2] = {reg, 0}; + uint8_t rx_buf[2] = {0, 0}; + + max3421_spi_lock(rhport, in_isr); + bool ret = tuh_max3421_spi_xfer_api(rhport, tx_buf, rx_buf, 2); + max3421_spi_unlock(rhport, in_isr); + + _hcd_data.hirq = rx_buf[0]; + return ret ? rx_buf[1] : 0; +} + +bool tuh_max3421_reg_write(uint8_t rhport, uint8_t reg, uint8_t data, bool in_isr) { + uint8_t tx_buf[2] = {reg | CMDBYTE_WRITE, data}; + uint8_t rx_buf[2] = {0, 0}; + + max3421_spi_lock(rhport, in_isr); + bool ret = tuh_max3421_spi_xfer_api(rhport, tx_buf, rx_buf, 2); + max3421_spi_unlock(rhport, in_isr); + + // HIRQ register since we are in full-duplex mode + _hcd_data.hirq = rx_buf[0]; + + return ret; +} + +//-------------------------------------------------------------------- +// Register helper +//-------------------------------------------------------------------- +TU_ATTR_ALWAYS_INLINE static inline void hirq_write(uint8_t rhport, uint8_t data, bool in_isr) { + reg_write(rhport, HIRQ_ADDR, data, in_isr); + // HIRQ write 1 is clear + _hcd_data.hirq &= (uint8_t) ~data; +} + +TU_ATTR_ALWAYS_INLINE static inline void hien_write(uint8_t rhport, uint8_t data, bool in_isr) { + _hcd_data.hien = data; + reg_write(rhport, HIEN_ADDR, data, in_isr); +} + +TU_ATTR_ALWAYS_INLINE static inline void mode_write(uint8_t rhport, uint8_t data, bool in_isr) { + _hcd_data.mode = data; + reg_write(rhport, MODE_ADDR, data, in_isr); +} + +TU_ATTR_ALWAYS_INLINE static inline void peraddr_write(uint8_t rhport, uint8_t data, bool in_isr) { + if (_hcd_data.peraddr == data) { + return; // no need to change address + } + + _hcd_data.peraddr = data; + reg_write(rhport, PERADDR_ADDR, data, in_isr); +} + +TU_ATTR_ALWAYS_INLINE static inline void hxfr_write(uint8_t rhport, uint8_t data, bool in_isr) { + _hcd_data.hxfr = data; + reg_write(rhport, HXFR_ADDR, data, in_isr); +} + +TU_ATTR_ALWAYS_INLINE static inline void sndbc_write(uint8_t rhport, uint8_t data, bool in_isr) { + _hcd_data.sndbc = data; + reg_write(rhport, SNDBC_ADDR, data, in_isr); +} + +//-------------------------------------------------------------------- +// FIFO access (receive, send, setup) +//-------------------------------------------------------------------- +static void hwfifo_write(uint8_t rhport, uint8_t reg, const uint8_t* buffer, uint8_t len, bool in_isr) { + uint8_t hirq; + reg |= CMDBYTE_WRITE; + + max3421_spi_lock(rhport, in_isr); + + tuh_max3421_spi_xfer_api(rhport, ®, &hirq, 1); + _hcd_data.hirq = hirq; + tuh_max3421_spi_xfer_api(rhport, buffer, NULL, len); + + max3421_spi_unlock(rhport, in_isr); +} + +// Write to SNDFIFO if len > 0 and update SNDBC +TU_ATTR_ALWAYS_INLINE static inline void hwfifo_send(uint8_t rhport, const uint8_t* buffer, uint8_t len, bool in_isr) { + if (len) { + hwfifo_write(rhport, SNDFIFO_ADDR, buffer, len, in_isr); + } + sndbc_write(rhport, len, in_isr); +} + +TU_ATTR_ALWAYS_INLINE static inline void hwfifo_setup(uint8_t rhport, const uint8_t* buffer, bool in_isr) { + hwfifo_write(rhport, SUDFIFO_ADDR, buffer, 8, in_isr); +} + +static void hwfifo_receive(uint8_t rhport, uint8_t * buffer, uint16_t len, bool in_isr) { + uint8_t hirq; + const uint8_t reg = RCVVFIFO_ADDR; + + max3421_spi_lock(rhport, in_isr); + + tuh_max3421_spi_xfer_api(rhport, ®, &hirq, 1); + _hcd_data.hirq = hirq; + tuh_max3421_spi_xfer_api(rhport, NULL, buffer, len); + + max3421_spi_unlock(rhport, in_isr); +} + +//--------------------------------------------------------------------+ +// Endpoint helper +//--------------------------------------------------------------------+ + +static max3421_ep_t* find_ep_not_addr0(uint8_t daddr, uint8_t ep_num, uint8_t ep_dir) { + const uint8_t is_out = 1-ep_dir; + for(size_t i=1; idaddr && ep_num == ep->hxfr_bm.ep_num && (ep_num == 0 || is_out == ep->hxfr_bm.is_out)) { + return ep; + } + } + + return NULL; +} + +// daddr = 0 and ep_num = 0 means find a free (allocate) endpoint +TU_ATTR_ALWAYS_INLINE static inline max3421_ep_t * allocate_ep(void) { + return find_ep_not_addr0(0, 0, 0); +} + +TU_ATTR_ALWAYS_INLINE static inline max3421_ep_t * find_opened_ep(uint8_t daddr, uint8_t ep_num, uint8_t ep_dir) { + if (daddr == 0 && ep_num == 0) { + return &_hcd_data.ep[0]; + }else{ + return find_ep_not_addr0(daddr, ep_num, ep_dir); + } +} + +// free all endpoints belong to device address +static void free_ep(uint8_t daddr) { + for (size_t i=1; idaddr == daddr) { + tu_memclr(ep, sizeof(max3421_ep_t)); + } + } +} + +// Check if endpoint has a queued transfer and not reach max NAK in this frame +TU_ATTR_ALWAYS_INLINE static inline bool is_ep_pending(max3421_ep_t const * ep) { + uint8_t const state = ep->state; + return ep->packet_size && (state >= EP_STATE_ATTEMPT_1) && + (_tuh_cfg.max_nak == 0 || state < EP_STATE_ATTEMPT_1 + _tuh_cfg.max_nak); +} + +// Find the next pending endpoint using round-robin scheduling, starting from next endpoint. +// return NULL if not found +// TODO respect interrupt endpoint's interval +static max3421_ep_t * find_next_pending_ep(max3421_ep_t * cur_ep) { + size_t const idx = (size_t) (cur_ep - _hcd_data.ep); + + // starting from next endpoint + for (size_t i = idx + 1; i < CFG_TUH_MAX3421_ENDPOINT_TOTAL; i++) { + max3421_ep_t* ep = &_hcd_data.ep[i]; + if (is_ep_pending(ep)) { + return ep; + } + } + + // wrap around including current endpoint + for (size_t i = 0; i <= idx; i++) { + max3421_ep_t* ep = &_hcd_data.ep[i]; + if (is_ep_pending(ep)) { + return ep; + } + } + + return NULL; +} + +//--------------------------------------------------------------------+ +// Controller API +//--------------------------------------------------------------------+ + +// optional hcd configuration, called by tuh_configure() +bool hcd_configure(uint8_t rhport, uint32_t cfg_id, const void* cfg_param) { + (void) rhport; + TU_VERIFY(cfg_id == TUH_CFGID_MAX3421 && cfg_param != NULL); + + tuh_configure_param_t const* cfg = (tuh_configure_param_t const*) cfg_param; + _tuh_cfg = cfg->max3421; + _tuh_cfg.max_nak = tu_min8(_tuh_cfg.max_nak, EP_STATE_ATTEMPT_MAX-EP_STATE_ATTEMPT_1); + return true; +} + +// Initialize controller to host mode +bool hcd_init(uint8_t rhport, const tusb_rhport_init_t* rh_init) { + (void) rh_init; + + tuh_max3421_int_api(rhport, false); + + TU_LOG2_INT(sizeof(max3421_ep_t)); + TU_LOG2_INT(sizeof(max3421_data_t)); + TU_LOG2_INT(offsetof(max3421_data_t, ep)); + + tu_memclr(&_hcd_data, sizeof(_hcd_data)); + _hcd_data.peraddr = 0xff; // invalid + +#if OSAL_MUTEX_REQUIRED + _hcd_data.spi_mutex = osal_mutex_create(&_hcd_data.spi_mutexdef); +#endif + + // NOTE: driver does not seem to work without nRST pin signal + + // full duplex, interrupt negative edge + reg_write(rhport, PINCTL_ADDR, _tuh_cfg.pinctl | PINCTL_FDUPSPI, false); + + // v1 is 0x01, v2 is 0x12, v3 is 0x13 + // Note: v1 and v2 has host OUT errata whose workaround is not implemented in this driver + uint8_t const revision = reg_read(rhport, REVISION_ADDR, false); + TU_LOG2_HEX(revision); + TU_ASSERT(revision == 0x01 || revision == 0x12 || revision == 0x13, false); + + // reset + reg_write(rhport, USBCTL_ADDR, USBCTL_CHIPRES, false); + reg_write(rhport, USBCTL_ADDR, 0, false); + while( !(reg_read(rhport, USBIRQ_ADDR, false) & USBIRQ_OSCOK_IRQ) ) { + // wait for oscillator to stabilize + } + + // Mode: Host and DP/DM pull down + mode_write(rhport, MODE_DPPULLDN | MODE_DMPULLDN | MODE_HOST, false); + + // frame reset & bus reset, this will trigger CONDET IRQ if device is already connected + reg_write(rhport, HCTL_ADDR, HCTL_BUSRST | HCTL_FRMRST, false); + + // clear all previously pending IRQ + hirq_write(rhport, 0xff, false); + + // Enable IRQ + hien_write(rhport, DEFAULT_HIEN, false); + + tuh_max3421_int_api(rhport, true); + + // Enable Interrupt pin + reg_write(rhport, CPUCTL_ADDR, _tuh_cfg.cpuctl | CPUCTL_IE, false); + + return true; +} + +bool hcd_deinit(uint8_t rhport) { + (void) rhport; + + // disable interrupt + tuh_max3421_int_api(rhport, false); + + // reset max3421 and power down + reg_write(rhport, USBCTL_ADDR, USBCTL_CHIPRES, false); + reg_write(rhport, USBCTL_ADDR, USBCTL_PWRDOWN, false); + + #if OSAL_MUTEX_REQUIRED + osal_mutex_delete(_hcd_data.spi_mutex); + _hcd_data.spi_mutex = NULL; + #endif + + return true; +} + +// Enable USB interrupt +// Not actually enable GPIO interrupt, just set variable to prevent handler to process +void hcd_int_enable (uint8_t rhport) { + tuh_max3421_int_api(rhport, true); +} + +// Disable USB interrupt +// Not actually disable GPIO interrupt, just set variable to prevent handler to process +void hcd_int_disable(uint8_t rhport) { + tuh_max3421_int_api(rhport, false); +} + +// Get frame number (1ms) +uint32_t hcd_frame_number(uint8_t rhport) { + (void) rhport; + return (uint32_t ) _hcd_data.frame_count; +} + +//--------------------------------------------------------------------+ +// Port API +//--------------------------------------------------------------------+ + +// Get the current connect status of roothub port +bool hcd_port_connect_status(uint8_t rhport) { + (void) rhport; + return (_hcd_data.mode & MODE_SOFKAENAB) ? true : false; +} + +// Reset USB bus on the port. Return immediately, bus reset sequence may not be complete. +// Some port would require hcd_port_reset_end() to be invoked after 10ms to complete the reset sequence. +void hcd_port_reset(uint8_t rhport) { + reg_write(rhport, HCTL_ADDR, HCTL_BUSRST, false); +} + +// Complete bus reset sequence, may be required by some controllers +void hcd_port_reset_end(uint8_t rhport) { + reg_write(rhport, HCTL_ADDR, 0, false); +} + +// Get port link speed +tusb_speed_t hcd_port_speed_get(uint8_t rhport) { + (void) rhport; + return (_hcd_data.mode & MODE_LOWSPEED) ? TUSB_SPEED_LOW : TUSB_SPEED_FULL; +} + +// HCD closes all opened endpoints belong to this device +void hcd_device_close(uint8_t rhport, uint8_t dev_addr) { + (void) rhport; + (void) dev_addr; +} + +//--------------------------------------------------------------------+ +// Endpoints API +//--------------------------------------------------------------------+ + +// Open an endpoint +bool hcd_edpt_open(uint8_t rhport, uint8_t daddr, tusb_desc_endpoint_t const * ep_desc) { + (void) rhport; + + uint8_t const ep_num = tu_edpt_number(ep_desc->bEndpointAddress); + tusb_dir_t const ep_dir = tu_edpt_dir(ep_desc->bEndpointAddress); + + max3421_ep_t * ep; + if (daddr == 0 && ep_num == 0) { + ep = &_hcd_data.ep[0]; + }else { + if (NULL != find_ep_not_addr0(daddr, ep_num, ep_dir)) { + return true; // already opened + } + ep = allocate_ep(); + TU_ASSERT(ep); + ep->daddr = daddr; + ep->hxfr_bm.ep_num = (uint8_t) (ep_num & 0x0f); + ep->hxfr_bm.is_out = (ep_dir == TUSB_DIR_OUT) ? 1 : 0; + ep->hxfr_bm.is_iso = (TUSB_XFER_ISOCHRONOUS == ep_desc->bmAttributes.xfer) ? 1 : 0; + } + + ep->packet_size = (uint16_t) (tu_edpt_packet_size(ep_desc) & 0x7ff); + + return true; +} + +bool hcd_edpt_close(uint8_t rhport, uint8_t daddr, uint8_t ep_addr) { + (void) rhport; + uint8_t const ep_num = tu_edpt_number(ep_addr); + tusb_dir_t const ep_dir = tu_edpt_dir(ep_addr); + max3421_ep_t * ep = find_ep_not_addr0(daddr, ep_num, ep_dir); + + if (!ep) { + return false; // not opened + } + + tu_memclr(ep, sizeof(max3421_ep_t)); + + return true; +} + +/* The microcontroller repeatedly writes the SNDFIFO register R2 to load the FIFO with up to 64 data bytes. + * Then the microcontroller writes the SNDBC register, which this does three things: + * 1. Tells the MAX3421E SIE (Serial Interface Engine) how many bytes in the FIFO to send. + * 2. Connects the SNDFIFO and SNDBC register to the USB logic for USB transmission. + * 3. Clears the SNDBAVIRQ interrupt flag. If the second FIFO is available for µC loading, the SNDBAVIRQ immediately re-asserts. + + +-----------+ + --->| SNDBC-A | + / | SNDFIFO-A | + / +-----------+ + +------+ +-------------+ / +----------+ + | MCU |------>| R2: SNDFIFO |---- << Write R7 Flip >> ---| MAX3241E | + |(hcd) | | R7: SNDBC | / | SIE | + +------+ +-------------+ / +----------+ + +-----------+ / + | SNDBC-B | / + | SNDFIFO-B |<--- + +-----------+ + Note: xact_out() is called when starting a new transfer, continue a transfer (isr) or retry a transfer (NAK) + For NAK retry, we do not need to write to FIFO or SNDBC register again. +*/ +static void xact_out(uint8_t rhport, max3421_ep_t *ep, bool switch_ep, bool in_isr) { + // Page 12: Programming BULK-OUT Transfers + // TODO: double buffering for ISO transfer + if (switch_ep) { + peraddr_write(rhport, ep->daddr, in_isr); + const uint8_t hctl = (ep->data_toggle ? HCTL_SNDTOG1 : HCTL_SNDTOG0); + reg_write(rhport, HCTL_ADDR, hctl, in_isr); + } + + // Only write to sndfifo and sdnbc register if it is not a NAKed retry + if (!(ep->daddr == _hcd_data.sndfifo_owner.daddr && ep->hxfr == _hcd_data.sndfifo_owner.hxfr)) { + // skip SNDBAV IRQ check, overwrite sndfifo if needed + const uint8_t xact_len = (uint8_t) tu_min16(ep->total_len - ep->xferred_len, ep->packet_size); + hwfifo_send(rhport, ep->buf, xact_len, in_isr); + } + _hcd_data.sndfifo_owner.daddr = ep->daddr; + _hcd_data.sndfifo_owner.hxfr = ep->hxfr; + + hxfr_write(rhport, ep->hxfr, in_isr); +} + +static void xact_in(uint8_t rhport, max3421_ep_t *ep, bool switch_ep, bool in_isr) { + // Page 13: Programming BULK-IN Transfers + if (switch_ep) { + peraddr_write(rhport, ep->daddr, in_isr); + + uint8_t const hctl = (ep->data_toggle ? HCTL_RCVTOG1 : HCTL_RCVTOG0); + reg_write(rhport, HCTL_ADDR, hctl, in_isr); + } + + hxfr_write(rhport, ep->hxfr, in_isr); +} + +static void xact_setup(uint8_t rhport, max3421_ep_t *ep, bool in_isr) { + peraddr_write(rhport, ep->daddr, in_isr); + hwfifo_setup(rhport, ep->buf, in_isr); + hxfr_write(rhport, HXFR_SETUP, in_isr); +} + +static void xact_generic(uint8_t rhport, max3421_ep_t *ep, bool switch_ep, bool in_isr) { + if (ep->hxfr_bm.ep_num == 0 ) { + // setup + if (ep->hxfr_bm.is_setup) { + xact_setup(rhport, ep, in_isr); + return; + } + + // status + if (ep->buf == NULL || ep->total_len == 0) { + const uint8_t hxfr = (uint8_t) (HXFR_HS | (ep->hxfr & HXFR_OUT_NIN)); + peraddr_write(rhport, ep->daddr, in_isr); + hxfr_write(rhport, hxfr, in_isr); + return; + } + } + + if (ep->hxfr_bm.is_out) { + xact_out(rhport, ep, switch_ep, in_isr); + }else { + xact_in(rhport, ep, switch_ep, in_isr); + } +} + +// Submit a transfer, when complete hcd_event_xfer_complete() must be invoked +bool hcd_edpt_xfer(uint8_t rhport, uint8_t daddr, uint8_t ep_addr, uint8_t * buffer, uint16_t buflen) { + const uint8_t ep_num = tu_edpt_number(ep_addr); + const uint8_t ep_dir = (uint8_t) tu_edpt_dir(ep_addr); + max3421_ep_t* ep = find_opened_ep(daddr, ep_num, ep_dir); + TU_VERIFY(ep); + + if (ep_num == 0) { + // control transfer can switch direction + ep->hxfr_bm.is_out = ep_dir ? 0 : 1; + ep->hxfr_bm.is_setup = 0; + ep->data_toggle = 1; + } + + ep->buf = buffer; + ep->total_len = buflen; + ep->xferred_len = 0; + ep->state = EP_STATE_ATTEMPT_1; + + bool has_xfer = false; + + usbh_spin_lock(false); + if (!_hcd_data.busy_lock) { + _hcd_data.busy_lock = true; + has_xfer = true; + } + usbh_spin_unlock(false); + + // carry out transfer if not busy + if (has_xfer) { + xact_generic(rhport, ep, true, false); + } + + return true; +} + +bool hcd_edpt_abort_xfer(uint8_t rhport, uint8_t daddr, uint8_t ep_addr) { + uint8_t const ep_num = tu_edpt_number(ep_addr); + uint8_t const ep_dir = (uint8_t) tu_edpt_dir(ep_addr); + max3421_ep_t* ep = find_opened_ep(daddr, ep_num, ep_dir); + TU_VERIFY(ep); + + if (EP_STATE_ATTEMPT_1 <= ep->state && ep->state < EP_STATE_ATTEMPT_MAX) { + hcd_int_disable(rhport); + ep->state = EP_STATE_ABORTING; + hcd_int_enable(rhport); + } + + return true; +} + +// Submit a special transfer to send 8-byte Setup Packet, when complete hcd_event_xfer_complete() must be invoked +bool hcd_setup_send(uint8_t rhport, uint8_t daddr, uint8_t const setup_packet[8]) { + (void) rhport; + + max3421_ep_t* ep = find_opened_ep(daddr, 0, 0); + TU_ASSERT(ep); + + ep->hxfr_bm.is_out = 1; + ep->hxfr_bm.is_setup = 1; + ep->buf = (uint8_t*)(uintptr_t) setup_packet; + ep->total_len = 8; + ep->xferred_len = 0; + ep->state = EP_STATE_ATTEMPT_1; + + bool has_xfer = false; + + usbh_spin_lock(false); + if (!_hcd_data.busy_lock) { + _hcd_data.busy_lock = true; + has_xfer = true; + } + usbh_spin_unlock(false); + + // carry out transfer if not busy + if (has_xfer) { + xact_setup(rhport, ep, false); + } + + return true; +} + +// clear stall, data toggle is also reset to DATA0 +bool hcd_edpt_clear_stall(uint8_t rhport, uint8_t dev_addr, uint8_t ep_addr) { + (void) rhport; + (void) dev_addr; + (void) ep_addr; + + return false; +} + +//--------------------------------------------------------------------+ +// Interrupt Handler +//--------------------------------------------------------------------+ + +static void handle_connect_irq(uint8_t rhport, bool in_isr) { + uint8_t const hrsl = reg_read(rhport, HRSL_ADDR, in_isr); + uint8_t const jk = hrsl & (HRSL_JSTATUS | HRSL_KSTATUS); + + uint8_t new_mode = MODE_DPPULLDN | MODE_DMPULLDN | MODE_HOST; + TU_LOG2_HEX(jk); + + switch(jk) { + case 0x00: // SEO is disconnected + case (HRSL_JSTATUS | HRSL_KSTATUS): // SE1 is illegal + mode_write(rhport, new_mode, in_isr); + + // port reset anyway, this will help to stable bus signal for next connection + reg_write(rhport, HCTL_ADDR, HCTL_BUSRST, in_isr); + hcd_event_device_remove(rhport, in_isr); + reg_write(rhport, HCTL_ADDR, 0, in_isr); + break; + + default: { + // Bus Reset also cause CONDET IRQ, skip if we are already connected and doing bus reset + if ((_hcd_data.hirq & HIRQ_BUSEVENT_IRQ) && (_hcd_data.mode & MODE_SOFKAENAB)) { + break; + } + + // Low speed if (LS = 1 and J-state) or (LS = 0 and K-State) + // However, since we are always in full speed mode, we can just check J-state + if (jk == HRSL_KSTATUS) { + new_mode |= MODE_LOWSPEED; + TU_LOG3("Low speed\r\n"); + }else { + TU_LOG3("Full speed\r\n"); + } + new_mode |= MODE_SOFKAENAB; + mode_write(rhport, new_mode, in_isr); + + // FIXME multiple MAX3421 rootdevice address is not 1 + uint8_t const daddr = 1; + free_ep(daddr); + + hcd_event_device_attach(rhport, in_isr); + break; + } + } +} + +static void xfer_complete_isr(uint8_t rhport, max3421_ep_t *ep, xfer_result_t result, uint8_t hrsl, bool in_isr) { + const uint8_t ep_dir = 1 - ep->hxfr_bm.is_out; + const uint8_t ep_addr = tu_edpt_addr(ep->hxfr_bm.ep_num, ep_dir); + + // save data toggle + if (ep_dir) { + ep->data_toggle = (hrsl & HRSL_RCVTOGRD) ? 1u : 0u; + }else { + ep->data_toggle = (hrsl & HRSL_SNDTOGRD) ? 1u : 0u; + } + + ep->state = EP_STATE_IDLE; + hcd_event_xfer_complete(ep->daddr, ep_addr, ep->xferred_len, result, in_isr); + + // Find next pending endpoint + max3421_ep_t * next_ep = find_next_pending_ep(ep); + if (next_ep) { + xact_generic(rhport, next_ep, true, in_isr); + }else { + // no more pending + usbh_spin_lock(in_isr); + _hcd_data.busy_lock = false; + usbh_spin_unlock(in_isr); + } +} + +static void handle_xfer_done(uint8_t rhport, bool in_isr) { + const uint8_t hrsl = reg_read(rhport, HRSL_ADDR, in_isr); + const uint8_t hresult = hrsl & HRSL_RESULT_MASK; + const uint8_t ep_num = _hcd_data.hxfr_bm.ep_num; + const uint8_t hxfr_type = _hcd_data.hxfr & 0xf0; + const uint8_t ep_dir = ((hxfr_type & HXFR_SETUP) || (hxfr_type & HXFR_OUT_NIN)) ? 0 : 1; + + max3421_ep_t *ep = find_opened_ep(_hcd_data.peraddr, ep_num, ep_dir); + TU_VERIFY(ep, ); + + xfer_result_t xfer_result; + switch(hresult) { + case HRSL_NAK: + if (ep->state == EP_STATE_ABORTING) { + ep->state = EP_STATE_IDLE; + } else { + if (ep_num == 0) { + // control endpoint -> retry immediately and return + hxfr_write(rhport, _hcd_data.hxfr, in_isr); + return; + } + if (EP_STATE_ATTEMPT_1 <= ep->state && ep->state < EP_STATE_ATTEMPT_MAX) { + ep->state++; + } + } + + max3421_ep_t * next_ep = find_next_pending_ep(ep); + if (ep == next_ep) { + // this endpoint is only one pending -> retry immediately + hxfr_write(rhport, _hcd_data.hxfr, in_isr); + } else if (next_ep) { + // switch to next pending endpoint + xact_generic(rhport, next_ep, true, in_isr); + } else { + // no more pending in this frame -> clear busy + usbh_spin_lock(in_isr); + _hcd_data.busy_lock = false; + usbh_spin_unlock(in_isr); + } + return; + + case HRSL_BAD_REQ: + // occurred when initialized without any pending transfer. Skip for now + return; + + case HRSL_SUCCESS: + xfer_result = XFER_RESULT_SUCCESS; + break; + + case HRSL_STALL: + xfer_result = XFER_RESULT_STALLED; + break; + + default: + TU_LOG3("HRSL: %02X\r\n", hrsl); + xfer_result = XFER_RESULT_FAILED; + break; + } + + if (xfer_result != XFER_RESULT_SUCCESS) { + xfer_complete_isr(rhport, ep, xfer_result, hrsl, in_isr); + return; + } + + if (ep_dir) { + // IN transfer: fifo data is already received in RCVDAV IRQ + + // mark control handshake as complete + if (hxfr_type & HXFR_HS) { + ep->state = EP_STATE_COMPLETE; + } + + // short packet or all bytes transferred + if (ep->state == EP_STATE_COMPLETE) { + xfer_complete_isr(rhport, ep, xfer_result, hrsl, in_isr); + }else { + hxfr_write(rhport, _hcd_data.hxfr, in_isr); // more to transfer + } + } else { + // SETUP or OUT transfer + + // clear sndfifo owner since data is sent + _hcd_data.sndfifo_owner.daddr = 0xff; + _hcd_data.sndfifo_owner.hxfr = 0xff; + + uint8_t xact_len; + + if (hxfr_type & HXFR_SETUP) { + xact_len = 8; + } else if (hxfr_type & HXFR_HS) { + xact_len = 0; + } else { + xact_len = _hcd_data.sndbc; + } + + ep->xferred_len += xact_len; + ep->buf += xact_len; + + if (xact_len < ep->packet_size || ep->xferred_len >= ep->total_len) { + xfer_complete_isr(rhport, ep, xfer_result, hrsl, in_isr); + } else { + xact_out(rhport, ep, false, in_isr); // more to transfer + } + } +} + +#if CFG_TUSB_DEBUG >= 3 +void print_hirq(uint8_t hirq) { + TU_LOG3_HEX(hirq); + + if (hirq & HIRQ_HXFRDN_IRQ) TU_LOG3(" HXFRDN"); + if (hirq & HIRQ_FRAME_IRQ) TU_LOG3(" FRAME"); + if (hirq & HIRQ_CONDET_IRQ) TU_LOG3(" CONDET"); + if (hirq & HIRQ_SUSDN_IRQ) TU_LOG3(" SUSDN"); + if (hirq & HIRQ_SNDBAV_IRQ) TU_LOG3(" SNDBAV"); + if (hirq & HIRQ_RCVDAV_IRQ) TU_LOG3(" RCVDAV"); + if (hirq & HIRQ_RWU_IRQ) TU_LOG3(" RWU"); + if (hirq & HIRQ_BUSEVENT_IRQ) TU_LOG3(" BUSEVENT"); + + TU_LOG3("\r\n"); +} +#else + #define print_hirq(hirq) +#endif + +// Interrupt handler +void hcd_int_handler(uint8_t rhport, bool in_isr) { + uint8_t hirq = reg_read(rhport, HIRQ_ADDR, in_isr) & _hcd_data.hien; + if (!hirq) { return; } + // print_hirq(hirq); + + if (hirq & HIRQ_FRAME_IRQ) { + _hcd_data.frame_count++; + + // reset all endpoints nak counter, retry with 1st pending ep. + max3421_ep_t* ep_retry = NULL; + for (size_t i = 0; i < CFG_TUH_MAX3421_ENDPOINT_TOTAL; i++) { + max3421_ep_t* ep = &_hcd_data.ep[i]; + if (ep->packet_size && ep->state > EP_STATE_ATTEMPT_1) { + ep->state = EP_STATE_ATTEMPT_1; + + if (ep_retry == NULL) { + ep_retry = ep; + } + } + } + + // start usb transfer if not busy + if (ep_retry != NULL) { + bool has_xfer = false; + + usbh_spin_lock(in_isr); + if (!_hcd_data.busy_lock) { + _hcd_data.busy_lock = true; + has_xfer = true; + } + usbh_spin_unlock(in_isr); + + if (has_xfer) { + xact_generic(rhport, ep_retry, true, in_isr); + } + } + } + + if (hirq & HIRQ_CONDET_IRQ) { + handle_connect_irq(rhport, in_isr); + } + + // queue more transfer in handle_xfer_done() can cause hirq to be set again while external IRQ may not catch and/or + // not call this handler again. So we need to loop until all IRQ are cleared + while (hirq & (HIRQ_RCVDAV_IRQ | HIRQ_HXFRDN_IRQ)) { + if (hirq & HIRQ_RCVDAV_IRQ) { + const uint8_t ep_num = _hcd_data.hxfr_bm.ep_num; + max3421_ep_t* ep = find_opened_ep(_hcd_data.peraddr, ep_num, 1); + uint8_t xact_len = 0; + + // RCVDAV_IRQ can trigger 2 times (dual buffered) + while (hirq & HIRQ_RCVDAV_IRQ) { + const uint8_t rcvbc = reg_read(rhport, RCVBC_ADDR, in_isr); + xact_len = (uint8_t) tu_min16(rcvbc, ep->total_len - ep->xferred_len); + if (xact_len) { + hwfifo_receive(rhport, ep->buf, xact_len, in_isr); + ep->buf += xact_len; + ep->xferred_len += xact_len; + } + + // ack RCVDVAV IRQ + hirq_write(rhport, HIRQ_RCVDAV_IRQ, in_isr); + hirq = reg_read(rhport, HIRQ_ADDR, in_isr); + } + + if (xact_len < ep->packet_size || ep->xferred_len >= ep->total_len) { + ep->state = EP_STATE_COMPLETE; + } + } + + if (hirq & HIRQ_HXFRDN_IRQ) { + hirq_write(rhport, HIRQ_HXFRDN_IRQ, in_isr); + handle_xfer_done(rhport, in_isr); + } + + hirq = reg_read(rhport, HIRQ_ADDR, in_isr); + } + + // clear all interrupt except SNDBAV_IRQ (never clear by us). Note RCVDAV_IRQ, HXFRDN_IRQ already clear while processing + hirq &= (uint8_t) ~HIRQ_SNDBAV_IRQ; + if (hirq) { + hirq_write(rhport, hirq, in_isr); + } +} + +#endif diff --git a/cores/nRF5/TinyUSB/Adafruit_TinyUSB_ArduinoCore/tinyusb/src/portable/analog/max3421/hcd_max3421.h b/cores/nRF5/TinyUSB/Adafruit_TinyUSB_ArduinoCore/tinyusb/src/portable/analog/max3421/hcd_max3421.h new file mode 100644 index 0000000..4631fa2 --- /dev/null +++ b/cores/nRF5/TinyUSB/Adafruit_TinyUSB_ArduinoCore/tinyusb/src/portable/analog/max3421/hcd_max3421.h @@ -0,0 +1,63 @@ +/* + * The MIT License (MIT) + * + * Copyright (c) 2025 Ha Thach (tinyusb.org) + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + * This file is part of the TinyUSB stack. + */ +#ifndef TUSB_HCD_MAX3421_H +#define TUSB_HCD_MAX3421_H + +#ifdef __cplusplus + extern "C" { +#endif + +//--------------------------------------------------------------------+ +// SPI transfer API with MAX3421E are implemented by application +// - spi_cs_api(), spi_xfer_api(), int_api() +//--------------------------------------------------------------------+ + +// API to control MAX3421 SPI CS +extern void tuh_max3421_spi_cs_api(uint8_t rhport, bool active); + +// API to transfer data with MAX3421 SPI +// Either tx_buf or rx_buf can be NULL, which means transfer is write or read only +extern bool tuh_max3421_spi_xfer_api(uint8_t rhport, uint8_t const* tx_buf, uint8_t* rx_buf, size_t xfer_bytes); + +// API to enable/disable MAX3421 INTR pin interrupt +extern void tuh_max3421_int_api(uint8_t rhport, bool enabled); + +//--------------------------------------------------------------------+ +// API for read/write MAX3421 registers +// are implemented by this driver, can be used by application +//--------------------------------------------------------------------+ + +// API to read MAX3421's register. Implemented by TinyUSB +uint8_t tuh_max3421_reg_read(uint8_t rhport, uint8_t reg, bool in_isr); + +// API to write MAX3421's register. Implemented by TinyUSB +bool tuh_max3421_reg_write(uint8_t rhport, uint8_t reg, uint8_t data, bool in_isr); + +#ifdef __cplusplus + } +#endif + +#endif diff --git a/cores/nRF5/TinyUSB/Adafruit_TinyUSB_ArduinoCore/tinyusb/src/portable/dialog/da146xx/dcd_da146xx.c b/cores/nRF5/TinyUSB/Adafruit_TinyUSB_ArduinoCore/tinyusb/src/portable/dialog/da146xx/dcd_da146xx.c deleted file mode 100644 index 602836e..0000000 --- a/cores/nRF5/TinyUSB/Adafruit_TinyUSB_ArduinoCore/tinyusb/src/portable/dialog/da146xx/dcd_da146xx.c +++ /dev/null @@ -1,842 +0,0 @@ -/* - * The MIT License (MIT) - * - * Copyright (c) 2020 Jerzy Kasenberg - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - * - * This file is part of the TinyUSB stack. - */ - -#include "tusb_option.h" - -#if TUSB_OPT_DEVICE_ENABLED && CFG_TUSB_MCU == OPT_MCU_DA1469X - -#include "DA1469xAB.h" - -#include "device/dcd.h" - -/*------------------------------------------------------------------*/ -/* MACRO TYPEDEF CONSTANT ENUM - *------------------------------------------------------------------*/ - -// Since TinyUSB doesn't use SOF for now, and this interrupt too often (1ms interval) -// We disable SOF for now until needed later on -#define USE_SOF 0 - -#define EP_MAX 4 - -#define NFSR_NODE_RESET 0 -#define NFSR_NODE_RESUME 1 -#define NFSR_NODE_OPERATIONAL 2 -#define NFSR_NODE_SUSPEND 3 - -static TU_ATTR_ALIGNED(4) uint8_t _setup_packet[8]; - -typedef struct -{ - union - { - __IOM uint32_t epc_in; - __IOM uint32_t USB_EPC0_REG; /*!< (@ 0x00000080) Endpoint Control 0 Register */ - __IOM uint32_t USB_EPC1_REG; /*!< (@ 0x000000A0) Endpoint Control Register 1 */ - __IOM uint32_t USB_EPC3_REG; /*!< (@ 0x000000C0) Endpoint Control Register 3 */ - __IOM uint32_t USB_EPC5_REG; /*!< (@ 0x000000E0) Endpoint Control Register 5 */ - }; - union - { - __IOM uint32_t txd; - __IOM uint32_t USB_TXD0_REG; /*!< (@ 0x00000084) Transmit Data 0 Register */ - __IOM uint32_t USB_TXD1_REG; /*!< (@ 0x000000A4) Transmit Data Register 1 */ - __IOM uint32_t USB_TXD2_REG; /*!< (@ 0x000000C4) Transmit Data Register 2 */ - __IOM uint32_t USB_TXD3_REG; /*!< (@ 0x000000E4) Transmit Data Register 3 */ - }; - union - { - __IOM uint32_t txs; - __IOM uint32_t USB_TXS0_REG; /*!< (@ 0x00000088) Transmit Status 0 Register */ - __IOM uint32_t USB_TXS1_REG; /*!< (@ 0x000000A8) Transmit Status Register 1 */ - __IOM uint32_t USB_TXS2_REG; /*!< (@ 0x000000C8) Transmit Status Register 2 */ - __IOM uint32_t USB_TXS3_REG; /*!< (@ 0x000000E8) Transmit Status Register 3 */ - }; - union - { - __IOM uint32_t txc; - __IOM uint32_t USB_TXC0_REG; /*!< (@ 0x0000008C) Transmit command 0 Register */ - __IOM uint32_t USB_TXC1_REG; /*!< (@ 0x000000AC) Transmit Command Register 1 */ - __IOM uint32_t USB_TXC2_REG; /*!< (@ 0x000000CC) Transmit Command Register 2 */ - __IOM uint32_t USB_TXC3_REG; /*!< (@ 0x000000EC) Transmit Command Register 3 */ - }; - union - { - __IOM uint32_t epc_out; - __IOM uint32_t USB_EP0_NAK_REG; /*!< (@ 0x00000090) EP0 INNAK and OUTNAK Register */ - __IOM uint32_t USB_EPC2_REG; /*!< (@ 0x000000B0) Endpoint Control Register 2 */ - __IOM uint32_t USB_EPC4_REG; /*!< (@ 0x000000D0) Endpoint Control Register 4 */ - __IOM uint32_t USB_EPC6_REG; /*!< (@ 0x000000F0) Endpoint Control Register 6 */ - }; - union - { - __IOM uint32_t rxd; - __IOM uint32_t USB_RXD0_REG; /*!< (@ 0x00000094) Receive Data 0 Register */ - __IOM uint32_t USB_RXD1_REG; /*!< (@ 0x000000B4) Receive Data Register,1 */ - __IOM uint32_t USB_RXD2_REG; /*!< (@ 0x000000D4) Receive Data Register 2 */ - __IOM uint32_t USB_RXD3_REG; /*!< (@ 0x000000F4) Receive Data Register 3 */ - }; - union - { - __IOM uint32_t rxs; - __IOM uint32_t USB_RXS0_REG; /*!< (@ 0x00000098) Receive Status 0 Register */ - __IOM uint32_t USB_RXS1_REG; /*!< (@ 0x000000B8) Receive Status Register 1 */ - __IOM uint32_t USB_RXS2_REG; /*!< (@ 0x000000D8) Receive Status Register 2 */ - __IOM uint32_t USB_RXS3_REG; /*!< (@ 0x000000F8) Receive Status Register 3 */ - }; - union - { - __IOM uint32_t rxc; - __IOM uint32_t USB_RXC0_REG; /*!< (@ 0x0000009C) Receive Command 0 Register */ - __IOM uint32_t USB_RXC1_REG; /*!< (@ 0x000000BC) Receive Command Register 1 */ - __IOM uint32_t USB_RXC2_REG; /*!< (@ 0x000000DC) Receive Command Register 2 */ - __IOM uint32_t USB_RXC3_REG; /*!< (@ 0x000000FC) Receive Command Register 3 */ - }; -} EPx_REGS; - -#define EP_REGS(first_ep_reg) (EPx_REGS*)(&USB->first_ep_reg) - -// Dialog register fields and bit mask are very long. Filed masks repeat register names. -// Those convenience macros are a way to reduce complexity of register modification lines. -#define GET_BIT(val, field) (val & field ## _Msk) >> field ## _Pos -#define REG_GET_BIT(reg, field) (USB->reg & USB_ ## reg ## _ ## field ## _Msk) -#define REG_SET_BIT(reg, field) USB->reg |= USB_ ## reg ## _ ## field ## _Msk -#define REG_CLR_BIT(reg, field) USB->reg &= ~USB_ ## reg ## _ ## field ## _Msk -#define REG_SET_VAL(reg, field, val) USB->reg = (USB->reg & ~USB_ ## reg ## _ ## field ## _Msk) | (val << USB_ ## reg ## _ ## field ## _Pos) - -typedef struct { - EPx_REGS * regs; - uint8_t * buffer; - // Total length of current transfer - uint16_t total_len; - // Bytes transferred so far - uint16_t transferred; - uint16_t max_packet_size; - // Packet size sent or received so far. It is used to modify transferred field - // after ACK is received or when filling ISO endpoint with size larger then - // FIFO size. - uint16_t last_packet_size; - uint8_t ep_addr; - // DATA0/1 toggle bit 1 DATA1 is expected or transmitted - uint8_t data1 : 1; - // Endpoint is stalled - uint8_t stall : 1; -} xfer_ctl_t; - -static struct -{ - bool vbus_present; - bool in_reset; - xfer_ctl_t xfer_status[EP_MAX][2]; -} _dcd = -{ - .vbus_present = false, - .xfer_status = - { - { { .regs = EP_REGS(USB_EPC0_REG) }, { .regs = EP_REGS(USB_EPC0_REG) } }, - { { .regs = EP_REGS(USB_EPC1_REG) }, { .regs = EP_REGS(USB_EPC1_REG) } }, - { { .regs = EP_REGS(USB_EPC3_REG) }, { .regs = EP_REGS(USB_EPC3_REG) } }, - { { .regs = EP_REGS(USB_EPC5_REG) }, { .regs = EP_REGS(USB_EPC5_REG) } }, - } -}; - -// Two endpoint 0 descriptor definition for unified dcd_edpt_open() -static const tusb_desc_endpoint_t ep0OUT_desc = -{ - .bLength = sizeof(tusb_desc_endpoint_t), - .bDescriptorType = TUSB_DESC_ENDPOINT, - - .bEndpointAddress = 0x00, - .bmAttributes = { .xfer = TUSB_XFER_CONTROL }, - .wMaxPacketSize = { .size = CFG_TUD_ENDPOINT0_SIZE }, - .bInterval = 0 -}; - -static const tusb_desc_endpoint_t ep0IN_desc = -{ - .bLength = sizeof(tusb_desc_endpoint_t), - .bDescriptorType = TUSB_DESC_ENDPOINT, - - .bEndpointAddress = 0x80, - .bmAttributes = { .xfer = TUSB_XFER_CONTROL }, - .wMaxPacketSize = { .size = CFG_TUD_ENDPOINT0_SIZE }, - .bInterval = 0 -}; - -#define XFER_CTL_BASE(_ep, _dir) &_dcd.xfer_status[_ep][_dir] - -// Function could be called when VBUS change was detected. -void tusb_vbus_changed(bool present) -{ - if (present != _dcd.vbus_present) - { - _dcd.vbus_present = present; - if (present) - { - USB->USB_MCTRL_REG = USB_USB_MCTRL_REG_USBEN_Msk; - USB->USB_NFSR_REG = 0; - USB->USB_FAR_REG = 0x80; - USB->USB_NFSR_REG = NFSR_NODE_RESET; - USB->USB_TXMSK_REG = 0; - USB->USB_RXMSK_REG = 0; - - USB->USB_MAMSK_REG = USB_USB_MAMSK_REG_USB_M_INTR_Msk | - USB_USB_MAMSK_REG_USB_M_ALT_Msk | - USB_USB_MAMSK_REG_USB_M_WARN_Msk; - USB->USB_ALTMSK_REG = USB_USB_ALTMSK_REG_USB_M_RESET_Msk; - } - else - { - USB->USB_MCTRL_REG = 0; - } - } -} - -static void transmit_packet(xfer_ctl_t * xfer) -{ - int left_to_send; - uint8_t const *src; - EPx_REGS *regs = xfer->regs; - uint32_t txc; - - txc = USB_USB_TXC1_REG_USB_TX_EN_Msk; - if (xfer->data1) txc |= USB_USB_TXC1_REG_USB_TOGGLE_TX_Msk; - - src = &xfer->buffer[xfer->transferred]; - left_to_send = xfer->total_len - xfer->transferred; - if (left_to_send > xfer->max_packet_size - xfer->last_packet_size) - { - left_to_send = xfer->max_packet_size - xfer->last_packet_size; - } - - // Loop checks TCOUNT all the time since this value is saturated to 31 - // and can't be read just once before. - while ((regs->txs & USB_USB_TXS1_REG_USB_TCOUNT_Msk) > 0 && left_to_send > 0) - { - regs->txd = *src++; - xfer->last_packet_size++; - left_to_send--; - } - if (tu_edpt_number(xfer->ep_addr) != 0) - { - if (left_to_send > 0) - { - // Max packet size is set to value greater then FIFO. Enable fifo level warning - // to handle larger packets. - txc |= USB_USB_TXC1_REG_USB_TFWL_Msk; - } - else - { - // Whole packet already in fifo, no need to refill it later. Mark last. - txc |= USB_USB_TXC1_REG_USB_LAST_Msk; - } - } - // Enable transfer with correct interrupts enabled - regs->txc = txc; -} - -static void receive_packet(xfer_ctl_t *xfer, uint16_t bytes_in_fifo) -{ - EPx_REGS *regs = xfer->regs; - uint16_t remaining = xfer->total_len - xfer->transferred; - uint16_t receive_this_time = bytes_in_fifo; - - if (remaining <= bytes_in_fifo) receive_this_time = remaining; - - uint8_t *buf = xfer->buffer + xfer->transferred + xfer->last_packet_size; - - for (int i = 0; i < receive_this_time; ++i) buf[i] = regs->rxd; - - xfer->transferred += receive_this_time; - xfer->last_packet_size += receive_this_time; -} - -static void handle_ep0_rx(void) -{ - int packet_size; - uint32_t rxs0 = USB->USB_RXS0_REG; - - xfer_ctl_t *xfer = XFER_CTL_BASE(0, TUSB_DIR_OUT); - - packet_size = GET_BIT(rxs0, USB_USB_RXS0_REG_USB_RCOUNT); - if (rxs0 & USB_USB_RXS0_REG_USB_SETUP_Msk) - { - xfer_ctl_t *xfer_in = XFER_CTL_BASE(0, TUSB_DIR_IN); - // Setup packet is in - for (int i = 0; i < packet_size; ++i) _setup_packet[i] = USB->USB_RXD0_REG; - - xfer->stall = 0; - xfer->data1 = 1; - xfer_in->stall = 0; - xfer_in->data1 = 1; - REG_SET_BIT(USB_TXC0_REG, USB_TOGGLE_TX0); - REG_CLR_BIT(USB_EPC0_REG, USB_STALL); - dcd_event_setup_received(0, _setup_packet,true); - } - else - { - if (GET_BIT(rxs0, USB_USB_RXS0_REG_USB_TOGGLE_RX0) != xfer->data1) - { - // Toggle bit does not match discard packet - REG_SET_BIT(USB_RXC0_REG, USB_FLUSH); - } - else - { - receive_packet(xfer, packet_size); - xfer->data1 ^= 1; - - if (xfer->total_len == xfer->transferred || xfer->last_packet_size < xfer->max_packet_size) - { - dcd_event_xfer_complete(0, 0, xfer->transferred, XFER_RESULT_SUCCESS, true); - } - else - { - xfer->last_packet_size = 0; - // Re-enable reception - REG_SET_BIT(USB_RXC0_REG, USB_RX_EN); - } - } - } -} - -static void handle_ep0_tx(void) -{ - uint32_t txs0; - xfer_ctl_t *xfer = XFER_CTL_BASE(0, TUSB_DIR_IN); - EPx_REGS *regs = xfer->regs; - - txs0 = regs->USB_TXS0_REG; - - if (GET_BIT(txs0, USB_USB_TXS0_REG_USB_TX_DONE)) - { - // ACK received - if (GET_BIT(txs0, USB_USB_TXS0_REG_USB_ACK_STAT)) - { - xfer->transferred += xfer->last_packet_size; - xfer->last_packet_size = 0; - xfer->data1 ^= 1; - REG_SET_VAL(USB_TXC0_REG, USB_TOGGLE_TX0, xfer->data1); - if (xfer->transferred == xfer->total_len) - { - dcd_event_xfer_complete(0, 0 | TUSB_DIR_IN_MASK, xfer->total_len, XFER_RESULT_SUCCESS, true); - return; - } - } - else - { - // Start from the beginning - xfer->last_packet_size = 0; - } - transmit_packet(xfer); - } -} - -static void handle_epx_rx_ev(uint8_t ep) -{ - uint32_t rxs; - int packet_size; - xfer_ctl_t *xfer = XFER_CTL_BASE(ep, TUSB_DIR_OUT); - - EPx_REGS *regs = xfer->regs; - - rxs = regs->rxs; - - if (GET_BIT(rxs, USB_USB_RXS1_REG_USB_RX_ERR)) - { - regs->rxc |= USB_USB_RXC1_REG_USB_FLUSH_Msk; - } - else - { - packet_size = GET_BIT(rxs, USB_USB_RXS1_REG_USB_RXCOUNT); - receive_packet(xfer, packet_size); - if (GET_BIT(rxs, USB_USB_RXS1_REG_USB_RX_LAST)) - { - if (GET_BIT(rxs, USB_USB_RXS1_REG_USB_TOGGLE_RX) != xfer->data1) - { - // Toggle bit does not match discard packet - regs->rxc |= USB_USB_RXC1_REG_USB_FLUSH_Msk; - } - else - { - xfer->data1 ^= 1; - if (xfer->total_len == xfer->transferred || xfer->last_packet_size < xfer->max_packet_size) - { - dcd_event_xfer_complete(0, xfer->ep_addr, xfer->transferred, XFER_RESULT_SUCCESS, true); - } - else - { - xfer->last_packet_size = 0; - // Re-enable reception - regs->rxc |= USB_USB_RXC1_REG_USB_RX_EN_Msk; - } - } - } - } -} - -static void handle_rx_ev(void) -{ - if (USB->USB_RXEV_REG & 1) - handle_epx_rx_ev(1); - if (USB->USB_RXEV_REG & 2) - handle_epx_rx_ev(2); - if (USB->USB_RXEV_REG & 4) - handle_epx_rx_ev(3); -} - -static void handle_epx_tx_ev(xfer_ctl_t *xfer) -{ - uint32_t usb_txs1_reg; - EPx_REGS *regs = xfer->regs; - - usb_txs1_reg = regs->USB_TXS1_REG; - - if (GET_BIT(usb_txs1_reg, USB_USB_TXS1_REG_USB_TX_DONE)) - { - if (GET_BIT(usb_txs1_reg, USB_USB_TXS1_REG_USB_ACK_STAT)) - { - // ACK received, update transfer state and DATA0/1 bit - xfer->transferred += xfer->last_packet_size; - xfer->last_packet_size = 0; - xfer->data1 ^= 1; - - if (xfer->transferred == xfer->total_len) - { - dcd_event_xfer_complete(0, xfer->ep_addr, xfer->total_len, XFER_RESULT_SUCCESS, true); - return; - } - } - else - { - xfer->last_packet_size = 0; - } - transmit_packet(xfer); - } -} - -static void handle_tx_ev(void) -{ - if (USB->USB_TXEV_REG & 1) - handle_epx_tx_ev(XFER_CTL_BASE(1, TUSB_DIR_IN)); - if (USB->USB_TXEV_REG & 2) - handle_epx_tx_ev(XFER_CTL_BASE(2, TUSB_DIR_IN)); - if (USB->USB_TXEV_REG & 4) - handle_epx_tx_ev(XFER_CTL_BASE(3, TUSB_DIR_IN)); -} - -static void handle_bus_reset(void) -{ - USB->USB_NFSR_REG = 0; - USB->USB_FAR_REG = 0x80; - USB->USB_ALTMSK_REG = 0; - USB->USB_NFSR_REG = NFSR_NODE_RESET; - USB->USB_TXMSK_REG = 0; - USB->USB_RXMSK_REG = 0; - (void)USB->USB_ALTEV_REG; - _dcd.in_reset = true; - - dcd_event_bus_signal(0, DCD_EVENT_BUS_RESET, true); - - USB->USB_MAMSK_REG = USB_USB_MAMSK_REG_USB_M_INTR_Msk | -#if USE_SOF - USB_USB_MAMSK_REG_USB_M_FRAME_Msk | -#endif - USB_USB_MAMSK_REG_USB_M_WARN_Msk | - USB_USB_MAMSK_REG_USB_M_ALT_Msk; - USB->USB_NFSR_REG = NFSR_NODE_OPERATIONAL; - USB->USB_ALTMSK_REG = USB_USB_ALTMSK_REG_USB_M_SD3_Msk | - USB_USB_ALTMSK_REG_USB_M_RESUME_Msk; - // There is no information about end of reset state - // USB_FRAME event will be used to enable reset detection again - REG_SET_BIT(USB_MAEV_REG, USB_FRAME); - dcd_edpt_open (0, &ep0OUT_desc); - dcd_edpt_open (0, &ep0IN_desc); -} - -static void handle_alt_ev(void) -{ - uint32_t alt_ev = USB->USB_ALTEV_REG; - - if (GET_BIT(alt_ev, USB_USB_ALTEV_REG_USB_RESET)) - { - handle_bus_reset(); - } - else - { - if (GET_BIT(alt_ev, USB_USB_ALTEV_REG_USB_RESUME)) - { - USB->USB_NFSR_REG = NFSR_NODE_OPERATIONAL; - USB->USB_ALTMSK_REG &= ~USB_USB_ALTMSK_REG_USB_M_RESUME_Msk; - USB->USB_ALTMSK_REG |= USB_USB_ALTMSK_REG_USB_M_SD3_Msk; - dcd_event_bus_signal(0, DCD_EVENT_RESUME, true); - } - if (GET_BIT(alt_ev, USB_USB_ALTEV_REG_USB_SD3)) - { - USB->USB_NFSR_REG = NFSR_NODE_SUSPEND; - USB->USB_ALTMSK_REG |= USB_USB_ALTMSK_REG_USB_M_RESUME_Msk; - USB->USB_ALTMSK_REG &= ~USB_USB_ALTMSK_REG_USB_M_SD3_Msk | USB_USB_ALTMSK_REG_USB_M_SD5_Msk; - dcd_event_bus_signal(0, DCD_EVENT_SUSPEND, true); - } - } -} - -static void handle_epx_tx_refill(uint8_t ep) -{ - transmit_packet(XFER_CTL_BASE(ep, TUSB_DIR_IN)); -} - -static void handle_fifo_warning(void) -{ - uint32_t fifo_warning = USB->USB_FWEV_REG; - - if (fifo_warning & 0x01) - handle_epx_tx_refill(1); - if (fifo_warning & 0x02) - handle_epx_tx_refill(2); - if (fifo_warning & 0x04) - handle_epx_tx_refill(3); - if (fifo_warning & 0x10) - handle_epx_rx_ev(1); - if (fifo_warning & 0x20) - handle_epx_rx_ev(2); - if (fifo_warning & 0x40) - handle_epx_rx_ev(3); -} - -static void handle_ep0_nak(void) -{ - uint32_t ep0_nak = USB->USB_EP0_NAK_REG; - - if (REG_GET_BIT(USB_EPC0_REG, USB_STALL)) - { - if (GET_BIT(ep0_nak, USB_USB_EP0_NAK_REG_USB_EP0_INNAK)) - { - // EP0 is stalled and NAK was sent, it means that RX is enabled - // Disable RX for now. - REG_CLR_BIT(USB_RXC0_REG, USB_RX_EN); - REG_SET_BIT(USB_TXC0_REG, USB_TX_EN); - } - if (GET_BIT(ep0_nak, USB_USB_EP0_NAK_REG_USB_EP0_OUTNAK)) - { - REG_SET_BIT(USB_RXC0_REG, USB_RX_EN); - } - } - else - { - REG_CLR_BIT(USB_MAMSK_REG, USB_M_EP0_NAK); - } -} - -/*------------------------------------------------------------------*/ -/* Controller API - *------------------------------------------------------------------*/ -void dcd_init(uint8_t rhport) -{ - USB->USB_MCTRL_REG = USB_USB_MCTRL_REG_USBEN_Msk; - tusb_vbus_changed((CRG_TOP->ANA_STATUS_REG & CRG_TOP_ANA_STATUS_REG_VBUS_AVAILABLE_Msk) != 0); - - dcd_connect(rhport); -} - -void dcd_int_enable(uint8_t rhport) -{ - (void)rhport; - - NVIC_EnableIRQ(USB_IRQn); -} - -void dcd_int_disable(uint8_t rhport) -{ - (void)rhport; - - NVIC_DisableIRQ(USB_IRQn); -} - -void dcd_set_address(uint8_t rhport, uint8_t dev_addr) -{ - (void)rhport; - - // Set default address for one ZLP - USB->USB_EPC0_REG = USB_USB_EPC0_REG_USB_DEF_Msk; - USB->USB_FAR_REG = (dev_addr & USB_USB_FAR_REG_USB_AD_Msk) | USB_USB_FAR_REG_USB_AD_EN_Msk; - dcd_edpt_xfer(rhport, tu_edpt_addr(0, TUSB_DIR_IN), NULL, 0); -} - -void dcd_remote_wakeup(uint8_t rhport) -{ - (void)rhport; -} - -void dcd_connect(uint8_t rhport) -{ - (void)rhport; - - REG_SET_BIT(USB_MCTRL_REG, USB_NAT); -} - -void dcd_disconnect(uint8_t rhport) -{ - (void)rhport; - - REG_CLR_BIT(USB_MCTRL_REG, USB_NAT); -} - - -/*------------------------------------------------------------------*/ -/* DCD Endpoint port - *------------------------------------------------------------------*/ - -bool dcd_edpt_open(uint8_t rhport, tusb_desc_endpoint_t const * desc_edpt) -{ - uint8_t const epnum = tu_edpt_number(desc_edpt->bEndpointAddress); - uint8_t const dir = tu_edpt_dir(desc_edpt->bEndpointAddress); - xfer_ctl_t * xfer = XFER_CTL_BASE(epnum, dir); - uint8_t iso_mask = 0; - - (void)rhport; - - TU_ASSERT(desc_edpt->wMaxPacketSize.size <= 1023); - TU_ASSERT(epnum < EP_MAX); - - xfer->max_packet_size = desc_edpt->wMaxPacketSize.size; - xfer->ep_addr = desc_edpt->bEndpointAddress; - xfer->data1 = 0; - - if (epnum != 0 && desc_edpt->bmAttributes.xfer == 1) iso_mask = USB_USB_EPC1_REG_USB_ISO_Msk; - - if (epnum == 0) - { - USB->USB_MAMSK_REG |= USB_USB_MAMSK_REG_USB_M_EP0_RX_Msk | - USB_USB_MAMSK_REG_USB_M_EP0_TX_Msk; - } - else - { - if (dir == TUSB_DIR_OUT) - { - xfer->regs->epc_out = epnum | USB_USB_EPC1_REG_USB_EP_EN_Msk | iso_mask; - USB->USB_RXMSK_REG |= 0x101 << (epnum - 1); - REG_SET_BIT(USB_MAMSK_REG, USB_M_RX_EV); - } - else - { - xfer->regs->epc_in = epnum | USB_USB_EPC1_REG_USB_EP_EN_Msk | iso_mask; - USB->USB_TXMSK_REG |= 0x101 << (epnum - 1); - REG_SET_BIT(USB_MAMSK_REG, USB_M_TX_EV); - } - } - - return true; -} - -bool dcd_edpt_xfer(uint8_t rhport, uint8_t ep_addr, uint8_t * buffer, uint16_t total_bytes) -{ - uint8_t const epnum = tu_edpt_number(ep_addr); - uint8_t const dir = tu_edpt_dir(ep_addr); - xfer_ctl_t * xfer = XFER_CTL_BASE(epnum, dir); - - (void)rhport; - - xfer->buffer = buffer; - xfer->total_len = total_bytes; - xfer->last_packet_size = 0; - xfer->transferred = 0; - - if (dir == TUSB_DIR_OUT) - { - if (epnum != 0) - { - if (xfer->max_packet_size > 64) - { - // For endpoint size greater then FIFO size enable FIFO level warning interrupt - // when FIFO has less then 17 bytes free. - xfer->regs->rxc |= USB_USB_RXC1_REG_USB_RFWL_Msk; - } - else - { - // If max_packet_size would fit in FIFO no need for FIFO level warning interrupt. - xfer->regs->rxc &= ~USB_USB_RXC1_REG_USB_RFWL_Msk; - } - } - // USB_RX_EN bit is in same place for all endpoints. - xfer->regs->rxc = USB_USB_RXC0_REG_USB_RX_EN_Msk; - } - else // IN - { - transmit_packet(xfer); - } - - return true; -} - -void dcd_edpt_stall(uint8_t rhport, uint8_t ep_addr) -{ - uint8_t const epnum = tu_edpt_number(ep_addr); - uint8_t const dir = tu_edpt_dir(ep_addr); - - (void)rhport; - - xfer_ctl_t * xfer = XFER_CTL_BASE(epnum, dir); - xfer->stall = 1; - - if (epnum == 0) - { - // EP0 has just one registers to control stall for IN and OUT - REG_SET_BIT(USB_EPC0_REG, USB_STALL); - if (dir == TUSB_DIR_OUT) - { - xfer->regs->USB_RXC0_REG = USB_USB_RXC0_REG_USB_RX_EN_Msk; - } - else - { - if (xfer->regs->USB_RXC0_REG & USB_USB_RXC0_REG_USB_RX_EN_Msk) - { - // If RX is also enabled TX will not be stalled since RX has - // higher priority. Enable NAK interrupt to handle stall. - REG_SET_BIT(USB_MAMSK_REG, USB_M_EP0_NAK); - } - else - { - xfer->regs->USB_TXC0_REG |= USB_USB_TXC0_REG_USB_TX_EN_Msk; - } - } - } - else - { - if (dir == TUSB_DIR_OUT) - { - xfer->regs->epc_out |= USB_USB_EPC1_REG_USB_STALL_Msk; - xfer->regs->rxc |= USB_USB_RXC1_REG_USB_RX_EN_Msk; - } - else - { - xfer->regs->epc_in |= USB_USB_EPC1_REG_USB_STALL_Msk; - xfer->regs->txc |= USB_USB_TXC1_REG_USB_TX_EN_Msk | USB_USB_TXC1_REG_USB_LAST_Msk; - } - } -} - -void dcd_edpt_clear_stall(uint8_t rhport, uint8_t ep_addr) -{ - uint8_t const epnum = tu_edpt_number(ep_addr); - uint8_t const dir = tu_edpt_dir(ep_addr); - - (void)rhport; - - xfer_ctl_t * xfer = XFER_CTL_BASE(epnum, dir); - - // Clear stall is called in response to Clear Feature ENDPOINT_HALT, reset toggle - xfer->data1 = 0; - xfer->stall = 0; - - if (dir == TUSB_DIR_OUT) - { - xfer->regs->epc_out &= ~USB_USB_EPC1_REG_USB_STALL_Msk; - } - else - { - xfer->regs->epc_in &= ~USB_USB_EPC1_REG_USB_STALL_Msk; - } - if (epnum == 0) - { - REG_CLR_BIT(USB_MAMSK_REG, USB_M_EP0_NAK); - } -} - -/*------------------------------------------------------------------*/ -/* Interrupt Handler - *------------------------------------------------------------------*/ - -void dcd_int_handler(uint8_t rhport) -{ - uint32_t int_status = USB->USB_MAEV_REG; - - (void)rhport; - - if (GET_BIT(int_status, USB_USB_MAEV_REG_USB_WARN)) - { - handle_fifo_warning(); - } - - if (GET_BIT(int_status, USB_USB_MAEV_REG_USB_CH_EV)) - { - // TODO: for now just clear interrupt - (void)USB->USB_CHARGER_STAT_REG; - } - - if (GET_BIT(int_status, USB_USB_MAEV_REG_USB_EP0_NAK)) - { - handle_ep0_nak(); - } - - if (GET_BIT(int_status, USB_USB_MAEV_REG_USB_EP0_RX)) - { - handle_ep0_rx(); - } - - if (GET_BIT(int_status, USB_USB_MAEV_REG_USB_EP0_TX)) - { - handle_ep0_tx(); - } - - if (GET_BIT(int_status, USB_USB_MAEV_REG_USB_RX_EV)) - { - handle_rx_ev(); - } - - if (GET_BIT(int_status, USB_USB_MAEV_REG_USB_NAK)) - { - (void)USB->USB_NAKEV_REG; - } - - if (GET_BIT(int_status, USB_USB_MAEV_REG_USB_FRAME)) - { - if (_dcd.in_reset) - { - // Enable reset detection - _dcd.in_reset = false; - (void)USB->USB_ALTEV_REG; - } -#if USE_SOF - dcd_event_bus_signal(0, DCD_EVENT_SOF, true); -#else - // SOF was used to re-enable reset detection - // No need to keep it enabled - USB->USB_MAMSK_REG &= ~USB_USB_MAMSK_REG_USB_M_FRAME_Msk; -#endif - } - - if (GET_BIT(int_status, USB_USB_MAEV_REG_USB_TX_EV)) - { - handle_tx_ev(); - } - - if (GET_BIT(int_status, USB_USB_MAEV_REG_USB_ALT)) - { - handle_alt_ev(); - } -} - -#endif diff --git a/cores/nRF5/TinyUSB/Adafruit_TinyUSB_ArduinoCore/tinyusb/src/portable/espressif/esp32s2/dcd_esp32s2.c b/cores/nRF5/TinyUSB/Adafruit_TinyUSB_ArduinoCore/tinyusb/src/portable/espressif/esp32s2/dcd_esp32s2.c deleted file mode 100644 index 58d92e7..0000000 --- a/cores/nRF5/TinyUSB/Adafruit_TinyUSB_ArduinoCore/tinyusb/src/portable/espressif/esp32s2/dcd_esp32s2.c +++ /dev/null @@ -1,778 +0,0 @@ -/* - * The MIT License (MIT) - * - * Copyright (c) 2018 Scott Shawcroft, 2019 William D. Jones for Adafruit Industries - * Copyright (c) 2019 Ha Thach (tinyusb.org) - * Additions Copyright (c) 2020, Espressif Systems (Shanghai) Co. Ltd. - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - * - * This file is part of the TinyUSB stack. - */ - -#include "tusb_option.h" - -#if CFG_TUSB_MCU == OPT_MCU_ESP32S2 && TUSB_OPT_DEVICE_ENABLED - -// Espressif -#include "driver/periph_ctrl.h" -#include "freertos/xtensa_api.h" -#include "esp_intr_alloc.h" -#include "esp_log.h" -#include "esp32s2/rom/gpio.h" -#include "soc/dport_reg.h" -#include "soc/gpio_sig_map.h" -#include "soc/usb_periph.h" - -#include "device/dcd.h" - -// Since TinyUSB doesn't use SOF for now, and this interrupt too often (1ms interval) -// We disable SOF for now until needed later on -#define USE_SOF 0 - -// Max number of bi-directional endpoints including EP0 -// Note: ESP32S2 specs say there are only up to 5 IN active endpoints include EP0 -// We should probably prohibit enabling Endpoint IN > 4 (not done yet) -#define EP_MAX USB_OUT_EP_NUM - -// FIFO size in bytes -#define EP_FIFO_SIZE 1024 - -// Max number of IN EP FIFOs -#define EP_FIFO_NUM 5 - -typedef struct { - uint8_t *buffer; - uint16_t total_len; - uint16_t queued_len; - uint16_t max_size; - bool short_packet; -} xfer_ctl_t; - -static const char *TAG = "TUSB:DCD"; -static intr_handle_t usb_ih; - - -static uint32_t _setup_packet[2]; - -#define XFER_CTL_BASE(_ep, _dir) &xfer_status[_ep][_dir] -static xfer_ctl_t xfer_status[EP_MAX][2]; - -// Keep count of how many FIFOs are in use -static uint8_t _allocated_fifos = 1; //FIFO0 is always in use - -// Will either return an unused FIFO number, or 0 if all are used. -static uint8_t get_free_fifo(void) -{ - if (_allocated_fifos < EP_FIFO_NUM) return _allocated_fifos++; - return 0; -} - -// Setup the control endpoint 0. -static void bus_reset(void) -{ - for (int ep_num = 0; ep_num < USB_OUT_EP_NUM; ep_num++) { - USB0.out_ep_reg[ep_num].doepctl |= USB_DO_SNAK0_M; // DOEPCTL0_SNAK - } - - USB0.dcfg &= ~USB_DEVADDR_M; // reset address - - USB0.daintmsk |= USB_OUTEPMSK0_M | USB_INEPMSK0_M; - USB0.doepmsk |= USB_SETUPMSK_M | USB_XFERCOMPLMSK; - USB0.diepmsk |= USB_TIMEOUTMSK_M | USB_DI_XFERCOMPLMSK_M /*| USB_INTKNTXFEMPMSK_M*/; - - // "USB Data FIFOs" section in reference manual - // Peripheral FIFO architecture - // - // --------------- 320 or 1024 ( 1280 or 4096 bytes ) - // | IN FIFO MAX | - // --------------- - // | ... | - // --------------- y + x + 16 + GRXFSIZ - // | IN FIFO 2 | - // --------------- x + 16 + GRXFSIZ - // | IN FIFO 1 | - // --------------- 16 + GRXFSIZ - // | IN FIFO 0 | - // --------------- GRXFSIZ - // | OUT FIFO | - // | ( Shared ) | - // --------------- 0 - // - // According to "FIFO RAM allocation" section in RM, FIFO RAM are allocated as follows (each word 32-bits): - // - Each EP IN needs at least max packet size, 16 words is sufficient for EP0 IN - // - // - All EP OUT shared a unique OUT FIFO which uses - // * 10 locations in hardware for setup packets + setup control words (up to 3 setup packets). - // * 2 locations for OUT endpoint control words. - // * 16 for largest packet size of 64 bytes. ( TODO Highspeed is 512 bytes) - // * 1 location for global NAK (not required/used here). - // * It is recommended to allocate 2 times the largest packet size, therefore - // Recommended value = 10 + 1 + 2 x (16+2) = 47 --> Let's make it 52 - USB0.grstctl |= 0x10 << USB_TXFNUM_S; // fifo 0x10, - USB0.grstctl |= USB_TXFFLSH_M; // Flush fifo - USB0.grxfsiz = 52; - - // Control IN uses FIFO 0 with 64 bytes ( 16 32-bit word ) - USB0.gnptxfsiz = (16 << USB_NPTXFDEP_S) | (USB0.grxfsiz & 0x0000ffffUL); - - // Ready to receive SETUP packet - USB0.out_ep_reg[0].doeptsiz |= USB_SUPCNT0_M; - - USB0.gintmsk |= USB_IEPINTMSK_M | USB_OEPINTMSK_M; -} - -static void enum_done_processing(void) -{ - ESP_EARLY_LOGV(TAG, "dcd_int_handler - Speed enumeration done! Sending DCD_EVENT_BUS_RESET then"); - // On current silicon on the Full Speed core, speed is fixed to Full Speed. - // However, keep for debugging and in case Low Speed is ever supported. - uint32_t enum_spd = (USB0.dsts >> USB_ENUMSPD_S) & (USB_ENUMSPD_V); - - // Maximum packet size for EP 0 is set for both directions by writing DIEPCTL - if (enum_spd == 0x03) { // Full-Speed (PHY on 48 MHz) - USB0.in_ep_reg[0].diepctl &= ~USB_D_MPS0_V; // 64 bytes - USB0.in_ep_reg[0].diepctl &= ~USB_D_STALL0_M; // clear Stall - xfer_status[0][TUSB_DIR_OUT].max_size = 64; - xfer_status[0][TUSB_DIR_IN].max_size = 64; - } else { - USB0.in_ep_reg[0].diepctl |= USB_D_MPS0_V; // 8 bytes - USB0.in_ep_reg[0].diepctl &= ~USB_D_STALL0_M; // clear Stall - xfer_status[0][TUSB_DIR_OUT].max_size = 8; - xfer_status[0][TUSB_DIR_IN].max_size = 8; - } -} - - -/*------------------------------------------------------------------*/ -/* Controller API - *------------------------------------------------------------------*/ -void dcd_init(uint8_t rhport) -{ - ESP_LOGV(TAG, "DCD init - Start"); - - // A. Disconnect - ESP_LOGV(TAG, "DCD init - Soft DISCONNECT and Setting up"); - USB0.dctl |= USB_SFTDISCON_M; // Soft disconnect - - // B. Programming DCFG - /* If USB host misbehaves during status portion of control xfer - (non zero-length packet), send STALL back and discard. Full speed. */ - USB0.dcfg |= USB_NZSTSOUTHSHK_M | // NonZero .... STALL - (3 << 0); // dev speed: fullspeed 1.1 on 48 mhz // TODO no value in usb_reg.h (IDF-1476) - - USB0.gahbcfg |= USB_NPTXFEMPLVL_M | USB_GLBLLNTRMSK_M; // Global interruptions ON - USB0.gusbcfg |= USB_FORCEDEVMODE_M; // force devmode - USB0.gotgctl &= ~(USB_BVALIDOVVAL_M | USB_BVALIDOVEN_M | USB_VBVALIDOVVAL_M); //no overrides - - // C. Setting SNAKs, then connect - for (int n = 0; n < USB_OUT_EP_NUM; n++) { - USB0.out_ep_reg[n].doepctl |= USB_DO_SNAK0_M; // DOEPCTL0_SNAK - } - - // D. Interruption masking - USB0.gintmsk = 0; //mask all - USB0.gotgint = ~0U; //clear OTG ints - USB0.gintsts = ~0U; //clear pending ints - USB0.gintmsk = USB_OTGINTMSK_M | - USB_MODEMISMSK_M | - #if USE_SOF - USB_SOFMSK_M | - #endif - USB_RXFLVIMSK_M | - USB_ERLYSUSPMSK_M | - USB_USBSUSPMSK_M | - USB_USBRSTMSK_M | - USB_ENUMDONEMSK_M | - USB_RESETDETMSK_M | - USB_DISCONNINTMSK_M; // host most only - - dcd_connect(rhport); -} - -void dcd_set_address(uint8_t rhport, uint8_t dev_addr) -{ - (void)rhport; - ESP_LOGV(TAG, "DCD init - Set address : %u", dev_addr); - USB0.dcfg |= ((dev_addr & USB_DEVADDR_V) << USB_DEVADDR_S); - // Response with status after changing device address - dcd_edpt_xfer(rhport, tu_edpt_addr(0, TUSB_DIR_IN), NULL, 0); -} - -void dcd_remote_wakeup(uint8_t rhport) -{ - (void)rhport; -} - -// connect by enabling internal pull-up resistor on D+/D- -void dcd_connect(uint8_t rhport) -{ - (void) rhport; - USB0.dctl &= ~USB_SFTDISCON_M; -} - -// disconnect by disabling internal pull-up resistor on D+/D- -void dcd_disconnect(uint8_t rhport) -{ - (void) rhport; - USB0.dctl |= USB_SFTDISCON_M; -} - -/*------------------------------------------------------------------*/ -/* DCD Endpoint port - *------------------------------------------------------------------*/ - -bool dcd_edpt_open(uint8_t rhport, tusb_desc_endpoint_t const *desc_edpt) -{ - ESP_LOGV(TAG, "DCD endpoint opened"); - (void)rhport; - - usb_out_endpoint_t *out_ep = &(USB0.out_ep_reg[0]); - usb_in_endpoint_t *in_ep = &(USB0.in_ep_reg[0]); - - uint8_t const epnum = tu_edpt_number(desc_edpt->bEndpointAddress); - uint8_t const dir = tu_edpt_dir(desc_edpt->bEndpointAddress); - - TU_ASSERT(desc_edpt->wMaxPacketSize.size <= 64); - TU_ASSERT(epnum < EP_MAX); - - xfer_ctl_t *xfer = XFER_CTL_BASE(epnum, dir); - xfer->max_size = desc_edpt->wMaxPacketSize.size; - - if (dir == TUSB_DIR_OUT) { - out_ep[epnum].doepctl |= USB_USBACTEP0_M | - desc_edpt->bmAttributes.xfer << USB_EPTYPE0_S | - desc_edpt->wMaxPacketSize.size << USB_MPS0_S; - USB0.daintmsk |= (1 << (16 + epnum)); - } else { - // "USB Data FIFOs" section in reference manual - // Peripheral FIFO architecture - // - // --------------- 320 or 1024 ( 1280 or 4096 bytes ) - // | IN FIFO MAX | - // --------------- - // | ... | - // --------------- y + x + 16 + GRXFSIZ - // | IN FIFO 2 | - // --------------- x + 16 + GRXFSIZ - // | IN FIFO 1 | - // --------------- 16 + GRXFSIZ - // | IN FIFO 0 | - // --------------- GRXFSIZ - // | OUT FIFO | - // | ( Shared ) | - // --------------- 0 - // - // Since OUT FIFO = GRXFSIZ, FIFO 0 = 16, for simplicity, we equally allocated for the rest of endpoints - // - Size : (FIFO_SIZE/4 - GRXFSIZ - 16) / (EP_MAX-1) - // - Offset: GRXFSIZ + 16 + Size*(epnum-1) - // - IN EP 1 gets FIFO 1, IN EP "n" gets FIFO "n". - - uint8_t fifo_num = get_free_fifo(); - TU_ASSERT(fifo_num != 0); - - in_ep[epnum].diepctl &= ~(USB_D_TXFNUM1_M | USB_D_EPTYPE1_M | USB_DI_SETD0PID1 | USB_D_MPS1_M); - in_ep[epnum].diepctl |= USB_D_USBACTEP1_M | - fifo_num << USB_D_TXFNUM1_S | - desc_edpt->bmAttributes.xfer << USB_D_EPTYPE1_S | - (desc_edpt->bmAttributes.xfer != TUSB_XFER_ISOCHRONOUS ? (1 << USB_DI_SETD0PID1_S) : 0) | - desc_edpt->wMaxPacketSize.size << 0; - - USB0.daintmsk |= (1 << (0 + epnum)); - - // Both TXFD and TXSA are in unit of 32-bit words. - // IN FIFO 0 was configured during enumeration, hence the "+ 16". - uint16_t const allocated_size = (USB0.grxfsiz & 0x0000ffff) + 16; - uint16_t const fifo_size = (EP_FIFO_SIZE/4 - allocated_size) / (EP_FIFO_NUM-1); - uint32_t const fifo_offset = allocated_size + fifo_size*(fifo_num-1); - - // DIEPTXF starts at FIFO #1. - USB0.dieptxf[epnum - 1] = (fifo_size << USB_NPTXFDEP_S) | fifo_offset; - } - return true; -} - -bool dcd_edpt_xfer(uint8_t rhport, uint8_t ep_addr, uint8_t *buffer, uint16_t total_bytes) -{ - (void)rhport; - - uint8_t const epnum = tu_edpt_number(ep_addr); - uint8_t const dir = tu_edpt_dir(ep_addr); - - xfer_ctl_t * xfer = XFER_CTL_BASE(epnum, dir); - xfer->buffer = buffer; - xfer->total_len = total_bytes; - xfer->queued_len = 0; - xfer->short_packet = false; - - uint16_t num_packets = (total_bytes / xfer->max_size); - uint8_t short_packet_size = total_bytes % xfer->max_size; - - // Zero-size packet is special case. - if (short_packet_size > 0 || (total_bytes == 0)) { - num_packets++; - } - - ESP_LOGV(TAG, "Transfer <-> EP%i, %s, pkgs: %i, bytes: %i", - epnum, ((dir == TUSB_DIR_IN) ? "USB0.HOST (in)" : "HOST->DEV (out)"), - num_packets, total_bytes); - - // IN and OUT endpoint xfers are interrupt-driven, we just schedule them - // here. - if (dir == TUSB_DIR_IN) { - // A full IN transfer (multiple packets, possibly) triggers XFRC. - USB0.in_ep_reg[epnum].dieptsiz = (num_packets << USB_D_PKTCNT0_S) | total_bytes; - USB0.in_ep_reg[epnum].diepctl |= USB_D_EPENA1_M | USB_D_CNAK1_M; // Enable | CNAK - - // Enable fifo empty interrupt only if there are something to put in the fifo. - if(total_bytes != 0) { - USB0.dtknqr4_fifoemptymsk |= (1 << epnum); - } - } else { - // Each complete packet for OUT xfers triggers XFRC. - USB0.out_ep_reg[epnum].doeptsiz |= USB_PKTCNT0_M | ((xfer->max_size & USB_XFERSIZE0_V) << USB_XFERSIZE0_S); - USB0.out_ep_reg[epnum].doepctl |= USB_EPENA0_M | USB_CNAK0_M; - } - return true; -} - -void dcd_edpt_stall(uint8_t rhport, uint8_t ep_addr) -{ - (void)rhport; - - usb_out_endpoint_t *out_ep = &(USB0.out_ep_reg[0]); - usb_in_endpoint_t *in_ep = &(USB0.in_ep_reg[0]); - - uint8_t const epnum = tu_edpt_number(ep_addr); - uint8_t const dir = tu_edpt_dir(ep_addr); - - if (dir == TUSB_DIR_IN) { - // Only disable currently enabled non-control endpoint - if ((epnum == 0) || !(in_ep[epnum].diepctl & USB_D_EPENA1_M)) { - in_ep[epnum].diepctl |= (USB_DI_SNAK1_M | USB_D_STALL1_M); - } else { - // Stop transmitting packets and NAK IN xfers. - in_ep[epnum].diepctl |= USB_DI_SNAK1_M; - while ((in_ep[epnum].diepint & USB_DI_SNAK1_M) == 0) ; - - // Disable the endpoint. Note that both SNAK and STALL are set here. - in_ep[epnum].diepctl |= (USB_DI_SNAK1_M | USB_D_STALL1_M | USB_D_EPDIS1_M); - while ((in_ep[epnum].diepint & USB_D_EPDISBLD0_M) == 0) ; - in_ep[epnum].diepint = USB_D_EPDISBLD0_M; - } - - // Flush the FIFO, and wait until we have confirmed it cleared. - uint8_t const fifo_num = ((in_ep[epnum].diepctl >> USB_D_TXFNUM1_S) & USB_D_TXFNUM1_V); - USB0.grstctl |= (fifo_num << USB_TXFNUM_S); - USB0.grstctl |= USB_TXFFLSH_M; - while ((USB0.grstctl & USB_TXFFLSH_M) != 0) ; - } else { - // Only disable currently enabled non-control endpoint - if ((epnum == 0) || !(out_ep[epnum].doepctl & USB_EPENA0_M)) { - out_ep[epnum].doepctl |= USB_STALL0_M; - } else { - // Asserting GONAK is required to STALL an OUT endpoint. - // Simpler to use polling here, we don't use the "B"OUTNAKEFF interrupt - // anyway, and it can't be cleared by user code. If this while loop never - // finishes, we have bigger problems than just the stack. - USB0.dctl |= USB_SGOUTNAK_M; - while ((USB0.gintsts & USB_GOUTNAKEFF_M) == 0) ; - - // Ditto here- disable the endpoint. Note that only STALL and not SNAK - // is set here. - out_ep[epnum].doepctl |= (USB_STALL0_M | USB_EPDIS0_M); - while ((out_ep[epnum].doepint & USB_EPDISBLD0_M) == 0) ; - out_ep[epnum].doepint = USB_EPDISBLD0_M; - - // Allow other OUT endpoints to keep receiving. - USB0.dctl |= USB_CGOUTNAK_M; - } - } -} - -void dcd_edpt_clear_stall(uint8_t rhport, uint8_t ep_addr) -{ - (void)rhport; - - usb_out_endpoint_t *out_ep = &(USB0.out_ep_reg[0]); - usb_in_endpoint_t *in_ep = &(USB0.in_ep_reg[0]); - - uint8_t const epnum = tu_edpt_number(ep_addr); - uint8_t const dir = tu_edpt_dir(ep_addr); - - if (dir == TUSB_DIR_IN) { - in_ep[epnum].diepctl &= ~USB_D_STALL1_M; - - uint8_t eptype = (in_ep[epnum].diepctl & USB_D_EPTYPE1_M) >> USB_D_EPTYPE1_S; - // Required by USB spec to reset DATA toggle bit to DATA0 on interrupt - // and bulk endpoints. - if (eptype == 2 || eptype == 3) { - in_ep[epnum].diepctl |= USB_DI_SETD0PID1_M; - } - } else { - out_ep[epnum].doepctl &= ~USB_STALL1_M; - - uint8_t eptype = (out_ep[epnum].doepctl & USB_EPTYPE1_M) >> USB_EPTYPE1_S; - // Required by USB spec to reset DATA toggle bit to DATA0 on interrupt - // and bulk endpoints. - if (eptype == 2 || eptype == 3) { - out_ep[epnum].doepctl |= USB_DO_SETD0PID1_M; - } - } -} - -/*------------------------------------------------------------------*/ - -static void receive_packet(xfer_ctl_t *xfer, /* usb_out_endpoint_t * out_ep, */ uint16_t xfer_size) -{ - ESP_EARLY_LOGV(TAG, "USB - receive_packet"); - volatile uint32_t *rx_fifo = USB0.fifo[0]; - - // See above TODO - // uint16_t remaining = (out_ep->DOEPTSIZ & UsbDOEPTSIZ_XFRSIZ_Msk) >> UsbDOEPTSIZ_XFRSIZ_Pos; - // xfer->queued_len = xfer->total_len - remaining; - - uint16_t remaining = xfer->total_len - xfer->queued_len; - uint16_t to_recv_size; - - if (remaining <= xfer->max_size) { - // Avoid buffer overflow. - to_recv_size = (xfer_size > remaining) ? remaining : xfer_size; - } else { - // Room for full packet, choose recv_size based on what the microcontroller - // claims. - to_recv_size = (xfer_size > xfer->max_size) ? xfer->max_size : xfer_size; - } - - uint8_t to_recv_rem = to_recv_size % 4; - uint16_t to_recv_size_aligned = to_recv_size - to_recv_rem; - - // Do not assume xfer buffer is aligned. - uint8_t *base = (xfer->buffer + xfer->queued_len); - - // This for loop always runs at least once- skip if less than 4 bytes - // to collect. - if (to_recv_size >= 4) { - for (uint16_t i = 0; i < to_recv_size_aligned; i += 4) { - uint32_t tmp = (*rx_fifo); - base[i] = tmp & 0x000000FF; - base[i + 1] = (tmp & 0x0000FF00) >> 8; - base[i + 2] = (tmp & 0x00FF0000) >> 16; - base[i + 3] = (tmp & 0xFF000000) >> 24; - } - } - - // Do not read invalid bytes from RX FIFO. - if (to_recv_rem != 0) { - uint32_t tmp = (*rx_fifo); - uint8_t *last_32b_bound = base + to_recv_size_aligned; - - last_32b_bound[0] = tmp & 0x000000FF; - if (to_recv_rem > 1) { - last_32b_bound[1] = (tmp & 0x0000FF00) >> 8; - } - if (to_recv_rem > 2) { - last_32b_bound[2] = (tmp & 0x00FF0000) >> 16; - } - } - - xfer->queued_len += xfer_size; - - // Per USB spec, a short OUT packet (including length 0) is always - // indicative of the end of a transfer (at least for ctl, bulk, int). - xfer->short_packet = (xfer_size < xfer->max_size); -} - -static void transmit_packet(xfer_ctl_t *xfer, volatile usb_in_endpoint_t *in_ep, uint8_t fifo_num) -{ - ESP_EARLY_LOGV(TAG, "USB - transmit_packet"); - volatile uint32_t *tx_fifo = USB0.fifo[fifo_num]; - - uint16_t remaining = (in_ep->dieptsiz & 0x7FFFFU) >> USB_D_XFERSIZE0_S; - xfer->queued_len = xfer->total_len - remaining; - - uint16_t to_xfer_size = (remaining > xfer->max_size) ? xfer->max_size : remaining; - uint8_t to_xfer_rem = to_xfer_size % 4; - uint16_t to_xfer_size_aligned = to_xfer_size - to_xfer_rem; - - // Buffer might not be aligned to 32b, so we need to force alignment - // by copying to a temp var. - uint8_t *base = (xfer->buffer + xfer->queued_len); - - // This for loop always runs at least once- skip if less than 4 bytes - // to send off. - if (to_xfer_size >= 4) { - for (uint16_t i = 0; i < to_xfer_size_aligned; i += 4) { - uint32_t tmp = base[i] | (base[i + 1] << 8) | - (base[i + 2] << 16) | (base[i + 3] << 24); - (*tx_fifo) = tmp; - } - } - - // Do not read beyond end of buffer if not divisible by 4. - if (to_xfer_rem != 0) { - uint32_t tmp = 0; - uint8_t *last_32b_bound = base + to_xfer_size_aligned; - - tmp |= last_32b_bound[0]; - if (to_xfer_rem > 1) { - tmp |= (last_32b_bound[1] << 8); - } - if (to_xfer_rem > 2) { - tmp |= (last_32b_bound[2] << 16); - } - - (*tx_fifo) = tmp; - } -} - -static void read_rx_fifo(void) -{ - // Pop control word off FIFO (completed xfers will have 2 control words, - // we only pop one ctl word each interrupt). - uint32_t const ctl_word = USB0.grxstsp; - uint8_t const pktsts = (ctl_word & USB_PKTSTS_M) >> USB_PKTSTS_S; - uint8_t const epnum = (ctl_word & USB_CHNUM_M ) >> USB_CHNUM_S; - uint16_t const bcnt = (ctl_word & USB_BCNT_M ) >> USB_BCNT_S; - - switch (pktsts) { - case 0x01: // Global OUT NAK (Interrupt) - ESP_EARLY_LOGV(TAG, "TUSB IRQ - RX type : Global OUT NAK"); - break; - - case 0x02: { // Out packet recvd - ESP_EARLY_LOGV(TAG, "TUSB IRQ - RX type : Out packet"); - xfer_ctl_t *xfer = XFER_CTL_BASE(epnum, TUSB_DIR_OUT); - receive_packet(xfer, bcnt); - } - break; - - case 0x03: // Out packet done (Interrupt) - ESP_EARLY_LOGV(TAG, "TUSB IRQ - RX type : Out packet done"); - break; - - case 0x04: // Step 2: Setup transaction completed (Interrupt) - // After this event, OEPINT interrupt will occur with SETUP bit set - ESP_EARLY_LOGV(TAG, "TUSB IRQ - RX : Setup packet done"); - USB0.out_ep_reg[epnum].doeptsiz |= USB_SUPCNT0_M; - break; - - case 0x06: { // Step1: Setup data packet received - volatile uint32_t *rx_fifo = USB0.fifo[0]; - - // We can receive up to three setup packets in succession, but - // only the last one is valid. Therefore we just overwrite it - _setup_packet[0] = (*rx_fifo); - _setup_packet[1] = (*rx_fifo); - - ESP_EARLY_LOGV(TAG, "TUSB IRQ - RX : Setup packet : 0x%08x 0x%08x", _setup_packet[0], _setup_packet[1]); - } - break; - - default: // Invalid, do something here, like breakpoint? - TU_BREAKPOINT(); - break; - } -} - -static void handle_epout_ints(void) -{ - // GINTSTS will be cleared with DAINT == 0 - // DAINT for a given EP clears when DOEPINTx is cleared. - // DOEPINT will be cleared when DAINT's out bits are cleared. - for (int n = 0; n < USB_OUT_EP_NUM; n++) { - xfer_ctl_t *xfer = XFER_CTL_BASE(n, TUSB_DIR_OUT); - - if (USB0.daint & (1 << (16 + n))) { - // SETUP packet Setup Phase done. - if ((USB0.out_ep_reg[n].doepint & USB_SETUP0_M)) { - USB0.out_ep_reg[n].doepint = USB_STUPPKTRCVD0_M | USB_SETUP0_M; // clear - dcd_event_setup_received(0, (uint8_t *)&_setup_packet[0], true); - } - - // OUT XFER complete (single packet).q - if (USB0.out_ep_reg[n].doepint & USB_XFERCOMPL0_M) { - - ESP_EARLY_LOGV(TAG, "TUSB IRQ - EP OUT - XFER complete (single packet)"); - USB0.out_ep_reg[n].doepint = USB_XFERCOMPL0_M; - - // Transfer complete if short packet or total len is transferred - if (xfer->short_packet || (xfer->queued_len == xfer->total_len)) { - xfer->short_packet = false; - dcd_event_xfer_complete(0, n, xfer->queued_len, XFER_RESULT_SUCCESS, true); - } else { - // Schedule another packet to be received. - USB0.out_ep_reg[n].doeptsiz |= USB_PKTCNT0_M | ((xfer->max_size & USB_XFERSIZE0_V) << USB_XFERSIZE0_S); - USB0.out_ep_reg[n].doepctl |= USB_EPENA0_M | USB_CNAK0_M; - } - } - } - } -} - -static void handle_epin_ints(void) -{ - // GINTSTS will be cleared with DAINT == 0 - // DAINT for a given EP clears when DIEPINTx is cleared. - // IEPINT will be cleared when DAINT's out bits are cleared. - for (uint32_t n = 0; n < USB_IN_EP_NUM; n++) { - xfer_ctl_t *xfer = &xfer_status[n][TUSB_DIR_IN]; - - if (USB0.daint & (1 << (0 + n))) { - ESP_EARLY_LOGV(TAG, "TUSB IRQ - EP IN %u", n); - // IN XFER complete (entire xfer). - if (USB0.in_ep_reg[n].diepint & USB_D_XFERCOMPL0_M) { - ESP_EARLY_LOGV(TAG, "TUSB IRQ - IN XFER complete!"); - USB0.in_ep_reg[n].diepint = USB_D_XFERCOMPL0_M; - dcd_event_xfer_complete(0, n | TUSB_DIR_IN_MASK, xfer->total_len, XFER_RESULT_SUCCESS, true); - } - - // XFER FIFO empty - if (USB0.in_ep_reg[n].diepint & USB_D_TXFEMP0_M) { - ESP_EARLY_LOGV(TAG, "TUSB IRQ - IN XFER FIFO empty!"); - USB0.in_ep_reg[n].diepint = USB_D_TXFEMP0_M; - transmit_packet(xfer, &USB0.in_ep_reg[n], n); - - // Turn off TXFE if all bytes are written. - if (xfer->queued_len == xfer->total_len) - { - USB0.dtknqr4_fifoemptymsk &= ~(1 << n); - } - } - - // XFER Timeout - if (USB0.in_ep_reg[n].diepint & USB_D_TIMEOUT0_M) { - // Clear interrupt or enpoint will hang. - USB0.in_ep_reg[n].diepint = USB_D_TIMEOUT0_M; - // Maybe retry? - } - } - } -} - - -static void _dcd_int_handler(void* arg) -{ - (void) arg; - - const uint32_t int_status = USB0.gintsts; - //const uint32_t int_msk = USB0.gintmsk; - - if (int_status & USB_USBRST_M) { - // start of reset - ESP_EARLY_LOGV(TAG, "dcd_int_handler - reset"); - USB0.gintsts = USB_USBRST_M; - // FIFOs will be reassigned when the endpoints are reopen - _allocated_fifos = 1; - bus_reset(); - } - - if (int_status & USB_RESETDET_M) { - ESP_EARLY_LOGV(TAG, "dcd_int_handler - reset while suspend"); - USB0.gintsts = USB_RESETDET_M; - bus_reset(); - } - - if (int_status & USB_ENUMDONE_M) { - // ENUMDNE detects speed of the link. For full-speed, we - // always expect the same value. This interrupt is considered - // the end of reset. - USB0.gintsts = USB_ENUMDONE_M; - enum_done_processing(); - dcd_event_bus_signal(0, DCD_EVENT_BUS_RESET, true); - } - - if (int_status & USB_OTGINT_M) - { - // OTG INT bit is read-only - ESP_EARLY_LOGV(TAG, "dcd_int_handler - disconnected"); - - uint32_t const otg_int = USB0.gotgint; - - if (otg_int & USB_SESENDDET_M) - { - dcd_event_bus_signal(0, DCD_EVENT_UNPLUGGED, true); - } - - USB0.gotgint = otg_int; - } - -#if USE_SOF - if (int_status & USB_SOF_M) { - USB0.gintsts = USB_SOF_M; - dcd_event_bus_signal(0, DCD_EVENT_SOF, true); // do nothing actually - } -#endif - - if (int_status & USB_RXFLVI_M) { - // RXFLVL bit is read-only - ESP_EARLY_LOGV(TAG, "dcd_int_handler - rx!"); - - // Mask out RXFLVL while reading data from FIFO - USB0.gintmsk &= ~USB_RXFLVIMSK_M; - read_rx_fifo(); - USB0.gintmsk |= USB_RXFLVIMSK_M; - } - - // OUT endpoint interrupt handling. - if (int_status & USB_OEPINT_M) { - // OEPINT is read-only - ESP_EARLY_LOGV(TAG, "dcd_int_handler - OUT endpoint!"); - handle_epout_ints(); - } - - // IN endpoint interrupt handling. - if (int_status & USB_IEPINT_M) { - // IEPINT bit read-only - ESP_EARLY_LOGV(TAG, "dcd_int_handler - IN endpoint!"); - handle_epin_ints(); - } - - // Without handling - USB0.gintsts |= USB_CURMOD_INT_M | - USB_MODEMIS_M | - USB_OTGINT_M | - USB_NPTXFEMP_M | - USB_GINNAKEFF_M | - USB_GOUTNAKEFF | - USB_ERLYSUSP_M | - USB_USBSUSP_M | - USB_ISOOUTDROP_M | - USB_EOPF_M | - USB_EPMIS_M | - USB_INCOMPISOIN_M | - USB_INCOMPIP_M | - USB_FETSUSP_M | - USB_PTXFEMP_M; -} - -void dcd_int_enable (uint8_t rhport) -{ - (void) rhport; - esp_intr_alloc(ETS_USB_INTR_SOURCE, ESP_INTR_FLAG_LOWMED, (intr_handler_t) _dcd_int_handler, NULL, &usb_ih); -} - -void dcd_int_disable (uint8_t rhport) -{ - (void) rhport; - esp_intr_free(usb_ih); -} - -#endif // OPT_MCU_ESP32S2 - diff --git a/cores/nRF5/TinyUSB/Adafruit_TinyUSB_ArduinoCore/tinyusb/src/portable/microchip/samd/dcd_samd.c b/cores/nRF5/TinyUSB/Adafruit_TinyUSB_ArduinoCore/tinyusb/src/portable/microchip/samd/dcd_samd.c deleted file mode 100644 index f1dca88..0000000 --- a/cores/nRF5/TinyUSB/Adafruit_TinyUSB_ArduinoCore/tinyusb/src/portable/microchip/samd/dcd_samd.c +++ /dev/null @@ -1,400 +0,0 @@ -/* - * The MIT License (MIT) - * - * Copyright (c) 2018 Scott Shawcroft for Adafruit Industries - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - * - * This file is part of the TinyUSB stack. - */ - -#include "tusb_option.h" - -#if TUSB_OPT_DEVICE_ENABLED && \ - (CFG_TUSB_MCU == OPT_MCU_SAMD11 || CFG_TUSB_MCU == OPT_MCU_SAMD21 || \ - CFG_TUSB_MCU == OPT_MCU_SAMD51 || CFG_TUSB_MCU == OPT_MCU_SAME5X) - -#include "sam.h" -#include "device/dcd.h" - -/*------------------------------------------------------------------*/ -/* MACRO TYPEDEF CONSTANT ENUM - *------------------------------------------------------------------*/ -static TU_ATTR_ALIGNED(4) UsbDeviceDescBank sram_registers[8][2]; -static TU_ATTR_ALIGNED(4) uint8_t _setup_packet[8]; - -// ready for receiving SETUP packet -static inline void prepare_setup(void) -{ - // Only make sure the EP0 OUT buffer is ready - sram_registers[0][0].ADDR.reg = (uint32_t) _setup_packet; - sram_registers[0][0].PCKSIZE.bit.MULTI_PACKET_SIZE = sizeof(_setup_packet); - sram_registers[0][0].PCKSIZE.bit.BYTE_COUNT = 0; -} - -// Setup the control endpoint 0. -static void bus_reset(void) -{ - // Max size of packets is 64 bytes. - UsbDeviceDescBank* bank_out = &sram_registers[0][TUSB_DIR_OUT]; - bank_out->PCKSIZE.bit.SIZE = 0x3; - UsbDeviceDescBank* bank_in = &sram_registers[0][TUSB_DIR_IN]; - bank_in->PCKSIZE.bit.SIZE = 0x3; - - UsbDeviceEndpoint* ep = &USB->DEVICE.DeviceEndpoint[0]; - ep->EPCFG.reg = USB_DEVICE_EPCFG_EPTYPE0(0x1) | USB_DEVICE_EPCFG_EPTYPE1(0x1); - ep->EPINTENSET.reg = USB_DEVICE_EPINTENSET_TRCPT0 | USB_DEVICE_EPINTENSET_TRCPT1 | USB_DEVICE_EPINTENSET_RXSTP; - - // Prepare for setup packet - prepare_setup(); -} - -/*------------------------------------------------------------------*/ -/* Controller API - *------------------------------------------------------------------*/ -void dcd_init (uint8_t rhport) -{ - (void) rhport; - - // Reset to get in a clean state. - USB->DEVICE.CTRLA.bit.SWRST = true; - while (USB->DEVICE.SYNCBUSY.bit.SWRST == 0) {} - while (USB->DEVICE.SYNCBUSY.bit.SWRST == 1) {} - - USB->DEVICE.PADCAL.bit.TRANSP = (*((uint32_t*) USB_FUSES_TRANSP_ADDR) & USB_FUSES_TRANSP_Msk) >> USB_FUSES_TRANSP_Pos; - USB->DEVICE.PADCAL.bit.TRANSN = (*((uint32_t*) USB_FUSES_TRANSN_ADDR) & USB_FUSES_TRANSN_Msk) >> USB_FUSES_TRANSN_Pos; - USB->DEVICE.PADCAL.bit.TRIM = (*((uint32_t*) USB_FUSES_TRIM_ADDR) & USB_FUSES_TRIM_Msk) >> USB_FUSES_TRIM_Pos; - - USB->DEVICE.QOSCTRL.bit.CQOS = 3; // High Quality - USB->DEVICE.QOSCTRL.bit.DQOS = 3; // High Quality - - // Configure registers - USB->DEVICE.DESCADD.reg = (uint32_t) &sram_registers; - USB->DEVICE.CTRLB.reg = USB_DEVICE_CTRLB_SPDCONF_FS; - USB->DEVICE.CTRLA.reg = USB_CTRLA_MODE_DEVICE | USB_CTRLA_ENABLE | USB_CTRLA_RUNSTDBY; - while (USB->DEVICE.SYNCBUSY.bit.ENABLE == 1) {} - - USB->DEVICE.INTFLAG.reg |= USB->DEVICE.INTFLAG.reg; // clear pending - USB->DEVICE.INTENSET.reg = /* USB_DEVICE_INTENSET_SOF | */ USB_DEVICE_INTENSET_EORST; -} - -#if CFG_TUSB_MCU == OPT_MCU_SAMD51 || CFG_TUSB_MCU == OPT_MCU_SAME5X - -void dcd_int_enable(uint8_t rhport) -{ - (void) rhport; - NVIC_EnableIRQ(USB_0_IRQn); - NVIC_EnableIRQ(USB_1_IRQn); - NVIC_EnableIRQ(USB_2_IRQn); - NVIC_EnableIRQ(USB_3_IRQn); -} - -void dcd_int_disable(uint8_t rhport) -{ - (void) rhport; - NVIC_DisableIRQ(USB_3_IRQn); - NVIC_DisableIRQ(USB_2_IRQn); - NVIC_DisableIRQ(USB_1_IRQn); - NVIC_DisableIRQ(USB_0_IRQn); -} - -#elif CFG_TUSB_MCU == OPT_MCU_SAMD11 || CFG_TUSB_MCU == OPT_MCU_SAMD21 - -void dcd_int_enable(uint8_t rhport) -{ - (void) rhport; - NVIC_EnableIRQ(USB_IRQn); -} - -void dcd_int_disable(uint8_t rhport) -{ - (void) rhport; - NVIC_DisableIRQ(USB_IRQn); -} - -#else - -#error "No implementation available for dcd_int_enable / dcd_int_disable" - -#endif - -void dcd_set_address (uint8_t rhport, uint8_t dev_addr) -{ - (void) dev_addr; - - // Response with zlp status - dcd_edpt_xfer(rhport, 0x80, NULL, 0); - - // DCD can only set address after status for this request is complete - // do it at dcd_edpt0_status_complete() - - // Enable SUSPEND interrupt since the bus signal D+/D- are stable now. - USB->DEVICE.INTFLAG.reg = USB_DEVICE_INTENCLR_SUSPEND; // clear pending - USB->DEVICE.INTENSET.reg = USB_DEVICE_INTENSET_SUSPEND; -} - -void dcd_remote_wakeup(uint8_t rhport) -{ - (void) rhport; - USB->DEVICE.CTRLB.bit.UPRSM = 1; -} - -// disconnect by disabling internal pull-up resistor on D+/D- -void dcd_disconnect(uint8_t rhport) -{ - (void) rhport; - USB->DEVICE.CTRLB.reg |= USB_DEVICE_CTRLB_DETACH; -} - -// connect by enabling internal pull-up resistor on D+/D- -void dcd_connect(uint8_t rhport) -{ - (void) rhport; - USB->DEVICE.CTRLB.reg &= ~USB_DEVICE_CTRLB_DETACH; -} - -/*------------------------------------------------------------------*/ -/* DCD Endpoint port - *------------------------------------------------------------------*/ - -// Invoked when a control transfer's status stage is complete. -// May help DCD to prepare for next control transfer, this API is optional. -void dcd_edpt0_status_complete(uint8_t rhport, tusb_control_request_t const * request) -{ - (void) rhport; - - if (request->bmRequestType_bit.recipient == TUSB_REQ_RCPT_DEVICE && - request->bmRequestType_bit.type == TUSB_REQ_TYPE_STANDARD && - request->bRequest == TUSB_REQ_SET_ADDRESS ) - { - uint8_t const dev_addr = (uint8_t) request->wValue; - USB->DEVICE.DADD.reg = USB_DEVICE_DADD_DADD(dev_addr) | USB_DEVICE_DADD_ADDEN; - } - - // Just finished status stage, prepare for next setup packet - // Note: we may already prepare setup when queueing the control status. - // but it has no harm to do it again here - prepare_setup(); -} - -bool dcd_edpt_open (uint8_t rhport, tusb_desc_endpoint_t const * desc_edpt) -{ - (void) rhport; - - uint8_t const epnum = tu_edpt_number(desc_edpt->bEndpointAddress); - uint8_t const dir = tu_edpt_dir(desc_edpt->bEndpointAddress); - - UsbDeviceDescBank* bank = &sram_registers[epnum][dir]; - uint32_t size_value = 0; - while (size_value < 7) { - if (1 << (size_value + 3) == desc_edpt->wMaxPacketSize.size) { - break; - } - size_value++; - } - - // unsupported endpoint size - if ( size_value == 7 && desc_edpt->wMaxPacketSize.size != 1023 ) return false; - - bank->PCKSIZE.bit.SIZE = size_value; - - UsbDeviceEndpoint* ep = &USB->DEVICE.DeviceEndpoint[epnum]; - - if ( dir == TUSB_DIR_OUT ) - { - ep->EPCFG.bit.EPTYPE0 = desc_edpt->bmAttributes.xfer + 1; - ep->EPINTENSET.bit.TRCPT0 = true; - }else - { - ep->EPCFG.bit.EPTYPE1 = desc_edpt->bmAttributes.xfer + 1; - ep->EPINTENSET.bit.TRCPT1 = true; - } - - return true; -} - -bool dcd_edpt_xfer (uint8_t rhport, uint8_t ep_addr, uint8_t * buffer, uint16_t total_bytes) -{ - (void) rhport; - - uint8_t const epnum = tu_edpt_number(ep_addr); - uint8_t const dir = tu_edpt_dir(ep_addr); - - UsbDeviceDescBank* bank = &sram_registers[epnum][dir]; - UsbDeviceEndpoint* ep = &USB->DEVICE.DeviceEndpoint[epnum]; - - bank->ADDR.reg = (uint32_t) buffer; - - // A SETUP token can occur immediately after an ZLP Status. - // So make sure we have a valid buffer for setup packet. - // Status = ZLP EP0 with direction opposite to one in the dir bit of current setup - if ( (epnum == 0) && (buffer == NULL) && (total_bytes == 0) && (dir != tu_edpt_dir(_setup_packet[0])) ) { - prepare_setup(); - } - - if ( dir == TUSB_DIR_OUT ) - { - bank->PCKSIZE.bit.MULTI_PACKET_SIZE = total_bytes; - bank->PCKSIZE.bit.BYTE_COUNT = 0; - ep->EPSTATUSCLR.reg |= USB_DEVICE_EPSTATUSCLR_BK0RDY; - ep->EPINTFLAG.reg |= USB_DEVICE_EPINTFLAG_TRFAIL0; - } else - { - bank->PCKSIZE.bit.MULTI_PACKET_SIZE = 0; - bank->PCKSIZE.bit.BYTE_COUNT = total_bytes; - ep->EPSTATUSSET.reg |= USB_DEVICE_EPSTATUSSET_BK1RDY; - ep->EPINTFLAG.reg |= USB_DEVICE_EPINTFLAG_TRFAIL1; - } - - return true; -} - -void dcd_edpt_stall (uint8_t rhport, uint8_t ep_addr) -{ - (void) rhport; - - uint8_t const epnum = tu_edpt_number(ep_addr); - UsbDeviceEndpoint* ep = &USB->DEVICE.DeviceEndpoint[epnum]; - - if (tu_edpt_dir(ep_addr) == TUSB_DIR_IN) { - ep->EPSTATUSSET.reg = USB_DEVICE_EPSTATUSSET_STALLRQ1; - } else { - ep->EPSTATUSSET.reg = USB_DEVICE_EPSTATUSSET_STALLRQ0; - } -} - -void dcd_edpt_clear_stall (uint8_t rhport, uint8_t ep_addr) -{ - (void) rhport; - - uint8_t const epnum = tu_edpt_number(ep_addr); - UsbDeviceEndpoint* ep = &USB->DEVICE.DeviceEndpoint[epnum]; - - if (tu_edpt_dir(ep_addr) == TUSB_DIR_IN) { - ep->EPSTATUSCLR.reg = USB_DEVICE_EPSTATUSCLR_STALLRQ1 | USB_DEVICE_EPSTATUSCLR_DTGLIN; - } else { - ep->EPSTATUSCLR.reg = USB_DEVICE_EPSTATUSCLR_STALLRQ0 | USB_DEVICE_EPSTATUSCLR_DTGLOUT; - } -} - -//--------------------------------------------------------------------+ -// Interrupt Handler -//--------------------------------------------------------------------+ -void maybe_transfer_complete(void) { - uint32_t epints = USB->DEVICE.EPINTSMRY.reg; - - for (uint8_t epnum = 0; epnum < USB_EPT_NUM; epnum++) { - if ((epints & (1 << epnum)) == 0) { - continue; - } - - UsbDeviceEndpoint* ep = &USB->DEVICE.DeviceEndpoint[epnum]; - uint32_t epintflag = ep->EPINTFLAG.reg; - - // Handle IN completions - if ((epintflag & USB_DEVICE_EPINTFLAG_TRCPT1) != 0) { - UsbDeviceDescBank* bank = &sram_registers[epnum][TUSB_DIR_IN]; - uint16_t const total_transfer_size = bank->PCKSIZE.bit.BYTE_COUNT; - - dcd_event_xfer_complete(0, epnum | TUSB_DIR_IN_MASK, total_transfer_size, XFER_RESULT_SUCCESS, true); - - ep->EPINTFLAG.reg = USB_DEVICE_EPINTFLAG_TRCPT1; - } - - // Handle OUT completions - if ((epintflag & USB_DEVICE_EPINTFLAG_TRCPT0) != 0) { - UsbDeviceDescBank* bank = &sram_registers[epnum][TUSB_DIR_OUT]; - uint16_t const total_transfer_size = bank->PCKSIZE.bit.BYTE_COUNT; - - dcd_event_xfer_complete(0, epnum, total_transfer_size, XFER_RESULT_SUCCESS, true); - - ep->EPINTFLAG.reg = USB_DEVICE_EPINTFLAG_TRCPT0; - } - } -} - - -void dcd_int_handler (uint8_t rhport) -{ - (void) rhport; - - uint32_t int_status = USB->DEVICE.INTFLAG.reg & USB->DEVICE.INTENSET.reg; - - // Start of Frame - if ( int_status & USB_DEVICE_INTFLAG_SOF ) - { - USB->DEVICE.INTFLAG.reg = USB_DEVICE_INTFLAG_SOF; - dcd_event_bus_signal(0, DCD_EVENT_SOF, true); - } - - // SAMD doesn't distinguish between Suspend and Disconnect state. - // Both condition will cause SUSPEND interrupt triggered. - // To prevent being triggered when D+/D- are not stable, SUSPEND interrupt is only - // enabled when we received SET_ADDRESS request and cleared on Bus Reset - if ( int_status & USB_DEVICE_INTFLAG_SUSPEND ) - { - USB->DEVICE.INTFLAG.reg = USB_DEVICE_INTFLAG_SUSPEND; - - // Enable wakeup interrupt - USB->DEVICE.INTFLAG.reg = USB_DEVICE_INTFLAG_WAKEUP; // clear pending - USB->DEVICE.INTENSET.reg = USB_DEVICE_INTFLAG_WAKEUP; - - dcd_event_bus_signal(0, DCD_EVENT_SUSPEND, true); - } - - // Wakeup interrupt is only enabled when we got suspended. - // Wakeup interrupt will disable itself - if ( int_status & USB_DEVICE_INTFLAG_WAKEUP ) - { - USB->DEVICE.INTFLAG.reg = USB_DEVICE_INTFLAG_WAKEUP; - - // disable wakeup interrupt itself - USB->DEVICE.INTENCLR.reg = USB_DEVICE_INTFLAG_WAKEUP; - dcd_event_bus_signal(0, DCD_EVENT_RESUME, true); - } - - // Enable of Reset - if ( int_status & USB_DEVICE_INTFLAG_EORST ) - { - USB->DEVICE.INTFLAG.reg = USB_DEVICE_INTFLAG_EORST; - - // Disable both suspend and wakeup interrupt - USB->DEVICE.INTENCLR.reg = USB_DEVICE_INTFLAG_WAKEUP | USB_DEVICE_INTFLAG_SUSPEND; - - bus_reset(); - dcd_event_bus_signal(0, DCD_EVENT_BUS_RESET, true); - } - - // Handle SETUP packet - if (USB->DEVICE.DeviceEndpoint[0].EPINTFLAG.bit.RXSTP) - { - // This copies the data elsewhere so we can reuse the buffer. - dcd_event_setup_received(0, _setup_packet, true); - - // Although Setup packet only set RXSTP bit, - // TRCPT0 bit could already be set by previous ZLP OUT Status (not handled until now). - // Since control status complete event is optional, we can just clear TRCPT0 and skip the status event - USB->DEVICE.DeviceEndpoint[0].EPINTFLAG.reg = USB_DEVICE_EPINTFLAG_RXSTP | USB_DEVICE_EPINTFLAG_TRCPT0; - } - - // Handle complete transfer - maybe_transfer_complete(); -} - -#endif diff --git a/cores/nRF5/TinyUSB/Adafruit_TinyUSB_ArduinoCore/tinyusb/src/portable/microchip/samg/dcd_samg.c b/cores/nRF5/TinyUSB/Adafruit_TinyUSB_ArduinoCore/tinyusb/src/portable/microchip/samg/dcd_samg.c deleted file mode 100644 index 2b64df5..0000000 --- a/cores/nRF5/TinyUSB/Adafruit_TinyUSB_ArduinoCore/tinyusb/src/portable/microchip/samg/dcd_samg.c +++ /dev/null @@ -1,456 +0,0 @@ -/* - * The MIT License (MIT) - * - * Copyright (c) 2018, hathach (tinyusb.org) - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - * - * This file is part of the TinyUSB stack. - */ - -#include "tusb_option.h" - -#if CFG_TUSB_MCU == OPT_MCU_SAMG - -#include "sam.h" -#include "device/dcd.h" - -// TODO should support (SAM3S || SAM4S || SAM4E || SAMG55) - -//--------------------------------------------------------------------+ -// MACRO TYPEDEF CONSTANT ENUM DECLARATION -//--------------------------------------------------------------------+ - -#define EP_COUNT 6 - -// Transfer descriptor -typedef struct -{ - uint8_t* buffer; - uint16_t total_len; - volatile uint16_t actual_len; - uint16_t epsize; -} xfer_desc_t; - -// Endpoint 0-5, each can only be either OUT or In -xfer_desc_t _dcd_xfer[EP_COUNT]; - -void xfer_epsize_set(xfer_desc_t* xfer, uint16_t epsize) -{ - xfer->epsize = epsize; -} - -void xfer_begin(xfer_desc_t* xfer, uint8_t * buffer, uint16_t total_bytes) -{ - xfer->buffer = buffer; - xfer->total_len = total_bytes; - xfer->actual_len = 0; -} - -void xfer_end(xfer_desc_t* xfer) -{ - xfer->buffer = NULL; - xfer->total_len = 0; - xfer->actual_len = 0; -} - -uint16_t xfer_packet_len(xfer_desc_t* xfer) -{ - // also cover zero-length packet - return tu_min16(xfer->total_len - xfer->actual_len, xfer->epsize); -} - -void xfer_packet_done(xfer_desc_t* xfer) -{ - uint16_t const xact_len = xfer_packet_len(xfer); - - xfer->buffer += xact_len; - xfer->actual_len += xact_len; -} - -//------------- Transaction helpers -------------// - -// Write data to EP FIFO, return number of written bytes -static void xact_ep_write(uint8_t epnum, uint8_t* buffer, uint16_t xact_len) -{ - for(uint16_t i=0; iUDP_FDR[epnum] = (uint32_t) buffer[i]; - } -} - -// Read data from EP FIFO -static void xact_ep_read(uint8_t epnum, uint8_t* buffer, uint16_t xact_len) -{ - for(uint16_t i=0; iUDP_FDR[epnum]; - } -} - - -//! Bitmap for all status bits in CSR that are not affected by a value 1. -#define CSR_NO_EFFECT_1_ALL (UDP_CSR_RX_DATA_BK0 | UDP_CSR_RX_DATA_BK1 | UDP_CSR_STALLSENT | UDP_CSR_RXSETUP | UDP_CSR_TXCOMP) - -// Per Specs: CSR need synchronization each write -static inline void csr_write(uint8_t epnum, uint32_t value) -{ - uint32_t const csr = value; - UDP->UDP_CSR[epnum] = csr; - - volatile uint32_t nop_count; - for (nop_count = 0; nop_count < 20; nop_count ++) __NOP(); -} - -// Per Specs: CSR need synchronization each write -static inline void csr_set(uint8_t epnum, uint32_t mask) -{ - csr_write(epnum, UDP->UDP_CSR[epnum] | CSR_NO_EFFECT_1_ALL | mask); -} - -// Per Specs: CSR need synchronization each write -static inline void csr_clear(uint8_t epnum, uint32_t mask) -{ - csr_write(epnum, (UDP->UDP_CSR[epnum] | CSR_NO_EFFECT_1_ALL) & ~mask); -} - -/*------------------------------------------------------------------*/ -/* Device API - *------------------------------------------------------------------*/ - -// Set up endpoint 0, clear all other endpoints -static void bus_reset(void) -{ - tu_memclr(_dcd_xfer, sizeof(_dcd_xfer)); - - xfer_epsize_set(&_dcd_xfer[0], CFG_TUD_ENDPOINT0_SIZE); - - // Enable EP0 control - csr_write(0, UDP_CSR_EPEDS_Msk); - - // Enable interrupt : EP0, Suspend, Resume, Wakeup - UDP->UDP_IER = UDP_IER_EP0INT_Msk | UDP_IER_RXSUSP_Msk | UDP_IER_RXRSM_Msk | UDP_IER_WAKEUP_Msk; - - // Enable transceiver - UDP->UDP_TXVC &= ~UDP_TXVC_TXVDIS_Msk; -} - -// Initialize controller to device mode -void dcd_init (uint8_t rhport) -{ - tu_memclr(_dcd_xfer, sizeof(_dcd_xfer)); - dcd_connect(rhport); -} - -// Enable device interrupt -void dcd_int_enable (uint8_t rhport) -{ - (void) rhport; - NVIC_EnableIRQ(UDP_IRQn); -} - -// Disable device interrupt -void dcd_int_disable (uint8_t rhport) -{ - (void) rhport; - NVIC_DisableIRQ(UDP_IRQn); -} - -// Receive Set Address request, mcu port must also include status IN response -void dcd_set_address (uint8_t rhport, uint8_t dev_addr) -{ - (void) rhport; - (void) dev_addr; - - // Response with zlp status - dcd_edpt_xfer(rhport, 0x80, NULL, 0); - - // DCD can only set address after status for this request is complete. - // do it at dcd_edpt0_status_complete() -} - -// Wake up host -void dcd_remote_wakeup (uint8_t rhport) -{ - (void) rhport; -} - -void dcd_connect(uint8_t rhport) -{ - (void) rhport; - - // Enable pull-up, disable transceiver - UDP->UDP_TXVC = UDP_TXVC_PUON | UDP_TXVC_TXVDIS_Msk; -} - -void dcd_disconnect(uint8_t rhport) -{ - (void) rhport; - - // disable both pullup and transceiver - UDP->UDP_TXVC = UDP_TXVC_TXVDIS_Msk; -} - -//--------------------------------------------------------------------+ -// Endpoint API -//--------------------------------------------------------------------+ - -// Invoked when a control transfer's status stage is complete. -// May help DCD to prepare for next control transfer, this API is optional. -void dcd_edpt0_status_complete(uint8_t rhport, tusb_control_request_t const * request) -{ - (void) rhport; - - if (request->bmRequestType_bit.recipient == TUSB_REQ_RCPT_DEVICE && - request->bmRequestType_bit.type == TUSB_REQ_TYPE_STANDARD ) - { - if (request->bRequest == TUSB_REQ_SET_ADDRESS) - { - uint8_t const dev_addr = (uint8_t) request->wValue; - - // Enable addressed state - UDP->UDP_GLB_STAT |= UDP_GLB_STAT_FADDEN_Msk; - - // Set new address & Function enable bit - UDP->UDP_FADDR = UDP_FADDR_FEN_Msk | UDP_FADDR_FADD(dev_addr); - } - else if (request->bRequest == TUSB_REQ_SET_CONFIGURATION) - { - // Configured State - UDP->UDP_GLB_STAT |= UDP_GLB_STAT_CONFG_Msk; - } - } -} - -// Configure endpoint's registers according to descriptor -// SAMG doesn't support a same endpoint number with IN and OUT -// e.g EP1 OUT & EP1 IN cannot exist together -bool dcd_edpt_open (uint8_t rhport, tusb_desc_endpoint_t const * ep_desc) -{ - (void) rhport; - - uint8_t const epnum = tu_edpt_number(ep_desc->bEndpointAddress); - uint8_t const dir = tu_edpt_dir(ep_desc->bEndpointAddress); - - // TODO Isochronous is not supported yet - TU_VERIFY(ep_desc->bmAttributes.xfer != TUSB_XFER_ISOCHRONOUS); - TU_VERIFY(epnum < EP_COUNT); - - // Must not already enabled - TU_ASSERT((UDP->UDP_CSR[epnum] & UDP_CSR_EPEDS_Msk) == 0); - - xfer_epsize_set(&_dcd_xfer[epnum], ep_desc->wMaxPacketSize.size); - - // Configure type and enable EP - csr_write(epnum, UDP_CSR_EPEDS_Msk | UDP_CSR_EPTYPE(ep_desc->bmAttributes.xfer + 4*dir)); - - // Enable EP Interrupt for IN - if (dir == TUSB_DIR_IN) UDP->UDP_IER |= (1 << epnum); - - return true; -} - -// Submit a transfer, When complete dcd_event_xfer_complete() is invoked to notify the stack -bool dcd_edpt_xfer (uint8_t rhport, uint8_t ep_addr, uint8_t * buffer, uint16_t total_bytes) -{ - (void) rhport; - - uint8_t const epnum = tu_edpt_number(ep_addr); - uint8_t const dir = tu_edpt_dir(ep_addr); - - xfer_desc_t* xfer = &_dcd_xfer[epnum]; - xfer_begin(xfer, buffer, total_bytes); - - if (dir == TUSB_DIR_OUT) - { - // Enable interrupt when starting OUT transfer - if (epnum != 0) UDP->UDP_IER |= (1 << epnum); - } - else - { - xact_ep_write(epnum, xfer->buffer, xfer_packet_len(xfer)); - - // TX ready for transfer - csr_set(epnum, UDP_CSR_TXPKTRDY_Msk); - } - - return true; -} - -// Stall endpoint -void dcd_edpt_stall (uint8_t rhport, uint8_t ep_addr) -{ - (void) rhport; - - // For EP0 USBD will stall both EP0 Out and In with 0x00 and 0x80 - // only handle one by skipping 0x80 - if ( ep_addr == tu_edpt_addr(0, TUSB_DIR_IN_MASK) ) return; - - uint8_t const epnum = tu_edpt_number(ep_addr); - - // Set force stall bit - csr_set(epnum, UDP_CSR_FORCESTALL_Msk); -} - -// clear stall, data toggle is also reset to DATA0 -void dcd_edpt_clear_stall (uint8_t rhport, uint8_t ep_addr) -{ - (void) rhport; - - uint8_t const epnum = tu_edpt_number(ep_addr); - - // clear stall - csr_clear(epnum, UDP_CSR_FORCESTALL_Msk); - - // must also reset EP to clear data toggle - UDP->UDP_RST_EP |= (1 << epnum); - UDP->UDP_RST_EP &= ~(1 << epnum); -} - -//--------------------------------------------------------------------+ -// ISR -//--------------------------------------------------------------------+ -void dcd_int_handler(uint8_t rhport) -{ - uint32_t const intr_mask = UDP->UDP_IMR; - uint32_t const intr_status = UDP->UDP_ISR & intr_mask; - - // clear interrupt - UDP->UDP_ICR = intr_status; - - // Bus reset - if (intr_status & UDP_ISR_ENDBUSRES_Msk) - { - bus_reset(); - dcd_event_bus_signal(rhport, DCD_EVENT_BUS_RESET, true); - } - - // SOF -// if (intr_status & UDP_ISR_SOFINT_Msk) dcd_event_bus_signal(rhport, DCD_EVENT_SOF, true); - - // Suspend - if (intr_status & UDP_ISR_RXSUSP_Msk) dcd_event_bus_signal(rhport, DCD_EVENT_SUSPEND, true); - - // Resume - if (intr_status & UDP_ISR_RXRSM_Msk) dcd_event_bus_signal(rhport, DCD_EVENT_RESUME, true); - - // Wakeup - if (intr_status & UDP_ISR_WAKEUP_Msk) dcd_event_bus_signal(rhport, DCD_EVENT_RESUME, true); - - //------------- Endpoints -------------// - - if ( intr_status & TU_BIT(0) ) - { - // setup packet - if ( UDP->UDP_CSR[0] & UDP_CSR_RXSETUP ) - { - // get setup from FIFO - uint8_t setup[8]; - for(uint8_t i=0; iUDP_FDR[0]; - } - - // notify usbd - dcd_event_setup_received(rhport, setup, true); - - // Set EP direction bit according to DATA stage - // MUST only be set before RXSETUP is clear per specs - if ( tu_edpt_dir(setup[0]) ) - { - csr_set(0, UDP_CSR_DIR_Msk); - } - else - { - csr_clear(0, UDP_CSR_DIR_Msk); - } - - // Clear Setup, stall and other on-going transfer bits - csr_clear(0, UDP_CSR_RXSETUP_Msk | UDP_CSR_TXPKTRDY_Msk | UDP_CSR_TXCOMP_Msk | UDP_CSR_RX_DATA_BK0 | UDP_CSR_RX_DATA_BK1 | UDP_CSR_STALLSENT_Msk | UDP_CSR_FORCESTALL_Msk); - } - } - - for(uint8_t epnum = 0; epnum < EP_COUNT; epnum++) - { - if ( intr_status & TU_BIT(epnum) ) - { - xfer_desc_t* xfer = &_dcd_xfer[epnum]; - - //------------- Endpoint IN -------------// - if (UDP->UDP_CSR[epnum] & UDP_CSR_TXCOMP_Msk) - { - xfer_packet_done(xfer); - - uint16_t const xact_len = xfer_packet_len(xfer); - - if (xact_len) - { - // write to EP fifo - xact_ep_write(epnum, xfer->buffer, xact_len); - - // TX ready for transfer - csr_set(epnum, UDP_CSR_TXPKTRDY_Msk); - }else - { - // xfer is complete - dcd_event_xfer_complete(rhport, epnum | TUSB_DIR_IN_MASK, xfer->actual_len, XFER_RESULT_SUCCESS, true); - - // Required since control OUT can happen right after before stack handle this event - xfer_end(xfer); - } - - // Clear TX Complete bit - csr_clear(epnum, UDP_CSR_TXCOMP_Msk); - } - - //------------- Endpoint OUT -------------// - // Ping-Pong is a MUST for Bulk/Iso - // NOTE: When both Bank0 and Bank1 are both set, there is no way to know which one comes first - uint32_t const banks_complete = UDP->UDP_CSR[epnum] & (UDP_CSR_RX_DATA_BK0_Msk | UDP_CSR_RX_DATA_BK1_Msk); - if (banks_complete) - { - uint16_t const xact_len = (uint16_t) ((UDP->UDP_CSR[epnum] & UDP_CSR_RXBYTECNT_Msk) >> UDP_CSR_RXBYTECNT_Pos); - - // Read from EP fifo - xact_ep_read(epnum, xfer->buffer, xact_len); - xfer_packet_done(xfer); - - if ( 0 == xfer_packet_len(xfer) ) - { - // Disable OUT EP interrupt when transfer is complete - if (epnum != 0) UDP->UDP_IDR |= (1 << epnum); - - dcd_event_xfer_complete(rhport, epnum, xfer->actual_len, XFER_RESULT_SUCCESS, true); - xfer_end(xfer); - } - - // Clear DATA Bank0/1 bit - csr_clear(epnum, banks_complete); - } - - // Stall sent to host - if (UDP->UDP_CSR[epnum] & UDP_CSR_STALLSENT_Msk) - { - csr_clear(epnum, UDP_CSR_STALLSENT_Msk); - } - } - } -} - -#endif diff --git a/cores/nRF5/TinyUSB/Adafruit_TinyUSB_ArduinoCore/tinyusb/src/portable/nordic/nrf5x/dcd_nrf5x.c b/cores/nRF5/TinyUSB/Adafruit_TinyUSB_ArduinoCore/tinyusb/src/portable/nordic/nrf5x/dcd_nrf5x.c index 677db39..9e5f511 100644 --- a/cores/nRF5/TinyUSB/Adafruit_TinyUSB_ArduinoCore/tinyusb/src/portable/nordic/nrf5x/dcd_nrf5x.c +++ b/cores/nRF5/TinyUSB/Adafruit_TinyUSB_ArduinoCore/tinyusb/src/portable/nordic/nrf5x/dcd_nrf5x.c @@ -26,217 +26,228 @@ #include "tusb_option.h" -#if TUSB_OPT_DEVICE_ENABLED && CFG_TUSB_MCU == OPT_MCU_NRF5X +#if CFG_TUD_ENABLED && CFG_TUSB_MCU == OPT_MCU_NRF5X + +#include + +// Suppress warning caused by nrfx driver +#ifdef __GNUC__ +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wcast-qual" +#pragma GCC diagnostic ignored "-Wcast-align" +#pragma GCC diagnostic ignored "-Wunused-parameter" +#endif #include "nrf.h" -#include "nrf_clock.h" -#include "nrf_power.h" -#include "nrfx_usbd_errata.h" +#include "nrfx_clock.h" +#include "nrf_erratas.h" + +#ifdef __GNUC__ +#pragma GCC diagnostic pop +#endif + #include "device/dcd.h" // TODO remove later #include "device/usbd.h" #include "device/usbd_pvt.h" // to use defer function helper +#if CFG_TUSB_OS == OPT_OS_MYNEWT +#include "mcu/mcu.h" +#endif + +/* Try to detect nrfx version if not configured with CFG_TUD_NRF_NRFX_VERSION + * nrfx v1 and v2 are concurrently developed. There is no NRFX_VERSION only MDK VERSION which is as follows: + * - v3.0.0: 8.53.1 (conflict with v2.11.0), v3.1.0: 8.55.0 ... + * - v2.11.0: 8.53.1, v2.6.0: 8.44.1, v2.5.0: 8.40.2, v2.4.0: 8.37.0, v2.3.0: 8.35.0, v2.2.0: 8.32.1, v2.1.0: 8.30.2, v2.0.0: 8.29.0 + * - v1.9.0: 8.40.3, v1.8.6: 8.35.0 (conflict with v2.3.0), v1.8.5: 8.32.3, v1.8.4: 8.32.1 (conflict with v2.2.0), + * v1.8.2: 8.32.1 (conflict with v2.2.0), v1.8.1: 8.27.1 + * Therefore the check for v1 would be: + * - MDK < 8.29.0 (v2.0), MDK == 8.32.3, 8.40.3 + * - in case of conflict User of those version must upgrade to other 1.x version or set CFG_TUD_NRF_NRFX_VERSION +*/ +#ifndef CFG_TUD_NRF_NRFX_VERSION + #define _MDK_VERSION (10000*MDK_MAJOR_VERSION + 100*MDK_MINOR_VERSION + MDK_MICRO_VERSION) + + #if _MDK_VERSION < 82900 || _MDK_VERSION == 83203 || _MDK_VERSION == 84003 + // nrfx <= 1.8.1, or 1.8.5 or 1.9.0 + #define CFG_TUD_NRF_NRFX_VERSION 1 + #else + #define CFG_TUD_NRF_NRFX_VERSION 2 + #endif +#endif + /*------------------------------------------------------------------*/ /* MACRO TYPEDEF CONSTANT ENUM *------------------------------------------------------------------*/ -enum -{ +enum { // Max allowed by USB specs - MAX_PACKET_SIZE = 64, + MAX_PACKET_SIZE = 64, // Mask of all END event (IN & OUT) for all endpoints. ENDEPIN0-7, ENDEPOUT0-7, ENDISOIN, ENDISOOUT EDPT_END_ALL_MASK = (0xff << USBD_INTEN_ENDEPIN0_Pos) | (0xff << USBD_INTEN_ENDEPOUT0_Pos) | USBD_INTENCLR_ENDISOIN_Msk | USBD_INTEN_ENDISOOUT_Msk }; -enum -{ - EP_COUNT = 8 +enum { + EP_ISO_NUM = 8, // Endpoint number is fixed (8) for ISOOUT and ISOIN + EP_CBI_COUNT = 8 // Control Bulk Interrupt endpoints count }; -// Transfer descriptor -typedef struct -{ +// Transfer Descriptor +typedef struct { uint8_t* buffer; uint16_t total_len; volatile uint16_t actual_len; - uint8_t mps; // max packet size + uint16_t mps; // max packet size - // nrf52840 will auto ACK OUT packet after DMA is done + // nRF will auto accept OUT packet after DMA is done // indicate packet is already ACK volatile bool data_received; + volatile bool started; + + // Set to true when data was transferred from RAM to ISO IN output buffer. + // New data can be put in ISO IN output buffer after SOF. + bool iso_in_transfer_ready; } xfer_td_t; // Data for managing dcd -static struct -{ +static struct { // All 8 endpoints including control IN & OUT (offset 1) - xfer_td_t xfer[EP_COUNT][2]; + // +1 for ISO endpoints + xfer_td_t xfer[EP_CBI_COUNT + 1][2]; + + // nRF can only carry one DMA at a time, this is used to guard the access to EasyDMA + atomic_flag dma_running; - // Number of pending DMA that is started but not handled yet by dcd_int_handler(). - // Since nRF can only carry one DMA can run at a time, this value is normally be either 0 or 1. - // However, in critical section with interrupt disabled, the DMA can be finished and added up - // until handled by dcd_init_handler() when exiting critical section. - volatile uint8_t dma_pending; -}_dcd; + // Track whether sof has been manually enabled + bool sof_enabled; +} _dcd; /*------------------------------------------------------------------*/ /* Control / Bulk / Interrupt (CBI) Transfer *------------------------------------------------------------------*/ -// NVIC_GetEnableIRQ is only available in CMSIS v5 -#ifndef NVIC_GetEnableIRQ -static inline uint32_t NVIC_GetEnableIRQ(IRQn_Type IRQn) -{ - if ((int32_t)(IRQn) >= 0) - { - return((uint32_t)(((NVIC->ISER[(((uint32_t)(int32_t)IRQn) >> 5UL)] & (1UL << (((uint32_t)(int32_t)IRQn) & 0x1FUL))) != 0UL) ? 1UL : 0UL)); - } - else - { - return(0U); - } +// check if we are in ISR +TU_ATTR_ALWAYS_INLINE static inline bool is_in_isr(void) { + return (SCB->ICSR & SCB_ICSR_VECTACTIVE_Msk) ? true : false; } -#endif // helper to start DMA -static void edpt_dma_start(volatile uint32_t* reg_startep) -{ - // Only one dma can be active - if ( _dcd.dma_pending ) - { - if (SCB->ICSR & SCB_ICSR_VECTACTIVE_Msk) - { - // Called within ISR, use usbd task to defer later - usbd_defer_func( (osal_task_func_t) edpt_dma_start, (void*) reg_startep, true ); - return; - } - else - { - if ( __get_PRIMASK() || !NVIC_GetEnableIRQ(USBD_IRQn) ) - { - // Called in critical section with interrupt disabled. We have to manually check - // for the DMA complete by comparing current pending DMA with number of ENDED Events - uint32_t ended = 0; - - while ( _dcd.dma_pending < ((uint8_t) ended) ) - { - ended = NRF_USBD->EVENTS_ENDISOIN + NRF_USBD->EVENTS_ENDISOOUT; - - for (uint8_t i=0; iEVENTS_ENDEPIN[i] + NRF_USBD->EVENTS_ENDEPOUT[i]; - } - } - }else - { - // Called in non-critical thread-mode, should be 99% of the time. - // Should be safe to blocking wait until previous DMA transfer complete - while ( _dcd.dma_pending ) { } - } - } +static void start_dma(volatile uint32_t* reg_startep) { + (*reg_startep) = 1; + __ISB(); + __DSB(); + + // TASKS_EP0STATUS, TASKS_EP0RCVOUT seem to need EasyDMA to be available + // However these don't trigger any DMA transfer and got ENDED event subsequently + // Therefore dma_pending is corrected right away + if ((reg_startep == &NRF_USBD->TASKS_EP0STATUS) || (reg_startep == &NRF_USBD->TASKS_EP0RCVOUT)) { + atomic_flag_clear(&_dcd.dma_running); } +} - _dcd.dma_pending++; - - (*reg_startep) = 1; - __ISB(); __DSB(); +static void edpt_dma_start(volatile uint32_t* reg_startep) { + if (atomic_flag_test_and_set(&_dcd.dma_running)) { + usbd_defer_func((osal_task_func_t)(uintptr_t ) edpt_dma_start, (void*) (uintptr_t) reg_startep, is_in_isr()); + } else { + start_dma(reg_startep); + } } // DMA is complete -static void edpt_dma_end(void) -{ - TU_ASSERT(_dcd.dma_pending, ); - _dcd.dma_pending = 0; +static void edpt_dma_end(void) { + atomic_flag_clear(&_dcd.dma_running); } // helper getting td -static inline xfer_td_t* get_td(uint8_t epnum, uint8_t dir) -{ +static inline xfer_td_t* get_td(uint8_t epnum, uint8_t dir) { return &_dcd.xfer[epnum][dir]; } -/*------------- CBI OUT Transfer -------------*/ +static void xact_out_dma(uint8_t epnum); -// Prepare for a CBI transaction OUT, call at the start -// Allow ACK incoming data -static void xact_out_prepare(uint8_t epnum) -{ - if ( epnum == 0 ) - { - NRF_USBD->TASKS_EP0RCVOUT = 1; - } - else - { - // Write zero value to SIZE register will allow hw to ACK (accept data) - // If it is not already done by DMA - NRF_USBD->SIZE.EPOUT[epnum] = 0; - } - - __ISB(); __DSB(); +// Function wraps xact_out_dma which wants uint8_t while usbd_defer_func wants void (*)(void *) +static void xact_out_dma_wrapper(void* epnum) { + xact_out_dma((uint8_t) ((uintptr_t) epnum)); } // Start DMA to move data from Endpoint -> RAM -static void xact_out_dma(uint8_t epnum) -{ +static void xact_out_dma(uint8_t epnum) { xfer_td_t* xfer = get_td(epnum, TUSB_DIR_OUT); + uint32_t xact_len; - uint8_t const xact_len = NRF_USBD->SIZE.EPOUT[epnum]; - - // Trigger DMA move data from Endpoint -> SRAM - NRF_USBD->EPOUT[epnum].PTR = (uint32_t) xfer->buffer; - NRF_USBD->EPOUT[epnum].MAXCNT = xact_len; + // DMA can't be active during read of SIZE.EPOUT or SIZE.ISOOUT, so try to lock, + // If already running defer call regardless if it was called from ISR or task, + if (atomic_flag_test_and_set(&_dcd.dma_running)) { + usbd_defer_func((osal_task_func_t) xact_out_dma_wrapper, (void*) (uint32_t) epnum, is_in_isr()); + return; + } + if (epnum == EP_ISO_NUM) { + xact_len = NRF_USBD->SIZE.ISOOUT; + // If ZERO bit is set, ignore ISOOUT length + if (xact_len & USBD_SIZE_ISOOUT_ZERO_Msk) { + xact_len = 0; + atomic_flag_clear(&_dcd.dma_running); + } else { + if (xfer->started) { + // Trigger DMA move data from Endpoint -> SRAM + NRF_USBD->ISOOUT.PTR = (uint32_t) xfer->buffer; + NRF_USBD->ISOOUT.MAXCNT = xact_len; + + start_dma(&NRF_USBD->TASKS_STARTISOOUT); + } else { + atomic_flag_clear(&_dcd.dma_running); + } + } + } else { + // limit xact len to remaining length + xact_len = tu_min16((uint16_t) NRF_USBD->SIZE.EPOUT[epnum], xfer->total_len - xfer->actual_len); - edpt_dma_start(&NRF_USBD->TASKS_STARTEPOUT[epnum]); + // Trigger DMA move data from Endpoint -> SRAM + NRF_USBD->EPOUT[epnum].PTR = (uint32_t) xfer->buffer; + NRF_USBD->EPOUT[epnum].MAXCNT = xact_len; - xfer->buffer += xact_len; - xfer->actual_len += xact_len; + start_dma(&NRF_USBD->TASKS_STARTEPOUT[epnum]); + } } -/*------------- CBI IN Transfer -------------*/ - // Prepare for a CBI transaction IN, call at the start // it start DMA to transfer data from RAM -> Endpoint -static void xact_in_prepare(uint8_t epnum) -{ +static void xact_in_dma(uint8_t epnum) { xfer_td_t* xfer = get_td(epnum, TUSB_DIR_IN); // Each transaction is up to Max Packet Size - uint8_t const xact_len = tu_min16(xfer->total_len - xfer->actual_len, xfer->mps); + uint16_t const xact_len = tu_min16(xfer->total_len - xfer->actual_len, xfer->mps); - NRF_USBD->EPIN[epnum].PTR = (uint32_t) xfer->buffer; + NRF_USBD->EPIN[epnum].PTR = (uint32_t) xfer->buffer; NRF_USBD->EPIN[epnum].MAXCNT = xact_len; - xfer->buffer += xact_len; - edpt_dma_start(&NRF_USBD->TASKS_STARTEPIN[epnum]); } //--------------------------------------------------------------------+ // Controller API //--------------------------------------------------------------------+ -void dcd_init (uint8_t rhport) -{ +bool dcd_init(uint8_t rhport, const tusb_rhport_init_t* rh_init) { (void) rhport; + (void) rh_init; + TU_LOG2("dcd init\r\n"); + return true; } -void dcd_int_enable(uint8_t rhport) -{ +void dcd_int_enable(uint8_t rhport) { (void) rhport; NVIC_EnableIRQ(USBD_IRQn); } -void dcd_int_disable(uint8_t rhport) -{ +void dcd_int_disable(uint8_t rhport) { (void) rhport; NVIC_DisableIRQ(USBD_IRQn); } -void dcd_set_address (uint8_t rhport, uint8_t dev_addr) -{ +void dcd_set_address(uint8_t rhport, uint8_t dev_addr) { (void) rhport; (void) dev_addr; // Set Address is automatically update by hw controller, nothing to do @@ -251,24 +262,16 @@ void dcd_set_address (uint8_t rhport, uint8_t dev_addr) NRF_USBD->INTENSET = USBD_INTEN_USBEVENT_Msk; } -void dcd_remote_wakeup(uint8_t rhport) -{ +void dcd_remote_wakeup(uint8_t rhport) { (void) rhport; // Bring controller out of low power mode + // will start wakeup when USBWUALLOWED is set NRF_USBD->LOWPOWER = 0; - - // Initiate RESUME signal - NRF_USBD->DPDMVALUE = USBD_DPDMVALUE_STATE_Resume; - NRF_USBD->TASKS_DPDMDRIVE = 1; - - // TODO There is no USBEVENT Resume interrupt - // We may manually raise DCD_EVENT_RESUME event here } // disconnect by disabling internal pull-up resistor on D+/D- -void dcd_disconnect(uint8_t rhport) -{ +void dcd_disconnect(uint8_t rhport) { (void) rhport; NRF_USBD->USBPULLUP = 0; @@ -278,214 +281,411 @@ void dcd_disconnect(uint8_t rhport) } // connect by enabling internal pull-up resistor on D+/D- -void dcd_connect(uint8_t rhport) -{ +void dcd_connect(uint8_t rhport) { (void) rhport; NRF_USBD->USBPULLUP = 1; } +void dcd_sof_enable(uint8_t rhport, bool en) { + (void) rhport; + if (en) { + _dcd.sof_enabled = true; + NRF_USBD->INTENSET = USBD_INTENSET_SOF_Msk; + } else { + _dcd.sof_enabled = false; + NRF_USBD->INTENCLR = USBD_INTENCLR_SOF_Msk; + } +} + //--------------------------------------------------------------------+ // Endpoint API //--------------------------------------------------------------------+ -bool dcd_edpt_open (uint8_t rhport, tusb_desc_endpoint_t const * desc_edpt) -{ +bool dcd_edpt_open(uint8_t rhport, tusb_desc_endpoint_t const* desc_edpt) { (void) rhport; - uint8_t const epnum = tu_edpt_number(desc_edpt->bEndpointAddress); - uint8_t const dir = tu_edpt_dir(desc_edpt->bEndpointAddress); + uint8_t const ep_addr = desc_edpt->bEndpointAddress; + uint8_t const epnum = tu_edpt_number(ep_addr); + uint8_t const dir = tu_edpt_dir(ep_addr); + + _dcd.xfer[epnum][dir].mps = tu_edpt_packet_size(desc_edpt); + + if (desc_edpt->bmAttributes.xfer != TUSB_XFER_ISOCHRONOUS) { + if (dir == TUSB_DIR_OUT) { + NRF_USBD->INTENSET = TU_BIT(USBD_INTEN_ENDEPOUT0_Pos + epnum); + NRF_USBD->EPOUTEN |= TU_BIT(epnum); + + // Write any value to SIZE register will allow nRF to ACK/accept data + NRF_USBD->SIZE.EPOUT[epnum] = 0; + } else { + NRF_USBD->INTENSET = TU_BIT(USBD_INTEN_ENDEPIN0_Pos + epnum); + NRF_USBD->EPINEN |= TU_BIT(epnum); + } + // clear stall and reset DataToggle + NRF_USBD->EPSTALL = (USBD_EPSTALL_STALL_UnStall << USBD_EPSTALL_STALL_Pos) | ep_addr; + NRF_USBD->DTOGGLE = (USBD_DTOGGLE_VALUE_Data0 << USBD_DTOGGLE_VALUE_Pos) | ep_addr; + } else { + TU_ASSERT(epnum == EP_ISO_NUM); + if (dir == TUSB_DIR_OUT) { + // SPLIT ISO buffer when ISO IN endpoint is already opened. + if (_dcd.xfer[EP_ISO_NUM][TUSB_DIR_IN].mps) NRF_USBD->ISOSPLIT = USBD_ISOSPLIT_SPLIT_HalfIN; + + // Clear old events + NRF_USBD->EVENTS_ENDISOOUT = 0; + + // Clear SOF event in case interrupt was not enabled yet. + if ((NRF_USBD->INTEN & USBD_INTEN_SOF_Msk) == 0) NRF_USBD->EVENTS_SOF = 0; + + // Enable SOF and ISOOUT interrupts, and ISOOUT endpoint. + NRF_USBD->INTENSET = USBD_INTENSET_ENDISOOUT_Msk | USBD_INTENSET_SOF_Msk; + NRF_USBD->EPOUTEN |= USBD_EPOUTEN_ISOOUT_Msk; + } else { + NRF_USBD->EVENTS_ENDISOIN = 0; - _dcd.xfer[epnum][dir].mps = desc_edpt->wMaxPacketSize.size; + // SPLIT ISO buffer when ISO OUT endpoint is already opened. + if (_dcd.xfer[EP_ISO_NUM][TUSB_DIR_OUT].mps) NRF_USBD->ISOSPLIT = USBD_ISOSPLIT_SPLIT_HalfIN; - if ( dir == TUSB_DIR_OUT ) - { - NRF_USBD->INTENSET = TU_BIT(USBD_INTEN_ENDEPOUT0_Pos + epnum); - NRF_USBD->EPOUTEN |= TU_BIT(epnum); - }else - { - NRF_USBD->INTENSET = TU_BIT(USBD_INTEN_ENDEPIN0_Pos + epnum); - NRF_USBD->EPINEN |= TU_BIT(epnum); + // Clear SOF event in case interrupt was not enabled yet. + if ((NRF_USBD->INTEN & USBD_INTEN_SOF_Msk) == 0) NRF_USBD->EVENTS_SOF = 0; + + // Enable SOF and ISOIN interrupts, and ISOIN endpoint. + NRF_USBD->INTENSET = USBD_INTENSET_ENDISOIN_Msk | USBD_INTENSET_SOF_Msk; + NRF_USBD->EPINEN |= USBD_EPINEN_ISOIN_Msk; + } } - __ISB(); __DSB(); + + __ISB(); + __DSB(); return true; } -bool dcd_edpt_xfer (uint8_t rhport, uint8_t ep_addr, uint8_t * buffer, uint16_t total_bytes) -{ +void dcd_edpt_close_all(uint8_t rhport) { + // disable interrupt to prevent race condition + dcd_int_disable(rhport); + + // disable all non-control (bulk + interrupt) endpoints + for (uint8_t ep = 1; ep < EP_CBI_COUNT; ep++) { + NRF_USBD->INTENCLR = TU_BIT(USBD_INTEN_ENDEPOUT0_Pos + ep) | TU_BIT(USBD_INTEN_ENDEPIN0_Pos + ep); + + NRF_USBD->TASKS_STARTEPIN[ep] = 0; + NRF_USBD->TASKS_STARTEPOUT[ep] = 0; + + tu_memclr(_dcd.xfer[ep], 2 * sizeof(xfer_td_t)); + } + + // disable both ISO + NRF_USBD->INTENCLR = USBD_INTENCLR_SOF_Msk | USBD_INTENCLR_ENDISOOUT_Msk | USBD_INTENCLR_ENDISOIN_Msk; + NRF_USBD->ISOSPLIT = USBD_ISOSPLIT_SPLIT_OneDir; + + NRF_USBD->TASKS_STARTISOIN = 0; + NRF_USBD->TASKS_STARTISOOUT = 0; + + tu_memclr(_dcd.xfer[EP_ISO_NUM], 2 * sizeof(xfer_td_t)); + + // de-activate all non-control + NRF_USBD->EPOUTEN = 1UL; + NRF_USBD->EPINEN = 1UL; + + dcd_int_enable(rhport); +} + +void dcd_edpt_close(uint8_t rhport, uint8_t ep_addr) { + (void) rhport; + + uint8_t const epnum = tu_edpt_number(ep_addr); + uint8_t const dir = tu_edpt_dir(ep_addr); + + if (epnum != EP_ISO_NUM) { + // CBI + if (dir == TUSB_DIR_OUT) { + NRF_USBD->INTENCLR = TU_BIT(USBD_INTEN_ENDEPOUT0_Pos + epnum); + NRF_USBD->EPOUTEN &= ~TU_BIT(epnum); + } else { + NRF_USBD->INTENCLR = TU_BIT(USBD_INTEN_ENDEPIN0_Pos + epnum); + NRF_USBD->EPINEN &= ~TU_BIT(epnum); + } + } else { + _dcd.xfer[EP_ISO_NUM][dir].mps = 0; + // ISO + if (dir == TUSB_DIR_OUT) { + NRF_USBD->INTENCLR = USBD_INTENCLR_ENDISOOUT_Msk; + NRF_USBD->EPOUTEN &= ~USBD_EPOUTEN_ISOOUT_Msk; + NRF_USBD->EVENTS_ENDISOOUT = 0; + } else { + NRF_USBD->INTENCLR = USBD_INTENCLR_ENDISOIN_Msk; + NRF_USBD->EPINEN &= ~USBD_EPINEN_ISOIN_Msk; + } + // One of the ISO endpoints closed, no need to split buffers any more. + NRF_USBD->ISOSPLIT = USBD_ISOSPLIT_SPLIT_OneDir; + // When both ISO endpoint are close there is no need for SOF any more. + if (_dcd.xfer[EP_ISO_NUM][TUSB_DIR_IN].mps + _dcd.xfer[EP_ISO_NUM][TUSB_DIR_OUT].mps == 0) + NRF_USBD->INTENCLR = USBD_INTENCLR_SOF_Msk; + } + _dcd.xfer[epnum][dir].started = false; + __ISB(); + __DSB(); +} + +bool dcd_edpt_xfer(uint8_t rhport, uint8_t ep_addr, uint8_t* buffer, uint16_t total_bytes) { (void) rhport; uint8_t const epnum = tu_edpt_number(ep_addr); - uint8_t const dir = tu_edpt_dir(ep_addr); + uint8_t const dir = tu_edpt_dir(ep_addr); xfer_td_t* xfer = get_td(epnum, dir); - xfer->buffer = buffer; - xfer->total_len = total_bytes; + TU_ASSERT(!xfer->started); + xfer->buffer = buffer; + xfer->total_len = total_bytes; xfer->actual_len = 0; // Control endpoint with zero-length packet and opposite direction to 1st request byte --> status stage bool const control_status = (epnum == 0 && total_bytes == 0 && dir != tu_edpt_dir(NRF_USBD->BMREQUESTTYPE)); - if ( control_status ) - { - // Status Phase also requires Easy DMA has to be available as well !!!! - // However TASKS_EP0STATUS doesn't trigger any DMA transfer and got ENDED event subsequently - // Therefore dma_running state will be corrected right away - edpt_dma_start(&NRF_USBD->TASKS_EP0STATUS); - if (_dcd.dma_pending) _dcd.dma_pending--; // correct the dma_running++ in dma start - + if (control_status) { // The nRF doesn't interrupt on status transmit so we queue up a success response. - dcd_event_xfer_complete(0, ep_addr, 0, XFER_RESULT_SUCCESS, false); - } - else if ( dir == TUSB_DIR_OUT ) - { - if ( xfer->data_received ) - { - // nrf52840 auto ACK OUT packet after DMA is done - // Data already received previously --> trigger DMA to copy to SRAM - xact_out_dma(epnum); - } - else - { - xact_out_prepare(epnum); + dcd_event_xfer_complete(0, ep_addr, 0, XFER_RESULT_SUCCESS, is_in_isr()); + + // Status Phase also requires EasyDMA has to be available as well !!!! + edpt_dma_start(&NRF_USBD->TASKS_EP0STATUS); + } else if (dir == TUSB_DIR_OUT) { + xfer->started = true; + if (epnum == 0) { + // Accept next Control Out packet. TASKS_EP0RCVOUT also require EasyDMA + edpt_dma_start(&NRF_USBD->TASKS_EP0RCVOUT); + } else { + // started just set, it could start DMA transfer if interrupt was trigger after this line + // code only needs to start transfer (from Endpoint to RAM) when data_received was set + // before started was set. If started is NOT set but data_received is, it means that + // current transfer was already finished and next data is already present in endpoint and + // can be consumed by future transfer + __ISB(); + __DSB(); + if (xfer->data_received && xfer->started) { + // Data is already received previously + // start DMA to copy to SRAM + xfer->data_received = false; + xact_out_dma(epnum); + } else { + // nRF auto accept next Bulk/Interrupt OUT packet + // nothing to do + } } - } - else - { - xact_in_prepare(epnum); + } else { + // Start DMA to copy data from RAM -> Endpoint + xact_in_dma(epnum); } return true; } -void dcd_edpt_stall (uint8_t rhport, uint8_t ep_addr) -{ +void dcd_edpt_stall(uint8_t rhport, uint8_t ep_addr) { (void) rhport; - if ( tu_edpt_number(ep_addr) == 0 ) - { + uint8_t const epnum = tu_edpt_number(ep_addr); + uint8_t const dir = tu_edpt_dir(ep_addr); + + xfer_td_t* xfer = get_td(epnum, dir); + + if (epnum == 0) { NRF_USBD->TASKS_EP0STALL = 1; - }else - { + } else if (epnum != EP_ISO_NUM) { NRF_USBD->EPSTALL = (USBD_EPSTALL_STALL_Stall << USBD_EPSTALL_STALL_Pos) | ep_addr; + + // Note: nRF can auto ACK packet OUT before get stalled. + // There maybe data in endpoint fifo already, we need to pull it out + if ((dir == TUSB_DIR_OUT) && xfer->data_received) { + xfer->data_received = false; + xact_out_dma(epnum); + } } - __ISB(); __DSB(); + __ISB(); + __DSB(); } -void dcd_edpt_clear_stall (uint8_t rhport, uint8_t ep_addr) -{ +void dcd_edpt_clear_stall(uint8_t rhport, uint8_t ep_addr) { (void) rhport; + uint8_t const epnum = tu_edpt_number(ep_addr); + uint8_t const dir = tu_edpt_dir(ep_addr); + + if (epnum != 0 && epnum != EP_ISO_NUM) { + // reset data toggle to DATA0 + // First write this register with VALUE=Nop to select the endpoint, then either read it to get the status from + // VALUE, or write it again with VALUE=Data0 or Data1 + NRF_USBD->DTOGGLE = ep_addr; + NRF_USBD->DTOGGLE = (USBD_DTOGGLE_VALUE_Data0 << USBD_DTOGGLE_VALUE_Pos) | ep_addr; - if ( tu_edpt_number(ep_addr) ) - { // clear stall NRF_USBD->EPSTALL = (USBD_EPSTALL_STALL_UnStall << USBD_EPSTALL_STALL_Pos) | ep_addr; - // reset data toggle to DATA0 - NRF_USBD->DTOGGLE = (USBD_DTOGGLE_VALUE_Data0 << USBD_DTOGGLE_VALUE_Pos) | ep_addr; + // Write any value to SIZE register will allow nRF to ACK/accept data + if (dir == TUSB_DIR_OUT) NRF_USBD->SIZE.EPOUT[epnum] = 0; - __ISB(); __DSB(); + __ISB(); + __DSB(); } } /*------------------------------------------------------------------*/ /* Interrupt Handler *------------------------------------------------------------------*/ -void bus_reset(void) -{ - for(int i=0; i<8; i++) - { +static void bus_reset(void) { + // 6.35.6 USB controller automatically disabled all endpoints (except control) + NRF_USBD->EPOUTEN = 1UL; + NRF_USBD->EPINEN = 1UL; + + for (int i = 0; i < 8; i++) { NRF_USBD->TASKS_STARTEPIN[i] = 0; NRF_USBD->TASKS_STARTEPOUT[i] = 0; } - NRF_USBD->TASKS_STARTISOIN = 0; + NRF_USBD->TASKS_STARTISOIN = 0; NRF_USBD->TASKS_STARTISOOUT = 0; + // Clear USB Event Interrupt + NRF_USBD->EVENTS_USBEVENT = 0; + NRF_USBD->EVENTCAUSE |= NRF_USBD->EVENTCAUSE; + + // Reset interrupt + NRF_USBD->INTENCLR = NRF_USBD->INTEN; + NRF_USBD->INTENSET = USBD_INTEN_USBRESET_Msk | USBD_INTEN_USBEVENT_Msk | USBD_INTEN_EPDATA_Msk | + USBD_INTEN_EP0SETUP_Msk | USBD_INTEN_EP0DATADONE_Msk | USBD_INTEN_ENDEPIN0_Msk | + USBD_INTEN_ENDEPOUT0_Msk; + tu_varclr(&_dcd); _dcd.xfer[0][TUSB_DIR_IN].mps = MAX_PACKET_SIZE; _dcd.xfer[0][TUSB_DIR_OUT].mps = MAX_PACKET_SIZE; } -void dcd_int_handler(uint8_t rhport) -{ +void dcd_int_handler(uint8_t rhport) { (void) rhport; - uint32_t const inten = NRF_USBD->INTEN; + uint32_t const inten = NRF_USBD->INTEN; uint32_t int_status = 0; volatile uint32_t* regevt = &NRF_USBD->EVENTS_USBRESET; - for(uint8_t i=0; iactual_len = NRF_USBD->ISOIN.AMOUNT; + // Data transferred from RAM to endpoint output buffer. + // Next transfer can be scheduled after SOF. + xfer->iso_in_transfer_ready = true; } - if ( int_status & USBD_INTEN_USBEVENT_Msk ) - { - uint32_t const evt_cause = NRF_USBD->EVENTCAUSE & (USBD_EVENTCAUSE_SUSPEND_Msk | USBD_EVENTCAUSE_RESUME_Msk); - NRF_USBD->EVENTCAUSE = evt_cause; // clear interrupt + if (int_status & USBD_INTEN_SOF_Msk) { + bool iso_enabled = false; - if ( evt_cause & USBD_EVENTCAUSE_SUSPEND_Msk ) - { - dcd_event_bus_signal(0, DCD_EVENT_SUSPEND, true); + // ISOOUT: Transfer data gathered in previous frame from buffer to RAM + if (NRF_USBD->EPOUTEN & USBD_EPOUTEN_ISOOUT_Msk) { + iso_enabled = true; + // Transfer from endpoint to RAM only if data is not corrupted + if ((int_status & USBD_INTEN_USBEVENT_Msk) == 0 || + (NRF_USBD->EVENTCAUSE & USBD_EVENTCAUSE_ISOOUTCRC_Msk) == 0) { + xact_out_dma(EP_ISO_NUM); + } + } + + // ISOIN: Notify client that data was transferred + if (NRF_USBD->EPINEN & USBD_EPINEN_ISOIN_Msk) { + iso_enabled = true; + xfer_td_t* xfer = get_td(EP_ISO_NUM, TUSB_DIR_IN); + if (xfer->iso_in_transfer_ready) { + xfer->iso_in_transfer_ready = false; + dcd_event_xfer_complete(0, EP_ISO_NUM | TUSB_DIR_IN_MASK, xfer->actual_len, XFER_RESULT_SUCCESS, true); + } + } + + if (!iso_enabled && !_dcd.sof_enabled) { + // SOF interrupt not manually enabled and ISO endpoint is not used, + // SOF is only enabled one-time for remote wakeup so we disable it now + + NRF_USBD->INTENCLR = USBD_INTENCLR_SOF_Msk; + } + + const uint32_t frame = NRF_USBD->FRAMECNTR; + dcd_event_sof(0, frame, true); + //dcd_event_bus_signal(0, DCD_EVENT_SOF, true); + } + + if (int_status & USBD_INTEN_USBEVENT_Msk) { + TU_LOG(3, "EVENTCAUSE = 0x%04" PRIX32 "\r\n", NRF_USBD->EVENTCAUSE); + + enum { + EVT_CAUSE_MASK = USBD_EVENTCAUSE_SUSPEND_Msk | USBD_EVENTCAUSE_RESUME_Msk | USBD_EVENTCAUSE_USBWUALLOWED_Msk | + USBD_EVENTCAUSE_ISOOUTCRC_Msk + }; + uint32_t const evt_cause = NRF_USBD->EVENTCAUSE & EVT_CAUSE_MASK; + NRF_USBD->EVENTCAUSE = evt_cause; // clear interrupt + + if (evt_cause & USBD_EVENTCAUSE_SUSPEND_Msk) { // Put controller into low power mode + // Leave HFXO disable to application, since it may be used by other peripherals NRF_USBD->LOWPOWER = 1; - // Leave HFXO disable to application, since it may be used by other + dcd_event_bus_signal(0, DCD_EVENT_SUSPEND, true); } - if ( evt_cause & USBD_EVENTCAUSE_RESUME_Msk ) - { - dcd_event_bus_signal(0, DCD_EVENT_RESUME , true); + if (evt_cause & USBD_EVENTCAUSE_USBWUALLOWED_Msk) { + // USB is out of low power mode, and wakeup is allowed + // Initiate RESUME signal + NRF_USBD->DPDMVALUE = USBD_DPDMVALUE_STATE_Resume; + NRF_USBD->TASKS_DPDMDRIVE = 1; + + // There is no Resume interrupt for remote wakeup, enable SOF for to report bus ready state + // Clear SOF event in case interrupt was not enabled yet. + if ((NRF_USBD->INTEN & USBD_INTEN_SOF_Msk) == 0) NRF_USBD->EVENTS_SOF = 0; + NRF_USBD->INTENSET = USBD_INTENSET_SOF_Msk; } - } - if ( int_status & EDPT_END_ALL_MASK ) - { - // DMA complete move data from SRAM -> Endpoint - edpt_dma_end(); + if (evt_cause & USBD_EVENTCAUSE_RESUME_Msk) { + dcd_event_bus_signal(0, DCD_EVENT_RESUME, true); + } } // Setup tokens are specific to the Control endpoint. - if ( int_status & USBD_INTEN_EP0SETUP_Msk ) - { + if (int_status & USBD_INTEN_EP0SETUP_Msk) { uint8_t const setup[8] = { - NRF_USBD->BMREQUESTTYPE , NRF_USBD->BREQUEST, NRF_USBD->WVALUEL , NRF_USBD->WVALUEH, - NRF_USBD->WINDEXL , NRF_USBD->WINDEXH , NRF_USBD->WLENGTHL, NRF_USBD->WLENGTHH + NRF_USBD->BMREQUESTTYPE, NRF_USBD->BREQUEST, NRF_USBD->WVALUEL, NRF_USBD->WVALUEH, + NRF_USBD->WINDEXL, NRF_USBD->WINDEXH, NRF_USBD->WLENGTHL, NRF_USBD->WLENGTHH }; // nrf5x hw auto handle set address, there is no need to inform usb stack - tusb_control_request_t const * request = (tusb_control_request_t const *) setup; + tusb_control_request_t const* request = (tusb_control_request_t const*) setup; - if ( !(TUSB_REQ_RCPT_DEVICE == request->bmRequestType_bit.recipient && - TUSB_REQ_TYPE_STANDARD == request->bmRequestType_bit.type && - TUSB_REQ_SET_ADDRESS == request->bRequest) ) - { + if (!(TUSB_REQ_RCPT_DEVICE == request->bmRequestType_bit.recipient && + TUSB_REQ_TYPE_STANDARD == request->bmRequestType_bit.type && + TUSB_REQ_SET_ADDRESS == request->bRequest)) { dcd_event_setup_received(0, setup, true); } } + if (int_status & EDPT_END_ALL_MASK) { + // DMA complete move data from SRAM <-> Endpoint + // Must before endpoint transfer handling + edpt_dma_end(); + } + //--------------------------------------------------------------------+ /* Control/Bulk/Interrupt (CBI) Transfer * @@ -496,15 +696,15 @@ void dcd_int_handler(uint8_t rhport) * For CBI OUT: * - Host -> Endpoint * EPDATA (or EP0DATADONE) interrupted, check EPDATASTATUS.EPOUT[i] - * to start DMA. This step can occur automatically (without sw), - * which means data may or may not ready (data_received flag). + * to start DMA. For Bulk/Interrupt, this step can occur automatically (without sw), + * which means data may or may not be ready (out_received flag). * - Endpoint -> RAM * ENDEPOUT[i] interrupted, transaction complete, sw prepare next transaction * * For CBI IN: * - RAM -> Endpoint * ENDEPIN[i] interrupted indicate DMA is complete. HW will start - * to move daat to host + * to move data to host * - Endpoint -> Host * EPDATA (or EP0DATADONE) interrupted, check EPDATASTATUS.EPIN[i]. * Transaction is complete, sw prepare next transaction @@ -516,27 +716,33 @@ void dcd_int_handler(uint8_t rhport) /* CBI OUT: Endpoint -> SRAM (aka transaction complete) * Note: Since nRF controller auto ACK next packet without SW awareness - * We must handle this stage before Host -> Endpoint just in case - * 2 event happens at once + * We must handle this stage before Host -> Endpoint just in case 2 event happens at once + * + * ISO OUT: Transaction must fit in single packet, it can be shorter then total + * len if Host decides to sent fewer bytes, it this case transaction is also + * complete and next transfer is not initiated here like for CBI. */ - for(uint8_t epnum=0; epnum<8; epnum++) - { - if ( tu_bit_test(int_status, USBD_INTEN_ENDEPOUT0_Pos+epnum)) - { + for (uint8_t epnum = 0; epnum < EP_CBI_COUNT + 1; epnum++) { + if (tu_bit_test(int_status, USBD_INTEN_ENDEPOUT0_Pos + epnum)) { xfer_td_t* xfer = get_td(epnum, TUSB_DIR_OUT); - uint8_t const xact_len = NRF_USBD->EPOUT[epnum].AMOUNT; + uint16_t const xact_len = NRF_USBD->EPOUT[epnum].AMOUNT; - // Data in endpoint has been consumed - xfer->data_received = false; + xfer->buffer += xact_len; + xfer->actual_len += xact_len; // Transfer complete if transaction len < Max Packet Size or total len is transferred - if ( (xact_len == xfer->mps) && (xfer->actual_len < xfer->total_len) ) - { - // Prepare for next transaction - xact_out_prepare(epnum); - }else - { + if ((epnum != EP_ISO_NUM) && (xact_len == xfer->mps) && (xfer->actual_len < xfer->total_len)) { + if (epnum == 0) { + // Accept next Control Out packet. TASKS_EP0RCVOUT also require EasyDMA + edpt_dma_start(&NRF_USBD->TASKS_EP0RCVOUT); + } else { + // nRF auto accept next Bulk/Interrupt OUT packet + // nothing to do + } + } else { + TU_ASSERT(xfer->started,); xfer->total_len = xfer->actual_len; + xfer->started = false; // CBI OUT complete dcd_event_xfer_complete(0, epnum, xfer->actual_len, XFER_RESULT_SUCCESS, true); @@ -546,12 +752,12 @@ void dcd_int_handler(uint8_t rhport) // Ended event for CBI IN : nothing to do } - // Endpoint <-> Host - if ( int_status & (USBD_INTEN_EPDATA_Msk | USBD_INTEN_EP0DATADONE_Msk) ) - { + // Endpoint <-> Host ( In & OUT ) + if (int_status & (USBD_INTEN_EPDATA_Msk | USBD_INTEN_EP0DATADONE_Msk)) { uint32_t data_status = NRF_USBD->EPDATASTATUS; NRF_USBD->EPDATASTATUS = data_status; - __ISB(); __DSB(); + __ISB(); + __DSB(); // EP0DATADONE is set with either Control Out on IN Data // Since EPDATASTATUS cannot be used to determine whether it is control OUT or IN. @@ -560,20 +766,18 @@ void dcd_int_handler(uint8_t rhport) bool const is_control_out = (int_status & USBD_INTEN_EP0DATADONE_Msk) && !(NRF_USBD->BMREQUESTTYPE & TUSB_DIR_IN_MASK); // CBI In: Endpoint -> Host (transaction complete) - for(uint8_t epnum=0; epnum<8; epnum++) - { - if ( tu_bit_test(data_status, epnum ) || ( epnum == 0 && is_control_in) ) - { + for (uint8_t epnum = 0; epnum < EP_CBI_COUNT; epnum++) { + if (tu_bit_test(data_status, epnum) || (epnum == 0 && is_control_in)) { xfer_td_t* xfer = get_td(epnum, TUSB_DIR_IN); + uint8_t const xact_len = NRF_USBD->EPIN[epnum].AMOUNT; - xfer->actual_len += NRF_USBD->EPIN[epnum].MAXCNT; + xfer->buffer += xact_len; + xfer->actual_len += xact_len; - if ( xfer->actual_len < xfer->total_len ) - { - // prepare next transaction - xact_in_prepare(epnum); - } else - { + if (xfer->actual_len < xfer->total_len) { + // Start DMA to copy next data packet + xact_in_dma(epnum); + } else { // CBI IN complete dcd_event_xfer_complete(0, epnum | TUSB_DIR_IN_MASK, xfer->actual_len, XFER_RESULT_SUCCESS, true); } @@ -581,18 +785,14 @@ void dcd_int_handler(uint8_t rhport) } // CBI OUT: Host -> Endpoint - for(uint8_t epnum=0; epnum<8; epnum++) - { - if ( tu_bit_test(data_status, 16+epnum ) || ( epnum == 0 && is_control_out) ) - { + for (uint8_t epnum = 0; epnum < EP_CBI_COUNT; epnum++) { + if (tu_bit_test(data_status, 16 + epnum) || (epnum == 0 && is_control_out)) { xfer_td_t* xfer = get_td(epnum, TUSB_DIR_OUT); - if (xfer->actual_len < xfer->total_len) - { + if (xfer->started && xfer->actual_len < xfer->total_len) { xact_out_dma(epnum); - }else - { - // Data overflow !!! Nah, nrf52840 will auto ACK OUT packet after DMA is done + } else { + // Data overflow !!! Nah, nRF will auto accept next Bulk/Interrupt OUT packet // Mark this endpoint with data received xfer->data_received = true; } @@ -615,71 +815,79 @@ void dcd_int_handler(uint8_t rhport) #define SD_MAGIC_NUMBER 0x51B1E5DB #endif -static inline bool is_sd_existed(void) -{ +TU_ATTR_ALWAYS_INLINE static inline bool is_sd_existed(void) { return *((uint32_t*)(SOFTDEVICE_INFO_STRUCT_ADDRESS+4)) == SD_MAGIC_NUMBER; } // check if SD is existed and enabled -static inline bool is_sd_enabled(void) -{ +TU_ATTR_ALWAYS_INLINE static inline bool is_sd_enabled(void) { if ( !is_sd_existed() ) return false; - uint8_t sd_en = false; (void) sd_softdevice_is_enabled(&sd_en); return sd_en; } #endif -static bool hfclk_running(void) -{ +static bool hfclk_running(void) { #ifdef SOFTDEVICE_PRESENT - if ( is_sd_enabled() ) - { - uint32_t is_running; + if ( is_sd_enabled() ) { + uint32_t is_running = 0; (void) sd_clock_hfclk_is_running(&is_running); return (is_running ? true : false); } #endif +#if CFG_TUD_NRF_NRFX_VERSION == 1 + return nrf_clock_hf_is_running(NRF_CLOCK_HFCLK_HIGH_ACCURACY); +#else return nrf_clock_hf_is_running(NRF_CLOCK, NRF_CLOCK_HFCLK_HIGH_ACCURACY); +#endif } -static void hfclk_enable(void) -{ -#if 0 +static void hfclk_enable(void) { +#if CFG_TUSB_OS == OPT_OS_MYNEWT + usb_clock_request(); + return; +#else + // already running, nothing to do - if ( hfclk_running() ) return; + if (hfclk_running()) return; #ifdef SOFTDEVICE_PRESENT - if ( is_sd_enabled() ) - { + if ( is_sd_enabled() ) { (void)sd_clock_hfclk_request(); return; } #endif +#if CFG_TUD_NRF_NRFX_VERSION == 1 + nrf_clock_event_clear(NRF_CLOCK_EVENT_HFCLKSTARTED); + nrf_clock_task_trigger(NRF_CLOCK_TASK_HFCLKSTART); +#else nrf_clock_event_clear(NRF_CLOCK, NRF_CLOCK_EVENT_HFCLKSTARTED); nrf_clock_task_trigger(NRF_CLOCK, NRF_CLOCK_TASK_HFCLKSTART); -#else - hw_clock_hfxo_request(); +#endif #endif } -static void hfclk_disable(void) -{ -#if 0 +static void hfclk_disable(void) { +#if CFG_TUSB_OS == OPT_OS_MYNEWT + usb_clock_release(); + return; +#else + #ifdef SOFTDEVICE_PRESENT - if ( is_sd_enabled() ) - { + if ( is_sd_enabled() ) { (void)sd_clock_hfclk_release(); return; } #endif - nrf_clock_task_trigger(NRF_CLOCK, NRF_CLOCK_TASK_HFCLKSTOP); +#if CFG_TUD_NRF_NRFX_VERSION == 1 + nrf_clock_task_trigger(NRF_CLOCK_TASK_HFCLKSTOP); #else - hw_clock_hfxo_release(); + nrf_clock_task_trigger(NRF_CLOCK, NRF_CLOCK_TASK_HFCLKSTOP); +#endif #endif } @@ -693,8 +901,8 @@ static void hfclk_disable(void) // Therefore this function must be called to handle USB power event by // - nrfx_power_usbevt_init() : if Softdevice is not used or enabled // - SoftDevice SOC event : if SD is used and enabled -void tusb_hal_nrf_power_event (uint32_t event) -{ +void tusb_hal_nrf_power_event(uint32_t event); +void tusb_hal_nrf_power_event(uint32_t event) { // Value is chosen to be as same as NRFX_POWER_USB_EVT_* in nrfx_power.h enum { USB_EVT_DETECTED = 0, @@ -702,142 +910,137 @@ void tusb_hal_nrf_power_event (uint32_t event) USB_EVT_READY = 2 }; - switch ( event ) - { - case USB_EVT_DETECTED: - TU_LOG2("Power USB Detect\r\n"); +#if CFG_TUSB_DEBUG >= 3 + const char* const power_evt_str[] = {"Detected", "Removed", "Ready"}; + TU_LOG(3, "Power USB event: %s\r\n", power_evt_str[event]); +#endif - if ( !NRF_USBD->ENABLE ) - { - /* Prepare for READY event receiving */ + switch (event) { + case USB_EVT_DETECTED: + if (!NRF_USBD->ENABLE) { + // Prepare for receiving READY event: disable interrupt since we will blocking wait + NRF_USBD->INTENCLR = USBD_INTEN_USBEVENT_Msk; NRF_USBD->EVENTCAUSE = USBD_EVENTCAUSE_READY_Msk; - __ISB(); __DSB(); // for sync + __ISB(); + __DSB(); // for sync - /* Enable the peripheral */ +#ifdef NRF52_SERIES // NRF53 does not need this errata // ERRATA 171, 187, 166 - - if ( nrfx_usbd_errata_187() ) - { + if (nrf52_errata_187()) { // CRITICAL_REGION_ENTER(); - if ( *((volatile uint32_t *) (0x4006EC00)) == 0x00000000 ) - { - *((volatile uint32_t *) (0x4006EC00)) = 0x00009375; - *((volatile uint32_t *) (0x4006ED14)) = 0x00000003; - *((volatile uint32_t *) (0x4006EC00)) = 0x00009375; - } - else - { - *((volatile uint32_t *) (0x4006ED14)) = 0x00000003; + if (*((volatile uint32_t*) (0x4006EC00)) == 0x00000000) { + *((volatile uint32_t*) (0x4006EC00)) = 0x00009375; + *((volatile uint32_t*) (0x4006ED14)) = 0x00000003; + *((volatile uint32_t*) (0x4006EC00)) = 0x00009375; + } else { + *((volatile uint32_t*) (0x4006ED14)) = 0x00000003; } // CRITICAL_REGION_EXIT(); } - if ( nrfx_usbd_errata_171() ) - { + if (nrf52_errata_171()) { // CRITICAL_REGION_ENTER(); - if ( *((volatile uint32_t *) (0x4006EC00)) == 0x00000000 ) - { - *((volatile uint32_t *) (0x4006EC00)) = 0x00009375; - *((volatile uint32_t *) (0x4006EC14)) = 0x000000C0; - *((volatile uint32_t *) (0x4006EC00)) = 0x00009375; - } - else - { - *((volatile uint32_t *) (0x4006EC14)) = 0x000000C0; + if (*((volatile uint32_t*) (0x4006EC00)) == 0x00000000) { + *((volatile uint32_t*) (0x4006EC00)) = 0x00009375; + *((volatile uint32_t*) (0x4006EC14)) = 0x000000C0; + *((volatile uint32_t*) (0x4006EC00)) = 0x00009375; + } else { + *((volatile uint32_t*) (0x4006EC14)) = 0x000000C0; } // CRITICAL_REGION_EXIT(); } +#endif + // Enable the peripheral (will cause Ready event) NRF_USBD->ENABLE = 1; - __ISB(); __DSB(); // for sync + __ISB(); + __DSB(); // for sync // Enable HFCLK hfclk_enable(); } - break; + break; case USB_EVT_READY: - TU_LOG2("Power USB Ready\r\n"); - // Skip if pull-up is enabled and HCLK is already running. // Application probably call this more than necessary. - if ( NRF_USBD->USBPULLUP && hfclk_running() ) break; + if (NRF_USBD->USBPULLUP && hfclk_running()) break; - /* Waiting for USBD peripheral enabled */ - while ( !(USBD_EVENTCAUSE_READY_Msk & NRF_USBD->EVENTCAUSE) ) { } + // Waiting for USBD peripheral enabled + while (!(USBD_EVENTCAUSE_READY_Msk & NRF_USBD->EVENTCAUSE)) {} NRF_USBD->EVENTCAUSE = USBD_EVENTCAUSE_READY_Msk; - __ISB(); __DSB(); // for sync + __ISB(); + __DSB(); // for sync - if ( nrfx_usbd_errata_171() ) - { +#ifdef NRF52_SERIES + if (nrf52_errata_171()) { // CRITICAL_REGION_ENTER(); - if ( *((volatile uint32_t *) (0x4006EC00)) == 0x00000000 ) - { - *((volatile uint32_t *) (0x4006EC00)) = 0x00009375; - *((volatile uint32_t *) (0x4006EC14)) = 0x00000000; - *((volatile uint32_t *) (0x4006EC00)) = 0x00009375; - } - else - { - *((volatile uint32_t *) (0x4006EC14)) = 0x00000000; + if (*((volatile uint32_t*) (0x4006EC00)) == 0x00000000) { + *((volatile uint32_t*) (0x4006EC00)) = 0x00009375; + *((volatile uint32_t*) (0x4006EC14)) = 0x00000000; + *((volatile uint32_t*) (0x4006EC00)) = 0x00009375; + } else { + *((volatile uint32_t*) (0x4006EC14)) = 0x00000000; } // CRITICAL_REGION_EXIT(); } - if ( nrfx_usbd_errata_187() ) - { + if (nrf52_errata_187()) { // CRITICAL_REGION_ENTER(); - if ( *((volatile uint32_t *) (0x4006EC00)) == 0x00000000 ) - { - *((volatile uint32_t *) (0x4006EC00)) = 0x00009375; - *((volatile uint32_t *) (0x4006ED14)) = 0x00000000; - *((volatile uint32_t *) (0x4006EC00)) = 0x00009375; - } - else - { - *((volatile uint32_t *) (0x4006ED14)) = 0x00000000; + if (*((volatile uint32_t*) (0x4006EC00)) == 0x00000000) { + *((volatile uint32_t*) (0x4006EC00)) = 0x00009375; + *((volatile uint32_t*) (0x4006ED14)) = 0x00000000; + *((volatile uint32_t*) (0x4006EC00)) = 0x00009375; + } else { + *((volatile uint32_t*) (0x4006ED14)) = 0x00000000; } // CRITICAL_REGION_EXIT(); } - if ( nrfx_usbd_errata_166() ) - { - *((volatile uint32_t *) (NRF_USBD_BASE + 0x800)) = 0x7E3; - *((volatile uint32_t *) (NRF_USBD_BASE + 0x804)) = 0x40; + if (nrf52_errata_166()) { + *((volatile uint32_t*) (NRF_USBD_BASE + 0x800)) = 0x7E3; + *((volatile uint32_t*) (NRF_USBD_BASE + 0x804)) = 0x40; - __ISB(); __DSB(); + __ISB(); + __DSB(); } +#endif // ISO buffer Lower half for IN, upper half for OUT NRF_USBD->ISOSPLIT = USBD_ISOSPLIT_SPLIT_HalfIN; - // Enable interrupt - NRF_USBD->INTENSET = USBD_INTEN_USBRESET_Msk | USBD_INTEN_EPDATA_Msk | - USBD_INTEN_EP0SETUP_Msk | USBD_INTEN_EP0DATADONE_Msk | USBD_INTEN_ENDEPIN0_Msk | USBD_INTEN_ENDEPOUT0_Msk; + // Enable bus-reset interrupt + NRF_USBD->INTENSET = USBD_INTEN_USBRESET_Msk; // Enable interrupt, priorities should be set by application NVIC_ClearPendingIRQ(USBD_IRQn); - NVIC_EnableIRQ(USBD_IRQn); + + // Don't enable USBD interrupt yet, if dcd_init() did not finish yet + // Interrupt will be enabled by tud_init(), when USB stack is ready + // to handle interrupts. + if (tud_inited()) { + NVIC_EnableIRQ(USBD_IRQn); + } // Wait for HFCLK - while ( !hfclk_running() ) { } + while (!hfclk_running()) {} // Enable pull up NRF_USBD->USBPULLUP = 1; - __ISB(); __DSB(); // for sync - break; + __ISB(); + __DSB(); // for sync + break; case USB_EVT_REMOVED: - TU_LOG2("Power USB Removed\r\n"); - if ( NRF_USBD->ENABLE ) - { + if (NRF_USBD->ENABLE) { // Abort all transfers // Disable pull up NRF_USBD->USBPULLUP = 0; - __ISB(); __DSB(); // for sync + __ISB(); + __DSB(); // for sync // Disable Interrupt NVIC_DisableIRQ(USBD_IRQn); @@ -846,15 +1049,17 @@ void tusb_hal_nrf_power_event (uint32_t event) NRF_USBD->INTENCLR = NRF_USBD->INTEN; NRF_USBD->ENABLE = 0; - __ISB(); __DSB(); // for sync + __ISB(); + __DSB(); // for sync hfclk_disable(); - dcd_event_bus_signal(0, DCD_EVENT_UNPLUGGED, (SCB->ICSR & SCB_ICSR_VECTACTIVE_Msk) ? true : false); + dcd_event_bus_signal(0, DCD_EVENT_UNPLUGGED, is_in_isr()); } - break; + break; - default: break; + default: + break; } } diff --git a/cores/nRF5/TinyUSB/Adafruit_TinyUSB_ArduinoCore/tinyusb/src/portable/nuvoton/nuc120/dcd_nuc120.c b/cores/nRF5/TinyUSB/Adafruit_TinyUSB_ArduinoCore/tinyusb/src/portable/nuvoton/nuc120/dcd_nuc120.c deleted file mode 100644 index 1a0007d..0000000 --- a/cores/nRF5/TinyUSB/Adafruit_TinyUSB_ArduinoCore/tinyusb/src/portable/nuvoton/nuc120/dcd_nuc120.c +++ /dev/null @@ -1,435 +0,0 @@ -/* - * The MIT License (MIT) - * - * Copyright (c) 2019-2020 Peter Lawrence - * - * 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. - */ - -/* - Theory of operation: - - The NUC100/NUC120 USBD peripheral has six "EP"s, but each is simplex, - so two collectively (peripheral nomenclature of "EP0" and "EP1") are needed to - implement USB EP0. PERIPH_EP0 and PERIPH_EP1 are used by this driver for - EP0_IN and EP0_OUT respectively. This leaves up to four for user usage. -*/ - -#include "tusb_option.h" - -#if TUSB_OPT_DEVICE_ENABLED && (CFG_TUSB_MCU == OPT_MCU_NUC120) - -#include "device/dcd.h" -#include "NUC100Series.h" - -/* allocation of USBD RAM for Setup, EP0_IN, and and EP_OUT */ -#define PERIPH_SETUP_BUF_BASE 0 -#define PERIPH_SETUP_BUF_LEN 8 -#define PERIPH_EP0_BUF_BASE (PERIPH_SETUP_BUF_BASE + PERIPH_SETUP_BUF_LEN) -#define PERIPH_EP0_BUF_LEN CFG_TUD_ENDPOINT0_SIZE -#define PERIPH_EP1_BUF_BASE (PERIPH_EP0_BUF_BASE + PERIPH_EP0_BUF_LEN) -#define PERIPH_EP1_BUF_LEN CFG_TUD_ENDPOINT0_SIZE -#define PERIPH_EP2_BUF_BASE (PERIPH_EP1_BUF_BASE + PERIPH_EP1_BUF_LEN) - -/* rather important info unfortunately not provided by device include files: how much there is */ -#define USBD_BUF_SIZE 512 - -enum ep_enum -{ - PERIPH_EP0 = 0, - PERIPH_EP1 = 1, - PERIPH_EP2 = 2, - PERIPH_EP3 = 3, - PERIPH_EP4 = 4, - PERIPH_EP5 = 5, - PERIPH_MAX_EP, -}; - -/* set by dcd_set_address() */ -static volatile uint8_t assigned_address; - -/* reset by dcd_init(), this is used by dcd_edpt_open() to assign USBD peripheral buffer addresses */ -static uint32_t bufseg_addr; - -/* used by dcd_edpt_xfer() and the ISR to reset the data sync (DATA0/DATA1) in an EP0_IN transfer */ -static bool active_ep0_xfer; - -/* RAM table needed to track ongoing transfers performed by dcd_edpt_xfer(), dcd_in_xfer(), and the ISR */ -static struct xfer_ctl_t -{ - uint8_t *data_ptr; /* data_ptr tracks where to next copy data to (for OUT) or from (for IN) */ - union { - uint16_t in_remaining_bytes; /* for IN endpoints, we track how many bytes are left to transfer */ - uint16_t out_bytes_so_far; /* but for OUT endpoints, we track how many bytes we've transferred so far */ - }; - uint16_t max_packet_size; /* needed since device driver only finds out this at runtime */ - uint16_t total_bytes; /* quantity needed to pass as argument to dcd_event_xfer_complete() (for IN endpoints) */ -} xfer_table[PERIPH_MAX_EP]; - -/* - local helper functions -*/ - -static void usb_attach(void) -{ - USBD->DRVSE0 &= ~USBD_DRVSE0_DRVSE0_Msk; -} - -static void usb_detach(void) -{ - USBD->DRVSE0 |= USBD_DRVSE0_DRVSE0_Msk; -} - -static void usb_control_send_zlp(void) -{ - USBD->EP[PERIPH_EP0].CFG |= USBD_CFG_DSQ_SYNC_Msk; - USBD->EP[PERIPH_EP0].MXPLD = 0; -} - -/* reconstruct ep_addr from particular USB Configuration Register */ -static uint8_t decode_ep_addr(USBD_EP_T *ep) -{ - uint8_t ep_addr = ep->CFG & USBD_CFG_EP_NUM_Msk; - if ( USBD_CFG_EPMODE_IN == (ep->CFG & USBD_CFG_STATE_Msk) ) - ep_addr |= TUSB_DIR_IN_MASK; - return ep_addr; -} - -/* map 8-bit ep_addr into peripheral endpoint index (PERIPH_EP0...) */ -static USBD_EP_T *ep_entry(uint8_t ep_addr, bool add) -{ - USBD_EP_T *ep; - enum ep_enum ep_index; - - for (ep_index = PERIPH_EP0, ep = USBD->EP; ep_index < PERIPH_MAX_EP; ep_index++, ep++) - { - if (add) - { - /* take first peripheral endpoint that is unused */ - if (0 == (ep->CFG & USBD_CFG_STATE_Msk)) return ep; - } - else - { - /* find a peripheral endpoint that matches ep_addr */ - uint8_t candidate_ep_addr = decode_ep_addr(ep); - if (candidate_ep_addr == ep_addr) return ep; - } - } - - return NULL; -} - -/* perform an IN endpoint transfer; this is called by dcd_edpt_xfer() and the ISR */ -static void dcd_in_xfer(struct xfer_ctl_t *xfer, USBD_EP_T *ep) -{ - uint16_t bytes_now = tu_min16(xfer->in_remaining_bytes, xfer->max_packet_size); - - memcpy((uint8_t *)(USBD_BUF_BASE + ep->BUFSEG), xfer->data_ptr, bytes_now); - ep->MXPLD = bytes_now; -} - -/* called by dcd_init() as well as by the ISR during a USB bus reset */ -static void bus_reset(void) -{ - USBD->STBUFSEG = PERIPH_SETUP_BUF_BASE; - - for (enum ep_enum ep_index = PERIPH_EP0; ep_index < PERIPH_MAX_EP; ep_index++) - { - USBD->EP[ep_index].CFG = 0; - USBD->EP[ep_index].CFGP = 0; - } - - /* allocate the default EP0 endpoints */ - - USBD->EP[PERIPH_EP0].CFG = USBD_CFG_CSTALL_Msk | USBD_CFG_EPMODE_IN; - USBD->EP[PERIPH_EP0].BUFSEG = PERIPH_EP0_BUF_BASE; - xfer_table[PERIPH_EP0].max_packet_size = PERIPH_EP0_BUF_LEN; - - USBD->EP[PERIPH_EP1].CFG = USBD_CFG_CSTALL_Msk | USBD_CFG_EPMODE_OUT; - USBD->EP[PERIPH_EP1].BUFSEG = PERIPH_EP1_BUF_BASE; - xfer_table[PERIPH_EP1].max_packet_size = PERIPH_EP1_BUF_LEN; - - /* USB RAM beyond what we've allocated above is available to the user */ - bufseg_addr = PERIPH_EP2_BUF_BASE; - - /* Reset USB device address */ - USBD->FADDR = 0; - - /* reset EP0_IN flag */ - active_ep0_xfer = false; -} - -/* centralized location for USBD interrupt enable bit mask */ -static const uint32_t enabled_irqs = USBD_INTSTS_FLDET_STS_Msk | USBD_INTSTS_BUS_STS_Msk | USBD_INTSTS_SETUP_Msk | USBD_INTSTS_USB_STS_Msk; - -/* - NUC100/NUC120 TinyUSB API driver implementation -*/ - -void dcd_init(uint8_t rhport) -{ - (void) rhport; - - USBD->ATTR = 0x7D0; - - usb_detach(); - - bus_reset(); - - usb_attach(); - - USBD->INTSTS = enabled_irqs; - USBD->INTEN = enabled_irqs; -} - -void dcd_int_enable(uint8_t rhport) -{ - (void) rhport; - NVIC_EnableIRQ(USBD_IRQn); -} - -void dcd_int_disable(uint8_t rhport) -{ - (void) rhport; - NVIC_DisableIRQ(USBD_IRQn); -} - -void dcd_set_address(uint8_t rhport, uint8_t dev_addr) -{ - (void) rhport; - usb_control_send_zlp(); /* SET_ADDRESS is the one exception where TinyUSB doesn't use dcd_edpt_xfer() to generate a ZLP */ - assigned_address = dev_addr; -} - -void dcd_remote_wakeup(uint8_t rhport) -{ - (void) rhport; - USBD->ATTR = USBD_ATTR_RWAKEUP_Msk; -} - -bool dcd_edpt_open(uint8_t rhport, tusb_desc_endpoint_t const * p_endpoint_desc) -{ - (void) rhport; - - USBD_EP_T *ep = ep_entry(p_endpoint_desc->bEndpointAddress, true); - TU_ASSERT(ep); - - /* mine the data for the information we need */ - int const dir = tu_edpt_dir(p_endpoint_desc->bEndpointAddress); - int const size = p_endpoint_desc->wMaxPacketSize.size; - tusb_xfer_type_t const type = p_endpoint_desc->bmAttributes.xfer; - struct xfer_ctl_t *xfer = &xfer_table[ep - USBD->EP]; - - /* allocate buffer from USB RAM */ - ep->BUFSEG = bufseg_addr; - bufseg_addr += size; - TU_ASSERT(bufseg_addr <= USBD_BUF_SIZE); - - /* construct USB Configuration Register value and then write it */ - uint32_t cfg = tu_edpt_number(p_endpoint_desc->bEndpointAddress); - cfg |= (TUSB_DIR_IN == dir) ? USBD_CFG_EPMODE_IN : USBD_CFG_EPMODE_OUT; - if (TUSB_XFER_ISOCHRONOUS == type) - cfg |= USBD_CFG_TYPE_ISO; - ep->CFG = cfg; - - /* make a note of the endpoint size */ - xfer->max_packet_size = size; - - return true; -} - -bool dcd_edpt_xfer(uint8_t rhport, uint8_t ep_addr, uint8_t *buffer, uint16_t total_bytes) -{ - (void) rhport; - - /* mine the data for the information we need */ - tusb_dir_t dir = tu_edpt_dir(ep_addr); - USBD_EP_T *ep = ep_entry(ep_addr, false); - struct xfer_ctl_t *xfer = &xfer_table[ep - USBD->EP]; - - /* store away the information we'll needing now and later */ - xfer->data_ptr = buffer; - xfer->in_remaining_bytes = total_bytes; - xfer->total_bytes = total_bytes; - - /* for the first of one or more EP0_IN packets in a message, the first must be DATA1 */ - if ( (0x80 == ep_addr) && !active_ep0_xfer ) ep->CFG |= USBD_CFG_DSQ_SYNC_Msk; - - if (TUSB_DIR_IN == dir) - { - dcd_in_xfer(xfer, ep); - } - else - { - xfer->out_bytes_so_far = 0; - ep->MXPLD = xfer->max_packet_size; - } - - return true; -} - -void dcd_edpt_stall(uint8_t rhport, uint8_t ep_addr) -{ - (void) rhport; - USBD_EP_T *ep = ep_entry(ep_addr, false); - ep->CFGP |= USBD_CFGP_SSTALL_Msk; -} - -void dcd_edpt_clear_stall(uint8_t rhport, uint8_t ep_addr) -{ - (void) rhport; - USBD_EP_T *ep = ep_entry(ep_addr, false); - ep->CFG |= USBD_CFG_CSTALL_Msk; -} - -void dcd_int_handler(uint8_t rhport) -{ - (void) rhport; - - uint32_t status = USBD->INTSTS; - uint32_t state = USBD->ATTR & 0xf; - - if(status & USBD_INTSTS_FLDET_STS_Msk) - { - if(USBD->FLDET & USBD_FLDET_FLDET_Msk) - { - /* USB connect */ - USBD->ATTR |= USBD_ATTR_USB_EN_Msk | USBD_ATTR_PHY_EN_Msk; - } - else - { - /* USB disconnect */ - USBD->ATTR &= ~USBD_ATTR_USB_EN_Msk; - } - } - - if(status & USBD_INTSTS_BUS_STS_Msk) - { - if(state & USBD_STATE_USBRST) - { - /* USB bus reset */ - USBD->ATTR |= USBD_ATTR_USB_EN_Msk | USBD_ATTR_PHY_EN_Msk; - - bus_reset(); - - dcd_event_bus_signal(0, DCD_EVENT_BUS_RESET, true); - } - - if(state & USBD_STATE_SUSPEND) - { - /* Enable USB but disable PHY */ - USBD->ATTR &= ~USBD_ATTR_PHY_EN_Msk; - dcd_event_bus_signal(0, DCD_EVENT_SUSPEND, true); - } - - if(state & USBD_STATE_RESUME) - { - /* Enable USB and enable PHY */ - USBD->ATTR |= USBD_ATTR_USB_EN_Msk | USBD_ATTR_PHY_EN_Msk; - dcd_event_bus_signal(0, DCD_EVENT_RESUME, true); - } - } - - if(status & USBD_INTSTS_SETUP_Msk) - { - /* clear the data ready flag of control endpoints */ - USBD->EP[PERIPH_EP0].CFGP |= USBD_CFGP_CLRRDY_Msk; - USBD->EP[PERIPH_EP1].CFGP |= USBD_CFGP_CLRRDY_Msk; - - /* get SETUP packet from USB buffer */ - dcd_event_setup_received(0, (uint8_t *)USBD_BUF_BASE, true); - } - - if(status & USBD_INTSTS_USB_STS_Msk) - { - if (status & (1UL << USBD_INTSTS_EPEVT_Pos)) /* PERIPH_EP0 (EP0_IN) event: this is treated separately from the rest */ - { - /* given ACK from host has happened, we can now set the address (if not already done) */ - if((USBD->FADDR != assigned_address) && (USBD->FADDR == 0)) USBD->FADDR = assigned_address; - - uint16_t const available_bytes = USBD->EP[PERIPH_EP0].MXPLD; - - active_ep0_xfer = (available_bytes == xfer_table[PERIPH_EP0].max_packet_size); - - dcd_event_xfer_complete(0, 0x80, available_bytes, XFER_RESULT_SUCCESS, true); - } - - /* service PERIPH_EP1 through PERIPH_EP7 */ - enum ep_enum ep_index; - uint32_t mask; - struct xfer_ctl_t *xfer; - USBD_EP_T *ep; - for (ep_index = PERIPH_EP1, mask = (2UL << USBD_INTSTS_EPEVT_Pos), xfer = &xfer_table[PERIPH_EP1], ep = &USBD->EP[PERIPH_EP1]; ep_index < PERIPH_MAX_EP; ep_index++, mask <<= 1, xfer++, ep++) - { - if(status & mask) - { - USBD->INTSTS = mask; - - uint16_t const available_bytes = ep->MXPLD; - uint8_t const ep_addr = decode_ep_addr(ep); - bool const out_ep = !(ep_addr & TUSB_DIR_IN_MASK); - - if (out_ep) - { - /* copy the data from the PC to the previously provided buffer */ - memcpy(xfer->data_ptr, (uint8_t *)(USBD_BUF_BASE + ep->BUFSEG), available_bytes); - xfer->out_bytes_so_far += available_bytes; - xfer->data_ptr += available_bytes; - - /* when the transfer is finished, alert TinyUSB; otherwise, accept more data */ - if ( (xfer->total_bytes == xfer->out_bytes_so_far) || (available_bytes < xfer->max_packet_size) ) - dcd_event_xfer_complete(0, ep_addr, xfer->out_bytes_so_far, XFER_RESULT_SUCCESS, true); - else - ep->MXPLD = xfer->max_packet_size; - } - else - { - /* update the bookkeeping to reflect the data that has now been sent to the PC */ - xfer->in_remaining_bytes -= available_bytes; - xfer->data_ptr += available_bytes; - - /* if more data to send, send it; otherwise, alert TinyUSB that we've finished */ - if (xfer->in_remaining_bytes) - dcd_in_xfer(xfer, ep); - else - dcd_event_xfer_complete(0, ep_addr, xfer->total_bytes, XFER_RESULT_SUCCESS, true); - } - } - } - } - - /* acknowledge all interrupts */ - USBD->INTSTS = status & enabled_irqs; -} - -void dcd_disconnect(uint8_t rhport) -{ - (void) rhport; - usb_detach(); -} - -void dcd_connect(uint8_t rhport) -{ - (void) rhport; - usb_attach(); -} - -#endif diff --git a/cores/nRF5/TinyUSB/Adafruit_TinyUSB_ArduinoCore/tinyusb/src/portable/nuvoton/nuc121/dcd_nuc121.c b/cores/nRF5/TinyUSB/Adafruit_TinyUSB_ArduinoCore/tinyusb/src/portable/nuvoton/nuc121/dcd_nuc121.c deleted file mode 100644 index af7fee8..0000000 --- a/cores/nRF5/TinyUSB/Adafruit_TinyUSB_ArduinoCore/tinyusb/src/portable/nuvoton/nuc121/dcd_nuc121.c +++ /dev/null @@ -1,451 +0,0 @@ -/* - * The MIT License (MIT) - * - * Copyright (c) 2019 Peter Lawrence - * - * 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. - */ - -/* - Theory of operation: - - The NUC121/NUC125/NUC126 USBD peripheral has eight "EP"s, but each is simplex, - so two collectively (peripheral nomenclature of "EP0" and "EP1") are needed to - implement USB EP0. PERIPH_EP0 and PERIPH_EP1 are used by this driver for - EP0_IN and EP0_OUT respectively. This leaves up to six for user usage. -*/ - -#include "tusb_option.h" - -#if TUSB_OPT_DEVICE_ENABLED && ( (CFG_TUSB_MCU == OPT_MCU_NUC121) || (CFG_TUSB_MCU == OPT_MCU_NUC126) ) - -#include "device/dcd.h" -#include "NuMicro.h" - -/* allocation of USBD RAM for Setup, EP0_IN, and and EP_OUT */ -#define PERIPH_SETUP_BUF_BASE 0 -#define PERIPH_SETUP_BUF_LEN 8 -#define PERIPH_EP0_BUF_BASE (PERIPH_SETUP_BUF_BASE + PERIPH_SETUP_BUF_LEN) -#define PERIPH_EP0_BUF_LEN CFG_TUD_ENDPOINT0_SIZE -#define PERIPH_EP1_BUF_BASE (PERIPH_EP0_BUF_BASE + PERIPH_EP0_BUF_LEN) -#define PERIPH_EP1_BUF_LEN CFG_TUD_ENDPOINT0_SIZE -#define PERIPH_EP2_BUF_BASE (PERIPH_EP1_BUF_BASE + PERIPH_EP1_BUF_LEN) - -/* rather important info unfortunately not provided by device include files: how much there is */ -#define USBD_BUF_SIZE ((CFG_TUSB_MCU == OPT_MCU_NUC121) ? 768 : 512) - -enum ep_enum -{ - PERIPH_EP0 = 0, - PERIPH_EP1 = 1, - PERIPH_EP2 = 2, - PERIPH_EP3 = 3, - PERIPH_EP4 = 4, - PERIPH_EP5 = 5, - PERIPH_EP6 = 6, - PERIPH_EP7 = 7, - PERIPH_MAX_EP, -}; - -/* set by dcd_set_address() */ -static volatile uint8_t assigned_address; - -/* reset by dcd_init(), this is used by dcd_edpt_open() to assign USBD peripheral buffer addresses */ -static uint32_t bufseg_addr; - -/* used by dcd_edpt_xfer() and the ISR to reset the data sync (DATA0/DATA1) in an EP0_IN transfer */ -static bool active_ep0_xfer; - -/* RAM table needed to track ongoing transfers performed by dcd_edpt_xfer(), dcd_in_xfer(), and the ISR */ -static struct xfer_ctl_t -{ - uint8_t *data_ptr; /* data_ptr tracks where to next copy data to (for OUT) or from (for IN) */ - union { - uint16_t in_remaining_bytes; /* for IN endpoints, we track how many bytes are left to transfer */ - uint16_t out_bytes_so_far; /* but for OUT endpoints, we track how many bytes we've transferred so far */ - }; - uint16_t max_packet_size; /* needed since device driver only finds out this at runtime */ - uint16_t total_bytes; /* quantity needed to pass as argument to dcd_event_xfer_complete() (for IN endpoints) */ -} xfer_table[PERIPH_MAX_EP]; - -/* - local helper functions -*/ - -static void usb_attach(void) -{ - USBD->SE0 &= ~USBD_SE0_SE0_Msk; -} - -static void usb_detach(void) -{ - USBD->SE0 |= USBD_SE0_SE0_Msk; -} - -static void usb_control_send_zlp(void) -{ - USBD->EP[PERIPH_EP0].CFG |= USBD_CFG_DSQSYNC_Msk; - USBD->EP[PERIPH_EP0].MXPLD = 0; -} - -/* reconstruct ep_addr from particular USB Configuration Register */ -static uint8_t decode_ep_addr(USBD_EP_T *ep) -{ - uint8_t ep_addr = ep->CFG & USBD_CFG_EPNUM_Msk; - if ( USBD_CFG_EPMODE_IN == (ep->CFG & USBD_CFG_STATE_Msk) ) - ep_addr |= TUSB_DIR_IN_MASK; - return ep_addr; -} - -/* map 8-bit ep_addr into peripheral endpoint index (PERIPH_EP0...) */ -static USBD_EP_T *ep_entry(uint8_t ep_addr, bool add) -{ - USBD_EP_T *ep; - enum ep_enum ep_index; - - for (ep_index = PERIPH_EP0, ep = USBD->EP; ep_index < PERIPH_MAX_EP; ep_index++, ep++) - { - if (add) - { - /* take first peripheral endpoint that is unused */ - if (0 == (ep->CFG & USBD_CFG_STATE_Msk)) return ep; - } - else - { - /* find a peripheral endpoint that matches ep_addr */ - uint8_t candidate_ep_addr = decode_ep_addr(ep); - if (candidate_ep_addr == ep_addr) return ep; - } - } - - return NULL; -} - -/* perform an IN endpoint transfer; this is called by dcd_edpt_xfer() and the ISR */ -static void dcd_in_xfer(struct xfer_ctl_t *xfer, USBD_EP_T *ep) -{ - uint16_t bytes_now = tu_min16(xfer->in_remaining_bytes, xfer->max_packet_size); - - memcpy((uint8_t *)(USBD_BUF_BASE + ep->BUFSEG), xfer->data_ptr, bytes_now); - ep->MXPLD = bytes_now; -} - -/* called by dcd_init() as well as by the ISR during a USB bus reset */ -static void bus_reset(void) -{ - USBD->STBUFSEG = PERIPH_SETUP_BUF_BASE; - - for (enum ep_enum ep_index = PERIPH_EP0; ep_index < PERIPH_MAX_EP; ep_index++) - { - USBD->EP[ep_index].CFG = 0; - USBD->EP[ep_index].CFGP = 0; - } - - /* allocate the default EP0 endpoints */ - - USBD->EP[PERIPH_EP0].CFG = USBD_CFG_CSTALL_Msk | USBD_CFG_EPMODE_IN; - USBD->EP[PERIPH_EP0].BUFSEG = PERIPH_EP0_BUF_BASE; - xfer_table[PERIPH_EP0].max_packet_size = PERIPH_EP0_BUF_LEN; - - USBD->EP[PERIPH_EP1].CFG = USBD_CFG_CSTALL_Msk | USBD_CFG_EPMODE_OUT; - USBD->EP[PERIPH_EP1].BUFSEG = PERIPH_EP1_BUF_BASE; - xfer_table[PERIPH_EP1].max_packet_size = PERIPH_EP1_BUF_LEN; - - /* USB RAM beyond what we've allocated above is available to the user */ - bufseg_addr = PERIPH_EP2_BUF_BASE; - - /* Reset USB device address */ - USBD->FADDR = 0; - - /* reset EP0_IN flag */ - active_ep0_xfer = false; -} - -/* centralized location for USBD interrupt enable bit mask */ -static const uint32_t enabled_irqs = USBD_INTSTS_VBDETIF_Msk | USBD_INTSTS_BUSIF_Msk | USBD_INTSTS_SETUP_Msk | USBD_INTSTS_USBIF_Msk | USBD_INTSTS_SOFIF_Msk; - -/* - NUC121/NUC125/NUC126 TinyUSB API driver implementation -*/ - -void dcd_init(uint8_t rhport) -{ - (void) rhport; - -#ifdef SUPPORT_LPM - USBD->ATTR = 0x7D0 | USBD_LPMACK; -#else - USBD->ATTR = 0x7D0; -#endif - - usb_detach(); - - bus_reset(); - - usb_attach(); - - USBD->INTSTS = enabled_irqs; - USBD->INTEN = enabled_irqs; -} - -void dcd_int_enable(uint8_t rhport) -{ - (void) rhport; - NVIC_EnableIRQ(USBD_IRQn); -} - -void dcd_int_disable(uint8_t rhport) -{ - (void) rhport; - NVIC_DisableIRQ(USBD_IRQn); -} - -void dcd_set_address(uint8_t rhport, uint8_t dev_addr) -{ - (void) rhport; - usb_control_send_zlp(); /* SET_ADDRESS is the one exception where TinyUSB doesn't use dcd_edpt_xfer() to generate a ZLP */ - assigned_address = dev_addr; -} - -void dcd_remote_wakeup(uint8_t rhport) -{ - (void) rhport; - USBD->ATTR = USBD_ATTR_RWAKEUP_Msk; -} - -bool dcd_edpt_open(uint8_t rhport, tusb_desc_endpoint_t const * p_endpoint_desc) -{ - (void) rhport; - - USBD_EP_T *ep = ep_entry(p_endpoint_desc->bEndpointAddress, true); - TU_ASSERT(ep); - - /* mine the data for the information we need */ - int const dir = tu_edpt_dir(p_endpoint_desc->bEndpointAddress); - int const size = p_endpoint_desc->wMaxPacketSize.size; - tusb_xfer_type_t const type = p_endpoint_desc->bmAttributes.xfer; - struct xfer_ctl_t *xfer = &xfer_table[ep - USBD->EP]; - - /* allocate buffer from USB RAM */ - ep->BUFSEG = bufseg_addr; - bufseg_addr += size; - TU_ASSERT(bufseg_addr <= USBD_BUF_SIZE); - - /* construct USB Configuration Register value and then write it */ - uint32_t cfg = tu_edpt_number(p_endpoint_desc->bEndpointAddress); - cfg |= (TUSB_DIR_IN == dir) ? USBD_CFG_EPMODE_IN : USBD_CFG_EPMODE_OUT; - if (TUSB_XFER_ISOCHRONOUS == type) - cfg |= USBD_CFG_TYPE_ISO; - ep->CFG = cfg; - - /* make a note of the endpoint size */ - xfer->max_packet_size = size; - - return true; -} - -bool dcd_edpt_xfer(uint8_t rhport, uint8_t ep_addr, uint8_t *buffer, uint16_t total_bytes) -{ - (void) rhport; - - /* mine the data for the information we need */ - tusb_dir_t dir = tu_edpt_dir(ep_addr); - USBD_EP_T *ep = ep_entry(ep_addr, false); - struct xfer_ctl_t *xfer = &xfer_table[ep - USBD->EP]; - - /* store away the information we'll needing now and later */ - xfer->data_ptr = buffer; - xfer->in_remaining_bytes = total_bytes; - xfer->total_bytes = total_bytes; - - /* for the first of one or more EP0_IN packets in a message, the first must be DATA1 */ - if ( (0x80 == ep_addr) && !active_ep0_xfer ) ep->CFG |= USBD_CFG_DSQSYNC_Msk; - - if (TUSB_DIR_IN == dir) - { - dcd_in_xfer(xfer, ep); - } - else - { - xfer->out_bytes_so_far = 0; - ep->MXPLD = xfer->max_packet_size; - } - - return true; -} - -void dcd_edpt_stall(uint8_t rhport, uint8_t ep_addr) -{ - (void) rhport; - USBD_EP_T *ep = ep_entry(ep_addr, false); - ep->CFGP |= USBD_CFGP_SSTALL_Msk; -} - -void dcd_edpt_clear_stall(uint8_t rhport, uint8_t ep_addr) -{ - (void) rhport; - USBD_EP_T *ep = ep_entry(ep_addr, false); - ep->CFG |= USBD_CFG_CSTALL_Msk; -} - -void dcd_int_handler(uint8_t rhport) -{ - (void) rhport; - - uint32_t status = USBD->INTSTS; -#ifdef SUPPORT_LPM - uint32_t state = USBD->ATTR & 0x300f; -#else - uint32_t state = USBD->ATTR & 0xf; -#endif - - if(status & USBD_INTSTS_VBDETIF_Msk) - { - if(USBD->VBUSDET & USBD_VBUSDET_VBUSDET_Msk) - { - /* USB connect */ - USBD->ATTR |= USBD_ATTR_USBEN_Msk | USBD_ATTR_PHYEN_Msk; - } - else - { - /* USB disconnect */ - USBD->ATTR &= ~USBD_ATTR_USBEN_Msk; - } - } - - if(status & USBD_INTSTS_BUSIF_Msk) - { - if(state & USBD_ATTR_USBRST_Msk) - { - /* USB bus reset */ - USBD->ATTR |= USBD_ATTR_USBEN_Msk | USBD_ATTR_PHYEN_Msk; - - bus_reset(); - - dcd_event_bus_signal(0, DCD_EVENT_BUS_RESET, true); - } - - if(state & USBD_ATTR_SUSPEND_Msk) - { - /* Enable USB but disable PHY */ - USBD->ATTR &= ~USBD_ATTR_PHYEN_Msk; - dcd_event_bus_signal(0, DCD_EVENT_SUSPEND, true); - } - - if(state & USBD_ATTR_RESUME_Msk) - { - /* Enable USB and enable PHY */ - USBD->ATTR |= USBD_ATTR_USBEN_Msk | USBD_ATTR_PHYEN_Msk; - dcd_event_bus_signal(0, DCD_EVENT_RESUME, true); - } - } - - if(status & USBD_INTSTS_SETUP_Msk) - { - /* clear the data ready flag of control endpoints */ - USBD->EP[PERIPH_EP0].CFGP |= USBD_CFGP_CLRRDY_Msk; - USBD->EP[PERIPH_EP1].CFGP |= USBD_CFGP_CLRRDY_Msk; - - /* get SETUP packet from USB buffer */ - dcd_event_setup_received(0, (uint8_t *)USBD_BUF_BASE, true); - } - - if(status & USBD_INTSTS_USBIF_Msk) - { - if (status & USBD_INTSTS_EPEVT0_Msk) /* PERIPH_EP0 (EP0_IN) event: this is treated separately from the rest */ - { - /* given ACK from host has happened, we can now set the address (if not already done) */ - if((USBD->FADDR != assigned_address) && (USBD->FADDR == 0)) USBD->FADDR = assigned_address; - - uint16_t const available_bytes = USBD->EP[PERIPH_EP0].MXPLD; - - active_ep0_xfer = (available_bytes == xfer_table[PERIPH_EP0].max_packet_size); - - dcd_event_xfer_complete(0, 0x80, available_bytes, XFER_RESULT_SUCCESS, true); - } - - /* service PERIPH_EP1 through PERIPH_EP7 */ - enum ep_enum ep_index; - uint32_t mask; - struct xfer_ctl_t *xfer; - USBD_EP_T *ep; - for (ep_index = PERIPH_EP1, mask = USBD_INTSTS_EPEVT1_Msk, xfer = &xfer_table[PERIPH_EP1], ep = &USBD->EP[PERIPH_EP1]; ep_index <= PERIPH_EP7; ep_index++, mask <<= 1, xfer++, ep++) - { - if(status & mask) - { - USBD->INTSTS = mask; - - uint16_t const available_bytes = ep->MXPLD; - uint8_t const ep_addr = decode_ep_addr(ep); - bool const out_ep = !(ep_addr & TUSB_DIR_IN_MASK); - - if (out_ep) - { - /* copy the data from the PC to the previously provided buffer */ - memcpy(xfer->data_ptr, (uint8_t *)(USBD_BUF_BASE + ep->BUFSEG), available_bytes); - xfer->out_bytes_so_far += available_bytes; - xfer->data_ptr += available_bytes; - - /* when the transfer is finished, alert TinyUSB; otherwise, accept more data */ - if ( (xfer->total_bytes == xfer->out_bytes_so_far) || (available_bytes < xfer->max_packet_size) ) - dcd_event_xfer_complete(0, ep_addr, xfer->out_bytes_so_far, XFER_RESULT_SUCCESS, true); - else - ep->MXPLD = xfer->max_packet_size; - } - else - { - /* update the bookkeeping to reflect the data that has now been sent to the PC */ - xfer->in_remaining_bytes -= available_bytes; - xfer->data_ptr += available_bytes; - - /* if more data to send, send it; otherwise, alert TinyUSB that we've finished */ - if (xfer->in_remaining_bytes) - dcd_in_xfer(xfer, ep); - else - dcd_event_xfer_complete(0, ep_addr, xfer->total_bytes, XFER_RESULT_SUCCESS, true); - } - } - } - } - - if(status & USBD_INTSTS_SOFIF_Msk) - { - /* Start-Of-Frame event */ - dcd_event_bus_signal(0, DCD_EVENT_SOF, true); - } - - /* acknowledge all interrupts */ - USBD->INTSTS = status & enabled_irqs; -} - -void dcd_disconnect(uint8_t rhport) -{ - (void) rhport; - usb_detach(); -} - -void dcd_connect(uint8_t rhport) -{ - (void) rhport; - usb_attach(); -} - -#endif diff --git a/cores/nRF5/TinyUSB/Adafruit_TinyUSB_ArduinoCore/tinyusb/src/portable/nuvoton/nuc505/dcd_nuc505.c b/cores/nRF5/TinyUSB/Adafruit_TinyUSB_ArduinoCore/tinyusb/src/portable/nuvoton/nuc505/dcd_nuc505.c deleted file mode 100644 index a7961a6..0000000 --- a/cores/nRF5/TinyUSB/Adafruit_TinyUSB_ArduinoCore/tinyusb/src/portable/nuvoton/nuc505/dcd_nuc505.c +++ /dev/null @@ -1,657 +0,0 @@ -/* - * The MIT License (MIT) - * - * Copyright (c) 2020 Peter Lawrence - * - * 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. - */ - -/* - Theory of operation: - - The NUC505 USBD peripheral has twelve "EP"s, where each is simplex, in addition - to dedicated support for the control endpoint (EP0). The non-user endpoints - are referred to as "user" EPs in this code, and follow the datasheet - nomenclature of EPA through EPL. -*/ - -#include "tusb_option.h" - -#if TUSB_OPT_DEVICE_ENABLED && (CFG_TUSB_MCU == OPT_MCU_NUC505) - -#include "device/dcd.h" -#include "NUC505Series.h" - -/* - * The DMA functionality of the USBD peripheral does not appear to succeed with - * transfer lengths that are longer (> 64 bytes) and are not a multiple of 4. - * Keep disabled for now. - */ -#define USE_DMA 0 - -/* rather important info unfortunately not provided by device include files */ -#define USBD_BUF_SIZE 2048 /* how much USB buffer space there is */ -#define USBD_MAX_DMA_LEN 0x1000 /* max bytes that can be DMAed at one time */ - -enum ep_enum -{ - PERIPH_EPA = 0, - PERIPH_EPB = 1, - PERIPH_EPC = 2, - PERIPH_EPD = 3, - PERIPH_EPE = 4, - PERIPH_EPF = 5, - PERIPH_EPG = 6, - PERIPH_EPH = 7, - PERIPH_EPI = 8, - PERIPH_EPJ = 9, - PERIPH_EPK = 10, - PERIPH_EPL = 11, - PERIPH_MAX_EP, -}; - -static const uint8_t epcfg_eptype_table[] = -{ - [TUSB_XFER_CONTROL] = 0, /* won't happen, since control EPs have dedicated registers */ - [TUSB_XFER_ISOCHRONOUS] = 3 << USBD_EPCFG_EPTYPE_Pos, - [TUSB_XFER_BULK] = 1 << USBD_EPCFG_EPTYPE_Pos, - [TUSB_XFER_INTERRUPT] = 2 << USBD_EPCFG_EPTYPE_Pos, -}; - -static const uint8_t eprspctl_eptype_table[] = -{ - [TUSB_XFER_CONTROL] = 0, /* won't happen, since control EPs have dedicated registers */ - [TUSB_XFER_ISOCHRONOUS] = 2 << USBD_EPRSPCTL_MODE_Pos, /* Fly Mode */ - [TUSB_XFER_BULK] = 0 << USBD_EPRSPCTL_MODE_Pos, /* Auto-Validate Mode */ - [TUSB_XFER_INTERRUPT] = 1 << USBD_EPRSPCTL_MODE_Pos, /* Manual-Validate Mode */ -}; - -/* set by dcd_set_address() */ -static volatile uint8_t assigned_address; - -/* reset by bus_reset(), this is used by dcd_edpt_open() to assign USBD peripheral buffer addresses */ -static uint32_t bufseg_addr; - -/* RAM table needed to track ongoing transfers performed by dcd_edpt_xfer(), dcd_userEP_in_xfer(), and the ISR */ -static struct xfer_ctl_t -{ - uint8_t *data_ptr; /* data_ptr tracks where to next copy data to (for OUT) or from (for IN) */ - union { - uint16_t in_remaining_bytes; /* for IN endpoints, we track how many bytes are left to transfer */ - uint16_t out_bytes_so_far; /* but for OUT endpoints, we track how many bytes we've transferred so far */ - }; - uint16_t max_packet_size; /* needed since device driver only finds out this at runtime */ - uint16_t total_bytes; /* quantity needed to pass as argument to dcd_event_xfer_complete() (for IN endpoints) */ - uint8_t ep_addr; - bool dma_requested; -} xfer_table[PERIPH_MAX_EP]; - -/* in addition to xfer_table, additional bespoke bookkeeping is maintained for control EP0 IN */ -static struct -{ - uint8_t *data_ptr; - uint16_t in_remaining_bytes; - uint16_t total_bytes; -} ctrl_in_xfer; - -static volatile struct xfer_ctl_t *current_dma_xfer; - - -/* - local helper functions -*/ - -static void usb_attach(void) -{ - USBD->PHYCTL |= USBD_PHYCTL_DPPUEN_Msk; -} - -static void usb_detach(void) -{ - USBD->PHYCTL &= ~USBD_PHYCTL_DPPUEN_Msk; -} - -static void usb_control_send_zlp(void) -{ - USBD->CEPINTSTS = USBD_CEPINTSTS_STSDONEIF_Msk; - USBD->CEPCTL = 0; /* clear NAKCLR bit */ - USBD->CEPINTEN = USBD_CEPINTEN_STSDONEIEN_Msk; -} - -/* map 8-bit ep_addr into peripheral endpoint index (PERIPH_EPA...) */ -static USBD_EP_T *ep_entry(uint8_t ep_addr, bool add) -{ - USBD_EP_T *ep; - enum ep_enum ep_index; - struct xfer_ctl_t *xfer; - - for (ep_index = PERIPH_EPA, xfer = &xfer_table[PERIPH_EPA], ep = USBD->EP; ep_index < PERIPH_MAX_EP; ep_index++, xfer++, ep++) - { - if (add) - { - /* take first peripheral endpoint that is unused */ - if (0 == (ep->EPCFG & USBD_EPCFG_EPEN_Msk)) return ep; - } - else - { - /* find a peripheral endpoint that matches ep_addr */ - if (xfer->ep_addr == ep_addr) return ep; - } - } - - return NULL; -} - -/* perform a non-control IN endpoint transfer; this is called by the ISR */ -static void dcd_userEP_in_xfer(struct xfer_ctl_t *xfer, USBD_EP_T *ep) -{ - uint16_t bytes_now = tu_min16(xfer->in_remaining_bytes, xfer->max_packet_size); - uint16_t countdown = bytes_now; - - /* precompute what amount of data will be left */ - xfer->in_remaining_bytes -= bytes_now; - - /* - if there will be no more data to send, we replace the BUFEMPTYIF EP interrupt with TXPKIF; - that way, we alert TinyUSB as soon as this last packet has been sent - */ - if (0 == xfer->in_remaining_bytes) - { - ep->EPINTSTS = USBD_EPINTSTS_TXPKIF_Msk; - ep->EPINTEN = USBD_EPINTEN_TXPKIEN_Msk; - } - - /* provided buffers are thankfully 32-bit aligned, allowing most data to be transfered as 32-bit */ - while (countdown > 3) - { - uint32_t u32; - memcpy(&u32, xfer->data_ptr, 4); - - ep->EPDAT = u32; - xfer->data_ptr += 4; countdown -= 4; - } - while (countdown--) - ep->EPDAT_BYTE = *xfer->data_ptr++; - - /* for short packets, we must nudge the peripheral to say 'that's all folks' */ - if (bytes_now != xfer->max_packet_size) - ep->EPRSPCTL = USBD_EPRSPCTL_SHORTTXEN_Msk; -} - -/* called by dcd_init() as well as by the ISR during a USB bus reset */ -static void bus_reset(void) -{ - for (enum ep_enum ep_index = PERIPH_EPA; ep_index < PERIPH_MAX_EP; ep_index++) - { - USBD->EP[ep_index].EPCFG = 0; - xfer_table[ep_index].dma_requested = false; - } - - USBD->DMACNT = 0; - USBD->DMACTL = USBD_DMACTL_DMARST_Msk; - USBD->DMACTL = 0; - - /* allocate the default EP0 endpoints */ - - USBD->CEPBUFSTART = 0; - USBD->CEPBUFEND = 0 + CFG_TUD_ENDPOINT0_SIZE - 1; - - /* USB RAM beyond what we've allocated above is available to the user */ - bufseg_addr = CFG_TUD_ENDPOINT0_SIZE; - - /* Reset USB device address */ - USBD->FADDR = 0; - - current_dma_xfer = NULL; -} - -#if USE_DMA -/* this must only be called by the ISR; it does its best to share the single DMA engine across all user EPs (IN and OUT) */ -static void service_dma(void) -{ - if (current_dma_xfer) - return; - - enum ep_enum ep_index; - struct xfer_ctl_t *xfer; - USBD_EP_T *ep; - - for (ep_index = PERIPH_EPA, xfer = &xfer_table[PERIPH_EPA], ep = &USBD->EP[PERIPH_EPA]; ep_index < PERIPH_MAX_EP; ep_index++, xfer++, ep++) - { - uint16_t const available_bytes = ep->EPDATCNT & USBD_EPDATCNT_DATCNT_Msk; - - if (!xfer->dma_requested || !available_bytes) - continue; - - /* - instruct DMA to copy the data from the PC to the previously provided buffer - when the bus interrupt DMADONEIEN subsequently fires, the transfer will have finished - */ - USBD->DMACTL = xfer->ep_addr & USBD_DMACTL_EPNUM_Msk; - USBD->DMAADDR = (uint32_t)xfer->data_ptr; - USBD->DMACNT = available_bytes; - USBD->BUSINTSTS = USBD_BUSINTSTS_DMADONEIF_Msk; - xfer->out_bytes_so_far += available_bytes; - current_dma_xfer = xfer; - USBD->DMACTL |= USBD_DMACTL_DMAEN_Msk; - - return; - } -} -#endif - -/* centralized location for USBD interrupt enable bit masks */ -static const uint32_t enabled_irqs = USBD_GINTEN_USBIEN_Msk | \ - USBD_GINTEN_EPAIEN_Msk | USBD_GINTEN_EPBIEN_Msk | USBD_GINTEN_EPCIEN_Msk | USBD_GINTEN_EPDIEN_Msk | USBD_GINTEN_EPEIEN_Msk | USBD_GINTEN_EPFIEN_Msk | \ - USBD_GINTEN_EPGIEN_Msk | USBD_GINTEN_EPHIEN_Msk | USBD_GINTEN_EPIIEN_Msk | USBD_GINTEN_EPJIEN_Msk | USBD_GINTEN_EPKIEN_Msk | USBD_GINTEN_EPLIEN_Msk | \ - USBD_GINTEN_CEPIEN_Msk; - -/* - NUC505 TinyUSB API driver implementation -*/ - -void dcd_init(uint8_t rhport) -{ - (void) rhport; - - /* configure interrupts in their initial state; BUSINTEN and CEPINTEN will be subsequently and dynamically re-written as needed */ - USBD->GINTEN = enabled_irqs; - USBD->BUSINTEN = USBD_BUSINTEN_RSTIEN_Msk | USBD_BUSINTEN_VBUSDETIEN_Msk | USBD_BUSINTEN_RESUMEIEN_Msk | USBD_BUSINTEN_DMADONEIEN_Msk; - USBD->CEPINTEN = 0; - - bus_reset(); - - usb_attach(); -} - -void dcd_int_enable(uint8_t rhport) -{ - (void) rhport; - NVIC_EnableIRQ(USBD_IRQn); -} - -void dcd_int_disable(uint8_t rhport) -{ - (void) rhport; - NVIC_DisableIRQ(USBD_IRQn); -} - -void dcd_set_address(uint8_t rhport, uint8_t dev_addr) -{ - (void) rhport; - usb_control_send_zlp(); /* SET_ADDRESS is the one exception where TinyUSB doesn't use dcd_edpt_xfer() to generate a ZLP */ - assigned_address = dev_addr; -} - -void dcd_remote_wakeup(uint8_t rhport) -{ - (void) rhport; - USBD->OPER |= USBD_OPER_RESUMEEN_Msk; -} - -bool dcd_edpt_open(uint8_t rhport, tusb_desc_endpoint_t const * p_endpoint_desc) -{ - (void) rhport; - - USBD_EP_T *ep = ep_entry(p_endpoint_desc->bEndpointAddress, true); - TU_ASSERT(ep); - - /* mine the data for the information we need */ - int const dir = tu_edpt_dir(p_endpoint_desc->bEndpointAddress); - int const size = p_endpoint_desc->wMaxPacketSize.size; - tusb_xfer_type_t const type = p_endpoint_desc->bmAttributes.xfer; - struct xfer_ctl_t *xfer = &xfer_table[ep - USBD->EP]; - - /* allocate buffer from USB RAM */ - ep->EPBUFSTART = bufseg_addr; - bufseg_addr += size; - ep->EPBUFEND = bufseg_addr - 1; - TU_ASSERT(bufseg_addr <= USBD_BUF_SIZE); - - ep->EPMPS = size; - - ep->EPRSPCTL = USB_EP_RSPCTL_FLUSH | eprspctl_eptype_table[type]; - - /* construct USB Configuration Register value and then write it */ - uint32_t cfg = (uint32_t)tu_edpt_number(p_endpoint_desc->bEndpointAddress) << USBD_EPCFG_EPNUM_Pos; - if (TUSB_DIR_IN == dir) - cfg |= USBD_EPCFG_EPDIR_Msk; - cfg |= epcfg_eptype_table[type] | USBD_EPCFG_EPEN_Msk; - ep->EPCFG = cfg; - - /* make a note of the endpoint particulars */ - xfer->max_packet_size = size; - xfer->ep_addr = p_endpoint_desc->bEndpointAddress; - - return true; -} - -bool dcd_edpt_xfer(uint8_t rhport, uint8_t ep_addr, uint8_t *buffer, uint16_t total_bytes) -{ - (void) rhport; - - if (0x80 == ep_addr) /* control EP0 IN */ - { - if (total_bytes) - { - USBD->CEPCTL = USBD_CEPCTL_FLUSH_Msk; - ctrl_in_xfer.data_ptr = buffer; - ctrl_in_xfer.in_remaining_bytes = total_bytes; - ctrl_in_xfer.total_bytes = total_bytes; - USBD->CEPINTSTS = USBD_CEPINTSTS_INTKIF_Msk; - USBD->CEPINTEN = USBD_CEPINTEN_INTKIEN_Msk; - } - else - { - usb_control_send_zlp(); - } - } - else if (0x00 == ep_addr) /* control EP0 OUT */ - { - if (total_bytes) - { - /* if TinyUSB is asking for EP0 OUT data, it is almost certainly already in the buffer */ - while (total_bytes < USBD->CEPRXCNT); - for (int count = 0; count < total_bytes; count++) - *buffer++ = USBD->CEPDAT_BYTE; - - dcd_event_xfer_complete(0, ep_addr, total_bytes, XFER_RESULT_SUCCESS, true); - } - } - else - { - /* mine the data for the information we need */ - tusb_dir_t dir = tu_edpt_dir(ep_addr); - USBD_EP_T *ep = ep_entry(ep_addr, false); - struct xfer_ctl_t *xfer = &xfer_table[ep - USBD->EP]; - - /* store away the information we'll needing now and later */ - xfer->data_ptr = buffer; - xfer->in_remaining_bytes = total_bytes; - xfer->total_bytes = total_bytes; - - if (TUSB_DIR_IN == dir) - { - ep->EPINTEN = USBD_EPINTEN_BUFEMPTYIEN_Msk; - } - else - { - xfer->out_bytes_so_far = 0; - ep->EPINTEN = USBD_EPINTEN_RXPKIEN_Msk; - } - } - - return true; -} - -void dcd_edpt_stall(uint8_t rhport, uint8_t ep_addr) -{ - (void) rhport; - - if (tu_edpt_number(ep_addr)) - { - USBD_EP_T *ep = ep_entry(ep_addr, false); - ep->EPRSPCTL = (ep->EPRSPCTL & 0xf7) | USBD_EPRSPCTL_HALT_Msk; - } - else - { - USBD->CEPCTL = USBD_CEPCTL_STALLEN_Msk; - } -} - -void dcd_edpt_clear_stall(uint8_t rhport, uint8_t ep_addr) -{ - (void) rhport; - - if (tu_edpt_number(ep_addr)) - { - USBD_EP_T *ep = ep_entry(ep_addr, false); - ep->EPRSPCTL = USBD_EPRSPCTL_TOGGLE_Msk; - } -} - -void dcd_int_handler(uint8_t rhport) -{ - (void) rhport; - - uint32_t status = USBD->GINTSTS; - - /* USB interrupt */ - if (status & USBD_GINTSTS_USBIF_Msk) - { - uint32_t bus_state = USBD->BUSINTSTS; - - if (bus_state & USBD_BUSINTSTS_SOFIF_Msk) - { - /* Start-Of-Frame event */ - dcd_event_bus_signal(0, DCD_EVENT_SOF, true); - } - - if (bus_state & USBD_BUSINTSTS_RSTIF_Msk) - { - bus_reset(); - - USBD->CEPINTEN = USBD_CEPINTEN_SETUPPKIEN_Msk; - USBD->BUSINTEN = USBD_BUSINTEN_RSTIEN_Msk | USBD_BUSINTEN_RESUMEIEN_Msk | USBD_BUSINTEN_SUSPENDIEN_Msk | USBD_BUSINTEN_DMADONEIEN_Msk; - USBD->CEPINTSTS = 0x1ffc; - - tusb_speed_t speed = (USBD->OPER & USBD_OPER_CURSPD_Msk) ? TUSB_SPEED_HIGH : TUSB_SPEED_FULL; - dcd_event_bus_reset(0, speed, true); - } - - if (bus_state & USBD_BUSINTSTS_RESUMEIF_Msk) - { - USBD->BUSINTEN = USBD_BUSINTEN_RSTIEN_Msk | USBD_BUSINTEN_SUSPENDIEN_Msk | USBD_BUSINTEN_DMADONEIEN_Msk; - dcd_event_bus_signal(0, DCD_EVENT_RESUME, true); - } - - if (bus_state & USBD_BUSINTSTS_SUSPENDIF_Msk) - { - USBD->BUSINTEN = USBD_BUSINTEN_RSTIEN_Msk | USBD_BUSINTEN_RESUMEIEN_Msk | USBD_BUSINTEN_DMADONEIEN_Msk; - dcd_event_bus_signal(0, DCD_EVENT_SUSPEND, true); - } - - if (bus_state & USBD_BUSINTSTS_HISPDIF_Msk) - { - USBD->CEPINTEN = USBD_CEPINTEN_SETUPPKIEN_Msk; - } - - if (bus_state & USBD_BUSINTSTS_DMADONEIF_Msk) - { -#if USE_DMA - if (current_dma_xfer) - { - current_dma_xfer->dma_requested = false; - - uint16_t available_bytes = USBD->DMACNT & USBD_DMACNT_DMACNT_Msk; - - /* if the most recent DMA finishes the transfer, alert TinyUSB; otherwise, the next RXPKIF/INTKIF endpoint interrupt will prompt the next DMA */ - if ( (current_dma_xfer->total_bytes == current_dma_xfer->out_bytes_so_far) || (available_bytes < current_dma_xfer->max_packet_size) ) - { - dcd_event_xfer_complete(0, current_dma_xfer->ep_addr, current_dma_xfer->out_bytes_so_far, XFER_RESULT_SUCCESS, true); - } - - current_dma_xfer = NULL; - service_dma(); - } -#endif - } - - if (bus_state & USBD_BUSINTSTS_VBUSDETIF_Msk) - { - if (USBD->PHYCTL & USBD_PHYCTL_VBUSDET_Msk) - { - /* USB connect */ - USBD->PHYCTL |= USBD_PHYCTL_PHYEN_Msk | USBD_PHYCTL_DPPUEN_Msk; - } - else - { - /* USB disconnect */ - USBD->PHYCTL &= ~USBD_PHYCTL_DPPUEN_Msk; - } - } - - USBD->BUSINTSTS = bus_state & (USBD_BUSINTSTS_SOFIF_Msk | USBD_BUSINTSTS_RSTIF_Msk | USBD_BUSINTSTS_RESUMEIF_Msk | USBD_BUSINTSTS_SUSPENDIF_Msk | USBD_BUSINTSTS_HISPDIF_Msk | USBD_BUSINTSTS_DMADONEIF_Msk | USBD_BUSINTSTS_PHYCLKVLDIF_Msk | USBD_BUSINTSTS_VBUSDETIF_Msk); - } - - if (status & USBD_GINTSTS_CEPIF_Msk) - { - uint32_t cep_state = USBD->CEPINTSTS & USBD->CEPINTEN; - - if (cep_state & USBD_CEPINTSTS_SETUPPKIF_Msk) - { - /* get SETUP packet from USB buffer */ - uint8_t setup_packet[8]; - setup_packet[0] = (uint8_t)(USBD->SETUP1_0 >> 0); - setup_packet[1] = (uint8_t)(USBD->SETUP1_0 >> 8); - setup_packet[2] = (uint8_t)(USBD->SETUP3_2 >> 0); - setup_packet[3] = (uint8_t)(USBD->SETUP3_2 >> 8); - setup_packet[4] = (uint8_t)(USBD->SETUP5_4 >> 0); - setup_packet[5] = (uint8_t)(USBD->SETUP5_4 >> 8); - setup_packet[6] = (uint8_t)(USBD->SETUP7_6 >> 0); - setup_packet[7] = (uint8_t)(USBD->SETUP7_6 >> 8); - dcd_event_setup_received(0, setup_packet, true); - } - else if (cep_state & USBD_CEPINTSTS_INTKIF_Msk) - { - USBD->CEPINTSTS = USBD_CEPINTSTS_TXPKIF_Msk; - - if (!(cep_state & USBD_CEPINTSTS_STSDONEIF_Msk)) - { - USBD->CEPINTEN = USBD_CEPINTEN_TXPKIEN_Msk; - uint16_t bytes_now = tu_min16(ctrl_in_xfer.in_remaining_bytes, CFG_TUD_ENDPOINT0_SIZE); - for (int count = 0; count < bytes_now; count++) - USBD->CEPDAT_BYTE = *ctrl_in_xfer.data_ptr++; - ctrl_in_xfer.in_remaining_bytes -= bytes_now; - USBD_START_CEP_IN(bytes_now); - } - else - { - USBD->CEPINTEN = USBD_CEPINTEN_TXPKIEN_Msk | USBD_CEPINTEN_STSDONEIEN_Msk; - } - } - else if (cep_state & USBD_CEPINTSTS_TXPKIF_Msk) - { - USBD->CEPINTSTS = USBD_CEPINTSTS_STSDONEIF_Msk; - USBD_SET_CEP_STATE(USB_CEPCTL_NAKCLR); - - /* alert TinyUSB that the EP0 IN transfer has finished */ - if ( (0 == ctrl_in_xfer.in_remaining_bytes) || (0 == ctrl_in_xfer.total_bytes) ) - dcd_event_xfer_complete(0, 0x80, ctrl_in_xfer.total_bytes, XFER_RESULT_SUCCESS, true); - - if (ctrl_in_xfer.in_remaining_bytes) - { - USBD->CEPINTSTS = USBD_CEPINTSTS_INTKIF_Msk; - USBD->CEPINTEN = USBD_CEPINTEN_INTKIEN_Msk; - } - else - { - /* TinyUSB does its own fragmentation and ZLP for EP0; a transfer of zero means a ZLP */ - if (0 == ctrl_in_xfer.total_bytes) USBD->CEPCTL = USBD_CEPCTL_ZEROLEN_Msk; - - USBD->CEPINTSTS = USBD_CEPINTSTS_STSDONEIF_Msk; - USBD->CEPINTEN = USBD_CEPINTEN_SETUPPKIEN_Msk | USBD_CEPINTEN_STSDONEIEN_Msk; - } - } - else if (cep_state & USBD_CEPINTSTS_STSDONEIF_Msk) - { - /* given ACK from host has happened, we can now set the address (if not already done) */ - if((USBD->FADDR != assigned_address) && (USBD->FADDR == 0)) - { - USBD->FADDR = assigned_address; - - for (enum ep_enum ep_index = PERIPH_EPA; ep_index < PERIPH_MAX_EP; ep_index++) - { - if (USBD->EP[ep_index].EPCFG & USBD_EPCFG_EPEN_Msk) USBD->EP[ep_index].EPRSPCTL = USBD_EPRSPCTL_TOGGLE_Msk; - } - } - - USBD->CEPINTEN = USBD_CEPINTEN_SETUPPKIEN_Msk; - } - - USBD->CEPINTSTS = cep_state; - - return; - } - - if (status & (USBD_GINTSTS_EPAIF_Msk | USBD_GINTSTS_EPBIF_Msk | USBD_GINTSTS_EPCIF_Msk | USBD_GINTSTS_EPDIF_Msk | USBD_GINTSTS_EPEIF_Msk | USBD_GINTSTS_EPFIF_Msk | USBD_GINTSTS_EPGIF_Msk | USBD_GINTSTS_EPHIF_Msk | USBD_GINTSTS_EPIIF_Msk | USBD_GINTSTS_EPJIF_Msk | USBD_GINTSTS_EPKIF_Msk | USBD_GINTSTS_EPLIF_Msk)) - { - /* service PERIPH_EPA through PERIPH_EPL */ - enum ep_enum ep_index; - uint32_t mask; - struct xfer_ctl_t *xfer; - USBD_EP_T *ep; - for (ep_index = PERIPH_EPA, mask = USBD_GINTSTS_EPAIF_Msk, xfer = &xfer_table[PERIPH_EPA], ep = &USBD->EP[PERIPH_EPA]; ep_index < PERIPH_MAX_EP; ep_index++, mask <<= 1, xfer++, ep++) - { - if(status & mask) - { - uint8_t const ep_addr = xfer->ep_addr; - bool const out_ep = !(ep_addr & TUSB_DIR_IN_MASK); - uint32_t ep_state = ep->EPINTSTS & ep->EPINTEN; - - if (out_ep) - { -#if USE_DMA - xfer->dma_requested = true; - service_dma(); -#else - uint16_t const available_bytes = ep->EPDATCNT & USBD_EPDATCNT_DATCNT_Msk; - /* copy the data from the PC to the previously provided buffer */ - for (int count = 0; (count < available_bytes) && (xfer->out_bytes_so_far < xfer->total_bytes); count++, xfer->out_bytes_so_far++) - *xfer->data_ptr++ = ep->EPDAT_BYTE; - - /* when the transfer is finished, alert TinyUSB; otherwise, continue accepting more data */ - if ( (xfer->total_bytes == xfer->out_bytes_so_far) || (available_bytes < xfer->max_packet_size) ) - dcd_event_xfer_complete(0, ep_addr, xfer->out_bytes_so_far, XFER_RESULT_SUCCESS, true); -#endif - - } - else if (ep_state & USBD_EPINTSTS_BUFEMPTYIF_Msk) - { - /* send any remaining data */ - dcd_userEP_in_xfer(xfer, ep); - } - else if (ep_state & USBD_EPINTSTS_TXPKIF_Msk) - { - /* alert TinyUSB that we've finished */ - dcd_event_xfer_complete(0, ep_addr, xfer->total_bytes, XFER_RESULT_SUCCESS, true); - ep->EPINTEN = 0; - } - - ep->EPINTSTS = ep_state; - } - } - } -} - -void dcd_disconnect(uint8_t rhport) -{ - (void) rhport; - usb_detach(); -} - -void dcd_connect(uint8_t rhport) -{ - (void) rhport; - usb_attach(); -} - -#endif diff --git a/cores/nRF5/TinyUSB/Adafruit_TinyUSB_ArduinoCore/tinyusb/src/portable/nxp/lpc17_40/dcd_lpc17_40.c b/cores/nRF5/TinyUSB/Adafruit_TinyUSB_ArduinoCore/tinyusb/src/portable/nxp/lpc17_40/dcd_lpc17_40.c deleted file mode 100644 index 39c5e66..0000000 --- a/cores/nRF5/TinyUSB/Adafruit_TinyUSB_ArduinoCore/tinyusb/src/portable/nxp/lpc17_40/dcd_lpc17_40.c +++ /dev/null @@ -1,581 +0,0 @@ -/* - * 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. - */ - -#include "tusb_option.h" - -#if TUSB_OPT_DEVICE_ENABLED && (CFG_TUSB_MCU == OPT_MCU_LPC175X_6X || CFG_TUSB_MCU == OPT_MCU_LPC40XX) - -#include "device/dcd.h" -#include "dcd_lpc17_40.h" -#include "chip.h" - -//--------------------------------------------------------------------+ -// MACRO CONSTANT TYPEDEF -//--------------------------------------------------------------------+ -#define DCD_ENDPOINT_MAX 32 - -typedef struct TU_ATTR_ALIGNED(4) -{ - //------------- Word 0 -------------// - uint32_t next; - - //------------- Word 1 -------------// - uint16_t atle_mode : 2; // 00: normal, 01: ATLE (auto length extraction) - uint16_t next_valid : 1; - uint16_t : 1; ///< reserved - uint16_t isochronous : 1; // is an iso endpoint - uint16_t max_packet_size : 11; - - volatile uint16_t buflen; // bytes for non-iso, number of packets for iso endpoint - - //------------- Word 2 -------------// - volatile uint32_t buffer; - - //------------- Word 3 -------------// - volatile uint16_t retired : 1; // initialized to zero - volatile uint16_t status : 4; - volatile uint16_t iso_last_packet_valid : 1; - volatile uint16_t atle_lsb_extracted : 1; // used in ATLE mode - volatile uint16_t atle_msb_extracted : 1; // used in ATLE mode - volatile uint16_t atle_mess_len_position : 6; // used in ATLE mode - uint16_t : 2; - - volatile uint16_t present_count; // For non-iso : The number of bytes transferred by the DMA engine - // For iso : number of packets - - //------------- Word 4 -------------// - // uint32_t iso_packet_size_addr; // iso only, can be omitted for non-iso -}dma_desc_t; - -TU_VERIFY_STATIC( sizeof(dma_desc_t) == 16, "size is not correct"); // TODO not support ISO for now - -typedef struct -{ - // must be 128 byte aligned - volatile dma_desc_t* udca[DCD_ENDPOINT_MAX]; - - // TODO DMA does not support control transfer (0-1 are not used, offset to reduce memory) - dma_desc_t dd[DCD_ENDPOINT_MAX]; - - struct - { - uint8_t* out_buffer; - uint8_t out_bytes; - volatile bool out_received; // indicate if data is already received in endpoint - - uint8_t in_bytes; - } control; - -} dcd_data_t; - -CFG_TUSB_MEM_SECTION TU_ATTR_ALIGNED(128) static dcd_data_t _dcd; - - -//--------------------------------------------------------------------+ -// SIE Command -//--------------------------------------------------------------------+ -static void sie_cmd_code (sie_cmdphase_t phase, uint8_t code_data) -{ - LPC_USB->DevIntClr = (DEV_INT_COMMAND_CODE_EMPTY_MASK | DEV_INT_COMMAND_DATA_FULL_MASK); - LPC_USB->CmdCode = (phase << 8) | (code_data << 16); - - uint32_t const wait_flag = (phase == SIE_CMDPHASE_READ) ? DEV_INT_COMMAND_DATA_FULL_MASK : DEV_INT_COMMAND_CODE_EMPTY_MASK; - while ((LPC_USB->DevIntSt & wait_flag) == 0) {} - - LPC_USB->DevIntClr = wait_flag; -} - -static void sie_write (uint8_t cmd_code, uint8_t data_len, uint8_t data) -{ - sie_cmd_code(SIE_CMDPHASE_COMMAND, cmd_code); - - if (data_len) - { - sie_cmd_code(SIE_CMDPHASE_WRITE, data); - } -} - -static uint8_t sie_read (uint8_t cmd_code) -{ - sie_cmd_code(SIE_CMDPHASE_COMMAND , cmd_code); - sie_cmd_code(SIE_CMDPHASE_READ , cmd_code); - return (uint8_t) LPC_USB->CmdData; -} - -//--------------------------------------------------------------------+ -// PIPE HELPER -//--------------------------------------------------------------------+ -static inline uint8_t ep_addr2idx(uint8_t ep_addr) -{ - return 2*(ep_addr & 0x0F) + ((ep_addr & TUSB_DIR_IN_MASK) ? 1 : 0); -} - -static void set_ep_size(uint8_t ep_id, uint16_t max_packet_size) -{ - // follows example in 11.10.4.2 - LPC_USB->ReEp |= TU_BIT(ep_id); - LPC_USB->EpInd = ep_id; // select index before setting packet size - LPC_USB->MaxPSize = max_packet_size; - - while ((LPC_USB->DevIntSt & DEV_INT_ENDPOINT_REALIZED_MASK) == 0) {} - LPC_USB->DevIntClr = DEV_INT_ENDPOINT_REALIZED_MASK; -} - - -//--------------------------------------------------------------------+ -// CONTROLLER API -//--------------------------------------------------------------------+ -static void bus_reset(void) -{ - // step 7 : slave mode set up - LPC_USB->EpIntClr = 0xFFFFFFFF; // clear all pending interrupt - LPC_USB->DevIntClr = 0xFFFFFFFF; // clear all pending interrupt - LPC_USB->EpIntEn = 0x03UL; // control endpoint cannot use DMA, non-control all use DMA - LPC_USB->EpIntPri = 0x03UL; // fast for control endpoint - - // step 8 : DMA set up - LPC_USB->EpDMADis = 0xFFFFFFFF; // firstly disable all dma - LPC_USB->DMARClr = 0xFFFFFFFF; // clear all pending interrupt - LPC_USB->EoTIntClr = 0xFFFFFFFF; - LPC_USB->NDDRIntClr = 0xFFFFFFFF; - LPC_USB->SysErrIntClr = 0xFFFFFFFF; - - tu_memclr(&_dcd, sizeof(dcd_data_t)); -} - -void dcd_init(uint8_t rhport) -{ - (void) rhport; - - //------------- user manual 11.13 usb device controller initialization -------------// - // step 6 : set up control endpoint - set_ep_size(0, CFG_TUD_ENDPOINT0_SIZE); - set_ep_size(1, CFG_TUD_ENDPOINT0_SIZE); - - bus_reset(); - - LPC_USB->DevIntEn = (DEV_INT_DEVICE_STATUS_MASK | DEV_INT_ENDPOINT_FAST_MASK | DEV_INT_ENDPOINT_SLOW_MASK | DEV_INT_ERROR_MASK); - LPC_USB->UDCAH = (uint32_t) _dcd.udca; - LPC_USB->DMAIntEn = (DMA_INT_END_OF_XFER_MASK /*| DMA_INT_NEW_DD_REQUEST_MASK*/ | DMA_INT_ERROR_MASK); - - dcd_connect(rhport); - - // Clear pending IRQ - NVIC_ClearPendingIRQ(USB_IRQn); -} - -void dcd_int_enable(uint8_t rhport) -{ - (void) rhport; - NVIC_EnableIRQ(USB_IRQn); -} - -void dcd_int_disable(uint8_t rhport) -{ - (void) rhport; - NVIC_DisableIRQ(USB_IRQn); -} - -void dcd_set_address(uint8_t rhport, uint8_t dev_addr) -{ - // Response with status first before changing device address - dcd_edpt_xfer(rhport, tu_edpt_addr(0, TUSB_DIR_IN), NULL, 0); - - sie_write(SIE_CMDCODE_SET_ADDRESS, 1, 0x80 | dev_addr); // 7th bit is : device_enable - - // Also Set Configure Device to enable non-control endpoint response - sie_write(SIE_CMDCODE_CONFIGURE_DEVICE, 1, 1); -} - -void dcd_remote_wakeup(uint8_t rhport) -{ - (void) rhport; -} - -void dcd_connect(uint8_t rhport) -{ - (void) rhport; - sie_write(SIE_CMDCODE_DEVICE_STATUS, 1, SIE_DEV_STATUS_CONNECT_STATUS_MASK); -} - -void dcd_disconnect(uint8_t rhport) -{ - (void) rhport; - sie_write(SIE_CMDCODE_DEVICE_STATUS, 1, 0); -} - -//--------------------------------------------------------------------+ -// CONTROL HELPER -//--------------------------------------------------------------------+ -static inline uint8_t byte2dword(uint8_t bytes) -{ - return (bytes + 3) / 4; // length in dwords -} - -static void control_ep_write(void const * buffer, uint8_t len) -{ - uint32_t const * buf32 = (uint32_t const *) buffer; - - LPC_USB->Ctrl = USBCTRL_WRITE_ENABLE_MASK; // logical endpoint = 0 - LPC_USB->TxPLen = (uint32_t) len; - - for (uint8_t count = 0; count < byte2dword(len); count++) - { - LPC_USB->TxData = *buf32; // NOTE: cortex M3 have no problem with alignment - buf32++; - } - - LPC_USB->Ctrl = 0; - - // select control IN & validate the endpoint - sie_write(SIE_CMDCODE_ENDPOINT_SELECT+1, 0, 0); - sie_write(SIE_CMDCODE_BUFFER_VALIDATE , 0, 0); -} - -static uint8_t control_ep_read(void * buffer, uint8_t len) -{ - LPC_USB->Ctrl = USBCTRL_READ_ENABLE_MASK; // logical endpoint = 0 - while ((LPC_USB->RxPLen & USBRXPLEN_PACKET_READY_MASK) == 0) {} // TODO blocking, should have timeout - - len = tu_min8(len, (uint8_t) (LPC_USB->RxPLen & USBRXPLEN_PACKET_LENGTH_MASK) ); - uint32_t *buf32 = (uint32_t*) buffer; - - for (uint8_t count=0; count < byte2dword(len); count++) - { - *buf32 = LPC_USB->RxData; - buf32++; - } - - LPC_USB->Ctrl = 0; - - // select control OUT & clear the endpoint - sie_write(SIE_CMDCODE_ENDPOINT_SELECT+0, 0, 0); - sie_write(SIE_CMDCODE_BUFFER_CLEAR , 0, 0); - - return len; -} - -//--------------------------------------------------------------------+ -// DCD Endpoint Port -//--------------------------------------------------------------------+ - -bool dcd_edpt_open(uint8_t rhport, tusb_desc_endpoint_t const * p_endpoint_desc) -{ - (void) rhport; - - uint8_t const epnum = tu_edpt_number(p_endpoint_desc->bEndpointAddress); - uint8_t const ep_id = ep_addr2idx(p_endpoint_desc->bEndpointAddress); - - // Endpoint type is fixed to endpoint number - // 1: interrupt, 2: Bulk, 3: Iso and so on - switch ( p_endpoint_desc->bmAttributes.xfer ) - { - case TUSB_XFER_INTERRUPT: - TU_ASSERT((epnum % 3) == 1); - break; - - case TUSB_XFER_BULK: - TU_ASSERT((epnum % 3) == 2 || (epnum == 15)); - break; - - case TUSB_XFER_ISOCHRONOUS: - TU_ASSERT((epnum % 3) == 0 && (epnum != 0) && (epnum != 15)); - break; - - default: - break; - } - - //------------- Realize Endpoint with Max Packet Size -------------// - set_ep_size(ep_id, p_endpoint_desc->wMaxPacketSize.size); - - //------------- first DD prepare -------------// - dma_desc_t* const dd = &_dcd.dd[ep_id]; - tu_memclr(dd, sizeof(dma_desc_t)); - - dd->isochronous = (p_endpoint_desc->bmAttributes.xfer == TUSB_XFER_ISOCHRONOUS) ? 1 : 0; - dd->max_packet_size = p_endpoint_desc->wMaxPacketSize.size; - dd->retired = 1; // invalid at first - - sie_write(SIE_CMDCODE_ENDPOINT_SET_STATUS + ep_id, 1, 0); // clear all endpoint status - - return true; -} - -void dcd_edpt_stall(uint8_t rhport, uint8_t ep_addr) -{ - (void) rhport; - - if ( tu_edpt_number(ep_addr) == 0 ) - { - sie_write(SIE_CMDCODE_ENDPOINT_SET_STATUS+0, 1, SIE_SET_ENDPOINT_STALLED_MASK | SIE_SET_ENDPOINT_CONDITION_STALLED_MASK); - }else - { - uint8_t ep_id = ep_addr2idx( ep_addr ); - sie_write(SIE_CMDCODE_ENDPOINT_SET_STATUS+ep_id, 1, SIE_SET_ENDPOINT_STALLED_MASK); - } -} - -void dcd_edpt_clear_stall(uint8_t rhport, uint8_t ep_addr) -{ - (void) rhport; - uint8_t ep_id = ep_addr2idx(ep_addr); - - sie_write(SIE_CMDCODE_ENDPOINT_SET_STATUS+ep_id, 1, 0); -} - -static bool control_xact(uint8_t rhport, uint8_t dir, uint8_t * buffer, uint8_t len) -{ - (void) rhport; - - if ( dir ) - { - _dcd.control.in_bytes = len; - control_ep_write(buffer, len); - }else - { - if ( _dcd.control.out_received ) - { - // Already received the DATA OUT packet - _dcd.control.out_received = false; - _dcd.control.out_buffer = NULL; - _dcd.control.out_bytes = 0; - - uint8_t received = control_ep_read(buffer, len); - dcd_event_xfer_complete(0, 0, received, XFER_RESULT_SUCCESS, true); - }else - { - _dcd.control.out_buffer = buffer; - _dcd.control.out_bytes = len; - } - } - - return true; -} - -bool dcd_edpt_xfer (uint8_t rhport, uint8_t ep_addr, uint8_t* buffer, uint16_t total_bytes) -{ - // Control transfer is not DMA support, and must be done in slave mode - if ( tu_edpt_number(ep_addr) == 0 ) - { - return control_xact(rhport, tu_edpt_dir(ep_addr), buffer, (uint8_t) total_bytes); - } - else - { - uint8_t ep_id = ep_addr2idx(ep_addr); - dma_desc_t* dd = &_dcd.dd[ep_id]; - - // Prepare DMA descriptor - // Isochronous & max packet size must be preserved, Other fields of dd should be clear - uint16_t const ep_size = dd->max_packet_size; - uint8_t is_iso = dd->isochronous; - - tu_memclr(dd, sizeof(dma_desc_t)); - dd->isochronous = is_iso; - dd->max_packet_size = ep_size; - dd->buffer = (uint32_t) buffer; - dd->buflen = total_bytes; - - _dcd.udca[ep_id] = dd; - - if ( ep_id % 2 ) - { - // Clear EP interrupt before Enable DMA - LPC_USB->EpIntEn &= ~TU_BIT(ep_id); - LPC_USB->EpDMAEn = TU_BIT(ep_id); - - // endpoint IN need to actively raise DMA request - LPC_USB->DMARSet = TU_BIT(ep_id); - }else - { - // Enable DMA - LPC_USB->EpDMAEn = TU_BIT(ep_id); - } - - return true; - } -} - -//--------------------------------------------------------------------+ -// ISR -//--------------------------------------------------------------------+ - -// handle control xfer (slave mode) -static void control_xfer_isr(uint8_t rhport, uint32_t ep_int_status) -{ - // Control out complete - if ( ep_int_status & TU_BIT(0) ) - { - bool is_setup = sie_read(SIE_CMDCODE_ENDPOINT_SELECT+0) & SIE_SELECT_ENDPOINT_SETUP_RECEIVED_MASK; - - LPC_USB->EpIntClr = TU_BIT(0); - - if (is_setup) - { - uint8_t setup_packet[8]; - control_ep_read(setup_packet, 8); // TODO read before clear setup above - - dcd_event_setup_received(rhport, setup_packet, true); - } - else if ( _dcd.control.out_buffer ) - { - // software queued transfer previously - uint8_t received = control_ep_read(_dcd.control.out_buffer, _dcd.control.out_bytes); - - _dcd.control.out_buffer = NULL; - _dcd.control.out_bytes = 0; - - dcd_event_xfer_complete(rhport, 0, received, XFER_RESULT_SUCCESS, true); - }else - { - // hardware auto ack packet -> mark as received - _dcd.control.out_received = true; - } - } - - // Control In complete - if ( ep_int_status & TU_BIT(1) ) - { - LPC_USB->EpIntClr = TU_BIT(1); - dcd_event_xfer_complete(rhport, TUSB_DIR_IN_MASK, _dcd.control.in_bytes, XFER_RESULT_SUCCESS, true); - } -} - -// handle bus event signal -static void bus_event_isr(uint8_t rhport) -{ - uint8_t const dev_status = sie_read(SIE_CMDCODE_DEVICE_STATUS); - if (dev_status & SIE_DEV_STATUS_RESET_MASK) - { - bus_reset(); - dcd_event_bus_signal(rhport, DCD_EVENT_BUS_RESET, true); - } - - if (dev_status & SIE_DEV_STATUS_CONNECT_CHANGE_MASK) - { - // device is disconnected, require using VBUS (P1_30) - dcd_event_bus_signal(rhport, DCD_EVENT_UNPLUGGED, true); - } - - if (dev_status & SIE_DEV_STATUS_SUSPEND_CHANGE_MASK) - { - if (dev_status & SIE_DEV_STATUS_SUSPEND_MASK) - { - dcd_event_bus_signal(rhport, DCD_EVENT_SUSPEND, true); - } - else - { - dcd_event_bus_signal(rhport, DCD_EVENT_RESUME, true); - } - } -} - -// Helper to complete a DMA descriptor for non-control transfer -static void dd_complete_isr(uint8_t rhport, uint8_t ep_id) -{ - dma_desc_t* const dd = &_dcd.dd[ep_id]; - uint8_t result = (dd->status == DD_STATUS_NORMAL || dd->status == DD_STATUS_DATA_UNDERUN) ? XFER_RESULT_SUCCESS : XFER_RESULT_FAILED; - uint8_t const ep_addr = (ep_id / 2) | ((ep_id & 0x01) ? TUSB_DIR_IN_MASK : 0); - - dcd_event_xfer_complete(rhport, ep_addr, dd->present_count, result, true); -} - -// main USB IRQ handler -void dcd_int_handler(uint8_t rhport) -{ - uint32_t const dev_int_status = LPC_USB->DevIntSt & LPC_USB->DevIntEn; - LPC_USB->DevIntClr = dev_int_status;// Acknowledge handled interrupt - - // Bus event - if (dev_int_status & DEV_INT_DEVICE_STATUS_MASK) - { - bus_event_isr(rhport); - } - - // Endpoint interrupt - uint32_t const ep_int_status = LPC_USB->EpIntSt & LPC_USB->EpIntEn; - - // Control Endpoint are fast - if (dev_int_status & DEV_INT_ENDPOINT_FAST_MASK) - { - // Note clear USBEpIntClr will also clear the setup received bit --> clear after handle setup packet - // Only clear USBEpIntClr 1 endpoint each, and should wait for CDFULL bit set - control_xfer_isr(rhport, ep_int_status); - } - - // non-control IN are slow - if (dev_int_status & DEV_INT_ENDPOINT_SLOW_MASK) - { - for ( uint8_t ep_id = 3; ep_id < DCD_ENDPOINT_MAX; ep_id += 2 ) - { - if ( tu_bit_test(ep_int_status, ep_id) ) - { - LPC_USB->EpIntClr = TU_BIT(ep_id); - - // Clear Ep interrupt for next DMA - LPC_USB->EpIntEn &= ~TU_BIT(ep_id); - - dd_complete_isr(rhport, ep_id); - } - } - } - - // DMA transfer complete (RAM <-> EP) for Non-Control - // OUT: USB transfer is fully complete - // IN : UBS transfer is still on-going -> enable EpIntEn to know when it is complete - uint32_t const dma_int_status = LPC_USB->DMAIntSt & LPC_USB->DMAIntEn; - if (dma_int_status & DMA_INT_END_OF_XFER_MASK) - { - uint32_t const eot = LPC_USB->EoTIntSt; - LPC_USB->EoTIntClr = eot; // acknowledge interrupt source - - for ( uint8_t ep_id = 2; ep_id < DCD_ENDPOINT_MAX; ep_id++ ) - { - if ( tu_bit_test(eot, ep_id) ) - { - if ( ep_id & 0x01 ) - { - // IN enable EpInt for end of usb transfer - LPC_USB->EpIntEn |= TU_BIT(ep_id); - }else - { - // OUT - dd_complete_isr(rhport, ep_id); - } - } - } - } - - // Errors - if ( (dev_int_status & DEV_INT_ERROR_MASK) || (dma_int_status & DMA_INT_ERROR_MASK) ) - { - uint32_t error_status = sie_read(SIE_CMDCODE_READ_ERROR_STATUS); - (void) error_status; - TU_BREAKPOINT(); - } -} - -#endif diff --git a/cores/nRF5/TinyUSB/Adafruit_TinyUSB_ArduinoCore/tinyusb/src/portable/nxp/lpc17_40/dcd_lpc17_40.h b/cores/nRF5/TinyUSB/Adafruit_TinyUSB_ArduinoCore/tinyusb/src/portable/nxp/lpc17_40/dcd_lpc17_40.h deleted file mode 100644 index 1e7c0fb..0000000 --- a/cores/nRF5/TinyUSB/Adafruit_TinyUSB_ArduinoCore/tinyusb/src/portable/nxp/lpc17_40/dcd_lpc17_40.h +++ /dev/null @@ -1,152 +0,0 @@ -/* - * The MIT License (MIT) - * - * Copyright (c) 2019 Ha Thach (tinyusb.org) - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - * - * This file is part of the TinyUSB stack. - */ - -#ifndef _TUSB_DCD_LPC175X_6X_H_ -#define _TUSB_DCD_LPC175X_6X_H_ - -#include "common/tusb_common.h" - -#ifdef __cplusplus - extern "C" { -#endif - -//--------------------------------------------------------------------+ -// Register Interface -//--------------------------------------------------------------------+ - -//------------- USB Interrupt USBIntSt -------------// -//enum { -// DCD_USB_REQ_LOW_PRIO_MASK = TU_BIT(0), -// DCD_USB_REQ_HIGH_PRIO_MASK = TU_BIT(1), -// DCD_USB_REQ_DMA_MASK = TU_BIT(2), -// DCD_USB_REQ_NEED_CLOCK_MASK = TU_BIT(8), -// DCD_USB_REQ_ENABLE_MASK = TU_BIT(31) -//}; - -//------------- Device Interrupt USBDevInt -------------// -enum { - DEV_INT_FRAME_MASK = TU_BIT(0), - DEV_INT_ENDPOINT_FAST_MASK = TU_BIT(1), - DEV_INT_ENDPOINT_SLOW_MASK = TU_BIT(2), - DEV_INT_DEVICE_STATUS_MASK = TU_BIT(3), - DEV_INT_COMMAND_CODE_EMPTY_MASK = TU_BIT(4), - DEV_INT_COMMAND_DATA_FULL_MASK = TU_BIT(5), - DEV_INT_RX_ENDPOINT_PACKET_MASK = TU_BIT(6), - DEV_INT_TX_ENDPOINT_PACKET_MASK = TU_BIT(7), - DEV_INT_ENDPOINT_REALIZED_MASK = TU_BIT(8), - DEV_INT_ERROR_MASK = TU_BIT(9) -}; - -//------------- DMA Interrupt USBDMAInt-------------// -enum { - DMA_INT_END_OF_XFER_MASK = TU_BIT(0), - DMA_INT_NEW_DD_REQUEST_MASK = TU_BIT(1), - DMA_INT_ERROR_MASK = TU_BIT(2) -}; - -//------------- USBCtrl -------------// -enum { - USBCTRL_READ_ENABLE_MASK = TU_BIT(0), - USBCTRL_WRITE_ENABLE_MASK = TU_BIT(1), -}; - -//------------- USBRxPLen -------------// -enum { - USBRXPLEN_PACKET_LENGTH_MASK = (TU_BIT(10)-1), - USBRXPLEN_DATA_VALID_MASK = TU_BIT(10), - USBRXPLEN_PACKET_READY_MASK = TU_BIT(11), -}; - -//------------- SIE Command Code -------------// -typedef enum -{ - SIE_CMDPHASE_WRITE = 1, - SIE_CMDPHASE_READ = 2, - SIE_CMDPHASE_COMMAND = 5 -} sie_cmdphase_t; - -enum { - // device commands - SIE_CMDCODE_SET_ADDRESS = 0xd0, - SIE_CMDCODE_CONFIGURE_DEVICE = 0xd8, - SIE_CMDCODE_SET_MODE = 0xf3, - SIE_CMDCODE_READ_FRAME_NUMBER = 0xf5, - SIE_CMDCODE_READ_TEST_REGISTER = 0xfd, - SIE_CMDCODE_DEVICE_STATUS = 0xfe, - SIE_CMDCODE_GET_ERROR = 0xff, - SIE_CMDCODE_READ_ERROR_STATUS = 0xfb, - - // endpoint commands - SIE_CMDCODE_ENDPOINT_SELECT = 0x00, // + endpoint index - SIE_CMDCODE_ENDPOINT_SELECT_CLEAR_INTERRUPT = 0x40, // + endpoint index, should use USBEpIntClr instead - SIE_CMDCODE_ENDPOINT_SET_STATUS = 0x40, // + endpoint index - SIE_CMDCODE_BUFFER_CLEAR = 0xf2, - SIE_CMDCODE_BUFFER_VALIDATE = 0xfa -}; - -//------------- SIE Device Status (get/set from SIE_CMDCODE_DEVICE_STATUS) -------------// -enum { - SIE_DEV_STATUS_CONNECT_STATUS_MASK = TU_BIT(0), - SIE_DEV_STATUS_CONNECT_CHANGE_MASK = TU_BIT(1), - SIE_DEV_STATUS_SUSPEND_MASK = TU_BIT(2), - SIE_DEV_STATUS_SUSPEND_CHANGE_MASK = TU_BIT(3), - SIE_DEV_STATUS_RESET_MASK = TU_BIT(4) -}; - -//------------- SIE Select Endpoint Command -------------// -enum { - SIE_SELECT_ENDPOINT_FULL_EMPTY_MASK = TU_BIT(0), // 0: empty, 1 full. IN endpoint checks empty, OUT endpoint check full - SIE_SELECT_ENDPOINT_STALL_MASK = TU_BIT(1), - SIE_SELECT_ENDPOINT_SETUP_RECEIVED_MASK = TU_BIT(2), // clear by SIE_CMDCODE_ENDPOINT_SELECT_CLEAR_INTERRUPT - SIE_SELECT_ENDPOINT_PACKET_OVERWRITTEN_MASK = TU_BIT(3), // previous packet is overwritten by a SETUP packet - SIE_SELECT_ENDPOINT_NAK_MASK = TU_BIT(4), // last packet response is NAK (auto clear by an ACK) - SIE_SELECT_ENDPOINT_BUFFER1_FULL_MASK = TU_BIT(5), - SIE_SELECT_ENDPOINT_BUFFER2_FULL_MASK = TU_BIT(6) -}; - -typedef enum -{ - SIE_SET_ENDPOINT_STALLED_MASK = TU_BIT(0), - SIE_SET_ENDPOINT_DISABLED_MASK = TU_BIT(5), - SIE_SET_ENDPOINT_RATE_FEEDBACK_MASK = TU_BIT(6), - SIE_SET_ENDPOINT_CONDITION_STALLED_MASK = TU_BIT(7), -}sie_endpoint_set_status_mask_t; - -//------------- DMA Descriptor Status -------------// -enum { - DD_STATUS_NOT_SERVICED = 0, - DD_STATUS_BEING_SERVICED, - DD_STATUS_NORMAL, - DD_STATUS_DATA_UNDERUN, // short packet - DD_STATUS_DATA_OVERRUN, - DD_STATUS_SYSTEM_ERROR -}; - -#ifdef __cplusplus - } -#endif - -#endif /* _TUSB_DCD_LPC175X_6X_H_ */ diff --git a/cores/nRF5/TinyUSB/Adafruit_TinyUSB_ArduinoCore/tinyusb/src/portable/nxp/lpc17_40/hcd_lpc17_40.c b/cores/nRF5/TinyUSB/Adafruit_TinyUSB_ArduinoCore/tinyusb/src/portable/nxp/lpc17_40/hcd_lpc17_40.c deleted file mode 100644 index 537b3da..0000000 --- a/cores/nRF5/TinyUSB/Adafruit_TinyUSB_ArduinoCore/tinyusb/src/portable/nxp/lpc17_40/hcd_lpc17_40.c +++ /dev/null @@ -1,46 +0,0 @@ -/* - * 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. - */ - -#include "tusb_option.h" - -#if (CFG_TUSB_MCU == OPT_MCU_LPC175X_6X || CFG_TUSB_MCU == OPT_MCU_LPC40XX) - -#include "chip.h" - -void hcd_int_enable(uint8_t rhport) -{ - (void) rhport; - NVIC_EnableIRQ(USB_IRQn); -} - -void hcd_int_disable(uint8_t rhport) -{ - (void) rhport; - NVIC_DisableIRQ(USB_IRQn); -} - -#endif - diff --git a/cores/nRF5/TinyUSB/Adafruit_TinyUSB_ArduinoCore/tinyusb/src/portable/nxp/lpc_ip3511/dcd_lpc_ip3511.c b/cores/nRF5/TinyUSB/Adafruit_TinyUSB_ArduinoCore/tinyusb/src/portable/nxp/lpc_ip3511/dcd_lpc_ip3511.c deleted file mode 100644 index 3d1420c..0000000 --- a/cores/nRF5/TinyUSB/Adafruit_TinyUSB_ArduinoCore/tinyusb/src/portable/nxp/lpc_ip3511/dcd_lpc_ip3511.c +++ /dev/null @@ -1,415 +0,0 @@ -/* - * 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. - */ - -#include "tusb_option.h" - -/* Since 2012 starting with LPC11uxx, NXP start to use common USB Device Controller with code name LPC IP3511 - * for almost their new MCUs. Currently supported and tested families are - * - LPC11U68, LPC11U37 - * - LPC1347 - * - LPC51U68 - * - LPC54114 - * - LPC55s69 - * - * For similar controller of other families, this file may require some minimal changes to work with. - * Previous MCUs such as LPC17xx, LPC40xx, LPC18xx, LPC43xx have their own driver implementation. - */ - -#if TUSB_OPT_DEVICE_ENABLED && ( CFG_TUSB_MCU == OPT_MCU_LPC11UXX || \ - CFG_TUSB_MCU == OPT_MCU_LPC13XX || \ - CFG_TUSB_MCU == OPT_MCU_LPC15XX || \ - CFG_TUSB_MCU == OPT_MCU_LPC51UXX || \ - CFG_TUSB_MCU == OPT_MCU_LPC54XXX || \ - CFG_TUSB_MCU == OPT_MCU_LPC55XX) - -#if CFG_TUSB_MCU == OPT_MCU_LPC11UXX || CFG_TUSB_MCU == OPT_MCU_LPC13XX || CFG_TUSB_MCU == OPT_MCU_LPC15XX - // LPC 11Uxx, 13xx, 15xx use lpcopen - #include "chip.h" - #define DCD_REGS LPC_USB - -#elif CFG_TUSB_MCU == OPT_MCU_LPC51UXX || CFG_TUSB_MCU == OPT_MCU_LPC54XXX || \ - CFG_TUSB_MCU == OPT_MCU_LPC55XX // TODO 55xx has dual usb controllers - #include "fsl_device_registers.h" - #define DCD_REGS USB0 - -#endif - -#include "device/dcd.h" - -//--------------------------------------------------------------------+ -// MACRO CONSTANT TYPEDEF -//--------------------------------------------------------------------+ - -// Number of endpoints -// - 11 13 15 51 54 has 5x2 endpoints -// - 18/43 usb0 & 55s usb1 (HS) has 6x2 endpoints -// - 18/43 usb1 & 55s usb0 (FS) has 4x2 endpoints -#define EP_COUNT 10 - -// only SRAM1 & USB RAM can be used for transfer. -// Used to set DATABUFSTART which is 22-bit aligned -// 2000 0000 to 203F FFFF -#define SRAM_REGION 0x20000000 - -/* Although device controller are the same. Somehow only LPC134x can execute - * DMA with 1023 bytes for Bulk/Control. Others (11u, 51u, 54xxx) can only work - * with max 64 bytes - */ -enum { - #if CFG_TUSB_MCU == OPT_MCU_LPC13XX - DMA_NBYTES_MAX = 1023 - #else - DMA_NBYTES_MAX = 64 - #endif -}; - -enum { - INT_SOF_MASK = TU_BIT(30), - INT_DEVICE_STATUS_MASK = TU_BIT(31) -}; - -enum { - CMDSTAT_DEVICE_ADDR_MASK = TU_BIT(7 )-1, - CMDSTAT_DEVICE_ENABLE_MASK = TU_BIT(7 ), - CMDSTAT_SETUP_RECEIVED_MASK = TU_BIT(8 ), - CMDSTAT_DEVICE_CONNECT_MASK = TU_BIT(16), ///< reflect the soft-connect only, does not reflect the actual attached state - CMDSTAT_DEVICE_SUSPEND_MASK = TU_BIT(17), - CMDSTAT_CONNECT_CHANGE_MASK = TU_BIT(24), - CMDSTAT_SUSPEND_CHANGE_MASK = TU_BIT(25), - CMDSTAT_RESET_CHANGE_MASK = TU_BIT(26), - CMDSTAT_VBUS_DEBOUNCED_MASK = TU_BIT(28), -}; - -typedef struct TU_ATTR_PACKED -{ - // Bits 21:6 (aligned 64) used in conjunction with bit 31:22 of DATABUFSTART - volatile uint16_t buffer_offset; - - volatile uint16_t nbytes : 10 ; - uint16_t is_iso : 1 ; - uint16_t toggle_mode : 1 ; - uint16_t toggle_reset : 1 ; - uint16_t stall : 1 ; - uint16_t disable : 1 ; - volatile uint16_t active : 1 ; -}ep_cmd_sts_t; - -TU_VERIFY_STATIC( sizeof(ep_cmd_sts_t) == 4, "size is not correct" ); - -typedef struct -{ - uint16_t total_bytes; - uint16_t xferred_bytes; - - uint16_t nbytes; -}xfer_dma_t; - -// NOTE data will be transferred as soon as dcd get request by dcd_pipe(_queue)_xfer using double buffering. -// current_td is used to keep track of number of remaining & xferred bytes of the current request. -typedef struct -{ - // 256 byte aligned, 2 for double buffer (not used) - // Each cmd_sts can only transfer up to DMA_NBYTES_MAX bytes each - ep_cmd_sts_t ep[EP_COUNT][2]; - - xfer_dma_t dma[EP_COUNT]; - - TU_ATTR_ALIGNED(64) uint8_t setup_packet[8]; -}dcd_data_t; - -//--------------------------------------------------------------------+ -// INTERNAL OBJECT & FUNCTION DECLARATION -//--------------------------------------------------------------------+ - -// EP list must be 256-byte aligned -CFG_TUSB_MEM_SECTION TU_ATTR_ALIGNED(256) static dcd_data_t _dcd; - -static inline uint16_t get_buf_offset(void const * buffer) -{ - uint32_t addr = (uint32_t) buffer; - TU_ASSERT( (addr & 0x3f) == 0, 0 ); - return ( (addr >> 6) & 0xFFFFUL ) ; -} - -static inline uint8_t ep_addr2id(uint8_t endpoint_addr) -{ - return 2*(endpoint_addr & 0x0F) + ((endpoint_addr & TUSB_DIR_IN_MASK) ? 1 : 0); -} - -//--------------------------------------------------------------------+ -// CONTROLLER API -//--------------------------------------------------------------------+ -void dcd_init(uint8_t rhport) -{ - (void) rhport; - - DCD_REGS->EPLISTSTART = (uint32_t) _dcd.ep; - DCD_REGS->DATABUFSTART = SRAM_REGION; // 22-bit alignment - - DCD_REGS->INTSTAT = DCD_REGS->INTSTAT; // clear all pending interrupt - DCD_REGS->INTEN = INT_DEVICE_STATUS_MASK; - DCD_REGS->DEVCMDSTAT |= CMDSTAT_DEVICE_ENABLE_MASK | CMDSTAT_DEVICE_CONNECT_MASK | - CMDSTAT_RESET_CHANGE_MASK | CMDSTAT_CONNECT_CHANGE_MASK | CMDSTAT_SUSPEND_CHANGE_MASK; - - NVIC_ClearPendingIRQ(USB0_IRQn); -} - -void dcd_int_enable(uint8_t rhport) -{ - (void) rhport; - NVIC_EnableIRQ(USB0_IRQn); -} - -void dcd_int_disable(uint8_t rhport) -{ - (void) rhport; - NVIC_DisableIRQ(USB0_IRQn); -} - -void dcd_set_address(uint8_t rhport, uint8_t dev_addr) -{ - // Response with status first before changing device address - dcd_edpt_xfer(rhport, tu_edpt_addr(0, TUSB_DIR_IN), NULL, 0); - - DCD_REGS->DEVCMDSTAT &= ~CMDSTAT_DEVICE_ADDR_MASK; - DCD_REGS->DEVCMDSTAT |= dev_addr; -} - -void dcd_remote_wakeup(uint8_t rhport) -{ - (void) rhport; -} - -void dcd_connect(uint8_t rhport) -{ - (void) rhport; - DCD_REGS->DEVCMDSTAT |= CMDSTAT_DEVICE_CONNECT_MASK; -} - -void dcd_disconnect(uint8_t rhport) -{ - (void) rhport; - DCD_REGS->DEVCMDSTAT &= ~CMDSTAT_DEVICE_CONNECT_MASK; -} - -//--------------------------------------------------------------------+ -// DCD Endpoint Port -//--------------------------------------------------------------------+ -void dcd_edpt_stall(uint8_t rhport, uint8_t ep_addr) -{ - (void) rhport; - - // TODO cannot able to STALL Control OUT endpoint !!!!! FIXME try some walk-around - uint8_t const ep_id = ep_addr2id(ep_addr); - _dcd.ep[ep_id][0].stall = 1; -} - -void dcd_edpt_clear_stall(uint8_t rhport, uint8_t ep_addr) -{ - (void) rhport; - - uint8_t const ep_id = ep_addr2id(ep_addr); - - _dcd.ep[ep_id][0].stall = 0; - _dcd.ep[ep_id][0].toggle_reset = 1; - _dcd.ep[ep_id][0].toggle_mode = 0; -} - -bool dcd_edpt_open(uint8_t rhport, tusb_desc_endpoint_t const * p_endpoint_desc) -{ - (void) rhport; - - // TODO not support ISO yet - if (p_endpoint_desc->bmAttributes.xfer == TUSB_XFER_ISOCHRONOUS) return false; - - //------------- Prepare Queue Head -------------// - uint8_t ep_id = ep_addr2id(p_endpoint_desc->bEndpointAddress); - - // Check if endpoint is available - TU_ASSERT( _dcd.ep[ep_id][0].disable && _dcd.ep[ep_id][1].disable ); - - tu_memclr(_dcd.ep[ep_id], 2*sizeof(ep_cmd_sts_t)); - _dcd.ep[ep_id][0].is_iso = (p_endpoint_desc->bmAttributes.xfer == TUSB_XFER_ISOCHRONOUS); - - // Enable EP interrupt - DCD_REGS->INTEN |= TU_BIT(ep_id); - - return true; -} - -static void prepare_ep_xfer(uint8_t ep_id, uint16_t buf_offset, uint16_t total_bytes) -{ - uint16_t const nbytes = tu_min16(total_bytes, DMA_NBYTES_MAX); - - _dcd.dma[ep_id].nbytes = nbytes; - - _dcd.ep[ep_id][0].buffer_offset = buf_offset; - _dcd.ep[ep_id][0].nbytes = nbytes; - _dcd.ep[ep_id][0].active = 1; -} - -bool dcd_edpt_xfer(uint8_t rhport, uint8_t ep_addr, uint8_t* buffer, uint16_t total_bytes) -{ - (void) rhport; - - uint8_t const ep_id = ep_addr2id(ep_addr); - - tu_varclr(&_dcd.dma[ep_id]); - _dcd.dma[ep_id].total_bytes = total_bytes; - - prepare_ep_xfer(ep_id, get_buf_offset(buffer), total_bytes); - - return true; -} - -//--------------------------------------------------------------------+ -// IRQ -//--------------------------------------------------------------------+ -static void bus_reset(void) -{ - tu_memclr(&_dcd, sizeof(dcd_data_t)); - - // disable all non-control endpoints on bus reset - for(uint8_t ep_id = 2; ep_id < EP_COUNT; ep_id++) - { - _dcd.ep[ep_id][0].disable = _dcd.ep[ep_id][1].disable = 1; - } - - _dcd.ep[0][1].buffer_offset = get_buf_offset(_dcd.setup_packet); - - DCD_REGS->EPINUSE = 0; - DCD_REGS->EPBUFCFG = 0; - DCD_REGS->EPSKIP = 0xFFFFFFFF; - - DCD_REGS->INTSTAT = DCD_REGS->INTSTAT; // clear all pending interrupt - DCD_REGS->DEVCMDSTAT |= CMDSTAT_SETUP_RECEIVED_MASK; // clear setup received interrupt - DCD_REGS->INTEN = INT_DEVICE_STATUS_MASK | TU_BIT(0) | TU_BIT(1); // enable device status & control endpoints -} - -static void process_xfer_isr(uint32_t int_status) -{ - for(uint8_t ep_id = 0; ep_id < EP_COUNT; ep_id++ ) - { - if ( tu_bit_test(int_status, ep_id) ) - { - ep_cmd_sts_t * ep_cs = &_dcd.ep[ep_id][0]; - xfer_dma_t* xfer_dma = &_dcd.dma[ep_id]; - - xfer_dma->xferred_bytes += xfer_dma->nbytes - ep_cs->nbytes; - - if ( (ep_cs->nbytes == 0) && (xfer_dma->total_bytes > xfer_dma->xferred_bytes) ) - { - // There is more data to transfer - // buff_offset has been already increased by hw to correct value for next transfer - prepare_ep_xfer(ep_id, ep_cs->buffer_offset, xfer_dma->total_bytes - xfer_dma->xferred_bytes); - } - else - { - xfer_dma->total_bytes = xfer_dma->xferred_bytes; - - uint8_t const ep_addr = (ep_id / 2) | ((ep_id & 0x01) ? TUSB_DIR_IN_MASK : 0); - - // TODO no way determine if the transfer is failed or not - dcd_event_xfer_complete(0, ep_addr, xfer_dma->xferred_bytes, XFER_RESULT_SUCCESS, true); - } - } - } -} - -void dcd_int_handler(uint8_t rhport) -{ - (void) rhport; // TODO support multiple USB on supported mcu such as LPC55s69 - - uint32_t const cmd_stat = DCD_REGS->DEVCMDSTAT; - - uint32_t int_status = DCD_REGS->INTSTAT & DCD_REGS->INTEN; - DCD_REGS->INTSTAT = int_status; // Acknowledge handled interrupt - - if (int_status == 0) return; - - //------------- Device Status -------------// - if ( int_status & INT_DEVICE_STATUS_MASK ) - { - DCD_REGS->DEVCMDSTAT |= CMDSTAT_RESET_CHANGE_MASK | CMDSTAT_CONNECT_CHANGE_MASK | CMDSTAT_SUSPEND_CHANGE_MASK; - if ( cmd_stat & CMDSTAT_RESET_CHANGE_MASK) // bus reset - { - bus_reset(); - dcd_event_bus_signal(0, DCD_EVENT_BUS_RESET, true); - } - - if (cmd_stat & CMDSTAT_CONNECT_CHANGE_MASK) - { - // device disconnect - if (cmd_stat & CMDSTAT_DEVICE_ADDR_MASK) - { - // debouncing as this can be set when device is powering - dcd_event_bus_signal(0, DCD_EVENT_UNPLUGGED, true); - } - } - - // TODO support suspend & resume - if (cmd_stat & CMDSTAT_SUSPEND_CHANGE_MASK) - { - if (cmd_stat & CMDSTAT_DEVICE_SUSPEND_MASK) - { // suspend signal, bus idle for more than 3ms - // Note: Host may delay more than 3 ms before and/or after bus reset before doing enumeration. - if (cmd_stat & CMDSTAT_DEVICE_ADDR_MASK) - { - dcd_event_bus_signal(0, DCD_EVENT_SUSPEND, true); - } - } - } -// else -// { // resume signal -// dcd_event_bus_signal(0, DCD_EVENT_RESUME, true); -// } -// } - } - - // Setup Receive - if ( tu_bit_test(int_status, 0) && (cmd_stat & CMDSTAT_SETUP_RECEIVED_MASK) ) - { - // Follow UM flowchart to clear Active & Stall on both Control IN/OUT endpoints - _dcd.ep[0][0].active = _dcd.ep[1][0].active = 0; - _dcd.ep[0][0].stall = _dcd.ep[1][0].stall = 0; - - DCD_REGS->DEVCMDSTAT |= CMDSTAT_SETUP_RECEIVED_MASK; - - dcd_event_setup_received(0, _dcd.setup_packet, true); - - // keep waiting for next setup - _dcd.ep[0][1].buffer_offset = get_buf_offset(_dcd.setup_packet); - - // clear bit0 - int_status = tu_bit_clear(int_status, 0); - } - - // Endpoint transfer complete interrupt - process_xfer_isr(int_status); -} - -#endif - diff --git a/cores/nRF5/TinyUSB/Adafruit_TinyUSB_ArduinoCore/tinyusb/src/portable/nxp/transdimension/dcd_transdimension.c b/cores/nRF5/TinyUSB/Adafruit_TinyUSB_ArduinoCore/tinyusb/src/portable/nxp/transdimension/dcd_transdimension.c deleted file mode 100644 index 755e763..0000000 --- a/cores/nRF5/TinyUSB/Adafruit_TinyUSB_ArduinoCore/tinyusb/src/portable/nxp/transdimension/dcd_transdimension.c +++ /dev/null @@ -1,590 +0,0 @@ -/* - * 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. - */ - -#include "tusb_option.h" - -#if TUSB_OPT_DEVICE_ENABLED && (CFG_TUSB_MCU == OPT_MCU_LPC18XX || \ - CFG_TUSB_MCU == OPT_MCU_LPC43XX || \ - CFG_TUSB_MCU == OPT_MCU_MIMXRT10XX) - -//--------------------------------------------------------------------+ -// INCLUDE -//--------------------------------------------------------------------+ -#include "common/tusb_common.h" -#include "device/dcd.h" - -#if CFG_TUSB_MCU == OPT_MCU_MIMXRT10XX - #include "fsl_device_registers.h" -#else - // LPCOpen for 18xx & 43xx - #include "chip.h" -#endif - -#if defined(__CORTEX_M) && __CORTEX_M == 7 && __DCACHE_PRESENT == 1 - #define CleanInvalidateDCache_by_Addr SCB_CleanInvalidateDCache_by_Addr -#else - #define CleanInvalidateDCache_by_Addr(_addr, _dsize) -#endif - -//--------------------------------------------------------------------+ -// MACRO CONSTANT TYPEDEF -//--------------------------------------------------------------------+ - -// ENDPTCTRL -enum { - ENDPTCTRL_STALL = TU_BIT(0), - ENDPTCTRL_TOGGLE_INHIBIT = TU_BIT(5), ///< used for test only - ENDPTCTRL_TOGGLE_RESET = TU_BIT(6), - ENDPTCTRL_ENABLE = TU_BIT(7) -}; - -// USBCMD -enum { - USBCMD_RUN_STOP = TU_BIT(0), - USBCMD_RESET = TU_BIT(1), - USBCMD_SETUP_TRIPWIRE = TU_BIT(13), - USBCMD_ADD_QTD_TRIPWIRE = TU_BIT(14) ///< This bit is used as a semaphore to ensure the to proper addition of a new dTD to an active (primed) endpoint’s linked list. This bit is set and cleared by software during the process of adding a new dTD -}; -// Interrupt Threshold bit 23:16 - -// USBSTS, USBINTR -enum { - INTR_USB = TU_BIT(0), - INTR_ERROR = TU_BIT(1), - INTR_PORT_CHANGE = TU_BIT(2), - INTR_RESET = TU_BIT(6), - INTR_SOF = TU_BIT(7), - INTR_SUSPEND = TU_BIT(8), - INTR_NAK = TU_BIT(16) -}; - -// PORTSC1 -#define PORTSC1_PORT_SPEED_POS 26 - -enum { - PORTSC1_CURRENT_CONNECT_STATUS = TU_BIT(0), - PORTSC1_FORCE_PORT_RESUME = TU_BIT(6), - PORTSC1_SUSPEND = TU_BIT(7), - PORTSC1_FORCE_FULL_SPEED = TU_BIT(24), - PORTSC1_PORT_SPEED = TU_BIT(26) | TU_BIT(27) -}; - -// OTGSC -enum { - OTGSC_VBUS_DISCHARGE = TU_BIT(0), - OTGSC_VBUS_CHARGE = TU_BIT(1), -// OTGSC_HWASSIST_AUTORESET = TU_BIT(2), - OTGSC_OTG_TERMINATION = TU_BIT(3), ///< Must set to 1 when OTG go to device mode - OTGSC_DATA_PULSING = TU_BIT(4), - OTGSC_ID_PULLUP = TU_BIT(5), -// OTGSC_HWASSIT_DATA_PULSE = TU_BIT(6), -// OTGSC_HWASSIT_BDIS_ACONN = TU_BIT(7), - OTGSC_ID = TU_BIT(8), ///< 0 = A device, 1 = B Device - OTGSC_A_VBUS_VALID = TU_BIT(9), - OTGSC_A_SESSION_VALID = TU_BIT(10), - OTGSC_B_SESSION_VALID = TU_BIT(11), - OTGSC_B_SESSION_END = TU_BIT(12), - OTGSC_1MS_TOGGLE = TU_BIT(13), - OTGSC_DATA_BUS_PULSING_STATUS = TU_BIT(14), -}; - -// USBMode -enum { - USBMODE_CM_DEVICE = 2, - USBMODE_CM_HOST = 3, - - USBMODE_SLOM = TU_BIT(3), - USBMODE_SDIS = TU_BIT(4), - - USBMODE_VBUS_POWER_SELCT = TU_BIT(5), // Enable for LPC18XX/43XX in host most only -}; - -// Device Registers -typedef struct -{ - //------------- ID + HW Parameter Registers-------------// - __I uint32_t TU_RESERVED[64]; ///< For iMX RT10xx, but not used by LPC18XX/LPC43XX - - //------------- Capability Registers-------------// - __I uint8_t CAPLENGTH; ///< Capability Registers Length - __I uint8_t TU_RESERVED[1]; - __I uint16_t HCIVERSION; ///< Host Controller Interface Version - - __I uint32_t HCSPARAMS; ///< Host Controller Structural Parameters - __I uint32_t HCCPARAMS; ///< Host Controller Capability Parameters - __I uint32_t TU_RESERVED[5]; - - __I uint16_t DCIVERSION; ///< Device Controller Interface Version - __I uint8_t TU_RESERVED[2]; - - __I uint32_t DCCPARAMS; ///< Device Controller Capability Parameters - __I uint32_t TU_RESERVED[6]; - - //------------- Operational Registers -------------// - __IO uint32_t USBCMD; ///< USB Command Register - __IO uint32_t USBSTS; ///< USB Status Register - __IO uint32_t USBINTR; ///< Interrupt Enable Register - __IO uint32_t FRINDEX; ///< USB Frame Index - __I uint32_t TU_RESERVED; - __IO uint32_t DEVICEADDR; ///< Device Address - __IO uint32_t ENDPTLISTADDR; ///< Endpoint List Address - __I uint32_t TU_RESERVED; - __IO uint32_t BURSTSIZE; ///< Programmable Burst Size - __IO uint32_t TXFILLTUNING; ///< TX FIFO Fill Tuning - uint32_t TU_RESERVED[4]; - __IO uint32_t ENDPTNAK; ///< Endpoint NAK - __IO uint32_t ENDPTNAKEN; ///< Endpoint NAK Enable - __I uint32_t TU_RESERVED; - __IO uint32_t PORTSC1; ///< Port Status & Control - __I uint32_t TU_RESERVED[7]; - __IO uint32_t OTGSC; ///< On-The-Go Status & control - __IO uint32_t USBMODE; ///< USB Device Mode - __IO uint32_t ENDPTSETUPSTAT; ///< Endpoint Setup Status - __IO uint32_t ENDPTPRIME; ///< Endpoint Prime - __IO uint32_t ENDPTFLUSH; ///< Endpoint Flush - __I uint32_t ENDPTSTAT; ///< Endpoint Status - __IO uint32_t ENDPTCOMPLETE; ///< Endpoint Complete - __IO uint32_t ENDPTCTRL[8]; ///< Endpoint Control 0 - 7 -} dcd_registers_t; - - -// Queue Transfer Descriptor -typedef struct -{ - // Word 0: Next QTD Pointer - uint32_t next; ///< Next link pointer This field contains the physical memory address of the next dTD to be processed - - // Word 1: qTQ Token - uint32_t : 3 ; - volatile uint32_t xact_err : 1 ; - uint32_t : 1 ; - volatile uint32_t buffer_err : 1 ; - volatile uint32_t halted : 1 ; - volatile uint32_t active : 1 ; - uint32_t : 2 ; - uint32_t iso_mult_override : 2 ; ///< This field can be used for transmit ISOs to override the MULT field in the dQH. This field must be zero for all packet types that are not transmit-ISO. - uint32_t : 3 ; - uint32_t int_on_complete : 1 ; - volatile uint32_t total_bytes : 15 ; - uint32_t : 0 ; - - // Word 2-6: Buffer Page Pointer List, Each element in the list is a 4K page aligned, physical memory address. The lower 12 bits in each pointer are reserved (except for the first one) as each memory pointer must reference the start of a 4K page - uint32_t buffer[5]; ///< buffer1 has frame_n for TODO Isochronous - - //------------- DCD Area -------------// - uint16_t expected_bytes; - uint8_t reserved[2]; -} dcd_qtd_t; - -TU_VERIFY_STATIC( sizeof(dcd_qtd_t) == 32, "size is not correct"); - -// Queue Head -typedef struct -{ - // Word 0: Capabilities and Characteristics - uint32_t : 15 ; ///< Number of packets executed per transaction descriptor 00 - Execute N transactions as demonstrated by the USB variable length protocol where N is computed using Max_packet_length and the Total_bytes field in the dTD. 01 - Execute one transaction 10 - Execute two transactions 11 - Execute three transactions Remark: Non-isochronous endpoints must set MULT = 00. Remark: Isochronous endpoints must set MULT = 01, 10, or 11 as needed. - uint32_t int_on_setup : 1 ; ///< Interrupt on setup This bit is used on control type endpoints to indicate if USBINT is set in response to a setup being received. - uint32_t max_package_size : 11 ; ///< This directly corresponds to the maximum packet size of the associated endpoint (wMaxPacketSize) - uint32_t : 2 ; - uint32_t zero_length_termination : 1 ; ///< This bit is used for non-isochronous endpoints to indicate when a zero-length packet is received to terminate transfers in case the total transfer length is “multiple”. 0 - Enable zero-length packet to terminate transfers equal to a multiple of Max_packet_length (default). 1 - Disable zero-length packet on transfers that are equal in length to a multiple Max_packet_length. - uint32_t iso_mult : 2 ; ///< - uint32_t : 0 ; - - // Word 1: Current qTD Pointer - volatile uint32_t qtd_addr; - - // Word 2-9: Transfer Overlay - volatile dcd_qtd_t qtd_overlay; - - // Word 10-11: Setup request (control OUT only) - volatile tusb_control_request_t setup_request; - - //--------------------------------------------------------------------+ - /// Due to the fact QHD is 64 bytes aligned but occupies only 48 bytes - /// thus there are 16 bytes padding free that we can make use of. - //--------------------------------------------------------------------+ - uint8_t reserved[16]; -} dcd_qhd_t; - -TU_VERIFY_STATIC( sizeof(dcd_qhd_t) == 64, "size is not correct"); - -//--------------------------------------------------------------------+ -// Variables -//--------------------------------------------------------------------+ - -typedef struct -{ - dcd_registers_t* regs; // registers - const IRQn_Type irqnum; // IRQ number - const uint8_t ep_count; // Max bi-directional Endpoints -}dcd_controller_t; - -#if CFG_TUSB_MCU == OPT_MCU_MIMXRT10XX - // Each endpoint with direction (IN/OUT) occupies a queue head - // Therefore QHD_MAX is 2 x max endpoint count - #define QHD_MAX (8*2) - - static const dcd_controller_t _dcd_controller[] = - { - // RT1010 and RT1020 only has 1 USB controller - #if FSL_FEATURE_SOC_USBHS_COUNT == 1 - { .regs = (dcd_registers_t*) USB_BASE , .irqnum = USB_OTG1_IRQn, .ep_count = 8 } - #else - { .regs = (dcd_registers_t*) USB1_BASE, .irqnum = USB_OTG1_IRQn, .ep_count = 8 }, - { .regs = (dcd_registers_t*) USB2_BASE, .irqnum = USB_OTG2_IRQn, .ep_count = 8 } - #endif - }; - -#else - #define QHD_MAX (6*2) - - static const dcd_controller_t _dcd_controller[] = - { - { .regs = (dcd_registers_t*) LPC_USB0_BASE, .irqnum = USB0_IRQn, .ep_count = 6 }, - { .regs = (dcd_registers_t*) LPC_USB1_BASE, .irqnum = USB1_IRQn, .ep_count = 4 } - }; -#endif - -#define QTD_NEXT_INVALID 0x01 - -typedef struct { - // Must be at 2K alignment - dcd_qhd_t qhd[QHD_MAX] TU_ATTR_ALIGNED(64); - dcd_qtd_t qtd[QHD_MAX] TU_ATTR_ALIGNED(32); // for portability, TinyUSB only queue 1 TD for each Qhd -}dcd_data_t; - -CFG_TUSB_MEM_SECTION TU_ATTR_ALIGNED(2048) -static dcd_data_t _dcd_data; - -//--------------------------------------------------------------------+ -// CONTROLLER API -//--------------------------------------------------------------------+ - -/// follows LPC43xx User Manual 23.10.3 -static void bus_reset(uint8_t rhport) -{ - dcd_registers_t* dcd_reg = _dcd_controller[rhport].regs; - - // The reset value for all endpoint types is the control endpoint. If one endpoint - // direction is enabled and the paired endpoint of opposite direction is disabled, then the - // endpoint type of the unused direction must be changed from the control type to any other - // type (e.g. bulk). Leaving an un-configured endpoint control will cause undefined behavior - // for the data PID tracking on the active endpoint. - for( int i=1; i < _dcd_controller[rhport].ep_count; i++) - { - dcd_reg->ENDPTCTRL[i] = (TUSB_XFER_BULK << 2) | (TUSB_XFER_BULK << 18); - } - - //------------- Clear All Registers -------------// - dcd_reg->ENDPTNAK = dcd_reg->ENDPTNAK; - dcd_reg->ENDPTNAKEN = 0; - dcd_reg->USBSTS = dcd_reg->USBSTS; - dcd_reg->ENDPTSETUPSTAT = dcd_reg->ENDPTSETUPSTAT; - dcd_reg->ENDPTCOMPLETE = dcd_reg->ENDPTCOMPLETE; - - while (dcd_reg->ENDPTPRIME) {} - dcd_reg->ENDPTFLUSH = 0xFFFFFFFF; - while (dcd_reg->ENDPTFLUSH) {} - - // read reset bit in portsc - - //------------- Queue Head & Queue TD -------------// - tu_memclr(&_dcd_data, sizeof(dcd_data_t)); - - //------------- Set up Control Endpoints (0 OUT, 1 IN) -------------// - _dcd_data.qhd[0].zero_length_termination = _dcd_data.qhd[1].zero_length_termination = 1; - _dcd_data.qhd[0].max_package_size = _dcd_data.qhd[1].max_package_size = CFG_TUD_ENDPOINT0_SIZE; - _dcd_data.qhd[0].qtd_overlay.next = _dcd_data.qhd[1].qtd_overlay.next = QTD_NEXT_INVALID; - - _dcd_data.qhd[0].int_on_setup = 1; // OUT only -} - -void dcd_init(uint8_t rhport) -{ - tu_memclr(&_dcd_data, sizeof(dcd_data_t)); - - dcd_registers_t* dcd_reg = _dcd_controller[rhport].regs; - - // Reset controller - dcd_reg->USBCMD |= USBCMD_RESET; - while( dcd_reg->USBCMD & USBCMD_RESET ) {} - - // Set mode to device, must be set immediately after reset - dcd_reg->USBMODE = USBMODE_CM_DEVICE; - dcd_reg->OTGSC = OTGSC_VBUS_DISCHARGE | OTGSC_OTG_TERMINATION; - - // TODO Force fullspeed on non-highspeed port - // dcd_reg->PORTSC1 = PORTSC1_FORCE_FULL_SPEED; - - CleanInvalidateDCache_by_Addr((uint32_t*) &_dcd_data, sizeof(dcd_data_t)); - - dcd_reg->ENDPTLISTADDR = (uint32_t) _dcd_data.qhd; // Endpoint List Address has to be 2K alignment - dcd_reg->USBSTS = dcd_reg->USBSTS; - dcd_reg->USBINTR = INTR_USB | INTR_ERROR | INTR_PORT_CHANGE | INTR_RESET | INTR_SUSPEND /*| INTR_SOF*/; - - dcd_reg->USBCMD &= ~0x00FF0000; // Interrupt Threshold Interval = 0 - dcd_reg->USBCMD |= USBCMD_RUN_STOP; // Connect -} - -void dcd_int_enable(uint8_t rhport) -{ - NVIC_EnableIRQ(_dcd_controller[rhport].irqnum); -} - -void dcd_int_disable(uint8_t rhport) -{ - NVIC_DisableIRQ(_dcd_controller[rhport].irqnum); -} - -void dcd_set_address(uint8_t rhport, uint8_t dev_addr) -{ - // Response with status first before changing device address - dcd_edpt_xfer(rhport, tu_edpt_addr(0, TUSB_DIR_IN), NULL, 0); - - dcd_registers_t* dcd_reg = _dcd_controller[rhport].regs; - dcd_reg->DEVICEADDR = (dev_addr << 25) | TU_BIT(24); -} - -void dcd_remote_wakeup(uint8_t rhport) -{ - (void) rhport; -} - -void dcd_connect(uint8_t rhport) -{ - dcd_registers_t* dcd_reg = _dcd_controller[rhport].regs; - dcd_reg->USBCMD |= USBCMD_RUN_STOP; -} - -void dcd_disconnect(uint8_t rhport) -{ - dcd_registers_t* dcd_reg = _dcd_controller[rhport].regs; - dcd_reg->USBCMD &= ~USBCMD_RUN_STOP; -} - -//--------------------------------------------------------------------+ -// HELPER -//--------------------------------------------------------------------+ -// index to bit position in register -static inline uint8_t ep_idx2bit(uint8_t ep_idx) -{ - return ep_idx/2 + ( (ep_idx%2) ? 16 : 0); -} - -static void qtd_init(dcd_qtd_t* p_qtd, void * data_ptr, uint16_t total_bytes) -{ - tu_memclr(p_qtd, sizeof(dcd_qtd_t)); - - p_qtd->next = QTD_NEXT_INVALID; - p_qtd->active = 1; - p_qtd->total_bytes = p_qtd->expected_bytes = total_bytes; - - if (data_ptr != NULL) - { - p_qtd->buffer[0] = (uint32_t) data_ptr; - for(uint8_t i=1; i<5; i++) - { - p_qtd->buffer[i] |= tu_align4k( p_qtd->buffer[i-1] ) + 4096; - } - } -} - -//--------------------------------------------------------------------+ -// DCD Endpoint Port -//--------------------------------------------------------------------+ -void dcd_edpt_stall(uint8_t rhport, uint8_t ep_addr) -{ - uint8_t const epnum = tu_edpt_number(ep_addr); - uint8_t const dir = tu_edpt_dir(ep_addr); - - dcd_registers_t* dcd_reg = _dcd_controller[rhport].regs; - dcd_reg->ENDPTCTRL[epnum] |= ENDPTCTRL_STALL << (dir ? 16 : 0); -} - -void dcd_edpt_clear_stall(uint8_t rhport, uint8_t ep_addr) -{ - uint8_t const epnum = tu_edpt_number(ep_addr); - uint8_t const dir = tu_edpt_dir(ep_addr); - - // data toggle also need to be reset - dcd_registers_t* dcd_reg = _dcd_controller[rhport].regs; - dcd_reg->ENDPTCTRL[epnum] |= ENDPTCTRL_TOGGLE_RESET << ( dir ? 16 : 0 ); - dcd_reg->ENDPTCTRL[epnum] &= ~(ENDPTCTRL_STALL << ( dir ? 16 : 0)); -} - -bool dcd_edpt_open(uint8_t rhport, tusb_desc_endpoint_t const * p_endpoint_desc) -{ - // TODO not support ISO yet - TU_VERIFY ( p_endpoint_desc->bmAttributes.xfer != TUSB_XFER_ISOCHRONOUS); - - uint8_t const epnum = tu_edpt_number(p_endpoint_desc->bEndpointAddress); - uint8_t const dir = tu_edpt_dir(p_endpoint_desc->bEndpointAddress); - uint8_t const ep_idx = 2*epnum + dir; - - // Must not exceed max endpoint number - TU_ASSERT( epnum < _dcd_controller[rhport].ep_count ); - - //------------- Prepare Queue Head -------------// - dcd_qhd_t * p_qhd = &_dcd_data.qhd[ep_idx]; - tu_memclr(p_qhd, sizeof(dcd_qhd_t)); - - p_qhd->zero_length_termination = 1; - p_qhd->max_package_size = p_endpoint_desc->wMaxPacketSize.size; - p_qhd->qtd_overlay.next = QTD_NEXT_INVALID; - - CleanInvalidateDCache_by_Addr((uint32_t*) &_dcd_data, sizeof(dcd_data_t)); - - // Enable EP Control - dcd_registers_t* dcd_reg = _dcd_controller[rhport].regs; - dcd_reg->ENDPTCTRL[epnum] |= ((p_endpoint_desc->bmAttributes.xfer << 2) | ENDPTCTRL_ENABLE | ENDPTCTRL_TOGGLE_RESET) << (dir ? 16 : 0); - - return true; -} - -bool dcd_edpt_xfer(uint8_t rhport, uint8_t ep_addr, uint8_t * buffer, uint16_t total_bytes) -{ - dcd_registers_t* dcd_reg = _dcd_controller[rhport].regs; - uint8_t const epnum = tu_edpt_number(ep_addr); - uint8_t const dir = tu_edpt_dir(ep_addr); - uint8_t const ep_idx = 2*epnum + dir; - - if ( epnum == 0 ) - { - // follows UM 24.10.8.1.1 Setup packet handling using setup lockout mechanism - // wait until ENDPTSETUPSTAT before priming data/status in response TODO add time out - while(dcd_reg->ENDPTSETUPSTAT & TU_BIT(0)) {} - } - - dcd_qhd_t * p_qhd = &_dcd_data.qhd[ep_idx]; - dcd_qtd_t * p_qtd = &_dcd_data.qtd[ep_idx]; - - // Force the CPU to flush the buffer. We increase the size by 32 because the call aligns the - // address to 32-byte boundaries. - // void* cast to suppress cast-align warning, buffer must be - CleanInvalidateDCache_by_Addr((uint32_t*) tu_align((uint32_t) buffer, 4), total_bytes + 31); - - //------------- Prepare qtd -------------// - qtd_init(p_qtd, buffer, total_bytes); - p_qtd->int_on_complete = true; - p_qhd->qtd_overlay.next = (uint32_t) p_qtd; // link qtd to qhd - - CleanInvalidateDCache_by_Addr((uint32_t*) &_dcd_data, sizeof(dcd_data_t)); - - // start transfer - dcd_reg->ENDPTPRIME = TU_BIT( ep_idx2bit(ep_idx) ) ; - - return true; -} - -//--------------------------------------------------------------------+ -// ISR -//--------------------------------------------------------------------+ -void dcd_int_handler(uint8_t rhport) -{ - dcd_registers_t* const dcd_reg = _dcd_controller[rhport].regs; - - uint32_t const int_enable = dcd_reg->USBINTR; - uint32_t const int_status = dcd_reg->USBSTS & int_enable; - dcd_reg->USBSTS = int_status; // Acknowledge handled interrupt - - // disabled interrupt sources - if (int_status == 0) return; - - if (int_status & INTR_RESET) - { - bus_reset(rhport); - uint32_t speed = (dcd_reg->PORTSC1 & PORTSC1_PORT_SPEED) >> PORTSC1_PORT_SPEED_POS; - dcd_event_bus_reset(rhport, (tusb_speed_t) speed, true); - } - - if (int_status & INTR_SUSPEND) - { - if (dcd_reg->PORTSC1 & PORTSC1_SUSPEND) - { - // Note: Host may delay more than 3 ms before and/or after bus reset before doing enumeration. - if ((dcd_reg->DEVICEADDR >> 25) & 0x0f) - { - dcd_event_bus_signal(rhport, DCD_EVENT_SUSPEND, true); - } - } - } - - // Make sure we read the latest version of _dcd_data. - CleanInvalidateDCache_by_Addr((uint32_t*) &_dcd_data, sizeof(dcd_data_t)); - - // TODO disconnection does not generate interrupt !!!!!! -// if (int_status & INTR_PORT_CHANGE) -// { -// if ( !(dcd_reg->PORTSC1 & PORTSC1_CURRENT_CONNECT_STATUS) ) -// { -// dcd_event_t event = { .rhport = rhport, .event_id = DCD_EVENT_UNPLUGGED }; -// dcd_event_handler(&event, true); -// } -// } - - if (int_status & INTR_USB) - { - uint32_t const edpt_complete = dcd_reg->ENDPTCOMPLETE; - dcd_reg->ENDPTCOMPLETE = edpt_complete; // acknowledge - - if (dcd_reg->ENDPTSETUPSTAT) - { - //------------- Set up Received -------------// - // 23.10.10.2 Operational model for setup transfers - dcd_reg->ENDPTSETUPSTAT = dcd_reg->ENDPTSETUPSTAT;// acknowledge - - dcd_event_setup_received(rhport, (uint8_t*) &_dcd_data.qhd[0].setup_request, true); - } - - if ( edpt_complete ) - { - for(uint8_t ep_idx = 0; ep_idx < QHD_MAX; ep_idx++) - { - if ( tu_bit_test(edpt_complete, ep_idx2bit(ep_idx)) ) - { - // 23.10.12.3 Failed QTD also get ENDPTCOMPLETE set - dcd_qtd_t * p_qtd = &_dcd_data.qtd[ep_idx]; - - uint8_t result = p_qtd->halted ? XFER_RESULT_STALLED : - ( p_qtd->xact_err ||p_qtd->buffer_err ) ? XFER_RESULT_FAILED : XFER_RESULT_SUCCESS; - - uint8_t const ep_addr = (ep_idx/2) | ( (ep_idx & 0x01) ? TUSB_DIR_IN_MASK : 0 ); - dcd_event_xfer_complete(rhport, ep_addr, p_qtd->expected_bytes - p_qtd->total_bytes, result, true); // only number of bytes in the IOC qtd - } - } - } - } - - if (int_status & INTR_SOF) - { - dcd_event_bus_signal(rhport, DCD_EVENT_SOF, true); - } - - if (int_status & INTR_NAK) {} - if (int_status & INTR_ERROR) TU_ASSERT(false, ); -} - -#endif diff --git a/cores/nRF5/TinyUSB/Adafruit_TinyUSB_ArduinoCore/tinyusb/src/portable/sony/cxd56/dcd_cxd56.c b/cores/nRF5/TinyUSB/Adafruit_TinyUSB_ArduinoCore/tinyusb/src/portable/sony/cxd56/dcd_cxd56.c deleted file mode 100644 index 9269903..0000000 --- a/cores/nRF5/TinyUSB/Adafruit_TinyUSB_ArduinoCore/tinyusb/src/portable/sony/cxd56/dcd_cxd56.c +++ /dev/null @@ -1,336 +0,0 @@ -/* - * The MIT License (MIT) - * - * Copyright 2019 Sony Semiconductor Solutions Corporation - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - * - * This file is part of the TinyUSB stack. - */ - -#include "tusb_option.h" - -#if TUSB_OPT_DEVICE_ENABLED && CFG_TUSB_MCU == OPT_MCU_CXD56 - -#include -#include -#include - -#include "device/dcd.h" - -#define CXD56_EPNUM (7) - -struct usbdcd_driver_s -{ - struct usbdevclass_driver_s usbdevclass_driver; - FAR struct usbdev_ep_s *ep[CXD56_EPNUM]; - FAR struct usbdev_req_s *req[CXD56_EPNUM]; -}; - -static struct usbdcd_driver_s usbdcd_driver; -static struct usbdev_s *usbdev; - -static int _dcd_bind (FAR struct usbdevclass_driver_s *driver, FAR struct usbdev_s *dev); -static void _dcd_unbind (FAR struct usbdevclass_driver_s *driver, FAR struct usbdev_s *dev); -static int _dcd_setup (FAR struct usbdevclass_driver_s *driver, FAR struct usbdev_s *dev, - FAR const struct usb_ctrlreq_s *ctrl, FAR uint8_t *dataout, size_t outlen); -static void _dcd_disconnect (FAR struct usbdevclass_driver_s *driver, FAR struct usbdev_s *dev); -static void _dcd_suspend (FAR struct usbdevclass_driver_s *driver, FAR struct usbdev_s *dev); -static void _dcd_resume (FAR struct usbdevclass_driver_s *driver, FAR struct usbdev_s *dev); - -static const struct usbdevclass_driverops_s g_driverops = -{ - _dcd_bind, /* bind */ - _dcd_unbind, /* unbind */ - _dcd_setup, /* setup */ - _dcd_disconnect, /* disconnect */ - _dcd_suspend, /* suspend */ - _dcd_resume, /* resume */ -}; - -static void usbdcd_ep0incomplete(FAR struct usbdev_ep_s *ep, FAR struct usbdev_req_s *req) -{ - (void) ep; - - uint8_t ep_addr = (uint32_t)req->priv; - - if (req->result || req->xfrd != req->len) - { - if (req->len) - { - dcd_event_xfer_complete(0, ep_addr, req->xfrd, XFER_RESULT_SUCCESS, true); - } - } - else - { - if (req->xfrd) - { - dcd_event_xfer_complete(0, ep_addr, req->xfrd, XFER_RESULT_SUCCESS, true); - } - } -} - -static int _dcd_bind(FAR struct usbdevclass_driver_s *driver, FAR struct usbdev_s *dev) -{ - (void) driver; - - usbdev = dev; - usbdcd_driver.ep[0] = dev->ep0; - - usbdcd_driver.req[0] = EP_ALLOCREQ(usbdcd_driver.ep[0]); - if (usbdcd_driver.req[0] != NULL) - { - usbdcd_driver.req[0]->len = 64; - usbdcd_driver.req[0]->buf = EP_ALLOCBUFFER(usbdcd_driver.ep[0], 64); - if (!usbdcd_driver.req[0]->buf) - { - EP_FREEREQ(usbdcd_driver.ep[0], usbdcd_driver.req[0]); - usbdcd_driver.req[0] = NULL; - } - } - - usbdcd_driver.req[0]->callback = usbdcd_ep0incomplete; - - DEV_CONNECT(dev); - return 0; -} - -static void _dcd_unbind(FAR struct usbdevclass_driver_s *driver, FAR struct usbdev_s *dev) -{ - (void) driver; - (void) dev; -} - -static int _dcd_setup(FAR struct usbdevclass_driver_s *driver, FAR struct usbdev_s *dev, - FAR const struct usb_ctrlreq_s *ctrl, FAR uint8_t *dataout, size_t outlen) -{ - (void) driver; - (void) dev; - (void) dataout; - (void) outlen; - - dcd_event_setup_received(0, (uint8_t *)ctrl, true); - - return 0; -} - -static void _dcd_disconnect(FAR struct usbdevclass_driver_s *driver, FAR struct usbdev_s *dev) -{ - (void) driver; - - tusb_speed_t speed = (dev->speed == 3) ? TUSB_SPEED_HIGH : TUSB_SPEED_FULL; - dcd_event_bus_reset(0, speed, true); - DEV_CONNECT(dev); -} - -static void _dcd_suspend(FAR struct usbdevclass_driver_s *driver, FAR struct usbdev_s *dev) -{ - (void) driver; - (void) dev; - - dcd_event_bus_signal(0, DCD_EVENT_SUSPEND, true); -} - -static void _dcd_resume(FAR struct usbdevclass_driver_s *driver, FAR struct usbdev_s *dev) -{ - (void) driver; - (void) dev; - - dcd_event_bus_signal(0, DCD_EVENT_RESUME, true); -} - -void dcd_init(uint8_t rhport) -{ - (void) rhport; - - usbdcd_driver.usbdevclass_driver.speed = USB_SPEED_HIGH; - usbdcd_driver.usbdevclass_driver.ops = &g_driverops; - - usbdev_register(&usbdcd_driver.usbdevclass_driver); -} - -// Enable device interrupt -void dcd_int_enable(uint8_t rhport) -{ - (void) rhport; - - up_enable_irq(CXD56_IRQ_USB_INT); -} - -// Disable device interrupt -void dcd_int_disable(uint8_t rhport) -{ - (void) rhport; - - up_disable_irq(CXD56_IRQ_USB_INT); -} - -// Receive Set Address request, mcu port must also include status IN response -void dcd_set_address(uint8_t rhport, uint8_t dev_addr) -{ - (void) rhport; - (void) dev_addr; -} - -void dcd_remote_wakeup(uint8_t rhport) -{ - (void) rhport; - - DEV_WAKEUP(usbdev); -} - -void dcd_connect(uint8_t rhport) -{ - (void) rhport; - DEV_CONNECT(usbdev); -} - -void dcd_disconnect(uint8_t rhport) -{ - (void) rhport; - DEV_DISCONNECT(usbdev); -} - -//--------------------------------------------------------------------+ -// Endpoint API -//--------------------------------------------------------------------+ - -bool dcd_edpt_open(uint8_t rhport, tusb_desc_endpoint_t const *p_endpoint_desc) -{ - (void) rhport; - - uint8_t epnum = tu_edpt_number(p_endpoint_desc->bEndpointAddress); - uint8_t const dir = tu_edpt_dir(p_endpoint_desc->bEndpointAddress); - uint8_t xfrtype = 0; - struct usb_epdesc_s epdesc; - - if (epnum >= CXD56_EPNUM) - { - return false; - } - - switch (p_endpoint_desc->bmAttributes.xfer) - { - case 1: - xfrtype = USB_EP_ATTR_XFER_ISOC; - break; - case 2: - xfrtype = USB_EP_ATTR_XFER_BULK; - break; - case 3: - xfrtype = USB_EP_ATTR_XFER_INT; - break; - } - - usbdcd_driver.ep[epnum] = DEV_ALLOCEP(usbdev, epnum, dir == TUSB_DIR_IN, xfrtype); - if (usbdcd_driver.ep[epnum] == NULL) - { - return false; - } - - usbdcd_driver.req[epnum] = NULL; - usbdcd_driver.req[epnum] = EP_ALLOCREQ(usbdcd_driver.ep[epnum]); - if (usbdcd_driver.req[epnum] != NULL) - { - usbdcd_driver.req[epnum]->len = p_endpoint_desc->wMaxPacketSize.size; - } - else - { - return false; - } - - usbdcd_driver.req[epnum]->callback = usbdcd_ep0incomplete; - - epdesc.len = p_endpoint_desc->bLength; - epdesc.type = p_endpoint_desc->bDescriptorType; - epdesc.addr = p_endpoint_desc->bEndpointAddress; - epdesc.attr = xfrtype; - epdesc.mxpacketsize[0] = LSBYTE(p_endpoint_desc->wMaxPacketSize.size); - epdesc.mxpacketsize[1] = MSBYTE(p_endpoint_desc->wMaxPacketSize.size); - epdesc.interval = p_endpoint_desc->bInterval; - - if (EP_CONFIGURE(usbdcd_driver.ep[epnum], &epdesc, false) < 0) - { - return false; - } - - return true; -} - -bool dcd_edpt_xfer(uint8_t rhport, uint8_t ep_addr, uint8_t *buffer, uint16_t total_bytes) -{ - (void) rhport; - - uint8_t epnum = tu_edpt_number(ep_addr); - - if (epnum >= CXD56_EPNUM) - { - return false; - } - - usbdcd_driver.req[epnum]->len = total_bytes; - usbdcd_driver.req[epnum]->priv = (void *)((uint32_t)ep_addr); - usbdcd_driver.req[epnum]->flags = 0; - - if (total_bytes) - { - usbdcd_driver.req[epnum]->buf = buffer; - } - else - { - return true; - } - - if (EP_SUBMIT(usbdcd_driver.ep[epnum], usbdcd_driver.req[epnum]) < 0) - { - return false; - } - - return true; -} - -void dcd_edpt_stall(uint8_t rhport, uint8_t ep_addr) -{ - (void) rhport; - - uint8_t epnum = tu_edpt_number(ep_addr); - - if (epnum >= CXD56_EPNUM) - { - return; - } - - EP_STALL(usbdcd_driver.ep[epnum]); -} - -void dcd_edpt_clear_stall(uint8_t rhport, uint8_t ep_addr) -{ - (void) rhport; - - uint8_t epnum = tu_edpt_number(ep_addr); - - if (epnum >= CXD56_EPNUM) - { - return; - } - - EP_RESUME(usbdcd_driver.ep[epnum]); -} - -#endif diff --git a/cores/nRF5/TinyUSB/Adafruit_TinyUSB_ArduinoCore/tinyusb/src/portable/st/stm32_fsdev/dcd_stm32_fsdev.c b/cores/nRF5/TinyUSB/Adafruit_TinyUSB_ArduinoCore/tinyusb/src/portable/st/stm32_fsdev/dcd_stm32_fsdev.c deleted file mode 100644 index 3217987..0000000 --- a/cores/nRF5/TinyUSB/Adafruit_TinyUSB_ArduinoCore/tinyusb/src/portable/st/stm32_fsdev/dcd_stm32_fsdev.c +++ /dev/null @@ -1,927 +0,0 @@ -/* - * The MIT License (MIT) - * - * Copyright (c) 2019 Nathan Conrad - * - * Portions: - * Copyright (c) 2016 STMicroelectronics - * 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. - */ - -/********************************************** - * This driver has been tested with the following MCUs: - * - F070, F072, L053, F042F6 - * - * It also should work with minimal changes for any ST MCU with an "USB A"/"PCD"/"HCD" peripheral. This - * covers: - * - * F04x, F072, F078, 070x6/B 1024 byte buffer - * F102, F103 512 byte buffer; no internal D+ pull-up (maybe many more changes?) - * F302xB/C, F303xB/C, F373 512 byte buffer; no internal D+ pull-up - * F302x6/8, F302xD/E2, F303xD/E 1024 byte buffer; no internal D+ pull-up - * L0x2, L0x3 1024 byte buffer - * L1 512 byte buffer - * L4x2, L4x3 1024 byte buffer - * - * To use this driver, you must: - * - If you are using a device with crystal-less USB, set up the clock recovery system (CRS) - * - Remap pins to be D+/D- on devices that they are shared (for example: F042Fx) - * - This is different to the normal "alternate function" GPIO interface, needs to go through SYSCFG->CFGRx register - * - Enable USB clock; Perhaps use __HAL_RCC_USB_CLK_ENABLE(); - * - (Optionally configure GPIO HAL to tell it the USB driver is using the USB pins) - * - call tusb_init(); - * - periodically call tusb_task(); - * - * Assumptions of the driver: - * - You are not using CAN (it must share the packet buffer) - * - APB clock is >= 10 MHz - * - On some boards, series resistors are required, but not on others. - * - On some boards, D+ pull up resistor (1.5kohm) is required, but not on others. - * - You don't have long-running interrupts; some USB packets must be quickly responded to. - * - You have the ST CMSIS library linked into the project. HAL is not used. - * - * Current driver limitations (i.e., a list of features for you to add): - * - STALL handled, but not tested. - * - Does it work? No clue. - * - All EP BTABLE buffers are created based on max packet size of first EP opened with that address. - * - No isochronous endpoints - * - Endpoint index is the ID of the endpoint - * - This means that priority is given to endpoints with lower ID numbers - * - Code is mixing up EP IX with EP ID. Everywhere. - * - Packet buffer memory is copied in the interrupt. - * - This is better for performance, but means interrupts are disabled for longer - * - DMA may be the best choice, but it could also be pushed to the USBD task. - * - No double-buffering - * - No DMA - * - Minimal error handling - * - Perhaps error interrupts should be reported to the stack, or cause a device reset? - * - Assumes a single USB peripheral; I think that no hardware has multiple so this is fine. - * - Add a callback for enabling/disabling the D+ PU on devices without an internal PU. - * - F3 models use three separate interrupts. I think we could only use the LP interrupt for - * everything? However, the interrupts are configurable so the DisableInt and EnableInt - * below functions could be adjusting the wrong interrupts (if they had been reconfigured) - * - LPM is not used correctly, or at all? - * - * USB documentation and Reference implementations - * - STM32 Reference manuals - * - STM32 USB Hardware Guidelines AN4879 - * - * - STM32 HAL (much of this driver is based on this) - * - libopencm3/lib/stm32/common/st_usbfs_core.c - * - Keil USB Device http://www.keil.com/pack/doc/mw/USB/html/group__usbd.html - * - * - YouTube OpenTechLab 011; https://www.youtube.com/watch?v=4FOkJLp_PUw - * - * Advantages over HAL driver: - * - Tiny (saves RAM, assumes a single USB peripheral) - * - * Notes: - * - The buffer table is allocated as endpoints are opened. The allocation is only - * cleared when the device is reset. This may be bad if the USB device needs - * to be reconfigured. - */ - -#include "tusb_option.h" - -#if defined(STM32F102x6) || defined(STM32F102xB) || \ - defined(STM32F103x6) || defined(STM32F103xB) || \ - defined(STM32F103xE) || defined(STM32F103xG) -#define STM32F1_FSDEV -#endif - -#if (TUSB_OPT_DEVICE_ENABLED) && ( \ - (CFG_TUSB_MCU == OPT_MCU_STM32F0 ) || \ - (CFG_TUSB_MCU == OPT_MCU_STM32F1 && defined(STM32F1_FSDEV)) || \ - (CFG_TUSB_MCU == OPT_MCU_STM32F3 ) || \ - (CFG_TUSB_MCU == OPT_MCU_STM32L0 ) \ - ) - -// In order to reduce the dependance on HAL, we undefine this. -// Some definitions are copied to our private include file. -#undef USE_HAL_DRIVER - -#include "device/dcd.h" -#include "portable/st/stm32_fsdev/dcd_stm32_fsdev_pvt_st.h" - - -/***************************************************** - * Configuration - *****************************************************/ - -// HW supports max of 8 bidirectional endpoints, but this can be reduced to save RAM -// (8u here would mean 8 IN and 8 OUT) -#ifndef MAX_EP_COUNT -# define MAX_EP_COUNT 8U -#endif - -// If sharing with CAN, one can set this to be non-zero to give CAN space where it wants it -// Both of these MUST be a multiple of 2, and are in byte units. -#ifndef DCD_STM32_BTABLE_BASE -# define DCD_STM32_BTABLE_BASE 0U -#endif - -#ifndef DCD_STM32_BTABLE_LENGTH -# define DCD_STM32_BTABLE_LENGTH (PMA_LENGTH - DCD_STM32_BTABLE_BASE) -#endif - -// Since TinyUSB doesn't use SOF for now, and this interrupt too often (1ms interval) -// We disable SOF for now until needed later on -#ifndef USE_SOF -# define USE_SOF 0 -#endif - -/*************************************************** - * Checks, structs, defines, function definitions, etc. - */ - -TU_VERIFY_STATIC((MAX_EP_COUNT) <= STFSDEV_EP_COUNT, "Only 8 endpoints supported on the hardware"); - -TU_VERIFY_STATIC(((DCD_STM32_BTABLE_BASE) + (DCD_STM32_BTABLE_LENGTH))<=(PMA_LENGTH), - "BTABLE does not fit in PMA RAM"); - -TU_VERIFY_STATIC(((DCD_STM32_BTABLE_BASE) % 8) == 0, "BTABLE base must be aligned to 8 bytes"); - -// One of these for every EP IN & OUT, uses a bit of RAM.... -typedef struct -{ - uint8_t * buffer; - uint16_t total_len; - uint16_t queued_len; - uint16_t pma_ptr; - uint8_t max_packet_size; - uint8_t pma_alloc_size; -} xfer_ctl_t; - -static xfer_ctl_t xfer_status[MAX_EP_COUNT][2]; - -static inline xfer_ctl_t* xfer_ctl_ptr(uint32_t epnum, uint32_t dir) -{ - return &xfer_status[epnum][dir]; -} - -static TU_ATTR_ALIGNED(4) uint32_t _setup_packet[6]; - -static uint8_t remoteWakeCountdown; // When wake is requested - -// into the stack. -static void dcd_handle_bus_reset(void); -static void dcd_transmit_packet(xfer_ctl_t * xfer, uint16_t ep_ix); -static void dcd_ep_ctr_handler(void); - -// PMA allocation/access -static uint8_t open_ep_count; -static uint16_t ep_buf_ptr; ///< Points to first free memory location -static void dcd_pma_alloc_reset(void); -static uint16_t dcd_pma_alloc(uint8_t ep_addr, size_t length); -static void dcd_pma_free(uint8_t ep_addr); -static bool dcd_write_packet_memory(uint16_t dst, const void *__restrict src, size_t wNBytes); -static bool dcd_read_packet_memory(void *__restrict dst, uint16_t src, size_t wNBytes); - -// Using a function due to better type checks -// This seems better than having to do type casts everywhere else -static inline void reg16_clear_bits(__IO uint16_t *reg, uint16_t mask) { - *reg = (uint16_t)(*reg & ~mask); -} - -void dcd_init (uint8_t rhport) -{ - /* Clocks should already be enabled */ - /* Use __HAL_RCC_USB_CLK_ENABLE(); to enable the clocks before calling this function */ - - /* The RM mentions to use a special ordering of PDWN and FRES, but this isn't done in HAL. - * Here, the RM is followed. */ - - for(uint32_t i = 0; i<200; i++) // should be a few us - { - asm("NOP"); - } - // Perform USB peripheral reset - USB->CNTR = USB_CNTR_FRES | USB_CNTR_PDWN; - for(uint32_t i = 0; i<200; i++) // should be a few us - { - asm("NOP"); - } - reg16_clear_bits(&USB->CNTR, USB_CNTR_PDWN);// Remove powerdown - // Wait startup time, for F042 and F070, this is <= 1 us. - for(uint32_t i = 0; i<200; i++) // should be a few us - { - asm("NOP"); - } - USB->CNTR = 0; // Enable USB - - USB->BTABLE = DCD_STM32_BTABLE_BASE; - - reg16_clear_bits(&USB->ISTR, USB_ISTR_ALL_EVENTS); // Clear pending interrupts - - // Reset endpoints to disabled - for(uint32_t i=0; iCNTR |= USB_CNTR_RESETM | (USE_SOF ? USB_CNTR_SOFM : 0) | USB_CNTR_ESOFM | USB_CNTR_CTRM | USB_CNTR_SUSPM | USB_CNTR_WKUPM; - dcd_handle_bus_reset(); - - // Enable pull-up if supported - if ( dcd_connect ) dcd_connect(rhport); -} - -// Define only on MCU with internal pull-up. BSP can define on MCU without internal PU. -#if defined(USB_BCDR_DPPU) - -// Disable internal D+ PU -void dcd_disconnect(uint8_t rhport) -{ - (void) rhport; - USB->BCDR &= ~(USB_BCDR_DPPU); -} - -// Enable internal D+ PU -void dcd_connect(uint8_t rhport) -{ - (void) rhport; - USB->BCDR |= USB_BCDR_DPPU; -} - -#endif - -// Enable device interrupt -void dcd_int_enable (uint8_t rhport) -{ - (void)rhport; - // Member here forces write to RAM before allowing ISR to execute - __DSB(); - __ISB(); -#if CFG_TUSB_MCU == OPT_MCU_STM32F0 || CFG_TUSB_MCU == OPT_MCU_STM32L0 - NVIC_EnableIRQ(USB_IRQn); -#elif CFG_TUSB_MCU == OPT_MCU_STM32F3 - NVIC_EnableIRQ(USB_HP_CAN_TX_IRQn); - NVIC_EnableIRQ(USB_LP_CAN_RX0_IRQn); - NVIC_EnableIRQ(USBWakeUp_IRQn); -#elif CFG_TUSB_MCU == OPT_MCU_STM32F1 - NVIC_EnableIRQ(USB_HP_CAN1_TX_IRQn); - NVIC_EnableIRQ(USB_LP_CAN1_RX0_IRQn); - NVIC_EnableIRQ(USBWakeUp_IRQn); -#else - #error Unknown arch in USB driver -#endif -} - -// Disable device interrupt -void dcd_int_disable(uint8_t rhport) -{ - (void)rhport; - -#if CFG_TUSB_MCU == OPT_MCU_STM32F0 || CFG_TUSB_MCU == OPT_MCU_STM32L0 - NVIC_DisableIRQ(USB_IRQn); -#elif CFG_TUSB_MCU == OPT_MCU_STM32F3 - NVIC_DisableIRQ(USB_HP_CAN_TX_IRQn); - NVIC_DisableIRQ(USB_LP_CAN_RX0_IRQn); - NVIC_DisableIRQ(USBWakeUp_IRQn); -#elif CFG_TUSB_MCU == OPT_MCU_STM32F1 - NVIC_DisableIRQ(USB_HP_CAN1_TX_IRQn); - NVIC_DisableIRQ(USB_LP_CAN1_RX0_IRQn); - NVIC_DisableIRQ(USBWakeUp_IRQn); -#else - #error Unknown arch in USB driver -#endif - - // CMSIS has a membar after disabling interrupts -} - -// Receive Set Address request, mcu port must also include status IN response -void dcd_set_address(uint8_t rhport, uint8_t dev_addr) -{ - (void) rhport; - (void) dev_addr; - - // Respond with status - dcd_edpt_xfer(rhport, tu_edpt_addr(0, TUSB_DIR_IN), NULL, 0); - - // DCD can only set address after status for this request is complete. - // do it at dcd_edpt0_status_complete() -} - -void dcd_remote_wakeup(uint8_t rhport) -{ - (void) rhport; - - USB->CNTR |= (uint16_t) USB_CNTR_RESUME; - remoteWakeCountdown = 4u; // required to be 1 to 15 ms, ESOF should trigger every 1ms. -} - -static const tusb_desc_endpoint_t ep0OUT_desc = -{ - .bLength = sizeof(tusb_desc_endpoint_t), - .bDescriptorType = TUSB_DESC_ENDPOINT, - - .bEndpointAddress = 0x00, - .bmAttributes = { .xfer = TUSB_XFER_CONTROL }, - .wMaxPacketSize = { .size = CFG_TUD_ENDPOINT0_SIZE }, - .bInterval = 0 -}; - -static const tusb_desc_endpoint_t ep0IN_desc = -{ - .bLength = sizeof(tusb_desc_endpoint_t), - .bDescriptorType = TUSB_DESC_ENDPOINT, - - .bEndpointAddress = 0x80, - .bmAttributes = { .xfer = TUSB_XFER_CONTROL }, - .wMaxPacketSize = { .size = CFG_TUD_ENDPOINT0_SIZE }, - .bInterval = 0 -}; - -static void dcd_handle_bus_reset(void) -{ - //__IO uint16_t * const epreg = &(EPREG(0)); - USB->DADDR = 0u; // disable USB peripheral by clearing the EF flag - - // Clear all EPREG (or maybe this is automatic? I'm not sure) - for(uint32_t i=0; iDADDR = USB_DADDR_EF; // Set enable flag, and leaving the device address as zero. -} - -// Handle CTR interrupt for the TX/IN direction -// -// Upon call, (wIstr & USB_ISTR_DIR) == 0U -static void dcd_ep_ctr_tx_handler(uint32_t wIstr) -{ - uint32_t EPindex = wIstr & USB_ISTR_EP_ID; - uint32_t wEPRegVal = pcd_get_endpoint(USB, EPindex); - - // Verify the CTR_TX bit is set. This was in the ST Micro code, - // but I'm not sure it's actually necessary? - if((wEPRegVal & USB_EP_CTR_TX) == 0U) - { - return; - } - - /* clear int flag */ - pcd_clear_tx_ep_ctr(USB, EPindex); - - xfer_ctl_t * xfer = xfer_ctl_ptr(EPindex,TUSB_DIR_IN); - if((xfer->total_len != xfer->queued_len)) /* TX not complete */ - { - dcd_transmit_packet(xfer, EPindex); - } - else /* TX Complete */ - { - dcd_event_xfer_complete(0, (uint8_t)(0x80 + EPindex), xfer->total_len, XFER_RESULT_SUCCESS, true); - } -} - -// Handle CTR interrupt for the RX/OUT direction -// -// Upon call, (wIstr & USB_ISTR_DIR) == 0U -static void dcd_ep_ctr_rx_handler(uint32_t wIstr) -{ - uint32_t EPindex = wIstr & USB_ISTR_EP_ID; - uint32_t wEPRegVal = pcd_get_endpoint(USB, EPindex); - uint32_t count = pcd_get_ep_rx_cnt(USB,EPindex); - - xfer_ctl_t *xfer = xfer_ctl_ptr(EPindex,TUSB_DIR_OUT); - - // Verify the CTR_RX bit is set. This was in the ST Micro code, - // but I'm not sure it's actually necessary? - if((wEPRegVal & USB_EP_CTR_RX) == 0U) - { - return; - } - - if((EPindex == 0U) && ((wEPRegVal & USB_EP_SETUP) != 0U)) /* Setup packet */ - { - // The setup_received function uses memcpy, so this must first copy the setup data into - // user memory, to allow for the 32-bit access that memcpy performs. - uint8_t userMemBuf[8]; - /* Get SETUP Packet*/ - if(count == 8) // Setup packet should always be 8 bytes. If not, ignore it, and try again. - { - // Must reset EP to NAK (in case it had been stalling) (though, maybe too late here) - pcd_set_ep_rx_status(USB,0u,USB_EP_RX_NAK); - pcd_set_ep_tx_status(USB,0u,USB_EP_TX_NAK); - dcd_read_packet_memory(userMemBuf, *pcd_ep_rx_address_ptr(USB,EPindex), 8); - dcd_event_setup_received(0, (uint8_t*)userMemBuf, true); - } - } - else - { - // Clear RX CTR interrupt flag - if(EPindex != 0u) - { - pcd_clear_rx_ep_ctr(USB, EPindex); - } - - if (count != 0U) - { - dcd_read_packet_memory(&(xfer->buffer[xfer->queued_len]), - *pcd_ep_rx_address_ptr(USB,EPindex), count); - xfer->queued_len = (uint16_t)(xfer->queued_len + count); - } - - if ((count < xfer->max_packet_size) || (xfer->queued_len == xfer->total_len)) - { - /* RX COMPLETE */ - dcd_event_xfer_complete(0, EPindex, xfer->queued_len, XFER_RESULT_SUCCESS, true); - // Though the host could still send, we don't know. - // Does the bulk pipe need to be reset to valid to allow for a ZLP? - } - else - { - uint32_t remaining = (uint32_t)xfer->total_len - (uint32_t)xfer->queued_len; - if(remaining >= xfer->max_packet_size) { - pcd_set_ep_rx_cnt(USB, EPindex,xfer->max_packet_size); - } else { - pcd_set_ep_rx_cnt(USB, EPindex,remaining); - } - pcd_set_ep_rx_status(USB, EPindex, USB_EP_RX_VALID); - } - } - - // For EP0, prepare to receive another SETUP packet. - // Clear CTR last so that a new packet does not overwrite the packing being read. - // (Based on the docs, it seems SETUP will always be accepted after CTR is cleared) - if(EPindex == 0u) - { - // Always be prepared for a status packet... - pcd_set_ep_rx_cnt(USB, EPindex, CFG_TUD_ENDPOINT0_SIZE); - pcd_clear_rx_ep_ctr(USB, EPindex); - } -} - -static void dcd_ep_ctr_handler(void) -{ - uint32_t wIstr; - - /* stay in loop while pending interrupts */ - while (((wIstr = USB->ISTR) & USB_ISTR_CTR) != 0U) - { - - if ((wIstr & USB_ISTR_DIR) == 0U) /* TX/IN */ - { - dcd_ep_ctr_tx_handler(wIstr); - } - else /* RX/OUT*/ - { - dcd_ep_ctr_rx_handler(wIstr); - } - } -} - -void dcd_int_handler(uint8_t rhport) { - - (void) rhport; - - uint32_t int_status = USB->ISTR; - //const uint32_t handled_ints = USB_ISTR_CTR | USB_ISTR_RESET | USB_ISTR_WKUP - // | USB_ISTR_SUSP | USB_ISTR_SOF | USB_ISTR_ESOF; - // unused IRQs: (USB_ISTR_PMAOVR | USB_ISTR_ERR | USB_ISTR_L1REQ ) - - // The ST driver loops here on the CTR bit, but that loop has been moved into the - // dcd_ep_ctr_handler(), so less need to loop here. The other interrupts shouldn't - // be triggered repeatedly. - - if(int_status & USB_ISTR_RESET) { - // USBRST is start of reset. - reg16_clear_bits(&USB->ISTR, USB_ISTR_RESET); - dcd_handle_bus_reset(); - dcd_event_bus_signal(0, DCD_EVENT_BUS_RESET, true); - return; // Don't do the rest of the things here; perhaps they've been cleared? - } - - if (int_status & USB_ISTR_CTR) - { - /* servicing of the endpoint correct transfer interrupt */ - /* clear of the CTR flag into the sub */ - dcd_ep_ctr_handler(); - reg16_clear_bits(&USB->ISTR, USB_ISTR_CTR); - } - - if (int_status & USB_ISTR_WKUP) - { - reg16_clear_bits(&USB->CNTR, USB_CNTR_LPMODE); - reg16_clear_bits(&USB->CNTR, USB_CNTR_FSUSP); - reg16_clear_bits(&USB->ISTR, USB_ISTR_WKUP); - dcd_event_bus_signal(0, DCD_EVENT_RESUME, true); - } - - if (int_status & USB_ISTR_SUSP) - { - /* Suspend is asserted for both suspend and unplug events. without Vbus monitoring, - * these events cannot be differentiated, so we only trigger suspend. */ - - /* Force low-power mode in the macrocell */ - USB->CNTR |= USB_CNTR_FSUSP; - USB->CNTR |= USB_CNTR_LPMODE; - - /* clear of the ISTR bit must be done after setting of CNTR_FSUSP */ - reg16_clear_bits(&USB->ISTR, USB_ISTR_SUSP); - dcd_event_bus_signal(0, DCD_EVENT_SUSPEND, true); - } - -#if USE_SOF - if(int_status & USB_ISTR_SOF) { - reg16_clear_bits(&USB->ISTR, USB_ISTR_SOF); - dcd_event_bus_signal(0, DCD_EVENT_SOF, true); - } -#endif - - if(int_status & USB_ISTR_ESOF) { - if(remoteWakeCountdown == 1u) - { - USB->CNTR &= (uint16_t)(~USB_CNTR_RESUME); - } - if(remoteWakeCountdown > 0u) - { - remoteWakeCountdown--; - } - reg16_clear_bits(&USB->ISTR, USB_ISTR_ESOF); - } -} - -//--------------------------------------------------------------------+ -// Endpoint API -//--------------------------------------------------------------------+ - -// Invoked when a control transfer's status stage is complete. -// May help DCD to prepare for next control transfer, this API is optional. -void dcd_edpt0_status_complete(uint8_t rhport, tusb_control_request_t const * request) -{ - (void) rhport; - - if (request->bmRequestType_bit.recipient == TUSB_REQ_RCPT_DEVICE && - request->bmRequestType_bit.type == TUSB_REQ_TYPE_STANDARD && - request->bRequest == TUSB_REQ_SET_ADDRESS ) - { - uint8_t const dev_addr = (uint8_t) request->wValue; - - // Setting new address after the whole request is complete - reg16_clear_bits(&USB->DADDR, USB_DADDR_ADD); - USB->DADDR = (uint16_t)(USB->DADDR | dev_addr); // leave the enable bit set - } -} - -static void dcd_pma_alloc_reset(void) -{ - ep_buf_ptr = DCD_STM32_BTABLE_BASE + 8*MAX_EP_COUNT; // 8 bytes per endpoint (two TX and two RX words, each) - //TU_LOG2("dcd_pma_alloc_reset()\r\n"); - for(uint32_t i=0; ipma_alloc_size = 0U; - xfer_ctl_ptr(i,TUSB_DIR_IN)->pma_alloc_size = 0U; - xfer_ctl_ptr(i,TUSB_DIR_OUT)->pma_ptr = 0U; - xfer_ctl_ptr(i,TUSB_DIR_IN)->pma_ptr = 0U; - } -} - -/*** - * Allocate a section of PMA - * - * If the EP number has already been allocated, and the new allocation - * is larger than the old allocation, then this will fail with a TU_ASSERT. - * (This is done to simplify the code. More complicated algorithms could be used) - * - * During failure, TU_ASSERT is used. If this happens, rework/reallocate memory manually. - */ -static uint16_t dcd_pma_alloc(uint8_t ep_addr, size_t length) -{ - uint8_t const epnum = tu_edpt_number(ep_addr); - uint8_t const dir = tu_edpt_dir(ep_addr); - xfer_ctl_t* epXferCtl = xfer_ctl_ptr(epnum,dir); - - if(epXferCtl->pma_alloc_size != 0U) - { - //TU_LOG2("dcd_pma_alloc(%x,%x)=%x (cached)\r\n",ep_addr,length,epXferCtl->pma_ptr); - // Previously allocated - TU_ASSERT(length <= epXferCtl->pma_alloc_size, 0xFFFF); // Verify no larger than previous alloc - return epXferCtl->pma_ptr; - } - - uint16_t addr = ep_buf_ptr; - ep_buf_ptr = (uint16_t)(ep_buf_ptr + length); // increment buffer pointer - - // Verify no overflow - TU_ASSERT(ep_buf_ptr <= PMA_LENGTH, 0xFFFF); - - epXferCtl->pma_ptr = addr; - epXferCtl->pma_alloc_size = length; - //TU_LOG2("dcd_pma_alloc(%x,%x)=%x\r\n",ep_addr,length,addr); - - return addr; -} - -/*** - * Free a block of PMA space - */ -static void dcd_pma_free(uint8_t ep_addr) -{ - uint8_t const epnum = tu_edpt_number(ep_addr); - uint8_t const dir = tu_edpt_dir(ep_addr); - - // Presently, this should never be called for EP0 IN/OUT - TU_ASSERT(open_ep_count > 2, /**/); - TU_ASSERT(xfer_ctl_ptr(epnum,dir)->max_packet_size != 0, /**/); - open_ep_count--; - - // If count is 2, only EP0 should be open, so allocations can be mostly reset. - - if(open_ep_count == 2) - { - ep_buf_ptr = DCD_STM32_BTABLE_BASE + 8*MAX_EP_COUNT + 2*CFG_TUD_ENDPOINT0_SIZE; // 8 bytes per endpoint (two TX and two RX words, each), and EP0 - - // Skip EP0 - for(uint32_t i=1; ipma_alloc_size = 0U; - xfer_ctl_ptr(i,TUSB_DIR_IN)->pma_alloc_size = 0U; - xfer_ctl_ptr(i,TUSB_DIR_OUT)->pma_ptr = 0U; - xfer_ctl_ptr(i,TUSB_DIR_IN)->pma_ptr = 0U; - } - } -} - -// The STM32F0 doesn't seem to like |= or &= to manipulate the EP#R registers, -// so I'm using the #define from HAL here, instead. - -bool dcd_edpt_open (uint8_t rhport, tusb_desc_endpoint_t const * p_endpoint_desc) -{ - (void)rhport; - uint8_t const epnum = tu_edpt_number(p_endpoint_desc->bEndpointAddress); - uint8_t const dir = tu_edpt_dir(p_endpoint_desc->bEndpointAddress); - const uint16_t epMaxPktSize = p_endpoint_desc->wMaxPacketSize.size; - uint16_t pma_addr; - uint32_t wType; - - // Isochronous not supported (yet), and some other driver assumptions. - TU_ASSERT(p_endpoint_desc->bmAttributes.xfer != TUSB_XFER_ISOCHRONOUS); - TU_ASSERT(epnum < MAX_EP_COUNT); - - // Set type - switch(p_endpoint_desc->bmAttributes.xfer) { - case TUSB_XFER_CONTROL: - wType = USB_EP_CONTROL; - break; -#if (0) - case TUSB_XFER_ISOCHRONOUS: // FIXME: Not yet supported - wType = USB_EP_ISOCHRONOUS; - break; -#endif - - case TUSB_XFER_BULK: - wType = USB_EP_CONTROL; - break; - - case TUSB_XFER_INTERRUPT: - wType = USB_EP_INTERRUPT; - break; - - default: - TU_ASSERT(false); - } - - pcd_set_eptype(USB, epnum, wType); - pcd_set_ep_address(USB, epnum, epnum); - // Be normal, for now, instead of only accepting zero-byte packets (on control endpoint) - // or being double-buffered (bulk endpoints) - pcd_clear_ep_kind(USB,0); - - pma_addr = dcd_pma_alloc(p_endpoint_desc->bEndpointAddress, p_endpoint_desc->wMaxPacketSize.size); - - if(dir == TUSB_DIR_IN) - { - *pcd_ep_tx_address_ptr(USB, epnum) = pma_addr; - pcd_set_ep_tx_cnt(USB, epnum, p_endpoint_desc->wMaxPacketSize.size); - pcd_clear_tx_dtog(USB, epnum); - pcd_set_ep_tx_status(USB,epnum,USB_EP_TX_NAK); - } - else - { - *pcd_ep_rx_address_ptr(USB, epnum) = pma_addr; - pcd_set_ep_rx_cnt(USB, epnum, p_endpoint_desc->wMaxPacketSize.size); - pcd_clear_rx_dtog(USB, epnum); - pcd_set_ep_rx_status(USB, epnum, USB_EP_RX_NAK); - } - - xfer_ctl_ptr(epnum, dir)->max_packet_size = epMaxPktSize; - - return true; -} - -/** - * Close an endpoint. - * - * This function may be called with interrupts enabled or disabled. - * - * This also clears transfers in progress, should there be any. - */ -void dcd_edpt_close (uint8_t rhport, uint8_t ep_addr) -{ - (void)rhport; - uint32_t const epnum = tu_edpt_number(ep_addr); - uint32_t const dir = tu_edpt_dir(ep_addr); - - if(dir == TUSB_DIR_IN) - { - pcd_set_ep_tx_status(USB,epnum,USB_EP_TX_DIS); - } - else - { - pcd_set_ep_rx_status(USB, epnum, USB_EP_RX_DIS); - } - - dcd_pma_free(ep_addr); -} - -// Currently, single-buffered, and only 64 bytes at a time (max) - -static void dcd_transmit_packet(xfer_ctl_t * xfer, uint16_t ep_ix) -{ - uint16_t len = (uint16_t)(xfer->total_len - xfer->queued_len); - - if(len > xfer->max_packet_size) // max packet size for FS transfer - { - len = xfer->max_packet_size; - } - uint16_t oldAddr = *pcd_ep_tx_address_ptr(USB,ep_ix); - dcd_write_packet_memory(oldAddr, &(xfer->buffer[xfer->queued_len]), len); - xfer->queued_len = (uint16_t)(xfer->queued_len + len); - - pcd_set_ep_tx_cnt(USB,ep_ix,len); - pcd_set_ep_tx_status(USB, ep_ix, USB_EP_TX_VALID); -} - -bool dcd_edpt_xfer (uint8_t rhport, uint8_t ep_addr, uint8_t * buffer, uint16_t total_bytes) -{ - (void) rhport; - - uint8_t const epnum = tu_edpt_number(ep_addr); - uint8_t const dir = tu_edpt_dir(ep_addr); - - xfer_ctl_t * xfer = xfer_ctl_ptr(epnum,dir); - - xfer->buffer = buffer; - xfer->total_len = total_bytes; - xfer->queued_len = 0; - - if ( dir == TUSB_DIR_OUT ) - { - // A setup token can occur immediately after an OUT STATUS packet so make sure we have a valid - // buffer for the control endpoint. - if (epnum == 0 && buffer == NULL) - { - xfer->buffer = (uint8_t*)_setup_packet; - } - if(total_bytes > xfer->max_packet_size) - { - pcd_set_ep_rx_cnt(USB,epnum,xfer->max_packet_size); - } else { - pcd_set_ep_rx_cnt(USB,epnum,total_bytes); - } - pcd_set_ep_rx_status(USB, epnum, USB_EP_RX_VALID); - } - else // IN - { - dcd_transmit_packet(xfer,epnum); - } - return true; -} - -void dcd_edpt_stall (uint8_t rhport, uint8_t ep_addr) -{ - (void)rhport; - - if (ep_addr & 0x80) - { // IN - pcd_set_ep_tx_status(USB, ep_addr & 0x7F, USB_EP_TX_STALL); - } - else - { // OUT - pcd_set_ep_rx_status(USB, ep_addr, USB_EP_RX_STALL); - } -} - -void dcd_edpt_clear_stall (uint8_t rhport, uint8_t ep_addr) -{ - (void)rhport; - - if (ep_addr & 0x80) - { // IN - ep_addr &= 0x7F; - - pcd_set_ep_tx_status(USB,ep_addr, USB_EP_TX_NAK); - - /* Reset to DATA0 if clearing stall condition. */ - pcd_clear_tx_dtog(USB,ep_addr); - } - else - { // OUT - /* Reset to DATA0 if clearing stall condition. */ - pcd_clear_rx_dtog(USB,ep_addr); - - pcd_set_ep_rx_status(USB,ep_addr, USB_EP_RX_NAK); - } -} - -// Packet buffer access can only be 8- or 16-bit. -/** - * @brief Copy a buffer from user memory area to packet memory area (PMA). - * This uses byte-access for user memory (so support non-aligned buffers) - * and 16-bit access for packet memory. - * @param dst, byte address in PMA; must be 16-bit aligned - * @param src pointer to user memory area. - * @param wPMABufAddr address into PMA. - * @param wNBytes no. of bytes to be copied. - * @retval None - */ -static bool dcd_write_packet_memory(uint16_t dst, const void *__restrict src, size_t wNBytes) -{ - uint32_t n = ((uint32_t)wNBytes + 1U) >> 1U; - uint32_t i; - uint16_t temp1, temp2; - const uint8_t * srcVal; - - // The GCC optimizer will combine access to 32-bit sizes if we let it. Force - // it volatile so that it won't do that. - __IO uint16_t *pdwVal; - - srcVal = src; - pdwVal = &pma[PMA_STRIDE*(dst>>1)]; - - for (i = n; i != 0; i--) - { - temp1 = (uint16_t) *srcVal; - srcVal++; - temp2 = temp1 | ((uint16_t)((uint16_t) ((*srcVal) << 8U))) ; - *pdwVal = temp2; - pdwVal += PMA_STRIDE; - srcVal++; - } - return true; -} - -/** - * @brief Copy a buffer from user memory area to packet memory area (PMA). - * Uses byte-access of system memory and 16-bit access of packet memory - * @param wNBytes no. of bytes to be copied. - * @retval None - */ -static bool dcd_read_packet_memory(void *__restrict dst, uint16_t src, size_t wNBytes) -{ - uint32_t n = (uint32_t)wNBytes >> 1U; - uint32_t i; - // The GCC optimizer will combine access to 32-bit sizes if we let it. Force - // it volatile so that it won't do that. - __IO const uint16_t *pdwVal; - uint32_t temp; - - pdwVal = &pma[PMA_STRIDE*(src>>1)]; - uint8_t *dstVal = (uint8_t*)dst; - - for (i = n; i != 0U; i--) - { - temp = *pdwVal; - pdwVal += PMA_STRIDE; - *dstVal++ = ((temp >> 0) & 0xFF); - *dstVal++ = ((temp >> 8) & 0xFF); - } - - if (wNBytes % 2) - { - temp = *pdwVal; - pdwVal += PMA_STRIDE; - *dstVal++ = ((temp >> 0) & 0xFF); - } - return true; -} - -#endif - diff --git a/cores/nRF5/TinyUSB/Adafruit_TinyUSB_ArduinoCore/tinyusb/src/portable/st/stm32_fsdev/dcd_stm32_fsdev_pvt_st.h b/cores/nRF5/TinyUSB/Adafruit_TinyUSB_ArduinoCore/tinyusb/src/portable/st/stm32_fsdev/dcd_stm32_fsdev_pvt_st.h deleted file mode 100644 index 4f2b229..0000000 --- a/cores/nRF5/TinyUSB/Adafruit_TinyUSB_ArduinoCore/tinyusb/src/portable/st/stm32_fsdev/dcd_stm32_fsdev_pvt_st.h +++ /dev/null @@ -1,403 +0,0 @@ -/** - ****************************************************************************** - * @file dcd_stm32f0_pvt_st.h - * @brief DCD utilities from ST code - ****************************************************************************** - * @attention - * - *

© COPYRIGHT(c) 2016 STMicroelectronics

- *

© parts COPYRIGHT(c) N Conrad

- * - * Redistribution and use in source and binary forms, with or without modification, - * are permitted provided that the following conditions are met: - * 1. Redistributions of source code must retain the above copyright notice, - * this list of conditions and the following disclaimer. - * 2. Redistributions in binary form must reproduce the above copyright notice, - * this list of conditions and the following disclaimer in the documentation - * and/or other materials provided with the distribution. - * 3. Neither the name of STMicroelectronics nor the names of its contributors - * may be used to endorse or promote products derived from this software - * without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" - * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE - * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE - * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL - * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR - * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER - * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, - * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - * - **********/ - -// This file contains source copied from ST's HAL, and thus should have their copyright statement. - -// PMA_LENGTH is PMA buffer size in bytes. -// On 512-byte devices, access with a stride of two words (use every other 16-bit address) -// On 1024-byte devices, access with a stride of one word (use every 16-bit address) - -#ifndef PORTABLE_ST_STM32F0_DCD_STM32F0_FSDEV_PVT_ST_H_ -#define PORTABLE_ST_STM32F0_DCD_STM32F0_FSDEV_PVT_ST_H_ - -#if defined(STM32F042x6) || \ - defined(STM32F070x6) || defined(STM32F070xB) || \ - defined(STM32F072xB) || \ - defined(STM32F078xx) - #include "stm32f0xx.h" - #define PMA_LENGTH (1024u) - // F0x2 models are crystal-less - // All have internal D+ pull-up - // 070RB: 2 x 16 bits/word memory LPM Support, BCD Support - // PMA dedicated to USB (no sharing with CAN) - -#elif defined(STM32F1_FSDEV) - #include "stm32f1xx.h" - #define PMA_LENGTH (512u) - // NO internal Pull-ups - // *B, and *C: 2 x 16 bits/word - - // F1 names this differently from the rest - #define USB_CNTR_LPMODE USB_CNTR_LP_MODE - -#elif defined(STM32F302xB) || defined(STM32F302xC) || \ - defined(STM32F303xB) || defined(STM32F303xC) || \ - defined(STM32F373xC) - #include "stm32f3xx.h" - #define PMA_LENGTH (512u) - // NO internal Pull-ups - // *B, and *C: 1 x 16 bits/word - // PMA dedicated to USB (no sharing with CAN) - -#elif defined(STM32F302x6) || defined(STM32F302x8) || \ - defined(STM32F302xD) || defined(STM32F302xE) || \ - defined(STM32F303xD) || defined(STM32F303xE) - #include "stm32f3xx.h" - #define PMA_LENGTH (1024u) - // NO internal Pull-ups - // *6, *8, *D, and *E: 2 x 16 bits/word LPM Support - // When CAN clock is enabled, USB can use first 768 bytes ONLY. - -#elif CFG_TUSB_MCU == OPT_MCU_STM32L0 - #include "stm32l0xx.h" - #define PMA_LENGTH (1024u) - -#else - #error You are using an untested or unimplemented STM32 variant. Please update the driver. - // This includes L1x0, L1x1, L1x2, L4x2 and L4x3, G1x1, G1x3, and G1x4 -#endif - -// For purposes of accessing the packet -#if ((PMA_LENGTH) == 512u) - #define PMA_STRIDE (2u) -#elif ((PMA_LENGTH) == 1024u) - #define PMA_STRIDE (1u) -#endif - -// And for type-safety create a new macro for the volatile address of PMAADDR -// The compiler should warn us if we cast it to a non-volatile type? -// Volatile is also needed to prevent the optimizer from changing access to 32-bit (as 32-bit access is forbidden) -static __IO uint16_t * const pma = (__IO uint16_t*)USB_PMAADDR; - -// prototypes -static inline __IO uint16_t* pcd_ep_rx_cnt_ptr(USB_TypeDef * USBx, uint32_t bEpNum); -static inline __IO uint16_t* pcd_ep_tx_cnt_ptr(USB_TypeDef * USBx, uint32_t bEpNum); -static inline void pcd_set_endpoint(USB_TypeDef * USBx, uint32_t bEpNum, uint32_t wRegValue); - - -/* SetENDPOINT */ -static inline void pcd_set_endpoint(USB_TypeDef * USBx, uint32_t bEpNum, uint32_t wRegValue) -{ - __O uint16_t *reg = (__O uint16_t *)((&USBx->EP0R) + bEpNum*2u); - *reg = (uint16_t)wRegValue; -} - -/* GetENDPOINT */ -static inline uint16_t pcd_get_endpoint(USB_TypeDef * USBx, uint32_t bEpNum) { - __I uint16_t *reg = (__I uint16_t *)((&USBx->EP0R) + bEpNum*2u); - return *reg; -} - -static inline void pcd_set_eptype(USB_TypeDef * USBx, uint32_t bEpNum, uint32_t wType) -{ - uint32_t regVal = pcd_get_endpoint(USBx, bEpNum); - regVal &= (uint32_t)USB_EP_T_MASK; - regVal |= wType; - regVal |= USB_EP_CTR_RX | USB_EP_CTR_TX; // These clear on write0, so must set high - pcd_set_endpoint(USBx, bEpNum, regVal); -} - -static inline uint32_t pcd_get_eptype(USB_TypeDef * USBx, uint32_t bEpNum) -{ - uint32_t regVal = pcd_get_endpoint(USBx, bEpNum); - regVal &= USB_EP_T_FIELD; - return regVal; -} -/** - * @brief Clears bit CTR_RX / CTR_TX in the endpoint register. - * @param USBx USB peripheral instance register address. - * @param bEpNum Endpoint Number. - * @retval None - */ -static inline void pcd_clear_rx_ep_ctr(USB_TypeDef * USBx, uint32_t bEpNum) -{ - uint32_t regVal = pcd_get_endpoint(USBx, bEpNum); - regVal &= 0x7FFFu & USB_EPREG_MASK; - pcd_set_endpoint(USBx, bEpNum, regVal); -} -static inline void pcd_clear_tx_ep_ctr(USB_TypeDef * USBx, uint32_t bEpNum) -{ - uint32_t regVal = pcd_get_endpoint(USBx, bEpNum); - regVal &= regVal & 0xFF7FU & USB_EPREG_MASK; - pcd_set_endpoint(USBx, bEpNum,regVal); -} -/** - * @brief gets counter of the tx buffer. - * @param USBx USB peripheral instance register address. - * @param bEpNum Endpoint Number. - * @retval Counter value - */ -static inline uint32_t pcd_get_ep_tx_cnt(USB_TypeDef * USBx, uint32_t bEpNum) -{ - __I uint16_t *regPtr = pcd_ep_tx_cnt_ptr(USBx, bEpNum); - return *regPtr & 0x3ffU; -} - -static inline uint32_t pcd_get_ep_rx_cnt(USB_TypeDef * USBx, uint32_t bEpNum) -{ - __I uint16_t *regPtr = pcd_ep_rx_cnt_ptr(USBx, bEpNum); - return *regPtr & 0x3ffU; -} - -/** - * @brief Sets counter of rx buffer with no. of blocks. - * @param dwReg Register - * @param wCount Counter. - * @param wNBlocks no. of Blocks. - * @retval None - */ - -static inline void pcd_set_ep_cnt_rx_reg(__O uint16_t * pdwReg, size_t wCount) { - uint32_t wNBlocks; - if(wCount > 62u) - { - wNBlocks = wCount >> 5u; - if((wCount & 0x1fU) == 0u) - { - wNBlocks--; - } - wNBlocks = wNBlocks << 10u; - wNBlocks |= 0x8000u; // Mark block size as 32byte - *pdwReg = (uint16_t)wNBlocks; - } - else - { - wNBlocks = wCount >> 1u; - if((wCount & 0x1U) != 0u) - { - wNBlocks++; - } - *pdwReg = (uint16_t)((wNBlocks) << 10u); - } -} - - -/** - * @brief Sets address in an endpoint register. - * @param USBx USB peripheral instance register address. - * @param bEpNum Endpoint Number. - * @param bAddr Address. - * @retval None - */ -static inline void pcd_set_ep_address(USB_TypeDef * USBx, uint32_t bEpNum, uint32_t bAddr) -{ - uint32_t regVal = pcd_get_endpoint(USBx, bEpNum); - regVal &= USB_EPREG_MASK; - regVal |= bAddr; - regVal |= USB_EP_CTR_RX|USB_EP_CTR_TX; - pcd_set_endpoint(USBx, bEpNum,regVal); -} - -static inline __IO uint16_t * pcd_btable_word_ptr(USB_TypeDef * USBx, size_t x) -{ - size_t total_word_offset = (((USBx)->BTABLE)>>1) + x; - total_word_offset *= PMA_STRIDE; - return &(pma[total_word_offset]); -} - -// Pointers to the PMA table entries (using the ARM address space) -static inline __IO uint16_t* pcd_ep_tx_address_ptr(USB_TypeDef * USBx, uint32_t bEpNum) -{ - return pcd_btable_word_ptr(USBx,(bEpNum)*4u + 0u); -} -static inline __IO uint16_t* pcd_ep_tx_cnt_ptr(USB_TypeDef * USBx, uint32_t bEpNum) -{ - return pcd_btable_word_ptr(USBx,(bEpNum)*4u + 1u); -} - -static inline __IO uint16_t* pcd_ep_rx_address_ptr(USB_TypeDef * USBx, uint32_t bEpNum) -{ - return pcd_btable_word_ptr(USBx,(bEpNum)*4u + 2u); -} - -static inline __IO uint16_t* pcd_ep_rx_cnt_ptr(USB_TypeDef * USBx, uint32_t bEpNum) -{ - return pcd_btable_word_ptr(USBx,(bEpNum)*4u + 3u); -} - -static inline void pcd_set_ep_tx_cnt(USB_TypeDef * USBx, uint32_t bEpNum, uint32_t wCount) -{ - *pcd_ep_tx_cnt_ptr(USBx, bEpNum) = (uint16_t)wCount; -} - -static inline void pcd_set_ep_rx_cnt(USB_TypeDef * USBx, uint32_t bEpNum, uint32_t wCount) -{ - __IO uint16_t *pdwReg = pcd_ep_rx_cnt_ptr((USBx),(bEpNum)); - pcd_set_ep_cnt_rx_reg(pdwReg, wCount); -} - -/** - * @brief sets the status for tx transfer (bits STAT_TX[1:0]). - * @param USBx USB peripheral instance register address. - * @param bEpNum Endpoint Number. - * @param wState new state - * @retval None - */ -static inline void pcd_set_ep_tx_status(USB_TypeDef * USBx, uint32_t bEpNum, uint32_t wState) -{ - uint32_t regVal = pcd_get_endpoint(USBx, bEpNum); - regVal &= USB_EPTX_DTOGMASK; - - /* toggle first bit ? */ - if((USB_EPTX_DTOG1 & (wState))!= 0U) - { - regVal ^= USB_EPTX_DTOG1; - } - /* toggle second bit ? */ - if((USB_EPTX_DTOG2 & ((uint32_t)(wState)))!= 0U) - { - regVal ^= USB_EPTX_DTOG2; - } - regVal |= USB_EP_CTR_RX|USB_EP_CTR_TX; - pcd_set_endpoint(USBx, bEpNum, regVal); -} /* pcd_set_ep_tx_status */ - -/** - * @brief sets the status for rx transfer (bits STAT_TX[1:0]) - * @param USBx USB peripheral instance register address. - * @param bEpNum Endpoint Number. - * @param wState new state - * @retval None - */ - -static inline void pcd_set_ep_rx_status(USB_TypeDef * USBx, uint32_t bEpNum, uint32_t wState) -{ - uint32_t regVal = pcd_get_endpoint(USBx, bEpNum); - regVal &= USB_EPRX_DTOGMASK; - - /* toggle first bit ? */ - if((USB_EPRX_DTOG1 & wState)!= 0U) - { - regVal ^= USB_EPRX_DTOG1; - } - /* toggle second bit ? */ - if((USB_EPRX_DTOG2 & wState)!= 0U) - { - regVal ^= USB_EPRX_DTOG2; - } - regVal |= USB_EP_CTR_RX|USB_EP_CTR_TX; - pcd_set_endpoint(USBx, bEpNum, regVal); -} /* pcd_set_ep_rx_status */ - -static inline uint32_t pcd_get_ep_rx_status(USB_TypeDef * USBx, uint32_t bEpNum) -{ - uint32_t regVal = pcd_get_endpoint(USBx, bEpNum); - return (regVal & USB_EPRX_STAT) >> (12u); -} /* pcd_get_ep_rx_status */ - - -/** - * @brief Toggles DTOG_RX / DTOG_TX bit in the endpoint register. - * @param USBx USB peripheral instance register address. - * @param bEpNum Endpoint Number. - * @retval None - */ -static inline void pcd_rx_dtog(USB_TypeDef * USBx, uint32_t bEpNum) -{ - uint32_t regVal = pcd_get_endpoint(USBx, bEpNum); - regVal &= USB_EPREG_MASK; - regVal |= USB_EP_CTR_RX|USB_EP_CTR_TX|USB_EP_DTOG_RX; - pcd_set_endpoint(USBx, bEpNum, regVal); -} - -static inline void pcd_tx_dtog(USB_TypeDef * USBx, uint32_t bEpNum) -{ - uint32_t regVal = pcd_get_endpoint(USBx, bEpNum); - regVal &= USB_EPREG_MASK; - regVal |= USB_EP_CTR_RX|USB_EP_CTR_TX|USB_EP_DTOG_TX; - pcd_set_endpoint(USBx, bEpNum, regVal); -} - -/** - * @brief Clears DTOG_RX / DTOG_TX bit in the endpoint register. - * @param USBx USB peripheral instance register address. - * @param bEpNum Endpoint Number. - * @retval None - */ - -static inline void pcd_clear_rx_dtog(USB_TypeDef * USBx, uint32_t bEpNum) -{ - uint32_t regVal = pcd_get_endpoint(USBx, bEpNum); - if((regVal & USB_EP_DTOG_RX) != 0) - { - pcd_rx_dtog(USBx,bEpNum); - } -} - -static inline void pcd_clear_tx_dtog(USB_TypeDef * USBx, uint32_t bEpNum) -{ - uint32_t regVal = pcd_get_endpoint(USBx, bEpNum); - if((regVal & USB_EP_DTOG_TX) != 0) - { - pcd_tx_dtog(USBx,bEpNum); - } -} - -/** - * @brief set & clear EP_KIND bit. - * @param USBx USB peripheral instance register address. - * @param bEpNum Endpoint Number. - * @retval None - */ - -static inline void pcd_set_ep_kind(USB_TypeDef * USBx, uint32_t bEpNum) -{ - uint32_t regVal = pcd_get_endpoint(USBx, bEpNum); - regVal |= USB_EP_KIND; - regVal &= USB_EPREG_MASK; - regVal |= USB_EP_CTR_RX|USB_EP_CTR_TX; - pcd_set_endpoint(USBx, bEpNum, regVal); -} -static inline void pcd_clear_ep_kind(USB_TypeDef * USBx, uint32_t bEpNum) -{ - uint32_t regVal = pcd_get_endpoint(USBx, bEpNum); - regVal &= USB_EPKIND_MASK; - regVal |= USB_EP_CTR_RX|USB_EP_CTR_TX; - pcd_set_endpoint(USBx, bEpNum, regVal); -} - -// This checks if the device has "LPM" -#if defined(USB_ISTR_L1REQ) -#define USB_ISTR_L1REQ_FORCED (USB_ISTR_L1REQ) -#else -#define USB_ISTR_L1REQ_FORCED ((uint16_t)0x0000U) -#endif - -#define USB_ISTR_ALL_EVENTS (USB_ISTR_PMAOVR | USB_ISTR_ERR | USB_ISTR_WKUP | USB_ISTR_SUSP | \ - USB_ISTR_RESET | USB_ISTR_SOF | USB_ISTR_ESOF | USB_ISTR_L1REQ_FORCED ) - -// Number of endpoints in hardware -#define STFSDEV_EP_COUNT (8u) - -#endif /* PORTABLE_ST_STM32F0_DCD_STM32F0_FSDEV_PVT_ST_H_ */ diff --git a/cores/nRF5/TinyUSB/Adafruit_TinyUSB_ArduinoCore/tinyusb/src/portable/st/synopsys/dcd_synopsys.c b/cores/nRF5/TinyUSB/Adafruit_TinyUSB_ArduinoCore/tinyusb/src/portable/st/synopsys/dcd_synopsys.c deleted file mode 100644 index cc7181c..0000000 --- a/cores/nRF5/TinyUSB/Adafruit_TinyUSB_ArduinoCore/tinyusb/src/portable/st/synopsys/dcd_synopsys.c +++ /dev/null @@ -1,1045 +0,0 @@ -/* - * The MIT License (MIT) - * - * Copyright (c) 2018 Scott Shawcroft, 2019 William D. Jones for Adafruit Industries - * Copyright (c) 2019 Ha Thach (tinyusb.org) - * Copyright (c) 2020 Jan Duempelmann - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - * - * This file is part of the TinyUSB stack. - */ - -#include "tusb_option.h" - -// Since TinyUSB doesn't use SOF for now, and this interrupt too often (1ms interval) -// We disable SOF for now until needed later on -#define USE_SOF 0 - -#if defined (STM32F105x8) || defined (STM32F105xB) || defined (STM32F105xC) || \ - defined (STM32F107xB) || defined (STM32F107xC) -#define STM32F1_SYNOPSYS -#endif - -#if defined (STM32L475xx) || defined (STM32L476xx) || \ - defined (STM32L485xx) || defined (STM32L486xx) || defined (STM32L496xx) || \ - defined (STM32L4R5xx) || defined (STM32L4R7xx) || defined (STM32L4R9xx) || \ - defined (STM32L4S5xx) || defined (STM32L4S7xx) || defined (STM32L4S9xx) -#define STM32L4_SYNOPSYS -#endif - -#if TUSB_OPT_DEVICE_ENABLED && \ - ( (CFG_TUSB_MCU == OPT_MCU_STM32F1 && defined(STM32F1_SYNOPSYS)) || \ - CFG_TUSB_MCU == OPT_MCU_STM32F2 || \ - CFG_TUSB_MCU == OPT_MCU_STM32F4 || \ - CFG_TUSB_MCU == OPT_MCU_STM32F7 || \ - CFG_TUSB_MCU == OPT_MCU_STM32H7 || \ - (CFG_TUSB_MCU == OPT_MCU_STM32L4 && defined(STM32L4_SYNOPSYS)) \ - ) - -// EP_MAX : Max number of bi-directional endpoints including EP0 -// EP_FIFO_SIZE : Size of dedicated USB SRAM -#if CFG_TUSB_MCU == OPT_MCU_STM32F1 - #include "stm32f1xx.h" - #define EP_MAX_FS 4 - #define EP_FIFO_SIZE_FS 1280 - -#elif CFG_TUSB_MCU == OPT_MCU_STM32F2 - #include "stm32f2xx.h" - #define EP_MAX_FS USB_OTG_FS_MAX_IN_ENDPOINTS - #define EP_FIFO_SIZE_FS USB_OTG_FS_TOTAL_FIFO_SIZE - -#elif CFG_TUSB_MCU == OPT_MCU_STM32F4 - #include "stm32f4xx.h" - #define EP_MAX_FS USB_OTG_FS_MAX_IN_ENDPOINTS - #define EP_FIFO_SIZE_FS USB_OTG_FS_TOTAL_FIFO_SIZE - #define EP_MAX_HS USB_OTG_HS_MAX_IN_ENDPOINTS - #define EP_FIFO_SIZE_HS USB_OTG_HS_TOTAL_FIFO_SIZE - -#elif CFG_TUSB_MCU == OPT_MCU_STM32H7 - #include "stm32h7xx.h" - #define EP_MAX_FS 9 - #define EP_FIFO_SIZE_FS 4096 - #define EP_MAX_HS 9 - #define EP_FIFO_SIZE_HS 4096 - -#elif CFG_TUSB_MCU == OPT_MCU_STM32F7 - #include "stm32f7xx.h" - #define EP_MAX_FS 6 - #define EP_FIFO_SIZE_FS 1280 - #define EP_MAX_HS 9 - #define EP_FIFO_SIZE_HS 4096 - -#elif CFG_TUSB_MCU == OPT_MCU_STM32L4 - #include "stm32l4xx.h" - #define EP_MAX_FS 6 - #define EP_FIFO_SIZE_FS 1280 - -#else - #error "Unsupported MCUs" - -#endif - -#include "device/dcd.h" - -//--------------------------------------------------------------------+ -// MACRO TYPEDEF CONSTANT ENUM -//--------------------------------------------------------------------+ - -// On STM32 we associate Port0 to OTG_FS, and Port1 to OTG_HS -#if TUD_OPT_RHPORT == 0 - #define EP_MAX EP_MAX_FS - #define EP_FIFO_SIZE EP_FIFO_SIZE_FS - #define RHPORT_REGS_BASE USB_OTG_FS_PERIPH_BASE - #define RHPORT_IRQn OTG_FS_IRQn - -#else - #define EP_MAX EP_MAX_HS - #define EP_FIFO_SIZE EP_FIFO_SIZE_HS - #define RHPORT_REGS_BASE USB_OTG_HS_PERIPH_BASE - #define RHPORT_IRQn OTG_HS_IRQn -#endif - -#define GLOBAL_BASE(_port) ((USB_OTG_GlobalTypeDef*) RHPORT_REGS_BASE) -#define DEVICE_BASE(_port) (USB_OTG_DeviceTypeDef *) (RHPORT_REGS_BASE + USB_OTG_DEVICE_BASE) -#define OUT_EP_BASE(_port) (USB_OTG_OUTEndpointTypeDef *) (RHPORT_REGS_BASE + USB_OTG_OUT_ENDPOINT_BASE) -#define IN_EP_BASE(_port) (USB_OTG_INEndpointTypeDef *) (RHPORT_REGS_BASE + USB_OTG_IN_ENDPOINT_BASE) -#define FIFO_BASE(_port, _x) ((volatile uint32_t *) (RHPORT_REGS_BASE + USB_OTG_FIFO_BASE + (_x) * USB_OTG_FIFO_SIZE)) - -enum -{ - DCD_HIGH_SPEED = 0, // Highspeed mode - DCD_FULL_SPEED_USE_HS = 1, // Full speed in Highspeed port (probably with internal PHY) - DCD_FULL_SPEED = 3, // Full speed with internal PHY -}; - -static TU_ATTR_ALIGNED(4) uint32_t _setup_packet[2]; - -typedef struct { - uint8_t * buffer; - uint16_t total_len; - uint16_t max_size; -} xfer_ctl_t; - -typedef volatile uint32_t * usb_fifo_t; - -xfer_ctl_t xfer_status[EP_MAX][2]; -#define XFER_CTL_BASE(_ep, _dir) &xfer_status[_ep][_dir] - -// EP0 transfers are limited to 1 packet - larger sizes has to be split -static uint16_t ep0_pending[2]; // Index determines direction as tusb_dir_t type - -// FIFO RAM allocation so far in words -static uint16_t _allocated_fifo_words; - -// Setup the control endpoint 0. -static void bus_reset(uint8_t rhport) -{ - (void) rhport; - - USB_OTG_GlobalTypeDef * usb_otg = GLOBAL_BASE(rhport); - USB_OTG_DeviceTypeDef * dev = DEVICE_BASE(rhport); - USB_OTG_OUTEndpointTypeDef * out_ep = OUT_EP_BASE(rhport); - USB_OTG_INEndpointTypeDef * in_ep = IN_EP_BASE(rhport); - - tu_memclr(xfer_status, sizeof(xfer_status)); - - for(uint8_t n = 0; n < EP_MAX; n++) { - out_ep[n].DOEPCTL |= USB_OTG_DOEPCTL_SNAK; - } - - dev->DAINTMSK |= (1 << USB_OTG_DAINTMSK_OEPM_Pos) | (1 << USB_OTG_DAINTMSK_IEPM_Pos); - dev->DOEPMSK |= USB_OTG_DOEPMSK_STUPM | USB_OTG_DOEPMSK_XFRCM; - dev->DIEPMSK |= USB_OTG_DIEPMSK_TOM | USB_OTG_DIEPMSK_XFRCM; - - // "USB Data FIFOs" section in reference manual - // Peripheral FIFO architecture - // - // --------------- 320 or 1024 ( 1280 or 4096 bytes ) - // | IN FIFO MAX | - // --------------- - // | ... | - // --------------- y + x + 16 + GRXFSIZ - // | IN FIFO 2 | - // --------------- x + 16 + GRXFSIZ - // | IN FIFO 1 | - // --------------- 16 + GRXFSIZ - // | IN FIFO 0 | - // --------------- GRXFSIZ - // | OUT FIFO | - // | ( Shared ) | - // --------------- 0 - // - // According to "FIFO RAM allocation" section in RM, FIFO RAM are allocated as follows (each word 32-bits): - // - Each EP IN needs at least max packet size, 16 words is sufficient for EP0 IN - // - // - All EP OUT shared a unique OUT FIFO which uses - // - 13 for setup packets + control words (up to 3 setup packets). - // - 1 for global NAK (not required/used here). - // - Largest-EPsize / 4 + 1. ( FS: 64 bytes, HS: 512 bytes). Recommended is "2 x (Largest-EPsize/4) + 1" - // - 2 for each used OUT endpoint - // - // Therefore GRXFSIZ = 13 + 1 + 1 + 2 x (Largest-EPsize/4) + 2 x EPOUTnum - // - FullSpeed (64 Bytes ): GRXFSIZ = 15 + 2 x 16 + 2 x EP_MAX = 47 + 2 x EP_MAX - // - Highspeed (512 bytes): GRXFSIZ = 15 + 2 x 128 + 2 x EP_MAX = 271 + 2 x EP_MAX - // - // NOTE: Largest-EPsize & EPOUTnum is actual used endpoints in configuration. Since DCD has no knowledge - // of the overall picture yet. We will use the worst scenario: largest possible + EP_MAX - // - // FIXME: for Isochronous, largest EP size can be 1023/1024 for FS/HS respectively. In addition if multiple ISO - // are enabled at least "2 x (Largest-EPsize/4) + 1" are recommended. Maybe provide a macro for application to - // overwrite this. - -#if TUD_OPT_HIGH_SPEED - _allocated_fifo_words = 271 + 2*EP_MAX; -#else - _allocated_fifo_words = 47 + 2*EP_MAX; -#endif - - usb_otg->GRXFSIZ = _allocated_fifo_words; - - // Control IN uses FIFO 0 with 64 bytes ( 16 32-bit word ) - usb_otg->DIEPTXF0_HNPTXFSIZ = (16 << USB_OTG_TX0FD_Pos) | _allocated_fifo_words; - - _allocated_fifo_words += 16; - - // TU_LOG2_INT(_allocated_fifo_words); - - // Fixed control EP0 size to 64 bytes - in_ep[0].DIEPCTL &= ~(0x03 << USB_OTG_DIEPCTL_MPSIZ_Pos); - xfer_status[0][TUSB_DIR_OUT].max_size = xfer_status[0][TUSB_DIR_IN].max_size = 64; - - out_ep[0].DOEPTSIZ |= (3 << USB_OTG_DOEPTSIZ_STUPCNT_Pos); - - usb_otg->GINTMSK |= USB_OTG_GINTMSK_OEPINT | USB_OTG_GINTMSK_IEPINT; -} - -// Set turn-around timeout according to link speed -extern uint32_t SystemCoreClock; -static void set_turnaround(USB_OTG_GlobalTypeDef * usb_otg, tusb_speed_t speed) -{ - usb_otg->GUSBCFG &= ~USB_OTG_GUSBCFG_TRDT; - - if ( speed == TUSB_SPEED_HIGH ) - { - // Use fixed 0x09 for Highspeed - usb_otg->GUSBCFG |= (0x09 << USB_OTG_GUSBCFG_TRDT_Pos); - } - else - { - // Turnaround timeout depends on the MCU clock - uint32_t turnaround; - - if ( SystemCoreClock >= 32000000U ) - turnaround = 0x6U; - else if ( SystemCoreClock >= 27500000U ) - turnaround = 0x7U; - else if ( SystemCoreClock >= 24000000U ) - turnaround = 0x8U; - else if ( SystemCoreClock >= 21800000U ) - turnaround = 0x9U; - else if ( SystemCoreClock >= 20000000U ) - turnaround = 0xAU; - else if ( SystemCoreClock >= 18500000U ) - turnaround = 0xBU; - else if ( SystemCoreClock >= 17200000U ) - turnaround = 0xCU; - else if ( SystemCoreClock >= 16000000U ) - turnaround = 0xDU; - else if ( SystemCoreClock >= 15000000U ) - turnaround = 0xEU; - else - turnaround = 0xFU; - - // Fullspeed depends on MCU clocks, but we will use 0x06 for 32+ Mhz - usb_otg->GUSBCFG |= (turnaround << USB_OTG_GUSBCFG_TRDT_Pos); - } -} - -static tusb_speed_t get_speed(uint8_t rhport) -{ - (void) rhport; - USB_OTG_DeviceTypeDef * dev = DEVICE_BASE(rhport); - uint32_t const enum_spd = (dev->DSTS & USB_OTG_DSTS_ENUMSPD_Msk) >> USB_OTG_DSTS_ENUMSPD_Pos; - return (enum_spd == DCD_HIGH_SPEED) ? TUSB_SPEED_HIGH : TUSB_SPEED_FULL; -} - -static void set_speed(uint8_t rhport, tusb_speed_t speed) -{ - uint32_t bitvalue; - - if ( rhport == 1 ) - { - bitvalue = ((TUSB_SPEED_HIGH == speed) ? DCD_HIGH_SPEED : DCD_FULL_SPEED_USE_HS); - } - else - { - bitvalue = DCD_FULL_SPEED; - } - - USB_OTG_DeviceTypeDef * dev = DEVICE_BASE(rhport); - - // Clear and set speed bits - dev->DCFG &= ~(3 << USB_OTG_DCFG_DSPD_Pos); - dev->DCFG |= (bitvalue << USB_OTG_DCFG_DSPD_Pos); -} - - -#if defined(USB_HS_PHYC) -static bool USB_HS_PHYCInit(void) -{ - USB_HS_PHYC_GlobalTypeDef *usb_hs_phyc = (USB_HS_PHYC_GlobalTypeDef*) USB_HS_PHYC_CONTROLLER_BASE; - - // Enable LDO - usb_hs_phyc->USB_HS_PHYC_LDO |= USB_HS_PHYC_LDO_ENABLE; - - // Wait until LDO ready - while ( 0 == (usb_hs_phyc->USB_HS_PHYC_LDO & USB_HS_PHYC_LDO_STATUS) ) {} - - uint32_t phyc_pll = 0; - - // TODO Try to get HSE_VALUE from registers instead of depending CFLAGS - switch ( HSE_VALUE ) - { - case 12000000: phyc_pll = USB_HS_PHYC_PLL1_PLLSEL_12MHZ ; break; - case 12500000: phyc_pll = USB_HS_PHYC_PLL1_PLLSEL_12_5MHZ ; break; - case 16000000: phyc_pll = USB_HS_PHYC_PLL1_PLLSEL_16MHZ ; break; - case 24000000: phyc_pll = USB_HS_PHYC_PLL1_PLLSEL_24MHZ ; break; - case 25000000: phyc_pll = USB_HS_PHYC_PLL1_PLLSEL_25MHZ ; break; - case 32000000: phyc_pll = USB_HS_PHYC_PLL1_PLLSEL_Msk ; break; // Value not defined in header - default: - TU_ASSERT(0); - } - usb_hs_phyc->USB_HS_PHYC_PLL = phyc_pll; - - // Control the tuning interface of the High Speed PHY - // Use magic value (USB_HS_PHYC_TUNE_VALUE) from ST driver - usb_hs_phyc->USB_HS_PHYC_TUNE |= 0x00000F13U; - - // Enable PLL internal PHY - usb_hs_phyc->USB_HS_PHYC_PLL |= USB_HS_PHYC_PLL_PLLEN; - - // Original ST code has 2 ms delay for PLL stabilization. - // Primitive test shows that more than 10 USB un/replug cycle showed no error with enumeration - - return true; -} -#endif - -static void edpt_schedule_packets(uint8_t rhport, uint8_t const epnum, uint8_t const dir, uint16_t const num_packets, uint16_t total_bytes) -{ - (void) rhport; - - USB_OTG_DeviceTypeDef * dev = DEVICE_BASE(rhport); - USB_OTG_OUTEndpointTypeDef * out_ep = OUT_EP_BASE(rhport); - USB_OTG_INEndpointTypeDef * in_ep = IN_EP_BASE(rhport); - - // EP0 is limited to one packet each xfer - // We use multiple transaction of xfer->max_size length to get a whole transfer done - if(epnum == 0) { - xfer_ctl_t * const xfer = XFER_CTL_BASE(epnum, dir); - total_bytes = tu_min16(ep0_pending[dir], xfer->max_size); - ep0_pending[dir] -= total_bytes; - } - - // IN and OUT endpoint xfers are interrupt-driven, we just schedule them here. - if(dir == TUSB_DIR_IN) { - // A full IN transfer (multiple packets, possibly) triggers XFRC. - in_ep[epnum].DIEPTSIZ = (num_packets << USB_OTG_DIEPTSIZ_PKTCNT_Pos) | - ((total_bytes << USB_OTG_DIEPTSIZ_XFRSIZ_Pos) & USB_OTG_DIEPTSIZ_XFRSIZ_Msk); - - in_ep[epnum].DIEPCTL |= USB_OTG_DIEPCTL_EPENA | USB_OTG_DIEPCTL_CNAK; - // For ISO endpoint set correct odd/even bit for next frame. - if ((in_ep[epnum].DIEPCTL & USB_OTG_DIEPCTL_EPTYP) == USB_OTG_DIEPCTL_EPTYP_0) - { - // Take odd/even bit from frame counter. - uint32_t const odd_frame_now = (dev->DSTS & (1u << USB_OTG_DSTS_FNSOF_Pos)); - in_ep[epnum].DIEPCTL |= (odd_frame_now ? USB_OTG_DIEPCTL_SD0PID_SEVNFRM_Msk : USB_OTG_DIEPCTL_SODDFRM_Msk); - } - // Enable fifo empty interrupt only if there are something to put in the fifo. - if(total_bytes != 0) { - dev->DIEPEMPMSK |= (1 << epnum); - } - } else { - // A full OUT transfer (multiple packets, possibly) triggers XFRC. - out_ep[epnum].DOEPTSIZ &= ~(USB_OTG_DOEPTSIZ_PKTCNT_Msk | USB_OTG_DOEPTSIZ_XFRSIZ); - out_ep[epnum].DOEPTSIZ |= (num_packets << USB_OTG_DOEPTSIZ_PKTCNT_Pos) | - ((total_bytes << USB_OTG_DOEPTSIZ_XFRSIZ_Pos) & USB_OTG_DOEPTSIZ_XFRSIZ_Msk); - - out_ep[epnum].DOEPCTL |= USB_OTG_DOEPCTL_EPENA | USB_OTG_DOEPCTL_CNAK; - } -} - -/*------------------------------------------------------------------*/ -/* Controller API - *------------------------------------------------------------------*/ -void dcd_init (uint8_t rhport) -{ - // Programming model begins in the last section of the chapter on the USB - // peripheral in each Reference Manual. - - USB_OTG_GlobalTypeDef * usb_otg = GLOBAL_BASE(rhport); - - // No HNP/SRP (no OTG support), program timeout later. - if ( rhport == 1 ) - { - // On selected MCUs HS port1 can be used with external PHY via ULPI interface - - // deactivate internal PHY - usb_otg->GCCFG &= ~USB_OTG_GCCFG_PWRDWN; - - // Init The UTMI Interface - usb_otg->GUSBCFG &= ~(USB_OTG_GUSBCFG_TSDPS | USB_OTG_GUSBCFG_ULPIFSLS | USB_OTG_GUSBCFG_PHYSEL); - - // Select default internal VBUS Indicator and Drive for ULPI - usb_otg->GUSBCFG &= ~(USB_OTG_GUSBCFG_ULPIEVBUSD | USB_OTG_GUSBCFG_ULPIEVBUSI); - -#if defined(USB_HS_PHYC) - // Highspeed with embedded UTMI PHYC - - // Select UTMI Interface - usb_otg->GUSBCFG &= ~USB_OTG_GUSBCFG_ULPI_UTMI_SEL; - usb_otg->GCCFG |= USB_OTG_GCCFG_PHYHSEN; - - // Enables control of a High Speed USB PHY - USB_HS_PHYCInit(); -#endif - } else - { - // Enable internal PHY - usb_otg->GUSBCFG |= USB_OTG_GUSBCFG_PHYSEL; - } - - // Reset core after selecting PHY - // Wait AHB IDLE, reset then wait until it is cleared - while ((usb_otg->GRSTCTL & USB_OTG_GRSTCTL_AHBIDL) == 0U) {} - usb_otg->GRSTCTL |= USB_OTG_GRSTCTL_CSRST; - while ((usb_otg->GRSTCTL & USB_OTG_GRSTCTL_CSRST) == USB_OTG_GRSTCTL_CSRST) {} - - // Restart PHY clock - *((volatile uint32_t *)(RHPORT_REGS_BASE + USB_OTG_PCGCCTL_BASE)) = 0; - - // Clear all interrupts - usb_otg->GINTSTS |= usb_otg->GINTSTS; - - // Required as part of core initialization. - // TODO: How should mode mismatch be handled? It will cause - // the core to stop working/require reset. - usb_otg->GINTMSK |= USB_OTG_GINTMSK_OTGINT | USB_OTG_GINTMSK_MMISM; - - USB_OTG_DeviceTypeDef * dev = DEVICE_BASE(rhport); - - // If USB host misbehaves during status portion of control xfer - // (non zero-length packet), send STALL back and discard. - dev->DCFG |= USB_OTG_DCFG_NZLSOHSK; - - set_speed(rhport, TUD_OPT_HIGH_SPEED ? TUSB_SPEED_HIGH : TUSB_SPEED_FULL); - - // Enable internal USB transceiver. - if ( rhport == 0 ) usb_otg->GCCFG |= USB_OTG_GCCFG_PWRDWN; - - usb_otg->GINTMSK |= USB_OTG_GINTMSK_USBRST | USB_OTG_GINTMSK_ENUMDNEM | - USB_OTG_GINTMSK_USBSUSPM | USB_OTG_GINTMSK_WUIM | - USB_OTG_GINTMSK_RXFLVLM | (USE_SOF ? USB_OTG_GINTMSK_SOFM : 0); - - // Enable global interrupt - usb_otg->GAHBCFG |= USB_OTG_GAHBCFG_GINT; - - dcd_connect(rhport); -} - -void dcd_int_enable (uint8_t rhport) -{ - (void) rhport; - NVIC_EnableIRQ(RHPORT_IRQn); -} - -void dcd_int_disable (uint8_t rhport) -{ - (void) rhport; - NVIC_DisableIRQ(RHPORT_IRQn); -} - -void dcd_set_address (uint8_t rhport, uint8_t dev_addr) -{ - USB_OTG_DeviceTypeDef * dev = DEVICE_BASE(rhport); - dev->DCFG |= (dev_addr << USB_OTG_DCFG_DAD_Pos) & USB_OTG_DCFG_DAD_Msk; - - // Response with status after changing device address - dcd_edpt_xfer(rhport, tu_edpt_addr(0, TUSB_DIR_IN), NULL, 0); -} - -void dcd_remote_wakeup(uint8_t rhport) -{ - (void) rhport; -} - -void dcd_connect(uint8_t rhport) -{ - (void) rhport; - USB_OTG_DeviceTypeDef * dev = DEVICE_BASE(rhport); - - dev->DCTL &= ~USB_OTG_DCTL_SDIS; -} - -void dcd_disconnect(uint8_t rhport) -{ - (void) rhport; - USB_OTG_DeviceTypeDef * dev = DEVICE_BASE(rhport); - - dev->DCTL |= USB_OTG_DCTL_SDIS; -} - - -/*------------------------------------------------------------------*/ -/* DCD Endpoint port - *------------------------------------------------------------------*/ - -bool dcd_edpt_open (uint8_t rhport, tusb_desc_endpoint_t const * desc_edpt) -{ - USB_OTG_GlobalTypeDef * usb_otg = GLOBAL_BASE(rhport); - USB_OTG_DeviceTypeDef * dev = DEVICE_BASE(rhport); - USB_OTG_OUTEndpointTypeDef * out_ep = OUT_EP_BASE(rhport); - USB_OTG_INEndpointTypeDef * in_ep = IN_EP_BASE(rhport); - - uint8_t const epnum = tu_edpt_number(desc_edpt->bEndpointAddress); - uint8_t const dir = tu_edpt_dir(desc_edpt->bEndpointAddress); - - TU_ASSERT(epnum < EP_MAX); - - if (desc_edpt->bmAttributes.xfer == TUSB_XFER_ISOCHRONOUS) - { - TU_ASSERT(desc_edpt->wMaxPacketSize.size <= (get_speed(rhport) == TUSB_SPEED_HIGH ? 1024 : 1023)); - } - else - { - TU_ASSERT(desc_edpt->wMaxPacketSize.size <= (get_speed(rhport) == TUSB_SPEED_HIGH ? 512 : 64)); - } - - xfer_ctl_t * xfer = XFER_CTL_BASE(epnum, dir); - xfer->max_size = desc_edpt->wMaxPacketSize.size; - - if(dir == TUSB_DIR_OUT) - { - out_ep[epnum].DOEPCTL |= (1 << USB_OTG_DOEPCTL_USBAEP_Pos) | - (desc_edpt->bmAttributes.xfer << USB_OTG_DOEPCTL_EPTYP_Pos) | - (desc_edpt->wMaxPacketSize.size << USB_OTG_DOEPCTL_MPSIZ_Pos); - - dev->DAINTMSK |= (1 << (USB_OTG_DAINTMSK_OEPM_Pos + epnum)); - } - else - { - // "USB Data FIFOs" section in reference manual - // Peripheral FIFO architecture - // - // --------------- 320 or 1024 ( 1280 or 4096 bytes ) - // | IN FIFO MAX | - // --------------- - // | ... | - // --------------- y + x + 16 + GRXFSIZ - // | IN FIFO 2 | - // --------------- x + 16 + GRXFSIZ - // | IN FIFO 1 | - // --------------- 16 + GRXFSIZ - // | IN FIFO 0 | - // --------------- GRXFSIZ - // | OUT FIFO | - // | ( Shared ) | - // --------------- 0 - // - // In FIFO is allocated by following rules: - // - IN EP 1 gets FIFO 1, IN EP "n" gets FIFO "n". - // - Offset: allocated so far - // - Size - // - Interrupt is EPSize - // - Bulk/ISO is max(EPSize, remaining-fifo / non-opened-EPIN) - - uint16_t const fifo_remaining = EP_FIFO_SIZE/4 - _allocated_fifo_words; - uint16_t fifo_size = desc_edpt->wMaxPacketSize.size / 4; - - if ( desc_edpt->bmAttributes.xfer != TUSB_XFER_INTERRUPT ) - { - uint8_t opened = 0; - for(uint8_t i = 0; i < EP_MAX; i++) - { - if ( (i != epnum) && (xfer_status[i][TUSB_DIR_IN].max_size > 0) ) opened++; - } - - // EP Size or equally divided of remaining whichever is larger - fifo_size = tu_max16(fifo_size, fifo_remaining / (EP_MAX - opened)); - } - - // FIFO overflows, we probably need a better allocating scheme - TU_ASSERT(fifo_size <= fifo_remaining); - - // DIEPTXF starts at FIFO #1. - // Both TXFD and TXSA are in unit of 32-bit words. - usb_otg->DIEPTXF[epnum - 1] = (fifo_size << USB_OTG_DIEPTXF_INEPTXFD_Pos) | _allocated_fifo_words; - - _allocated_fifo_words += fifo_size; - - in_ep[epnum].DIEPCTL |= (1 << USB_OTG_DIEPCTL_USBAEP_Pos) | - (epnum << USB_OTG_DIEPCTL_TXFNUM_Pos) | - (desc_edpt->bmAttributes.xfer << USB_OTG_DIEPCTL_EPTYP_Pos) | - (desc_edpt->bmAttributes.xfer != TUSB_XFER_ISOCHRONOUS ? USB_OTG_DOEPCTL_SD0PID_SEVNFRM : 0) | - (desc_edpt->wMaxPacketSize.size << USB_OTG_DIEPCTL_MPSIZ_Pos); - - dev->DAINTMSK |= (1 << (USB_OTG_DAINTMSK_IEPM_Pos + epnum)); - } - - return true; -} - -bool dcd_edpt_xfer (uint8_t rhport, uint8_t ep_addr, uint8_t * buffer, uint16_t total_bytes) -{ - uint8_t const epnum = tu_edpt_number(ep_addr); - uint8_t const dir = tu_edpt_dir(ep_addr); - - xfer_ctl_t * xfer = XFER_CTL_BASE(epnum, dir); - xfer->buffer = buffer; - xfer->total_len = total_bytes; - - // EP0 can only handle one packet - if(epnum == 0) { - ep0_pending[dir] = total_bytes; - // Schedule the first transaction for EP0 transfer - edpt_schedule_packets(rhport, epnum, dir, 1, ep0_pending[dir]); - return true; - } - - uint16_t num_packets = (total_bytes / xfer->max_size); - uint8_t const short_packet_size = total_bytes % xfer->max_size; - - // Zero-size packet is special case. - if(short_packet_size > 0 || (total_bytes == 0)) { - num_packets++; - } - - // Schedule packets to be sent within interrupt - edpt_schedule_packets(rhport, epnum, dir, num_packets, total_bytes); - - return true; -} - -static void dcd_edpt_disable (uint8_t rhport, uint8_t ep_addr, bool stall) -{ - (void) rhport; - - USB_OTG_GlobalTypeDef * usb_otg = GLOBAL_BASE(rhport); - USB_OTG_DeviceTypeDef * dev = DEVICE_BASE(rhport); - USB_OTG_OUTEndpointTypeDef * out_ep = OUT_EP_BASE(rhport); - USB_OTG_INEndpointTypeDef * in_ep = IN_EP_BASE(rhport); - - uint8_t const epnum = tu_edpt_number(ep_addr); - uint8_t const dir = tu_edpt_dir(ep_addr); - - if(dir == TUSB_DIR_IN) { - // Only disable currently enabled non-control endpoint - if ( (epnum == 0) || !(in_ep[epnum].DIEPCTL & USB_OTG_DIEPCTL_EPENA) ){ - in_ep[epnum].DIEPCTL |= USB_OTG_DIEPCTL_SNAK | (stall ? USB_OTG_DIEPCTL_STALL : 0); - } else { - // Stop transmitting packets and NAK IN xfers. - in_ep[epnum].DIEPCTL |= USB_OTG_DIEPCTL_SNAK; - while((in_ep[epnum].DIEPINT & USB_OTG_DIEPINT_INEPNE) == 0); - - // Disable the endpoint. - in_ep[epnum].DIEPCTL |= USB_OTG_DIEPCTL_EPDIS | (stall ? USB_OTG_DIEPCTL_STALL : 0); - while((in_ep[epnum].DIEPINT & USB_OTG_DIEPINT_EPDISD_Msk) == 0); - in_ep[epnum].DIEPINT = USB_OTG_DIEPINT_EPDISD; - } - - // Flush the FIFO, and wait until we have confirmed it cleared. - usb_otg->GRSTCTL |= (epnum << USB_OTG_GRSTCTL_TXFNUM_Pos); - usb_otg->GRSTCTL |= USB_OTG_GRSTCTL_TXFFLSH; - while((usb_otg->GRSTCTL & USB_OTG_GRSTCTL_TXFFLSH_Msk) != 0); - } else { - // Only disable currently enabled non-control endpoint - if ( (epnum == 0) || !(out_ep[epnum].DOEPCTL & USB_OTG_DOEPCTL_EPENA) ){ - out_ep[epnum].DOEPCTL |= stall ? USB_OTG_DOEPCTL_STALL : 0; - } else { - // Asserting GONAK is required to STALL an OUT endpoint. - // Simpler to use polling here, we don't use the "B"OUTNAKEFF interrupt - // anyway, and it can't be cleared by user code. If this while loop never - // finishes, we have bigger problems than just the stack. - dev->DCTL |= USB_OTG_DCTL_SGONAK; - while((usb_otg->GINTSTS & USB_OTG_GINTSTS_BOUTNAKEFF_Msk) == 0); - - // Ditto here- disable the endpoint. - out_ep[epnum].DOEPCTL |= USB_OTG_DOEPCTL_EPDIS | (stall ? USB_OTG_DOEPCTL_STALL : 0); - while((out_ep[epnum].DOEPINT & USB_OTG_DOEPINT_EPDISD_Msk) == 0); - out_ep[epnum].DOEPINT = USB_OTG_DOEPINT_EPDISD; - - // Allow other OUT endpoints to keep receiving. - dev->DCTL |= USB_OTG_DCTL_CGONAK; - } - } -} - -/** - * Close an endpoint. - */ -void dcd_edpt_close (uint8_t rhport, uint8_t ep_addr) -{ - USB_OTG_GlobalTypeDef * usb_otg = GLOBAL_BASE(rhport); - - uint8_t const epnum = tu_edpt_number(ep_addr); - uint8_t const dir = tu_edpt_dir(ep_addr); - - dcd_edpt_disable(rhport, ep_addr, false); - if (dir == TUSB_DIR_IN) - { - uint16_t const fifo_size = (usb_otg->DIEPTXF[epnum - 1] & USB_OTG_DIEPTXF_INEPTXFD_Msk) >> USB_OTG_DIEPTXF_INEPTXFD_Pos; - uint16_t const fifo_start = (usb_otg->DIEPTXF[epnum - 1] & USB_OTG_DIEPTXF_INEPTXSA_Msk) >> USB_OTG_DIEPTXF_INEPTXSA_Pos; - // For now only endpoint that has FIFO at the end of FIFO memory can be closed without fuss. - TU_ASSERT(fifo_start + fifo_size == _allocated_fifo_words,); - _allocated_fifo_words -= fifo_size; - } -} - -void dcd_edpt_stall (uint8_t rhport, uint8_t ep_addr) -{ - dcd_edpt_disable(rhport, ep_addr, true); -} - -void dcd_edpt_clear_stall (uint8_t rhport, uint8_t ep_addr) -{ - (void) rhport; - - USB_OTG_OUTEndpointTypeDef * out_ep = OUT_EP_BASE(rhport); - USB_OTG_INEndpointTypeDef * in_ep = IN_EP_BASE(rhport); - - uint8_t const epnum = tu_edpt_number(ep_addr); - uint8_t const dir = tu_edpt_dir(ep_addr); - - if(dir == TUSB_DIR_IN) { - in_ep[epnum].DIEPCTL &= ~USB_OTG_DIEPCTL_STALL; - - uint8_t eptype = (in_ep[epnum].DIEPCTL & USB_OTG_DIEPCTL_EPTYP_Msk) >> USB_OTG_DIEPCTL_EPTYP_Pos; - // Required by USB spec to reset DATA toggle bit to DATA0 on interrupt and bulk endpoints. - if(eptype == 2 || eptype == 3) { - in_ep[epnum].DIEPCTL |= USB_OTG_DIEPCTL_SD0PID_SEVNFRM; - } - } else { - out_ep[epnum].DOEPCTL &= ~USB_OTG_DOEPCTL_STALL; - - uint8_t eptype = (out_ep[epnum].DOEPCTL & USB_OTG_DOEPCTL_EPTYP_Msk) >> USB_OTG_DOEPCTL_EPTYP_Pos; - // Required by USB spec to reset DATA toggle bit to DATA0 on interrupt and bulk endpoints. - if(eptype == 2 || eptype == 3) { - out_ep[epnum].DOEPCTL |= USB_OTG_DOEPCTL_SD0PID_SEVNFRM; - } - } -} - -/*------------------------------------------------------------------*/ - -// Read a single data packet from receive FIFO -static void read_fifo_packet(uint8_t rhport, uint8_t * dst, uint16_t len) -{ - (void) rhport; - - usb_fifo_t rx_fifo = FIFO_BASE(rhport, 0); - - // Reading full available 32 bit words from fifo - uint16_t full_words = len >> 2; - for(uint16_t i = 0; i < full_words; i++) { - uint32_t tmp = *rx_fifo; - dst[0] = tmp & 0x000000FF; - dst[1] = (tmp & 0x0000FF00) >> 8; - dst[2] = (tmp & 0x00FF0000) >> 16; - dst[3] = (tmp & 0xFF000000) >> 24; - dst += 4; - } - - // Read the remaining 1-3 bytes from fifo - uint8_t bytes_rem = len & 0x03; - if(bytes_rem != 0) { - uint32_t tmp = *rx_fifo; - dst[0] = tmp & 0x000000FF; - if(bytes_rem > 1) { - dst[1] = (tmp & 0x0000FF00) >> 8; - } - if(bytes_rem > 2) { - dst[2] = (tmp & 0x00FF0000) >> 16; - } - } -} - -// Write a single data packet to EPIN FIFO -static void write_fifo_packet(uint8_t rhport, uint8_t fifo_num, uint8_t * src, uint16_t len) -{ - (void) rhport; - - usb_fifo_t tx_fifo = FIFO_BASE(rhport, fifo_num); - - // Pushing full available 32 bit words to fifo - uint16_t full_words = len >> 2; - for(uint16_t i = 0; i < full_words; i++){ - *tx_fifo = (src[3] << 24) | (src[2] << 16) | (src[1] << 8) | src[0]; - src += 4; - } - - // Write the remaining 1-3 bytes into fifo - uint8_t bytes_rem = len & 0x03; - if(bytes_rem){ - uint32_t tmp_word = 0; - tmp_word |= src[0]; - if(bytes_rem > 1){ - tmp_word |= src[1] << 8; - } - if(bytes_rem > 2){ - tmp_word |= src[2] << 16; - } - *tx_fifo = tmp_word; - } -} - -static void handle_rxflvl_ints(uint8_t rhport, USB_OTG_OUTEndpointTypeDef * out_ep) { - USB_OTG_GlobalTypeDef * usb_otg = GLOBAL_BASE(rhport); - usb_fifo_t rx_fifo = FIFO_BASE(rhport, 0); - - // Pop control word off FIFO - uint32_t ctl_word = usb_otg->GRXSTSP; - uint8_t pktsts = (ctl_word & USB_OTG_GRXSTSP_PKTSTS_Msk) >> USB_OTG_GRXSTSP_PKTSTS_Pos; - uint8_t epnum = (ctl_word & USB_OTG_GRXSTSP_EPNUM_Msk) >> USB_OTG_GRXSTSP_EPNUM_Pos; - uint16_t bcnt = (ctl_word & USB_OTG_GRXSTSP_BCNT_Msk) >> USB_OTG_GRXSTSP_BCNT_Pos; - - switch(pktsts) { - case 0x01: // Global OUT NAK (Interrupt) - break; - - case 0x02: // Out packet recvd - { - xfer_ctl_t * xfer = XFER_CTL_BASE(epnum, TUSB_DIR_OUT); - - // Read packet off RxFIFO - read_fifo_packet(rhport, xfer->buffer, bcnt); - - // Increment pointer to xfer data - xfer->buffer += bcnt; - - // Truncate transfer length in case of short packet - if(bcnt < xfer->max_size) { - xfer->total_len -= (out_ep[epnum].DOEPTSIZ & USB_OTG_DOEPTSIZ_XFRSIZ_Msk) >> USB_OTG_DOEPTSIZ_XFRSIZ_Pos; - if(epnum == 0) { - xfer->total_len -= ep0_pending[TUSB_DIR_OUT]; - ep0_pending[TUSB_DIR_OUT] = 0; - } - } - } - break; - - case 0x03: // Out packet done (Interrupt) - break; - - case 0x04: // Setup packet done (Interrupt) - out_ep[epnum].DOEPTSIZ |= (3 << USB_OTG_DOEPTSIZ_STUPCNT_Pos); - break; - - case 0x06: // Setup packet recvd - // We can receive up to three setup packets in succession, but - // only the last one is valid. - _setup_packet[0] = (* rx_fifo); - _setup_packet[1] = (* rx_fifo); - break; - - default: // Invalid - TU_BREAKPOINT(); - break; - } -} - -static void handle_epout_ints(uint8_t rhport, USB_OTG_DeviceTypeDef * dev, USB_OTG_OUTEndpointTypeDef * out_ep) { - // DAINT for a given EP clears when DOEPINTx is cleared. - // OEPINT will be cleared when DAINT's out bits are cleared. - for(uint8_t n = 0; n < EP_MAX; n++) { - xfer_ctl_t * xfer = XFER_CTL_BASE(n, TUSB_DIR_OUT); - - if(dev->DAINT & (1 << (USB_OTG_DAINT_OEPINT_Pos + n))) { - // SETUP packet Setup Phase done. - if(out_ep[n].DOEPINT & USB_OTG_DOEPINT_STUP) { - out_ep[n].DOEPINT = USB_OTG_DOEPINT_STUP; - dcd_event_setup_received(rhport, (uint8_t*) &_setup_packet[0], true); - } - - // OUT XFER complete - if(out_ep[n].DOEPINT & USB_OTG_DOEPINT_XFRC) { - out_ep[n].DOEPINT = USB_OTG_DOEPINT_XFRC; - - // EP0 can only handle one packet - if((n == 0) && ep0_pending[TUSB_DIR_OUT]) { - // Schedule another packet to be received. - edpt_schedule_packets(rhport, n, TUSB_DIR_OUT, 1, ep0_pending[TUSB_DIR_OUT]); - } else { - dcd_event_xfer_complete(rhport, n, xfer->total_len, XFER_RESULT_SUCCESS, true); - } - } - } - } -} - -static void handle_epin_ints(uint8_t rhport, USB_OTG_DeviceTypeDef * dev, USB_OTG_INEndpointTypeDef * in_ep) { - // DAINT for a given EP clears when DIEPINTx is cleared. - // IEPINT will be cleared when DAINT's out bits are cleared. - for ( uint8_t n = 0; n < EP_MAX; n++ ) - { - xfer_ctl_t *xfer = XFER_CTL_BASE(n, TUSB_DIR_IN); - - if ( dev->DAINT & (1 << (USB_OTG_DAINT_IEPINT_Pos + n)) ) - { - // IN XFER complete (entire xfer). - if ( in_ep[n].DIEPINT & USB_OTG_DIEPINT_XFRC ) - { - in_ep[n].DIEPINT = USB_OTG_DIEPINT_XFRC; - - // EP0 can only handle one packet - if((n == 0) && ep0_pending[TUSB_DIR_IN]) { - // Schedule another packet to be transmitted. - edpt_schedule_packets(rhport, n, TUSB_DIR_IN, 1, ep0_pending[TUSB_DIR_IN]); - } else { - dcd_event_xfer_complete(rhport, n | TUSB_DIR_IN_MASK, xfer->total_len, XFER_RESULT_SUCCESS, true); - } - } - - // XFER FIFO empty - if ( (in_ep[n].DIEPINT & USB_OTG_DIEPINT_TXFE) && (dev->DIEPEMPMSK & (1 << n)) ) - { - // DIEPINT's TXFE bit is read-only, software cannot clear it. - // It will only be cleared by hardware when written bytes is more than - // - 64 bytes or - // - Half of TX FIFO size (configured by DIEPTXF) - - uint16_t remaining_packets = (in_ep[n].DIEPTSIZ & USB_OTG_DIEPTSIZ_PKTCNT_Msk) >> USB_OTG_DIEPTSIZ_PKTCNT_Pos; - - // Process every single packet (only whole packets can be written to fifo) - for(uint16_t i = 0; i < remaining_packets; i++){ - uint16_t remaining_bytes = (in_ep[n].DIEPTSIZ & USB_OTG_DIEPTSIZ_XFRSIZ_Msk) >> USB_OTG_DIEPTSIZ_XFRSIZ_Pos; - // Packet can not be larger than ep max size - uint16_t packet_size = tu_min16(remaining_bytes, xfer->max_size); - - // It's only possible to write full packets into FIFO. Therefore DTXFSTS register of current - // EP has to be checked if the buffer can take another WHOLE packet - if(packet_size > ((in_ep[n].DTXFSTS & USB_OTG_DTXFSTS_INEPTFSAV_Msk) << 2)){ - break; - } - - // Push packet to Tx-FIFO - write_fifo_packet(rhport, n, xfer->buffer, packet_size); - - // Increment pointer to xfer data - xfer->buffer += packet_size; - } - - // Turn off TXFE if all bytes are written. - if (((in_ep[n].DIEPTSIZ & USB_OTG_DIEPTSIZ_XFRSIZ_Msk) >> USB_OTG_DIEPTSIZ_XFRSIZ_Pos) == 0) - { - dev->DIEPEMPMSK &= ~(1 << n); - } - } - } - } -} - -void dcd_int_handler(uint8_t rhport) -{ - USB_OTG_GlobalTypeDef * usb_otg = GLOBAL_BASE(rhport); - USB_OTG_DeviceTypeDef * dev = DEVICE_BASE(rhport); - USB_OTG_OUTEndpointTypeDef * out_ep = OUT_EP_BASE(rhport); - USB_OTG_INEndpointTypeDef * in_ep = IN_EP_BASE(rhport); - - uint32_t int_status = usb_otg->GINTSTS; - - if(int_status & USB_OTG_GINTSTS_USBRST) { - // USBRST is start of reset. - usb_otg->GINTSTS = USB_OTG_GINTSTS_USBRST; - bus_reset(rhport); - } - - if(int_status & USB_OTG_GINTSTS_ENUMDNE) { - // ENUMDNE is the end of reset where speed of the link is detected - - usb_otg->GINTSTS = USB_OTG_GINTSTS_ENUMDNE; - - tusb_speed_t const speed = get_speed(rhport); - - set_turnaround(usb_otg, speed); - dcd_event_bus_reset(rhport, speed, true); - } - - if(int_status & USB_OTG_GINTSTS_USBSUSP) - { - usb_otg->GINTSTS = USB_OTG_GINTSTS_USBSUSP; - dcd_event_bus_signal(rhport, DCD_EVENT_SUSPEND, true); - } - - if(int_status & USB_OTG_GINTSTS_WKUINT) - { - usb_otg->GINTSTS = USB_OTG_GINTSTS_WKUINT; - dcd_event_bus_signal(rhport, DCD_EVENT_RESUME, true); - } - - if(int_status & USB_OTG_GINTSTS_OTGINT) - { - // OTG INT bit is read-only - uint32_t const otg_int = usb_otg->GOTGINT; - - if (otg_int & USB_OTG_GOTGINT_SEDET) - { - dcd_event_bus_signal(rhport, DCD_EVENT_UNPLUGGED, true); - } - - usb_otg->GOTGINT = otg_int; - } - -#if USE_SOF - if(int_status & USB_OTG_GINTSTS_SOF) { - usb_otg->GINTSTS = USB_OTG_GINTSTS_SOF; - dcd_event_bus_signal(rhport, DCD_EVENT_SOF, true); - } -#endif - - // RxFIFO non-empty interrupt handling. - if(int_status & USB_OTG_GINTSTS_RXFLVL) { - // RXFLVL bit is read-only - - // Mask out RXFLVL while reading data from FIFO - usb_otg->GINTMSK &= ~USB_OTG_GINTMSK_RXFLVLM; - - // Loop until all available packets were handled - do { - handle_rxflvl_ints(rhport, out_ep); - int_status = usb_otg->GINTSTS; - } while(int_status & USB_OTG_GINTSTS_RXFLVL); - - usb_otg->GINTMSK |= USB_OTG_GINTMSK_RXFLVLM; - } - - // OUT endpoint interrupt handling. - if(int_status & USB_OTG_GINTSTS_OEPINT) { - // OEPINT is read-only - handle_epout_ints(rhport, dev, out_ep); - } - - // IN endpoint interrupt handling. - if(int_status & USB_OTG_GINTSTS_IEPINT) { - // IEPINT bit read-only - handle_epin_ints(rhport, dev, in_ep); - } -} - -#endif diff --git a/cores/nRF5/TinyUSB/Adafruit_TinyUSB_ArduinoCore/tinyusb/src/portable/ti/msp430x5xx/dcd_msp430x5xx.c b/cores/nRF5/TinyUSB/Adafruit_TinyUSB_ArduinoCore/tinyusb/src/portable/ti/msp430x5xx/dcd_msp430x5xx.c deleted file mode 100644 index ad71e11..0000000 --- a/cores/nRF5/TinyUSB/Adafruit_TinyUSB_ArduinoCore/tinyusb/src/portable/ti/msp430x5xx/dcd_msp430x5xx.c +++ /dev/null @@ -1,642 +0,0 @@ -/* - * The MIT License (MIT) - * - * Copyright (c) 2019-2020 William D. Jones - * Copyright (c) 2019-2020 Ha Thach (tinyusb.org) - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - * - * This file is part of the TinyUSB stack. - */ - -#include "tusb_option.h" - -#if TUSB_OPT_DEVICE_ENABLED && ( CFG_TUSB_MCU == OPT_MCU_MSP430x5xx ) - -#include "msp430.h" -#include "device/dcd.h" - -/*------------------------------------------------------------------*/ -/* MACRO TYPEDEF CONSTANT ENUM - *------------------------------------------------------------------*/ -// usbpllir_mirror and usbmaintl_mirror can be added later if needed. -static volatile uint16_t usbiepie_mirror = 0; -static volatile uint16_t usboepie_mirror = 0; -static volatile uint8_t usbie_mirror = 0; -static volatile uint16_t usbpwrctl_mirror = 0; -static bool in_isr = false; - -uint8_t _setup_packet[8]; - -// Xfer control -typedef struct -{ - uint8_t * buffer; - uint16_t total_len; - uint16_t queued_len; - uint16_t max_size; - bool short_packet; -} xfer_ctl_t; - -xfer_ctl_t xfer_status[8][2]; -#define XFER_CTL_BASE(_ep, _dir) &xfer_status[_ep][_dir] - -// Accessing endpoint regs -typedef volatile uint8_t * ep_regs_t; - -typedef enum -{ - CNF = 0, - BBAX = 1, - BCTX = 2, - BBAY = 5, - BCTY = 6, - SIZXY = 7 -} ep_regs_index_t; - -#define EP_REGS(epnum, dir) ((ep_regs_t) ((uintptr_t)&USBOEPCNF_1 + 64*dir + 8*(epnum - 1))) - -static void bus_reset(void) -{ - // Hardcoded into the USB core. - xfer_status[0][TUSB_DIR_OUT].max_size = 8; - xfer_status[0][TUSB_DIR_IN].max_size = 8; - - USBKEYPID = USBKEY; - - // Enable the control EP 0. Also enable Indication Enable- a guard flag - // separate from the Interrupt Enable mask. - USBOEPCNF_0 |= (UBME | USBIIE); - USBIEPCNF_0 |= (UBME | USBIIE); - - // Enable interrupts for this endpoint. - USBOEPIE |= BIT0; - USBIEPIE |= BIT0; - - // Clear NAK until a setup packet is received. - USBOEPCNT_0 &= ~NAK; - USBIEPCNT_0 &= ~NAK; - - USBCTL |= FEN; // Enable responding to packets. - - // Dedicated buffers in hardware for SETUP and EP0, no setup needed. - // Now safe to respond to SETUP packets. - USBIE |= SETUPIE; - - USBKEYPID = 0; -} - - -/*------------------------------------------------------------------*/ -/* Controller API - *------------------------------------------------------------------*/ -void dcd_init (uint8_t rhport) -{ - (void) rhport; - - USBKEYPID = USBKEY; - - // Enable the module (required to write config regs)! - USBCNF |= USB_EN; - - // Reset used interrupts - USBOEPIE = 0; - USBIEPIE = 0; - USBIE = 0; - USBOEPIFG = 0; - USBIEPIFG = 0; - USBIFG = 0; - USBPWRCTL &= ~(VUOVLIE | VBONIE | VBOFFIE | VUOVLIFG | VBONIFG | VBOFFIFG); - usboepie_mirror = 0; - usbiepie_mirror = 0; - usbie_mirror = 0; - usbpwrctl_mirror = 0; - - USBVECINT = 0; - - // Enable reset and wait for it before continuing. - USBIE |= RSTRIE; - - // Enable pullup. - USBCNF |= PUR_EN; - - USBKEYPID = 0; -} - -// There is no "USB peripheral interrupt disable" bit on MSP430, so we have -// to save the relevant registers individually. -// WARNING: Unlike the ARM/NVIC routines, these functions are _not_ idempotent -// if you modified the registers saved in between calls so they don't match -// the mirrors; mirrors will be updated to reflect most recent register -// contents. -void dcd_int_enable (uint8_t rhport) -{ - (void) rhport; - - __bic_SR_register(GIE); // Unlikely to be called in ISR, but let's be safe. - // Also, this cleanly disables all USB interrupts - // atomically from application's POV. - - // This guard is required because tinyusb can enable interrupts without - // having disabled them first. - if(in_isr) - { - USBOEPIE = usboepie_mirror; - USBIEPIE = usbiepie_mirror; - USBIE = usbie_mirror; - USBPWRCTL |= usbpwrctl_mirror; - } - - in_isr = false; - __bis_SR_register(GIE); -} - -void dcd_int_disable (uint8_t rhport) -{ - (void) rhport; - - __bic_SR_register(GIE); - usboepie_mirror = USBOEPIE; - usbiepie_mirror = USBIEPIE; - usbie_mirror = USBIE; - usbpwrctl_mirror = (USBPWRCTL & (VUOVLIE | VBONIE | VBOFFIE)); - USBOEPIE = 0; - USBIEPIE = 0; - USBIE = 0; - USBPWRCTL &= ~(VUOVLIE | VBONIE | VBOFFIE); - in_isr = true; - __bis_SR_register(GIE); -} - -void dcd_set_address (uint8_t rhport, uint8_t dev_addr) -{ - (void) rhport; - - USBFUNADR = dev_addr; - - // Response with status after changing device address - dcd_edpt_xfer(rhport, tu_edpt_addr(0, TUSB_DIR_IN), NULL, 0); -} - -void dcd_remote_wakeup(uint8_t rhport) -{ - (void) rhport; -} - -void dcd_connect(uint8_t rhport) -{ - dcd_int_disable(rhport); - - USBKEYPID = USBKEY; - USBCNF |= PUR_EN; // Enable pullup. - USBKEYPID = 0; - - dcd_int_enable(rhport); -} - -void dcd_disconnect(uint8_t rhport) -{ - dcd_int_disable(rhport); - - USBKEYPID = USBKEY; - USBCNF &= ~PUR_EN; // Disable pullup. - USBKEYPID = 0; - - dcd_int_enable(rhport); -} - -/*------------------------------------------------------------------*/ -/* DCD Endpoint port - *------------------------------------------------------------------*/ - -bool dcd_edpt_open (uint8_t rhport, tusb_desc_endpoint_t const * desc_edpt) -{ - (void) rhport; - - uint8_t const epnum = tu_edpt_number(desc_edpt->bEndpointAddress); - uint8_t const dir = tu_edpt_dir(desc_edpt->bEndpointAddress); - - // Unsupported endpoint numbers/size or type (Iso not supported. Control - // not supported on nonzero endpoints). - if((desc_edpt->wMaxPacketSize.size > 64) || (epnum > 7) || \ - (desc_edpt->bmAttributes.xfer == 0) || \ - (desc_edpt->bmAttributes.xfer == 1)) { - return false; - } - - xfer_ctl_t * xfer = XFER_CTL_BASE(epnum, dir); - xfer->max_size = desc_edpt->wMaxPacketSize.size; - - // Buffer allocation scheme: - // For simplicity, only single buffer for now, since tinyusb currently waits - // for an xfer to complete before scheduling another one. This means only - // the X buffer is used. - // - // 1904 bytes are available, the max endpoint size supported on msp430 is - // 64 bytes. This is enough RAM for all 14 endpoints enabled _with_ double - // bufferring (64*14*2 = 1792 bytes). Extra RAM exists for triple and higher - // order bufferring, which must be maintained in software. - // - // For simplicity, each endpoint gets a hardcoded 64 byte chunk (regardless - // of actual wMaxPacketSize) whose start address is the following: - // addr = 128 * (epnum - 1) + 64 * dir. - // - // Double buffering equation: - // x_addr = 256 * (epnum - 1) + 128 * dir - // y_addr = x_addr + 64 - // Address is right-shifted by 3 to fit into 8 bits. - - uint8_t buf_base = (128 * (epnum - 1) + 64 * dir) >> 3; - - // IN and OUT EP registers have the same structure. - ep_regs_t ep_regs = EP_REGS(epnum, dir); - - // FIXME: I was able to get into a situation where OUT EP 3 would stall - // while debugging, despite stall code never being called. It appears - // these registers don't get cleared on reset, being part of RAM. - // Investigate and see if I can duplicate. - // Also, DBUF got set on OUT EP 2 while debugging. Only OUT EPs seem to be - // affected at this time. USB RAM directly precedes main RAM; perhaps I'm - // overwriting registers via buffer overflow w/ my debugging code? - ep_regs[SIZXY] = desc_edpt->wMaxPacketSize.size; - ep_regs[BCTX] |= NAK; - ep_regs[BBAX] = buf_base; - ep_regs[CNF] &= ~(TOGGLE | STALL | DBUF); // ISO xfers not supported on - // MSP430, so no need to gate DATA0/1 and frame - // behavior. Clear stall and double buffer bit as - // well- see above comment. - ep_regs[CNF] |= (UBME | USBIIE); - - USBKEYPID = USBKEY; - if(dir == TUSB_DIR_OUT) - { - USBOEPIE |= (1 << epnum); - } - else - { - USBIEPIE |= (1 << epnum); - } - USBKEYPID = 0; - - return true; -} - -bool dcd_edpt_xfer (uint8_t rhport, uint8_t ep_addr, uint8_t * buffer, uint16_t total_bytes) -{ - (void) rhport; - - uint8_t const epnum = tu_edpt_number(ep_addr); - uint8_t const dir = tu_edpt_dir(ep_addr); - - xfer_ctl_t * xfer = XFER_CTL_BASE(epnum, dir); - xfer->buffer = buffer; - xfer->total_len = total_bytes; - xfer->queued_len = 0; - xfer->short_packet = false; - - if(epnum == 0) - { - if(dir == TUSB_DIR_OUT) - { - // Interrupt will notify us when data was received. - USBCTL &= ~DIR; - USBOEPCNT_0 &= ~NAK; - } - else - { - // Kickstart the IN packet handler by queuing initial data and calling - // the ISR to transmit the first packet. - // Interrupt only fires on completed xfer. - USBCTL |= DIR; - USBIEPIFG |= BIT0; - } - } - else - { - ep_regs_t ep_regs = EP_REGS(epnum, dir); - - if(dir == TUSB_DIR_OUT) - { - ep_regs[BCTX] &= ~NAK; - } - else - { - USBIEPIFG |= (1 << epnum); - } - } - - return true; -} - -void dcd_edpt_stall (uint8_t rhport, uint8_t ep_addr) -{ - (void) rhport; - - uint8_t const epnum = tu_edpt_number(ep_addr); - uint8_t const dir = tu_edpt_dir(ep_addr); - - if(epnum == 0) - { - if(dir == TUSB_DIR_OUT) - { - USBOEPCNT_0 |= NAK; - USBOEPCNF_0 |= STALL; - } - else - { - USBIEPCNT_0 |= NAK; - USBIEPCNF_0 |= STALL; - } - } - else - { - ep_regs_t ep_regs = EP_REGS(epnum, dir); - ep_regs[CNF] |= STALL; - } -} - -void dcd_edpt_clear_stall (uint8_t rhport, uint8_t ep_addr) -{ - (void) rhport; - - uint8_t const epnum = tu_edpt_number(ep_addr); - uint8_t const dir = tu_edpt_dir(ep_addr); - - if(epnum == 0) - { - if(dir == TUSB_DIR_OUT) - { - USBOEPCNF_0 &= ~STALL; - } - else - { - USBIEPCNF_0 &= ~STALL; - } - } - else - { - ep_regs_t ep_regs = EP_REGS(epnum, dir); - // Required by USB spec to reset DATA toggle bit to DATA0 on interrupt - // and bulk endpoints. - ep_regs[CNF] &= ~(STALL + TOGGLE); - } -} - -void dcd_edpt0_status_complete(uint8_t rhport, tusb_control_request_t const * request) -{ - (void) rhport; - (void) request; - - // FIXME: Per manual, we should be clearing the NAK bits of EP0 after the - // Status Phase of a control xfer is done, in preparation of another possible - // SETUP packet. However, from my own testing, SETUP packets _are_ correctly - // handled by the USB core without clearing the NAKs. - // - // Right now, clearing NAKs in this callbacks causes a direction mismatch - // between host and device on EP0. Figure out why and come back to this. - // USBOEPCNT_0 &= ~NAK; - // USBIEPCNT_0 &= ~NAK; -} - -/*------------------------------------------------------------------*/ - -static void receive_packet(uint8_t ep_num) -{ - xfer_ctl_t * xfer = XFER_CTL_BASE(ep_num, TUSB_DIR_OUT); - ep_regs_t ep_regs = EP_REGS(ep_num, TUSB_DIR_OUT); - uint8_t xfer_size; - - if(ep_num == 0) - { - xfer_size = USBOEPCNT_0 & 0x0F; - } - else - { - xfer_size = ep_regs[BCTX] & 0x7F; - } - - uint16_t remaining = xfer->total_len - xfer->queued_len; - uint16_t to_recv_size; - - if(remaining <= xfer->max_size) { - // Avoid buffer overflow. - to_recv_size = (xfer_size > remaining) ? remaining : xfer_size; - } else { - // Room for full packet, choose recv_size based on what the microcontroller - // claims. - to_recv_size = (xfer_size > xfer->max_size) ? xfer->max_size : xfer_size; - } - - uint8_t * base = (xfer->buffer + xfer->queued_len); - - if(ep_num == 0) - { - volatile uint8_t * ep0out_buf = &USBOEP0BUF; - for(uint16_t i = 0; i < to_recv_size; i++) - { - base[i] = ep0out_buf[i]; - } - } - else - { - volatile uint8_t * ep_buf = &USBSTABUFF + (ep_regs[BBAX] << 3); - for(uint16_t i = 0; i < to_recv_size ; i++) - { - base[i] = ep_buf[i]; - } - } - - xfer->queued_len += xfer_size; - - xfer->short_packet = (xfer_size < xfer->max_size); - if((xfer->total_len == xfer->queued_len) || xfer->short_packet) - { - dcd_event_xfer_complete(0, ep_num, xfer->queued_len, XFER_RESULT_SUCCESS, true); - } - else - { - // Schedule to receive another packet. - if(ep_num == 0) - { - USBOEPCNT_0 &= ~NAK; - } - else - { - ep_regs[BCTX] &= ~NAK; - } - } -} - -static void transmit_packet(uint8_t ep_num) -{ - xfer_ctl_t * xfer = XFER_CTL_BASE(ep_num, TUSB_DIR_IN); - - // First, determine whether we should even send a packet or finish - // up the xfer. - bool zlp = (xfer->total_len == 0); // By necessity, xfer->total_len will - // equal xfer->queued_len for ZLPs. - // Of course a ZLP is a short packet. - if((!zlp && (xfer->total_len == xfer->queued_len)) || xfer->short_packet) - { - dcd_event_xfer_complete(0, ep_num | TUSB_DIR_IN_MASK, xfer->queued_len, XFER_RESULT_SUCCESS, true); - return; - } - - // Then actually commit to transmit a packet. - uint8_t * base = (xfer->buffer + xfer->queued_len); - uint16_t remaining = xfer->total_len - xfer->queued_len; - uint8_t xfer_size = (xfer->max_size < xfer->total_len) ? xfer->max_size : remaining; - - xfer->queued_len += xfer_size; - if(xfer_size < xfer->max_size) - { - // Next "xfer complete interrupt", the transfer will end. - xfer->short_packet = true; - } - - if(ep_num == 0) - { - volatile uint8_t * ep0in_buf = &USBIEP0BUF; - for(uint16_t i = 0; i < xfer_size; i++) - { - ep0in_buf[i] = base[i]; - } - - USBIEPCNT_0 = (USBIEPCNT_0 & 0xF0) + xfer_size; - USBIEPCNT_0 &= ~NAK; - } - else - { - ep_regs_t ep_regs = EP_REGS(ep_num, TUSB_DIR_IN); - volatile uint8_t * ep_buf = &USBSTABUFF + (ep_regs[BBAX] << 3); - - for(int i = 0; i < xfer_size; i++) - { - ep_buf[i] = base[i]; - } - - ep_regs[BCTX] = (ep_regs[BCTX] & 0x80) + (xfer_size & 0x7F); - ep_regs[BCTX] &= ~NAK; - } -} - -static void handle_setup_packet(void) -{ - volatile uint8_t * setup_buf = &USBSUBLK; - - for(int i = 0; i < 8; i++) - { - _setup_packet[i] = setup_buf[i]; - } - - // Clearing SETUPIFG by reading USBVECINT does not set NAK, so now that we - // have a SETUP packet, force NAKs until tinyusb can handle the SETUP - // packet and prepare for a new xfer. - USBIEPCNT_0 |= NAK; - USBOEPCNT_0 |= NAK; - dcd_event_setup_received(0, (uint8_t*) &_setup_packet[0], true); -} - -void dcd_int_handler(uint8_t rhport) -{ - (void) rhport; - - // Setup is special- reading USBVECINT to handle setup packets is done to - // stop hardware-generated NAKs on EP0. - uint8_t setup_status = USBIFG & SETUPIFG; - - if(setup_status) - { - handle_setup_packet(); - } - - uint16_t curr_vector = USBVECINT; - - switch(curr_vector) - { - case USBVECINT_RSTR: - bus_reset(); - dcd_event_bus_signal(0, DCD_EVENT_BUS_RESET, true); - break; - - // Clear the (hardware-enforced) NAK on EP 0 after a SETUP packet - // is received. At this point, even though the hardware is no longer - // forcing NAKs, the EP0 NAK bits should still be set to avoid - // sending/receiving data before tinyusb is ready. - // - // Furthermore, it's possible for the hardware to STALL in the middle of - // a control xfer if the EP0 NAK bits aren't set properly. - // See: https://e2e.ti.com/support/microcontrollers/msp430/f/166/t/845259 - // From my testing, if all of the following hold: - // * OUT EP0 NAK is cleared. - // * IN EP0 NAK is set. - // * DIR bit in USBCTL is clear. - // and an IN packet is received on EP0, the USB core will STALL. Setting - // both EP0 NAKs manually when a SETUP packet is received, as is done - // in handle_setup_packet(), avoids meeting STALL conditions. - // - // TODO: Figure out/explain why the STALL condition can be reached in the - // first place. When I first noticed the STALL, the only two places I - // touched the NAK bits were in dcd_edpt_xfer() and to _set_ (sic) them in - // bus_reset(). SETUP packet handling should've been unaffected. - case USBVECINT_SETUP_PACKET_RECEIVED: - break; - - case USBVECINT_INPUT_ENDPOINT0: - transmit_packet(0); - break; - - case USBVECINT_OUTPUT_ENDPOINT0: - receive_packet(0); - break; - - case USBVECINT_INPUT_ENDPOINT1: - case USBVECINT_INPUT_ENDPOINT2: - case USBVECINT_INPUT_ENDPOINT3: - case USBVECINT_INPUT_ENDPOINT4: - case USBVECINT_INPUT_ENDPOINT5: - case USBVECINT_INPUT_ENDPOINT6: - case USBVECINT_INPUT_ENDPOINT7: - { - uint8_t ep = ((curr_vector - USBVECINT_INPUT_ENDPOINT1) >> 1) + 1; - transmit_packet(ep); - } - break; - - case USBVECINT_OUTPUT_ENDPOINT1: - case USBVECINT_OUTPUT_ENDPOINT2: - case USBVECINT_OUTPUT_ENDPOINT3: - case USBVECINT_OUTPUT_ENDPOINT4: - case USBVECINT_OUTPUT_ENDPOINT5: - case USBVECINT_OUTPUT_ENDPOINT6: - case USBVECINT_OUTPUT_ENDPOINT7: - { - uint8_t ep = ((curr_vector - USBVECINT_OUTPUT_ENDPOINT1) >> 1) + 1; - receive_packet(ep); - } - break; - - default: - while(true); - break; - } - -} - -#endif diff --git a/cores/nRF5/TinyUSB/Adafruit_TinyUSB_ArduinoCore/tinyusb/src/portable/valentyusb/eptri/dcd_eptri.c b/cores/nRF5/TinyUSB/Adafruit_TinyUSB_ArduinoCore/tinyusb/src/portable/valentyusb/eptri/dcd_eptri.c deleted file mode 100644 index d670dd3..0000000 --- a/cores/nRF5/TinyUSB/Adafruit_TinyUSB_ArduinoCore/tinyusb/src/portable/valentyusb/eptri/dcd_eptri.c +++ /dev/null @@ -1,643 +0,0 @@ -/* - * The MIT License (MIT) - * - * Copyright (c) 2019 Ha Thach (tinyusb.org) - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - * - * This file is part of the TinyUSB stack. - */ - -#ifndef DEBUG -#define DEBUG 0 -#endif - -#ifndef LOG_USB -#define LOG_USB 0 -#endif - -#include "tusb_option.h" - -#if TUSB_OPT_DEVICE_ENABLED && (CFG_TUSB_MCU == OPT_MCU_VALENTYUSB_EPTRI) - -#include "device/dcd.h" -#include "dcd_eptri.h" -#include "csr.h" -#include "irq.h" -void fomu_error(uint32_t line); - -#if LOG_USB -struct usb_log { - uint8_t ep_num; - uint8_t size; - uint8_t data[66]; -}; -__attribute__((used)) -struct usb_log usb_log[128]; -__attribute__((used)) -uint8_t usb_log_offset; - -struct xfer_log { - uint8_t ep_num; - uint16_t size; -}; -__attribute__((used)) -struct xfer_log xfer_log[64]; -__attribute__((used)) -uint8_t xfer_log_offset; - -__attribute__((used)) -struct xfer_log queue_log[64]; -__attribute__((used)) -uint8_t queue_log_offset; -#endif - -//--------------------------------------------------------------------+ -// SIE Command -//--------------------------------------------------------------------+ - -#define EP_SIZE 64 - -uint16_t volatile rx_buffer_offset[16]; -uint8_t* volatile rx_buffer[16]; -uint16_t volatile rx_buffer_max[16]; - -volatile uint8_t tx_ep; -volatile bool tx_active; -volatile uint16_t tx_buffer_offset[16]; -uint8_t* volatile tx_buffer[16]; -volatile uint16_t tx_buffer_max[16]; -volatile uint8_t reset_count; - -#if DEBUG -__attribute__((used)) uint8_t volatile * last_tx_buffer; -__attribute__((used)) volatile uint8_t last_tx_ep; -uint8_t setup_packet_bfr[10]; -#endif - -//--------------------------------------------------------------------+ -// PIPE HELPER -//--------------------------------------------------------------------+ - -static bool advance_tx_ep(void) { - // Move on to the next transmit buffer in a round-robin manner - uint8_t prev_tx_ep = tx_ep; - for (tx_ep = (tx_ep + 1) & 0xf; tx_ep != prev_tx_ep; tx_ep = ((tx_ep + 1) & 0xf)) { - if (tx_buffer[tx_ep]) - return true; - } - if (!tx_buffer[tx_ep]) - return false; - return true; -} - -#if LOG_USB -void xfer_log_append(uint8_t ep_num, uint16_t sz) { - xfer_log[xfer_log_offset].ep_num = ep_num; - xfer_log[xfer_log_offset].size = sz; - xfer_log_offset++; - if (xfer_log_offset >= sizeof(xfer_log)/sizeof(*xfer_log)) - xfer_log_offset = 0; -} - -void queue_log_append(uint8_t ep_num, uint16_t sz) { - queue_log[queue_log_offset].ep_num = ep_num; - queue_log[queue_log_offset].size = sz; - queue_log_offset++; - if (queue_log_offset >= sizeof(queue_log)/sizeof(*queue_log)) - queue_log_offset = 0; -} -#endif - -static void tx_more_data(void) { - // Send more data - uint8_t added_bytes; - for (added_bytes = 0; (added_bytes < EP_SIZE) && (tx_buffer_offset[tx_ep] < tx_buffer_max[tx_ep]); added_bytes++) { -#if LOG_USB - usb_log[usb_log_offset].data[added_bytes] = tx_buffer[tx_ep][tx_buffer_offset[tx_ep]]; -#endif - usb_in_data_write(tx_buffer[tx_ep][tx_buffer_offset[tx_ep]++]); - } - -#if LOG_USB - usb_log[usb_log_offset].ep_num = tu_edpt_addr(tx_ep, TUSB_DIR_IN); - usb_log[usb_log_offset].size = added_bytes; - usb_log_offset++; - if (usb_log_offset >= sizeof(usb_log)/sizeof(*usb_log)) - usb_log_offset = 0; -#endif - - // Updating the epno queues the data - usb_in_ctrl_write(tx_ep & 0xf); -} - -static void process_tx(void) { -#if DEBUG - // If the system isn't idle, then something is very wrong. - uint8_t in_status = usb_in_status_read(); - if (!(in_status & (1 << CSR_USB_IN_STATUS_IDLE_OFFSET))) - fomu_error(__LINE__); -#endif - - // If the buffer is now empty, search for the next buffer to fill. - if (!tx_buffer[tx_ep]) { - if (advance_tx_ep()) - tx_more_data(); - else - tx_active = false; - return; - } - - if (tx_buffer_offset[tx_ep] >= tx_buffer_max[tx_ep]) { -#if DEBUG - last_tx_buffer = tx_buffer[tx_ep]; - last_tx_ep = tx_ep; -#endif - tx_buffer[tx_ep] = NULL; - uint16_t xferred_bytes = tx_buffer_max[tx_ep]; - uint8_t xferred_ep = tx_ep; - - if (!advance_tx_ep()) - tx_active = false; -#if LOG_USB - xfer_log_append(tu_edpt_addr(xferred_ep, TUSB_DIR_IN), xferred_bytes); -#endif - dcd_event_xfer_complete(0, tu_edpt_addr(xferred_ep, TUSB_DIR_IN), xferred_bytes, XFER_RESULT_SUCCESS, true); - if (!tx_active) - return; - } - - tx_more_data(); - return; -} - -static void process_rx(void) { - uint8_t out_status = usb_out_status_read(); -#if DEBUG - // If the OUT handler is still waiting to send, don't do anything. - if (!(out_status & (1 << CSR_USB_OUT_STATUS_HAVE_OFFSET))) - fomu_error(__LINE__); - // return; -#endif - uint8_t rx_ep = (out_status >> CSR_USB_OUT_STATUS_EPNO_OFFSET) & 0xf; - - // If the destination buffer doesn't exist, don't drain the hardware - // fifo. Note that this can cause deadlocks if the host is waiting - // on some other endpoint's data! -#if DEBUG - if (rx_buffer[rx_ep] == NULL) { - fomu_error(__LINE__); - return; - } -#endif - - // Drain the FIFO into the destination buffer - uint32_t total_read = 0; - uint32_t current_offset = rx_buffer_offset[rx_ep]; -#if DEBUG - uint8_t test_buffer[256]; - memset(test_buffer, 0, sizeof(test_buffer)); - if (current_offset > rx_buffer_max[rx_ep]) - fomu_error(__LINE__); -#endif -#if LOG_USB - usb_log[usb_log_offset].ep_num = tu_edpt_addr(rx_ep, TUSB_DIR_OUT); - usb_log[usb_log_offset].size = 0; -#endif - while (usb_out_status_read() & (1 << CSR_USB_OUT_STATUS_HAVE_OFFSET)) { - uint8_t c = usb_out_data_read(); -#if DEBUG - test_buffer[total_read] = c; -#endif - total_read++; - if (current_offset < rx_buffer_max[rx_ep]) { -#if LOG_USB - usb_log[usb_log_offset].data[usb_log[usb_log_offset].size++] = c; -#endif - if (rx_buffer[rx_ep] != (volatile uint8_t *)0xffffffff) - rx_buffer[rx_ep][current_offset++] = c; - } - } -#if LOG_USB - usb_log_offset++; - if (usb_log_offset >= sizeof(usb_log)/sizeof(*usb_log)) - usb_log_offset = 0; -#endif -#if DEBUG - if (total_read > 66) - fomu_error(__LINE__); - if (total_read < 2) - total_read = 2; - // fomu_error(__LINE__); -#endif - - // Strip off the CRC16 - rx_buffer_offset[rx_ep] += (total_read - 2); - if (rx_buffer_offset[rx_ep] > rx_buffer_max[rx_ep]) - rx_buffer_offset[rx_ep] = rx_buffer_max[rx_ep]; - - // If there's no more data, complete the transfer to tinyusb - if ((rx_buffer_max[rx_ep] == rx_buffer_offset[rx_ep]) - // ZLP with less than the total amount of data - || ((total_read == 2) && ((rx_buffer_offset[rx_ep] & 63) == 0)) - // Short read, but not a full packet - || (((rx_buffer_offset[rx_ep] & 63) != 0) && (total_read < 66))) { -#if DEBUG - if (rx_buffer[rx_ep] == NULL) - fomu_error(__LINE__); -#endif - - // Free up this buffer. - rx_buffer[rx_ep] = NULL; - uint16_t len = rx_buffer_offset[rx_ep]; - -#if DEBUG - // Validate that all enabled endpoints have buffers, - // and no disabled endpoints have buffers. - uint16_t ep_en_mask = usb_out_enable_status_read(); - int i; - for (i = 0; i < 16; i++) { - if ((!!(ep_en_mask & (1 << i))) ^ (!!(rx_buffer[i]))) { - uint8_t new_status = usb_out_status_read(); - // Another IRQ came in while we were processing, so ignore this endpoint. - if ((new_status & 0x20) && ((new_status & 0xf) == i)) - continue; - fomu_error(__LINE__); - } - } -#endif -#if LOG_USB - xfer_log_append(tu_edpt_addr(rx_ep, TUSB_DIR_OUT), len); -#endif - dcd_event_xfer_complete(0, tu_edpt_addr(rx_ep, TUSB_DIR_OUT), len, XFER_RESULT_SUCCESS, true); - } - else { - // If there's more data, re-enable data reception on this endpoint - usb_out_ctrl_write((1 << CSR_USB_OUT_CTRL_ENABLE_OFFSET) | rx_ep); - } - - // Now that the buffer is drained, clear the pending IRQ. - usb_out_ev_pending_write(usb_out_ev_pending_read()); -} - -//--------------------------------------------------------------------+ -// CONTROLLER API -//--------------------------------------------------------------------+ - -static void dcd_reset(void) -{ - reset_count++; - usb_setup_ev_enable_write(0); - usb_in_ev_enable_write(0); - usb_out_ev_enable_write(0); - - usb_address_write(0); - - // Reset all three FIFO handlers - usb_setup_ctrl_write(1 << CSR_USB_SETUP_CTRL_RESET_OFFSET); - usb_in_ctrl_write(1 << CSR_USB_IN_CTRL_RESET_OFFSET); - usb_out_ctrl_write(1 << CSR_USB_OUT_CTRL_RESET_OFFSET); - - memset((void *)rx_buffer, 0, sizeof(rx_buffer)); - memset((void *)rx_buffer_max, 0, sizeof(rx_buffer_max)); - memset((void *)rx_buffer_offset, 0, sizeof(rx_buffer_offset)); - - memset((void *)tx_buffer, 0, sizeof(tx_buffer)); - memset((void *)tx_buffer_max, 0, sizeof(tx_buffer_max)); - memset((void *)tx_buffer_offset, 0, sizeof(tx_buffer_offset)); - tx_ep = 0; - tx_active = false; - - // Enable all event handlers and clear their contents - usb_setup_ev_pending_write(0xff); - usb_in_ev_pending_write(0xff); - usb_out_ev_pending_write(0xff); - usb_in_ev_enable_write(1); - usb_out_ev_enable_write(1); - usb_setup_ev_enable_write(3); - - dcd_event_bus_signal(0, DCD_EVENT_BUS_RESET, true); -} - -// Initializes the USB peripheral for device mode and enables it. -void dcd_init(uint8_t rhport) -{ - (void) rhport; - - usb_pullup_out_write(0); - - // Enable all event handlers and clear their contents - usb_setup_ev_pending_write(usb_setup_ev_pending_read()); - usb_in_ev_pending_write(usb_in_ev_pending_read()); - usb_out_ev_pending_write(usb_out_ev_pending_read()); - usb_in_ev_enable_write(1); - usb_out_ev_enable_write(1); - usb_setup_ev_enable_write(3); - - // Turn on the external pullup - usb_pullup_out_write(1); -} - -// Enables or disables the USB device interrupt(s). May be used to -// prevent concurrency issues when mutating data structures shared -// between main code and the interrupt handler. -void dcd_int_enable(uint8_t rhport) -{ - (void) rhport; - irq_setmask(irq_getmask() | (1 << USB_INTERRUPT)); -} - -void dcd_int_disable(uint8_t rhport) -{ - (void) rhport; - irq_setmask(irq_getmask() & ~(1 << USB_INTERRUPT)); -} - -// Called when the device is given a new bus address. -void dcd_set_address(uint8_t rhport, uint8_t dev_addr) -{ - // Respond with ACK status first before changing device address - dcd_edpt_xfer(rhport, tu_edpt_addr(0, TUSB_DIR_IN), NULL, 0); - - // Wait for the response packet to get sent - while (tx_active) - ; - - // Activate the new address - usb_address_write(dev_addr); -} - -// Called to remote wake up host when suspended (e.g hid keyboard) -void dcd_remote_wakeup(uint8_t rhport) -{ - (void) rhport; -} - -void dcd_connect(uint8_t rhport) -{ - (void) rhport; - usb_pullup_out_write(1); -} - -void dcd_disconnect(uint8_t rhport) -{ - (void) rhport; - usb_pullup_out_write(0); -} - - -//--------------------------------------------------------------------+ -// DCD Endpoint Port -//--------------------------------------------------------------------+ -bool dcd_edpt_open(uint8_t rhport, tusb_desc_endpoint_t const * p_endpoint_desc) -{ - (void) rhport; - uint8_t ep_num = tu_edpt_number(p_endpoint_desc->bEndpointAddress); - uint8_t ep_dir = tu_edpt_dir(p_endpoint_desc->bEndpointAddress); - - if (p_endpoint_desc->bmAttributes.xfer == TUSB_XFER_ISOCHRONOUS) - return false; // Not supported - - if (ep_dir == TUSB_DIR_OUT) { - rx_buffer_offset[ep_num] = 0; - rx_buffer_max[ep_num] = 0; - rx_buffer[ep_num] = NULL; - } - - else if (ep_dir == TUSB_DIR_IN) { - tx_buffer_offset[ep_num] = 0; - tx_buffer_max[ep_num] = 0; - tx_buffer[ep_num] = NULL; - } - - return true; -} - -void dcd_edpt_stall(uint8_t rhport, uint8_t ep_addr) -{ - (void) rhport; - - if (tu_edpt_dir(ep_addr) == TUSB_DIR_OUT) { - uint8_t enable = 0; - if (rx_buffer[ep_addr]) - enable = 1; - usb_out_ctrl_write((1 << CSR_USB_OUT_CTRL_STALL_OFFSET) | (enable << CSR_USB_OUT_CTRL_ENABLE_OFFSET) | tu_edpt_number(ep_addr)); - } - else - usb_in_ctrl_write((1 << CSR_USB_IN_CTRL_STALL_OFFSET) | tu_edpt_number(ep_addr)); -} - -void dcd_edpt_clear_stall(uint8_t rhport, uint8_t ep_addr) -{ - (void) rhport; - if (tu_edpt_dir(ep_addr) == TUSB_DIR_OUT) { - uint8_t enable = 0; - if (rx_buffer[ep_addr]) - enable = 1; - usb_out_ctrl_write((0 << CSR_USB_OUT_CTRL_STALL_OFFSET) | (enable << CSR_USB_OUT_CTRL_ENABLE_OFFSET) | tu_edpt_number(ep_addr)); - } - // IN endpoints will get unstalled when more data is written. -} - -bool dcd_edpt_xfer (uint8_t rhport, uint8_t ep_addr, uint8_t* buffer, uint16_t total_bytes) -{ - (void)rhport; - uint8_t ep_num = tu_edpt_number(ep_addr); - uint8_t ep_dir = tu_edpt_dir(ep_addr); - TU_ASSERT(ep_num < 16); - - // Give a nonzero buffer when we transmit 0 bytes, so that the - // system doesn't think the endpoint is idle. - if ((buffer == NULL) && (total_bytes == 0)) { - buffer = (uint8_t *)0xffffffff; - } - - TU_ASSERT(buffer != NULL); - - if (ep_dir == TUSB_DIR_IN) { - // Wait for the tx pipe to free up - uint8_t previous_reset_count = reset_count; - // Continue until the buffer is empty, the system is idle, and the fifo is empty. - while (tx_buffer[ep_num] != NULL) - ; - - dcd_int_disable(0); -#if LOG_USB - queue_log_append(ep_addr, total_bytes); -#endif - // If a reset happens while we're waiting, abort the transfer - if (previous_reset_count != reset_count) - return true; - - TU_ASSERT(tx_buffer[ep_num] == NULL); - tx_buffer_offset[ep_num] = 0; - tx_buffer_max[ep_num] = total_bytes; - tx_buffer[ep_num] = buffer; - - // If the current buffer is NULL, then that means the tx logic is idle. - // Update the tx_ep to point to our endpoint number and queue the data. - // Otherwise, let it be and it'll get picked up after the next transfer - // finishes. - if (!tx_active) { - tx_ep = ep_num; - tx_active = true; - tx_more_data(); - } - dcd_int_enable(0); - } - - else if (ep_dir == TUSB_DIR_OUT) { - while (rx_buffer[ep_num] != NULL) - ; - - TU_ASSERT(rx_buffer[ep_num] == NULL); - dcd_int_disable(0); -#if LOG_USB - queue_log_append(ep_addr, total_bytes); -#endif - rx_buffer[ep_num] = buffer; - rx_buffer_offset[ep_num] = 0; - rx_buffer_max[ep_num] = total_bytes; - - // Enable receiving on this particular endpoint - usb_out_ctrl_write((1 << CSR_USB_OUT_CTRL_ENABLE_OFFSET) | ep_num); -#if DEBUG - uint16_t ep_en_mask = usb_out_enable_status_read(); - int i; - for (i = 0; i < 16; i++) { - if ((!!(ep_en_mask & (1 << i))) ^ (!!(rx_buffer[i]))) { - if (rx_buffer[i] && usb_out_ev_pending_read() && (usb_out_status_read() & 0xf) == i) - continue; - fomu_error(__LINE__); - } - } -#endif - dcd_int_enable(0); - } - return true; -} - -//--------------------------------------------------------------------+ -// ISR -//--------------------------------------------------------------------+ - -static void handle_out(void) -{ - // An "OUT" transaction just completed so we have new data. - // (But only if we can accept the data) -#if DEBUG - if (!usb_out_ev_pending_read()) - fomu_error(__LINE__); - if (!usb_out_ev_enable_read()) - fomu_error(__LINE__); -#endif - process_rx(); -} - -static void handle_in(void) -{ -#if DEBUG - if (!usb_in_ev_pending_read()) - fomu_error(__LINE__); - if (!usb_in_ev_enable_read()) - fomu_error(__LINE__); -#endif - usb_in_ev_pending_write(usb_in_ev_pending_read()); - process_tx(); -} - -static void handle_reset(void) -{ -#if DEBUG - uint8_t setup_pending = usb_setup_ev_pending_read() & usb_setup_ev_enable_read(); - if (!(setup_pending & 2)) - fomu_error(__LINE__); -#endif - usb_setup_ev_pending_write(2); - - // This event means a bus reset occurred. Reset everything, and - // abandon any further processing. - dcd_reset(); -} - -static void handle_setup(void) -{ -#if !DEBUG - uint8_t setup_packet_bfr[10]; -#endif - -#if DEBUG - uint8_t setup_pending = usb_setup_ev_pending_read() & usb_setup_ev_enable_read(); - if (!(setup_pending & 1)) - fomu_error(__LINE__); -#endif - - // We got a SETUP packet. Copy it to the setup buffer and clear - // the "pending" bit. - // Setup packets are always 8 bytes, plus two bytes of crc16. - uint32_t setup_length = 0; - -#if DEBUG - if (!(usb_setup_status_read() & (1 << CSR_USB_SETUP_STATUS_HAVE_OFFSET))) - fomu_error(__LINE__); -#endif - - while (usb_setup_status_read() & (1 << CSR_USB_SETUP_STATUS_HAVE_OFFSET)) { - uint8_t c = usb_setup_data_read(); - if (setup_length < sizeof(setup_packet_bfr)) - setup_packet_bfr[setup_length] = c; - setup_length++; - } - - // If we have 10 bytes, that's a full SETUP packet plus CRC16. - // Otherwise, it was an RX error. - if (setup_length == 10) { - dcd_event_setup_received(0, setup_packet_bfr, true); - } -#if DEBUG - else { - fomu_error(__LINE__); - } -#endif - - usb_setup_ev_pending_write(1); -} -void dcd_int_handler(uint8_t rhport) -{ - (void)rhport; - uint8_t next_ev; - while ((next_ev = usb_next_ev_read())) { - switch (next_ev) { - case 1 << CSR_USB_NEXT_EV_IN_OFFSET: - handle_in(); - break; - case 1 << CSR_USB_NEXT_EV_OUT_OFFSET: - handle_out(); - break; - case 1 << CSR_USB_NEXT_EV_SETUP_OFFSET: - handle_setup(); - break; - case 1 << CSR_USB_NEXT_EV_RESET_OFFSET: - handle_reset(); - break; - } - } -} - -#endif diff --git a/cores/nRF5/TinyUSB/Adafruit_TinyUSB_ArduinoCore/tinyusb/src/tusb.c b/cores/nRF5/TinyUSB/Adafruit_TinyUSB_ArduinoCore/tinyusb/src/tusb.c index 1bc134d..083e6d8 100644 --- a/cores/nRF5/TinyUSB/Adafruit_TinyUSB_ArduinoCore/tinyusb/src/tusb.c +++ b/cores/nRF5/TinyUSB/Adafruit_TinyUSB_ArduinoCore/tinyusb/src/tusb.c @@ -26,101 +26,563 @@ #include "tusb_option.h" -#if TUSB_OPT_HOST_ENABLED || TUSB_OPT_DEVICE_ENABLED +#if CFG_TUH_ENABLED || CFG_TUD_ENABLED #include "tusb.h" +#include "common/tusb_private.h" -static bool _initialized = false; - -// TODO clean up -#if TUSB_OPT_DEVICE_ENABLED +#if CFG_TUD_ENABLED #include "device/usbd_pvt.h" #endif -bool tusb_init(void) -{ - // skip if already initialized - if (_initialized) return true; - -#if TUSB_OPT_HOST_ENABLED - TU_ASSERT( usbh_init() ); // init host stack +#if CFG_TUH_ENABLED +#include "host/usbh_pvt.h" #endif -#if TUSB_OPT_DEVICE_ENABLED - TU_ASSERT ( tud_init() ); // init device stack +tusb_role_t _tusb_rhport_role[TUP_USBIP_CONTROLLER_NUM] = { TUSB_ROLE_INVALID }; + +//-------------------------------------------------------------------- +// Weak/Default API, can be overwritten by Application +//-------------------------------------------------------------------- + +TU_ATTR_WEAK void tusb_time_delay_ms_api(uint32_t ms) { +#if CFG_TUSB_OS != OPT_OS_NONE + osal_task_delay(ms); +#else + // delay using millis() (if implemented) and/or frame number if possible + const uint32_t time_ms = tusb_time_millis_api(); + while ((tusb_time_millis_api() - time_ms) < ms) {} #endif +} + +TU_ATTR_WEAK void* tusb_app_virt_to_phys(void *virt_addr) { + return virt_addr; +} + +TU_ATTR_WEAK void* tusb_app_phys_to_virt(void *phys_addr) { + return phys_addr; +} + +//--------------------------------------------------------------------+ +// Public API +//--------------------------------------------------------------------+ +bool tusb_rhport_init(uint8_t rhport, const tusb_rhport_init_t* rh_init) { + // backward compatible called with tusb_init(void) + #if defined(TUD_OPT_RHPORT) || defined(TUH_OPT_RHPORT) + if (rh_init == NULL) { + #if CFG_TUD_ENABLED && defined(TUD_OPT_RHPORT) + // init device stack CFG_TUSB_RHPORTx_MODE must be defined + const tusb_rhport_init_t dev_init = { + .role = TUSB_ROLE_DEVICE, + .speed = TUD_OPT_HIGH_SPEED ? TUSB_SPEED_HIGH : TUSB_SPEED_FULL + }; + TU_ASSERT ( tud_rhport_init(TUD_OPT_RHPORT, &dev_init) ); + _tusb_rhport_role[TUD_OPT_RHPORT] = TUSB_ROLE_DEVICE; + #endif + + #if CFG_TUH_ENABLED && defined(TUH_OPT_RHPORT) + // init host stack CFG_TUSB_RHPORTx_MODE must be defined + const tusb_rhport_init_t host_init = { + .role = TUSB_ROLE_HOST, + .speed = TUH_OPT_HIGH_SPEED ? TUSB_SPEED_HIGH : TUSB_SPEED_FULL + }; + TU_ASSERT( tuh_rhport_init(TUH_OPT_RHPORT, &host_init) ); + _tusb_rhport_role[TUH_OPT_RHPORT] = TUSB_ROLE_HOST; + #endif + + return true; + } + #endif + + // new API with explicit rhport and role + TU_ASSERT(rhport < TUP_USBIP_CONTROLLER_NUM && rh_init->role != TUSB_ROLE_INVALID); + _tusb_rhport_role[rhport] = rh_init->role; + + #if CFG_TUD_ENABLED + if (rh_init->role == TUSB_ROLE_DEVICE) { + TU_ASSERT(tud_rhport_init(rhport, rh_init)); + } + #endif + + #if CFG_TUH_ENABLED + if (rh_init->role == TUSB_ROLE_HOST) { + TU_ASSERT(tuh_rhport_init(rhport, rh_init)); + } + #endif + + return true; +} + +bool tusb_inited(void) { + bool ret = false; + + #if CFG_TUD_ENABLED + ret = ret || tud_inited(); + #endif + + #if CFG_TUH_ENABLED + ret = ret || tuh_inited(); + #endif + + return ret; +} + +void tusb_int_handler(uint8_t rhport, bool in_isr) { + TU_VERIFY(rhport < TUP_USBIP_CONTROLLER_NUM,); + + #if CFG_TUD_ENABLED + if (_tusb_rhport_role[rhport] == TUSB_ROLE_DEVICE) { + (void) in_isr; + dcd_int_handler(rhport); + } + #endif + + #if CFG_TUH_ENABLED + if (_tusb_rhport_role[rhport] == TUSB_ROLE_HOST) { + hcd_int_handler(rhport, in_isr); + } + #endif +} + +bool tusb_deinit(uint8_t rhport) { + TU_VERIFY(rhport < TUP_USBIP_CONTROLLER_NUM); + bool ret = false; + + #if CFG_TUD_ENABLED + if (_tusb_rhport_role[rhport] == TUSB_ROLE_DEVICE) { + TU_ASSERT(tud_deinit(rhport)); + _tusb_rhport_role[rhport] = TUSB_ROLE_INVALID; + ret = true; + } + #endif + + #if CFG_TUH_ENABLED + if (_tusb_rhport_role[rhport] == TUSB_ROLE_HOST) { + TU_ASSERT(tuh_deinit(rhport)); + _tusb_rhport_role[rhport] = TUSB_ROLE_INVALID; + ret = true; + } + #endif + + return ret; +} + +//--------------------------------------------------------------------+ +// Descriptor helper +//--------------------------------------------------------------------+ + +uint8_t const* tu_desc_find(uint8_t const* desc, uint8_t const* end, uint8_t byte1) { + while (desc + 1 < end) { + if (desc[1] == byte1) { + return desc; + } + desc += desc[DESC_OFFSET_LEN]; + } + return NULL; +} + +uint8_t const* tu_desc_find2(uint8_t const* desc, uint8_t const* end, uint8_t byte1, uint8_t byte2) { + while (desc + 2 < end) { + if (desc[1] == byte1 && desc[2] == byte2) { + return desc; + } + desc += desc[DESC_OFFSET_LEN]; + } + return NULL; +} + +uint8_t const* tu_desc_find3(uint8_t const* desc, uint8_t const* end, uint8_t byte1, uint8_t byte2, uint8_t byte3) { + while (desc + 3 < end) { + if (desc[1] == byte1 && desc[2] == byte2 && desc[3] == byte3) { + return desc; + } + desc += desc[DESC_OFFSET_LEN]; + } + return NULL; +} + +//--------------------------------------------------------------------+ +// Endpoint Helper for both Host and Device stack +//--------------------------------------------------------------------+ + +bool tu_edpt_claim(tu_edpt_state_t* ep_state, osal_mutex_t mutex) { + (void) mutex; + + // pre-check to help reducing mutex lock + TU_VERIFY((ep_state->busy == 0) && (ep_state->claimed == 0)); + (void) osal_mutex_lock(mutex, OSAL_TIMEOUT_WAIT_FOREVER); + + // can only claim the endpoint if it is not busy and not claimed yet. + bool const available = (ep_state->busy == 0) && (ep_state->claimed == 0); + if (available) { + ep_state->claimed = 1; + } + + (void) osal_mutex_unlock(mutex); + return available; +} + +bool tu_edpt_release(tu_edpt_state_t* ep_state, osal_mutex_t mutex) { + (void) mutex; + (void) osal_mutex_lock(mutex, OSAL_TIMEOUT_WAIT_FOREVER); + + // can only release the endpoint if it is claimed and not busy + bool const ret = (ep_state->claimed == 1) && (ep_state->busy == 0); + if (ret) { + ep_state->claimed = 0; + } + + (void) osal_mutex_unlock(mutex); + return ret; +} + +bool tu_edpt_validate(tusb_desc_endpoint_t const* desc_ep, tusb_speed_t speed, bool is_host) { + uint16_t const max_packet_size = tu_edpt_packet_size(desc_ep); + TU_LOG2(" Open EP %02X with Size = %u\r\n", desc_ep->bEndpointAddress, max_packet_size); + + switch (desc_ep->bmAttributes.xfer) { + case TUSB_XFER_ISOCHRONOUS: { + uint16_t const spec_size = (speed == TUSB_SPEED_HIGH ? 1024 : 1023); + TU_ASSERT(max_packet_size <= spec_size); + break; + } + + case TUSB_XFER_BULK: + if (speed == TUSB_SPEED_HIGH) { + // Bulk highspeed must be EXACTLY 512 + TU_ASSERT(max_packet_size == 512); + } else { + // Bulk fullspeed can only be 8, 16, 32, 64 + if (is_host && max_packet_size == 512) { + // HACK: while in host mode, some device incorrectly always report 512 regardless of link speed + // overwrite descriptor to force 64 + TU_LOG1(" WARN: EP max packet size is 512 in fullspeed, force to 64\r\n"); + tusb_desc_endpoint_t* hacked_ep = (tusb_desc_endpoint_t*) (uintptr_t) desc_ep; + hacked_ep->wMaxPacketSize = tu_htole16(64); + } else { + TU_ASSERT(max_packet_size == 8 || max_packet_size == 16 || + max_packet_size == 32 || max_packet_size == 64); + } + } + break; + + case TUSB_XFER_INTERRUPT: { + uint16_t const spec_size = (speed == TUSB_SPEED_HIGH ? 1024 : 64); + TU_ASSERT(max_packet_size <= spec_size); + break; + } + + default: + return false; + } + + return true; +} + +void tu_edpt_bind_driver(uint8_t ep2drv[][2], tusb_desc_interface_t const* desc_itf, uint16_t desc_len, + uint8_t driver_id) { + uint8_t const* p_desc = (uint8_t const*) desc_itf; + uint8_t const* desc_end = p_desc + desc_len; + + while (p_desc < desc_end) { + if (TUSB_DESC_ENDPOINT == tu_desc_type(p_desc)) { + uint8_t const ep_addr = ((tusb_desc_endpoint_t const*) p_desc)->bEndpointAddress; + TU_LOG(2, " Bind EP %02x to driver id %u\r\n", ep_addr, driver_id); + ep2drv[tu_edpt_number(ep_addr)][tu_edpt_dir(ep_addr)] = driver_id; + } + p_desc = tu_desc_next(p_desc); + } +} + +uint16_t tu_desc_get_interface_total_len(tusb_desc_interface_t const* desc_itf, uint8_t itf_count, uint16_t max_len) { + uint8_t const* p_desc = (uint8_t const*) desc_itf; + uint16_t len = 0; + + while (itf_count--) { + // Next on interface desc + len += tu_desc_len(desc_itf); + p_desc = tu_desc_next(p_desc); + + while (len < max_len) { + if (tu_desc_len(p_desc) == 0) { + // Escape infinite loop + break; + } + // return on IAD regardless of itf count + if (tu_desc_type(p_desc) == TUSB_DESC_INTERFACE_ASSOCIATION) { + return len; + } + if ((tu_desc_type(p_desc) == TUSB_DESC_INTERFACE) && + ((tusb_desc_interface_t const*) p_desc)->bAlternateSetting == 0) { + break; + } + + len += tu_desc_len(p_desc); + p_desc = tu_desc_next(p_desc); + } + } + + return len; +} + +//--------------------------------------------------------------------+ +// Endpoint Stream Helper for both Host and Device stack +//--------------------------------------------------------------------+ + +bool tu_edpt_stream_init(tu_edpt_stream_t* s, bool is_host, bool is_tx, bool overwritable, + void* ff_buf, uint16_t ff_bufsize, uint8_t* ep_buf, uint16_t ep_bufsize) { + (void) is_tx; + + s->is_host = is_host; + tu_fifo_config(&s->ff, ff_buf, ff_bufsize, 1, overwritable); + + #if OSAL_MUTEX_REQUIRED + if (ff_buf && ff_bufsize) { + osal_mutex_t new_mutex = osal_mutex_create(&s->ff_mutexdef); + tu_fifo_config_mutex(&s->ff, is_tx ? new_mutex : NULL, is_tx ? NULL : new_mutex); + } + #endif + + s->ep_buf = ep_buf; + s->ep_bufsize = ep_bufsize; + + return true; +} - _initialized = true; +bool tu_edpt_stream_deinit(tu_edpt_stream_t* s) { + (void) s; + #if OSAL_MUTEX_REQUIRED + if (s->ff.mutex_wr) osal_mutex_delete(s->ff.mutex_wr); + if (s->ff.mutex_rd) osal_mutex_delete(s->ff.mutex_rd); + #endif + return true; +} - return TUSB_ERROR_NONE; +TU_ATTR_ALWAYS_INLINE static inline bool stream_claim(uint8_t hwid, tu_edpt_stream_t* s) { + if (s->is_host) { + #if CFG_TUH_ENABLED + return usbh_edpt_claim(hwid, s->ep_addr); + #endif + } else { + #if CFG_TUD_ENABLED + return usbd_edpt_claim(hwid, s->ep_addr); + #endif + } + return false; } -bool tusb_inited(void) -{ - return _initialized; +TU_ATTR_ALWAYS_INLINE static inline bool stream_xfer(uint8_t hwid, tu_edpt_stream_t* s, uint16_t count) { + if (s->is_host) { + #if CFG_TUH_ENABLED + return usbh_edpt_xfer(hwid, s->ep_addr, count ? s->ep_buf : NULL, count); + #endif + } else { + #if CFG_TUD_ENABLED + return usbd_edpt_xfer(hwid, s->ep_addr, count ? s->ep_buf : NULL, count); + #endif + } + return false; } -/*------------------------------------------------------------------*/ -/* Debug - *------------------------------------------------------------------*/ +TU_ATTR_ALWAYS_INLINE static inline bool stream_release(uint8_t hwid, tu_edpt_stream_t* s) { + if (s->is_host) { + #if CFG_TUH_ENABLED + return usbh_edpt_release(hwid, s->ep_addr); + #endif + } else { + #if CFG_TUD_ENABLED + return usbd_edpt_release(hwid, s->ep_addr); + #endif + } + return false; +} + +//--------------------------------------------------------------------+ +// Stream Write +//--------------------------------------------------------------------+ +bool tu_edpt_stream_write_zlp_if_needed(uint8_t hwid, tu_edpt_stream_t* s, uint32_t last_xferred_bytes) { + // ZLP condition: no pending data, last transferred bytes is multiple of packet size + const uint16_t mps = s->is_mps512 ? TUSB_EPSIZE_BULK_HS : TUSB_EPSIZE_BULK_FS; + TU_VERIFY(!tu_fifo_count(&s->ff) && last_xferred_bytes && (0 == (last_xferred_bytes & (mps - 1)))); + TU_VERIFY(stream_claim(hwid, s)); + TU_ASSERT(stream_xfer(hwid, s, 0)); + return true; +} + +uint32_t tu_edpt_stream_write_xfer(uint8_t hwid, tu_edpt_stream_t* s) { + // skip if no data + TU_VERIFY(tu_fifo_count(&s->ff), 0); + + TU_VERIFY(stream_claim(hwid, s), 0); + + // Pull data from FIFO -> EP buf + uint16_t const count = tu_fifo_read_n(&s->ff, s->ep_buf, s->ep_bufsize); + + if (count) { + TU_ASSERT(stream_xfer(hwid, s, count), 0); + return count; + } else { + // Release endpoint since we don't make any transfer + // Note: data is dropped if terminal is not connected + stream_release(hwid, s); + return 0; + } +} + +uint32_t tu_edpt_stream_write(uint8_t hwid, tu_edpt_stream_t* s, void const* buffer, uint32_t bufsize) { + TU_VERIFY(bufsize); // TODO support ZLP + + if (0 == tu_fifo_depth(&s->ff)) { + // no fifo for buffered + TU_VERIFY(stream_claim(hwid, s), 0); + const uint32_t xact_len = tu_min32(bufsize, s->ep_bufsize); + memcpy(s->ep_buf, buffer, xact_len); + TU_ASSERT(stream_xfer(hwid, s, (uint16_t) xact_len), 0); + return xact_len; + } else { + const uint16_t ret = tu_fifo_write_n(&s->ff, buffer, (uint16_t) bufsize); + + // flush if fifo has more than packet size or + // in rare case: fifo depth is configured too small (which never reach packet size) + const uint16_t mps = s->is_mps512 ? TUSB_EPSIZE_BULK_HS : TUSB_EPSIZE_BULK_FS; + if ((tu_fifo_count(&s->ff) >= mps) || (tu_fifo_depth(&s->ff) < mps)) { + tu_edpt_stream_write_xfer(hwid, s); + } + return ret; + } +} + +uint32_t tu_edpt_stream_write_available(uint8_t hwid, tu_edpt_stream_t* s) { + if (tu_fifo_depth(&s->ff)) { + return (uint32_t) tu_fifo_remaining(&s->ff); + } else { + bool is_busy = true; + if (s->is_host) { + #if CFG_TUH_ENABLED + is_busy = usbh_edpt_busy(hwid, s->ep_addr); + #endif + } else { + #if CFG_TUD_ENABLED + is_busy = usbd_edpt_busy(hwid, s->ep_addr); + #endif + } + return is_busy ? 0 : s->ep_bufsize; + } +} + +//--------------------------------------------------------------------+ +// Stream Read +//--------------------------------------------------------------------+ +uint32_t tu_edpt_stream_read_xfer(uint8_t hwid, tu_edpt_stream_t* s) { + if (0 == tu_fifo_depth(&s->ff)) { + // no fifo for buffered + TU_VERIFY(stream_claim(hwid, s), 0); + TU_ASSERT(stream_xfer(hwid, s, s->ep_bufsize), 0); + return s->ep_bufsize; + } else { + const uint16_t mps = s->is_mps512 ? TUSB_EPSIZE_BULK_HS : TUSB_EPSIZE_BULK_FS; + uint16_t available = tu_fifo_remaining(&s->ff); + + // Prepare for incoming data but only allow what we can store in the ring buffer. + // TODO Actually we can still carry out the transfer, keeping count of received bytes + // and slowly move it to the FIFO when read(). + // This pre-check reduces endpoint claiming + TU_VERIFY(available >= mps); + + TU_VERIFY(stream_claim(hwid, s), 0); + + // get available again since fifo can be changed before endpoint is claimed + available = tu_fifo_remaining(&s->ff); + + if (available >= mps) { + // multiple of packet size limit by ep bufsize + uint16_t count = (uint16_t) (available & ~(mps - 1)); + count = tu_min16(count, s->ep_bufsize); + TU_ASSERT(stream_xfer(hwid, s, count), 0); + return count; + } else { + // Release endpoint since we don't make any transfer + stream_release(hwid, s); + return 0; + } + } +} + +uint32_t tu_edpt_stream_read(uint8_t hwid, tu_edpt_stream_t* s, void* buffer, uint32_t bufsize) { + uint32_t num_read = tu_fifo_read_n(&s->ff, buffer, (uint16_t) bufsize); + tu_edpt_stream_read_xfer(hwid, s); + return num_read; +} + +//--------------------------------------------------------------------+ +// Debug +//--------------------------------------------------------------------+ + #if CFG_TUSB_DEBUG #include -char const* const tusb_strerr[TUSB_ERROR_COUNT] = { ERROR_TABLE(ERROR_STRING) }; +#if CFG_TUSB_DEBUG >= CFG_TUH_LOG_LEVEL || CFG_TUSB_DEBUG >= CFG_TUD_LOG_LEVEL +char const* const tu_str_speed[] = {"Full", "Low", "High"}; +char const* const tu_str_std_request[] = { + "Get Status", + "Clear Feature", + "Reserved", + "Set Feature", + "Reserved", + "Set Address", + "Get Descriptor", + "Set Descriptor", + "Get Configuration", + "Set Configuration", + "Get Interface", + "Set Interface", + "Synch Frame" +}; -static void dump_str_line(uint8_t const* buf, uint16_t count) -{ +char const* const tu_str_xfer_result[] = { + "OK", "FAILED", "STALLED", "TIMEOUT" +}; +#endif + +static void dump_str_line(uint8_t const* buf, uint16_t count) { + tu_printf(" |"); // each line is 16 bytes - for(uint16_t i=0; i CFG_TUSB_DEBUG) +// #define CFG_TUD_LOG_LEVEL 3 +// #define CFG_TUH_LOG_LEVEL 3 + +#define CFG_TUSB_MEM_SECTION +#define CFG_TUSB_MEM_ALIGN __attribute__((aligned(4))) + +#ifdef USE_TINYUSB + +// Enable device stack +#define CFG_TUD_ENABLED 1 + +// Enable host stack with MAX3421E (host shield) +#define CFG_TUH_ENABLED 1 +#define CFG_TUH_MAX3421 1 + +#else +#define CFG_TUD_ENABLED 0 +#define CFG_TUH_ENABLED 0 +#endif + +//-------------------------------------------------------------------- +// DEVICE CONFIGURATION +//-------------------------------------------------------------------- + +#if CFG_TUD_ENABLED +#define CFG_TUD_ENDPOINT0_SIZE 64 + +//------------- CLASS -------------// +#ifndef CFG_TUD_CDC +#define CFG_TUD_CDC 1 +#endif +#ifndef CFG_TUD_MSC +#define CFG_TUD_MSC 1 +#endif +#ifndef CFG_TUD_HID +#define CFG_TUD_HID 2 +#endif +#ifndef CFG_TUD_MIDI +#define CFG_TUD_MIDI 1 +#endif +#ifndef CFG_TUD_VENDOR +#define CFG_TUD_VENDOR 1 +#endif +#ifndef CFG_TUD_VIDEO +#define CFG_TUD_VIDEO 1 // number of video control interfaces +#endif +#ifndef CFG_TUD_VIDEO_STREAMING +#define CFG_TUD_VIDEO_STREAMING 1 // number of video streaming interfaces +#endif + +// video streaming endpoint buffer size +#define CFG_TUD_VIDEO_STREAMING_EP_BUFSIZE 256 + +// CDC FIFO size of TX and RX +#define CFG_TUD_CDC_RX_BUFSIZE 256 +#define CFG_TUD_CDC_TX_BUFSIZE 256 + +// MSC Buffer size of Device Mass storage +#define CFG_TUD_MSC_EP_BUFSIZE 512 + +// HID buffer size Should be sufficient to hold ID (if any) + Data +#define CFG_TUD_HID_EP_BUFSIZE 64 + +// MIDI FIFO size of TX and RX +#define CFG_TUD_MIDI_RX_BUFSIZE 128 +#define CFG_TUD_MIDI_TX_BUFSIZE 128 + +// Vendor FIFO size of TX and RX +#ifndef CFG_TUD_VENDOR_RX_BUFSIZE +#define CFG_TUD_VENDOR_RX_BUFSIZE 64 +#endif + +#ifndef CFG_TUD_VENDOR_TX_BUFSIZE +#define CFG_TUD_VENDOR_TX_BUFSIZE 64 +#endif + +#endif + +//-------------------------------------------------------------------- +// Host Configuration +//-------------------------------------------------------------------- + +#if CFG_TUH_ENABLED + +// Size of buffer to hold descriptors and other data used for enumeration +#define CFG_TUH_ENUMERATION_BUFSIZE 256 + +// Number of hub devices +#define CFG_TUH_HUB 1 + +// max device support (excluding hub device): 1 hub typically has 4 ports +#define CFG_TUH_DEVICE_MAX (3 * CFG_TUH_HUB + 1) + +// Enable tuh_edpt_xfer() API +// #define CFG_TUH_API_EDPT_XFER 1 + +// Number of mass storage +#define CFG_TUH_MSC 1 + +// Number of HIDs +// typical keyboard + mouse device can have 3,4 HID interfaces +#define CFG_TUH_HID (3 * CFG_TUH_DEVICE_MAX) + +// Number of CDC interfaces +// FTDI and CP210x are not part of CDC class, only to re-use CDC driver API +#define CFG_TUH_CDC 1 +#define CFG_TUH_CDC_FTDI 1 +#define CFG_TUH_CDC_CP210X 1 +#define CFG_TUH_CDC_CH34X 1 + +// RX & TX fifo size +#define CFG_TUH_CDC_RX_BUFSIZE 64 +#define CFG_TUH_CDC_TX_BUFSIZE 64 + +// Set Line Control state on enumeration/mounted: +// DTR ( bit 0), RTS (bit 1) +#define CFG_TUH_CDC_LINE_CONTROL_ON_ENUM 0x03 + +// Set Line Coding on enumeration/mounted, value for cdc_line_coding_t +// bit rate = 115200, 1 stop bit, no parity, 8 bit data width +// This need Pico-PIO-USB at least 0.5.1 +#define CFG_TUH_CDC_LINE_CODING_ON_ENUM \ + { 115200, CDC_LINE_CONDING_STOP_BITS_1, CDC_LINE_CODING_PARITY_NONE, 8 } + +#endif + +// Debug TinyUSB with Serial1 +#if CFG_TUSB_DEBUG +#define CFG_TUSB_DEBUG_PRINTF log_printf +#endif + +#ifdef __cplusplus + } +#endif + +#endif diff --git a/cores/nRF5/TinyUSB/Adafruit_TinyUSB_ArduinoCore/tinyusb/src/tusb_option.h b/cores/nRF5/TinyUSB/Adafruit_TinyUSB_ArduinoCore/tinyusb/src/tusb_option.h index 7e486bf..7fbaf1c 100644 --- a/cores/nRF5/TinyUSB/Adafruit_TinyUSB_ArduinoCore/tinyusb/src/tusb_option.h +++ b/cores/nRF5/TinyUSB/Adafruit_TinyUSB_ArduinoCore/tinyusb/src/tusb_option.h @@ -27,14 +27,20 @@ #ifndef _TUSB_OPTION_H_ #define _TUSB_OPTION_H_ +#include "common/tusb_compiler.h" + +// Version is release as major.minor.revision eg 1.0.0 #define TUSB_VERSION_MAJOR 0 -#define TUSB_VERSION_MINOR 5 +#define TUSB_VERSION_MINOR 19 #define TUSB_VERSION_REVISION 0 -#define TUSB_VERSION_STRING TU_STRING(TUSB_VERSION_MAJOR) "." TU_STRING(TUSB_VERSION_MINOR) "." TU_STRING(TUSB_VERSION_REVISION) -/** \defgroup group_mcu Supported MCU - * \ref CFG_TUSB_MCU must be defined to one of these - * @{ */ +#define TUSB_VERSION_NUMBER (TUSB_VERSION_MAJOR * 10000 + TUSB_VERSION_MINOR * 100 + TUSB_VERSION_REVISION) +#define TUSB_VERSION_STRING TU_XSTRING(TUSB_VERSION_MAJOR) "." TU_XSTRING(TUSB_VERSION_MINOR) "." TU_XSTRING(TUSB_VERSION_REVISION) + +//--------------------------------------------------------------------+ +// Supported MCUs +// CFG_TUSB_MCU must be defined to one of following value +//--------------------------------------------------------------------+ #define OPT_MCU_NONE 0 @@ -47,43 +53,66 @@ #define OPT_MCU_LPC18XX 6 ///< NXP LPC18xx #define OPT_MCU_LPC40XX 7 ///< NXP LPC40xx #define OPT_MCU_LPC43XX 8 ///< NXP LPC43xx -#define OPT_MCU_LPC51UXX 9 ///< NXP LPC51U6x -#define OPT_MCU_LPC54XXX 10 ///< NXP LPC54xxx -#define OPT_MCU_LPC55XX 11 ///< NXP LPC55xx +#define OPT_MCU_LPC51 9 ///< NXP LPC51 +#define OPT_MCU_LPC51UXX OPT_MCU_LPC51 ///< NXP LPC51 +#define OPT_MCU_LPC54 10 ///< NXP LPC54 +#define OPT_MCU_LPC55 11 ///< NXP LPC55 +// legacy naming +#define OPT_MCU_LPC54XXX OPT_MCU_LPC54 +#define OPT_MCU_LPC55XX OPT_MCU_LPC55 // NRF #define OPT_MCU_NRF5X 100 ///< Nordic nRF5x series // SAM -#define OPT_MCU_SAMD11 204 ///< MicroChip SAMD11 #define OPT_MCU_SAMD21 200 ///< MicroChip SAMD21 #define OPT_MCU_SAMD51 201 ///< MicroChip SAMD51 -#define OPT_MCU_SAME5X 203 ///< MicroChip SAM E5x #define OPT_MCU_SAMG 202 ///< MicroChip SAMDG series +#define OPT_MCU_SAME5X 203 ///< MicroChip SAM E5x +#define OPT_MCU_SAMD11 204 ///< MicroChip SAMD11 +#define OPT_MCU_SAML22 205 ///< MicroChip SAML22 +#define OPT_MCU_SAML21 206 ///< MicroChip SAML21 +#define OPT_MCU_SAMX7X 207 ///< MicroChip SAME70, S70, V70, V71 family // STM32 -#define OPT_MCU_STM32F0 300 ///< ST STM32F0 -#define OPT_MCU_STM32F1 301 ///< ST STM32F1 -#define OPT_MCU_STM32F2 302 ///< ST STM32F2 -#define OPT_MCU_STM32F3 303 ///< ST STM32F3 -#define OPT_MCU_STM32F4 304 ///< ST STM32F4 -#define OPT_MCU_STM32F7 305 ///< ST STM32F7 -#define OPT_MCU_STM32H7 306 ///< ST STM32H7 -#define OPT_MCU_STM32L0 307 ///< ST STM32L0 -#define OPT_MCU_STM32L1 308 ///< ST STM32L1 -#define OPT_MCU_STM32L4 309 ///< ST STM32L4 +#define OPT_MCU_STM32F0 300 ///< ST F0 +#define OPT_MCU_STM32F1 301 ///< ST F1 +#define OPT_MCU_STM32F2 302 ///< ST F2 +#define OPT_MCU_STM32F3 303 ///< ST F3 +#define OPT_MCU_STM32F4 304 ///< ST F4 +#define OPT_MCU_STM32F7 305 ///< ST F7 +#define OPT_MCU_STM32H7 306 ///< ST H7 +#define OPT_MCU_STM32L1 308 ///< ST L1 +#define OPT_MCU_STM32L0 307 ///< ST L0 +#define OPT_MCU_STM32L4 309 ///< ST L4 +#define OPT_MCU_STM32G0 310 ///< ST G0 +#define OPT_MCU_STM32G4 311 ///< ST G4 +#define OPT_MCU_STM32WB 312 ///< ST WB +#define OPT_MCU_STM32U5 313 ///< ST U5 +#define OPT_MCU_STM32L5 314 ///< ST L5 +#define OPT_MCU_STM32H5 315 ///< ST H5 +#define OPT_MCU_STM32U0 316 ///< ST U0 +#define OPT_MCU_STM32H7RS 317 ///< ST F7RS +#define OPT_MCU_STM32C0 318 ///< ST C0 +#define OPT_MCU_STM32N6 319 ///< ST N6 +#define OPT_MCU_STM32WBA 320 ///< ST WBA // Sony #define OPT_MCU_CXD56 400 ///< SONY CXD56 -// TI MSP430 +// TI #define OPT_MCU_MSP430x5xx 500 ///< TI MSP430x5xx +#define OPT_MCU_MSP432E4 510 ///< TI MSP432E4xx +#define OPT_MCU_TM4C123 511 ///< TI Tiva-C 123x +#define OPT_MCU_TM4C129 512 ///< TI Tiva-C 129x // ValentyUSB eptri #define OPT_MCU_VALENTYUSB_EPTRI 600 ///< Fomu eptri config // NXP iMX RT -#define OPT_MCU_MIMXRT10XX 700 ///< NXP iMX RT10xx +#define OPT_MCU_MIMXRT1XXX 700 ///< NXP iMX RT1xxx Series +#define OPT_MCU_MIMXRT10XX OPT_MCU_MIMXRT1XXX ///< RT10xx +#define OPT_MCU_MIMXRT11XX OPT_MCU_MIMXRT1XXX ///< RT11xx // Nuvoton #define OPT_MCU_NUC121 800 @@ -93,80 +122,289 @@ // Espressif #define OPT_MCU_ESP32S2 900 ///< Espressif ESP32-S2 +#define OPT_MCU_ESP32S3 901 ///< Espressif ESP32-S3 +#define OPT_MCU_ESP32 902 ///< Espressif ESP32 (for host max3421e) +#define OPT_MCU_ESP32C3 903 ///< Espressif ESP32-C3 +#define OPT_MCU_ESP32C6 904 ///< Espressif ESP32-C6 +#define OPT_MCU_ESP32C2 905 ///< Espressif ESP32-C2 +#define OPT_MCU_ESP32H2 906 ///< Espressif ESP32-H2 +#define OPT_MCU_ESP32P4 907 ///< Espressif ESP32-P4 +#define OPT_MCU_ESP32C5 908 ///< Espressif ESP32-C5 +#define OPT_MCU_ESP32C61 909 ///< Espressif ESP32-C61 +#define OPT_MCU_ESP32H4 910 ///< Espressif ESP32-H4 +#define TUSB_MCU_VENDOR_ESPRESSIF (CFG_TUSB_MCU >= 900 && CFG_TUSB_MCU < 1000) // check if Espressif MCU +#define TUP_MCU_ESPRESSIF TUSB_MCU_VENDOR_ESPRESSIF // for backward compatibility // Dialog #define OPT_MCU_DA1469X 1000 ///< Dialog Semiconductor DA1469x -/** @} */ +// Raspberry Pi +#define OPT_MCU_RP2040 1100 ///< Raspberry Pi RP2040 + +// NXP Kinetis +#define OPT_MCU_KINETIS_KL 1200 ///< NXP KL series +#define OPT_MCU_KINETIS_K32L 1201 ///< NXP K32L series +#define OPT_MCU_KINETIS_K32 1201 ///< Alias to K32L +#define OPT_MCU_KINETIS_K 1202 ///< NXP K series + +#define OPT_MCU_MKL25ZXX 1200 ///< Alias to KL (obsolete) +#define OPT_MCU_K32L2BXX 1201 ///< Alias to K32 (obsolete) + +// Silabs +#define OPT_MCU_EFM32GG 1300 ///< Silabs EFM32GG + +// Renesas RX +#define OPT_MCU_RX63X 1400 ///< Renesas RX63N/631 +#define OPT_MCU_RX65X 1401 ///< Renesas RX65N/RX651 +#define OPT_MCU_RX72N 1402 ///< Renesas RX72N +#define OPT_MCU_RAXXX 1403 ///< Renesas RA generic + +// Mind Motion +#define OPT_MCU_MM32F327X 1500 ///< Mind Motion MM32F327 + +// GigaDevice +#define OPT_MCU_GD32VF103 1600 ///< GigaDevice GD32VF103 + +// Broadcom +#define OPT_MCU_BCM2711 1700 ///< Broadcom BCM2711 +#define OPT_MCU_BCM2835 1701 ///< Broadcom BCM2835 +#define OPT_MCU_BCM2837 1702 ///< Broadcom BCM2837 + +// Infineon +#define OPT_MCU_XMC4000 1800 ///< Infineon XMC4000 + +// PIC +#define OPT_MCU_PIC32MZ 1900 ///< MicroChip PIC32MZ family +#define OPT_MCU_PIC32MM 1901 ///< MicroChip PIC32MM family +#define OPT_MCU_PIC32MX 1902 ///< MicroChip PIC32MX family +#define OPT_MCU_PIC32MK 1903 ///< MicroChip PIC32MK family +#define OPT_MCU_PIC24 1910 ///< MicroChip PIC24 family +#define OPT_MCU_DSPIC33 1911 ///< MicroChip DSPIC33 family + +// BridgeTek +#define OPT_MCU_FT90X 2000 ///< BridgeTek FT90x +#define OPT_MCU_FT93X 2001 ///< BridgeTek FT93x + +// Allwinner +#define OPT_MCU_F1C100S 2100 ///< Allwinner F1C100s family + +// WCH +#define OPT_MCU_CH32V307 2200 ///< WCH CH32V307 +#define OPT_MCU_CH32F20X 2210 ///< WCH CH32F20x +#define OPT_MCU_CH32V20X 2220 ///< WCH CH32V20X +#define OPT_MCU_CH32V103 2230 ///< WCH CH32V103 + +// NXP LPC MCX +#define OPT_MCU_MCXN9 2300 ///< NXP MCX N9 Series +#define OPT_MCU_MCXA15 2301 ///< NXP MCX A15 Series + +// Analog Devices +#define OPT_MCU_MAX32690 2400 ///< ADI MAX32690 +#define OPT_MCU_MAX32665 2401 ///< ADI MAX32666/5 +#define OPT_MCU_MAX32666 2401 ///< ADI MAX32666/5 +#define OPT_MCU_MAX32650 2402 ///< ADI MAX32650/1/2 +#define OPT_MCU_MAX78002 2403 ///< ADI MAX78002 + +// ArteryTek +#define OPT_MCU_AT32F403A_407 2500 ///< ArteryTek AT32F403A_AT32F407 +#define OPT_MCU_AT32F415 2501 ///< ArteryTek AT32F415 +#define OPT_MCU_AT32F435_437 2502 ///< ArteryTek AT32F435_AT32F437 +#define OPT_MCU_AT32F423 2503 ///< ArteryTek AT32F423 +#define OPT_MCU_AT32F402_405 2504 ///< ArteryTek AT32F402_405 +#define OPT_MCU_AT32F425 2505 ///< ArteryTek AT32F425 +#define OPT_MCU_AT32F413 2506 ///< ArteryTek AT32F413 + +// Check if configured MCU is one of listed +// Apply _TU_CHECK_MCU with || as separator to list of input +#define _TU_CHECK_MCU(_m) (CFG_TUSB_MCU == _m) +#define TU_CHECK_MCU(...) (TU_ARGS_APPLY(_TU_CHECK_MCU, ||, __VA_ARGS__)) + +//--------------------------------------------------------------------+ +// Supported OS +//--------------------------------------------------------------------+ -/** \defgroup group_supported_os Supported RTOS - * \ref CFG_TUSB_OS must be defined to one of these - * @{ */ #define OPT_OS_NONE 1 ///< No RTOS #define OPT_OS_FREERTOS 2 ///< FreeRTOS #define OPT_OS_MYNEWT 3 ///< Mynewt OS #define OPT_OS_CUSTOM 4 ///< Custom OS is implemented by application -/** @} */ +#define OPT_OS_PICO 5 ///< Raspberry Pi Pico SDK +#define OPT_OS_RTTHREAD 6 ///< RT-Thread +#define OPT_OS_RTX4 7 ///< Keil RTX 4 +#define OPT_OS_ZEPHYR 8 ///< Zephyr +//--------------------------------------------------------------------+ +// Mode and Speed +//--------------------------------------------------------------------+ + +// Low byte is operational mode +#define OPT_MODE_NONE 0x0000 ///< Disabled +#define OPT_MODE_DEVICE 0x0001 ///< Device Mode +#define OPT_MODE_HOST 0x0002 ///< Host Mode + +// High byte is max operational speed (corresponding to tusb_speed_t) +#define OPT_MODE_DEFAULT_SPEED 0x0000 ///< Default (max) speed supported by MCU +#define OPT_MODE_LOW_SPEED 0x0100 ///< Low Speed +#define OPT_MODE_FULL_SPEED 0x0200 ///< Full Speed +#define OPT_MODE_HIGH_SPEED 0x0400 ///< High Speed +#define OPT_MODE_SPEED_MASK 0xff00 + +//--------------------------------------------------------------------+ +// Include tusb_config.h +//--------------------------------------------------------------------+ // Allow to use command line to change the config name/location -#ifndef CFG_TUSB_CONFIG_FILE - #define CFG_TUSB_CONFIG_FILE "tusb_config.h" +#ifdef CFG_TUSB_CONFIG_FILE + #include CFG_TUSB_CONFIG_FILE +#elif defined(ARDUINO_ARCH_ESP32) + // ESP32 out-of-sync + #include "arduino/ports/esp32/tusb_config_esp32.h" +#else + #include "tusb_config.h" +#endif + +#include "common/tusb_mcu.h" + +//--------------------------------------------------------------------+ +// USBIP +//--------------------------------------------------------------------+ + +#ifndef CFG_TUD_DWC2_SLAVE_ENABLE + #ifndef CFG_TUD_DWC2_SLAVE_ENABLE_DEFAULT + #define CFG_TUD_DWC2_SLAVE_ENABLE_DEFAULT 1 + #endif + + #define CFG_TUD_DWC2_SLAVE_ENABLE CFG_TUD_DWC2_SLAVE_ENABLE_DEFAULT +#endif + +// Enable DWC2 DMA for device +#ifndef CFG_TUD_DWC2_DMA_ENABLE + #ifndef CFG_TUD_DWC2_DMA_ENABLE_DEFAULT + #define CFG_TUD_DWC2_DMA_ENABLE_DEFAULT 0 + #endif + + #define CFG_TUD_DWC2_DMA_ENABLE CFG_TUD_DWC2_DMA_ENABLE_DEFAULT +#endif + +// Enable CI_HS VBUS Charge. Set this to 1 if the USB_VBUS pin is not connected to 5V VBUS (note: 3.3V is insufficient). +#ifndef CFG_TUD_CI_HS_VBUS_CHARGE + #ifndef CFG_TUD_CI_HS_VBUS_CHARGE_DEFAULT + #define CFG_TUD_CI_HS_VBUS_CHARGE_DEFAULT 0 + #endif + + #define CFG_TUD_CI_HS_VBUS_CHARGE CFG_TUD_CI_HS_VBUS_CHARGE_DEFAULT +#endif + +// Enable DWC2 Slave mode for host +#ifndef CFG_TUH_DWC2_SLAVE_ENABLE + #ifndef CFG_TUH_DWC2_SLAVE_ENABLE_DEFAULT + #define CFG_TUH_DWC2_SLAVE_ENABLE_DEFAULT 1 + #endif + + #define CFG_TUH_DWC2_SLAVE_ENABLE CFG_TUH_DWC2_SLAVE_ENABLE_DEFAULT +#endif + +// Enable DWC2 DMA for host +#ifndef CFG_TUH_DWC2_DMA_ENABLE + #ifndef CFG_TUH_DWC2_DMA_ENABLE_DEFAULT + #define CFG_TUH_DWC2_DMA_ENABLE_DEFAULT 0 + #endif + + #define CFG_TUH_DWC2_DMA_ENABLE CFG_TUH_DWC2_DMA_ENABLE_DEFAULT #endif -#include CFG_TUSB_CONFIG_FILE +// Enable PIO-USB software host controller +#ifndef CFG_TUH_RPI_PIO_USB + #define CFG_TUH_RPI_PIO_USB 0 +#endif -/** \addtogroup group_configuration - * @{ */ +#ifndef CFG_TUD_RPI_PIO_USB + #define CFG_TUD_RPI_PIO_USB 0 +#endif + +// MAX3421 Host controller option +#ifndef CFG_TUH_MAX3421 + #define CFG_TUH_MAX3421 0 +#endif //-------------------------------------------------------------------- -// RootHub Mode Configuration -// CFG_TUSB_RHPORTx_MODE contains operation mode and speed for that port +// RootHub Mode detection //-------------------------------------------------------------------- -// Lower 4-bit is operational mode -#define OPT_MODE_NONE 0x00 ///< Disabled -#define OPT_MODE_DEVICE 0x01 ///< Device Mode -#define OPT_MODE_HOST 0x02 ///< Host Mode +//------------- Root hub as Device -------------// -// Higher 4-bit is max operational speed (corresponding to tusb_speed_t) -#define OPT_MODE_FULL_SPEED 0x00 ///< Max Full Speed -#define OPT_MODE_LOW_SPEED 0x10 ///< Max Low Speed -#define OPT_MODE_HIGH_SPEED 0x20 ///< Max High Speed +#if defined(CFG_TUSB_RHPORT0_MODE) && ((CFG_TUSB_RHPORT0_MODE) & OPT_MODE_DEVICE) + #define TUD_RHPORT_MODE (CFG_TUSB_RHPORT0_MODE) + #define TUD_OPT_RHPORT 0 +#elif defined(CFG_TUSB_RHPORT1_MODE) && ((CFG_TUSB_RHPORT1_MODE) & OPT_MODE_DEVICE) + #define TUD_RHPORT_MODE (CFG_TUSB_RHPORT1_MODE) + #define TUD_OPT_RHPORT 1 +#else + #define TUD_RHPORT_MODE OPT_MODE_NONE +#endif +#ifndef CFG_TUD_ENABLED + // fallback to use CFG_TUSB_RHPORTx_MODE + #define CFG_TUD_ENABLED (TUD_RHPORT_MODE & OPT_MODE_DEVICE) +#endif -#ifndef CFG_TUSB_RHPORT0_MODE - #define CFG_TUSB_RHPORT0_MODE OPT_MODE_NONE +#ifndef CFG_TUD_MAX_SPEED + // fallback to use CFG_TUSB_RHPORTx_MODE + #define CFG_TUD_MAX_SPEED (TUD_RHPORT_MODE & OPT_MODE_SPEED_MASK) #endif +// For backward compatible +#define TUSB_OPT_DEVICE_ENABLED CFG_TUD_ENABLED + +// highspeed support indicator +#define TUD_OPT_HIGH_SPEED (CFG_TUD_MAX_SPEED ? (CFG_TUD_MAX_SPEED & OPT_MODE_HIGH_SPEED) : TUP_RHPORT_HIGHSPEED) + +//------------- Root hub as Host -------------// + +#if defined(CFG_TUSB_RHPORT0_MODE) && ((CFG_TUSB_RHPORT0_MODE) & OPT_MODE_HOST) + #define TUH_RHPORT_MODE (CFG_TUSB_RHPORT0_MODE) + #define TUH_OPT_RHPORT 0 +#elif defined(CFG_TUSB_RHPORT1_MODE) && ((CFG_TUSB_RHPORT1_MODE) & OPT_MODE_HOST) + #define TUH_RHPORT_MODE (CFG_TUSB_RHPORT1_MODE) + #define TUH_OPT_RHPORT 1 +#else + #define TUH_RHPORT_MODE OPT_MODE_NONE +#endif -#ifndef CFG_TUSB_RHPORT1_MODE - #define CFG_TUSB_RHPORT1_MODE OPT_MODE_NONE +#ifndef CFG_TUH_ENABLED + // fallback to use CFG_TUSB_RHPORTx_MODE + #define CFG_TUH_ENABLED (TUH_RHPORT_MODE & OPT_MODE_HOST) #endif -#if ((CFG_TUSB_RHPORT0_MODE & OPT_MODE_HOST ) && (CFG_TUSB_RHPORT1_MODE & OPT_MODE_HOST )) || \ - ((CFG_TUSB_RHPORT0_MODE & OPT_MODE_DEVICE) && (CFG_TUSB_RHPORT1_MODE & OPT_MODE_DEVICE)) - #error "TinyUSB currently does not support same modes on more than 1 roothub port" +#ifndef CFG_TUH_MAX_SPEED + // fallback to use CFG_TUSB_RHPORTx_MODE + #define CFG_TUH_MAX_SPEED (TUH_RHPORT_MODE & OPT_MODE_SPEED_MASK) #endif -// Which roothub port is configured as host -#define TUH_OPT_RHPORT ( (CFG_TUSB_RHPORT0_MODE & OPT_MODE_HOST) ? 0 : ((CFG_TUSB_RHPORT1_MODE & OPT_MODE_HOST) ? 1 : -1) ) -#define TUSB_OPT_HOST_ENABLED ( TUH_OPT_RHPORT >= 0 ) +// For backward compatible +#define TUSB_OPT_HOST_ENABLED CFG_TUH_ENABLED -// Which roothub port is configured as device -#define TUD_OPT_RHPORT ( (CFG_TUSB_RHPORT0_MODE & OPT_MODE_DEVICE) ? 0 : ((CFG_TUSB_RHPORT1_MODE & OPT_MODE_DEVICE) ? 1 : -1) ) +// highspeed support indicator +#define TUH_OPT_HIGH_SPEED (CFG_TUH_MAX_SPEED ? (CFG_TUH_MAX_SPEED & OPT_MODE_HIGH_SPEED) : TUP_RHPORT_HIGHSPEED) -#if TUD_OPT_RHPORT == 0 -#define TUD_OPT_HIGH_SPEED ( CFG_TUSB_RHPORT0_MODE & OPT_MODE_HIGH_SPEED ) + +//--------------------------------------------------------------------+ +// TODO move later +//--------------------------------------------------------------------+ + +// TUP_MCU_STRICT_ALIGN will overwrite TUP_ARCH_STRICT_ALIGN. +// In case TUP_MCU_STRICT_ALIGN = 1 and TUP_ARCH_STRICT_ALIGN =0, we will not reply on compiler +// to generate unaligned access code. +// LPC_IP3511 Highspeed cannot access unaligned memory on USB_RAM +#if TUD_OPT_HIGH_SPEED && TU_CHECK_MCU(OPT_MCU_LPC54XXX, OPT_MCU_LPC55XX) + #define TUP_MCU_STRICT_ALIGN 1 #else -#define TUD_OPT_HIGH_SPEED ( CFG_TUSB_RHPORT1_MODE & OPT_MODE_HIGH_SPEED ) + #define TUP_MCU_STRICT_ALIGN 0 #endif -#define TUSB_OPT_DEVICE_ENABLED ( TUD_OPT_RHPORT >= 0 ) //--------------------------------------------------------------------+ -// COMMON OPTIONS +// Common Options (Default) //--------------------------------------------------------------------+ // Debug enable to print out error message @@ -174,27 +412,106 @@ #define CFG_TUSB_DEBUG 0 #endif -// place data in accessible RAM for usb controller +// Level where CFG_TUSB_DEBUG must be at least for USBH is logged +#ifndef CFG_TUH_LOG_LEVEL + #define CFG_TUH_LOG_LEVEL 2 +#endif + +// Level where CFG_TUSB_DEBUG must be at least for USBD is logged +#ifndef CFG_TUD_LOG_LEVEL + #define CFG_TUD_LOG_LEVEL 2 +#endif + +// Memory section for placing buffer used for usb transferring. If MEM_SECTION is different for +// host and device use: CFG_TUD_MEM_SECTION, CFG_TUH_MEM_SECTION instead #ifndef CFG_TUSB_MEM_SECTION #define CFG_TUSB_MEM_SECTION #endif +// Alignment requirement of buffer used for usb transferring. if MEM_ALIGN is different for +// host and device controller use: CFG_TUD_MEM_ALIGN, CFG_TUH_MEM_ALIGN instead #ifndef CFG_TUSB_MEM_ALIGN #define CFG_TUSB_MEM_ALIGN TU_ATTR_ALIGNED(4) #endif +#ifndef CFG_TUSB_MEM_DCACHE_LINE_SIZE + #ifndef CFG_TUSB_MEM_DCACHE_LINE_SIZE_DEFAULT + #define CFG_TUSB_MEM_DCACHE_LINE_SIZE_DEFAULT 1 + #endif + + #define CFG_TUSB_MEM_DCACHE_LINE_SIZE CFG_TUSB_MEM_DCACHE_LINE_SIZE_DEFAULT +#endif + +// OS selection #ifndef CFG_TUSB_OS - #define CFG_TUSB_OS OPT_OS_NONE + #define CFG_TUSB_OS OPT_OS_NONE +#endif + +#ifndef CFG_TUSB_OS_INC_PATH + #ifndef CFG_TUSB_OS_INC_PATH_DEFAULT + #define CFG_TUSB_OS_INC_PATH_DEFAULT + #endif + + #define CFG_TUSB_OS_INC_PATH CFG_TUSB_OS_INC_PATH_DEFAULT #endif //-------------------------------------------------------------------- -// DEVICE OPTIONS +// Device Options (Default) //-------------------------------------------------------------------- +// Attribute to place data in accessible RAM for device controller (default: CFG_TUSB_MEM_SECTION) +#ifndef CFG_TUD_MEM_SECTION + #define CFG_TUD_MEM_SECTION CFG_TUSB_MEM_SECTION +#endif + +// Attribute to align memory for device controller (default: CFG_TUSB_MEM_ALIGN) +#ifndef CFG_TUD_MEM_ALIGN + #define CFG_TUD_MEM_ALIGN CFG_TUSB_MEM_ALIGN +#endif + +#ifndef CFG_TUD_MEM_DCACHE_ENABLE + #ifndef CFG_TUD_MEM_DCACHE_ENABLE_DEFAULT + #define CFG_TUD_MEM_DCACHE_ENABLE_DEFAULT 0 + #endif + + #define CFG_TUD_MEM_DCACHE_ENABLE CFG_TUD_MEM_DCACHE_ENABLE_DEFAULT +#endif + +#ifndef CFG_TUD_MEM_DCACHE_LINE_SIZE + #define CFG_TUD_MEM_DCACHE_LINE_SIZE CFG_TUSB_MEM_DCACHE_LINE_SIZE +#endif + #ifndef CFG_TUD_ENDPOINT0_SIZE #define CFG_TUD_ENDPOINT0_SIZE 64 #endif +#ifndef CFG_TUD_INTERFACE_MAX + #define CFG_TUD_INTERFACE_MAX 16 +#endif + +// default to max hardware endpoint, but can be smaller to save RAM +#ifndef CFG_TUD_ENDPPOINT_MAX + #define CFG_TUD_ENDPPOINT_MAX TUP_DCD_ENDPOINT_MAX +#endif + +#if CFG_TUD_ENDPPOINT_MAX > TUP_DCD_ENDPOINT_MAX + #error "CFG_TUD_ENDPPOINT_MAX must be less than or equal to TUP_DCD_ENDPOINT_MAX" +#endif + +// USB 2.0 7.1.20: compliance test mode support +#ifndef CFG_TUD_TEST_MODE + #define CFG_TUD_TEST_MODE 0 +#endif + +//------------- Device Class Driver -------------// +#ifndef CFG_TUD_BTH + #define CFG_TUD_BTH 0 +#endif + +#if CFG_TUD_BTH && !defined(CFG_TUD_BTH_ISO_ALT_COUNT) +#error CFG_TUD_BTH_ISO_ALT_COUNT must be defined to tell Bluetooth driver the number of ISO endpoints to use +#endif + #ifndef CFG_TUD_CDC #define CFG_TUD_CDC 0 #endif @@ -203,10 +520,22 @@ #define CFG_TUD_MSC 0 #endif +#ifndef CFG_TUD_MTP + #define CFG_TUD_MTP 0 +#endif + #ifndef CFG_TUD_HID #define CFG_TUD_HID 0 #endif +#ifndef CFG_TUD_AUDIO + #define CFG_TUD_AUDIO 0 +#endif + +#ifndef CFG_TUD_VIDEO + #define CFG_TUD_VIDEO 0 +#endif + #ifndef CFG_TUD_MIDI #define CFG_TUD_MIDI 0 #endif @@ -219,42 +548,175 @@ #define CFG_TUD_USBTMC 0 #endif -#ifndef CFG_TUD_DFU_RT - #define CFG_TUD_DFU_RT 0 +#ifndef CFG_TUD_DFU_RUNTIME + #define CFG_TUD_DFU_RUNTIME 0 #endif -#ifndef CFG_TUD_NET - #define CFG_TUD_NET 0 +#ifndef CFG_TUD_DFU + #define CFG_TUD_DFU 0 #endif -#ifndef CFG_TUD_BTH - #define CFG_TUD_BTH 0 +#ifndef CFG_TUD_ECM_RNDIS + #ifdef CFG_TUD_NET + #warning "CFG_TUD_NET is renamed to CFG_TUD_ECM_RNDIS" + #define CFG_TUD_ECM_RNDIS CFG_TUD_NET + #else + #define CFG_TUD_ECM_RNDIS 0 + #endif +#endif + +#ifndef CFG_TUD_NCM + #define CFG_TUD_NCM 0 #endif //-------------------------------------------------------------------- -// HOST OPTIONS +// Host Options (Default) //-------------------------------------------------------------------- -#if TUSB_OPT_HOST_ENABLED - #ifndef CFG_TUSB_HOST_DEVICE_MAX - #define CFG_TUSB_HOST_DEVICE_MAX 1 - #warning CFG_TUSB_HOST_DEVICE_MAX is not defined, default value is 1 +#if CFG_TUH_ENABLED + #ifndef CFG_TUH_DEVICE_MAX + #define CFG_TUH_DEVICE_MAX 1 #endif - //------------- HUB CLASS -------------// - #if CFG_TUH_HUB && (CFG_TUSB_HOST_DEVICE_MAX == 1) - #error there is no benefit enable hub with max device is 1. Please disable hub or increase CFG_TUSB_HOST_DEVICE_MAX + #ifndef CFG_TUH_ENUMERATION_BUFSIZE + #define CFG_TUH_ENUMERATION_BUFSIZE 256 #endif +#endif // CFG_TUH_ENABLED - //------------- HID CLASS -------------// - #define HOST_CLASS_HID ( CFG_TUH_HID_KEYBOARD + CFG_TUH_HID_MOUSE + CFG_TUSB_HOST_HID_GENERIC ) +// Attribute to place data in accessible RAM for host controller (default: CFG_TUSB_MEM_SECTION) +#ifndef CFG_TUH_MEM_SECTION + #define CFG_TUH_MEM_SECTION CFG_TUSB_MEM_SECTION +#endif - #ifndef CFG_TUSB_HOST_ENUM_BUFFER_SIZE - #define CFG_TUSB_HOST_ENUM_BUFFER_SIZE 256 +// Attribute to align memory for host controller +#ifndef CFG_TUH_MEM_ALIGN + #define CFG_TUH_MEM_ALIGN CFG_TUSB_MEM_ALIGN +#endif + +#ifndef CFG_TUH_MEM_DCACHE_ENABLE + #ifndef CFG_TUH_MEM_DCACHE_ENABLE_DEFAULT + #define CFG_TUH_MEM_DCACHE_ENABLE_DEFAULT 0 #endif - //------------- CLASS -------------// -#endif // TUSB_OPT_HOST_ENABLED + #define CFG_TUH_MEM_DCACHE_ENABLE CFG_TUH_MEM_DCACHE_ENABLE_DEFAULT +#endif + +#ifndef CFG_TUH_MEM_DCACHE_LINE_SIZE + #define CFG_TUH_MEM_DCACHE_LINE_SIZE CFG_TUSB_MEM_DCACHE_LINE_SIZE +#endif +//------------- CLASS -------------// + +#ifndef CFG_TUH_HUB + #define CFG_TUH_HUB 0 +#endif + +#ifndef CFG_TUH_CDC + #define CFG_TUH_CDC 0 +#endif + +// FTDI is not part of CDC class, only to re-use CDC driver API +#ifndef CFG_TUH_CDC_FTDI + #define CFG_TUH_CDC_FTDI 0 +#endif + +// List of product IDs that can use the FTDI CDC driver. 0x0403 is FTDI's VID +#ifndef CFG_TUH_CDC_FTDI_VID_PID_LIST + #define CFG_TUH_CDC_FTDI_VID_PID_LIST \ + {0x0403, 0x6001}, /* Similar device to SIO above */ \ + {0x0403, 0x6006}, /* FTDI's alternate PID for above */ \ + {0x0403, 0x6010}, /* Dual channel device */ \ + {0x0403, 0x6011}, /* Quad channel hi-speed device */ \ + {0x0403, 0x6014}, /* Single channel hi-speed device */ \ + {0x0403, 0x6015}, /* FT-X series (FT201X, FT230X, FT231X, etc) */ \ + {0x0403, 0x6040}, /* Dual channel hi-speed device with PD */ \ + {0x0403, 0x6041}, /* Quad channel hi-speed device with PD */ \ + {0x0403, 0x6042}, /* Dual channel hi-speed device with PD */ \ + {0x0403, 0x6043}, /* Quad channel hi-speed device with PD */ \ + {0x0403, 0x6044}, /* Dual channel hi-speed device with PD */ \ + {0x0403, 0x6045}, /* Dual channel hi-speed device with PD */ \ + {0x0403, 0x6048}, /* Quad channel automotive grade hi-speed device */ \ + {0x0403, 0x8372}, /* Product Id SIO application of 8U100AX */ \ + {0x0403, 0xFBFA}, /* Product ID for FT232RL */ \ + {0x0403, 0xCD18}, /* ??? */ +#endif + +// CP210X is not part of CDC class, only to re-use CDC driver API +#ifndef CFG_TUH_CDC_CP210X + #define CFG_TUH_CDC_CP210X 0 +#endif + +// List of product IDs that can use the CP210X CDC driver. 0x10C4 is Silicon Labs' VID +#ifndef CFG_TUH_CDC_CP210X_VID_PID_LIST + #define CFG_TUH_CDC_CP210X_VID_PID_LIST \ + { 0x10C4, 0xEA60 }, /* Silicon Labs factory default */ \ + { 0x10C4, 0xEA61 }, /* Silicon Labs factory default */ \ + { 0x10C4, 0xEA70 } /* Silicon Labs Dual Port factory default */ +#endif + +#ifndef CFG_TUH_CDC_CH34X + // CH34X is not part of CDC class, only to re-use CDC driver API + #define CFG_TUH_CDC_CH34X 0 +#endif + +// List of product IDs that can use the CH34X CDC driver +#ifndef CFG_TUH_CDC_CH34X_VID_PID_LIST + #define CFG_TUH_CDC_CH34X_VID_PID_LIST \ + { 0x1a86, 0x5523 }, /* ch341 chip */ \ + { 0x1a86, 0x7522 }, /* ch340k chip */ \ + { 0x1a86, 0x7523 }, /* ch340 chip */ \ + { 0x1a86, 0xe523 }, /* ch330 chip */ \ + { 0x4348, 0x5523 }, /* ch340 custom chip */ \ + { 0x2184, 0x0057 }, /* overtaken from Linux Kernel driver /drivers/usb/serial/ch341.c */ \ + { 0x9986, 0x7523 } /* overtaken from Linux Kernel driver /drivers/usb/serial/ch341.c */ +#endif + +#ifndef CFG_TUH_CDC_PL2303 + // PL2303 is not part of CDC class, only to re-use CDC driver API + #define CFG_TUH_CDC_PL2303 0 +#endif + +#ifndef CFG_TUH_CDC_PL2303_VID_PID_QUIRKS_LIST + // List of product IDs that can use the PL2303 CDC driver + #define CFG_TUH_CDC_PL2303_VID_PID_LIST \ + { 0x067b, 0x2303 }, /* initial 2303 */ \ + { 0x067b, 0x2304 }, /* TB */ \ + { 0x067b, 0x23a3 }, /* GC */ \ + { 0x067b, 0x23b3 }, /* GB */ \ + { 0x067b, 0x23c3 }, /* GT */ \ + { 0x067b, 0x23d3 }, /* GL */ \ + { 0x067b, 0x23e3 }, /* GE */ \ + { 0x067b, 0x23f3 } /* GS */ +#endif + +#ifndef CFG_TUH_HID + #define CFG_TUH_HID 0 +#endif + +#ifndef CFG_TUH_MIDI + #define CFG_TUH_MIDI 0 +#endif + +#ifndef CFG_TUH_MSC + #define CFG_TUH_MSC 0 +#endif + +#ifndef CFG_TUH_VENDOR + #define CFG_TUH_VENDOR 0 +#endif + +#ifndef CFG_TUH_API_EDPT_XFER + #define CFG_TUH_API_EDPT_XFER 0 +#endif + +//--------------------------------------------------------------------+ +// TypeC Options (Default) +//--------------------------------------------------------------------+ + +#ifndef CFG_TUC_ENABLED +#define CFG_TUC_ENABLED 0 + +#define tuc_int_handler(_p) +#endif //------------------------------------------------------------------ // Configuration Validation @@ -263,6 +725,9 @@ #error Control Endpoint Max Packet Size cannot be larger than 64 #endif +// To avoid GCC compiler warnings when -pedantic option is used (strict ISO C) +typedef int make_iso_compilers_happy; + #endif /* _TUSB_OPTION_H_ */ /** @} */ diff --git a/cores/nRF5/TinyUSB/tusb_config.h b/cores/nRF5/TinyUSB/tusb_config.h deleted file mode 100644 index af8622f..0000000 --- a/cores/nRF5/TinyUSB/tusb_config.h +++ /dev/null @@ -1,90 +0,0 @@ -/* - * The MIT License (MIT) - * - * Copyright (c) 2018, hathach for Adafruit - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - */ - -#ifndef _TUSB_CONFIG_H_ -#define _TUSB_CONFIG_H_ - -#ifdef __cplusplus - extern "C" { -#endif - -//-------------------------------------------------------------------- -// COMMON CONFIGURATION -//-------------------------------------------------------------------- -#define CFG_TUSB_MCU OPT_MCU_NRF5X - -#ifdef USE_TINYUSB -#define CFG_TUSB_RHPORT0_MODE OPT_MODE_DEVICE -#else -#define CFG_TUSB_RHPORT0_MODE OPT_MODE_NONE -#endif - -#define CFG_TUSB_OS OPT_OS_FREERTOS -#define CFG_TUSB_MEM_SECTION -#define CFG_TUSB_MEM_ALIGN __attribute__ ((aligned(4))) - -#ifndef CFG_TUSB_DEBUG -#define CFG_TUSB_DEBUG 0 -#endif - -//-------------------------------------------------------------------- -// DEVICE CONFIGURATION -//-------------------------------------------------------------------- - -#define CFG_TUD_ENDOINT0_SIZE 64 - -//------------- CLASS -------------// -#define CFG_TUD_CDC 1 -#define CFG_TUD_MSC 1 -#define CFG_TUD_HID 1 -#define CFG_TUD_MIDI 1 -#define CFG_TUD_VENDOR 1 - -// CDC FIFO size of TX and RX -#define CFG_TUD_CDC_RX_BUFSIZE 256 -#define CFG_TUD_CDC_TX_BUFSIZE 256 - -// MSC Buffer size of Device Mass storage -#define CFG_TUD_MSC_BUFSIZE 512 - -// HID buffer size Should be sufficient to hold ID (if any) + Data -#define CFG_TUD_HID_BUFSIZE 64 - -// MIDI FIFO size of TX and RX -#define CFG_TUD_MIDI_RX_BUFSIZE 128 -#define CFG_TUD_MIDI_TX_BUFSIZE 128 - -// Vendor FIFO size of TX and RX -#ifndef CFG_TUD_VENDOR_RX_BUFSIZE -#define CFG_TUD_VENDOR_RX_BUFSIZE 64 -#endif -#ifndef CFG_TUD_VENDOR_TX_BUFSIZE -#define CFG_TUD_VENDOR_TX_BUFSIZE 64 -#endif - -#ifdef __cplusplus - } -#endif - -#endif /* _TUSB_CONFIG_H_ */ diff --git a/cores/nRF5/delay.c b/cores/nRF5/delay.c index e120e43..696d916 100644 --- a/cores/nRF5/delay.c +++ b/cores/nRF5/delay.c @@ -29,9 +29,6 @@ extern "C" { void yield(void) { -#ifdef USE_TINYUSB - tud_cdc_write_flush(); -#endif taskYIELD(); } @@ -47,19 +44,7 @@ uint32_t micros( void ) void delay( uint32_t ms ) { - uint32_t ticks = ms * configTICK_RATE_HZ / 1000; - -#ifdef USE_TINYUSB - // Take chance to flush usb cdc - uint32_t flush_tick = xTaskGetTickCount(); - tud_cdc_write_flush(); - - flush_tick = xTaskGetTickCount()-flush_tick; - if (flush_tick >= ticks) return; - - ticks -= flush_tick; -#endif - vTaskDelay(ticks); + vTaskDelay(pdMS_TO_TICKS(ms)); } #ifdef __cplusplus diff --git a/cores/nRF5/freertos/FreeRTOS.h b/cores/nRF5/freertos/FreeRTOS.h index 2c5c8dd..c81e2fa 100644 --- a/cores/nRF5/freertos/FreeRTOS.h +++ b/cores/nRF5/freertos/FreeRTOS.h @@ -431,7 +431,7 @@ #endif #ifndef configRECORD_STACK_HIGH_ADDRESS - #define configRECORD_STACK_HIGH_ADDRESS 1 + #define configRECORD_STACK_HIGH_ADDRESS 0 #endif #ifndef configINCLUDE_FREERTOS_TASK_C_ADDITIONS_H diff --git a/cores/nRF5/freertos/FreeRTOSConfig.h b/cores/nRF5/freertos/FreeRTOSConfig.h index f3a732d..7ee1c53 100644 --- a/cores/nRF5/freertos/FreeRTOSConfig.h +++ b/cores/nRF5/freertos/FreeRTOSConfig.h @@ -87,6 +87,10 @@ #define CONFIG_RTOS_TIMER_TASK_PRIORITY (2) #endif +#ifndef CONFIG_RTOS_THREAD_LOCAL_STORAGE_POINTERS +#define CONFIG_RTOS_THREAD_LOCAL_STORAGE_POINTERS 1 +#endif + #define configTICK_SOURCE FREERTOS_USE_RTC #define configUSE_PREEMPTION 1 @@ -115,6 +119,8 @@ #define configUSE_NEWLIB_REENTRANT 0 #define configENABLE_BACKWARD_COMPATIBILITY 1 #define configSUPPORT_STATIC_ALLOCATION 1 +#define configRECORD_STACK_HIGH_ADDRESS 1 +#define configNUM_THREAD_LOCAL_STORAGE_POINTERS CONFIG_RTOS_THREAD_LOCAL_STORAGE_POINTERS /* Hook function related definitions. */ #define configUSE_IDLE_HOOK 1 diff --git a/cores/nRF5/libc/printf/putchar.cpp b/cores/nRF5/libc/printf/putchar.cpp index 193caac..ce18af6 100644 --- a/cores/nRF5/libc/printf/putchar.cpp +++ b/cores/nRF5/libc/printf/putchar.cpp @@ -1,21 +1,120 @@ #include "Arduino.h" #include "printf.h" +#include "freertos/FreeRTOS.h" +#include "freertos/task.h" -extern "C" +#ifdef CONFIG_PRINTF_BUFFER_SIZE +#if CONFIG_PRINTF_BUFFER_SIZE < 1 +#error "CONFIG_PRINTF_BUFFER_SIZE must be at least 1" +#endif +#define PRINTF_BUFFER_SIZE CONFIG_PRINTF_BUFFER_SIZE - 1 // reserve 1 byte for potential carriage return +#else +#ifdef NRF51 +#define PRINTF_BUFFER_SIZE 1 // NRF51 has very limited RAM, no buffer +#else +#define PRINTF_BUFFER_SIZE 128 - 1 // Default to half the CDC write buffer to minimize task waits - 1 for carriage return if needed +#endif +#endif + +// Structure to hold buffer info for each task +struct PrintfBuffer { + char buffer[PRINTF_BUFFER_SIZE + 1]; // allocate 1 more byte to insert carriage return if needed + size_t index; +}; + +// FreeRTOS task notification index for buffer management +#ifdef CONFIG_PRINTF_BUFFER_INDEX +#define PRINTF_BUFFER_INDEX CONFIG_PRINTF_BUFFER_INDEX +#else +#define PRINTF_BUFFER_INDEX 0 +#endif -void _putchar(const char c) +extern "C" { - if(c == '\n') { - Serial.write('\r'); + // Helper function to get or create buffer for current task + static PrintfBuffer *getPrintfBuffer(void) + { +#if (configNUM_THREAD_LOCAL_STORAGE_POINTERS) + if (PRINTF_BUFFER_SIZE == 1) + { + // No buffer, just output single bytes + return nullptr; + } + + PrintfBuffer *pbuf = (PrintfBuffer *)pvTaskGetThreadLocalStoragePointer(NULL, PRINTF_BUFFER_INDEX); + + if (!pbuf) + { + pbuf = (PrintfBuffer *)malloc(sizeof(PrintfBuffer)); + if (pbuf) + { + pbuf->index = 0; + vTaskSetThreadLocalStoragePointer(NULL, PRINTF_BUFFER_INDEX, (void *)pbuf); + } + } + + return pbuf; +#else + return nullptr; +#endif } - Serial.write(c); -} -int _write (int fd, const void *buf, size_t count) -{ - (void) fd; - return (int)Serial.write((const uint8_t*)buf, count); -} + // Flush buffer to Serial + static void flushPrintfBuffer(PrintfBuffer *pbuf) + { + if (pbuf && pbuf->index > 0) + { + Serial.write((const uint8_t *)pbuf->buffer, pbuf->index); + pbuf->index = 0; + } +#ifdef USB_CDC_DEFAULT_SERIAL + Serial.flush(); +#endif + } + + void _putchar(const char c) + { + PrintfBuffer *pbuf = getPrintfBuffer(); + + if (pbuf == nullptr) + { + // Fallback if malloc failed or buffer size is 1 - write directly + if (c == '\n') + { + Serial.write('\r'); + } + Serial.write(c); +#ifdef USB_CDC_DEFAULT_SERIAL + Serial.flush(); +#endif + return; + } + + // Handle newline by adding CR before LF + if (c == '\n' && (pbuf->index == 0 || pbuf->buffer[pbuf->index - 1] != '\r')) + { + // 1 extra byte was allocated for this case, so it won't overflow the buffer + pbuf->buffer[pbuf->index++] = '\r'; + } + + // Add character to buffer + if (pbuf->index < PRINTF_BUFFER_SIZE) + { + pbuf->buffer[pbuf->index++] = c; + } + + // Flush if buffer full or newline detected + if (pbuf->index >= PRINTF_BUFFER_SIZE || c == '\n') + { + flushPrintfBuffer(pbuf); + } + } + + int _write(int fd, const void *buf, size_t count) + { + (void)fd; + return (int)Serial.write((const uint8_t *)buf, count); + } -} //extern "C" +} // extern "C" diff --git a/cores/nRF5/main.cpp b/cores/nRF5/main.cpp index 45d09ea..e4cd88e 100644 --- a/cores/nRF5/main.cpp +++ b/cores/nRF5/main.cpp @@ -22,6 +22,10 @@ #include "freertos/task.h" #include "Arduino.h" +#ifdef USE_TINYUSB +#include "Adafruit_USBD_Device.h" +#endif + #if defined(CONFIG_MAIN_TASK_STACK_SIZE) # define MAIN_TASK_STACK_SIZE CONFIG_MAIN_TASK_STACK_SIZE #elif defined(DEVICE_RAM_SIZE) @@ -42,7 +46,7 @@ void initVariant() { } static TaskHandle_t _loopTaskHandle = NULL; static StackType_t _mainStack[ MAIN_TASK_STACK_SIZE ]; static StaticTask_t _mainTaskBuffer; - +//#include "Adafruit_USBD_Device.h" void loopTask(void *pvParameters) { setup(); @@ -64,7 +68,7 @@ int main( void ) initVariant(); #ifdef USE_TINYUSB - Adafruit_TinyUSB_Core_init(); + TinyUSBDevice.begin(0); #endif _loopTaskHandle = xTaskCreateStatic(loopTask, "mlt", MAIN_TASK_STACK_SIZE, diff --git a/cores/nRF5/rtos.cpp b/cores/nRF5/rtos.cpp index 7292ad2..c580763 100644 --- a/cores/nRF5/rtos.cpp +++ b/cores/nRF5/rtos.cpp @@ -42,9 +42,6 @@ void vApplicationMallocFailedHook(void) void vApplicationIdleHook(void) { NRF_WDT->RR[0] = WDT_RR_RR_Reload; -#ifdef USB_CDC_DEFAULT_SERIAL - Serial.flush(); -#endif } UBaseType_t nimble_port_freertos_get_ll_hwm(void) __attribute__((weak)); diff --git a/cores/nRF5/TinyUSB/Adafruit_TinyUSB_ArduinoCore/LICENSE b/libraries/Adafruit_TinyUSB_Arduino/LICENSE similarity index 100% rename from cores/nRF5/TinyUSB/Adafruit_TinyUSB_ArduinoCore/LICENSE rename to libraries/Adafruit_TinyUSB_Arduino/LICENSE diff --git a/libraries/Adafruit_TinyUSB_Arduino/README.md b/libraries/Adafruit_TinyUSB_Arduino/README.md new file mode 100644 index 0000000..5b346b2 --- /dev/null +++ b/libraries/Adafruit_TinyUSB_Arduino/README.md @@ -0,0 +1,88 @@ +# Adafruit TinyUSB Library for Arduino + +[![Build Status](https://github.com/adafruit/Adafruit_TinyUSB_Arduino/workflows/Build/badge.svg)](https://github.com/adafruit/Adafruit_TinyUSB_Arduino/actions) [![License](https://img.shields.io/badge/license-MIT-brightgreen.svg)](https://opensource.org/licenses/MIT) + +This library is a Arduino-friendly version of [TinyUSB](https://github.com/hathach/tinyusb) stack. +It is designed with structure and APIs that are easily integrated to an Arduino Core. + +## Features + +### Device Stack + +Supported device class drivers are: + +- Communication (CDC): which is used to implement `Serial` monitor +- Human Interface Device (HID): Generic (In & Out), Keyboard, Mouse, Gamepad etc ... +- Mass Storage Class (MSC): with multiple LUNs +- Musical Instrument Digital Interface (MIDI) +- Video (UVC): work in progress +- WebUSB with vendor specific class + +### Host Stack + +Host stack is available with either addition of MAX3421E hardware (e.g [Host FeatherWing](https://www.adafruit.com/product/5858)) or rp2040 core (thanks to [Pico-PIO-USB](https://github.com/sekigon-gonnoc/Pico-PIO-USB)). Supported class driver are: + +- Communication (CDC): including vendor usb2uart such as FTDI, CP210x, CH34x +- MassStorage class + +Note: Host stack is still work-in-progress + +## Supported Cores + +There are 2 type of supported cores: with and without built-in support for TinyUSB. Built-in support provide seamless integration but requires extra code added to core's source code. Unfortunately it is not always easy or possible to make those modification. + +### Cores with built-in support + +Following core has TinyUSB as either the primary usb stack or selectable via menu `Tools->USB Stack`. You only need to include `` in your sketch to use. + +- [adafruit/Adafruit_nRF52_Arduino](https://github.com/adafruit/Adafruit_nRF52_Arduino) +- [adafruit/ArduinoCore-samd](https://github.com/adafruit/ArduinoCore-samd) +- [earlephilhower/arduino-pico](https://github.com/earlephilhower/arduino-pico) +- [espressif/arduino-esp32](https://github.com/espressif/arduino-esp32) Host mode using MAX3421E controller should work with all chips. Device mode only support S2/S3/P4 and additional Tools menu are needed + - `USB Mode=USB-OTG (TinyUSB)` for S3 and P4 + - `USB CDC On Boot=Enabled`, `USB Firmware MSC On Boot=Disabled`, `USB DFU On Boot=Disabled` +- [openwch/arduino_core_ch32](https://github.com/openwch/arduino_core_ch32) + +Note: For ESP32 port, version before v3.0 requires all descriptors must be specified in usb objects declaration i.e constructors. Therefore all descriptor-related fields must be part of object declaration and descriptor-related API have no effect afterwards. This limitation is not the case for version from v3.0. + +### Cores without built-in support + +Following is cores without built-in support + +- **mbed_rp2040** + +It is still possible to use TinyUSB but with some limits such as: + +- `TinyUSB_Device_Init()` need to be manually called in setup() +- `TinyUSB_Device_Task()` and/or `TinyUSB_Device_FlushCDC()` may (or not) need to be manually called in loop() +- Use `SerialTinyUSB` name instead of Serial for serial monitor +- And there could be more other issues, using on these cores should be considered as experimental + +## Class Driver API + +More document to write ... + +## Porting Guide + +To integrate TinyUSB library to a Arduino core, you will need to make changes to the core for built-in support and library for porting the mcu/platform. + +### Arduino Core Changes + +If possible, making changes to core will allow it to have built-in which make it almost transparent to user sketch + +1. Add this repo as submodule (or have local copy) at your ArduioCore/libraries/Adafruit_TinyUSB_Arduino (much like SPI). +2. Since Serial as CDC is considered as part of the core, we need to have `#include "Adafruit_USBD_CDC.h"` within your `Arduino.h`. For this to work, your `platform.txt` include path need to have `"-I{runtime.platform.path}/libraries/Adafruit_TinyUSB_Arduino/src/arduino"`. +3. In your `main.cpp` before setup() invoke the `TinyUSB_Device_Init(rhport)`. This will initialize usb device hardware and tinyusb stack and also include Serial as an instance of CDC class. +4. `TinyUSB_Device_Task()` must be called whenever there is new USB event. Depending on your core and MCU with or without RTOS. There are many ways to run the task. For example: + - Use USB IRQn to set flag then invoke function later on after exiting IRQ. + - Just invoke function after the loop(), within yield(), and delay() +5. `TinyUSB_Device_FlushCDC()` should also be called often to send out Serial data as well. +6. Note: For low power platform that make use of WFI()/WFE(), extra care is required before mcu go into low power mode. Check out my PR to circuipython for reference https://github.com/adafruit/circuitpython/pull/2956 + +### Library Changes + +In addition to core changes, library need to be ported to your platform. Don't worry, tinyusb stack has already done most of heavy-lifting. You only need to write a few APIs + +1. `TinyUSB_Port_InitDevice()` hardware specific (clock, phy) to enable usb hardware then call tud_init(). This API is called as part of TinyUSB_Device_Init() invocation. +2. `TinyUSB_Port_EnterDFU()` which is called when device need to enter DFU mode, usually by touch1200 feature +3. `TinyUSB_Port_GetSerialNumber()` which is called to get unique MCU Serial ID to used as Serial string descriptor. diff --git a/libraries/Adafruit_TinyUSB_Arduino/docs/examples/webusb-rgb/application.css b/libraries/Adafruit_TinyUSB_Arduino/docs/examples/webusb-rgb/application.css new file mode 100644 index 0000000..d0661e8 --- /dev/null +++ b/libraries/Adafruit_TinyUSB_Arduino/docs/examples/webusb-rgb/application.css @@ -0,0 +1,75 @@ +.main-content { + width: 1440px; + margin: auto; + font-size: 14px; +} + +.connect-container { + margin: 20px 0; +} + +.button::before { + -webkit-border-radius: 3px; + -moz-border-radius: 3px; + -webkit-box-shadow: #959595 0 2px 5px; + -moz-box-shadow: #959595 0 2px 5px; + border-radius: 3px; + box-shadow: #959595 0 2px 5px; + content: ""; + display: block; + left: 0; + padding: 2px 0 0; + position: absolute; + top: 0; +} + +.button:active::before { padding: 1px 0 0; } + +.button.black { + background: #656565; + background: -webkit-gradient(linear, 0 0, 0 bottom, from(#656565), to(#444)); + background: -moz-linear-gradient(#656565, #444); + background: linear-gradient(#656565, #444); + border: solid 1px #535353; + border-bottom: solid 3px #414141; + box-shadow: inset 0 0 0 1px #939393; + color: #fff; + text-shadow: 0 1px 0 #2f2f2f; + padding: 8px 16px; + outline: none; +} + +.button.black:hover { + background: #4c4c4c; + background: -webkit-gradient(linear, 0 0, 0 bottom, from(#4c4c4c), to(#565656)); + background: -moz-linear-gradient(#4c4c4c, #565656); + background: linear-gradient(#4c4c4c, #565656); + border: solid 1px #464646; + border-bottom: solid 3px #414141; + box-shadow: inset 0 0 0 1px #818181; +} + +.button.black:active { + background: #474747; + background: -webkit-gradient(linear, 0 0, 0 bottom, from(#474747), to(#444)); + background: -moz-linear-gradient(#474747, #444); + background: linear-gradient(#474747, #444); + border: solid 1px #2f2f2f; + box-shadow: inset 0 10px 15px 0 #3e3e3e; +} + +.color-picker-container { + width: 100px; + height: 100px; + border-radius: 50px; + overflow: hidden; +} + +.color-picker { + padding: 0; + border: none; + width: 200px; + height: 200px; + outline: none; + transform: translate(-25%, -25%) +} diff --git a/libraries/Adafruit_TinyUSB_Arduino/docs/examples/webusb-rgb/application.js b/libraries/Adafruit_TinyUSB_Arduino/docs/examples/webusb-rgb/application.js new file mode 100644 index 0000000..b0fb5a5 --- /dev/null +++ b/libraries/Adafruit_TinyUSB_Arduino/docs/examples/webusb-rgb/application.js @@ -0,0 +1,54 @@ +(function() { + 'use strict'; + + document.addEventListener('DOMContentLoaded', event => { + let connectButton = document.querySelector("#connect"); + let statusDisplay = document.querySelector('#status'); + let port; + + function connect() { + port.connect().then(() => { + statusDisplay.textContent = ''; + connectButton.textContent = 'Disconnect'; + + port.onReceiveError = error => { + console.error(error); + }; + }, error => { + statusDisplay.textContent = error; + }); + } + + connectButton.addEventListener('click', function() { + if (port) { + port.disconnect(); + connectButton.textContent = 'Connect'; + statusDisplay.textContent = ''; + port = null; + } else { + serial.requestPort().then(selectedPort => { + port = selectedPort; + connect(); + }).catch(error => { + statusDisplay.textContent = error; + }); + } + }); + + serial.getPorts().then(ports => { + if (ports.length === 0) { + statusDisplay.textContent = 'No device found.'; + } else { + statusDisplay.textContent = 'Connecting...'; + port = ports[0]; + connect(); + } + }); + + let colorPicker = document.getElementById("color_picker"); + + colorPicker.addEventListener("change", function(event) { + port.send(new TextEncoder("utf-8").encode(colorPicker.value)); + }); + }); +})(); diff --git a/libraries/Adafruit_TinyUSB_Arduino/docs/examples/webusb-rgb/index.html b/libraries/Adafruit_TinyUSB_Arduino/docs/examples/webusb-rgb/index.html new file mode 100644 index 0000000..44d00ab --- /dev/null +++ b/libraries/Adafruit_TinyUSB_Arduino/docs/examples/webusb-rgb/index.html @@ -0,0 +1,21 @@ + + + + TinyUSB + + + + + +
+

TinyUSB - WebUSB RGB Example

+
+ + +
+
+ +
+
+ + \ No newline at end of file diff --git a/libraries/Adafruit_TinyUSB_Arduino/docs/examples/webusb-rgb/serial.js b/libraries/Adafruit_TinyUSB_Arduino/docs/examples/webusb-rgb/serial.js new file mode 100644 index 0000000..ddaff52 --- /dev/null +++ b/libraries/Adafruit_TinyUSB_Arduino/docs/examples/webusb-rgb/serial.js @@ -0,0 +1,92 @@ +var serial = {}; + +(function() { + 'use strict'; + + serial.getPorts = function() { + return navigator.usb.getDevices().then(devices => { + return devices.map(device => new serial.Port(device)); + }); + }; + + serial.requestPort = function() { + const filters = [ + { 'vendorId': 0xcafe }, // TinyUSB + { 'vendorId': 0x239a }, // Adafruit + { 'vendorId': 0x2e8a }, // Raspberry Pi + { 'vendorId': 0x303a }, // Espressif + { 'vendorId': 0x2341 }, // Arduino + ]; + return navigator.usb.requestDevice({ 'filters': filters }).then( + device => new serial.Port(device) + ); + } + + serial.Port = function(device) { + this.device_ = device; + this.interfaceNumber = 0; + this.endpointIn = 0; + this.endpointOut = 0; + }; + + serial.Port.prototype.connect = function() { + let readLoop = () => { + this.device_.transferIn(this.endpointIn, 64).then(result => { + this.onReceive(result.data); + readLoop(); + }, error => { + this.onReceiveError(error); + }); + }; + + return this.device_.open() + .then(() => { + if (this.device_.configuration === null) { + return this.device_.selectConfiguration(1); + } + }) + .then(() => { + var interfaces = this.device_.configuration.interfaces; + interfaces.forEach(element => { + element.alternates.forEach(elementalt => { + if (elementalt.interfaceClass==0xFF) { + this.interfaceNumber = element.interfaceNumber; + elementalt.endpoints.forEach(elementendpoint => { + if (elementendpoint.direction == "out") { + this.endpointOut = elementendpoint.endpointNumber; + } + if (elementendpoint.direction=="in") { + this.endpointIn =elementendpoint.endpointNumber; + } + }) + } + }) + }) + }) + .then(() => this.device_.claimInterface(this.interfaceNumber)) + .then(() => this.device_.selectAlternateInterface(this.interfaceNumber, 0)) + .then(() => this.device_.controlTransferOut({ + 'requestType': 'class', + 'recipient': 'interface', + 'request': 0x22, + 'value': 0x01, + 'index': this.interfaceNumber})) + .then(() => { + readLoop(); + }); + }; + + serial.Port.prototype.disconnect = function() { + return this.device_.controlTransferOut({ + 'requestType': 'class', + 'recipient': 'interface', + 'request': 0x22, + 'value': 0x00, + 'index': this.interfaceNumber}) + .then(() => this.device_.close()); + }; + + serial.Port.prototype.send = function(data) { + return this.device_.transferOut(this.endpointOut, data); + }; +})(); diff --git a/libraries/Adafruit_TinyUSB_Arduino/docs/examples/webusb-serial/application.css b/libraries/Adafruit_TinyUSB_Arduino/docs/examples/webusb-serial/application.css new file mode 100644 index 0000000..4ae0138 --- /dev/null +++ b/libraries/Adafruit_TinyUSB_Arduino/docs/examples/webusb-serial/application.css @@ -0,0 +1,107 @@ +.main-content { + width: 1440px; + margin: auto; + font-size: 14px; +} + +.connect-container { + margin: 20px 0; +} + +.container { + display: flex; +} + +.sender, .receiver { + flex: 1; +} + +.sender { + margin-right: 8px; +} + +.receiver { + margin-left: 8px; +} + +.lines-header { + height: 30px; + width: 100%; + box-sizing: border-box; + background-color: #444; + line-height: 30px; + color: white; + padding-left: 10px; +} + +.lines-body { + width: 100%; + background-color: #222; + min-height: 300px; + padding: 10px 0 20px 0; +} + +.line, .command-line { + box-sizing: border-box; + width: 100%; + color: #f1f1f1; + background-color: #222; + outline: none; + border: none; + padding: 5px 10px; + font-size: 14px; +} + +.line:hover { + background-color: #444; +} + +.button::before { + -webkit-border-radius: 3px; + -moz-border-radius: 3px; + -webkit-box-shadow: #959595 0 2px 5px; + -moz-box-shadow: #959595 0 2px 5px; + border-radius: 3px; + box-shadow: #959595 0 2px 5px; + content: ""; + display: block; + left: 0; + padding: 2px 0 0; + position: absolute; + top: 0; +} + +.button:active::before { padding: 1px 0 0; } + +.button.black { + background: #656565; + background: -webkit-gradient(linear, 0 0, 0 bottom, from(#656565), to(#444)); + background: -moz-linear-gradient(#656565, #444); + background: linear-gradient(#656565, #444); + border: solid 1px #535353; + border-bottom: solid 3px #414141; + box-shadow: inset 0 0 0 1px #939393; + color: #fff; + text-shadow: 0 1px 0 #2f2f2f; + padding: 8px 16px; + outline: none; +} + +.button.black:hover { + background: #4c4c4c; + background: -webkit-gradient(linear, 0 0, 0 bottom, from(#4c4c4c), to(#565656)); + background: -moz-linear-gradient(#4c4c4c, #565656); + background: linear-gradient(#4c4c4c, #565656); + border: solid 1px #464646; + border-bottom: solid 3px #414141; + box-shadow: inset 0 0 0 1px #818181; +} + +.button.black:active { + background: #474747; + background: -webkit-gradient(linear, 0 0, 0 bottom, from(#474747), to(#444)); + background: -moz-linear-gradient(#474747, #444); + background: linear-gradient(#474747, #444); + border: solid 1px #2f2f2f; + box-shadow: inset 0 10px 15px 0 #3e3e3e; +} diff --git a/libraries/Adafruit_TinyUSB_Arduino/docs/examples/webusb-serial/application.js b/libraries/Adafruit_TinyUSB_Arduino/docs/examples/webusb-serial/application.js new file mode 100644 index 0000000..b990bd5 --- /dev/null +++ b/libraries/Adafruit_TinyUSB_Arduino/docs/examples/webusb-serial/application.js @@ -0,0 +1,96 @@ +(function() { + 'use strict'; + + document.addEventListener('DOMContentLoaded', event => { + let connectButton = document.querySelector("#connect"); + let statusDisplay = document.querySelector('#status'); + let port; + + function addLine(linesId, text) { + var senderLine = document.createElement("div"); + senderLine.className = 'line'; + var textnode = document.createTextNode(text); + senderLine.appendChild(textnode); + document.getElementById(linesId).appendChild(senderLine); + return senderLine; + } + + let currentReceiverLine; + + function appendLines(linesId, text) { + const lines = text.split('\r'); + if (currentReceiverLine) { + currentReceiverLine.innerHTML = currentReceiverLine.innerHTML + lines[0]; + for (let i = 1; i < lines.length; i++) { + currentReceiverLine = addLine(linesId, lines[i]); + } + } else { + for (let i = 0; i < lines.length; i++) { + currentReceiverLine = addLine(linesId, lines[i]); + } + } + } + + function connect() { + port.connect().then(() => { + statusDisplay.textContent = ''; + connectButton.textContent = 'Disconnect'; + + port.onReceive = data => { + let textDecoder = new TextDecoder(); + console.log(textDecoder.decode(data)); + if (data.getInt8() === 13) { + currentReceiverLine = null; + } else { + appendLines('receiver_lines', textDecoder.decode(data)); + } + }; + port.onReceiveError = error => { + console.error(error); + }; + }, error => { + statusDisplay.textContent = error; + }); + } + + connectButton.addEventListener('click', function() { + if (port) { + port.disconnect(); + connectButton.textContent = 'Connect'; + statusDisplay.textContent = ''; + port = null; + } else { + serial.requestPort().then(selectedPort => { + port = selectedPort; + connect(); + }).catch(error => { + statusDisplay.textContent = error; + }); + } + }); + + serial.getPorts().then(ports => { + if (ports.length === 0) { + statusDisplay.textContent = 'No device found.'; + } else { + statusDisplay.textContent = 'Connecting...'; + port = ports[0]; + connect(); + } + }); + + + let commandLine = document.getElementById("command_line"); + + commandLine.addEventListener("keypress", function(event) { + if (event.keyCode === 13) { + if (commandLine.value.length > 0) { + addLine('sender_lines', commandLine.value); + commandLine.value = ''; + } + } + + port.send(new TextEncoder('utf-8').encode(String.fromCharCode(event.which || event.keyCode))); + }); + }); +})(); diff --git a/libraries/Adafruit_TinyUSB_Arduino/docs/examples/webusb-serial/index.html b/libraries/Adafruit_TinyUSB_Arduino/docs/examples/webusb-serial/index.html new file mode 100644 index 0000000..2f1432b --- /dev/null +++ b/libraries/Adafruit_TinyUSB_Arduino/docs/examples/webusb-serial/index.html @@ -0,0 +1,33 @@ + + + + TinyUSB + + + + + +
+

TinyUSB - WebUSB Serial Example

+
+ + +
+
+
+
Sender
+
+
+ +
+
+
+
Receiver
+
+
+
+
+
+
+ + diff --git a/libraries/Adafruit_TinyUSB_Arduino/docs/examples/webusb-serial/serial.js b/libraries/Adafruit_TinyUSB_Arduino/docs/examples/webusb-serial/serial.js new file mode 100644 index 0000000..ddaff52 --- /dev/null +++ b/libraries/Adafruit_TinyUSB_Arduino/docs/examples/webusb-serial/serial.js @@ -0,0 +1,92 @@ +var serial = {}; + +(function() { + 'use strict'; + + serial.getPorts = function() { + return navigator.usb.getDevices().then(devices => { + return devices.map(device => new serial.Port(device)); + }); + }; + + serial.requestPort = function() { + const filters = [ + { 'vendorId': 0xcafe }, // TinyUSB + { 'vendorId': 0x239a }, // Adafruit + { 'vendorId': 0x2e8a }, // Raspberry Pi + { 'vendorId': 0x303a }, // Espressif + { 'vendorId': 0x2341 }, // Arduino + ]; + return navigator.usb.requestDevice({ 'filters': filters }).then( + device => new serial.Port(device) + ); + } + + serial.Port = function(device) { + this.device_ = device; + this.interfaceNumber = 0; + this.endpointIn = 0; + this.endpointOut = 0; + }; + + serial.Port.prototype.connect = function() { + let readLoop = () => { + this.device_.transferIn(this.endpointIn, 64).then(result => { + this.onReceive(result.data); + readLoop(); + }, error => { + this.onReceiveError(error); + }); + }; + + return this.device_.open() + .then(() => { + if (this.device_.configuration === null) { + return this.device_.selectConfiguration(1); + } + }) + .then(() => { + var interfaces = this.device_.configuration.interfaces; + interfaces.forEach(element => { + element.alternates.forEach(elementalt => { + if (elementalt.interfaceClass==0xFF) { + this.interfaceNumber = element.interfaceNumber; + elementalt.endpoints.forEach(elementendpoint => { + if (elementendpoint.direction == "out") { + this.endpointOut = elementendpoint.endpointNumber; + } + if (elementendpoint.direction=="in") { + this.endpointIn =elementendpoint.endpointNumber; + } + }) + } + }) + }) + }) + .then(() => this.device_.claimInterface(this.interfaceNumber)) + .then(() => this.device_.selectAlternateInterface(this.interfaceNumber, 0)) + .then(() => this.device_.controlTransferOut({ + 'requestType': 'class', + 'recipient': 'interface', + 'request': 0x22, + 'value': 0x01, + 'index': this.interfaceNumber})) + .then(() => { + readLoop(); + }); + }; + + serial.Port.prototype.disconnect = function() { + return this.device_.controlTransferOut({ + 'requestType': 'class', + 'recipient': 'interface', + 'request': 0x22, + 'value': 0x00, + 'index': this.interfaceNumber}) + .then(() => this.device_.close()); + }; + + serial.Port.prototype.send = function(data) { + return this.device_.transferOut(this.endpointOut, data); + }; +})(); diff --git a/libraries/Adafruit_TinyUSB_Arduino/examples/CDC/cdc_multi/.skip.txt b/libraries/Adafruit_TinyUSB_Arduino/examples/CDC/cdc_multi/.skip.txt new file mode 100644 index 0000000..0c82774 --- /dev/null +++ b/libraries/Adafruit_TinyUSB_Arduino/examples/CDC/cdc_multi/.skip.txt @@ -0,0 +1,8 @@ +feather_esp32_v2 +feather_esp32s2 +feather_esp32s3 +funhouse +magtag +metroesp32s2 +esp32p4 +pico_rp2040_tinyusb_host diff --git a/libraries/Adafruit_TinyUSB_Arduino/examples/CDC/cdc_multi/cdc_multi.ino b/libraries/Adafruit_TinyUSB_Arduino/examples/CDC/cdc_multi/cdc_multi.ino new file mode 100644 index 0000000..6e2cd2e --- /dev/null +++ b/libraries/Adafruit_TinyUSB_Arduino/examples/CDC/cdc_multi/cdc_multi.ino @@ -0,0 +1,127 @@ +/* + This example demonstrates the use of multiple USB CDC/ACM "Virtual + Serial" ports + + Written by Bill Westfield (aka WestfW), June 2021. + Copyright 2021 by Bill Westfield + MIT license, check LICENSE for more information +*/ + + +/* The example creates two virtual serial ports. Text entered on + * any of the ports will be echoed to the all ports with + * - all lower case in port0 (Serial) + * - all upper case in port1 + * + * Requirement: + * The max number of CDC ports (CFG_TUD_CDC) has to be changed to at least 2. + * Config file is located in Adafruit_TinyUSB_Arduino/src/arduino/ports/{platform}/tusb_config_{platform}.h + * where platform is one of: nrf, rp2040, samd + * + * NOTE: Currnetly multiple CDCs on ESP32-Sx is not yet supported. + * An PR to update core/esp32/USBCDC and/or pre-built libusb are needed. + * We would implement this later when we could. + */ + +#include + +// Create 2nd instance of CDC Ports. +#ifdef ARDUINO_ARCH_ESP32 + #error "Currently multiple CDCs on ESP32 is not yet supported" + // for ESP32, we need to specify instance number when declaring object + Adafruit_USBD_CDC USBSer1(1); +#else + Adafruit_USBD_CDC USBSer1; +#endif + +void setup() { +#ifdef LED_BUILTIN + pinMode(LED_BUILTIN, OUTPUT); +#endif + + Serial.begin(115200); + + // check to see if multiple CDCs are enabled + if ( CFG_TUD_CDC < 2 ) { + #ifdef LED_BUILTIN + digitalWrite(LED_BUILTIN, HIGH); // LED on for error indicator + #endif + + while(1) { + Serial.printf("CFG_TUD_CDC must be at least 2, current value is %u\n", CFG_TUD_CDC); + Serial.println(" Config file is located in Adafruit_TinyUSB_Arduino/src/arduino/ports/{platform}/tusb_config_{platform}.h"); + Serial.println(" where platform is one of: nrf, rp2040, samd"); + delay(1000); + } + } + + // initialize 2nd CDC interface + USBSer1.begin(115200); + + // If already enumerated, additional class driverr begin() e.g msc, hid, midi won't take effect until re-enumeration + if (TinyUSBDevice.mounted()) { + TinyUSBDevice.detach(); + delay(10); + TinyUSBDevice.attach(); + } + + while (!Serial || !USBSer1) { + if (Serial) { + Serial.println("Waiting for other USB ports"); + } + + if (USBSer1) { + USBSer1.println("Waiting for other USB ports"); + } + + delay(1000); + } + + Serial.print("You are port 0\n\r\n0> "); + USBSer1.print("You are port 1\n\r\n1> "); +} + +int LEDstate = 0; + +void loop() { + int ch; + + ch = Serial.read(); + if (ch > 0) { + printAll(ch); + } + + ch = USBSer1.read(); + if (ch > 0) { + printAll(ch); + } + + if (delay_without_delaying(500)) { + LEDstate = !LEDstate; + #ifdef LED_BUILTIN + digitalWrite(LED_BUILTIN, LEDstate); + #endif + } +} + +// print to all CDC ports +void printAll(int ch) { + // always lower case + Serial.write(tolower(ch)); + + // always upper case + USBSer1.write(toupper(ch)); +} + +// Helper: non-blocking "delay" alternative. +boolean delay_without_delaying(unsigned long time) { + // return false if we're still "delaying", true if time ms has passed. + // this should look a lot like "blink without delay" + static unsigned long previousmillis = 0; + unsigned long currentmillis = millis(); + if (currentmillis - previousmillis >= time) { + previousmillis = currentmillis; + return true; + } + return false; +} diff --git a/libraries/Adafruit_TinyUSB_Arduino/examples/CDC/no_serial/.skip.txt b/libraries/Adafruit_TinyUSB_Arduino/examples/CDC/no_serial/.skip.txt new file mode 100644 index 0000000..e4f9319 --- /dev/null +++ b/libraries/Adafruit_TinyUSB_Arduino/examples/CDC/no_serial/.skip.txt @@ -0,0 +1,2 @@ +feather_esp32_v2 +pico_rp2040_tinyusb_host diff --git a/libraries/Adafruit_TinyUSB_Arduino/examples/CDC/no_serial/no_serial.ino b/libraries/Adafruit_TinyUSB_Arduino/examples/CDC/no_serial/no_serial.ino new file mode 100644 index 0000000..e87020d --- /dev/null +++ b/libraries/Adafruit_TinyUSB_Arduino/examples/CDC/no_serial/no_serial.ino @@ -0,0 +1,57 @@ +/********************************************************************* + Adafruit invests time and resources providing this open source code, + please support Adafruit and open-source hardware by purchasing + products from Adafruit! + + MIT license, check LICENSE for more information + Copyright (c) 2019 Ha Thach for Adafruit Industries + All text above, and the splash screen below must be included in + any redistribution +*********************************************************************/ + +#include "Adafruit_TinyUSB.h" + +/* This sketch demonstrates USB CDC Serial can be dropped by simply + * call Serial.end() within setup(). This must be called before any + * other USB interfaces (MSC / HID) begin to have a clean configuration + * + * Note: this will cause device to loose the touch1200 and require + * user manual interaction to put device into bootloader/DFU mode. + */ +void setup() { + // Manual begin() is required on core without built-in support e.g. mbed rp2040 + if (!TinyUSBDevice.isInitialized()) { + TinyUSBDevice.begin(0); + } + + // clear configuration will remove all USB interfaces including CDC (Serial) + TinyUSBDevice.clearConfiguration(); + + // If already enumerated, additional class driverr begin() e.g msc, hid, midi won't take effect until re-enumeration + if (TinyUSBDevice.mounted()) { + TinyUSBDevice.detach(); + delay(10); + TinyUSBDevice.attach(); + } + + #ifdef LED_BUILTIN + pinMode(LED_BUILTIN, OUTPUT); + #endif +} + +void loop() { + #ifdef TINYUSB_NEED_POLLING_TASK + // Manual call tud_task since it isn't called by Core's background + TinyUSBDevice.task(); + #endif + + // toggle LED + static uint32_t ms = 0; + static uint8_t led_state = 0; + if (millis() - ms > 1000) { + ms = millis(); + #ifdef LED_BUILTIN + digitalWrite(LED_BUILTIN, 1-led_state); + #endif + } +} diff --git a/libraries/Adafruit_TinyUSB_Arduino/examples/CDC/serial_echo/.skip.txt b/libraries/Adafruit_TinyUSB_Arduino/examples/CDC/serial_echo/.skip.txt new file mode 100644 index 0000000..e4f9319 --- /dev/null +++ b/libraries/Adafruit_TinyUSB_Arduino/examples/CDC/serial_echo/.skip.txt @@ -0,0 +1,2 @@ +feather_esp32_v2 +pico_rp2040_tinyusb_host diff --git a/libraries/Adafruit_TinyUSB_Arduino/examples/CDC/serial_echo/serial_echo.ino b/libraries/Adafruit_TinyUSB_Arduino/examples/CDC/serial_echo/serial_echo.ino new file mode 100644 index 0000000..0d48089 --- /dev/null +++ b/libraries/Adafruit_TinyUSB_Arduino/examples/CDC/serial_echo/serial_echo.ino @@ -0,0 +1,41 @@ +/********************************************************************* + Adafruit invests time and resources providing this open source code, + please support Adafruit and open-source hardware by purchasing + products from Adafruit! + + MIT license, check LICENSE for more information + Copyright (c) 2019 Ha Thach for Adafruit Industries + All text above, and the splash screen below must be included in + any redistribution +*********************************************************************/ + +#include "Adafruit_TinyUSB.h" + +/* This sketch demonstrates USB CDC Serial echo (convert to upper case) using SerialTinyUSB which + * is available for both core with built-in USB support and without. + * Note: on core with built-in support Serial is alias to SerialTinyUSB + */ + +void setup() { + // Manual begin() is required on core without built-in support e.g. mbed rp2040 + if (!TinyUSBDevice.isInitialized()) { + TinyUSBDevice.begin(0); + } +} + +void loop() { + #ifdef TINYUSB_NEED_POLLING_TASK + // Manual call tud_task since it isn't called by Core's background + TinyUSBDevice.task(); + #endif + + uint8_t buf[64]; + uint32_t count = 0; + while (SerialTinyUSB.available()) { + buf[count++] = (uint8_t) toupper(SerialTinyUSB.read()); + } + + if (count) { + SerialTinyUSB.write(buf, count); + } +} diff --git a/libraries/Adafruit_TinyUSB_Arduino/examples/Composite/mouse_ramdisk/.skip.txt b/libraries/Adafruit_TinyUSB_Arduino/examples/Composite/mouse_ramdisk/.skip.txt new file mode 100644 index 0000000..e4f9319 --- /dev/null +++ b/libraries/Adafruit_TinyUSB_Arduino/examples/Composite/mouse_ramdisk/.skip.txt @@ -0,0 +1,2 @@ +feather_esp32_v2 +pico_rp2040_tinyusb_host diff --git a/libraries/Adafruit_TinyUSB_Arduino/examples/Composite/mouse_ramdisk/mouse_ramdisk.ino b/libraries/Adafruit_TinyUSB_Arduino/examples/Composite/mouse_ramdisk/mouse_ramdisk.ino new file mode 100644 index 0000000..e188bc4 --- /dev/null +++ b/libraries/Adafruit_TinyUSB_Arduino/examples/Composite/mouse_ramdisk/mouse_ramdisk.ino @@ -0,0 +1,178 @@ +/********************************************************************* + Adafruit invests time and resources providing this open source code, + please support Adafruit and open-source hardware by purchasing + products from Adafruit! + + MIT license, check LICENSE for more information + Copyright (c) 2019 Ha Thach for Adafruit Industries + All text above, and the splash screen below must be included in + any redistribution +*********************************************************************/ + +/* This sketch demonstrates USB Mass Storage and HID mouse (and CDC) + * - Enumerated as 8KB flash disk + * - Press button pin will move mouse toward bottom right of monitor + */ + +#include "Adafruit_TinyUSB.h" + +//--------------------------------------------------------------------+ +// MSC RAM Disk Config +//--------------------------------------------------------------------+ + +// 8KB is the smallest size that windows allow to mount +#define DISK_BLOCK_NUM 16 +#define DISK_BLOCK_SIZE 512 + +#include "ramdisk.h" + +Adafruit_USBD_MSC usb_msc; + +//--------------------------------------------------------------------+ +// HID Config +//--------------------------------------------------------------------+ + +// HID report descriptor using TinyUSB's template +// Single Report (no ID) descriptor +uint8_t const desc_hid_report[] = { + TUD_HID_REPORT_DESC_MOUSE() +}; + +// USB HID object +Adafruit_USBD_HID usb_hid; + +#if defined(ARDUINO_SAMD_CIRCUITPLAYGROUND_EXPRESS) || defined(ARDUINO_NRF52840_CIRCUITPLAY) +const int pin = 4; // Left Button +bool activeState = true; + +#elif defined(ARDUINO_FUNHOUSE_ESP32S2) +const int pin = BUTTON_DOWN; +bool activeState = true; + +#elif defined PIN_BUTTON1 +const int pin = PIN_BUTTON1; +bool activeState = false; + +#elif defined(ARDUINO_ARCH_ESP32) +const int pin = 0; +bool activeState = false; + +#elif defined(ARDUINO_ARCH_RP2040) +const int pin = D0; +bool activeState = false; + +#else +const int pin = A0; +bool activeState = false; +#endif + + +// the setup function runs once when you press reset or power the board +void setup() { + // Manual begin() is required on core without built-in support e.g. mbed rp2040 + if (!TinyUSBDevice.isInitialized()) { + TinyUSBDevice.begin(0); + } + + Serial.begin(115200); + + // Set disk vendor id, product id and revision with string up to 8, 16, 4 characters respectively + usb_msc.setID("Adafruit", "Mass Storage", "1.0"); + + // Set disk size + usb_msc.setCapacity(DISK_BLOCK_NUM, DISK_BLOCK_SIZE); + + // Set callback + usb_msc.setReadWriteCallback(msc_read_cb, msc_write_cb, msc_flush_cb); + + // Set Lun ready (RAM disk is always ready) + usb_msc.setUnitReady(true); + + usb_msc.begin(); + + // Set up button + pinMode(pin, activeState ? INPUT_PULLDOWN : INPUT_PULLUP); + + // Set up HID + usb_hid.setReportDescriptor(desc_hid_report, sizeof(desc_hid_report)); + usb_hid.setBootProtocol(HID_ITF_PROTOCOL_NONE); + usb_hid.setPollInterval(2); + usb_hid.begin(); + + // If already enumerated, additional class driverr begin() e.g msc, hid, midi won't take effect until re-enumeration + if (TinyUSBDevice.mounted()) { + TinyUSBDevice.detach(); + delay(10); + TinyUSBDevice.attach(); + } + + Serial.println("Adafruit TinyUSB Mouse + Mass Storage (ramdisk) example"); +} + +void process_hid() { + // button is active low + uint32_t const btn = (digitalRead(pin) == activeState); + + // Remote wakeup + if (TinyUSBDevice.suspended() && btn) { + // Wake up host if we are in suspend mode + // and REMOTE_WAKEUP feature is enabled by host + tud_remote_wakeup(); + } + + /*------------- Mouse -------------*/ + if (usb_hid.ready()) { + if (btn) { + int8_t const delta = 5; + usb_hid.mouseMove(0, delta, delta); // no ID: right + down + + // delay a bit before attempt to send keyboard report + delay(10); + } + } +} + +void loop() { + #ifdef TINYUSB_NEED_POLLING_TASK + // Manual call tud_task since it isn't called by Core's background + TinyUSBDevice.task(); + #endif + + // not enumerated()/mounted() yet: nothing to do + if (!TinyUSBDevice.mounted()) { + return; + } + + // poll gpio once each 10 ms + static uint32_t ms = 0; + if (millis() - ms > 10) { + ms = millis(); + process_hid(); + } +} + +// Callback invoked when received READ10 command. +// Copy disk's data to buffer (up to bufsize) and +// return number of copied bytes (must be multiple of block size) +int32_t msc_read_cb(uint32_t lba, void* buffer, uint32_t bufsize) { + uint8_t const* addr = msc_disk[lba]; + memcpy(buffer, addr, bufsize); + + return bufsize; +} + +// Callback invoked when received WRITE10 command. +// Process data in buffer to disk's storage and +// return number of written bytes (must be multiple of block size) +int32_t msc_write_cb(uint32_t lba, uint8_t* buffer, uint32_t bufsize) { + uint8_t* addr = msc_disk[lba]; + memcpy(addr, buffer, bufsize); + + return bufsize; +} + +// Callback invoked when WRITE10 command is completed (status received and accepted by host). +// used to flush any pending cache. +void msc_flush_cb(void) { + // nothing to do +} diff --git a/libraries/Adafruit_TinyUSB_Arduino/examples/Composite/mouse_ramdisk/ramdisk.h b/libraries/Adafruit_TinyUSB_Arduino/examples/Composite/mouse_ramdisk/ramdisk.h new file mode 100644 index 0000000..11aae40 --- /dev/null +++ b/libraries/Adafruit_TinyUSB_Arduino/examples/Composite/mouse_ramdisk/ramdisk.h @@ -0,0 +1,113 @@ +/* + * The MIT License (MIT) + * + * Copyright (c) 2019 Ha Thach for Adafruit Industries + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#ifndef RAMDISK_H_ +#define RAMDISK_H_ + +#define README_CONTENTS \ + "This is TinyUSB MassStorage device demo for Arduino on RAM disk." + +uint8_t msc_disk[DISK_BLOCK_NUM][DISK_BLOCK_SIZE] = { + //------------- Block0: Boot Sector -------------// + // byte_per_sector = DISK_BLOCK_SIZE; fat12_sector_num_16 = + // DISK_BLOCK_NUM; sector_per_cluster = 1; reserved_sectors = 1; fat_num = + // 1; fat12_root_entry_num = 16; sector_per_fat = 1; sector_per_track = + // 1; head_num = 1; hidden_sectors = 0; drive_number = 0x80; + // media_type = 0xf8; extended_boot_signature = 0x29; filesystem_type = + // "FAT12 "; volume_serial_number = 0x1234; volume_label = "TinyUSB MSC"; + // FAT magic code at offset 510-511 + {0xEB, 0x3C, 0x90, 0x4D, 0x53, 0x44, 0x4F, 0x53, 0x35, 0x2E, 0x30, 0x00, + 0x02, 0x01, 0x01, 0x00, 0x01, 0x10, 0x00, 0x10, 0x00, 0xF8, 0x01, 0x00, + 0x01, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x80, 0x00, 0x29, 0x34, 0x12, 0x00, 0x00, 'T', 'i', 'n', 'y', 'U', 'S', + 'B', ' ', 'M', 'S', 'C', 0x46, 0x41, 0x54, 0x31, 0x32, 0x20, 0x20, 0x20, + 0x00, 0x00, + + // Zero up to 2 last bytes of FAT magic code + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x55, 0xAA}, + + //------------- Block1: FAT12 Table -------------// + { + 0xF8, 0xFF, 0xFF, 0xFF, 0x0F // // first 2 entries must be F8FF, third + // entry is cluster end of readme file + }, + + //------------- Block2: Root Directory -------------// + { + // first entry is volume label + 'T', 'i', 'n', 'y', 'U', 'S', 'B', ' ', 'M', 'S', 'C', 0x08, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x4F, 0x6D, 0x65, 0x43, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + // second entry is readme file + 'R', 'E', 'A', 'D', 'M', 'E', ' ', ' ', 'T', 'X', 'T', 0x20, 0x00, 0xC6, + 0x52, 0x6D, 0x65, 0x43, 0x65, 0x43, 0x00, 0x00, 0x88, 0x6D, 0x65, 0x43, + 0x02, 0x00, sizeof(README_CONTENTS) - 1, 0x00, 0x00, + 0x00 // readme's files size (4 Bytes) + }, + + //------------- Block3: Readme Content -------------// + README_CONTENTS}; + +#endif /* RAMDISK_H_ */ diff --git a/libraries/Adafruit_TinyUSB_Arduino/examples/DualRole/CDC/serial_host_bridge/.skip.txt b/libraries/Adafruit_TinyUSB_Arduino/examples/DualRole/CDC/serial_host_bridge/.skip.txt new file mode 100644 index 0000000..f8e761c --- /dev/null +++ b/libraries/Adafruit_TinyUSB_Arduino/examples/DualRole/CDC/serial_host_bridge/.skip.txt @@ -0,0 +1,2 @@ +pico_rp2040_tinyusb_host +CH32V20x_EVT diff --git a/libraries/Adafruit_TinyUSB_Arduino/examples/DualRole/CDC/serial_host_bridge/serial_host_bridge.ino b/libraries/Adafruit_TinyUSB_Arduino/examples/DualRole/CDC/serial_host_bridge/serial_host_bridge.ino new file mode 100644 index 0000000..7e831e2 --- /dev/null +++ b/libraries/Adafruit_TinyUSB_Arduino/examples/DualRole/CDC/serial_host_bridge/serial_host_bridge.ino @@ -0,0 +1,170 @@ +/********************************************************************* + Adafruit invests time and resources providing this open source code, + please support Adafruit and open-source hardware by purchasing + products from Adafruit! + + MIT license, check LICENSE for more information + Copyright (c) 2019 Ha Thach for Adafruit Industries + All text above, and the splash screen below must be included in + any redistribution +*********************************************************************/ + +/* This example demonstrates use of both device and host, where + * - Device run on native usb controller (roothub port0) + * - Host depending on MCUs run on either: + * - rp2040: bit-banging 2 GPIOs with the help of Pico-PIO-USB library (roothub port1) + * - samd21/51, nrf52840, esp32: using MAX3421e controller (host shield) + * + * Requirements: + * - For rp2040: + * - [Pico-PIO-USB](https://github.com/sekigon-gonnoc/Pico-PIO-USB) library + * - 2 consecutive GPIOs: D+ is defined by PIN_USB_HOST_DP, D- = D+ +1 + * - Provide VBus (5v) and GND for peripheral + * - CPU Speed must be either 120 or 240 Mhz. Selected via "Menu -> CPU Speed" + * - For samd21/51, nrf52840, esp32: + * - Additional MAX2341e USB Host shield or featherwing is required + * - SPI instance, CS pin, INT pin are correctly configured in usbh_helper.h + */ + +/* This example demonstrates use of Host Serial (CDC). SerialHost (declared below) is + * an object to manage an CDC peripheral connected to our USB Host connector. This example + * will forward all characters from Serial to SerialHost and vice versa. + */ + +// nRF52 and ESP32 use freeRTOS, we may need to run USBhost.task() in its own rtos's thread. +// Since USBHost.task() will put loop() into dormant state and prevent followed code from running +// until there is USB host event. +#if defined(ARDUINO_NRF52_ADAFRUIT) || defined(ARDUINO_ARCH_ESP32) + #define USE_FREERTOS +#endif + +// USBHost is defined in usbh_helper.h +#include "usbh_helper.h" + +// CDC Host object +Adafruit_USBH_CDC SerialHost; + +// forward Seral <-> SerialHost +void forward_serial(void) { + uint8_t buf[64]; + + // Serial -> SerialHost + if (Serial.available()) { + size_t count = Serial.read(buf, sizeof(buf)); + if (SerialHost && SerialHost.connected()) { + SerialHost.write(buf, count); + SerialHost.flush(); + } + } + + // SerialHost -> Serial + if (SerialHost.connected() && SerialHost.available()) { + size_t count = SerialHost.read(buf, sizeof(buf)); + Serial.write(buf, count); + Serial.flush(); + } +} + +#if defined(CFG_TUH_MAX3421) && CFG_TUH_MAX3421 +//--------------------------------------------------------------------+ +// Using Host shield MAX3421E controller +//--------------------------------------------------------------------+ + +#ifdef USE_FREERTOS + +#ifdef ARDUINO_ARCH_ESP32 + #define USBH_STACK_SZ 2048 +#else + #define USBH_STACK_SZ 200 +#endif + +void usbhost_rtos_task(void *param) { + (void) param; + while (1) { + USBHost.task(); + } +} +#endif + +void setup() { + Serial.begin(115200); + + // init host stack on controller (rhport) 1 + USBHost.begin(1); + + // Initialize SerialHost + SerialHost.begin(115200); + +#ifdef USE_FREERTOS + // Create a task to run USBHost.task() in background + xTaskCreate(usbhost_rtos_task, "usbh", USBH_STACK_SZ, NULL, 3, NULL); +#endif + +// while ( !Serial ) delay(10); // wait for native usb + Serial.println("TinyUSB Host Serial Echo Example"); +} + +void loop() { +#ifndef USE_FREERTOS + USBHost.task(); +#endif + + forward_serial(); +} + +#elif defined(ARDUINO_ARCH_RP2040) +//--------------------------------------------------------------------+ +// For RP2040 use both core0 for device stack, core1 for host stack +//--------------------------------------------------------------------+ + +//------------- Core0 -------------// +void setup() { + Serial.begin(115200); + // while ( !Serial ) delay(10); // wait for native usb + Serial.println("TinyUSB Host Serial Echo Example"); +} + +void loop() { + forward_serial(); +} + +//------------- Core1 -------------// +void setup1() { + // configure pio-usb: defined in usbh_helper.h + rp2040_configure_pio_usb(); + + // run host stack on controller (rhport) 1 + // Note: For rp2040 pico-pio-usb, calling USBHost.begin() on core1 will have most of the + // host bit-banging processing works done in core1 to free up core0 for other works + USBHost.begin(1); + + // Initialize SerialHost + SerialHost.begin(115200); +} + +void loop1() { + USBHost.task(); +} + +#endif + +//--------------------------------------------------------------------+ +// TinyUSB Host callbacks +//--------------------------------------------------------------------+ +extern "C" { + +// Invoked when a device with CDC interface is mounted +// idx is index of cdc interface in the internal pool. +void tuh_cdc_mount_cb(uint8_t idx) { + // bind SerialHost object to this interface index + SerialHost.mount(idx); + Serial.println("SerialHost is connected to a new CDC device"); +} + +// Invoked when a device with CDC interface is unmounted +void tuh_cdc_umount_cb(uint8_t idx) { + SerialHost.umount(idx); + Serial.println("SerialHost is disconnected"); +} + +} diff --git a/libraries/Adafruit_TinyUSB_Arduino/examples/DualRole/CDC/serial_host_bridge/usbh_helper.h b/libraries/Adafruit_TinyUSB_Arduino/examples/DualRole/CDC/serial_host_bridge/usbh_helper.h new file mode 100644 index 0000000..29c1a6a --- /dev/null +++ b/libraries/Adafruit_TinyUSB_Arduino/examples/DualRole/CDC/serial_host_bridge/usbh_helper.h @@ -0,0 +1,99 @@ +/********************************************************************* + Adafruit invests time and resources providing this open source code, + please support Adafruit and open-source hardware by purchasing + products from Adafruit! + + MIT license, check LICENSE for more information + Copyright (c) 2019 Ha Thach for Adafruit Industries + All text above, and the splash screen below must be included in + any redistribution +*********************************************************************/ + +#ifndef USBH_HELPER_H +#define USBH_HELPER_H + +#ifdef ARDUINO_ARCH_RP2040 + // pio-usb is required for rp2040 host + #include "pio_usb.h" + + // Pin D+ for host, D- = D+ + 1 + #ifndef PIN_USB_HOST_DP + #define PIN_USB_HOST_DP 16 + #endif + + // Pin for enabling Host VBUS. comment out if not used + #ifndef PIN_5V_EN + #define PIN_5V_EN 18 + #endif + + #ifndef PIN_5V_EN_STATE + #define PIN_5V_EN_STATE 1 + #endif +#endif // ARDUINO_ARCH_RP2040 + +#include "Adafruit_TinyUSB.h" + +#if defined(CFG_TUH_MAX3421) && CFG_TUH_MAX3421 + // USB Host using MAX3421E: SPI, CS, INT + #include "SPI.h" + + #if defined(ARDUINO_METRO_ESP32S2) + Adafruit_USBH_Host USBHost(&SPI, 15, 14); + #elif defined(ARDUINO_ADAFRUIT_FEATHER_ESP32_V2) + Adafruit_USBH_Host USBHost(&SPI, 33, 15); + #else + // Default CS and INT are pin 10, 9 + Adafruit_USBH_Host USBHost(&SPI, 10, 9); + #endif +#else + // Native USB Host such as rp2040 + Adafruit_USBH_Host USBHost; +#endif + +//--------------------------------------------------------------------+ +// Helper Functions +//--------------------------------------------------------------------+ + +#ifdef ARDUINO_ARCH_RP2040 +static void rp2040_configure_pio_usb(void) { + //while ( !Serial ) delay(10); // wait for native usb + Serial.println("Core1 setup to run TinyUSB host with pio-usb"); + + // Check for CPU frequency, must be multiple of 12 Mhz for bit-banging USB + uint32_t cpu_hz = clock_get_hz(clk_sys); + if (cpu_hz % 12000000UL) { + while (!Serial) { + delay(10); // wait for native usb + } + Serial.printf("Error: CPU Clock = %lu, PIO USB require CPU clock must be multiple of 12 Mhz\r\n", cpu_hz); + Serial.printf("Change your CPU Clock to 12*n Mhz in Menu->CPU Speed \r\n"); + while (1) { + delay(1); + } + } + +#ifdef PIN_5V_EN + pinMode(PIN_5V_EN, OUTPUT); + digitalWrite(PIN_5V_EN, PIN_5V_EN_STATE); +#endif + + pio_usb_configuration_t pio_cfg = PIO_USB_DEFAULT_CONFIG; + pio_cfg.pin_dp = PIN_USB_HOST_DP; + +#if defined(ARDUINO_RASPBERRY_PI_PICO_W) + // For pico-w, PIO is also used to communicate with cyw43 + // Therefore we need to alternate the pio-usb configuration + // details https://github.com/sekigon-gonnoc/Pico-PIO-USB/issues/46 + pio_cfg.sm_tx = 3; + pio_cfg.sm_rx = 2; + pio_cfg.sm_eop = 3; + pio_cfg.pio_rx_num = 0; + pio_cfg.pio_tx_num = 1; + pio_cfg.tx_ch = 9; +#endif + + USBHost.configure_pio_usb(1, &pio_cfg); +} +#endif + +#endif diff --git a/libraries/Adafruit_TinyUSB_Arduino/examples/DualRole/HID/hid_device_report/.skip.txt b/libraries/Adafruit_TinyUSB_Arduino/examples/DualRole/HID/hid_device_report/.skip.txt new file mode 100644 index 0000000..f8e761c --- /dev/null +++ b/libraries/Adafruit_TinyUSB_Arduino/examples/DualRole/HID/hid_device_report/.skip.txt @@ -0,0 +1,2 @@ +pico_rp2040_tinyusb_host +CH32V20x_EVT diff --git a/libraries/Adafruit_TinyUSB_Arduino/examples/DualRole/HID/hid_device_report/hid_device_report.ino b/libraries/Adafruit_TinyUSB_Arduino/examples/DualRole/HID/hid_device_report/hid_device_report.ino new file mode 100644 index 0000000..2abe49a --- /dev/null +++ b/libraries/Adafruit_TinyUSB_Arduino/examples/DualRole/HID/hid_device_report/hid_device_report.ino @@ -0,0 +1,123 @@ +/********************************************************************* + Adafruit invests time and resources providing this open source code, + please support Adafruit and open-source hardware by purchasing + products from Adafruit! + + MIT license, check LICENSE for more information + Copyright (c) 2019 Ha Thach for Adafruit Industries + All text above, and the splash screen below must be included in + any redistribution +*********************************************************************/ + + +/* This example demonstrates use of both device and host, where + * - Device run on native usb controller (roothub port0) + * - Host depending on MCUs run on either: + * - rp2040: bit-banging 2 GPIOs with the help of Pico-PIO-USB library (roothub port1) + * - samd21/51, nrf52840, esp32: using MAX3421e controller (host shield) + * + * Requirements: + * - For rp2040: + * - [Pico-PIO-USB](https://github.com/sekigon-gonnoc/Pico-PIO-USB) library + * - 2 consecutive GPIOs: D+ is defined by PIN_USB_HOST_DP, D- = D+ +1 + * - Provide VBus (5v) and GND for peripheral + * - CPU Speed must be either 120 or 240 Mhz. Selected via "Menu -> CPU Speed" + * - For samd21/51, nrf52840, esp32: + * - Additional MAX2341e USB Host shield or featherwing is required + * - SPI instance, CS pin, INT pin are correctly configured in usbh_helper.h + */ + +// USBHost is defined in usbh_helper.h +#include "usbh_helper.h" + +#if defined(CFG_TUH_MAX3421) && CFG_TUH_MAX3421 +//--------------------------------------------------------------------+ +// Using Host shield MAX3421E controller +//--------------------------------------------------------------------+ +void setup() { + Serial.begin(115200); + + // init host stack on controller (rhport) 1 + USBHost.begin(1); + +// while ( !Serial ) delay(10); // wait for native usb + Serial.println("TinyUSB Dual: HID Device Report Example"); +} + +void loop() { + USBHost.task(); + Serial.flush(); +} + +#elif defined(ARDUINO_ARCH_RP2040) +//--------------------------------------------------------------------+ +// For RP2040 use both core0 for device stack, core1 for host stack +//--------------------------------------------------------------------+ + +//------------- Core0 -------------// +void setup() { + Serial.begin(115200); + //while ( !Serial ) delay(10); // wait for native usb + Serial.println("TinyUSB Dual: HID Device Report Example"); +} + +void loop() { + Serial.flush(); +} + +//------------- Core1 -------------// +void setup1() { + // configure pio-usb: defined in usbh_helper.h + rp2040_configure_pio_usb(); + + // run host stack on controller (rhport) 1 + // Note: For rp2040 pico-pio-usb, calling USBHost.begin() on core1 will have most of the + // host bit-banging processing works done in core1 to free up core0 for other works + USBHost.begin(1); +} + +void loop1() { + USBHost.task(); +} + +#endif + +extern "C" { + +// Invoked when device with hid interface is mounted +// Report descriptor is also available for use. +// tuh_hid_parse_report_descriptor() can be used to parse common/simple enough +// descriptor. Note: if report descriptor length > CFG_TUH_ENUMERATION_BUFSIZE, +// it will be skipped therefore report_desc = NULL, desc_len = 0 +void tuh_hid_mount_cb(uint8_t dev_addr, uint8_t instance, uint8_t const *desc_report, uint16_t desc_len) { + (void) desc_report; + (void) desc_len; + uint16_t vid, pid; + tuh_vid_pid_get(dev_addr, &vid, &pid); + + Serial.printf("HID device address = %d, instance = %d is mounted\r\n", dev_addr, instance); + Serial.printf("VID = %04x, PID = %04x\r\n", vid, pid); + if (!tuh_hid_receive_report(dev_addr, instance)) { + Serial.printf("Error: cannot request to receive report\r\n"); + } +} + +// Invoked when device with hid interface is un-mounted +void tuh_hid_umount_cb(uint8_t dev_addr, uint8_t instance) { + Serial.printf("HID device address = %d, instance = %d is unmounted\r\n", dev_addr, instance); +} + +// Invoked when received report from device via interrupt endpoint +void tuh_hid_report_received_cb(uint8_t dev_addr, uint8_t instance, uint8_t const *report, uint16_t len) { + Serial.printf("HIDreport : "); + for (uint16_t i = 0; i < len; i++) { + Serial.printf("0x%02X ", report[i]); + } + Serial.println(); + // continue to request to receive report + if (!tuh_hid_receive_report(dev_addr, instance)) { + Serial.printf("Error: cannot request to receive report\r\n"); + } +} + +} // extern C \ No newline at end of file diff --git a/libraries/Adafruit_TinyUSB_Arduino/examples/DualRole/HID/hid_device_report/usbh_helper.h b/libraries/Adafruit_TinyUSB_Arduino/examples/DualRole/HID/hid_device_report/usbh_helper.h new file mode 100644 index 0000000..29c1a6a --- /dev/null +++ b/libraries/Adafruit_TinyUSB_Arduino/examples/DualRole/HID/hid_device_report/usbh_helper.h @@ -0,0 +1,99 @@ +/********************************************************************* + Adafruit invests time and resources providing this open source code, + please support Adafruit and open-source hardware by purchasing + products from Adafruit! + + MIT license, check LICENSE for more information + Copyright (c) 2019 Ha Thach for Adafruit Industries + All text above, and the splash screen below must be included in + any redistribution +*********************************************************************/ + +#ifndef USBH_HELPER_H +#define USBH_HELPER_H + +#ifdef ARDUINO_ARCH_RP2040 + // pio-usb is required for rp2040 host + #include "pio_usb.h" + + // Pin D+ for host, D- = D+ + 1 + #ifndef PIN_USB_HOST_DP + #define PIN_USB_HOST_DP 16 + #endif + + // Pin for enabling Host VBUS. comment out if not used + #ifndef PIN_5V_EN + #define PIN_5V_EN 18 + #endif + + #ifndef PIN_5V_EN_STATE + #define PIN_5V_EN_STATE 1 + #endif +#endif // ARDUINO_ARCH_RP2040 + +#include "Adafruit_TinyUSB.h" + +#if defined(CFG_TUH_MAX3421) && CFG_TUH_MAX3421 + // USB Host using MAX3421E: SPI, CS, INT + #include "SPI.h" + + #if defined(ARDUINO_METRO_ESP32S2) + Adafruit_USBH_Host USBHost(&SPI, 15, 14); + #elif defined(ARDUINO_ADAFRUIT_FEATHER_ESP32_V2) + Adafruit_USBH_Host USBHost(&SPI, 33, 15); + #else + // Default CS and INT are pin 10, 9 + Adafruit_USBH_Host USBHost(&SPI, 10, 9); + #endif +#else + // Native USB Host such as rp2040 + Adafruit_USBH_Host USBHost; +#endif + +//--------------------------------------------------------------------+ +// Helper Functions +//--------------------------------------------------------------------+ + +#ifdef ARDUINO_ARCH_RP2040 +static void rp2040_configure_pio_usb(void) { + //while ( !Serial ) delay(10); // wait for native usb + Serial.println("Core1 setup to run TinyUSB host with pio-usb"); + + // Check for CPU frequency, must be multiple of 12 Mhz for bit-banging USB + uint32_t cpu_hz = clock_get_hz(clk_sys); + if (cpu_hz % 12000000UL) { + while (!Serial) { + delay(10); // wait for native usb + } + Serial.printf("Error: CPU Clock = %lu, PIO USB require CPU clock must be multiple of 12 Mhz\r\n", cpu_hz); + Serial.printf("Change your CPU Clock to 12*n Mhz in Menu->CPU Speed \r\n"); + while (1) { + delay(1); + } + } + +#ifdef PIN_5V_EN + pinMode(PIN_5V_EN, OUTPUT); + digitalWrite(PIN_5V_EN, PIN_5V_EN_STATE); +#endif + + pio_usb_configuration_t pio_cfg = PIO_USB_DEFAULT_CONFIG; + pio_cfg.pin_dp = PIN_USB_HOST_DP; + +#if defined(ARDUINO_RASPBERRY_PI_PICO_W) + // For pico-w, PIO is also used to communicate with cyw43 + // Therefore we need to alternate the pio-usb configuration + // details https://github.com/sekigon-gonnoc/Pico-PIO-USB/issues/46 + pio_cfg.sm_tx = 3; + pio_cfg.sm_rx = 2; + pio_cfg.sm_eop = 3; + pio_cfg.pio_rx_num = 0; + pio_cfg.pio_tx_num = 1; + pio_cfg.tx_ch = 9; +#endif + + USBHost.configure_pio_usb(1, &pio_cfg); +} +#endif + +#endif diff --git a/libraries/Adafruit_TinyUSB_Arduino/examples/DualRole/HID/hid_mouse_log_filter/.skip.txt b/libraries/Adafruit_TinyUSB_Arduino/examples/DualRole/HID/hid_mouse_log_filter/.skip.txt new file mode 100644 index 0000000..ae593a8 --- /dev/null +++ b/libraries/Adafruit_TinyUSB_Arduino/examples/DualRole/HID/hid_mouse_log_filter/.skip.txt @@ -0,0 +1,3 @@ +feather_esp32_v2 +pico_rp2040_tinyusb_host +CH32V20x_EVT diff --git a/libraries/Adafruit_TinyUSB_Arduino/examples/DualRole/HID/hid_mouse_log_filter/hid_mouse_log_filter.ino b/libraries/Adafruit_TinyUSB_Arduino/examples/DualRole/HID/hid_mouse_log_filter/hid_mouse_log_filter.ino new file mode 100644 index 0000000..985efba --- /dev/null +++ b/libraries/Adafruit_TinyUSB_Arduino/examples/DualRole/HID/hid_mouse_log_filter/hid_mouse_log_filter.ino @@ -0,0 +1,184 @@ +/********************************************************************* + Adafruit invests time and resources providing this open source code, + please support Adafruit and open-source hardware by purchasing + products from Adafruit! + + MIT license, check LICENSE for more information + Copyright (c) 2023 Bill Binko for Adafruit Industries + Based on tremor_filter example by Thach Ha + All text above, and the splash screen below must be included in + any redistribution +*********************************************************************/ + +/* This example demonstrates use of both device and host, where + * - Device run on native usb controller (roothub port0) + * - Host depending on MCUs run on either: + * - rp2040: bit-banging 2 GPIOs with the help of Pico-PIO-USB library (roothub port1) + * - samd21/51, nrf52840, esp32: using MAX3421e controller (host shield) + * + * Requirements: + * - For rp2040: + * - [Pico-PIO-USB](https://github.com/sekigon-gonnoc/Pico-PIO-USB) library + * - 2 consecutive GPIOs: D+ is defined by PIN_USB_HOST_DP, D- = D+ +1 + * - Provide VBus (5v) and GND for peripheral + * - CPU Speed must be either 120 or 240 Mhz. Selected via "Menu -> CPU Speed" + * - For samd21/51, nrf52840, esp32: + * - Additional MAX2341e USB Host shield or featherwing is required + * - SPI instance, CS pin, INT pin are correctly configured in usbh_helper.h + */ + +/* Example sketch receive mouse report from host interface (from e.g consumer mouse) + * and reduce large motions due to tremors by applying the natural log function. + * It handles negative values and a dead zone where small values will not be adjusted. + * Adjusted mouse movement are send via device interface (to PC). + */ + +// USBHost is defined in usbh_helper.h +#include "usbh_helper.h" + +// HID report descriptor using TinyUSB's template +// Single Report (no ID) descriptor +uint8_t const desc_hid_report[] = { + TUD_HID_REPORT_DESC_MOUSE() +}; + +// USB HID object: desc report, desc len, protocol, interval, use out endpoint +Adafruit_USBD_HID usb_hid(desc_hid_report, sizeof(desc_hid_report), HID_ITF_PROTOCOL_MOUSE, 2, false); + +/* Adjustable parameters for the log_filter() method. + * Adjust for each user (would be ideal to have this adjustable w/o recompiling) */ +#define PRESCALE 8.0 // Must be > 0, Higher numbers increase rate of attenuation +#define POSTSCALE 1.5 // Must be > 0, Higher numbers compensate for PRESCALE attenuation +#define DEADZONE 1.0 // Must be > 1, Movements < this magnitude will not be filtered + + +void setup() { + Serial.begin(115200); + usb_hid.begin(); + +#if defined(CFG_TUH_MAX3421) && CFG_TUH_MAX3421 + // init host stack on controller (rhport) 1 + // For rp2040: this is called in core1's setup1() + USBHost.begin(1); +#endif + + //while ( !Serial ) delay(10); // wait for native usb + Serial.println("ATMakers Logarithm Tremor Filter Example"); +} + +#if defined(CFG_TUH_MAX3421) && CFG_TUH_MAX3421 +//--------------------------------------------------------------------+ +// Using Host shield MAX3421E controller +//--------------------------------------------------------------------+ +void loop() { + USBHost.task(); + Serial.flush(); +} + +#elif defined(ARDUINO_ARCH_RP2040) +//--------------------------------------------------------------------+ +// For RP2040 use both core0 for device stack, core1 for host stack +//--------------------------------------------------------------------+ + +void loop() { + Serial.flush(); +} + +//------------- Core1 -------------// +void setup1() { + // configure pio-usb: defined in usbh_helper.h + rp2040_configure_pio_usb(); + + // run host stack on controller (rhport) 1 + // Note: For rp2040 pico-pio-usb, calling USBHost.begin() on core1 will have most of the + // host bit-banging processing works done in core1 to free up core0 for other works + USBHost.begin(1); +} + +void loop1() { + USBHost.task(); +} + +#endif + +//--------------------------------------------------------------------+ +// TinyUSB Host callbacks +//--------------------------------------------------------------------+ +extern "C" +{ + +// Invoked when device with hid interface is mounted +// Report descriptor is also available for use. +// tuh_hid_parse_report_descriptor() can be used to parse common/simple enough +// descriptor. Note: if report descriptor length > CFG_TUH_ENUMERATION_BUFSIZE, +// it will be skipped therefore report_desc = NULL, desc_len = 0 +void tuh_hid_mount_cb(uint8_t dev_addr, uint8_t instance, uint8_t const *desc_report, uint16_t desc_len) { + (void) desc_report; + (void) desc_len; + uint16_t vid, pid; + tuh_vid_pid_get(dev_addr, &vid, &pid); + + Serial.printf("HID device address = %d, instance = %d is mounted\r\n", dev_addr, instance); + Serial.printf("VID = %04x, PID = %04x\r\n", vid, pid); + + uint8_t const itf_protocol = tuh_hid_interface_protocol(dev_addr, instance); + if (itf_protocol == HID_ITF_PROTOCOL_MOUSE) { + Serial.printf("HID Mouse\r\n"); + if (!tuh_hid_receive_report(dev_addr, instance)) { + Serial.printf("Error: cannot request to receive report\r\n"); + } + } +} + +// Invoked when device with hid interface is un-mounted +void tuh_hid_umount_cb(uint8_t dev_addr, uint8_t instance) { + Serial.printf("HID device address = %d, instance = %d is unmounted\r\n", dev_addr, instance); +} + + +// Invoked when received report from device via interrupt endpoint +void tuh_hid_report_received_cb(uint8_t dev_addr, uint8_t instance, uint8_t const *report, uint16_t len) { + filter_report((hid_mouse_report_t const *) report); + + // continue to request to receive report + if (!tuh_hid_receive_report(dev_addr, instance)) { + Serial.printf("Error: cannot request to receive report\r\n"); + } +} + +} // extern C + +//--------------------------------------------------------------------+ +// Low pass filter Functions +//--------------------------------------------------------------------+ + +/* + * log_filter: Reduce large motions due to tremors by applying the natural log function + * Handles negative values and a dead zone where small values will not be adjusted + */ +int8_t log_filter(int8_t val) { + if (val < -1 * DEADZONE) { + return (int8_t) (-1.0 * POSTSCALE * logf(-1.0 * PRESCALE * (float) val)); + } else if (val > DEADZONE) { + return (int8_t) (POSTSCALE * logf(PRESCALE * (float) val)); + } else { + return val; + } +} + +/* + * Adjust HID report by applying log_filter + */ +void filter_report(hid_mouse_report_t const *report) { + + int8_t old_x = report->x; + int8_t old_y = report->y; + + hid_mouse_report_t filtered_report = *report; + filtered_report.x = log_filter(old_x); + filtered_report.y = log_filter(old_y); + + //Serial.printf("%d,%d,%d,%d\n", old_x, filtered_report.x, old_y, filtered_report.y); + usb_hid.sendReport(0, &filtered_report, sizeof(filtered_report)); + +} diff --git a/libraries/Adafruit_TinyUSB_Arduino/examples/DualRole/HID/hid_mouse_log_filter/usbh_helper.h b/libraries/Adafruit_TinyUSB_Arduino/examples/DualRole/HID/hid_mouse_log_filter/usbh_helper.h new file mode 100644 index 0000000..29c1a6a --- /dev/null +++ b/libraries/Adafruit_TinyUSB_Arduino/examples/DualRole/HID/hid_mouse_log_filter/usbh_helper.h @@ -0,0 +1,99 @@ +/********************************************************************* + Adafruit invests time and resources providing this open source code, + please support Adafruit and open-source hardware by purchasing + products from Adafruit! + + MIT license, check LICENSE for more information + Copyright (c) 2019 Ha Thach for Adafruit Industries + All text above, and the splash screen below must be included in + any redistribution +*********************************************************************/ + +#ifndef USBH_HELPER_H +#define USBH_HELPER_H + +#ifdef ARDUINO_ARCH_RP2040 + // pio-usb is required for rp2040 host + #include "pio_usb.h" + + // Pin D+ for host, D- = D+ + 1 + #ifndef PIN_USB_HOST_DP + #define PIN_USB_HOST_DP 16 + #endif + + // Pin for enabling Host VBUS. comment out if not used + #ifndef PIN_5V_EN + #define PIN_5V_EN 18 + #endif + + #ifndef PIN_5V_EN_STATE + #define PIN_5V_EN_STATE 1 + #endif +#endif // ARDUINO_ARCH_RP2040 + +#include "Adafruit_TinyUSB.h" + +#if defined(CFG_TUH_MAX3421) && CFG_TUH_MAX3421 + // USB Host using MAX3421E: SPI, CS, INT + #include "SPI.h" + + #if defined(ARDUINO_METRO_ESP32S2) + Adafruit_USBH_Host USBHost(&SPI, 15, 14); + #elif defined(ARDUINO_ADAFRUIT_FEATHER_ESP32_V2) + Adafruit_USBH_Host USBHost(&SPI, 33, 15); + #else + // Default CS and INT are pin 10, 9 + Adafruit_USBH_Host USBHost(&SPI, 10, 9); + #endif +#else + // Native USB Host such as rp2040 + Adafruit_USBH_Host USBHost; +#endif + +//--------------------------------------------------------------------+ +// Helper Functions +//--------------------------------------------------------------------+ + +#ifdef ARDUINO_ARCH_RP2040 +static void rp2040_configure_pio_usb(void) { + //while ( !Serial ) delay(10); // wait for native usb + Serial.println("Core1 setup to run TinyUSB host with pio-usb"); + + // Check for CPU frequency, must be multiple of 12 Mhz for bit-banging USB + uint32_t cpu_hz = clock_get_hz(clk_sys); + if (cpu_hz % 12000000UL) { + while (!Serial) { + delay(10); // wait for native usb + } + Serial.printf("Error: CPU Clock = %lu, PIO USB require CPU clock must be multiple of 12 Mhz\r\n", cpu_hz); + Serial.printf("Change your CPU Clock to 12*n Mhz in Menu->CPU Speed \r\n"); + while (1) { + delay(1); + } + } + +#ifdef PIN_5V_EN + pinMode(PIN_5V_EN, OUTPUT); + digitalWrite(PIN_5V_EN, PIN_5V_EN_STATE); +#endif + + pio_usb_configuration_t pio_cfg = PIO_USB_DEFAULT_CONFIG; + pio_cfg.pin_dp = PIN_USB_HOST_DP; + +#if defined(ARDUINO_RASPBERRY_PI_PICO_W) + // For pico-w, PIO is also used to communicate with cyw43 + // Therefore we need to alternate the pio-usb configuration + // details https://github.com/sekigon-gonnoc/Pico-PIO-USB/issues/46 + pio_cfg.sm_tx = 3; + pio_cfg.sm_rx = 2; + pio_cfg.sm_eop = 3; + pio_cfg.pio_rx_num = 0; + pio_cfg.pio_tx_num = 1; + pio_cfg.tx_ch = 9; +#endif + + USBHost.configure_pio_usb(1, &pio_cfg); +} +#endif + +#endif diff --git a/libraries/Adafruit_TinyUSB_Arduino/examples/DualRole/HID/hid_mouse_tremor_filter/.skip.txt b/libraries/Adafruit_TinyUSB_Arduino/examples/DualRole/HID/hid_mouse_tremor_filter/.skip.txt new file mode 100644 index 0000000..ae593a8 --- /dev/null +++ b/libraries/Adafruit_TinyUSB_Arduino/examples/DualRole/HID/hid_mouse_tremor_filter/.skip.txt @@ -0,0 +1,3 @@ +feather_esp32_v2 +pico_rp2040_tinyusb_host +CH32V20x_EVT diff --git a/libraries/Adafruit_TinyUSB_Arduino/examples/DualRole/HID/hid_mouse_tremor_filter/hid_mouse_tremor_filter.ino b/libraries/Adafruit_TinyUSB_Arduino/examples/DualRole/HID/hid_mouse_tremor_filter/hid_mouse_tremor_filter.ino new file mode 100644 index 0000000..d256c87 --- /dev/null +++ b/libraries/Adafruit_TinyUSB_Arduino/examples/DualRole/HID/hid_mouse_tremor_filter/hid_mouse_tremor_filter.ino @@ -0,0 +1,205 @@ +/********************************************************************* + Adafruit invests time and resources providing this open source code, + please support Adafruit and open-source hardware by purchasing + products from Adafruit! + + MIT license, check LICENSE for more information + Copyright (c) 2019 Ha Thach for Adafruit Industries + All text above, and the splash screen below must be included in + any redistribution +*********************************************************************/ + +/* This example demonstrates use of both device and host, where + * - Device run on native usb controller (roothub port0) + * - Host depending on MCUs run on either: + * - rp2040: bit-banging 2 GPIOs with the help of Pico-PIO-USB library (roothub port1) + * - samd21/51, nrf52840, esp32: using MAX3421e controller (host shield) + * + * Requirements: + * - For rp2040: + * - [Pico-PIO-USB](https://github.com/sekigon-gonnoc/Pico-PIO-USB) library + * - 2 consecutive GPIOs: D+ is defined by PIN_USB_HOST_DP, D- = D+ +1 + * - Provide VBus (5v) and GND for peripheral + * - CPU Speed must be either 120 or 240 Mhz. Selected via "Menu -> CPU Speed" + * - For samd21/51, nrf52840, esp32: + * - Additional MAX2341e USB Host shield or featherwing is required + * - SPI instance, CS pin, INT pin are correctly configured in usbh_helper.h + */ + +/* Example sketch receive mouse report from host interface (from e.g consumer mouse) + * and apply a butterworth low pass filter with a specific CUTOFF_FREQUENCY on hid mouse movement report. + * Filtered report are send via device interface (to PC) acting as a "Mouse Tremor Filter". + */ + +// USBHost is defined in usbh_helper.h +#include "usbh_helper.h" + +// HID report descriptor using TinyUSB's template +// Single Report (no ID) descriptor +uint8_t const desc_hid_report[] = { + TUD_HID_REPORT_DESC_MOUSE() +}; + +// USB HID object: desc report, desc len, protocol, interval, use out endpoint +Adafruit_USBD_HID usb_hid(desc_hid_report, sizeof(desc_hid_report), HID_ITF_PROTOCOL_MOUSE, 2, false); + +//------------- Low pass filter with Butterworth -------------// +// Butterworth low-pass filter coefficients +typedef struct { + float b0, b1, b2, a1, a2; +} butterworth_coeffs_t; + +#define SAMPLING_FREQUENCY 100.0 // Hz +#define CUTOFF_FREQUENCY 10.0 // Hz + +// x, y +butterworth_coeffs_t coeffs[2]; + +butterworth_coeffs_t butterworth_lowpass(float cutoff_frequency, float sampling_frequency); + +void filter_report(hid_mouse_report_t const *report); + + +void setup() { + Serial.begin(115200); + usb_hid.begin(); + + coeffs[0] = butterworth_lowpass(CUTOFF_FREQUENCY, SAMPLING_FREQUENCY); + coeffs[1] = butterworth_lowpass(CUTOFF_FREQUENCY, SAMPLING_FREQUENCY); + +#if defined(CFG_TUH_MAX3421) && CFG_TUH_MAX3421 + // init host stack on controller (rhport) 1 + // For rp2040: this is called in core1's setup1() + USBHost.begin(1); +#endif + + //while ( !Serial ) delay(10); // wait for native usb + Serial.println("TinyUSB Mouse Tremor Filter Example"); +} + + +#if defined(CFG_TUH_MAX3421) && CFG_TUH_MAX3421 +//--------------------------------------------------------------------+ +// Using Host shield MAX3421E controller +//--------------------------------------------------------------------+ +void loop() { + USBHost.task(); + Serial.flush(); +} + +#elif defined(ARDUINO_ARCH_RP2040) +//--------------------------------------------------------------------+ +// For RP2040 use both core0 for device stack, core1 for host stack +//--------------------------------------------------------------------+ +void loop() { + Serial.flush(); +} + +//------------- Core1 -------------// +void setup1() { + // configure pio-usb: defined in usbh_helper.h + rp2040_configure_pio_usb(); + + // run host stack on controller (rhport) 1 + // Note: For rp2040 pico-pio-usb, calling USBHost.begin() on core1 will have most of the + // host bit-banging processing works done in core1 to free up core0 for other works + USBHost.begin(1); +} + +void loop1() { + USBHost.task(); +} + +#endif + +//--------------------------------------------------------------------+ +// TinyUSB Host callbacks +//--------------------------------------------------------------------+ +extern "C" +{ + +// Invoked when device with hid interface is mounted +// Report descriptor is also available for use. +// tuh_hid_parse_report_descriptor() can be used to parse common/simple enough +// descriptor. Note: if report descriptor length > CFG_TUH_ENUMERATION_BUFSIZE, +// it will be skipped therefore report_desc = NULL, desc_len = 0 +void tuh_hid_mount_cb(uint8_t dev_addr, uint8_t instance, uint8_t const *desc_report, uint16_t desc_len) { + (void) desc_report; + (void) desc_len; + uint16_t vid, pid; + tuh_vid_pid_get(dev_addr, &vid, &pid); + + Serial.printf("HID device address = %d, instance = %d is mounted\r\n", dev_addr, instance); + Serial.printf("VID = %04x, PID = %04x\r\n", vid, pid); + + uint8_t const itf_protocol = tuh_hid_interface_protocol(dev_addr, instance); + if (itf_protocol == HID_ITF_PROTOCOL_MOUSE) { + Serial.printf("HID Mouse\r\n"); + if (!tuh_hid_receive_report(dev_addr, instance)) { + Serial.printf("Error: cannot request to receive report\r\n"); + } + } +} + +// Invoked when device with hid interface is un-mounted +void tuh_hid_umount_cb(uint8_t dev_addr, uint8_t instance) { + Serial.printf("HID device address = %d, instance = %d is unmounted\r\n", dev_addr, instance); +} + + +// Invoked when received report from device via interrupt endpoint +void tuh_hid_report_received_cb(uint8_t dev_addr, uint8_t instance, uint8_t const *report, uint16_t len) { + filter_report((hid_mouse_report_t const *) report); + + // continue to request to receive report + if (!tuh_hid_receive_report(dev_addr, instance)) { + Serial.printf("Error: cannot request to receive report\r\n"); + } +} + +} // extern C + +//--------------------------------------------------------------------+ +// Low pass filter Functions +//--------------------------------------------------------------------+ + +butterworth_coeffs_t butterworth_lowpass(float cutoff_frequency, float sampling_frequency) { + butterworth_coeffs_t coe; + + float omega = 2.0 * PI * cutoff_frequency / sampling_frequency; + float s = sin(omega); + float t = tan(omega / 2.0); + float alpha = s / (2.0 * t); + + coe.b0 = 1.0 / (1.0 + 2.0 * alpha + 2.0 * alpha * alpha); + coe.b1 = 2.0 * coe.b0; + coe.b2 = coe.b0; + coe.a1 = 2.0 * (alpha * alpha - 1.0) * coe.b0; + coe.a2 = (1.0 - 2.0 * alpha + 2.0 * alpha * alpha) * coe.b0; + + return coe; +} + +float butterworth_filter(float data, butterworth_coeffs_t *coeffs, float *filtered, float *prev1, float *prev2) { + float output = coeffs->b0 * data + coeffs->b1 * (*prev1) + coeffs->b2 * (*prev2) - coeffs->a1 * (*filtered) - + coeffs->a2 * (*prev1); + *prev2 = *prev1; + *prev1 = data; + *filtered = output; + return output; +} + +void filter_report(hid_mouse_report_t const *report) { + static float filtered[2] = { 0.0, 0.0 }; + static float prev1[2] = { 0.0, 0.0 }; + static float prev2[2] = { 0.0, 0.0 }; + + butterworth_filter(report->x, &coeffs[0], &filtered[0], &prev1[0], &prev2[0]); + butterworth_filter(report->y, &coeffs[1], &filtered[1], &prev1[1], &prev2[1]); + + hid_mouse_report_t filtered_report = *report; + filtered_report.x = (int8_t) filtered[0]; + filtered_report.y = (int8_t) filtered[1]; + + usb_hid.sendReport(0, &filtered_report, sizeof(filtered_report)); +} diff --git a/libraries/Adafruit_TinyUSB_Arduino/examples/DualRole/HID/hid_mouse_tremor_filter/usbh_helper.h b/libraries/Adafruit_TinyUSB_Arduino/examples/DualRole/HID/hid_mouse_tremor_filter/usbh_helper.h new file mode 100644 index 0000000..29c1a6a --- /dev/null +++ b/libraries/Adafruit_TinyUSB_Arduino/examples/DualRole/HID/hid_mouse_tremor_filter/usbh_helper.h @@ -0,0 +1,99 @@ +/********************************************************************* + Adafruit invests time and resources providing this open source code, + please support Adafruit and open-source hardware by purchasing + products from Adafruit! + + MIT license, check LICENSE for more information + Copyright (c) 2019 Ha Thach for Adafruit Industries + All text above, and the splash screen below must be included in + any redistribution +*********************************************************************/ + +#ifndef USBH_HELPER_H +#define USBH_HELPER_H + +#ifdef ARDUINO_ARCH_RP2040 + // pio-usb is required for rp2040 host + #include "pio_usb.h" + + // Pin D+ for host, D- = D+ + 1 + #ifndef PIN_USB_HOST_DP + #define PIN_USB_HOST_DP 16 + #endif + + // Pin for enabling Host VBUS. comment out if not used + #ifndef PIN_5V_EN + #define PIN_5V_EN 18 + #endif + + #ifndef PIN_5V_EN_STATE + #define PIN_5V_EN_STATE 1 + #endif +#endif // ARDUINO_ARCH_RP2040 + +#include "Adafruit_TinyUSB.h" + +#if defined(CFG_TUH_MAX3421) && CFG_TUH_MAX3421 + // USB Host using MAX3421E: SPI, CS, INT + #include "SPI.h" + + #if defined(ARDUINO_METRO_ESP32S2) + Adafruit_USBH_Host USBHost(&SPI, 15, 14); + #elif defined(ARDUINO_ADAFRUIT_FEATHER_ESP32_V2) + Adafruit_USBH_Host USBHost(&SPI, 33, 15); + #else + // Default CS and INT are pin 10, 9 + Adafruit_USBH_Host USBHost(&SPI, 10, 9); + #endif +#else + // Native USB Host such as rp2040 + Adafruit_USBH_Host USBHost; +#endif + +//--------------------------------------------------------------------+ +// Helper Functions +//--------------------------------------------------------------------+ + +#ifdef ARDUINO_ARCH_RP2040 +static void rp2040_configure_pio_usb(void) { + //while ( !Serial ) delay(10); // wait for native usb + Serial.println("Core1 setup to run TinyUSB host with pio-usb"); + + // Check for CPU frequency, must be multiple of 12 Mhz for bit-banging USB + uint32_t cpu_hz = clock_get_hz(clk_sys); + if (cpu_hz % 12000000UL) { + while (!Serial) { + delay(10); // wait for native usb + } + Serial.printf("Error: CPU Clock = %lu, PIO USB require CPU clock must be multiple of 12 Mhz\r\n", cpu_hz); + Serial.printf("Change your CPU Clock to 12*n Mhz in Menu->CPU Speed \r\n"); + while (1) { + delay(1); + } + } + +#ifdef PIN_5V_EN + pinMode(PIN_5V_EN, OUTPUT); + digitalWrite(PIN_5V_EN, PIN_5V_EN_STATE); +#endif + + pio_usb_configuration_t pio_cfg = PIO_USB_DEFAULT_CONFIG; + pio_cfg.pin_dp = PIN_USB_HOST_DP; + +#if defined(ARDUINO_RASPBERRY_PI_PICO_W) + // For pico-w, PIO is also used to communicate with cyw43 + // Therefore we need to alternate the pio-usb configuration + // details https://github.com/sekigon-gonnoc/Pico-PIO-USB/issues/46 + pio_cfg.sm_tx = 3; + pio_cfg.sm_rx = 2; + pio_cfg.sm_eop = 3; + pio_cfg.pio_rx_num = 0; + pio_cfg.pio_tx_num = 1; + pio_cfg.tx_ch = 9; +#endif + + USBHost.configure_pio_usb(1, &pio_cfg); +} +#endif + +#endif diff --git a/libraries/Adafruit_TinyUSB_Arduino/examples/DualRole/HID/hid_remapper/.skip.txt b/libraries/Adafruit_TinyUSB_Arduino/examples/DualRole/HID/hid_remapper/.skip.txt new file mode 100644 index 0000000..ae593a8 --- /dev/null +++ b/libraries/Adafruit_TinyUSB_Arduino/examples/DualRole/HID/hid_remapper/.skip.txt @@ -0,0 +1,3 @@ +feather_esp32_v2 +pico_rp2040_tinyusb_host +CH32V20x_EVT diff --git a/libraries/Adafruit_TinyUSB_Arduino/examples/DualRole/HID/hid_remapper/hid_remapper.ino b/libraries/Adafruit_TinyUSB_Arduino/examples/DualRole/HID/hid_remapper/hid_remapper.ino new file mode 100644 index 0000000..5c2abfb --- /dev/null +++ b/libraries/Adafruit_TinyUSB_Arduino/examples/DualRole/HID/hid_remapper/hid_remapper.ino @@ -0,0 +1,166 @@ +/********************************************************************* + Adafruit invests time and resources providing this open source code, + please support Adafruit and open-source hardware by purchasing + products from Adafruit! + + MIT license, check LICENSE for more information + Copyright (c) 2019 Ha Thach for Adafruit Industries + All text above, and the splash screen below must be included in + any redistribution +*********************************************************************/ + +/* This example demonstrates use of both device and host, where + * - Device run on native usb controller (roothub port0) + * - Host depending on MCUs run on either: + * - rp2040: bit-banging 2 GPIOs with the help of Pico-PIO-USB library (roothub port1) + * - samd21/51, nrf52840, esp32: using MAX3421e controller (host shield) + * + * Requirements: + * - For rp2040: + * - [Pico-PIO-USB](https://github.com/sekigon-gonnoc/Pico-PIO-USB) library + * - 2 consecutive GPIOs: D+ is defined by PIN_USB_HOST_DP, D- = D+ +1 + * - Provide VBus (5v) and GND for peripheral + * - CPU Speed must be either 120 or 240 Mhz. Selected via "Menu -> CPU Speed" + * - For samd21/51, nrf52840, esp32: + * - Additional MAX2341e USB Host shield or featherwing is required + * - SPI instance, CS pin, INT pin are correctly configured in usbh_helper.h + */ + +/* Example sketch receive keyboard report from host interface (from e.g consumer keyboard) + * and remap it to another key and send it via device interface (to PC). For simplicity, + * this example only toggle shift key to the report, effectively remap: + * - all character key <-> upper case + * - number <-> its symbol (with shift) + */ + +// USBHost is defined in usbh_helper.h +#include "usbh_helper.h" + +// HID report descriptor using TinyUSB's template +// Single Report (no ID) descriptor +uint8_t const desc_hid_report[] = { + TUD_HID_REPORT_DESC_KEYBOARD() +}; + +// USB HID object: desc report, desc len, protocol, interval, use out endpoint +Adafruit_USBD_HID usb_hid(desc_hid_report, sizeof(desc_hid_report), HID_ITF_PROTOCOL_KEYBOARD, 2, false); + +void setup() { + Serial.begin(115200); + usb_hid.begin(); + +#if defined(CFG_TUH_MAX3421) && CFG_TUH_MAX3421 + // init host stack on controller (rhport) 1 + // For rp2040: this is called in core1's setup1() + USBHost.begin(1); +#endif + + //while ( !Serial ) delay(10); // wait for native usb + Serial.println("TinyUSB Host HID Remap Example"); +} + +#if defined(CFG_TUH_MAX3421) && CFG_TUH_MAX3421 +//--------------------------------------------------------------------+ +// Using Host shield MAX3421E controller +//--------------------------------------------------------------------+ +void loop() { + USBHost.task(); +} + +#elif defined(ARDUINO_ARCH_RP2040) +//--------------------------------------------------------------------+ +// For RP2040 use both core0 for device stack, core1 for host stack +//--------------------------------------------------------------------+ + +void loop() { + // nothing to do +} + +//------------- Core1 -------------// +void setup1() { + // configure pio-usb: defined in usbh_helper.h + rp2040_configure_pio_usb(); + + // run host stack on controller (rhport) 1 + // Note: For rp2040 pico-pio-usb, calling USBHost.begin() on core1 will have most of the + // host bit-banging processing works done in core1 to free up core0 for other works + USBHost.begin(1); +} + +void loop1() { + USBHost.task(); +} +#endif + +//--------------------------------------------------------------------+ +// TinyUSB Host callbacks +//--------------------------------------------------------------------+ +extern "C" +{ + +// Invoked when device with hid interface is mounted +// Report descriptor is also available for use. +// tuh_hid_parse_report_descriptor() can be used to parse common/simple enough +// descriptor. Note: if report descriptor length > CFG_TUH_ENUMERATION_BUFSIZE, +// it will be skipped therefore report_desc = NULL, desc_len = 0 +void tuh_hid_mount_cb(uint8_t dev_addr, uint8_t instance, uint8_t const *desc_report, uint16_t desc_len) { + (void) desc_report; + (void) desc_len; + uint16_t vid, pid; + tuh_vid_pid_get(dev_addr, &vid, &pid); + + Serial.printf("HID device address = %d, instance = %d is mounted\r\n", dev_addr, instance); + Serial.printf("VID = %04x, PID = %04x\r\n", vid, pid); + + uint8_t const itf_protocol = tuh_hid_interface_protocol(dev_addr, instance); + if (itf_protocol == HID_ITF_PROTOCOL_KEYBOARD) { + Serial.printf("HID Keyboard\r\n"); + if (!tuh_hid_receive_report(dev_addr, instance)) { + Serial.printf("Error: cannot request to receive report\r\n"); + } + } +} + +// Invoked when device with hid interface is un-mounted +void tuh_hid_umount_cb(uint8_t dev_addr, uint8_t instance) { + Serial.printf("HID device address = %d, instance = %d is unmounted\r\n", dev_addr, instance); +} + +void remap_key(hid_keyboard_report_t const *original_report, hid_keyboard_report_t *remapped_report) { + memcpy(remapped_report, original_report, sizeof(hid_keyboard_report_t)); + + // only remap if not empty report i.e key released + for (uint8_t i = 0; i < 6; i++) { + if (remapped_report->keycode[i] != 0) { + // Note: we ignore right shift here + remapped_report->modifier ^= KEYBOARD_MODIFIER_LEFTSHIFT; + break; + } + } +} + +// Invoked when received report from device via interrupt endpoint +void tuh_hid_report_received_cb(uint8_t dev_addr, uint8_t instance, uint8_t const *report, uint16_t len) { + if (len != 8) { + Serial.printf("report len = %u NOT 8, probably something wrong !!\r\n", len); + } else { + hid_keyboard_report_t remapped_report; + remap_key((hid_keyboard_report_t const *) report, &remapped_report); + + // send remapped report to PC + // NOTE: for better performance you should save/queue remapped report instead of + // blocking wait for usb_hid ready here + while (!usb_hid.ready()) { + yield(); + } + + usb_hid.sendReport(0, &remapped_report, sizeof(hid_keyboard_report_t)); + } + + // continue to request to receive report + if (!tuh_hid_receive_report(dev_addr, instance)) { + Serial.printf("Error: cannot request to receive report\r\n"); + } +} + +} diff --git a/libraries/Adafruit_TinyUSB_Arduino/examples/DualRole/HID/hid_remapper/usbh_helper.h b/libraries/Adafruit_TinyUSB_Arduino/examples/DualRole/HID/hid_remapper/usbh_helper.h new file mode 100644 index 0000000..29c1a6a --- /dev/null +++ b/libraries/Adafruit_TinyUSB_Arduino/examples/DualRole/HID/hid_remapper/usbh_helper.h @@ -0,0 +1,99 @@ +/********************************************************************* + Adafruit invests time and resources providing this open source code, + please support Adafruit and open-source hardware by purchasing + products from Adafruit! + + MIT license, check LICENSE for more information + Copyright (c) 2019 Ha Thach for Adafruit Industries + All text above, and the splash screen below must be included in + any redistribution +*********************************************************************/ + +#ifndef USBH_HELPER_H +#define USBH_HELPER_H + +#ifdef ARDUINO_ARCH_RP2040 + // pio-usb is required for rp2040 host + #include "pio_usb.h" + + // Pin D+ for host, D- = D+ + 1 + #ifndef PIN_USB_HOST_DP + #define PIN_USB_HOST_DP 16 + #endif + + // Pin for enabling Host VBUS. comment out if not used + #ifndef PIN_5V_EN + #define PIN_5V_EN 18 + #endif + + #ifndef PIN_5V_EN_STATE + #define PIN_5V_EN_STATE 1 + #endif +#endif // ARDUINO_ARCH_RP2040 + +#include "Adafruit_TinyUSB.h" + +#if defined(CFG_TUH_MAX3421) && CFG_TUH_MAX3421 + // USB Host using MAX3421E: SPI, CS, INT + #include "SPI.h" + + #if defined(ARDUINO_METRO_ESP32S2) + Adafruit_USBH_Host USBHost(&SPI, 15, 14); + #elif defined(ARDUINO_ADAFRUIT_FEATHER_ESP32_V2) + Adafruit_USBH_Host USBHost(&SPI, 33, 15); + #else + // Default CS and INT are pin 10, 9 + Adafruit_USBH_Host USBHost(&SPI, 10, 9); + #endif +#else + // Native USB Host such as rp2040 + Adafruit_USBH_Host USBHost; +#endif + +//--------------------------------------------------------------------+ +// Helper Functions +//--------------------------------------------------------------------+ + +#ifdef ARDUINO_ARCH_RP2040 +static void rp2040_configure_pio_usb(void) { + //while ( !Serial ) delay(10); // wait for native usb + Serial.println("Core1 setup to run TinyUSB host with pio-usb"); + + // Check for CPU frequency, must be multiple of 12 Mhz for bit-banging USB + uint32_t cpu_hz = clock_get_hz(clk_sys); + if (cpu_hz % 12000000UL) { + while (!Serial) { + delay(10); // wait for native usb + } + Serial.printf("Error: CPU Clock = %lu, PIO USB require CPU clock must be multiple of 12 Mhz\r\n", cpu_hz); + Serial.printf("Change your CPU Clock to 12*n Mhz in Menu->CPU Speed \r\n"); + while (1) { + delay(1); + } + } + +#ifdef PIN_5V_EN + pinMode(PIN_5V_EN, OUTPUT); + digitalWrite(PIN_5V_EN, PIN_5V_EN_STATE); +#endif + + pio_usb_configuration_t pio_cfg = PIO_USB_DEFAULT_CONFIG; + pio_cfg.pin_dp = PIN_USB_HOST_DP; + +#if defined(ARDUINO_RASPBERRY_PI_PICO_W) + // For pico-w, PIO is also used to communicate with cyw43 + // Therefore we need to alternate the pio-usb configuration + // details https://github.com/sekigon-gonnoc/Pico-PIO-USB/issues/46 + pio_cfg.sm_tx = 3; + pio_cfg.sm_rx = 2; + pio_cfg.sm_eop = 3; + pio_cfg.pio_rx_num = 0; + pio_cfg.pio_tx_num = 1; + pio_cfg.tx_ch = 9; +#endif + + USBHost.configure_pio_usb(1, &pio_cfg); +} +#endif + +#endif diff --git a/libraries/Adafruit_TinyUSB_Arduino/examples/DualRole/MassStorage/msc_data_logger/.skip.txt b/libraries/Adafruit_TinyUSB_Arduino/examples/DualRole/MassStorage/msc_data_logger/.skip.txt new file mode 100644 index 0000000..f8e761c --- /dev/null +++ b/libraries/Adafruit_TinyUSB_Arduino/examples/DualRole/MassStorage/msc_data_logger/.skip.txt @@ -0,0 +1,2 @@ +pico_rp2040_tinyusb_host +CH32V20x_EVT diff --git a/libraries/Adafruit_TinyUSB_Arduino/examples/DualRole/MassStorage/msc_data_logger/msc_data_logger.ino b/libraries/Adafruit_TinyUSB_Arduino/examples/DualRole/MassStorage/msc_data_logger/msc_data_logger.ino new file mode 100644 index 0000000..9869186 --- /dev/null +++ b/libraries/Adafruit_TinyUSB_Arduino/examples/DualRole/MassStorage/msc_data_logger/msc_data_logger.ino @@ -0,0 +1,231 @@ +/********************************************************************* + Adafruit invests time and resources providing this open source code, + please support Adafruit and open-source hardware by purchasing + products from Adafruit! + + MIT license, check LICENSE for more information + Copyright (c) 2019 Ha Thach for Adafruit Industries + All text above, and the splash screen below must be included in + any redistribution +*********************************************************************/ + + +/* This example demonstrates use of both device and host, where + * - Device run on native usb controller (roothub port0) + * - Host depending on MCUs run on either: + * - rp2040: bit-banging 2 GPIOs with the help of Pico-PIO-USB library (roothub port1) + * - samd21/51, nrf52840, esp32: using MAX3421e controller (host shield) + * + * Requirements: + * - For rp2040: + * - [Pico-PIO-USB](https://github.com/sekigon-gonnoc/Pico-PIO-USB) library + * - 2 consecutive GPIOs: D+ is defined by PIN_USB_HOST_DP, D- = D+ +1 + * - Provide VBus (5v) and GND for peripheral + * - CPU Speed must be either 120 or 240 Mhz. Selected via "Menu -> CPU Speed" + * - For samd21/51, nrf52840, esp32: + * - Additional MAX2341e USB Host shield or featherwing is required + * - SPI instance, CS pin, INT pin are correctly configured in usbh_helper.h + */ + +/* Example sketch read analog pin (default A0) and log it to LOG_FILE on the msc device + * every LOG_INTERVAL ms. */ + +// nRF52 and ESP32 use freeRTOS, we may need to run USBhost.task() in its own rtos's thread. +// Since USBHost.task() will put loop() into dormant state and prevent followed code from running +// until there is USB host event. +#if defined(ARDUINO_NRF52_ADAFRUIT) || defined(ARDUINO_ARCH_ESP32) + #define USE_FREERTOS +#endif + +// SdFat is required for using Adafruit_USBH_MSC_SdFatDevice +#include "SdFat_Adafruit_Fork.h" + +// USBHost is defined in usbh_helper.h +#include "usbh_helper.h" + +#define LOG_FILE "cpu_temp.csv" +#define LOG_INTERVAL 5000 + +// Analog pin for reading +const int analogPin = A0; + +// USB Host MSC Block Device object which implemented API for use with SdFat +Adafruit_USBH_MSC_BlockDevice msc_block_dev; + +// file system object from SdFat +FatVolume fatfs; +File32 f_log; + +// if file system is successfully mounted on usb block device +volatile bool is_mounted = false; + +void data_log(void) { + if (!is_mounted) { + // nothing to do + return; + } + + static unsigned long last_ms = 0; + unsigned long ms = millis(); + + if ( ms - last_ms < LOG_INTERVAL ) { + return; + } + + // Turn on LED when start writing + #ifdef LED_BUILTIN + digitalWrite(LED_BUILTIN, HIGH); + #endif + + f_log = fatfs.open(LOG_FILE, O_WRITE | O_APPEND | O_CREAT); + + if (!f_log) { + Serial.println("Cannot create file: " LOG_FILE); + } else { + int value = analogRead(analogPin); + + Serial.printf("%lu,%d\r\n", ms, value); + f_log.printf("%lu,%d\r\n", ms, value); + + f_log.close(); + } + + last_ms = ms; + Serial.flush(); +} + +#ifdef USE_FREERTOS + +#ifdef ARDUINO_ARCH_ESP32 + #define USBH_STACK_SZ 2048 +#else + #define USBH_STACK_SZ 200 +#endif + +void usbhost_rtos_task(void *param) { + (void) param; + while (1) { + USBHost.task(); + } +} + +#endif + +void setup() { + Serial.begin(115200); + + #ifdef LED_BUILTIN + pinMode(LED_BUILTIN, OUTPUT); + #endif + +#if defined(CFG_TUH_MAX3421) && CFG_TUH_MAX3421 + // init host stack on controller (rhport) 1 + // For rp2040: this is called in core1's setup1() + USBHost.begin(1); +#endif + +#ifdef USE_FREERTOS + // Create a task to run USBHost.task() in background + xTaskCreate(usbhost_rtos_task, "usbh", USBH_STACK_SZ, NULL, 3, NULL); +#endif + +// while ( !Serial ) delay(10); // wait for native usb + Serial.println("TinyUSB Host MassStorage Data Logger Example"); +} + +#if defined(CFG_TUH_MAX3421) && CFG_TUH_MAX3421 +//--------------------------------------------------------------------+ +// Using Host shield MAX3421E controller +//--------------------------------------------------------------------+ +void loop() { +#ifndef USE_FREERTOS + USBHost.task(); +#endif + data_log(); +} + +#elif defined(ARDUINO_ARCH_RP2040) +//--------------------------------------------------------------------+ +// For RP2040 use both core0 for device stack, core1 for host stack +//--------------------------------------------------------------------+ +void loop() { + data_log(); +} + +//------------- Core1 -------------// +void setup1() { + // configure pio-usb: defined in usbh_helper.h + rp2040_configure_pio_usb(); + + // run host stack on controller (rhport) 1 + // Note: For rp2040 pico-pio-usb, calling USBHost.begin() on core1 will have most of the + // host bit-banging processing works done in core1 to free up core0 for other works + USBHost.begin(1); +} + +void loop1() { + USBHost.task(); +} + +#endif + +//--------------------------------------------------------------------+ +// TinyUSB Host callbacks +//--------------------------------------------------------------------+ +bool write_complete_callback(uint8_t dev_addr, tuh_msc_complete_data_t const *cb_data) { + (void) dev_addr; + (void) cb_data; + + #ifdef LED_BUILTIN + // turn off LED after write is complete + // Note this only marks the usb transfer is complete, device can take longer to actual + // write data to physical flash + digitalWrite(LED_BUILTIN, LOW); + #endif + + return true; +} + +extern "C" +{ + +// Invoked when device is mounted (configured) +void tuh_mount_cb(uint8_t daddr) { + (void) daddr; +} + +/// Invoked when device is unmounted (bus reset/unplugged) +void tuh_umount_cb(uint8_t daddr) { + (void) daddr; +} + +// Invoked when a device with MassStorage interface is mounted +void tuh_msc_mount_cb(uint8_t dev_addr) { + // Initialize block device with MSC device address + msc_block_dev.begin(dev_addr); + + // For simplicity this example only support LUN 0 + msc_block_dev.setActiveLUN(0); + msc_block_dev.setWriteCompleteCallback(write_complete_callback); + is_mounted = fatfs.begin(&msc_block_dev); + + if (is_mounted) { + fatfs.ls(&Serial, LS_SIZE); + } else { + Serial.println("Failed to mount mass storage device. Make sure it is formatted as FAT"); + } +} + +// Invoked when a device with MassStorage interface is unmounted +void tuh_msc_umount_cb(uint8_t dev_addr) { + (void) dev_addr; + + // unmount file system + is_mounted = false; + fatfs.end(); + + // end block device + msc_block_dev.end(); +} + +} diff --git a/libraries/Adafruit_TinyUSB_Arduino/examples/DualRole/MassStorage/msc_data_logger/usbh_helper.h b/libraries/Adafruit_TinyUSB_Arduino/examples/DualRole/MassStorage/msc_data_logger/usbh_helper.h new file mode 100644 index 0000000..29c1a6a --- /dev/null +++ b/libraries/Adafruit_TinyUSB_Arduino/examples/DualRole/MassStorage/msc_data_logger/usbh_helper.h @@ -0,0 +1,99 @@ +/********************************************************************* + Adafruit invests time and resources providing this open source code, + please support Adafruit and open-source hardware by purchasing + products from Adafruit! + + MIT license, check LICENSE for more information + Copyright (c) 2019 Ha Thach for Adafruit Industries + All text above, and the splash screen below must be included in + any redistribution +*********************************************************************/ + +#ifndef USBH_HELPER_H +#define USBH_HELPER_H + +#ifdef ARDUINO_ARCH_RP2040 + // pio-usb is required for rp2040 host + #include "pio_usb.h" + + // Pin D+ for host, D- = D+ + 1 + #ifndef PIN_USB_HOST_DP + #define PIN_USB_HOST_DP 16 + #endif + + // Pin for enabling Host VBUS. comment out if not used + #ifndef PIN_5V_EN + #define PIN_5V_EN 18 + #endif + + #ifndef PIN_5V_EN_STATE + #define PIN_5V_EN_STATE 1 + #endif +#endif // ARDUINO_ARCH_RP2040 + +#include "Adafruit_TinyUSB.h" + +#if defined(CFG_TUH_MAX3421) && CFG_TUH_MAX3421 + // USB Host using MAX3421E: SPI, CS, INT + #include "SPI.h" + + #if defined(ARDUINO_METRO_ESP32S2) + Adafruit_USBH_Host USBHost(&SPI, 15, 14); + #elif defined(ARDUINO_ADAFRUIT_FEATHER_ESP32_V2) + Adafruit_USBH_Host USBHost(&SPI, 33, 15); + #else + // Default CS and INT are pin 10, 9 + Adafruit_USBH_Host USBHost(&SPI, 10, 9); + #endif +#else + // Native USB Host such as rp2040 + Adafruit_USBH_Host USBHost; +#endif + +//--------------------------------------------------------------------+ +// Helper Functions +//--------------------------------------------------------------------+ + +#ifdef ARDUINO_ARCH_RP2040 +static void rp2040_configure_pio_usb(void) { + //while ( !Serial ) delay(10); // wait for native usb + Serial.println("Core1 setup to run TinyUSB host with pio-usb"); + + // Check for CPU frequency, must be multiple of 12 Mhz for bit-banging USB + uint32_t cpu_hz = clock_get_hz(clk_sys); + if (cpu_hz % 12000000UL) { + while (!Serial) { + delay(10); // wait for native usb + } + Serial.printf("Error: CPU Clock = %lu, PIO USB require CPU clock must be multiple of 12 Mhz\r\n", cpu_hz); + Serial.printf("Change your CPU Clock to 12*n Mhz in Menu->CPU Speed \r\n"); + while (1) { + delay(1); + } + } + +#ifdef PIN_5V_EN + pinMode(PIN_5V_EN, OUTPUT); + digitalWrite(PIN_5V_EN, PIN_5V_EN_STATE); +#endif + + pio_usb_configuration_t pio_cfg = PIO_USB_DEFAULT_CONFIG; + pio_cfg.pin_dp = PIN_USB_HOST_DP; + +#if defined(ARDUINO_RASPBERRY_PI_PICO_W) + // For pico-w, PIO is also used to communicate with cyw43 + // Therefore we need to alternate the pio-usb configuration + // details https://github.com/sekigon-gonnoc/Pico-PIO-USB/issues/46 + pio_cfg.sm_tx = 3; + pio_cfg.sm_rx = 2; + pio_cfg.sm_eop = 3; + pio_cfg.pio_rx_num = 0; + pio_cfg.pio_tx_num = 1; + pio_cfg.tx_ch = 9; +#endif + + USBHost.configure_pio_usb(1, &pio_cfg); +} +#endif + +#endif diff --git a/libraries/Adafruit_TinyUSB_Arduino/examples/DualRole/MassStorage/msc_file_explorer/.skip.txt b/libraries/Adafruit_TinyUSB_Arduino/examples/DualRole/MassStorage/msc_file_explorer/.skip.txt new file mode 100644 index 0000000..f8e761c --- /dev/null +++ b/libraries/Adafruit_TinyUSB_Arduino/examples/DualRole/MassStorage/msc_file_explorer/.skip.txt @@ -0,0 +1,2 @@ +pico_rp2040_tinyusb_host +CH32V20x_EVT diff --git a/libraries/Adafruit_TinyUSB_Arduino/examples/DualRole/MassStorage/msc_file_explorer/msc_file_explorer.ino b/libraries/Adafruit_TinyUSB_Arduino/examples/DualRole/MassStorage/msc_file_explorer/msc_file_explorer.ino new file mode 100644 index 0000000..54d7276 --- /dev/null +++ b/libraries/Adafruit_TinyUSB_Arduino/examples/DualRole/MassStorage/msc_file_explorer/msc_file_explorer.ino @@ -0,0 +1,136 @@ +/********************************************************************* + Adafruit invests time and resources providing this open source code, + please support Adafruit and open-source hardware by purchasing + products from Adafruit! + + MIT license, check LICENSE for more information + Copyright (c) 2019 Ha Thach for Adafruit Industries + All text above, and the splash screen below must be included in + any redistribution +*********************************************************************/ + +/* This example demonstrates use of both device and host, where + * - Device run on native usb controller (roothub port0) + * - Host depending on MCUs run on either: + * - rp2040: bit-banging 2 GPIOs with the help of Pico-PIO-USB library (roothub port1) + * - samd21/51, nrf52840, esp32: using MAX3421e controller (host shield) + * + * Requirements: + * - For rp2040: + * - [Pico-PIO-USB](https://github.com/sekigon-gonnoc/Pico-PIO-USB) library + * - 2 consecutive GPIOs: D+ is defined by PIN_USB_HOST_DP, D- = D+ +1 + * - Provide VBus (5v) and GND for peripheral + * - CPU Speed must be either 120 or 240 Mhz. Selected via "Menu -> CPU Speed" + * - For samd21/51, nrf52840, esp32: + * - Additional MAX2341e USB Host shield or featherwing is required + * - SPI instance, CS pin, INT pin are correctly configured in usbh_helper.h + */ + +// SdFat is required for using Adafruit_USBH_MSC_SdFatDevice +#include "SdFat_Adafruit_Fork.h" + +// USBHost is defined in usbh_helper.h +#include "usbh_helper.h" + +// USB Host MSC Block Device object which implemented API for use with SdFat +Adafruit_USBH_MSC_BlockDevice msc_block_dev; + +// file system object from SdFat +FatVolume fatfs; + +// if file system is successfully mounted on usb block device +bool is_mounted = false; + +void setup() { + Serial.begin(115200); + +#if defined(CFG_TUH_MAX3421) && CFG_TUH_MAX3421 + // init host stack on controller (rhport) 1 + // For rp2040: this is called in core1's setup1() + USBHost.begin(1); +#endif + + // while ( !Serial ) delay(10); // wait for native usb + Serial.println("TinyUSB Host Mass Storage File Explorer Example"); +} + + +#if defined(CFG_TUH_MAX3421) && CFG_TUH_MAX3421 +//--------------------------------------------------------------------+ +// Using Host shield MAX3421E controller +//--------------------------------------------------------------------+ +void loop() { + USBHost.task(); + Serial.flush(); +} + +#elif defined(ARDUINO_ARCH_RP2040) +//--------------------------------------------------------------------+ +// For RP2040 use both core0 for device stack, core1 for host stack +//--------------------------------------------------------------------+ +void loop() { +} + +//------------- Core1 -------------// +void setup1() { + // configure pio-usb: defined in usbh_helper.h + rp2040_configure_pio_usb(); + + // run host stack on controller (rhport) 1 + // Note: For rp2040 pico-pio-usb, calling USBHost.begin() on core1 will have most of the + // host bit-banging processing works done in core1 to free up core0 for other works + USBHost.begin(1); +} + +void loop1() { + USBHost.task(); +} + +#endif + +//--------------------------------------------------------------------+ +// TinyUSB Host callbacks +//--------------------------------------------------------------------+ +extern "C" +{ + +// Invoked when device is mounted (configured) +void tuh_mount_cb(uint8_t daddr) { + (void) daddr; +} + +/// Invoked when device is unmounted (bus reset/unplugged) +void tuh_umount_cb(uint8_t daddr) { + (void) daddr; +} + +// Invoked when a device with MassStorage interface is mounted +void tuh_msc_mount_cb(uint8_t dev_addr) { + Serial.printf("Device attached, address = %d\r\n", dev_addr); + + // Initialize block device with MSC device address + msc_block_dev.begin(dev_addr); + + // For simplicity this example only support LUN 0 + msc_block_dev.setActiveLUN(0); + + is_mounted = fatfs.begin(&msc_block_dev); + + if (is_mounted) { + fatfs.ls(&Serial, LS_SIZE); + } +} + +// Invoked when a device with MassStorage interface is unmounted +void tuh_msc_umount_cb(uint8_t dev_addr) { + Serial.printf("Device removed, address = %d\r\n", dev_addr); + + // unmount file system + is_mounted = false; + fatfs.end(); + + // end block device + msc_block_dev.end(); +} + +} \ No newline at end of file diff --git a/libraries/Adafruit_TinyUSB_Arduino/examples/DualRole/MassStorage/msc_file_explorer/usbh_helper.h b/libraries/Adafruit_TinyUSB_Arduino/examples/DualRole/MassStorage/msc_file_explorer/usbh_helper.h new file mode 100644 index 0000000..29c1a6a --- /dev/null +++ b/libraries/Adafruit_TinyUSB_Arduino/examples/DualRole/MassStorage/msc_file_explorer/usbh_helper.h @@ -0,0 +1,99 @@ +/********************************************************************* + Adafruit invests time and resources providing this open source code, + please support Adafruit and open-source hardware by purchasing + products from Adafruit! + + MIT license, check LICENSE for more information + Copyright (c) 2019 Ha Thach for Adafruit Industries + All text above, and the splash screen below must be included in + any redistribution +*********************************************************************/ + +#ifndef USBH_HELPER_H +#define USBH_HELPER_H + +#ifdef ARDUINO_ARCH_RP2040 + // pio-usb is required for rp2040 host + #include "pio_usb.h" + + // Pin D+ for host, D- = D+ + 1 + #ifndef PIN_USB_HOST_DP + #define PIN_USB_HOST_DP 16 + #endif + + // Pin for enabling Host VBUS. comment out if not used + #ifndef PIN_5V_EN + #define PIN_5V_EN 18 + #endif + + #ifndef PIN_5V_EN_STATE + #define PIN_5V_EN_STATE 1 + #endif +#endif // ARDUINO_ARCH_RP2040 + +#include "Adafruit_TinyUSB.h" + +#if defined(CFG_TUH_MAX3421) && CFG_TUH_MAX3421 + // USB Host using MAX3421E: SPI, CS, INT + #include "SPI.h" + + #if defined(ARDUINO_METRO_ESP32S2) + Adafruit_USBH_Host USBHost(&SPI, 15, 14); + #elif defined(ARDUINO_ADAFRUIT_FEATHER_ESP32_V2) + Adafruit_USBH_Host USBHost(&SPI, 33, 15); + #else + // Default CS and INT are pin 10, 9 + Adafruit_USBH_Host USBHost(&SPI, 10, 9); + #endif +#else + // Native USB Host such as rp2040 + Adafruit_USBH_Host USBHost; +#endif + +//--------------------------------------------------------------------+ +// Helper Functions +//--------------------------------------------------------------------+ + +#ifdef ARDUINO_ARCH_RP2040 +static void rp2040_configure_pio_usb(void) { + //while ( !Serial ) delay(10); // wait for native usb + Serial.println("Core1 setup to run TinyUSB host with pio-usb"); + + // Check for CPU frequency, must be multiple of 12 Mhz for bit-banging USB + uint32_t cpu_hz = clock_get_hz(clk_sys); + if (cpu_hz % 12000000UL) { + while (!Serial) { + delay(10); // wait for native usb + } + Serial.printf("Error: CPU Clock = %lu, PIO USB require CPU clock must be multiple of 12 Mhz\r\n", cpu_hz); + Serial.printf("Change your CPU Clock to 12*n Mhz in Menu->CPU Speed \r\n"); + while (1) { + delay(1); + } + } + +#ifdef PIN_5V_EN + pinMode(PIN_5V_EN, OUTPUT); + digitalWrite(PIN_5V_EN, PIN_5V_EN_STATE); +#endif + + pio_usb_configuration_t pio_cfg = PIO_USB_DEFAULT_CONFIG; + pio_cfg.pin_dp = PIN_USB_HOST_DP; + +#if defined(ARDUINO_RASPBERRY_PI_PICO_W) + // For pico-w, PIO is also used to communicate with cyw43 + // Therefore we need to alternate the pio-usb configuration + // details https://github.com/sekigon-gonnoc/Pico-PIO-USB/issues/46 + pio_cfg.sm_tx = 3; + pio_cfg.sm_rx = 2; + pio_cfg.sm_eop = 3; + pio_cfg.pio_rx_num = 0; + pio_cfg.pio_tx_num = 1; + pio_cfg.tx_ch = 9; +#endif + + USBHost.configure_pio_usb(1, &pio_cfg); +} +#endif + +#endif diff --git a/libraries/Adafruit_TinyUSB_Arduino/examples/DualRole/Simple/device_info/.skip.txt b/libraries/Adafruit_TinyUSB_Arduino/examples/DualRole/Simple/device_info/.skip.txt new file mode 100644 index 0000000..98d71f5 --- /dev/null +++ b/libraries/Adafruit_TinyUSB_Arduino/examples/DualRole/Simple/device_info/.skip.txt @@ -0,0 +1 @@ +CH32V20x_EVT diff --git a/libraries/Adafruit_TinyUSB_Arduino/examples/DualRole/Simple/device_info/device_info.ino b/libraries/Adafruit_TinyUSB_Arduino/examples/DualRole/Simple/device_info/device_info.ino new file mode 100644 index 0000000..c8b22ea --- /dev/null +++ b/libraries/Adafruit_TinyUSB_Arduino/examples/DualRole/Simple/device_info/device_info.ino @@ -0,0 +1,268 @@ +/********************************************************************* + Adafruit invests time and resources providing this open source code, + please support Adafruit and open-source hardware by purchasing + products from Adafruit! + + MIT license, check LICENSE for more information + Copyright (c) 2019 Ha Thach for Adafruit Industries + All text above, and the splash screen below must be included in + any redistribution +*********************************************************************/ + + +/* This example demonstrates use of both device and host, where + * - Device run on native usb controller (roothub port0) + * - Host depending on MCUs run on either: + * - rp2040: bit-banging 2 GPIOs with the help of Pico-PIO-USB library (roothub port1) + * - samd21/51, nrf52840, esp32: using MAX3421e controller (host shield) + * + * Requirements: + * - For rp2040: + * - [Pico-PIO-USB](https://github.com/sekigon-gonnoc/Pico-PIO-USB) library + * - 2 consecutive GPIOs: D+ is defined by PIN_USB_HOST_DP, D- = D+ +1 + * - Provide VBus (5v) and GND for peripheral + * - CPU Speed must be either 120 or 240 Mhz. Selected via "Menu -> CPU Speed" + * - For samd21/51, nrf52840, esp32: + * - Additional MAX2341e USB Host shield or featherwing is required + * - SPI instance, CS pin, INT pin are correctly configured in usbh_helper.h + */ + +/* Host example will get device descriptors of attached devices and print it out via + * device cdc (Serial) as follows: + * Device 1: ID 046d:c52f + Device Descriptor: + bLength 18 + bDescriptorType 1 + bcdUSB 0200 + bDeviceClass 0 + bDeviceSubClass 0 + bDeviceProtocol 0 + bMaxPacketSize0 8 + idVendor 0x046d + idProduct 0xc52f + bcdDevice 2200 + iManufacturer 1 Logitech + iProduct 2 USB Receiver + iSerialNumber 0 + bNumConfigurations 1 + * + */ + +// USBHost is defined in usbh_helper.h +#include "usbh_helper.h" + +// Language ID: English +#define LANGUAGE_ID 0x0409 + +typedef struct { + tusb_desc_device_t desc_device; + uint16_t manufacturer[32]; + uint16_t product[48]; + uint16_t serial[16]; + bool mounted; +} dev_info_t; + +// CFG_TUH_DEVICE_MAX is defined by tusb_config header +dev_info_t dev_info[CFG_TUH_DEVICE_MAX] = { 0 }; + +void setup() { + Serial.begin(115200); + +#if defined(CFG_TUH_MAX3421) && CFG_TUH_MAX3421 + // init host stack on controller (rhport) 1 + // For rp2040: this is called in core1's setup1() + USBHost.begin(1); +#endif + +// while ( !Serial ) delay(10); // wait for native usb + Serial.println("TinyUSB Dual Device Info Example"); +} + +#if defined(CFG_TUH_MAX3421) && CFG_TUH_MAX3421 +//--------------------------------------------------------------------+ +// Using Host shield MAX3421E controller +//--------------------------------------------------------------------+ +void loop() { + USBHost.task(); + Serial.flush(); +} + +#elif defined(ARDUINO_ARCH_RP2040) +//--------------------------------------------------------------------+ +// For RP2040 use both core0 for device stack, core1 for host stack +//--------------------------------------------------------------------+ + +//------------- Core0 -------------// +void loop() { +} + +//------------- Core1 -------------// +void setup1() { + //while ( !Serial ) delay(10); // wait for native usb + // configure pio-usb: defined in usbh_helper.h + rp2040_configure_pio_usb(); + + // run host stack on controller (rhport) 1 + // Note: For rp2040 pico-pio-usb, calling USBHost.begin() on core1 will have most of the + // host bit-banging processing works done in core1 to free up core0 for other works + USBHost.begin(1); +} + +void loop1() { + USBHost.task(); + Serial.flush(); +} +#endif + +//--------------------------------------------------------------------+ +// TinyUSB Host callbacks +//--------------------------------------------------------------------+ +void print_device_descriptor(tuh_xfer_t *xfer); + +void utf16_to_utf8(uint16_t *temp_buf, size_t buf_len); + +void print_lsusb(void) { + bool no_device = true; + for (uint8_t daddr = 1; daddr < CFG_TUH_DEVICE_MAX + 1; daddr++) { + // TODO can use tuh_mounted(daddr), but tinyusb has an bug + // use local connected flag instead + dev_info_t *dev = &dev_info[daddr - 1]; + if (dev->mounted) { + Serial.printf("Device %u: ID %04x:%04x %s %s\r\n", daddr, + dev->desc_device.idVendor, dev->desc_device.idProduct, + (char *) dev->manufacturer, (char *) dev->product); + + no_device = false; + } + } + + if (no_device) { + Serial.println("No device connected (except hub)"); + } +} + +// Invoked when device is mounted (configured) +void tuh_mount_cb(uint8_t daddr) { + Serial.printf("Device attached, address = %d\r\n", daddr); + + dev_info_t *dev = &dev_info[daddr - 1]; + dev->mounted = true; + + // Get Device Descriptor + tuh_descriptor_get_device(daddr, &dev->desc_device, 18, print_device_descriptor, 0); +} + +/// Invoked when device is unmounted (bus reset/unplugged) +void tuh_umount_cb(uint8_t daddr) { + Serial.printf("Device removed, address = %d\r\n", daddr); + dev_info_t *dev = &dev_info[daddr - 1]; + dev->mounted = false; + + // print device summary + print_lsusb(); +} + +void print_device_descriptor(tuh_xfer_t *xfer) { + if (XFER_RESULT_SUCCESS != xfer->result) { + Serial.printf("Failed to get device descriptor\r\n"); + return; + } + + uint8_t const daddr = xfer->daddr; + dev_info_t *dev = &dev_info[daddr - 1]; + tusb_desc_device_t *desc = &dev->desc_device; + + Serial.printf("Device %u: ID %04x:%04x\r\n", daddr, desc->idVendor, desc->idProduct); + Serial.printf("Device Descriptor:\r\n"); + Serial.printf(" bLength %u\r\n" , desc->bLength); + Serial.printf(" bDescriptorType %u\r\n" , desc->bDescriptorType); + Serial.printf(" bcdUSB %04x\r\n" , desc->bcdUSB); + Serial.printf(" bDeviceClass %u\r\n" , desc->bDeviceClass); + Serial.printf(" bDeviceSubClass %u\r\n" , desc->bDeviceSubClass); + Serial.printf(" bDeviceProtocol %u\r\n" , desc->bDeviceProtocol); + Serial.printf(" bMaxPacketSize0 %u\r\n" , desc->bMaxPacketSize0); + Serial.printf(" idVendor 0x%04x\r\n" , desc->idVendor); + Serial.printf(" idProduct 0x%04x\r\n" , desc->idProduct); + Serial.printf(" bcdDevice %04x\r\n" , desc->bcdDevice); + + // Get String descriptor using Sync API + Serial.printf(" iManufacturer %u ", desc->iManufacturer); + if (XFER_RESULT_SUCCESS == + tuh_descriptor_get_manufacturer_string_sync(daddr, LANGUAGE_ID, dev->manufacturer, sizeof(dev->manufacturer))) { + utf16_to_utf8(dev->manufacturer, sizeof(dev->manufacturer)); + Serial.printf((char *) dev->manufacturer); + } + Serial.printf("\r\n"); + + Serial.printf(" iProduct %u ", desc->iProduct); + if (XFER_RESULT_SUCCESS == + tuh_descriptor_get_product_string_sync(daddr, LANGUAGE_ID, dev->product, sizeof(dev->product))) { + utf16_to_utf8(dev->product, sizeof(dev->product)); + Serial.printf((char *) dev->product); + } + Serial.printf("\r\n"); + + Serial.printf(" iSerialNumber %u ", desc->iSerialNumber); + if (XFER_RESULT_SUCCESS == + tuh_descriptor_get_serial_string_sync(daddr, LANGUAGE_ID, dev->serial, sizeof(dev->serial))) { + utf16_to_utf8(dev->serial, sizeof(dev->serial)); + Serial.printf((char *) dev->serial); + } + Serial.printf("\r\n"); + + Serial.printf(" bNumConfigurations %u\r\n", desc->bNumConfigurations); + + // print device summary + print_lsusb(); +} + +//--------------------------------------------------------------------+ +// String Descriptor Helper +//--------------------------------------------------------------------+ + +static void _convert_utf16le_to_utf8(const uint16_t *utf16, size_t utf16_len, uint8_t *utf8, size_t utf8_len) { + // TODO: Check for runover. + (void) utf8_len; + // Get the UTF-16 length out of the data itself. + + for (size_t i = 0; i < utf16_len; i++) { + uint16_t chr = utf16[i]; + if (chr < 0x80) { + *utf8++ = chr & 0xff; + } else if (chr < 0x800) { + *utf8++ = (uint8_t) (0xC0 | (chr >> 6 & 0x1F)); + *utf8++ = (uint8_t) (0x80 | (chr >> 0 & 0x3F)); + } else { + // TODO: Verify surrogate. + *utf8++ = (uint8_t) (0xE0 | (chr >> 12 & 0x0F)); + *utf8++ = (uint8_t) (0x80 | (chr >> 6 & 0x3F)); + *utf8++ = (uint8_t) (0x80 | (chr >> 0 & 0x3F)); + } + // TODO: Handle UTF-16 code points that take two entries. + } +} + +// Count how many bytes a utf-16-le encoded string will take in utf-8. +static int _count_utf8_bytes(const uint16_t *buf, size_t len) { + size_t total_bytes = 0; + for (size_t i = 0; i < len; i++) { + uint16_t chr = buf[i]; + if (chr < 0x80) { + total_bytes += 1; + } else if (chr < 0x800) { + total_bytes += 2; + } else { + total_bytes += 3; + } + // TODO: Handle UTF-16 code points that take two entries. + } + return total_bytes; +} + +void utf16_to_utf8(uint16_t *temp_buf, size_t buf_len) { + size_t utf16_len = ((temp_buf[0] & 0xff) - 2) / sizeof(uint16_t); + size_t utf8_len = _count_utf8_bytes(temp_buf + 1, utf16_len); + + _convert_utf16le_to_utf8(temp_buf + 1, utf16_len, (uint8_t *) temp_buf, buf_len); + ((uint8_t *) temp_buf)[utf8_len] = '\0'; +} diff --git a/libraries/Adafruit_TinyUSB_Arduino/examples/DualRole/Simple/device_info/usbh_helper.h b/libraries/Adafruit_TinyUSB_Arduino/examples/DualRole/Simple/device_info/usbh_helper.h new file mode 100644 index 0000000..29c1a6a --- /dev/null +++ b/libraries/Adafruit_TinyUSB_Arduino/examples/DualRole/Simple/device_info/usbh_helper.h @@ -0,0 +1,99 @@ +/********************************************************************* + Adafruit invests time and resources providing this open source code, + please support Adafruit and open-source hardware by purchasing + products from Adafruit! + + MIT license, check LICENSE for more information + Copyright (c) 2019 Ha Thach for Adafruit Industries + All text above, and the splash screen below must be included in + any redistribution +*********************************************************************/ + +#ifndef USBH_HELPER_H +#define USBH_HELPER_H + +#ifdef ARDUINO_ARCH_RP2040 + // pio-usb is required for rp2040 host + #include "pio_usb.h" + + // Pin D+ for host, D- = D+ + 1 + #ifndef PIN_USB_HOST_DP + #define PIN_USB_HOST_DP 16 + #endif + + // Pin for enabling Host VBUS. comment out if not used + #ifndef PIN_5V_EN + #define PIN_5V_EN 18 + #endif + + #ifndef PIN_5V_EN_STATE + #define PIN_5V_EN_STATE 1 + #endif +#endif // ARDUINO_ARCH_RP2040 + +#include "Adafruit_TinyUSB.h" + +#if defined(CFG_TUH_MAX3421) && CFG_TUH_MAX3421 + // USB Host using MAX3421E: SPI, CS, INT + #include "SPI.h" + + #if defined(ARDUINO_METRO_ESP32S2) + Adafruit_USBH_Host USBHost(&SPI, 15, 14); + #elif defined(ARDUINO_ADAFRUIT_FEATHER_ESP32_V2) + Adafruit_USBH_Host USBHost(&SPI, 33, 15); + #else + // Default CS and INT are pin 10, 9 + Adafruit_USBH_Host USBHost(&SPI, 10, 9); + #endif +#else + // Native USB Host such as rp2040 + Adafruit_USBH_Host USBHost; +#endif + +//--------------------------------------------------------------------+ +// Helper Functions +//--------------------------------------------------------------------+ + +#ifdef ARDUINO_ARCH_RP2040 +static void rp2040_configure_pio_usb(void) { + //while ( !Serial ) delay(10); // wait for native usb + Serial.println("Core1 setup to run TinyUSB host with pio-usb"); + + // Check for CPU frequency, must be multiple of 12 Mhz for bit-banging USB + uint32_t cpu_hz = clock_get_hz(clk_sys); + if (cpu_hz % 12000000UL) { + while (!Serial) { + delay(10); // wait for native usb + } + Serial.printf("Error: CPU Clock = %lu, PIO USB require CPU clock must be multiple of 12 Mhz\r\n", cpu_hz); + Serial.printf("Change your CPU Clock to 12*n Mhz in Menu->CPU Speed \r\n"); + while (1) { + delay(1); + } + } + +#ifdef PIN_5V_EN + pinMode(PIN_5V_EN, OUTPUT); + digitalWrite(PIN_5V_EN, PIN_5V_EN_STATE); +#endif + + pio_usb_configuration_t pio_cfg = PIO_USB_DEFAULT_CONFIG; + pio_cfg.pin_dp = PIN_USB_HOST_DP; + +#if defined(ARDUINO_RASPBERRY_PI_PICO_W) + // For pico-w, PIO is also used to communicate with cyw43 + // Therefore we need to alternate the pio-usb configuration + // details https://github.com/sekigon-gonnoc/Pico-PIO-USB/issues/46 + pio_cfg.sm_tx = 3; + pio_cfg.sm_rx = 2; + pio_cfg.sm_eop = 3; + pio_cfg.pio_rx_num = 0; + pio_cfg.pio_tx_num = 1; + pio_cfg.tx_ch = 9; +#endif + + USBHost.configure_pio_usb(1, &pio_cfg); +} +#endif + +#endif diff --git a/libraries/Adafruit_TinyUSB_Arduino/examples/DualRole/Simple/device_info_max3421e/.skip.txt b/libraries/Adafruit_TinyUSB_Arduino/examples/DualRole/Simple/device_info_max3421e/.skip.txt new file mode 100644 index 0000000..f8e761c --- /dev/null +++ b/libraries/Adafruit_TinyUSB_Arduino/examples/DualRole/Simple/device_info_max3421e/.skip.txt @@ -0,0 +1,2 @@ +pico_rp2040_tinyusb_host +CH32V20x_EVT diff --git a/libraries/Adafruit_TinyUSB_Arduino/examples/DualRole/Simple/device_info_max3421e/device_info_max3421e.ino b/libraries/Adafruit_TinyUSB_Arduino/examples/DualRole/Simple/device_info_max3421e/device_info_max3421e.ino new file mode 100644 index 0000000..0ab8843 --- /dev/null +++ b/libraries/Adafruit_TinyUSB_Arduino/examples/DualRole/Simple/device_info_max3421e/device_info_max3421e.ino @@ -0,0 +1,241 @@ +/********************************************************************* + Adafruit invests time and resources providing this open source code, + please support Adafruit and open-source hardware by purchasing + products from Adafruit! + + MIT license, check LICENSE for more information + Copyright (c) 2019 Ha Thach for Adafruit Industries + All text above, and the splash screen below must be included in + any redistribution +*********************************************************************/ + +/* This example demonstrates use of both device and host, where + * - Device run on native usb controller (roothub port0) + * - Host run on MAX3421E controller (roothub port1) tested with: + * - SAMD21, SAMD51, nRF52840, ESP32S2, ESP32S3, ESP32 + * - RP2040: "pio_usb.h" must not be included, otherwise pio-usb will be used as host controller + * + * Requirements: + * - SPI instance, CS pin, INT pin are correctly configured + */ + +/* Host example will get device descriptors of attached devices and print it out: + * Device 1: ID 046d:c52f + Device Descriptor: + bLength 18 + bDescriptorType 1 + bcdUSB 0200 + bDeviceClass 0 + bDeviceSubClass 0 + bDeviceProtocol 0 + bMaxPacketSize0 8 + idVendor 0x046d + idProduct 0xc52f + bcdDevice 2200 + iManufacturer 1 Logitech + iProduct 2 USB Receiver + iSerialNumber 0 + bNumConfigurations 1 + * + */ +#include "Adafruit_TinyUSB.h" +#include "SPI.h" + +// USB Host using MAX3421E: SPI, CS, INT +#if defined(ARDUINO_METRO_ESP32S2) + Adafruit_USBH_Host USBHost(&SPI, 15, 14); +#elif defined(ARDUINO_ADAFRUIT_FEATHER_ESP32_V2) + Adafruit_USBH_Host USBHost(&SPI, 33, 15); +#elif defined(ARDUINO_ADAFRUIT_FEATHER_ESP32C6) + Adafruit_USBH_Host USBHost(&SPI, 8, 7); +#elif defined(ARDUINO_ESP32C3_DEV) + Adafruit_USBH_Host USBHost(&SPI, 10, 7); +#else + // Default CS and INT are pin 10, 9 + Adafruit_USBH_Host USBHost(&SPI, 10, 9); +#endif + +// Language ID: English +#define LANGUAGE_ID 0x0409 + +typedef struct { + tusb_desc_device_t desc_device; + uint16_t manufacturer[32]; + uint16_t product[48]; + uint16_t serial[16]; + bool mounted; +} dev_info_t; + +// CFG_TUH_DEVICE_MAX is defined by tusb_config header +dev_info_t dev_info[CFG_TUH_DEVICE_MAX] = { 0 }; + +//--------------------------------------------------------------------+ +// setup() & loop() +//--------------------------------------------------------------------+ +void setup() { + Serial.begin(115200); + + // init host stack on controller (rhport) 1 + USBHost.begin(1); + +// while ( !Serial ) delay(10); // wait for native usb + Serial.println("TinyUSB Dual: Device Info Example with MAX3421E"); +} + +void loop() { + USBHost.task(); + Serial.flush(); +} + +//--------------------------------------------------------------------+ +// TinyUSB Host callbacks +//--------------------------------------------------------------------+ +void print_device_descriptor(tuh_xfer_t *xfer); + +void utf16_to_utf8(uint16_t *temp_buf, size_t buf_len); + +void print_lsusb(void) { + bool no_device = true; + for (uint8_t daddr = 1; daddr < CFG_TUH_DEVICE_MAX + 1; daddr++) { + // TODO can use tuh_mounted(daddr), but tinyusb has an bug + // use local connected flag instead + dev_info_t *dev = &dev_info[daddr - 1]; + if (dev->mounted) { + Serial.printf("Device %u: ID %04x:%04x %s %s\r\n", daddr, + dev->desc_device.idVendor, dev->desc_device.idProduct, + (char *) dev->manufacturer, (char *) dev->product); + + no_device = false; + } + } + + if (no_device) { + Serial.println("No device connected (except hub)"); + } +} + +// Invoked when device is mounted (configured) +void tuh_mount_cb(uint8_t daddr) { + Serial.printf("Device attached, address = %d\r\n", daddr); + + dev_info_t *dev = &dev_info[daddr - 1]; + dev->mounted = true; + + // Get Device Descriptor + tuh_descriptor_get_device(daddr, &dev->desc_device, 18, print_device_descriptor, 0); +} + +/// Invoked when device is unmounted (bus reset/unplugged) +void tuh_umount_cb(uint8_t daddr) { + Serial.printf("Device removed, address = %d\r\n", daddr); + dev_info_t *dev = &dev_info[daddr - 1]; + dev->mounted = false; + + // print device summary + print_lsusb(); +} + +void print_device_descriptor(tuh_xfer_t *xfer) { + if (XFER_RESULT_SUCCESS != xfer->result) { + Serial.printf("Failed to get device descriptor\r\n"); + return; + } + + uint8_t const daddr = xfer->daddr; + dev_info_t *dev = &dev_info[daddr - 1]; + tusb_desc_device_t *desc = &dev->desc_device; + + Serial.printf("Device %u: ID %04x:%04x\r\n", daddr, desc->idVendor, desc->idProduct); + Serial.printf("Device Descriptor:\r\n"); + Serial.printf(" bLength %u\r\n" , desc->bLength); + Serial.printf(" bDescriptorType %u\r\n" , desc->bDescriptorType); + Serial.printf(" bcdUSB %04x\r\n" , desc->bcdUSB); + Serial.printf(" bDeviceClass %u\r\n" , desc->bDeviceClass); + Serial.printf(" bDeviceSubClass %u\r\n" , desc->bDeviceSubClass); + Serial.printf(" bDeviceProtocol %u\r\n" , desc->bDeviceProtocol); + Serial.printf(" bMaxPacketSize0 %u\r\n" , desc->bMaxPacketSize0); + Serial.printf(" idVendor 0x%04x\r\n" , desc->idVendor); + Serial.printf(" idProduct 0x%04x\r\n" , desc->idProduct); + Serial.printf(" bcdDevice %04x\r\n" , desc->bcdDevice); + + // Get String descriptor using Sync API + Serial.printf(" iManufacturer %u ", desc->iManufacturer); + if (XFER_RESULT_SUCCESS == + tuh_descriptor_get_manufacturer_string_sync(daddr, LANGUAGE_ID, dev->manufacturer, sizeof(dev->manufacturer))) { + utf16_to_utf8(dev->manufacturer, sizeof(dev->manufacturer)); + Serial.printf((char *) dev->manufacturer); + } + Serial.printf("\r\n"); + + Serial.printf(" iProduct %u ", desc->iProduct); + if (XFER_RESULT_SUCCESS == + tuh_descriptor_get_product_string_sync(daddr, LANGUAGE_ID, dev->product, sizeof(dev->product))) { + utf16_to_utf8(dev->product, sizeof(dev->product)); + Serial.printf((char *) dev->product); + } + Serial.printf("\r\n"); + + Serial.printf(" iSerialNumber %u ", desc->iSerialNumber); + if (XFER_RESULT_SUCCESS == + tuh_descriptor_get_serial_string_sync(daddr, LANGUAGE_ID, dev->serial, sizeof(dev->serial))) { + utf16_to_utf8(dev->serial, sizeof(dev->serial)); + Serial.printf((char *) dev->serial); + } + Serial.printf("\r\n"); + + Serial.printf(" bNumConfigurations %u\r\n", desc->bNumConfigurations); + + // print device summary + print_lsusb(); +} + +//--------------------------------------------------------------------+ +// String Descriptor Helper +//--------------------------------------------------------------------+ + +static void _convert_utf16le_to_utf8(const uint16_t *utf16, size_t utf16_len, uint8_t *utf8, size_t utf8_len) { + // TODO: Check for runover. + (void) utf8_len; + // Get the UTF-16 length out of the data itself. + + for (size_t i = 0; i < utf16_len; i++) { + uint16_t chr = utf16[i]; + if (chr < 0x80) { + *utf8++ = chr & 0xff; + } else if (chr < 0x800) { + *utf8++ = (uint8_t) (0xC0 | (chr >> 6 & 0x1F)); + *utf8++ = (uint8_t) (0x80 | (chr >> 0 & 0x3F)); + } else { + // TODO: Verify surrogate. + *utf8++ = (uint8_t) (0xE0 | (chr >> 12 & 0x0F)); + *utf8++ = (uint8_t) (0x80 | (chr >> 6 & 0x3F)); + *utf8++ = (uint8_t) (0x80 | (chr >> 0 & 0x3F)); + } + // TODO: Handle UTF-16 code points that take two entries. + } +} + +// Count how many bytes a utf-16-le encoded string will take in utf-8. +static int _count_utf8_bytes(const uint16_t *buf, size_t len) { + size_t total_bytes = 0; + for (size_t i = 0; i < len; i++) { + uint16_t chr = buf[i]; + if (chr < 0x80) { + total_bytes += 1; + } else if (chr < 0x800) { + total_bytes += 2; + } else { + total_bytes += 3; + } + // TODO: Handle UTF-16 code points that take two entries. + } + return total_bytes; +} + +void utf16_to_utf8(uint16_t *temp_buf, size_t buf_len) { + size_t utf16_len = ((temp_buf[0] & 0xff) - 2) / sizeof(uint16_t); + size_t utf8_len = _count_utf8_bytes(temp_buf + 1, utf16_len); + + _convert_utf16le_to_utf8(temp_buf + 1, utf16_len, (uint8_t *) temp_buf, buf_len); + ((uint8_t *) temp_buf)[utf8_len] = '\0'; +} diff --git a/libraries/Adafruit_TinyUSB_Arduino/examples/HID/hid_boot_keyboard/.skip.txt b/libraries/Adafruit_TinyUSB_Arduino/examples/HID/hid_boot_keyboard/.skip.txt new file mode 100644 index 0000000..e4f9319 --- /dev/null +++ b/libraries/Adafruit_TinyUSB_Arduino/examples/HID/hid_boot_keyboard/.skip.txt @@ -0,0 +1,2 @@ +feather_esp32_v2 +pico_rp2040_tinyusb_host diff --git a/libraries/Adafruit_TinyUSB_Arduino/examples/HID/hid_boot_keyboard/hid_boot_keyboard.ino b/libraries/Adafruit_TinyUSB_Arduino/examples/HID/hid_boot_keyboard/hid_boot_keyboard.ino new file mode 100644 index 0000000..e666e22 --- /dev/null +++ b/libraries/Adafruit_TinyUSB_Arduino/examples/HID/hid_boot_keyboard/hid_boot_keyboard.ino @@ -0,0 +1,185 @@ +/********************************************************************* + Adafruit invests time and resources providing this open source code, + please support Adafruit and open-source hardware by purchasing + products from Adafruit! + + MIT license, check LICENSE for more information + Copyright (c) 2019 Ha Thach for Adafruit Industries + All text above, and the splash screen below must be included in + any redistribution +*********************************************************************/ + +#include "Adafruit_TinyUSB.h" + +/* This sketch demonstrates USB HID keyboard. + * - PIN A0-A3 is used to send digit '0' to '3' respectively + * (On the RP2040, pins D0-D5 used) + * - LED and/or Neopixels will be used as Capslock indicator + */ + +// HID report descriptor using TinyUSB's template +// Single Report (no ID) descriptor +uint8_t const desc_hid_report[] = { + TUD_HID_REPORT_DESC_KEYBOARD() +}; + +// USB HID object. For ESP32 these values cannot be changed after this declaration +// desc report, desc len, protocol, interval, use out endpoint +Adafruit_USBD_HID usb_hid; + +//------------- Input Pins -------------// +// Array of pins and its keycode. +// Notes: these pins can be replaced by PIN_BUTTONn if defined in setup() +#ifdef ARDUINO_ARCH_RP2040 +uint8_t pins[] = { D0, D1, D2, D3 }; +#else +uint8_t pins[] = {A0, A1, A2, A3}; +#endif + +// number of pins +uint8_t pincount = sizeof(pins) / sizeof(pins[0]); + +// For keycode definition check out https://github.com/hathach/tinyusb/blob/master/src/class/hid/hid.h +uint8_t hidcode[] = {HID_KEY_0, HID_KEY_1, HID_KEY_2, HID_KEY_3}; + +#if defined(ARDUINO_SAMD_CIRCUITPLAYGROUND_EXPRESS) || defined(ARDUINO_NRF52840_CIRCUITPLAY) || defined(ARDUINO_FUNHOUSE_ESP32S2) +bool activeState = true; +#else +bool activeState = false; +#endif + +// the setup function runs once when you press reset or power the board +void setup() { + // Manual begin() is required on core without built-in support e.g. mbed rp2040 + if (!TinyUSBDevice.isInitialized()) { + TinyUSBDevice.begin(0); + } + + // Setup HID + usb_hid.setBootProtocol(HID_ITF_PROTOCOL_KEYBOARD); + usb_hid.setPollInterval(2); + usb_hid.setReportDescriptor(desc_hid_report, sizeof(desc_hid_report)); + usb_hid.setStringDescriptor("TinyUSB Keyboard"); + + // Set up output report (on control endpoint) for Capslock indicator + usb_hid.setReportCallback(NULL, hid_report_callback); + + usb_hid.begin(); + + // If already enumerated, additional class driverr begin() e.g msc, hid, midi won't take effect until re-enumeration + if (TinyUSBDevice.mounted()) { + TinyUSBDevice.detach(); + delay(10); + TinyUSBDevice.attach(); + } + + // led pin +#ifdef LED_BUILTIN + pinMode(LED_BUILTIN, OUTPUT); + digitalWrite(LED_BUILTIN, LOW); +#endif + + // overwrite input pin with PIN_BUTTONx +#ifdef PIN_BUTTON1 + pins[0] = PIN_BUTTON1; +#endif + +#ifdef PIN_BUTTON2 + pins[1] = PIN_BUTTON2; +#endif + +#ifdef PIN_BUTTON3 + pins[2] = PIN_BUTTON3; +#endif + +#ifdef PIN_BUTTON4 + pins[3] = PIN_BUTTON4; +#endif + + // Set up pin as input + for (uint8_t i = 0; i < pincount; i++) { + pinMode(pins[i], activeState ? INPUT_PULLDOWN : INPUT_PULLUP); + } +} + +void process_hid() { + // used to avoid send multiple consecutive zero report for keyboard + static bool keyPressedPreviously = false; + + uint8_t count = 0; + uint8_t keycode[6] = {0}; + + // scan normal key and send report + for (uint8_t i = 0; i < pincount; i++) { + if (activeState == digitalRead(pins[i])) { + // if pin is active (low), add its hid code to key report + keycode[count++] = hidcode[i]; + + // 6 is max keycode per report + if (count == 6) break; + } + } + + if (TinyUSBDevice.suspended() && count) { + // Wake up host if we are in suspend mode + // and REMOTE_WAKEUP feature is enabled by host + TinyUSBDevice.remoteWakeup(); + } + + // skip if hid is not ready e.g still transferring previous report + if (!usb_hid.ready()) return; + + if (count) { + // Send report if there is key pressed + uint8_t const report_id = 0; + uint8_t const modifier = 0; + + keyPressedPreviously = true; + usb_hid.keyboardReport(report_id, modifier, keycode); + } else { + // Send All-zero report to indicate there is no keys pressed + // Most of the time, it is, though we don't need to send zero report + // every loop(), only a key is pressed in previous loop() + if (keyPressedPreviously) { + keyPressedPreviously = false; + usb_hid.keyboardRelease(0); + } + } +} + +void loop() { + #ifdef TINYUSB_NEED_POLLING_TASK + // Manual call tud_task since it isn't called by Core's background + TinyUSBDevice.task(); + #endif + + // not enumerated()/mounted() yet: nothing to do + if (!TinyUSBDevice.mounted()) { + return; + } + + // poll gpio once each 2 ms + static uint32_t ms = 0; + if (millis() - ms > 2) { + ms = millis(); + process_hid(); + } +} + +// Output report callback for LED indicator such as Caplocks +void hid_report_callback(uint8_t report_id, hid_report_type_t report_type, uint8_t const* buffer, uint16_t bufsize) { + (void) report_id; + (void) bufsize; + + // LED indicator is output report with only 1 byte length + if (report_type != HID_REPORT_TYPE_OUTPUT) return; + + // The LED bit map is as follows: (also defined by KEYBOARD_LED_* ) + // Kana (4) | Compose (3) | ScrollLock (2) | CapsLock (1) | Numlock (0) + uint8_t ledIndicator = buffer[0]; + +#ifdef LED_BUILTIN + // turn on LED if capslock is set + digitalWrite(LED_BUILTIN, ledIndicator & KEYBOARD_LED_CAPSLOCK); +#endif +} diff --git a/libraries/Adafruit_TinyUSB_Arduino/examples/HID/hid_boot_mouse/.skip.txt b/libraries/Adafruit_TinyUSB_Arduino/examples/HID/hid_boot_mouse/.skip.txt new file mode 100644 index 0000000..e4f9319 --- /dev/null +++ b/libraries/Adafruit_TinyUSB_Arduino/examples/HID/hid_boot_mouse/.skip.txt @@ -0,0 +1,2 @@ +feather_esp32_v2 +pico_rp2040_tinyusb_host diff --git a/libraries/Adafruit_TinyUSB_Arduino/examples/HID/hid_boot_mouse/hid_boot_mouse.ino b/libraries/Adafruit_TinyUSB_Arduino/examples/HID/hid_boot_mouse/hid_boot_mouse.ino new file mode 100644 index 0000000..feb458b --- /dev/null +++ b/libraries/Adafruit_TinyUSB_Arduino/examples/HID/hid_boot_mouse/hid_boot_mouse.ino @@ -0,0 +1,122 @@ +/********************************************************************* + Adafruit invests time and resources providing this open source code, + please support Adafruit and open-source hardware by purchasing + products from Adafruit! + + MIT license, check LICENSE for more information + Copyright (c) 2019 Ha Thach for Adafruit Industries + All text above, and the splash screen below must be included in + any redistribution +*********************************************************************/ + +#include "Adafruit_TinyUSB.h" + +/* This sketch demonstrates USB HID mouse + * Press button pin will move + * - mouse toward bottom right of monitor + * + * Depending on the board, the button pin + * and its active state (when pressed) are different + */ +#if defined(ARDUINO_SAMD_CIRCUITPLAYGROUND_EXPRESS) || defined(ARDUINO_NRF52840_CIRCUITPLAY) +const int pin = 4; // Left Button +bool activeState = true; + +#elif defined(ARDUINO_FUNHOUSE_ESP32S2) +const int pin = BUTTON_DOWN; +bool activeState = true; + +#elif defined PIN_BUTTON1 +const int pin = PIN_BUTTON1; +bool activeState = false; + +#elif defined(ARDUINO_ARCH_ESP32) +const int pin = 0; +bool activeState = false; + +#elif defined(ARDUINO_ARCH_RP2040) +const int pin = D0; +bool activeState = false; +#else +const int pin = A0; +bool activeState = false; +#endif + + +// HID report descriptor using TinyUSB's template +// Single Report (no ID) descriptor +uint8_t const desc_hid_report[] = { + TUD_HID_REPORT_DESC_MOUSE() +}; + +// USB HID object +Adafruit_USBD_HID usb_hid; + +// the setup function runs once when you press reset or power the board +void setup() { + // Manual begin() is required on core without built-in support e.g. mbed rp2040 + if (!TinyUSBDevice.isInitialized()) { + TinyUSBDevice.begin(0); + } + + Serial.begin(115200); + + // Set up button, pullup opposite to active state + pinMode(pin, activeState ? INPUT_PULLDOWN : INPUT_PULLUP); + + // Set up HID + usb_hid.setBootProtocol(HID_ITF_PROTOCOL_MOUSE); + usb_hid.setPollInterval(2); + usb_hid.setReportDescriptor(desc_hid_report, sizeof(desc_hid_report)); + usb_hid.setStringDescriptor("TinyUSB Mouse"); + usb_hid.begin(); + + // If already enumerated, additional class driverr begin() e.g msc, hid, midi won't take effect until re-enumeration + if (TinyUSBDevice.mounted()) { + TinyUSBDevice.detach(); + delay(10); + TinyUSBDevice.attach(); + } + + Serial.println("Adafruit TinyUSB HID Mouse example"); +} + +void process_hid() { + // Whether button is pressed + bool btn_pressed = (digitalRead(pin) == activeState); + + // nothing to do if button is not pressed + if (!btn_pressed) return; + + // Remote wakeup + if (TinyUSBDevice.suspended()) { + // Wake up host if we are in suspend mode + // and REMOTE_WAKEUP feature is enabled by host + TinyUSBDevice.remoteWakeup(); + } + + if (usb_hid.ready()) { + uint8_t const report_id = 0; // no ID + int8_t const delta = 5; + usb_hid.mouseMove(report_id, delta, delta); // right + down + } +} + +void loop() { + #ifdef TINYUSB_NEED_POLLING_TASK + // Manual call tud_task since it isn't called by Core's background + TinyUSBDevice.task(); + #endif + + // not enumerated()/mounted() yet: nothing to do + if (!TinyUSBDevice.mounted()) { + return; + } + + // poll gpio once each 10 ms + static uint32_t ms = 0; + if (millis() - ms > 10) { + ms = millis(); + process_hid(); + } +} diff --git a/libraries/Adafruit_TinyUSB_Arduino/examples/HID/hid_composite/.skip.txt b/libraries/Adafruit_TinyUSB_Arduino/examples/HID/hid_composite/.skip.txt new file mode 100644 index 0000000..e4f9319 --- /dev/null +++ b/libraries/Adafruit_TinyUSB_Arduino/examples/HID/hid_composite/.skip.txt @@ -0,0 +1,2 @@ +feather_esp32_v2 +pico_rp2040_tinyusb_host diff --git a/libraries/Adafruit_TinyUSB_Arduino/examples/HID/hid_composite/hid_composite.ino b/libraries/Adafruit_TinyUSB_Arduino/examples/HID/hid_composite/hid_composite.ino new file mode 100644 index 0000000..db2143d --- /dev/null +++ b/libraries/Adafruit_TinyUSB_Arduino/examples/HID/hid_composite/hid_composite.ino @@ -0,0 +1,174 @@ +/********************************************************************* + Adafruit invests time and resources providing this open source code, + please support Adafruit and open-source hardware by purchasing + products from Adafruit! + + MIT license, check LICENSE for more information + Copyright (c) 2019 Ha Thach for Adafruit Industries + All text above, and the splash screen below must be included in + any redistribution +*********************************************************************/ + +#include "Adafruit_TinyUSB.h" + +/* This sketch demonstrates multiple report USB HID. + * Press button pin will move + * - mouse toward bottom right of monitor + * - send 'a' key + * + * Depending on the board, the button pin + * and its active state (when pressed) are different + */ +#if defined(ARDUINO_SAMD_CIRCUITPLAYGROUND_EXPRESS) || defined(ARDUINO_NRF52840_CIRCUITPLAY) +const int pin = 4; // Left Button +bool activeState = true; + +#elif defined(ARDUINO_FUNHOUSE_ESP32S2) +const int pin = BUTTON_DOWN; +bool activeState = true; + +#elif defined PIN_BUTTON1 +const int pin = PIN_BUTTON1; +bool activeState = false; + +#elif defined(ARDUINO_ARCH_ESP32) +const int pin = 0; +bool activeState = false; + +#elif defined(ARDUINO_ARCH_RP2040) +const int pin = D0; +bool activeState = false; + +#else +const int pin = A0; +bool activeState = false; + +#endif + + +// Report ID +enum { + RID_KEYBOARD = 1, + RID_MOUSE, + RID_CONSUMER_CONTROL, // Media, volume etc .. +}; + +// HID report descriptor using TinyUSB's template +uint8_t const desc_hid_report[] = { + TUD_HID_REPORT_DESC_KEYBOARD(HID_REPORT_ID(RID_KEYBOARD)), + TUD_HID_REPORT_DESC_MOUSE (HID_REPORT_ID(RID_MOUSE)), + TUD_HID_REPORT_DESC_CONSUMER(HID_REPORT_ID(RID_CONSUMER_CONTROL)) +}; + +// USB HID object. +Adafruit_USBD_HID usb_hid; + +// the setup function runs once when you press reset or power the board +void setup() { + // Manual begin() is required on core without built-in support e.g. mbed rp2040 + if (!TinyUSBDevice.isInitialized()) { + TinyUSBDevice.begin(0); + } + + Serial.begin(115200); + + // Set up HID + usb_hid.setPollInterval(2); + usb_hid.setReportDescriptor(desc_hid_report, sizeof(desc_hid_report)); + usb_hid.setStringDescriptor("TinyUSB HID Composite"); + usb_hid.begin(); + + // If already enumerated, additional class driverr begin() e.g msc, hid, midi won't take effect until re-enumeration + if (TinyUSBDevice.mounted()) { + TinyUSBDevice.detach(); + delay(10); + TinyUSBDevice.attach(); + } + + // Set up button, pullup opposite to active state + pinMode(pin, activeState ? INPUT_PULLDOWN : INPUT_PULLUP); + + Serial.println("Adafruit TinyUSB HID Composite example"); +} + +void process_hid() { + // Whether button is pressed + bool btn_pressed = (digitalRead(pin) == activeState); + + // Remote wakeup + if (TinyUSBDevice.suspended() && btn_pressed) { + // Wake up host if we are in suspend mode + // and REMOTE_WAKEUP feature is enabled by host + TinyUSBDevice.remoteWakeup(); + } + + /*------------- Mouse -------------*/ + if (usb_hid.ready() && btn_pressed) { + int8_t const delta = 5; + usb_hid.mouseMove(RID_MOUSE, delta, delta); // right + down + + // delay a bit before attempt to send keyboard report + delay(10); + } + + /*------------- Keyboard -------------*/ + if (usb_hid.ready()) { + // use to send key release report + static bool has_key = false; + + if (btn_pressed) { + uint8_t keycode[6] = {0}; + keycode[0] = HID_KEY_A; + + usb_hid.keyboardReport(RID_KEYBOARD, 0, keycode); + + has_key = true; + } else { + // send empty key report if previously has key pressed + if (has_key) usb_hid.keyboardRelease(RID_KEYBOARD); + has_key = false; + } + + // delay a bit before attempt to send consumer report + delay(10); + } + + /*------------- Consumer Control -------------*/ + if (usb_hid.ready()) { + // Consumer Control is used to control Media playback, Volume, Brightness etc ... + // Consumer report is 2-byte containing the control code of the key + // For list of control check out https://github.com/hathach/tinyusb/blob/master/src/class/hid/hid.h + + // use to send consumer release report + static bool has_consumer_key = false; + + if (btn_pressed) { + // send volume down (0x00EA) + usb_hid.sendReport16(RID_CONSUMER_CONTROL, HID_USAGE_CONSUMER_VOLUME_DECREMENT); + has_consumer_key = true; + } else { + // release the consume key by sending zero (0x0000) + if (has_consumer_key) usb_hid.sendReport16(RID_CONSUMER_CONTROL, 0); + has_consumer_key = false; + } + } +} + +void loop() { + #ifdef TINYUSB_NEED_POLLING_TASK + // Manual call tud_task since it isn't called by Core's background + TinyUSBDevice.task(); + #endif + + // not enumerated()/mounted() yet: nothing to do + if (!TinyUSBDevice.mounted()) { + return; + } + + // poll gpio once each 10 ms + static uint32_t ms = 0; + if (millis() - ms > 10) { + ms = millis(); + process_hid(); + } +} diff --git a/libraries/Adafruit_TinyUSB_Arduino/examples/HID/hid_composite_joy_featherwing/.skip.txt b/libraries/Adafruit_TinyUSB_Arduino/examples/HID/hid_composite_joy_featherwing/.skip.txt new file mode 100644 index 0000000..ae593a8 --- /dev/null +++ b/libraries/Adafruit_TinyUSB_Arduino/examples/HID/hid_composite_joy_featherwing/.skip.txt @@ -0,0 +1,3 @@ +feather_esp32_v2 +pico_rp2040_tinyusb_host +CH32V20x_EVT diff --git a/libraries/Adafruit_TinyUSB_Arduino/examples/HID/hid_composite_joy_featherwing/hid_composite_joy_featherwing.ino b/libraries/Adafruit_TinyUSB_Arduino/examples/HID/hid_composite_joy_featherwing/hid_composite_joy_featherwing.ino new file mode 100644 index 0000000..5792ad3 --- /dev/null +++ b/libraries/Adafruit_TinyUSB_Arduino/examples/HID/hid_composite_joy_featherwing/hid_composite_joy_featherwing.ino @@ -0,0 +1,169 @@ +/********************************************************************* + Adafruit invests time and resources providing this open source code, + please support Adafruit and open-source hardware by purchasing + products from Adafruit! + + MIT license, check LICENSE for more information + Copyright (c) 2019 Ha Thach for Adafruit Industries + All text above, and the splash screen below must be included in + any redistribution +*********************************************************************/ + +/* This sketch demonstrates USB HID Mouse and Keyboard with Joy Feather Wing. + * - The analog stick move mouse cursor + * - Button A, B, X, Y will send character a, b, x, y + * - Any actions will wake up PC host if it is in suspended (standby) mode. + * + * Joy Feather Wing: https://www.adafruit.com/product/3632 + * + * Following library is required + * - Adafruit_seesaw + */ + +#include "Adafruit_TinyUSB.h" +#include "Adafruit_seesaw.h" + +#define BUTTON_A 6 +#define BUTTON_B 7 +#define BUTTON_Y 9 +#define BUTTON_X 10 +uint32_t button_mask = (1 << BUTTON_A) | (1 << BUTTON_B) | + (1 << BUTTON_Y) | (1 << BUTTON_X); + +Adafruit_seesaw ss; + +// Report ID +enum { + RID_KEYBOARD = 1, + RID_MOUSE +}; + +// HID report descriptor using TinyUSB's template +uint8_t const desc_hid_report[] = { + TUD_HID_REPORT_DESC_KEYBOARD(HID_REPORT_ID(RID_KEYBOARD)), + TUD_HID_REPORT_DESC_MOUSE (HID_REPORT_ID(RID_MOUSE)) +}; + +// USB HID object. For ESP32 these values cannot be changed after this declaration +// desc report, desc len, protocol, interval, use out endpoint +Adafruit_USBD_HID usb_hid(desc_hid_report, sizeof(desc_hid_report), HID_ITF_PROTOCOL_NONE, 2, false); + +int last_x, last_y; + +// the setup function runs once when you press reset or power the board +void setup() { + // Manual begin() is required on core without built-in support e.g. mbed rp2040 + if (!TinyUSBDevice.isInitialized()) { + TinyUSBDevice.begin(0); + } + Serial.begin(115200); + + // Notes: following commented-out functions has no affect on ESP32 + // usb_hid.setPollInterval(2); + // usb_hid.setReportDescriptor(desc_hid_report, sizeof(desc_hid_report)); + usb_hid.begin(); + + // If already enumerated, additional class driverr begin() e.g msc, hid, midi won't take effect until re-enumeration + if (TinyUSBDevice.mounted()) { + TinyUSBDevice.detach(); + delay(10); + TinyUSBDevice.attach(); + } + + Serial.println("Adafruit TinyUSB HID Mouse with Joy FeatherWing example"); + + if (!ss.begin(0x49)) { + Serial.println("ERROR! seesaw not found"); + while (1) {} + } else { + Serial.println("seesaw started"); + Serial.print("version: "); + Serial.println(ss.getVersion(), HEX); + } + ss.pinModeBulk(button_mask, INPUT_PULLUP); + ss.setGPIOInterrupts(button_mask, 1); + + last_y = ss.analogRead(2); + last_x = ss.analogRead(3); +} + +void process_hid() { + // If either analog stick or any buttons is pressed + bool has_action = false; + + /*------------- Mouse -------------*/ + int y = ss.analogRead(2); + int x = ss.analogRead(3); + + // reduce the delta by half to slow down the cursor move + int dx = (x - last_x) / 2; + int dy = (y - last_y) / 2; + + if ((abs(dx) > 3) || (abs(dy) > 3)) { + has_action = true; + + if (usb_hid.ready()) { + usb_hid.mouseMove(RID_MOUSE, dx, dy); // no ID: right + down + + last_x = x; + last_y = y; + + // delay a bit before attempt to send keyboard report + delay(10); + } + } + + /*------------- Keyboard -------------*/ + // button is active low, invert read value for convenience + uint32_t buttons = ~ss.digitalReadBulk(button_mask); + + if (usb_hid.ready()) { + // use to prevent sending multiple consecutive zero report + static bool has_key = false; + + if (buttons & button_mask) { + has_action = true; + has_key = true; + + uint8_t keycode[6] = {0}; + + if (buttons & (1 << BUTTON_A)) keycode[0] = HID_KEY_A; + if (buttons & (1 << BUTTON_B)) keycode[0] = HID_KEY_B; + if (buttons & (1 << BUTTON_X)) keycode[0] = HID_KEY_X; + if (buttons & (1 << BUTTON_Y)) keycode[0] = HID_KEY_Y; + + usb_hid.keyboardReport(RID_KEYBOARD, 0, keycode); + } else { + // send empty key report if previously has key pressed + if (has_key) usb_hid.keyboardRelease(RID_KEYBOARD); + has_key = false; + } + } + + /*------------- Remote Wakeup -------------*/ + // Remote wakeup if PC is suspended and we has user interaction with joy feather wing + if (has_action && TinyUSBDevice.suspended()) { + // Wake up only works if REMOTE_WAKEUP feature is enable by host + // Usually this is the case with Mouse/Keyboard device + TinyUSBDevice.remoteWakeup(); + } +} + +void loop() { + #ifdef TINYUSB_NEED_POLLING_TASK + // Manual call tud_task since it isn't called by Core's background + TinyUSBDevice.task(); + #endif + + // not enumerated()/mounted() yet: nothing to do + if (!TinyUSBDevice.mounted()) { + return; + } + + // poll gpio once each 10 ms + static uint32_t ms = 0; + if (millis() - ms > 10) { + ms = millis(); + process_hid(); + } +} diff --git a/libraries/Adafruit_TinyUSB_Arduino/examples/HID/hid_dual_interfaces/.skip.txt b/libraries/Adafruit_TinyUSB_Arduino/examples/HID/hid_dual_interfaces/.skip.txt new file mode 100644 index 0000000..e4f9319 --- /dev/null +++ b/libraries/Adafruit_TinyUSB_Arduino/examples/HID/hid_dual_interfaces/.skip.txt @@ -0,0 +1,2 @@ +feather_esp32_v2 +pico_rp2040_tinyusb_host diff --git a/libraries/Adafruit_TinyUSB_Arduino/examples/HID/hid_dual_interfaces/hid_dual_interfaces.ino b/libraries/Adafruit_TinyUSB_Arduino/examples/HID/hid_dual_interfaces/hid_dual_interfaces.ino new file mode 100644 index 0000000..9c4b3e1 --- /dev/null +++ b/libraries/Adafruit_TinyUSB_Arduino/examples/HID/hid_dual_interfaces/hid_dual_interfaces.ino @@ -0,0 +1,153 @@ +/********************************************************************* + Adafruit invests time and resources providing this open source code, + please support Adafruit and open-source hardware by purchasing + products from Adafruit! + + MIT license, check LICENSE for more information + Copyright (c) 2019 Ha Thach for Adafruit Industries + All text above, and the splash screen below must be included in + any redistribution +*********************************************************************/ + +#include "Adafruit_TinyUSB.h" + +/* This sketch demonstrates multiple USB HID interfaces. Pressing the button will + * - mouse toward bottom right of monitor + * - send 'a' key + * + * Depending on the board, the button pin + * and its active state (when pressed) are different + */ +#if defined(ARDUINO_SAMD_CIRCUITPLAYGROUND_EXPRESS) || defined(ARDUINO_NRF52840_CIRCUITPLAY) +const int pin = 4; // Left Button +bool activeState = true; + +#elif defined(ARDUINO_FUNHOUSE_ESP32S2) +const int pin = BUTTON_DOWN; +bool activeState = true; + +#elif defined PIN_BUTTON1 +const int pin = PIN_BUTTON1; +bool activeState = false; + +#elif defined PIN_BUTTON +const int pin = PIN_BUTTON; +bool activeState = false; + +#elif defined(ARDUINO_ARCH_ESP32) +const int pin = 0; +bool activeState = false; + +#elif defined(ARDUINO_ARCH_RP2040) +const int pin = D0; +bool activeState = false; +#else + +const int pin = A0; +bool activeState = false; +#endif + +// HID report descriptor using TinyUSB's template +uint8_t const desc_keyboard_report[] = { + TUD_HID_REPORT_DESC_KEYBOARD() +}; + +uint8_t const desc_mouse_report[] = { + TUD_HID_REPORT_DESC_MOUSE() +}; + +// USB HID objects +Adafruit_USBD_HID usb_keyboard; +Adafruit_USBD_HID usb_mouse; + +// the setup function runs once when you press reset or power the board +void setup() { + // Manual begin() is required on core without built-in support e.g. mbed rp2040 + if (!TinyUSBDevice.isInitialized()) { + TinyUSBDevice.begin(0); + } + + Serial.begin(115200); + + // HID Keyboard + usb_keyboard.setPollInterval(2); + usb_keyboard.setBootProtocol(HID_ITF_PROTOCOL_KEYBOARD); + usb_keyboard.setReportDescriptor(desc_keyboard_report, sizeof(desc_keyboard_report)); + usb_keyboard.setStringDescriptor("TinyUSB HID Keyboard"); + usb_keyboard.begin(); + + // HID Mouse + usb_mouse.setPollInterval(2); + usb_mouse.setBootProtocol(HID_ITF_PROTOCOL_MOUSE); + usb_mouse.setReportDescriptor(desc_mouse_report, sizeof(desc_mouse_report)); + usb_mouse.setStringDescriptor("TinyUSB HID Keyboard"); + usb_mouse.begin(); + + // If already enumerated, additional class driverr begin() e.g msc, hid, midi won't take effect until re-enumeration + if (TinyUSBDevice.mounted()) { + TinyUSBDevice.detach(); + delay(10); + TinyUSBDevice.attach(); + } + + // Set up button, pullup opposite to active state + pinMode(pin, activeState ? INPUT_PULLDOWN : INPUT_PULLUP); + + Serial.println("Adafruit TinyUSB HID Composite example"); +} + +void process_hid() { + // Whether button is pressed + bool btn_pressed = (digitalRead(pin) == activeState); + + // Remote wakeup + if (TinyUSBDevice.suspended() && btn_pressed) { + // Wake up host if we are in suspend mode + // and REMOTE_WAKEUP feature is enabled by host + TinyUSBDevice.remoteWakeup(); + } + + /*------------- Mouse -------------*/ + if (usb_mouse.ready() && btn_pressed) { + int8_t const delta = 5; + usb_mouse.mouseMove(0, delta, delta); // right + down + } + + /*------------- Keyboard -------------*/ + if (usb_keyboard.ready()) { + // use to send key release report + static bool has_key = false; + + if (btn_pressed) { + uint8_t keycode[6] = {0}; + keycode[0] = HID_KEY_A; + + usb_keyboard.keyboardReport(0, 0, keycode); + + has_key = true; + } else { + // send empty key report if previously has key pressed + if (has_key) usb_keyboard.keyboardRelease(0); + has_key = false; + } + } +} + +void loop() { + #ifdef TINYUSB_NEED_POLLING_TASK + // Manual call tud_task since it isn't called by Core's background + TinyUSBDevice.task(); + #endif + + // not enumerated()/mounted() yet: nothing to do + if (!TinyUSBDevice.mounted()) { + return; + } + + // poll gpio once each 10 ms + static uint32_t ms = 0; + if (millis() - ms > 10) { + ms = millis(); + process_hid(); + } +} \ No newline at end of file diff --git a/libraries/Adafruit_TinyUSB_Arduino/examples/HID/hid_gamepad/.skip.txt b/libraries/Adafruit_TinyUSB_Arduino/examples/HID/hid_gamepad/.skip.txt new file mode 100644 index 0000000..e4f9319 --- /dev/null +++ b/libraries/Adafruit_TinyUSB_Arduino/examples/HID/hid_gamepad/.skip.txt @@ -0,0 +1,2 @@ +feather_esp32_v2 +pico_rp2040_tinyusb_host diff --git a/libraries/Adafruit_TinyUSB_Arduino/examples/HID/hid_gamepad/hid_gamepad.ino b/libraries/Adafruit_TinyUSB_Arduino/examples/HID/hid_gamepad/hid_gamepad.ino new file mode 100644 index 0000000..83acf6b --- /dev/null +++ b/libraries/Adafruit_TinyUSB_Arduino/examples/HID/hid_gamepad/hid_gamepad.ino @@ -0,0 +1,284 @@ +/********************************************************************* + Adafruit invests time and resources providing this open source code, + please support Adafruit and open-source hardware by purchasing + products from Adafruit! + + MIT license, check LICENSE for more information + Copyright (c) 2021 NeKuNeKo for Adafruit Industries + All text above, and the splash screen below must be included in + any redistribution +*********************************************************************/ + +#include "Adafruit_TinyUSB.h" + +/* This sketch demonstrates USB HID gamepad use. + * This sketch is only valid on boards which have native USB support + * and compatibility with Adafruit TinyUSB library. + * For example SAMD21, SAMD51, nRF52840. + * + * Make sure you select the TinyUSB USB stack if you have a SAMD board. + * You can test the gamepad on a Windows system by pressing WIN+R, writing Joy.cpl and pressing Enter. + */ + +// HID report descriptor using TinyUSB's template +// Single Report (no ID) descriptor +uint8_t const desc_hid_report[] = { + TUD_HID_REPORT_DESC_GAMEPAD() +}; + +// USB HID object +Adafruit_USBD_HID usb_hid; + +// Report payload defined in src/class/hid/hid.h +// - For Gamepad Button Bit Mask see hid_gamepad_button_bm_t +// - For Gamepad Hat Bit Mask see hid_gamepad_hat_t +hid_gamepad_report_t gp; + +void setup() { + // Manual begin() is required on core without built-in support e.g. mbed rp2040 + if (!TinyUSBDevice.isInitialized()) { + TinyUSBDevice.begin(0); + } + + Serial.begin(115200); + + // Setup HID + usb_hid.setPollInterval(2); + usb_hid.setReportDescriptor(desc_hid_report, sizeof(desc_hid_report)); + usb_hid.begin(); + + // If already enumerated, additional class driverr begin() e.g msc, hid, midi won't take effect until re-enumeration + if (TinyUSBDevice.mounted()) { + TinyUSBDevice.detach(); + delay(10); + TinyUSBDevice.attach(); + } + + Serial.println("Adafruit TinyUSB HID Gamepad example"); +} + +void loop() { + #ifdef TINYUSB_NEED_POLLING_TASK + // Manual call tud_task since it isn't called by Core's background + TinyUSBDevice.task(); + #endif + + // not enumerated()/mounted() yet: nothing to do + if (!TinyUSBDevice.mounted()) { + return; + } + +// // Remote wakeup +// if ( TinyUSBDevice.suspended() && btn ) +// { +// // Wake up host if we are in suspend mode +// // and REMOTE_WAKEUP feature is enabled by host +// TinyUSBDevice.remoteWakeup(); +// } + + if (!usb_hid.ready()) return; + + // Reset buttons + Serial.println("No pressing buttons"); + gp.x = 0; + gp.y = 0; + gp.z = 0; + gp.rz = 0; + gp.rx = 0; + gp.ry = 0; + gp.hat = 0; + gp.buttons = 0; + usb_hid.sendReport(0, &gp, sizeof(gp)); + delay(2000); + + + // Hat/DPAD UP + Serial.println("Hat/DPAD UP"); + gp.hat = 1; // GAMEPAD_HAT_UP; + usb_hid.sendReport(0, &gp, sizeof(gp)); + delay(2000); + + // Hat/DPAD UP RIGHT + Serial.println("Hat/DPAD UP RIGHT"); + gp.hat = 2; // GAMEPAD_HAT_UP_RIGHT; + usb_hid.sendReport(0, &gp, sizeof(gp)); + delay(2000); + + // Hat/DPAD RIGHT + Serial.println("Hat/DPAD RIGHT"); + gp.hat = 3; // GAMEPAD_HAT_RIGHT; + usb_hid.sendReport(0, &gp, sizeof(gp)); + delay(2000); + + // Hat/DPAD DOWN RIGHT + Serial.println("Hat/DPAD DOWN RIGHT"); + gp.hat = 4; // GAMEPAD_HAT_DOWN_RIGHT; + usb_hid.sendReport(0, &gp, sizeof(gp)); + delay(2000); + + // Hat/DPAD DOWN + Serial.println("Hat/DPAD DOWN"); + gp.hat = 5; // GAMEPAD_HAT_DOWN; + usb_hid.sendReport(0, &gp, sizeof(gp)); + delay(2000); + + // Hat/DPAD DOWN LEFT + Serial.println("Hat/DPAD DOWN LEFT"); + gp.hat = 6; // GAMEPAD_HAT_DOWN_LEFT; + usb_hid.sendReport(0, &gp, sizeof(gp)); + delay(2000); + + // Hat/DPAD LEFT + Serial.println("Hat/DPAD LEFT"); + gp.hat = 7; // GAMEPAD_HAT_LEFT; + usb_hid.sendReport(0, &gp, sizeof(gp)); + delay(2000); + + // Hat/DPAD UP LEFT + Serial.println("Hat/DPAD UP LEFT"); + gp.hat = 8; // GAMEPAD_HAT_UP_LEFT; + usb_hid.sendReport(0, &gp, sizeof(gp)); + delay(2000); + + // Hat/DPAD CENTER + Serial.println("Hat/DPAD CENTER"); + gp.hat = 0; // GAMEPAD_HAT_CENTERED; + usb_hid.sendReport(0, &gp, sizeof(gp)); + delay(2000); + + + // Joystick 1 UP + Serial.println("Joystick 1 UP"); + gp.x = 0; + gp.y = -127; + usb_hid.sendReport(0, &gp, sizeof(gp)); + delay(2000); + + // Joystick 1 DOWN + Serial.println("Joystick 1 DOWN"); + gp.x = 0; + gp.y = 127; + usb_hid.sendReport(0, &gp, sizeof(gp)); + delay(2000); + + // Joystick 1 RIGHT + Serial.println("Joystick 1 RIGHT"); + gp.x = 127; + gp.y = 0; + usb_hid.sendReport(0, &gp, sizeof(gp)); + delay(2000); + + // Joystick 1 LEFT + Serial.println("Joystick 1 LEFT"); + gp.x = -127; + gp.y = 0; + usb_hid.sendReport(0, &gp, sizeof(gp)); + delay(2000); + + // Joystick 1 CENTER + Serial.println("Joystick 1 CENTER"); + gp.x = 0; + gp.y = 0; + usb_hid.sendReport(0, &gp, sizeof(gp)); + delay(2000); + + + // Joystick 2 UP + Serial.println("Joystick 2 UP"); + gp.z = 0; + gp.rz = 127; + usb_hid.sendReport(0, &gp, sizeof(gp)); + delay(2000); + + // Joystick 2 DOWN + Serial.println("Joystick 2 DOWN"); + gp.z = 0; + gp.rz = -127; + usb_hid.sendReport(0, &gp, sizeof(gp)); + delay(2000); + + // Joystick 2 RIGHT + Serial.println("Joystick 2 RIGHT"); + gp.z = 127; + gp.rz = 0; + usb_hid.sendReport(0, &gp, sizeof(gp)); + delay(2000); + + // Joystick 2 LEFT + Serial.println("Joystick 2 LEFT"); + gp.z = -127; + gp.rz = 0; + usb_hid.sendReport(0, &gp, sizeof(gp)); + delay(2000); + + // Joystick 2 CENTER + Serial.println("Joystick 2 CENTER"); + gp.z = 0; + gp.rz = 0; + usb_hid.sendReport(0, &gp, sizeof(gp)); + delay(2000); + + + // Analog Trigger 1 UP + Serial.println("Analog Trigger 1 UP"); + gp.rx = 127; + usb_hid.sendReport(0, &gp, sizeof(gp)); + delay(2000); + + // Analog Trigger 1 DOWN + Serial.println("Analog Trigger 1 DOWN"); + gp.rx = -127; + usb_hid.sendReport(0, &gp, sizeof(gp)); + delay(2000); + + // Analog Trigger 1 CENTER + Serial.println("Analog Trigger 1 CENTER"); + gp.rx = 0; + usb_hid.sendReport(0, &gp, sizeof(gp)); + delay(2000); + + + // Analog Trigger 2 UP + Serial.println("Analog Trigger 2 UP"); + gp.ry = 127; + usb_hid.sendReport(0, &gp, sizeof(gp)); + delay(2000); + + // Analog Trigger 2 DOWN + Serial.println("Analog Trigger 2 DOWN"); + gp.ry = -127; + usb_hid.sendReport(0, &gp, sizeof(gp)); + delay(2000); + + // Analog Trigger 2 CENTER + Serial.println("Analog Trigger 2 CENTER"); + gp.ry = 0; + usb_hid.sendReport(0, &gp, sizeof(gp)); + delay(2000); + + + // Test buttons (up to 32 buttons) + for (int i = 0; i < 32; ++i) { + Serial.print("Pressing button "); + Serial.println(i); + gp.buttons = (1U << i); + usb_hid.sendReport(0, &gp, sizeof(gp)); + delay(1000); + } + + + // Random touch + Serial.println("Random touch"); + gp.x = random(-127, 128); + gp.y = random(-127, 128); + gp.z = random(-127, 128); + gp.rz = random(-127, 128); + gp.rx = random(-127, 128); + gp.ry = random(-127, 128); + gp.hat = random(0, 9); + gp.buttons = random(0, 0xffff); + usb_hid.sendReport(0, &gp, sizeof(gp)); + delay(2000); + + // */ +} diff --git a/libraries/Adafruit_TinyUSB_Arduino/examples/HID/hid_generic_inout/.skip.txt b/libraries/Adafruit_TinyUSB_Arduino/examples/HID/hid_generic_inout/.skip.txt new file mode 100644 index 0000000..e4f9319 --- /dev/null +++ b/libraries/Adafruit_TinyUSB_Arduino/examples/HID/hid_generic_inout/.skip.txt @@ -0,0 +1,2 @@ +feather_esp32_v2 +pico_rp2040_tinyusb_host diff --git a/libraries/Adafruit_TinyUSB_Arduino/examples/HID/hid_generic_inout/README.md b/libraries/Adafruit_TinyUSB_Arduino/examples/HID/hid_generic_inout/README.md new file mode 100644 index 0000000..3749e6b --- /dev/null +++ b/libraries/Adafruit_TinyUSB_Arduino/examples/HID/hid_generic_inout/README.md @@ -0,0 +1,10 @@ +Instructions for node.js based example + +===Setup=== + +1. Upload example code to your board +2. Install node.js if you haven't already +3. Run `npm install` to install the dependencies +4. If this should fail on windows try installing the build tools via `npm i -g windows-build-tools` +5. While the board is connected run `node hid_test.js` +6. If this should fail make sure the VID and PID of your board is listed in boards.js \ No newline at end of file diff --git a/libraries/Adafruit_TinyUSB_Arduino/examples/HID/hid_generic_inout/boards.js b/libraries/Adafruit_TinyUSB_Arduino/examples/HID/hid_generic_inout/boards.js new file mode 100644 index 0000000..6b78231 --- /dev/null +++ b/libraries/Adafruit_TinyUSB_Arduino/examples/HID/hid_generic_inout/boards.js @@ -0,0 +1,4 @@ +module.exports = { + "Adafruit Boards":[0x239A,0xFFFF], + "TinyUSB example":[0xCAFE,0xFFFF] +} diff --git a/libraries/Adafruit_TinyUSB_Arduino/examples/HID/hid_generic_inout/hid_generic_inout.ino b/libraries/Adafruit_TinyUSB_Arduino/examples/HID/hid_generic_inout/hid_generic_inout.ino new file mode 100644 index 0000000..335fdc7 --- /dev/null +++ b/libraries/Adafruit_TinyUSB_Arduino/examples/HID/hid_generic_inout/hid_generic_inout.ino @@ -0,0 +1,104 @@ +/********************************************************************* + Adafruit invests time and resources providing this open source code, + please support Adafruit and open-source hardware by purchasing + products from Adafruit! + + MIT license, check LICENSE for more information + Copyright (c) 2019 Ha Thach for Adafruit Industries + All text above, and the splash screen below must be included in + any redistribution +*********************************************************************/ + +/* This example demonstrate HID Generic raw Input & Output. + * It will receive data from Host (In endpoint) and echo back (Out endpoint). + * HID Report descriptor use vendor for usage page (using template TUD_HID_REPORT_DESC_GENERIC_INOUT) + * + * There are 2 ways to test the sketch + * 1. Using nodejs + * - Install nodejs and npm to your PC + * + * - Install excellent node-hid (https://github.com/node-hid/node-hid) by + * $ npm install node-hid + * + * - Run provided hid test script + * $ node hid_test.js + * + * 2. Using python + * - Install `hid` package (https://pypi.org/project/hid/) by + * $ pip install hid + * + * - hid package replies on hidapi (https://github.com/libusb/hidapi) for backend, + * which already available in Linux. However on windows, you may need to download its dlls from their release page and + * copy it over to folder where python is installed. + * + * - Run provided hid test script to send and receive data to this device. + * $ python3 hid_test.py + */ + +#include "Adafruit_TinyUSB.h" + +// HID report descriptor using TinyUSB's template +// Generic In Out with 64 bytes report (max) +uint8_t const desc_hid_report[] = { + TUD_HID_REPORT_DESC_GENERIC_INOUT(64) +}; + +// USB HID object +Adafruit_USBD_HID usb_hid; + +// the setup function runs once when you press reset or power the board +void setup() { + // Manual begin() is required on core without built-in support e.g. mbed rp2040 + if (!TinyUSBDevice.isInitialized()) { + TinyUSBDevice.begin(0); + } + + Serial.begin(115200); + + // Notes: following commented-out functions has no affect on ESP32 + usb_hid.enableOutEndpoint(true); + usb_hid.setPollInterval(2); + usb_hid.setReportDescriptor(desc_hid_report, sizeof(desc_hid_report)); + usb_hid.setStringDescriptor("TinyUSB HID Generic"); + usb_hid.setReportCallback(get_report_callback, set_report_callback); + usb_hid.begin(); + + // If already enumerated, additional class driverr begin() e.g msc, hid, midi won't take effect until re-enumeration + if (TinyUSBDevice.mounted()) { + TinyUSBDevice.detach(); + delay(10); + TinyUSBDevice.attach(); + } + + Serial.println("Adafruit TinyUSB HID Generic In Out example"); +} + +void loop() { + #ifdef TINYUSB_NEED_POLLING_TASK + // Manual call tud_task since it isn't called by Core's background + TinyUSBDevice.task(); + #endif +} + +// Invoked when received GET_REPORT control request +// Application must fill buffer report's content and return its length. +// Return zero will cause the stack to STALL request +uint16_t get_report_callback(uint8_t report_id, hid_report_type_t report_type, uint8_t* buffer, uint16_t reqlen) { + // not used in this example + (void) report_id; + (void) report_type; + (void) buffer; + (void) reqlen; + return 0; +} + +// Invoked when received SET_REPORT control request or +// received data on OUT endpoint ( Report ID = 0, Type = 0 ) +void set_report_callback(uint8_t report_id, hid_report_type_t report_type, uint8_t const* buffer, uint16_t bufsize) { + // This example doesn't use multiple report and report ID + (void) report_id; + (void) report_type; + + // echo back anything we received from host + usb_hid.sendReport(0, buffer, bufsize); +} diff --git a/libraries/Adafruit_TinyUSB_Arduino/examples/HID/hid_generic_inout/hid_test.js b/libraries/Adafruit_TinyUSB_Arduino/examples/HID/hid_generic_inout/hid_test.js new file mode 100644 index 0000000..daa958f --- /dev/null +++ b/libraries/Adafruit_TinyUSB_Arduino/examples/HID/hid_generic_inout/hid_test.js @@ -0,0 +1,68 @@ +// IMPORTANT: install the dependency via 'npm i node-hid' in the same location as the script +// If the install fails on windows you may need to run 'npm i -g windows-build-tools' first to be able to compile native code needed for this library + +var HID = require('node-hid'); +var os = require('os') +// list of supported devices +var boards = require('./boards.js') +var devices = HID.devices(); + +// this will choose any device found in the boards.js file +var deviceInfo = devices.find(anySupportedBoard); +var reportLen = 64; + +var message = "Hello World!" + +// Turn our string into an array of integers e.g. 'ascii codes', though charCodeAt spits out UTF-16 +// This means if you have characters in your string that are not Latin-1 you will have to add additional logic for character codes above 255 +var messageBuffer = Array.from(message, function(c){return c.charCodeAt(0)}); + +// HIDAPI requires us to prepend a 0 for single hid report as dummy reportID +messageBuffer.unshift(0) + +// Some OSes expect that you always send a buffer that equals your report length +// So lets fill up the rest of the buffer with zeros +var paddingBuf = Array(reportLen-messageBuffer.length); +paddingBuf.fill(0) +messageBuffer = messageBuffer.concat(paddingBuf) + +// check if we actually found a device and if so send our messageBuffer to it +if( deviceInfo ) { + console.log(deviceInfo) + var device = new HID.HID( deviceInfo.path ); + + // register an event listener for data coming from the device + device.on("data", function(data) { + // Print what we get from the device + console.log(data.toString('ascii')); + }); + + // the same for any error that occur + device.on("error", function(err) {console.log(err)}); + + // send our message to the device every 500ms + setInterval(function () { + device.write(messageBuffer); + },500) +} + + +function anySupportedBoard(d) { + + for (var key in boards) { + if (boards.hasOwnProperty(key)) { + if (isDevice(boards[key],d)) { + console.log("Found " + d.product); + return true; + } + } + } + return false; +} + + +function isDevice(board,d){ + // product id 0xff is matches all + return d.vendorId==board[0] && (d.productId==board[1] || board[1] == 0xFFFF); +} + diff --git a/libraries/Adafruit_TinyUSB_Arduino/examples/HID/hid_generic_inout/hid_test.py b/libraries/Adafruit_TinyUSB_Arduino/examples/HID/hid_generic_inout/hid_test.py new file mode 100644 index 0000000..e90b87a --- /dev/null +++ b/libraries/Adafruit_TinyUSB_Arduino/examples/HID/hid_generic_inout/hid_test.py @@ -0,0 +1,22 @@ +# Install python3 HID package https://pypi.org/project/hid/ +import hid + +# default is TinyUSB (0xcafe), Adafruit (0x239a), RaspberryPi (0x2e8a), Espressif (0x303a) VID +USB_VID = (0xcafe, 0x239a, 0x2e8a, 0x303a) + +print("VID list: " + ", ".join('%02x' % v for v in USB_VID)) + +for vid in USB_VID: + for dict in hid.enumerate(vid): + print(dict) + dev = hid.Device(dict['vendor_id'], dict['product_id']) + if dev: + while True: + # Get input from console and encode to UTF8 for array of chars. + # hid generic inout is single report therefore by HIDAPI requirement + # it must be preceded with 0x00 as dummy reportID + str_out = b'\x00' + str_out += input("Send text to HID Device : ").encode('utf-8') + dev.write(str_out) + str_in = dev.read(64) + print("Received from HID Device:", str_in, '\n') diff --git a/libraries/Adafruit_TinyUSB_Arduino/examples/HID/hid_generic_inout/package.json b/libraries/Adafruit_TinyUSB_Arduino/examples/HID/hid_generic_inout/package.json new file mode 100644 index 0000000..9047c32 --- /dev/null +++ b/libraries/Adafruit_TinyUSB_Arduino/examples/HID/hid_generic_inout/package.json @@ -0,0 +1,14 @@ +{ + "name": "hid_example", + "version": "1.0.0", + "description": "Test application for hid_generic_inout example sketch", + "main": "hid_test.js", + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1" + }, + "author": "Timon, Tod E. Kurt", + "license": "MIT", + "dependencies": { + "node-hid": "^0.7.9" + } +} diff --git a/libraries/Adafruit_TinyUSB_Arduino/examples/Host/Simple/host_device_info/.pico_rp2040_tinyusb_host.test.only b/libraries/Adafruit_TinyUSB_Arduino/examples/Host/Simple/host_device_info/.pico_rp2040_tinyusb_host.test.only new file mode 100644 index 0000000..e69de29 diff --git a/libraries/Adafruit_TinyUSB_Arduino/examples/Host/Simple/host_device_info/host_device_info.ino b/libraries/Adafruit_TinyUSB_Arduino/examples/Host/Simple/host_device_info/host_device_info.ino new file mode 100644 index 0000000..12b1abd --- /dev/null +++ b/libraries/Adafruit_TinyUSB_Arduino/examples/Host/Simple/host_device_info/host_device_info.ino @@ -0,0 +1,225 @@ +/********************************************************************* + Adafruit invests time and resources providing this open source code, + please support Adafruit and open-source hardware by purchasing + products from Adafruit! + + MIT license, check LICENSE for more information + Copyright (c) 2019 Ha Thach for Adafruit Industries + All text above, and the splash screen below must be included in + any redistribution +*********************************************************************/ + +/* This example demonstrates use of native controller as host (TinyUSB Host) + * Note: + * - For most mcu with only 1 native usb, Serial is not available. We will use Serial1 instead + * + * Host example will get device descriptors of attached devices and print it out as follows: + Device 1: ID 046d:c52f + Device Descriptor: + bLength 18 + bDescriptorType 1 + bcdUSB 0200 + bDeviceClass 0 + bDeviceSubClass 0 + bDeviceProtocol 0 + bMaxPacketSize0 8 + idVendor 0x046d + idProduct 0xc52f + bcdDevice 2200 + iManufacturer 1 Logitech + iProduct 2 USB Receiver + iSerialNumber 0 + bNumConfigurations 1 + * + */ + +#include "Adafruit_TinyUSB.h" + +#ifndef USE_TINYUSB_HOST + #error This example requires usb stack configured as host in "Tools -> USB Stack -> Adafruit TinyUSB Host" +#endif + +// Language ID: English +#define LANGUAGE_ID 0x0409 + +typedef struct { + tusb_desc_device_t desc_device; + uint16_t manufacturer[32]; + uint16_t product[48]; + uint16_t serial[16]; + bool mounted; +} dev_info_t; + +// CFG_TUH_DEVICE_MAX is defined by tusb_config header +dev_info_t dev_info[CFG_TUH_DEVICE_MAX] = { 0 }; + +Adafruit_USBH_Host USBHost; + +//--------------------------------------------------------------------+ +// setup() & loop() +//--------------------------------------------------------------------+ +void setup() { + Serial1.begin(115200); + Serial1.println("TinyUSB Host: Device Info Example"); + + // Init USB Host on native controller roothub port0 + USBHost.begin(0); +} + +void loop() { + USBHost.task(); + Serial1.flush(); +} + +//--------------------------------------------------------------------+ +// TinyUSB Host callbacks +//--------------------------------------------------------------------+ +void print_device_descriptor(tuh_xfer_t *xfer); + +void utf16_to_utf8(uint16_t *temp_buf, size_t buf_len); + +void print_lsusb(void) { + bool no_device = true; + for (uint8_t daddr = 1; daddr < CFG_TUH_DEVICE_MAX + 1; daddr++) { + // TODO can use tuh_mounted(daddr), but tinyusb has an bug + // use local connected flag instead + dev_info_t *dev = &dev_info[daddr - 1]; + if (dev->mounted) { + Serial1.printf("Device %u: ID %04x:%04x %s %s\r\n", daddr, + dev->desc_device.idVendor, dev->desc_device.idProduct, + (char *) dev->manufacturer, (char *) dev->product); + + no_device = false; + } + } + + if (no_device) { + Serial1.println("No device connected (except hub)"); + } +} + +// Invoked when device is mounted (configured) +void tuh_mount_cb(uint8_t daddr) { + Serial1.printf("Device attached, address = %d\r\n", daddr); + + dev_info_t *dev = &dev_info[daddr - 1]; + dev->mounted = true; + + // Get Device Descriptor + tuh_descriptor_get_device(daddr, &dev->desc_device, 18, print_device_descriptor, 0); +} + +/// Invoked when device is unmounted (bus reset/unplugged) +void tuh_umount_cb(uint8_t daddr) { + Serial1.printf("Device removed, address = %d\r\n", daddr); + dev_info_t *dev = &dev_info[daddr - 1]; + dev->mounted = false; + + // print device summary + print_lsusb(); +} + +void print_device_descriptor(tuh_xfer_t *xfer) { + if (XFER_RESULT_SUCCESS != xfer->result) { + Serial1.printf("Failed to get device descriptor\r\n"); + return; + } + + uint8_t const daddr = xfer->daddr; + dev_info_t *dev = &dev_info[daddr - 1]; + tusb_desc_device_t *desc = &dev->desc_device; + + Serial1.printf("Device %u: ID %04x:%04x\r\n", daddr, desc->idVendor, desc->idProduct); + Serial1.printf("Device Descriptor:\r\n"); + Serial1.printf(" bLength %u\r\n" , desc->bLength); + Serial1.printf(" bDescriptorType %u\r\n" , desc->bDescriptorType); + Serial1.printf(" bcdUSB %04x\r\n" , desc->bcdUSB); + Serial1.printf(" bDeviceClass %u\r\n" , desc->bDeviceClass); + Serial1.printf(" bDeviceSubClass %u\r\n" , desc->bDeviceSubClass); + Serial1.printf(" bDeviceProtocol %u\r\n" , desc->bDeviceProtocol); + Serial1.printf(" bMaxPacketSize0 %u\r\n" , desc->bMaxPacketSize0); + Serial1.printf(" idVendor 0x%04x\r\n" , desc->idVendor); + Serial1.printf(" idProduct 0x%04x\r\n" , desc->idProduct); + Serial1.printf(" bcdDevice %04x\r\n" , desc->bcdDevice); + + // Get String descriptor using Sync API + Serial1.printf(" iManufacturer %u ", desc->iManufacturer); + if (XFER_RESULT_SUCCESS == + tuh_descriptor_get_manufacturer_string_sync(daddr, LANGUAGE_ID, dev->manufacturer, sizeof(dev->manufacturer))) { + utf16_to_utf8(dev->manufacturer, sizeof(dev->manufacturer)); + Serial1.printf((char *) dev->manufacturer); + } + Serial1.printf("\r\n"); + + Serial1.printf(" iProduct %u ", desc->iProduct); + if (XFER_RESULT_SUCCESS == + tuh_descriptor_get_product_string_sync(daddr, LANGUAGE_ID, dev->product, sizeof(dev->product))) { + utf16_to_utf8(dev->product, sizeof(dev->product)); + Serial1.printf((char *) dev->product); + } + Serial1.printf("\r\n"); + + Serial1.printf(" iSerialNumber %u ", desc->iSerialNumber); + if (XFER_RESULT_SUCCESS == + tuh_descriptor_get_serial_string_sync(daddr, LANGUAGE_ID, dev->serial, sizeof(dev->serial))) { + utf16_to_utf8(dev->serial, sizeof(dev->serial)); + Serial1.printf((char *) dev->serial); + } + Serial1.printf("\r\n"); + + Serial1.printf(" bNumConfigurations %u\r\n", desc->bNumConfigurations); + + // print device summary + print_lsusb(); +} + +//--------------------------------------------------------------------+ +// String Descriptor Helper +//--------------------------------------------------------------------+ + +static void _convert_utf16le_to_utf8(const uint16_t *utf16, size_t utf16_len, uint8_t *utf8, size_t utf8_len) { + // TODO: Check for runover. + (void) utf8_len; + // Get the UTF-16 length out of the data itself. + + for (size_t i = 0; i < utf16_len; i++) { + uint16_t chr = utf16[i]; + if (chr < 0x80) { + *utf8++ = chr & 0xff; + } else if (chr < 0x800) { + *utf8++ = (uint8_t) (0xC0 | (chr >> 6 & 0x1F)); + *utf8++ = (uint8_t) (0x80 | (chr >> 0 & 0x3F)); + } else { + // TODO: Verify surrogate. + *utf8++ = (uint8_t) (0xE0 | (chr >> 12 & 0x0F)); + *utf8++ = (uint8_t) (0x80 | (chr >> 6 & 0x3F)); + *utf8++ = (uint8_t) (0x80 | (chr >> 0 & 0x3F)); + } + // TODO: Handle UTF-16 code points that take two entries. + } +} + +// Count how many bytes a utf-16-le encoded string will take in utf-8. +static int _count_utf8_bytes(const uint16_t *buf, size_t len) { + size_t total_bytes = 0; + for (size_t i = 0; i < len; i++) { + uint16_t chr = buf[i]; + if (chr < 0x80) { + total_bytes += 1; + } else if (chr < 0x800) { + total_bytes += 2; + } else { + total_bytes += 3; + } + // TODO: Handle UTF-16 code points that take two entries. + } + return total_bytes; +} + +void utf16_to_utf8(uint16_t *temp_buf, size_t buf_len) { + size_t utf16_len = ((temp_buf[0] & 0xff) - 2) / sizeof(uint16_t); + size_t utf8_len = _count_utf8_bytes(temp_buf + 1, utf16_len); + + _convert_utf16le_to_utf8(temp_buf + 1, utf16_len, (uint8_t *) temp_buf, buf_len); + ((uint8_t *) temp_buf)[utf8_len] = '\0'; +} diff --git a/libraries/Adafruit_TinyUSB_Arduino/examples/MIDI/midi_multi_ports/.skip.txt b/libraries/Adafruit_TinyUSB_Arduino/examples/MIDI/midi_multi_ports/.skip.txt new file mode 100644 index 0000000..e4f9319 --- /dev/null +++ b/libraries/Adafruit_TinyUSB_Arduino/examples/MIDI/midi_multi_ports/.skip.txt @@ -0,0 +1,2 @@ +feather_esp32_v2 +pico_rp2040_tinyusb_host diff --git a/libraries/Adafruit_TinyUSB_Arduino/examples/MIDI/midi_multi_ports/midi_multi_ports.ino b/libraries/Adafruit_TinyUSB_Arduino/examples/MIDI/midi_multi_ports/midi_multi_ports.ino new file mode 100644 index 0000000..6271ae9 --- /dev/null +++ b/libraries/Adafruit_TinyUSB_Arduino/examples/MIDI/midi_multi_ports/midi_multi_ports.ino @@ -0,0 +1,61 @@ +/********************************************************************* + Adafruit invests time and resources providing this open source code, + please support Adafruit and open-source hardware by purchasing + products from Adafruit! + + MIT license, check LICENSE for more information + Copyright (c) 2019 Ha Thach for Adafruit Industries + All text above, and the splash screen below must be included in + any redistribution +*********************************************************************/ + +// This sketch is enumerated as USB MIDI device with multiple ports +// and how to set their name + +#include +#include +#include + +// USB MIDI object with 3 ports +Adafruit_USBD_MIDI usb_midi(3); + +void setup() { +#ifdef LED_BUILTIN + pinMode(LED_BUILTIN, OUTPUT); +#endif + + // Manual begin() is required on core without built-in support e.g. mbed rp2040 + if (!TinyUSBDevice.isInitialized()) { + TinyUSBDevice.begin(0); + } + + // Set name for each cable, must be done before usb_midi.begin() + usb_midi.setCableName(1, "Keyboard"); + usb_midi.setCableName(2, "Drum Pads"); + usb_midi.setCableName(3, "Lights"); + usb_midi.begin(); + + // If already enumerated, additional class driverr begin() e.g msc, hid, midi won't take effect until re-enumeration + if (TinyUSBDevice.mounted()) { + TinyUSBDevice.detach(); + delay(10); + TinyUSBDevice.attach(); + } +} + +void loop() { + #ifdef TINYUSB_NEED_POLLING_TASK + // Manual call tud_task since it isn't called by Core's background + TinyUSBDevice.task(); + #endif + + // toggle LED + static uint32_t ms = 0; + static uint8_t led_state = 0; + if (millis() - ms > 1000) { + ms = millis(); +#ifdef LED_BUILTIN + digitalWrite(LED_BUILTIN, 1-led_state); +#endif + } +} diff --git a/libraries/Adafruit_TinyUSB_Arduino/examples/MIDI/midi_pizza_box_dj/.cpb.test.only b/libraries/Adafruit_TinyUSB_Arduino/examples/MIDI/midi_pizza_box_dj/.cpb.test.only new file mode 100644 index 0000000..e69de29 diff --git a/libraries/Adafruit_TinyUSB_Arduino/examples/MIDI/midi_pizza_box_dj/.cpx_ada.test.only b/libraries/Adafruit_TinyUSB_Arduino/examples/MIDI/midi_pizza_box_dj/.cpx_ada.test.only new file mode 100644 index 0000000..e69de29 diff --git a/libraries/Adafruit_TinyUSB_Arduino/examples/MIDI/midi_pizza_box_dj/midi_pizza_box_dj.ino b/libraries/Adafruit_TinyUSB_Arduino/examples/MIDI/midi_pizza_box_dj/midi_pizza_box_dj.ino new file mode 100644 index 0000000..0d62bb3 --- /dev/null +++ b/libraries/Adafruit_TinyUSB_Arduino/examples/MIDI/midi_pizza_box_dj/midi_pizza_box_dj.ino @@ -0,0 +1,427 @@ +//Circuit Playground PZ-1 Pizza Box DJ +// by John Park +// a.k.a. DJ Sternocleidomastoid +// for Adafruit Industries +// MIT License +/////////////////////////////////////////////////////////////////////////////// +// MIDI controller for Traktor, Mixxx and other DJ software +// Uses TinyUSB to send MIDI over USB +// +// MIDI controller command mapping must be set up in your DJ software, check +// below for MIDI signal outputs +// +// For detail tutorial https://learn.adafruit.com/circuit-playground-pizza-box-dj-controller +/////////////////////////////////////////////////////////////////////////////// + +#include +#include +#include +#include +#include + +// USB MIDI object +Adafruit_USBD_MIDI usb_midi; + +// Create a new instance of the Arduino MIDI Library, +// and attach usb_midi as the transport. +MIDI_CREATE_INSTANCE(Adafruit_USBD_MIDI, usb_midi, usbMIDI); + +/////////////////////////////////////////////////////////////////////////////// +//MIDI CC (change control) assignments +const int CHANNEL = 14; //MIDI channel +const int LEFT_BUTTON_CC = 4; //Play/Pause Deck A +const int RIGHT_BUTTON_CC = 19; //MIDI CC for left button: FX 1 On +const int CAP1_CONTROL = 1; //Select/Set+Store Hotcue 2 (1 is load point) +const int CAP0_CONTROL = 0; //Left fader mixes Deck B volume down +const int CAP2_CONTROL = 2; //Delete hotcue 2 point +const int CAP3_CONTROL = 3; //Remix Trigger 6-1 or Loop Size /2 +const int CAP12_CONTROL = 12; //Right fader mixes Deck A volume down +const int CAP6_CONTROL = 6; //Scratch (jog turn) Deck B +const int CAP9_CONTROL = 9; //Play/Pause Deck B +const int CAP10_CONTROL = 10; //Remix Trigger 6-4 or Loop Set 4 bar + +int leftButtonState = 0; +int rightButtonState = 0; +int leftButtonLastState = 0; +int rightButtonLastState = 0; +int cap0State = 0; +int cap0LastState = 0; +int cap1State = 0; +int cap1LastState = 0; +int cap2State = 0; +int cap2LastState = 0; +int cap3State = 0; +int cap3LastState = 0; +int cap12State = 0; +int cap12LastState = 0; +int cap6State = 0; +int cap6LastState = 0; +int cap9State = 0; +int cap9LastState = 0; +int cap10State = 0; +int cap10LastState = 0; +bool cap1ON = false; //prevents off command from being spewed +bool cap12ON = false; //prevents off command from being spewed +bool cap6ON = false; //prevents off command from being spewed + +const int CAPMIN = 25; //lowest value that's considered an intentional touch to minimize crosstalk +const int CAPSLOP = 0; //value of capacitive pad variance that's considered noise + + +/////////////////////////////////////////////////////////////////////////////// +//mic_meter code +// To keep the display 'lively,' an upper and lower range of volume +// levels are dynamically adjusted based on recent audio history, and +// the graph is fit into this range. +#define FRAMES 8 +uint16_t lvl[FRAMES], // Audio level for the prior #FRAMES frames + avgLo = 6, // Audio volume lower end of range + avgHi = 512, // Audio volume upper end of range + sum = 256 * FRAMES; // Sum of lvl[] array +uint8_t lvlIdx = 0; // Counter into lvl[] array +int16_t peak = 0; // Falling dot shows recent max +int8_t peakV = 0; // Velocity of peak dot + +/////////////////////////////////////////////////////////////////////////////// +void setup() { + + usbMIDI.begin(); + Serial.begin(115200); + + CircuitPlayground.begin(); + CircuitPlayground.setBrightness(30); //make brighter for performance, up to 255 + CircuitPlayground.clearPixels(); + for(uint8_t i=0; i maxLvl) maxLvl = lvl[i]; + } + + // Keep some minimum distance between min & max levels, + // else the display gets "jumpy." + if((maxLvl - minLvl) < 40) { + maxLvl = (minLvl < (512-40)) ? minLvl + 40 : 512; + } + avgLo = (avgLo * 7 + minLvl + 2) / 8; // Dampen min/max levels + avgHi = (maxLvl >= avgHi) ? // (fake rolling averages) + (avgHi * 3 + maxLvl + 1) / 4 : // Fast rise + (avgHi * 31 + maxLvl + 8) / 32; // Slow decay + + a = sum / FRAMES; // Average of lvl[] array + if(a <= avgLo) { // Below min? + scaled = 0; // Bargraph = zero + } else { // Else scale to fixed-point coordspace 0-2560 + scaled = 2560L * (a - avgLo) / (avgHi - avgLo); + if(scaled > 2560) scaled = 2560; + } + if(scaled >= peak) { // New peak + peakV = (scaled - peak) / 4; // Give it an upward nudge + peak = scaled; + } + + uint8_t whole = scaled / 256, // Full-brightness pixels (0-10) + frac = scaled & 255; // Brightness of fractional pixel + int whole2 = peak / 256, // Index of peak pixel + frac2 = peak & 255; // Between-pixels position of peak + uint16_t a1, a2; // Scaling factors for color blending + + for(i=0; i<10; i++) { // For each NeoPixel... + if(i <= whole) { // In currently-lit area? + r = pgm_read_byte(&reds[i]), // Look up pixel color + g = pgm_read_byte(&greens[i]), + b = pgm_read_byte(&blues[i]); + if(i == whole) { // Fraction pixel at top of range? + a1 = (uint16_t)frac + 1; // Fade toward black + r = (r * a1) >> 8; + g = (g * a1) >> 8; + b = (b * a1) >> 8; + } + } else { + r = g = b = 0; // In unlit area + } + // Composite the peak pixel atop whatever else is happening... + if(i == whole2) { // Peak pixel? + a1 = 256 - frac2; // Existing pixel blend factor 1-256 + a2 = frac2 + 1; // Peak pixel blend factor 1-256 + r = ((r * a1) + (0x84 * a2)) >> 8; // Will + g = ((g * a1) + (0x87 * a2)) >> 8; // it + b = ((b * a1) + (0xC3 * a2)) >> 8; // blend? + } else if(i == (whole2-1)) { // Just below peak pixel + a1 = frac2 + 1; // Opposite blend ratios to above, + a2 = 256 - frac2; // but same idea + r = ((r * a1) + (0x84 * a2)) >> 8; + g = ((g * a1) + (0x87 * a2)) >> 8; + b = ((b * a1) + (0xC3 * a2)) >> 8; + } + CircuitPlayground.strip.setPixelColor(i, + pgm_read_byte(&gamma8[r]), + pgm_read_byte(&gamma8[g]), + pgm_read_byte(&gamma8[b])); + } + CircuitPlayground.strip.show(); + + peak += peakV; + if(peak <= 0) { + peak = 0; + peakV = 0; + } else if(peakV >= -126) { + peakV -= 2; + } + + if(++lvlIdx >= FRAMES) lvlIdx = 0; + +/////////////////////////////////////////////////////////////////////////////// +//BUTTONS +// +//read the buttons + leftButtonState = (CircuitPlayground.leftButton()); + //not pressed = 0, pressed = 1 + rightButtonState = (CircuitPlayground.rightButton()); + +//Left Button +//compare current button states to previous button states + if(leftButtonState != leftButtonLastState){ //the state has changed + if(leftButtonState == 1){ //went from off to on, it's pressed + usbMIDI.sendControlChange(LEFT_BUTTON_CC, 1, CHANNEL); //send MIDI note ON command + CircuitPlayground.redLED(HIGH); //turn on the red LED + } + else{ //the button went from on to off, it isn't pressed + usbMIDI.sendControlChange(LEFT_BUTTON_CC, 0, CHANNEL); //send MIDI note OFF + CircuitPlayground.redLED(LOW); //turn off the red LED + } + leftButtonLastState = leftButtonState; //toggle + } + + +//Right Button + if(rightButtonState != rightButtonLastState){ + if(rightButtonState == 1){ + usbMIDI.sendControlChange(RIGHT_BUTTON_CC, 1, CHANNEL); + CircuitPlayground.redLED(HIGH); + } + else{ + usbMIDI.sendControlChange(RIGHT_BUTTON_CC, 0, CHANNEL); + CircuitPlayground.redLED(LOW); + } + rightButtonLastState = rightButtonState; + } +/////////////////////////////////////////////////////////////////////////////// +//Cap sense pads +// +//read the capacative pads + int cap1 = CircuitPlayground.readCap(1); + int cap0 = CircuitPlayground.readCap(0); + int cap2 = CircuitPlayground.readCap(2); + int cap3 = CircuitPlayground.readCap(3); + int cap12 = CircuitPlayground.readCap(12); + int cap6 = CircuitPlayground.readCap(6); + int cap9 = CircuitPlayground.readCap(9); + int cap10 = CircuitPlayground.readCap(10); + + //cap1 Crossfader Deck B + + if(cap1 > CAPMIN){ + cap1State=1; + } + else{ + cap1State=0; + } + if(cap1State != cap1LastState){ + if(cap1State==1){ + usbMIDI.sendControlChange(CAP1_CONTROL, 127, CHANNEL); + CircuitPlayground.redLED(HIGH); + } + else { + usbMIDI.sendControlChange(CAP1_CONTROL, 0, CHANNEL); + CircuitPlayground.redLED(LOW); + } + cap1LastState = cap1State; + } + + //cap0 + if(cap0 > CAPMIN){ + cap0State=1; + } + else{ + cap0State=0; + } + if(cap0State != cap0LastState){ + if(cap0State==1){ + usbMIDI.sendControlChange(CAP0_CONTROL, 1, CHANNEL); + CircuitPlayground.redLED(HIGH); + } + else { + usbMIDI.sendControlChange(CAP0_CONTROL, 0, CHANNEL); + CircuitPlayground.redLED(LOW); + } + cap0LastState = cap0State; + } + +//cap2 + if(cap2 > CAPMIN){ + cap2State=1; + } + else{ + cap2State=0; + } + if(cap2State != cap2LastState){ + if(cap2State==1){ + usbMIDI.sendControlChange(CAP2_CONTROL, 1, CHANNEL); + CircuitPlayground.redLED(HIGH); + } + else { + usbMIDI.sendControlChange(CAP2_CONTROL, 0, CHANNEL); + CircuitPlayground.redLED(LOW); + } + cap2LastState = cap2State; + } + + //cap3 + if(cap3 > CAPMIN){ + cap3State=1; + } + else{ + cap3State=0; + } + if(cap3State != cap3LastState){ + if(cap3State==1){ + usbMIDI.sendControlChange(CAP3_CONTROL, 1, CHANNEL); + CircuitPlayground.redLED(HIGH); + } + else { + usbMIDI.sendControlChange(CAP3_CONTROL, 0, CHANNEL); + CircuitPlayground.redLED(LOW); + } + cap3LastState = cap3State; + } + + //cap12 Crossfader Deck B + + if(cap12 > CAPMIN){ + cap12State=1; + } + else{ + cap12State=0; + } + if(cap12State != cap12LastState){ + if(cap12State==1){ + usbMIDI.sendControlChange(CAP12_CONTROL, 127, CHANNEL); + CircuitPlayground.redLED(HIGH); + } + else { + usbMIDI.sendControlChange(CAP12_CONTROL, 0, CHANNEL); + CircuitPlayground.redLED(LOW); + } + cap12LastState = cap12State; + } + + //cap6 + if (cap6>CAPMIN){ + int cap6NewVal = map(CircuitPlayground.readCap(6),0,200,0,127); + if (abs(cap6 - cap6NewVal>CAPSLOP)) { + cap6 = cap6NewVal; + usbMIDI.sendControlChange(CAP6_CONTROL, cap6, CHANNEL); + CircuitPlayground.redLED(HIGH); + cap6ON = true; + } + else{ + if (cap6ON==true){ + usbMIDI.sendControlChange(CAP6_CONTROL, 0, CHANNEL); //send a 0 + CircuitPlayground.redLED(LOW); + cap6ON=false; + } + } + } + + //cap9 + if(cap9 > CAPMIN){ + cap9State=1; + } + else{ + cap9State=0; + } + if(cap9State != cap9LastState){ + if(cap9State==1){ + usbMIDI.sendControlChange(CAP9_CONTROL, 1, CHANNEL); + CircuitPlayground.redLED(HIGH); + } + else { + usbMIDI.sendControlChange(CAP9_CONTROL, 0, CHANNEL); + CircuitPlayground.redLED(LOW); + } + cap9LastState = cap9State; + } + + //cap10 + if(cap10 > CAPMIN){ + cap10State=1; + } + else{ + cap10State=0; + } + if(cap10State != cap10LastState){ + if(cap10State==1){ + usbMIDI.sendControlChange(CAP10_CONTROL, 1, CHANNEL); + CircuitPlayground.redLED(HIGH); + } + else { + usbMIDI.sendControlChange(CAP10_CONTROL, 0, CHANNEL); + CircuitPlayground.redLED(LOW); + } + cap10LastState = cap10State; + } + + // MIDI Controllers should discard incoming MIDI messages. + while (usbMIDI.read()) { + } +} diff --git a/libraries/Adafruit_TinyUSB_Arduino/examples/MIDI/midi_test/.skip.txt b/libraries/Adafruit_TinyUSB_Arduino/examples/MIDI/midi_test/.skip.txt new file mode 100644 index 0000000..e4f9319 --- /dev/null +++ b/libraries/Adafruit_TinyUSB_Arduino/examples/MIDI/midi_test/.skip.txt @@ -0,0 +1,2 @@ +feather_esp32_v2 +pico_rp2040_tinyusb_host diff --git a/libraries/Adafruit_TinyUSB_Arduino/examples/MIDI/midi_test/midi_test.ino b/libraries/Adafruit_TinyUSB_Arduino/examples/MIDI/midi_test/midi_test.ino new file mode 100644 index 0000000..6e8f8a4 --- /dev/null +++ b/libraries/Adafruit_TinyUSB_Arduino/examples/MIDI/midi_test/midi_test.ino @@ -0,0 +1,134 @@ +/********************************************************************* + Adafruit invests time and resources providing this open source code, + please support Adafruit and open-source hardware by purchasing + products from Adafruit! + + MIT license, check LICENSE for more information + Copyright (c) 2019 Ha Thach for Adafruit Industries + All text above, and the splash screen below must be included in + any redistribution +*********************************************************************/ + +/* This sketch is enumerated as USB MIDI device. + * Following library is required + * - MIDI Library by Forty Seven Effects + * https://github.com/FortySevenEffects/arduino_midi_library + */ + +#include +#include +#include + +// USB MIDI object +Adafruit_USBD_MIDI usb_midi; + +// Create a new instance of the Arduino MIDI Library, +// and attach usb_midi as the transport. +MIDI_CREATE_INSTANCE(Adafruit_USBD_MIDI, usb_midi, MIDI); + +// Variable that holds the current position in the sequence. +uint32_t position = 0; + +// Store example melody as an array of note values +byte note_sequence[] = { + 74, 78, 81, 86, 90, 93, 98, 102, 57, 61, 66, 69, 73, 78, 81, 85, 88, 92, 97, 100, 97, 92, 88, 85, 81, 78, + 74, 69, 66, 62, 57, 62, 66, 69, 74, 78, 81, 86, 90, 93, 97, 102, 97, 93, 90, 85, 81, 78, 73, 68, 64, 61, + 56, 61, 64, 68, 74, 78, 81, 86, 90, 93, 98, 102 +}; + +void setup() { + // Manual begin() is required on core without built-in support e.g. mbed rp2040 + if (!TinyUSBDevice.isInitialized()) { + TinyUSBDevice.begin(0); + } + + Serial.begin(115200); + + usb_midi.setStringDescriptor("TinyUSB MIDI"); + + // Initialize MIDI, and listen to all MIDI channels + // This will also call usb_midi's begin() + MIDI.begin(MIDI_CHANNEL_OMNI); + + // If already enumerated, additional class driverr begin() e.g msc, hid, midi won't take effect until re-enumeration + if (TinyUSBDevice.mounted()) { + TinyUSBDevice.detach(); + delay(10); + TinyUSBDevice.attach(); + } + + // Attach the handleNoteOn function to the MIDI Library. It will + // be called whenever the Bluefruit receives MIDI Note On messages. + MIDI.setHandleNoteOn(handleNoteOn); + + // Do the same for MIDI Note Off messages. + MIDI.setHandleNoteOff(handleNoteOff); +} + +void loop() { + #ifdef TINYUSB_NEED_POLLING_TASK + // Manual call tud_task since it isn't called by Core's background + TinyUSBDevice.task(); + #endif + + // not enumerated()/mounted() yet: nothing to do + if (!TinyUSBDevice.mounted()) { + return; + } + + static uint32_t start_ms = 0; + if (millis() - start_ms > 266) { + start_ms += 266; + + // Setup variables for the current and previous + // positions in the note sequence. + int previous = position - 1; + + // If we currently are at position 0, set the + // previous position to the last note in the sequence. + if (previous < 0) { + previous = sizeof(note_sequence) - 1; + } + + // Send Note On for current position at full velocity (127) on channel 1. + MIDI.sendNoteOn(note_sequence[position], 127, 1); + + // Send Note Off for previous note. + MIDI.sendNoteOff(note_sequence[previous], 0, 1); + + // Increment position + position++; + + // If we are at the end of the sequence, start over. + if (position >= sizeof(note_sequence)) { + position = 0; + } + } + + // read any new MIDI messages + MIDI.read(); +} + +void handleNoteOn(byte channel, byte pitch, byte velocity) { + // Log when a note is pressed. + Serial.print("Note on: channel = "); + Serial.print(channel); + + Serial.print(" pitch = "); + Serial.print(pitch); + + Serial.print(" velocity = "); + Serial.println(velocity); +} + +void handleNoteOff(byte channel, byte pitch, byte velocity) { + // Log when a note is released. + Serial.print("Note off: channel = "); + Serial.print(channel); + + Serial.print(" pitch = "); + Serial.print(pitch); + + Serial.print(" velocity = "); + Serial.println(velocity); +} diff --git a/libraries/Adafruit_TinyUSB_Arduino/examples/MassStorage/msc_esp32_file_browser/.funhouse.test.only b/libraries/Adafruit_TinyUSB_Arduino/examples/MassStorage/msc_esp32_file_browser/.funhouse.test.only new file mode 100644 index 0000000..e69de29 diff --git a/libraries/Adafruit_TinyUSB_Arduino/examples/MassStorage/msc_esp32_file_browser/.magtag.test.only b/libraries/Adafruit_TinyUSB_Arduino/examples/MassStorage/msc_esp32_file_browser/.magtag.test.only new file mode 100644 index 0000000..e69de29 diff --git a/libraries/Adafruit_TinyUSB_Arduino/examples/MassStorage/msc_esp32_file_browser/.metroesp32s2.test.only b/libraries/Adafruit_TinyUSB_Arduino/examples/MassStorage/msc_esp32_file_browser/.metroesp32s2.test.only new file mode 100644 index 0000000..e69de29 diff --git a/libraries/Adafruit_TinyUSB_Arduino/examples/MassStorage/msc_esp32_file_browser/data/edit.htm b/libraries/Adafruit_TinyUSB_Arduino/examples/MassStorage/msc_esp32_file_browser/data/edit.htm new file mode 100644 index 0000000..cfd70ba --- /dev/null +++ b/libraries/Adafruit_TinyUSB_Arduino/examples/MassStorage/msc_esp32_file_browser/data/edit.htm @@ -0,0 +1,3 @@ +ESP Editor
diff --git a/libraries/Adafruit_TinyUSB_Arduino/examples/MassStorage/msc_esp32_file_browser/data/favicon.ico b/libraries/Adafruit_TinyUSB_Arduino/examples/MassStorage/msc_esp32_file_browser/data/favicon.ico new file mode 100644 index 0000000..71b25fe Binary files /dev/null and b/libraries/Adafruit_TinyUSB_Arduino/examples/MassStorage/msc_esp32_file_browser/data/favicon.ico differ diff --git a/libraries/Adafruit_TinyUSB_Arduino/examples/MassStorage/msc_esp32_file_browser/data/graphs.js b/libraries/Adafruit_TinyUSB_Arduino/examples/MassStorage/msc_esp32_file_browser/data/graphs.js new file mode 100644 index 0000000..b7e878b --- /dev/null +++ b/libraries/Adafruit_TinyUSB_Arduino/examples/MassStorage/msc_esp32_file_browser/data/graphs.js @@ -0,0 +1 @@ +eval(function(p,a,c,k,e,r){e=function(c){return(c35?String.fromCharCode(c+29):c.toString(36))};if(!''.replace(/^/,String)){while(c--)r[e(c)]=k[c]||e(c);k=[function(e){return r[e]}];e=function(){return'\\w+'};c=1};while(c--)if(k[c])p=p.replace(new RegExp('\\b'+e(c)+'\\b','g'),k[c]);return p}('8 H(t){K 21.1Y(t)}8 9(a,b){a.1S(b)}8 1O(b,t,w,h,l,m,d,f,j){6(Q b==="S"||!b)K 1f;6(Q d==="S"||!d)d=1P;w=(w>1b)?w:1b;h=(h<1N)?1N:h;4 k=1M 1L();4 n="V";4 o=(Q f==="S"&&f)?"1J":f;4 p="#1I";4 q=(Q j==="S"&&j)?1f:j;4 r=m;4 s=0;4 u=1;4 x=l;4 y=h+20;b.7.1H="1G";b.7.1F="#1s";b.7.1c="12 11 "+p;b.7.1D="N";b.7.1C="N";b.7.P=(w+2)+"1B";4 g=H("1a");g.7.1c="12 11 "+p;g.7.1z="N";g.P=w;g.1w=y;4 c=g.1v("2d");4 z=H("1j");z.M="1u: ";z.7.1m="N";4 A=H("1p");A.1i="1r";A.1e=3;A.I=u;A.1h=8(e){u=A.I};4 B=H("T");B.M="-";B.U=8(e){6(u>1)u--;A.I=u};4 C=H("T");C.M="+";C.U=8(e){u++;A.I=1n(u)};4 D=H("1j");D.M="1Q: ";D.7.1m="N";4 E=H("1p");E.1i="1r";E.1e=5;E.I=x;E.1h=8(e){x=1n(E.I)};4 F=H("T");F.M="-";F.U=8(e){6(x>l)x--;E.I=x};4 G=H("T");G.M="+";G.U=8(e){6(x(((m-l)/u)+x))K h;K 1d.1o((v-x)*(h/((m-l)/u)))}g.1x=8(v){6(q)v=q(v);k.1y(v);6(vs)s=v;6(k.L>w)k.1A();c.1a.P=w;c.1g=1;c.17=2;c.Y="V";c.O=p;c.J(0,0,w,20);c.O=o;c.X="1q Z";4 a=t+": "+(k[k.L-1])+" | 1U: "+r+" | 23: "+s;c.W(a,5,15);c.O=n;c.J(0,20,w,y);c.1K=o;18(4 i=0;i1b)?w:1b;h=(h<20)?20:h;4 l=1M 1L();4 n="V";4 o=(Q k==="S"&&k)?"1X":k;4 q="#1I";4 r=1;4 u=h+20;j.7.1H="1G";j.7.1F="#1s";j.7.1c="12 11 "+q;j.7.1D="N";j.7.1C="N";j.7.P=(w+2)+"1B";4 g=H("1a");g.7.1c="12 11 "+q;g.7.1z="N";g.P=w;g.1w=u;4 c=g.1v("2d");4 x=H("1j");x.M="1u: ";x.7.1m="N";4 y=H("1p");y.1i="1r";y.1e=3;y.I=r;y.1h=8(e){r=y.I};4 z=H("T");z.M="-";z.U=8(e){6(r>1)r--;y.I=r};4 A=H("T");A.M="+";A.U=8(e){r++;y.I=1n(r)};9(j,x);9(j,z);9(j,y);9(j,A);9(j,H("1E"));9(j,g);8 1l(p){4 a=1d.1Z(((h-((m.L-1)*2))/m.L));4 s=(p*2)+(p*a);K[s,s+a]}8 1k(i,p){K((l[i]&(1<w)l.1A();c.1a.P=w;c.1g=1;c.17=2;c.Y="V";c.O=q;c.J(0,0,w,20);c.O=o;c.X="1q Z";c.W(t,5,15);c.O=n;c.J(0,20,w,u);c.1K=q;c.1g=1;c.17=0;c.Y="";18(4 p=0;p + + + + + ESP Monitor + + + + +
+ + + + +
+
+
+
+ + \ No newline at end of file diff --git a/libraries/Adafruit_TinyUSB_Arduino/examples/MassStorage/msc_esp32_file_browser/msc_esp32_file_browser.ino b/libraries/Adafruit_TinyUSB_Arduino/examples/MassStorage/msc_esp32_file_browser/msc_esp32_file_browser.ino new file mode 100644 index 0000000..9a145e8 --- /dev/null +++ b/libraries/Adafruit_TinyUSB_Arduino/examples/MassStorage/msc_esp32_file_browser/msc_esp32_file_browser.ino @@ -0,0 +1,442 @@ +/********************************************************************* + Adafruit invests time and resources providing this open source code, + please support Adafruit and open-source hardware by purchasing + products from Adafruit! + + MIT license, check LICENSE for more information + Copyright (c) 2019 Ha Thach for Adafruit Industries + All text above, and the splash screen below must be included in + any redistribution +*********************************************************************/ + +/* This example is based on "arduino-esp32/libraries/WebServer/examples/FSBrowser" + * to expose on-board external Flash as USB Mass Storage and webserver. Both interfaces + * can make changes to file system + * + * How to run this example + * 1. Create secrets.h and define your "SECRET_SSID" and "SECRET_PASSWORD" + * 2. Compile and upload this sketch + * 3. Your ESP will be expose as MassStorage device. + * 4. If it is your first run (otherwise skip this step): + * - you may need to format the drive as FAT. Note: If your PC failed to format, you could format + * it using follow sketch "https://github.com/adafruit/Adafruit_SPIFlash/tree/master/examples/SdFat_format" + * - Copy all files in 'data/' folder of this example to the root directory of the MassStorage disk drive + * 5. When prompted, open http://esp32fs.local/edit to access the file browser + * 6. Modify/Update USB drive then refresh your browser to see if the change is updated + * 7. Upload/Edit a file using web browser then see if the USB Drive is updated. Note: the usb drive could + * briefly disappear and reappear to force PC to refresh its cache + * + * NOTE: Following library is required + * - Adafruit_SPIFlash https://github.com/adafruit/Adafruit_SPIFlash + * - SdFat https://github.com/adafruit/SdFat + */ + +#include "SPI.h" +#include "SdFat_Adafruit_Fork.h" +#include "Adafruit_SPIFlash.h" +#include "Adafruit_TinyUSB.h" + +#include +#include +#include +#include + +// check if secrets.h is includable, if not please +// create one with SSDI & PASSWORD macro as following example: +#if __has_include("secrets.h") + #include "secrets.h" +#else + #warning "Please create secrets.h with SSID & PASSWORD defined" + #define SECRET_SSID "your-ssid" + #define SECRET_PASSWORD "your-password" +#endif + +// Debug with FTDI (Serial0) or USBCDC (Serial) +#define DBG_SERIAL Serial + +// ESP32 use same flash device that store code. +// Therefore there is no need to specify the SPI and SS +Adafruit_FlashTransport_ESP32 flashTransport; +Adafruit_SPIFlash flash(&flashTransport); + +// file system object from SdFat +FatVolume fatfs; + +// USB Mass Storage object +Adafruit_USBD_MSC usb_msc; + +bool fs_formatted; // Check if flash is formatted +bool fs_changed; // Set to true when browser write to flash + +const char* host = "esp32fs"; +WebServer server(80); +//holds the current upload +File32 fsUploadFile; + +//--------------------------------------------------------------------+ +// Setup +//--------------------------------------------------------------------+ + +void setupMassStorage(void) +{ + flash.begin(); + + // Set disk vendor id, product id and revision with string up to 8, 16, 4 characters respectively + usb_msc.setID("Adafruit", "External Flash", "1.0"); + + // Set callback + usb_msc.setReadWriteCallback(msc_read_cb, msc_write_cb, msc_flush_cb); + + // Set disk size, block size should be 512 regardless of spi flash page size + usb_msc.setCapacity(flash.size()/512, 512); + + // MSC is ready for read/write + fs_changed = false; + usb_msc.setReadyCallback(0, msc_ready_callback); + usb_msc.begin(); + + // If already enumerated, additional class driverr begin() e.g msc, hid, midi won't take effect until re-enumeration + if (TinyUSBDevice.mounted()) { + TinyUSBDevice.detach(); + delay(10); + TinyUSBDevice.attach(); + } + + // Init file system on the flash + fs_formatted = fatfs.begin(&flash); + + if ( !fs_formatted ) { + DBG_SERIAL.println("Failed to init files system, flash may not be formatted"); + } +} + +void refreshMassStorage(void) +{ + fs_changed = true; +} + +void setupServer(void) +{ + //WIFI INIT + DBG_SERIAL.printf("Connecting to %s\n", SECRET_SSID); + if (String(WiFi.SSID()) != String(SECRET_SSID)) { + WiFi.mode(WIFI_STA); + WiFi.begin(SECRET_SSID, SECRET_PASSWORD); + } + + while (WiFi.status() != WL_CONNECTED) { + delay(500); + DBG_SERIAL.print("."); + } + DBG_SERIAL.println(""); + DBG_SERIAL.print("Connected! IP address: "); + DBG_SERIAL.println(WiFi.localIP()); + + MDNS.begin(host); + DBG_SERIAL.print("Open http://"); + DBG_SERIAL.print(host); + DBG_SERIAL.println(".local/edit to access the file browser"); + + //SERVER INIT + + //list directory + server.on("/list", HTTP_GET, handleFileList); + + //load editor + server.on("/edit", HTTP_GET, []() { + if (!handleFileRead("/edit.htm")) { + server.send(404, "text/plain", "FileNotFound"); + } + }); + + //create file + server.on("/edit", HTTP_PUT, handleFileCreate); + + //delete file + server.on("/edit", HTTP_DELETE, handleFileDelete); + + //first callback is called after the request has ended with all parsed arguments + //second callback handles file uploads at that location + server.on("/edit", HTTP_POST, []() { + server.send(200, "text/plain", ""); + }, handleFileUpload); + + //called when the url is not defined here + //use it to load content from fatfs + server.onNotFound([]() { + if (!handleFileRead(server.uri())) { + server.send(404, "text/plain", "FileNotFound"); + } + }); + + //get heap status, analog input value and all GPIO statuses in one json call + server.on("/all", HTTP_GET, []() { + String json = "{"; + json += "\"heap\":" + String(ESP.getFreeHeap()); + json += ", \"analog\":" + String(analogRead(A0)); + json += ", \"gpio\":" + String((uint32_t)(0)); + json += "}"; + server.send(200, "text/json", json); + json = String(); + }); + server.begin(); + DBG_SERIAL.println("HTTP server started"); +} + +void setup() +{ +#ifdef LED_BUILTIN + pinMode(LED_BUILTIN, OUTPUT); +#endif + + DBG_SERIAL.begin(115200); + + setupMassStorage(); + + // while ( !DBG_SERIAL ) delay(10); // wait for native usb + DBG_SERIAL.println("TinyUSB Mass Storage with ESP32 File Browser example"); + DBG_SERIAL.print("JEDEC ID: 0x"); DBG_SERIAL.println(flash.getJEDECID(), HEX); + DBG_SERIAL.print("Flash size: "); DBG_SERIAL.print(flash.size() / 1024); DBG_SERIAL.println(" KB"); + + setupServer(); +} + +//--------------------------------------------------------------------+ +// Handle requests +//--------------------------------------------------------------------+ + +//format bytes +String formatBytes(size_t bytes) { + if (bytes < 1024) { + return String(bytes) + "B"; + } else if (bytes < (1024 * 1024)) { + return String(bytes / 1024.0) + "KB"; + } else if (bytes < (1024 * 1024 * 1024)) { + return String(bytes / 1024.0 / 1024.0) + "MB"; + } else { + return String(bytes / 1024.0 / 1024.0 / 1024.0) + "GB"; + } +} + +String getContentType(String filename) { + if (server.hasArg("download")) { + return "application/octet-stream"; + } else if (filename.endsWith(".htm")) { + return "text/html"; + } else if (filename.endsWith(".html")) { + return "text/html"; + } else if (filename.endsWith(".css")) { + return "text/css"; + } else if (filename.endsWith(".js")) { + return "application/javascript"; + } else if (filename.endsWith(".png")) { + return "image/png"; + } else if (filename.endsWith(".gif")) { + return "image/gif"; + } else if (filename.endsWith(".jpg")) { + return "image/jpeg"; + } else if (filename.endsWith(".ico")) { + return "image/x-icon"; + } else if (filename.endsWith(".xml")) { + return "text/xml"; + } else if (filename.endsWith(".pdf")) { + return "application/x-pdf"; + } else if (filename.endsWith(".zip")) { + return "application/x-zip"; + } else if (filename.endsWith(".gz")) { + return "application/x-gzip"; + } + return "text/plain"; +} + +bool exists(String path){ + bool yes = false; + File32 file = fatfs.open(path, O_READ); + if(file && !file.isDirectory()){ + yes = true; + } + file.close(); + return yes; +} + +bool handleFileRead(String path) { + DBG_SERIAL.println("handleFileRead: " + path); + if (path.endsWith("/")) { + path += "index.htm"; + } + String contentType = getContentType(path); +// String pathWithGz = path + ".gz"; + if ( /*exists(pathWithGz) ||*/ exists(path)) { +// if (exists(pathWithGz)) { +// path += ".gz"; +// } + File32 file = fatfs.open(path, O_READ); + server.streamFile(file, contentType); + file.close(); + return true; + } + return false; +} + +void handleFileUpload() { + if (server.uri() != "/edit") { + return; + } + HTTPUpload& upload = server.upload(); + if (upload.status == UPLOAD_FILE_START) { + String filename = upload.filename; + if (!filename.startsWith("/")) { + filename = "/" + filename; + } + DBG_SERIAL.print("handleFileUpload Name: "); DBG_SERIAL.println(filename); + fsUploadFile = fatfs.open(filename, O_WRITE | O_CREAT | O_TRUNC); + filename = String(); + } else if (upload.status == UPLOAD_FILE_WRITE) { + DBG_SERIAL.print("handleFileUpload Data: "); DBG_SERIAL.println(upload.currentSize); + if (fsUploadFile) { + fsUploadFile.write(upload.buf, upload.currentSize); + }else + { + DBG_SERIAL.print("handleFileUpload file is not opened !!!"); + } + } else if (upload.status == UPLOAD_FILE_END) { + if (fsUploadFile) { + fsUploadFile.close(); + refreshMassStorage(); + } + DBG_SERIAL.print("handleFileUpload Size: "); DBG_SERIAL.println(upload.totalSize); + } +} + +void handleFileDelete() { + if (server.args() == 0) { + return server.send(500, "text/plain", "BAD ARGS"); + } + String path = server.arg(0); + DBG_SERIAL.println("handleFileDelete: " + path); + if (path == "/") { + return server.send(500, "text/plain", "BAD PATH"); + } + if (!exists(path)) { + return server.send(404, "text/plain", "FileNotFound"); + } + fatfs.remove(path.c_str()); + refreshMassStorage(); + server.send(200, "text/plain", ""); + path = String(); +} + +void handleFileCreate() { + if (server.args() == 0) { + return server.send(500, "text/plain", "BAD ARGS"); + } + String path = server.arg(0); + DBG_SERIAL.println("handleFileCreate: " + path); + if (path == "/") { + return server.send(500, "text/plain", "BAD PATH"); + } + if (exists(path)) { + return server.send(500, "text/plain", "FILE EXISTS"); + } + File32 file = fatfs.open(path, O_WRITE | O_CREAT); + if (file) { + file.close(); + } else { + return server.send(500, "text/plain", "CREATE FAILED"); + } + server.send(200, "text/plain", ""); + path = String(); +} + +void handleFileList() { + if (!server.hasArg("dir")) { + server.send(500, "text/plain", "BAD ARGS"); + return; + } + + String path = server.arg("dir"); + DBG_SERIAL.println("handleFileList: " + path); + + File32 root = fatfs.open(path); + path = String(); + + String output = "["; + if(root.isDirectory()){ + File32 file = root.openNextFile(); + char fname[256]; + while(file){ + if (output != "[") { + output += ','; + } + output += "{\"type\":\""; + output += (file.isDirectory()) ? "dir" : "file"; + output += "\",\"name\":\""; + //output += String(file.path()).substring(1); + file.getName(fname, sizeof(fname)); + output += fname; + output += "\"}"; + file = root.openNextFile(); + } + } + output += "]"; + server.send(200, "text/json", output); +} + +//--------------------------------------------------------------------+ +// Loop +//--------------------------------------------------------------------+ + +void loop() +{ + server.handleClient(); + delay(2);//allow the cpu to switch to other tasks +} + +// Callback invoked when received READ10 command. +// Copy disk's data to buffer (up to bufsize) and +// return number of copied bytes (must be multiple of block size) +int32_t msc_read_cb (uint32_t lba, void* buffer, uint32_t bufsize) +{ + // Note: SPIFLash Bock API: readBlocks/writeBlocks/syncBlocks + // already include 4K sector caching internally. We don't need to cache it, yahhhh!! + return flash.readBlocks(lba, (uint8_t*) buffer, bufsize/512) ? bufsize : -1; +} + +// Callback invoked when received WRITE10 command. +// Process data in buffer to disk's storage and +// return number of written bytes (must be multiple of block size) +int32_t msc_write_cb (uint32_t lba, uint8_t* buffer, uint32_t bufsize) +{ +#ifdef LED_BUILTIN + digitalWrite(LED_BUILTIN, HIGH); +#endif + + // Note: SPIFLash Bock API: readBlocks/writeBlocks/syncBlocks + // already include 4K sector caching internally. We don't need to cache it, yahhhh!! + return flash.writeBlocks(lba, buffer, bufsize/512) ? bufsize : -1; +} + +// Callback invoked when WRITE10 command is completed (status received and accepted by host). +// used to flush any pending cache. +void msc_flush_cb (void) +{ + // sync with flash + flash.syncBlocks(); + + // clear file system's cache to force refresh + fatfs.cacheClear(); + +#ifdef LED_BUILTIN + digitalWrite(LED_BUILTIN, LOW); +#endif +} + +// Invoked when received Test Unit Ready command. +// return true allowing host to read/write this LUN e.g SD card inserted +bool msc_ready_callback(void) +{ + // if fs has changed, mark unit as not ready temporarily to force PC to flush cache + bool ret = !fs_changed; + fs_changed = false; + return ret; +} diff --git a/libraries/Adafruit_TinyUSB_Arduino/examples/MassStorage/msc_external_flash/.skip.txt b/libraries/Adafruit_TinyUSB_Arduino/examples/MassStorage/msc_external_flash/.skip.txt new file mode 100644 index 0000000..ae593a8 --- /dev/null +++ b/libraries/Adafruit_TinyUSB_Arduino/examples/MassStorage/msc_external_flash/.skip.txt @@ -0,0 +1,3 @@ +feather_esp32_v2 +pico_rp2040_tinyusb_host +CH32V20x_EVT diff --git a/libraries/Adafruit_TinyUSB_Arduino/examples/MassStorage/msc_external_flash/flash_config.h b/libraries/Adafruit_TinyUSB_Arduino/examples/MassStorage/msc_external_flash/flash_config.h new file mode 100644 index 0000000..0dd8551 --- /dev/null +++ b/libraries/Adafruit_TinyUSB_Arduino/examples/MassStorage/msc_external_flash/flash_config.h @@ -0,0 +1,79 @@ +/* + * The MIT License (MIT) + * + * Copyright (c) 2022 Ha Thach (tinyusb.org) for Adafruit Industries + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#ifndef FLASH_CONFIG_H_ +#define FLASH_CONFIG_H_ + +// Un-comment to run example with custom SPI and SS e.g with FRAM breakout +// #define CUSTOM_CS A5 +// #define CUSTOM_SPI SPI + +#if defined(CUSTOM_CS) && defined(CUSTOM_SPI) +Adafruit_FlashTransport_SPI flashTransport(CUSTOM_CS, CUSTOM_SPI); + +#elif defined(ARDUINO_ARCH_ESP32) + +// ESP32 use same flash device that store code for file system. +// SPIFlash will parse partition.cvs to detect FATFS partition to use +Adafruit_FlashTransport_ESP32 flashTransport; + +#elif defined(ARDUINO_ARCH_RP2040) + +// RP2040 use same flash device that store code for file system. Therefore we +// only need to specify start address and size (no need SPI or SS) +// By default (start=0, size=0), values that match file system setting in +// 'Tools->Flash Size' menu selection will be used. +Adafruit_FlashTransport_RP2040 flashTransport; + +// To be compatible with CircuitPython partition scheme (start_address = 1 MB, +// size = total flash - 1 MB) use const value (CPY_START_ADDR, CPY_SIZE) or +// subclass Adafruit_FlashTransport_RP2040_CPY. Un-comment either of the +// following line: +// Adafruit_FlashTransport_RP2040 +// flashTransport(Adafruit_FlashTransport_RP2040::CPY_START_ADDR, +// Adafruit_FlashTransport_RP2040::CPY_SIZE); +// Adafruit_FlashTransport_RP2040_CPY flashTransport; + +#else + +// On-board external flash (QSPI or SPI) macros should already +// defined in your board variant if supported +// - EXTERNAL_FLASH_USE_QSPI +// - EXTERNAL_FLASH_USE_CS/EXTERNAL_FLASH_USE_SPI + +#if defined(EXTERNAL_FLASH_USE_QSPI) + +Adafruit_FlashTransport_QSPI flashTransport; + +#elif defined(EXTERNAL_FLASH_USE_SPI) + +Adafruit_FlashTransport_SPI flashTransport(EXTERNAL_FLASH_USE_CS, + EXTERNAL_FLASH_USE_SPI); + +#else +#error No (Q)SPI flash are defined for your board ! +#endif +#endif + +#endif diff --git a/libraries/Adafruit_TinyUSB_Arduino/examples/MassStorage/msc_external_flash/msc_external_flash.ino b/libraries/Adafruit_TinyUSB_Arduino/examples/MassStorage/msc_external_flash/msc_external_flash.ino new file mode 100644 index 0000000..06dba7f --- /dev/null +++ b/libraries/Adafruit_TinyUSB_Arduino/examples/MassStorage/msc_external_flash/msc_external_flash.ino @@ -0,0 +1,177 @@ +/********************************************************************* + Adafruit invests time and resources providing this open source code, + please support Adafruit and open-source hardware by purchasing + products from Adafruit! + + MIT license, check LICENSE for more information + Copyright (c) 2019 Ha Thach for Adafruit Industries + All text above, and the splash screen below must be included in + any redistribution +*********************************************************************/ + +/* This example demo how to expose on-board external Flash as USB Mass Storage. + * Following library is required + * - Adafruit_SPIFlash https://github.com/adafruit/Adafruit_SPIFlash + * - SdFat https://github.com/adafruit/SdFat + * + * Note: Adafruit fork of SdFat enabled ENABLE_EXTENDED_TRANSFER_CLASS and FAT12_SUPPORT + * in SdFatConfig.h, which is needed to run SdFat on external flash. You can use original + * SdFat library and manually change those macros + * + * Note2: If your flash is not formatted as FAT12 previously, you could format it using + * follow sketch https://github.com/adafruit/Adafruit_SPIFlash/tree/master/examples/SdFat_format + */ + +#include "SPI.h" +#include "SdFat_Adafruit_Fork.h" +#include "Adafruit_SPIFlash.h" +#include "Adafruit_TinyUSB.h" + +// for flashTransport definition +#include "flash_config.h" + +Adafruit_SPIFlash flash(&flashTransport); + +// file system object from SdFat +FatVolume fatfs; + +FatFile root; +FatFile file; + +// USB Mass Storage object +Adafruit_USBD_MSC usb_msc; + +// Check if flash is formatted +bool fs_formatted = false; + +// Set to true when PC write to flash +bool fs_changed = true;; + +// the setup function runs once when you press reset or power the board +void setup() { +#ifdef LED_BUILTIN + pinMode(LED_BUILTIN, OUTPUT); +#endif + + Serial.begin(115200); + + flash.begin(); + + // Set disk vendor id, product id and revision with string up to 8, 16, 4 characters respectively + usb_msc.setID("Adafruit", "External Flash", "1.0"); + + // Set callback + usb_msc.setReadWriteCallback(msc_read_cb, msc_write_cb, msc_flush_cb); + + // Set disk size, block size should be 512 regardless of spi flash page size + usb_msc.setCapacity(flash.size()/512, 512); + + // MSC is ready for read/write + usb_msc.setUnitReady(true); + + usb_msc.begin(); + + // If already enumerated, additional class driverr begin() e.g msc, hid, midi won't take effect until re-enumeration + if (TinyUSBDevice.mounted()) { + TinyUSBDevice.detach(); + delay(10); + TinyUSBDevice.attach(); + } + + // Init file system on the flash + fs_formatted = fatfs.begin(&flash); + + //while ( !Serial ) delay(10); // wait for native usb + + Serial.println("Adafruit TinyUSB Mass Storage External Flash example"); + Serial.print("JEDEC ID: 0x"); Serial.println(flash.getJEDECID(), HEX); + Serial.print("Flash size: "); Serial.print(flash.size() / 1024); Serial.println(" KB"); +} + +void loop() { + // check if formatted + if ( !fs_formatted ) { + fs_formatted = fatfs.begin(&flash); + + if (!fs_formatted) { + Serial.println("Failed to init files system, flash may not be formatted"); + Serial.println("Please format it as FAT12 with your PC or using Adafruit_SPIFlash's SdFat_format example:"); + Serial.println("- https://github.com/adafruit/Adafruit_SPIFlash/tree/master/examples/SdFat_format"); + Serial.println(); + + delay(1000); + return; + } + } + + if ( fs_changed ) { + fs_changed = false; + + Serial.println("Opening root"); + + if ( !root.open("/") ) { + Serial.println("open root failed"); + return; + } + + Serial.println("Flash contents:"); + + // Open next file in root. + // Warning, openNext starts at the current directory position + // so a rewind of the directory may be required. + while ( file.openNext(&root, O_RDONLY) ) { + file.printFileSize(&Serial); + Serial.write(' '); + file.printName(&Serial); + if ( file.isDir() ) { + // Indicate a directory. + Serial.write('/'); + } + Serial.println(); + file.close(); + } + + root.close(); + + Serial.println(); + delay(1000); // refresh every 1 second + } +} + +// Callback invoked when received READ10 command. +// Copy disk's data to buffer (up to bufsize) and +// return number of copied bytes (must be multiple of block size) +int32_t msc_read_cb (uint32_t lba, void* buffer, uint32_t bufsize) { + // Note: SPIFLash Block API: readBlocks/writeBlocks/syncBlocks + // already include 4K sector caching internally. We don't need to cache it, yahhhh!! + return flash.readBlocks(lba, (uint8_t*) buffer, bufsize/512) ? bufsize : -1; +} + +// Callback invoked when received WRITE10 command. +// Process data in buffer to disk's storage and +// return number of written bytes (must be multiple of block size) +int32_t msc_write_cb (uint32_t lba, uint8_t* buffer, uint32_t bufsize) { +#ifdef LED_BUILTIN + digitalWrite(LED_BUILTIN, HIGH); +#endif + + // Note: SPIFLash Block API: readBlocks/writeBlocks/syncBlocks + // already include 4K sector caching internally. We don't need to cache it, yahhhh!! + return flash.writeBlocks(lba, buffer, bufsize/512) ? bufsize : -1; +} + +// Callback invoked when WRITE10 command is completed (status received and accepted by host). +// used to flush any pending cache. +void msc_flush_cb (void) { + // sync with flash + flash.syncBlocks(); + + // clear file system's cache to force refresh + fatfs.cacheClear(); + + fs_changed = true; + +#ifdef LED_BUILTIN + digitalWrite(LED_BUILTIN, LOW); +#endif +} diff --git a/libraries/Adafruit_TinyUSB_Arduino/examples/MassStorage/msc_external_flash_sdcard/.skip.txt b/libraries/Adafruit_TinyUSB_Arduino/examples/MassStorage/msc_external_flash_sdcard/.skip.txt new file mode 100644 index 0000000..ae593a8 --- /dev/null +++ b/libraries/Adafruit_TinyUSB_Arduino/examples/MassStorage/msc_external_flash_sdcard/.skip.txt @@ -0,0 +1,3 @@ +feather_esp32_v2 +pico_rp2040_tinyusb_host +CH32V20x_EVT diff --git a/libraries/Adafruit_TinyUSB_Arduino/examples/MassStorage/msc_external_flash_sdcard/flash_config.h b/libraries/Adafruit_TinyUSB_Arduino/examples/MassStorage/msc_external_flash_sdcard/flash_config.h new file mode 100644 index 0000000..0dd8551 --- /dev/null +++ b/libraries/Adafruit_TinyUSB_Arduino/examples/MassStorage/msc_external_flash_sdcard/flash_config.h @@ -0,0 +1,79 @@ +/* + * The MIT License (MIT) + * + * Copyright (c) 2022 Ha Thach (tinyusb.org) for Adafruit Industries + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#ifndef FLASH_CONFIG_H_ +#define FLASH_CONFIG_H_ + +// Un-comment to run example with custom SPI and SS e.g with FRAM breakout +// #define CUSTOM_CS A5 +// #define CUSTOM_SPI SPI + +#if defined(CUSTOM_CS) && defined(CUSTOM_SPI) +Adafruit_FlashTransport_SPI flashTransport(CUSTOM_CS, CUSTOM_SPI); + +#elif defined(ARDUINO_ARCH_ESP32) + +// ESP32 use same flash device that store code for file system. +// SPIFlash will parse partition.cvs to detect FATFS partition to use +Adafruit_FlashTransport_ESP32 flashTransport; + +#elif defined(ARDUINO_ARCH_RP2040) + +// RP2040 use same flash device that store code for file system. Therefore we +// only need to specify start address and size (no need SPI or SS) +// By default (start=0, size=0), values that match file system setting in +// 'Tools->Flash Size' menu selection will be used. +Adafruit_FlashTransport_RP2040 flashTransport; + +// To be compatible with CircuitPython partition scheme (start_address = 1 MB, +// size = total flash - 1 MB) use const value (CPY_START_ADDR, CPY_SIZE) or +// subclass Adafruit_FlashTransport_RP2040_CPY. Un-comment either of the +// following line: +// Adafruit_FlashTransport_RP2040 +// flashTransport(Adafruit_FlashTransport_RP2040::CPY_START_ADDR, +// Adafruit_FlashTransport_RP2040::CPY_SIZE); +// Adafruit_FlashTransport_RP2040_CPY flashTransport; + +#else + +// On-board external flash (QSPI or SPI) macros should already +// defined in your board variant if supported +// - EXTERNAL_FLASH_USE_QSPI +// - EXTERNAL_FLASH_USE_CS/EXTERNAL_FLASH_USE_SPI + +#if defined(EXTERNAL_FLASH_USE_QSPI) + +Adafruit_FlashTransport_QSPI flashTransport; + +#elif defined(EXTERNAL_FLASH_USE_SPI) + +Adafruit_FlashTransport_SPI flashTransport(EXTERNAL_FLASH_USE_CS, + EXTERNAL_FLASH_USE_SPI); + +#else +#error No (Q)SPI flash are defined for your board ! +#endif +#endif + +#endif diff --git a/libraries/Adafruit_TinyUSB_Arduino/examples/MassStorage/msc_external_flash_sdcard/msc_external_flash_sdcard.ino b/libraries/Adafruit_TinyUSB_Arduino/examples/MassStorage/msc_external_flash_sdcard/msc_external_flash_sdcard.ino new file mode 100644 index 0000000..27eccd0 --- /dev/null +++ b/libraries/Adafruit_TinyUSB_Arduino/examples/MassStorage/msc_external_flash_sdcard/msc_external_flash_sdcard.ino @@ -0,0 +1,271 @@ +/********************************************************************* + Adafruit invests time and resources providing this open source code, + please support Adafruit and open-source hardware by purchasing + products from Adafruit! + + MIT license, check LICENSE for more information + Copyright (c) 2019 Ha Thach for Adafruit Industries + All text above, and the splash screen below must be included in + any redistribution +*********************************************************************/ + +/* This example exposes both external flash and SD card as mass storage (dual LUNs) + * Following library is required + * - Adafruit_SPIFlash https://github.com/adafruit/Adafruit_SPIFlash + * - SdFat https://github.com/adafruit/SdFat + * + * Note: Adafruit fork of SdFat enabled ENABLE_EXTENDED_TRANSFER_CLASS and FAT12_SUPPORT + * in SdFatConfig.h, which is needed to run SdFat on external flash. You can use original + * SdFat library and manually change those macros + * + * Note2: If your flash is not formatted as FAT12 previously, you could format it using + * follow sketch https://github.com/adafruit/Adafruit_SPIFlash/tree/master/examples/SdFat_format + */ + +#include "SPI.h" +#include "SdFat_Adafruit_Fork.h" +#include "Adafruit_SPIFlash.h" +#include "Adafruit_TinyUSB.h" + +//--------------------------------------------------------------------+ +// External Flash Config +//--------------------------------------------------------------------+ + +// for flashTransport definition +#include "flash_config.h" +Adafruit_SPIFlash flash(&flashTransport); + +//--------------------------------------------------------------------+ +// SDCard Config +//--------------------------------------------------------------------+ +#if defined(ARDUINO_PYPORTAL_M4) || defined(ARDUINO_PYPORTAL_M4_TITANO) + // PyPortal has on-board card reader + #define SDCARD_CS 32 + #define SDCARD_DETECT 33 + #define SDCARD_DETECT_ACTIVE HIGH + +#elif defined(ARDUINO_ADAFRUIT_METRO_RP2040) + #define SDIO_CLK_PIN 18 + #define SDIO_CMD_PIN 19 // MOSI + #define SDIO_DAT0_PIN 20 // DAT1: 21, DAT2: 22, DAT3: 23 + + #define SDCARD_DETECT 15 + #define SDCARD_DETECT_ACTIVE LOW + +#elif defined(ARDUINO_ADAFRUIT_METRO_RP2350) + // Note: not working yet (need troubleshoot later) + #define SDIO_CLK_PIN 34 + #define SDIO_CMD_PIN 35 // MOSI + #define SDIO_DAT0_PIN 36 // DAT1: 37, DAT2: 38, DAT3: 39 + + #define SDCARD_DETECT 40 + #define SDCARD_DETECT_ACTIVE LOW + +#elif defined(ARDUINO_ADAFRUIT_FRUITJAM_RP2350) + #define SDIO_CLK_PIN 34 + #define SDIO_CMD_PIN 35 // MOSI + #define SDIO_DAT0_PIN 36 // DAT1: 37, DAT2: 38, DAT3: 39 + + #define SDCARD_DETECT 33 + #define SDCARD_DETECT_ACTIVE LOW + +#else + // Use SPI, no detect + #define SDCARD_CS 10 +#endif + +#if defined(SDIO_CLK_PIN) && defined(SDIO_CMD_PIN) && defined(SDIO_DAT0_PIN) + #define SD_CONFIG SdioConfig(SDIO_CLK_PIN, SDIO_CMD_PIN, SDIO_DAT0_PIN) +#else + #define SD_CONFIG SdSpiConfig(SDCARD_CS, SHARED_SPI, SD_SCK_MHZ(50)) +#endif + +// File system on SD Card +SdFat sd; + +// USB Mass Storage object +Adafruit_USBD_MSC usb_msc; + +// Set to true when PC write to flash +bool sd_inited = false; + +// the setup function runs once when you press reset or power the board +void setup() { +#ifdef LED_BUILTIN + pinMode(LED_BUILTIN, OUTPUT); +#endif + Serial.begin(115200); + + // MSC with 2 Logical Units: LUN0: External Flash, LUN1: SDCard + usb_msc.setMaxLun(2); + + usb_msc.setID(0, "Adafruit", "External Flash", "1.0"); + usb_msc.setID(1, "Adafruit", "SD Card", "1.0"); + + // Since initialize both external flash and SD card can take time. + // If it takes too long, our board could be enumerated as CDC device only + // i.e without Mass Storage. To prevent this, we call Mass Storage begin first + // LUN readiness will always be set later on + usb_msc.begin(); + + // If already enumerated, additional class driverr begin() e.g msc, hid, midi won't take effect until re-enumeration + if (TinyUSBDevice.mounted()) { + TinyUSBDevice.detach(); + delay(10); + TinyUSBDevice.attach(); + } + + //------------- Lun 0 for external flash -------------// + flash.begin(); + + usb_msc.setCapacity(0, flash.size()/512, 512); + usb_msc.setReadWriteCallback(0, external_flash_read_cb, external_flash_write_cb, external_flash_flush_cb); + usb_msc.setUnitReady(0, true); + + //------------- Lun 1 for SD card -------------// +#ifdef SDCARD_DETECT + // DETECT pin is available, detect card present on the fly with test unit ready + pinMode(SDCARD_DETECT, INPUT); + usb_msc.setReadyCallback(1, sdcard_ready_callback); +#else + // no DETECT pin, card must be inserted when powered on + init_sdcard(); + sd_inited = true; + usb_msc.setUnitReady(1, true); +#endif + + // while ( !Serial ) delay(10); // wait for native usb + Serial.println("Adafruit TinyUSB Mass Storage External Flash + SD Card example"); + delay(1000); +} + +bool init_sdcard(void) { + Serial.print("Init SDCard ... "); + + if (!sd.begin(SD_CONFIG)) { + Serial.println("initialization failed. Things to check:"); + Serial.println("- is a card inserted?"); + Serial.println("- is your wiring correct?"); + Serial.println("- did you change the SDCARD_CS or SDIO pin to match your shield or module?"); + return false; + } + + uint32_t block_count = sd.card()->sectorCount(); + usb_msc.setCapacity(1, block_count, 512); + usb_msc.setReadWriteCallback(1, sdcard_read_cb, sdcard_write_cb, sdcard_flush_cb); + + Serial.print("OK, Card size = "); + Serial.print((block_count / (1024 * 1024)) * 512); + Serial.println(" MB"); + + return true; +} + +void print_rootdir(File32* rdir) { + File32 file; + + // Open next file in root. + // Warning, openNext starts at the current directory position + // so a rewind of the directory may be required. + while (file.openNext(rdir, O_RDONLY)) { + file.printFileSize(&Serial); + Serial.write(' '); + file.printName(&Serial); + if (file.isDir()) { + // Indicate a directory. + Serial.write('/'); + } + Serial.println(); + file.close(); + } +} + +void loop() { + // nothing to do +} + + +//--------------------------------------------------------------------+ +// SD Card callbacks +//--------------------------------------------------------------------+ + +int32_t sdcard_read_cb (uint32_t lba, void* buffer, uint32_t bufsize) { + bool rc = sd.card()->readSectors(lba, (uint8_t*) buffer, bufsize/512); + return rc ? bufsize : -1; +} + +// Callback invoked when received WRITE10 command. +// Process data in buffer to disk's storage and +// return number of written bytes (must be multiple of block size) +int32_t sdcard_write_cb (uint32_t lba, uint8_t* buffer, uint32_t bufsize) { +#ifdef LED_BUILTIN + digitalWrite(LED_BUILTIN, HIGH); +#endif + bool rc = sd.card()->writeSectors(lba, buffer, bufsize/512); + return rc ? bufsize : -1; +} + +// Callback invoked when WRITE10 command is completed (status received and accepted by host). +// used to flush any pending cache. +void sdcard_flush_cb (void) { + sd.card()->syncDevice(); + sd.cacheClear(); // clear file system's cache to force refresh + +#ifdef LED_BUILTIN + digitalWrite(LED_BUILTIN, LOW); +#endif +} + +#ifdef SDCARD_DETECT +// Invoked when received Test Unit Ready command. +// return true allowing host to read/write this LUN e.g SD card inserted +bool sdcard_ready_callback(void) { + // Card is inserted + if (digitalRead(SDCARD_DETECT) == SDCARD_DETECT_ACTIVE) { + // init SD card if not already + if (!sd_inited) { + sd_inited = init_sdcard(); + } + } else { + sd_inited = false; + usb_msc.setReadWriteCallback(1, NULL, NULL, NULL); + } + + return sd_inited; +} +#endif + +//--------------------------------------------------------------------+ +// External Flash callbacks +//--------------------------------------------------------------------+ + +// Callback invoked when received READ10 command. +// Copy disk's data to buffer (up to bufsize) and +// return number of copied bytes (must be multiple of block size) +int32_t external_flash_read_cb (uint32_t lba, void* buffer, uint32_t bufsize) { + // Note: SPIFLash Bock API: readBlocks/writeBlocks/syncBlocks + // already include 4K sector caching internally. We don't need to cache it, yahhhh!! + return flash.readBlocks(lba, (uint8_t*) buffer, bufsize/512) ? bufsize : -1; +} + +// Callback invoked when received WRITE10 command. +// Process data in buffer to disk's storage and +// return number of written bytes (must be multiple of block size) +int32_t external_flash_write_cb (uint32_t lba, uint8_t* buffer, uint32_t bufsize) { +#ifdef LED_BUILTIN + digitalWrite(LED_BUILTIN, HIGH); +#endif + + // Note: SPIFLash Bock API: readBlocks/writeBlocks/syncBlocks + // already include 4K sector caching internally. We don't need to cache it, yahhhh!! + return flash.writeBlocks(lba, buffer, bufsize/512) ? bufsize : -1; +} + +// Callback invoked when WRITE10 command is completed (status received and accepted by host). +// used to flush any pending cache. +void external_flash_flush_cb (void) { + flash.syncBlocks(); +#ifdef LED_BUILTIN + digitalWrite(LED_BUILTIN, LOW); +#endif +} diff --git a/libraries/Adafruit_TinyUSB_Arduino/examples/MassStorage/msc_internal_flash_samd/.metro_m0_tinyusb.test.only b/libraries/Adafruit_TinyUSB_Arduino/examples/MassStorage/msc_internal_flash_samd/.metro_m0_tinyusb.test.only new file mode 100644 index 0000000..e69de29 diff --git a/libraries/Adafruit_TinyUSB_Arduino/examples/MassStorage/msc_internal_flash_samd/msc_internal_flash_samd.ino b/libraries/Adafruit_TinyUSB_Arduino/examples/MassStorage/msc_internal_flash_samd/msc_internal_flash_samd.ino new file mode 100644 index 0000000..ff99940 --- /dev/null +++ b/libraries/Adafruit_TinyUSB_Arduino/examples/MassStorage/msc_internal_flash_samd/msc_internal_flash_samd.ino @@ -0,0 +1,137 @@ +/********************************************************************* + Adafruit invests time and resources providing this open source code, + please support Adafruit and open-source hardware by purchasing + products from Adafruit! + + MIT license, check LICENSE for more information + Copyright (c) 2019 Ha Thach for Adafruit Industries + All text above, and the splash screen below must be included in + any redistribution +*********************************************************************/ + +#include "SPI.h" +#include "SdFat_Adafruit_Fork.h" +#include "Adafruit_InternalFlash.h" +#include "Adafruit_TinyUSB.h" + +// Start address and size should matches value in the CircuitPython (INTERNAL_FLASH_FILESYSTEM = 1) +// to make it easier to switch between Arduino and CircuitPython +#define INTERNAL_FLASH_FILESYSTEM_START_ADDR (0x00040000 - 256 - 0 - INTERNAL_FLASH_FILESYSTEM_SIZE) +#define INTERNAL_FLASH_FILESYSTEM_SIZE (64*1024) + +// Internal Flash object +Adafruit_InternalFlash flash(INTERNAL_FLASH_FILESYSTEM_START_ADDR, INTERNAL_FLASH_FILESYSTEM_SIZE); + +// file system object from SdFat +FatVolume fatfs; + +FatFile root; +FatFile file; + +// USB MSC object +Adafruit_USBD_MSC usb_msc; + +// Set to true when PC write to flash +bool fs_changed; + +// the setup function runs once when you press reset or power the board +void setup() { + Serial.begin(115200); + + // Initialize internal flash + flash.begin(); + + // Set disk vendor id, product id and revision with string up to 8, 16, 4 characters respectively + usb_msc.setID("Adafruit", "Internal Flash", "1.0"); + + // Set callback + usb_msc.setReadWriteCallback(msc_read_callback, msc_write_callback, msc_flush_callback); + usb_msc.setWritableCallback(msc_writable_callback); + usb_msc.setCapacity(flash.size()/512, 512); // Set disk size, block size should be 512 regardless of flash page size + usb_msc.setUnitReady(true); // Set Lun ready + usb_msc.begin(); + + // If already enumerated, additional class driverr begin() e.g msc, hid, midi won't take effect until re-enumeration + if (TinyUSBDevice.mounted()) { + TinyUSBDevice.detach(); + delay(10); + TinyUSBDevice.attach(); + } + + // Init file system on the flash + fatfs.begin(&flash); + + //while ( !Serial ) delay(10); // wait for native usb + + fs_changed = true; // to print contents initially +} + +void loop() { + if ( fs_changed ) { + fs_changed = false; + + if ( !root.open("/") ) { + Serial.println("open root failed"); + return; + } + + Serial.println("Flash contents:"); + + // Open next file in root. + // Warning, openNext starts at the current directory position + // so a rewind of the directory may be required. + while ( file.openNext(&root, O_RDONLY) ) { + file.printFileSize(&Serial); + Serial.write(' '); + file.printName(&Serial); + if ( file.isDir() ) { + // Indicate a directory. + Serial.write('/'); + } + Serial.println(); + file.close(); + } + + root.close(); + + Serial.println(); + delay(1000); // refresh every 1 second + } +} + +// Callback invoked when received READ10 command. +// Copy disk's data to buffer (up to bufsize) and +// return number of copied bytes (must be multiple of block size) +int32_t msc_read_callback (uint32_t lba, void* buffer, uint32_t bufsize) { + // Note: InternalFlash Block API: readBlocks/writeBlocks/syncBlocks + // already include sector caching (if needed). We don't need to cache it, yahhhh!! + return flash.readBlocks(lba, (uint8_t*) buffer, bufsize/512) ? bufsize : -1; +} + +// Callback invoked when received WRITE10 command. +// Process data in buffer to disk's storage and +// return number of written bytes (must be multiple of block size) +int32_t msc_write_callback (uint32_t lba, uint8_t* buffer, uint32_t bufsize) { + // Note: InternalFlash Block API: readBlocks/writeBlocks/syncBlocks + // already include sector caching (if needed). We don't need to cache it, yahhhh!! + return flash.writeBlocks(lba, buffer, bufsize/512) ? bufsize : -1; +} + +// Callback invoked when WRITE10 command is completed (status received and accepted by host). +// used to flush any pending cache. +void msc_flush_callback (void) { + // sync with flash + flash.syncBlocks(); + + // clear file system's cache to force refresh + fatfs.cacheClear(); + + fs_changed = true; +} + +// Invoked to check if device is writable as part of SCSI WRITE10 +// Default mode is writable +bool msc_writable_callback(void) { + // true for writable, false for read-only + return true; +} diff --git a/libraries/Adafruit_TinyUSB_Arduino/examples/MassStorage/msc_ramdisk/.skip.txt b/libraries/Adafruit_TinyUSB_Arduino/examples/MassStorage/msc_ramdisk/.skip.txt new file mode 100644 index 0000000..e4f9319 --- /dev/null +++ b/libraries/Adafruit_TinyUSB_Arduino/examples/MassStorage/msc_ramdisk/.skip.txt @@ -0,0 +1,2 @@ +feather_esp32_v2 +pico_rp2040_tinyusb_host diff --git a/libraries/Adafruit_TinyUSB_Arduino/examples/MassStorage/msc_ramdisk/msc_ramdisk.ino b/libraries/Adafruit_TinyUSB_Arduino/examples/MassStorage/msc_ramdisk/msc_ramdisk.ino new file mode 100644 index 0000000..95d3b33 --- /dev/null +++ b/libraries/Adafruit_TinyUSB_Arduino/examples/MassStorage/msc_ramdisk/msc_ramdisk.ino @@ -0,0 +1,131 @@ +/********************************************************************* + Adafruit invests time and resources providing this open source code, + please support Adafruit and open-source hardware by purchasing + products from Adafruit! + + MIT license, check LICENSE for more information + Copyright (c) 2019 Ha Thach for Adafruit Industries + All text above, and the splash screen below must be included in + any redistribution +*********************************************************************/ + +#include "Adafruit_TinyUSB.h" + +// 8KB is the smallest size that windows allow to mount +#define DISK_BLOCK_NUM 16 +#define DISK_BLOCK_SIZE 512 + +#include "ramdisk.h" + +Adafruit_USBD_MSC usb_msc; + +// Eject button to demonstrate medium is not ready e.g SDCard is not present +// whenever this button is pressed and hold, it will report to host as not ready +#if defined(ARDUINO_SAMD_CIRCUITPLAYGROUND_EXPRESS) || defined(ARDUINO_NRF52840_CIRCUITPLAY) + #define BTN_EJECT 4 // Left Button + bool activeState = true; + +#elif defined(ARDUINO_FUNHOUSE_ESP32S2) + #define BTN_EJECT BUTTON_DOWN + bool activeState = true; + +#elif defined PIN_BUTTON1 + #define BTN_EJECT PIN_BUTTON1 + bool activeState = false; +#endif + + +int32_t msc_read_callback(uint32_t lba, void* buffer, uint32_t bufsize); +int32_t msc_write_callback(uint32_t lba, uint8_t* buffer, uint32_t bufsize); +void msc_flush_callback(void); +bool msc_start_stop_callback(uint8_t power_condition, bool start, bool load_eject); +bool msc_ready_callback(void); + + +// the setup function runs once when you press reset or power the board +void setup() { + // Manual begin() is required on core without built-in support e.g. mbed rp2040 + if (!TinyUSBDevice.isInitialized()) { + TinyUSBDevice.begin(0); + } + + Serial.begin(115200); + +#ifdef BTN_EJECT + pinMode(BTN_EJECT, activeState ? INPUT_PULLDOWN : INPUT_PULLUP); +#endif + + // Set disk vendor id, product id and revision with string up to 8, 16, 4 characters respectively + usb_msc.setID("Adafruit", "Mass Storage", "1.0"); + + // Set disk size + usb_msc.setCapacity(DISK_BLOCK_NUM, DISK_BLOCK_SIZE); + + // Set callback + usb_msc.setReadWriteCallback(msc_read_callback, msc_write_callback, msc_flush_callback); + usb_msc.setStartStopCallback(msc_start_stop_callback); + usb_msc.setReadyCallback(msc_ready_callback); + + // Set Lun ready (RAM disk is always ready) + usb_msc.setUnitReady(true); + usb_msc.begin(); + + // If already enumerated, additional class driverr begin() e.g msc, hid, midi won't take effect until re-enumeration + if (TinyUSBDevice.mounted()) { + TinyUSBDevice.detach(); + delay(10); + TinyUSBDevice.attach(); + } + +// while ( !Serial ) delay(10); // wait for native usb + Serial.println("Adafruit TinyUSB Mass Storage RAM Disk example"); +} + +void loop() { + #ifdef TINYUSB_NEED_POLLING_TASK + // Manual call tud_task since it isn't called by Core's background + TinyUSBDevice.task(); + #endif +} + +// Callback invoked when received READ10 command. +// Copy disk's data to buffer (up to bufsize) and +// return number of copied bytes (must be multiple of block size) +int32_t msc_read_callback(uint32_t lba, void* buffer, uint32_t bufsize) { + uint8_t const* addr = msc_disk[lba]; + memcpy(buffer, addr, bufsize); + + return bufsize; +} + +// Callback invoked when received WRITE10 command. +// Process data in buffer to disk's storage and +// return number of written bytes (must be multiple of block size) +int32_t msc_write_callback(uint32_t lba, uint8_t* buffer, uint32_t bufsize) { + uint8_t* addr = msc_disk[lba]; + memcpy(addr, buffer, bufsize); + + return bufsize; +} + +// Callback invoked when WRITE10 command is completed (status received and accepted by host). +// used to flush any pending cache. +void msc_flush_callback(void) { + // nothing to do +} + +bool msc_start_stop_callback(uint8_t power_condition, bool start, bool load_eject) { + Serial.printf("Start/Stop callback: power condition %u, start %u, load_eject %u\n", power_condition, start, load_eject); + return true; +} + +// Invoked when received Test Unit Ready command. +// return true allowing host to read/write this LUN e.g SD card inserted +bool msc_ready_callback(void) { + #ifdef BTN_EJECT + // button not active --> medium ready + return digitalRead(BTN_EJECT) != activeState; + #else + return true; + #endif +} diff --git a/libraries/Adafruit_TinyUSB_Arduino/examples/MassStorage/msc_ramdisk/ramdisk.h b/libraries/Adafruit_TinyUSB_Arduino/examples/MassStorage/msc_ramdisk/ramdisk.h new file mode 100644 index 0000000..82b3df6 --- /dev/null +++ b/libraries/Adafruit_TinyUSB_Arduino/examples/MassStorage/msc_ramdisk/ramdisk.h @@ -0,0 +1,119 @@ +/* + * The MIT License (MIT) + * + * Copyright (c) 2019 Ha Thach for Adafruit Industries + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#ifndef RAMDISK_H_ +#define RAMDISK_H_ + +#define README_CONTENTS \ + "This is TinyUSB MassStorage device demo for Arduino on RAM disk." + +uint8_t msc_disk[DISK_BLOCK_NUM][DISK_BLOCK_SIZE] = { + //------------- Block0: Boot Sector -------------// + // byte_per_sector = DISK_BLOCK_SIZE; fat12_sector_num_16 = + // DISK_BLOCK_NUM; sector_per_cluster = 1; reserved_sectors = 1; fat_num = + // 1; fat12_root_entry_num = 16; sector_per_fat = 1; sector_per_track = + // 1; head_num = 1; hidden_sectors = 0; drive_number = 0x80; + // media_type = 0xf8; extended_boot_signature = 0x29; filesystem_type = + // "FAT12 "; volume_serial_number = 0x1234; volume_label = "TinyUSB MSC"; + // FAT magic code at offset 510-511 + {0xEB, 0x3C, 0x90, 0x4D, 0x53, 0x44, 0x4F, 0x53, 0x35, 0x2E, 0x30, 0x00, + 0x02, 0x01, 0x01, 0x00, 0x01, 0x10, 0x00, 0x10, 0x00, 0xF8, 0x01, 0x00, + 0x01, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x80, 0x00, 0x29, 0x34, 0x12, 0x00, 0x00, 'T', 'i', 'n', 'y', 'U', 'S', + 'B', ' ', 'M', 'S', 'C', 0x46, 0x41, 0x54, 0x31, 0x32, 0x20, 0x20, 0x20, + 0x00, 0x00, + + // Zero up to 2 last bytes of FAT magic code + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x55, 0xAA}, + + //------------- Block1: FAT12 Table -------------// + { + 0xF8, 0xFF, 0x00, // The first 2 12bit entries of the FAT linked list are reserved. Therefore the first 3 bytes are reserved. + // The 2 12 bit values must be 0xF8 and 0xFF + +// [Entry2 | Entry3] + 0xFF, 0x0F, 0x00 // <<< These 3 bytes represents the 12 bit entries 2 and 3 of the FAT linked list. + // The README.TXT file first block is 2 (see line 112), therefore to find the next block, we go to this section Entry[2] + // to read the address of the next block. Since the size of the README.TXT is 65 bytes and less then one sector (512) byte. + // There is no next block to read therefore the Entry[2] contains the value 0xFFF, which is the EOF. + }, + + //------------- Block2: Root Directory -------------// + { + // first entry is volume label + 'T', 'i', 'n', 'y', 'U', 'S', 'B', ' ', 'M', 'S', 'C', 0x08, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x4F, 0x6D, 0x65, 0x43, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + // second entry is readme file + 'R', 'E', 'A', 'D', 'M', 'E', ' ', ' ', 'T', 'X', 'T', 0x20, 0x00, 0xC6, + 0x52, 0x6D, 0x65, 0x43, 0x65, 0x43, 0x00, 0x00, 0x88, 0x6D, 0x65, 0x43, + 0x02, 0x00, sizeof(README_CONTENTS) - 1, 0x00, 0x00, + 0x00 // readme's files size (4 Bytes) + }, + + //------------- Block3: Readme Content -------------// + README_CONTENTS}; + +#endif /* RAMDISK_H_ */ diff --git a/libraries/Adafruit_TinyUSB_Arduino/examples/MassStorage/msc_ramdisk_dual/.skip.txt b/libraries/Adafruit_TinyUSB_Arduino/examples/MassStorage/msc_ramdisk_dual/.skip.txt new file mode 100644 index 0000000..ae593a8 --- /dev/null +++ b/libraries/Adafruit_TinyUSB_Arduino/examples/MassStorage/msc_ramdisk_dual/.skip.txt @@ -0,0 +1,3 @@ +feather_esp32_v2 +pico_rp2040_tinyusb_host +CH32V20x_EVT diff --git a/libraries/Adafruit_TinyUSB_Arduino/examples/MassStorage/msc_ramdisk_dual/msc_ramdisk_dual.ino b/libraries/Adafruit_TinyUSB_Arduino/examples/MassStorage/msc_ramdisk_dual/msc_ramdisk_dual.ino new file mode 100644 index 0000000..c9014e7 --- /dev/null +++ b/libraries/Adafruit_TinyUSB_Arduino/examples/MassStorage/msc_ramdisk_dual/msc_ramdisk_dual.ino @@ -0,0 +1,125 @@ +/********************************************************************* + Adafruit invests time and resources providing this open source code, + please support Adafruit and open-source hardware by purchasing + products from Adafruit! + + MIT license, check LICENSE for more information + Copyright (c) 2019 Ha Thach for Adafruit Industries + All text above, and the splash screen below must be included in + any redistribution +*********************************************************************/ + +#include "Adafruit_TinyUSB.h" + +// 8KB is the smallest size that windows allow to mount +#define DISK_BLOCK_NUM 16 +#define DISK_BLOCK_SIZE 512 + +#include "ramdisk.h" + +Adafruit_USBD_MSC usb_msc; + +// the setup function runs once when you press reset or power the board +void setup() { + // Manual begin() is required on core without built-in support e.g. mbed rp2040 + if (!TinyUSBDevice.isInitialized()) { + TinyUSBDevice.begin(0); + } + + Serial.begin(115200); + + usb_msc.setMaxLun(2); + + // Set disk size and callback for Logical Unit 0 (LUN 0) + usb_msc.setID(0, "Adafruit", "Lun0", "1.0"); + usb_msc.setCapacity(0, DISK_BLOCK_NUM, DISK_BLOCK_SIZE); + usb_msc.setReadWriteCallback(0, ram0_read_cb, ram0_write_cb, ram0_flush_cb); + usb_msc.setUnitReady(0, true); + + // Set disk size and callback for Logical Unit 1 (LUN 1) + usb_msc.setID(1, "Adafruit", "Lun1", "1.0"); + usb_msc.setCapacity(1, DISK_BLOCK_NUM, DISK_BLOCK_SIZE); + usb_msc.setReadWriteCallback(1, ram1_read_cb, ram1_write_cb, ram1_flush_cb); + usb_msc.setUnitReady(1, true); + + usb_msc.begin(); + + // If already enumerated, additional class driverr begin() e.g msc, hid, midi won't take effect until re-enumeration + if (TinyUSBDevice.mounted()) { + TinyUSBDevice.detach(); + delay(10); + TinyUSBDevice.attach(); + } + + //while ( !Serial ) delay(10); // wait for native usb + Serial.println("Adafruit TinyUSB Mass Storage Dual RAM Disks example"); +} + +void loop() { + #ifdef TINYUSB_NEED_POLLING_TASK + // Manual call tud_task since it isn't called by Core's background + TinyUSBDevice.task(); + #endif +} + + +//--------------------------------------------------------------------+ +// LUN 0 callback +//--------------------------------------------------------------------+ + +// Callback invoked when received READ10 command. +// Copy disk's data to buffer (up to bufsize) and +// return number of copied bytes (must be multiple of block size) +int32_t ram0_read_cb(uint32_t lba, void* buffer, uint32_t bufsize) { + uint8_t const* addr = msc_disk0[lba]; + memcpy(buffer, addr, bufsize); + + return bufsize; +} + +// Callback invoked when received WRITE10 command. +// Process data in buffer to disk's storage and +// return number of written bytes (must be multiple of block size) +int32_t ram0_write_cb(uint32_t lba, uint8_t* buffer, uint32_t bufsize) { + uint8_t* addr = msc_disk0[lba]; + memcpy(addr, buffer, bufsize); + + return bufsize; +} + +// Callback invoked when WRITE10 command is completed (status received and accepted by host). +// used to flush any pending cache. +void ram0_flush_cb(void) { + // nothing to do +} + + +//--------------------------------------------------------------------+ +// LUN 1 callback +//--------------------------------------------------------------------+ + +// Callback invoked when received READ10 command. +// Copy disk's data to buffer (up to bufsize) and +// return number of copied bytes (must be multiple of block size) +int32_t ram1_read_cb(uint32_t lba, void* buffer, uint32_t bufsize) { + uint8_t const* addr = msc_disk1[lba]; + memcpy(buffer, addr, bufsize); + + return bufsize; +} + +// Callback invoked when received WRITE10 command. +// Process data in buffer to disk's storage and +// return number of written bytes (must be multiple of block size) +int32_t ram1_write_cb(uint32_t lba, uint8_t* buffer, uint32_t bufsize) { + uint8_t* addr = msc_disk1[lba]; + memcpy(addr, buffer, bufsize); + + return bufsize; +} + +// Callback invoked when WRITE10 command is completed (status received and accepted by host). +// used to flush any pending cache. +void ram1_flush_cb(void) { + // nothing to do +} diff --git a/libraries/Adafruit_TinyUSB_Arduino/examples/MassStorage/msc_ramdisk_dual/ramdisk.h b/libraries/Adafruit_TinyUSB_Arduino/examples/MassStorage/msc_ramdisk_dual/ramdisk.h new file mode 100644 index 0000000..6765fe8 --- /dev/null +++ b/libraries/Adafruit_TinyUSB_Arduino/examples/MassStorage/msc_ramdisk_dual/ramdisk.h @@ -0,0 +1,204 @@ +/* + * The MIT License (MIT) + * + * Copyright (c) 2019 Ha Thach for Adafruit Industries + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#ifndef RAMDISK_H_ +#define RAMDISK_H_ + +//--------------------------------------------------------------------+ +// LUN 0 +//--------------------------------------------------------------------+ +#define README0_CONTENTS \ + "LUN0: This is TinyUSB MassStorage device demo for Arduino on RAM disk." + +uint8_t msc_disk0[DISK_BLOCK_NUM][DISK_BLOCK_SIZE] = { + //------------- Block0: Boot Sector -------------// + // byte_per_sector = DISK_BLOCK_SIZE; fat12_sector_num_16 = + // DISK_BLOCK_NUM; sector_per_cluster = 1; reserved_sectors = 1; fat_num = + // 1; fat12_root_entry_num = 16; sector_per_fat = 1; sector_per_track = + // 1; head_num = 1; hidden_sectors = 0; drive_number = 0x80; + // media_type = 0xf8; extended_boot_signature = 0x29; filesystem_type = + // "FAT12 "; volume_serial_number = 0x1234; volume_label = "TinyUSB 0 "; + // FAT magic code at offset 510-511 + {0xEB, 0x3C, 0x90, 0x4D, 0x53, 0x44, 0x4F, 0x53, 0x35, 0x2E, 0x30, 0x00, + 0x02, 0x01, 0x01, 0x00, 0x01, 0x10, 0x00, 0x10, 0x00, 0xF8, 0x01, 0x00, + 0x01, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x80, 0x00, 0x29, 0x34, 0x12, 0x00, 0x00, 'T', 'i', 'n', 'y', 'U', 'S', + 'B', ' ', '0', ' ', ' ', 0x46, 0x41, 0x54, 0x31, 0x32, 0x20, 0x20, 0x20, + 0x00, 0x00, + + // Zero up to 2 last bytes of FAT magic code + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x55, 0xAA}, + + //------------- Block1: FAT12 Table -------------// + { + 0xF8, 0xFF, 0xFF, 0xFF, 0x0F // // first 2 entries must be F8FF, third + // entry is cluster end of readme file + }, + + //------------- Block2: Root Directory -------------// + { + // first entry is volume label + 'T', 'i', 'n', 'y', 'U', 'S', 'B', ' ', '0', ' ', ' ', 0x08, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x4F, 0x6D, 0x65, 0x43, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + // second entry is readme file + 'R', 'E', 'A', 'D', 'M', 'E', '0', ' ', 'T', 'X', 'T', 0x20, 0x00, 0xC6, + 0x52, 0x6D, 0x65, 0x43, 0x65, 0x43, 0x00, 0x00, 0x88, 0x6D, 0x65, 0x43, + 0x02, 0x00, sizeof(README0_CONTENTS) - 1, 0x00, 0x00, + 0x00 // readme's files size (4 Bytes) + }, + + //------------- Block3: Readme Content -------------// + README0_CONTENTS}; + +//--------------------------------------------------------------------+ +// LUN 1 +//--------------------------------------------------------------------+ +#define README1_CONTENTS \ + "LUN1: This is TinyUSB MassStorage device demo for Arduino on RAM disk." + +uint8_t msc_disk1[DISK_BLOCK_NUM][DISK_BLOCK_SIZE] = { + //------------- Block0: Boot Sector -------------// + // byte_per_sector = DISK_BLOCK_SIZE; fat12_sector_num_16 = + // DISK_BLOCK_NUM; sector_per_cluster = 1; reserved_sectors = 1; fat_num = + // 1; fat12_root_entry_num = 16; sector_per_fat = 1; sector_per_track = + // 1; head_num = 1; hidden_sectors = 0; drive_number = 0x80; + // media_type = 0xf8; extended_boot_signature = 0x29; filesystem_type = + // "FAT12 "; volume_serial_number = 0x5678; volume_label = "TinyUSB 1 "; + // FAT magic code at offset 510-511 + {0xEB, 0x3C, 0x90, 0x4D, 0x53, 0x44, 0x4F, 0x53, 0x35, 0x2E, 0x30, 0x00, + 0x02, 0x01, 0x01, 0x00, 0x01, 0x10, 0x00, 0x10, 0x00, 0xF8, 0x01, 0x00, + 0x01, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x80, 0x00, 0x29, 0x78, 0x56, 0x00, 0x00, 'T', 'i', 'n', 'y', 'U', 'S', + 'B', ' ', '1', ' ', ' ', 0x46, 0x41, 0x54, 0x31, 0x32, 0x20, 0x20, 0x20, + 0x00, 0x00, + + // Zero up to 2 last bytes of FAT magic code + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x55, 0xAA}, + + //------------- Block1: FAT12 Table -------------// + { + 0xF8, 0xFF, 0xFF, 0xFF, 0x0F // // first 2 entries must be F8FF, third + // entry is cluster end of readme file + }, + + //------------- Block2: Root Directory -------------// + { + // first entry is volume label + 'T', 'i', 'n', 'y', 'U', 'S', 'B', ' ', '1', ' ', ' ', 0x08, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x4F, 0x6D, 0x65, 0x43, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + // second entry is readme file + 'R', 'E', 'A', 'D', 'M', 'E', '1', ' ', 'T', 'X', 'T', 0x20, 0x00, 0xC6, + 0x52, 0x6D, 0x65, 0x43, 0x65, 0x43, 0x00, 0x00, 0x88, 0x6D, 0x65, 0x43, + 0x02, 0x00, sizeof(README1_CONTENTS) - 1, 0x00, 0x00, + 0x00 // readme's files size (4 Bytes) + }, + + //------------- Block3: Readme Content -------------// + README1_CONTENTS}; + +#endif /* RAMDISK_H_ */ diff --git a/libraries/Adafruit_TinyUSB_Arduino/examples/MassStorage/msc_sd/.skip.txt b/libraries/Adafruit_TinyUSB_Arduino/examples/MassStorage/msc_sd/.skip.txt new file mode 100644 index 0000000..67031a9 --- /dev/null +++ b/libraries/Adafruit_TinyUSB_Arduino/examples/MassStorage/msc_sd/.skip.txt @@ -0,0 +1,10 @@ +feather_esp32_v2 +feather_esp32s2 +feather_esp32s3 +funhouse +magtag +metroesp32s2 +esp32p4 +feather_rp2040_tinyusb +pico_rp2040_tinyusb_host +CH32V20x_EVT diff --git a/libraries/Adafruit_TinyUSB_Arduino/examples/MassStorage/msc_sd/msc_sd.ino b/libraries/Adafruit_TinyUSB_Arduino/examples/MassStorage/msc_sd/msc_sd.ino new file mode 100644 index 0000000..4747d2b --- /dev/null +++ b/libraries/Adafruit_TinyUSB_Arduino/examples/MassStorage/msc_sd/msc_sd.ino @@ -0,0 +1,103 @@ +/********************************************************************* + Adafruit invests time and resources providing this open source code, + please support Adafruit and open-source hardware by purchasing + products from Adafruit! + + MIT license, check LICENSE for more information + Copyright (c) 2019 Ha Thach for Adafruit Industries + All text above, and the splash screen below must be included in + any redistribution +*********************************************************************/ + +/* This example expose SD card as mass storage using + * default SD Library + */ + +#include "SD.h" +#include "Adafruit_TinyUSB.h" + +const int chipSelect = 10; + +Adafruit_USBD_MSC usb_msc; + +Sd2Card card; +SdVolume volume; + +// the setup function runs once when you press reset or power the board +void setup() +{ + Serial.begin(115200); + + // Set disk vendor id, product id and revision with string up to 8, 16, 4 characters respectively + usb_msc.setID("Adafruit", "SD Card", "1.0"); + + // Set read write callback + usb_msc.setReadWriteCallback(msc_read_cb, msc_write_cb, msc_flush_cb); + + // Still initialize MSC but tell usb stack that MSC is not ready to read/write + // If we don't initialize, board will be enumerated as CDC only + usb_msc.setUnitReady(false); + usb_msc.begin(); + + // If already enumerated, additional class driverr begin() e.g msc, hid, midi won't take effect until re-enumeration + if (TinyUSBDevice.mounted()) { + TinyUSBDevice.detach(); + delay(10); + TinyUSBDevice.attach(); + } + + //while ( !Serial ) delay(10); // wait for native usb + Serial.println("Adafruit TinyUSB Mass Storage SD Card example"); + Serial.println("\nInitializing SD card..."); + + if ( !card.init(SPI_HALF_SPEED, chipSelect) ) { + Serial.println("initialization failed. Things to check:"); + Serial.println("* is a card inserted?"); + Serial.println("* is your wiring correct?"); + Serial.println("* did you change the chipSelect pin to match your shield or module?"); + while (1) delay(1); + } + + // Now we will try to open the 'volume'/'partition' - it should be FAT16 or FAT32 + if (!volume.init(card)) { + Serial.println("Could not find FAT16/FAT32 partition.\nMake sure you've formatted the card"); + while (1) delay(1); + } + + uint32_t block_count = volume.blocksPerCluster()*volume.clusterCount(); + + Serial.print("Volume size (MB): "); + Serial.println((block_count/2) / 1024); + + // Set disk size, SD block size is always 512 + usb_msc.setCapacity(block_count, 512); + + // MSC is ready for read/write + usb_msc.setUnitReady(true); +} + +void loop() { + // nothing to do +} + +// Callback invoked when received READ10 command. +// Copy disk's data to buffer (up to bufsize) and +// return number of copied bytes (must be multiple of block size) +int32_t msc_read_cb (uint32_t lba, void* buffer, uint32_t bufsize) { + (void) bufsize; + return card.readBlock(lba, (uint8_t*) buffer) ? 512 : -1; +} + +// Callback invoked when received WRITE10 command. +// Process data in buffer to disk's storage and +// return number of written bytes (must be multiple of block size) +int32_t msc_write_cb (uint32_t lba, uint8_t* buffer, uint32_t bufsize) { + (void) bufsize; + return card.writeBlock(lba, buffer) ? 512 : -1; +} + +// Callback invoked when WRITE10 command is completed (status received and accepted by host). +// used to flush any pending cache. +void msc_flush_cb (void) { + // nothing to do +} diff --git a/libraries/Adafruit_TinyUSB_Arduino/examples/MassStorage/msc_sdfat/.skip.txt b/libraries/Adafruit_TinyUSB_Arduino/examples/MassStorage/msc_sdfat/.skip.txt new file mode 100644 index 0000000..e4f9319 --- /dev/null +++ b/libraries/Adafruit_TinyUSB_Arduino/examples/MassStorage/msc_sdfat/.skip.txt @@ -0,0 +1,2 @@ +feather_esp32_v2 +pico_rp2040_tinyusb_host diff --git a/libraries/Adafruit_TinyUSB_Arduino/examples/MassStorage/msc_sdfat/msc_sdfat.ino b/libraries/Adafruit_TinyUSB_Arduino/examples/MassStorage/msc_sdfat/msc_sdfat.ino new file mode 100644 index 0000000..1dd1c54 --- /dev/null +++ b/libraries/Adafruit_TinyUSB_Arduino/examples/MassStorage/msc_sdfat/msc_sdfat.ino @@ -0,0 +1,151 @@ +/********************************************************************* + Adafruit invests time and resources providing this open source code, + please support Adafruit and open-source hardware by purchasing + products from Adafruit! + + MIT license, check LICENSE for more information + Copyright (c) 2019 Ha Thach for Adafruit Industries + All text above, and the splash screen below must be included in + any redistribution +*********************************************************************/ + +/* This example expose SD card as mass storage using + * - SdFat https://github.com/adafruit/SdFat + */ + +#include "SPI.h" +#include "SdFat_Adafruit_Fork.h" +#include "Adafruit_TinyUSB.h" + +//--------------------------------------------------------------------+ +// SDCard Config +//--------------------------------------------------------------------+ +#if defined(ARDUINO_PYPORTAL_M4) || defined(ARDUINO_PYPORTAL_M4_TITANO) + // PyPortal has on-board card reader + #define SDCARD_CS 32 + #define SDCARD_DETECT 33 + #define SDCARD_DETECT_ACTIVE HIGH + +#elif defined(ARDUINO_ADAFRUIT_METRO_RP2040) + #define SDIO_CLK_PIN 18 + #define SDIO_CMD_PIN 19 // MOSI + #define SDIO_DAT0_PIN 20 // DAT1: 21, DAT2: 22, DAT3: 23 + + #define SDCARD_DETECT 15 + #define SDCARD_DETECT_ACTIVE LOW + +#elif defined(ARDUINO_ADAFRUIT_METRO_RP2350) + #define SDIO_CLK_PIN 34 + #define SDIO_CMD_PIN 35 // MOSI + #define SDIO_DAT0_PIN 36 // DAT1: 37, DAT2: 38, DAT3: 39 + + #define SDCARD_DETECT 40 + #define SDCARD_DETECT_ACTIVE LOW + +#elif defined(ARDUINO_ADAFRUIT_FRUITJAM_RP2350) + #define SDIO_CLK_PIN 34 + #define SDIO_CMD_PIN 35 // MOSI + #define SDIO_DAT0_PIN 36 // DAT1: 37, DAT2: 38, DAT3: 39 + + #define SDCARD_DETECT 33 + #define SDCARD_DETECT_ACTIVE LOW + +#else + // Use SPI, no detect + #define SDCARD_CS 10 +#endif + +#if defined(SDIO_CLK_PIN) && defined(SDIO_CMD_PIN) && defined(SDIO_DAT0_PIN) + #define SD_CONFIG SdioConfig(SDIO_CLK_PIN, SDIO_CMD_PIN, SDIO_DAT0_PIN) +#else + #define SD_CONFIG SdSpiConfig(SDCARD_CS, SHARED_SPI, SD_SCK_MHZ(50)) +#endif + +// File system on SD Card +SdFat sd; + +// USB Mass Storage object +Adafruit_USBD_MSC usb_msc; + +// the setup function runs once when you press reset or power the board +void setup() { +#ifdef LED_BUILTIN + pinMode(LED_BUILTIN, OUTPUT); +#endif + Serial.begin(115200); + + // Set disk vendor id, product id and revision with string up to 8, 16, 4 characters respectively + usb_msc.setID("Adafruit", "SD Card", "1.0"); + + // Set read write callback + usb_msc.setReadWriteCallback(msc_read_cb, msc_write_cb, msc_flush_cb); + + // Still initialize MSC but tell usb stack that MSC is not ready to read/write + // If we don't initialize, board will be enumerated as CDC only + usb_msc.setUnitReady(false); + usb_msc.begin(); + + // If already enumerated, additional class driverr begin() e.g msc, hid, midi won't take effect until re-enumeration + if (TinyUSBDevice.mounted()) { + TinyUSBDevice.detach(); + delay(10); + TinyUSBDevice.attach(); + } + + //while ( !Serial ) delay(10); // wait for native usb + Serial.println("Adafruit TinyUSB Mass Storage SD Card example"); + Serial.print("\nInitializing SD card ... "); + + if (!sd.begin(SD_CONFIG)) { + Serial.println("initialization failed. Things to check:"); + Serial.println("- is a card inserted?"); + Serial.println("- is your wiring correct?"); + Serial.println("- did you change the SDCARD_CS or SDIO pin to match your shield or module?"); + while (1) delay(1); + } + + // Size in blocks (512 bytes) + uint32_t block_count = sd.card()->sectorCount(); + Serial.print("Volume size (MB): "); + Serial.println((block_count/2) / 1024); + + // Set disk size, SD block size is always 512 + usb_msc.setCapacity(block_count, 512); + + // MSC is ready for read/write + usb_msc.setUnitReady(true); +} + +void loop() { + // noting to do +} + +// Callback invoked when received READ10 command. +// Copy disk's data to buffer (up to bufsize) and +// return number of copied bytes (must be multiple of block size) +int32_t msc_read_cb (uint32_t lba, void* buffer, uint32_t bufsize) { + bool rc = sd.card()->readSectors(lba, (uint8_t*) buffer, bufsize/512); + return rc ? bufsize : -1; +} + +// Callback invoked when received WRITE10 command. +// Process data in buffer to disk's storage and +// return number of written bytes (must be multiple of block size) +int32_t msc_write_cb (uint32_t lba, uint8_t* buffer, uint32_t bufsize) { +#ifdef LED_BUILTIN + digitalWrite(LED_BUILTIN, HIGH); +#endif + bool rc = sd.card()->writeSectors(lba, buffer, bufsize/512); + return rc ? bufsize : -1; +} + +// Callback invoked when WRITE10 command is completed (status received and accepted by host). +// used to flush any pending cache. +void msc_flush_cb (void) { + sd.card()->syncDevice(); + sd.cacheClear(); // clear file system's cache to force refresh + +#ifdef LED_BUILTIN + digitalWrite(LED_BUILTIN, LOW); +#endif +} diff --git a/libraries/Adafruit_TinyUSB_Arduino/examples/Vendor/i2c_tiny_usb_adapter/.skip.txt b/libraries/Adafruit_TinyUSB_Arduino/examples/Vendor/i2c_tiny_usb_adapter/.skip.txt new file mode 100644 index 0000000..ae593a8 --- /dev/null +++ b/libraries/Adafruit_TinyUSB_Arduino/examples/Vendor/i2c_tiny_usb_adapter/.skip.txt @@ -0,0 +1,3 @@ +feather_esp32_v2 +pico_rp2040_tinyusb_host +CH32V20x_EVT diff --git a/libraries/Adafruit_TinyUSB_Arduino/examples/Vendor/i2c_tiny_usb_adapter/Adafruit_USBD_I2C.cpp b/libraries/Adafruit_TinyUSB_Arduino/examples/Vendor/i2c_tiny_usb_adapter/Adafruit_USBD_I2C.cpp new file mode 100644 index 0000000..750a329 --- /dev/null +++ b/libraries/Adafruit_TinyUSB_Arduino/examples/Vendor/i2c_tiny_usb_adapter/Adafruit_USBD_I2C.cpp @@ -0,0 +1,225 @@ +/* + * The MIT License (MIT) + * + * Copyright (c) 2023 Ha Thach (tinyusb.org) for Adafruit Industries + * + * 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. + */ + +/* + * Reference: + * - https://github.com/torvalds/linux/blob/master/drivers/i2c/busses/i2c-tiny-usb.c + * - https://github.com/harbaum/I2C-Tiny-USB + * + * Requirement: + * - Install i2c-tools with + * sudo apt install i2c-tools + * + * How to test example: + * - Compile and flash this sketch on your board with an i2c device, it should enumerated as + * ID 1c40:0534 EZPrototypes i2c-tiny-usb interface + * + * - Run "i2cdetect -l" to find our bus ID e.g + * i2c-8 i2c i2c-tiny-usb at bus 003 device 039 I2C adapter + * + * - Run "i2cdetect -y 8" to scan for on-board device (8 is the above bus ID) + * 0 1 2 3 4 5 6 7 8 9 a b c d e f + 00: -- -- -- -- -- -- -- -- + 10: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- + 20: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- + 30: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- + 40: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- + 50: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- + 60: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- + 70: -- -- -- -- -- -- -- 77 + + - You can then interact with sensor using following commands: + i2cget i2cset i2cdump i2ctransfer or using any driver/tools that work on i2c device. + */ + +#include "Adafruit_USBD_I2C.h" + +Adafruit_USBD_I2C::Adafruit_USBD_I2C(TwoWire* wire) { + _wire = wire; + _buf = NULL; + _bufsize = 0; // not used to verify length yet + _state = I2C_STATUS_IDLE; + _functionality = 0x8eff0001; // check out _functionality_* defines + setStringDescriptor("I2C Interface"); +} + +uint16_t Adafruit_USBD_I2C::getInterfaceDescriptor(uint8_t itfnum_deprecated, uint8_t* buf, uint16_t bufsize) { + uint8_t itfnum = 0; + uint8_t ep_in = 0; + uint8_t ep_out = 0; + (void) itfnum_deprecated; + + // null buffer is used to get the length of descriptor only + if (buf) { + itfnum = TinyUSBDevice.allocInterface(1); + ep_in = TinyUSBDevice.allocEndpoint(TUSB_DIR_IN); + ep_out = TinyUSBDevice.allocEndpoint(TUSB_DIR_OUT); + } + + uint8_t const desc[] = { TUD_VENDOR_DESCRIPTOR(itfnum, _strid, ep_out, ep_in, 64) }; + uint16_t const len = sizeof(desc); + + if (buf) { + if (bufsize < len) { + return 0; + } + memcpy(buf, desc, len); + } + + return len; +} + +bool Adafruit_USBD_I2C::begin(uint8_t* buffer, size_t bufsize) { + _buf = buffer; + _bufsize = (uint16_t) bufsize; + + if (!_wire || !_buf || !_bufsize) return false; + + // needed to identify as a device for i2c_tiny_usb (EZPrototypes VID/PID) + TinyUSBDevice.setID(0x1c40, 0x0534); + if (!TinyUSBDevice.addInterface(*this)) return false; + + _wire->begin(); + return true; +} + +uint16_t Adafruit_USBD_I2C::i2c_read(uint8_t addr, uint8_t* buf, uint16_t len, bool stop_bit) +{ + uint16_t const rd_count = (uint16_t) _wire->requestFrom(addr, len, stop_bit); + + _state = (len && !rd_count) ? I2C_STATUS_NAK : I2C_STATUS_ACK; + + // Serial.printf("I2C Read: addr = 0x%02X, len = %u, rd_count %u bytes, status = %u\r\n", addr, len, rd_count, i2c_state); + + for(uint16_t i = 0; i read(); + } + + return rd_count; +} + +uint16_t Adafruit_USBD_I2C::i2c_write(uint8_t addr, uint8_t const* buf, uint16_t len, bool stop_bit) +{ + _wire->beginTransmission(addr); + uint16_t wr_count = (uint16_t) _wire->write(buf, len); + uint8_t const sts = _wire->endTransmission(stop_bit); + + _state = (sts == 0) ? I2C_STATUS_ACK : I2C_STATUS_NAK; + + // Serial.printf("I2C Write: addr = 0x%02X, len = %u, wr_count = %u, status = %u\r\n", addr, len, wr_count, i2c_state); + + return wr_count; +} + +bool Adafruit_USBD_I2C::handleControlTransfer(uint8_t rhport, uint8_t stage, tusb_control_request_t const* request) { + uint8_t const cmd = request->bRequest; + + if ( stage == CONTROL_STAGE_SETUP ) + { + switch ( cmd ) + { + case CMD_ECHO: + // echo + return tud_control_xfer(rhport, request, (void*) &request->wValue, sizeof(request->wValue)); + + case CMD_GET_FUNC: + // capabilities + return tud_control_xfer(rhport, request, (void*) &_functionality, sizeof(_functionality)); + + case CMD_SET_DELAY: + if ( request->wValue == 0 ) + { + _wire->setClock(115200); + } + else + { + int baudrate = 1000000 / request->wValue; + if ( baudrate > 400000 ) baudrate = 400000; + _wire->setClock(baudrate); + } + return tud_control_status(rhport, request); + + case CMD_GET_STATUS: + return tud_control_xfer(rhport, request, (void*) &_state, sizeof(_state)); + + case CMD_I2C_IO: + case CMD_I2C_IO | CMD_I2C_IO_BEGIN: + case CMD_I2C_IO | CMD_I2C_IO_END: + case CMD_I2C_IO | CMD_I2C_IO_BEGIN | CMD_I2C_IO_END: + { + uint8_t const addr = (uint8_t) request->wIndex; + // uint16_t const flags = request->wValue; + uint16_t const len = tu_min16(request->wLength, _bufsize); + bool const stop_bit = (cmd & CMD_I2C_IO_END) ? true : false; + + if (request->bmRequestType_bit.direction == TUSB_DIR_OUT) + { + if (len == 0) + { + // zero write: do it here since there will be no data stage for len = 0 + i2c_write(addr, _buf, len, stop_bit); + } + return tud_control_xfer(rhport, request, _buf, len); + }else + { + uint16_t const rd_count = i2c_read(addr, _buf, len, stop_bit); + return tud_control_xfer(rhport, request, rd_count ? _buf : NULL, rd_count); + } + } + break; + + default: return true; + } + } + else if ( stage == CONTROL_STAGE_DATA ) + { + switch ( cmd ) + { + case CMD_I2C_IO: + case CMD_I2C_IO | CMD_I2C_IO_BEGIN: + case CMD_I2C_IO | CMD_I2C_IO_END: + case CMD_I2C_IO | CMD_I2C_IO_BEGIN | CMD_I2C_IO_END: + if (request->bmRequestType_bit.direction == TUSB_DIR_OUT) + { + uint8_t const addr = (uint8_t) request->wIndex; + // uint16_t const flags = request->wValue; + uint16_t const len = tu_min16(request->wLength, _bufsize); + bool const stop_bit = (cmd & CMD_I2C_IO_END) ? true : false; + + i2c_write(addr, _buf, len, stop_bit); + } + return true; + + default: return true; + } + } + else + { + // CONTROL_STAGE_STATUS + return true; + } +} + + diff --git a/libraries/Adafruit_TinyUSB_Arduino/examples/Vendor/i2c_tiny_usb_adapter/Adafruit_USBD_I2C.h b/libraries/Adafruit_TinyUSB_Arduino/examples/Vendor/i2c_tiny_usb_adapter/Adafruit_USBD_I2C.h new file mode 100644 index 0000000..c616089 --- /dev/null +++ b/libraries/Adafruit_TinyUSB_Arduino/examples/Vendor/i2c_tiny_usb_adapter/Adafruit_USBD_I2C.h @@ -0,0 +1,109 @@ +/* + * The MIT License (MIT) + * + * Copyright (c) 2023 Ha Thach (tinyusb.org) for Adafruit Industries + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#ifndef ADAFRUIT_USBD_I2C_H_ +#define ADAFRUIT_USBD_I2C_H_ + +#include "Adafruit_TinyUSB.h" +#include "Wire.h" + +/* commands from USB, must e.g. match command ids in kernel driver */ +#define CMD_ECHO 0 +#define CMD_GET_FUNC 1 +#define CMD_SET_DELAY 2 +#define CMD_GET_STATUS 3 + +#define CMD_I2C_IO 4 +#define CMD_I2C_IO_BEGIN (1<<0) // flag for I2C_IO +#define CMD_I2C_IO_END (1<<1) // flag for I2C_IO + +/* linux kernel flags */ +#define I2C_M_TEN 0x10 /* we have a ten bit chip address */ +#define I2C_M_RD 0x01 +#define I2C_M_NOSTART 0x4000 +#define I2C_M_REV_DIR_ADDR 0x2000 +#define I2C_M_IGNORE_NAK 0x1000 +#define I2C_M_NO_RD_ACK 0x0800 + +/* To determine what functionality is present */ +#define I2C_FUNC_I2C 0x00000001 +#define I2C_FUNC_10BIT_ADDR 0x00000002 /* required for I2C_M_TEN */ +#define I2C_FUNC_PROTOCOL_MANGLING 0x00000004 /* required for I2C_M_IGNORE_NAK etc. */ +#define I2C_FUNC_SMBUS_PEC 0x00000008 +#define I2C_FUNC_NOSTART 0x00000010 /* required for I2C_M_NOSTART */ +#define I2C_FUNC_SLAVE 0x00000020 +#define I2C_FUNC_SMBUS_BLOCK_PROC_CALL 0x00008000 /* SMBus 2.0 or later */ +#define I2C_FUNC_SMBUS_QUICK 0x00010000 +#define I2C_FUNC_SMBUS_READ_BYTE 0x00020000 +#define I2C_FUNC_SMBUS_WRITE_BYTE 0x00040000 +#define I2C_FUNC_SMBUS_READ_BYTE_DATA 0x00080000 +#define I2C_FUNC_SMBUS_WRITE_BYTE_DATA 0x00100000 +#define I2C_FUNC_SMBUS_READ_WORD_DATA 0x00200000 +#define I2C_FUNC_SMBUS_WRITE_WORD_DATA 0x00400000 +#define I2C_FUNC_SMBUS_PROC_CALL 0x00800000 +#define I2C_FUNC_SMBUS_READ_BLOCK_DATA 0x01000000 /* required for I2C_M_RECV_LEN */ +#define I2C_FUNC_SMBUS_WRITE_BLOCK_DATA 0x02000000 +#define I2C_FUNC_SMBUS_READ_I2C_BLOCK 0x04000000 /* I2C-like block xfer */ +#define I2C_FUNC_SMBUS_WRITE_I2C_BLOCK 0x08000000 /* w/ 1-byte reg. addr. */ +#define I2C_FUNC_SMBUS_HOST_NOTIFY 0x10000000 /* SMBus 2.0 or later */ + +#define I2C_FUNC_SMBUS_BYTE (I2C_FUNC_SMBUS_READ_BYTE | I2C_FUNC_SMBUS_WRITE_BYTE) +#define I2C_FUNC_SMBUS_BYTE_DATA (I2C_FUNC_SMBUS_READ_BYTE_DATA | I2C_FUNC_SMBUS_WRITE_BYTE_DATA) +#define I2C_FUNC_SMBUS_WORD_DATA (I2C_FUNC_SMBUS_READ_WORD_DATA | I2C_FUNC_SMBUS_WRITE_WORD_DATA) +#define I2C_FUNC_SMBUS_BLOCK_DATA (I2C_FUNC_SMBUS_READ_BLOCK_DATA | I2C_FUNC_SMBUS_WRITE_BLOCK_DATA) +#define I2C_FUNC_SMBUS_I2C_BLOCK (I2C_FUNC_SMBUS_READ_I2C_BLOCK | I2C_FUNC_SMBUS_WRITE_I2C_BLOCK) + +#define I2C_FUNC_SMBUS_EMUL (I2C_FUNC_SMBUS_QUICK | I2C_FUNC_SMBUS_BYTE | I2C_FUNC_SMBUS_BYTE_DATA | \ + I2C_FUNC_SMBUS_WORD_DATA | I2C_FUNC_SMBUS_PROC_CALL | I2C_FUNC_SMBUS_WRITE_BLOCK_DATA | \ + I2C_FUNC_SMBUS_I2C_BLOCK | I2C_FUNC_SMBUS_PEC) + +/* if I2C_M_RECV_LEN is also supported */ +#define I2C_FUNC_SMBUS_EMUL_ALL (I2C_FUNC_SMBUS_EMUL | I2C_FUNC_SMBUS_READ_BLOCK_DATA | I2C_FUNC_SMBUS_BLOCK_PROC_CALL) + +#define I2C_STATUS_IDLE 0 +#define I2C_STATUS_ACK 1 +#define I2C_STATUS_NAK 2 + +class Adafruit_USBD_I2C : public Adafruit_USBD_Interface { +public: + Adafruit_USBD_I2C(TwoWire* wire); + bool begin(uint8_t* buffer, size_t bufsize); + + bool handleControlTransfer(uint8_t rhport, uint8_t stage, tusb_control_request_t const* request); + + // from Adafruit_USBD_Interface + virtual uint16_t getInterfaceDescriptor(uint8_t itfnum, uint8_t* buf, uint16_t bufsize); + +private: + TwoWire* _wire; + uint8_t _state; + uint32_t _functionality; + uint8_t* _buf; + uint16_t _bufsize; + + uint16_t i2c_write(uint8_t addr, uint8_t const* buf, uint16_t len, bool stop_bit); + uint16_t i2c_read(uint8_t addr, uint8_t* buf, uint16_t len, bool stop_bit); +}; + +#endif diff --git a/libraries/Adafruit_TinyUSB_Arduino/examples/Vendor/i2c_tiny_usb_adapter/i2c_tiny_usb_adapter.ino b/libraries/Adafruit_TinyUSB_Arduino/examples/Vendor/i2c_tiny_usb_adapter/i2c_tiny_usb_adapter.ino new file mode 100644 index 0000000..99a64f6 --- /dev/null +++ b/libraries/Adafruit_TinyUSB_Arduino/examples/Vendor/i2c_tiny_usb_adapter/i2c_tiny_usb_adapter.ino @@ -0,0 +1,84 @@ +/********************************************************************* + Adafruit invests time and resources providing this open source code, + please support Adafruit and open-source hardware by purchasing + products from Adafruit! + + MIT license, check LICENSE for more information + Copyright (c) 2019 Ha Thach for Adafruit Industries + All text above, and the splash screen below must be included in + any redistribution +*********************************************************************/ + +#include +#include "Adafruit_TinyUSB.h" + +#include "Adafruit_USBD_I2C.h" + +/* This sketch demonstrates how to use tinyusb vendor interface to implement + * i2c-tiny-usb adapter to use with Linux + * + * Reference: + * - https://github.com/torvalds/linux/blob/master/drivers/i2c/busses/i2c-tiny-usb.c + * - https://github.com/harbaum/I2C-Tiny-USB + * + * Requirement: + * - Install i2c-tools with + * sudo apt install i2c-tools + * + * How to test example: + * - Compile and flash this sketch on your board with an i2c device, it should enumerated as + * ID 1c40:0534 EZPrototypes i2c-tiny-usb interface + * + * - Run "i2cdetect -l" to find our bus ID e.g + * i2c-8 i2c i2c-tiny-usb at bus 003 device 039 I2C adapter + * + * - Run "i2cdetect -y 8" to scan for on-board device (8 is the above bus ID) + * 0 1 2 3 4 5 6 7 8 9 a b c d e f + 00: -- -- -- -- -- -- -- -- + 10: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- + 20: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- + 30: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- + 40: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- + 50: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- + 60: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- + 70: -- -- -- -- -- -- -- 77 + + - You can then interact with sensor using following commands: + i2cget i2cset i2cdump i2ctransfer or using any driver/tools that work on i2c device. + + Adafruit CircuitPython library for PC + - You can use run CircuitPython library with Extended Bus to read sensor data. + - 'i2c_usb.py' is provided a sample script + */ + +static uint8_t i2c_buf[800]; + +#define MyWire Wire +//#define MyWire Wire1 + +Adafruit_USBD_I2C i2c_usb(&MyWire); + +void setup() { + Serial.begin(115200); + + // init i2c usb with buffer and size + i2c_usb.begin(i2c_buf, sizeof(i2c_buf)); + + // If already enumerated, additional class driverr begin() e.g msc, hid, midi won't take effect until re-enumeration + if (TinyUSBDevice.mounted()) { + TinyUSBDevice.detach(); + delay(10); + TinyUSBDevice.attach(); + } +} + +void loop() { +} + + +// callback from tinyusb +bool tud_vendor_control_xfer_cb(uint8_t rhport, uint8_t stage, tusb_control_request_t const* request) +{ + return i2c_usb.handleControlTransfer(rhport, stage, request); +} + diff --git a/libraries/Adafruit_TinyUSB_Arduino/examples/Vendor/i2c_tiny_usb_adapter/i2c_usb.py b/libraries/Adafruit_TinyUSB_Arduino/examples/Vendor/i2c_tiny_usb_adapter/i2c_usb.py new file mode 100644 index 0000000..448ca5c --- /dev/null +++ b/libraries/Adafruit_TinyUSB_Arduino/examples/Vendor/i2c_tiny_usb_adapter/i2c_usb.py @@ -0,0 +1,18 @@ +#! /usr/bin/env python3 +# +# before running, install required libraries: +# pip3 install adafruit-extended-bus +# pip3 install adafruit-circuitpython-bme280 + +import time +import adafruit_extended_bus +from adafruit_bme280 import basic as adafruit_bme280 + +# Run "i2cdetect -l" to find your bus number +i2c_usb = adafruit_extended_bus.ExtendedI2C(8) +bme280 = adafruit_bme280.Adafruit_BME280_I2C(i2c_usb) +while 1: + print("\nTemperature: %0.1f C" % bme280.temperature) + print("Humidity: %0.1f %%" % bme280.humidity) + print("Pressure: %0.1f hPa" % bme280.pressure) + time.sleep(1) \ No newline at end of file diff --git a/libraries/Adafruit_TinyUSB_Arduino/examples/Video/video_capture/.skip.txt b/libraries/Adafruit_TinyUSB_Arduino/examples/Video/video_capture/.skip.txt new file mode 100644 index 0000000..ae593a8 --- /dev/null +++ b/libraries/Adafruit_TinyUSB_Arduino/examples/Video/video_capture/.skip.txt @@ -0,0 +1,3 @@ +feather_esp32_v2 +pico_rp2040_tinyusb_host +CH32V20x_EVT diff --git a/libraries/Adafruit_TinyUSB_Arduino/examples/Video/video_capture/video_capture.ino b/libraries/Adafruit_TinyUSB_Arduino/examples/Video/video_capture/video_capture.ino new file mode 100644 index 0000000..eaa13db --- /dev/null +++ b/libraries/Adafruit_TinyUSB_Arduino/examples/Video/video_capture/video_capture.ino @@ -0,0 +1,237 @@ +/********************************************************************* + Adafruit invests time and resources providing this open source code, + please support Adafruit and open-source hardware by purchasing + products from Adafruit! + + MIT license, check LICENSE for more information + Copyright (c) 2019 Ha Thach for Adafruit Industries + All text above, and the splash screen below must be included in + any redistribution +*********************************************************************/ + +#include "Adafruit_TinyUSB.h" + +//--------------------------------------------------------------------+ +// Video descriptors +//--------------------------------------------------------------------+ + +/* Time stamp base clock. It is a deprecated parameter. */ +#define FRAME_WIDTH 128 +#define FRAME_HEIGHT 96 +#define FRAME_RATE 30 + +/* video capture path + * + * | Camera Terminal (0x01) | ----> | Output Terminal (0x02 Streaming) | + * */ +#define TERMID_CAMERA 0x01 +#define TERMID_OUTPUT 0x02 + +tusb_desc_video_control_camera_terminal_t const desc_camera_terminal = { + .bLength = sizeof(tusb_desc_video_control_camera_terminal_t), + .bDescriptorType = TUSB_DESC_CS_INTERFACE, + .bDescriptorSubType = VIDEO_CS_ITF_VC_INPUT_TERMINAL, + + .bTerminalID = TERMID_CAMERA, + .wTerminalType = VIDEO_ITT_CAMERA, + .bAssocTerminal = 0, + .iTerminal = 0, + .wObjectiveFocalLengthMin = 0, + .wObjectiveFocalLengthMax = 0, + .wOcularFocalLength = 0, + .bControlSize = 3, + .bmControls = { 0, 0, 0 } +}; + +tusb_desc_video_control_output_terminal_t const desc_output_terminal = { + .bLength = sizeof(tusb_desc_video_control_output_terminal_t), + .bDescriptorType = TUSB_DESC_CS_INTERFACE, + .bDescriptorSubType = VIDEO_CS_ITF_VC_OUTPUT_TERMINAL, + + .bTerminalID = TERMID_OUTPUT, + .wTerminalType = VIDEO_TT_STREAMING, + .bAssocTerminal = 0, + .bSourceID = TERMID_CAMERA, + .iTerminal = 0 +}; + +tusb_desc_video_format_uncompressed_t const desc_format = { + .bLength = sizeof(tusb_desc_video_format_uncompressed_t), + .bDescriptorType = TUSB_DESC_CS_INTERFACE, + .bDescriptorSubType = VIDEO_CS_ITF_VS_FORMAT_UNCOMPRESSED, + .bFormatIndex = 1, // 1-based index + .bNumFrameDescriptors = 1, + .guidFormat = { TUD_VIDEO_GUID_YUY2 }, + .bBitsPerPixel = 16, + .bDefaultFrameIndex = 1, + .bAspectRatioX = 0, + .bAspectRatioY = 0, + .bmInterlaceFlags = 0, + .bCopyProtect = 0 +}; + +tusb_desc_video_frame_uncompressed_continuous_t desc_frame = { + .bLength = sizeof(tusb_desc_video_frame_uncompressed_continuous_t), + .bDescriptorType = TUSB_DESC_CS_INTERFACE, + .bDescriptorSubType = VIDEO_CS_ITF_VS_FRAME_UNCOMPRESSED, + .bFrameIndex = 1, // 1-based index + .bmCapabilities = 0, + .wWidth = FRAME_WIDTH, + .wHeight = FRAME_HEIGHT, + .dwMinBitRate = FRAME_WIDTH * FRAME_HEIGHT * 16 * 1, + .dwMaxBitRate = FRAME_WIDTH * FRAME_HEIGHT * 16 * FRAME_RATE, + .dwMaxVideoFrameBufferSize = FRAME_WIDTH * FRAME_HEIGHT * 16 / 8, + .dwDefaultFrameInterval = 10000000 / FRAME_RATE, + .bFrameIntervalType = 0, // continuous + .dwFrameInterval = { + 10000000 / FRAME_RATE, // min + 10000000, // max + 10000000 / FRAME_RATE // step + } +}; + +tusb_desc_video_streaming_color_matching_t desc_color = { + .bLength = sizeof(tusb_desc_video_streaming_color_matching_t), + .bDescriptorType = TUSB_DESC_CS_INTERFACE, + .bDescriptorSubType = VIDEO_CS_ITF_VS_COLORFORMAT, + + .bColorPrimaries = VIDEO_COLOR_PRIMARIES_BT709, + .bTransferCharacteristics = VIDEO_COLOR_XFER_CH_BT709, + .bMatrixCoefficients = VIDEO_COLOR_COEF_SMPTE170M +}; + +//--------------------------------------------------------------------+ +// Video and frame buffer +//--------------------------------------------------------------------+ +Adafruit_USBD_Video usb_video; + +// YUY2 frame buffer +static uint8_t frame_buffer[FRAME_WIDTH * FRAME_HEIGHT * 16 / 8]; + +static unsigned frame_num = 0; +static unsigned tx_busy = 0; +static unsigned interval_ms = 1000 / FRAME_RATE; +static unsigned start_ms = 0; +static unsigned already_sent = 0; + +//--------------------------------------------------------------------+ +// +//--------------------------------------------------------------------+ +static void fill_color_bar(uint8_t* buffer, unsigned start_position); + +void setup() { + // Manual begin() is required on core without built-in support e.g. mbed rp2040 + if (!TinyUSBDevice.isInitialized()) { + TinyUSBDevice.begin(0); + } + + Serial.begin(115200); + + usb_video.addTerminal(&desc_camera_terminal); + usb_video.addTerminal(&desc_output_terminal); + usb_video.addFormat(&desc_format); + usb_video.addFrame(&desc_frame); + usb_video.addColorMatching(&desc_color); + + usb_video.begin(); + + // If already enumerated, additional class driverr begin() e.g msc, hid, midi won't take effect until re-enumeration + if (TinyUSBDevice.mounted()) { + TinyUSBDevice.detach(); + delay(10); + TinyUSBDevice.attach(); + } +} + +void loop() { + #ifdef TINYUSB_NEED_POLLING_TASK + // Manual call tud_task since it isn't called by Core's background + TinyUSBDevice.task(); + #endif + + if (!tud_video_n_streaming(0, 0)) { + already_sent = 0; + frame_num = 0; + return; + } + + if (!already_sent) { + already_sent = 1; + tx_busy = 1; + start_ms = millis(); + fill_color_bar(frame_buffer, frame_num); + tud_video_n_frame_xfer(0, 0, (void*) frame_buffer, FRAME_WIDTH * FRAME_HEIGHT * 16 / 8); + } + + unsigned cur = millis(); + if (cur - start_ms < interval_ms) return; // not enough time + if (tx_busy) return; + start_ms += interval_ms; + tx_busy = 1; + + fill_color_bar(frame_buffer, frame_num); + tud_video_n_frame_xfer(0, 0, (void*) frame_buffer, FRAME_WIDTH * FRAME_HEIGHT * 16 / 8); +} + +//--------------------------------------------------------------------+ +// TinyUSB Video Callbacks +//--------------------------------------------------------------------+ +extern "C" { + +void tud_video_frame_xfer_complete_cb(uint_fast8_t ctl_idx, uint_fast8_t stm_idx) { + (void) ctl_idx; + (void) stm_idx; + tx_busy = 0; + /* flip buffer */ + ++frame_num; +} + +int tud_video_commit_cb(uint_fast8_t ctl_idx, uint_fast8_t stm_idx, + video_probe_and_commit_control_t const* parameters) { + (void) ctl_idx; + (void) stm_idx; + /* convert unit to ms from 100 ns */ + interval_ms = parameters->dwFrameInterval / 10000; + return VIDEO_ERROR_NONE; +} + +} // extern C + +//------------- Helper -------------// +static void fill_color_bar(uint8_t* buffer, unsigned start_position) { + /* EBU color bars + * See also https://stackoverflow.com/questions/6939422 */ + static uint8_t const bar_color[8][4] = { + /* Y, U, Y, V */ + { 235, 128, 235, 128}, /* 100% White */ + { 219, 16, 219, 138}, /* Yellow */ + { 188, 154, 188, 16}, /* Cyan */ + { 173, 42, 173, 26}, /* Green */ + { 78, 214, 78, 230}, /* Magenta */ + { 63, 102, 63, 240}, /* Red */ + { 32, 240, 32, 118}, /* Blue */ + { 16, 128, 16, 128}, /* Black */ + }; + uint8_t* p; + + /* Generate the 1st line */ + uint8_t* end = &buffer[FRAME_WIDTH * 2]; + unsigned idx = (FRAME_WIDTH / 2 - 1) - (start_position % (FRAME_WIDTH / 2)); + p = &buffer[idx * 4]; + for (unsigned i = 0; i < 8; ++i) { + for (int j = 0; j < FRAME_WIDTH / (2 * 8); ++j) { + memcpy(p, &bar_color[i], 4); + p += 4; + if (end <= p) { + p = buffer; + } + } + } + + /* Duplicate the 1st line to the others */ + p = &buffer[FRAME_WIDTH * 2]; + for (unsigned i = 1; i < FRAME_HEIGHT; ++i) { + memcpy(p, buffer, FRAME_WIDTH * 2); + p += FRAME_WIDTH * 2; + } +} diff --git a/libraries/Adafruit_TinyUSB_Arduino/examples/WebUSB/webusb_rgb/.skip.txt b/libraries/Adafruit_TinyUSB_Arduino/examples/WebUSB/webusb_rgb/.skip.txt new file mode 100644 index 0000000..1f3c021 --- /dev/null +++ b/libraries/Adafruit_TinyUSB_Arduino/examples/WebUSB/webusb_rgb/.skip.txt @@ -0,0 +1,4 @@ +feather_esp32_v2 +pico_rp2040_tinyusb_host +CH32V20x_EVT +# CH32V20x_EVT is not supported due to Adafruit_Neopixel \ No newline at end of file diff --git a/libraries/Adafruit_TinyUSB_Arduino/examples/WebUSB/webusb_rgb/webusb_rgb.ino b/libraries/Adafruit_TinyUSB_Arduino/examples/WebUSB/webusb_rgb/webusb_rgb.ino new file mode 100644 index 0000000..43c6317 --- /dev/null +++ b/libraries/Adafruit_TinyUSB_Arduino/examples/WebUSB/webusb_rgb/webusb_rgb.ino @@ -0,0 +1,130 @@ +/********************************************************************* + Adafruit invests time and resources providing this open source code, + please support Adafruit and open-source hardware by purchasing + products from Adafruit! + + MIT license, check LICENSE for more information + Copyright (c) 2019 Ha Thach for Adafruit Industries + All text above, and the splash screen below must be included in + any redistribution +*********************************************************************/ + +/* This sketch demonstrates WebUSB as web serial with browser with WebUSB support (e.g Chrome). + * After enumerated successfully, Browser will pop-up notification + * with URL to landing page, click on it to test + * - Click "Connect" and select device, When connected the neopixel LED will change color to Green. + * - When received color from browser in format '#RRGGBB', device will change the color of neopixel accordingly + * + * Note: + * - The WebUSB landing page notification is currently disabled in Chrome + * on Windows due to Chromium issue 656702 (https://crbug.com/656702). You have to + * go to landing page (below) to test + * + * - On Windows 7 and prior: You need to use Zadig tool to manually bind the + * WebUSB interface with the WinUSB driver for Chrome to access. From windows 8 and 10, this + * is done automatically by firmware. + */ + +#include "Adafruit_TinyUSB.h" +#include + +// Which pin on the Arduino is connected to the NeoPixels? +// On a Trinket or Gemma we suggest changing this to 1 +// use on-board neopixel PIN_NEOPIXEL if existed +#ifndef PIN_NEOPIXEL + #define PIN_NEOPIXEL 8 +#endif + +// How many NeoPixels are attached to the Arduino? +// use on-board defined NEOPIXEL_NUM if existed +#ifndef NEOPIXEL_NUM + #define NEOPIXEL_NUM 10 +#endif + +// When we setup the NeoPixel library, we tell it how many pixels, and which pin to use to send signals. +// Note that for older NeoPixel strips you might need to change the third parameter--see the strandtest +// example for more information on possible values. +Adafruit_NeoPixel pixels = Adafruit_NeoPixel(NEOPIXEL_NUM, PIN_NEOPIXEL, NEO_GRB + NEO_KHZ800); + +// USB WebUSB object +Adafruit_USBD_WebUSB usb_web; + +// Landing Page: scheme (0: http, 1: https), url +// Page source can be found at https://github.com/hathach/tinyusb-webusb-page/tree/main/webusb-rgb +WEBUSB_URL_DEF(landingPage, 1 /*https*/, "example.tinyusb.org/webusb-rgb/index.html"); + +// the setup function runs once when you press reset or power the board +void setup() { + // Manual begin() is required on core without built-in support e.g. mbed rp2040 + if (!TinyUSBDevice.isInitialized()) { + TinyUSBDevice.begin(0); + } + + Serial.begin(115200); + + //usb_web.setStringDescriptor("TinyUSB WebUSB"); + usb_web.setLandingPage(&landingPage); + usb_web.setLineStateCallback(line_state_callback); + usb_web.begin(); + + // If already enumerated, additional class driverr begin() e.g msc, hid, midi won't take effect until re-enumeration + if (TinyUSBDevice.mounted()) { + TinyUSBDevice.detach(); + delay(10); + TinyUSBDevice.attach(); + } + + // This initializes the NeoPixel with RED +#ifdef NEOPIXEL_POWER + pinMode(NEOPIXEL_POWER, OUTPUT); + digitalWrite(NEOPIXEL_POWER, NEOPIXEL_POWER_ON); +#endif + + pixels.begin(); + pixels.setBrightness(50); + pixels.fill(0xff0000); + pixels.show(); + + // wait until device mounted + while (!TinyUSBDevice.mounted()) delay(1); + + Serial.println("TinyUSB WebUSB RGB example"); +} + +// convert a hex character to number +uint8_t char2num(char c) { + if (c >= 'a') return c - 'a' + 10; + if (c >= 'A') return c - 'A' + 10; + return c - '0'; +} + +void loop() { + #ifdef TINYUSB_NEED_POLLING_TASK + // Manual call tud_task since it isn't called by Core's background + TinyUSBDevice.task(); + #endif + + // Landing Page 7 characters as hex color '#RRGGBB' + if (usb_web.available() < 7) return; + + uint8_t input[7]; + usb_web.readBytes(input, 7); + + // Print to serial for debugging + Serial.write(input, 7); + Serial.println(); + + uint8_t red = 16 * char2num(input[1]) + char2num(input[2]); + uint8_t green = 16 * char2num(input[3]) + char2num(input[4]); + uint8_t blue = 16 * char2num(input[5]) + char2num(input[6]); + + uint32_t color = (red << 16) | (green << 8) | blue; + pixels.fill(color); + pixels.show(); +} + +void line_state_callback(bool connected) { + // connected = green, disconnected = red + pixels.fill(connected ? 0x00ff00 : 0xff0000); + pixels.show(); +} diff --git a/libraries/Adafruit_TinyUSB_Arduino/examples/WebUSB/webusb_serial/.skip.txt b/libraries/Adafruit_TinyUSB_Arduino/examples/WebUSB/webusb_serial/.skip.txt new file mode 100644 index 0000000..a90edb3 --- /dev/null +++ b/libraries/Adafruit_TinyUSB_Arduino/examples/WebUSB/webusb_serial/.skip.txt @@ -0,0 +1,4 @@ +feather_esp32_v2 +pico_rp2040_tinyusb_host +CH32V20x_EVT +# CH32V20x_EVT is not supported due to lacking of HardwareSerial::read(uint8_t [64], int) \ No newline at end of file diff --git a/libraries/Adafruit_TinyUSB_Arduino/examples/WebUSB/webusb_serial/webusb_serial.ino b/libraries/Adafruit_TinyUSB_Arduino/examples/WebUSB/webusb_serial/webusb_serial.ino new file mode 100644 index 0000000..f9ca603 --- /dev/null +++ b/libraries/Adafruit_TinyUSB_Arduino/examples/WebUSB/webusb_serial/webusb_serial.ino @@ -0,0 +1,115 @@ +/********************************************************************* + Adafruit invests time and resources providing this open source code, + please support Adafruit and open-source hardware by purchasing + products from Adafruit! + + MIT license, check LICENSE for more information + Copyright (c) 2019 Ha Thach for Adafruit Industries + All text above, and the splash screen below must be included in + any redistribution +*********************************************************************/ + +/* This sketch demonstrates WebUSB as web serial with browser with WebUSB support (e.g Chrome). + * After enumerated successfully, Browser will pop-up notification + * with URL to landing page, click on it to test + * - Click "Connect" and select device, When connected the on-board LED will litted up. + * - Any charters received from either webusb/Serial will be echo back to webusb and Serial + * + * Note: + * - The WebUSB landing page notification is currently disabled in Chrome + * on Windows due to Chromium issue 656702 (https://crbug.com/656702). You have to + * go to landing page (below) to test + * + * - On Windows 7 and prior: You need to use Zadig tool to manually bind the + * WebUSB interface with the WinUSB driver for Chrome to access. From windows 8 and 10, this + * is done automatically by firmware. + */ + +#include "Adafruit_TinyUSB.h" + +// USB WebUSB object +Adafruit_USBD_WebUSB usb_web; + +// Landing Page: scheme (0: http, 1: https), url +// Page source can be found at https://github.com/hathach/tinyusb-webusb-page/tree/main/webusb-serial +WEBUSB_URL_DEF(landingPage, 1 /*https*/, "example.tinyusb.org/webusb-serial/index.html"); + +// the setup function runs once when you press reset or power the board +void setup() { + // Manual begin() is required on core without built-in support e.g. mbed rp2040 + if (!TinyUSBDevice.isInitialized()) { + TinyUSBDevice.begin(0); + } + Serial.begin(115200); + +#ifdef LED_BUILTIN + pinMode(LED_BUILTIN, OUTPUT); + digitalWrite(LED_BUILTIN, LOW); +#endif + + usb_web.setLandingPage(&landingPage); + usb_web.setLineStateCallback(line_state_callback); + //usb_web.setStringDescriptor("TinyUSB WebUSB"); + usb_web.begin(); + + // If already enumerated, additional class driverr begin() e.g msc, hid, midi won't take effect until re-enumeration + if (TinyUSBDevice.mounted()) { + TinyUSBDevice.detach(); + delay(10); + TinyUSBDevice.attach(); + } + + // wait until device mounted + while (!TinyUSBDevice.mounted()) delay(1); + + Serial.println("TinyUSB WebUSB Serial example"); +} + +// function to echo to both Serial and WebUSB +void echo_all(uint8_t buf[], uint32_t count) { + if (usb_web.connected()) { + usb_web.write(buf, count); + usb_web.flush(); + } + + if (Serial) { + for (uint32_t i = 0; i < count; i++) { + Serial.write(buf[i]); + if (buf[i] == '\r') Serial.write('\n'); + } + Serial.flush(); + } +} + +void loop() { + #ifdef TINYUSB_NEED_POLLING_TASK + // Manual call tud_task since it isn't called by Core's background + TinyUSBDevice.task(); + #endif + + uint8_t buf[64]; + uint32_t count; + + // From Serial to both Serial & webUSB + if (Serial.available()) { + count = Serial.read(buf, 64); + echo_all(buf, count); + } + + // from WebUSB to both Serial & webUSB + if (usb_web.available()) { + count = usb_web.read(buf, 64); + echo_all(buf, count); + } +} + +void line_state_callback(bool connected) { +#ifdef LED_BUILTIN + digitalWrite(LED_BUILTIN, connected); +#endif + + if (connected) { + usb_web.println("WebUSB interface connected !!"); + usb_web.flush(); + } +} diff --git a/libraries/Adafruit_TinyUSB_Arduino/library.json b/libraries/Adafruit_TinyUSB_Arduino/library.json new file mode 100644 index 0000000..c42375b --- /dev/null +++ b/libraries/Adafruit_TinyUSB_Arduino/library.json @@ -0,0 +1,22 @@ +{ + "name": "Adafruit TinyUSB Library", + "keywords": "usb, arduino, tinyusb", + "description": "Arduino library for TinyUSB", + "version": "3.7.3", + "repository": { + "type": "git", + "url": "https://github.com/adafruit/Adafruit_TinyUSB_Arduino.git" + }, + "frameworks": "*", + "platforms": "*", + "build": { + "libArchive": false + }, + "authors": + [ + { + "name": "Adafruit", + "maintainer": true + } + ] +} diff --git a/libraries/Adafruit_TinyUSB_Arduino/library.properties b/libraries/Adafruit_TinyUSB_Arduino/library.properties new file mode 100644 index 0000000..ee1d5d1 --- /dev/null +++ b/libraries/Adafruit_TinyUSB_Arduino/library.properties @@ -0,0 +1,11 @@ +name=Adafruit TinyUSB Library +version=3.7.3 +author=Adafruit +maintainer=Adafruit +sentence=TinyUSB library for Arduino +paragraph=Support nRF5x, SAMD21, SAMD51, RP2040, ESP32-S2/S3, CH32V +category=Communication +url=https://github.com/adafruit/Adafruit_TinyUSB_Arduino +architectures=* +includes=Adafruit_TinyUSB.h +depends=Adafruit SPIFlash, MIDI Library, SdFat - Adafruit Fork diff --git a/cores/nRF5/TinyUSB/Adafruit_TinyUSB_ArduinoCore/Adafruit_TinyUSB_Core.h b/libraries/Adafruit_TinyUSB_Arduino/src/Adafruit_TinyUSB.h similarity index 58% rename from cores/nRF5/TinyUSB/Adafruit_TinyUSB_ArduinoCore/Adafruit_TinyUSB_Core.h rename to libraries/Adafruit_TinyUSB_Arduino/src/Adafruit_TinyUSB.h index 44e160f..c837f2d 100644 --- a/cores/nRF5/TinyUSB/Adafruit_TinyUSB_ArduinoCore/Adafruit_TinyUSB_Core.h +++ b/libraries/Adafruit_TinyUSB_Arduino/src/Adafruit_TinyUSB.h @@ -1,4 +1,4 @@ -/* +/* * The MIT License (MIT) * * Copyright (c) 2019 Ha Thach for Adafruit Industries @@ -22,24 +22,59 @@ * THE SOFTWARE. */ -#ifndef ADAFRUIT_TINYUSB_CORE_H_ -#define ADAFRUIT_TINYUSB_CORE_H_ +#ifndef ADAFRUIT_TINYUSB_H_ +#define ADAFRUIT_TINYUSB_H_ -#ifndef USE_TINYUSB -#error TinyUSB is not selected, please select it in Tools->Menu->USB Stack -#endif +#include "tusb_option.h" -#include "tusb.h" +// Device +#if CFG_TUD_ENABLED -#ifdef __cplusplus #include "Adafruit_USBD_Device.h" + +#if CFG_TUD_CDC #include "Adafruit_USBD_CDC.h" #endif -// Called by main.cpp to initialize usb device typically with CDC device for Serial -void Adafruit_TinyUSB_Core_init(void); +#if CFG_TUD_HID +#include "arduino/hid/Adafruit_USBD_HID.h" +#endif + +#if CFG_TUD_MIDI +#include "arduino/midi/Adafruit_USBD_MIDI.h" +#endif + +#if CFG_TUD_MSC +#include "arduino/msc/Adafruit_USBD_MSC.h" +#endif + +#if CFG_TUD_VENDOR +#include "arduino/webusb/Adafruit_USBD_WebUSB.h" +#endif + +#if CFG_TUD_VIDEO +#include "arduino/video/Adafruit_USBD_Video.h" +#endif -// Invoked when host disconnects cdc at baud 1200, usually touch feature to go into DFU mode -void Adafruit_TinyUSB_Core_touch1200(void); +// Initialize device hardware, stack, also Serial as CDC +// Wrapper for TinyUSBDevice.begin(rhport) +void TinyUSB_Device_Init(uint8_t rhport); + +#endif + +// Host +#if CFG_TUH_ENABLED + +#include "arduino/Adafruit_USBH_Host.h" + +#if CFG_TUH_CDC +#include "arduino/cdc/Adafruit_USBH_CDC.h" +#endif + +#if CFG_TUH_MSC +#include "arduino/msc/Adafruit_USBH_MSC.h" +#endif + +#endif -#endif /* ADAFRUIT_TINYUSB_CORE_H_ */ +#endif /* ADAFRUIT_TINYUSB_H_ */ diff --git a/libraries/Adafruit_TinyUSB_Arduino/src/arduino/Adafruit_USBH_Host.cpp b/libraries/Adafruit_TinyUSB_Arduino/src/arduino/Adafruit_USBH_Host.cpp new file mode 100644 index 0000000..d9189ab --- /dev/null +++ b/libraries/Adafruit_TinyUSB_Arduino/src/arduino/Adafruit_USBH_Host.cpp @@ -0,0 +1,344 @@ +/* + * The MIT License (MIT) + * + * Copyright (c) 2022, Ha Thach (tinyusb.org) for Adafruit + * + * 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. + */ + +#ifdef ARDUINO_ARCH_ESP32 +#include "arduino/ports/esp32/tusb_config_esp32.h" +#include "driver/gpio.h" +#include +#endif + +#include "tusb_option.h" + +#if CFG_TUH_ENABLED + +#include "Adafruit_TinyUSB_API.h" +#include "Adafruit_USBH_Host.h" + +Adafruit_USBH_Host *Adafruit_USBH_Host::_instance = NULL; + +Adafruit_USBH_Host::Adafruit_USBH_Host(void) { + Adafruit_USBH_Host::_instance = this; + _rhport = 0; +#if defined(CFG_TUH_MAX3421) && CFG_TUH_MAX3421 + _spi = NULL; + _cs = _intr = _sck = _mosi = _miso = -1; +#endif +} + +#if defined(CFG_TUH_MAX3421) && CFG_TUH_MAX3421 + +// API to read MAX3421's register. Implemented by TinyUSB +extern "C" uint8_t tuh_max3421_reg_read(uint8_t rhport, uint8_t reg, + bool in_isr); + +// API to write MAX3421's register. Implemented by TinyUSB +extern "C" bool tuh_max3421_reg_write(uint8_t rhport, uint8_t reg, uint8_t data, + bool in_isr); + +static void max3421_isr(void); + +#if defined(ARDUINO_ARCH_ESP32) +SemaphoreHandle_t max3421_intr_sem; +static void max3421_intr_task(void *param); +#endif + +Adafruit_USBH_Host::Adafruit_USBH_Host(SPIClass *spi, int8_t cs, int8_t intr) { + Adafruit_USBH_Host::_instance = this; + _rhport = 0; + _spi = spi; + _cs = cs; + _intr = intr; + _sck = _mosi = _miso = -1; +} + +Adafruit_USBH_Host::Adafruit_USBH_Host(SPIClass *spi, int8_t sck, int8_t mosi, + int8_t miso, int8_t cs, int8_t intr) { + Adafruit_USBH_Host::_instance = this; + _rhport = 0; + _spi = spi; + _cs = cs; + _intr = intr; + _sck = sck; + _mosi = mosi; + _miso = miso; +} + +uint8_t Adafruit_USBH_Host::max3421_readRegister(uint8_t reg, bool in_isr) { + return tuh_max3421_reg_read(_rhport, reg, in_isr); +} + +bool Adafruit_USBH_Host::max3421_writeRegister(uint8_t reg, uint8_t data, + bool in_isr) { + return tuh_max3421_reg_write(_rhport, reg, data, in_isr); +} + +#endif + +bool Adafruit_USBH_Host::configure(uint8_t rhport, uint32_t cfg_id, + const void *cfg_param) { + return tuh_configure(rhport, cfg_id, cfg_param); +} + +#ifdef ARDUINO_ARCH_RP2040 +bool Adafruit_USBH_Host::configure_pio_usb(uint8_t rhport, + const void *cfg_param) { + return configure(rhport, TUH_CFGID_RPI_PIO_USB_CONFIGURATION, cfg_param); +} +#endif + +bool Adafruit_USBH_Host::begin(uint8_t rhport) { +#if defined(CFG_TUH_MAX3421) && CFG_TUH_MAX3421 + if (_intr < 0 || _cs < 0 || !_spi) { + return false; + } + + // CS pin + pinMode(_cs, OUTPUT); + digitalWrite(_cs, HIGH); + + // SPI init + // Software SPI is not supported yet +#ifdef ARDUINO_ARCH_ESP32 + // ESP32 SPI assign pins when begin() of declaration as standard API + _spi->begin(_sck, _miso, _mosi, -1); + + // Create an task for executing interrupt handler in thread mode + max3421_intr_sem = xSemaphoreCreateBinary(); + xTaskCreateUniversal(max3421_intr_task, "max3421 intr", 2048, NULL, 5, NULL, + ARDUINO_RUNNING_CORE); +#else + _spi->begin(); +#endif + + // Interrupt pin + pinMode(_intr, INPUT_PULLUP); + attachInterrupt(_intr, max3421_isr, FALLING); +#endif + + _rhport = rhport; + return tuh_init(rhport); +} + +void Adafruit_USBH_Host::task(uint32_t timeout_ms, bool in_isr) { + tuh_task_ext(timeout_ms, in_isr); +} + +// Invoked when device with hid interface is mounted +// Report descriptor is also available for use. +// tuh_hid_parse_report_descriptor() can be used to parse common/simple enough +// descriptor. Note: if report descriptor length > CFG_TUH_ENUMERATION_BUFSIZE, +// it will be skipped therefore report_desc = NULL, desc_len = 0 +TU_ATTR_WEAK void tuh_hid_mount_cb(uint8_t dev_addr, uint8_t instance, + uint8_t const *desc_report, + uint16_t desc_len) { + (void)dev_addr; + (void)instance; + (void)desc_report; + (void)desc_len; +} + +// Invoked when device with hid interface is un-mounted +TU_ATTR_WEAK void tuh_hid_umount_cb(uint8_t dev_addr, uint8_t instance) { + (void)dev_addr; + (void)instance; +} + +// Invoked when received report from device via interrupt endpoint +TU_ATTR_WEAK void tuh_hid_report_received_cb(uint8_t dev_addr, uint8_t instance, + uint8_t const *report, + uint16_t len) { + (void)dev_addr; + (void)instance; + (void)report; + (void)len; +} + +//--------------------------------------------------------------------+ +// USB Host using MAX3421E +//--------------------------------------------------------------------+ +#if CFG_TUH_ENABLED && defined(CFG_TUH_MAX3421) && CFG_TUH_MAX3421 + +#if defined(ARDUINO_ARCH_ESP32) + +// ESP32 out-of-sync +#if ESP_ARDUINO_VERSION < ESP_ARDUINO_VERSION_VAL(2, 0, 14) && \ + !defined(PLATFORMIO) +extern "C" void hcd_int_handler_esp32(uint8_t rhport, bool in_isr); +#define tuh_int_handler_esp32 hcd_int_handler_esp32 + +#else +#define tuh_int_handler_esp32 tuh_int_handler + +#endif + +static void max3421_intr_task(void *param) { + (void)param; + + while (1) { + xSemaphoreTake(max3421_intr_sem, portMAX_DELAY); + tuh_int_handler_esp32(1, false); + } +} + +static void max3421_isr(void) { + BaseType_t xHigherPriorityTaskWoken = pdFALSE; + xSemaphoreGiveFromISR(max3421_intr_sem, &xHigherPriorityTaskWoken); + if (xHigherPriorityTaskWoken) { + portYIELD_FROM_ISR(); + } +} + +#else + +static void max3421_isr(void) { tuh_int_handler(1, true); } + +#endif // ESP32 + +extern "C" { + +void tuh_max3421_spi_cs_api(uint8_t rhport, bool active) { + (void)rhport; + + if (!Adafruit_USBH_Host::_instance) { + return; + } + Adafruit_USBH_Host *host = Adafruit_USBH_Host::_instance; + SPIClass *spi = host->_spi; + + if (active) { + // MAX3421e max clock is 26MHz + // Depending on mcu ports, it may need to be clipped down +#ifdef ARDUINO_ARCH_SAMD + // SAMD 21/51 can only work reliably at 12MHz + uint32_t const max_clock = 12000000ul; +#else + uint32_t const max_clock = 26000000ul; +#endif + + spi->beginTransaction(SPISettings(max_clock, MSBFIRST, SPI_MODE0)); + digitalWrite(Adafruit_USBH_Host::_instance->_cs, LOW); + } else { + spi->endTransaction(); + digitalWrite(Adafruit_USBH_Host::_instance->_cs, HIGH); + } +} + +bool tuh_max3421_spi_xfer_api(uint8_t rhport, uint8_t const *tx_buf, + uint8_t *rx_buf, size_t xfer_bytes) { + (void)rhport; + + if (!Adafruit_USBH_Host::_instance) { + return false; + } + Adafruit_USBH_Host *host = Adafruit_USBH_Host::_instance; + SPIClass *spi = host->_spi; + +#ifdef ARDUINO_ARCH_SAMD + // SAMD cannot use transfer(tx_buf, rx_buf, len) API since it default to use + // DMA. However, since this can be invoked within EIC_Handler (ISR) which may + // have priority higher than DMA ISR. That will cause blocking wait for + // dma_busy not working + for (size_t count = 0; count < xfer_bytes; count++) { + uint8_t data = 0x00; + if (tx_buf) { + data = tx_buf[count]; + } + data = spi->transfer(data); + + if (rx_buf) { + rx_buf[count] = data; + } + } +#elif defined(ARDUINO_ARCH_ESP32) + spi->transferBytes(tx_buf, rx_buf, xfer_bytes); +#else + spi->transfer(tx_buf, rx_buf, xfer_bytes); +#endif + + return true; +} + +void tuh_max3421_int_api(uint8_t rhport, bool enabled) { + (void)rhport; + + if (!Adafruit_USBH_Host::_instance) { + return; + } + Adafruit_USBH_Host *host = Adafruit_USBH_Host::_instance; + (void)host; + +#ifdef ARDUINO_ARCH_SAMD + //--- SAMD51 ---// +#ifdef __SAMD51__ + const IRQn_Type irq = + (IRQn_Type)(EIC_0_IRQn + g_APinDescription[host->_intr].ulExtInt); + + if (enabled) { + NVIC_EnableIRQ(irq); + } else { + NVIC_DisableIRQ(irq); + } +#else + //--- SAMD21 ---// + if (enabled) { + NVIC_EnableIRQ(EIC_IRQn); + } else { + NVIC_DisableIRQ(EIC_IRQn); + } +#endif + +#elif defined(ARDUINO_NRF52_ADAFRUIT) + //--- nRF52 ---// + if (enabled) { + NVIC_EnableIRQ(GPIOTE_IRQn); + } else { + NVIC_DisableIRQ(GPIOTE_IRQn); + } + +#elif defined(ARDUINO_ARCH_ESP32) + //--- ESP32 ---// + if (enabled) { + gpio_intr_enable((gpio_num_t)host->_intr); + } else { + gpio_intr_disable((gpio_num_t)host->_intr); + } + +#elif defined(ARDUINO_ARCH_RP2040) + //--- RP2040 ---// + irq_set_enabled(IO_IRQ_BANK0, enabled); + +#else +//#error "MAX3421e host is not supported by this architecture" + if (enabled) { + NVIC_EnableIRQ(GPIOTE_IRQn); + } else { + NVIC_DisableIRQ(GPIOTE_IRQn); + } +#endif +} + +} // extern C +#endif + +#endif diff --git a/libraries/Adafruit_TinyUSB_Arduino/src/arduino/Adafruit_USBH_Host.h b/libraries/Adafruit_TinyUSB_Arduino/src/arduino/Adafruit_USBH_Host.h new file mode 100644 index 0000000..8daa8a7 --- /dev/null +++ b/libraries/Adafruit_TinyUSB_Arduino/src/arduino/Adafruit_USBH_Host.h @@ -0,0 +1,100 @@ +/* + * The MIT License (MIT) + * + * Copyright (c) 2019 Ha Thach for Adafruit Industries + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#ifndef ADAFRUIT_USBH_HOST_H_ +#define ADAFRUIT_USBH_HOST_H_ + +#include "Adafruit_USBD_Interface.h" +#include "tusb.h" +#include + +#ifdef ARDUINO_ARCH_ESP32 +#include "esp32-hal-tinyusb.h" +#endif + +#if defined(CFG_TUH_MAX3421) && CFG_TUH_MAX3421 +extern "C" { +void tuh_max3421_spi_cs_api(uint8_t rhport, bool active); +bool tuh_max3421_spi_xfer_api(uint8_t rhport, uint8_t const *tx_buf, + uint8_t *rx_buf, size_t xfer_bytes); +void tuh_max3421_int_api(uint8_t rhport, bool enabled); +} + +class Adafruit_USBH_Host { +private: + SPIClass *_spi; + int8_t _cs; + int8_t _intr; + + // for esp32 or using softwareSPI + int8_t _sck, _mosi, _miso; + +public: + // constructor for using MAX3421E (host shield) + Adafruit_USBH_Host(SPIClass *spi, int8_t cs, int8_t intr); + Adafruit_USBH_Host(SPIClass *spi, int8_t sck, int8_t mosi, int8_t miso, + int8_t cs, int8_t intr); + + uint8_t max3421_readRegister(uint8_t reg, bool in_isr); + bool max3421_writeRegister(uint8_t reg, uint8_t data, bool in_isr); + bool max3421_writeIOPINS1(uint8_t data, bool in_isr) { + enum { IOPINS1_ADDR = 20u << 3 }; // 0xA0 + return max3421_writeRegister(IOPINS1_ADDR, data, in_isr); + } + bool max3421_writeIOPINS2(uint8_t data, bool in_isr) { + enum { IOPINS2_ADDR = 21u << 3 }; // 0xA8 + return max3421_writeRegister(IOPINS2_ADDR, data, in_isr); + } + +private: + friend void tuh_max3421_spi_cs_api(uint8_t rhport, bool active); + friend bool tuh_max3421_spi_xfer_api(uint8_t rhport, uint8_t const *tx_buf, + uint8_t *rx_buf, size_t xfer_bytes); + friend void tuh_max3421_int_api(uint8_t rhport, bool enabled); +#else + +class Adafruit_USBH_Host { +#endif + +public: + // default constructor + Adafruit_USBH_Host(void); + + bool configure(uint8_t rhport, uint32_t cfg_id, const void *cfg_param); + +#ifdef ARDUINO_ARCH_RP2040 + bool configure_pio_usb(uint8_t rhport, const void *cfg_param); +#endif + + bool begin(uint8_t rhport); + void task(uint32_t timeout_ms = UINT32_MAX, bool in_isr = false); + + //------------- internal usage -------------// + static Adafruit_USBH_Host *_instance; + +private: + uint8_t _rhport; +}; + +#endif diff --git a/libraries/Adafruit_TinyUSB_Arduino/src/arduino/cdc/Adafruit_USBH_CDC.cpp b/libraries/Adafruit_TinyUSB_Arduino/src/arduino/cdc/Adafruit_USBH_CDC.cpp new file mode 100644 index 0000000..1fcafb2 --- /dev/null +++ b/libraries/Adafruit_TinyUSB_Arduino/src/arduino/cdc/Adafruit_USBH_CDC.cpp @@ -0,0 +1,178 @@ +/* + * The MIT License (MIT) + * + * Copyright (c) 2022 Ha Thach (tinyusb.org) for Adafruit Industries + * + * 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. + */ + +// ESP32 out-of-sync +#ifdef ARDUINO_ARCH_ESP32 +#include "arduino/ports/esp32/tusb_config_esp32.h" +#endif + +#include "tusb_option.h" + +#if CFG_TUH_ENABLED && CFG_TUH_MSC + +#include "tusb.h" + +#include "Adafruit_USBH_CDC.h" + +Adafruit_USBH_CDC::Adafruit_USBH_CDC(void) +#ifdef ARDUINO_ARCH_ESP32 + : HardwareSerial(0) +#endif +{ + _idx = TUSB_INDEX_INVALID_8; +} + +void Adafruit_USBH_CDC::begin(unsigned long baudrate) { + // default to index 0 when begin + if (_idx == TUSB_INDEX_INVALID_8) { + _idx = 0; + } + + _baud = baudrate; + if (_baud == 0) { + _baud = 115200; // default, backward compatible with previous API begin(0) + } + + // if already mounted, this will set baudrate + if (mounted()) { + setBaudrate(_baud); + } +} + +void Adafruit_USBH_CDC::begin(unsigned long baudrate, uint16_t config) { + (void)config; // TODO support line coding later + begin(baudrate); +} + +void Adafruit_USBH_CDC::end(void) { _idx = TUSB_INDEX_INVALID_8; } + +bool Adafruit_USBH_CDC::mount(uint8_t idx) { + _idx = idx; + + uint32_t local_baud = baud(); + if (local_baud != _baud) { + return setBaudrate(_baud); + } + + return true; +} + +void Adafruit_USBH_CDC::umount(uint8_t idx) { + if (_idx == idx) { + _idx = TUSB_INDEX_INVALID_8; + } +} + +bool Adafruit_USBH_CDC::connected(void) { return tuh_cdc_connected(_idx); } + +bool Adafruit_USBH_CDC::mounted(void) { return tuh_cdc_mounted(_idx); } +uint32_t Adafruit_USBH_CDC::baud() { + cdc_line_coding_t line_coding; + if (!tuh_cdc_get_local_line_coding(_idx, &line_coding)) { + return 0; + } + return line_coding.bit_rate; +} + +//--------------------------------------------------------------------+ +// Control API +//--------------------------------------------------------------------+ + +bool Adafruit_USBH_CDC::setDtrRts(bool dtr, bool rts, tuh_xfer_cb_t complete_cb, + uintptr_t user_data) { + if (!tuh_cdc_mounted(_idx)) { + return false; + } + + uint16_t const line_state = (dtr ? CDC_CONTROL_LINE_STATE_DTR : 0) | + (rts ? CDC_CONTROL_LINE_STATE_RTS : 0); + + return tuh_cdc_set_control_line_state(_idx, line_state, complete_cb, + user_data); +} + +bool Adafruit_USBH_CDC::setBaudrate(uint32_t baudrate, + tuh_xfer_cb_t complete_cb, + uintptr_t user_data) { + if (!tuh_cdc_mounted(_idx)) { + return false; + } + + if (baud() == baudrate) { + // skip if already matched + return true; + } + + return tuh_cdc_set_baudrate(_idx, baudrate, complete_cb, user_data); +} + +//--------------------------------------------------------------------+ +// Stream API +//--------------------------------------------------------------------+ + +int Adafruit_USBH_CDC::available(void) { + return (int)tuh_cdc_read_available(_idx); +} + +int Adafruit_USBH_CDC::peek(void) { + uint8_t ch; + return tuh_cdc_peek(_idx, &ch) ? (int)ch : -1; +} + +int Adafruit_USBH_CDC::read(void) { + uint8_t ch; + return read(&ch, 1) ? (int)ch : -1; +} + +size_t Adafruit_USBH_CDC::read(uint8_t *buffer, size_t size) { + return tuh_cdc_read(_idx, buffer, size); +} + +void Adafruit_USBH_CDC::flush(void) { (void)tuh_cdc_write_flush(_idx); } + +size_t Adafruit_USBH_CDC::write(uint8_t ch) { return write(&ch, 1); } + +size_t Adafruit_USBH_CDC::write(const uint8_t *buffer, size_t size) { + size_t remain = size; + while (remain && tuh_cdc_mounted(_idx)) { + size_t wrcount = tuh_cdc_write(_idx, buffer, remain); + remain -= wrcount; + buffer += wrcount; + + // Write FIFO is full, run host task while wait for space become available + if (remain) { + tuh_task(); + } + } + + return size - remain; + + return tuh_cdc_write(_idx, buffer, size); +} + +int Adafruit_USBH_CDC::availableForWrite(void) { + return (int)tuh_cdc_write_available(_idx); +} + +#endif diff --git a/libraries/Adafruit_TinyUSB_Arduino/src/arduino/cdc/Adafruit_USBH_CDC.h b/libraries/Adafruit_TinyUSB_Arduino/src/arduino/cdc/Adafruit_USBH_CDC.h new file mode 100644 index 0000000..1f7d593 --- /dev/null +++ b/libraries/Adafruit_TinyUSB_Arduino/src/arduino/cdc/Adafruit_USBH_CDC.h @@ -0,0 +1,86 @@ +/* + * The MIT License (MIT) + * + * Copyright (c) 2022 Ha Thach (tinyusb.org) for Adafruit Industries + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#ifndef ADAFRUIT_USBH_CDC_H_ +#define ADAFRUIT_USBH_CDC_H_ + +#include "HardwareSerial.h" + +class Adafruit_USBH_CDC : public HardwareSerial { +public: + Adafruit_USBH_CDC(void); + + // Set/Get index of cdc interface + void setInterfaceIndex(uint8_t idx) { _idx = idx; } + uint8_t getInterfaceIndex(void) { return _idx; } + + void begin(unsigned long baudrate); + void begin(unsigned long baudrate, uint16_t config); + + bool mount(uint8_t idx); + void umount(uint8_t idx); + + // unbind cdc interface + void end(void); + + // If cdc is mounted + bool mounted(void); + operator bool() { return mounted(); } + + // if cdc's DTR is asserted + bool connected(void); + + // Line encoding + uint32_t baud(); + + //------------- Control API -------------// + bool setDtrRts(bool dtr, bool rts, tuh_xfer_cb_t complete_cb = NULL, + uintptr_t user_data = 0); + bool setBaudrate(uint32_t baudrate, tuh_xfer_cb_t complete_cb = NULL, + uintptr_t user_data = 0); + + //------------- Stream API -------------// + virtual int available(void); + virtual int peek(void); + + virtual int read(void); + size_t read(uint8_t *buffer, size_t size); + + virtual void flush(void); + virtual size_t write(uint8_t ch); + + virtual size_t write(const uint8_t *buffer, size_t size); + size_t write(const char *buffer, size_t size) { + return write((const uint8_t *)buffer, size); + } + + virtual int availableForWrite(void); + using Print::write; // pull in write(str) from Print + +private: + uint8_t _idx; // TinyUSB CDC Interface Index + uint32_t _baud; +}; + +#endif diff --git a/libraries/Adafruit_TinyUSB_Arduino/src/arduino/hid/Adafruit_USBD_HID.cpp b/libraries/Adafruit_TinyUSB_Arduino/src/arduino/hid/Adafruit_USBD_HID.cpp new file mode 100644 index 0000000..0bacb95 --- /dev/null +++ b/libraries/Adafruit_TinyUSB_Arduino/src/arduino/hid/Adafruit_USBD_HID.cpp @@ -0,0 +1,288 @@ +/* + * The MIT License (MIT) + * + * Copyright (c) 2019 Ha Thach for Adafruit Industries + * + * 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. + */ + +#include "tusb_option.h" + +#if CFG_TUD_ENABLED && CFG_TUD_HID + +#include "Adafruit_USBD_HID.h" + +uint8_t const _ascii2keycode[128][2] = {HID_ASCII_TO_KEYCODE}; +static Adafruit_USBD_HID *_hid_instances[CFG_TUD_HID] = {0}; + +uint8_t Adafruit_USBD_HID::_instance_count = 0; + +//------------- IMPLEMENTATION -------------// +Adafruit_USBD_HID::Adafruit_USBD_HID(void) + : Adafruit_USBD_HID(NULL, 0, HID_ITF_PROTOCOL_NONE, 4, false) {} + +Adafruit_USBD_HID::Adafruit_USBD_HID(uint8_t const *desc_report, uint16_t len, + uint8_t protocol, uint8_t interval_ms, + bool has_out_endpoint) { + _instance = INVALID_INSTANCE; + _interval_ms = interval_ms; + _protocol = protocol; + + _out_endpoint = has_out_endpoint; + _mouse_button = 0; + + _desc_report = desc_report; + _desc_report_len = len; + + _get_report_cb = NULL; + _set_report_cb = NULL; +} + +void Adafruit_USBD_HID::setPollInterval(uint8_t interval_ms) { + _interval_ms = interval_ms; +} + +void Adafruit_USBD_HID::setBootProtocol(uint8_t protocol) { + _protocol = protocol; +} + +bool Adafruit_USBD_HID::isOutEndpointEnabled(void) { return _out_endpoint; } + +void Adafruit_USBD_HID::enableOutEndpoint(bool enable) { + _out_endpoint = enable; +} + +void Adafruit_USBD_HID::setReportDescriptor(uint8_t const *desc_report, + uint16_t len) { + _desc_report = desc_report; + _desc_report_len = len; +} + +void Adafruit_USBD_HID::setReportCallback(get_report_callback_t get_report, + set_report_callback_t set_report) { + _get_report_cb = get_report; + _set_report_cb = set_report; +} + +uint16_t Adafruit_USBD_HID::makeItfDesc(uint8_t itfnum, uint8_t *buf, + uint16_t bufsize, uint8_t ep_in, + uint8_t ep_out) { + if (!_desc_report_len) { + return 0; + } + + uint8_t const desc_inout[] = {TUD_HID_INOUT_DESCRIPTOR( + itfnum, _strid, _protocol, _desc_report_len, ep_in, ep_out, + CFG_TUD_HID_EP_BUFSIZE, _interval_ms)}; + uint8_t const desc_in_only[] = { + TUD_HID_DESCRIPTOR(itfnum, _strid, _protocol, _desc_report_len, ep_in, + CFG_TUD_HID_EP_BUFSIZE, _interval_ms)}; + + uint8_t const *desc; + uint16_t len; + + if (_out_endpoint) { + desc = desc_inout; + len = sizeof(desc_inout); + } else { + desc = desc_in_only; + len = sizeof(desc_in_only); + } + + // null buffer is used to get the length of descriptor only + if (buf) { + if (bufsize < len) { + return 0; + } + memcpy(buf, desc, len); + } + + return len; +} + +uint16_t Adafruit_USBD_HID::getInterfaceDescriptor(uint8_t itfnum_deprecated, + uint8_t *buf, + uint16_t bufsize) { + (void)itfnum_deprecated; + + uint8_t itfnum = 0; + uint8_t ep_in = 0; + uint8_t ep_out = 0; + + // null buffer is used to get the length of descriptor only + if (buf) { + itfnum = TinyUSBDevice.allocInterface(1); + ep_in = TinyUSBDevice.allocEndpoint(TUSB_DIR_IN); + + if (_out_endpoint) { + ep_out = TinyUSBDevice.allocEndpoint(TUSB_DIR_OUT); + } + } + + return makeItfDesc(itfnum, buf, bufsize, ep_in, ep_out); +} + +bool Adafruit_USBD_HID::begin(void) { + if (isValid()) { + return true; + } + + if (_instance_count >= CFG_TUD_HID) { + return false; + } + + if (!TinyUSBDevice.addInterface(*this)) { + return false; + } + _instance = _instance_count++; + _hid_instances[_instance] = this; + + return true; +} + +bool Adafruit_USBD_HID::ready(void) { return tud_hid_n_ready(_instance); } + +bool Adafruit_USBD_HID::sendReport(uint8_t report_id, void const *report, + uint8_t len) { + return tud_hid_n_report(_instance, report_id, report, len); +} + +bool Adafruit_USBD_HID::sendReport8(uint8_t report_id, uint8_t num) { + return tud_hid_n_report(_instance, report_id, &num, sizeof(num)); +} + +bool Adafruit_USBD_HID::sendReport16(uint8_t report_id, uint16_t num) { + return tud_hid_n_report(_instance, report_id, &num, sizeof(num)); +} + +bool Adafruit_USBD_HID::sendReport32(uint8_t report_id, uint32_t num) { + return tud_hid_n_report(_instance, report_id, &num, sizeof(num)); +} + +uint8_t Adafruit_USBD_HID::getProtocol() { + return tud_hid_n_get_protocol(_instance); +} + +//------------- TinyUSB callbacks -------------// +extern "C" { + +// Invoked when received GET HID REPORT DESCRIPTOR +// Application return pointer to descriptor, whose contents must exist long +// enough for transfer to complete +uint8_t const *tud_hid_descriptor_report_cb(uint8_t itf) { + Adafruit_USBD_HID *p_hid = _hid_instances[itf]; + + if (!p_hid) { + return NULL; + } + + return p_hid->_desc_report; +} + +// Invoked when received GET_REPORT control request +// Application must fill buffer report's content and return its length. +// Return zero will cause the stack to STALL request +uint16_t tud_hid_get_report_cb(uint8_t itf, uint8_t report_id, + hid_report_type_t report_type, uint8_t *buffer, + uint16_t reqlen) { + Adafruit_USBD_HID *p_hid = _hid_instances[itf]; + + if (!(p_hid && p_hid->_get_report_cb)) { + return 0; + } + + return p_hid->_get_report_cb(report_id, report_type, buffer, reqlen); +} + +// Invoked when received SET_REPORT control request or +// received data on OUT endpoint ( Report ID = 0, Type = 0 ) +void tud_hid_set_report_cb(uint8_t itf, uint8_t report_id, + hid_report_type_t report_type, uint8_t const *buffer, + uint16_t bufsize) { + Adafruit_USBD_HID *p_hid = _hid_instances[itf]; + + if (!(p_hid && p_hid->_set_report_cb)) { + return; + } + + p_hid->_set_report_cb(report_id, report_type, buffer, bufsize); +} + +} // extern "C" + +//--------------------------------------------------------------------+ +// Keyboard +//--------------------------------------------------------------------+ + +bool Adafruit_USBD_HID::keyboardReport(uint8_t report_id, uint8_t modifier, + uint8_t keycode[6]) { + return tud_hid_n_keyboard_report(_instance, report_id, modifier, keycode); +} + +bool Adafruit_USBD_HID::keyboardPress(uint8_t report_id, char ch) { + uint8_t keycode[6] = {0}; + uint8_t modifier = 0; + uint8_t uch = (uint8_t)ch; + + if (_ascii2keycode[uch][0]) { + modifier = KEYBOARD_MODIFIER_LEFTSHIFT; + } + keycode[0] = _ascii2keycode[uch][1]; + + return tud_hid_n_keyboard_report(_instance, report_id, modifier, keycode); +} + +bool Adafruit_USBD_HID::keyboardRelease(uint8_t report_id) { + return tud_hid_n_keyboard_report(_instance, report_id, 0, NULL); +} + +//--------------------------------------------------------------------+ +// Mouse +//--------------------------------------------------------------------+ + +bool Adafruit_USBD_HID::mouseReport(uint8_t report_id, uint8_t buttons, + int8_t x, int8_t y, int8_t vertical, + int8_t horizontal) { + // cache mouse button for other API such as move, scroll + _mouse_button = buttons; + + return tud_hid_n_mouse_report(_instance, report_id, buttons, x, y, vertical, + horizontal); +} + +bool Adafruit_USBD_HID::mouseMove(uint8_t report_id, int8_t x, int8_t y) { + return tud_hid_n_mouse_report(_instance, report_id, _mouse_button, x, y, 0, + 0); +} + +bool Adafruit_USBD_HID::mouseScroll(uint8_t report_id, int8_t scroll, + int8_t pan) { + return tud_hid_n_mouse_report(_instance, report_id, _mouse_button, 0, 0, + scroll, pan); +} + +bool Adafruit_USBD_HID::mouseButtonPress(uint8_t report_id, uint8_t buttons) { + return tud_hid_n_mouse_report(_instance, report_id, buttons, 0, 0, 0, 0); +} + +bool Adafruit_USBD_HID::mouseButtonRelease(uint8_t report_id) { + return tud_hid_n_mouse_report(_instance, report_id, 0, 0, 0, 0, 0); +} + +#endif // CFG_TUD_ENABLED diff --git a/libraries/Adafruit_TinyUSB_Arduino/src/arduino/hid/Adafruit_USBD_HID.h b/libraries/Adafruit_TinyUSB_Arduino/src/arduino/hid/Adafruit_USBD_HID.h new file mode 100644 index 0000000..1ea0ae5 --- /dev/null +++ b/libraries/Adafruit_TinyUSB_Arduino/src/arduino/hid/Adafruit_USBD_HID.h @@ -0,0 +1,116 @@ +/* + * The MIT License (MIT) + * + * Copyright (c) 2019 Ha Thach for Adafruit Industries + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#ifndef ADAFRUIT_USBD_HID_H_ +#define ADAFRUIT_USBD_HID_H_ + +#include "Adafruit_USBD_Device.h" + +class Adafruit_USBD_HID : public Adafruit_USBD_Interface { +public: + typedef uint16_t (*get_report_callback_t)(uint8_t report_id, + hid_report_type_t report_type, + uint8_t *buffer, uint16_t reqlen); + typedef void (*set_report_callback_t)(uint8_t report_id, + hid_report_type_t report_type, + uint8_t const *buffer, + uint16_t bufsize); + + enum { INVALID_INSTANCE = 0xffu }; + static uint8_t getInstanceCount(void) { return _instance_count; } + + Adafruit_USBD_HID(void); + Adafruit_USBD_HID(uint8_t const *desc_report, uint16_t len, + uint8_t protocol = HID_ITF_PROTOCOL_NONE, + uint8_t interval_ms = 4, bool has_out_endpoint = false); + + void setPollInterval(uint8_t interval_ms); + void setBootProtocol(uint8_t protocol); // 0: None, 1: Keyboard, 2:Mouse + + void enableOutEndpoint(bool enable); + bool isOutEndpointEnabled(void); + + void setReportDescriptor(uint8_t const *desc_report, uint16_t len); + void setReportCallback(get_report_callback_t get_report, + set_report_callback_t set_report); + + bool begin(void); + bool isValid(void) { return _instance != INVALID_INSTANCE; } + + bool ready(void); + bool sendReport(uint8_t report_id, void const *report, uint8_t len); + + uint8_t getProtocol(); + + // Report helpers + bool sendReport8(uint8_t report_id, uint8_t num); + bool sendReport16(uint8_t report_id, uint16_t num); + bool sendReport32(uint8_t report_id, uint32_t num); + + //------------- Keyboard API -------------// + bool keyboardReport(uint8_t report_id, uint8_t modifier, uint8_t keycode[6]); + bool keyboardPress(uint8_t report_id, char ch); + bool keyboardRelease(uint8_t report_id); + + //------------- Mouse API -------------// + bool mouseReport(uint8_t report_id, uint8_t buttons, int8_t x, int8_t y, + int8_t vertical, int8_t horizontal); + bool mouseMove(uint8_t report_id, int8_t x, int8_t y); + bool mouseScroll(uint8_t report_id, int8_t scroll, int8_t pan); + bool mouseButtonPress(uint8_t report_id, uint8_t buttons); + bool mouseButtonRelease(uint8_t report_id); + + // from Adafruit_USBD_Interface + virtual uint16_t getInterfaceDescriptor(uint8_t itfnum_deprecated, + uint8_t *buf, uint16_t bufsize); + + // internal use only + uint16_t makeItfDesc(uint8_t itfnum, uint8_t *buf, uint16_t bufsize, + uint8_t ep_in, uint8_t ep_out); + +private: + static uint8_t _instance_count; + + uint8_t _instance; + uint8_t _interval_ms; + uint8_t _protocol; + bool _out_endpoint; + uint8_t _mouse_button; + + uint16_t _desc_report_len; + uint8_t const *_desc_report; + + get_report_callback_t _get_report_cb; + set_report_callback_t _set_report_cb; + + friend uint16_t tud_hid_get_report_cb(uint8_t itf, uint8_t report_id, + hid_report_type_t report_type, + uint8_t *buffer, uint16_t reqlen); + friend void tud_hid_set_report_cb(uint8_t itf, uint8_t report_id, + hid_report_type_t report_type, + uint8_t const *buffer, uint16_t bufsize); + friend uint8_t const *tud_hid_descriptor_report_cb(uint8_t itf); +}; + +#endif /* ADAFRUIT_USBD_HID_H_ */ diff --git a/libraries/Adafruit_TinyUSB_Arduino/src/arduino/midi/Adafruit_USBD_MIDI.cpp b/libraries/Adafruit_TinyUSB_Arduino/src/arduino/midi/Adafruit_USBD_MIDI.cpp new file mode 100644 index 0000000..ad9729d --- /dev/null +++ b/libraries/Adafruit_TinyUSB_Arduino/src/arduino/midi/Adafruit_USBD_MIDI.cpp @@ -0,0 +1,166 @@ +/* + * The MIT License (MIT) + * + * Copyright (c) 2019 hathach for Adafruit Industries + * + * 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. + */ + +#include "tusb_option.h" + +#if CFG_TUD_ENABLED && CFG_TUD_MIDI + +#include "Adafruit_USBD_MIDI.h" + +//--------------------------------------------------------------------+ +// MACRO TYPEDEF CONSTANT ENUM DECLARATION +//--------------------------------------------------------------------+ +#define BULK_PACKET_SIZE (TUD_OPT_HIGH_SPEED ? 512 : 64) + +// TODO multiple instances +static Adafruit_USBD_MIDI *_midi_dev = NULL; + +Adafruit_USBD_MIDI::Adafruit_USBD_MIDI(uint8_t n_cables) { + _n_cables = n_cables; + memset(_cable_name_strid, 0, sizeof(_cable_name_strid)); +} + +void Adafruit_USBD_MIDI::setCables(uint8_t n_cables) { _n_cables = n_cables; } + +bool Adafruit_USBD_MIDI::setCableName(uint8_t cable_id, const char *str) { + if (cable_id == 0 || cable_id > sizeof(_cable_name_strid)) { + return false; + } + + uint8_t strid = TinyUSBDevice.addStringDescriptor(str); + _cable_name_strid[cable_id - 1] = strid; + + return strid > 0; +} + +bool Adafruit_USBD_MIDI::begin(void) { + if (!TinyUSBDevice.addInterface(*this)) { + return false; + } + + _midi_dev = this; + return true; +} + +uint16_t Adafruit_USBD_MIDI::getInterfaceDescriptor(uint8_t itfnum_deprecated, + uint8_t *buf, + uint16_t bufsize) { + (void)itfnum_deprecated; + + uint16_t const desc_len = TUD_MIDI_DESC_HEAD_LEN + + TUD_MIDI_DESC_JACK_LEN * _n_cables + + 2 * TUD_MIDI_DESC_EP_LEN(_n_cables); + + // null buffer is used to get the length of descriptor only + if (!buf) { + return desc_len; + } + + if (bufsize < desc_len) { + return 0; + } + + uint8_t itfnum = TinyUSBDevice.allocInterface(2); + uint8_t ep_in = TinyUSBDevice.allocEndpoint(TUSB_DIR_IN); + uint8_t ep_out = TinyUSBDevice.allocEndpoint(TUSB_DIR_OUT); + + uint16_t len = 0; + + // Header + { + uint8_t desc[] = {TUD_MIDI_DESC_HEAD(itfnum, _strid, _n_cables)}; + memcpy(buf + len, desc, sizeof(desc)); + len += sizeof(desc); + } + + // Jack + for (uint8_t i = 1; i <= _n_cables; i++) { + uint8_t jack[] = {TUD_MIDI_DESC_JACK_DESC(i, _cable_name_strid[i - 1])}; + memcpy(buf + len, jack, sizeof(jack)); + len += sizeof(jack); + } + + // Endpoint OUT + jack mapping + { + uint8_t desc[] = {TUD_MIDI_DESC_EP(ep_out, BULK_PACKET_SIZE, _n_cables)}; + memcpy(buf + len, desc, sizeof(desc)); + len += sizeof(desc); + } + + for (uint8_t i = 1; i <= _n_cables; i++) { + uint8_t jack[] = {TUD_MIDI_JACKID_IN_EMB(i)}; + memcpy(buf + len, jack, sizeof(jack)); + len += sizeof(jack); + } + + // Endpoint IN + jack mapping + { + uint8_t desc[] = {TUD_MIDI_DESC_EP(ep_in, BULK_PACKET_SIZE, _n_cables)}; + memcpy(buf + len, desc, sizeof(desc)); + len += sizeof(desc); + } + + for (uint8_t i = 1; i <= _n_cables; i++) { + uint8_t jack[] = {TUD_MIDI_JACKID_OUT_EMB(i)}; + memcpy(buf + len, jack, sizeof(jack)); + len += sizeof(jack); + } + + if (len != desc_len) { + // TODO should throw an error message + return 0; + } + + return desc_len; +} + +int Adafruit_USBD_MIDI::read(void) { + uint8_t ch; + return tud_midi_stream_read(&ch, 1) ? (int)ch : (-1); +} + +size_t Adafruit_USBD_MIDI::write(uint8_t b) { + return tud_midi_stream_write(0, &b, 1); +} + +int Adafruit_USBD_MIDI::available(void) { return tud_midi_available(); } + +int Adafruit_USBD_MIDI::peek(void) { + // MIDI Library does not use peek + return -1; +} + +void Adafruit_USBD_MIDI::flush(void) { + // MIDI Library does not use flush +} + +bool Adafruit_USBD_MIDI::writePacket(const uint8_t packet[4]) { + return tud_midi_packet_write(packet); +} + +bool Adafruit_USBD_MIDI::readPacket(uint8_t packet[4]) { + return tud_midi_packet_read(packet); +} + +#endif // CFG_TUD_ENABLED diff --git a/libraries/Adafruit_TinyUSB_Arduino/src/arduino/midi/Adafruit_USBD_MIDI.h b/libraries/Adafruit_TinyUSB_Arduino/src/arduino/midi/Adafruit_USBD_MIDI.h new file mode 100644 index 0000000..21d9848 --- /dev/null +++ b/libraries/Adafruit_TinyUSB_Arduino/src/arduino/midi/Adafruit_USBD_MIDI.h @@ -0,0 +1,71 @@ +/* + * The MIT License (MIT) + * + * Copyright (c) 2019 hathach for Adafruit Industries + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#ifndef ADAFRUIT_USBD_MIDI_H_ +#define ADAFRUIT_USBD_MIDI_H_ + +#include "Stream.h" +#include "Adafruit_USBD_Device.h" + +class Adafruit_USBD_MIDI : public Stream, public Adafruit_USBD_Interface { +public: + Adafruit_USBD_MIDI(uint8_t n_cables = 1); + + void setCables(uint8_t n_cables); + + // Set the cable number with a USB device descriptor string + // Note: per MIDI specs cable_id (or jackid/elementid) starting from 1. 0 is + // reserved for undefined ID. + bool setCableName(uint8_t cable_id, const char *str); + bool begin(void); + + // for MIDI library + bool begin(uint32_t baud) { + (void)baud; + return begin(); + } + + // Stream interface to use with MIDI Library + virtual int read(void); + virtual size_t write(uint8_t b); + virtual int available(void); + virtual int peek(void); + virtual void flush(void); + + using Stream::write; + + // Raw MIDI USB packet interface. + bool writePacket(const uint8_t packet[4]); + bool readPacket(uint8_t packet[4]); + + // from Adafruit_USBD_Interface + virtual uint16_t getInterfaceDescriptor(uint8_t itfnum_deprecated, + uint8_t *buf, uint16_t bufsize); + +private: + uint8_t _n_cables; + uint8_t _cable_name_strid[16]; +}; + +#endif /* ADAFRUIT_USBD_MIDI_H_ */ diff --git a/libraries/Adafruit_TinyUSB_Arduino/src/arduino/msc/Adafruit_USBD_MSC.cpp b/libraries/Adafruit_TinyUSB_Arduino/src/arduino/msc/Adafruit_USBD_MSC.cpp new file mode 100644 index 0000000..c23c799 --- /dev/null +++ b/libraries/Adafruit_TinyUSB_Arduino/src/arduino/msc/Adafruit_USBD_MSC.cpp @@ -0,0 +1,279 @@ +/* + * The MIT License (MIT) + * + * Copyright (c) 2019 Ha Thach for Adafruit Industries + * + * 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. + */ + +#include "tusb_option.h" + +#if CFG_TUD_ENABLED && CFG_TUD_MSC + +#include "Adafruit_USBD_MSC.h" + +static Adafruit_USBD_MSC *_msc_dev = NULL; + +Adafruit_USBD_MSC::Adafruit_USBD_MSC(void) { + _maxlun = 1; + memset(_lun_info, 0, sizeof(_lun_info)); +} + +uint16_t Adafruit_USBD_MSC::getInterfaceDescriptor(uint8_t itfnum_deprecated, + uint8_t *buf, + uint16_t bufsize) { + (void)itfnum_deprecated; + + // null buffer is used to get the length of descriptor only + if (!buf) { + return TUD_MSC_DESC_LEN; + } + + uint8_t const itfnum = TinyUSBDevice.allocInterface(1); + uint8_t const ep_in = TinyUSBDevice.allocEndpoint(TUSB_DIR_IN); + uint8_t const ep_out = TinyUSBDevice.allocEndpoint(TUSB_DIR_OUT); + + uint16_t const mps = + (TUD_OPT_HIGH_SPEED ? 512 : 64); // TODO actual link speed + uint8_t const desc[] = { + TUD_MSC_DESCRIPTOR(itfnum, _strid, ep_out, ep_in, mps)}; + uint16_t const len = sizeof(desc); + + if (bufsize < len) { + return 0; + } + memcpy(buf, desc, len); + + return len; +} + +void Adafruit_USBD_MSC::setMaxLun(uint8_t maxlun) { _maxlun = maxlun; } + +uint8_t Adafruit_USBD_MSC::getMaxLun(void) { return _maxlun; } + +void Adafruit_USBD_MSC::setID(uint8_t lun, const char *vendor_id, + const char *product_id, const char *product_rev) { + _lun_info[lun]._inquiry_vid = vendor_id; + _lun_info[lun]._inquiry_pid = product_id; + _lun_info[lun]._inquiry_rev = product_rev; +} + +void Adafruit_USBD_MSC::setCapacity(uint8_t lun, uint32_t block_count, + uint16_t block_size) { + _lun_info[lun].block_count = block_count; + _lun_info[lun].block_size = block_size; +} + +void Adafruit_USBD_MSC::setUnitReady(uint8_t lun, bool ready) { + _lun_info[lun].unit_ready = ready; +} + +void Adafruit_USBD_MSC::setReadWriteCallback(uint8_t lun, read_callback_t rd_cb, + write_callback_t wr_cb, + flush_callback_t fl_cb) { + _lun_info[lun].rd_cb = rd_cb; + _lun_info[lun].wr_cb = wr_cb; + _lun_info[lun].fl_cb = fl_cb; +} + +void Adafruit_USBD_MSC::setStartStopCallback(uint8_t lun, + start_stop_callback_t cb) { + _lun_info[lun].start_stop_cb = cb; +} + +void Adafruit_USBD_MSC::setReadyCallback(uint8_t lun, ready_callback_t cb) { + _lun_info[lun].ready_cb = cb; +} + +void Adafruit_USBD_MSC::setWritableCallback(uint8_t lun, + writable_callback_t cb) { + _lun_info[lun].writable_cb = cb; +} + +bool Adafruit_USBD_MSC::begin(void) { + if (!TinyUSBDevice.addInterface(*this)) { + return false; + } + + _msc_dev = this; + return true; +} + +//------------- TinyUSB callbacks -------------// +extern "C" { + +// Invoked to determine max LUN +uint8_t tud_msc_get_maxlun_cb(void) { + if (!_msc_dev) { + return 0; + } + return _msc_dev->getMaxLun(); +} + +// Invoked when received SCSI_CMD_INQUIRY +// Application fill vendor id, product id and revision with string up to 8, 16, +// 4 characters respectively +void tud_msc_inquiry_cb(uint8_t lun, uint8_t vendor_id[8], + uint8_t product_id[16], uint8_t product_rev[4]) { + if (!_msc_dev) { + return; + } + + // If not set use default ID "Adafruit - Mass Storage - 1.0" + const char *vid = (_msc_dev->_lun_info[lun]._inquiry_vid + ? _msc_dev->_lun_info[lun]._inquiry_vid + : "Adafruit"); + const char *pid = (_msc_dev->_lun_info[lun]._inquiry_pid + ? _msc_dev->_lun_info[lun]._inquiry_pid + : "Mass Storage"); + const char *rev = (_msc_dev->_lun_info[lun]._inquiry_rev + ? _msc_dev->_lun_info[lun]._inquiry_rev + : "1.0"); + + memcpy(vendor_id, vid, tu_min32(strlen(vid), 8)); + memcpy(product_id, pid, tu_min32(strlen(pid), 16)); + memcpy(product_rev, rev, tu_min32(strlen(rev), 4)); +} + +// Invoked when received Test Unit Ready command. +// return true allowing host to read/write this LUN e.g SD card inserted +bool tud_msc_test_unit_ready_cb(uint8_t lun) { + if (!_msc_dev) { + return false; + } + + if (_msc_dev->_lun_info[lun].ready_cb) { + _msc_dev->_lun_info[lun].unit_ready = _msc_dev->_lun_info[lun].ready_cb(); + } + + bool const ret = _msc_dev->_lun_info[lun].unit_ready; + + if (!ret) { + // Addition Sense: 3A-00 is NOT FOUND + tud_msc_set_sense(lun, SCSI_SENSE_NOT_READY, 0x3a, 0x00); + } + + return ret; +} + +// Callback invoked to determine disk's size +void tud_msc_capacity_cb(uint8_t lun, uint32_t *block_count, + uint16_t *block_size) { + if (!_msc_dev) { + return; + } + + *block_count = _msc_dev->_lun_info[lun].block_count; + *block_size = _msc_dev->_lun_info[lun].block_size; +} + +// Callback invoked when received an SCSI command not in built-in list below +// - READ_CAPACITY10, READ_FORMAT_CAPACITY, INQUIRY, MODE_SENSE6, REQUEST_SENSE +// - READ10 and WRITE10 has their own callbacks +int32_t tud_msc_scsi_cb(uint8_t lun, const uint8_t scsi_cmd[16], void *buffer, + uint16_t bufsize) { + const void *response = NULL; + int32_t resplen = 0; + + switch (scsi_cmd[0]) { + + default: + // Set Sense = Invalid Command Operation + tud_msc_set_sense(lun, SCSI_SENSE_ILLEGAL_REQUEST, 0x20, 0x00); + + // negative means error -> tinyusb could stall and/or response with failed + // status + resplen = -1; + break; + } + + // return len must not larger than bufsize + if (resplen > bufsize) { + resplen = bufsize; + } + + // copy response to stack's buffer if any + if (response && (resplen > 0)) { + memcpy(buffer, response, resplen); + } + + return resplen; +} + +// Callback invoked on start/stop +bool tud_msc_start_stop_cb(uint8_t lun, uint8_t power_condition, bool start, + bool load_eject) { + if (!(_msc_dev && _msc_dev->_lun_info[lun].start_stop_cb)) { + return true; + } + + return _msc_dev->_lun_info[lun].start_stop_cb(power_condition, start, + load_eject); +} + +// Callback invoked when received READ10 command. +// Copy disk's data to buffer (up to bufsize) and return number of copied bytes. +int32_t tud_msc_read10_cb(uint8_t lun, uint32_t lba, uint32_t offset, + void *buffer, uint32_t bufsize) { + (void)offset; + + if (!(_msc_dev && _msc_dev->_lun_info[lun].rd_cb)) { + return -1; + } + + return _msc_dev->_lun_info[lun].rd_cb(lba, buffer, bufsize); +} + +// Callback invoked when received WRITE10 command. +// Process data in buffer to disk's storage and return number of written bytes +int32_t tud_msc_write10_cb(uint8_t lun, uint32_t lba, uint32_t offset, + uint8_t *buffer, uint32_t bufsize) { + (void)offset; + + if (!(_msc_dev && _msc_dev->_lun_info[lun].wr_cb)) { + return -1; + } + + return _msc_dev->_lun_info[lun].wr_cb(lba, buffer, bufsize); +} + +// Callback invoked when WRITE10 command is completed (status received and +// accepted by host). used to flush any pending cache. +void tud_msc_write10_complete_cb(uint8_t lun) { + if (!(_msc_dev && _msc_dev->_lun_info[lun].fl_cb)) { + return; + } + + // flush pending cache when write10 is complete + return _msc_dev->_lun_info[lun].fl_cb(); +} + +// Invoked to check if device is writable as part of SCSI WRITE10 +// Default mode is writable +bool tud_msc_is_writable_cb(uint8_t lun) { + if (!(_msc_dev && _msc_dev->_lun_info[lun].writable_cb)) { + return true; + } + + return _msc_dev->_lun_info[lun].writable_cb(); +} + +} // extern "C" + +#endif // CFG_TUD_ENABLED diff --git a/libraries/Adafruit_TinyUSB_Arduino/src/arduino/msc/Adafruit_USBD_MSC.h b/libraries/Adafruit_TinyUSB_Arduino/src/arduino/msc/Adafruit_USBD_MSC.h new file mode 100644 index 0000000..91be92e --- /dev/null +++ b/libraries/Adafruit_TinyUSB_Arduino/src/arduino/msc/Adafruit_USBD_MSC.h @@ -0,0 +1,128 @@ +/* + * The MIT License (MIT) + * + * Copyright (c) 2019 Ha Thach for Adafruit Industries + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#ifndef ADAFRUIT_USBD_MSC_H_ +#define ADAFRUIT_USBD_MSC_H_ + +#include "Adafruit_USBD_Device.h" + +class Adafruit_USBD_MSC : public Adafruit_USBD_Interface { +public: + typedef int32_t (*read_callback_t)(uint32_t lba, void *buffer, + uint32_t bufsize); + typedef int32_t (*write_callback_t)(uint32_t lba, uint8_t *buffer, + uint32_t bufsize); + typedef void (*flush_callback_t)(void); + typedef bool (*ready_callback_t)(void); + typedef bool (*writable_callback_t)(void); + typedef bool (*start_stop_callback_t)(uint8_t power_condition, bool start, + bool load_eject); + + Adafruit_USBD_MSC(void); + + bool begin(void); + + void setMaxLun(uint8_t maxlun); + uint8_t getMaxLun(void); + + //------------- Multiple LUN API -------------// + void setID(uint8_t lun, const char *vendor_id, const char *product_id, + const char *product_rev); + void setCapacity(uint8_t lun, uint32_t block_count, uint16_t block_size); + void setUnitReady(uint8_t lun, bool ready); + void setReadWriteCallback(uint8_t lun, read_callback_t rd_cb, + write_callback_t wr_cb, flush_callback_t fl_cb); + void setReadyCallback(uint8_t lun, ready_callback_t cb); + void setWritableCallback(uint8_t lun, writable_callback_t cb); + void setStartStopCallback(uint8_t lun, start_stop_callback_t cb); + + //------------- Single LUN API -------------// + void setID(const char *vendor_id, const char *product_id, + const char *product_rev) { + setID(0, vendor_id, product_id, product_rev); + } + + void setCapacity(uint32_t block_count, uint16_t block_size) { + setCapacity(0, block_count, block_size); + } + + void setUnitReady(bool ready) { setUnitReady(0, ready); } + + void setReadWriteCallback(read_callback_t rd_cb, write_callback_t wr_cb, + flush_callback_t fl_cb) { + setReadWriteCallback(0, rd_cb, wr_cb, fl_cb); + } + + void setReadyCallback(ready_callback_t cb) { setReadyCallback(0, cb); } + void setWritableCallback(writable_callback_t cb) { + setWritableCallback(0, cb); + } + void setStartStopCallback(start_stop_callback_t cb) { + setStartStopCallback(0, cb); + } + + // from Adafruit_USBD_Interface + virtual uint16_t getInterfaceDescriptor(uint8_t itfnum_deprecated, + uint8_t *buf, uint16_t bufsize); + +private: + enum { MAX_LUN = 2 }; // TODO make it configurable + struct { + read_callback_t rd_cb; + write_callback_t wr_cb; + flush_callback_t fl_cb; + ready_callback_t ready_cb; + writable_callback_t writable_cb; + start_stop_callback_t start_stop_cb; + + const char *_inquiry_vid; + const char *_inquiry_pid; + const char *_inquiry_rev; + + uint32_t block_count; + uint16_t block_size; + bool unit_ready; + + } _lun_info[MAX_LUN]; + + uint8_t _maxlun; + + // Make all tinyusb callback friend to access private data + friend void tud_msc_inquiry_cb(uint8_t lun, uint8_t vendor_id[8], + uint8_t product_id[16], + uint8_t product_rev[4]); + friend bool tud_msc_test_unit_ready_cb(uint8_t lun); + friend void tud_msc_capacity_cb(uint8_t lun, uint32_t *block_count, + uint16_t *block_size); + friend int32_t tud_msc_read10_cb(uint8_t lun, uint32_t lba, uint32_t offset, + void *buffer, uint32_t bufsize); + friend int32_t tud_msc_write10_cb(uint8_t lun, uint32_t lba, uint32_t offset, + uint8_t *buffer, uint32_t bufsize); + friend void tud_msc_write10_complete_cb(uint8_t lun); + friend bool tud_msc_is_writable_cb(uint8_t lun); + friend bool tud_msc_start_stop_cb(uint8_t lun, uint8_t power_condition, + bool start, bool load_eject); +}; + +#endif /* ADAFRUIT_USBD_MSC_H_ */ diff --git a/libraries/Adafruit_TinyUSB_Arduino/src/arduino/msc/Adafruit_USBH_MSC.cpp b/libraries/Adafruit_TinyUSB_Arduino/src/arduino/msc/Adafruit_USBH_MSC.cpp new file mode 100644 index 0000000..48f88a8 --- /dev/null +++ b/libraries/Adafruit_TinyUSB_Arduino/src/arduino/msc/Adafruit_USBH_MSC.cpp @@ -0,0 +1,152 @@ +/* + * The MIT License (MIT) + * + * Copyright (c) 2022 Ha Thach (tinyusb.org) for Adafruit Industries + * + * 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. + */ + +// ESP32 out-of-sync +#ifdef ARDUINO_ARCH_ESP32 +#include "arduino/ports/esp32/tusb_config_esp32.h" +#endif + +#include "tusb_option.h" + +#if CFG_TUH_ENABLED && CFG_TUH_MSC + +#include "Adafruit_USBH_MSC.h" +#include "tusb.h" + +#if __has_include("SdFat_Adafruit_Fork.h") + +Adafruit_USBH_MSC_BlockDevice::Adafruit_USBH_MSC_BlockDevice() { + _daddr = _lun = 0; + _busy = false; + _wr_cb = NULL; +} + +bool Adafruit_USBH_MSC_BlockDevice::begin(uint8_t dev_addr) { + _daddr = dev_addr; + return true; +} + +bool Adafruit_USBH_MSC_BlockDevice::setActiveLUN(uint8_t lun) { + _lun = lun; + return true; +} +void Adafruit_USBH_MSC_BlockDevice::setWriteCompleteCallback( + tuh_msc_complete_cb_t cb) { + _wr_cb = cb; +} + +void Adafruit_USBH_MSC_BlockDevice::end(void) { _daddr = _lun = 0; } + +bool Adafruit_USBH_MSC_BlockDevice::mounted(void) { return _daddr > 0; } + +bool Adafruit_USBH_MSC_BlockDevice::isBusy(void) { return _busy; } + +bool Adafruit_USBH_MSC_BlockDevice::wait_for_io(void) { + while (_busy) { + if (tuh_task_event_ready()) { + tuh_task(); + } + } + + return true; +} + +bool Adafruit_USBH_MSC_BlockDevice::_io_complete_cb( + uint8_t dev_addr, tuh_msc_complete_data_t const *cb_data) { + (void)cb_data; + if (dev_addr != _daddr) { + // something wrong occurred, maybe device removed while transferring + return false; + } + + // TODO skip csw status: assuming io is successful + _busy = false; + + switch (cb_data->cbw->command[0]) { + case SCSI_CMD_WRITE_10: + if (_wr_cb) { + _wr_cb(dev_addr, cb_data); + } + break; + } + + return true; +} + +static bool _msc_io_complete_cb(uint8_t dev_addr, + tuh_msc_complete_data_t const *cb_data) { + Adafruit_USBH_MSC_BlockDevice *sdfat_dev = + (Adafruit_USBH_MSC_BlockDevice *)cb_data->user_arg; + sdfat_dev->_io_complete_cb(dev_addr, cb_data); + return true; +} + +uint32_t Adafruit_USBH_MSC_BlockDevice::sectorCount(void) { + return tuh_msc_get_block_count(_daddr, _lun); +} + +bool Adafruit_USBH_MSC_BlockDevice::syncDevice(void) { + // no caching + return true; +} + +bool Adafruit_USBH_MSC_BlockDevice::readSectors(uint32_t block, uint8_t *dst, + size_t ns) { + _busy = true; + if (tuh_msc_read10(_daddr, _lun, dst, block, (uint16_t)ns, + _msc_io_complete_cb, (uintptr_t)this)) { + wait_for_io(); + return true; + } else { + _busy = false; + return false; + } +} + +bool Adafruit_USBH_MSC_BlockDevice::writeSectors(uint32_t block, + const uint8_t *src, + size_t ns) { + _busy = true; + if (tuh_msc_write10(_daddr, _lun, src, block, (uint16_t)ns, + _msc_io_complete_cb, (uintptr_t)this)) { + wait_for_io(); + return true; + } else { + _busy = false; + return false; + } +} + +bool Adafruit_USBH_MSC_BlockDevice::readSector(uint32_t block, uint8_t *dst) { + return readSectors(block, dst, 1); +} + +bool Adafruit_USBH_MSC_BlockDevice::writeSector(uint32_t block, + const uint8_t *src) { + return writeSectors(block, src, 1); +} + +#endif + +#endif diff --git a/libraries/Adafruit_TinyUSB_Arduino/src/arduino/msc/Adafruit_USBH_MSC.h b/libraries/Adafruit_TinyUSB_Arduino/src/arduino/msc/Adafruit_USBH_MSC.h new file mode 100644 index 0000000..d731495 --- /dev/null +++ b/libraries/Adafruit_TinyUSB_Arduino/src/arduino/msc/Adafruit_USBH_MSC.h @@ -0,0 +1,77 @@ +/* + * The MIT License (MIT) + * + * Copyright (c) 2022 Ha Thach (tinyusb.org) for Adafruit Industries + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#ifndef ADAFRUIT_USBH_MSC_H_ +#define ADAFRUIT_USBH_MSC_H_ + +#include "tusb.h" + +// define SdFat host helper class if SdFat library is available +#if __has_include("SdFat_Adafruit_Fork.h") + +#include "SdFat_Adafruit_Fork.h" + +class Adafruit_USBH_MSC_BlockDevice : public FsBlockDeviceInterface { +public: + Adafruit_USBH_MSC_BlockDevice(); + + bool begin(uint8_t dev_addr); + void end(void); + + bool mounted(void); + + // Set active LUN + bool setActiveLUN(uint8_t lun); + void setWriteCompleteCallback(tuh_msc_complete_cb_t cb); + + //------------- SdFat v2 FsBlockDeviceInterface API -------------// + virtual bool isBusy(); + virtual uint32_t sectorCount(); + virtual bool syncDevice(); + + virtual bool readSector(uint32_t block, uint8_t *dst); + virtual bool readSectors(uint32_t block, uint8_t *dst, size_t ns); + virtual bool writeSector(uint32_t block, const uint8_t *src); + virtual bool writeSectors(uint32_t block, const uint8_t *src, size_t ns); + + //------------- Internal APIs -------------// + bool _io_complete_cb(uint8_t dev_addr, + tuh_msc_complete_data_t const *cb_data); + +private: + uint8_t _daddr; + uint8_t _lun; + + // TODO use mutex to prevent race condition or atomic for better + // implementation + volatile bool _busy; + + tuh_msc_complete_cb_t _wr_cb; + + bool wait_for_io(void); +}; + +#endif + +#endif diff --git a/libraries/Adafruit_TinyUSB_Arduino/src/arduino/ports/ch32/Adafruit_TinyUSB_ch32.cpp b/libraries/Adafruit_TinyUSB_Arduino/src/arduino/ports/ch32/Adafruit_TinyUSB_ch32.cpp new file mode 100644 index 0000000..d30629a --- /dev/null +++ b/libraries/Adafruit_TinyUSB_Arduino/src/arduino/ports/ch32/Adafruit_TinyUSB_ch32.cpp @@ -0,0 +1,191 @@ +/* + * The MIT License (MIT) + * + * Copyright (c) 2019, hathach for Adafruit + * + * 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. + */ + +#include "tusb_option.h" + +#if CFG_TUD_ENABLED && \ + (defined(ARDUINO_ARCH_CH32) || defined(CH32V20x) || defined(CH32V30x)) + +#include "Arduino.h" +#include "Adafruit_USBD_Device.h" + +//--------------------------------------------------------------------+ +// MACRO TYPEDEF CONSTANT ENUM DECLARATION +//--------------------------------------------------------------------+ + +//--------------------------------------------------------------------+ +// Forward USB interrupt events to TinyUSB IRQ Handler +//--------------------------------------------------------------------+ +extern "C" { + +// USBD (fsdev) +#if CFG_TUD_WCH_USBIP_FSDEV +__attribute__((interrupt("WCH-Interrupt-fast"))) void +USB_LP_CAN1_RX0_IRQHandler(void) { + tud_int_handler(0); +} + +__attribute__((interrupt("WCH-Interrupt-fast"))) void +USB_HP_CAN1_TX_IRQHandler(void) { + tud_int_handler(0); +} + +__attribute__((interrupt("WCH-Interrupt-fast"))) void +USBWakeUp_IRQHandler(void) { + tud_int_handler(0); +} +#endif + +// USBFS +#if CFG_TUD_WCH_USBIP_USBFS + +#if defined(CH32V10x) || defined(CH32V20x) + +#if defined(CH32V10x) +#define USBHDWakeUp_IRQHandler USBWakeUp_IRQHandler +#endif + +__attribute__((interrupt("WCH-Interrupt-fast"))) void USBHD_IRQHandler(void) { + tud_int_handler(0); +} + +__attribute__((interrupt("WCH-Interrupt-fast"))) void +USBHDWakeUp_IRQHandler(void) { + tud_int_handler(0); +} +#endif + +#ifdef CH32V30x +__attribute__((interrupt("WCH-Interrupt-fast"))) void OTG_FS_IRQHandler(void) { + tud_int_handler(0); +} +#endif + +#endif + +// USBHS +#if CFG_TUD_WCH_USBIP_USBHS +__attribute__((interrupt("WCH-Interrupt-fast"))) void USBHS_IRQHandler(void) { + tud_int_handler(0); +} +#endif + +void yield(void) { + tud_task(); + if (tud_cdc_connected()) { + tud_cdc_write_flush(); + } +} +} + +//--------------------------------------------------------------------+ +// Porting API +//--------------------------------------------------------------------+ + +void TinyUSB_Port_InitDevice(uint8_t rhport) { +#if CFG_TUD_WCH_USBIP_FSDEV || CFG_TUD_WCH_USBIP_USBFS + // Full speed OTG or FSDev + +#if defined(CH32V10x) + EXTEN->EXTEN_CTR |= EXTEN_USBHD_IO_EN; + EXTEN->EXTEN_CTR &= ~EXTEN_USB_5V_SEL; + +#define RCC_AHBPeriph_OTG_FS RCC_AHBPeriph_USBHD +#endif + + uint8_t usb_div; + switch (SystemCoreClock) { +#if defined(CH32V20x) || defined(CH32V30x) + case 48000000: + usb_div = 0; // div1 + break; + case 96000000: + usb_div = 1; // div2 + break; + case 144000000: + usb_div = 2; // div3 + break; +#elif defined(CH32V10x) + case 48000000: + usb_div = RCC_USBCLKSource_PLLCLK_Div1; + break; + case 72000000: + usb_div = RCC_USBCLKSource_PLLCLK_1Div5; + break; +#endif + default: + return; // unsupported + } + +#if defined(CH32V30x) + RCC_OTGFSCLKConfig(usb_div); +#else + RCC_USBCLKConfig(usb_div); +#endif + +#if CFG_TUD_WCH_USBIP_FSDEV + RCC_APB1PeriphClockCmd(RCC_APB1Periph_USB, ENABLE); +#endif + +#if CFG_TUD_WCH_USBIP_USBFS + RCC_AHBPeriphClockCmd(RCC_AHBPeriph_OTG_FS, ENABLE); +#endif +#endif + +#if CFG_TUD_WCH_USBIP_USBHS + // High speed USB: currently require 144MHz HSE, update later + RCC_USBCLK48MConfig(RCC_USBCLK48MCLKSource_USBPHY); + RCC_USBHSPLLCLKConfig(RCC_HSBHSPLLCLKSource_HSE); + RCC_USBHSConfig(RCC_USBPLL_Div2); + RCC_USBHSPLLCKREFCLKConfig(RCC_USBHSPLLCKREFCLK_4M); + RCC_USBHSPHYPLLALIVEcmd(ENABLE); + RCC_AHBPeriphClockCmd(RCC_AHBPeriph_USBHS, ENABLE); +#endif + + tud_init(rhport); +} + +void TinyUSB_Port_EnterDFU(void) { + // Reset to Boot ROM + // Serial1.println("Reset to Boot ROM"); + //__disable_irq(); + // tud_deinit(0); + // + // // define function pointer to BOOT ROM address + // void (*bootloader_entry)(void) = (void (*)(void))0x1FFF8000; + // bootloader_entry(); + // while(1) {} +} + +uint8_t TinyUSB_Port_GetSerialNumber(uint8_t serial_id[16]) { + volatile uint32_t *ch32_uuid = ((volatile uint32_t *)0x1FFFF7E8UL); + uint32_t *serial_32 = (uint32_t *)serial_id; + serial_32[0] = ch32_uuid[0]; + serial_32[1] = ch32_uuid[1]; + serial_32[2] = ch32_uuid[2]; + + return 12; +} + +#endif diff --git a/libraries/Adafruit_TinyUSB_Arduino/src/arduino/ports/ch32/tusb_config_ch32.h b/libraries/Adafruit_TinyUSB_Arduino/src/arduino/ports/ch32/tusb_config_ch32.h new file mode 100644 index 0000000..1591566 --- /dev/null +++ b/libraries/Adafruit_TinyUSB_Arduino/src/arduino/ports/ch32/tusb_config_ch32.h @@ -0,0 +1,184 @@ +/* + * The MIT License (MIT) + * + * Copyright (c) 2018, hathach for Adafruit + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#ifndef TUSB_CONFIG_CH32_H_ +#define TUSB_CONFIG_CH32_H_ + +#ifdef __cplusplus +extern "C" { +#endif + +//-------------------------------------------------------------------- +// COMMON CONFIGURATION +//-------------------------------------------------------------------- +#if defined(CH32V10x) +#define CFG_TUSB_MCU OPT_MCU_CH32V103 +#warnning "CH32v103 is not working yet" +#elif defined(CH32V20x) +#define CFG_TUSB_MCU OPT_MCU_CH32V20X +#elif defined(CH32V30x) +#define CFG_TUSB_MCU OPT_MCU_CH32V307 +#endif + +#define CFG_TUSB_OS OPT_OS_NONE + +#ifndef CFG_TUSB_DEBUG +#define CFG_TUSB_DEBUG 0 +#endif + +// #define SERIAL_TUSB_DEBUG Serial2 + +// For selectively disable device log (when > CFG_TUSB_DEBUG) +// #define CFG_TUD_LOG_LEVEL 3 +// #define CFG_TUH_LOG_LEVEL 3 + +#define CFG_TUSB_MEM_SECTION +#define CFG_TUSB_MEM_ALIGN __attribute__((aligned(4))) + +#define CFG_TUD_ENABLED 1 + +// #ifdef USE_TINYUSB +//// Enable device stack +// #define CFG_TUD_ENABLED 1 +// +//// Enable host stack with MAX3421E (host shield) +// #define CFG_TUH_ENABLED 1 +// #define CFG_TUH_MAX3421 1 +// +// #else +// #define CFG_TUD_ENABLED 0 +// #define CFG_TUH_ENABLED 0 +// #endif + +//-------------------------------------------------------------------- +// DEVICE CONFIGURATION +//-------------------------------------------------------------------- + +#define CFG_TUD_ENDPOINT0_SIZE 64 + +//------------- CLASS -------------// +#ifndef CFG_TUD_CDC +#define CFG_TUD_CDC 1 +#endif + +#ifndef CFG_TUD_MSC +#define CFG_TUD_MSC 1 +#endif + +#ifndef CFG_TUD_HID +#define CFG_TUD_HID 2 +#endif + +#ifndef CFG_TUD_MIDI +#define CFG_TUD_MIDI 1 +#endif + +#ifndef CFG_TUD_VENDOR +#define CFG_TUD_VENDOR 1 +#endif + +#ifndef CFG_TUD_VIDEO +#define CFG_TUD_VIDEO 0 // number of video control interfaces +#endif + +#ifndef CFG_TUD_VIDEO_STREAMING +#define CFG_TUD_VIDEO_STREAMING 0 // number of video streaming interfaces +#endif + +// video streaming endpoint buffer size +#define CFG_TUD_VIDEO_STREAMING_EP_BUFSIZE 256 + +// CDC FIFO size of TX and RX +#define CFG_TUD_CDC_RX_BUFSIZE (TUD_OPT_HIGH_SPEED ? 512 : 256) +#define CFG_TUD_CDC_TX_BUFSIZE (TUD_OPT_HIGH_SPEED ? 512 : 256) + +// MSC Buffer size of Device Mass storage +#define CFG_TUD_MSC_EP_BUFSIZE 512 + +// HID buffer size Should be sufficient to hold ID (if any) + Data +#define CFG_TUD_HID_EP_BUFSIZE 64 + +// MIDI FIFO size of TX and RX +#define CFG_TUD_MIDI_RX_BUFSIZE 128 +#define CFG_TUD_MIDI_TX_BUFSIZE 128 + +// Vendor FIFO size of TX and RX +#ifndef CFG_TUD_VENDOR_RX_BUFSIZE +#define CFG_TUD_VENDOR_RX_BUFSIZE (TUD_OPT_HIGH_SPEED ? 512 : 64) +#endif + +#ifndef CFG_TUD_VENDOR_TX_BUFSIZE +#define CFG_TUD_VENDOR_TX_BUFSIZE (TUD_OPT_HIGH_SPEED ? 512 : 64) +#endif + +//-------------------------------------------------------------------- +// Host Configuration +//-------------------------------------------------------------------- +#if 0 +// Size of buffer to hold descriptors and other data used for enumeration +#define CFG_TUH_ENUMERATION_BUFSIZE 256 + +// Number of hub devices +#define CFG_TUH_HUB 1 + +// max device support (excluding hub device): 1 hub typically has 4 ports +#define CFG_TUH_DEVICE_MAX (3 * CFG_TUH_HUB + 1) + +// Enable tuh_edpt_xfer() API +// #define CFG_TUH_API_EDPT_XFER 1 + +// Number of mass storage +#define CFG_TUH_MSC 1 + +// Number of HIDs +// typical keyboard + mouse device can have 3,4 HID interfaces +#define CFG_TUH_HID (3 * CFG_TUH_DEVICE_MAX) + +// Number of CDC interfaces +// FTDI and CP210x are not part of CDC class, only to re-use CDC driver API +#define CFG_TUH_CDC 1 +#define CFG_TUH_CDC_FTDI 1 +#define CFG_TUH_CDC_CP210X 1 +#define CFG_TUH_CDC_CH34X 1 + +// RX & TX fifo size +#define CFG_TUH_CDC_RX_BUFSIZE (TUD_OPT_HIGH_SPEED ? 512 : 64) +#define CFG_TUH_CDC_TX_BUFSIZE (TUD_OPT_HIGH_SPEED ? 512 : 64) + +// Set Line Control state on enumeration/mounted: +// DTR ( bit 0), RTS (bit 1) +#define CFG_TUH_CDC_LINE_CONTROL_ON_ENUM 0x03 + +// Set Line Coding on enumeration/mounted, value for cdc_line_coding_t +// bit rate = 115200, 1 stop bit, no parity, 8 bit data width +// This need Pico-PIO-USB at least 0.5.1 +#define CFG_TUH_CDC_LINE_CODING_ON_ENUM \ + { 115200, CDC_LINE_CONDING_STOP_BITS_1, CDC_LINE_CODING_PARITY_NONE, 8 } +#endif + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/libraries/Adafruit_TinyUSB_Arduino/src/arduino/ports/esp32/Adafruit_TinyUSB_esp32.cpp b/libraries/Adafruit_TinyUSB_Arduino/src/arduino/ports/esp32/Adafruit_TinyUSB_esp32.cpp new file mode 100644 index 0000000..0b46812 --- /dev/null +++ b/libraries/Adafruit_TinyUSB_Arduino/src/arduino/ports/esp32/Adafruit_TinyUSB_esp32.cpp @@ -0,0 +1,186 @@ +/* + * The MIT License (MIT) + * + * Copyright (c) 2019, hathach for Adafruit + * + * 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. + */ + +#include "tusb_option.h" + +#if defined(ARDUINO_ARCH_ESP32) && CFG_TUD_ENABLED + +#include + +// ESP32 will use the arduino-esp32 core initialization +// These port API is empty to prevent compilation error + +void TinyUSB_Port_EnterDFU(void) {} + +void TinyUSB_Port_InitDevice(uint8_t rhport) { (void)rhport; } + +uint8_t TinyUSB_Port_GetSerialNumber(uint8_t serial_id[16]) { + (void)serial_id; + return 0; +} + +#if 0 + +// This port implemented is not needed and left here for reference only + +#include "sdkconfig.h" + +#include "soc/soc.h" + +#include "soc/efuse_reg.h" +#include "soc/periph_defs.h" +#include "soc/rtc_cntl_reg.h" + +#include "soc/system_reg.h" +#include "soc/timer_group_struct.h" +#include "soc/usb_periph.h" +#include "soc/usb_reg.h" +#include "soc/usb_struct.h" +#include "soc/usb_wrap_reg.h" +#include "soc/usb_wrap_struct.h" + +#include "hal/usb_hal.h" + +// compiler error with gpio_ll_get_drive_capability() +// invalid conversion from 'uint32_t' {aka 'unsigned int'} to 'gpio_drive_cap_t' +// [-fpermissive] #include "hal/gpio_ll.h" + +#include "freertos/FreeRTOS.h" +#include "freertos/task.h" + +#include "driver/gpio.h" +#include "driver/periph_ctrl.h" + +#include "esp32-hal.h" +#include "esp_rom_gpio.h" + +#include "esp32s2/rom/usb/chip_usb_dw_wrapper.h" +#include "esp32s2/rom/usb/usb_dc.h" +#include "esp32s2/rom/usb/usb_persist.h" + +#include "arduino/Adafruit_TinyUSB_API.h" +#include "Adafruit_USBD_Device.h" + +//--------------------------------------------------------------------+ +// MACRO TYPEDEF CONSTANT ENUM DECLARATION +//--------------------------------------------------------------------+ + +static void configure_pins(usb_hal_context_t *usb) { + for (const usb_iopin_dsc_t *iopin = usb_periph_iopins; iopin->pin != -1; + ++iopin) { + if ((usb->use_external_phy) || (iopin->ext_phy_only == 0)) { + esp_rom_gpio_pad_select_gpio(iopin->pin); + if (iopin->is_output) { + esp_rom_gpio_connect_out_signal(iopin->pin, iopin->func, false, false); + } else { + esp_rom_gpio_connect_in_signal(iopin->pin, iopin->func, false); + if ((iopin->pin != GPIO_FUNC_IN_LOW) && + (iopin->pin != GPIO_FUNC_IN_HIGH)) { + // workaround for compiler error with including "hal/gpio_ll.h" + // gpio_ll_input_enable(&GPIO, (gpio_num_t) iopin->pin); + PIN_INPUT_ENABLE(GPIO_PIN_MUX_REG[iopin->pin]); + } + } + esp_rom_gpio_pad_unhold(iopin->pin); + } + } + if (!usb->use_external_phy) { + gpio_set_drive_capability((gpio_num_t)USBPHY_DM_NUM, GPIO_DRIVE_CAP_3); + gpio_set_drive_capability((gpio_num_t)USBPHY_DP_NUM, GPIO_DRIVE_CAP_3); + } +} + +//--------------------------------------------------------------------+ +// Porting API +//--------------------------------------------------------------------+ + +#define USBD_STACK_SZ (4096) + +// USB Device Driver task +// This top level thread processes all usb events and invokes callbacks +static void usb_device_task(void *param) { + (void)param; + // RTOS forever loop + while (1) { + tud_task(); + } +} + + +void TinyUSB_Port_InitDevice(uint8_t rhport) { + (void)rhport; + + // Reset USB module + periph_module_reset(PERIPH_USB_MODULE); + periph_module_enable(PERIPH_USB_MODULE); + + usb_hal_context_t hal = {.use_external_phy = false}; + usb_hal_init(&hal); + configure_pins(&hal); + + // reset core, should be in dcd_esp32sx.c (do that later with more proper + // testing) + USB0.grstctl |= USB_CSFTRST; + while ((USB0.grstctl & USB_CSFTRST) == USB_CSFTRST) { + } + + tusb_init(); + + // Create a task for tinyusb device stack + xTaskCreate(usb_device_task, "usbd", USBD_STACK_SZ, NULL, + configMAX_PRIORITIES - 1, NULL); +} + +void TinyUSB_Port_EnterDFU(void) { + // Reset to Bootloader + + // Reset USB Core + USB0.grstctl |= USB_CSFTRST; + while ((USB0.grstctl & USB_CSFTRST) == USB_CSFTRST) { + } + + REG_WRITE(RTC_CNTL_OPTION1_REG, RTC_CNTL_FORCE_DOWNLOAD_BOOT); + esp_restart(); +} + +uint8_t TinyUSB_Port_GetSerialNumber(uint8_t serial_id[16]) { + /* Get the MAC address */ + const uint32_t mac0 = + __builtin_bswap32(REG_GET_FIELD(EFUSE_RD_MAC_SPI_SYS_0_REG, EFUSE_MAC_0)); + const uint16_t mac1 = __builtin_bswap16( + (uint16_t)REG_GET_FIELD(EFUSE_RD_MAC_SPI_SYS_1_REG, EFUSE_MAC_1)); + + memcpy(serial_id, &mac1, 2); + memcpy(serial_id + 2, &mac0, 4); + + return 6; +} + +extern "C" void yield(void) { + TinyUSB_Device_FlushCDC(); + vPortYield(); +} +#endif // commented out + +#endif diff --git a/libraries/Adafruit_TinyUSB_Arduino/src/arduino/ports/esp32/tusb_config_esp32.h b/libraries/Adafruit_TinyUSB_Arduino/src/arduino/ports/esp32/tusb_config_esp32.h new file mode 100644 index 0000000..1fdc7e8 --- /dev/null +++ b/libraries/Adafruit_TinyUSB_Arduino/src/arduino/ports/esp32/tusb_config_esp32.h @@ -0,0 +1,197 @@ +/* + * The MIT License (MIT) + * + * Copyright (c) 2018, hathach for Adafruit + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#ifndef TUSB_CONFIG_ESP32_H_ +#define TUSB_CONFIG_ESP32_H_ + +#ifdef __cplusplus +extern "C" { +#endif + +// ESP32 Arduino core already integrated tinyusb using lib arduino_tinyusb +// https://github.com/espressif/esp32-arduino-lib-builder/tree/master/components/arduino_tinyusb +// Although it is possible to use .c files in this library for linking instead +// of lib arduino_tinyusb. However, include path of lib arduino_tinyusb is first +// in order. Therefore, changes in this Adafruit TinyUSB library's header are +// not picked up by its .c file. Due to the version difference between the 2 +// libraries, this file is used to make it compatible with ESP32 Arduino core. + +// This file also contains additional configuration for EPS32 in addition to +// tools/esp32-arduino-libs/esp32xx/include/arduino_tinyusb/include/tusb_config.h + +//--------------------------------------------------------------------+ +// ESP32 out-of-sync +//--------------------------------------------------------------------+ +#include "esp_arduino_version.h" +#include "sdkconfig.h" + +#if ESP_ARDUINO_VERSION < ESP_ARDUINO_VERSION_VAL(2, 0, 8) +#error "ESP32 Arduino core version 2.0.8 or later is required" +#endif + +//-------------------------------------------------------------------- +// COMMON CONFIGURATION +// Note: it is possible to use tinyusb + max3421e as host controller +// with no OTG USB MCU such as eps32, c3 etc... +//-------------------------------------------------------------------- + +#if CONFIG_IDF_TARGET_ESP32S2 +#define CFG_TUSB_MCU OPT_MCU_ESP32S2 +#elif CONFIG_IDF_TARGET_ESP32S3 +#define CFG_TUSB_MCU OPT_MCU_ESP32S3 +#elif CONFIG_IDF_TARGET_ESP32P4 +#define CFG_TUSB_MCU OPT_MCU_ESP32P4 +#else +#define CFG_TUSB_MCU OPT_MCU_ESP32 +#endif + +#if CONFIG_IDF_TARGET_ESP32P4 +#define CFG_TUD_MAX_SPEED OPT_MODE_HIGH_SPEED +#else +#define CFG_TUD_MAX_SPEED OPT_MODE_FULL_SPEED +#endif + +#ifndef CFG_TUSB_OS +#define CFG_TUSB_OS OPT_OS_FREERTOS +// clang-format off +#define CFG_TUSB_OS_INC_PATH freertos/ +// clang-format on +#endif + +#ifndef CFG_TUD_LOG_LEVEL +#define CFG_TUD_LOG_LEVEL 2 +#endif + +#ifndef CFG_TUH_LOG_LEVEL +#define CFG_TUH_LOG_LEVEL 2 +#endif + +#ifndef CFG_TUSB_DEBUG +#define CFG_TUSB_DEBUG 0 +#endif + +// For selectively disable device log (when > CFG_TUSB_DEBUG) +// #define CFG_TUD_LOG_LEVEL 3 + +#define CFG_TUSB_MEM_SECTION +#define CFG_TUSB_MEM_ALIGN TU_ATTR_ALIGNED(4) + +//-------------------------------------------------------------------- +// DEVICE CONFIGURATION +// only enabled if USB OTG is supported +//-------------------------------------------------------------------- +#if defined(CONFIG_USB_OTG_SUPPORTED) && CONFIG_USB_OTG_SUPPORTED +#define CFG_TUD_ENABLED 1 +#else +#define CFG_TUD_ENABLED 0 +#endif + +#define CFG_TUD_CDC 2 +#define CFG_TUD_MSC 1 +#define CFG_TUD_HID 2 +#define CFG_TUD_MIDI 1 +#define CFG_TUD_VENDOR 1 +#define CFG_TUD_VIDEO 1 +#define CFG_TUD_VIDEO_STREAMING 1 + +// video streaming endpoint buffer size +#define CFG_TUD_VIDEO_STREAMING_EP_BUFSIZE \ + CONFIG_TINYUSB_VIDEO_STREAMING_BUFSIZE + +// CDC FIFO size of TX and RX +#define CFG_TUD_CDC_RX_BUFSIZE CONFIG_TINYUSB_CDC_RX_BUFSIZE +#define CFG_TUD_CDC_TX_BUFSIZE CONFIG_TINYUSB_CDC_TX_BUFSIZE + +// MSC Buffer size of Device Mass storage +#define CFG_TUD_MSC_EP_BUFSIZE CONFIG_TINYUSB_MSC_BUFSIZE + +// HID buffer size Should be sufficient to hold ID (if any) + Data +#define CFG_TUD_HID_BUFSIZE CONFIG_TINYUSB_HID_BUFSIZE + +// MIDI FIFO size of TX and RX +#define CFG_TUD_MIDI_RX_BUFSIZE CONFIG_TINYUSB_MIDI_RX_BUFSIZE +#define CFG_TUD_MIDI_TX_BUFSIZE CONFIG_TINYUSB_MIDI_TX_BUFSIZE + +// Vendor FIFO size of TX and RX +#define CFG_TUD_VENDOR_RX_BUFSIZE CONFIG_TINYUSB_VENDOR_RX_BUFSIZE +#define CFG_TUD_VENDOR_TX_BUFSIZE CONFIG_TINYUSB_VENDOR_TX_BUFSIZE + +//-------------------------------------------------------------------- +// Host Configuration +//-------------------------------------------------------------------- + +// Enable host stack with MAX3421E (host shield) +#define CFG_TUH_ENABLED 1 +#define CFG_TUH_MAX3421 1 +#define CFG_TUH_MAX_SPEED OPT_MODE_FULL_SPEED + +#ifndef CFG_TUH_MAX3421_ENDPOINT_TOTAL +#define CFG_TUH_MAX3421_ENDPOINT_TOTAL (8 + 4 * (CFG_TUH_DEVICE_MAX - 1)) +#endif + +// Size of buffer to hold descriptors and other data used for enumeration +#define CFG_TUH_ENUMERATION_BUFSIZE 256 + +// Number of hub devices +#define CFG_TUH_HUB 1 + +// max device support (excluding hub device): 1 hub typically has 4 ports +#define CFG_TUH_DEVICE_MAX (3 * CFG_TUH_HUB + 1) + +// Enable tuh_edpt_xfer() API +// #define CFG_TUH_API_EDPT_XFER 1 + +// Number of mass storage +#define CFG_TUH_MSC 1 + +// Number of HIDs +// typical keyboard + mouse device can have 3,4 HID interfaces +#define CFG_TUH_HID (3 * CFG_TUH_DEVICE_MAX) + +// Number of CDC interfaces +// FTDI and CP210x are not part of CDC class, only to re-use CDC driver API +#define CFG_TUH_CDC 1 +#define CFG_TUH_CDC_FTDI 1 +#define CFG_TUH_CDC_CP210X 1 +#define CFG_TUH_CDC_CH34X 1 + +// RX & TX fifo size +#define CFG_TUH_CDC_RX_BUFSIZE 64 +#define CFG_TUH_CDC_TX_BUFSIZE 64 + +// Set Line Control state on enumeration/mounted: +// DTR ( bit 0), RTS (bit 1) +#define CFG_TUH_CDC_LINE_CONTROL_ON_ENUM 0x03 + +// Set Line Coding on enumeration/mounted, value for cdc_line_coding_t +// bit rate = 115200, 1 stop bit, no parity, 8 bit data width +// This need Pico-PIO-USB at least 0.5.1 +#define CFG_TUH_CDC_LINE_CODING_ON_ENUM \ + { 115200, CDC_LINE_CONDING_STOP_BITS_1, CDC_LINE_CODING_PARITY_NONE, 8 } + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/cores/nRF5/TinyUSB/Adafruit_TinyUSB_nRF.cpp b/libraries/Adafruit_TinyUSB_Arduino/src/arduino/ports/nrf/Adafruit_TinyUSB_nrf.cpp similarity index 67% rename from cores/nRF5/TinyUSB/Adafruit_TinyUSB_nRF.cpp rename to libraries/Adafruit_TinyUSB_Arduino/src/arduino/ports/nrf/Adafruit_TinyUSB_nrf.cpp index d3feed4..094ba09 100644 --- a/cores/nRF5/TinyUSB/Adafruit_TinyUSB_nRF.cpp +++ b/libraries/Adafruit_TinyUSB_Arduino/src/arduino/ports/nrf/Adafruit_TinyUSB_nrf.cpp @@ -22,29 +22,26 @@ * THE SOFTWARE. */ -#ifdef USE_TINYUSB +#include "tusb_option.h" + +#if defined ARDUINO_NRF52_ADAFRUIT && CFG_TUD_ENABLED #include "nrfx.h" #include "nrfx_power.h" #include "Arduino.h" -#include "Adafruit_TinyUSB_Core.h" +#include "Adafruit_USBD_Device.h" //--------------------------------------------------------------------+ // MACRO TYPEDEF CONSTANT ENUM DECLARATION //--------------------------------------------------------------------+ -#define USBD_STACK_SZ (200) - -// tinyusb function that handles power event (detected, ready, removed) -// We must call it within SD's SOC event handler, or set it as power event handler if SD is not enabled. -extern "C" void tusb_hal_nrf_power_event(uint32_t event); +#define USBD_STACK_SZ (200) //--------------------------------------------------------------------+ // Forward USB interrupt events to TinyUSB IRQ Handler //--------------------------------------------------------------------+ -extern "C" void USBD_IRQHandler(void) -{ +extern "C" void USBD_IRQHandler(void) { #if CFG_SYSVIEW SEGGER_SYSVIEW_RecordEnterISR(); #endif @@ -57,82 +54,86 @@ extern "C" void USBD_IRQHandler(void) } //--------------------------------------------------------------------+ -// Core Init & Touch1200 +// Porting API //--------------------------------------------------------------------+ -// Init usb hardware when starting up. Softdevice is not enabled yet -static void usb_hardware_init(void) -{ - // Priorities 0, 1, 4 (nRF52) are reserved for SoftDevice - // 2 is highest for application - NVIC_SetPriority(USBD_IRQn, 2); - - // USB power may already be ready at this time -> no event generated - // We need to invoke the handler based on the status initially - uint32_t usb_reg = NRF_POWER->USBREGSTATUS; - - // Power module init - const nrfx_power_config_t pwr_cfg = { 0, 0 }; - nrfx_power_init(&pwr_cfg); - - // Register tusb function as USB power handler - const nrfx_power_usbevt_config_t config = { .handler = (nrfx_power_usb_event_handler_t) (void*)tusb_hal_nrf_power_event }; - nrfx_power_usbevt_init(&config); - - nrfx_power_usbevt_enable(); - - if ( usb_reg & POWER_USBREGSTATUS_VBUSDETECT_Msk ) tusb_hal_nrf_power_event(NRFX_POWER_USB_EVT_DETECTED); - if ( usb_reg & POWER_USBREGSTATUS_OUTPUTRDY_Msk ) tusb_hal_nrf_power_event(NRFX_POWER_USB_EVT_READY); -} +static void usb_hardware_init(void); // USB Device Driver task // This top level thread process all usb events and invoke callbacks -static void usb_device_task(void* param) -{ - (void) param; +static void usb_device_task(void *param) { + (void)param; + + // Priorities 0, 1, 4 (nRF52) are reserved for SoftDevice + // 2 is highest for application + NVIC_SetPriority(USBD_IRQn, 2); - Serial.setStringDescriptor("TinyUSB Serial"); - USBDevice.addInterface(Serial); - USBDevice.setID(USB_VID, USB_PID); - USBDevice.begin(); + // init device on rhport0 + tud_init(0); usb_hardware_init(); - // Init tinyusb stack - tusb_init(); - // RTOS forever loop - while (1) - { - // tinyusb device task + while (1) { tud_task(); + TinyUSB_Device_FlushCDC(); } } -void Adafruit_TinyUSB_Core_init(void) -{ +void TinyUSB_Port_InitDevice(uint8_t rhport) { + (void)rhport; + // Create a task for tinyusb device stack - xTaskCreate(usb_device_task, "usbd", USBD_STACK_SZ, NULL, (configMAX_PRIORITIES - 1), NULL); + xTaskCreate(usb_device_task, "usbd", USBD_STACK_SZ, NULL, TASK_PRIO_HIGH, + NULL); } -void Adafruit_TinyUSB_Core_touch1200(void) -{ - delay(5); // a few millisecond for USB control status completion +void TinyUSB_Port_EnterDFU(void) { + // Reset to Bootloader enterSerialDfu(); } +uint8_t TinyUSB_Port_GetSerialNumber(uint8_t serial_id[16]) { + uint32_t *serial_32 = (uint32_t *)serial_id; + + serial_32[0] = __builtin_bswap32(NRF_FICR->DEVICEID[1]); + serial_32[1] = __builtin_bswap32(NRF_FICR->DEVICEID[0]); + + return 8; +} + //--------------------------------------------------------------------+ -// Adafruit_USBD_Device platform dependent +// Helper //--------------------------------------------------------------------+ -uint8_t Adafruit_USBD_Device::getSerialDescriptor(uint16_t* serial_str) -{ - // Serial is 64-bit DeviceID -> 16 chars len - char tmp_serial[17]; - sprintf(tmp_serial, "%08lX%08lX", NRF_FICR->DEVICEID[1], NRF_FICR->DEVICEID[0]); +// tinyusb function that handles power event (detected, ready, removed) +// We must call it within SD's SOC event handler, or set it as power event +// handler if SD is not enabled. +extern "C" void tusb_hal_nrf_power_event(uint32_t event); + +static void power_event_handler(nrfx_power_usb_evt_t event) { + tusb_hal_nrf_power_event((uint32_t)event); +} - for(uint8_t i=0; i<16; i++) serial_str[i] = tmp_serial[i]; - return 16; +// Init usb hardware when starting up. Softdevice is not enabled yet +static void usb_hardware_init(void) { + // USB power may already be ready at this time -> no event generated + // We need to invoke the handler based on the status initially + uint32_t usb_reg = NRF_POWER->USBREGSTATUS; + + // Power module init + const nrfx_power_config_t pwr_cfg = {0}; + nrfx_power_init(&pwr_cfg); + + // Register tusb function as USB power handler + const nrfx_power_usbevt_config_t config = {.handler = power_event_handler}; + + nrfx_power_usbevt_init(&config); + nrfx_power_usbevt_enable(); + + if (usb_reg & POWER_USBREGSTATUS_VBUSDETECT_Msk) { + tusb_hal_nrf_power_event(NRFX_POWER_USB_EVT_DETECTED); + } } #endif // USE_TINYUSB diff --git a/libraries/Adafruit_TinyUSB_Arduino/src/arduino/ports/nrf/tusb_config_nrf.h b/libraries/Adafruit_TinyUSB_Arduino/src/arduino/ports/nrf/tusb_config_nrf.h new file mode 100644 index 0000000..4472f46 --- /dev/null +++ b/libraries/Adafruit_TinyUSB_Arduino/src/arduino/ports/nrf/tusb_config_nrf.h @@ -0,0 +1,173 @@ +/* + * The MIT License (MIT) + * + * Copyright (c) 2018, hathach for Adafruit + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#ifndef _TUSB_CONFIG_NRF_H_ +#define _TUSB_CONFIG_NRF_H_ + +#ifdef __cplusplus +extern "C" { +#endif + +//-------------------------------------------------------------------- +// COMMON CONFIGURATION +//-------------------------------------------------------------------- +#define CFG_TUSB_MCU OPT_MCU_NRF5X +#define CFG_TUSB_OS OPT_OS_FREERTOS + +#ifndef CFG_TUSB_DEBUG +#define CFG_TUSB_DEBUG 0 +#endif + +// For selectively disable device log (when > CFG_TUSB_DEBUG) +// #define CFG_TUD_LOG_LEVEL 3 +// #define CFG_TUH_LOG_LEVEL 3 + +#define CFG_TUSB_MEM_SECTION +#define CFG_TUSB_MEM_ALIGN __attribute__((aligned(4))) + +#ifdef USE_TINYUSB + +// Enable device stack +#define CFG_TUD_ENABLED 1 + +// Enable host stack with MAX3421E (host shield) +#define CFG_TUH_ENABLED 1 +#define CFG_TUH_MAX3421 1 + +#else +#define CFG_TUD_ENABLED 0 +#define CFG_TUH_ENABLED 0 +#endif + +//-------------------------------------------------------------------- +// DEVICE CONFIGURATION +//-------------------------------------------------------------------- + +#if CFG_TUD_ENABLED +#define CFG_TUD_ENDPOINT0_SIZE 64 + +//------------- CLASS -------------// +#ifndef CFG_TUD_CDC +#define CFG_TUD_CDC 1 +#endif +#ifndef CFG_TUD_MSC +#define CFG_TUD_MSC 1 +#endif +#ifndef CFG_TUD_HID +#define CFG_TUD_HID 2 +#endif +#ifndef CFG_TUD_MIDI +#define CFG_TUD_MIDI 1 +#endif +#ifndef CFG_TUD_VENDOR +#define CFG_TUD_VENDOR 1 +#endif +#ifndef CFG_TUD_VIDEO +#define CFG_TUD_VIDEO 1 // number of video control interfaces +#endif +#ifndef CFG_TUD_VIDEO_STREAMING +#define CFG_TUD_VIDEO_STREAMING 1 // number of video streaming interfaces +#endif + +// video streaming endpoint buffer size +#define CFG_TUD_VIDEO_STREAMING_EP_BUFSIZE 256 + +// CDC FIFO size of TX and RX +#define CFG_TUD_CDC_RX_BUFSIZE 256 +#define CFG_TUD_CDC_TX_BUFSIZE 256 + +// MSC Buffer size of Device Mass storage +#define CFG_TUD_MSC_EP_BUFSIZE 512 + +// HID buffer size Should be sufficient to hold ID (if any) + Data +#define CFG_TUD_HID_EP_BUFSIZE 64 + +// MIDI FIFO size of TX and RX +#define CFG_TUD_MIDI_RX_BUFSIZE 128 +#define CFG_TUD_MIDI_TX_BUFSIZE 128 + +// Vendor FIFO size of TX and RX +#ifndef CFG_TUD_VENDOR_RX_BUFSIZE +#define CFG_TUD_VENDOR_RX_BUFSIZE 64 +#endif + +#ifndef CFG_TUD_VENDOR_TX_BUFSIZE +#define CFG_TUD_VENDOR_TX_BUFSIZE 64 +#endif + +#endif + +//-------------------------------------------------------------------- +// Host Configuration +//-------------------------------------------------------------------- + +#if CFG_TUH_ENABLED + +// Size of buffer to hold descriptors and other data used for enumeration +#define CFG_TUH_ENUMERATION_BUFSIZE 256 + +// Number of hub devices +#define CFG_TUH_HUB 1 + +// max device support (excluding hub device): 1 hub typically has 4 ports +#define CFG_TUH_DEVICE_MAX (3 * CFG_TUH_HUB + 1) + +// Enable tuh_edpt_xfer() API +// #define CFG_TUH_API_EDPT_XFER 1 + +// Number of mass storage +#define CFG_TUH_MSC 1 + +// Number of HIDs +// typical keyboard + mouse device can have 3,4 HID interfaces +#define CFG_TUH_HID (3 * CFG_TUH_DEVICE_MAX) + +// Number of CDC interfaces +// FTDI and CP210x are not part of CDC class, only to re-use CDC driver API +#define CFG_TUH_CDC 1 +#define CFG_TUH_CDC_FTDI 1 +#define CFG_TUH_CDC_CP210X 1 +#define CFG_TUH_CDC_CH34X 1 + +// RX & TX fifo size +#define CFG_TUH_CDC_RX_BUFSIZE 64 +#define CFG_TUH_CDC_TX_BUFSIZE 64 + +// Set Line Control state on enumeration/mounted: +// DTR ( bit 0), RTS (bit 1) +#define CFG_TUH_CDC_LINE_CONTROL_ON_ENUM 0x03 + +// Set Line Coding on enumeration/mounted, value for cdc_line_coding_t +// bit rate = 115200, 1 stop bit, no parity, 8 bit data width +// This need Pico-PIO-USB at least 0.5.1 +#define CFG_TUH_CDC_LINE_CODING_ON_ENUM \ + { 115200, CDC_LINE_CONDING_STOP_BITS_1, CDC_LINE_CODING_PARITY_NONE, 8 } + +#endif + +#ifdef __cplusplus +} +#endif + +#endif /* _TUSB_CONFIG_NRF_H_ */ diff --git a/libraries/Adafruit_TinyUSB_Arduino/src/arduino/ports/rp2040/Adafruit_TinyUSB_rp2040.cpp b/libraries/Adafruit_TinyUSB_Arduino/src/arduino/ports/rp2040/Adafruit_TinyUSB_rp2040.cpp new file mode 100644 index 0000000..dde534c --- /dev/null +++ b/libraries/Adafruit_TinyUSB_Arduino/src/arduino/ports/rp2040/Adafruit_TinyUSB_rp2040.cpp @@ -0,0 +1,143 @@ +/* + * The MIT License (MIT) + * + * Copyright (c) 2019, hathach for Adafruit + * + * 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. + */ + +#include "tusb_option.h" + +#if defined ARDUINO_ARCH_RP2040 && CFG_TUD_ENABLED + +#include "Arduino.h" + +// mbed old pico-sdk need to wrap with cpp +extern "C" { +#include "hardware/flash.h" +#include "hardware/irq.h" +#include "pico/bootrom.h" +#include "pico/mutex.h" +#include "pico/time.h" +} + +#include "arduino/Adafruit_TinyUSB_API.h" +#include "tusb.h" + +// SDK >= 1.4.0 need to dynamically request the IRQ to avoid conflicts +#if (PICO_SDK_VERSION_MAJOR * 100 + PICO_SDK_VERSION_MINOR) < 104 +#define USB_TASK_IRQ 31 +#else +static unsigned int USB_TASK_IRQ; +#endif + +//--------------------------------------------------------------------+ +// Forward USB interrupt events to TinyUSB IRQ Handler +// rp2040 implementation will install appropriate handler when initializing +// tinyusb. There is no need to forward IRQ from application +//--------------------------------------------------------------------+ + +//--------------------------------------------------------------------+ +// Earle Philhower and mbed specific +//--------------------------------------------------------------------+ + +// mbed use old pico-sdk does not have unique_id +#ifdef ARDUINO_ARCH_MBED + +#define get_unique_id(_serial) flash_get_unique_id(_serial) + +#else + +#include "pico/unique_id.h" +#define get_unique_id(_serial) \ + pico_get_unique_board_id((pico_unique_board_id_t *)_serial); + +#endif + +//--------------------------------------------------------------------+ +// Porting API +//--------------------------------------------------------------------+ + +// Big, global USB mutex, shared with all USB devices to make sure we don't +// have multiple cores updating the TUSB state in parallel +mutex_t __usb_mutex; + +static void usb_task_irq(void) { + // if the mutex is already owned, then we are in user code + // in this file which will do a tud_task itself, so we'll just do nothing + // until the next tick; we won't starve + if (mutex_try_enter(&__usb_mutex, NULL)) { + tud_task(); + mutex_exit(&__usb_mutex); + } +} + +#ifndef PICO_SHARED_IRQ_HANDLER_LOWEST_ORDER_PRIORITY +#define PICO_SHARED_IRQ_HANDLER_LOWEST_ORDER_PRIORITY 0x00 +#endif + +// invoked when there is hardware usb irq, trigger task runner later +static void usb_task_trigger_irq(void) { irq_set_pending(USB_TASK_IRQ); } + +void TinyUSB_Port_InitDevice(uint8_t rhport) { + mutex_init(&__usb_mutex); + + tud_init(rhport); + + // soft irq for task runner +#if (PICO_SDK_VERSION_MAJOR * 100 + PICO_SDK_VERSION_MINOR) >= 104 + USB_TASK_IRQ = user_irq_claim_unused(true); +#endif + irq_set_exclusive_handler(USB_TASK_IRQ, usb_task_irq); + irq_set_enabled(USB_TASK_IRQ, true); + + // add shared irq to trigger task runner + irq_add_shared_handler(USBCTRL_IRQ, usb_task_trigger_irq, + PICO_SHARED_IRQ_HANDLER_LOWEST_ORDER_PRIORITY); +} + +void TinyUSB_Port_EnterDFU(void) { + reset_usb_boot(0, 0); + while (1) { + } +} + +uint8_t TinyUSB_Port_GetSerialNumber(uint8_t serial_id[16]) { + get_unique_id(serial_id); + return FLASH_UNIQUE_ID_SIZE_BYTES; +} + +//--------------------------------------------------------------------+ +// Core API +// Implement Core API since rp2040 need mutex for calling tud_task in +// IRQ context +//--------------------------------------------------------------------+ + +extern "C" { + +void TinyUSB_Device_Task(void) { + // Since tud_task() is also invoked in ISR, we need to get the mutex first + if (mutex_try_enter(&__usb_mutex, NULL)) { + tud_task(); + mutex_exit(&__usb_mutex); + } +} +} + +#endif diff --git a/libraries/Adafruit_TinyUSB_Arduino/src/arduino/ports/rp2040/tusb_config_rp2040.h b/libraries/Adafruit_TinyUSB_Arduino/src/arduino/ports/rp2040/tusb_config_rp2040.h new file mode 100644 index 0000000..2a207c5 --- /dev/null +++ b/libraries/Adafruit_TinyUSB_Arduino/src/arduino/ports/rp2040/tusb_config_rp2040.h @@ -0,0 +1,175 @@ +/* + The MIT License (MIT) + + Copyright (c) 2018, hathach for Adafruit + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to + deal in the Software without restriction, including without limitation the + rights to use, copy, modify, merge, publish, distribute, sublicense, and/or + sell copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + IN THE SOFTWARE. +*/ + +#ifndef _TUSB_CONFIG_RP2040_H_ +#define _TUSB_CONFIG_RP2040_H_ + +#ifdef __cplusplus +extern "C" { +#endif + +//-------------------------------------------------------------------- +// COMMON CONFIGURATION +//-------------------------------------------------------------------- + +#ifdef USE_TINYUSB_HOST +// native as host +#define CFG_TUD_ENABLED 0 +#define CFG_TUH_ENABLED 1 +#define CFG_TUH_RPI_PIO_USB 0 + +#else +// native as device +#define CFG_TUD_ENABLED 1 + +#if __has_include("pio_usb.h") +// Enable host stack with pio-usb if Pico-PIO-USB library is available +#define CFG_TUH_ENABLED 1 +#define CFG_TUH_RPI_PIO_USB 1 + +#else +// Otherwise enable host controller with MAX3421E +#define CFG_TUH_ENABLED 1 +#define CFG_TUH_MAX3421 1 + +#endif // pio_usb.h +#endif // USE_TINYUSB_HOST + +#ifndef CFG_TUSB_MCU +#define CFG_TUSB_MCU OPT_MCU_RP2040 +#endif + +#ifndef CFG_TUSB_OS +#define CFG_TUSB_OS OPT_OS_PICO +#endif + +#ifndef CFG_TUSB_DEBUG +#define CFG_TUSB_DEBUG 0 +#endif + +// For selectively disable device log (when > CFG_TUSB_DEBUG) +// #define CFG_TUD_LOG_LEVEL 3 +// #define CFG_TUH_LOG_LEVEL 3 + +#define CFG_TUSB_MEM_SECTION +#define CFG_TUSB_MEM_ALIGN TU_ATTR_ALIGNED(4) + +//-------------------------------------------------------------------- +// Device Configuration +//-------------------------------------------------------------------- + +#define CFG_TUD_ENDPOINT0_SIZE 64 + +#ifndef CFG_TUD_CDC +#define CFG_TUD_CDC 2 +#endif +#ifndef CFG_TUD_MSC +#define CFG_TUD_MSC 1 +#endif +#ifndef CFG_TUD_HID +#define CFG_TUD_HID 2 +#endif +#ifndef CFG_TUD_MIDI +#define CFG_TUD_MIDI 1 +#endif +#ifndef CFG_TUD_VENDOR +#define CFG_TUD_VENDOR 1 +#endif +#ifndef CFG_TUD_VIDEO +#define CFG_TUD_VIDEO 1 // number of video control interfaces +#endif +#ifndef CFG_TUD_VIDEO_STREAMING +#define CFG_TUD_VIDEO_STREAMING 1 // number of video streaming interfaces +#endif + +// video streaming endpoint buffer size +#define CFG_TUD_VIDEO_STREAMING_EP_BUFSIZE 256 + +// CDC FIFO size of TX and RX +#define CFG_TUD_CDC_RX_BUFSIZE 256 +#define CFG_TUD_CDC_TX_BUFSIZE 256 + +// MSC Buffer size of Device Mass storage +#define CFG_TUD_MSC_EP_BUFSIZE 512 + +// HID buffer size Should be sufficient to hold ID (if any) + Data +#define CFG_TUD_HID_EP_BUFSIZE 64 + +// MIDI FIFO size of TX and RX +#define CFG_TUD_MIDI_RX_BUFSIZE 64 +#define CFG_TUD_MIDI_TX_BUFSIZE 64 + +// Vendor FIFO size of TX and RX +#define CFG_TUD_VENDOR_RX_BUFSIZE 64 +#define CFG_TUD_VENDOR_TX_BUFSIZE 64 + +//-------------------------------------------------------------------- +// Host Configuration +//-------------------------------------------------------------------- + +// Size of buffer to hold descriptors and other data used for enumeration +#define CFG_TUH_ENUMERATION_BUFSIZE 256 + +// Number of hub devices +#define CFG_TUH_HUB 1 + +// max device support (excluding hub device): 1 hub typically has 4 ports +#define CFG_TUH_DEVICE_MAX (3 * CFG_TUH_HUB + 1) + +// Enable tuh_edpt_xfer() API +// #define CFG_TUH_API_EDPT_XFER 1 + +// Number of mass storage +#define CFG_TUH_MSC 1 + +// Number of HIDs +// typical keyboard + mouse device can have 3,4 HID interfaces +#define CFG_TUH_HID (3 * CFG_TUH_DEVICE_MAX) + +// Number of CDC interfaces +// FTDI and CP210x are not part of CDC class, only to re-use CDC driver API +#define CFG_TUH_CDC 1 +#define CFG_TUH_CDC_FTDI 1 +#define CFG_TUH_CDC_CP210X 1 +#define CFG_TUH_CDC_CH34X 1 + +// RX & TX fifo size +#define CFG_TUH_CDC_RX_BUFSIZE 128 +#define CFG_TUH_CDC_TX_BUFSIZE 128 + +// Set Line Control state on enumeration/mounted: +// DTR ( bit 0), RTS (bit 1) +#define CFG_TUH_CDC_LINE_CONTROL_ON_ENUM 0x03 + +// Set Line Coding on enumeration/mounted, value for cdc_line_coding_t +// bit rate = 115200, 1 stop bit, no parity, 8 bit data width +// This need Pico-PIO-USB at least 0.5.1 +#define CFG_TUH_CDC_LINE_CODING_ON_ENUM \ + { 115200, CDC_LINE_CODING_STOP_BITS_1, CDC_LINE_CODING_PARITY_NONE, 8 } + +#ifdef __cplusplus +} +#endif + +#endif /* _TUSB_CONFIG_RP2040_H_ */ diff --git a/libraries/Adafruit_TinyUSB_Arduino/src/arduino/ports/samd/Adafruit_TinyUSB_samd.cpp b/libraries/Adafruit_TinyUSB_Arduino/src/arduino/ports/samd/Adafruit_TinyUSB_samd.cpp new file mode 100644 index 0000000..e5aee7b --- /dev/null +++ b/libraries/Adafruit_TinyUSB_Arduino/src/arduino/ports/samd/Adafruit_TinyUSB_samd.cpp @@ -0,0 +1,142 @@ +/* + * The MIT License (MIT) + * + * Copyright (c) 2019, hathach for Adafruit + * + * 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. + */ + +#include "tusb_option.h" + +#if defined ARDUINO_ARCH_SAMD && CFG_TUD_ENABLED + +#include "Arduino.h" +#include // Needed for auto-reset with 1200bps port touch + +#include "arduino/Adafruit_TinyUSB_API.h" +#include "tusb.h" + +//--------------------------------------------------------------------+ +// Forward USB interrupt events to TinyUSB IRQ Handler +//--------------------------------------------------------------------+ +extern "C" { + +#if CFG_TUSB_MCU == OPT_MCU_SAMD51 || CFG_TUSB_MCU == OPT_MCU_SAME5X + +// SAMD51 +void USB_0_Handler(void) { tud_int_handler(0); } +void USB_1_Handler(void) { tud_int_handler(0); } +void USB_2_Handler(void) { tud_int_handler(0); } +void USB_3_Handler(void) { tud_int_handler(0); } + +#elif CFG_TUSB_MCU == OPT_MCU_SAMD21 + +// SAMD21 +void USB_Handler(void) { tud_int_handler(0); } + +#endif + +} // extern C + +//--------------------------------------------------------------------+ +// Porting API +//--------------------------------------------------------------------+ +void TinyUSB_Port_InitDevice(uint8_t rhport) { + (void)rhport; + + /* Enable USB clock */ +#if defined(__SAMD51__) + MCLK->APBBMASK.reg |= MCLK_APBBMASK_USB; + MCLK->AHBMASK.reg |= MCLK_AHBMASK_USB; + + // Set up the USB DP/DN pins + PORT->Group[0].PINCFG[PIN_PA24H_USB_DM].bit.PMUXEN = 1; + PORT->Group[0].PMUX[PIN_PA24H_USB_DM / 2].reg &= + ~(0xF << (4 * (PIN_PA24H_USB_DM & 0x01u))); + PORT->Group[0].PMUX[PIN_PA24H_USB_DM / 2].reg |= + MUX_PA24H_USB_DM << (4 * (PIN_PA24H_USB_DM & 0x01u)); + PORT->Group[0].PINCFG[PIN_PA25H_USB_DP].bit.PMUXEN = 1; + PORT->Group[0].PMUX[PIN_PA25H_USB_DP / 2].reg &= + ~(0xF << (4 * (PIN_PA25H_USB_DP & 0x01u))); + PORT->Group[0].PMUX[PIN_PA25H_USB_DP / 2].reg |= + MUX_PA25H_USB_DP << (4 * (PIN_PA25H_USB_DP & 0x01u)); + + GCLK->PCHCTRL[USB_GCLK_ID].reg = + GCLK_PCHCTRL_GEN_GCLK1_Val | (1 << GCLK_PCHCTRL_CHEN_Pos); + + NVIC_SetPriority(USB_0_IRQn, 0UL); + NVIC_SetPriority(USB_1_IRQn, 0UL); + NVIC_SetPriority(USB_2_IRQn, 0UL); + NVIC_SetPriority(USB_3_IRQn, 0UL); +#else + PM->APBBMASK.reg |= PM_APBBMASK_USB; + + // Set up the USB DP/DN pins + PORT->Group[0].PINCFG[PIN_PA24G_USB_DM].bit.PMUXEN = 1; + PORT->Group[0].PMUX[PIN_PA24G_USB_DM / 2].reg &= + ~(0xF << (4 * (PIN_PA24G_USB_DM & 0x01u))); + PORT->Group[0].PMUX[PIN_PA24G_USB_DM / 2].reg |= + MUX_PA24G_USB_DM << (4 * (PIN_PA24G_USB_DM & 0x01u)); + PORT->Group[0].PINCFG[PIN_PA25G_USB_DP].bit.PMUXEN = 1; + PORT->Group[0].PMUX[PIN_PA25G_USB_DP / 2].reg &= + ~(0xF << (4 * (PIN_PA25G_USB_DP & 0x01u))); + PORT->Group[0].PMUX[PIN_PA25G_USB_DP / 2].reg |= + MUX_PA25G_USB_DP << (4 * (PIN_PA25G_USB_DP & 0x01u)); + + // Put Generic Clock Generator 0 as source for Generic Clock Multiplexer 6 + // (USB reference) + GCLK->CLKCTRL.reg = + GCLK_CLKCTRL_ID(6) | // Generic Clock Multiplexer 6 + GCLK_CLKCTRL_GEN_GCLK0 | // Generic Clock Generator 0 is source + GCLK_CLKCTRL_CLKEN; + while (GCLK->STATUS.bit.SYNCBUSY) { + // blocking wait + } + + NVIC_SetPriority((IRQn_Type)USB_IRQn, 0UL); +#endif + + // Init port 0 as device + tud_init(0); +} + +void TinyUSB_Port_EnterDFU(void) { + // Reset to bootloader + initiateReset(250); +} + +uint8_t TinyUSB_Port_GetSerialNumber(uint8_t serial_id[16]) { +#ifdef __SAMD51__ + uint32_t *id_addresses[4] = {(uint32_t *)0x008061FC, (uint32_t *)0x00806010, + (uint32_t *)0x00806014, (uint32_t *)0x00806018}; +#else // samd21 + uint32_t *id_addresses[4] = {(uint32_t *)0x0080A00C, (uint32_t *)0x0080A040, + (uint32_t *)0x0080A044, (uint32_t *)0x0080A048}; +#endif + + uint32_t *serial_32 = (uint32_t *)serial_id; + + for (int i = 0; i < 4; i++) { + *serial_32++ = __builtin_bswap32(*id_addresses[i]); + } + + return 16; +} + +#endif diff --git a/libraries/Adafruit_TinyUSB_Arduino/src/arduino/ports/samd/tusb_config_samd.h b/libraries/Adafruit_TinyUSB_Arduino/src/arduino/ports/samd/tusb_config_samd.h new file mode 100644 index 0000000..9de0902 --- /dev/null +++ b/libraries/Adafruit_TinyUSB_Arduino/src/arduino/ports/samd/tusb_config_samd.h @@ -0,0 +1,161 @@ +/* + * The MIT License (MIT) + * + * Copyright (c) 2018, hathach for Adafruit + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#ifndef _TUSB_CONFIG_SAMD_H_ +#define _TUSB_CONFIG_SAMD_H_ + +#ifdef __cplusplus +extern "C" { +#endif + +//-------------------------------------------------------------------- +// COMMON CONFIGURATION +//-------------------------------------------------------------------- +#ifdef __SAMD51__ +#define CFG_TUSB_MCU OPT_MCU_SAMD51 +#else +#define CFG_TUSB_MCU OPT_MCU_SAMD21 +#endif + +#ifndef CFG_TUSB_OS +#define CFG_TUSB_OS OPT_OS_NONE +#endif + +#ifndef CFG_TUSB_DEBUG +#define CFG_TUSB_DEBUG 0 +#endif + +// For selectively disable device log (when > CFG_TUSB_DEBUG) +// #define CFG_TUD_LOG_LEVEL 3 +// #define CFG_TUH_LOG_LEVEL 3 + +#define CFG_TUSB_MEM_SECTION +#define CFG_TUSB_MEM_ALIGN TU_ATTR_ALIGNED(4) + +// Enable device stack +#define CFG_TUD_ENABLED 1 + +// Enable host stack with MAX3421E (host shield) +#define CFG_TUH_ENABLED 1 +#define CFG_TUH_MAX3421 1 + +//-------------------------------------------------------------------- +// DEVICE CONFIGURATION +//-------------------------------------------------------------------- + +#define CFG_TUD_ENDPOINT0_SIZE 64 + +//------------- CLASS -------------// +#ifndef CFG_TUD_CDC +#define CFG_TUD_CDC 1 +#endif +#ifndef CFG_TUD_MSC +#define CFG_TUD_MSC 1 +#endif +#ifndef CFG_TUD_HID +#define CFG_TUD_HID 2 +#endif +#ifndef CFG_TUD_MIDI +#define CFG_TUD_MIDI 1 +#endif +#ifndef CFG_TUD_VENDOR +#define CFG_TUD_VENDOR 1 +#endif +#ifndef CFG_TUD_VIDEO +#define CFG_TUD_VIDEO 1 // number of video control interfaces +#endif +#ifndef CFG_TUD_VIDEO_STREAMING +#define CFG_TUD_VIDEO_STREAMING 1 // number of video streaming interfaces +#endif + +// video streaming endpoint buffer size +#define CFG_TUD_VIDEO_STREAMING_EP_BUFSIZE 256 + +// CDC FIFO size of TX and RX +#define CFG_TUD_CDC_RX_BUFSIZE 256 +#define CFG_TUD_CDC_TX_BUFSIZE 256 + +// MSC Buffer size of Device Mass storage +#define CFG_TUD_MSC_EP_BUFSIZE 512 + +// HID buffer size Should be sufficient to hold ID (if any) + Data +#define CFG_TUD_HID_EP_BUFSIZE 64 + +// MIDI FIFO size of TX and RX +#define CFG_TUD_MIDI_RX_BUFSIZE 128 +#define CFG_TUD_MIDI_TX_BUFSIZE 128 + +// Vendor FIFO size of TX and RX +#define CFG_TUD_VENDOR_RX_BUFSIZE 64 +#define CFG_TUD_VENDOR_TX_BUFSIZE 64 + +//-------------------------------------------------------------------- +// Host Configuration +//-------------------------------------------------------------------- + +// Size of buffer to hold descriptors and other data used for enumeration +#define CFG_TUH_ENUMERATION_BUFSIZE 256 + +// Number of hub devices +#define CFG_TUH_HUB 1 + +// max device support (excluding hub device): 1 hub typically has 4 ports +#define CFG_TUH_DEVICE_MAX (3 * CFG_TUH_HUB + 1) + +// Enable tuh_edpt_xfer() API +// #define CFG_TUH_API_EDPT_XFER 1 + +// Number of mass storage +#define CFG_TUH_MSC 1 + +// Number of HIDs +// typical keyboard + mouse device can have 3,4 HID interfaces +#define CFG_TUH_HID (3 * CFG_TUH_DEVICE_MAX) + +// Number of CDC interfaces +// FTDI and CP210x are not part of CDC class, only to re-use CDC driver API +#define CFG_TUH_CDC 1 +#define CFG_TUH_CDC_FTDI 1 +#define CFG_TUH_CDC_CP210X 1 +#define CFG_TUH_CDC_CH34X 1 + +// RX & TX fifo size +#define CFG_TUH_CDC_RX_BUFSIZE 64 +#define CFG_TUH_CDC_TX_BUFSIZE 64 + +// Set Line Control state on enumeration/mounted: +// DTR ( bit 0), RTS (bit 1) +#define CFG_TUH_CDC_LINE_CONTROL_ON_ENUM 0x03 + +// Set Line Coding on enumeration/mounted, value for cdc_line_coding_t +// bit rate = 115200, 1 stop bit, no parity, 8 bit data width +// This need Pico-PIO-USB at least 0.5.1 +#define CFG_TUH_CDC_LINE_CODING_ON_ENUM \ + { 115200, CDC_LINE_CONDING_STOP_BITS_1, CDC_LINE_CODING_PARITY_NONE, 8 } + +#ifdef __cplusplus +} +#endif + +#endif /* _TUSB_CONFIG_SAMD_H_ */ diff --git a/libraries/Adafruit_TinyUSB_Arduino/src/arduino/video/Adafruit_USBD_Video.cpp b/libraries/Adafruit_TinyUSB_Arduino/src/arduino/video/Adafruit_USBD_Video.cpp new file mode 100644 index 0000000..34727f4 --- /dev/null +++ b/libraries/Adafruit_TinyUSB_Arduino/src/arduino/video/Adafruit_USBD_Video.cpp @@ -0,0 +1,269 @@ +/* + * The MIT License (MIT) + * + * Copyright (c) 2024 Ha Thach (tinyusb.org) + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + * This file is part of the TinyUSB stack. + */ + +#include "tusb_option.h" + +#if CFG_TUD_ENABLED && CFG_TUD_VIDEO && CFG_TUD_VIDEO_STREAMING + +#include "Adafruit_USBD_Video.h" + +#define BULK_PACKET_SIZE (TUD_OPT_HIGH_SPEED ? 512 : 64) + +Adafruit_USBD_Video::Adafruit_USBD_Video(void) { + _vc_id = 0; + memset(&_camera_terminal, 0, sizeof(_camera_terminal)); +} + +bool Adafruit_USBD_Video::addTerminal( + tusb_desc_video_control_camera_terminal_t const *camera_terminal) { + _camera_terminal = *camera_terminal; + + // override constants + _camera_terminal.bLength = sizeof(tusb_desc_video_control_camera_terminal_t); + _camera_terminal.bDescriptorType = TUSB_DESC_CS_INTERFACE; + _camera_terminal.bDescriptorSubType = VIDEO_CS_ITF_VC_INPUT_TERMINAL; + + _camera_terminal.bControlSize = 3; + + return true; +} + +bool Adafruit_USBD_Video::addTerminal( + tusb_desc_video_control_output_terminal_t const *output_terminal) { + _output_terminal = *output_terminal; + + // override constants + _output_terminal.bLength = sizeof(tusb_desc_video_control_output_terminal_t); + _output_terminal.bDescriptorType = TUSB_DESC_CS_INTERFACE; + _output_terminal.bDescriptorSubType = VIDEO_CS_ITF_VC_OUTPUT_TERMINAL; + + return true; +} + +bool Adafruit_USBD_Video::addFormat( + tusb_desc_video_format_uncompressed_t const *format) { + _format.uncompressed = *format; + + // override constants + _format.uncompressed.bLength = sizeof(tusb_desc_video_format_uncompressed_t); + _format.uncompressed.bDescriptorType = TUSB_DESC_CS_INTERFACE; + _format.uncompressed.bDescriptorSubType = VIDEO_CS_ITF_VS_FORMAT_UNCOMPRESSED; + + return true; +} + +bool Adafruit_USBD_Video::addFrame( + tusb_desc_video_frame_uncompressed_continuous_t const *frame) { + _frame.uncompressed_cont = *frame; + + // override constants + _frame.uncompressed_cont.bLength = + sizeof(tusb_desc_video_frame_uncompressed_continuous_t); + _frame.uncompressed_cont.bDescriptorType = TUSB_DESC_CS_INTERFACE; + _frame.uncompressed_cont.bDescriptorSubType = + VIDEO_CS_ITF_VS_FRAME_UNCOMPRESSED; + _frame.uncompressed_cont.bFrameIntervalType = 0; // continuous + + return true; +} + +void Adafruit_USBD_Video::addColorMatching( + tusb_desc_video_streaming_color_matching_t const *color) { + _color_matching = *color; + + // override constants + _color_matching.bLength = sizeof(tusb_desc_video_streaming_color_matching_t); + _color_matching.bDescriptorType = TUSB_DESC_CS_INTERFACE; + _color_matching.bDescriptorSubType = VIDEO_CS_ITF_VS_COLORFORMAT; +} + +bool Adafruit_USBD_Video::begin() { + if (!TinyUSBDevice.addInterface(*this)) { + return false; + } + + return true; +} + +uint16_t Adafruit_USBD_Video::getInterfaceDescriptor(uint8_t itfnum_deprecated, + uint8_t *buf, + uint16_t bufsize) { + (void)itfnum_deprecated; + + uint8_t itfnum = 0; + uint8_t ep_in = 0; + + // check if necessary descriptors are added + if (!(_camera_terminal.bLength && _output_terminal.bLength && + _format.uncompressed.bLength && _frame.uncompressed_cont.bLength && + _color_matching.bLength)) { + return 0; + } + + // Null buf is for length only + if (buf) { + itfnum = TinyUSBDevice.allocInterface(2); + ep_in = TinyUSBDevice.allocEndpoint(TUSB_DIR_IN); + } + + typedef struct TU_ATTR_PACKED { + tusb_desc_interface_t itf; + tusb_desc_video_control_header_1itf_t header; + tusb_desc_video_control_camera_terminal_t camera_terminal; + tusb_desc_video_control_output_terminal_t output_terminal; + } uvc_control_desc_t; + + // hard code for now +#define UVC_CLOCK_FREQUENCY 27000000 + + /* Windows support YUY2 and NV12 + * https://docs.microsoft.com/en-us/windows-hardware/drivers/stream/usb-video-class-driver-overview + */ + typedef struct TU_ATTR_PACKED { + tusb_desc_interface_t itf; + tusb_desc_video_streaming_input_header_1byte_t header; + tusb_desc_video_format_uncompressed_t format; + tusb_desc_video_frame_uncompressed_continuous_t frame; + tusb_desc_video_streaming_color_matching_t color; + + // #if USE_ISO_STREAMING + // // For ISO streaming, USB spec requires to alternate interface + // tusb_desc_interface_t itf_alt; + // #endif + + tusb_desc_endpoint_t ep; + } uvc_streaming_desc_t; + + const tusb_desc_interface_assoc_t desc_iad = { + .bLength = sizeof(tusb_desc_interface_assoc_t), + .bDescriptorType = TUSB_DESC_INTERFACE_ASSOCIATION, + + .bFirstInterface = itfnum, + .bInterfaceCount = 2, + .bFunctionClass = TUSB_CLASS_VIDEO, + .bFunctionSubClass = VIDEO_SUBCLASS_INTERFACE_COLLECTION, + .bFunctionProtocol = VIDEO_ITF_PROTOCOL_UNDEFINED, + .iFunction = 0}; + + uvc_control_desc_t desc_video_control{ + .itf = {.bLength = sizeof(tusb_desc_interface_t), + .bDescriptorType = TUSB_DESC_INTERFACE, + + .bInterfaceNumber = itfnum, + .bAlternateSetting = 0, + .bNumEndpoints = 0, + .bInterfaceClass = TUSB_CLASS_VIDEO, + .bInterfaceSubClass = VIDEO_SUBCLASS_CONTROL, + .bInterfaceProtocol = VIDEO_ITF_PROTOCOL_15, + .iInterface = _strid}, + .header = {.bLength = sizeof(tusb_desc_video_control_header_1itf_t), + .bDescriptorType = TUSB_DESC_CS_INTERFACE, + .bDescriptorSubType = VIDEO_CS_ITF_VC_HEADER, + + .bcdUVC = VIDEO_BCD_1_50, + .wTotalLength = + sizeof(uvc_control_desc_t) - + sizeof(tusb_desc_interface_t), // CS VC descriptors only + .dwClockFrequency = UVC_CLOCK_FREQUENCY, + .bInCollection = 1, + .baInterfaceNr = {(uint8_t)(itfnum + 1)}}, + .camera_terminal = _camera_terminal, + .output_terminal = _output_terminal}; + + uvc_streaming_desc_t desc_video_streaming = { + .itf = + { + .bLength = sizeof(tusb_desc_interface_t), + .bDescriptorType = TUSB_DESC_INTERFACE, + + .bInterfaceNumber = (uint8_t)(itfnum + 1), + .bAlternateSetting = 0, + .bNumEndpoints = 1, // bulk 1, iso 0 + .bInterfaceClass = TUSB_CLASS_VIDEO, + .bInterfaceSubClass = VIDEO_SUBCLASS_STREAMING, + .bInterfaceProtocol = VIDEO_ITF_PROTOCOL_15, + .iInterface = _strid, + }, + .header = {.bLength = + sizeof(tusb_desc_video_streaming_input_header_1byte_t), + .bDescriptorType = TUSB_DESC_CS_INTERFACE, + .bDescriptorSubType = VIDEO_CS_ITF_VS_INPUT_HEADER, + + .bNumFormats = 1, + .wTotalLength = + sizeof(uvc_streaming_desc_t) - + sizeof(tusb_desc_interface_t) - + sizeof(tusb_desc_endpoint_t), // CS VS descriptors only + .bEndpointAddress = ep_in, + .bmInfo = 0, + .bTerminalLink = _output_terminal.bTerminalID, + .bStillCaptureMethod = 0, + .bTriggerSupport = 0, + .bTriggerUsage = 0, + .bControlSize = 1, + .bmaControls = {0}}, + .format = _format.uncompressed, + .frame = _frame.uncompressed_cont, + .color = _color_matching, + .ep = {.bLength = sizeof(tusb_desc_endpoint_t), + .bDescriptorType = TUSB_DESC_ENDPOINT, + + .bEndpointAddress = ep_in, + .bmAttributes = {.xfer = TUSB_XFER_BULK, .sync = 0, .usage = 0}, + .wMaxPacketSize = BULK_PACKET_SIZE, + .bInterval = 1}}; + + uint16_t const len_iad = sizeof(desc_iad); + uint16_t const len_vc = sizeof(desc_video_control); + uint16_t const len_vs = sizeof(desc_video_streaming); + uint16_t const len_total = len_iad + len_vc + len_vs; + + if (buf) { + if (bufsize < len_total) { + return 0; + } + + memcpy(buf, &desc_iad, len_iad); + buf += len_iad; + + memcpy(buf, &desc_video_control, len_vc); + buf += len_vc; + + memcpy(buf, &desc_video_streaming, len_vs); + } + + return len_total; +} + +//--------------------------------------------------------------------+ +// API +//--------------------------------------------------------------------+ + +// bool Adafruit_USBD_Video::isStreaming(uint8_t stream_idx) { +// return tud_video_n_streaming(_vc_id, stream_idx); +// } + +#endif diff --git a/libraries/Adafruit_TinyUSB_Arduino/src/arduino/video/Adafruit_USBD_Video.h b/libraries/Adafruit_TinyUSB_Arduino/src/arduino/video/Adafruit_USBD_Video.h new file mode 100644 index 0000000..c1196e3 --- /dev/null +++ b/libraries/Adafruit_TinyUSB_Arduino/src/arduino/video/Adafruit_USBD_Video.h @@ -0,0 +1,78 @@ +/* + * The MIT License (MIT) + * + * Copyright (c) 2024 Ha Thach (tinyusb.org) + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + * This file is part of the TinyUSB stack. + */ + +#ifndef ADAFRUIT_USBD_VIDEO_H +#define ADAFRUIT_USBD_VIDEO_H + +#include "Adafruit_USBD_Device.h" + +class Adafruit_USBD_Video : public Adafruit_USBD_Interface { +public: + Adafruit_USBD_Video(void); + + bool begin(); + + //------------- Video Control -------------// + // bool isStreaming(uint8_t stream_idx); + bool + addTerminal(tusb_desc_video_control_camera_terminal_t const *camera_terminal); + bool + addTerminal(tusb_desc_video_control_output_terminal_t const *output_terminal); + + //------------- Video Streaming -------------// + // bool setIsochronousStreaming(bool enabled); + + // Add format descriptor, return format index + bool addFormat(tusb_desc_video_format_uncompressed_t const *format); + bool addFrame(tusb_desc_video_frame_uncompressed_continuous_t const *frame); + void + addColorMatching(tusb_desc_video_streaming_color_matching_t const *color); + + // from Adafruit_USBD_Interface + virtual uint16_t getInterfaceDescriptor(uint8_t itfnum_deprecated, + uint8_t *buf, uint16_t bufsize); + +private: + uint8_t _vc_id; + + tusb_desc_video_control_camera_terminal_t _camera_terminal; + tusb_desc_video_control_output_terminal_t _output_terminal; + + // currently only support 1 format + union { + tusb_desc_video_format_uncompressed_t uncompressed; + tusb_desc_video_format_mjpeg_t mjpeg; + } _format; + + union { + tusb_desc_video_frame_uncompressed_continuous_t uncompressed_cont; + tusb_desc_video_frame_mjpeg_continuous_t mjpeg; + } _frame; + + tusb_desc_video_streaming_color_matching_t _color_matching; +}; + +#endif diff --git a/libraries/Adafruit_TinyUSB_Arduino/src/arduino/webusb/Adafruit_USBD_WebUSB.cpp b/libraries/Adafruit_TinyUSB_Arduino/src/arduino/webusb/Adafruit_USBD_WebUSB.cpp new file mode 100644 index 0000000..435c0d9 --- /dev/null +++ b/libraries/Adafruit_TinyUSB_Arduino/src/arduino/webusb/Adafruit_USBD_WebUSB.cpp @@ -0,0 +1,322 @@ +/* + * The MIT License (MIT) + * + * Copyright (c) 2019 hathach for Adafruit Industries + * + * 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. + */ + +#include "tusb_option.h" + +#if CFG_TUD_ENABLED && CFG_TUD_VENDOR + +#include "Adafruit_USBD_WebUSB.h" +#include "Arduino.h" + +#ifdef ARDUINO_ARCH_ESP32 +#include "USB.h" +#endif + +//--------------------------------------------------------------------+ +// MACRO TYPEDEF CONSTANT ENUM DECLARATION +//--------------------------------------------------------------------+ +#define BULK_PACKET_SIZE (TUD_OPT_HIGH_SPEED ? 512 : 64) + +enum { VENDOR_REQUEST_WEBUSB = 1, VENDOR_REQUEST_MICROSOFT = 2 }; + +// TODO multiple instances +static Adafruit_USBD_WebUSB *_webusb_dev = NULL; + +//--------------------------------------------------------------------+ +// BOS Descriptor +//--------------------------------------------------------------------+ + +/* Microsoft OS 2.0 registry property descriptor +Per MS requirements +https://msdn.microsoft.com/en-us/library/windows/hardware/hh450799(v=vs.85).aspx +device should create DeviceInterfaceGUIDs. It can be done by driver and +in case of real PnP solution device should expose MS "Microsoft OS 2.0 +registry property descriptor". Such descriptor can insert any record +into Windows registry per device/configuration/interface. In our case it +will insert "DeviceInterfaceGUIDs" multistring property. + +GUID is freshly generated and should be OK to use. + +https://developers.google.com/web/fundamentals/native-hardware/build-for-webusb/ +(Section Microsoft OS compatibility descriptors) +*/ + +#define BOS_TOTAL_LEN \ + (TUD_BOS_DESC_LEN + TUD_BOS_WEBUSB_DESC_LEN + TUD_BOS_MICROSOFT_OS_DESC_LEN) + +#define MS_OS_20_DESC_LEN 0xB2 + +// BOS Descriptor is required for webUSB +uint8_t const desc_bos[] = { + // total length, number of device caps + TUD_BOS_DESCRIPTOR(BOS_TOTAL_LEN, 2), + + // Vendor Code, iLandingPage + TUD_BOS_WEBUSB_DESCRIPTOR(VENDOR_REQUEST_WEBUSB, 1), + + // Microsoft OS 2.0 descriptor + TUD_BOS_MS_OS_20_DESCRIPTOR(MS_OS_20_DESC_LEN, VENDOR_REQUEST_MICROSOFT)}; + +uint8_t desc_ms_os_20[] = { + // Set header: length, type, windows version, total length + U16_TO_U8S_LE(0x000A), U16_TO_U8S_LE(MS_OS_20_SET_HEADER_DESCRIPTOR), + U32_TO_U8S_LE(0x06030000), U16_TO_U8S_LE(MS_OS_20_DESC_LEN), + + // Configuration subset header: length, type, configuration index, reserved, + // configuration total length + U16_TO_U8S_LE(0x0008), U16_TO_U8S_LE(MS_OS_20_SUBSET_HEADER_CONFIGURATION), + 0, 0, U16_TO_U8S_LE(MS_OS_20_DESC_LEN - 0x0A), + + // Function Subset header: length, type, first interface, reserved, subset + // length + U16_TO_U8S_LE(0x0008), U16_TO_U8S_LE(MS_OS_20_SUBSET_HEADER_FUNCTION), + 0 /*itf num*/, 0, U16_TO_U8S_LE(MS_OS_20_DESC_LEN - 0x0A - 0x08), + + // MS OS 2.0 Compatible ID descriptor: length, type, compatible ID, sub + // compatible ID + U16_TO_U8S_LE(0x0014), U16_TO_U8S_LE(MS_OS_20_FEATURE_COMPATBLE_ID), 'W', + 'I', 'N', 'U', 'S', 'B', 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, // sub-compatible + + // MS OS 2.0 Registry property descriptor: length, type + U16_TO_U8S_LE(MS_OS_20_DESC_LEN - 0x0A - 0x08 - 0x08 - 0x14), + U16_TO_U8S_LE(MS_OS_20_FEATURE_REG_PROPERTY), U16_TO_U8S_LE(0x0007), + U16_TO_U8S_LE(0x002A), // wPropertyDataType, wPropertyNameLength and + // PropertyName "DeviceInterfaceGUIDs\0" in UTF-16 + 'D', 0x00, 'e', 0x00, 'v', 0x00, 'i', 0x00, 'c', 0x00, 'e', 0x00, 'I', 0x00, + 'n', 0x00, 't', 0x00, 'e', 0x00, 'r', 0x00, 'f', 0x00, 'a', 0x00, 'c', 0x00, + 'e', 0x00, 'G', 0x00, 'U', 0x00, 'I', 0x00, 'D', 0x00, 's', 0x00, 0x00, + 0x00, + U16_TO_U8S_LE(0x0050), // wPropertyDataLength + // bPropertyData: “{975F44D9-0D08-43FD-8B3E-127CA8AFFF9D}”. + '{', 0x00, '9', 0x00, '7', 0x00, '5', 0x00, 'F', 0x00, '4', 0x00, '4', 0x00, + 'D', 0x00, '9', 0x00, '-', 0x00, '0', 0x00, 'D', 0x00, '0', 0x00, '8', 0x00, + '-', 0x00, '4', 0x00, '3', 0x00, 'F', 0x00, 'D', 0x00, '-', 0x00, '8', 0x00, + 'B', 0x00, '3', 0x00, 'E', 0x00, '-', 0x00, '1', 0x00, '2', 0x00, '7', 0x00, + 'C', 0x00, 'A', 0x00, '8', 0x00, 'A', 0x00, 'F', 0x00, 'F', 0x00, 'F', 0x00, + '9', 0x00, 'D', 0x00, '}', 0x00, 0x00, 0x00, 0x00, 0x00}; + +TU_VERIFY_STATIC(sizeof(desc_ms_os_20) == MS_OS_20_DESC_LEN, "Incorrect size"); + +//--------------------------------------------------------------------+ +// IMPLEMENTATION +//--------------------------------------------------------------------+ +Adafruit_USBD_WebUSB::Adafruit_USBD_WebUSB(const void *url) { + _connected = false; + _url = (const uint8_t *)url; + _linestate_cb = NULL; +} + +bool Adafruit_USBD_WebUSB::begin(void) { + if (!TinyUSBDevice.addInterface(*this)) { + return false; + } + + // WebUSB requires USB version at least 2.1 (or 3.x) + TinyUSBDevice.setVersion(0x0210); + + _webusb_dev = this; + return true; +} + +bool Adafruit_USBD_WebUSB::setLandingPage(const void *url) { + _url = (const uint8_t *)url; + return true; +} + +void Adafruit_USBD_WebUSB::setLineStateCallback(linestate_callback_t fp) { + _linestate_cb = fp; +} + +uint16_t Adafruit_USBD_WebUSB::getInterfaceDescriptor(uint8_t itfnum_deprecated, + uint8_t *buf, + uint16_t bufsize) { + (void)itfnum_deprecated; + + if (!buf) { + return TUD_VENDOR_DESC_LEN; + } + + uint8_t const itfnum = TinyUSBDevice.allocInterface(1); + uint8_t const ep_in = TinyUSBDevice.allocEndpoint(TUSB_DIR_IN); + uint8_t const ep_out = TinyUSBDevice.allocEndpoint(TUSB_DIR_OUT); + + uint8_t desc[] = { + TUD_VENDOR_DESCRIPTOR(itfnum, _strid, ep_out, ep_in, BULK_PACKET_SIZE)}; + uint16_t const len = sizeof(desc); + + // null buffer for length only + if (bufsize < len) { + return 0; + } + + memcpy(buf, desc, len); + + // update the bFirstInterface in MS OS 2.0 descriptor + // that is bound to WinUSB driver + desc_ms_os_20[0x0a + 0x08 + 4] = itfnum; + + return len; +} + +bool Adafruit_USBD_WebUSB::connected(void) { + return tud_vendor_mounted() && _connected; +} + +Adafruit_USBD_WebUSB::operator bool() { + // Add an yield to run usb background in case sketch block wait as follows + // while( !webusb ) {} + if (!connected()) { + yield(); + } + + return connected(); +} + +int Adafruit_USBD_WebUSB::available(void) { + uint32_t count = tud_vendor_available(); + + // Add an yield to run usb background in case sketch block wait as follows + // while( !webusb.available() ) {} + if (!count) { + yield(); + } + + return count; +} + +int Adafruit_USBD_WebUSB::read(void) { + uint8_t ch; + return tud_vendor_read(&ch, 1) ? (int)ch : -1; +} + +size_t Adafruit_USBD_WebUSB::read(uint8_t *buffer, size_t size) { + return tud_vendor_read(buffer, size); +} + +size_t Adafruit_USBD_WebUSB::write(uint8_t b) { return this->write(&b, 1); } + +size_t Adafruit_USBD_WebUSB::write(const uint8_t *buffer, size_t size) { + size_t remain = size; + while (remain && _connected) { + size_t wrcount = tud_vendor_write(buffer, remain); + remain -= wrcount; + buffer += wrcount; + + // Write FIFO is full, run usb background to flush + if (remain) { + yield(); + } + } + + return size - remain; +} + +int Adafruit_USBD_WebUSB::peek(void) { + uint8_t ch; + return tud_vendor_peek(&ch) ? (int)ch : -1; +} + +void Adafruit_USBD_WebUSB::flush(void) { tud_vendor_flush(); } + +//--------------------------------------------------------------------+ +// TinyUSB stack callbacks +//--------------------------------------------------------------------+ +extern "C" { + +TU_ATTR_WEAK uint8_t const *tud_descriptor_bos_cb(void) { return desc_bos; } + +// Invoked when a control transfer occurred on an interface of this class +// Driver response accordingly to the request and the transfer stage +// (setup/data/ack) return false to stall control endpoint (e.g unsupported +// request) +TU_ATTR_WEAK bool +tud_vendor_control_xfer_cb(uint8_t rhport, uint8_t stage, + tusb_control_request_t const *request) { + if (!_webusb_dev) { + return false; + } + + // nothing to with DATA & ACK stage + if (stage != CONTROL_STAGE_SETUP) { + return true; + } + + switch (request->bmRequestType_bit.type) { + case TUSB_REQ_TYPE_VENDOR: + switch (request->bRequest) { + case VENDOR_REQUEST_WEBUSB: + // match vendor request in BOS descriptor + // Get landing page url + if (!_webusb_dev->_url) { + return false; + } + return tud_control_xfer(rhport, request, (void *)_webusb_dev->_url, + _webusb_dev->_url[0]); + + case VENDOR_REQUEST_MICROSOFT: + if (request->wIndex == 7) { + // Get Microsoft OS 2.0 compatible descriptor + uint16_t total_len; + memcpy(&total_len, desc_ms_os_20 + 8, 2); + + return tud_control_xfer(rhport, request, (void *)desc_ms_os_20, + total_len); + } else { + return false; + } + + default: + break; + } + break; + + case TUSB_REQ_TYPE_CLASS: + if (request->bRequest == 0x22) { + // Webserial simulate the CDC_REQUEST_SET_CONTROL_LINE_STATE (0x22) to + // connect and disconnect. + _webusb_dev->_connected = (request->wValue != 0); + + // response with status OK + tud_control_status(rhport, request); + + // invoked callback if any (TODO should be done at ACK stage) + if (_webusb_dev->_linestate_cb) { + _webusb_dev->_linestate_cb(_webusb_dev->_connected); + } + + return true; + } + break; + + default: + // stall unknown request + return false; + } + + return true; +} +} + +#endif // CFG_TUD_ENABLED diff --git a/libraries/Adafruit_TinyUSB_Arduino/src/arduino/webusb/Adafruit_USBD_WebUSB.h b/libraries/Adafruit_TinyUSB_Arduino/src/arduino/webusb/Adafruit_USBD_WebUSB.h new file mode 100644 index 0000000..6348af8 --- /dev/null +++ b/libraries/Adafruit_TinyUSB_Arduino/src/arduino/webusb/Adafruit_USBD_WebUSB.h @@ -0,0 +1,81 @@ +/* + * The MIT License (MIT) + * + * Copyright (c) 2019 hathach for Adafruit Industries + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#ifndef ADAFRUIT_USBD_WEBUSB_H_ +#define ADAFRUIT_USBD_WEBUSB_H_ + +#include "Stream.h" +#include "Adafruit_USBD_Device.h" + +#define WEBUSB_URL_DEF(_name, _scheme, _url) \ + struct TU_ATTR_PACKED { \ + uint8_t bLength; \ + uint8_t bDescriptorType; \ + uint8_t bScheme; \ + char url[3 + sizeof(_url)]; \ + } const _name = {3 + sizeof(_url) - 1, 3, _scheme, _url} + +class Adafruit_USBD_WebUSB : public Stream, public Adafruit_USBD_Interface { +public: + typedef void (*linestate_callback_t)(bool connected); + Adafruit_USBD_WebUSB(const void *url = NULL); + + bool begin(void); + + bool setLandingPage(const void *url); + void setLineStateCallback(linestate_callback_t fp); + + // Stream API + virtual int available(void); + virtual int peek(void); + + virtual int read(void); + size_t read(uint8_t *buffer, size_t size); + + virtual void flush(void); + virtual size_t write(uint8_t b); + + virtual size_t write(const uint8_t *buffer, size_t size); + size_t write(const char *buffer, size_t size) { + return write((const uint8_t *)buffer, size); + } + + bool connected(void); + operator bool(); + + // from Adafruit_USBD_Interface + virtual uint16_t getInterfaceDescriptor(uint8_t itfnum_deprecated, + uint8_t *buf, uint16_t bufsize); + +private: + bool _connected; + const uint8_t *_url; + linestate_callback_t _linestate_cb; + + // Make all tinyusb callback friend to access private data + friend bool tud_vendor_control_xfer_cb(uint8_t rhport, uint8_t stage, + tusb_control_request_t const *request); +}; + +#endif /* ADAFRUIT_USBD_WEBUSB_H_ */ diff --git a/libraries/SPI/SPI.cpp b/libraries/SPI/SPI.cpp index 6fde272..e47ddd8 100644 --- a/libraries/SPI/SPI.cpp +++ b/libraries/SPI/SPI.cpp @@ -216,6 +216,32 @@ void SPIClass::setClockDivider(uint8_t div) _p_spi->FREQUENCY = clockFreq; } +void SPIClass::transfer(const void *tx_buf, void *rx_buf, size_t count) +{ + const uint8_t* tx_buf8 = (const uint8_t*) tx_buf; + uint8_t* rx_buf8 = (uint8_t*) rx_buf; + + for (size_t i = 0; i < count; i++) + { + // Send byte (or 0xFF if no tx buffer) + _p_spi->TXD = tx_buf8 ? tx_buf8[i] : 0xFF; + + // Wait for transfer to complete + while (!_p_spi->EVENTS_READY); + + // Read received byte if rx buffer provided + if (rx_buf8) { + rx_buf8[i] = _p_spi->RXD; + } else { + // Read to clear the register + (void)_p_spi->RXD; + } + + // Clear event flag + _p_spi->EVENTS_READY = 0x0UL; + } +} + byte SPIClass::transfer(uint8_t data) { _p_spi->TXD = data; diff --git a/libraries/SPI/SPI.h b/libraries/SPI/SPI.h index b87a76d..bea018b 100644 --- a/libraries/SPI/SPI.h +++ b/libraries/SPI/SPI.h @@ -87,6 +87,7 @@ class SPIClass { SPIClass(NRF_SPI_Type *p_spi, uint8_t uc_pinMISO, uint8_t uc_pinSCK, uint8_t uc_pinMOSI); + void transfer(const void *tx_buf, void *rx_buf, size_t count); byte transfer(uint8_t data); uint16_t transfer16(uint16_t data); inline void transfer(void *buf, size_t count);