Initial commit

This commit is contained in:
Gericom
2025-11-23 14:02:39 +01:00
commit 3bb550c12e
53 changed files with 4301 additions and 0 deletions

View File

@@ -0,0 +1,137 @@
#---------------------------------------------------------------------------------
.SUFFIXES:
#---------------------------------------------------------------------------------
ifeq ($(strip $(DEVKITARM)),)
$(error "Please set DEVKITARM in your environment. export DEVKITARM=<path to>devkitARM")
endif
include $(DEVKITARM)/ds_rules
#---------------------------------------------------------------------------------
# BUILD is the directory where object files & intermediate files will be placed
# SOURCES is a list of directories containing source code
# INCLUDES is a list of directories containing extra header files
# DATA is a list of directories containing binary files
# all directories are relative to this makefile
#---------------------------------------------------------------------------------
BUILD := build
SOURCES := source \
../common/ipc \
source/ipcServices \
../../../platform \
../../../libs/tinyusb/src \
../../../libs/tinyusb/src/device \
../../../libs/tinyusb/src/class/audio \
../../../libs/tinyusb/src/class/cdc \
../../../libs/tinyusb/src/class/msc \
../../../libs/tinyusb/src/common
INCLUDES := source include build ../common ../../../libs/tinyusb/src ../../../platform
DATA :=
#---------------------------------------------------------------------------------
# options for code generation
#---------------------------------------------------------------------------------
ARCH := -marm -mthumb-interwork -DLIBTWL_ARM7 -DARM7
CFLAGS := -g -Wall -O2\
-mcpu=arm7tdmi -mtune=arm7tdmi -fomit-frame-pointer\
-ffunction-sections -fdata-sections\
-ffast-math \
$(ARCH)
CFLAGS += $(INCLUDE)
CXXFLAGS := $(CFLAGS) -std=gnu++23 -Wno-volatile -fno-rtti -fno-exceptions -fno-threadsafe-statics\
-Wsuggest-override -Werror=suggest-override
ASFLAGS := -g $(ARCH)
LDFLAGS = -specs=../dldi_ds_arm7.specs -g $(ARCH) -Wl,--nmagic -Wl,-Map,$(notdir $*).map,--gc-sections -ffunction-sections -fdata-sections
LIBS := -ltwl7 -lnds7
#---------------------------------------------------------------------------------
# list of directories containing libraries, this must be the top level containing
# include and lib
#---------------------------------------------------------------------------------
LIBDIRS := $(LIBNDS) $(CURDIR)/../../../libs/libtwl/libtwl7 $(CURDIR)/../../../libs/libtwl/common
#---------------------------------------------------------------------------------
# no real need to edit anything past this point unless you need to add additional
# rules for different file extensions
#---------------------------------------------------------------------------------
ifneq ($(BUILD),$(notdir $(CURDIR)))
#---------------------------------------------------------------------------------
export ARM7ELF := $(CURDIR)/$(TARGET).elf
export DEPSDIR := $(CURDIR)/$(BUILD)
export VPATH := $(foreach dir,$(SOURCES),$(CURDIR)/$(dir))
CFILES := $(foreach dir,$(SOURCES),$(notdir $(wildcard $(dir)/*.c)))
CPPFILES := $(foreach dir,$(SOURCES),$(notdir $(wildcard $(dir)/*.cpp)))
SFILES := $(foreach dir,$(SOURCES),$(notdir $(wildcard $(dir)/*.s)))
BINFILES := $(foreach dir,$(DATA),$(notdir $(wildcard $(dir)/*.*)))
export OFILES := $(addsuffix .o,$(BINFILES)) \
$(CPPFILES:.cpp=.o) $(CFILES:.c=.o) $(SFILES:.s=.o)
export INCLUDE := $(foreach dir,$(INCLUDES),-I$(CURDIR)/$(dir)) \
$(foreach dir,$(LIBDIRS),-I$(dir)/include) \
-I$(CURDIR)/$(BUILD)
export LIBPATHS := $(foreach dir,$(LIBDIRS),-L$(dir)/lib)
#---------------------------------------------------------------------------------
# use CXX for linking C++ projects, CC for standard C
#---------------------------------------------------------------------------------
ifeq ($(strip $(CPPFILES)),)
#---------------------------------------------------------------------------------
export LD := $(CC)
#---------------------------------------------------------------------------------
else
#---------------------------------------------------------------------------------
export LD := $(CXX)
#---------------------------------------------------------------------------------
endif
#---------------------------------------------------------------------------------
.PHONY: $(BUILD) clean
#---------------------------------------------------------------------------------
$(BUILD):
@[ -d $@ ] || mkdir -p $@
@$(MAKE) --no-print-directory -C $(BUILD) -f $(CURDIR)/Makefile
#---------------------------------------------------------------------------------
clean:
@echo clean ...
@rm -fr $(BUILD) *.elf
#---------------------------------------------------------------------------------
else
DEPENDS := $(OFILES:.o=.d)
#---------------------------------------------------------------------------------
# main targets
#---------------------------------------------------------------------------------
$(ARM7ELF) : $(OFILES)
@echo linking $(notdir $@)
@$(LD) $(LDFLAGS) $(OFILES) $(LIBPATHS) $(LIBS) -o $@
#---------------------------------------------------------------------------------
# you need a rule like this for each extension you use as binary data
#---------------------------------------------------------------------------------
%.bin.o : %.bin
#---------------------------------------------------------------------------------
@echo $(notdir $<)
@$(bin2o)
-include $(DEPENDS)
#---------------------------------------------------------------------------------------
endif
#---------------------------------------------------------------------------------------

View File

@@ -0,0 +1,232 @@
/*--------------------------------------------------------------------------------
This Source Code Form is subject to the terms of the Mozilla Public License,
v. 2.0. If a copy of the MPL was not distributed with this file, You can
obtain one at https://mozilla.org/MPL/2.0/.
--------------------------------------------------------------------------------*/
OUTPUT_FORMAT("elf32-littlearm", "elf32-bigarm", "elf32-littlearm")
OUTPUT_ARCH(arm)
ENTRY(_start)
PHDRS {
crt0 PT_LOAD FLAGS(7);
arm7 PT_LOAD FLAGS(7);
arm7i PT_LOAD FLAGS(0x100007);
}
MEMORY {
ewram : ORIGIN = 0x02380000, LENGTH = 12M - 512K
rom : ORIGIN = 0x08000000, LENGTH = 32M
iwram : ORIGIN = 0x037fc000, LENGTH = (96K - 16K)
twl_ewram : ORIGIN = 0x02e80000, LENGTH = 512K - 64K
twl_iwram : ORIGIN = 0x03000000, LENGTH = 256K
}
__iwram_start = ORIGIN(iwram);
__iwram_top = ORIGIN(iwram)+ LENGTH(iwram);
__sp_irq = __iwram_top - 0x100;
__sp_svc = __sp_irq - 0x100;
__sp_usr = __sp_svc - 0x100;
__irq_flags = 0x04000000 - 8;
__irq_flagsaux = 0x04000000 - 0x40;
__irq_vector = 0x04000000 - 4;
SECTIONS
{
.twl :
{
__arm7i_lma__ = LOADADDR(.twl);
__arm7i_start__ = .;
*(.twl)
*.twl*(.text .stub .text.* .gnu.linkonce.t.*)
*.twl*(.rodata)
*.twl*(.roda)
*.twl*(.rodata.*)
*.twl*(.data)
*.twl*(.data.*)
*.twl*(.gnu.linkonce.d*)
. = ALIGN(4);
__arm7i_end__ = .;
} >twl_iwram AT>twl_ewram :arm7i
.twl_bss ALIGN(4) (NOLOAD) :
{
__twl_bss_start__ = .;
*(.twl_bss)
*.twl.*(.dynbss)
*.twl.*(.gnu.linkonce.b*)
*.twl.*(.bss*)
*.twl.*(COMMON)
. = ALIGN(4);
__twl_bss_end__ = .;
} >twl_iwram :NONE
.crt0 :
{
KEEP (*(.crt0))
. = ALIGN(4); /* REQUIRED. LD is flaky without it. */
} >ewram :crt0
.text :
{
__arm7_lma__ = LOADADDR(.text);
__arm7_start__ = .;
KEEP (*(SORT_NONE(.init)))
*(.plt)
*(.text .stub .text.* .gnu.linkonce.t.*)
KEEP (*(.text.*personality*))
/* .gnu.warning sections are handled specially by elf32.em. */
*(.gnu.warning)
*(.glue_7t) *(.glue_7) *(.vfp11_veneer)
. = ALIGN(4); /* REQUIRED. LD is flaky without it. */
} >iwram AT>ewram :arm7
.fini :
{
KEEP (*(.fini))
} >iwram AT>ewram
.rodata :
{
*(.rodata)
*all.rodata*(*)
*(.roda)
*(.rodata.*)
*(.gnu.linkonce.r*)
SORT(CONSTRUCTORS)
. = ALIGN(4); /* REQUIRED. LD is flaky without it. */
} >iwram AT>ewram
.ARM.extab : { *(.ARM.extab* .gnu.linkonce.armextab.*) } >iwram AT>ewram
.ARM.exidx : {
__exidx_start = .;
*(.ARM.exidx* .gnu.linkonce.armexidx.*)
__exidx_end = .;
} >iwram AT>ewram
/* Ensure the __preinit_array_start label is properly aligned. We
could instead move the label definition inside the section, but
the linker would then create the section even if it turns out to
be empty, which isn't pretty. */
.preinit_array : {
. = ALIGN(32 / 8);
PROVIDE (__preinit_array_start = .);
KEEP (*(.preinit_array))
PROVIDE (__preinit_array_end = .);
} >iwram AT>ewram
.init_array : {
PROVIDE (__init_array_start = .);
KEEP (*(.init_array))
PROVIDE (__init_array_end = .);
} >iwram AT>ewram
.fini_array : {
PROVIDE (__fini_array_start = .);
KEEP (*(.fini_array))
PROVIDE (__fini_array_end = .);
} >iwram AT>ewram
.ctors :
{
/* gcc uses crtbegin.o to find the start of the constructors, so
we make sure it is first. Because this is a wildcard, it
doesn't matter if the user does not actually link against
crtbegin.o; the linker won't look for a file to match a
wildcard. The wildcard also means that it doesn't matter which
directory crtbegin.o is in. */
KEEP (*crtbegin.o(.ctors))
KEEP (*(EXCLUDE_FILE (*crtend.o) .ctors))
KEEP (*(SORT(.ctors.*)))
KEEP (*(.ctors))
. = ALIGN(4); /* REQUIRED. LD is flaky without it. */
} >iwram AT>ewram
.dtors :
{
KEEP (*crtbegin.o(.dtors))
KEEP (*(EXCLUDE_FILE (*crtend.o) .dtors))
KEEP (*(SORT(.dtors.*)))
KEEP (*(.dtors))
. = ALIGN(4); /* REQUIRED. LD is flaky without it. */
} >iwram AT>ewram
.eh_frame :
{
KEEP (*(.eh_frame))
. = ALIGN(4); /* REQUIRED. LD is flaky without it. */
} >iwram AT>ewram
.gcc_except_table :
{
*(.gcc_except_table)
. = ALIGN(4); /* REQUIRED. LD is flaky without it. */
} >iwram AT>ewram
.jcr : { KEEP (*(.jcr)) } >iwram AT>ewram
.got : { *(.got.plt) *(.got) } >iwram AT>ewram
.data ALIGN(4) : {
__data_start = ABSOLUTE(.);
*(.data)
*(.data.*)
*(.gnu.linkonce.d*)
CONSTRUCTORS
. = ALIGN(4);
__data_end = ABSOLUTE(.) ;
} >iwram AT>ewram
.bss ALIGN(4) (NOLOAD) :
{
__arm7_end__ = .;
__bss_start = ABSOLUTE(.);
__bss_start__ = ABSOLUTE(.);
*(.dynbss)
*(.gnu.linkonce.b*)
*(.bss*)
*(COMMON)
. = ALIGN(4); /* REQUIRED. LD is flaky without it. */
__bss_end__ = ABSOLUTE(.);
__end__ = ABSOLUTE(.);
} >iwram
/* Stabs debugging sections. */
.stab 0 : { *(.stab) }
.stabstr 0 : { *(.stabstr) }
.stab.excl 0 : { *(.stab.excl) }
.stab.exclstr 0 : { *(.stab.exclstr) }
.stab.index 0 : { *(.stab.index) }
.stab.indexstr 0 : { *(.stab.indexstr) }
.comment 0 : { *(.comment) }
/* DWARF debug sections.
Symbols in the DWARF debugging sections are relative to the beginning
of the section so we begin them at 0. */
/* DWARF 1 */
.debug 0 : { *(.debug) }
.line 0 : { *(.line) }
/* GNU DWARF 1 extensions */
.debug_srcinfo 0 : { *(.debug_srcinfo) }
.debug_sfnames 0 : { *(.debug_sfnames) }
/* DWARF 1.1 and DWARF 2 */
.debug_aranges 0 : { *(.debug_aranges) }
.debug_pubnames 0 : { *(.debug_pubnames) }
/* DWARF 2 */
.debug_info 0 : { *(.debug_info) }
.debug_abbrev 0 : { *(.debug_abbrev) }
.debug_line 0 : { *(.debug_line) }
.debug_frame 0 : { *(.debug_frame) }
.debug_str 0 : { *(.debug_str) }
.debug_loc 0 : { *(.debug_loc) }
.debug_macinfo 0 : { *(.debug_macinfo) }
/* SGI/MIPS DWARF 2 extensions */
.debug_weaknames 0 : { *(.debug_weaknames) }
.debug_funcnames 0 : { *(.debug_funcnames) }
.debug_typenames 0 : { *(.debug_typenames) }
.debug_varnames 0 : { *(.debug_varnames) }
/* These must appear regardless of . */
}

View File

@@ -0,0 +1,8 @@
%rename link old_link
*link:
%(old_link) -T ../dldi_ds_arm7.ld%s --gc-sections --no-warn-rwx-segments
*startfile:
ds_arm7_crt0%O%s crti%O%s crtbegin%O%s

View File

@@ -0,0 +1,8 @@
#pragma once
/// @brief Enum representing the arm7 state.
enum class Arm7State
{
Idle,
ExitRequested
};

View File

@@ -0,0 +1,10 @@
#pragma once
/// @brief Enum representing the exit mode of launcher.
enum class ExitMode
{
/// @brief Reset the system (DSi mode only).
Reset,
/// @brief Power off the system.
PowerOff
};

View File

@@ -0,0 +1,5 @@
#pragma once
#include <nds.h>
#include <libtwl/rtos/rtosMutex.h>
extern rtos_mutex_t gCardMutex;

View File

@@ -0,0 +1,550 @@
#include "common.h"
#include <libtwl/rtos/rtosIrq.h>
#include <libtwl/rtos/rtosThread.h>
#include <libtwl/rtos/rtosEvent.h>
#include <libtwl/sound/soundChannel.h>
#include <libtwl/timer/timer.h>
#include <libtwl/sound/sound.h>
#include <libtwl/ipc/ipcSync.h>
#include <libtwl/ipc/ipcFifoSystem.h>
#include <libtwl/sys/sysPower.h>
#include <libtwl/sio/sioRtc.h>
#include <libtwl/sio/sio.h>
#include <libtwl/gfx/gfxStatus.h>
#include <libtwl/mem/memSwap.h>
#include <libtwl/i2c/i2cMcu.h>
#include <libtwl/spi/spiPmic.h>
#include "ExitMode.h"
#include "Arm7State.h"
#include "tusb.h"
#include "usb_descriptors.h"
const uint32_t sample_rates[] = { 44100 };
uint32_t current_sample_rate = 44100;
#define N_SAMPLE_RATES TU_ARRAY_SIZE(sample_rates)
enum
{
VOLUME_CTRL_0_DB = 0,
VOLUME_CTRL_10_DB = 2560,
VOLUME_CTRL_20_DB = 5120,
VOLUME_CTRL_30_DB = 7680,
VOLUME_CTRL_40_DB = 10240,
VOLUME_CTRL_50_DB = 12800,
VOLUME_CTRL_60_DB = 15360,
VOLUME_CTRL_70_DB = 17920,
VOLUME_CTRL_80_DB = 20480,
VOLUME_CTRL_90_DB = 23040,
VOLUME_CTRL_100_DB = 25600,
VOLUME_CTRL_SILENCE = 0x8000,
};
// Audio controls
// Current states
int8_t mute[CFG_TUD_AUDIO_FUNC_1_N_CHANNELS_RX + 1]; // +1 for master channel 0
int16_t volume[CFG_TUD_AUDIO_FUNC_1_N_CHANNELS_RX + 1]; // +1 for master channel 0
// Buffer for speaker data
int32_t spk_buf[CFG_TUD_AUDIO_FUNC_1_EP_OUT_SW_BUF_SZ / 4];
// Speaker data size received in the last frame
volatile int spk_data_size;
// Resolution per format
const uint8_t resolutions_per_format[CFG_TUD_AUDIO_FUNC_1_N_FORMATS] = { CFG_TUD_AUDIO_FUNC_1_FORMAT_1_RESOLUTION_RX };
// Current resolution, update on format change
volatile uint8_t current_resolution;
#define AUDIO_BUFFER_LENGTH 2048
static bool sAudioStarted;
#define AUDIO_STREAM_PLAYER_TIMER 3
#define AUDIO_STREAM_PLAYER_SAFETY_BLOCKS 4
#define AUDIO_STREAM_PLAYER_THREAD_PRIORITY 15
#define AUDIO_STREAM_PLAYER_RING_BLOCKS 32
#define AUDIO_STREAM_PLAYER_BLOCK_SAMPLES 64
static rtos_event_t sAudioBlockEvent;
static rtos_thread_t sAudioThread;
static u32 sAudioThreadStack[512];
static s16 sAudioRingL[AUDIO_STREAM_PLAYER_RING_BLOCKS][AUDIO_STREAM_PLAYER_BLOCK_SAMPLES] alignas(4);
static s16 sAudioRingR[AUDIO_STREAM_PLAYER_RING_BLOCKS][AUDIO_STREAM_PLAYER_BLOCK_SAMPLES] alignas(4);
static volatile u8 sReadBlock;
static volatile u8 sWriteBlock;
rtos_mutex_t gCardMutex;
static rtos_thread_t sUsbThread;
static u32 sUsbThreadStack[512];
static rtos_event_t sVBlankEvent;
static ExitMode sExitMode;
static Arm7State sState;
static volatile u8 sMcuIrqFlag = false;
static void vblankIrq(u32 irqMask)
{
rtos_signalEvent(&sVBlankEvent);
}
static void mcuIrq(u32 irq2Mask)
{
sMcuIrqFlag = true;
}
static void checkMcuIrq(void)
{
// mcu only exists in DSi mode
if (isDSiMode())
{
// check and ack the flag atomically
if (mem_swapByte(false, &sMcuIrqFlag))
{
// check the irq mask
u32 irqMask = mcu_getIrqMask();
if (irqMask & MCU_IRQ_RESET)
{
// power button was released
sExitMode = ExitMode::Reset;
sState = Arm7State::ExitRequested;
}
else if (irqMask & MCU_IRQ_POWER_OFF)
{
// power button was held long to trigger a power off
sExitMode = ExitMode::PowerOff;
sState = Arm7State::ExitRequested;
}
}
}
}
static void initializeVBlankIrq()
{
rtos_createEvent(&sVBlankEvent);
rtos_setIrqFunc(RTOS_IRQ_VBLANK, vblankIrq);
rtos_enableIrqMask(RTOS_IRQ_VBLANK);
gfx_setVBlankIrqEnabled(true);
}
static void usbThreadMain(void* arg)
{
while (true)
{
tud_task();
}
}
static void initializeArm7()
{
rtos_initIrq();
rtos_startMainThread();
ipc_initFifoSystem();
// clear sound registers
dmaFillWords(0, (void*)0x04000400, 0x100);
pmic_setAmplifierEnable(true);
sys_setSoundPower(true);
readUserSettings();
pmic_setPowerLedBlink(PMIC_CONTROL_POWER_LED_BLINK_NONE);
sio_setGpioSiIrq(false);
sio_setGpioMode(RCNT0_L_MODE_GPIO);
rtc_init();
snd_setMasterVolume(127);
snd_setMasterEnable(true);
initializeVBlankIrq();
if (isDSiMode())
{
rtos_setIrq2Func(RTOS_IRQ2_MCU, mcuIrq);
rtos_enableIrq2Mask(RTOS_IRQ2_MCU);
}
tusb_rhport_init_t dev_init =
{
.role = TUSB_ROLE_DEVICE,
.speed = TUSB_SPEED_AUTO
};
tusb_init(0, &dev_init);
sReadBlock = 0;
sWriteBlock = 0;
rtos_createThread(&sUsbThread, 3, usbThreadMain, NULL, sUsbThreadStack, sizeof(sUsbThreadStack));
rtos_wakeupThread(&sUsbThread);
ipc_setArm7SyncBits(7);
}
static void updateArm7IdleState()
{
checkMcuIrq();
if (sState == Arm7State::ExitRequested)
{
snd_setMasterVolume(0); // mute sound
}
}
static bool performExit(ExitMode exitMode)
{
switch (exitMode)
{
case ExitMode::Reset:
{
mcu_setWarmBootFlag(true);
mcu_hardReset();
break;
}
case ExitMode::PowerOff:
{
pmic_shutdown();
break;
}
}
while (true); // wait infinitely for exit
}
static void updateArm7ExitRequestedState()
{
performExit(sExitMode);
}
static void updateArm7()
{
switch (sState)
{
case Arm7State::Idle:
{
updateArm7IdleState();
break;
}
case Arm7State::ExitRequested:
{
updateArm7ExitRequestedState();
break;
}
}
}
// Helper for clock get requests
static bool tud_audio_clock_get_request(uint8_t rhport, audio_control_request_t const *request)
{
if (request->bControlSelector == AUDIO_CS_CTRL_SAM_FREQ)
{
if (request->bRequest == AUDIO_CS_REQ_CUR)
{
audio_control_cur_4_t curf = { (int32_t) tu_htole32(current_sample_rate) };
return tud_audio_buffer_and_schedule_control_xfer(rhport, (tusb_control_request_t const *)request, &curf, sizeof(curf));
}
else if (request->bRequest == AUDIO_CS_REQ_RANGE)
{
audio_control_range_4_n_t(N_SAMPLE_RATES) rangef =
{
.wNumSubRanges = tu_htole16(N_SAMPLE_RATES)
};
for (uint8_t i = 0; i < N_SAMPLE_RATES; i++)
{
rangef.subrange[i].bMin = (int32_t) sample_rates[i];
rangef.subrange[i].bMax = (int32_t) sample_rates[i];
rangef.subrange[i].bRes = 0;
}
return tud_audio_buffer_and_schedule_control_xfer(rhport, (const tusb_control_request_t*)request, &rangef, sizeof(rangef));
}
}
else if (request->bControlSelector == AUDIO_CS_CTRL_CLK_VALID
&& request->bRequest == AUDIO_CS_REQ_CUR)
{
audio_control_cur_1_t cur_valid = { .bCur = 1 };
return tud_audio_buffer_and_schedule_control_xfer(rhport, (const tusb_control_request_t*)request, &cur_valid, sizeof(cur_valid));
}
return false;
}
// Helper for clock set requests
static bool tud_audio_clock_set_request(u8 rhport, const audio_control_request_t* request, const u8* buf)
{
(void)rhport;
if (request->bControlSelector == AUDIO_CS_CTRL_SAM_FREQ)
{
current_sample_rate = (u32)((const audio_control_cur_4_t*)buf)->bCur;
return true;
}
else
{
return false;
}
}
// Helper for feature unit get requests
static bool tud_audio_feature_unit_get_request(u8 rhport, const audio_control_request_t* request)
{
if (request->bControlSelector == AUDIO_FU_CTRL_MUTE && request->bRequest == AUDIO_CS_REQ_CUR)
{
audio_control_cur_1_t mute1 = { .bCur = mute[request->bChannelNumber] };
return tud_audio_buffer_and_schedule_control_xfer(rhport, (const tusb_control_request_t*)request, &mute1, sizeof(mute1));
}
else if (request->bControlSelector == AUDIO_FU_CTRL_VOLUME)
{
if (request->bRequest == AUDIO_CS_REQ_RANGE)
{
audio_control_range_2_n_t(1) range_vol =
{
tu_htole16(1),
{ { tu_htole16(-VOLUME_CTRL_50_DB), tu_htole16(VOLUME_CTRL_0_DB), tu_htole16(256) } }
};
return tud_audio_buffer_and_schedule_control_xfer(rhport, (const tusb_control_request_t*)request, &range_vol, sizeof(range_vol));
}
else if (request->bRequest == AUDIO_CS_REQ_CUR)
{
audio_control_cur_2_t cur_vol = { .bCur = tu_htole16(volume[request->bChannelNumber]) };
return tud_audio_buffer_and_schedule_control_xfer(rhport, (const tusb_control_request_t*)request, &cur_vol, sizeof(cur_vol));
}
}
return false;
}
// Helper for feature unit set requests
static bool tud_audio_feature_unit_set_request(u8 rhport, const audio_control_request_t* request, const u8* buf)
{
(void)rhport;
if (request->bControlSelector == AUDIO_FU_CTRL_MUTE)
{
mute[request->bChannelNumber] = ((const audio_control_cur_1_t*)buf)->bCur;
return true;
}
else if (request->bControlSelector == AUDIO_FU_CTRL_VOLUME)
{
volume[request->bChannelNumber] = ((const audio_control_cur_2_t*)buf)->bCur;
return true;
}
else
{
return false;
}
}
// Invoked when audio class specific get request received for an entity
bool tud_audio_get_req_entity_cb(u8 rhport, const tusb_control_request_t* p_request)
{
auto request = (const audio_control_request_t*)p_request;
switch (request->bEntityID)
{
case UAC2_ENTITY_CLOCK:
{
return tud_audio_clock_get_request(rhport, request);
}
case UAC2_ENTITY_FEATURE_UNIT:
{
return tud_audio_feature_unit_get_request(rhport, request);
}
default:
{
return false;
}
}
}
// Invoked when audio class specific set request received for an entity
bool tud_audio_set_req_entity_cb(u8 rhport, const tusb_control_request_t* p_request, u8* buf)
{
auto request = (const audio_control_request_t*)p_request;
switch (request->bEntityID)
{
case UAC2_ENTITY_FEATURE_UNIT:
{
return tud_audio_feature_unit_set_request(rhport, request, buf);
}
case UAC2_ENTITY_CLOCK:
{
return tud_audio_clock_set_request(rhport, request, buf);
}
default:
{
return false;
}
}
}
bool tud_audio_set_itf_close_EP_cb(uint8_t rhport, const tusb_control_request_t* p_request)
{
(void)rhport;
const u8 itf = tu_u16_low(tu_le16toh(p_request->wIndex));
const u8 alt = tu_u16_low(tu_le16toh(p_request->wValue));
(void)itf;
(void)alt;
if (sAudioStarted)
{
// Stop audio playback
snd_stopChannel(0);
snd_stopChannel(1);
tmr_stop(AUDIO_STREAM_PLAYER_TIMER);
rtos_disableIrqMask(RTOS_IRQ_TIMER(AUDIO_STREAM_PLAYER_TIMER));
rtos_ackIrqMask(RTOS_IRQ_TIMER(AUDIO_STREAM_PLAYER_TIMER));
rtos_sleepThread(&sAudioThread);
sReadBlock = 0;
sWriteBlock = 0;
sAudioStarted = false;
}
return true;
}
bool tud_audio_set_itf_cb(uint8_t rhport, const tusb_control_request_t* p_request)
{
(void)rhport;
uint8_t const itf = tu_u16_low(tu_le16toh(p_request->wIndex));
uint8_t const alt = tu_u16_low(tu_le16toh(p_request->wValue));
spk_data_size = 0;
if (alt != 0)
{
current_resolution = resolutions_per_format[alt - 1];
}
return true;
}
static void fillRingBlock(u32 block)
{
s16* blockPtrL = &sAudioRingL[block][0];
s16* blockPtrR = &sAudioRingR[block][0];
tud_audio_read(spk_buf, AUDIO_STREAM_PLAYER_BLOCK_SAMPLES * 4);
s16* src = (s16*)spk_buf;
s16* limit = (s16*)spk_buf + AUDIO_STREAM_PLAYER_BLOCK_SAMPLES * 2;
while (src < limit)
{
*blockPtrL++ = *src++;
*blockPtrR++ = *src++;
}
}
static void audioThreadMain(void* arg)
{
do
{
bool doUpdate = true;
while (doUpdate)
{
u32 writeBlock = sWriteBlock;
int freeBlocks = sReadBlock - writeBlock - 1;
if (freeBlocks < 0)
{
freeBlocks += AUDIO_STREAM_PLAYER_RING_BLOCKS;
}
if (freeBlocks > AUDIO_STREAM_PLAYER_SAFETY_BLOCKS && tud_audio_available() >= AUDIO_STREAM_PLAYER_BLOCK_SAMPLES * 4)
{
fillRingBlock(writeBlock);
if (++writeBlock == AUDIO_STREAM_PLAYER_RING_BLOCKS)
{
writeBlock = 0;
}
sWriteBlock = writeBlock;
}
else
{
doUpdate = false;
}
}
rtos_waitEvent(&sAudioBlockEvent, false, true);
} while(true);
}
void tud_audio_feedback_params_cb(u8 func_id, u8 alt_itf, audio_feedback_params_t* feedback_param)
{
(void)func_id;
(void)alt_itf;
feedback_param->method = AUDIO_FEEDBACK_METHOD_FIFO_COUNT;
feedback_param->sample_freq = current_sample_rate;
}
bool tud_audio_rx_done_pre_read_cb(u8 rhport, u16 n_bytes_received, u8 func_id, u8 ep_out, u8 cur_alt_setting)
{
(void)rhport;
(void)func_id;
(void)ep_out;
(void)cur_alt_setting;
while (!sAudioStarted && tud_audio_available() >= AUDIO_STREAM_PLAYER_BLOCK_SAMPLES * 4)
{
fillRingBlock(sWriteBlock);
sWriteBlock++;
if (sWriteBlock == 8)
{
u32 timer = -((33513982 + current_sample_rate) / (current_sample_rate * 2));
REG_SOUNDxSAD(0) = (u32)&sAudioRingL[0][0];
REG_SOUNDxSAD(1) = (u32)&sAudioRingR[0][0];
REG_SOUNDxTMR(0) = timer;
REG_SOUNDxTMR(1) = timer;
REG_SOUNDxPNT(0) = 0;
REG_SOUNDxPNT(1) = 0;
REG_SOUNDxLEN(0) = sizeof(sAudioRingL) >> 2;
REG_SOUNDxLEN(1) = sizeof(sAudioRingR) >> 2;
REG_SOUNDxCNT(0) = SOUNDCNT_VOLUME(127) | SOUNDCNT_MODE_LOOP | SOUNDCNT_FORMAT_PCM16;
REG_SOUNDxCNT(1) = SOUNDCNT_VOLUME(127) | SOUNDCNT_PAN(127) | SOUNDCNT_MODE_LOOP | SOUNDCNT_FORMAT_PCM16;
rtos_createEvent(&sAudioBlockEvent);
rtos_disableIrqMask(RTOS_IRQ_TIMER(AUDIO_STREAM_PLAYER_TIMER));
rtos_ackIrqMask(RTOS_IRQ_TIMER(AUDIO_STREAM_PLAYER_TIMER));
rtos_setIrqFunc(RTOS_IRQ_TIMER(AUDIO_STREAM_PLAYER_TIMER), [] (u32 irqMask)
{
u32 readBlock = sReadBlock;
if (++readBlock == AUDIO_STREAM_PLAYER_RING_BLOCKS)
{
readBlock = 0;
}
sReadBlock = readBlock;
rtos_signalEvent(&sAudioBlockEvent);
});
tmr_configure(AUDIO_STREAM_PLAYER_TIMER, TMCNT_H_CLK_SYS_DIV_64, timer << 1, true);
rtos_createThread(&sAudioThread, AUDIO_STREAM_PLAYER_THREAD_PRIORITY, audioThreadMain,
nullptr, sAudioThreadStack, sizeof(sAudioThreadStack));
tmr_start(AUDIO_STREAM_PLAYER_TIMER);
rtos_enableIrqMask(RTOS_IRQ_TIMER(AUDIO_STREAM_PLAYER_TIMER));
sAudioStarted = true;
rtos_wakeupThread(&sAudioThread);
snd_startChannel(0);
snd_startChannel(1);
break;
}
}
return true;
}
int main()
{
sState = Arm7State::Idle;
initializeArm7();
while (true)
{
rtos_waitEvent(&sVBlankEvent, true, true);
updateArm7();
}
return 0;
}

View File

@@ -0,0 +1,118 @@
#pragma once
#include "usb_descriptors.h"
#ifdef __cplusplus
extern "C" {
#endif
//--------------------------------------------------------------------+
// Board Specific Configuration
//--------------------------------------------------------------------+
// RHPort number used for device can be defined by board.mk, default to port 0
#ifndef BOARD_TUD_RHPORT
#define BOARD_TUD_RHPORT 0
#endif
// RHPort max operational speed can defined by board.mk
#ifndef BOARD_TUD_MAX_SPEED
#define BOARD_TUD_MAX_SPEED OPT_MODE_DEFAULT_SPEED
#endif
//--------------------------------------------------------------------
// COMMON CONFIGURATION
//--------------------------------------------------------------------
// defined by compiler flags for flexibility
#ifndef CFG_TUSB_MCU
#define CFG_TUSB_MCU OPT_MCU_DSPICO
#endif
#define TUP_DCD_ENDPOINT_MAX 16
#ifndef CFG_TUSB_OS
#define CFG_TUSB_OS OPT_OS_CUSTOM
#endif
#ifndef CFG_TUSB_DEBUG
#define CFG_TUSB_DEBUG 0
#endif
// Enable Device stack
#define CFG_TUD_ENABLED 1
// Default is max speed that hardware controller could support with on-chip PHY
#define CFG_TUD_MAX_SPEED BOARD_TUD_MAX_SPEED
/* 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
//--------------------------------------------------------------------
// Enable if Full-Speed on OSX, also set feedback EP size to 3
#define CFG_TUD_AUDIO_ENABLE_FEEDBACK_FORMAT_CORRECTION 0
#define CFG_TUD_AUDIO_ENABLE_FEEDBACK_EP 1
#define CFG_TUD_AUDIO_FUNC_1_DESC_LEN TUD_AUDIO_SPEAKER_STEREO_FB_DESC_LEN
// How many formats are used, need to adjust USB descriptor if changed
#define CFG_TUD_AUDIO_FUNC_1_N_FORMATS 1
// Audio format type I specifications
/* 24bit/48kHz is the best quality for headset or 24bit/96kHz for 2ch speaker,
high-speed is needed beyond this */
#define CFG_TUD_AUDIO_FUNC_1_MAX_SAMPLE_RATE 44100
#define CFG_TUD_AUDIO_FUNC_1_N_CHANNELS_RX 2
// 16bit in 16bit slots
#define CFG_TUD_AUDIO_FUNC_1_FORMAT_1_N_BYTES_PER_SAMPLE_RX 2
#define CFG_TUD_AUDIO_FUNC_1_FORMAT_1_RESOLUTION_RX 16
// 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_ENABLE_EP_OUT 1
#define CFG_TUD_AUDIO_FUNC_1_FORMAT_1_EP_SZ_OUT TUD_AUDIO_EP_SIZE(CFG_TUD_AUDIO_FUNC_1_MAX_SAMPLE_RATE, CFG_TUD_AUDIO_FUNC_1_FORMAT_1_N_BYTES_PER_SAMPLE_RX, CFG_TUD_AUDIO_FUNC_1_N_CHANNELS_RX)
#define CFG_TUD_AUDIO_FUNC_1_EP_OUT_SW_BUF_SZ (CFG_TUD_AUDIO_FUNC_1_FORMAT_1_EP_SZ_OUT*8)
#define CFG_TUD_AUDIO_FUNC_1_EP_OUT_SZ_MAX CFG_TUD_AUDIO_FUNC_1_FORMAT_1_EP_SZ_OUT // Maximum EP IN size for all AS alternate settings used
// Number of Standard AS Interface Descriptors (4.9.1) defined per audio function - this is required to be able to remember the current alternate settings of these interfaces - We restrict us here to have a constant number for all audio functions (which means this has to be the maximum number of AS interfaces an audio function has and a second audio function with less AS interfaces just wastes a few bytes)
#define CFG_TUD_AUDIO_FUNC_1_N_AS_INT 1
// Size of control request buffer
#define CFG_TUD_AUDIO_FUNC_1_CTRL_BUF_SZ 64
#ifdef __cplusplus
}
#endif

View File

@@ -0,0 +1,171 @@
#include "common.h"
#include <array>
#include "UsbStringDescriptor.h"
#include "tusb.h"
#include "usb_descriptors.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) )
const u8* tud_descriptor_device_cb(void)
{
static const tusb_desc_device_t deviceDescriptor =
{
.bLength = sizeof(tusb_desc_device_t),
.bDescriptorType = TUSB_DESC_DEVICE,
.bcdUSB = 0x0110,
// Use Interface Association Descriptor (IAD) for Audio
// 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 = STRID_MANUFACTURER,
.iProduct = STRID_PRODUCT,
.iSerialNumber = STRID_SERIAL,
.bNumConfigurations = 0x01
};
return (const u8*)&deviceDescriptor;
}
#define CONFIG_TOTAL_LEN (TUD_CONFIG_DESC_LEN + TUD_AUDIO_SPEAKER_STEREO_FB_DESC_LEN)
#define EPNUM_AUDIO_FB 0x01
#define EPNUM_AUDIO_OUT 0x01
const u8* tud_descriptor_configuration_cb(u8 index)
{
(void)index;
static const u8 configurationDescriptor[] =
{
// Config number, interface count, string index, total length, attribute, power in mA
TUD_CONFIG_DESCRIPTOR(1, ITF_NUM_TOTAL, 0, CONFIG_TOTAL_LEN, 0x00, 100),
/* Standard Interface Association Descriptor (IAD) */
TUD_AUDIO_DESC_IAD(/*_firstitf*/ 0, /*_nitfs*/ 0x02, /*_stridx*/ 0x00),
/* Standard AC Interface Descriptor(4.7.1) */\
TUD_AUDIO_DESC_STD_AC(/*_itfnum*/ 0, /*_nEPs*/ 0x00, /*_stridx*/ STRID_AUDIO_INTERFACE),
/* 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_TWO_CHANNEL_LEN,
/*_ctrl*/ AUDIO_CS_AS_INTERFACE_CTRL_LATENCY_POS),
/* Clock Source Descriptor(4.7.2.1) */
TUD_AUDIO_DESC_CLK_SRC(
/*_clkid*/ UAC2_ENTITY_CLOCK,
/*_attr*/ AUDIO_CLOCK_SOURCE_ATT_INT_PRO_CLK,
/*_ctrl*/ (AUDIO_CTRL_RW << AUDIO_CLOCK_SOURCE_CTRL_CLK_FRQ_POS),
/*_assocTerm*/ UAC2_ENTITY_INPUT_TERMINAL,
/*_stridx*/ 0x00),
/* Input Terminal Descriptor(4.7.2.4) */
TUD_AUDIO_DESC_INPUT_TERM(
/*_termid*/ UAC2_ENTITY_INPUT_TERMINAL,
/*_termtype*/ AUDIO_TERM_TYPE_USB_STREAMING,
/*_assocTerm*/ 0x00,
/*_clkid*/ UAC2_ENTITY_CLOCK,
/*_nchannelslogical*/ 0x02,
/*_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*/ UAC2_ENTITY_OUTPUT_TERMINAL,
/*_termtype*/ AUDIO_TERM_TYPE_OUT_DESKTOP_SPEAKER,
/*_assocTerm*/ UAC2_ENTITY_INPUT_TERMINAL,
/*_srcid*/ UAC2_ENTITY_FEATURE_UNIT,
/*_clkid*/ UAC2_ENTITY_CLOCK,
/*_ctrl*/ 0x0000,
/*_stridx*/ 0x00),
/* Feature Unit Descriptor(4.7.2.8) */
TUD_AUDIO_DESC_FEATURE_UNIT_TWO_CHANNEL(
/*_unitid*/ UAC2_ENTITY_FEATURE_UNIT,
/*_srcid*/ UAC2_ENTITY_INPUT_TERMINAL,
/*_ctrlch0master*/ AUDIO_CTRL_RW << AUDIO_FEATURE_UNIT_CTRL_MUTE_POS | AUDIO_CTRL_RW << AUDIO_FEATURE_UNIT_CTRL_VOLUME_POS,
/*_ctrlch1*/ AUDIO_CTRL_RW << AUDIO_FEATURE_UNIT_CTRL_MUTE_POS | AUDIO_CTRL_RW << AUDIO_FEATURE_UNIT_CTRL_VOLUME_POS,
/*_ctrlch2*/ AUDIO_CTRL_RW << AUDIO_FEATURE_UNIT_CTRL_MUTE_POS | AUDIO_CTRL_RW << AUDIO_FEATURE_UNIT_CTRL_VOLUME_POS,
/*_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*/ 1, /*_altset*/ 0x00, /*_nEPs*/ 0x00, /*_stridx*/ STRID_AUDIO_INTERFACE),
/* Standard AS Interface Descriptor(4.9.1) */
/* Interface 1, Alternate 1 - alternate interface for data streaming */ \
TUD_AUDIO_DESC_STD_AS_INT(/*_itfnum*/ 1, /*_altset*/ 0x01, /*_nEPs*/ 0x02, /*_stridx*/ STRID_AUDIO_INTERFACE),
/* Class-Specific AS Interface Descriptor(4.9.2) */
TUD_AUDIO_DESC_CS_AS_INT(
/*_termid*/ UAC2_ENTITY_INPUT_TERMINAL,
/*_ctrl*/ AUDIO_CTRL_NONE,
/*_formattype*/ AUDIO_FORMAT_TYPE_I,
/*_formats*/ AUDIO_DATA_FORMAT_TYPE_I_PCM,
/*_nchannelsphysical*/ 0x02,
/*_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(CFG_TUD_AUDIO_FUNC_1_FORMAT_1_N_BYTES_PER_SAMPLE_RX, CFG_TUD_AUDIO_FUNC_1_FORMAT_1_RESOLUTION_RX),
/* Standard AS Isochronous Audio Data Endpoint Descriptor(4.10.1.1) */
TUD_AUDIO_DESC_STD_AS_ISO_EP(
/*_ep*/ EPNUM_AUDIO_OUT,
/*_attr*/ (uint8_t) ((uint8_t)TUSB_XFER_ISOCHRONOUS | (uint8_t)TUSB_ISO_EP_ATT_ASYNCHRONOUS | (uint8_t)TUSB_ISO_EP_ATT_DATA),
/*_maxEPsize*/ CFG_TUD_AUDIO_FUNC_1_EP_OUT_SZ_MAX,
/*_interval*/ 0x01),
/* Class-Specific AS Isochronous Audio Data Endpoint Descriptor(4.10.1.2) */
TUD_AUDIO_DESC_CS_AS_ISO_EP(
/*_attr*/ AUDIO_CS_AS_ISO_DATA_EP_ATT_NON_MAX_PACKETS_OK,
/*_ctrl*/ AUDIO_CTRL_NONE,
/*_lockdelayunit*/ AUDIO_CS_AS_ISO_DATA_EP_LOCK_DELAY_UNIT_MILLISEC,
/*_lockdelay*/ 0x0001),
/* Standard AS Isochronous Feedback Endpoint Descriptor(4.10.2.1) */
TUD_AUDIO_DESC_STD_AS_ISO_FB_EP(/*_ep*/ EPNUM_AUDIO_FB | 0x80, /*_epsize*/ 4, /*_interval*/ 1)
};
return configurationDescriptor;
}
const u16* tud_descriptor_string_cb(u8 index, u16 langid)
{
switch (index)
{
case STRID_LANGID:
{
static const UsbStringDescriptor<2> descriptor(0x0409);
return (const u16*)&descriptor;
}
case STRID_MANUFACTURER:
{
return USB_STRING_DESCRIPTOR(u"LNH");
}
case STRID_PRODUCT:
{
return USB_STRING_DESCRIPTOR(u"DSpico Speaker Example");
}
case STRID_SERIAL:
{
return USB_STRING_DESCRIPTOR(u"123456789");
}
case STRID_AUDIO_INTERFACE:
{
return USB_STRING_DESCRIPTOR(u"DSpico Speakers");
}
default:
{
return nullptr;
}
}
}

View File

@@ -0,0 +1,37 @@
#pragma once
enum
{
STRID_LANGID = 0,
STRID_MANUFACTURER,
STRID_PRODUCT,
STRID_SERIAL,
STRID_AUDIO_INTERFACE
};
enum
{
ITF_NUM_AUDIO_CONTROL = 0,
ITF_NUM_AUDIO_STREAMING,
ITF_NUM_TOTAL
};
#define UAC2_ENTITY_INPUT_TERMINAL 0x01
#define UAC2_ENTITY_FEATURE_UNIT 0x02
#define UAC2_ENTITY_OUTPUT_TERMINAL 0x03
#define UAC2_ENTITY_CLOCK 0x04
#define TUD_AUDIO_SPEAKER_STEREO_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_TWO_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)