Added USB microphone example

This commit is contained in:
Gericom
2025-12-20 14:05:09 +01:00
parent 3919db5ec3
commit 651084bb75
20 changed files with 1366 additions and 1 deletions

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,197 @@
#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"
#include "microphone.h"
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(0);
snd_setMasterEnable(false);
initializeVBlankIrq();
if (isDSiMode())
{
rtos_setIrq2Func(RTOS_IRQ2_MCU, mcuIrq);
rtos_enableIrq2Mask(RTOS_IRQ2_MCU);
mic_initialize();
}
pmic_setTopBacklightEnable(false);
pmic_setBottomBacklightEnable(false);
tusb_rhport_init_t dev_init =
{
.role = TUSB_ROLE_DEVICE,
.speed = TUSB_SPEED_AUTO
};
tusb_init(0, &dev_init);
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;
}
}
}
int main()
{
sState = Arm7State::Idle;
initializeArm7();
while (true)
{
rtos_waitEvent(&sVBlankEvent, true, true);
updateArm7();
}
return 0;
}

View File

@@ -0,0 +1,282 @@
#include "common.h"
#include <libtwl/rtos/rtosIrq.h>
#include <libtwl/spi/spiCodec.h>
#include <libtwl/sound/twlMicrophone.h>
#include <libtwl/sound/twlI2s.h>
#include <libtwl/sys/swi.h>
#include "tusb.h"
#include "usb_descriptors.h"
#include "microphone.h"
#define AUDIO_BLOCK_SIZE_IN_BYTES 32
#define NUMBER_OF_AUDIO_BUFFERS 128
static s16 sAudioBuffer[NUMBER_OF_AUDIO_BUFFERS][16];
static volatile int sReadBlock;
static volatile int sWriteBlock;
static bool sCaptureStarted = false;
static int sOffset = 0;
static bool sChannelMute[CFG_TUD_AUDIO_FUNC_1_N_CHANNELS_TX + 1]; // +1 for master channel 0
static uint16_t sChannelVolume[CFG_TUD_AUDIO_FUNC_1_N_CHANNELS_TX + 1]; // +1 for master channel 0
void mic_initialize()
{
twlmic_stop();
REG_I2SCNT = I2SCNT_MIX_RATIO_DSP_0_NITRO_8 | I2SCNT_FREQUENCY_32728_HZ;
codec_setPage(CODEC_PAGE_0);
{
codec_writeRegister(CODEC_REG_PAGE0_DAC_NDAC_VAL, 0x87);
codec_writeRegister(CODEC_REG_PAGE0_ADC_NADC_VAL, 0x87);
codec_writeRegister(CODEC_REG_PAGE0_PLL_J, 21);
}
REG_I2SCNT |= I2SCNT_ENABLE;
codec_setPage(CODEC_PAGE_1);
{
codec_writeRegister(CODEC_REG_PAGE1_MICBIAS, 3);
}
bool adcOn, dacOn;
codec_setPage(CODEC_PAGE_0);
{
adcOn = codec_readRegister(CODEC_REG_PAGE0_ADC_DIGITAL_MIC) & 0x80;
dacOn = codec_readRegister(CODEC_REG_PAGE0_DAC_DATA_PATH_SETUP) & 0xC0;
codec_writeRegister(CODEC_REG_PAGE0_ADC_DIGITAL_MIC, 0x80);
if (!adcOn || !dacOn)
{
swi_waitByLoop(0x28E91F); // 20ms
}
codec_writeRegister(CODEC_REG_PAGE0_ADC_DIGITAL_VOLUME_CONTROL_FINE_ADJUST, 0);
codec_writeRegister(CODEC_REG_PAGE0_AGC_CONTROL_1, 0);
}
codec_setPage(CODEC_PAGE_1);
{
sChannelVolume[0] = 40; // dB
codec_writeRegister(CODEC_REG_PAGE1_MIC_PGA, sChannelVolume[0] * 2); // gain
}
}
static void micIrq(u32 irq2Mask)
{
u32 data[8];
data[0] = REG_MIC_FIFO;
data[1] = REG_MIC_FIFO;
data[2] = REG_MIC_FIFO;
data[3] = REG_MIC_FIFO;
data[4] = REG_MIC_FIFO;
data[5] = REG_MIC_FIFO;
data[6] = REG_MIC_FIFO;
data[7] = REG_MIC_FIFO;
int writeBlock = sWriteBlock;
int nextWriteBlock = (writeBlock + 1) % NUMBER_OF_AUDIO_BUFFERS;
if (nextWriteBlock != sReadBlock)
{
memcpy(&sAudioBuffer[writeBlock][0], data, sizeof(data));
sWriteBlock = nextWriteBlock;
}
}
static void startMicrophoneCapture()
{
sReadBlock = 0;
sWriteBlock = 0;
sOffset = 0;
twlmic_stop();
twlmic_configure(MICCNT_FORMAT_NORMAL, MICCNT_RATE_DIV_1, MICCNT_IRQ_HALF_OVERFLOW);
twlmic_clearFifo();
rtos_setIrq2Func(RTOS_IRQ2_MIC, micIrq);
rtos_ackIrq2Mask(RTOS_IRQ2_MIC);
rtos_enableIrq2Mask(RTOS_IRQ2_MIC);
twlmic_start();
sCaptureStarted = true;
}
static void stopMicrophoneCapture()
{
rtos_disableIrq2Mask(RTOS_IRQ2_MIC);
twlmic_stop();
rtos_ackIrq2Mask(RTOS_IRQ2_MIC);
sCaptureStarted = false;
}
static bool handleMicInputTerminalGetRequest(u8 rhport, const tusb_control_request_t* p_request)
{
u8 ctrlSel = TU_U16_HIGH(p_request->wValue);
switch (ctrlSel)
{
case AUDIO_TE_CTRL_CONNECTOR: // Get terminal connector
{
audio_desc_channel_cluster_t ret;
ret.bNrChannels = 1;
ret.bmChannelConfig = (audio_channel_config_t)0;
ret.iChannelNames = 0;
return tud_audio_buffer_and_schedule_control_xfer(rhport, p_request, (void*) &ret, sizeof(ret));
}
}
return false;
}
static bool handleFeatureUnitGetRequest(u8 rhport, const tusb_control_request_t* p_request)
{
u8 channelNum = TU_U16_LOW(p_request->wValue);
u8 ctrlSel = TU_U16_HIGH(p_request->wValue);
switch (ctrlSel)
{
case AUDIO_FU_CTRL_MUTE: // Get Mute of channel
{
return tud_control_xfer(rhport, p_request, &sChannelMute[channelNum], 1);
}
case AUDIO_FU_CTRL_VOLUME:
{
switch (p_request->bRequest)
{
case AUDIO_CS_REQ_CUR: // Get Volume of channel
{
return tud_control_xfer(rhport, p_request, &sChannelVolume[channelNum], sizeof(sChannelVolume[channelNum]));
}
case AUDIO_CS_REQ_RANGE: // Get Volume range of channel
{
audio_control_range_2_n_t(1) ret;
ret.wNumSubRanges = 1;
ret.subrange[0].bMin = 0; // 0 dB
ret.subrange[0].bMax = 59; // +59 dB
ret.subrange[0].bRes = 1; // 1 dB steps
return tud_audio_buffer_and_schedule_control_xfer(rhport, p_request, (void*) &ret, sizeof(ret));
}
}
break;
}
}
return false;
}
static bool handleFeatureUnitSetRequest(u8 rhport, const tusb_control_request_t* p_request, u8* pBuff)
{
u8 channelNum = TU_U16_LOW(p_request->wValue);
u8 ctrlSel = TU_U16_HIGH(p_request->wValue);
switch (ctrlSel)
{
case AUDIO_FU_CTRL_MUTE: // Set Mute of channel
{
sChannelMute[channelNum] = ((audio_control_cur_1_t*)pBuff)->bCur;
return true;
}
case AUDIO_FU_CTRL_VOLUME: // Set Volume of channel
{
// Request uses format layout 2
sChannelVolume[channelNum] = (uint16_t) ((audio_control_cur_2_t*)pBuff)->bCur;
codec_setPage(CODEC_PAGE_1);
{
codec_writeRegister(CODEC_REG_PAGE1_MIC_PGA, sChannelVolume[channelNum] * 2); // gain
}
return true;
}
}
return false;
}
static bool handleClockGetRequest(u8 rhport, const tusb_control_request_t* p_request)
{
u8 ctrlSel = TU_U16_HIGH(p_request->wValue);
switch (ctrlSel)
{
case AUDIO_CS_CTRL_SAM_FREQ:
{
switch (p_request->bRequest)
{
case AUDIO_CS_REQ_CUR: // Get Sample Freq.
{
uint32_t sampFreq = CFG_TUD_AUDIO_FUNC_1_SAMPLE_RATE;
return tud_control_xfer(rhport, p_request, &sampFreq, sizeof(sampFreq));
}
case AUDIO_CS_REQ_RANGE: // Get Sample Freq. range
{
audio_control_range_4_n_t(1) sampleFreqRng;
sampleFreqRng.wNumSubRanges = 1;
sampleFreqRng.subrange[0].bMin = CFG_TUD_AUDIO_FUNC_1_SAMPLE_RATE;
sampleFreqRng.subrange[0].bMax = CFG_TUD_AUDIO_FUNC_1_SAMPLE_RATE;
sampleFreqRng.subrange[0].bRes = 0;
return tud_control_xfer(rhport, p_request, &sampleFreqRng, sizeof(sampleFreqRng));
}
}
break;
}
case AUDIO_CS_CTRL_CLK_VALID: // Get Sample Freq. valid
{
uint8_t clkValid = 1;
return tud_control_xfer(rhport, p_request, &clkValid, sizeof(clkValid));
}
}
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)
{
u8 entityId = TU_U16_HIGH(p_request->wIndex);
switch (entityId)
{
case UAC2_ENTITY_MIC_INPUT_TERMINAL:
{
return handleMicInputTerminalGetRequest(rhport, p_request);
}
case UAC2_ENTITY_FEATURE_UNIT:
{
return handleFeatureUnitGetRequest(rhport, p_request);
}
case UAC2_ENTITY_CLOCK:
{
return handleClockGetRequest(rhport, p_request);
}
}
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* pBuff)
{
u8 entityId = TU_U16_HIGH(p_request->wIndex);
switch (entityId)
{
case UAC2_ENTITY_FEATURE_UNIT:
{
return handleFeatureUnitSetRequest(rhport, p_request, pBuff);
}
}
return false;
}
bool tud_audio_tx_done_pre_load_cb(u8 rhport, u8 itf, u8 ep_in, u8 cur_alt_setting)
{
if (!sCaptureStarted)
{
startMicrophoneCapture();
}
while (sReadBlock != sWriteBlock)
{
int bytesWritten = tud_audio_write(((u8*)&sAudioBuffer[sReadBlock][0]) + sOffset, AUDIO_BLOCK_SIZE_IN_BYTES - sOffset);
int offset = AUDIO_BLOCK_SIZE_IN_BYTES - bytesWritten;
sOffset = offset % AUDIO_BLOCK_SIZE_IN_BYTES;
if (offset > 0)
{
// Could not write entire block
break;
}
sReadBlock = (sReadBlock + 1) % NUMBER_OF_AUDIO_BUFFERS;
}
return true;
}
bool tud_audio_set_itf_close_EP_cb(u8 rhport, const tusb_control_request_t* p_request)
{
stopMicrophoneCapture();
return true;
}

View File

@@ -0,0 +1,3 @@
#pragma once
void mic_initialize();

View File

@@ -0,0 +1,97 @@
#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
//--------------------------------------------------------------------
#define CFG_TUD_AUDIO_FUNC_1_SAMPLE_RATE 32728
#define CFG_TUD_AUDIO_FUNC_1_DESC_LEN TUD_AUDIO_MIC_ONE_CH_DESC_LEN
#define CFG_TUD_AUDIO_FUNC_1_N_AS_INT 1 // Number of Standard AS Interface Descriptors (4.9.1) defined per audio function - this is required to be able to remember the current alternate settings of these interfaces - We restrict us here to have a constant number for all audio functions (which means this has to be the maximum number of AS interfaces an audio function has and a second audio function with less AS interfaces just wastes a few bytes)
#define CFG_TUD_AUDIO_FUNC_1_CTRL_BUF_SZ 64 // Size of control request buffer
#define CFG_TUD_AUDIO_ENABLE_EP_IN 1
#define CFG_TUD_AUDIO_FUNC_1_N_BYTES_PER_SAMPLE_TX 2 // Driver gets this info from the descriptors - we define it here to use it to setup the descriptors and to do calculations with it below
#define CFG_TUD_AUDIO_FUNC_1_N_CHANNELS_TX 1 // Driver gets this info from the descriptors - we define it here to use it to setup the descriptors and to do calculations with it below - be aware: for different number of channels you need another descriptor!
#define CFG_TUD_AUDIO_EP_SZ_IN TUD_AUDIO_EP_SIZE(CFG_TUD_AUDIO_FUNC_1_SAMPLE_RATE, CFG_TUD_AUDIO_FUNC_1_N_BYTES_PER_SAMPLE_TX, CFG_TUD_AUDIO_FUNC_1_N_CHANNELS_TX)
#define CFG_TUD_AUDIO_FUNC_1_EP_IN_SZ_MAX CFG_TUD_AUDIO_EP_SZ_IN
#define CFG_TUD_AUDIO_FUNC_1_EP_IN_SW_BUF_SZ 256 //(TUD_OPT_HIGH_SPEED ? 8 : 1) * CFG_TUD_AUDIO_EP_SZ_IN // Example write FIFO every 1ms, so it should be 8 times larger for HS device
#ifdef __cplusplus
}
#endif

View File

@@ -0,0 +1,94 @@
#include "common.h"
#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 (0x5000 | _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 = 0x0200,
// 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_MIC_ONE_CH_DESC_LEN)
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),
TUD_AUDIO_MIC_ONE_CH_DESCRIPTOR(0, STRID_AUDIO_INTERFACE,
CFG_TUD_AUDIO_FUNC_1_N_BYTES_PER_SAMPLE_TX, CFG_TUD_AUDIO_FUNC_1_N_BYTES_PER_SAMPLE_TX * 8,
EPNUM_AUDIO_IN, CFG_TUD_AUDIO_EP_SZ_IN)
};
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 Microphone Example");
}
case STRID_SERIAL:
{
return USB_STRING_DESCRIPTOR(u"123456789");
}
case STRID_AUDIO_INTERFACE:
{
return USB_STRING_DESCRIPTOR(u"DSpico Microphone");
}
default:
{
return nullptr;
}
}
}

View File

@@ -0,0 +1,24 @@
#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_MIC_INPUT_TERMINAL 0x01
#define UAC2_ENTITY_FEATURE_UNIT 0x02
#define UAC2_ENTITY_MIC_OUTPUT_TERMINAL 0x03
#define UAC2_ENTITY_CLOCK 0x04
#define EPNUM_AUDIO_IN 0x81