diff --git a/examples/device/audio_4_channel_mic/.skip.MCU_SAMD11 b/examples/device/audio_4_channel_mic/.skip.MCU_SAMD11 new file mode 100644 index 000000000..e69de29bb diff --git a/examples/device/audio_4_channel_mic/.skip.MCU_SAME5X b/examples/device/audio_4_channel_mic/.skip.MCU_SAME5X new file mode 100644 index 000000000..e69de29bb diff --git a/examples/device/audio_4_channel_mic/.skip.MCU_SAMG b/examples/device/audio_4_channel_mic/.skip.MCU_SAMG new file mode 100644 index 000000000..e69de29bb diff --git a/examples/device/audio_4_channel_mic/CMakeLists.txt b/examples/device/audio_4_channel_mic/CMakeLists.txt new file mode 100644 index 000000000..9cae652ea --- /dev/null +++ b/examples/device/audio_4_channel_mic/CMakeLists.txt @@ -0,0 +1,37 @@ +# use BOARD-Directory name for project id +get_filename_component(PROJECT ${CMAKE_CURRENT_SOURCE_DIR} NAME) +set(PROJECT ${BOARD}-${PROJECT}) + +# TOP is absolute path to root directory of TinyUSB git repo +set(TOP "../../..") +get_filename_component(TOP "${TOP}" REALPATH) + +# Check for -DFAMILY= +if(FAMILY STREQUAL "rp2040") + cmake_minimum_required(VERSION 3.12) + + include(${TOP}/hw/bsp/${FAMILY}/pico_sdk_import.cmake) + project(${PROJECT}) + add_executable(${PROJECT}) + + include(${TOP}/hw/bsp/${FAMILY}/family.cmake) + + # Example source + target_sources(${PROJECT} PUBLIC + ${CMAKE_CURRENT_SOURCE_DIR}/src/main.c + ${CMAKE_CURRENT_SOURCE_DIR}/src/usb_descriptors.c + ) + + # Example include + target_include_directories(${PROJECT} PUBLIC + ${CMAKE_CURRENT_SOURCE_DIR}/src + ) + + # Example defines + target_compile_definitions(${PROJECT} PUBLIC + CFG_TUSB_OS=OPT_OS_PICO + ) + +else() + message(FATAL_ERROR "Invalid FAMILY specified") +endif() diff --git a/examples/device/audio_4_channel_mic/Makefile b/examples/device/audio_4_channel_mic/Makefile new file mode 100644 index 000000000..5a455078e --- /dev/null +++ b/examples/device/audio_4_channel_mic/Makefile @@ -0,0 +1,12 @@ +include ../../../tools/top.mk +include ../../make.mk + +INC += \ + src \ + $(TOP)/hw \ + +# Example source +EXAMPLE_SOURCE += $(wildcard src/*.c) +SRC_C += $(addprefix $(CURRENT_PATH)/, $(EXAMPLE_SOURCE)) + +include ../../rules.mk diff --git a/examples/device/audio_4_channel_mic/src/main.c b/examples/device/audio_4_channel_mic/src/main.c new file mode 100644 index 000000000..983b87e5b --- /dev/null +++ b/examples/device/audio_4_channel_mic/src/main.c @@ -0,0 +1,458 @@ +/* + * The MIT License (MIT) + * + * 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 + * 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. + * + */ + +/* plot_audio_samples.py requires following modules: + * $ sudo apt install libportaudio + * $ pip3 install sounddevice matplotlib + * + * Then run + * $ python3 plot_audio_samples.py + */ + +#include +#include +#include + +#include "bsp/board.h" +#include "tusb.h" + +//--------------------------------------------------------------------+ +// MACRO CONSTANT TYPEDEF PROTYPES +//--------------------------------------------------------------------+ + +#ifndef AUDIO_SAMPLE_RATE +#define AUDIO_SAMPLE_RATE 48000 +#endif + +/* Blink pattern + * - 250 ms : device not mounted + * - 1000 ms : device mounted + * - 2500 ms : device is suspended + */ +enum { + BLINK_NOT_MOUNTED = 250, + BLINK_MOUNTED = 1000, + BLINK_SUSPENDED = 2500, +}; + +static uint32_t blink_interval_ms = BLINK_NOT_MOUNTED; + +// Audio controls +// Current states +bool mute[CFG_TUD_AUDIO_FUNC_1_N_CHANNELS_TX + 1]; // +1 for master channel 0 +uint16_t volume[CFG_TUD_AUDIO_FUNC_1_N_CHANNELS_TX + 1]; // +1 for master channel 0 +uint32_t sampFreq; +uint8_t clkValid; + +// Range states +audio_control_range_2_n_t(1) volumeRng[CFG_TUD_AUDIO_FUNC_1_N_CHANNELS_TX+1]; // Volume range state +audio_control_range_4_n_t(1) sampleFreqRng; // Sample frequency range state + +// Audio test data +uint16_t i2s_dummy_buffer[CFG_TUD_AUDIO_FUNC_1_N_TX_SUPP_SW_FIFO][CFG_TUD_AUDIO_FUNC_1_TX_SUPP_SW_FIFO_SZ/2]; // Ensure half word aligned + +void led_blinking_task(void); +void audio_task(void); + +/*------------- MAIN -------------*/ +int main(void) +{ + board_init(); + + tusb_init(); + + // Init values + sampFreq = AUDIO_SAMPLE_RATE; + clkValid = 1; + + sampleFreqRng.wNumSubRanges = 1; + sampleFreqRng.subrange[0].bMin = AUDIO_SAMPLE_RATE; + sampleFreqRng.subrange[0].bMax = AUDIO_SAMPLE_RATE; + sampleFreqRng.subrange[0].bRes = 0; + + while (1) + { + tud_task(); // tinyusb device task + led_blinking_task(); + audio_task(); + } + + + return 0; +} + +//--------------------------------------------------------------------+ +// Device callbacks +//--------------------------------------------------------------------+ + +// Invoked when device is mounted +void tud_mount_cb(void) +{ + blink_interval_ms = BLINK_MOUNTED; +} + +// Invoked when device is unmounted +void tud_umount_cb(void) +{ + blink_interval_ms = BLINK_NOT_MOUNTED; +} + +// Invoked when usb bus is suspended +// remote_wakeup_en : if host allow us to perform remote wakeup +// Within 7ms, device must draw an average of current less than 2.5 mA from bus +void tud_suspend_cb(bool remote_wakeup_en) +{ + (void) remote_wakeup_en; + blink_interval_ms = BLINK_SUSPENDED; +} + +// Invoked when usb bus is resumed +void tud_resume_cb(void) +{ + blink_interval_ms = BLINK_MOUNTED; +} + +//--------------------------------------------------------------------+ +// AUDIO Task +//--------------------------------------------------------------------+ + +void audio_task(void) +{ + // Yet to be filled - e.g. put meas data into TX FIFOs etc. + asm("nop"); +} + +//--------------------------------------------------------------------+ +// Application Callback API Implementations +//--------------------------------------------------------------------+ + +// 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) +{ + (void) rhport; + (void) pBuff; + + // We do not support any set range requests here, only current value requests + TU_VERIFY(p_request->bRequest == AUDIO_CS_REQ_CUR); + + // Page 91 in UAC2 specification + uint8_t channelNum = TU_U16_LOW(p_request->wValue); + uint8_t ctrlSel = TU_U16_HIGH(p_request->wValue); + uint8_t ep = TU_U16_LOW(p_request->wIndex); + + (void) channelNum; (void) ctrlSel; (void) ep; + + return false; // Yet not implemented +} + +// 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) +{ + (void) rhport; + (void) pBuff; + + // We do not support any set range requests here, only current value requests + TU_VERIFY(p_request->bRequest == AUDIO_CS_REQ_CUR); + + // Page 91 in UAC2 specification + uint8_t channelNum = TU_U16_LOW(p_request->wValue); + uint8_t ctrlSel = TU_U16_HIGH(p_request->wValue); + uint8_t itf = TU_U16_LOW(p_request->wIndex); + + (void) channelNum; (void) ctrlSel; (void) itf; + + return false; // Yet not implemented +} + +// 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) +{ + (void) rhport; + + // Page 91 in UAC2 specification + uint8_t channelNum = TU_U16_LOW(p_request->wValue); + uint8_t ctrlSel = TU_U16_HIGH(p_request->wValue); + uint8_t itf = TU_U16_LOW(p_request->wIndex); + uint8_t entityID = TU_U16_HIGH(p_request->wIndex); + + (void) itf; + + // We do not support any set range requests here, only current value requests + TU_VERIFY(p_request->bRequest == AUDIO_CS_REQ_CUR); + + // If request is for our feature unit + if ( entityID == 2 ) + { + switch ( ctrlSel ) + { + case AUDIO_FU_CTRL_MUTE: + // Request uses format layout 1 + TU_VERIFY(p_request->wLength == sizeof(audio_control_cur_1_t)); + + mute[channelNum] = ((audio_control_cur_1_t*) pBuff)->bCur; + + TU_LOG2(" Set Mute: %d of channel: %u\r\n", mute[channelNum], channelNum); + return true; + + case AUDIO_FU_CTRL_VOLUME: + // Request uses format layout 2 + TU_VERIFY(p_request->wLength == sizeof(audio_control_cur_2_t)); + + volume[channelNum] = ((audio_control_cur_2_t*) pBuff)->bCur; + + TU_LOG2(" Set Volume: %d dB of channel: %u\r\n", volume[channelNum], channelNum); + return true; + + // Unknown/Unsupported control + default: + TU_BREAKPOINT(); + return false; + } + } + return false; // Yet not implemented +} + +// 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) +{ + (void) rhport; + + // Page 91 in UAC2 specification + uint8_t channelNum = TU_U16_LOW(p_request->wValue); + uint8_t ctrlSel = TU_U16_HIGH(p_request->wValue); + uint8_t ep = TU_U16_LOW(p_request->wIndex); + + (void) channelNum; (void) ctrlSel; (void) ep; + + // return tud_control_xfer(rhport, p_request, &tmp, 1); + + return false; // Yet not implemented +} + +// 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) +{ + (void) rhport; + + // Page 91 in UAC2 specification + uint8_t channelNum = TU_U16_LOW(p_request->wValue); + uint8_t ctrlSel = TU_U16_HIGH(p_request->wValue); + uint8_t itf = TU_U16_LOW(p_request->wIndex); + + (void) channelNum; (void) ctrlSel; (void) itf; + + return false; // Yet not implemented +} + +// 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) +{ + (void) rhport; + + // Page 91 in UAC2 specification + uint8_t channelNum = TU_U16_LOW(p_request->wValue); + uint8_t ctrlSel = TU_U16_HIGH(p_request->wValue); + // uint8_t itf = TU_U16_LOW(p_request->wIndex); // Since we have only one audio function implemented, we do not need the itf value + uint8_t entityID = TU_U16_HIGH(p_request->wIndex); + + // Input terminal (Microphone input) + if (entityID == 1) + { + switch ( ctrlSel ) + { + case AUDIO_TE_CTRL_CONNECTOR: + { + // The terminal connector control only has a get request with only the CUR attribute. + audio_desc_channel_cluster_t ret; + + // Those are dummy values for now + ret.bNrChannels = 1; + ret.bmChannelConfig = 0; + ret.iChannelNames = 0; + + TU_LOG2(" Get terminal connector\r\n"); + + return tud_audio_buffer_and_schedule_control_xfer(rhport, p_request, (void*) &ret, sizeof(ret)); + } + break; + + // Unknown/Unsupported control selector + default: + TU_BREAKPOINT(); + return false; + } + } + + // Feature unit + if (entityID == 2) + { + switch ( ctrlSel ) + { + case AUDIO_FU_CTRL_MUTE: + // Audio control mute cur parameter block consists of only one byte - we thus can send it right away + // There does not exist a range parameter block for mute + TU_LOG2(" Get Mute of channel: %u\r\n", channelNum); + return tud_control_xfer(rhport, p_request, &mute[channelNum], 1); + + case AUDIO_FU_CTRL_VOLUME: + switch ( p_request->bRequest ) + { + case AUDIO_CS_REQ_CUR: + TU_LOG2(" Get Volume of channel: %u\r\n", channelNum); + return tud_control_xfer(rhport, p_request, &volume[channelNum], sizeof(volume[channelNum])); + + case AUDIO_CS_REQ_RANGE: + TU_LOG2(" Get Volume range of channel: %u\r\n", channelNum); + + // Copy values - only for testing - better is version below + audio_control_range_2_n_t(1) + ret; + + ret.wNumSubRanges = 1; + ret.subrange[0].bMin = -90; // -90 dB + ret.subrange[0].bMax = 90; // +90 dB + ret.subrange[0].bRes = 1; // 1 dB steps + + return tud_audio_buffer_and_schedule_control_xfer(rhport, p_request, (void*) &ret, sizeof(ret)); + + // Unknown/Unsupported control + default: + TU_BREAKPOINT(); + return false; + } + break; + + // Unknown/Unsupported control + default: + TU_BREAKPOINT(); + return false; + } + } + + // Clock Source unit + if ( entityID == 4 ) + { + switch ( ctrlSel ) + { + case AUDIO_CS_CTRL_SAM_FREQ: + // channelNum is always zero in this case + switch ( p_request->bRequest ) + { + case AUDIO_CS_REQ_CUR: + TU_LOG2(" Get Sample Freq.\r\n"); + return tud_control_xfer(rhport, p_request, &sampFreq, sizeof(sampFreq)); + + case AUDIO_CS_REQ_RANGE: + TU_LOG2(" Get Sample Freq. range\r\n"); + return tud_control_xfer(rhport, p_request, &sampleFreqRng, sizeof(sampleFreqRng)); + + // Unknown/Unsupported control + default: + TU_BREAKPOINT(); + return false; + } + break; + + case AUDIO_CS_CTRL_CLK_VALID: + // Only cur attribute exists for this request + TU_LOG2(" Get Sample Freq. valid\r\n"); + return tud_control_xfer(rhport, p_request, &clkValid, sizeof(clkValid)); + + // Unknown/Unsupported control + default: + TU_BREAKPOINT(); + return false; + } + } + + TU_LOG2(" Unsupported entity: %d\r\n", entityID); + return false; // Yet not implemented +} + +bool tud_audio_tx_done_pre_load_cb(uint8_t rhport, uint8_t itf, uint8_t ep_in, uint8_t cur_alt_setting) +{ + (void) rhport; + (void) itf; + (void) ep_in; + (void) cur_alt_setting; + + for (uint8_t cnt=0; cnt < CFG_TUD_AUDIO_FUNC_1_N_TX_SUPP_SW_FIFO; cnt++) + { + tud_audio_write_support_ff(cnt, i2s_dummy_buffer[cnt], AUDIO_SAMPLE_RATE/1000 * CFG_TUD_AUDIO_FUNC_1_N_BYTES_PER_SAMPLE_TX * CFG_TUD_AUDIO_FUNC_1_CHANNEL_PER_FIFO_TX); + } + + return true; +} + +bool tud_audio_tx_done_post_load_cb(uint8_t rhport, uint16_t n_bytes_copied, uint8_t itf, uint8_t ep_in, uint8_t cur_alt_setting) +{ + (void) rhport; + (void) n_bytes_copied; + (void) itf; + (void) ep_in; + (void) cur_alt_setting; + + uint16_t dataVal; + + // Generate dummy data + for (uint16_t cnt = 0; cnt < CFG_TUD_AUDIO_FUNC_1_N_TX_SUPP_SW_FIFO; cnt++) + { + uint16_t * p_buff = i2s_dummy_buffer[cnt]; // 2 bytes per sample + dataVal = 1; + for (uint16_t cnt2 = 0; cnt2 < AUDIO_SAMPLE_RATE/1000; cnt2++) + { + for (uint8_t cnt3 = 0; cnt3 < CFG_TUD_AUDIO_FUNC_1_CHANNEL_PER_FIFO_TX; cnt3++) + { + *p_buff++ = dataVal; + } + dataVal++; + } + } + return true; +} + +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; +} + +//--------------------------------------------------------------------+ +// BLINKING TASK +//--------------------------------------------------------------------+ +void led_blinking_task(void) +{ + static uint32_t start_ms = 0; + static bool led_state = false; + + // Blink every interval ms + if ( board_millis() - start_ms < blink_interval_ms) return; // not enough time + start_ms += blink_interval_ms; + + board_led_write(led_state); + led_state = 1 - led_state; // toggle +} diff --git a/examples/device/audio_4_channel_mic/src/plot_audio_samples.py b/examples/device/audio_4_channel_mic/src/plot_audio_samples.py new file mode 100644 index 000000000..9ab15135d --- /dev/null +++ b/examples/device/audio_4_channel_mic/src/plot_audio_samples.py @@ -0,0 +1,34 @@ +import sounddevice as sd +import matplotlib.pyplot as plt +import numpy as np +import platform + +if __name__ == '__main__': + + # If you got "ValueError: No input device matching", that is because your PC name example device + # differently from tested list below. Uncomment the next line to see full list and try to pick correct one + # print(sd.query_devices()) + + fs = 48000 # Sample rate + duration = 100e-3 # Duration of recording + + if platform.system() == 'Windows': + # WDM-KS is needed since there are more than one MicNode device APIs (at least in Windows) + device = 'Microphone (MicNode_4_Ch), Windows WDM-KS' + elif platform.system() == 'Darwin': + device = 'MicNode_4_Ch' + else: + device ='default' + + myrecording = sd.rec(int(duration * fs), samplerate=fs, channels=4, dtype='int16', device=device) + print('Waiting...') + sd.wait() # Wait until recording is finished + print('Done!') + + time = np.arange(0, duration, 1 / fs) # time vector + plt.plot(time, myrecording) + plt.xlabel('Time [s]') + plt.ylabel('Amplitude') + plt.title('MicNode 4 Channel') + plt.show() + \ No newline at end of file diff --git a/examples/device/audio_4_channel_mic/src/tusb_config.h b/examples/device/audio_4_channel_mic/src/tusb_config.h new file mode 100644 index 000000000..44be5a0d7 --- /dev/null +++ b/examples/device/audio_4_channel_mic/src/tusb_config.h @@ -0,0 +1,118 @@ +/* + * 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. + * + */ + +#ifndef _TUSB_CONFIG_H_ +#define _TUSB_CONFIG_H_ + +#ifdef __cplusplus +extern "C" { +#endif + +//-------------------------------------------------------------------- +// COMMON CONFIGURATION +//-------------------------------------------------------------------- + +// defined by compiler flags for flexibility +#ifndef CFG_TUSB_MCU +#error CFG_TUSB_MCU must be defined +#endif + +#if CFG_TUSB_MCU == OPT_MCU_LPC43XX || CFG_TUSB_MCU == OPT_MCU_LPC18XX || CFG_TUSB_MCU == OPT_MCU_MIMXRT10XX +#define CFG_TUSB_RHPORT0_MODE (OPT_MODE_DEVICE | OPT_MODE_HIGH_SPEED) +#else +#define CFG_TUSB_RHPORT0_MODE OPT_MODE_DEVICE +#endif + +#ifndef CFG_TUSB_OS +#define CFG_TUSB_OS OPT_OS_NONE +#endif + +#ifndef CFG_TUSB_DEBUG +#define CFG_TUSB_DEBUG 0 +#endif + +// CFG_TUSB_DEBUG is defined by compiler in DEBUG build +// #define CFG_TUSB_DEBUG 0 + +/* USB DMA on some MCUs can only access a specific SRAM region with restriction on alignment. + * Tinyusb use follows macros to declare transferring memory so that they can be put + * into those specific section. + * e.g + * - CFG_TUSB_MEM SECTION : __attribute__ (( section(".usb_ram") )) + * - CFG_TUSB_MEM_ALIGN : __attribute__ ((aligned(4))) + */ +#ifndef CFG_TUSB_MEM_SECTION +#define CFG_TUSB_MEM_SECTION +#endif + +#ifndef CFG_TUSB_MEM_ALIGN +#define CFG_TUSB_MEM_ALIGN __attribute__ ((aligned(4))) +#endif + +//-------------------------------------------------------------------- +// DEVICE CONFIGURATION +//-------------------------------------------------------------------- + +#ifndef CFG_TUD_ENDPOINT0_SIZE +#define CFG_TUD_ENDPOINT0_SIZE 64 +#endif + +//------------- CLASS -------------// +#define CFG_TUD_CDC 0 +#define CFG_TUD_MSC 0 +#define CFG_TUD_HID 0 +#define CFG_TUD_MIDI 0 +#define CFG_TUD_AUDIO 1 +#define CFG_TUD_VENDOR 0 + +//-------------------------------------------------------------------- +// AUDIO CLASS DRIVER CONFIGURATION +//-------------------------------------------------------------------- + +// Have a look into audio_device.h for all configurations + +#define CFG_TUD_AUDIO_FUNC_1_DESC_LEN TUD_AUDIO_MIC_FOUR_CH_DESC_LEN + +#define CFG_TUD_AUDIO_FUNC_1_N_AS_INT 1 +#define CFG_TUD_AUDIO_FUNC_1_CTRL_BUF_SZ 64 + +#define CFG_TUD_AUDIO_ENABLE_EP_IN 1 +#define CFG_TUD_AUDIO_FUNC_1_N_BYTES_PER_SAMPLE_TX 2 // This value is not required by the driver, it parses this information from the descriptor once the alternate interface is set by the host - we use it for the setup +#define CFG_TUD_AUDIO_FUNC_1_N_CHANNELS_TX 4 // This value is not required by the driver, it parses this information from the descriptor once the alternate interface is set by the host - we use it for the setup +#define CFG_TUD_AUDIO_EP_SZ_IN (48 + 1) * CFG_TUD_AUDIO_FUNC_1_N_BYTES_PER_SAMPLE_TX * CFG_TUD_AUDIO_FUNC_1_N_CHANNELS_TX // 48 Samples (48 kHz) x 2 Bytes/Sample x CFG_TUD_AUDIO_N_CHANNELS_TX Channels - the Windows driver always needs an extra sample per channel of space more, otherwise it complains... found by trial and error +#define CFG_TUD_AUDIO_FUNC_1_EP_IN_SZ_MAX CFG_TUD_AUDIO_EP_SZ_IN +#define CFG_TUD_AUDIO_FUNC_1_EP_IN_SW_BUF_SZ CFG_TUD_AUDIO_EP_SZ_IN + +#define CFG_TUD_AUDIO_ENABLE_ENCODING 1 +#define CFG_TUD_AUDIO_ENABLE_TYPE_I_ENCODING 1 +#define CFG_TUD_AUDIO_FUNC_1_CHANNEL_PER_FIFO_TX 2 // One I2S stream contains two channels, each stream is saved within one support FIFO - this value is currently fixed, the driver does not support a changing value +#define CFG_TUD_AUDIO_FUNC_1_N_TX_SUPP_SW_FIFO (CFG_TUD_AUDIO_FUNC_1_N_CHANNELS_TX / CFG_TUD_AUDIO_FUNC_1_CHANNEL_PER_FIFO_TX) +#define CFG_TUD_AUDIO_FUNC_1_TX_SUPP_SW_FIFO_SZ (CFG_TUD_AUDIO_EP_SZ_IN / CFG_TUD_AUDIO_FUNC_1_N_TX_SUPP_SW_FIFO) + +#ifdef __cplusplus +} +#endif + +#endif /* _TUSB_CONFIG_H_ */ diff --git a/examples/device/audio_4_channel_mic/src/usb_descriptors.c b/examples/device/audio_4_channel_mic/src/usb_descriptors.c new file mode 100644 index 000000000..93ec6e9f7 --- /dev/null +++ b/examples/device/audio_4_channel_mic/src/usb_descriptors.c @@ -0,0 +1,160 @@ +/* + * 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. + * + */ + +#include "tusb.h" + +/* A combination of interfaces must have a unique product id, since PC will save device driver after the first plug. + * Same VID/PID with different interface e.g MSC (first), then CDC (later) will possibly cause system error on PC. + * + * Auto ProductID layout's Bitmap: + * [MSB] AUDIO | MIDI | HID | MSC | CDC [LSB] + */ +#define _PID_MAP(itf, n) ( (CFG_TUD_##itf) << (n) ) +#define USB_PID (0x4000 | _PID_MAP(CDC, 0) | _PID_MAP(MSC, 1) | _PID_MAP(HID, 2) | \ + _PID_MAP(MIDI, 3) | _PID_MAP(AUDIO, 4) | _PID_MAP(VENDOR, 5) ) + +//--------------------------------------------------------------------+ +// Device Descriptors +//--------------------------------------------------------------------+ +tusb_desc_device_t const desc_device = +{ + .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 = 0xCafe, + .idProduct = USB_PID, + .bcdDevice = 0x0100, + + .iManufacturer = 0x01, + .iProduct = 0x02, + .iSerialNumber = 0x03, + + .bNumConfigurations = 0x01 +}; + +// Invoked when received GET DEVICE DESCRIPTOR +// Application return pointer to descriptor +uint8_t const * tud_descriptor_device_cb(void) +{ + return (uint8_t const *) &desc_device; +} + +//--------------------------------------------------------------------+ +// Configuration Descriptor +//--------------------------------------------------------------------+ +enum +{ + ITF_NUM_AUDIO_CONTROL = 0, + ITF_NUM_AUDIO_STREAMING, + ITF_NUM_TOTAL +}; + +#define CONFIG_TOTAL_LEN (TUD_CONFIG_DESC_LEN + CFG_TUD_AUDIO * TUD_AUDIO_MIC_FOUR_CH_DESC_LEN) + +#if CFG_TUSB_MCU == OPT_MCU_LPC175X_6X || CFG_TUSB_MCU == OPT_MCU_LPC177X_8X || CFG_TUSB_MCU == OPT_MCU_LPC40XX +// LPC 17xx and 40xx endpoint type (bulk/interrupt/iso) are fixed by its number +// 0 control, 1 In, 2 Bulk, 3 Iso, 4 In etc ... +#define EPNUM_AUDIO 0x03 +#else +#define EPNUM_AUDIO 0x01 +#endif + +uint8_t const desc_configuration[] = +{ + // Interface count, string index, total length, attribute, power in mA + TUD_CONFIG_DESCRIPTOR(1, ITF_NUM_TOTAL, 0, CONFIG_TOTAL_LEN, TUSB_DESC_CONFIG_ATT_REMOTE_WAKEUP, 100), + + // Interface number, string index, EP Out & EP In address, EP size + TUD_AUDIO_MIC_FOUR_CH_DESCRIPTOR(/*_itfnum*/ ITF_NUM_AUDIO_CONTROL, /*_stridx*/ 0, /*_nBytesPerSample*/ CFG_TUD_AUDIO_FUNC_1_N_BYTES_PER_SAMPLE_TX, /*_nBitsUsedPerSample*/ CFG_TUD_AUDIO_FUNC_1_N_BYTES_PER_SAMPLE_TX*8, /*_epin*/ 0x80 | EPNUM_AUDIO, /*_epsize*/ CFG_TUD_AUDIO_EP_SZ_IN) +}; + +// Invoked when received GET CONFIGURATION DESCRIPTOR +// Application return pointer to descriptor +// Descriptor 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 desc_configuration; +} + +//--------------------------------------------------------------------+ +// String Descriptors +//--------------------------------------------------------------------+ + +// array of pointer to string descriptors +char const* string_desc_arr [] = +{ + (const char[]) { 0x09, 0x04 }, // 0: is supported language is English (0x0409) + "PaniRCorp", // 1: Manufacturer + "MicNode_4_Ch", // 2: Product + "123458", // 3: Serials, should use chip ID + "UAC2", // 4: Audio Interface +}; + +static uint16_t _desc_str[32]; + +// Invoked when received GET STRING DESCRIPTOR request +// 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) +{ + (void) langid; + + uint8_t chr_count; + + if ( index == 0) + { + memcpy(&_desc_str[1], string_desc_arr[0], 2); + chr_count = 1; + }else + { + // Convert ASCII string into UTF-16 + + if ( !(index < sizeof(string_desc_arr)/sizeof(string_desc_arr[0])) ) return NULL; + + const char* str = string_desc_arr[index]; + + // Cap at max char + chr_count = strlen(str); + if ( chr_count > 31 ) chr_count = 31; + + for(uint8_t i=0; i #include #include @@ -34,6 +42,10 @@ // MACRO CONSTANT TYPEDEF PROTYPES //--------------------------------------------------------------------+ +#ifndef AUDIO_SAMPLE_RATE +#define AUDIO_SAMPLE_RATE 48000 +#endif + /* Blink pattern * - 250 ms : device not mounted * - 1000 ms : device mounted @@ -49,17 +61,17 @@ static uint32_t blink_interval_ms = BLINK_NOT_MOUNTED; // Audio controls // Current states -bool mute[CFG_TUD_AUDIO_N_CHANNELS_TX + 1]; // +1 for master channel 0 -uint16_t volume[CFG_TUD_AUDIO_N_CHANNELS_TX + 1]; // +1 for master channel 0 +bool mute[CFG_TUD_AUDIO_FUNC_1_N_CHANNELS_TX + 1]; // +1 for master channel 0 +uint16_t volume[CFG_TUD_AUDIO_FUNC_1_N_CHANNELS_TX + 1]; // +1 for master channel 0 uint32_t sampFreq; uint8_t clkValid; // Range states -audio_control_range_2_n_t(1) volumeRng[CFG_TUD_AUDIO_N_CHANNELS_TX+1]; // Volume range state +audio_control_range_2_n_t(1) volumeRng[CFG_TUD_AUDIO_FUNC_1_N_CHANNELS_TX+1]; // Volume range state audio_control_range_4_n_t(1) sampleFreqRng; // Sample frequency range state // Audio test data -uint16_t test_buffer_audio[CFG_TUD_AUDIO_TX_FIFO_SIZE/2]; +uint16_t test_buffer_audio[CFG_TUD_AUDIO_EP_SZ_IN/2]; uint16_t startVal = 0; void led_blinking_task(void); @@ -73,12 +85,12 @@ int main(void) tusb_init(); // Init values - sampFreq = 44100; + sampFreq = AUDIO_SAMPLE_RATE; clkValid = 1; sampleFreqRng.wNumSubRanges = 1; - sampleFreqRng.subrange[0].bMin = 44100; - sampleFreqRng.subrange[0].bMax = 44100; + sampleFreqRng.subrange[0].bMin = AUDIO_SAMPLE_RATE; + sampleFreqRng.subrange[0].bMax = AUDIO_SAMPLE_RATE; sampleFreqRng.subrange[0].bRes = 0; while (1) @@ -203,7 +215,6 @@ bool tud_audio_set_req_entity_cb(uint8_t rhport, tusb_control_request_t const * mute[channelNum] = ((audio_control_cur_1_t*) pBuff)->bCur; TU_LOG2(" Set Mute: %d of channel: %u\r\n", mute[channelNum], channelNum); - return true; case AUDIO_FU_CTRL_VOLUME: @@ -213,8 +224,7 @@ bool tud_audio_set_req_entity_cb(uint8_t rhport, tusb_control_request_t const * volume[channelNum] = ((audio_control_cur_2_t*) pBuff)->bCur; TU_LOG2(" Set Volume: %d dB of channel: %u\r\n", volume[channelNum], channelNum); - - return true; + return true; // Unknown/Unsupported control default: @@ -271,96 +281,110 @@ bool tud_audio_get_req_entity_cb(uint8_t rhport, tusb_control_request_t const * // Input terminal (Microphone input) if (entityID == 1) { - switch (ctrlSel) + switch ( ctrlSel ) { - case AUDIO_TE_CTRL_CONNECTOR:; - // The terminal connector control only has a get request with only the CUR attribute. + case AUDIO_TE_CTRL_CONNECTOR: + { + // The terminal connector control only has a get request with only the CUR attribute. + audio_desc_channel_cluster_t ret; - audio_desc_channel_cluster_t ret; + // Those are dummy values for now + ret.bNrChannels = 1; + ret.bmChannelConfig = 0; + ret.iChannelNames = 0; - // Those are dummy values for now - ret.bNrChannels = 1; - ret.bmChannelConfig = 0; - ret.iChannelNames = 0; + TU_LOG2(" Get terminal connector\r\n"); - TU_LOG2(" Get terminal connector\r\n"); + return tud_audio_buffer_and_schedule_control_xfer(rhport, p_request, (void*) &ret, sizeof(ret)); + } + break; - return tud_audio_buffer_and_schedule_control_xfer(rhport, p_request, (void*)&ret, sizeof(ret)); - - // Unknown/Unsupported control selector - default: TU_BREAKPOINT(); return false; + // Unknown/Unsupported control selector + default: + TU_BREAKPOINT(); + return false; } } // Feature unit if (entityID == 2) { - switch (ctrlSel) + switch ( ctrlSel ) { case AUDIO_FU_CTRL_MUTE: - // Audio control mute cur parameter block consists of only one byte - we thus can send it right away - // There does not exist a range parameter block for mute - TU_LOG2(" Get Mute of channel: %u\r\n", channelNum); - return tud_control_xfer(rhport, p_request, &mute[channelNum], 1); + // Audio control mute cur parameter block consists of only one byte - we thus can send it right away + // There does not exist a range parameter block for mute + TU_LOG2(" Get Mute of channel: %u\r\n", channelNum); + return tud_control_xfer(rhport, p_request, &mute[channelNum], 1); case AUDIO_FU_CTRL_VOLUME: + switch ( p_request->bRequest ) + { + case AUDIO_CS_REQ_CUR: + TU_LOG2(" Get Volume of channel: %u\r\n", channelNum); + return tud_control_xfer(rhport, p_request, &volume[channelNum], sizeof(volume[channelNum])); - switch (p_request->bRequest) - { - case AUDIO_CS_REQ_CUR: - TU_LOG2(" Get Volume of channel: %u\r\n", channelNum); - return tud_control_xfer(rhport, p_request, &volume[channelNum], sizeof(volume[channelNum])); - case AUDIO_CS_REQ_RANGE: - TU_LOG2(" Get Volume range of channel: %u\r\n", channelNum); + case AUDIO_CS_REQ_RANGE: + TU_LOG2(" Get Volume range of channel: %u\r\n", channelNum); - // Copy values - only for testing - better is version below - audio_control_range_2_n_t(1) ret; + // Copy values - only for testing - better is version below + audio_control_range_2_n_t(1) + ret; - ret.wNumSubRanges = 1; - ret.subrange[0].bMin = -90; // -90 dB - ret.subrange[0].bMax = 90; // +90 dB - ret.subrange[0].bRes = 1; // 1 dB steps + ret.wNumSubRanges = 1; + ret.subrange[0].bMin = -90; // -90 dB + ret.subrange[0].bMax = 90; // +90 dB + ret.subrange[0].bRes = 1; // 1 dB steps - return tud_audio_buffer_and_schedule_control_xfer(rhport, p_request, (void*)&ret, sizeof(ret)); + return tud_audio_buffer_and_schedule_control_xfer(rhport, p_request, (void*) &ret, sizeof(ret)); - // Unknown/Unsupported control - default: TU_BREAKPOINT(); return false; - } + // Unknown/Unsupported control + default: + TU_BREAKPOINT(); + return false; + } + break; - // Unknown/Unsupported control - default: TU_BREAKPOINT(); return false; + // Unknown/Unsupported control + default: + TU_BREAKPOINT(); + return false; } } // Clock Source unit - if (entityID == 4) + if ( entityID == 4 ) { - switch (ctrlSel) + switch ( ctrlSel ) { case AUDIO_CS_CTRL_SAM_FREQ: + // channelNum is always zero in this case + switch ( p_request->bRequest ) + { + case AUDIO_CS_REQ_CUR: + TU_LOG2(" Get Sample Freq.\r\n"); + return tud_control_xfer(rhport, p_request, &sampFreq, sizeof(sampFreq)); - // channelNum is always zero in this case + case AUDIO_CS_REQ_RANGE: + TU_LOG2(" Get Sample Freq. range\r\n"); + return tud_control_xfer(rhport, p_request, &sampleFreqRng, sizeof(sampleFreqRng)); - switch (p_request->bRequest) - { - case AUDIO_CS_REQ_CUR: - TU_LOG2(" Get Sample Freq.\r\n"); - return tud_control_xfer(rhport, p_request, &sampFreq, sizeof(sampFreq)); - case AUDIO_CS_REQ_RANGE: - TU_LOG2(" Get Sample Freq. range\r\n"); - return tud_control_xfer(rhport, p_request, &sampleFreqRng, sizeof(sampleFreqRng)); + // Unknown/Unsupported control + default: + TU_BREAKPOINT(); + return false; + } + break; - // Unknown/Unsupported control - default: TU_BREAKPOINT(); return false; - } + case AUDIO_CS_CTRL_CLK_VALID: + // Only cur attribute exists for this request + TU_LOG2(" Get Sample Freq. valid\r\n"); + return tud_control_xfer(rhport, p_request, &clkValid, sizeof(clkValid)); - case AUDIO_CS_CTRL_CLK_VALID: - // Only cur attribute exists for this request - TU_LOG2(" Get Sample Freq. valid\r\n"); - return tud_control_xfer(rhport, p_request, &clkValid, sizeof(clkValid)); - - // Unknown/Unsupported control - default: TU_BREAKPOINT(); return false; + // Unknown/Unsupported control + default: + TU_BREAKPOINT(); + return false; } } @@ -375,7 +399,7 @@ bool tud_audio_tx_done_pre_load_cb(uint8_t rhport, uint8_t itf, uint8_t ep_in, u (void) ep_in; (void) cur_alt_setting; - tud_audio_write ((uint8_t *)test_buffer_audio, CFG_TUD_AUDIO_TX_FIFO_SIZE); + tud_audio_write ((uint8_t *)test_buffer_audio, CFG_TUD_AUDIO_EP_SZ_IN); return true; } @@ -388,7 +412,7 @@ bool tud_audio_tx_done_post_load_cb(uint8_t rhport, uint16_t n_bytes_copied, uin (void) ep_in; (void) cur_alt_setting; - for (size_t cnt = 0; cnt < CFG_TUD_AUDIO_TX_FIFO_SIZE/2; cnt++) + for (size_t cnt = 0; cnt < CFG_TUD_AUDIO_EP_SZ_IN/2; cnt++) { test_buffer_audio[cnt] = startVal++; } diff --git a/examples/device/audio_test/src/plot_audio_samples.py b/examples/device/audio_test/src/plot_audio_samples.py new file mode 100644 index 000000000..6e3c4978e --- /dev/null +++ b/examples/device/audio_test/src/plot_audio_samples.py @@ -0,0 +1,34 @@ +import sounddevice as sd +import matplotlib.pyplot as plt +import numpy as np +import platform + +if __name__ == '__main__': + + # If you got "ValueError: No input device matching", that is because your PC name example device + # differently from tested list below. Uncomment the next line to see full list and try to pick correct one + # print(sd.query_devices()) + + fs = 48000 # Sample rate + duration = 100e-3 # Duration of recording + + if platform.system() == 'Windows': + # MME is needed since there are more than one MicNode device APIs (at least in Windows) + device = 'Microphone (MicNode) MME' + elif platform.system() == 'Darwin': + device = 'MicNode' + else: + device ='default' + + myrecording = sd.rec(int(duration * fs), samplerate=fs, channels=1, dtype='int16', device=device) + print('Waiting...') + sd.wait() # Wait until recording is finished + print('Done!') + + time = np.arange(0, duration, 1 / fs) # time vector + plt.plot(time, myrecording) + plt.xlabel('Time [s]') + plt.ylabel('Amplitude') + plt.title('MicNode') + plt.show() + \ No newline at end of file diff --git a/examples/device/audio_test/src/tusb_config.h b/examples/device/audio_test/src/tusb_config.h index 61e63f073..683a1a567 100644 --- a/examples/device/audio_test/src/tusb_config.h +++ b/examples/device/audio_test/src/tusb_config.h @@ -91,25 +91,18 @@ extern "C" { // AUDIO CLASS DRIVER CONFIGURATION //-------------------------------------------------------------------- -// Audio format type -#define CFG_TUD_AUDIO_USE_TX_FIFO 1 -#define CFG_TUD_AUDIO_FORMAT_TYPE_TX AUDIO_FORMAT_TYPE_I -#define CFG_TUD_AUDIO_FORMAT_TYPE_RX AUDIO_FORMAT_TYPE_UNDEFINED +// Have a look into audio_device.h for all configurations -// Audio format type I specifications -#define CFG_TUD_AUDIO_FORMAT_TYPE_I_TX AUDIO_DATA_FORMAT_TYPE_I_PCM -#define CFG_TUD_AUDIO_N_CHANNELS_TX 1 -#define CFG_TUD_AUDIO_N_BYTES_PER_SAMPLE_TX 2 +#define CFG_TUD_AUDIO_FUNC_1_DESC_LEN TUD_AUDIO_MIC_ONE_CH_DESC_LEN +#define CFG_TUD_AUDIO_FUNC_1_N_AS_INT 1 // Number of Standard AS Interface Descriptors (4.9.1) defined per audio function - this is required to be able to remember the current alternate settings of these interfaces - We restrict us here to have a constant number for all audio functions (which means this has to be the maximum number of AS interfaces an audio function has and a second audio function with less AS interfaces just wastes a few bytes) +#define CFG_TUD_AUDIO_FUNC_1_CTRL_BUF_SZ 64 // Size of control request buffer -// EP and buffer size - for isochronous EP´s, the buffer and EP size are equal (different sizes would not make sense) -#define CFG_TUD_AUDIO_EPSIZE_IN 48*CFG_TUD_AUDIO_N_BYTES_PER_SAMPLE_TX*CFG_TUD_AUDIO_N_CHANNELS_TX // 48 Samples (48 kHz) x 2 Bytes/Sample x 1 Channels -#define CFG_TUD_AUDIO_TX_FIFO_SIZE 48*2 // 48 Samples (48 kHz) x 2 Bytes/Sample (1/2 word) - -// Number of Standard AS Interface Descriptors (4.9.1) defined per audio function - this is required to be able to remember the current alternate settings of these interfaces - We restrict us here to have a constant number for all audio functions (which means this has to be the maximum number of AS interfaces an audio function has and a second audio function with less AS interfaces just wastes a few bytes) -#define CFG_TUD_AUDIO_N_AS_INT 1 - -// Size of control request buffer -#define CFG_TUD_AUDIO_CTRL_BUF_SIZE 64 +#define CFG_TUD_AUDIO_ENABLE_EP_IN 1 +#define CFG_TUD_AUDIO_FUNC_1_N_BYTES_PER_SAMPLE_TX 2 // Driver gets this info from the descriptors - we define it here to use it to setup the descriptors and to do calculations with it below +#define CFG_TUD_AUDIO_FUNC_1_N_CHANNELS_TX 1 // Driver gets this info from the descriptors - we define it here to use it to setup the descriptors and to do calculations with it below - be aware: for different number of channels you need another descriptor! +#define CFG_TUD_AUDIO_EP_SZ_IN 48 * CFG_TUD_AUDIO_FUNC_1_N_BYTES_PER_SAMPLE_TX * CFG_TUD_AUDIO_FUNC_1_N_CHANNELS_TX // 48 Samples (48 kHz) x 2 Bytes/Sample x 1 Channel +#define CFG_TUD_AUDIO_FUNC_1_EP_IN_SZ_MAX CFG_TUD_AUDIO_EP_SZ_IN // Maximum EP IN size for all AS alternate settings used +#define CFG_TUD_AUDIO_FUNC_1_EP_IN_SW_BUF_SZ CFG_TUD_AUDIO_EP_SZ_IN + 1 #ifdef __cplusplus } diff --git a/examples/device/audio_test/src/usb_descriptors.c b/examples/device/audio_test/src/usb_descriptors.c index 02d018823..67dd34d2a 100644 --- a/examples/device/audio_test/src/usb_descriptors.c +++ b/examples/device/audio_test/src/usb_descriptors.c @@ -79,7 +79,7 @@ enum ITF_NUM_TOTAL }; -#define CONFIG_TOTAL_LEN (TUD_CONFIG_DESC_LEN + CFG_TUD_AUDIO * TUD_AUDIO_MIC_DESC_LEN) +#define CONFIG_TOTAL_LEN (TUD_CONFIG_DESC_LEN + CFG_TUD_AUDIO * TUD_AUDIO_MIC_ONE_CH_DESC_LEN) #if CFG_TUSB_MCU == OPT_MCU_LPC175X_6X || CFG_TUSB_MCU == OPT_MCU_LPC177X_8X || CFG_TUSB_MCU == OPT_MCU_LPC40XX // LPC 17xx and 40xx endpoint type (bulk/interrupt/iso) are fixed by its number @@ -89,20 +89,13 @@ enum #define EPNUM_AUDIO 0x01 #endif -// These variables are required by the audio driver in audio_device.c - -// List of audio descriptor lengths which is required by audio driver - you need as many entries as CFG_TUD_AUDIO - unfortunately this is not possible to determine otherwise -const uint16_t tud_audio_desc_lengths[] = {TUD_AUDIO_MIC_DESC_LEN}; - -// TAKE CARE - THE NUMBER OF AUDIO STREAMING INTERFACES PER AUDIO FUNCTION MUST NOT EXCEED CFG_TUD_AUDIO_N_AS_INT - IF IT DOES INCREASE CFG_TUD_AUDIO_N_AS_INT IN tusb_config.h! - uint8_t const desc_configuration[] = { // Interface count, string index, total length, attribute, power in mA TUD_CONFIG_DESCRIPTOR(1, ITF_NUM_TOTAL, 0, CONFIG_TOTAL_LEN, TUSB_DESC_CONFIG_ATT_REMOTE_WAKEUP, 100), // Interface number, string index, EP Out & EP In address, EP size - TUD_AUDIO_MIC_DESCRIPTOR(/*_itfnum*/ ITF_NUM_AUDIO_CONTROL, /*_stridx*/ 0, /*_nBytesPerSample*/ 3, /*_nBitsUsedPerSample*/ 24, /*_epin*/ 0x80 | EPNUM_AUDIO, /*_epsize*/ 48*4) + TUD_AUDIO_MIC_ONE_CH_DESCRIPTOR(/*_itfnum*/ ITF_NUM_AUDIO_CONTROL, /*_stridx*/ 0, /*_nBytesPerSample*/ CFG_TUD_AUDIO_FUNC_1_N_BYTES_PER_SAMPLE_TX, /*_nBitsUsedPerSample*/ CFG_TUD_AUDIO_FUNC_1_N_BYTES_PER_SAMPLE_TX*8, /*_epin*/ 0x80 | EPNUM_AUDIO, /*_epsize*/ CFG_TUD_AUDIO_EP_SZ_IN) }; // Invoked when received GET CONFIGURATION DESCRIPTOR diff --git a/examples/device/uac2_headset/src/main.c b/examples/device/uac2_headset/src/main.c index cdb29f3f4..d29deb588 100644 --- a/examples/device/uac2_headset/src/main.c +++ b/examples/device/uac2_headset/src/main.c @@ -34,6 +34,10 @@ // MACRO CONSTANT TYPEDEF PROTOTYPES //--------------------------------------------------------------------+ +#ifndef AUDIO_SAMPLE_RATE +#define AUDIO_SAMPLE_RATE 48000 +#endif + /* Blink pattern * - 25 ms : streaming data * - 250 ms : device not mounted @@ -68,8 +72,8 @@ static uint32_t blink_interval_ms = BLINK_NOT_MOUNTED; // Audio controls // Current states -int8_t mute[CFG_TUD_AUDIO_N_CHANNELS_TX + 1]; // +1 for master channel 0 -int16_t volume[CFG_TUD_AUDIO_N_CHANNELS_TX + 1]; // +1 for master channel 0 +int8_t mute[CFG_TUD_AUDIO_FUNC_1_N_CHANNELS_TX + 1]; // +1 for master channel 0 +int16_t volume[CFG_TUD_AUDIO_FUNC_1_N_CHANNELS_TX + 1]; // +1 for master channel 0 // Buffer for microphone data int16_t mic_buf[1000]; diff --git a/examples/device/uac2_headset/src/tusb_config.h b/examples/device/uac2_headset/src/tusb_config.h index ebf181671..88f9efcff 100644 --- a/examples/device/uac2_headset/src/tusb_config.h +++ b/examples/device/uac2_headset/src/tusb_config.h @@ -35,6 +35,8 @@ extern "C" { // COMMON CONFIGURATION //-------------------------------------------------------------------- +#include "usb_descriptors.h" + // defined by compiler flags for flexibility #ifndef CFG_TUSB_MCU #error CFG_TUSB_MCU must be defined @@ -91,43 +93,36 @@ extern "C" { //-------------------------------------------------------------------- // AUDIO CLASS DRIVER CONFIGURATION //-------------------------------------------------------------------- +#define CFG_TUD_AUDIO_IN_PATH (CFG_TUD_AUDIO) +#define CFG_TUD_AUDIO_OUT_PATH (CFG_TUD_AUDIO) -#ifndef AUDIO_SAMPLE_RATE -#define AUDIO_SAMPLE_RATE 48000 -#endif - -#define CFG_TUD_AUDIO_IN_PATH (CFG_TUD_AUDIO) -#define CFG_TUD_AUDIO_OUT_PATH (CFG_TUD_AUDIO) - -// Audio format type -#define CFG_TUD_AUDIO_FORMAT_TYPE_TX AUDIO_FORMAT_TYPE_I -#define CFG_TUD_AUDIO_FORMAT_TYPE_RX AUDIO_FORMAT_TYPE_I +//#define CFG_TUD_AUDIO_FUNC_1_DESC_LEN 220 // This equals TUD_AUDIO_HEADSET_STEREO_DESC_LEN, however, including it from usb_descriptors.h is not possible due to some strange include hassle +#define CFG_TUD_AUDIO_FUNC_1_DESC_LEN TUD_AUDIO_HEADSET_STEREO_DESC_LEN // Audio format type I specifications -#define CFG_TUD_AUDIO_FORMAT_TYPE_I_TX AUDIO_DATA_FORMAT_TYPE_I_PCM -#define CFG_TUD_AUDIO_FORMAT_TYPE_I_RX AUDIO_DATA_FORMAT_TYPE_I_PCM -#define CFG_TUD_AUDIO_N_CHANNELS_TX 1 -#define CFG_TUD_AUDIO_N_BYTES_PER_SAMPLE_TX 2 -#define CFG_TUD_AUDIO_N_CHANNELS_RX 2 -#define CFG_TUD_AUDIO_N_BYTES_PER_SAMPLE_RX 2 -#define CFG_TUD_AUDIO_RX_ITEMSIZE 2 -#define CFG_TUD_AUDIO_ENABLE_FEEDBACK_EP 0 +#define CFG_TUD_AUDIO_FUNC_1_N_CHANNELS_TX 1 +#define CFG_TUD_AUDIO_FUNC_1_N_BYTES_PER_SAMPLE_TX 2 +#define CFG_TUD_AUDIO_FUNC_1_N_CHANNELS_RX 2 +#define CFG_TUD_AUDIO_FUNC_1_N_BYTES_PER_SAMPLE_RX 2 +#define CFG_TUD_AUDIO_ENABLE_FEEDBACK_EP 0 // EP and buffer size - for isochronous EP´s, the buffer and EP size are equal (different sizes would not make sense) -#define CFG_TUD_AUDIO_EPSIZE_IN (CFG_TUD_AUDIO_IN_PATH * (48 + 1) * (CFG_TUD_AUDIO_N_BYTES_PER_SAMPLE_TX) * (CFG_TUD_AUDIO_N_CHANNELS_TX)) // 48 Samples (48 kHz) x 2 Bytes/Sample x n Channels -#define CFG_TUD_AUDIO_TX_FIFO_COUNT (CFG_TUD_AUDIO_IN_PATH * 1) -#define CFG_TUD_AUDIO_TX_FIFO_SIZE (CFG_TUD_AUDIO_IN_PATH ? ((CFG_TUD_AUDIO_EPSIZE_IN)) : 0) +#define CFG_TUD_AUDIO_ENABLE_EP_IN 1 +#define CFG_TUD_AUDIO_EP_SZ_IN (CFG_TUD_AUDIO_IN_PATH * (48 + 1) * (CFG_TUD_AUDIO_FUNC_1_N_BYTES_PER_SAMPLE_TX) * (CFG_TUD_AUDIO_FUNC_1_N_CHANNELS_TX)) // 48 Samples (48 kHz) x 2 Bytes/Sample x n Channels +#define CFG_TUD_AUDIO_FUNC_1_EP_IN_SW_BUF_SZ CFG_TUD_AUDIO_EP_SZ_IN +#define CFG_TUD_AUDIO_FUNC_1_EP_IN_SZ_MAX CFG_TUD_AUDIO_EP_SZ_IN // Maximum EP IN size for all AS alternate settings used // EP and buffer size - for isochronous EP´s, the buffer and EP size are equal (different sizes would not make sense) -#define CFG_TUD_AUDIO_EPSIZE_OUT (CFG_TUD_AUDIO_OUT_PATH * ((48 + CFG_TUD_AUDIO_ENABLE_FEEDBACK_EP) * (CFG_TUD_AUDIO_N_BYTES_PER_SAMPLE_RX) * (CFG_TUD_AUDIO_N_CHANNELS_RX))) // N Samples (N kHz) x 2 Bytes/Sample x n Channels -#define CFG_TUD_AUDIO_RX_FIFO_COUNT (CFG_TUD_AUDIO_OUT_PATH * 1) -#define CFG_TUD_AUDIO_RX_FIFO_SIZE (CFG_TUD_AUDIO_OUT_PATH ? (3 * (CFG_TUD_AUDIO_EPSIZE_OUT / CFG_TUD_AUDIO_RX_FIFO_COUNT)) : 0) +#define CFG_TUD_AUDIO_ENABLE_EP_OUT 1 +#define CFG_TUD_AUDIO_EP_OUT_SZ (CFG_TUD_AUDIO_OUT_PATH * ((48 + CFG_TUD_AUDIO_ENABLE_FEEDBACK_EP) * (CFG_TUD_AUDIO_FUNC_1_N_BYTES_PER_SAMPLE_RX) * (CFG_TUD_AUDIO_FUNC_1_N_CHANNELS_RX))) // N Samples (N kHz) x 2 Bytes/Sample x n Channels +#define CFG_TUD_AUDIO_FUNC_1_EP_OUT_SW_BUF_SZ CFG_TUD_AUDIO_EP_OUT_SZ*3 +#define CFG_TUD_AUDIO_FUNC_1_EP_OUT_SZ_MAX CFG_TUD_AUDIO_EP_OUT_SZ // Maximum EP IN size for all AS alternate settings used // Number of Standard AS Interface Descriptors (4.9.1) defined per audio function - this is required to be able to remember the current alternate settings of these interfaces - We restrict us here to have a constant number for all audio functions (which means this has to be the maximum number of AS interfaces an audio function has and a second audio function with less AS interfaces just wastes a few bytes) -#define CFG_TUD_AUDIO_N_AS_INT 1 +#define CFG_TUD_AUDIO_FUNC_1_N_AS_INT 1 // Size of control request buffer -#define CFG_TUD_AUDIO_CTRL_BUF_SIZE 64 +#define CFG_TUD_AUDIO_FUNC_1_CTRL_BUF_SZ 64 #ifdef __cplusplus } diff --git a/examples/device/uac2_headset/src/usb_descriptors.c b/examples/device/uac2_headset/src/usb_descriptors.c index 940f0fe2b..cd749eb65 100644 --- a/examples/device/uac2_headset/src/usb_descriptors.c +++ b/examples/device/uac2_headset/src/usb_descriptors.c @@ -87,18 +87,13 @@ uint8_t const * tud_descriptor_device_cb(void) #define EPNUM_AUDIO 0x01 #endif -// These variables are required by the audio driver in audio_device.c - -// List of audio descriptor lengths which is required by audio driver - you need as many entries as CFG_TUD_AUDIO -const uint16_t tud_audio_desc_lengths[] = {TUD_AUDIO_HEADSET_STEREO_DESC_LEN}; - uint8_t const desc_configuration[] = { // Interface count, string index, total length, attribute, power in mA TUD_CONFIG_DESCRIPTOR(1, ITF_NUM_TOTAL, 0, CONFIG_TOTAL_LEN, TUSB_DESC_CONFIG_ATT_REMOTE_WAKEUP, 100), // Interface number, string index, EP Out & EP In address, EP size - TUD_AUDIO_HEADSET_STEREO_DESCRIPTOR(2, 2, 16, EPNUM_AUDIO, CFG_TUD_AUDIO_EPSIZE_OUT, EPNUM_AUDIO | 0x80, CFG_TUD_AUDIO_EPSIZE_IN) + TUD_AUDIO_HEADSET_STEREO_DESCRIPTOR(2, 2, 16, EPNUM_AUDIO, CFG_TUD_AUDIO_EP_OUT_SZ, EPNUM_AUDIO | 0x80, CFG_TUD_AUDIO_EP_SZ_IN) }; // Invoked when received GET CONFIGURATION DESCRIPTOR diff --git a/examples/device/uac2_headset/src/usb_descriptors.h b/examples/device/uac2_headset/src/usb_descriptors.h index 6651897b0..d9c5a63a7 100644 --- a/examples/device/uac2_headset/src/usb_descriptors.h +++ b/examples/device/uac2_headset/src/usb_descriptors.h @@ -26,7 +26,7 @@ #ifndef _USB_DESCRIPTORS_H_ #define _USB_DESCRIPTORS_H_ -#include "tusb.h" +// #include "tusb.h" // Unit numbers are arbitrary selected #define UAC2_ENTITY_CLOCK 0x04 diff --git a/src/class/audio/audio.h b/src/class/audio/audio.h index 05e61f8df..936f09104 100644 --- a/src/class/audio/audio.h +++ b/src/class/audio/audio.h @@ -469,44 +469,28 @@ typedef enum /// 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; +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; -#define AUDIO_FORMAT_TYPE_UNDEFINED 0x00 -#define AUDIO_FORMAT_TYPE_I 0x01 -#define AUDIO_FORMAT_TYPE_II 0x02 -#define AUDIO_FORMAT_TYPE_III 0x03 -#define AUDIO_FORMAT_TYPE_IV 0x04 -#define AUDIO_EXT_FORMAT_TYPE_I 0x81 -#define AUDIO_EXT_FORMAT_TYPE_II 0x82 -#define AUDIO_EXT_FORMAT_TYPE_III 0x83 - -/// 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 = 0x100000000, -//} audio_data_format_type_I_t; - -#define AUDIO_DATA_FORMAT_TYPE_I_PCM ((uint32_t) (1 << 0)) -#define AUDIO_DATA_FORMAT_TYPE_I_PCM8 ((uint32_t) (1 << 1)) -#define AUDIO_DATA_FORMAT_TYPE_I_IEEE_FLOAT ((uint32_t) (1 << 2)) -#define AUDIO_DATA_FORMAT_TYPE_I_ALAW ((uint32_t) (1 << 3)) -#define AUDIO_DATA_FORMAT_TYPE_I_MULAW ((uint32_t) (1 << 4)) -#define AUDIO_DATA_FORMAT_TYPE_I_RAW_DATA 0x100000000 +// 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 = 0x100000000, +} audio_data_format_type_I_t; /// All remaining definitions are taken from the descriptor descriptions in the UAC2 main specification @@ -901,7 +885,7 @@ typedef struct TU_ATTR_PACKED { } subrange[numSubRanges] ; \ } - /// 5.2.3.2 2-byte Control RANGE Parameter Block +/// 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; \ @@ -912,7 +896,7 @@ typedef struct TU_ATTR_PACKED { } subrange[numSubRanges]; \ } - // 5.2.3.3 4-byte Control RANGE Parameter Block +// 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; \ @@ -923,12 +907,12 @@ typedef struct TU_ATTR_PACKED { } subrange[numSubRanges]; \ } - /** @} */ +/** @} */ #ifdef __cplusplus - } +} #endif #endif - /** @} */ +/** @} */ diff --git a/src/class/audio/audio_device.c b/src/class/audio/audio_device.c index 8581925e4..4057f5f64 100644 --- a/src/class/audio/audio_device.c +++ b/src/class/audio/audio_device.c @@ -30,6 +30,22 @@ * * 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" @@ -47,15 +63,179 @@ // MACRO CONSTANT TYPEDEF //--------------------------------------------------------------------+ -#if CFG_TUD_AUDIO_EPSIZE_IN && CFG_TUD_AUDIO_TX_FIFO_SIZE -#ifndef CFG_TUD_AUDIO_TX_FIFO_COUNT -#define CFG_TUD_AUDIO_TX_FIFO_COUNT CFG_TUD_AUDIO_N_CHANNELS_TX +// 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 synopsys use non-linear buffer for now +// Synopsys detection copied from dcd_synopsys.c (refactor later on) +#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 (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)) +#define USE_LINEAR_BUFFER 0 +#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 + +// EP IN software buffers and mutexes +#if CFG_TUD_AUDIO_ENABLE_EP_IN && !CFG_TUD_AUDIO_ENABLE_ENCODING +#if CFG_TUD_AUDIO_FUNC_1_EP_IN_SW_BUF_SZ > 0 +CFG_TUSB_MEM_ALIGN uint8_t audio_ep_in_sw_buf_1[CFG_TUD_AUDIO_FUNC_1_EP_IN_SW_BUF_SZ]; +#if CFG_FIFO_MUTEX +osal_mutex_def_t ep_in_ff_mutex_wr_1; // No need for read mutex as only USB driver reads from FIFO +#endif +#endif // CFG_TUD_AUDIO_FUNC_1_EP_IN_SW_BUF_SZ > 0 +#if CFG_TUD_AUDIO > 1 && CFG_TUD_AUDIO_FUNC_2_EP_IN_SW_BUF_SZ > 0 +CFG_TUSB_MEM_ALIGN uint8_t audio_ep_in_sw_buf_2[CFG_TUD_AUDIO_FUNC_2_EP_IN_SW_BUF_SZ]; +#if CFG_FIFO_MUTEX +osal_mutex_def_t ep_in_ff_mutex_wr_2; // No need for read mutex as only USB driver reads from FIFO +#endif +#endif // CFG_TUD_AUDIO > 1 && CFG_TUD_AUDIO_FUNC_2_EP_IN_SW_BUF_SZ > 0 +#if CFG_TUD_AUDIO > 2 && CFG_TUD_AUDIO_FUNC_3_EP_IN_SW_BUF_SZ > 0 +CFG_TUSB_MEM_ALIGN uint8_t audio_ep_in_sw_buf_3[CFG_TUD_AUDIO_FUNC_3_EP_IN_SW_BUF_SZ]; +#if CFG_FIFO_MUTEX +osal_mutex_def_t ep_in_ff_mutex_wr_3; // No need for read mutex as only USB driver reads from FIFO +#endif +#endif // CFG_TUD_AUDIO > 2 && CFG_TUD_AUDIO_FUNC_3_EP_IN_SW_BUF_SZ > 0 +#endif // CFG_TUD_AUDIO_ENABLE_EP_IN && !CFG_TUD_AUDIO_ENABLE_ENCODING + +// Linear buffer TX in case: +// - target MCU is not capable of handling a ring buffer FIFO e.g. no hardware buffer is available or driver is would need to be changed dramatically OR +// - the software encoding is used - in this case the linear buffers serve as a target memory where logical channels are encoded into +#if CFG_TUD_AUDIO_ENABLE_EP_IN && (USE_LINEAR_BUFFER || CFG_TUD_AUDIO_ENABLE_ENCODING) +#if CFG_TUD_AUDIO_FUNC_1_EP_IN_SZ_MAX > 0 +CFG_TUSB_MEM_ALIGN uint8_t lin_buf_in_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 +CFG_TUSB_MEM_ALIGN uint8_t lin_buf_in_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 +CFG_TUSB_MEM_ALIGN uint8_t lin_buf_in_3[CFG_TUD_AUDIO_FUNC_3_EP_IN_SZ_MAX]; +#endif +#endif // CFG_TUD_AUDIO_ENABLE_EP_IN && (USE_LINEAR_BUFFER || CFG_TUD_AUDIO_ENABLE_DECODING) + +// EP OUT software buffers and mutexes +#if CFG_TUD_AUDIO_ENABLE_EP_OUT && !CFG_TUD_AUDIO_ENABLE_DECODING +#if CFG_TUD_AUDIO_FUNC_1_EP_OUT_SW_BUF_SZ > 0 +CFG_TUSB_MEM_ALIGN uint8_t audio_ep_out_sw_buf_1[CFG_TUD_AUDIO_FUNC_1_EP_OUT_SW_BUF_SZ]; +#if CFG_FIFO_MUTEX +osal_mutex_def_t ep_out_ff_mutex_rd_1; // No need for write mutex as only USB driver writes into FIFO +#endif +#endif // CFG_TUD_AUDIO_FUNC_1_EP_OUT_SW_BUF_SZ > 0 +#if CFG_TUD_AUDIO > 1 && CFG_TUD_AUDIO_FUNC_2_EP_OUT_SW_BUF_SZ > 0 +CFG_TUSB_MEM_ALIGN uint8_t audio_ep_out_sw_buf_2[CFG_TUD_AUDIO_FUNC_2_EP_OUT_SW_BUF_SZ]; +#if CFG_FIFO_MUTEX +osal_mutex_def_t ep_out_ff_mutex_rd_2; // No need for write mutex as only USB driver writes into FIFO +#endif +#endif // CFG_TUD_AUDIO > 1 && CFG_TUD_AUDIO_FUNC_2_EP_OUT_SW_BUF_SZ > 0 +#if CFG_TUD_AUDIO > 2 && CFG_TUD_AUDIO_FUNC_3_EP_OUT_SW_BUF_SZ > 0 +CFG_TUSB_MEM_ALIGN uint8_t audio_ep_out_sw_buf_3[CFG_TUD_AUDIO_FUNC_3_EP_OUT_SW_BUF_SZ]; +#if CFG_FIFO_MUTEX +osal_mutex_def_t ep_out_ff_mutex_rd_3; // No need for write mutex as only USB driver writes into FIFO +#endif +#endif // CFG_TUD_AUDIO > 2 && CFG_TUD_AUDIO_FUNC_3_EP_OUT_SW_BUF_SZ > 0 +#endif // CFG_TUD_AUDIO_ENABLE_EP_OUT && !CFG_TUD_AUDIO_ENABLE_DECODING + +// Linear buffer RX in case: +// - target MCU is not capable of handling a ring buffer FIFO e.g. no hardware buffer is available or driver is would need to be changed dramatically OR +// - the software encoding is used - in this case the linear buffers serve as a target memory where logical channels are encoded into +#if CFG_TUD_AUDIO_ENABLE_EP_OUT && (USE_LINEAR_BUFFER || CFG_TUD_AUDIO_ENABLE_DECODING) +#if CFG_TUD_AUDIO_FUNC_1_EP_OUT_SZ_MAX > 0 +CFG_TUSB_MEM_ALIGN uint8_t lin_buf_out_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 +CFG_TUSB_MEM_ALIGN uint8_t lin_buf_out_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 +CFG_TUSB_MEM_ALIGN uint8_t lin_buf_out_3[CFG_TUD_AUDIO_FUNC_3_EP_OUT_SZ_MAX]; +#endif +#endif // CFG_TUD_AUDIO_ENABLE_EP_OUT && (USE_LINEAR_BUFFER || CFG_TUD_AUDIO_ENABLE_DECODING) + +// Control buffers +CFG_TUSB_MEM_ALIGN uint8_t ctrl_buf_1[CFG_TUD_AUDIO_FUNC_1_CTRL_BUF_SZ]; +#if CFG_TUD_AUDIO > 1 +CFG_TUSB_MEM_ALIGN uint8_t ctrl_buf_2[CFG_TUD_AUDIO_FUNC_2_CTRL_BUF_SZ]; +#endif +#if CFG_TUD_AUDIO > 2 +CFG_TUSB_MEM_ALIGN uint8_t ctrl_buf_3[CFG_TUD_AUDIO_FUNC_3_CTRL_BUF_SZ]; +#endif + +// Active alternate setting of interfaces +CFG_TUSB_MEM_ALIGN uint8_t alt_setting_1[CFG_TUD_AUDIO_FUNC_1_N_AS_INT]; +#if CFG_TUD_AUDIO > 1 && CFG_TUD_AUDIO_FUNC_2_N_AS_INT > 0 +CFG_TUSB_MEM_ALIGN uint8_t alt_setting_2[CFG_TUD_AUDIO_FUNC_2_N_AS_INT]; +#endif +#if CFG_TUD_AUDIO > 2 && CFG_TUD_AUDIO_FUNC_3_N_AS_INT > 0 +CFG_TUSB_MEM_ALIGN uint8_t alt_setting_3[CFG_TUD_AUDIO_FUNC_3_N_AS_INT]; +#endif + +// Software encoding/decoding support FIFOs +#if CFG_TUD_AUDIO_ENABLE_EP_IN && CFG_TUD_AUDIO_ENABLE_ENCODING +#if CFG_TUD_AUDIO_FUNC_1_TX_SUPP_SW_FIFO_SZ > 0 +CFG_TUSB_MEM_ALIGN uint8_t tx_supp_ff_buf_1[CFG_TUD_AUDIO_FUNC_1_N_TX_SUPP_SW_FIFO][CFG_TUD_AUDIO_FUNC_1_TX_SUPP_SW_FIFO_SZ]; +tu_fifo_t tx_supp_ff_1[CFG_TUD_AUDIO_FUNC_1_N_TX_SUPP_SW_FIFO]; +#if CFG_FIFO_MUTEX +osal_mutex_def_t tx_supp_ff_mutex_wr_1[CFG_TUD_AUDIO_FUNC_1_N_TX_SUPP_SW_FIFO]; // No need for read mutex as only USB driver reads from FIFO +#endif +#endif +#if CFG_TUD_AUDIO > 1 && CFG_TUD_AUDIO_FUNC_2_TX_SUPP_SW_FIFO_SZ > 0 +CFG_TUSB_MEM_ALIGN uint8_t tx_supp_ff_buf_2[CFG_TUD_AUDIO_FUNC_2_N_TX_SUPP_SW_FIFO][CFG_TUD_AUDIO_FUNC_2_TX_SUPP_SW_FIFO_SZ]; +tu_fifo_t tx_supp_ff_2[CFG_TUD_AUDIO_FUNC_2_N_TX_SUPP_SW_FIFO]; +#if CFG_FIFO_MUTEX +osal_mutex_def_t tx_supp_ff_mutex_wr_2[CFG_TUD_AUDIO_FUNC_2_N_TX_SUPP_SW_FIFO]; // No need for read mutex as only USB driver reads from FIFO +#endif +#endif +#if CFG_TUD_AUDIO > 2 && CFG_TUD_AUDIO_FUNC_3_TX_SUPP_SW_FIFO_SZ > 0 +CFG_TUSB_MEM_ALIGN uint8_t tx_supp_ff_buf_3[CFG_TUD_AUDIO_FUNC_3_N_TX_SUPP_SW_FIFO][CFG_TUD_AUDIO_FUNC_3_TX_SUPP_SW_FIFO_SZ]; +tu_fifo_t tx_supp_ff_3[CFG_TUD_AUDIO_FUNC_3_N_TX_SUPP_SW_FIFO]; +#if CFG_FIFO_MUTEX +osal_mutex_def_t tx_supp_ff_mutex_wr_3[CFG_TUD_AUDIO_FUNC_3_N_TX_SUPP_SW_FIFO]; // No need for read mutex as only USB driver reads from FIFO +#endif #endif #endif -#if CFG_TUD_AUDIO_EPSIZE_OUT && CFG_TUD_AUDIO_RX_FIFO_SIZE -#ifndef CFG_TUD_AUDIO_RX_FIFO_COUNT -#define CFG_TUD_AUDIO_RX_FIFO_COUNT CFG_TUD_AUDIO_N_CHANNELS_RX +#if CFG_TUD_AUDIO_ENABLE_EP_OUT && CFG_TUD_AUDIO_ENABLE_DECODING +#if CFG_TUD_AUDIO_FUNC_1_RX_SUPP_SW_FIFO_SZ > 0 +CFG_TUSB_MEM_ALIGN uint8_t rx_supp_ff_buf_1[CFG_TUD_AUDIO_FUNC_1_N_RX_SUPP_SW_FIFO][CFG_TUD_AUDIO_FUNC_1_RX_SUPP_SW_FIFO_SZ]; +tu_fifo_t rx_supp_ff_1[CFG_TUD_AUDIO_FUNC_1_N_RX_SUPP_SW_FIFO]; +#if CFG_FIFO_MUTEX +osal_mutex_def_t rx_supp_ff_mutex_rd_1[CFG_TUD_AUDIO_FUNC_1_N_RX_SUPP_SW_FIFO]; // No need for write mutex as only USB driver writes into FIFO +#endif +#endif +#if CFG_TUD_AUDIO > 1 && CFG_TUD_AUDIO_FUNC_2_RX_SUPP_SW_FIFO_SZ > 0 +CFG_TUSB_MEM_ALIGN uint8_t rx_supp_ff_buf_2[CFG_TUD_AUDIO_FUNC_2_N_RX_SUPP_SW_FIFO][CFG_TUD_AUDIO_FUNC_2_RX_SUPP_SW_FIFO_SZ]; +tu_fifo_t rx_supp_ff_2[CFG_TUD_AUDIO_FUNC_2_N_RX_SUPP_SW_FIFO]; +#if CFG_FIFO_MUTEX +osal_mutex_def_t rx_supp_ff_mutex_rd_2[CFG_TUD_AUDIO_FUNC_2_N_RX_SUPP_SW_FIFO]; // No need for write mutex as only USB driver writes into FIFO +#endif +#endif +#if CFG_TUD_AUDIO > 2 && CFG_TUD_AUDIO_FUNC_3_RX_SUPP_SW_FIFO_SZ > 0 +CFG_TUSB_MEM_ALIGN uint8_t rx_supp_ff_buf_3[CFG_TUD_AUDIO_FUNC_3_N_RX_SUPP_SW_FIFO][CFG_TUD_AUDIO_FUNC_3_RX_SUPP_SW_FIFO_SZ]; +tu_fifo_t rx_supp_ff_3[CFG_TUD_AUDIO_FUNC_3_N_RX_SUPP_SW_FIFO]; +#if CFG_FIFO_MUTEX +osal_mutex_def_t rx_supp_ff_mutex_rd_3[CFG_TUD_AUDIO_FUNC_3_N_RX_SUPP_SW_FIFO]; // No need for write mutex as only USB driver writes into FIFO +#endif #endif #endif @@ -64,14 +244,15 @@ 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_EPSIZE_IN - uint8_t ep_in; // Outgoing (out of uC) audio data EP. - uint16_t epin_buf_cnt; // Count filling status of EP in buffer - this is a shared state currently and is intended to be removed once EP buffers can be implemented as FIFOs! +#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) #endif -#if CFG_TUD_AUDIO_EPSIZE_OUT +#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) #if CFG_TUD_AUDIO_ENABLE_FEEDBACK_EP @@ -84,59 +265,98 @@ typedef struct uint8_t ep_int_ctr; // Audio control interrupt EP. #endif -#if CFG_TUD_AUDIO_N_AS_INT - uint8_t altSetting[CFG_TUD_AUDIO_N_AS_INT]; // We need to save the current alternate setting this way, because it is possible that there are AS interfaces which do not have an EP! -#endif + uint8_t * alt_setting; // We need to save the current alternate setting this way, because it is possible that there are AS interfaces which do not have an EP! + /*------------- From this point, data is not cleared by bus reset -------------*/ + // + uint16_t desc_length; // Length of audio function descriptor + // Buffer for control requests - CFG_TUSB_MEM_ALIGN uint8_t ctrl_buf[CFG_TUD_AUDIO_CTRL_BUF_SIZE]; + uint8_t * ctrl_buf; + uint8_t ctrl_buf_sz; - // FIFO -#if CFG_TUD_AUDIO_EPSIZE_IN && CFG_TUD_AUDIO_TX_FIFO_SIZE - tu_fifo_t tx_ff[CFG_TUD_AUDIO_TX_FIFO_COUNT]; - CFG_TUSB_MEM_ALIGN uint8_t tx_ff_buf[CFG_TUD_AUDIO_TX_FIFO_COUNT][CFG_TUD_AUDIO_TX_FIFO_SIZE]; -#if CFG_FIFO_MUTEX - osal_mutex_def_t tx_ff_mutex[CFG_TUD_AUDIO_TX_FIFO_COUNT]; + // EP Transfer buffers and FIFOs +#if CFG_TUD_AUDIO_ENABLE_EP_OUT +#if !CFG_TUD_AUDIO_ENABLE_DECODING + tu_fifo_t ep_out_ff; #endif -#endif - -#if CFG_TUD_AUDIO_EPSIZE_OUT && CFG_TUD_AUDIO_RX_FIFO_SIZE - tu_fifo_t rx_ff[CFG_TUD_AUDIO_RX_FIFO_COUNT]; - CFG_TUSB_MEM_ALIGN uint8_t rx_ff_buf[CFG_TUD_AUDIO_RX_FIFO_COUNT][CFG_TUD_AUDIO_RX_FIFO_SIZE]; -#if CFG_FIFO_MUTEX - osal_mutex_def_t rx_ff_mutex[CFG_TUD_AUDIO_RX_FIFO_COUNT]; -#endif -#endif - -#if CFG_TUD_AUDIO_INT_CTR_EPSIZE_IN - tu_fifo_t int_ctr_ff; - CFG_TUSB_MEM_ALIGN uint8_t int_ctr_ff_buf[CFG_TUD_AUDIO_INT_CTR_BUFSIZE]; -#if CFG_FIFO_MUTEX - osal_mutex_def_t int_ctr_ff_mutex; -#endif -#endif - - // Endpoint Transfer buffers -#if CFG_TUD_AUDIO_EPSIZE_OUT - CFG_TUSB_MEM_ALIGN uint8_t epout_buf[CFG_TUD_AUDIO_EPSIZE_OUT]; // Bigger makes no sense for isochronous EP's (but technically possible here) #if CFG_TUD_AUDIO_ENABLE_FEEDBACK_EP - uint32_t fb_val; // Feedback value for asynchronous mode (in 16.16 format). + uint32_t fb_val; // Feedback value for asynchronous mode (in 16.16 format). +#endif #endif +#if CFG_TUD_AUDIO_ENABLE_EP_IN && !CFG_TUD_AUDIO_ENABLE_ENCODING + tu_fifo_t ep_in_ff; #endif -#if CFG_TUD_AUDIO_EPSIZE_IN - CFG_TUSB_MEM_ALIGN uint8_t epin_buf[CFG_TUD_AUDIO_EPSIZE_IN]; // Bigger makes no sense for isochronous EP's (but technically possible here) -#endif - + // Audio control interrupt buffer - no FIFO - 6 Bytes according to UAC 2 specification (p. 74) #if CFG_TUD_AUDIO_INT_CTR_EPSIZE_IN - CFG_TUSB_MEM_ALIGN uint8_t ep_int_ctr_buf[CFG_TUD_AUDIO_INT_CTR_EPSIZE_IN]; + CFG_TUSB_MEM_ALIGN uint8_t ep_int_ctr_buf[CFG_TUD_AUDIO_INT_CTR_EP_IN_SW_BUFFER_SIZE]; +#endif + + // Decoding parameters - parameters are set when alternate AS interface is set by host + // Coding is currently only supported for EP. Software coding corresponding to AS interfaces without EPs are not supported currently. +#if CFG_TUD_AUDIO_ENABLE_EP_OUT && CFG_TUD_AUDIO_ENABLE_DECODING + audio_format_type_t format_type_rx; + uint8_t n_channels_rx; + +#if CFG_TUD_AUDIO_ENABLE_TYPE_I_DECODING + audio_data_format_type_I_t format_type_I_rx; + uint8_t n_bytes_per_sampe_rx; + uint8_t n_channels_per_ff_rx; + uint8_t n_ff_used_rx; +#endif +#endif + + // Encoding parameters - parameters are set when alternate AS interface is set by host +#if CFG_TUD_AUDIO_ENABLE_EP_IN && CFG_TUD_AUDIO_ENABLE_ENCODING + audio_format_type_t format_type_tx; + uint8_t n_channels_tx; + +#if CFG_TUD_AUDIO_ENABLE_TYPE_I_ENCODING + audio_data_format_type_I_t format_type_I_tx; + uint8_t n_bytes_per_sampe_tx; + uint8_t n_channels_per_ff_tx; + uint8_t n_ff_used_tx; +#endif +#endif + + // Support FIFOs for software encoding and decoding +#if CFG_TUD_AUDIO_ENABLE_EP_OUT && CFG_TUD_AUDIO_ENABLE_DECODING + tu_fifo_t * rx_supp_ff; + uint8_t n_rx_supp_ff; + uint16_t rx_supp_ff_sz_max; +#endif + +#if CFG_TUD_AUDIO_ENABLE_EP_IN && CFG_TUD_AUDIO_ENABLE_ENCODING + tu_fifo_t * tx_supp_ff; + uint8_t n_tx_supp_ff; + uint16_t tx_supp_ff_sz_max; +#endif + + // Linear buffer in case target MCU is not capable of handling a ring buffer FIFO e.g. no hardware buffer is available or driver is would need to be changed dramatically OR the support FIFOs are used +#if CFG_TUD_AUDIO_ENABLE_EP_OUT && (USE_LINEAR_BUFFER || CFG_TUD_AUDIO_ENABLE_DECODING) + uint8_t * lin_buf_out; +#define USE_LINEAR_BUFFER_RX 1 +#endif + +#if CFG_TUD_AUDIO_ENABLE_EP_IN && (USE_LINEAR_BUFFER || CFG_TUD_AUDIO_ENABLE_ENCODING) + uint8_t * lin_buf_in; +#define USE_LINEAR_BUFFER_TX 1 #endif } audiod_interface_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_interface_t, ctrl_buf) //--------------------------------------------------------------------+ @@ -144,14 +364,20 @@ typedef struct //--------------------------------------------------------------------+ CFG_TUSB_MEM_SECTION audiod_interface_t _audiod_itf[CFG_TUD_AUDIO]; -extern const uint16_t tud_audio_desc_lengths[]; - -#if CFG_TUD_AUDIO_EPSIZE_OUT -static bool audio_rx_done_type_I_pcm_ff_cb(uint8_t rhport, audiod_interface_t* audio, uint8_t * buffer, uint16_t bufsize); +#if CFG_TUD_AUDIO_ENABLE_EP_OUT +static bool audiod_rx_done_cb(uint8_t rhport, audiod_interface_t* audio, uint16_t n_bytes_received); #endif -#if CFG_TUD_AUDIO_EPSIZE_IN -static bool audiod_tx_done_type_I_pcm_ff_cb(uint8_t rhport, audiod_interface_t* audio); +#if CFG_TUD_AUDIO_ENABLE_DECODING && CFG_TUD_AUDIO_ENABLE_EP_OUT +static bool audiod_decode_type_I_pcm(uint8_t rhport, audiod_interface_t* audio, uint16_t n_bytes_received); +#endif + +#if CFG_TUD_AUDIO_ENABLE_EP_IN +static bool audiod_tx_done_cb(uint8_t rhport, audiod_interface_t* audio); +#endif + +#if CFG_TUD_AUDIO_ENABLE_ENCODING && CFG_TUD_AUDIO_ENABLE_EP_IN +static uint16_t audiod_encode_type_I_pcm(uint8_t rhport, audiod_interface_t* audio); #endif static bool audiod_get_interface(uint8_t rhport, tusb_control_request_t const * p_request); @@ -162,36 +388,34 @@ static bool audiod_verify_entity_exists(uint8_t itf, uint8_t entityID, uint8_t * static bool audiod_verify_itf_exists(uint8_t itf, uint8_t *idxDriver); static bool audiod_verify_ep_exists(uint8_t ep, uint8_t *idxDriver); -bool tud_audio_n_mounted(uint8_t itf) -{ - audiod_interface_t* audio = &_audiod_itf[itf]; - -#if CFG_TUD_AUDIO_EPSIZE_OUT - if (audio->ep_out == 0) - { - return false; - } +#if CFG_TUD_AUDIO_ENABLE_ENCODING || CFG_TUD_AUDIO_ENABLE_DECODING +static void audiod_parse_for_AS_params(audiod_interface_t* audio, uint8_t const * p_desc, uint8_t const * p_desc_end, uint8_t const itf); #endif -#if CFG_TUD_AUDIO_EPSIZE_IN - if (audio->ep_in == 0) - { - return false; - } +static inline uint8_t tu_desc_subtype(void const* desc) +{ + return ((uint8_t const*) desc)[2]; +} + +bool tud_audio_n_mounted(uint8_t itf) +{ + TU_VERIFY(itf < CFG_TUD_AUDIO); + audiod_interface_t* audio = &_audiod_itf[itf]; + +#if CFG_TUD_AUDIO_ENABLE_EP_OUT + if (audio->ep_out == 0) return false; +#endif + +#if CFG_TUD_AUDIO_ENABLE_EP_IN + if (audio->ep_in == 0) return false; #endif #if CFG_TUD_AUDIO_INT_CTR_EPSIZE_IN - if (audio->ep_int_ctr == 0) - { - return false; - } + if (audio->ep_int_ctr == 0) return false; #endif #if CFG_TUD_AUDIO_ENABLE_FEEDBACK_EP - if (audio->ep_fb == 0) - { - return false; - } + if (audio->ep_fb == 0) return false; #endif return true; @@ -201,70 +425,68 @@ bool tud_audio_n_mounted(uint8_t itf) // READ API //--------------------------------------------------------------------+ -#if CFG_TUD_AUDIO_EPSIZE_OUT && CFG_TUD_AUDIO_RX_FIFO_SIZE -#if CFG_TUD_AUDIO_RX_FIFO_COUNT > 1 -uint16_t tud_audio_n_available(uint8_t itf, uint8_t channelId) -{ - TU_VERIFY(channelId < CFG_TUD_AUDIO_N_CHANNELS_RX); - return tu_fifo_count(&_audiod_itf[itf].rx_ff[channelId]); -} +#if CFG_TUD_AUDIO_ENABLE_EP_OUT && !CFG_TUD_AUDIO_ENABLE_DECODING -uint16_t tud_audio_n_read(uint8_t itf, uint8_t channelId, void* buffer, uint16_t bufsize) -{ - TU_VERIFY(channelId < CFG_TUD_AUDIO_N_CHANNELS_RX); - return tu_fifo_read_n(&_audiod_itf[itf].rx_ff[channelId], buffer, bufsize); -} - -void tud_audio_n_read_flush (uint8_t itf, uint8_t channelId) -{ - TU_VERIFY(channelId < CFG_TUD_AUDIO_N_CHANNELS_RX, ); - tu_fifo_clear(&_audiod_itf[itf].rx_ff[channelId]); -} -#else uint16_t tud_audio_n_available(uint8_t itf) { - return tu_fifo_count(&_audiod_itf[itf].rx_ff[0]); + TU_VERIFY(itf < CFG_TUD_AUDIO && _audiod_itf[itf].p_desc != NULL); + return tu_fifo_count(&_audiod_itf[itf].ep_out_ff); } uint16_t tud_audio_n_read(uint8_t itf, void* buffer, uint16_t bufsize) { - return tu_fifo_read_n(&_audiod_itf[itf].rx_ff[0], buffer, bufsize); + TU_VERIFY(itf < CFG_TUD_AUDIO && _audiod_itf[itf].p_desc != NULL); + return tu_fifo_read_n(&_audiod_itf[itf].ep_out_ff, buffer, bufsize); } -void tud_audio_n_read_flush (uint8_t itf) +bool tud_audio_n_clear_ep_out_ff(uint8_t itf) { - tu_fifo_clear(&_audiod_itf[itf].rx_ff[0]); -} -#endif -#endif - -#if CFG_TUD_AUDIO_INT_CTR_EPSIZE_IN - -uint16_t tud_audio_int_ctr_n_available(uint8_t itf) -{ - return tu_fifo_count(&_audiod_itf[itf].int_ctr_ff); -} - -uint16_t tud_audio_int_ctr_n_read(uint8_t itf, void* buffer, uint16_t bufsize) -{ - return tu_fifo_read_n(&_audiod_itf[itf].int_ctr_ff, buffer, bufsize); -} - -void tud_audio_int_ctr_n_read_flush (uint8_t itf) -{ - tu_fifo_clear(&_audiod_itf[itf].int_ctr_ff); + TU_VERIFY(itf < CFG_TUD_AUDIO && _audiod_itf[itf].p_desc != NULL); + return tu_fifo_clear(&_audiod_itf[itf].ep_out_ff); } #endif -// This function is called once something is received by USB and is responsible for decoding received stream into audio channels. -// If you prefer your own (more efficient) implementation suiting your purpose set CFG_TUD_AUDIO_RX_FIFO_SIZE = 0. - -#if CFG_TUD_AUDIO_EPSIZE_OUT - -static bool audio_rx_done_cb(uint8_t rhport, audiod_interface_t* audio, uint8_t* buffer, uint16_t bufsize) +#if CFG_TUD_AUDIO_ENABLE_DECODING && CFG_TUD_AUDIO_ENABLE_EP_OUT +// Delete all content in the support RX FIFOs +bool tud_audio_n_clear_rx_support_ff(uint8_t itf, uint8_t channelId) { - switch (CFG_TUD_AUDIO_FORMAT_TYPE_RX) + TU_VERIFY(itf < CFG_TUD_AUDIO && _audiod_itf[itf].p_desc != NULL, channelId < _audiod_itf[itf].n_rx_supp_ff); + return tu_fifo_clear(&_audiod_itf[itf].rx_supp_ff[channelId]); +} + +uint16_t tud_audio_n_available_support_ff(uint8_t itf, uint8_t channelId) +{ + TU_VERIFY(itf < CFG_TUD_AUDIO && _audiod_itf[itf].p_desc != NULL, channelId < _audiod_itf[itf].n_rx_supp_ff); + return tu_fifo_count(&_audiod_itf[itf].rx_supp_ff[channelId]); +} + +uint16_t tud_audio_n_read_support_ff(uint8_t itf, uint8_t channelId, void* buffer, uint16_t bufsize) +{ + TU_VERIFY(itf < CFG_TUD_AUDIO && _audiod_itf[itf].p_desc != NULL, channelId < _audiod_itf[itf].n_rx_supp_ff); + return tu_fifo_read_n(&_audiod_itf[itf].rx_supp_ff[channelId], buffer, bufsize); +} +#endif + +// This function is called once an audio packet is received by the USB and is responsible for putting data from USB memory into EP_OUT_FIFO (or support FIFOs + decoding of received stream into audio channels). +// If you prefer your own (more efficient) implementation suiting your purpose set CFG_TUD_AUDIO_ENABLE_DECODING = 0. + +#if CFG_TUD_AUDIO_ENABLE_EP_OUT + +static bool audiod_rx_done_cb(uint8_t rhport, audiod_interface_t* audio, uint16_t n_bytes_received) +{ + uint8_t idxDriver, idxItf; + uint8_t const *dummy2; + + // Find index of audio streaming interface and index of interface + TU_VERIFY(audiod_get_AS_interface_index(audio->ep_out_as_intf_num, &idxDriver, &idxItf, &dummy2)); + + // Call a weak callback here - a possibility for user to get informed an audio packet was received and data gets now loaded into EP FIFO (or decoded into support RX software FIFO) + if (tud_audio_rx_done_pre_read_cb) TU_VERIFY(tud_audio_rx_done_pre_read_cb(rhport, n_bytes_received, idxDriver, audio->ep_out, audio->alt_setting[idxItf])); + +#if CFG_TUD_AUDIO_ENABLE_DECODING && CFG_TUD_AUDIO_ENABLE_EP_OUT + + switch (audio->format_type_rx) { case AUDIO_FORMAT_TYPE_UNDEFINED: // INDIVIDUAL DECODING PROCEDURE REQUIRED HERE! @@ -274,15 +496,10 @@ static bool audio_rx_done_cb(uint8_t rhport, audiod_interface_t* audio, uint8_t* case AUDIO_FORMAT_TYPE_I: - switch (CFG_TUD_AUDIO_FORMAT_TYPE_I_RX) + switch (audio->format_type_I_tx) { case AUDIO_DATA_FORMAT_TYPE_I_PCM: - -#if CFG_TUD_AUDIO_RX_FIFO_SIZE - TU_VERIFY(audio_rx_done_type_I_pcm_ff_cb(rhport, audio, buffer, bufsize)); -#else -#error YOUR DECODING AND BUFFERING IS REQUIRED HERE! -#endif + TU_VERIFY(audiod_decode_type_I_pcm(rhport, audio, n_bytes_received)); break; default: @@ -300,75 +517,159 @@ static bool audio_rx_done_cb(uint8_t rhport, audiod_interface_t* audio, uint8_t* break; } - // Call a weak callback here - a possibility for user to get informed RX was completed - if (tud_audio_rx_done_cb) TU_VERIFY(tud_audio_rx_done_cb(rhport, buffer, bufsize)); + // Prepare for next transmission + TU_VERIFY(usbd_edpt_xfer(rhport, audio->ep_out, audio->lin_buf_out, audio->ep_out_sz), false); - return true; -} - -#endif //CFG_TUD_AUDIO_EPSIZE_OUT - -// The following functions are used in case CFG_TUD_AUDIO_RX_FIFO_SIZE != 0 -#if CFG_TUD_AUDIO_RX_FIFO_SIZE -#if CFG_TUD_AUDIO_RX_FIFO_COUNT > 1 -static bool audio_rx_done_type_I_pcm_ff_cb(uint8_t rhport, audiod_interface_t* audio, uint8_t * buffer, uint16_t bufsize) -{ - (void) rhport; - - // We expect to get a multiple of CFG_TUD_AUDIO_N_BYTES_PER_SAMPLE_RX * CFG_TUD_AUDIO_N_CHANNELS_RX per channel - if (bufsize % (CFG_TUD_AUDIO_N_BYTES_PER_SAMPLE_RX * CFG_TUD_AUDIO_N_CHANNELS_RX) != 0) - { - return false; - } - - uint8_t chId = 0; - uint16_t cnt; -#if CFG_TUD_AUDIO_N_BYTES_PER_SAMPLE_RX == 1 - uint8_t sample = 0; -#elif CFG_TUD_AUDIO_N_BYTES_PER_SAMPLE_RX == 2 - uint16_t sample = 0; #else - uint32_t sample = 0; + +#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_iso_xfer(rhport, audio->ep_out, &audio->ep_out_ff, audio->ep_out_sz), false); #endif - for(cnt = 0; cnt < bufsize; cnt += CFG_TUD_AUDIO_N_BYTES_PER_SAMPLE_RX) - { - // Let alignment problems be handled by memcpy - memcpy(&sample, &buffer[cnt], CFG_TUD_AUDIO_N_BYTES_PER_SAMPLE_RX); - if(tu_fifo_write_n(&audio->rx_ff[chId++], &sample, CFG_TUD_AUDIO_RX_ITEMSIZE) != CFG_TUD_AUDIO_RX_ITEMSIZE) - { - // Buffer overflow - return false; - } +#endif + + // Call a weak callback here - a possibility for user to get informed decoding was completed + if (tud_audio_rx_done_post_read_cb) TU_VERIFY(tud_audio_rx_done_post_read_cb(rhport, n_bytes_received, idxDriver, audio->ep_out, audio->alt_setting[idxItf])); - if (chId == CFG_TUD_AUDIO_N_CHANNELS_RX) - { - chId = 0; - } - } return true; } -#else -static bool audio_rx_done_type_I_pcm_ff_cb(uint8_t rhport, audiod_interface_t *audio, uint8_t *buffer, uint16_t bufsize) + +#endif //CFG_TUD_AUDIO_ENABLE_EP_OUT + +// The following functions are used in case CFG_TUD_AUDIO_ENABLE_DECODING != 0 +#if CFG_TUD_AUDIO_ENABLE_DECODING && CFG_TUD_AUDIO_ENABLE_EP_OUT + +// Decoding according to 2.3.1.5 Audio Streams + +// Helper function +static inline uint8_t * audiod_interleaved_copy_bytes_fast_decode(uint16_t const nBytesToCopy, void * dst, uint8_t * dst_end, uint8_t * src, uint8_t const n_ff_used) +{ + + // This function is an optimized version of + // while((uint8_t *)dst < dst_end) + // { + // memcpy(dst, src, nBytesToCopy); + // dst = (uint8_t *)dst + nBytesToCopy; + // src += nBytesToCopy * n_ff_used; + // } + + // Optimize for fast half word copies + typedef struct{ + uint16_t val; + } __attribute((__packed__)) unaligned_uint16_t; + + // Optimize for fast word copies + typedef struct{ + uint32_t val; + } __attribute((__packed__)) unaligned_uint32_t; + + switch (nBytesToCopy) + { + case 1: + while((uint8_t *)dst < dst_end) + { + *(uint8_t *)dst++ = *src; + src += n_ff_used; + } + break; + + case 2: + while((uint8_t *)dst < dst_end) + { + *(unaligned_uint16_t*)dst = *(unaligned_uint16_t*)src; + dst += 2; + src += 2 * n_ff_used; + } + break; + + case 3: + while((uint8_t *)dst < dst_end) + { + // memcpy(dst, src, 3); + // dst = (uint8_t *)dst + 3; + // src += 3 * n_ff_used; + + // TODO: Is there a faster way to copy 3 bytes? + *(uint8_t *)dst++ = *src++; + *(uint8_t *)dst++ = *src++; + *(uint8_t *)dst++ = *src++; + + src += 3 * (n_ff_used - 1); + } + break; + + case 4: + while((uint8_t *)dst < dst_end) + { + *(unaligned_uint32_t*)dst = *(unaligned_uint32_t*)src; + dst += 4; + src += 4 * n_ff_used; + } + break; + } + + return src; +} + +static bool audiod_decode_type_I_pcm(uint8_t rhport, audiod_interface_t* audio, uint16_t n_bytes_received) { (void) rhport; - // We expect to get a multiple of CFG_TUD_AUDIO_N_BYTES_PER_SAMPLE_RX * CFG_TUD_AUDIO_N_CHANNELS_RX per channel - if (bufsize % (CFG_TUD_AUDIO_N_BYTES_PER_SAMPLE_RX * CFG_TUD_AUDIO_N_CHANNELS_RX) != 0) + // Determine amount of samples + uint8_t const n_ff_used = audio->n_ff_used_rx; + uint16_t const nBytesToCopy = audio->n_channels_per_ff_rx * audio->n_bytes_per_sampe_rx; + uint16_t const nBytesPerFFToRead = n_bytes_received / n_ff_used; + uint8_t cnt_ff; + + // Decode + void * dst; + uint8_t * src; + uint8_t * dst_end; + uint16_t len; + + for (cnt_ff = 0; cnt_ff < n_ff_used; cnt_ff++) { - return false; + src = &audio->lin_buf_out[cnt_ff*audio->n_channels_per_ff_rx * audio->n_bytes_per_sampe_rx]; + + len = tu_fifo_get_linear_write_info(&audio->rx_supp_ff[cnt_ff], 0, &dst, nBytesPerFFToRead); + tu_fifo_advance_write_pointer(&audio->rx_supp_ff[cnt_ff], len); + + dst_end = dst + len; + + src = audiod_interleaved_copy_bytes_fast_decode(nBytesToCopy, dst, dst_end, src, n_ff_used); + + // Handle wrapped part of FIFO + if (len < nBytesPerFFToRead) + { + len = tu_fifo_get_linear_write_info(&audio->rx_supp_ff[cnt_ff], 0, &dst, nBytesPerFFToRead - len); + tu_fifo_advance_write_pointer(&audio->rx_supp_ff[cnt_ff], len); + + dst_end = dst + len; + + audiod_interleaved_copy_bytes_fast_decode(nBytesToCopy, dst, dst_end, src, n_ff_used); + } } - tu_fifo_write_n(&audio->rx_ff[0], buffer, bufsize); + // Number of bytes should be a multiple of CFG_TUD_AUDIO_N_BYTES_PER_SAMPLE_RX * CFG_TUD_AUDIO_N_CHANNELS_RX but checking makes no sense - no way to correct it + // TU_VERIFY(cnt != n_bytes); + return true; } -#endif // CFG_TUD_AUDIO_RX_FIFO_COUNT > 1 -#endif //CFG_TUD_AUDIO_RX_FIFO_SIZE +#endif //CFG_TUD_AUDIO_ENABLE_DECODING //--------------------------------------------------------------------+ // WRITE API //--------------------------------------------------------------------+ +#if CFG_TUD_AUDIO_ENABLE_EP_IN && !CFG_TUD_AUDIO_ENABLE_ENCODING + /** * \brief Write data to EP in buffer * @@ -380,137 +681,118 @@ static bool audio_rx_done_type_I_pcm_ff_cb(uint8_t rhport, audiod_interface_t *a * \param[in] len: # of array elements to copy * \return Number of bytes actually written */ -#if CFG_TUD_AUDIO_EPSIZE_IN -#if !CFG_TUD_AUDIO_TX_FIFO_SIZE -/* This function is intended for later use once EP buffers (at least for ISO EPs) are implemented as ring buffers -uint16_t tud_audio_n_write_ep_in_buffer(uint8_t itf, const void * data, uint16_t len) +uint16_t tud_audio_n_write(uint8_t itf, const void * data, uint16_t len) { - audiod_interface_t* audio = &_audiod_itf[itf]; - if (audio->p_desc == NULL) { - return 0; - } - - // THIS IS A CRITICAL SECTION - audio->epin_buf_cnt MUST NOT BE MODIFIED FROM HERE - happens if audiod_tx_done_cb() is executed in between! - - // FOR SINGLE THREADED OPERATION: - // AS LONG AS THIS FUNCTION IS NOT EXECUTED WITHIN AN INTERRUPT ALL IS FINE! - - // Determine free space - uint16_t free = CFG_TUD_AUDIO_EPSIZE_IN - audio->epin_buf_cnt; - - // Clip length if needed - if (len > free) len = free; - - // Write data - memcpy((void *) &audio->epin_buf[audio->epin_buf_cnt], data, len); - - audio->epin_buf_cnt += len; - - // Return number of bytes written - return len; + TU_VERIFY(itf < CFG_TUD_AUDIO && _audiod_itf[itf].p_desc != NULL); + return tu_fifo_write_n(&_audiod_itf[itf].ep_in_ff, data, len); } -*/ -#else - -#if CFG_TUD_AUDIO_TX_FIFO_COUNT == 1 -uint16_t tud_audio_n_write(uint8_t itf, void const* data, uint16_t len) +bool tud_audio_n_clear_ep_in_ff(uint8_t itf) // Delete all content in the EP IN FIFO { - { - audiod_interface_t* audio = &_audiod_itf[itf]; - if (audio->p_desc == NULL) - { - return 0; - } - return tu_fifo_write_n(&audio->tx_ff[0], data, len); - } + TU_VERIFY(itf < CFG_TUD_AUDIO && _audiod_itf[itf].p_desc != NULL); + return tu_fifo_clear(&_audiod_itf[itf].ep_in_ff); } -#else -uint16_t tud_audio_n_write(uint8_t itf, uint8_t channelId, const void * data, uint16_t len) -{ - audiod_interface_t* audio = &_audiod_itf[itf]; - if (audio->p_desc == NULL) { - return 0; - } - return tu_fifo_write_n(&audio->tx_ff[channelId], data, len); -} #endif -static bool audiod_tx_done_cb(uint8_t rhport, audiod_interface_t* audio, uint16_t * n_bytes_copied); - -uint16_t tud_audio_n_write_flush(uint8_t itf) +#if CFG_TUD_AUDIO_ENABLE_ENCODING && CFG_TUD_AUDIO_ENABLE_EP_IN +uint16_t tud_audio_n_flush_tx_support_ff(uint8_t itf) // Force all content in the support TX FIFOs to be written into linear buffer and schedule a transmit { - audiod_interface_t *audio = &_audiod_itf[itf]; - if (audio->p_desc == NULL) { - return 0; - } + TU_VERIFY(itf < CFG_TUD_AUDIO && _audiod_itf[itf].p_desc != NULL); + audiod_interface_t* audio = &_audiod_itf[itf]; + + uint16_t n_bytes_copied = tu_fifo_count(&audio->tx_supp_ff[0]); + + TU_VERIFY(audiod_tx_done_cb(audio->rhport, audio)); + + n_bytes_copied -= tu_fifo_count(&audio->tx_supp_ff[0]); + n_bytes_copied = n_bytes_copied*audio->tx_supp_ff[0].item_size; - uint16_t n_bytes_copied; - TU_VERIFY(audiod_tx_done_cb(audio->rhport, audio, &n_bytes_copied)); return n_bytes_copied; } -#endif -#endif - -#if CFG_TUD_AUDIO_INT_CTR_EPSIZE_IN > 0 -uint32_t tud_audio_int_ctr_n_write(uint8_t itf, uint8_t const* buffer, uint32_t bufsize) +bool tud_audio_n_clear_tx_support_ff(uint8_t itf, uint8_t channelId) { - audiod_interface_t* audio = &_audiod_itf[itf]; - if (audio->p_desc == NULL) { - return 0; - } + TU_VERIFY(itf < CFG_TUD_AUDIO && _audiod_itf[itf].p_desc != NULL, channelId < _audiod_itf[itf].n_tx_supp_ff); + return tu_fifo_clear(&_audiod_itf[itf].tx_supp_ff[channelId]); +} - return tu_fifo_write_n(&audio->int_ctr_ff, buffer, bufsize); +uint16_t tud_audio_n_write_support_ff(uint8_t itf, uint8_t channelId, const void * data, uint16_t len) +{ + TU_VERIFY(itf < CFG_TUD_AUDIO && _audiod_itf[itf].p_desc != NULL, channelId < _audiod_itf[itf].n_tx_supp_ff); + return tu_fifo_write_n(&_audiod_itf[itf].tx_supp_ff[channelId], data, len); +} +#endif + + +#if CFG_TUD_AUDIO_INT_CTR_EPSIZE_IN + +// If no interrupt transmit is pending bytes get written into buffer and a transmit is scheduled - once transmit completed tud_audio_int_ctr_done_cb() is called in inform user +uint16_t tud_audio_int_ctr_n_write(uint8_t itf, uint8_t const* buffer, uint16_t len) +{ + TU_VERIFY(itf < CFG_TUD_AUDIO && _audiod_itf[itf].p_desc != NULL); + + // We write directly into the EP's buffer - abort if previous transfer not complete + TU_VERIFY(!usbd_edpt_busy(_audiod_itf[itf].rhport, _audiod_itf[itf].ep_int_ctr)); + + // Check length + TU_VERIFY(len <= CFG_TUD_AUDIO_INT_CTR_EP_IN_SW_BUFFER_SIZE); + + memcpy(_audiod_itf[itf].ep_int_ctr_buf, buffer, len); + + // Schedule transmit + TU_VERIFY(usbd_edpt_xfer(_audiod_itf[itf].rhport, _audiod_itf[itf].ep_int_ctr, _audiod_itf[itf].ep_int_ctr_buf, len)); + + return true; } #endif // This function is called once a transmit of an audio packet was successfully completed. Here, we encode samples and place it in IN EP's buffer for next transmission. -// If you prefer your own (more efficient) implementation suiting your purpose set CFG_TUD_AUDIO_TX_FIFO_SIZE = 0 and use tud_audio_n_write_ep_in_buffer() (NOT IMPLEMENTED SO FAR). +// If you prefer your own (more efficient) implementation suiting your purpose set CFG_TUD_AUDIO_ENABLE_ENCODING = 0 and use tud_audio_n_write. // n_bytes_copied - Informs caller how many bytes were loaded. In case n_bytes_copied = 0, a ZLP is scheduled to inform host no data is available for current frame. -#if CFG_TUD_AUDIO_EPSIZE_IN -static bool audiod_tx_done_cb(uint8_t rhport, audiod_interface_t* audio, uint16_t * n_bytes_copied) +#if CFG_TUD_AUDIO_ENABLE_EP_IN +static bool audiod_tx_done_cb(uint8_t rhport, audiod_interface_t * audio) { uint8_t idxDriver, idxItf; uint8_t const *dummy2; - // If a callback is used determine current alternate setting of - if (tud_audio_tx_done_pre_load_cb || tud_audio_tx_done_post_load_cb) - { - // Find index of audio streaming interface and index of interface - TU_VERIFY(audiod_get_AS_interface_index(audio->ep_in_as_intf_num, &idxDriver, &idxItf, &dummy2)); - } + // If a callback is used determine current alternate setting of - find index of audio streaming interface and index of interface + if (tud_audio_tx_done_pre_load_cb || tud_audio_tx_done_post_load_cb) TU_VERIFY(audiod_get_AS_interface_index(audio->ep_in_as_intf_num, &idxDriver, &idxItf, &dummy2)); // Call a weak callback here - a possibility for user to get informed former TX was completed and data gets now loaded into EP in buffer (in case FIFOs are used) or - // if no FIFOs are used the user may use this call back to load its data into the EP in buffer by use of tud_audio_n_write_ep_in_buffer(). - if (tud_audio_tx_done_pre_load_cb) TU_VERIFY(tud_audio_tx_done_pre_load_cb(rhport, idxDriver, audio->ep_in, audio->altSetting[idxItf])); + // if no FIFOs are used the user may use this call back to load its data into the EP IN buffer by use of tud_audio_n_write_ep_in_buffer(). + if (tud_audio_tx_done_pre_load_cb) TU_VERIFY(tud_audio_tx_done_pre_load_cb(rhport, idxDriver, audio->ep_in, audio->alt_setting[idxItf])); -#if CFG_TUD_AUDIO_TX_FIFO_SIZE - switch (CFG_TUD_AUDIO_FORMAT_TYPE_TX) + // Send everything in ISO EP FIFO + uint16_t n_bytes_tx; + + // If support FIFOs are used, encode and schedule transmit +#if CFG_TUD_AUDIO_ENABLE_ENCODING && CFG_TUD_AUDIO_ENABLE_EP_IN + switch (audio->format_type_tx) { case AUDIO_FORMAT_TYPE_UNDEFINED: // INDIVIDUAL ENCODING PROCEDURE REQUIRED HERE! TU_LOG2(" Desired CFG_TUD_AUDIO_FORMAT encoding not implemented!\r\n"); TU_BREAKPOINT(); + n_bytes_tx = 0; break; case AUDIO_FORMAT_TYPE_I: - switch (CFG_TUD_AUDIO_FORMAT_TYPE_I_TX) + switch (audio->format_type_I_tx) { case AUDIO_DATA_FORMAT_TYPE_I_PCM: - TU_VERIFY(audiod_tx_done_type_I_pcm_ff_cb(rhport, audio)); - + n_bytes_tx = audiod_encode_type_I_pcm(rhport, audio); break; default: // YOUR ENCODING IS REQUIRED HERE! TU_LOG2(" Desired CFG_TUD_AUDIO_FORMAT_TYPE_I_TX encoding not implemented!\r\n"); TU_BREAKPOINT(); + n_bytes_tx = 0; break; } break; @@ -519,199 +801,186 @@ static bool audiod_tx_done_cb(uint8_t rhport, audiod_interface_t* audio, uint16_ // Desired CFG_TUD_AUDIO_FORMAT_TYPE_TX not implemented! TU_LOG2(" Desired CFG_TUD_AUDIO_FORMAT_TYPE_TX not implemented!\r\n"); TU_BREAKPOINT(); + n_bytes_tx = 0; break; } + + TU_VERIFY(usbd_edpt_xfer(rhport, audio->ep_in, audio->lin_buf_in, n_bytes_tx)); + +#else + // No support FIFOs, if no linear buffer required schedule transmit, else put data into linear buffer and schedule + + 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 + +#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_iso_xfer(rhport, audio->ep_in, &audio->ep_in_ff, n_bytes_tx)); #endif - // THIS IS A CRITICAL SECTION - audio->epin_buf_cnt MUST NOT BE MODIFIED FROM HERE - happens if tud_audio_n_write_ep_in_buffer() is executed in between! - - // THIS IS NOT SOLVED SO FAR! - - // FOR SINGLE THREADED OPERATION: - // THIS FUNCTION IS NOT EXECUTED WITHIN AN INTERRUPT SO IT DOES NOT INTERRUPT tud_audio_n_write_ep_in_buffer()! AS LONG AS tud_audio_n_write_ep_in_buffer() IS NOT EXECUTED WITHIN AN INTERRUPT ALL IS FINE! - - // Schedule transmit - TU_VERIFY(usbd_edpt_xfer(rhport, audio->ep_in, audio->epin_buf, audio->epin_buf_cnt)); - - // Inform how many bytes were copied - *n_bytes_copied = audio->epin_buf_cnt; - - // Declare EP in buffer empty - audio->epin_buf_cnt = 0; - - // TO HERE +#endif // Call a weak callback here - a possibility for user to get informed former TX was completed and how many bytes were loaded for the next frame - if (tud_audio_tx_done_post_load_cb) TU_VERIFY(tud_audio_tx_done_post_load_cb(rhport, *n_bytes_copied, idxDriver, audio->ep_in, audio->altSetting[idxItf])); + if (tud_audio_tx_done_post_load_cb) TU_VERIFY(tud_audio_tx_done_post_load_cb(rhport, n_bytes_tx, idxDriver, audio->ep_in, audio->alt_setting[idxItf])); return true; } -#endif //CFG_TUD_AUDIO_EPSIZE_IN +#endif //CFG_TUD_AUDIO_ENABLE_EP_IN -#if CFG_TUD_AUDIO_TX_FIFO_SIZE -#if CFG_TUD_AUDIO_TX_FIFO_COUNT > 1 || (CFG_TUD_AUDIO_N_BYTES_PER_SAMPLE_TX != CFG_TUD_AUDIO_TX_ITEMSIZE) -static bool audiod_tx_done_type_I_pcm_ff_cb(uint8_t rhport, audiod_interface_t* audio) +#if CFG_TUD_AUDIO_ENABLE_ENCODING && CFG_TUD_AUDIO_ENABLE_EP_IN +// Take samples from the support buffer and encode them into the IN EP software FIFO +// Returns number of bytes written into linear buffer + +/* 2.3.1.7.1 PCM Format +The PCM (Pulse Coded Modulation) format is the most commonly used audio format to represent audio +data streams. The audio data is not compressed and uses a signed two’s-complement fixed point format. It +is left-justified (the sign bit is the Msb) and data is padded with trailing zeros to fill the remaining unused +bits of the subslot. The binary point is located to the right of the sign bit so that all values lie within the +range [-1, +1) + */ + +/* + * This function encodes channels saved within the support FIFOs into one stream by interleaving the PCM samples + * in the support FIFOs according to 2.3.1.5 Audio Streams. It does not control justification (left or right) and + * does not change the number of bytes per sample. + * */ + +// Helper function +static inline uint8_t * audiod_interleaved_copy_bytes_fast_encode(uint16_t const nBytesToCopy, void * src, uint8_t * src_end, uint8_t * dst, uint8_t const n_ff_used) { - // We encode directly into IN EP's buffer - abort if previous transfer not complete + // Optimize for fast half word copies + typedef struct{ + uint16_t val; + } __attribute((__packed__)) unaligned_uint16_t; + + // Optimize for fast word copies + typedef struct{ + uint32_t val; + } __attribute((__packed__)) unaligned_uint32_t; + + switch (nBytesToCopy) + { + case 1: + while((uint8_t *)src < src_end) + { + *dst = *(uint8_t *)src++; + dst += n_ff_used; + } + break; + + case 2: + while((uint8_t *)src < src_end) + { + *(unaligned_uint16_t*)dst = *(unaligned_uint16_t*)src; + src += 2; + dst += 2 * n_ff_used; + } + break; + + case 3: + while((uint8_t *)src < src_end) + { + // memcpy(dst, src, 3); + // src = (uint8_t *)src + 3; + // dst += 3 * n_ff_used; + + // TODO: Is there a faster way to copy 3 bytes? + *dst++ = *(uint8_t *)src++; + *dst++ = *(uint8_t *)src++; + *dst++ = *(uint8_t *)src++; + + dst += 3 * (n_ff_used - 1); + } + break; + + case 4: + while((uint8_t *)src < src_end) + { + *(unaligned_uint32_t*)dst = *(unaligned_uint32_t*)src; + src += 4; + dst += 4 * n_ff_used; + } + break; + } + + return dst; +} + +static uint16_t audiod_encode_type_I_pcm(uint8_t rhport, audiod_interface_t* audio) +{ + // This function relies on the fact that the length of the support FIFOs was configured to be a multiple of the active sample size in bytes s.t. no sample is split within a wrap + // This is ensured within set_interface, where the FIFOs are reconfigured according to this size + + // We encode directly into IN EP's linear buffer - abort if previous transfer not complete TU_VERIFY(!usbd_edpt_busy(rhport, audio->ep_in)); // Determine amount of samples - uint16_t const nEndpointSampleCapacity = CFG_TUD_AUDIO_EPSIZE_IN / CFG_TUD_AUDIO_N_CHANNELS_TX / CFG_TUD_AUDIO_N_BYTES_PER_SAMPLE_TX; - uint16_t nSamplesPerChannelToSend = tu_fifo_count(&audio->tx_ff[0]) / CFG_TUD_AUDIO_TX_ITEMSIZE; - uint16_t nBytesToSend; - uint8_t cntChannel; + uint8_t const n_ff_used = audio->n_ff_used_tx; + uint16_t const nBytesToCopy = audio->n_channels_per_ff_tx * audio->n_bytes_per_sampe_tx; + uint16_t const capPerFF = audio->ep_in_sz / n_ff_used; // Sample capacity per FIFO in bytes + uint16_t nBytesPerFFToSend = tu_fifo_count(&audio->tx_supp_ff[0]); + uint8_t cnt_ff; - for (cntChannel = 1; cntChannel < CFG_TUD_AUDIO_N_CHANNELS_TX; cntChannel++) + for (cnt_ff = 1; cnt_ff < n_ff_used; cnt_ff++) { - uint16_t const count = tu_fifo_count(&audio->tx_ff[cntChannel]); - if (count / CFG_TUD_AUDIO_TX_ITEMSIZE < nSamplesPerChannelToSend) + uint16_t const count = tu_fifo_count(&audio->tx_supp_ff[cnt_ff]); + if (count < nBytesPerFFToSend) { - nSamplesPerChannelToSend = count * CFG_TUD_AUDIO_TX_ITEMSIZE; + nBytesPerFFToSend = count; } } // Check if there is enough - if (nSamplesPerChannelToSend == 0) - { - audio->epin_buf_cnt = 0; - return true; - } + if (nBytesPerFFToSend == 0) return 0; // Limit to maximum sample number - THIS IS A POSSIBLE ERROR SOURCE IF TOO MANY SAMPLE WOULD NEED TO BE SENT BUT CAN NOT! - nSamplesPerChannelToSend = tu_min16(nSamplesPerChannelToSend, nEndpointSampleCapacity); - nBytesToSend = nSamplesPerChannelToSend * CFG_TUD_AUDIO_N_CHANNELS_TX * CFG_TUD_AUDIO_N_BYTES_PER_SAMPLE_TX; + nBytesPerFFToSend = tu_min16(nBytesPerFFToSend, capPerFF); + + // Round to full number of samples (flooring) + nBytesPerFFToSend = (nBytesPerFFToSend / nBytesToCopy) * nBytesToCopy; // Encode - uint16_t cntSample; - uint8_t * pBuff = audio->epin_buf; -#if CFG_TUD_AUDIO_N_BYTES_PER_SAMPLE_TX == 1 - uint8_t sample; -#elif CFG_TUD_AUDIO_N_BYTES_PER_SAMPLE_TX == 2 - uint16_t sample; -#else - uint32_t sample; -#endif - - // TODO: Big endianess handling - for (cntSample = 0; cntSample < nSamplesPerChannelToSend; cntSample++) - { - for (cntChannel = 0; cntChannel < CFG_TUD_AUDIO_N_CHANNELS_TX; cntChannel++) - { - // Get sample from buffer - tu_fifo_read_n(&audio->tx_ff[cntChannel], &sample, CFG_TUD_AUDIO_TX_ITEMSIZE); - - // Put it into EP's buffer - Let alignment problems be handled by memcpy - memcpy(pBuff, &sample, CFG_TUD_AUDIO_N_BYTES_PER_SAMPLE_TX); - - // Advance pointer - pBuff += CFG_TUD_AUDIO_N_BYTES_PER_SAMPLE_TX; - } - } - - audio->epin_buf_cnt = nBytesToSend; - - return true; -} - -#else -static bool audiod_tx_done_type_I_pcm_ff_cb(uint8_t rhport, audiod_interface_t* audio) -{ - // We encode directly into IN EP's buffer - abort if previous transfer not complete - TU_VERIFY(!usbd_edpt_busy(rhport, audio->ep_in)); - - // Determine amount of samples - uint16_t nByteCount = tu_fifo_count(&audio->tx_ff[0]); - - nByteCount = tu_min16(nByteCount, CFG_TUD_AUDIO_EPSIZE_IN); - - // Check if there is enough - if (nByteCount == 0) - { - return true; - } - - nByteCount = tu_fifo_read_n(&audio->tx_ff[0], audio->epin_buf, nByteCount); - audio->epin_buf_cnt = nByteCount; - - return true; -} -#endif // CFG_TUD_AUDIO_TX_FIFO_COUNT > 1 || (CFG_TUD_AUDIO_N_BYTES_PER_SAMPLE_TX != CFG_TUD_AUDIO_TX_ITEMSIZE) - -#endif //CFG_TUD_AUDIO_TX_FIFO_SIZE - -// This function is called once a transmit of an feedback packet was successfully completed. Here, we get the next feedback value to be sent - -#if CFG_TUD_AUDIO_EPSIZE_OUT && CFG_TUD_AUDIO_ENABLE_FEEDBACK_EP -static bool audio_fb_send(uint8_t rhport, audiod_interface_t *audio) -{ - uint8_t fb[4]; + void * src; + uint8_t * dst; + uint8_t * src_end; uint16_t len; - if (audio->fb_val == 0) + for (cnt_ff = 0; cnt_ff < n_ff_used; cnt_ff++) { - len = 0; - return true; - } - else - { - len = 4; - // Here we need to return the feedback value - if (rhport == 0) + dst = &audio->lin_buf_in[cnt_ff*audio->n_channels_per_ff_tx*audio->n_bytes_per_sampe_tx]; + + len = tu_fifo_get_linear_read_info(&audio->tx_supp_ff[cnt_ff], 0, &src, nBytesPerFFToSend); + tu_fifo_advance_read_pointer(&audio->tx_supp_ff[cnt_ff], len); + + src_end = src + len; + + dst = audiod_interleaved_copy_bytes_fast_encode(nBytesToCopy, src, src_end, dst, n_ff_used); + + // Handle wrapped part of FIFO + if (len < nBytesPerFFToSend) { - // For FS format is 10.14 - fb[0] = (audio->fb_val >> 2) & 0xFF; - fb[1] = (audio->fb_val >> 10) & 0xFF; - fb[2] = (audio->fb_val >> 18) & 0xFF; - // 4th byte is needed to work correctly with MS Windows - fb[3] = 0; + len = tu_fifo_get_linear_read_info(&audio->tx_supp_ff[cnt_ff], 0, &src, nBytesPerFFToSend - len); + tu_fifo_advance_read_pointer(&audio->tx_supp_ff[cnt_ff], len); + + src_end = src + len; + + audiod_interleaved_copy_bytes_fast_encode(nBytesToCopy, src, src_end, dst, n_ff_used); } - else - { - // For HS format is 16.16 - fb[0] = (audio->fb_val >> 0) & 0xFF; - fb[1] = (audio->fb_val >> 8) & 0xFF; - fb[2] = (audio->fb_val >> 16) & 0xFF; - fb[3] = (audio->fb_val >> 24) & 0xFF; - } - return usbd_edpt_xfer(rhport, audio->ep_fb, fb, len); } + return nBytesPerFFToSend * n_ff_used; } +#endif //CFG_TUD_AUDIO_ENABLE_ENCODING -//static uint16_t audio_fb_done_cb(uint8_t rhport, audiod_interface_t* audio) -//{ -// (void) rhport; -// (void) audio; -// -// if (tud_audio_fb_done_cb) TU_VERIFY(tud_audio_fb_done_cb(rhport)); -// return 0; -//} +// This function is called once a transmit of a feedback packet was successfully completed. Here, we get the next feedback value to be sent -#endif - -// This function is called once a transmit of an interrupt control packet was successfully completed. Here, we get the remaining bytes to send - -#if CFG_TUD_AUDIO_INT_CTR_EPSIZE_IN -static bool audio_int_ctr_done_cb(uint8_t rhport, audiod_interface_t* audio, uint16_t * n_bytes_copied) +#if CFG_TUD_AUDIO_ENABLE_EP_OUT && CFG_TUD_AUDIO_ENABLE_FEEDBACK_EP +static inline bool audiod_fb_send(uint8_t rhport, audiod_interface_t *audio) { - // We write directly into the EP's buffer - abort if previous transfer not complete - TU_VERIFY(!usbd_edpt_busy(rhport, audio->ep_int_ctr)); - - // TODO: Big endianess handling - uint16_t cnt = tu_fifo_read_n(audio->int_ctr_ff, audio->ep_int_ctr_buf, CFG_TUD_AUDIO_INT_CTR_EPSIZE_IN); - - if (cnt > 0) - { - // Schedule transmit - TU_VERIFY(usbd_edpt_xfer(rhport, audio->ep_int_ctr, audio->ep_int_ctr_buf, cnt)); - } - - *n_bytes_copied = cnt; - - if (tud_audio_int_ctr_done_cb) TU_VERIFY(tud_audio_int_ctr_done_cb(rhport, n_bytes_copied)); - - return true; + return usbd_edpt_xfer(rhport, audio->ep_fb, (uint8_t *) &audio->fb_val, 4); } #endif @@ -726,33 +995,308 @@ void audiod_init(void) { audiod_interface_t* audio = &_audiod_itf[i]; - // Initialize TX FIFOs if required -#if CFG_TUD_AUDIO_EPSIZE_IN && CFG_TUD_AUDIO_TX_FIFO_SIZE - for (uint8_t cnt = 0; cnt < CFG_TUD_AUDIO_TX_FIFO_COUNT; cnt++) + // Initialize control buffers + switch (i) { - tu_fifo_config(&audio->tx_ff[cnt], &audio->tx_ff_buf[cnt], CFG_TUD_AUDIO_TX_FIFO_SIZE, 1, true); -#if CFG_FIFO_MUTEX - tu_fifo_config_mutex(&audio->tx_ff[cnt], osal_mutex_create(&audio->tx_ff_mutex[cnt])); + case 0: + audio->ctrl_buf = ctrl_buf_1; + audio->ctrl_buf_sz = CFG_TUD_AUDIO_FUNC_1_CTRL_BUF_SZ; + break; +#if CFG_TUD_AUDIO > 1 && CFG_TUD_AUDIO_FUNC_2_CTRL_BUF_SZ > 0 + case 1: + audio->ctrl_buf = ctrl_buf_2; + audio->ctrl_buf_sz = CFG_TUD_AUDIO_FUNC_2_CTRL_BUF_SZ; + break; +#endif +#if CFG_TUD_AUDIO > 2 && CFG_TUD_AUDIO_FUNC_3_CTRL_BUF_SZ > 0 + case 2: + audio->ctrl_buf = ctrl_buf_3; + audio->ctrl_buf_sz = CFG_TUD_AUDIO_FUNC_3_CTRL_BUF_SZ; + break; #endif } -#endif -#if CFG_TUD_AUDIO_EPSIZE_OUT && CFG_TUD_AUDIO_RX_FIFO_SIZE - for (uint8_t cnt = 0; cnt < CFG_TUD_AUDIO_RX_FIFO_COUNT; cnt++) + // Initialize active alternate interface buffers + switch (i) { - tu_fifo_config(&audio->rx_ff[cnt], &audio->rx_ff_buf[cnt], CFG_TUD_AUDIO_RX_FIFO_SIZE, 1, true); -#if CFG_FIFO_MUTEX - tu_fifo_config_mutex(&audio->rx_ff[cnt], osal_mutex_create(&audio->rx_ff_mutex[cnt])); +#if CFG_TUD_AUDIO_FUNC_1_N_AS_INT > 0 + case 0: + audio->alt_setting = alt_setting_1; + break; +#endif +#if CFG_TUD_AUDIO > 1 && CFG_TUD_AUDIO_FUNC_2_N_AS_INT > 0 + case 1: + audio->alt_setting = alt_setting_2; + break; +#endif +#if CFG_TUD_AUDIO > 2 && CFG_TUD_AUDIO_FUNC_3_N_AS_INT > 0 + case 2: + audio->alt_setting = alt_setting_3; + break; #endif } -#endif -#if CFG_TUD_AUDIO_INT_CTR_EPSIZE_IN > 0 - tu_fifo_config(&audio->int_ctr_ff, &audio->int_ctr_ff_buf, CFG_TUD_AUDIO_INT_CTR_BUFSIZE, 1, true); + // Initialize IN EP FIFO if required +#if CFG_TUD_AUDIO_ENABLE_EP_IN && !CFG_TUD_AUDIO_ENABLE_ENCODING + + switch (i) + { +#if CFG_TUD_AUDIO_FUNC_1_EP_IN_SW_BUF_SZ > 0 + case 0: + tu_fifo_config(&audio->ep_in_ff, audio_ep_in_sw_buf_1, CFG_TUD_AUDIO_FUNC_1_EP_IN_SW_BUF_SZ, 1, true); #if CFG_FIFO_MUTEX - tu_fifo_config_mutex(&audio->int_ctr_ff, osal_mutex_create(&audio->int_ctr_ff_mutex)); + tu_fifo_config_mutex(&audio->ep_in_ff, osal_mutex_create(&ep_in_ff_mutex_wr_1), NULL); #endif + break; #endif +#if CFG_TUD_AUDIO > 1 && CFG_TUD_AUDIO_FUNC_2_EP_IN_SW_BUF_SZ > 0 + case 1: + tu_fifo_config(&audio->ep_in_ff, audio_ep_in_sw_buf_2, CFG_TUD_AUDIO_FUNC_2_EP_IN_SW_BUF_SZ, 1, true); +#if CFG_FIFO_MUTEX + tu_fifo_config_mutex(&audio->ep_in_ff, osal_mutex_create(&ep_in_ff_mutex_wr_2), NULL); +#endif + break; +#endif +#if CFG_TUD_AUDIO > 2 && CFG_TUD_AUDIO_FUNC_3_EP_IN_SW_BUF_SZ > 0 + case 2: + tu_fifo_config(&audio->ep_in_ff, audio_ep_in_sw_buf_3, CFG_TUD_AUDIO_FUNC_3_EP_IN_SW_BUF_SZ, 1, true); +#if CFG_FIFO_MUTEX + tu_fifo_config_mutex(&audio->ep_in_ff, osal_mutex_create(&ep_in_ff_mutex_wr_3), NULL); +#endif + break; +#endif + } +#endif // CFG_TUD_AUDIO_ENABLE_EP_IN && !CFG_TUD_AUDIO_ENABLE_ENCODING + + // 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_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_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_3; + break; +#endif + } +#endif // USE_LINEAR_BUFFER_TX + + // Initialize OUT EP FIFO if required +#if CFG_TUD_AUDIO_ENABLE_EP_OUT && !CFG_TUD_AUDIO_ENABLE_DECODING + + switch (i) + { +#if CFG_TUD_AUDIO_FUNC_1_EP_OUT_SW_BUF_SZ > 0 + case 0: + tu_fifo_config(&audio->ep_out_ff, audio_ep_out_sw_buf_1, CFG_TUD_AUDIO_FUNC_1_EP_OUT_SW_BUF_SZ, 1, true); +#if CFG_FIFO_MUTEX + tu_fifo_config_mutex(&audio->ep_out_ff, NULL, osal_mutex_create(&ep_out_ff_mutex_rd_1)); +#endif + break; +#endif +#if CFG_TUD_AUDIO > 1 && CFG_TUD_AUDIO_FUNC_2_EP_OUT_SW_BUF_SZ > 0 + case 1: + tu_fifo_config(&audio->ep_out_ff, audio_ep_out_sw_buf_2, CFG_TUD_AUDIO_FUNC_2_EP_OUT_SW_BUF_SZ, 1, true); +#if CFG_FIFO_MUTEX + tu_fifo_config_mutex(&audio->ep_out_ff, NULL, osal_mutex_create(&ep_out_ff_mutex_rd_2)); +#endif + break; +#endif +#if CFG_TUD_AUDIO > 2 && CFG_TUD_AUDIO_FUNC_3_EP_OUT_SW_BUF_SZ > 0 + case 2: + tu_fifo_config(&audio->ep_out_ff, audio_ep_out_sw_buf_3, CFG_TUD_AUDIO_FUNC_3_EP_OUT_SW_BUF_SZ, 1, true); +#if CFG_FIFO_MUTEX + tu_fifo_config_mutex(&audio->ep_out_ff, NULL, osal_mutex_create(&ep_out_ff_mutex_rd_3)); +#endif + break; +#endif + } +#endif // CFG_TUD_AUDIO_ENABLE_EP_OUT && !CFG_TUD_AUDIO_ENABLE_DECODING + + // 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_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_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_3; + break; +#endif + } +#endif // USE_LINEAR_BUFFER_TX + + // Initialize TX support FIFOs if required +#if CFG_TUD_AUDIO_ENABLE_EP_IN && CFG_TUD_AUDIO_ENABLE_ENCODING + + switch (i) + { +#if CFG_TUD_AUDIO_FUNC_1_TX_SUPP_SW_FIFO_SZ > 0 + case 0: + audio->tx_supp_ff = tx_supp_ff_1; + audio->n_tx_supp_ff = CFG_TUD_AUDIO_FUNC_1_N_TX_SUPP_SW_FIFO; + audio->tx_supp_ff_sz_max = CFG_TUD_AUDIO_FUNC_1_TX_SUPP_SW_FIFO_SZ; + for (uint8_t cnt = 0; cnt < CFG_TUD_AUDIO_FUNC_1_N_TX_SUPP_SW_FIFO; cnt++) + { + tu_fifo_config(&tx_supp_ff_1[cnt], tx_supp_ff_buf_1[cnt], CFG_TUD_AUDIO_FUNC_1_TX_SUPP_SW_FIFO_SZ, 1, true); +#if CFG_FIFO_MUTEX + tu_fifo_config_mutex(&tx_supp_ff_1[cnt], osal_mutex_create(&tx_supp_ff_mutex_wr_1[cnt]), NULL); +#endif + } + + break; +#endif // CFG_TUD_AUDIO_FUNC_1_TX_SUPP_SW_FIFO_SZ > 0 + +#if CFG_TUD_AUDIO > 1 && CFG_TUD_AUDIO_FUNC_2_TX_SUPP_SW_FIFO_SZ > 0 + case 1: + audio->tx_supp_ff = tx_supp_ff_2; + audio->n_tx_supp_ff = CFG_TUD_AUDIO_FUNC_2_N_TX_SUPP_SW_FIFO; + audio->tx_supp_ff_sz_max = CFG_TUD_AUDIO_FUNC_2_TX_SUPP_SW_FIFO_SZ; + for (uint8_t cnt = 0; cnt < CFG_TUD_AUDIO_FUNC_2_N_TX_SUPP_SW_FIFO; cnt++) + { + tu_fifo_config(&tx_supp_ff_2[cnt], tx_supp_ff_buf_2[cnt], CFG_TUD_AUDIO_FUNC_2_TX_SUPP_SW_FIFO_SZ, 1, true); +#if CFG_FIFO_MUTEX + tu_fifo_config_mutex(&tx_supp_ff_2[cnt], osal_mutex_create(&tx_supp_ff_mutex_wr_2[cnt]), NULL); +#endif + } + + break; +#endif // CFG_TUD_AUDIO > 1 && CFG_TUD_AUDIO_FUNC_2_TX_SUPP_SW_FIFO_SZ > 0 + +#if CFG_TUD_AUDIO > 2 && CFG_TUD_AUDIO_FUNC_3_TX_SUPP_SW_FIFO_SZ > 0 + case 2: + audio->tx_supp_ff = tx_supp_ff_3; + audio->n_tx_supp_ff = CFG_TUD_AUDIO_FUNC_3_N_TX_SUPP_SW_FIFO; + audio->tx_supp_ff_sz_max = CFG_TUD_AUDIO_FUNC_3_TX_SUPP_SW_FIFO_SZ; + for (uint8_t cnt = 0; cnt < CFG_TUD_AUDIO_FUNC_3_N_TX_SUPP_SW_FIFO; cnt++) + { + tu_fifo_config(&tx_supp_ff_3[cnt], tx_supp_ff_buf_3[cnt], CFG_TUD_AUDIO_FUNC_3_TX_SUPP_SW_FIFO_SZ, 1, true); +#if CFG_FIFO_MUTEX + tu_fifo_config_mutex(&tx_supp_ff_3[cnt], osal_mutex_create(&tx_supp_ff_mutex_wr_3[cnt]), NULL); +#endif + } + + break; +#endif // CFG_TUD_AUDIO > 1 && CFG_TUD_AUDIO_FUNC_2_TX_SUPP_SW_FIFO_SZ > 0 + } +#endif // CFG_TUD_AUDIO_ENABLE_EP_IN && CFG_TUD_AUDIO_ENABLE_ENCODING + + // Set encoding parameters for Type_I formats +#if CFG_TUD_AUDIO_ENABLE_TYPE_I_ENCODING + switch (i) + { +#if CFG_TUD_AUDIO_FUNC_1_TX_SUPP_SW_FIFO_SZ > 0 + case 0: + audio->n_channels_per_ff_tx = CFG_TUD_AUDIO_FUNC_1_CHANNEL_PER_FIFO_TX; + break; +#endif +#if CFG_TUD_AUDIO > 1 && CFG_TUD_AUDIO_FUNC_2_TX_SUPP_SW_FIFO_SZ > 0 + case 1: + audio->n_channels_per_ff_tx = CFG_TUD_AUDIO_FUNC_2_CHANNEL_PER_FIFO_TX; + break; +#endif +#if CFG_TUD_AUDIO > 2 && CFG_TUD_AUDIO_FUNC_3_TX_SUPP_SW_FIFO_SZ > 0 + case 2: + audio->n_channels_per_ff_tx = CFG_TUD_AUDIO_FUNC_3_CHANNEL_PER_FIFO_TX; + break; +#endif + } +#endif // CFG_TUD_AUDIO_ENABLE_TYPE_I_ENCODING + + // Initialize RX support FIFOs if required +#if CFG_TUD_AUDIO_ENABLE_EP_OUT && CFG_TUD_AUDIO_ENABLE_DECODING + + switch (i) + { +#if CFG_TUD_AUDIO_FUNC_1_RX_SUPP_SW_FIFO_SZ > 0 + case 0: + audio->rx_supp_ff = rx_supp_ff_1; + audio->n_rx_supp_ff = CFG_TUD_AUDIO_FUNC_1_N_RX_SUPP_SW_FIFO; + audio->rx_supp_ff_sz_max = CFG_TUD_AUDIO_FUNC_1_RX_SUPP_SW_FIFO_SZ; + for (uint8_t cnt = 0; cnt < CFG_TUD_AUDIO_FUNC_1_N_RX_SUPP_SW_FIFO; cnt++) + { + tu_fifo_config(&rx_supp_ff_1[cnt], rx_supp_ff_buf_1[cnt], CFG_TUD_AUDIO_FUNC_1_RX_SUPP_SW_FIFO_SZ, 1, true); +#if CFG_FIFO_MUTEX + tu_fifo_config_mutex(&rx_supp_ff_1[cnt], osal_mutex_create(&rx_supp_ff_mutex_rd_1[cnt]), NULL); +#endif + } + + break; +#endif // CFG_TUD_AUDIO_FUNC_1_RX_SUPP_SW_FIFO_SZ > 0 + +#if CFG_TUD_AUDIO > 1 && CFG_TUD_AUDIO_FUNC_2_RX_SUPP_SW_FIFO_SZ > 0 + case 1: + audio->rx_supp_ff = rx_supp_ff_2; + audio->n_rx_supp_ff = CFG_TUD_AUDIO_FUNC_2_N_RX_SUPP_SW_FIFO; + audio->rx_supp_ff_sz_max = CFG_TUD_AUDIO_FUNC_2_RX_SUPP_SW_FIFO_SZ; + for (uint8_t cnt = 0; cnt < CFG_TUD_AUDIO_FUNC_2_N_RX_SUPP_SW_FIFO; cnt++) + { + tu_fifo_config(&rx_supp_ff_2[cnt], rx_supp_ff_buf_2[cnt], CFG_TUD_AUDIO_FUNC_2_RX_SUPP_SW_FIFO_SZ, 1, true); +#if CFG_FIFO_MUTEX + tu_fifo_config_mutex(&rx_supp_ff_2[cnt], osal_mutex_create(&rx_supp_ff_mutex_rd_2[cnt]), NULL); +#endif + } + + break; +#endif // CFG_TUD_AUDIO > 1 && CFG_TUD_AUDIO_FUNC_2_RX_SUPP_SW_FIFO_SZ > 0 + +#if CFG_TUD_AUDIO > 2 && CFG_TUD_AUDIO_FUNC_3_RX_SUPP_SW_FIFO_SZ > 0 + case 2: + audio->rx_supp_ff = rx_supp_ff_3; + audio->n_rx_supp_ff = CFG_TUD_AUDIO_FUNC_3_N_RX_SUPP_SW_FIFO; + audio->rx_supp_ff_sz_max = CFG_TUD_AUDIO_FUNC_3_RX_SUPP_SW_FIFO_SZ; + for (uint8_t cnt = 0; cnt < CFG_TUD_AUDIO_FUNC_3_N_RX_SUPP_SW_FIFO; cnt++) + { + tu_fifo_config(&rx_supp_ff_3[cnt], rx_supp_ff_buf_3[cnt], CFG_TUD_AUDIO_FUNC_3_RX_SUPP_SW_FIFO_SZ, 1, true); +#if CFG_FIFO_MUTEX + tu_fifo_config_mutex(&rx_supp_ff_3[cnt], osal_mutex_create(&rx_supp_ff_mutex_rd_3[cnt]), NULL); +#endif + } + + break; +#endif // CFG_TUD_AUDIO > 1 && CFG_TUD_AUDIO_FUNC_2_RX_SUPP_SW_FIFO_SZ > 0 + } +#endif // CFG_TUD_AUDIO_ENABLE_EP_IN && CFG_TUD_AUDIO_ENABLE_ENCODING + + // Set encoding parameters for Type_I formats +#if CFG_TUD_AUDIO_ENABLE_TYPE_I_DECODING + switch (i) + { +#if CFG_TUD_AUDIO_FUNC_1_RX_SUPP_SW_FIFO_SZ > 0 + case 0: + audio->n_channels_per_ff_rx = CFG_TUD_AUDIO_FUNC_1_CHANNEL_PER_FIFO_RX; + break; +#endif +#if CFG_TUD_AUDIO > 1 && CFG_TUD_AUDIO_FUNC_2_RX_SUPP_SW_FIFO_SZ > 0 + case 1: + audio->n_channels_per_ff_rx = CFG_TUD_AUDIO_FUNC_2_CHANNEL_PER_FIFO_RX; + break; +#endif +#if CFG_TUD_AUDIO > 2 && CFG_TUD_AUDIO_FUNC_3_RX_SUPP_SW_FIFO_SZ > 0 + case 2: + audio->n_channels_per_ff_rx = CFG_TUD_AUDIO_FUNC_3_CHANNEL_PER_FIFO_RX; + break; +#endif + } +#endif // CFG_TUD_AUDIO_ENABLE_TYPE_I_DECODING } } @@ -765,17 +1309,25 @@ void audiod_reset(uint8_t rhport) audiod_interface_t* audio = &_audiod_itf[i]; tu_memclr(audio, ITF_MEM_RESET_SIZE); -#if CFG_TUD_AUDIO_EPSIZE_IN && CFG_TUD_AUDIO_TX_FIFO_SIZE - for (uint8_t cnt = 0; cnt < CFG_TUD_AUDIO_TX_FIFO_COUNT; cnt++) +#if CFG_TUD_AUDIO_ENABLE_EP_IN && !CFG_TUD_AUDIO_ENABLE_ENCODING + tu_fifo_clear(&audio->ep_in_ff); +#endif + +#if CFG_TUD_AUDIO_ENABLE_EP_OUT && !CFG_TUD_AUDIO_ENABLE_DECODING + tu_fifo_clear(&audio->ep_out_ff); +#endif + +#if CFG_TUD_AUDIO_ENABLE_EP_IN && CFG_TUD_AUDIO_ENABLE_ENCODING + for (uint8_t cnt = 0; cnt < audio->n_tx_supp_ff; cnt++) { - tu_fifo_clear(&audio->tx_ff[cnt]); + tu_fifo_clear(&audio->tx_supp_ff[cnt]); } #endif -#if CFG_TUD_AUDIO_EPSIZE_OUT && CFG_TUD_AUDIO_RX_FIFO_SIZE - for (uint8_t cnt = 0; cnt < CFG_TUD_AUDIO_RX_FIFO_COUNT; cnt++) +#if CFG_TUD_AUDIO_ENABLE_EP_OUT && CFG_TUD_AUDIO_ENABLE_DECODING + for (uint8_t cnt = 0; cnt < audio->n_rx_supp_ff; cnt++) { - tu_fifo_clear(&audio->rx_ff[cnt]); + tu_fifo_clear(&audio->rx_supp_ff[cnt]); } #endif } @@ -808,6 +1360,25 @@ uint16_t audiod_open(uint8_t rhport, tusb_desc_interface_t const * itf_desc, uin { _audiod_itf[i].p_desc = (uint8_t const *)itf_desc; // Save pointer to AC descriptor which is by specification always the first one _audiod_itf[i].rhport = rhport; + + // Setup descriptor lengths + switch (i) + { + case 0: + _audiod_itf[i].desc_length = CFG_TUD_AUDIO_FUNC_1_DESC_LEN; + break; +#if CFG_TUD_AUDIO > 1 + case 1: + _audiod_itf[i].desc_length = CFG_TUD_AUDIO_FUNC_2_DESC_LEN; + break; +#endif +#if CFG_TUD_AUDIO > 2 + case 2: + _audiod_itf[i].desc_length = CFG_TUD_AUDIO_FUNC_3_DESC_LEN; + break; +#endif + } + break; } } @@ -816,15 +1387,13 @@ uint16_t audiod_open(uint8_t rhport, tusb_desc_interface_t const * itf_desc, uin 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) - // TODO: Find a way to find end of current audio function and avoid necessity of tud_audio_desc_lengths - since now max_length is available we could do this surely somehow - uint16_t drv_len = tud_audio_desc_lengths[i] - TUD_AUDIO_DESC_IAD_LEN; // - TUD_AUDIO_DESC_IAD_LEN since tinyUSB already handles the IAD descriptor + uint16_t drv_len = _audiod_itf[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) { -#if CFG_TUD_AUDIO_N_AS_INT > 0 uint8_t const itf = tu_u16_low(p_request->wIndex); // Find index of audio streaming interface @@ -832,17 +1401,11 @@ static bool audiod_get_interface(uint8_t rhport, tusb_control_request_t const * uint8_t const *dummy; TU_VERIFY(audiod_get_AS_interface_index(itf, &idxDriver, &idxItf, &dummy)); - TU_VERIFY(tud_control_xfer(rhport, p_request, &_audiod_itf[idxDriver].altSetting[idxItf], 1)); + TU_VERIFY(tud_control_xfer(rhport, p_request, &_audiod_itf[idxDriver].alt_setting[idxItf], 1)); - TU_LOG2(" Get itf: %u - current alt: %u\r\n", itf, _audiod_itf[idxDriver].altSetting[idxItf]); + TU_LOG2(" Get itf: %u - current alt: %u\r\n", itf, _audiod_itf[idxDriver].alt_setting[idxItf]); return true; - -#else - (void) rhport; - (void) p_request; - return false; -#endif } static bool audiod_set_interface(uint8_t rhport, tusb_control_request_t const * p_request) @@ -870,41 +1433,60 @@ static bool audiod_set_interface(uint8_t rhport, tusb_control_request_t const * uint8_t const *p_desc; TU_VERIFY(audiod_get_AS_interface_index(itf, &idxDriver, &idxItf, &p_desc)); + audiod_interface_t* audio = &_audiod_itf[idxDriver]; + // 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_EPSIZE_IN > 0 - if (_audiod_itf[idxDriver].ep_in_as_intf_num == itf) +#if CFG_TUD_AUDIO_ENABLE_EP_IN + if (audio->ep_in_as_intf_num == itf) { - _audiod_itf[idxDriver].ep_in_as_intf_num = 0; - usbd_edpt_close(rhport, _audiod_itf[idxDriver].ep_in); + audio->ep_in_as_intf_num = 0; + usbd_edpt_close(rhport, audio->ep_in); // Invoke callback - can be used to stop data sampling if (tud_audio_set_itf_close_EP_cb) TU_VERIFY(tud_audio_set_itf_close_EP_cb(rhport, p_request)); - _audiod_itf[idxDriver].ep_in = 0; // Necessary? + audio->ep_in = 0; // Necessary? + + // Clear support FIFOs if used +#if CFG_TUD_AUDIO_ENABLE_ENCODING + for (uint8_t cnt = 0; cnt < audio->n_tx_supp_ff; cnt++) + { + tu_fifo_clear(&audio->tx_supp_ff[cnt]); + } +#endif + } #endif -#if CFG_TUD_AUDIO_EPSIZE_OUT - if (_audiod_itf[idxDriver].ep_out_as_intf_num == itf) +#if CFG_TUD_AUDIO_ENABLE_EP_OUT + if (audio->ep_out_as_intf_num == itf) { - _audiod_itf[idxDriver].ep_out_as_intf_num = 0; - usbd_edpt_close(rhport, _audiod_itf[idxDriver].ep_out); - _audiod_itf[idxDriver].ep_out = 0; // Necessary? + audio->ep_out_as_intf_num = 0; + usbd_edpt_close(rhport, audio->ep_out); + audio->ep_out = 0; // Necessary? + + // Clear support FIFOs if used +#if CFG_TUD_AUDIO_ENABLE_DECODING + for (uint8_t cnt = 0; cnt < audio->n_rx_supp_ff; cnt++) + { + tu_fifo_clear(&audio->rx_supp_ff[cnt]); + } +#endif // Close corresponding feedback EP #if CFG_TUD_AUDIO_ENABLE_FEEDBACK_EP - usbd_edpt_close(rhport, _audiod_itf[idxDriver].ep_fb); - _audiod_itf[idxDriver].ep_fb = 0; // Necessary? + usbd_edpt_close(rhport, audio->ep_fb); + audio->ep_fb = 0; // Necessary? #endif } #endif // Save current alternative interface setting - _audiod_itf[idxDriver].altSetting[idxItf] = alt; + audio->alt_setting[idxItf] = alt; // Open new EP if necessary - EPs are only to be closed or opened for AS interfaces - Look for AS interface with correct alternate interface // Get pointer at end - uint8_t const *p_desc_end = _audiod_itf[idxDriver].p_desc + tud_audio_desc_lengths[idxDriver] - TUD_AUDIO_DESC_IAD_LEN; + 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 while (p_desc < p_desc_end) @@ -912,6 +1494,9 @@ static bool audiod_set_interface(uint8_t rhport, tusb_control_request_t const * // 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_ENCODING || CFG_TUD_AUDIO_ENABLE_DECODING + 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; while (foundEPs < nEps && p_desc < p_desc_end) @@ -922,51 +1507,87 @@ static bool audiod_set_interface(uint8_t rhport, tusb_control_request_t const * uint8_t ep_addr = ((tusb_desc_endpoint_t const *) p_desc)->bEndpointAddress; - // We need to set EP non busy since this is not taken care of right now in ep_close() - THIS IS A WORKAROUND! + //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_EPSIZE_IN > 0 +#if CFG_TUD_AUDIO_ENABLE_EP_IN if (tu_edpt_dir(ep_addr) == TUSB_DIR_IN && ((tusb_desc_endpoint_t const *) p_desc)->bmAttributes.usage == 0x00) // Check if usage is data EP { // Save address - _audiod_itf[idxDriver].ep_in = ep_addr; - _audiod_itf[idxDriver].ep_in_as_intf_num = itf; + audio->ep_in = ep_addr; + audio->ep_in_as_intf_num = itf; + audio->ep_in_sz = ((tusb_desc_endpoint_t const *) p_desc)->wMaxPacketSize.size; + // If software encoding is enabled, parse for the corresponding parameters - doing this here means only AS interfaces with EPs get scanned for parameters +#if CFG_TUD_AUDIO_ENABLE_ENCODING + audiod_parse_for_AS_params(audio, p_desc_parse_for_params, p_desc_end, itf); + + // Reconfigure size of support FIFOs - this is necessary to avoid samples to get split in case of a wrap +#if CFG_TUD_AUDIO_ENABLE_TYPE_I_ENCODING + const uint16_t active_fifo_depth = (audio->tx_supp_ff_sz_max / audio->n_bytes_per_sampe_tx) * audio->n_bytes_per_sampe_tx; + for (uint8_t cnt = 0; cnt < audio->n_tx_supp_ff; cnt++) + { + tu_fifo_config(&audio->tx_supp_ff[cnt], audio->tx_supp_ff[cnt].buffer, active_fifo_depth, 1, true); + } + audio->n_ff_used_tx = audio->n_channels_tx / audio->n_channels_per_ff_tx; + TU_ASSERT( audio->n_ff_used_tx <= audio->n_tx_supp_ff ); +#endif + +#endif // Invoke callback - can be used to trigger data sampling if not already running if (tud_audio_set_itf_cb) TU_VERIFY(tud_audio_set_itf_cb(rhport, p_request)); // Schedule first transmit - in case no sample data is available a ZLP is loaded - uint16_t n_bytes_copied; - TU_VERIFY(audiod_tx_done_cb(rhport, &_audiod_itf[idxDriver], &n_bytes_copied)); + // It is necessary to trigger this here since the refill is done with an RX FIFO empty interrupt which can only trigger if something was in there + TU_VERIFY(audiod_tx_done_cb(rhport, &_audiod_itf[idxDriver])); } -#endif +#endif // CFG_TUD_AUDIO_ENABLE_EP_IN -#if CFG_TUD_AUDIO_EPSIZE_OUT +#if CFG_TUD_AUDIO_ENABLE_EP_OUT if (tu_edpt_dir(ep_addr) == TUSB_DIR_OUT) // Checking usage not necessary { // Save address - _audiod_itf[idxDriver].ep_out = ep_addr; - _audiod_itf[idxDriver].ep_out_as_intf_num = itf; + audio->ep_out = ep_addr; + audio->ep_out_as_intf_num = itf; + audio->ep_out_sz = ((tusb_desc_endpoint_t const *) p_desc)->wMaxPacketSize.size; +#if CFG_TUD_AUDIO_ENABLE_DECODING + audiod_parse_for_AS_params(audio, p_desc_parse_for_params, p_desc_end, itf); + + // Reconfigure size of support FIFOs - this is necessary to avoid samples to get split in case of a wrap +#if CFG_TUD_AUDIO_ENABLE_TYPE_I_DECODING + const uint16_t active_fifo_depth = (audio->rx_supp_ff_sz_max / audio->n_bytes_per_sampe_rx) * audio->n_bytes_per_sampe_rx; + for (uint8_t cnt = 0; cnt < audio->n_rx_supp_ff; cnt++) + { + tu_fifo_config(&audio->rx_supp_ff[cnt], audio->rx_supp_ff[cnt].buffer, active_fifo_depth, 1, true); + } + audio->n_ff_used_rx = audio->n_channels_rx / audio->n_channels_per_ff_rx; + TU_ASSERT( audio->n_ff_used_rx <= audio->n_rx_supp_ff ); +#endif +#endif // Invoke callback if (tud_audio_set_itf_cb) TU_VERIFY(tud_audio_set_itf_cb(rhport, p_request)); // Prepare for incoming data - TU_ASSERT(usbd_edpt_xfer(rhport, ep_addr, _audiod_itf[idxDriver].epout_buf, CFG_TUD_AUDIO_EPSIZE_OUT), false); +#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_iso_xfer(rhport, audio->ep_out, &audio->ep_out_ff, audio->ep_out_sz), false); +#endif } #if CFG_TUD_AUDIO_ENABLE_FEEDBACK_EP if (tu_edpt_dir(ep_addr) == TUSB_DIR_IN && ((tusb_desc_endpoint_t const *) p_desc)->bmAttributes.usage == 1) // Check if usage is explicit data feedback { - _audiod_itf[idxDriver].ep_fb = ep_addr; + audio->ep_fb = ep_addr; // Invoke callback if (tud_audio_set_itf_cb) TU_VERIFY(tud_audio_set_itf_cb(rhport, p_request)); } #endif +#endif // CFG_TUD_AUDIO_ENABLE_EP_OUT -#endif foundEPs += 1; } p_desc = tu_desc_next(p_desc); @@ -1166,7 +1787,7 @@ static bool audiod_control_request(uint8_t rhport, tusb_control_request_t const } // If we end here, the received request is a set request - we schedule a receive for the data stage and return true here. We handle the rest later in audiod_control_complete() once the data stage was finished - TU_VERIFY(tud_control_xfer(rhport, p_request, _audiod_itf[idxDriver].ctrl_buf, CFG_TUD_AUDIO_CTRL_BUF_SIZE)); + TU_VERIFY(tud_control_xfer(rhport, p_request, _audiod_itf[idxDriver].ctrl_buf, _audiod_itf[idxDriver].ctrl_buf_sz)); return true; } @@ -1208,21 +1829,15 @@ bool audiod_xfer_cb(uint8_t rhport, uint8_t ep_addr, xfer_result_t result, uint3 // 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 ??? - // Load new data - uint16 *n_bytes_copied; - TU_VERIFY(audio_int_ctr_done_cb(rhport, &_audiod_itf[idxDriver], n_bytes_copied)); + // I assume here, that things above are handled by PHY + // All transmission is done - what remains to do is to inform job was completed - if (*n_bytes_copied == 0 && xferred_bytes && (0 == (xferred_bytes % CFG_TUD_AUDIO_INT_CTR_EPSIZE_IN))) - { - // There is no data left to send, a ZLP should be sent if - // xferred_bytes is multiple of EP size and not zero - return usbd_edpt_xfer(rhport, ep_addr, NULL, 0); - } + if (tud_audio_int_ctr_done_cb) TU_VERIFY(tud_audio_int_ctr_done_cb(rhport, (uint16_t) xferred_bytes)); } #endif -#if CFG_TUD_AUDIO_EPSIZE_IN +#if CFG_TUD_AUDIO_ENABLE_EP_IN // Data transmission of audio packet finished if (_audiod_itf[idxDriver].ep_in == ep_addr) @@ -1236,25 +1851,19 @@ bool audiod_xfer_cb(uint8_t rhport, uint8_t ep_addr, xfer_result_t result, uint3 // This is the only place where we can fill something into the EPs buffer! // Load new data - uint16_t n_bytes_copied; - TU_VERIFY(audiod_tx_done_cb(rhport, &_audiod_itf[idxDriver], &n_bytes_copied)); + TU_VERIFY(audiod_tx_done_cb(rhport, &_audiod_itf[idxDriver])); // Transmission of ZLP is done by audiod_tx_done_cb() return true; } #endif -#if CFG_TUD_AUDIO_EPSIZE_OUT +#if CFG_TUD_AUDIO_ENABLE_EP_OUT // New audio packet received if (_audiod_itf[idxDriver].ep_out == ep_addr) { - // Save into buffer - do whatever has to be done - TU_VERIFY(audio_rx_done_cb(rhport, &_audiod_itf[idxDriver], _audiod_itf[idxDriver].epout_buf, xferred_bytes)); - - // prepare for next transmission - TU_ASSERT(usbd_edpt_xfer(rhport, ep_addr, _audiod_itf[idxDriver].epout_buf, CFG_TUD_AUDIO_EPSIZE_OUT), false); - + TU_VERIFY(audiod_rx_done_cb(rhport, &_audiod_itf[idxDriver], (uint16_t) xferred_bytes)); return true; } @@ -1265,14 +1874,14 @@ bool audiod_xfer_cb(uint8_t rhport, uint8_t ep_addr, xfer_result_t result, uint3 { if (tud_audio_fb_done_cb) TU_VERIFY(tud_audio_fb_done_cb(rhport)); - return audio_fb_send(rhport, &_audiod_itf[idxDriver]); + // Schedule next transmission - value is changed bytud_audio_n_fb_set() in the meantime or the old value gets sent + return audiod_fb_send(rhport, &_audiod_itf[idxDriver]); } #endif #endif } return false; - } bool tud_audio_buffer_and_schedule_control_xfer(uint8_t rhport, tusb_control_request_t const * p_request, void* data, uint16_t len) @@ -1317,7 +1926,7 @@ bool tud_audio_buffer_and_schedule_control_xfer(uint8_t rhport, tusb_control_req } // Crop length - if (len > CFG_TUD_AUDIO_CTRL_BUF_SIZE) len = CFG_TUD_AUDIO_CTRL_BUF_SIZE; + if (len > _audiod_itf[idxDriver].ctrl_buf_sz) len = _audiod_itf[idxDriver].ctrl_buf_sz; // Copy into buffer memcpy((void *)_audiod_itf[idxDriver].ctrl_buf, data, (size_t)len); @@ -1338,7 +1947,7 @@ static bool audiod_get_AS_interface_index(uint8_t itf, uint8_t *idxDriver, uint8 if (_audiod_itf[i].p_desc) { // Get pointer at end - uint8_t const *p_desc_end = _audiod_itf[i].p_desc + tud_audio_desc_lengths[i] - TUD_AUDIO_DESC_IAD_LEN; + uint8_t const *p_desc_end = _audiod_itf[i].p_desc + _audiod_itf[i].desc_length - TUD_AUDIO_DESC_IAD_LEN; // Advance past AC descriptors uint8_t const *p_desc = tu_desc_next(_audiod_itf[i].p_desc); @@ -1403,7 +2012,7 @@ static bool audiod_verify_itf_exists(uint8_t itf, uint8_t *idxDriver) { // Get pointer at beginning and end uint8_t const *p_desc = _audiod_itf[i].p_desc; - uint8_t const *p_desc_end = _audiod_itf[i].p_desc + tud_audio_desc_lengths[i] - TUD_AUDIO_DESC_IAD_LEN; + uint8_t const *p_desc_end = _audiod_itf[i].p_desc + _audiod_itf[i].desc_length - TUD_AUDIO_DESC_IAD_LEN; while (p_desc < p_desc_end) { @@ -1427,7 +2036,7 @@ static bool audiod_verify_ep_exists(uint8_t ep, uint8_t *idxDriver) if (_audiod_itf[i].p_desc) { // Get pointer at end - uint8_t const *p_desc_end = _audiod_itf[i].p_desc + tud_audio_desc_lengths[i]; + uint8_t const *p_desc_end = _audiod_itf[i].p_desc + _audiod_itf[i].desc_length; // Advance past AC descriptors - EP we look for are streaming EPs uint8_t const *p_desc = tu_desc_next(_audiod_itf[i].p_desc); @@ -1447,15 +2056,125 @@ static bool audiod_verify_ep_exists(uint8_t ep, uint8_t *idxDriver) return false; } -#if CFG_TUD_AUDIO_ENABLE_FEEDBACK_EP -bool tud_audio_fb_set(uint8_t rhport, uint32_t feedback) +#if CFG_TUD_AUDIO_ENABLE_ENCODING || CFG_TUD_AUDIO_ENABLE_DECODING +// p_desc points to the AS interface of alternate setting zero +// itf is the interface number of the corresponding interface - we check if the interface belongs to EP in or EP out to see if it is a TX or RX parameter +// Currently, only AS interfaces with an EP (in or out) are supposed to be parsed for! +static void audiod_parse_for_AS_params(audiod_interface_t* audio, uint8_t const * p_desc, uint8_t const * p_desc_end, uint8_t const itf) { - audiod_interface_t *audio = &_audiod_itf[0]; + p_desc = tu_desc_next(p_desc); // Exclude standard AS interface descriptor of current alternate interface descriptor - audio->fb_val = feedback; - TU_VERIFY(!usbd_edpt_busy(rhport, audio->ep_fb), true); + while (p_desc < p_desc_end) + { + // Abort if follow up descriptor is a new standard interface descriptor - indicates the last AS descriptor was already finished + if (tu_desc_type(p_desc) == TUSB_DESC_INTERFACE) break; - return audio_fb_send(rhport, audio); + // Look for a Class-Specific AS Interface Descriptor(4.9.2) to verify format type and format and also to get number of physical channels + if (tu_desc_type(p_desc) == TUSB_DESC_CS_INTERFACE && tu_desc_subtype(p_desc) == AUDIO_CS_AS_INTERFACE_AS_GENERAL) + { +#if CFG_TUD_AUDIO_ENABLE_EP_IN && CFG_TUD_AUDIO_ENABLE_EP_OUT + if (itf != audio->ep_in_as_intf_num && itf != audio->ep_out_as_intf_num) break; // Abort loop, this interface has no EP, this driver does not support this currently +#endif +#if CFG_TUD_AUDIO_ENABLE_EP_IN && !CFG_TUD_AUDIO_ENABLE_EP_OUT + if (itf != audio->ep_in_as_intf_num) break; +#endif +#if !CFG_TUD_AUDIO_ENABLE_EP_IN && CFG_TUD_AUDIO_ENABLE_EP_OUT + if (itf != audio->ep_out_as_intf_num) break; +#endif + +#if CFG_TUD_AUDIO_ENABLE_EP_IN + if (itf == audio->ep_in_as_intf_num) + { + audio->n_channels_tx = ((audio_desc_cs_as_interface_t const * )p_desc)->bNrChannels; + audio->format_type_tx = ((audio_desc_cs_as_interface_t const * )p_desc)->bFormatType; + +#if CFG_TUD_AUDIO_ENABLE_TYPE_I_ENCODING + audio->format_type_I_tx = ((audio_desc_cs_as_interface_t const * )p_desc)->bmFormats; +#endif + } +#endif + +#if CFG_TUD_AUDIO_ENABLE_EP_OUT + if (itf == audio->ep_out_as_intf_num) + { + audio->n_channels_rx = ((audio_desc_cs_as_interface_t const * )p_desc)->bNrChannels; + audio->format_type_rx = ((audio_desc_cs_as_interface_t const * )p_desc)->bFormatType; +#if CFG_TUD_AUDIO_ENABLE_TYPE_I_DECODING + audio->format_type_I_rx = ((audio_desc_cs_as_interface_t const * )p_desc)->bmFormats; +#endif + } +#endif + } + + // Look for a Type I Format Type Descriptor(2.3.1.6 - Audio Formats) +#if CFG_TUD_AUDIO_ENABLE_TYPE_I_ENCODING || CFG_TUD_AUDIO_ENABLE_TYPE_I_DECODING + if (tu_desc_type(p_desc) == TUSB_DESC_CS_INTERFACE && tu_desc_subtype(p_desc) == AUDIO_CS_AS_INTERFACE_FORMAT_TYPE && ((audio_desc_type_I_format_t const * )p_desc)->bFormatType == AUDIO_FORMAT_TYPE_I) + { +#if CFG_TUD_AUDIO_ENABLE_EP_IN && CFG_TUD_AUDIO_ENABLE_EP_OUT + if (itf != audio->ep_in_as_intf_num && itf != audio->ep_out_as_intf_num) break; // Abort loop, this interface has no EP, this driver does not support this currently +#endif +#if CFG_TUD_AUDIO_ENABLE_EP_IN && !CFG_TUD_AUDIO_ENABLE_EP_OUT + if (itf != audio->ep_in_as_intf_num) break; +#endif +#if !CFG_TUD_AUDIO_ENABLE_EP_IN && CFG_TUD_AUDIO_ENABLE_EP_OUT + if (itf != audio->ep_out_as_intf_num) break; +#endif + +#if CFG_TUD_AUDIO_ENABLE_EP_IN + if (itf == audio->ep_in_as_intf_num) + { + audio->n_bytes_per_sampe_tx = ((audio_desc_type_I_format_t const * )p_desc)->bSubslotSize; + } +#endif + +#if CFG_TUD_AUDIO_ENABLE_EP_OUT + if (itf == audio->ep_out_as_intf_num) + { + audio->n_bytes_per_sampe_rx = ((audio_desc_type_I_format_t const * )p_desc)->bSubslotSize; + } +#endif + } +#endif + + // Other format types are not supported yet + + p_desc = tu_desc_next(p_desc); + } +} +#endif + +#if CFG_TUD_AUDIO_ENABLE_FEEDBACK_EP + +// Input value feedback has to be in 16.16 format - the format will be converted according to speed settings automatically +bool tud_audio_n_fb_set(uint8_t itf, uint32_t feedback) +{ + TU_VERIFY(itf < CFG_TUD_AUDIO && _audiod_itf[itf].p_desc != NULL); + + // Format the feedback value + if (_audiod_itf[itf].rhport == 0) + { + uint8_t * fb = (uint8_t *) &_audiod_itf[itf].fb_val; + + // For FS format is 10.14 + *(fb++) = (feedback >> 2) & 0xFF; + *(fb++) = (feedback >> 10) & 0xFF; + *(fb++) = (feedback >> 18) & 0xFF; + // 4th byte is needed to work correctly with MS Windows + *fb = 0; + } + else + { + // For HS format is 16.16 as originally demanded + _audiod_itf[itf].fb_val = feedback; + } + + // Schedule a transmit with the new value if EP is not busy - this triggers repetitive scheduling of the feedback value + if (!usbd_edpt_busy(_audiod_itf[itf].rhport, _audiod_itf[itf].ep_fb)) + { + return audiod_fb_send(_audiod_itf[itf].rhport, &_audiod_itf[itf]); + } + + return true; } #endif diff --git a/src/class/audio/audio_device.h b/src/class/audio/audio_device.h index 5061501ce..f91540b64 100644 --- a/src/class/audio/audio_device.h +++ b/src/class/audio/audio_device.h @@ -38,116 +38,306 @@ // Class Driver Configuration //--------------------------------------------------------------------+ -// Number of Standard AS Interface Descriptors (4.9.1) defined per audio function - this is required to be able to remember the current alternate settings of these interfaces - We restrict us here to have a constant number for all audio functions (which means this has to be the maximum number of AS interfaces an audio function has and a second audio function with less AS interfaces just waste a few bytes) -#ifndef CFG_TUD_AUDIO_N_AS_INT -#define CFG_TUD_AUDIO_N_AS_INT 0 +// 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 + +// Number of Standard AS Interface Descriptors (4.9.1) defined per audio function - this is required to be able to remember the current alternate settings of these interfaces +#ifndef CFG_TUD_AUDIO_FUNC_1_N_AS_INT +#error You must tell the driver the number of Standard AS Interface Descriptors you have defined in the audio function descriptor! +#endif +#if CFG_TUD_AUDIO > 1 +#ifndef CFG_TUD_AUDIO_FUNC_2_N_AS_INT +#error You must tell the driver the number of Standard AS Interface Descriptors you have defined in the audio function descriptor! +#endif +#endif +#if CFG_TUD_AUDIO > 2 +#ifndef CFG_TUD_AUDIO_FUNC_3_N_AS_INT +#error You must tell the driver the number of Standard AS Interface Descriptors you have defined in the audio function 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_CTRL_BUF_SIZE +#ifndef CFG_TUD_AUDIO_FUNC_1_CTRL_BUF_SZ #error You must define an audio class control request buffer size! #endif -// Use of TX/RX FIFOs - If sizes are not zero, audio.c implements FIFOs for RX and TX (whatever defined). -// For RX: the input stream gets decoded into its corresponding channels, where for each channel a FIFO is setup to hold its data -> see: audio_rx_done_cb(). -// For TX: the output stream is composed from CFG_TUD_AUDIO_N_CHANNELS_TX channels, where for each channel a FIFO is defined. -// Further, it implements encoding and decoding of the individual channels (parameterized by the defines below). -// If you don't use the FIFOs you need to handle encoding and decoding on your own in audio_rx_done_cb() and audio_tx_done_cb(). This, however, allows for optimizations. - -#ifndef CFG_TUD_AUDIO_TX_FIFO_SIZE -#define CFG_TUD_AUDIO_TX_FIFO_SIZE 0 // Buffer size per channel +#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 -#ifndef CFG_TUD_AUDIO_RX_FIFO_SIZE -#define CFG_TUD_AUDIO_RX_FIFO_SIZE 0 // Buffer size per channel +#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 - Limits: Full Speed <= 1023, High Speed <= 1024 -#ifndef CFG_TUD_AUDIO_EPSIZE_IN -#define CFG_TUD_AUDIO_EPSIZE_IN 0 // TX +// 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_EPSIZE_OUT -#define CFG_TUD_AUDIO_EPSIZE_OUT 0 // RX +#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 + +// 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 +#define CFG_TUD_AUDIO_ENABLE_FEEDBACK_EP 0 // Feedback - 0 or 1 #endif +// Audio interrupt control EP size - disabled if 0 #ifndef CFG_TUD_AUDIO_INT_CTR_EPSIZE_IN -#define CFG_TUD_AUDIO_INT_CTR_EPSIZE_IN 0 // Audio interrupt control +#define CFG_TUD_AUDIO_INT_CTR_EPSIZE_IN 0 // Audio interrupt control - if required - 6 Bytes according to UAC 2 specification (p. 74) #endif -#if CFG_TUD_AUDIO_INT_CTR_EPSIZE_IN -#ifndef CFG_TUD_AUDIO_INT_CTR_BUFSIZE -#define CFG_TUD_AUDIO_INT_CTR_BUFSIZE 6 // Buffer size of audio control interrupt EP - 6 Bytes according to UAC 2 specification (p. 74) +#ifndef CFG_TUD_AUDIO_INT_CTR_EP_IN_SW_BUFFER_SIZE +#define CFG_TUD_AUDIO_INT_CTR_EP_IN_SW_BUFFER_SIZE 6 // Buffer size of audio control interrupt EP - 6 Bytes according to UAC 2 specification (p. 74) +#endif + +// Use software encoding/decoding + +// The software coding feature of the driver is not mandatory. It is useful if, for instance, you have two I2S streams which need to be interleaved +// into a single PCM stream as SAMPLE_1 | SAMPLE_2 | SAMPLE_3 | SAMPLE_4. +// +// Currently, only PCM type I encoding/decoding is supported! +// +// If the coding feature is to be used, support FIFOs need to be configured. Their sizes and numbers are defined below. + +// Encoding/decoding is done in software and thus time consuming. If you can encode/decode your stream more efficiently do not use the +// support FIFOs but write/read directly into/from the EP_X_SW_BUFFER_FIFOs using +// - tud_audio_n_write() or +// - tud_audio_n_read(). +// To write/read to/from the support FIFOs use +// - tud_audio_n_write_support_ff() or +// - tud_audio_n_read_support_ff(). +// +// The encoding/decoding format type done is defined below. +// +// The encoding/decoding starts when the private callback functions +// - audio_tx_done_cb() +// - audio_rx_done_cb() +// are invoked. If support FIFOs are used, the corresponding encoding/decoding functions are called from there. +// Once encoding/decoding is done the result is put directly into the EP_X_SW_BUFFER_FIFOs. You can use the public callback functions +// - tud_audio_tx_done_pre_load_cb() or tud_audio_tx_done_post_load_cb() +// - tud_audio_rx_done_pre_read_cb() or tud_audio_rx_done_post_read_cb() +// if you want to get informed what happened. +// +// If you don't use the support FIFOs you may use the public callback functions +// - tud_audio_tx_done_pre_load_cb() or tud_audio_tx_done_post_load_cb() +// - tud_audio_rx_done_pre_read_cb() or tud_audio_rx_done_post_read_cb() +// to write/read from/into the EP_X_SW_BUFFER_FIFOs at the right time. +// +// If you need a different encoding which is not support so far implement it in the +// - audio_tx_done_cb() +// - audio_rx_done_cb() +// functions. + +// Enable encoding/decodings - for these to work, support FIFOs need to be setup in appropriate numbers and size +// The actual coding parameters of active AS alternate interface is parsed from the descriptors + +// The item size of the FIFO is always fixed to one i.e. bytes! Furthermore, the actively used FIFO depth is reconfigured such that the depth is a multiple of the current sample size in order to avoid samples to get split up in case of a wrap in the FIFO ring buffer (depth = (max_depth / sampe_sz) * sampe_sz)! +// This is important to remind in case you use DMAs! If the sample sizes changes, the DMA MUST BE RECONFIGURED just like the FIFOs for a different depth!!! + +// For PCM encoding/decoding + +#ifndef CFG_TUD_AUDIO_ENABLE_ENCODING +#define CFG_TUD_AUDIO_ENABLE_ENCODING 0 +#endif + +#ifndef CFG_TUD_AUDIO_ENABLE_DECODING +#define CFG_TUD_AUDIO_ENABLE_DECODING 0 +#endif + +// This enabling allows to save the current coding parameters e.g. # of bytes per sample etc. - TYPE_I includes common PCM encoding +#ifndef CFG_TUD_AUDIO_ENABLE_TYPE_I_ENCODING +#define CFG_TUD_AUDIO_ENABLE_TYPE_I_ENCODING 0 +#endif + +#ifndef CFG_TUD_AUDIO_ENABLE_TYPE_I_DECODING +#define CFG_TUD_AUDIO_ENABLE_TYPE_I_DECODING 0 +#endif + +// Type I Coding parameters not given within UAC2 descriptors +// It would be possible to allow for a more flexible setting and not fix this parameter as done below. However, this is most often not needed and kept for later if really necessary. The more flexible setting could be implemented within set_interface(), however, how the values are saved per alternate setting is to be determined! +#if CFG_TUD_AUDIO_ENABLE_EP_IN && CFG_TUD_AUDIO_ENABLE_ENCODING && CFG_TUD_AUDIO_ENABLE_TYPE_I_ENCODING +#ifndef CFG_TUD_AUDIO_FUNC_1_CHANNEL_PER_FIFO_TX +#error You must tell the driver the number of channels per FIFO for the interleaved encoding! E.g. for an I2S interface having two channels, CHANNEL_PER_FIFO = 2 as the I2S stream having two channels is usually saved within one FIFO +#endif +#if CFG_TUD_AUDIO > 1 +#ifndef CFG_TUD_AUDIO_FUNC_2_CHANNEL_PER_FIFO_TX +#error You must tell the driver the number of channels per FIFO for the interleaved encoding! E.g. for an I2S interface having two channels, CHANNEL_PER_FIFO = 2 as the I2S stream having two channels is usually saved within one FIFO +#endif +#endif +#if CFG_TUD_AUDIO > 2 +#ifndef CFG_TUD_AUDIO_FUNC_3_CHANNEL_PER_FIFO_TX +#error You must tell the driver the number of channels per FIFO for the interleaved encoding! E.g. for an I2S interface having two channels, CHANNEL_PER_FIFO = 2 as the I2S stream having two channels is usually saved within one FIFO +#endif #endif #endif -#ifndef CFG_TUD_AUDIO_N_CHANNELS_TX -#define CFG_TUD_AUDIO_N_CHANNELS_TX 1 +#if CFG_TUD_AUDIO_ENABLE_EP_OUT && CFG_TUD_AUDIO_ENABLE_DECODING && CFG_TUD_AUDIO_ENABLE_TYPE_I_DECODING +#ifndef CFG_TUD_AUDIO_FUNC_1_CHANNEL_PER_FIFO_RX +#error You must tell the driver the number of channels per FIFO for the interleaved encoding! E.g. for an I2S interface having two channels, CHANNEL_PER_FIFO = 2 as the I2S stream having two channels is usually saved within one FIFO #endif - -#ifndef CFG_TUD_AUDIO_N_CHANNELS_RX -#define CFG_TUD_AUDIO_N_CHANNELS_RX 1 +#if CFG_TUD_AUDIO > 1 +#ifndef CFG_TUD_AUDIO_FUNC_2_CHANNEL_PER_FIFO_RX +#error You must tell the driver the number of channels per FIFO for the interleaved encoding! E.g. for an I2S interface having two channels, CHANNEL_PER_FIFO = 2 as the I2S stream having two channels is usually saved within one FIFO #endif - -// Audio data format types -#ifndef CFG_TUD_AUDIO_FORMAT_TYPE_TX -#define CFG_TUD_AUDIO_FORMAT_TYPE_TX AUDIO_FORMAT_TYPE_UNDEFINED // If this option is used, an encoding function has to be implemented in audio_device.c #endif - -#ifndef CFG_TUD_AUDIO_FORMAT_TYPE_RX -#define CFG_TUD_AUDIO_FORMAT_TYPE_RX AUDIO_FORMAT_TYPE_UNDEFINED // If this option is used, a decoding function has to be implemented in audio_device.c +#if CFG_TUD_AUDIO > 2 +#ifndef CFG_TUD_AUDIO_FUNC_3_CHANNEL_PER_FIFO_RX +#error You must tell the driver the number of channels per FIFO for the interleaved encoding! E.g. for an I2S interface having two channels, CHANNEL_PER_FIFO = 2 as the I2S stream having two channels is usually saved within one FIFO #endif - -// Audio data format type I specifications -#if CFG_TUD_AUDIO_FORMAT_TYPE_TX == AUDIO_FORMAT_TYPE_I - -// Type definitions - for possible formats see: audio_data_format_type_I_t and further in UAC2 specifications. -#ifndef CFG_TUD_AUDIO_FORMAT_TYPE_I_TX -#define CFG_TUD_AUDIO_FORMAT_TYPE_I_TX AUDIO_DATA_FORMAT_TYPE_I_PCM -#endif - -#ifndef CFG_TUD_AUDIO_N_BYTES_PER_SAMPLE_TX // bSubslotSize -#define CFG_TUD_AUDIO_N_BYTES_PER_SAMPLE_TX 1 -#endif - -#ifndef CFG_TUD_AUDIO_TX_ITEMSIZE -#if CFG_TUD_AUDIO_N_BYTES_PER_SAMPLE_TX == 1 -#define CFG_TUD_AUDIO_TX_ITEMSIZE 1 -#elif CFG_TUD_AUDIO_N_BYTES_PER_SAMPLE_TX == 2 -#define CFG_TUD_AUDIO_TX_ITEMSIZE 2 -#else -#define CFG_TUD_AUDIO_TX_ITEMSIZE 4 #endif #endif -#if CFG_TUD_AUDIO_TX_ITEMSIZE < CFG_TUD_AUDIO_N_BYTES_PER_SAMPLE_TX -#error FIFO element size (ITEMSIZE) must not be smaller then sample size +// Remaining types not support so far + +// Number of support FIFOs to set up - multiple channels can be handled by one FIFO - very common is two channels per FIFO stemming from one I2S interface +#ifndef CFG_TUD_AUDIO_FUNC_1_N_TX_SUPP_SW_FIFO +#define CFG_TUD_AUDIO_FUNC_1_N_TX_SUPP_SW_FIFO 0 +#endif +#ifndef CFG_TUD_AUDIO_FUNC_2_N_TX_SUPP_SW_FIFO +#define CFG_TUD_AUDIO_FUNC_2_N_TX_SUPP_SW_FIFO 0 +#endif +#ifndef CFG_TUD_AUDIO_FUNC_3_N_TX_SUPP_SW_FIFO +#define CFG_TUD_AUDIO_FUNC_3_N_TX_SUPP_SW_FIFO 0 #endif +#ifndef CFG_TUD_AUDIO_FUNC_1_N_RX_SUPP_SW_FIFO +#define CFG_TUD_AUDIO_FUNC_1_N_RX_SUPP_SW_FIFO 0 +#endif +#ifndef CFG_TUD_AUDIO_FUNC_2_N_RX_SUPP_SW_FIFO +#define CFG_TUD_AUDIO_FUNC_2_N_RX_SUPP_SW_FIFO 0 +#endif +#ifndef CFG_TUD_AUDIO_FUNC_3_N_RX_SUPP_SW_FIFO +#define CFG_TUD_AUDIO_FUNC_3_N_RX_SUPP_SW_FIFO 0 #endif -#if CFG_TUD_AUDIO_FORMAT_TYPE_RX == AUDIO_FORMAT_TYPE_I - -#ifndef CFG_TUD_AUDIO_FORMAT_TYPE_I_RX -#define CFG_TUD_AUDIO_FORMAT_TYPE_I_RX AUDIO_DATA_FORMAT_TYPE_I_PCM +// Size of support FIFOs IN BYTES - if size > 0 there are as many FIFOs set up as CFG_TUD_AUDIO_FUNC_X_N_TX_SUPP_SW_FIFO and CFG_TUD_AUDIO_FUNC_X_N_RX_SUPP_SW_FIFO +#ifndef CFG_TUD_AUDIO_FUNC_1_TX_SUPP_SW_FIFO_SZ +#define CFG_TUD_AUDIO_FUNC_1_TX_SUPP_SW_FIFO_SZ 0 // FIFO size - minimum size: ceil(f_s/1000) * max(# of TX channels) / (# of TX support FIFOs) * max(# of bytes per sample) +#endif +#ifndef CFG_TUD_AUDIO_FUNC_2_TX_SUPP_SW_FIFO_SZ +#define CFG_TUD_AUDIO_FUNC_2_TX_SUPP_SW_FIFO_SZ 0 +#endif +#ifndef CFG_TUD_AUDIO_FUNC_3_TX_SUPP_SW_FIFO_SZ +#define CFG_TUD_AUDIO_FUNC_3_TX_SUPP_SW_FIFO_SZ 0 #endif -#ifndef CFG_TUD_AUDIO_N_BYTES_PER_SAMPLE_RX // bSubslotSize -#define CFG_TUD_AUDIO_N_BYTES_PER_SAMPLE_RX 1 +#ifndef CFG_TUD_AUDIO_FUNC_1_RX_SUPP_SW_FIFO_SZ +#define CFG_TUD_AUDIO_FUNC_1_RX_SUPP_SW_FIFO_SZ 0 // FIFO size - minimum size: ceil(f_s/1000) * max(# of RX channels) / (# of RX support FIFOs) * max(# of bytes per sample) #endif - -#if CFG_TUD_AUDIO_N_BYTES_PER_SAMPLE_RX == 1 -#define CFG_TUD_AUDIO_RX_ITEMSIZE 1 -#elif CFG_TUD_AUDIO_N_BYTES_PER_SAMPLE_RX == 2 -#define CFG_TUD_AUDIO_RX_ITEMSIZE 2 -#else -#define CFG_TUD_AUDIO_RX_ITEMSIZE 4 +#ifndef CFG_TUD_AUDIO_FUNC_2_RX_SUPP_SW_FIFO_SZ +#define CFG_TUD_AUDIO_FUNC_2_RX_SUPP_SW_FIFO_SZ 0 #endif - +#ifndef CFG_TUD_AUDIO_FUNC_3_RX_SUPP_SW_FIFO_SZ +#define CFG_TUD_AUDIO_FUNC_3_RX_SUPP_SW_FIFO_SZ 0 #endif //static_assert(sizeof(tud_audio_desc_lengths) != CFG_TUD_AUDIO, "Supply audio function descriptor pack length!"); @@ -170,69 +360,70 @@ extern "C" { //--------------------------------------------------------------------+ bool tud_audio_n_mounted (uint8_t itf); -#if CFG_TUD_AUDIO_EPSIZE_OUT && CFG_TUD_AUDIO_RX_FIFO_SIZE -#if CFG_TUD_AUDIO_RX_FIFO_COUNT > 1 -uint16_t tud_audio_n_available (uint8_t itf, uint8_t channelId); -uint16_t tud_audio_n_read (uint8_t itf, uint8_t channelId, void* buffer, uint16_t bufsize); -void tud_audio_n_read_flush (uint8_t itf, uint8_t channelId); -#else -uint16_t tud_audio_n_available (uint8_t itf); -uint16_t tud_audio_n_read (uint8_t itf, void* buffer, uint16_t bufsize); -void tud_audio_n_read_flush (uint8_t itf); -#endif +#if CFG_TUD_AUDIO_ENABLE_EP_OUT && !CFG_TUD_AUDIO_ENABLE_DECODING +uint16_t tud_audio_n_available (uint8_t itf); +uint16_t tud_audio_n_read (uint8_t itf, void* buffer, uint16_t bufsize); +bool tud_audio_n_clear_ep_out_ff (uint8_t itf); // Delete all content in the EP OUT FIFO #endif -/* This function is intended for later use once EP buffers (at least for ISO EPs) are implemented as ring buffers -#if CFG_TUD_AUDIO_EPSIZE_IN && !CFG_TUD_AUDIO_TX_FIFO_SIZE -uint16_t tud_audio_n_write_ep_in_buffer(uint8_t itf, const void * data, uint16_t len) -#endif -*/ - -#ifndef CFG_TUD_AUDIO_TX_FIFO_COUNT -#define CFG_TUD_AUDIO_TX_FIFO_COUNT 1 +#if CFG_TUD_AUDIO_ENABLE_EP_OUT && CFG_TUD_AUDIO_ENABLE_DECODING +bool tud_audio_n_clear_rx_support_ff (uint8_t itf, uint8_t channelId); // Delete all content in the support RX FIFOs +uint16_t tud_audio_n_available_support_ff (uint8_t itf, uint8_t channelId); +uint16_t tud_audio_n_read_support_ff (uint8_t itf, uint8_t channelId, void* buffer, uint16_t bufsize); #endif -#if CFG_TUD_AUDIO_EPSIZE_IN && CFG_TUD_AUDIO_TX_FIFO_SIZE -#if CFG_TUD_AUDIO_TX_FIFO_COUNT > 1 -uint16_t tud_audio_n_write (uint8_t itf, uint8_t channelId, const void * data, uint16_t len); -#else -uint16_t tud_audio_n_write (uint8_t itf, const void * data, uint16_t len); -#endif -uint16_t tud_audio_n_write_flush(uint8_t itf); +#if CFG_TUD_AUDIO_ENABLE_EP_IN && !CFG_TUD_AUDIO_ENABLE_ENCODING +uint16_t tud_audio_n_write (uint8_t itf, const void * data, uint16_t len); +bool tud_audio_n_clear_ep_in_ff (uint8_t itf); // Delete all content in the EP IN FIFO #endif -#if CFG_TUD_AUDIO_INT_CTR_EPSIZE_IN > 0 -uint16_t tud_audio_int_ctr_n_available (uint8_t itf); -uint16_t tud_audio_int_ctr_n_read (uint8_t itf, void* buffer, uint16_t bufsize); -void tud_audio_int_ctr_n_read_flush (uint8_t itf); -uint16_t tud_audio_int_ctr_n_write (uint8_t itf, uint8_t const* buffer, uint16_t bufsize); +#if CFG_TUD_AUDIO_ENABLE_EP_IN && CFG_TUD_AUDIO_ENABLE_ENCODING +uint16_t tud_audio_n_flush_tx_support_ff (uint8_t itf); // Force all content in the support TX FIFOs to be written into EP SW FIFO +bool tud_audio_n_clear_tx_support_ff (uint8_t itf, uint8_t channelId); +uint16_t tud_audio_n_write_support_ff (uint8_t itf, uint8_t channelId, const void * data, uint16_t len); +#endif + +#if CFG_TUD_AUDIO_INT_CTR_EPSIZE_IN +uint16_t tud_audio_int_ctr_n_write (uint8_t itf, uint8_t const* buffer, uint16_t len); #endif //--------------------------------------------------------------------+ // Application API (Interface0) //--------------------------------------------------------------------+ -static inline bool tud_audio_mounted (void); +static inline bool tud_audio_mounted (void); -#if CFG_TUD_AUDIO_EPSIZE_OUT && CFG_TUD_AUDIO_RX_FIFO_SIZE -static inline uint16_t tud_audio_available (void); -static inline uint16_t tud_audio_read (void* buffer, uint16_t bufsize); -static inline void tud_audio_read_flush (void); +// RX API + +#if CFG_TUD_AUDIO_ENABLE_EP_OUT && !CFG_TUD_AUDIO_ENABLE_DECODING +static inline uint16_t tud_audio_available (void); +static inline bool tud_audio_clear_ep_out_ff (void); // Delete all content in the EP OUT FIFO +static inline uint16_t tud_audio_read (void* buffer, uint16_t bufsize); #endif -#if CFG_TUD_AUDIO_EPSIZE_IN && CFG_TUD_AUDIO_TX_FIFO_SIZE -#if CFG_TUD_AUDIO_TX_FIFO_COUNT > 1 -static inline uint16_t tud_audio_write (uint8_t channelId, uint8_t const* buffer, uint16_t bufsize); -#else -static inline uint16_t tud_audio_write (uint8_t const* buffer, uint16_t bufsize); -#endif +#if CFG_TUD_AUDIO_ENABLE_EP_OUT && CFG_TUD_AUDIO_ENABLE_DECODING +static inline bool tud_audio_clear_rx_support_ff (uint8_t channelId); +static inline uint16_t tud_audio_available_support_ff (uint8_t channelId); +static inline uint16_t tud_audio_read_support_ff (uint8_t channelId, void* buffer, uint16_t bufsize); #endif -#if CFG_TUD_AUDIO_INT_CTR_EPSIZE_IN > 0 -static inline uint32_t tud_audio_int_ctr_available (void); -static inline uint32_t tud_audio_int_ctr_read (void* buffer, uint32_t bufsize); -static inline void tud_audio_int_ctr_read_flush (void); -static inline uint32_t tud_audio_int_ctr_write (uint8_t const* buffer, uint32_t bufsize); +// TX API + +#if CFG_TUD_AUDIO_ENABLE_EP_IN && !CFG_TUD_AUDIO_ENABLE_ENCODING +static inline uint16_t tud_audio_write (const void * data, uint16_t len); +static inline bool tud_audio_clear_ep_in_ff (void); +#endif + +#if CFG_TUD_AUDIO_ENABLE_EP_IN && CFG_TUD_AUDIO_ENABLE_ENCODING +static inline uint16_t tud_audio_flush_tx_support_ff (void); +static inline uint16_t tud_audio_clear_tx_support_ff (uint8_t channelId); +static inline uint16_t tud_audio_write_support_ff (uint8_t channelId, const void * data, uint16_t len); +#endif + +// INT CTR API + +#if CFG_TUD_AUDIO_INT_CTR_EPSIZE_IN +static inline uint16_t tud_audio_int_ctr_write (uint8_t const* buffer, uint16_t len); #endif // Buffer control EP data and schedule a transmit @@ -247,26 +438,28 @@ bool tud_audio_buffer_and_schedule_control_xfer(uint8_t rhport, tusb_control_req // Application Callback API (weak is optional) //--------------------------------------------------------------------+ -#if CFG_TUD_AUDIO_EPSIZE_IN +#if CFG_TUD_AUDIO_ENABLE_EP_IN TU_ATTR_WEAK bool tud_audio_tx_done_pre_load_cb(uint8_t rhport, uint8_t itf, uint8_t ep_in, uint8_t cur_alt_setting); TU_ATTR_WEAK bool tud_audio_tx_done_post_load_cb(uint8_t rhport, uint16_t n_bytes_copied, uint8_t itf, uint8_t ep_in, uint8_t cur_alt_setting); #endif -#if CFG_TUD_AUDIO_EPSIZE_OUT -TU_ATTR_WEAK bool tud_audio_rx_done_cb(uint8_t rhport, uint8_t * buffer, uint16_t bufsize); +#if CFG_TUD_AUDIO_ENABLE_EP_OUT +TU_ATTR_WEAK bool tud_audio_rx_done_pre_read_cb(uint8_t rhport, uint16_t n_bytes_received, uint8_t itf, uint8_t ep_out, uint8_t cur_alt_setting); +TU_ATTR_WEAK bool tud_audio_rx_done_post_read_cb(uint8_t rhport, uint16_t n_bytes_received, uint8_t itf, uint8_t ep_out, uint8_t cur_alt_setting); #endif -#if CFG_TUD_AUDIO_EPSIZE_OUT > 0 && CFG_TUD_AUDIO_ENABLE_FEEDBACK_EP +#if CFG_TUD_AUDIO_ENABLE_EP_OUT && CFG_TUD_AUDIO_ENABLE_FEEDBACK_EP TU_ATTR_WEAK bool tud_audio_fb_done_cb(uint8_t rhport); // User code should call this function with feedback value in 16.16 format for FS and HS. // Value will be corrected for FS to 10.14 format automatically. // (see Universal Serial Bus Specification Revision 2.0 5.12.4.2). // Feedback value will be sent at FB endpoint interval till it's changed. -bool tud_audio_fb_set(uint8_t rhport, uint32_t feedback); +bool tud_audio_n_fb_set(uint8_t itf, uint32_t feedback); +static inline bool tud_audio_fb_set(uint32_t feedback); #endif #if CFG_TUD_AUDIO_INT_CTR_EPSIZE_IN -TU_ATTR_WEAK bool tud_audio_int_ctr_done_cb(uint8_t rhport, uint16_t * n_bytes_copied); +TU_ATTR_WEAK bool tud_audio_int_ctr_done_cb(uint8_t rhport, uint16_t n_bytes_copied); #endif // Invoked when audio set interface request received @@ -302,93 +495,103 @@ static inline bool tud_audio_mounted(void) return tud_audio_n_mounted(0); } -#if CFG_TUD_AUDIO_EPSIZE_IN -#if CFG_TUD_AUDIO_TX_FIFO_SIZE && CFG_TUD_AUDIO_TX_FIFO_COUNT > 1 -static inline uint16_t tud_audio_write (uint8_t channelId, uint8_t const* buffer, uint16_t n_bytes) // Short version if only one audio function is used -{ - return tud_audio_n_write(0, channelId, buffer, n_bytes); -} -#else -static inline uint16_t tud_audio_write (uint8_t const* buffer, uint16_t n_bytes) // Short version if only one audio function is used -{ - return tud_audio_n_write(0, buffer, n_bytes); -} -#endif +// RX API -static inline uint16_t tud_audio_write_flush (void) // Short version if only one audio function is used -{ -#if CFG_TUD_AUDIO_TX_FIFO_SIZE - return tud_audio_n_write_flush(0); -#else - return 0; -#endif -} -#endif // CFG_TUD_AUDIO_EPSIZE_IN && CFG_TUD_AUDIO_TX_FIFO_SIZE +#if CFG_TUD_AUDIO_ENABLE_EP_OUT && !CFG_TUD_AUDIO_ENABLE_DECODING -#if CFG_TUD_AUDIO_EPSIZE_OUT && CFG_TUD_AUDIO_RX_FIFO_SIZE -#if CFG_TUD_AUDIO_RX_FIFO_COUNT > 1 -static inline uint16_t tud_audio_available(uint8_t channelId) -{ - return tud_audio_n_available(0, channelId); -} - -static inline uint16_t tud_audio_read(uint8_t channelId, void* buffer, uint16_t bufsize) -{ - return tud_audio_n_read(0, channelId, buffer, bufsize); -} - -static inline void tud_audio_read_flush(uint8_t channelId) -{ - tud_audio_n_read_flush(0, channelId); -} -#else static inline uint16_t tud_audio_available(void) { return tud_audio_n_available(0); } -static inline uint16_t tud_audio_read(void *buffer, uint16_t bufsize) +static inline uint16_t tud_audio_read(void* buffer, uint16_t bufsize) { return tud_audio_n_read(0, buffer, bufsize); } -static inline void tud_audio_read_flush(void) +static inline bool tud_audio_clear_ep_out_ff(void) { - tud_audio_n_read_flush(0); + return tud_audio_n_clear_ep_out_ff(0); } -#endif + #endif -#if CFG_TUD_AUDIO_INT_CTR_EPSIZE_IN > 0 -static inline uint16_t tud_audio_int_ctr_available(void) +#if CFG_TUD_AUDIO_ENABLE_EP_OUT && CFG_TUD_AUDIO_ENABLE_DECODING + +static inline bool tud_audio_clear_rx_support_ff(uint8_t channelId) { - return tud_audio_int_ctr_n_available(0); + return tud_audio_n_clear_rx_support_ff(0, channelId); } -static inline uint16_t tud_audio_int_ctr_read(void* buffer, uint16_t bufsize) +static inline uint16_t tud_audio_available_support_ff(uint8_t channelId) { - return tud_audio_int_ctr_n_read(0, buffer, bufsize); + return tud_audio_n_available_support_ff(0, channelId); } -static inline void tud_audio_int_ctr_read_flush(void) +static inline uint16_t tud_audio_read_support_ff(uint8_t channelId, void* buffer, uint16_t bufsize) { - return tud_audio_int_ctr_n_read_flush(0); + return tud_audio_n_read_support_ff(0, channelId, buffer, bufsize); } -static inline uint16_t tud_audio_int_ctr_write(uint8_t const* buffer, uint16_t bufsize) +#endif + +// TX API + +#if CFG_TUD_AUDIO_ENABLE_EP_IN && !CFG_TUD_AUDIO_ENABLE_ENCODING + +static inline uint16_t tud_audio_write(const void * data, uint16_t len) { - return tud_audio_int_ctr_n_write(0, buffer, bufsize); + return tud_audio_n_write(0, data, len); +} + +static inline bool tud_audio_clear_ep_in_ff(void) +{ + return tud_audio_n_clear_ep_in_ff(0); +} + +#endif + +#if CFG_TUD_AUDIO_ENABLE_EP_IN && CFG_TUD_AUDIO_ENABLE_ENCODING + +static inline uint16_t tud_audio_flush_tx_support_ff(void) +{ + return tud_audio_n_flush_tx_support_ff(0); +} + +static inline uint16_t tud_audio_clear_tx_support_ff(uint8_t channelId) +{ + return tud_audio_n_clear_tx_support_ff(0, channelId); +} + +static inline uint16_t tud_audio_write_support_ff(uint8_t channelId, const void * data, uint16_t len) +{ + return tud_audio_n_write_support_ff(0, channelId, data, len); +} + +#endif + +#if CFG_TUD_AUDIO_INT_CTR_EPSIZE_IN +static inline uint16_t tud_audio_int_ctr_write(uint8_t const* buffer, uint16_t len) +{ + return tud_audio_int_ctr_n_write(0, buffer, len); +} +#endif + +#if CFG_TUD_AUDIO_ENABLE_EP_OUT && CFG_TUD_AUDIO_ENABLE_FEEDBACK_EP +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); -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); +void audiod_init (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); #ifdef __cplusplus } diff --git a/src/class/cdc/cdc_device.c b/src/class/cdc/cdc_device.c index c24edb757..0a7691916 100644 --- a/src/class/cdc/cdc_device.c +++ b/src/class/cdc/cdc_device.c @@ -244,8 +244,8 @@ void cdcd_init(void) tu_fifo_config(&p_cdc->tx_ff, p_cdc->tx_ff_buf, TU_ARRAY_SIZE(p_cdc->tx_ff_buf), 1, true); #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)); + tu_fifo_config_mutex(&p_cdc->rx_ff, NULL, osal_mutex_create(&p_cdc->rx_ff_mutex)); + tu_fifo_config_mutex(&p_cdc->tx_ff, osal_mutex_create(&p_cdc->tx_ff_mutex), NULL); #endif } } diff --git a/src/class/midi/midi_device.c b/src/class/midi/midi_device.c index 9ccbb733c..2c50bc4f3 100644 --- a/src/class/midi/midi_device.c +++ b/src/class/midi/midi_device.c @@ -378,8 +378,8 @@ void midid_init(void) 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)); + tu_fifo_config_mutex(&midi->rx_ff, NULL, osal_mutex_create(&midi->rx_ff_mutex)); + tu_fifo_config_mutex(&midi->tx_ff, osal_mutex_create(&midi->tx_ff_mutex), NULL); #endif } } diff --git a/src/class/vendor/vendor_device.c b/src/class/vendor/vendor_device.c index 3fcea89c4..9b0a3c25f 100644 --- a/src/class/vendor/vendor_device.c +++ b/src/class/vendor/vendor_device.c @@ -146,8 +146,8 @@ void vendord_init(void) 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)); + tu_fifo_config_mutex(&p_itf->rx_ff, NULL, osal_mutex_create(&p_itf->rx_ff_mutex)); + tu_fifo_config_mutex(&p_itf->tx_ff, osal_mutex_create(&p_itf->tx_ff_mutex), NULL); #endif } } diff --git a/src/common/tusb_fifo.c b/src/common/tusb_fifo.c index 30629af71..098d54801 100644 --- a/src/common/tusb_fifo.c +++ b/src/common/tusb_fifo.c @@ -39,34 +39,38 @@ // implement mutex lock and unlock #if CFG_FIFO_MUTEX -static void tu_fifo_lock(tu_fifo_t *f) +static inline void _ff_lock(tu_fifo_mutex_t mutex) { - if (f->mutex) - { - osal_mutex_lock(f->mutex, OSAL_TIMEOUT_WAIT_FOREVER); - } + if (mutex) osal_mutex_lock(mutex, OSAL_TIMEOUT_WAIT_FOREVER); } -static void tu_fifo_unlock(tu_fifo_t *f) +static inline void _ff_unlock(tu_fifo_mutex_t mutex) { - if (f->mutex) - { - osal_mutex_unlock(f->mutex); - } + if (mutex) osal_mutex_unlock(mutex); } #else -#define tu_fifo_lock(_ff) -#define tu_fifo_unlock(_ff) +#define _ff_lock(_mutex) +#define _ff_unlock(_mutex) #endif +/** \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 +{ + TU_FIFO_COPY_INC, ///< Copy from/to an increasing source/destination address - default mode + 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 +} tu_fifo_copy_mode_t; + bool tu_fifo_config(tu_fifo_t *f, void* buffer, uint16_t depth, uint16_t item_size, bool overwritable) { if (depth > 0x8000) return false; // Maximum depth is 2^15 items - tu_fifo_lock(f); + _ff_lock(f->mutex_wr); + _ff_lock(f->mutex_rd); f->buffer = (uint8_t*) buffer; f->depth = depth; @@ -78,66 +82,236 @@ bool tu_fifo_config(tu_fifo_t *f, void* buffer, uint16_t depth, uint16_t item_si f->rd_idx = f->wr_idx = 0; - tu_fifo_unlock(f); + _ff_unlock(f->mutex_wr); + _ff_unlock(f->mutex_rd); return true; } // Static functions are intended to work on local variables - static inline uint16_t _ff_mod(uint16_t idx, uint16_t depth) { while ( idx >= depth) idx -= depth; return idx; } -// send one item to FIFO WITHOUT updating write pointer -static inline void _ff_push(tu_fifo_t* f, void const * data, uint16_t wRel) +// 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_synopsis.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) { - memcpy(f->buffer + (wRel * f->item_size), data, f->item_size); + volatile uint32_t * rx_fifo = (volatile uint32_t *) app_buf; + + // Reading full available 32 bit words from const app address + uint16_t full_words = len >> 2; + while(full_words--) + { + tu_unaligned_write32(ff_buf, *rx_fifo); + 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 = *rx_fifo; + memcpy(ff_buf, &tmp32, bytes_rem); + } +} + +// 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) +{ + volatile uint32_t * tx_fifo = (volatile uint32_t *) app_buf; + + // Pushing full available 32 bit words to const app address + uint16_t full_words = len >> 2; + while(full_words--) + { + *tx_fifo = tu_unaligned_read32(ff_buf); + ff_buf += 4; + } + + // Write the remaining 1-3 bytes into const app address + uint8_t const bytes_rem = len & 0x03; + if ( bytes_rem ) + { + uint32_t tmp32 = 0; + memcpy(&tmp32, ff_buf, bytes_rem); + + *tx_fifo = tmp32; + } +} + +// 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); } // send n items to FIFO WITHOUT updating write pointer -static void _ff_push_n(tu_fifo_t* f, void const * data, uint16_t n, uint16_t wRel) +static void _ff_push_n(tu_fifo_t* f, void const * app_buf, uint16_t n, uint16_t rel, tu_fifo_copy_mode_t copy_mode) { - if(wRel + n <= f->depth) // Linear mode only - { - memcpy(f->buffer + (wRel * f->item_size), data, n*f->item_size); - } - else // Wrap around - { - uint16_t nLin = f->depth - wRel; + uint16_t const nLin = f->depth - rel; + uint16_t const nWrap = n - nLin; - // Write data to linear part of buffer - memcpy(f->buffer + (wRel * f->item_size), data, nLin*f->item_size); + uint16_t nLin_bytes = nLin * f->item_size; + uint16_t nWrap_bytes = nWrap * f->item_size; - // Write data wrapped around - memcpy(f->buffer, ((uint8_t const*) data) + nLin*f->item_size, (n - nLin) * f->item_size); + // current buffer of fifo + uint8_t* ff_buf = f->buffer + (rel * f->item_size); + + switch (copy_mode) + { + case TU_FIFO_COPY_INC: + if(n <= nLin) + { + // 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, nLin_bytes); + + // Write data wrapped around + memcpy(f->buffer, ((uint8_t const*) app_buf) + nLin_bytes, nWrap_bytes); + } + break; + + case TU_FIFO_COPY_CST_FULL_WORDS: + // Intended for hardware buffers from which it can be read word by word only + if(n <= nLin) + { + // 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 = nLin_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 + volatile uint32_t * rx_fifo = (volatile uint32_t *) app_buf; + uint8_t rem = nLin_bytes & 0x03; + if (rem > 0) + { + uint8_t remrem = tu_min16(nWrap_bytes, 4-rem); + nWrap_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 (nWrap_bytes > 0) _ff_push_const_addr(ff_buf, app_buf, nWrap_bytes); + } + break; } } // get one item from FIFO WITHOUT updating read pointer -static inline void _ff_pull(tu_fifo_t* f, void * p_buffer, uint16_t rRel) +static inline void _ff_pull(tu_fifo_t* f, void * app_buf, uint16_t rel) { - memcpy(p_buffer, f->buffer + (rRel * f->item_size), f->item_size); + memcpy(app_buf, f->buffer + (rel * f->item_size), f->item_size); } // get n items from FIFO WITHOUT updating read pointer -static void _ff_pull_n(tu_fifo_t* f, void * p_buffer, uint16_t n, uint16_t rRel) +static void _ff_pull_n(tu_fifo_t* f, void* app_buf, uint16_t n, uint16_t rel, tu_fifo_copy_mode_t copy_mode) { - if(rRel + n <= f->depth) // Linear mode only - { - memcpy(p_buffer, f->buffer + (rRel * f->item_size), n*f->item_size); - } - else // Wrap around - { - uint16_t nLin = f->depth - rRel; + uint16_t const nLin = f->depth - rel; + uint16_t const nWrap = n - nLin; // only used if wrapped - // Read data from linear part of buffer - memcpy(p_buffer, f->buffer + (rRel * f->item_size), nLin*f->item_size); + uint16_t nLin_bytes = nLin * f->item_size; + uint16_t nWrap_bytes = nWrap * f->item_size; - // Read data wrapped part - memcpy((uint8_t*)p_buffer + nLin*f->item_size, f->buffer, (n - nLin) * f->item_size); + // current buffer of fifo + uint8_t* ff_buf = f->buffer + (rel * f->item_size); + + switch (copy_mode) + { + case TU_FIFO_COPY_INC: + if ( n <= nLin ) + { + // 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, nLin_bytes); + + // Read data wrapped part + memcpy((uint8_t*) app_buf + nLin_bytes, f->buffer, nWrap_bytes); + } + break; + + case TU_FIFO_COPY_CST_FULL_WORDS: + if ( n <= nLin ) + { + // 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 nLin_4n_bytes = nLin_bytes & 0xFFFC; + _ff_pull_const_addr(app_buf, ff_buf, nLin_4n_bytes); + ff_buf += nLin_4n_bytes; + + // There could be odd 1-3 bytes before the wrap-around boundary + volatile uint32_t * tx_fifo = (volatile uint32_t *) app_buf; + uint8_t rem = nLin_bytes & 0x03; + if (rem > 0) + { + uint8_t remrem = tu_min16(nWrap_bytes, 4-rem); + nWrap_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++; + + *tx_fifo = tmp32; + } + else + { + ff_buf = f->buffer; // wrap around to beginning + } + + // Read data wrapped part + if (nWrap_bytes > 0) _ff_pull_const_addr(app_buf, ff_buf, nWrap_bytes); + } + break; + + default: break; } } @@ -179,7 +353,7 @@ static uint16_t get_relative_pointer(tu_fifo_t* f, uint16_t p, uint16_t offset) return _ff_mod(advance_pointer(f, p, offset), f->depth); } -// Works on local copies of w and r +// Works on local copies of w and r - return only the difference and as such can be used to determine an overflow static inline uint16_t _tu_fifo_count(tu_fifo_t* f, uint16_t wAbs, uint16_t rAbs) { uint16_t cnt = wAbs-rAbs; @@ -246,7 +420,7 @@ static bool _tu_fifo_peek_at(tu_fifo_t* f, uint16_t offset, void * p_buffer, uin // 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_at_n(tu_fifo_t* f, uint16_t offset, void * p_buffer, uint16_t n, uint16_t wAbs, uint16_t rAbs) +static uint16_t _tu_fifo_peek_at_n(tu_fifo_t* f, uint16_t offset, void * p_buffer, uint16_t n, uint16_t wAbs, uint16_t rAbs, tu_fifo_copy_mode_t copy_mode) { uint16_t cnt = _tu_fifo_count(f, wAbs, rAbs); @@ -263,15 +437,12 @@ static uint16_t _tu_fifo_peek_at_n(tu_fifo_t* f, uint16_t offset, void * p_buffe // Check if we can read something at and after offset - if too less is available we read what remains cnt -= offset; - if (cnt < n) { - if (cnt == 0) return 0; - n = cnt; - } + if (cnt < n) n = cnt; uint16_t rRel = get_relative_pointer(f, rAbs, offset); // Peek data - _ff_pull_n(f, p_buffer, n, rRel); + _ff_pull_n(f, p_buffer, n, rRel, copy_mode); return n; } @@ -282,12 +453,67 @@ static inline uint16_t _tu_fifo_remaining(tu_fifo_t* f, uint16_t wAbs, uint16_t return f->depth - _tu_fifo_count(f, wAbs, rAbs); } +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 w = f->wr_idx, r = f->rd_idx; + uint8_t const* buf8 = (uint8_t const*) data; + + if (!f->overwritable) + { + // Not overwritable limit up to full + n = tu_min16(n, _tu_fifo_remaining(f, w, r)); + } + else if (n >= f->depth) + { + // Only copy last part + buf8 = buf8 + (n - f->depth) * f->item_size; + n = f->depth; + + // We start writing at the read pointer's position since we fill the complete + // buffer and we do not want to modify the read pointer within a write function! + // This would end up in a race condition with read functions! + w = r; + } + + uint16_t wRel = get_relative_pointer(f, w, 0); + + // Write data + _ff_push_n(f, buf8, n, wRel, copy_mode); + + // Advance pointer + f->wr_idx = advance_pointer(f, w, n); + + _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 + n = _tu_fifo_peek_at_n(f, 0, buffer, n, f->wr_idx, f->rd_idx, copy_mode); // f->rd_idx might get modified in case of an overflow so we can not use a local variable + + // Advance read pointer + f->rd_idx = advance_pointer(f, f->rd_idx, n); + + _ff_unlock(f->mutex_rd); + return n; +} + /******************************************************************************/ /*! @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. + 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 @@ -297,7 +523,7 @@ static inline uint16_t _tu_fifo_remaining(tu_fifo_t* f, uint16_t wAbs, uint16_t /******************************************************************************/ uint16_t tu_fifo_count(tu_fifo_t* f) { - return _tu_fifo_count(f, f->wr_idx, f->rd_idx); + return tu_min16(_tu_fifo_count(f, f->wr_idx, f->rd_idx), f->depth); } /******************************************************************************/ @@ -361,7 +587,7 @@ uint16_t tu_fifo_remaining(tu_fifo_t* f) 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 you pointer states are messed up. This can only happen if you + 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 @@ -383,9 +609,9 @@ bool tu_fifo_overflowed(tu_fifo_t* f) // Only use in case tu_fifo_overflow() returned true! void tu_fifo_correct_read_pointer(tu_fifo_t* f) { - tu_fifo_lock(f); + _ff_lock(f->mutex_rd); _tu_fifo_correct_read_pointer(f, f->wr_idx); - tu_fifo_unlock(f); + _ff_unlock(f->mutex_rd); } /******************************************************************************/ @@ -406,7 +632,7 @@ void tu_fifo_correct_read_pointer(tu_fifo_t* f) /******************************************************************************/ bool tu_fifo_read(tu_fifo_t* f, void * buffer) { - tu_fifo_lock(f); // TODO: Here we may distinguish for read and write pointer mutexes! + _ff_lock(f->mutex_rd); // Peek the data bool ret = _tu_fifo_peek_at(f, 0, buffer, f->wr_idx, f->rd_idx); // f->rd_idx might get modified in case of an overflow so we can not use a local variable @@ -414,7 +640,7 @@ bool tu_fifo_read(tu_fifo_t* f, void * buffer) // Advance pointer f->rd_idx = advance_pointer(f, f->rd_idx, ret); - tu_fifo_unlock(f); + _ff_unlock(f->mutex_rd); return ret; } @@ -428,24 +654,20 @@ bool tu_fifo_read(tu_fifo_t* f, void * buffer) 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) { - tu_fifo_lock(f); // TODO: Here we may distinguish for read and write pointer mutexes! + return _tu_fifo_read_n(f, buffer, n, TU_FIFO_COPY_INC); +} - // Peek the data - count = _tu_fifo_peek_at_n(f, 0, buffer, count, f->wr_idx, f->rd_idx); // f->rd_idx might get modified in case of an overflow so we can not use a local variable - - // Advance read pointer - f->rd_idx = advance_pointer(f, f->rd_idx, count); - - tu_fifo_unlock(f); - return count; +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); } /******************************************************************************/ @@ -465,9 +687,9 @@ uint16_t tu_fifo_read_n(tu_fifo_t* f, void * buffer, uint16_t count) /******************************************************************************/ bool tu_fifo_peek_at(tu_fifo_t* f, uint16_t offset, void * p_buffer) { - tu_fifo_lock(f); // TODO: Here we may distinguish for read and write pointer mutexes! + _ff_lock(f->mutex_rd); bool ret = _tu_fifo_peek_at(f, offset, p_buffer, f->wr_idx, f->rd_idx); - tu_fifo_unlock(f); + _ff_unlock(f->mutex_rd); return ret; } @@ -490,9 +712,9 @@ bool tu_fifo_peek_at(tu_fifo_t* f, uint16_t offset, void * p_buffer) /******************************************************************************/ uint16_t tu_fifo_peek_at_n(tu_fifo_t* f, uint16_t offset, void * p_buffer, uint16_t n) { - tu_fifo_lock(f); // TODO: Here we may distinguish for read and write pointer mutexes! - bool ret = _tu_fifo_peek_at_n(f, offset, p_buffer, n, f->wr_idx, f->rd_idx); - tu_fifo_unlock(f); + _ff_lock(f->mutex_rd); + bool ret = _tu_fifo_peek_at_n(f, offset, p_buffer, n, f->wr_idx, f->rd_idx, TU_FIFO_COPY_INC); + _ff_unlock(f->mutex_rd); return ret; } @@ -514,7 +736,7 @@ uint16_t tu_fifo_peek_at_n(tu_fifo_t* f, uint16_t offset, void * p_buffer, uint1 /******************************************************************************/ bool tu_fifo_write(tu_fifo_t* f, const void * data) { - tu_fifo_lock(f); + _ff_lock(f->mutex_wr); uint16_t w = f->wr_idx; @@ -528,7 +750,7 @@ bool tu_fifo_write(tu_fifo_t* f, const void * data) // Advance pointer f->wr_idx = advance_pointer(f, w, 1); - tu_fifo_unlock(f); + _ff_unlock(f->mutex_wr); return true; } @@ -547,43 +769,29 @@ bool tu_fifo_write(tu_fifo_t* f, const void * data) @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); +/******************************************************************************/ +/*! + @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. - uint16_t w = f->wr_idx, r = f->rd_idx; - uint8_t const* buf8 = (uint8_t const*) data; - - if (!f->overwritable) - { - // Not overwritable limit up to full - count = tu_min16(count, _tu_fifo_remaining(f, w, r)); - } - else if (count > f->depth) - { - // Only copy last part - buf8 = buf8 + (count - f->depth) * f->item_size; - count = f->depth; - - // We start writing at the read pointer's position since we fill the complete - // buffer and we do not want to modify the read pointer within a write function! - // This would end up in a race condition with read functions! - f->wr_idx = r; - } - - uint16_t wRel = get_relative_pointer(f, w, 0); - - // Write data - _ff_push_n(f, buf8, count, wRel); - - // Advance pointer - f->wr_idx = advance_pointer(f, w, count); - - tu_fifo_unlock(f); - - return count; + @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); } /******************************************************************************/ @@ -596,12 +804,15 @@ uint16_t tu_fifo_write_n(tu_fifo_t* f, const void * data, uint16_t count) /******************************************************************************/ bool tu_fifo_clear(tu_fifo_t *f) { - tu_fifo_lock(f); + _ff_lock(f->mutex_wr); + _ff_lock(f->mutex_rd); + f->rd_idx = f->wr_idx = 0; f->max_pointer_idx = 2*f->depth-1; f->non_used_index_space = UINT16_MAX - f->max_pointer_idx; - tu_fifo_unlock(f); + _ff_unlock(f->mutex_wr); + _ff_unlock(f->mutex_rd); return true; } @@ -613,15 +824,17 @@ bool tu_fifo_clear(tu_fifo_t *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) { - tu_fifo_lock(f); + _ff_lock(f->mutex_wr); + _ff_lock(f->mutex_rd); f->overwritable = overwritable; - tu_fifo_unlock(f); + _ff_unlock(f->mutex_wr); + _ff_unlock(f->mutex_rd); return true; } @@ -667,3 +880,146 @@ void tu_fifo_advance_read_pointer(tu_fifo_t *f, uint16_t n) { f->rd_idx = advance_pointer(f, f->rd_idx, n); } + +/******************************************************************************/ +/*! + @brief Get linear 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 returned length is limited to the number + of ITEMS n which the user wants to write into the buffer. + The write pointer does NOT get advanced, use tu_fifo_advance_read_pointer() to + do so! If the length returned is less than n i.e. lenwr_idx, r = f->rd_idx; + + uint16_t cnt = _tu_fifo_count(f, w, r); + + // Check overflow and correct if required + if (cnt > f->depth) + { + _ff_lock(f->mutex_rd); + _tu_fifo_correct_read_pointer(f, w); + _ff_unlock(f->mutex_rd); + r = f->rd_idx; + cnt = f->depth; + } + + // Skip beginning of buffer + if (cnt == 0 || offset >= cnt) return 0; + + // Check if we can read something at and after offset - if too less is available we read what remains + cnt -= offset; + if (cnt < n) n = cnt; + + // Get relative pointers + w = get_relative_pointer(f, w, 0); + r = get_relative_pointer(f, r, offset); + + // Check if there is a wrap around necessary + uint16_t len; + + if (w > r) { + len = w - r; + } + else + { + len = f->depth - r; // Also the case if FIFO was full + } + + // Limit to required length + len = tu_min16(n, len); + + // Copy pointer to buffer to start reading from + *ptr = &f->buffer[r]; + + return len; +} + +/******************************************************************************/ +/*! + @brief Get linear write info + + Returns the length and pointer from which bytes can be written into buffer array in a linear manner. + This is of major interest for DMA transmissions not using circular mode. If returned length is zero the + corresponding pointer is invalid. The returned length is limited to the number of BYTES n which the user + wants to write into the buffer. + The write pointer does NOT get advanced, use tu_fifo_advance_write_pointer() to do so! If the length + returned is less than n i.e. lenwr_idx, r = f->rd_idx; + uint16_t free = _tu_fifo_remaining(f, w, r); + + if (!f->overwritable) + { + // Not overwritable limit up to full + n = tu_min16(n, free); + } + else if (n >= f->depth) + { + // If overwrite is allowed it must be less than or equal to 2 x buffer length, otherwise the overflow can not be resolved by the read functions + TU_VERIFY(n <= 2*f->depth); + + n = f->depth; + // We start writing at the read pointer's position since we fill the complete + // buffer and we do not want to modify the read pointer within a write function! + // This would end up in a race condition with read functions! + w = r; + } + + // Check if there is room to write to + if (free == 0 || offset >= free) return 0; + + // Get relative pointers + w = get_relative_pointer(f, w, offset); + r = get_relative_pointer(f, r, 0); + uint16_t len; + + if (w < r) + { + len = r-w; + } + else + { + len = f->depth - w; + } + + // Limit to required length + len = tu_min16(n, len); + + // Copy pointer to buffer to start reading from + *ptr = &f->buffer[w]; + + return len; +} diff --git a/src/common/tusb_fifo.h b/src/common/tusb_fifo.h index 294378496..b2d0b5be9 100644 --- a/src/common/tusb_fifo.h +++ b/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 @@ -52,10 +53,10 @@ extern "C" { #endif #if CFG_FIFO_MUTEX +#include "osal/osal.h" #define tu_fifo_mutex_t osal_mutex_t #endif - /** \struct tu_fifo_t * \brief Simple Circular FIFO */ @@ -66,14 +67,15 @@ typedef struct uint16_t item_size ; ///< size of each item bool overwritable ; - uint16_t max_pointer_idx ; ///< maximum absolute pointer index uint16_t non_used_index_space ; ///< required for non-power-of-two buffer length + uint16_t max_pointer_idx ; ///< maximum absolute pointer index volatile uint16_t wr_idx ; ///< write pointer volatile uint16_t rd_idx ; ///< read pointer #if CFG_FIFO_MUTEX - tu_fifo_mutex_t mutex; + tu_fifo_mutex_t mutex_wr; + tu_fifo_mutex_t mutex_rd; #endif } tu_fifo_t; @@ -85,29 +87,33 @@ typedef struct .item_size = sizeof(_type), \ .overwritable = _overwritable, \ .max_pointer_idx = 2*(_depth)-1, \ - .non_used_index_space = UINT16_MAX - (2*(_depth)-1) \ + .non_used_index_space = UINT16_MAX - (2*(_depth)-1), \ } #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) +static inline void tu_fifo_config_mutex(tu_fifo_t *f, tu_fifo_mutex_t write_mutex_hdl, tu_fifo_mutex_t read_mutex_hdl) { - f->mutex = mutex_hdl; + f->mutex_wr = write_mutex_hdl; + f->mutex_rd = read_mutex_hdl; } #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); +uint16_t tu_fifo_write_n (tu_fifo_t* f, void const * p_data, uint16_t n); +uint16_t tu_fifo_write_n_const_addr_full_words (tu_fifo_t* f, const void * data, uint16_t n); 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); +uint16_t tu_fifo_read_n (tu_fifo_t* f, void * p_buffer, uint16_t n); +uint16_t tu_fifo_read_n_const_addr_full_words (tu_fifo_t* f, void * buffer, uint16_t n); bool tu_fifo_peek_at (tu_fifo_t* f, uint16_t pos, void * p_buffer); uint16_t tu_fifo_peek_at_n (tu_fifo_t* f, uint16_t pos, void * p_buffer, uint16_t n); @@ -116,7 +122,7 @@ uint16_t tu_fifo_count (tu_fifo_t* f); bool tu_fifo_empty (tu_fifo_t* f); bool tu_fifo_full (tu_fifo_t* f); uint16_t tu_fifo_remaining (tu_fifo_t* f); -bool tu_fifo_overflowed (tu_fifo_t* f); +bool tu_fifo_overflowed (tu_fifo_t* f); void tu_fifo_correct_read_pointer (tu_fifo_t* f); // Pointer modifications intended to be used in combinations with DMAs. @@ -124,6 +130,13 @@ void tu_fifo_correct_read_pointer (tu_fifo_t* f); 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); +// 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 +// This functions deliver a pointer to start reading/writing from/to and a valid linear length along which no wrap occurs. +// In case not all of your data is available within one read/write, update the read/write pointer by +// tu_fifo_advance_read_pointer()/tu_fifo_advance_write_pointer and conduct a second read/write operation +uint16_t tu_fifo_get_linear_read_info (tu_fifo_t *f, uint16_t offset, void **ptr, uint16_t n); +uint16_t tu_fifo_get_linear_write_info (tu_fifo_t *f, uint16_t offset, void **ptr, uint16_t n); + static inline bool tu_fifo_peek(tu_fifo_t* f, void * p_buffer) { return tu_fifo_peek_at(f, 0, p_buffer); diff --git a/src/device/dcd.h b/src/device/dcd.h index b7e5a8da0..1e5b3ff1e 100644 --- a/src/device/dcd.h +++ b/src/device/dcd.h @@ -32,6 +32,7 @@ #define _TUSB_DCD_H_ #include "common/tusb_common.h" +#include "common/tusb_fifo.h" #ifdef __cplusplus extern "C" { @@ -124,20 +125,24 @@ void dcd_disconnect(uint8_t rhport) TU_ATTR_WEAK; void dcd_edpt0_status_complete(uint8_t rhport, tusb_control_request_t const * request) TU_ATTR_WEAK; // 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 * p_endpoint_desc); // 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; +void dcd_edpt_close (uint8_t rhport, uint8_t ep_addr) TU_ATTR_WEAK; // 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); + +// 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) TU_ATTR_WEAK; // Stall endpoint -void dcd_edpt_stall (uint8_t rhport, uint8_t ep_addr); +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); +void dcd_edpt_clear_stall (uint8_t rhport, uint8_t ep_addr); //--------------------------------------------------------------------+ // Event API (implemented by stack) diff --git a/src/device/usbd.c b/src/device/usbd.c index ca5a482fc..8a3d8ec2f 100644 --- a/src/device/usbd.c +++ b/src/device/usbd.c @@ -29,9 +29,9 @@ #if TUSB_OPT_DEVICE_ENABLED #include "tusb.h" -#include "usbd.h" +#include "device/usbd.h" #include "device/usbd_pvt.h" -#include "dcd.h" +#include "device/dcd.h" #ifndef CFG_TUD_TASK_QUEUE_SZ #define CFG_TUD_TASK_QUEUE_SZ 16 @@ -1241,6 +1241,39 @@ bool usbd_edpt_xfer(uint8_t rhport, uint8_t ep_addr, uint8_t * buffer, uint16_t } } +// 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_iso_xfer(uint8_t rhport, uint8_t ep_addr, tu_fifo_t * ff, uint16_t total_bytes) +{ + uint8_t const epnum = tu_edpt_number(ep_addr); + uint8_t const dir = tu_edpt_dir(ep_addr); + + TU_LOG2(" 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; + + if (dcd_edpt_xfer_fifo(rhport, ep_addr, ff, total_bytes)) + { + TU_LOG2("OK\r\n"); + return true; + }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].claimed = 0; + TU_LOG2("failed\r\n"); + TU_BREAKPOINT(); + return false; + } +} + bool usbd_edpt_busy(uint8_t rhport, uint8_t ep_addr) { (void) rhport; diff --git a/src/device/usbd.h b/src/device/usbd.h index 56615b081..7c50a6db0 100644 --- a/src/device/usbd.h +++ b/src/device/usbd.h @@ -353,6 +353,10 @@ TU_ATTR_WEAK bool tud_vendor_control_xfer_cb(uint8_t rhport, uint8_t stage, tusb #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 @@ -389,7 +393,7 @@ TU_ATTR_WEAK bool tud_vendor_control_xfer_cb(uint8_t rhport, uint8_t stage, tusb // 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_DESC_LEN (TUD_AUDIO_DESC_IAD_LEN\ +#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\ @@ -403,9 +407,9 @@ TU_ATTR_WEAK bool tud_vendor_control_xfer_cb(uint8_t rhport, uint8_t stage, tusb + TUD_AUDIO_DESC_STD_AS_ISO_EP_LEN\ + TUD_AUDIO_DESC_CS_AS_ISO_EP_LEN) -#define TUD_AUDIO_MIC_DESC_N_AS_INT 1 // Number of AS interfaces +#define TUD_AUDIO_MIC_ONE_CH_DESC_N_AS_INT 1 // Number of AS interfaces -#define TUD_AUDIO_MIC_DESCRIPTOR(_itfnum, _stridx, _nBytesPerSample, _nBitsUsedPerSample, _epin, _epsize) \ +#define TUD_AUDIO_MIC_ONE_CH_DESCRIPTOR(_itfnum, _stridx, _nBytesPerSample, _nBitsUsedPerSample, _epin, _epsize) \ /* Standard Interface Association Descriptor (IAD) */\ TUD_AUDIO_DESC_IAD(/*_firstitfs*/ _itfnum, /*_nitfs*/ 0x02, /*_stridx*/ 0x00),\ /* Standard AC Interface Descriptor(4.7.1) */\ @@ -435,6 +439,55 @@ TU_ATTR_WEAK bool tud_vendor_control_xfer_cb(uint8_t rhport, uint8_t stage, tusb /* 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(/*_firstitfs*/ _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*/ (TUSB_XFER_ISOCHRONOUS | TUSB_ISO_EP_ATT_ASYNCHRONOUS | TUSB_ISO_EP_ATT_DATA), /*_maxEPsize*/ _epsize, /*_interval*/ (CFG_TUSB_RHPORT0_MODE & OPT_MODE_HIGH_SPEED) ? 0x04 : 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 diff --git a/src/device/usbd_pvt.h b/src/device/usbd_pvt.h index 212c3a202..412a97a5d 100644 --- a/src/device/usbd_pvt.h +++ b/src/device/usbd_pvt.h @@ -72,6 +72,9 @@ 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_iso_xfer(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); diff --git a/src/osal/osal_pico.h b/src/osal/osal_pico.h index c277af271..bae1217eb 100644 --- a/src/osal/osal_pico.h +++ b/src/osal/osal_pico.h @@ -143,7 +143,7 @@ static inline bool osal_queue_receive(osal_queue_t qhdl, void* data) // TODO: revisit... docs say that mutexes are never used from IRQ context, // however osal_queue_recieve may be. therefore my assumption is that // the fifo mutex is not populated for queues used from an IRQ context - assert(!qhdl->ff.mutex); + //assert(!qhdl->ff.mutex); _osal_q_lock(qhdl); bool success = tu_fifo_read(&qhdl->ff, data); @@ -157,7 +157,7 @@ static inline bool osal_queue_send(osal_queue_t qhdl, void const * data, bool in // TODO: revisit... docs say that mutexes are never used from IRQ context, // however osal_queue_recieve may be. therefore my assumption is that // the fifo mutex is not populated for queues used from an IRQ context - assert(!qhdl->ff.mutex); + //assert(!qhdl->ff.mutex); _osal_q_lock(qhdl); bool success = tu_fifo_write(&qhdl->ff, data); diff --git a/src/portable/espressif/esp32s2/dcd_esp32s2.c b/src/portable/espressif/esp32s2/dcd_esp32s2.c index 703841759..edbc872fe 100644 --- a/src/portable/espressif/esp32s2/dcd_esp32s2.c +++ b/src/portable/espressif/esp32s2/dcd_esp32s2.c @@ -27,6 +27,7 @@ */ #include "tusb_option.h" +#include "common/tusb_fifo.h" #if CFG_TUSB_MCU == OPT_MCU_ESP32S2 && TUSB_OPT_DEVICE_ENABLED @@ -59,6 +60,7 @@ typedef struct { uint8_t *buffer; + // tu_fifo_t * ff; // TODO support dcd_edpt_xfer_fifo API uint16_t total_len; uint16_t queued_len; uint16_t max_size; @@ -319,6 +321,7 @@ bool dcd_edpt_xfer(uint8_t rhport, uint8_t ep_addr, uint8_t *buffer, uint16_t to xfer_ctl_t * xfer = XFER_CTL_BASE(epnum, dir); xfer->buffer = buffer; + // xfer->ff = NULL; // TODO support dcd_edpt_xfer_fifo API xfer->total_len = total_bytes; xfer->queued_len = 0; xfer->short_packet = false; @@ -354,6 +357,56 @@ bool dcd_edpt_xfer(uint8_t rhport, uint8_t ep_addr, uint8_t *buffer, uint16_t to return true; } +#if 0 // TODO support dcd_edpt_xfer_fifo API +bool dcd_edpt_xfer_fifo (uint8_t rhport, uint8_t ep_addr, tu_fifo_t * ff, uint16_t total_bytes) +{ + (void)rhport; + + // USB buffers always work in bytes so to avoid unnecessary divisions we demand item_size = 1 + TU_ASSERT(ff->item_size == 1); + + uint8_t const epnum = tu_edpt_number(ep_addr); + uint8_t const dir = tu_edpt_dir(ep_addr); + + xfer_ctl_t * xfer = XFER_CTL_BASE(epnum, dir); + xfer->buffer = NULL; + xfer->ff = ff; + xfer->total_len = total_bytes; + 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; +} +#endif + void dcd_edpt_stall(uint8_t rhport, uint8_t ep_addr) { (void)rhport; @@ -462,35 +515,46 @@ static void receive_packet(xfer_ctl_t *xfer, /* usb_out_endpoint_t * out_ep, */ 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; - } + // Common buffer read +#if 0 // TODO support dcd_edpt_xfer_fifo API + if (xfer->ff) + { + // Ring buffer + tu_fifo_write_n_const_addr_full_words(xfer->ff, (const void *) rx_fifo, to_recv_size); } + else +#endif + { + uint8_t to_recv_rem = to_recv_size % 4; + uint16_t to_recv_size_aligned = to_recv_size - to_recv_rem; - // 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; + // Do not assume xfer buffer is aligned. + uint8_t *base = (xfer->buffer + xfer->queued_len); - last_32b_bound[0] = tmp & 0x000000FF; - if (to_recv_rem > 1) { - last_32b_bound[1] = (tmp & 0x0000FF00) >> 8; + // 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; + } } - if (to_recv_rem > 2) { - last_32b_bound[2] = (tmp & 0x00FF0000) >> 16; + + // 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; + } } } @@ -510,38 +574,48 @@ static void transmit_packet(xfer_ctl_t *xfer, volatile usb_in_endpoint_t *in_ep, 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); +#if 0 // TODO support dcd_edpt_xfer_fifo API + if (xfer->ff) + { + tu_fifo_read_n_const_addr_full_words(xfer->ff, (void *) tx_fifo, to_xfer_size); + } + else +#endif + { + 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); + } - // 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) diff --git a/src/portable/microchip/samg/dcd_samg.c b/src/portable/microchip/samg/dcd_samg.c index 2f9f1097f..62fab8d17 100644 --- a/src/portable/microchip/samg/dcd_samg.c +++ b/src/portable/microchip/samg/dcd_samg.c @@ -25,6 +25,7 @@ */ #include "tusb_option.h" +#include "common/tusb_fifo.h" #if CFG_TUSB_MCU == OPT_MCU_SAMG @@ -43,6 +44,7 @@ typedef struct { uint8_t* buffer; + // tu_fifo_t* ff; // TODO support dcd_edpt_xfer_fifo API uint16_t total_len; volatile uint16_t actual_len; uint16_t epsize; @@ -59,6 +61,7 @@ void xfer_epsize_set(xfer_desc_t* xfer, uint16_t epsize) void xfer_begin(xfer_desc_t* xfer, uint8_t * buffer, uint16_t total_bytes) { xfer->buffer = buffer; + // xfer->ff = NULL; // TODO support dcd_edpt_xfer_fifo API xfer->total_len = total_bytes; xfer->actual_len = 0; } @@ -66,6 +69,7 @@ void xfer_begin(xfer_desc_t* xfer, uint8_t * buffer, uint16_t total_bytes) void xfer_end(xfer_desc_t* xfer) { xfer->buffer = NULL; + // xfer->ff = NULL; // TODO support dcd_edpt_xfer_fifo API xfer->total_len = 0; xfer->actual_len = 0; } @@ -293,6 +297,14 @@ bool dcd_edpt_xfer (uint8_t rhport, uint8_t ep_addr, uint8_t * buffer, uint16_t return true; } +#if 0 // TODO support dcd_edpt_xfer_fifo API +bool dcd_edpt_xfer_fifo (uint8_t rhport, uint8_t ep_addr, tu_fifo_t * ff, uint16_t total_bytes) +{ + (void) rhport; + return true; +} +#endif + // Stall endpoint void dcd_edpt_stall (uint8_t rhport, uint8_t ep_addr) { @@ -402,7 +414,16 @@ void dcd_int_handler(uint8_t rhport) if (xact_len) { // write to EP fifo - xact_ep_write(epnum, xfer->buffer, xact_len); +#if 0 // TODO support dcd_edpt_xfer_fifo + if (xfer->ff) + { + tu_fifo_read_n_const_addr_full_words(xfer->ff, (void *) &UDP->UDP_FDR[epnum], xact_len); + } + else +#endif + { + xact_ep_write(epnum, xfer->buffer, xact_len); + } // TX ready for transfer csr_set(epnum, UDP_CSR_TXPKTRDY_Msk); @@ -428,7 +449,17 @@ void dcd_int_handler(uint8_t rhport) 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); +#if 0 // TODO support dcd_edpt_xfer_fifo API + if (xfer->ff) + { + tu_fifo_write_n_const_addr_full_words(xfer->ff, (const void *) &UDP->UDP_FDR[epnum], xact_len); + } + else +#endif + { + xact_ep_read(epnum, xfer->buffer, xact_len); + } + xfer_packet_done(xfer); if ( 0 == xfer_packet_len(xfer) ) diff --git a/src/portable/nuvoton/nuc120/dcd_nuc120.c b/src/portable/nuvoton/nuc120/dcd_nuc120.c index dc48e54cc..5dc3bca21 100644 --- a/src/portable/nuvoton/nuc120/dcd_nuc120.c +++ b/src/portable/nuvoton/nuc120/dcd_nuc120.c @@ -34,6 +34,7 @@ */ #include "tusb_option.h" +#include "common/tusb_fifo.h" #if TUSB_OPT_DEVICE_ENABLED && (CFG_TUSB_MCU == OPT_MCU_NUC120) @@ -76,6 +77,7 @@ static bool active_ep0_xfer; static struct xfer_ctl_t { uint8_t *data_ptr; /* data_ptr tracks where to next copy data to (for OUT) or from (for IN) */ + // tu_fifo_t * ff; /* pointer to FIFO required for dcd_edpt_xfer_fifo() */ // TODO support dcd_edpt_xfer_fifo API 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 */ @@ -142,7 +144,17 @@ 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); +#if 0 // TODO support dcd_edpt_xfer_fifo API + if (xfer->ff) + { + tu_fifo_read_n(xfer->ff, (void *) (USBD_BUF_BASE + ep->BUFSEG), bytes_now); + } + else +#endif + { + memcpy((uint8_t *)(USBD_BUF_BASE + ep->BUFSEG), xfer->data_ptr, bytes_now); + } + ep->MXPLD = bytes_now; } @@ -267,6 +279,7 @@ bool dcd_edpt_xfer(uint8_t rhport, uint8_t ep_addr, uint8_t *buffer, uint16_t to /* store away the information we'll needing now and later */ xfer->data_ptr = buffer; + // xfer->ff = NULL; // TODO support dcd_edpt_xfer_fifo API xfer->in_remaining_bytes = total_bytes; xfer->total_bytes = total_bytes; @@ -286,6 +299,36 @@ bool dcd_edpt_xfer(uint8_t rhport, uint8_t ep_addr, uint8_t *buffer, uint16_t to return true; } +#if 0 // TODO support dcd_edpt_xfer_fifo API +bool dcd_edpt_xfer_fifo (uint8_t rhport, uint8_t ep_addr, tu_fifo_t * ff, 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 = NULL; // Indicates a FIFO shall be used + xfer->ff = ff; + xfer->in_remaining_bytes = total_bytes; + xfer->total_bytes = total_bytes; + + 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; +} +#endif + void dcd_edpt_stall(uint8_t rhport, uint8_t ep_addr) { (void) rhport; @@ -389,9 +432,19 @@ void dcd_int_handler(uint8_t rhport) 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); +#if 0 // // TODO support dcd_edpt_xfer_fifo API + if (xfer->ff) + { + tu_fifo_write_n(xfer->ff, (const void *) (USBD_BUF_BASE + ep->BUFSEG), available_bytes); + } + else +#endif + { + memcpy(xfer->data_ptr, (uint8_t *)(USBD_BUF_BASE + ep->BUFSEG), available_bytes); + xfer->data_ptr += 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) ) @@ -403,6 +456,7 @@ void dcd_int_handler(uint8_t rhport) { /* 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 */ diff --git a/src/portable/nuvoton/nuc121/dcd_nuc121.c b/src/portable/nuvoton/nuc121/dcd_nuc121.c index c9ead6de0..d50e82846 100644 --- a/src/portable/nuvoton/nuc121/dcd_nuc121.c +++ b/src/portable/nuvoton/nuc121/dcd_nuc121.c @@ -34,6 +34,7 @@ */ #include "tusb_option.h" +#include "common/tusb_fifo.h" #if TUSB_OPT_DEVICE_ENABLED && ( (CFG_TUSB_MCU == OPT_MCU_NUC121) || (CFG_TUSB_MCU == OPT_MCU_NUC126) ) @@ -78,6 +79,7 @@ static bool active_ep0_xfer; static struct xfer_ctl_t { uint8_t *data_ptr; /* data_ptr tracks where to next copy data to (for OUT) or from (for IN) */ + // tu_fifo_t * ff; // TODO support dcd_edpt_xfer_fifo API 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 */ @@ -144,7 +146,17 @@ 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); +#if 0 // TODO support dcd_edpt_xfer_fifo API + if (xfer->ff) + { + tu_fifo_read_n(xfer->ff, (void *) (USBD_BUF_BASE + ep->BUFSEG), bytes_now); + } + else +#endif + { + memcpy((uint8_t *)(USBD_BUF_BASE + ep->BUFSEG), xfer->data_ptr, bytes_now); + } + ep->MXPLD = bytes_now; } @@ -273,6 +285,7 @@ bool dcd_edpt_xfer(uint8_t rhport, uint8_t ep_addr, uint8_t *buffer, uint16_t to /* store away the information we'll needing now and later */ xfer->data_ptr = buffer; + // xfer->ff = NULL; // TODO support dcd_edpt_xfer_fifo API xfer->in_remaining_bytes = total_bytes; xfer->total_bytes = total_bytes; @@ -292,6 +305,36 @@ bool dcd_edpt_xfer(uint8_t rhport, uint8_t ep_addr, uint8_t *buffer, uint16_t to return true; } +#if 0 // TODO support dcd_edpt_xfer_fifo API +bool dcd_edpt_xfer_fifo (uint8_t rhport, uint8_t ep_addr, tu_fifo_t * ff, 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 = NULL; // Indicates a FIFO shall be used + xfer->ff = ff; + xfer->in_remaining_bytes = total_bytes; + xfer->total_bytes = total_bytes; + + 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; +} +#endif + void dcd_edpt_stall(uint8_t rhport, uint8_t ep_addr) { (void) rhport; @@ -400,9 +443,19 @@ void dcd_int_handler(uint8_t rhport) 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); +#if 0 // TODO support dcd_edpt_xfer_fifo API + if (xfer->ff) + { + tu_fifo_write_n(xfer->ff, (const void *) (USBD_BUF_BASE + ep->BUFSEG), available_bytes); + } + else +#endif + { + memcpy(xfer->data_ptr, (uint8_t *)(USBD_BUF_BASE + ep->BUFSEG), available_bytes); + xfer->data_ptr += 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) ) diff --git a/src/portable/nuvoton/nuc505/dcd_nuc505.c b/src/portable/nuvoton/nuc505/dcd_nuc505.c index a7961a687..b7bbb020b 100644 --- a/src/portable/nuvoton/nuc505/dcd_nuc505.c +++ b/src/portable/nuvoton/nuc505/dcd_nuc505.c @@ -34,6 +34,7 @@ */ #include "tusb_option.h" +#include "common/tusb_fifo.h" #if TUSB_OPT_DEVICE_ENABLED && (CFG_TUSB_MCU == OPT_MCU_NUC505) @@ -94,6 +95,7 @@ static uint32_t bufseg_addr; static struct xfer_ctl_t { uint8_t *data_ptr; /* data_ptr tracks where to next copy data to (for OUT) or from (for IN) */ + // tu_fifo_t* ff; // TODO support dcd_edpt_xfer_fifo API 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 */ @@ -163,8 +165,7 @@ static USBD_EP_T *ep_entry(uint8_t ep_addr, bool add) /* 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; + uint16_t const bytes_now = tu_min16(xfer->in_remaining_bytes, xfer->max_packet_size); /* precompute what amount of data will be left */ xfer->in_remaining_bytes -= bytes_now; @@ -180,20 +181,29 @@ static void dcd_userEP_in_xfer(struct xfer_ctl_t *xfer, USBD_EP_T *ep) } /* provided buffers are thankfully 32-bit aligned, allowing most data to be transfered as 32-bit */ - while (countdown > 3) +#if 0 // TODO support dcd_edpt_xfer_fifo API + if (xfer->ff) { - uint32_t u32; - memcpy(&u32, xfer->data_ptr, 4); - - ep->EPDAT = u32; - xfer->data_ptr += 4; countdown -= 4; + tu_fifo_read_n_const_addr_full_words(xfer->ff, (void *) (&ep->EPDAT_BYTE), bytes_now); + } + else +#endif + { + uint16_t countdown = bytes_now; + 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++; } - 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; + 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 */ @@ -385,6 +395,7 @@ bool dcd_edpt_xfer(uint8_t rhport, uint8_t ep_addr, uint8_t *buffer, uint16_t to /* store away the information we'll needing now and later */ xfer->data_ptr = buffer; + // xfer->ff = NULL; // TODO support dcd_edpt_xfer_fifo API xfer->in_remaining_bytes = total_bytes; xfer->total_bytes = total_bytes; @@ -402,6 +413,38 @@ bool dcd_edpt_xfer(uint8_t rhport, uint8_t ep_addr, uint8_t *buffer, uint16_t to return true; } +#if 0 // TODO support dcd_edpt_xfer_fifo API +bool dcd_edpt_xfer_fifo (uint8_t rhport, uint8_t ep_addr, tu_fifo_t * ff, uint16_t total_bytes) +{ + (void) rhport; + + TU_ASSERT(0x80 != ep_addr && 0x00 != ep_addr); // Must not be used for control stuff + + /* 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 = NULL; // Indicates a FIFO shall be used + xfer->ff = ff; + 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; +} +#endif + void dcd_edpt_stall(uint8_t rhport, uint8_t ep_addr) { (void) rhport; @@ -615,12 +658,25 @@ void dcd_int_handler(uint8_t rhport) #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; +#if 0 // TODO support dcd_edpt_xfer_fifo API + if (xfer->ff) + { + tu_fifo_write_n_const_addr_full_words(xfer->ff, (const void *) &ep->EPDAT_BYTE, tu_min16(available_bytes, xfer->total_bytes - xfer->out_bytes_so_far)); + } + else +#endif + { + 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 } diff --git a/src/portable/raspberrypi/rp2040/dcd_rp2040.c b/src/portable/raspberrypi/rp2040/dcd_rp2040.c index f844a0c65..7731078b5 100644 --- a/src/portable/raspberrypi/rp2040/dcd_rp2040.c +++ b/src/portable/raspberrypi/rp2040/dcd_rp2040.c @@ -35,7 +35,8 @@ #include "pico/fix/rp2040_usb_device_enumeration.h" #endif - +#include "osal/osal.h" +#include "common/tusb_fifo.h" #include "device/dcd.h" /*------------------------------------------------------------------*/ diff --git a/src/portable/st/stm32_fsdev/dcd_stm32_fsdev.c b/src/portable/st/stm32_fsdev/dcd_stm32_fsdev.c index 18f0bc8e1..b8b0fc104 100644 --- a/src/portable/st/stm32_fsdev/dcd_stm32_fsdev.c +++ b/src/portable/st/stm32_fsdev/dcd_stm32_fsdev.c @@ -102,6 +102,7 @@ */ #include "tusb_option.h" +#include "common/tusb_fifo.h" #if defined(STM32F102x6) || defined(STM32F102xB) || \ defined(STM32F103x6) || defined(STM32F103xB) || \ @@ -165,6 +166,7 @@ TU_VERIFY_STATIC(((DCD_STM32_BTABLE_BASE) % 8) == 0, "BTABLE base must be aligne typedef struct { uint8_t * buffer; + // tu_fifo_t * ff; // TODO support dcd_edpt_xfer_fifo API uint16_t total_len; uint16_t queued_len; uint16_t pma_ptr; @@ -197,6 +199,9 @@ 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); +//static bool dcd_write_packet_memory_ff(tu_fifo_t * ff, uint16_t dst, uint16_t wNBytes); +//static bool dcd_read_packet_memory_ff(tu_fifo_t * ff, uint16_t src, uint16_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) { @@ -476,8 +481,17 @@ static void dcd_ep_ctr_rx_handler(uint32_t wIstr) if (count != 0U) { - dcd_read_packet_memory(&(xfer->buffer[xfer->queued_len]), - *pcd_ep_rx_address_ptr(USB,EPindex), count); +#if 0 // TODO support dcd_edpt_xfer_fifo API + if (xfer->ff) + { + dcd_read_packet_memory_ff(xfer->ff, *pcd_ep_rx_address_ptr(USB,EPindex), count); + } + else +#endif + { + 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); } @@ -804,7 +818,17 @@ static void dcd_transmit_packet(xfer_ctl_t * xfer, uint16_t ep_ix) 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); + +#if 0 // TODO support dcd_edpt_xfer_fifo API + if (xfer->ff) + { + dcd_write_packet_memory_ff(xfer->ff, oldAddr, len); + } + else +#endif + { + 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); @@ -821,6 +845,7 @@ bool dcd_edpt_xfer (uint8_t rhport, uint8_t ep_addr, uint8_t * buffer, uint16_t xfer_ctl_t * xfer = xfer_ctl_ptr(epnum,dir); xfer->buffer = buffer; + // xfer->ff = NULL; // TODO support dcd_edpt_xfer_fifo API xfer->total_len = total_bytes; xfer->queued_len = 0; @@ -847,6 +872,39 @@ bool dcd_edpt_xfer (uint8_t rhport, uint8_t ep_addr, uint8_t * buffer, uint16_t return true; } +#if 0 // TODO support dcd_edpt_xfer_fifo API +bool dcd_edpt_xfer_fifo (uint8_t rhport, uint8_t ep_addr, tu_fifo_t * ff, 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 = NULL; + // xfer->ff = ff; // TODO support dcd_edpt_xfer_fifo API + xfer->total_len = total_bytes; + xfer->queued_len = 0; + + if ( dir == TUSB_DIR_OUT ) + { + 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; +} +#endif + void dcd_edpt_stall (uint8_t rhport, uint8_t ep_addr) { (void)rhport; @@ -920,8 +978,55 @@ static bool dcd_write_packet_memory(uint16_t dst, const void *__restrict src, si return true; } +#if 0 // TODO support dcd_edpt_xfer_fifo API /** - * @brief Copy a buffer from user memory area to packet memory area (PMA). + * @brief Copy from FIFO 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 + */ + +// THIS FUNCTION IS UNTESTED + +static bool dcd_write_packet_memory_ff(tu_fifo_t * ff, uint16_t dst, uint16_t wNBytes) +{ + // Since we copy from a ring buffer FIFO, a wrap might occur making it necessary to conduct two copies + // Check for first linear part + void * src; + uint16_t len = tu_fifo_get_linear_read_info(ff, 0, &src, wNBytes); // We want to read from the FIFO + TU_VERIFY(len && dcd_write_packet_memory(dst, src, len)); // and write it into the PMA + tu_fifo_advance_read_pointer(ff, len); + + // Check for wrapped part + if (len < wNBytes) + { + // Get remaining wrapped length + uint16_t len2 = tu_fifo_get_linear_read_info(ff, 0, &src, wNBytes - len); + TU_VERIFY(len2); + + // Update destination pointer + dst += len; + + // Since PMA is accessed 16-bit wise we need to handle the case when a 16 bit value was split + if (len % 2) // If len is uneven there is a byte left to copy + { + // Since PMA can accessed only 16 bit-wise we copy the last byte again + tu_fifo_backward_read_pointer(ff, 1); // Move one byte back and copy two bytes for the PMA + tu_fifo_read_n(ff, (void *) &pma[PMA_STRIDE*(dst>>1)], 2); // Since EP FIFOs must be of item size 1 this is safe to do + dst++; + len2--; + } + + TU_VERIFY(dcd_write_packet_memory(dst, src, len2)); + tu_fifo_advance_write_pointer(ff, len2); + } + + return true; +} +#endif + +/** + * @brief Copy a buffer from packet memory area (PMA) to user memory area. * Uses byte-access of system memory and 16-bit access of packet memory * @param wNBytes no. of bytes to be copied. * @retval None @@ -955,5 +1060,52 @@ static bool dcd_read_packet_memory(void *__restrict dst, uint16_t src, size_t wN return true; } +#if 0 // TODO support dcd_edpt_xfer_fifo API +/** + * @brief Copy a buffer from user packet memory area (PMA) to FIFO. + * Uses byte-access of system memory and 16-bit access of packet memory + * @param wNBytes no. of bytes to be copied. + * @retval None + */ + +// THIS FUNCTION IS UNTESTED + +static bool dcd_read_packet_memory_ff(tu_fifo_t * ff, uint16_t src, uint16_t wNBytes) +{ + // Since we copy into a ring buffer FIFO, a wrap might occur making it necessary to conduct two copies + // Check for first linear part + void * dst; + uint16_t len = tu_fifo_get_linear_write_info(ff, 0, &dst, wNBytes); + TU_VERIFY(len && dcd_read_packet_memory(dst, src, len)); + tu_fifo_advance_write_pointer(ff, len); + + // Check for wrapped part + if (len < wNBytes) + { + // Get remaining wrapped length + uint16_t len2 = tu_fifo_get_linear_write_info(ff, 0, &dst, wNBytes - len); + TU_VERIFY(len2); + + // Update source pointer + src += len; + + // Since PMA is accessed 16-bit wise we need to handle the case when a 16 bit value was split + if (len % 2) // If len is uneven there is a byte left to copy + { + uint32_t temp = pma[PMA_STRIDE*(src>>1)]; + *((uint8_t *)dst++) = ((temp >> 8) & 0xFF); + src++; + len2--; + } + + TU_VERIFY(dcd_read_packet_memory(dst, src, len2)); + tu_fifo_advance_write_pointer(ff, len2); + } + + return true; +} + +#endif + #endif diff --git a/src/portable/st/synopsys/dcd_synopsys.c b/src/portable/st/synopsys/dcd_synopsys.c index 548438b68..0fe7c2a3c 100644 --- a/src/portable/st/synopsys/dcd_synopsys.c +++ b/src/portable/st/synopsys/dcd_synopsys.c @@ -28,6 +28,7 @@ */ #include "tusb_option.h" +#include "common/tusb_fifo.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 @@ -135,6 +136,7 @@ static TU_ATTR_ALIGNED(4) uint32_t _setup_packet[2]; typedef struct { uint8_t * buffer; + tu_fifo_t * ff; uint16_t total_len; uint16_t max_size; uint8_t interval; @@ -644,6 +646,7 @@ bool dcd_edpt_xfer (uint8_t rhport, uint8_t ep_addr, uint8_t * buffer, uint16_t xfer_ctl_t * xfer = XFER_CTL_BASE(epnum, dir); xfer->buffer = buffer; + xfer->ff = NULL; xfer->total_len = total_bytes; // EP0 can only handle one packet @@ -668,6 +671,35 @@ bool dcd_edpt_xfer (uint8_t rhport, uint8_t ep_addr, uint8_t * buffer, uint16_t return true; } +// 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 dcd_edpt_xfer_fifo (uint8_t rhport, uint8_t ep_addr, tu_fifo_t * ff, uint16_t total_bytes) +{ + // USB buffers always work in bytes so to avoid unnecessary divisions we demand item_size = 1 + TU_ASSERT(ff->item_size == 1); + + uint8_t const epnum = tu_edpt_number(ep_addr); + uint8_t const dir = tu_edpt_dir(ep_addr); + + xfer_ctl_t * xfer = XFER_CTL_BASE(epnum, dir); + xfer->buffer = NULL; + xfer->ff = ff; + xfer->total_len = total_bytes; + + 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; @@ -867,10 +899,19 @@ static void handle_rxflvl_ints(uint8_t rhport, USB_OTG_OUTEndpointTypeDef * out_ xfer_ctl_t * xfer = XFER_CTL_BASE(epnum, TUSB_DIR_OUT); // Read packet off RxFIFO - read_fifo_packet(rhport, xfer->buffer, bcnt); + if (xfer->ff) + { + // Ring buffer + tu_fifo_write_n_const_addr_full_words(xfer->ff, (const void *) rx_fifo, bcnt); + } + else + { + // Linear buffer + read_fifo_packet(rhport, xfer->buffer, bcnt); - // Increment pointer to xfer data - xfer->buffer += bcnt; + // Increment pointer to xfer data + xfer->buffer += bcnt; + } // Truncate transfer length in case of short packet if(bcnt < xfer->max_size) { @@ -966,22 +1007,30 @@ static void handle_epin_ints(uint8_t rhport, USB_OTG_DeviceTypeDef * dev, USB_OT 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; + for(uint16_t i = 0; i < remaining_packets; i++) + { + uint16_t const 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); + uint16_t const 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; - } + 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); + if (xfer->ff) + { + usb_fifo_t tx_fifo = FIFO_BASE(rhport, n); + tu_fifo_read_n_const_addr_full_words(xfer->ff, (void *) tx_fifo, packet_size); + } + else + { + write_fifo_packet(rhport, n, xfer->buffer, packet_size); - // Increment pointer to xfer data - xfer->buffer += packet_size; + // Increment pointer to xfer data + xfer->buffer += packet_size; + } } // Turn off TXFE if all bytes are written. diff --git a/src/portable/template/dcd_template.c b/src/portable/template/dcd_template.c index 618812416..d8c8753d3 100644 --- a/src/portable/template/dcd_template.c +++ b/src/portable/template/dcd_template.c @@ -25,6 +25,7 @@ */ #include "tusb_option.h" +#include "common/tusb_fifo.h" #if CFG_TUSB_MCU == OPT_MCU_NONE @@ -104,6 +105,16 @@ bool dcd_edpt_xfer (uint8_t rhport, uint8_t ep_addr, uint8_t * buffer, uint16_t return false; } +// Submit a transfer where is managed by FIFO, When complete dcd_event_xfer_complete() is invoked to notify the stack - optional, however, must be listed in usbd.c +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; +} + // Stall endpoint void dcd_edpt_stall (uint8_t rhport, uint8_t ep_addr) { diff --git a/src/portable/ti/msp430x5xx/dcd_msp430x5xx.c b/src/portable/ti/msp430x5xx/dcd_msp430x5xx.c index e08c3536a..48e9dd592 100644 --- a/src/portable/ti/msp430x5xx/dcd_msp430x5xx.c +++ b/src/portable/ti/msp430x5xx/dcd_msp430x5xx.c @@ -26,6 +26,7 @@ */ #include "tusb_option.h" +#include "common/tusb_fifo.h" #if TUSB_OPT_DEVICE_ENABLED && ( CFG_TUSB_MCU == OPT_MCU_MSP430x5xx ) @@ -48,6 +49,7 @@ uint8_t _setup_packet[8]; typedef struct { uint8_t * buffer; + // tu_fifo_t * ff; // TODO support dcd_edpt_xfer_fifo API uint16_t total_len; uint16_t queued_len; uint16_t max_size; @@ -306,6 +308,7 @@ bool dcd_edpt_xfer (uint8_t rhport, uint8_t ep_addr, uint8_t * buffer, uint16_t xfer_ctl_t * xfer = XFER_CTL_BASE(epnum, dir); xfer->buffer = buffer; + // xfer->ff = NULL; // TODO support dcd_edpt_xfer_fifo API xfer->total_len = total_bytes; xfer->queued_len = 0; xfer->short_packet = false; @@ -344,6 +347,36 @@ bool dcd_edpt_xfer (uint8_t rhport, uint8_t ep_addr, uint8_t * buffer, uint16_t return true; } +#if 0 // TODO support dcd_edpt_xfer_fifo API +bool dcd_edpt_xfer_fifo (uint8_t rhport, uint8_t ep_addr, tu_fifo_t * ff, 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 = NULL; + xfer->ff = ff; + xfer->total_len = total_bytes; + xfer->queued_len = 0; + xfer->short_packet = false; + + ep_regs_t ep_regs = EP_REGS(epnum, dir); + + if(dir == TUSB_DIR_OUT) + { + ep_regs[BCTX] &= ~NAK; + } + else + { + USBIEPIFG |= (1 << epnum); + } + + return true; +} +#endif + void dcd_edpt_stall (uint8_t rhport, uint8_t ep_addr) { (void) rhport; @@ -443,22 +476,32 @@ static void receive_packet(uint8_t ep_num) 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) +#if 0 // TODO support dcd_edpt_xfer_fifo API + if (xfer->ff) { - volatile uint8_t * ep0out_buf = &USBOEP0BUF; - for(uint16_t i = 0; i < to_recv_size; i++) - { - base[i] = ep0out_buf[i]; - } + volatile uint8_t * ep_buf = (ep_num == 0) ? &USBOEP0BUF : (&USBSTABUFF + (ep_regs[BBAX] << 3)); + tu_fifo_write_n(xfer->ff, (const void *) ep_buf, to_recv_size); } else +#endif { - volatile uint8_t * ep_buf = &USBSTABUFF + (ep_regs[BBAX] << 3); - for(uint16_t i = 0; i < to_recv_size ; i++) + uint8_t * base = (xfer->buffer + xfer->queued_len); + + if(ep_num == 0) { - base[i] = ep_buf[i]; + 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]; + } } } @@ -499,7 +542,6 @@ static void transmit_packet(uint8_t ep_num) } // 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; @@ -513,6 +555,7 @@ static void transmit_packet(uint8_t ep_num) if(ep_num == 0) { volatile uint8_t * ep0in_buf = &USBIEP0BUF; + uint8_t * base = (xfer->buffer + xfer->queued_len); for(uint16_t i = 0; i < xfer_size; i++) { ep0in_buf[i] = base[i]; @@ -526,9 +569,19 @@ static void transmit_packet(uint8_t ep_num) 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++) +#if 0 // TODO support dcd_edpt_xfer_fifo API + if (xfer->ff) { - ep_buf[i] = base[i]; + tu_fifo_read_n(xfer->ff, (void *) ep_buf, xfer_size); + } + else +#endif + { + uint8_t * base = (xfer->buffer + xfer->queued_len); + for(int i = 0; i < xfer_size; i++) + { + ep_buf[i] = base[i]; + } } ep_regs[BCTX] = (ep_regs[BCTX] & 0x80) + (xfer_size & 0x7F);