diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 8bb382a8d..e5b8cd6ae 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -27,6 +27,7 @@ jobs: fail-fast: false matrix: example: + - 'device/audio_test' - 'device/board_test' - 'device/cdc_dual_ports' - 'device/cdc_msc' diff --git a/examples/device/audio_test/.skip.MCU_SAMD11 b/examples/device/audio_test/.skip.MCU_SAMD11 new file mode 100644 index 000000000..e69de29bb diff --git a/examples/device/audio_test/.skip.MCU_SAMG b/examples/device/audio_test/.skip.MCU_SAMG new file mode 100644 index 000000000..e69de29bb diff --git a/examples/device/audio_test/Makefile b/examples/device/audio_test/Makefile new file mode 100644 index 000000000..5a455078e --- /dev/null +++ b/examples/device/audio_test/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_test/src/main.c b/examples/device/audio_test/src/main.c new file mode 100644 index 000000000..7f0ce3652 --- /dev/null +++ b/examples/device/audio_test/src/main.c @@ -0,0 +1,422 @@ +/* + * 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. + * + */ + +#include +#include +#include + +#include "bsp/board.h" +#include "tusb.h" + +//--------------------------------------------------------------------+ +// MACRO CONSTANT TYPEDEF PROTYPES +//--------------------------------------------------------------------+ + +/* 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_N_CHANNELS_TX + 1]; // +1 for master channel 0 +uint16_t volume[CFG_TUD_AUDIO_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_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 startVal = 0; + +void led_blinking_task(void); +void audio_task(void); + +/*------------- MAIN -------------*/ +int main(void) +{ + board_init(); + + tusb_init(); + + // Init values + sampFreq = 44100; + clkValid = 1; + + sampleFreqRng.wNumSubRanges = 1; + sampleFreqRng.subrange[0].bMin = 44100; + sampleFreqRng.subrange[0].bMax = 44100; + 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)); + + // 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; + } + + // 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; + } + + 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; + + tud_audio_write ((uint8_t *)test_buffer_audio, CFG_TUD_AUDIO_TX_FIFO_SIZE); + + 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; + + for (size_t cnt = 0; cnt < CFG_TUD_AUDIO_TX_FIFO_SIZE/2; cnt++) + { + test_buffer_audio[cnt] = startVal++; + } + + 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; + startVal = 0; + + 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_test/src/tusb_config.h b/examples/device/audio_test/src/tusb_config.h new file mode 100644 index 000000000..16f88a4da --- /dev/null +++ b/examples/device/audio_test/src/tusb_config.h @@ -0,0 +1,114 @@ +/* + * 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 + +#define CFG_TUSB_OS OPT_OS_NONE + +// 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 +//-------------------------------------------------------------------- + +// 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 + +// 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 + +// 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_TUSB_DEBUG 0 + +#ifdef __cplusplus +} +#endif + +#endif /* _TUSB_CONFIG_H_ */ diff --git a/examples/device/audio_test/src/usb_descriptors.c b/examples/device/audio_test/src/usb_descriptors.c new file mode 100644 index 000000000..02d018823 --- /dev/null +++ b/examples/device/audio_test/src/usb_descriptors.c @@ -0,0 +1,167 @@ +/* + * 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_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 + +// 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) +}; + +// 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", // 2: Product + "123456", // 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; iep_out == 0) + { + return false; + } +#endif + +#if CFG_TUD_AUDIO_EPSIZE_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; + } +#endif + +#if CFG_TUD_AUDIO_ENABLE_FEEDBACK_EP + if (audio->ep_fb == 0) + { + return false; + } +#endif + + return true; +} + +//--------------------------------------------------------------------+ +// 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]); +} + +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]); +} + +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); +} + +void tud_audio_n_read_flush (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); +} + +#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) +{ + switch (CFG_TUD_AUDIO_FORMAT_TYPE_RX) + { + case AUDIO_FORMAT_TYPE_UNDEFINED: + // INDIVIDUAL DECODING PROCEDURE REQUIRED HERE! + TU_LOG2(" Desired CFG_TUD_AUDIO_FORMAT encoding not implemented!\r\n"); + TU_BREAKPOINT(); + break; + + case AUDIO_FORMAT_TYPE_I: + + switch (CFG_TUD_AUDIO_FORMAT_TYPE_I_RX) + { + 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 + break; + + default: + // DESIRED CFG_TUD_AUDIO_FORMAT_TYPE_I_RX NOT IMPLEMENTED! + TU_LOG2(" Desired CFG_TUD_AUDIO_FORMAT_TYPE_I_RX encoding not implemented!\r\n"); + TU_BREAKPOINT(); + break; + } + break; + + default: + // Desired CFG_TUD_AUDIO_FORMAT_TYPE_RX not implemented! + TU_LOG2(" Desired CFG_TUD_AUDIO_FORMAT_TYPE_RX not implemented!\r\n"); + TU_BREAKPOINT(); + break; + } + + // 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)); + + 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; +#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; + } + + 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) +{ + (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; + } + + tu_fifo_write_n(&audio->rx_ff[0], buffer, bufsize); + return true; +} +#endif // CFG_TUD_AUDIO_RX_FIFO_COUNT > 1 +#endif //CFG_TUD_AUDIO_RX_FIFO_SIZE + +//--------------------------------------------------------------------+ +// WRITE API +//--------------------------------------------------------------------+ + +/** + * \brief Write data to EP in buffer + * + * Write data to buffer. If it is full, new data can be inserted once a transmit was scheduled. See audiod_tx_done_cb(). + * If TX FIFOs are used, this function is not available in order to not let the user mess up the encoding process. + * + * \param[in] itf: Index of audio function interface + * \param[in] data: Pointer to data array to be copied from + * \param[in] len: # of array elements to copy + * \return Number of bytes actually written + */ +#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) +{ + 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; +} +*/ + +#else + +#if CFG_TUD_AUDIO_TX_FIFO_COUNT == 1 +uint16_t tud_audio_n_write(uint8_t itf, void const* 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[0], data, len); + } +} +#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) +{ + audiod_interface_t *audio = &_audiod_itf[itf]; + if (audio->p_desc == NULL) { + return 0; + } + + 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) +{ + audiod_interface_t* audio = &_audiod_itf[itf]; + if (audio->p_desc == NULL) { + return 0; + } + + return tu_fifo_write_n(&audio->int_ctr_ff, buffer, bufsize); +} +#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). + +// 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) +{ + 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)); + } + + // 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 CFG_TUD_AUDIO_TX_FIFO_SIZE + switch (CFG_TUD_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(); + break; + + case AUDIO_FORMAT_TYPE_I: + + switch (CFG_TUD_AUDIO_FORMAT_TYPE_I_TX) + { + case AUDIO_DATA_FORMAT_TYPE_I_PCM: + + TU_VERIFY(audiod_tx_done_type_I_pcm_ff_cb(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(); + break; + } + break; + + default: + // Desired CFG_TUD_AUDIO_FORMAT_TYPE_TX not implemented! + TU_LOG2(" Desired CFG_TUD_AUDIO_FORMAT_TYPE_TX not implemented!\r\n"); + TU_BREAKPOINT(); + break; + } +#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 + + // 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])); + + return true; +} + +#endif //CFG_TUD_AUDIO_EPSIZE_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) +{ + // 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 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; + + for (cntChannel = 1; cntChannel < CFG_TUD_AUDIO_N_CHANNELS_TX; cntChannel++) + { + uint16_t const count = tu_fifo_count(&audio->tx_ff[cntChannel]); + if (count / CFG_TUD_AUDIO_TX_ITEMSIZE < nSamplesPerChannelToSend) + { + nSamplesPerChannelToSend = count * CFG_TUD_AUDIO_TX_ITEMSIZE; + } + } + + // Check if there is enough + if (nSamplesPerChannelToSend == 0) + { + audio->epin_buf_cnt = 0; + return true; + } + + // 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; + + // 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]; + uint16_t len; + + if (audio->fb_val == 0) + { + len = 0; + return true; + } + else + { + len = 4; + // Here we need to return the feedback value + if (rhport == 0) + { + // 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; + } + 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); + } + +} + +//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; +//} + +#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) +{ + // 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; +} +#endif + +//--------------------------------------------------------------------+ +// USBD Driver API +//--------------------------------------------------------------------+ +void audiod_init(void) +{ + tu_memclr(_audiod_itf, sizeof(_audiod_itf)); + + for(uint8_t i=0; itx_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])); +#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++) + { + 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])); +#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); +#if CFG_FIFO_MUTEX + tu_fifo_config_mutex(&audio->int_ctr_ff, osal_mutex_create(&audio->int_ctr_ff_mutex)); +#endif +#endif + } +} + +void audiod_reset(uint8_t rhport) +{ + (void) rhport; + + for(uint8_t i=0; itx_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++) + { + tu_fifo_clear(&audio->rx_ff[cnt]); + } +#endif + } +} + +uint16_t audiod_open(uint8_t rhport, tusb_desc_interface_t const * itf_desc, uint16_t max_len) +{ + (void) max_len; + + TU_VERIFY ( TUSB_CLASS_AUDIO == itf_desc->bInterfaceClass && + AUDIO_SUBCLASS_CONTROL == itf_desc->bInterfaceSubClass); + + // Verify version is correct - this check can be omitted + TU_VERIFY(itf_desc->bInterfaceProtocol == AUDIO_INT_PROTOCOL_CODE_V2); + + // Verify interrupt control EP is enabled if demanded by descriptor - this should be best some static check however - this check can be omitted + if (itf_desc->bNumEndpoints == 1) // 0 or 1 EPs are allowed + { + TU_VERIFY(CFG_TUD_AUDIO_INT_CTR_EPSIZE_IN > 0); + } + + // Alternate setting MUST be zero - this check can be omitted + TU_VERIFY(itf_desc->bAlternateSetting == 0); + + // Find available audio driver interface + uint8_t i; + for (i = 0; i < CFG_TUD_AUDIO; i++) + { + if (!_audiod_itf[i].p_desc) + { + _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; + break; + } + } + + // Verify we found a free one + TU_ASSERT( i < CFG_TUD_AUDIO ); + + // This is all we need so far - the EPs are setup by a later set_interface request (as per UAC2 specification) + // 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 + + 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 + uint8_t idxDriver, idxItf; + 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_LOG2(" Get itf: %u - current alt: %u\r\n", itf, _audiod_itf[idxDriver].altSetting[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) +{ + (void) rhport; + + // Here we need to do the following: + + // 1. Find the audio driver assigned to the given interface to be set + // Since one audio driver interface has to be able to cover an unknown number of interfaces (AC, AS + its alternate settings), the best memory efficient way to solve this is to always search through the descriptors. + // The audio driver is mapped to an audio function by a reference pointer to the corresponding AC interface of this audio function which serves as a starting point for searching + + // 2. Close EPs which are currently open + // To do so it is not necessary to know the current active alternate interface since we already save the current EP addresses - we simply close them + + // 3. Open new EP + + uint8_t const itf = tu_u16_low(p_request->wIndex); + uint8_t const alt = tu_u16_low(p_request->wValue); + + TU_LOG2(" Set itf: %u - alt: %u\r\n", itf, alt); + + // Find index of audio streaming interface and index of interface + uint8_t idxDriver, idxItf; + uint8_t const *p_desc; + TU_VERIFY(audiod_get_AS_interface_index(itf, &idxDriver, &idxItf, &p_desc)); + + // 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) + { + _audiod_itf[idxDriver].ep_in_as_intf_num = 0; + usbd_edpt_close(rhport, _audiod_itf[idxDriver].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? + } +#endif + +#if CFG_TUD_AUDIO_EPSIZE_OUT + if (_audiod_itf[idxDriver].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? + + // 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? +#endif + } +#endif + + // Save current alternative interface setting + _audiod_itf[idxDriver].altSetting[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; + + // p_desc starts at required interface with alternate setting zero + while (p_desc < p_desc_end) + { + // 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) + { + // 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) + { + if (tu_desc_type(p_desc) == TUSB_DESC_ENDPOINT) + { + TU_ASSERT(usbd_edpt_open(rhport, (tusb_desc_endpoint_t const *)p_desc)); + + 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! + usbd_edpt_clear_stall(rhport, ep_addr); + +#if CFG_TUD_AUDIO_EPSIZE_IN > 0 + 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; + + // 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)); + } +#endif + +#if CFG_TUD_AUDIO_EPSIZE_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; + + // 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 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; + + // Invoke callback + if (tud_audio_set_itf_cb) TU_VERIFY(tud_audio_set_itf_cb(rhport, p_request)); + } +#endif + +#endif + foundEPs += 1; + } + p_desc = tu_desc_next(p_desc); + } + + TU_VERIFY(foundEPs == nEps); + + // We are done - abort loop + break; + } + + // Moving forward + p_desc = tu_desc_next(p_desc); + } + + tud_control_status(rhport, p_request); + + return true; +} + +// Invoked when class request DATA stage is finished. +// return false to stall control EP (e.g Host send non-sense DATA) +bool audiod_control_complete(uint8_t rhport, tusb_control_request_t const * p_request) +{ + // Handle audio class specific set requests + if(p_request->bmRequestType_bit.type == TUSB_REQ_TYPE_CLASS && p_request->bmRequestType_bit.direction == TUSB_DIR_OUT) + { + uint8_t idxDriver; + + switch (p_request->bmRequestType_bit.recipient) + { + case TUSB_REQ_RCPT_INTERFACE: ; // The semicolon is there to enable a declaration right after the label + + uint8_t itf = TU_U16_LOW(p_request->wIndex); + uint8_t entityID = TU_U16_HIGH(p_request->wIndex); + + if (entityID != 0) + { + if (tud_audio_set_req_entity_cb) + { + // Check if entity is present and get corresponding driver index + TU_VERIFY(audiod_verify_entity_exists(itf, entityID, &idxDriver)); + + // Invoke callback + return tud_audio_set_req_entity_cb(rhport, p_request, _audiod_itf[idxDriver].ctrl_buf); + } + else + { + TU_LOG2(" No entity set request callback available!\r\n"); + return false; // In case no callback function is present or request can not be conducted we stall it + } + } + else + { + if (tud_audio_set_req_itf_cb) + { + // Find index of audio driver structure and verify interface really exists + TU_VERIFY(audiod_verify_itf_exists(itf, &idxDriver)); + + // Invoke callback + return tud_audio_set_req_itf_cb(rhport, p_request, _audiod_itf[idxDriver].ctrl_buf); + } + else + { + TU_LOG2(" No interface set request callback available!\r\n"); + return false; // In case no callback function is present or request can not be conducted we stall it + } + } + + break; + + case TUSB_REQ_RCPT_ENDPOINT: ; // The semicolon is there to enable a declaration right after the label + + uint8_t ep = TU_U16_LOW(p_request->wIndex); + + if (tud_audio_set_req_ep_cb) + { + // Check if entity is present and get corresponding driver index + TU_VERIFY(audiod_verify_ep_exists(ep, &idxDriver)); + + // Invoke callback + return tud_audio_set_req_ep_cb(rhport, p_request, _audiod_itf[idxDriver].ctrl_buf); + } + else + { + TU_LOG2(" No EP set request callback available!\r\n"); + return false; // In case no callback function is present or request can not be conducted we stall it + } + + // Unknown/Unsupported recipient + default: TU_BREAKPOINT(); return false; + } + } + return true; +} + +// Handle class control request +// return false to stall control endpoint (e.g unsupported request) +bool audiod_control_request(uint8_t rhport, tusb_control_request_t const * p_request) +{ + (void) rhport; + + // Handle standard requests - standard set requests usually have no data stage so we also handle set requests here + if (p_request->bmRequestType_bit.type == TUSB_REQ_TYPE_STANDARD) + { + switch (p_request->bRequest) + { + case TUSB_REQ_GET_INTERFACE: + return audiod_get_interface(rhport, p_request); + + case TUSB_REQ_SET_INTERFACE: + return audiod_set_interface(rhport, p_request); + + // Unknown/Unsupported request + default: TU_BREAKPOINT(); return false; + } + } + + // Handle class requests + if (p_request->bmRequestType_bit.type == TUSB_REQ_TYPE_CLASS) + { + uint8_t itf = TU_U16_LOW(p_request->wIndex); + uint8_t idxDriver; + + // Conduct checks which depend on the recipient + switch (p_request->bmRequestType_bit.recipient) + { + case TUSB_REQ_RCPT_INTERFACE: ; // The semicolon is there to enable a declaration right after the label + + uint8_t entityID = TU_U16_HIGH(p_request->wIndex); + + // Verify if entity is present + if (entityID != 0) + { + // Find index of audio driver structure and verify entity really exists + TU_VERIFY(audiod_verify_entity_exists(itf, entityID, &idxDriver)); + + // In case we got a get request invoke callback - callback needs to answer as defined in UAC2 specification page 89 - 5. Requests + if (p_request->bmRequestType_bit.direction == TUSB_DIR_IN) + { + if (tud_audio_get_req_entity_cb) + { + return tud_audio_get_req_entity_cb(rhport, p_request); + } + else + { + TU_LOG2(" No entity get request callback available!\r\n"); + return false; // Stall + } + } + } + else + { + // Find index of audio driver structure and verify interface really exists + TU_VERIFY(audiod_verify_itf_exists(itf, &idxDriver)); + + // In case we got a get request invoke callback - callback needs to answer as defined in UAC2 specification page 89 - 5. Requests + if (p_request->bmRequestType_bit.direction == TUSB_DIR_IN) + { + if (tud_audio_get_req_itf_cb) + { + return tud_audio_get_req_itf_cb(rhport, p_request); + } + else + { + TU_LOG2(" No interface get request callback available!\r\n"); + return false; // Stall + } + } + } + break; + + case TUSB_REQ_RCPT_ENDPOINT: ; // The semicolon is there to enable a declaration right after the label + + uint8_t ep = TU_U16_LOW(p_request->wIndex); + + // Find index of audio driver structure and verify EP really exists + TU_VERIFY(audiod_verify_ep_exists(ep, &idxDriver)); + + // In case we got a get request invoke callback - callback needs to answer as defined in UAC2 specification page 89 - 5. Requests + if (p_request->bmRequestType_bit.direction == TUSB_DIR_IN) + { + if (tud_audio_get_req_ep_cb) + { + return tud_audio_get_req_ep_cb(rhport, p_request); + } + else + { + TU_LOG2(" No EP get request callback available!\r\n"); + return false; // Stall + } + } + break; + + // Unknown/Unsupported recipient + default: TU_LOG2(" Unsupported recipient: %d\r\n", p_request->bmRequestType_bit.recipient); TU_BREAKPOINT(); return false; + } + + // If we end here, the received request is a set request - we schedule a receive for the data stage and return true here. We handle the rest later in audiod_control_complete() once the data stage was finished + TU_VERIFY(tud_control_xfer(rhport, p_request, _audiod_itf[idxDriver].ctrl_buf, CFG_TUD_AUDIO_CTRL_BUF_SIZE)); + return true; + } + + // There went something wrong - unsupported control request type + TU_BREAKPOINT(); + return false; +} + +bool audiod_xfer_cb(uint8_t rhport, uint8_t ep_addr, xfer_result_t result, uint32_t xferred_bytes) +{ + (void) result; + (void) xferred_bytes; + + // Search for interface belonging to given end point address and proceed as required + uint8_t idxDriver; + for (idxDriver = 0; idxDriver < CFG_TUD_AUDIO; idxDriver++) + { + +#if CFG_TUD_AUDIO_INT_CTR_EPSIZE_IN + + // Data transmission of control interrupt finished + if (_audiod_itf[idxDriver].ep_int_ctr == ep_addr) + { + // According to USB2 specification, maximum payload of interrupt EP is 8 bytes on low speed, 64 bytes on full speed, and 1024 bytes on high speed (but only if an alternate interface other than 0 is used - see specification p. 49) + // In case there is nothing to send we have to return a NAK - this is taken care of by PHY ??? + // In case of an erroneous transmission a retransmission is conducted - this is taken care of by PHY ??? + + // Load new data + uint16 *n_bytes_copied; + TU_VERIFY(audio_int_ctr_done_cb(rhport, &_audiod_itf[idxDriver], n_bytes_copied)); + + 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); + } + } + +#endif + +#if CFG_TUD_AUDIO_EPSIZE_IN + + // Data transmission of audio packet finished + if (_audiod_itf[idxDriver].ep_in == ep_addr) + { + // USB 2.0, section 5.6.4, third paragraph, states "An isochronous endpoint must specify its required bus access period. However, an isochronous endpoint must be prepared to handle poll rates faster than the one specified." + // That paragraph goes on to say "An isochronous IN endpoint must return a zero-length packet whenever data is requested at a faster interval than the specified interval and data is not available." + // This can only be solved reliably if we load a ZLP after every IN transmission since we can not say if the host requests samples earlier than we declared! Once all samples are collected we overwrite the loaded ZLP. + + // Check if there is data to load into EPs buffer - if not load it with ZLP + // Be aware - we as a device are not able to know if the host polls for data with a faster rate as we stated this in the descriptors. Therefore we always have to put something into the EPs buffer. However, once we did that, there is no way of aborting this or replacing what we put into the buffer before! + // This is the only place where we can fill something into the EPs buffer! + + // Load new data + uint16_t n_bytes_copied; + TU_VERIFY(audiod_tx_done_cb(rhport, &_audiod_itf[idxDriver], &n_bytes_copied)); + + // Transmission of ZLP is done by audiod_tx_done_cb() + return true; + } +#endif + +#if CFG_TUD_AUDIO_EPSIZE_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); + + return true; + } + + +#if CFG_TUD_AUDIO_ENABLE_FEEDBACK_EP + // Transmission of feedback EP finished + if (_audiod_itf[idxDriver].ep_fb == ep_addr) + { + if (tud_audio_fb_done_cb) TU_VERIFY(tud_audio_fb_done_cb(rhport)); + + return audio_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) +{ + // Handles only sending of data not receiving + if (p_request->bmRequestType_bit.direction == TUSB_DIR_OUT) return false; + + // Get corresponding driver index + uint8_t idxDriver; + uint8_t itf = TU_U16_LOW(p_request->wIndex); + + // Conduct checks which depend on the recipient + switch (p_request->bmRequestType_bit.recipient) + { + case TUSB_REQ_RCPT_INTERFACE: ; // The semicolon is there to enable a declaration right after the label + + uint8_t entityID = TU_U16_HIGH(p_request->wIndex); + + // Verify if entity is present + if (entityID != 0) + { + // Find index of audio driver structure and verify entity really exists + TU_VERIFY(audiod_verify_entity_exists(itf, entityID, &idxDriver)); + } + else + { + // Find index of audio driver structure and verify interface really exists + TU_VERIFY(audiod_verify_itf_exists(itf, &idxDriver)); + } + break; + + case TUSB_REQ_RCPT_ENDPOINT: ; // The semicolon is there to enable a declaration right after the label + + uint8_t ep = TU_U16_LOW(p_request->wIndex); + + // Find index of audio driver structure and verify EP really exists + TU_VERIFY(audiod_verify_ep_exists(ep, &idxDriver)); + break; + + // Unknown/Unsupported recipient + default: TU_LOG2(" Unsupported recipient: %d\r\n", p_request->bmRequestType_bit.recipient); TU_BREAKPOINT(); return false; + } + + // Crop length + if (len > CFG_TUD_AUDIO_CTRL_BUF_SIZE) len = CFG_TUD_AUDIO_CTRL_BUF_SIZE; + + // Copy into buffer + memcpy((void *)_audiod_itf[idxDriver].ctrl_buf, data, (size_t)len); + + // Schedule transmit + return tud_control_xfer(rhport, p_request, (void*)_audiod_itf[idxDriver].ctrl_buf, len); +} + +// This helper function finds for a given AS interface number the index of the attached driver structure, the index of the interface in the audio function +// (e.g. the std. AS interface with interface number 15 is the first AS interface for the given audio function and thus gets index zero), and +// finally a pointer to the std. AS interface, where the pointer always points to the first alternate setting i.e. alternate interface zero. +static bool audiod_get_AS_interface_index(uint8_t itf, uint8_t *idxDriver, uint8_t *idxItf, uint8_t const **pp_desc_int) +{ + // Loop over audio driver interfaces + uint8_t i; + for (i = 0; i < CFG_TUD_AUDIO; i++) + { + 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; + + // Advance past AC descriptors + uint8_t const *p_desc = tu_desc_next(_audiod_itf[i].p_desc); + p_desc += ((audio_desc_cs_ac_interface_t const *)p_desc)->wTotalLength; + + uint8_t tmp = 0; + while (p_desc < p_desc_end) + { + // We assume the number of alternate settings is increasing thus we return the index of alternate setting zero! + if (tu_desc_type(p_desc) == TUSB_DESC_INTERFACE && ((tusb_desc_interface_t const * )p_desc)->bInterfaceNumber == itf) + { + *idxItf = tmp; + *idxDriver = i; + *pp_desc_int = p_desc; + return true; + } + + // Increase index, bytes read, and pointer + tmp++; + p_desc = tu_desc_next(p_desc); + } + } + } + + return false; +} + +// Verify an entity with the given ID exists and returns also the corresponding driver index +static bool audiod_verify_entity_exists(uint8_t itf, uint8_t entityID, uint8_t *idxDriver) +{ + uint8_t i; + for (i = 0; i < CFG_TUD_AUDIO; i++) + { + // Look for the correct driver by checking if the unique standard AC interface number fits + if (_audiod_itf[i].p_desc && ((tusb_desc_interface_t const *)_audiod_itf[i].p_desc)->bInterfaceNumber == itf) + { + // Get pointers after class specific AC descriptors and end of AC descriptors - entities are defined in between + uint8_t const *p_desc = tu_desc_next(_audiod_itf[i].p_desc); // Points to CS AC descriptor + uint8_t const *p_desc_end = ((audio_desc_cs_ac_interface_t const *)p_desc)->wTotalLength + p_desc; + p_desc = tu_desc_next(p_desc); // Get past CS AC descriptor + + while (p_desc < p_desc_end) + { + if (p_desc[3] == entityID) // Entity IDs are always at offset 3 + { + *idxDriver = i; + return true; + } + p_desc = tu_desc_next(p_desc); + } + } + } + return false; +} + +static bool audiod_verify_itf_exists(uint8_t itf, uint8_t *idxDriver) +{ + uint8_t i; + for (i = 0; i < CFG_TUD_AUDIO; i++) + { + if (_audiod_itf[i].p_desc) + { + // 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; + + while (p_desc < p_desc_end) + { + if (tu_desc_type(p_desc) == TUSB_DESC_INTERFACE && ((tusb_desc_interface_t const *)_audiod_itf[i].p_desc)->bInterfaceNumber == itf) + { + *idxDriver = i; + return true; + } + p_desc = tu_desc_next(p_desc); + } + } + } + return false; +} + +static bool audiod_verify_ep_exists(uint8_t ep, uint8_t *idxDriver) +{ + uint8_t i; + for (i = 0; i < CFG_TUD_AUDIO; i++) + { + 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]; + + // Advance past AC descriptors - EP we look for are streaming EPs + uint8_t const *p_desc = tu_desc_next(_audiod_itf[i].p_desc); + p_desc += ((audio_desc_cs_ac_interface_t const *)p_desc)->wTotalLength; + + while (p_desc < p_desc_end) + { + if (tu_desc_type(p_desc) == TUSB_DESC_ENDPOINT && ((tusb_desc_endpoint_t const * )p_desc)->bEndpointAddress == ep) + { + *idxDriver = i; + return true; + } + p_desc = tu_desc_next(p_desc); + } + } + } + return false; +} + +#if CFG_TUD_AUDIO_ENABLE_FEEDBACK_EP +bool tud_audio_fb_set(uint8_t rhport, uint32_t feedback) +{ + audiod_interface_t *audio = &_audiod_itf[0]; + + audio->fb_val = feedback; + TU_VERIFY(!usbd_edpt_busy(rhport, audio->ep_fb), true); + + return audio_fb_send(rhport, audio); +} +#endif + +#endif //TUSB_OPT_DEVICE_ENABLED && CFG_TUD_AUDIO diff --git a/src/class/audio/audio_device.h b/src/class/audio/audio_device.h new file mode 100644 index 000000000..d86232720 --- /dev/null +++ b/src/class/audio/audio_device.h @@ -0,0 +1,401 @@ +/* + * The MIT License (MIT) + * + * Copyright (c) 2020 Ha Thach (tinyusb.org) + * Copyright (c) 2020 Reinhard Panhuber + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + * This file is part of the TinyUSB stack. + */ + +#ifndef _TUSB_AUDIO_DEVICE_H_ +#define _TUSB_AUDIO_DEVICE_H_ + +#include "assert.h" +#include "common/tusb_common.h" +#include "device/usbd.h" + +#include "audio.h" + +//--------------------------------------------------------------------+ +// 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 +#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 +#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 +#endif + +#ifndef CFG_TUD_AUDIO_RX_FIFO_SIZE +#define CFG_TUD_AUDIO_RX_FIFO_SIZE 0 // Buffer size per channel +#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 +#endif + +#ifndef CFG_TUD_AUDIO_EPSIZE_OUT +#define CFG_TUD_AUDIO_EPSIZE_OUT 0 // RX +#endif + +#ifndef CFG_TUD_AUDIO_ENABLE_FEEDBACK_EP +#define CFG_TUD_AUDIO_ENABLE_FEEDBACK_EP 0 // Feedback +#endif + +#ifndef CFG_TUD_AUDIO_INT_CTR_EPSIZE_IN +#define CFG_TUD_AUDIO_INT_CTR_EPSIZE_IN 0 // Audio interrupt control +#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) +#endif +#endif + +#ifndef CFG_TUD_AUDIO_N_CHANNELS_TX +#define CFG_TUD_AUDIO_N_CHANNELS_TX 1 +#endif + +#ifndef CFG_TUD_AUDIO_N_CHANNELS_RX +#define CFG_TUD_AUDIO_N_CHANNELS_RX 1 +#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 +#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 +#endif + +#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 +#endif + +#ifndef CFG_TUD_AUDIO_N_BYTES_PER_SAMPLE_RX // bSubslotSize +#define CFG_TUD_AUDIO_N_BYTES_PER_SAMPLE_RX 1 +#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 +#endif + +#endif + +//static_assert(sizeof(tud_audio_desc_lengths) != CFG_TUD_AUDIO, "Supply audio function descriptor pack length!"); + +// Supported types of this driver: +// AUDIO_DATA_FORMAT_TYPE_I_PCM - Required definitions: CFG_TUD_AUDIO_N_CHANNELS and CFG_TUD_AUDIO_BYTES_PER_CHANNEL + +#ifdef __cplusplus +extern "C" { +#endif + +/** \addtogroup AUDIO_Serial Serial + * @{ + * \defgroup AUDIO_Serial_Device Device + * @{ */ + +//--------------------------------------------------------------------+ +// Application API (Multiple Interfaces) +// CFG_TUD_AUDIO > 1 +//--------------------------------------------------------------------+ +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 +#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 +#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); +#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); +#endif + +//--------------------------------------------------------------------+ +// Application API (Interface0) +//--------------------------------------------------------------------+ + +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); +#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 +#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); +#endif + +// Buffer control EP data and schedule a transmit +// This function is intended to be used if you do not have a persistent buffer or memory location available (e.g. non-local variables) and need to answer onto a +// get request. This function buffers your answer request frame into the control buffer of the corresponding audio driver and schedules a transmit for sending it. +// Since transmission is triggered via interrupts, a persistent memory location is required onto which the buffer pointer in pointing. If you already have such +// available you may directly use 'tud_control_xfer(...)'. In this case data does not need to be copied into an additional buffer and you save some time. +// If the request's wLength is zero, a status packet is sent instead. +bool tud_audio_buffer_and_schedule_control_xfer(uint8_t rhport, tusb_control_request_t const * p_request, void* data, uint16_t len); + +//--------------------------------------------------------------------+ +// Application Callback API (weak is optional) +//--------------------------------------------------------------------+ + +#if CFG_TUD_AUDIO_EPSIZE_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); +#endif + +#if CFG_TUD_AUDIO_EPSIZE_OUT > 0 && 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); +#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); +#endif + +// Invoked when audio set interface request received +TU_ATTR_WEAK bool tud_audio_set_itf_cb(uint8_t rhport, tusb_control_request_t const * p_request); + +// Invoked when audio set interface request received which closes an EP +TU_ATTR_WEAK bool tud_audio_set_itf_close_EP_cb(uint8_t rhport, tusb_control_request_t const * p_request); + +// Invoked when audio class specific set request received for an EP +TU_ATTR_WEAK bool tud_audio_set_req_ep_cb(uint8_t rhport, tusb_control_request_t const * p_request, uint8_t *pBuff); + +// Invoked when audio class specific set request received for an interface +TU_ATTR_WEAK bool tud_audio_set_req_itf_cb(uint8_t rhport, tusb_control_request_t const * p_request, uint8_t *pBuff); + +// Invoked when audio class specific set request received for an entity +TU_ATTR_WEAK bool tud_audio_set_req_entity_cb(uint8_t rhport, tusb_control_request_t const * p_request, uint8_t *pBuff); + +// Invoked when audio class specific get request received for an EP +TU_ATTR_WEAK bool tud_audio_get_req_ep_cb(uint8_t rhport, tusb_control_request_t const * p_request); + +// Invoked when audio class specific get request received for an interface +TU_ATTR_WEAK bool tud_audio_get_req_itf_cb(uint8_t rhport, tusb_control_request_t const * p_request); + +// Invoked when audio class specific get request received for an entity +TU_ATTR_WEAK bool tud_audio_get_req_entity_cb(uint8_t rhport, tusb_control_request_t const * p_request); + +//--------------------------------------------------------------------+ +// Inline Functions +//--------------------------------------------------------------------+ + +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 + +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_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) +{ + return tud_audio_n_read(0, buffer, bufsize); +} + +static inline void tud_audio_read_flush(void) +{ + tud_audio_n_read_flush(0); +} +#endif +#endif + +#if CFG_TUD_AUDIO_INT_CTR_EPSIZE_IN > 0 +static inline uint16_t tud_audio_int_ctr_available(void) +{ + return tud_audio_int_ctr_n_available(0); +} + +static inline uint16_t tud_audio_int_ctr_read(void* buffer, uint16_t bufsize) +{ + return tud_audio_int_ctr_n_read(0, buffer, bufsize); +} + +static inline void tud_audio_int_ctr_read_flush(void) +{ + return tud_audio_int_ctr_n_read_flush(0); +} + +static inline uint16_t tud_audio_int_ctr_write(uint8_t const* buffer, uint16_t bufsize) +{ + return tud_audio_int_ctr_n_write(0, buffer, bufsize); +} +#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_request (uint8_t rhport, tusb_control_request_t const * request); +bool audiod_control_complete (uint8_t rhport, 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 +} +#endif + +#endif /* _TUSB_AUDIO_DEVICE_H_ */ + +/** @} */ +/** @} */ diff --git a/src/class/midi/midi_device.c b/src/class/midi/midi_device.c index de05d93d1..a07acf0b8 100644 --- a/src/class/midi/midi_device.c +++ b/src/class/midi/midi_device.c @@ -300,9 +300,9 @@ void midid_reset(uint8_t rhport) uint16_t midid_open(uint8_t rhport, tusb_desc_interface_t const * desc_itf, uint16_t max_len) { // 1st Interface is Audio Control v1 - TU_VERIFY(TUSB_CLASS_AUDIO == desc_itf->bInterfaceClass && - AUDIO_SUBCLASS_CONTROL == desc_itf->bInterfaceSubClass && - AUDIO_PROTOCOL_V1 == desc_itf->bInterfaceProtocol, 0); + TU_VERIFY(TUSB_CLASS_AUDIO == desc_itf->bInterfaceClass && + AUDIO_SUBCLASS_CONTROL == desc_itf->bInterfaceSubClass && + AUDIO_FUNC_PROTOCOL_CODE_UNDEF == desc_itf->bInterfaceProtocol, 0); uint16_t drv_len = tu_desc_len(desc_itf); uint8_t const * p_desc = tu_desc_next(desc_itf); @@ -318,9 +318,9 @@ uint16_t midid_open(uint8_t rhport, tusb_desc_interface_t const * desc_itf, uint TU_VERIFY(TUSB_DESC_INTERFACE == tu_desc_type(p_desc), 0); tusb_desc_interface_t const * desc_midi = (tusb_desc_interface_t const *) p_desc; - TU_VERIFY(TUSB_CLASS_AUDIO == desc_midi->bInterfaceClass && - AUDIO_SUBCLASS_MIDI_STREAMING == desc_midi->bInterfaceSubClass && - AUDIO_PROTOCOL_V1 == desc_midi->bInterfaceProtocol, 0); + TU_VERIFY(TUSB_CLASS_AUDIO == desc_midi->bInterfaceClass && + AUDIO_SUBCLASS_MIDI_STREAMING == desc_midi->bInterfaceSubClass && + AUDIO_FUNC_PROTOCOL_CODE_UNDEF == desc_midi->bInterfaceProtocol, 0); // Find available interface midid_interface_t * p_midi = NULL; diff --git a/src/common/tusb_common.h b/src/common/tusb_common.h index d95c0ffc4..15892fa33 100644 --- a/src/common/tusb_common.h +++ b/src/common/tusb_common.h @@ -47,10 +47,10 @@ #define U16_TO_U8S_BE(u16) TU_U16_HIGH(u16), TU_U16_LOW(u16) #define U16_TO_U8S_LE(u16) TU_U16_LOW(u16), TU_U16_HIGH(u16) -#define U32_B1_U8(u32) ((uint8_t) (((u32) >> 24) & 0x000000ff)) // MSB -#define U32_B2_U8(u32) ((uint8_t) (((u32) >> 16) & 0x000000ff)) -#define U32_B3_U8(u32) ((uint8_t) (((u32) >> 8) & 0x000000ff)) -#define U32_B4_U8(u32) ((uint8_t) ((u32) & 0x000000ff)) // LSB +#define U32_B1_U8(u32) ((uint8_t) ((((uint32_t) u32) >> 24) & 0x000000ff)) // MSB +#define U32_B2_U8(u32) ((uint8_t) ((((uint32_t) u32) >> 16) & 0x000000ff)) +#define U32_B3_U8(u32) ((uint8_t) ((((uint32_t) u32) >> 8) & 0x000000ff)) +#define U32_B4_U8(u32) ((uint8_t) (((uint32_t) u32) & 0x000000ff)) // LSB #define U32_TO_U8S_BE(u32) U32_B1_U8(u32), U32_B2_U8(u32), U32_B3_U8(u32), U32_B4_U8(u32) #define U32_TO_U8S_LE(u32) U32_B4_U8(u32), U32_B3_U8(u32), U32_B2_U8(u32), U32_B1_U8(u32) diff --git a/src/device/usbd.c b/src/device/usbd.c index 3a6ce9b6c..90edc3dde 100644 --- a/src/device/usbd.c +++ b/src/device/usbd.c @@ -50,7 +50,6 @@ typedef struct { volatile uint8_t connected : 1; volatile uint8_t addressed : 1; - volatile uint8_t configured : 1; volatile uint8_t suspended : 1; uint8_t remote_wakeup_en : 1; // enable/disable by host @@ -58,6 +57,7 @@ typedef struct uint8_t self_powered : 1; // configuration descriptor's attribute }; + volatile uint8_t cfg_num; // current active configuration (0x00 is not configured) uint8_t speed; uint8_t itf2drv[16]; // map interface number to driver (0xff is invalid) @@ -130,6 +130,19 @@ static usbd_class_driver_t const _usbd_driver[] = }, #endif +#if CFG_TUD_AUDIO +{ + DRIVER_NAME("AUDIO") + .init = audiod_init, + .reset = audiod_reset, + .open = audiod_open, + .control_request = audiod_control_request, + .control_complete = audiod_control_complete, + .xfer_cb = audiod_xfer_cb, + .sof = NULL +}, +#endif + #if CFG_TUD_MIDI { DRIVER_NAME("MIDI") @@ -325,7 +338,7 @@ tusb_speed_t tud_speed_get(void) bool tud_mounted(void) { - return _usbd_dev.configured; + return _usbd_dev.cfg_num ? 1 : 0; } bool tud_suspended(void) @@ -618,8 +631,8 @@ static bool process_control_request(uint8_t rhport, tusb_control_request_t const case TUSB_REQ_GET_CONFIGURATION: { - uint8_t cfgnum = _usbd_dev.configured ? 1 : 0; - tud_control_xfer(rhport, p_request, &cfgnum, 1); + uint8_t cfg_num = _usbd_dev.cfg_num; + tud_control_xfer(rhport, p_request, &cfg_num, 1); } break; @@ -627,8 +640,8 @@ static bool process_control_request(uint8_t rhport, tusb_control_request_t const { uint8_t const cfg_num = (uint8_t) p_request->wValue; - if ( !_usbd_dev.configured && cfg_num ) TU_ASSERT( process_set_config(rhport, cfg_num) ); - _usbd_dev.configured = cfg_num ? 1 : 0; + if ( !_usbd_dev.cfg_num && cfg_num ) TU_ASSERT( process_set_config(rhport, cfg_num) ); + _usbd_dev.cfg_num = cfg_num; tud_control_status(rhport, p_request); } @@ -975,7 +988,7 @@ void dcd_event_handler(dcd_event_t const * event, bool in_isr) case DCD_EVENT_UNPLUGGED: _usbd_dev.connected = 0; _usbd_dev.addressed = 0; - _usbd_dev.configured = 0; + _usbd_dev.cfg_num = 0; _usbd_dev.suspended = 0; osal_queue_send(_usbd_q, event, in_isr); break; diff --git a/src/device/usbd.h b/src/device/usbd.h index 5338be157..1b0f0cbd3 100644 --- a/src/device/usbd.h +++ b/src/device/usbd.h @@ -31,7 +31,7 @@ #define _TUSB_USBD_H_ #ifdef __cplusplus - extern "C" { +extern "C" { #endif #include "common/tusb_common.h" @@ -168,7 +168,7 @@ TU_ATTR_WEAK bool tud_vendor_control_complete_cb(uint8_t rhport, tusb_control_re TUD_BOS_PLATFORM_DESCRIPTOR(TUD_BOS_MS_OS_20_UUID, U32_TO_U8S_LE(0x06030000), U16_TO_U8S_LE(_desc_set_len), _vendor_code, 0) #define TUD_BOS_MS_OS_20_UUID \ - 0xDF, 0x60, 0xDD, 0xD8, 0x89, 0x45, 0xC7, 0x4C, \ + 0xDF, 0x60, 0xDD, 0xD8, 0x89, 0x45, 0xC7, 0x4C, \ 0x9C, 0xD2, 0x65, 0x9D, 0x9E, 0x64, 0x8A, 0x9F //--------------------------------------------------------------------+ @@ -260,11 +260,11 @@ TU_ATTR_WEAK bool tud_vendor_control_complete_cb(uint8_t rhport, tusb_control_re #define TUD_MIDI_DESC_HEAD_LEN (9 + 9 + 9 + 7) #define TUD_MIDI_DESC_HEAD(_itfnum, _stridx, _numcables) \ /* Audio Control (AC) Interface */\ - 9, TUSB_DESC_INTERFACE, _itfnum, 0, 0, TUSB_CLASS_AUDIO, AUDIO_SUBCLASS_CONTROL, AUDIO_PROTOCOL_V1, _stridx,\ + 9, TUSB_DESC_INTERFACE, _itfnum, 0, 0, TUSB_CLASS_AUDIO, AUDIO_SUBCLASS_CONTROL, AUDIO_FUNC_PROTOCOL_CODE_UNDEF, _stridx,\ /* AC Header */\ - 9, TUSB_DESC_CS_INTERFACE, AUDIO_CS_INTERFACE_HEADER, U16_TO_U8S_LE(0x0100), U16_TO_U8S_LE(0x0009), 1, (uint8_t)((_itfnum) + 1),\ + 9, TUSB_DESC_CS_INTERFACE, AUDIO_CS_AC_INTERFACE_HEADER, U16_TO_U8S_LE(0x0100), U16_TO_U8S_LE(0x0009), 1, (uint8_t)((_itfnum) + 1),\ /* MIDI Streaming (MS) Interface */\ - 9, TUSB_DESC_INTERFACE, (uint8_t)((_itfnum) + 1), 0, 2, TUSB_CLASS_AUDIO, AUDIO_SUBCLASS_MIDI_STREAMING, AUDIO_PROTOCOL_V1, 0,\ + 9, TUSB_DESC_INTERFACE, (uint8_t)((_itfnum) + 1), 0, 2, TUSB_CLASS_AUDIO, AUDIO_SUBCLASS_MIDI_STREAMING, AUDIO_FUNC_PROTOCOL_CODE_UNDEF, 0,\ /* MS Header */\ 7, TUSB_DESC_CS_INTERFACE, MIDI_CS_INTERFACE_HEADER, U16_TO_U8S_LE(0x0100), U16_TO_U8S_LE(7 + (_numcables) * TUD_MIDI_DESC_JACK_LEN) @@ -312,6 +312,174 @@ TU_ATTR_WEAK bool tud_vendor_control_complete_cb(uint8_t rhport, tusb_control_re TUD_MIDI_DESC_EP(_epin, _epsize, 1),\ TUD_MIDI_JACKID_OUT_EMB(1) +//------------- AUDIO -------------// + +/* Standard Interface Association Descriptor (IAD) */ +#define TUD_AUDIO_DESC_IAD_LEN 8 +#define TUD_AUDIO_DESC_IAD(_firstitfs, _nitfs, _stridx) \ + TUD_AUDIO_DESC_IAD_LEN, TUSB_DESC_INTERFACE_ASSOCIATION, _firstitfs, _nitfs, TUSB_CLASS_AUDIO, AUDIO_FUNCTION_SUBCLASS_UNDEFINED, AUDIO_FUNC_PROTOCOL_CODE_V2, _stridx + +/* Standard AC Interface Descriptor(4.7.1) */ +#define TUD_AUDIO_DESC_STD_AC_LEN 9 +#define TUD_AUDIO_DESC_STD_AC(_itfnum, _nEPs, _stridx) /* _nEPs is 0 or 1 */\ + TUD_AUDIO_DESC_STD_AC_LEN, TUSB_DESC_INTERFACE, _itfnum, /* fixed to zero */ 0x00, _nEPs, TUSB_CLASS_AUDIO, AUDIO_SUBCLASS_CONTROL, AUDIO_INT_PROTOCOL_CODE_V2, _stridx + +/* Class-Specific AC Interface Header Descriptor(4.7.2) */ +#define TUD_AUDIO_DESC_CS_AC_LEN 9 +#define TUD_AUDIO_DESC_CS_AC(_bcdADC, _category, _totallen, _ctrl) /* _bcdADC : Audio Device Class Specification Release Number in Binary-Coded Decimal, _category : see audio_function_t, _totallen : Total number of bytes returned for the class-specific AudioControl interface i.e. Clock Source, Unit and Terminal descriptors - Do not include TUD_AUDIO_DESC_CS_AC_LEN, we already do this here*/ \ + TUD_AUDIO_DESC_CS_AC_LEN, TUSB_DESC_CS_INTERFACE, AUDIO_CS_AC_INTERFACE_HEADER, U16_TO_U8S_LE(_bcdADC), _category, U16_TO_U8S_LE(_totallen + TUD_AUDIO_DESC_CS_AC_LEN), _ctrl + +/* Clock Source Descriptor(4.7.2.1) */ +#define TUD_AUDIO_DESC_CLK_SRC_LEN 8 +#define TUD_AUDIO_DESC_CLK_SRC(_clkid, _attr, _ctrl, _assocTerm, _stridx) \ + TUD_AUDIO_DESC_CLK_SRC_LEN, TUSB_DESC_CS_INTERFACE, AUDIO_CS_AC_INTERFACE_CLOCK_SOURCE, _clkid, _attr, _ctrl, _assocTerm, _stridx + +/* Input Terminal Descriptor(4.7.2.4) */ +#define TUD_AUDIO_DESC_INPUT_TERM_LEN 17 +#define TUD_AUDIO_DESC_INPUT_TERM(_termid, _termtype, _assocTerm, _clkid, _nchannelslogical, _channelcfg, _idxchannelnames, _ctrl, _stridx) \ + TUD_AUDIO_DESC_INPUT_TERM_LEN, TUSB_DESC_CS_INTERFACE, AUDIO_CS_AC_INTERFACE_INPUT_TERMINAL, _termid, U16_TO_U8S_LE(_termtype), _assocTerm, _clkid, _nchannelslogical, U32_TO_U8S_LE(_channelcfg), _idxchannelnames, U16_TO_U8S_LE(_ctrl), _stridx + +/* Output Terminal Descriptor(4.7.2.5) */ +#define TUD_AUDIO_DESC_OUTPUT_TERM_LEN 12 +#define TUD_AUDIO_DESC_OUTPUT_TERM(_termid, _termtype, _assocTerm, _srcid, _clkid, _ctrl, _stridx) \ + TUD_AUDIO_DESC_OUTPUT_TERM_LEN, TUSB_DESC_CS_INTERFACE, AUDIO_CS_AC_INTERFACE_OUTPUT_TERMINAL, _termid, U16_TO_U8S_LE(_termtype), _assocTerm, _srcid, _clkid, U16_TO_U8S_LE(_ctrl), _stridx + +/* Feature Unit Descriptor(4.7.2.8) */ +// 1 - Channel +#define TUD_AUDIO_DESC_FEATURE_UNIT_ONE_CHANNEL_LEN 6+(1+1)*4 +#define TUD_AUDIO_DESC_FEATURE_UNIT_ONE_CHANNEL(_unitid, _srcid, _ctrlch0master, _ctrlch1, _stridx) \ + TUD_AUDIO_DESC_FEATURE_UNIT_ONE_CHANNEL_LEN, TUSB_DESC_CS_INTERFACE, AUDIO_CS_AC_INTERFACE_FEATURE_UNIT, _unitid, _srcid, U32_TO_U8S_LE(_ctrlch0master), U32_TO_U8S_LE(_ctrlch1), _stridx + +// For more channels, add definitions here + +/* Standard AS Interface Descriptor(4.9.1) */ +#define TUD_AUDIO_DESC_STD_AS_INT_LEN 9 +#define TUD_AUDIO_DESC_STD_AS_INT(_itfnum, _altset, _nEPs, _stridx) \ + TUD_AUDIO_DESC_STD_AS_INT_LEN, TUSB_DESC_INTERFACE, _itfnum, _altset, _nEPs, TUSB_CLASS_AUDIO, AUDIO_SUBCLASS_STREAMING, AUDIO_INT_PROTOCOL_CODE_V2, _stridx + +/* Class-Specific AS Interface Descriptor(4.9.2) */ +#define TUD_AUDIO_DESC_CS_AS_INT_LEN 16 +#define TUD_AUDIO_DESC_CS_AS_INT(_termid, _ctrl, _formattype, _formats, _nchannelsphysical, _channelcfg, _stridx) \ + TUD_AUDIO_DESC_CS_AS_INT_LEN, TUSB_DESC_CS_INTERFACE, AUDIO_CS_AS_INTERFACE_AS_GENERAL, _termid, _ctrl, _formattype, U32_TO_U8S_LE(_formats), _nchannelsphysical, U32_TO_U8S_LE(_channelcfg), _stridx + +/* Type I Format Type Descriptor(2.3.1.6 - Audio Formats) */ +#define TUD_AUDIO_DESC_TYPE_I_FORMAT_LEN 6 +#define TUD_AUDIO_DESC_TYPE_I_FORMAT(_subslotsize, _bitresolution) /* _subslotsize is number of bytes per sample (i.e. subslot) and can be 1,2,3, or 4 */\ + TUD_AUDIO_DESC_TYPE_I_FORMAT_LEN, TUSB_DESC_CS_INTERFACE, AUDIO_CS_AS_INTERFACE_FORMAT_TYPE, AUDIO_FORMAT_TYPE_I, _subslotsize, _bitresolution + +/* Standard AS Isochronous Audio Data Endpoint Descriptor(4.10.1.1) */ +#define TUD_AUDIO_DESC_STD_AS_ISO_EP_LEN 7 +#define TUD_AUDIO_DESC_STD_AS_ISO_EP(_ep, _attr, _maxEPsize, _interval) \ + TUD_AUDIO_DESC_STD_AS_ISO_EP_LEN, TUSB_DESC_ENDPOINT, _ep, _attr, U16_TO_U8S_LE(_maxEPsize), _interval + +/* Class-Specific AS Isochronous Audio Data Endpoint Descriptor(4.10.1.2) */ +#define TUD_AUDIO_DESC_CS_AS_ISO_EP_LEN 8 +#define TUD_AUDIO_DESC_CS_AS_ISO_EP(_attr, _ctrl, _lockdelayunit, _lockdelay) \ + TUD_AUDIO_DESC_CS_AS_ISO_EP_LEN, TUSB_DESC_CS_ENDPOINT, AUDIO_CS_EP_SUBTYPE_GENERAL, _attr, _ctrl, _lockdelayunit, U16_TO_U8S_LE(_lockdelay) + +/* Standard AS Isochronous Feedback Endpoint Descriptor(4.10.2.1) */ +#define TUD_AUDIO_DESC_STD_AS_ISO_FB_EP_LEN 7 +#define TUD_AUDIO_DESC_STD_AS_ISO_FB_EP(_ep, _interval) \ + TUD_AUDIO_DESC_STD_AS_ISO_FB_EP_LEN, TUSB_DESC_ENDPOINT, _ep, (TUSB_XFER_ISOCHRONOUS | TUSB_ISO_EP_ATT_NO_SYNC | TUSB_ISO_EP_ATT_EXPLICIT_FB), U16_TO_U8S_LE(4), _interval + +// AUDIO simple descriptor (UAC2) for 1 microphone input +// - 1 Input Terminal, 1 Feature Unit (Mute and Volume Control), 1 Output Terminal, 1 Clock Source + +#define TUD_AUDIO_MIC_DESC_LEN (TUD_AUDIO_DESC_IAD_LEN\ + + TUD_AUDIO_DESC_STD_AC_LEN\ + + TUD_AUDIO_DESC_CS_AC_LEN\ + + TUD_AUDIO_DESC_CLK_SRC_LEN\ + + TUD_AUDIO_DESC_INPUT_TERM_LEN\ + + TUD_AUDIO_DESC_OUTPUT_TERM_LEN\ + + TUD_AUDIO_DESC_FEATURE_UNIT_ONE_CHANNEL_LEN\ + + TUD_AUDIO_DESC_STD_AS_INT_LEN\ + + TUD_AUDIO_DESC_STD_AS_INT_LEN\ + + TUD_AUDIO_DESC_CS_AS_INT_LEN\ + + TUD_AUDIO_DESC_TYPE_I_FORMAT_LEN\ + + TUD_AUDIO_DESC_STD_AS_ISO_EP_LEN\ + + TUD_AUDIO_DESC_CS_AS_ISO_EP_LEN) + +#define TUD_AUDIO_MIC_DESC_N_AS_INT 1 // Number of AS interfaces + +#define TUD_AUDIO_MIC_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_ONE_CHANNEL_LEN, /*_ctrl*/ AUDIO_CS_AS_INTERFACE_CTRL_LATENCY_POS),\ + /* Clock Source Descriptor(4.7.2.1) */\ + TUD_AUDIO_DESC_CLK_SRC(/*_clkid*/ 0x04, /*_attr*/ AUDIO_CLOCK_SOURCE_ATT_INT_FIX_CLK, /*_ctrl*/ (AUDIO_CTRL_R << AUDIO_CLOCK_SOURCE_CTRL_CLK_FRQ_POS), /*_assocTerm*/ 0x01, /*_stridx*/ 0x00),\ + /* Input Terminal Descriptor(4.7.2.4) */\ + TUD_AUDIO_DESC_INPUT_TERM(/*_termid*/ 0x01, /*_termtype*/ AUDIO_TERM_TYPE_IN_GENERIC_MIC, /*_assocTerm*/ 0x03, /*_clkid*/ 0x04, /*_nchannelslogical*/ 0x01, /*_channelcfg*/ AUDIO_CHANNEL_CONFIG_NON_PREDEFINED, /*_idxchannelnames*/ 0x00, /*_ctrl*/ AUDIO_CTRL_R << AUDIO_IN_TERM_CTRL_CONNECTOR_POS, /*_stridx*/ 0x00),\ + /* Output Terminal Descriptor(4.7.2.5) */\ + TUD_AUDIO_DESC_OUTPUT_TERM(/*_termid*/ 0x03, /*_termtype*/ AUDIO_TERM_TYPE_USB_STREAMING, /*_assocTerm*/ 0x01, /*_srcid*/ 0x02, /*_clkid*/ 0x04, /*_ctrl*/ 0x0000, /*_stridx*/ 0x00),\ + /* Feature Unit Descriptor(4.7.2.8) */\ + TUD_AUDIO_DESC_FEATURE_UNIT_ONE_CHANNEL(/*_unitid*/ 0x02, /*_srcid*/ 0x01, /*_ctrlch0master*/ AUDIO_CTRL_RW << AUDIO_FEATURE_UNIT_CTRL_MUTE_POS | AUDIO_CTRL_RW << AUDIO_FEATURE_UNIT_CTRL_VOLUME_POS, /*_ctrlch1*/ AUDIO_CTRL_RW << AUDIO_FEATURE_UNIT_CTRL_MUTE_POS | AUDIO_CTRL_RW << AUDIO_FEATURE_UNIT_CTRL_VOLUME_POS, /*_stridx*/ 0x00),\ + /* Standard AS Interface Descriptor(4.9.1) */\ + /* Interface 1, Alternate 0 - default alternate setting with 0 bandwidth */\ + TUD_AUDIO_DESC_STD_AS_INT(/*_itfnum*/ (uint8_t)((_itfnum)+1), /*_altset*/ 0x00, /*_nEPs*/ 0x00, /*_stridx*/ 0x00),\ + /* Standard AS Interface Descriptor(4.9.1) */\ + /* Interface 1, Alternate 1 - alternate interface for data streaming */\ + TUD_AUDIO_DESC_STD_AS_INT(/*_itfnum*/ (uint8_t)((_itfnum)+1), /*_altset*/ 0x01, /*_nEPs*/ 0x01, /*_stridx*/ 0x00),\ + /* Class-Specific AS Interface Descriptor(4.9.2) */\ + TUD_AUDIO_DESC_CS_AS_INT(/*_termid*/ 0x03, /*_ctrl*/ AUDIO_CTRL_NONE, /*_formattype*/ AUDIO_FORMAT_TYPE_I, /*_formats*/ AUDIO_DATA_FORMAT_TYPE_I_PCM, /*_nchannelsphysical*/ 0x01, /*_channelcfg*/ AUDIO_CHANNEL_CONFIG_NON_PREDEFINED, /*_stridx*/ 0x00),\ + /* Type I Format Type Descriptor(2.3.1.6 - Audio Formats) */\ + TUD_AUDIO_DESC_TYPE_I_FORMAT(_nBytesPerSample, _nBitsUsedPerSample),\ + /* Standard AS Isochronous Audio Data Endpoint Descriptor(4.10.1.1) */\ + TUD_AUDIO_DESC_STD_AS_ISO_EP(/*_ep*/ _epin, /*_attr*/ (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 + +#define TUD_AUDIO_SPEAKER_MONO_FB_DESC_LEN (TUD_AUDIO_DESC_IAD_LEN\ + + TUD_AUDIO_DESC_STD_AC_LEN\ + + TUD_AUDIO_DESC_CS_AC_LEN\ + + TUD_AUDIO_DESC_CLK_SRC_LEN\ + + TUD_AUDIO_DESC_INPUT_TERM_LEN\ + + TUD_AUDIO_DESC_OUTPUT_TERM_LEN\ + + TUD_AUDIO_DESC_FEATURE_UNIT_ONE_CHANNEL_LEN\ + + TUD_AUDIO_DESC_STD_AS_INT_LEN\ + + TUD_AUDIO_DESC_STD_AS_INT_LEN\ + + TUD_AUDIO_DESC_CS_AS_INT_LEN\ + + TUD_AUDIO_DESC_TYPE_I_FORMAT_LEN\ + + TUD_AUDIO_DESC_STD_AS_ISO_EP_LEN\ + + TUD_AUDIO_DESC_CS_AS_ISO_EP_LEN\ + + TUD_AUDIO_DESC_STD_AS_ISO_FB_EP_LEN) + +#define TUD_AUDIO_SPEAKER_MONO_FB_DESCRIPTOR(_itfnum, _stridx, _nBytesPerSample, _nBitsUsedPerSample, _epout, _epsize, _epfb) \ + /* 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_DESKTOP_SPEAKER, /*_totallen*/ TUD_AUDIO_DESC_CLK_SRC_LEN+TUD_AUDIO_DESC_INPUT_TERM_LEN+TUD_AUDIO_DESC_OUTPUT_TERM_LEN+TUD_AUDIO_DESC_FEATURE_UNIT_ONE_CHANNEL_LEN, /*_ctrl*/ AUDIO_CS_AS_INTERFACE_CTRL_LATENCY_POS),\ + /* Clock Source Descriptor(4.7.2.1) */\ + TUD_AUDIO_DESC_CLK_SRC(/*_clkid*/ 0x04, /*_attr*/ AUDIO_CLOCK_SOURCE_ATT_INT_FIX_CLK, /*_ctrl*/ (AUDIO_CTRL_R << AUDIO_CLOCK_SOURCE_CTRL_CLK_FRQ_POS), /*_assocTerm*/ 0x01, /*_stridx*/ 0x00),\ + /* Input Terminal Descriptor(4.7.2.4) */\ + TUD_AUDIO_DESC_INPUT_TERM(/*_termid*/ 0x01, /*_termtype*/ AUDIO_TERM_TYPE_USB_STREAMING, /*_assocTerm*/ 0x00, /*_clkid*/ 0x04, /*_nchannelslogical*/ 0x01, /*_channelcfg*/ AUDIO_CHANNEL_CONFIG_NON_PREDEFINED, /*_idxchannelnames*/ 0x00, /*_ctrl*/ 0 * (AUDIO_CTRL_R << AUDIO_IN_TERM_CTRL_CONNECTOR_POS), /*_stridx*/ 0x00),\ + /* Output Terminal Descriptor(4.7.2.5) */\ + TUD_AUDIO_DESC_OUTPUT_TERM(/*_termid*/ 0x03, /*_termtype*/ AUDIO_TERM_TYPE_OUT_DESKTOP_SPEAKER, /*_assocTerm*/ 0x01, /*_srcid*/ 0x02, /*_clkid*/ 0x04, /*_ctrl*/ 0x0000, /*_stridx*/ 0x00),\ + /* Feature Unit Descriptor(4.7.2.8) */\ + TUD_AUDIO_DESC_FEATURE_UNIT_ONE_CHANNEL(/*_unitid*/ 0x02, /*_srcid*/ 0x01, /*_ctrlch0master*/ 0 * (AUDIO_CTRL_RW << AUDIO_FEATURE_UNIT_CTRL_MUTE_POS | AUDIO_CTRL_RW << AUDIO_FEATURE_UNIT_CTRL_VOLUME_POS), /*_ctrlch1*/ 0 * (AUDIO_CTRL_RW << AUDIO_FEATURE_UNIT_CTRL_MUTE_POS | AUDIO_CTRL_RW << AUDIO_FEATURE_UNIT_CTRL_VOLUME_POS), /*_stridx*/ 0x00),\ + /* Standard AS Interface Descriptor(4.9.1) */\ + /* Interface 1, Alternate 0 - default alternate setting with 0 bandwidth */\ + TUD_AUDIO_DESC_STD_AS_INT(/*_itfnum*/ (uint8_t)((_itfnum) + 1), /*_altset*/ 0x00, /*_nEPs*/ 0x00, /*_stridx*/ 0x00),\ + /* Standard AS Interface Descriptor(4.9.1) */\ + /* Interface 1, Alternate 1 - alternate interface for data streaming */\ + TUD_AUDIO_DESC_STD_AS_INT(/*_itfnum*/ (uint8_t)((_itfnum) + 1), /*_altset*/ 0x01, /*_nEPs*/ 0x02, /*_stridx*/ 0x00),\ + /* Class-Specific AS Interface Descriptor(4.9.2) */\ + TUD_AUDIO_DESC_CS_AS_INT(/*_termid*/ 0x01, /*_ctrl*/ AUDIO_CTRL_NONE, /*_formattype*/ AUDIO_FORMAT_TYPE_I, /*_formats*/ AUDIO_DATA_FORMAT_TYPE_I_PCM, /*_nchannelsphysical*/ 0x01, /*_channelcfg*/ AUDIO_CHANNEL_CONFIG_NON_PREDEFINED, /*_stridx*/ 0x00),\ + /* Type I Format Type Descriptor(2.3.1.6 - Audio Formats) */\ + TUD_AUDIO_DESC_TYPE_I_FORMAT(_nBytesPerSample, _nBitsUsedPerSample),\ + /* Standard AS Isochronous Audio Data Endpoint Descriptor(4.10.1.1) */\ + TUD_AUDIO_DESC_STD_AS_ISO_EP(/*_ep*/ _epout, /*_attr*/ (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),\ + /* Standard AS Isochronous Feedback Endpoint Descriptor(4.10.2.1) */\ + TUD_AUDIO_DESC_STD_AS_ISO_FB_EP(/*_ep*/ _epfb, /*_interval*/ 1)\ //------------- TUD_USBTMC/USB488 -------------// #define TUD_USBTMC_APP_CLASS (TUSB_CLASS_APPLICATION_SPECIFIC) @@ -405,15 +573,15 @@ TU_ATTR_WEAK bool tud_vendor_control_complete_cb(uint8_t rhport, tusb_control_re //------------- RNDIS -------------// #if 0 - /* Windows XP */ - #define TUD_RNDIS_ITF_CLASS TUSB_CLASS_CDC - #define TUD_RNDIS_ITF_SUBCLASS CDC_COMM_SUBCLASS_ABSTRACT_CONTROL_MODEL - #define TUD_RNDIS_ITF_PROTOCOL 0xFF /* CDC_COMM_PROTOCOL_MICROSOFT_RNDIS */ +/* Windows XP */ +#define TUD_RNDIS_ITF_CLASS TUSB_CLASS_CDC +#define TUD_RNDIS_ITF_SUBCLASS CDC_COMM_SUBCLASS_ABSTRACT_CONTROL_MODEL +#define TUD_RNDIS_ITF_PROTOCOL 0xFF /* CDC_COMM_PROTOCOL_MICROSOFT_RNDIS */ #else - /* Windows 7+ */ - #define TUD_RNDIS_ITF_CLASS TUSB_CLASS_WIRELESS_CONTROLLER - #define TUD_RNDIS_ITF_SUBCLASS 0x01 - #define TUD_RNDIS_ITF_PROTOCOL 0x03 +/* Windows 7+ */ +#define TUD_RNDIS_ITF_CLASS TUSB_CLASS_WIRELESS_CONTROLLER +#define TUD_RNDIS_ITF_SUBCLASS 0x01 +#define TUD_RNDIS_ITF_PROTOCOL 0x03 #endif // Length of template descriptor: 66 bytes @@ -479,18 +647,18 @@ TU_ATTR_WEAK bool tud_vendor_control_complete_cb(uint8_t rhport, tusb_control_re #define TUD_BTH_ISO_ITF_0(_itfnum, ...) #define TUD_BTH_ISO_ITF_1(_itfnum, _ep_in, _ep_out, ...) TUD_BTH_ISO_ITF(_itfnum, (CFG_TUD_BTH_ISO_ALT_COUNT) - 1, _ep_in, _ep_out, _FIRST(__VA_ARGS__)) #define TUD_BTH_ISO_ITF_2(_itfnum, _ep_in, _ep_out, ...) TUD_BTH_ISO_ITF(_itfnum, (CFG_TUD_BTH_ISO_ALT_COUNT) - 2, _ep_in, _ep_out, _FIRST(__VA_ARGS__)) \ - TUD_BTH_ISO_ITF_1(_itfnum, _ep_in, _ep_out, _REST(__VA_ARGS__)) + TUD_BTH_ISO_ITF_1(_itfnum, _ep_in, _ep_out, _REST(__VA_ARGS__)) #define TUD_BTH_ISO_ITF_3(_itfnum, _ep_in, _ep_out, ...) TUD_BTH_ISO_ITF(_itfnum, (CFG_TUD_BTH_ISO_ALT_COUNT) - 3, _ep_in, _ep_out, _FIRST(__VA_ARGS__)) \ - TUD_BTH_ISO_ITF_2(_itfnum, _ep_in, _ep_out, _REST(__VA_ARGS__)) + TUD_BTH_ISO_ITF_2(_itfnum, _ep_in, _ep_out, _REST(__VA_ARGS__)) #define TUD_BTH_ISO_ITF_4(_itfnum, _ep_in, _ep_out, ...) TUD_BTH_ISO_ITF(_itfnum, (CFG_TUD_BTH_ISO_ALT_COUNT) - 4, _ep_in, _ep_out, _FIRST(__VA_ARGS__)) \ - TUD_BTH_ISO_ITF_3(_itfnum, _ep_in, _ep_out, _REST(__VA_ARGS__)) + TUD_BTH_ISO_ITF_3(_itfnum, _ep_in, _ep_out, _REST(__VA_ARGS__)) #define TUD_BTH_ISO_ITF_5(_itfnum, _ep_in, _ep_out, ...) TUD_BTH_ISO_ITF(_itfnum, (CFG_TUD_BTH_ISO_ALT_COUNT) - 5, _ep_in, _ep_out, _FIRST(__VA_ARGS__)) \ - TUD_BTH_ISO_ITF_4(_itfnum, _ep_in, _ep_out, _REST(__VA_ARGS__)) + TUD_BTH_ISO_ITF_4(_itfnum, _ep_in, _ep_out, _REST(__VA_ARGS__)) #define TUD_BTH_ISO_ITF_6(_itfnum, _ep_in, _ep_out, ...) TUD_BTH_ISO_ITF(_itfnum, (CFG_TUD_BTH_ISO_ALT_COUNT) - 6, _ep_in, _ep_out, _FIRST(__VA_ARGS__)) \ - TUD_BTH_ISO_ITF_5(_itfnum, _ep_in, _ep_out, _REST(__VA_ARGS__)) + TUD_BTH_ISO_ITF_5(_itfnum, _ep_in, _ep_out, _REST(__VA_ARGS__)) #define TUD_BTH_ISO_ITFS(_itfnum, _ep_in, _ep_out, ...) \ - TU_XSTRCAT(TUD_BTH_ISO_ITF_, CFG_TUD_BTH_ISO_ALT_COUNT)(_itfnum, _ep_in, _ep_out, __VA_ARGS__) + TU_XSTRCAT(TUD_BTH_ISO_ITF_, CFG_TUD_BTH_ISO_ALT_COUNT)(_itfnum, _ep_in, _ep_out, __VA_ARGS__) // BT Primary controller descriptor // Interface number, string index, attributes, event endpoint, event endpoint size, interval, data in, data out, data endpoint size, iso endpoint sizes @@ -499,7 +667,7 @@ TU_ATTR_WEAK bool tud_vendor_control_complete_cb(uint8_t rhport, tusb_control_re TUD_BTH_ISO_ITFS(_itfnum + 1, _ep_in + 1, _ep_out + 1, __VA_ARGS__) #ifdef __cplusplus - } +} #endif #endif /* _TUSB_USBD_H_ */ diff --git a/src/portable/st/synopsys/dcd_synopsys.c b/src/portable/st/synopsys/dcd_synopsys.c index cc7181c3d..ca306ff8f 100644 --- a/src/portable/st/synopsys/dcd_synopsys.c +++ b/src/portable/st/synopsys/dcd_synopsys.c @@ -4,6 +4,7 @@ * Copyright (c) 2018 Scott Shawcroft, 2019 William D. Jones for Adafruit Industries * Copyright (c) 2019 Ha Thach (tinyusb.org) * Copyright (c) 2020 Jan Duempelmann + * 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 @@ -46,53 +47,53 @@ #if TUSB_OPT_DEVICE_ENABLED && \ ( (CFG_TUSB_MCU == OPT_MCU_STM32F1 && defined(STM32F1_SYNOPSYS)) || \ - CFG_TUSB_MCU == OPT_MCU_STM32F2 || \ - CFG_TUSB_MCU == OPT_MCU_STM32F4 || \ - CFG_TUSB_MCU == OPT_MCU_STM32F7 || \ - CFG_TUSB_MCU == OPT_MCU_STM32H7 || \ - (CFG_TUSB_MCU == OPT_MCU_STM32L4 && defined(STM32L4_SYNOPSYS)) \ + CFG_TUSB_MCU == OPT_MCU_STM32F2 || \ + CFG_TUSB_MCU == OPT_MCU_STM32F4 || \ + CFG_TUSB_MCU == OPT_MCU_STM32F7 || \ + CFG_TUSB_MCU == OPT_MCU_STM32H7 || \ + (CFG_TUSB_MCU == OPT_MCU_STM32L4 && defined(STM32L4_SYNOPSYS)) \ ) // EP_MAX : Max number of bi-directional endpoints including EP0 // EP_FIFO_SIZE : Size of dedicated USB SRAM #if CFG_TUSB_MCU == OPT_MCU_STM32F1 - #include "stm32f1xx.h" - #define EP_MAX_FS 4 - #define EP_FIFO_SIZE_FS 1280 +#include "stm32f1xx.h" +#define EP_MAX_FS 4 +#define EP_FIFO_SIZE_FS 1280 #elif CFG_TUSB_MCU == OPT_MCU_STM32F2 - #include "stm32f2xx.h" - #define EP_MAX_FS USB_OTG_FS_MAX_IN_ENDPOINTS - #define EP_FIFO_SIZE_FS USB_OTG_FS_TOTAL_FIFO_SIZE +#include "stm32f2xx.h" +#define EP_MAX_FS USB_OTG_FS_MAX_IN_ENDPOINTS +#define EP_FIFO_SIZE_FS USB_OTG_FS_TOTAL_FIFO_SIZE #elif CFG_TUSB_MCU == OPT_MCU_STM32F4 - #include "stm32f4xx.h" - #define EP_MAX_FS USB_OTG_FS_MAX_IN_ENDPOINTS - #define EP_FIFO_SIZE_FS USB_OTG_FS_TOTAL_FIFO_SIZE - #define EP_MAX_HS USB_OTG_HS_MAX_IN_ENDPOINTS - #define EP_FIFO_SIZE_HS USB_OTG_HS_TOTAL_FIFO_SIZE +#include "stm32f4xx.h" +#define EP_MAX_FS USB_OTG_FS_MAX_IN_ENDPOINTS +#define EP_FIFO_SIZE_FS USB_OTG_FS_TOTAL_FIFO_SIZE +#define EP_MAX_HS USB_OTG_HS_MAX_IN_ENDPOINTS +#define EP_FIFO_SIZE_HS USB_OTG_HS_TOTAL_FIFO_SIZE #elif CFG_TUSB_MCU == OPT_MCU_STM32H7 - #include "stm32h7xx.h" - #define EP_MAX_FS 9 - #define EP_FIFO_SIZE_FS 4096 - #define EP_MAX_HS 9 - #define EP_FIFO_SIZE_HS 4096 +#include "stm32h7xx.h" +#define EP_MAX_FS 9 +#define EP_FIFO_SIZE_FS 4096 +#define EP_MAX_HS 9 +#define EP_FIFO_SIZE_HS 4096 #elif CFG_TUSB_MCU == OPT_MCU_STM32F7 - #include "stm32f7xx.h" - #define EP_MAX_FS 6 - #define EP_FIFO_SIZE_FS 1280 - #define EP_MAX_HS 9 - #define EP_FIFO_SIZE_HS 4096 +#include "stm32f7xx.h" +#define EP_MAX_FS 6 +#define EP_FIFO_SIZE_FS 1280 +#define EP_MAX_HS 9 +#define EP_FIFO_SIZE_HS 4096 #elif CFG_TUSB_MCU == OPT_MCU_STM32L4 - #include "stm32l4xx.h" - #define EP_MAX_FS 6 - #define EP_FIFO_SIZE_FS 1280 +#include "stm32l4xx.h" +#define EP_MAX_FS 6 +#define EP_FIFO_SIZE_FS 1280 #else - #error "Unsupported MCUs" +#error "Unsupported MCUs" #endif @@ -104,16 +105,16 @@ // On STM32 we associate Port0 to OTG_FS, and Port1 to OTG_HS #if TUD_OPT_RHPORT == 0 - #define EP_MAX EP_MAX_FS - #define EP_FIFO_SIZE EP_FIFO_SIZE_FS - #define RHPORT_REGS_BASE USB_OTG_FS_PERIPH_BASE - #define RHPORT_IRQn OTG_FS_IRQn +#define EP_MAX EP_MAX_FS +#define EP_FIFO_SIZE EP_FIFO_SIZE_FS +#define RHPORT_REGS_BASE USB_OTG_FS_PERIPH_BASE +#define RHPORT_IRQn OTG_FS_IRQn #else - #define EP_MAX EP_MAX_HS - #define EP_FIFO_SIZE EP_FIFO_SIZE_HS - #define RHPORT_REGS_BASE USB_OTG_HS_PERIPH_BASE - #define RHPORT_IRQn OTG_HS_IRQn +#define EP_MAX EP_MAX_HS +#define EP_FIFO_SIZE EP_FIFO_SIZE_HS +#define RHPORT_REGS_BASE USB_OTG_HS_PERIPH_BASE +#define RHPORT_IRQn OTG_HS_IRQn #endif #define GLOBAL_BASE(_port) ((USB_OTG_GlobalTypeDef*) RHPORT_REGS_BASE) @@ -135,8 +136,18 @@ typedef struct { uint8_t * buffer; uint16_t total_len; uint16_t max_size; + uint8_t interval; } xfer_ctl_t; +// EP size and transfer type report +typedef struct TU_ATTR_PACKED { + // The following format may look complicated but it is the most elegant way of addressing the required fields: EP number, EP direction, and EP transfer type. + // The codes assigned to those fields, according to the USB specification, can be neatly used as indices. + uint16_t ep_size[EP_MAX][2]; ///< dim 1: EP number, dim 2: EP direction denoted by TUSB_DIR_OUT (= 0) and TUSB_DIR_IN (= 1) + bool ep_transfer_type[EP_MAX][2][4]; ///< dim 1: EP number, dim 2: EP direction, dim 3: transfer type, where 0 = Control, 1 = Isochronous, 2 = Bulk, and 3 = Interrupt + ///< I know very well that EP0 can only be used as control EP and we waste space here but for the sake of simplicity we accept that. It is used in a non-persistent way anyway! +} ep_sz_tt_report_t; + typedef volatile uint32_t * usb_fifo_t; xfer_ctl_t xfer_status[EP_MAX][2]; @@ -300,7 +311,6 @@ static void set_speed(uint8_t rhport, tusb_speed_t speed) dev->DCFG |= (bitvalue << USB_OTG_DCFG_DSPD_Pos); } - #if defined(USB_HS_PHYC) static bool USB_HS_PHYCInit(void) { @@ -366,7 +376,7 @@ static void edpt_schedule_packets(uint8_t rhport, uint8_t const epnum, uint8_t c in_ep[epnum].DIEPCTL |= USB_OTG_DIEPCTL_EPENA | USB_OTG_DIEPCTL_CNAK; // For ISO endpoint set correct odd/even bit for next frame. - if ((in_ep[epnum].DIEPCTL & USB_OTG_DIEPCTL_EPTYP) == USB_OTG_DIEPCTL_EPTYP_0) + if ((in_ep[epnum].DIEPCTL & USB_OTG_DIEPCTL_EPTYP) == USB_OTG_DIEPCTL_EPTYP_0 && (XFER_CTL_BASE(epnum, dir))->interval == 1) { // Take odd/even bit from frame counter. uint32_t const odd_frame_now = (dev->DSTS & (1u << USB_OTG_DSTS_FNSOF_Pos)); @@ -383,6 +393,12 @@ static void edpt_schedule_packets(uint8_t rhport, uint8_t const epnum, uint8_t c ((total_bytes << USB_OTG_DOEPTSIZ_XFRSIZ_Pos) & USB_OTG_DOEPTSIZ_XFRSIZ_Msk); out_ep[epnum].DOEPCTL |= USB_OTG_DOEPCTL_EPENA | USB_OTG_DOEPCTL_CNAK; + if ((out_ep[epnum].DOEPCTL & USB_OTG_DOEPCTL_EPTYP) == USB_OTG_DOEPCTL_EPTYP_0 && (XFER_CTL_BASE(epnum, dir))->interval == 1) + { + // Take odd/even bit from frame counter. + uint32_t const odd_frame_now = (dev->DSTS & (1u << USB_OTG_DSTS_FNSOF_Pos)); + out_ep[epnum].DOEPCTL |= (odd_frame_now ? USB_OTG_DOEPCTL_SD0PID_SEVNFRM_Msk : USB_OTG_DOEPCTL_SODDFRM_Msk); + } } } @@ -455,8 +471,8 @@ void dcd_init (uint8_t rhport) if ( rhport == 0 ) usb_otg->GCCFG |= USB_OTG_GCCFG_PWRDWN; usb_otg->GINTMSK |= USB_OTG_GINTMSK_USBRST | USB_OTG_GINTMSK_ENUMDNEM | - USB_OTG_GINTMSK_USBSUSPM | USB_OTG_GINTMSK_WUIM | - USB_OTG_GINTMSK_RXFLVLM | (USE_SOF ? USB_OTG_GINTMSK_SOFM : 0); + USB_OTG_GINTMSK_USBSUSPM | USB_OTG_GINTMSK_WUIM | + USB_OTG_GINTMSK_RXFLVLM | (USE_SOF ? USB_OTG_GINTMSK_SOFM : 0); // Enable global interrupt usb_otg->GAHBCFG |= USB_OTG_GAHBCFG_GINT; @@ -534,12 +550,13 @@ bool dcd_edpt_open (uint8_t rhport, tusb_desc_endpoint_t const * desc_edpt) xfer_ctl_t * xfer = XFER_CTL_BASE(epnum, dir); xfer->max_size = desc_edpt->wMaxPacketSize.size; + xfer->interval = desc_edpt->bInterval; if(dir == TUSB_DIR_OUT) { out_ep[epnum].DOEPCTL |= (1 << USB_OTG_DOEPCTL_USBAEP_Pos) | - (desc_edpt->bmAttributes.xfer << USB_OTG_DOEPCTL_EPTYP_Pos) | - (desc_edpt->wMaxPacketSize.size << USB_OTG_DOEPCTL_MPSIZ_Pos); + (desc_edpt->bmAttributes.xfer << USB_OTG_DOEPCTL_EPTYP_Pos) | + (desc_edpt->wMaxPacketSize.size << USB_OTG_DOEPCTL_MPSIZ_Pos); dev->DAINTMSK |= (1 << (USB_OTG_DAINTMSK_OEPM_Pos + epnum)); } @@ -571,7 +588,7 @@ bool dcd_edpt_open (uint8_t rhport, tusb_desc_endpoint_t const * desc_edpt) // - Bulk/ISO is max(EPSize, remaining-fifo / non-opened-EPIN) uint16_t const fifo_remaining = EP_FIFO_SIZE/4 - _allocated_fifo_words; - uint16_t fifo_size = desc_edpt->wMaxPacketSize.size / 4; + uint16_t fifo_size = (desc_edpt->wMaxPacketSize.size + 3) / 4; // +3 for rounding up to next full word if ( desc_edpt->bmAttributes.xfer != TUSB_XFER_INTERRUPT ) { @@ -595,10 +612,10 @@ bool dcd_edpt_open (uint8_t rhport, tusb_desc_endpoint_t const * desc_edpt) _allocated_fifo_words += fifo_size; in_ep[epnum].DIEPCTL |= (1 << USB_OTG_DIEPCTL_USBAEP_Pos) | - (epnum << USB_OTG_DIEPCTL_TXFNUM_Pos) | - (desc_edpt->bmAttributes.xfer << USB_OTG_DIEPCTL_EPTYP_Pos) | - (desc_edpt->bmAttributes.xfer != TUSB_XFER_ISOCHRONOUS ? USB_OTG_DOEPCTL_SD0PID_SEVNFRM : 0) | - (desc_edpt->wMaxPacketSize.size << USB_OTG_DIEPCTL_MPSIZ_Pos); + (epnum << USB_OTG_DIEPCTL_TXFNUM_Pos) | + (desc_edpt->bmAttributes.xfer << USB_OTG_DIEPCTL_EPTYP_Pos) | + (desc_edpt->bmAttributes.xfer != TUSB_XFER_ISOCHRONOUS ? USB_OTG_DOEPCTL_SD0PID_SEVNFRM : 0) | + (desc_edpt->wMaxPacketSize.size << USB_OTG_DIEPCTL_MPSIZ_Pos); dev->DAINTMSK |= (1 << (USB_OTG_DAINTMSK_IEPM_Pos + epnum)); } @@ -821,7 +838,7 @@ static void handle_rxflvl_ints(uint8_t rhport, USB_OTG_OUTEndpointTypeDef * out_ switch(pktsts) { case 0x01: // Global OUT NAK (Interrupt) - break; + break; case 0x02: // Out packet recvd { @@ -849,18 +866,18 @@ static void handle_rxflvl_ints(uint8_t rhport, USB_OTG_OUTEndpointTypeDef * out_ case 0x04: // Setup packet done (Interrupt) out_ep[epnum].DOEPTSIZ |= (3 << USB_OTG_DOEPTSIZ_STUPCNT_Pos); - break; + break; case 0x06: // Setup packet recvd // We can receive up to three setup packets in succession, but // only the last one is valid. _setup_packet[0] = (* rx_fifo); _setup_packet[1] = (* rx_fifo); - break; + break; default: // Invalid TU_BREAKPOINT(); - break; + break; } } @@ -1040,6 +1057,12 @@ void dcd_int_handler(uint8_t rhport) // IEPINT bit read-only handle_epin_ints(rhport, dev, in_ep); } + +// // Check for Incomplete isochronous IN transfer +// if(int_status & USB_OTG_GINTSTS_IISOIXFR) { +// printf(" IISOIXFR!\r\n"); +//// TU_LOG2(" IISOIXFR!\r\n"); +// } } #endif diff --git a/src/tusb.h b/src/tusb.h index c252a5789..8b0dd103c 100644 --- a/src/tusb.h +++ b/src/tusb.h @@ -76,6 +76,10 @@ #include "class/msc/msc_device.h" #endif +#if CFG_TUD_AUDIO + #include "class/audio/audio_device.h" +#endif + #if CFG_TUD_MIDI #include "class/midi/midi_device.h" #endif diff --git a/src/tusb_option.h b/src/tusb_option.h index 7e486bff0..e1ee45bde 100644 --- a/src/tusb_option.h +++ b/src/tusb_option.h @@ -207,6 +207,10 @@ #define CFG_TUD_HID 0 #endif +#ifndef CFG_TUD_AUDIO + #define CFG_TUD_AUDIO 0 +#endif + #ifndef CFG_TUD_MIDI #define CFG_TUD_MIDI 0 #endif