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,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)