From c3e503ce0935ff62f9bfb235c46941c7727c7738 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20G=C3=B6ttgens?= Date: Sun, 4 Oct 2026 17:51:31 +0200 Subject: [PATCH 01/10] Seed the SoftDevice RNG from the CRACEN TRNG on every request (#2) * Seed the SoftDevice RNG from the CRACEN TRNG on every request NRF_EVT_RAND_SEED_REQUEST was answered with FICR DEVICEID XOR the domain 0 GRTC SYSCOUNTER, which is predictable. The SoftDevice RNG feeds LESC key generation. Both the initial seed in begin() and later seed requests now go through seed_softdevice_rng(), which draws from the CRACEN TRNG. The RNG is no longer stopped after seeding, so a reseed cannot cut off key generation running on another task. * Retry the SoftDevice RNG seed until it succeeds A failed seed attempt on NRF_EVT_RAND_SEED_REQUEST left the SoftDevice RNG unseeded. Retry, restarting the CRACEN RNG between attempts so a generator stuck in its error state is reset. * Retry a failed RNG seed without blocking SoC event dispatch f94ff6e retried inline in the seed request handler, so a TRNG that kept failing would stop adafruit_soc_task from dispatching any further SoC events, flash completions included. Mark the seed as pending and retry it between event batches, waking every 10 ms while it is outstanding. * Recover a halted CRACEN RNG in place instead of stopping it 0acc127 recovered a failed seed by calling nRF54Crypto.end(), which disables the RNG while LESC key generation may be reading it on another task. cracen_rng_fill() now soft-resets a generator in its error state while it waits for the FIFO, the same reset cracen_rng_start() already applied, so no caller has to stop the RNG to recover it. The SoC task only retries the seed. * Serialize CRACEN RNG FIFO reads across tasks SoftDevice seed requests and LESC key generation read the RNG from different tasks. A read by one between the other's FIFO level check and its read, or a soft reset clearing the FIFO there, returned a word that is not random. Suspend the scheduler for the duration of each fill. * Limit the CRACEN RNG lock to the FIFO check and read bf63374 suspended the scheduler for a whole fill, including the FIFO polling wait, which can run for RNG_TIMEOUT iterations per word while the generator is failing. Take one word at a time in a critical section that covers only the level check and read, or the reset of a halted generator, and poll outside it. --- libraries/Bluefruit54Lib/src/bluefruit.cpp | 39 +++++++-------- libraries/nRF54Crypto/src/nRF54Crypto.cpp | 57 +++++++++++++++------- 2 files changed, 57 insertions(+), 39 deletions(-) diff --git a/libraries/Bluefruit54Lib/src/bluefruit.cpp b/libraries/Bluefruit54Lib/src/bluefruit.cpp index 182abad..3b05fcc 100644 --- a/libraries/Bluefruit54Lib/src/bluefruit.cpp +++ b/libraries/Bluefruit54Lib/src/bluefruit.cpp @@ -84,6 +84,14 @@ static void bluefruit_blinky_cb( TimerHandle_t xTimer ) #endif } +// s145 has no entropy source of its own and asks the application for a seed (NRF_EVT_RAND_SEED_REQUEST). +// The RNG is left running: LESC key generation draws from it on another task. +static bool seed_softdevice_rng(void) +{ + uint8_t seed[SD_RAND_SEED_SIZE]; + return nRF54Crypto.begin() && nRF54Crypto.random(seed, sizeof(seed)) && (sd_rand_seed_set(seed) == NRF_SUCCESS); +} + static void nrf_error_cb(uint32_t id, uint32_t pc, uint32_t info) { #if CFG_DEBUG @@ -288,15 +296,8 @@ bool AdafruitBluefruit::begin(uint8_t prph_count, uint8_t central_count) if ( sd_err != NRF_SUCCESS ) sd_isr_forwarding_disable(); VERIFY_STATUS( sd_err, false ); - // s145 asks for a seed (NRF_EVT_RAND_SEED_REQUEST) and sd_ble_enable() fails with INVALID_STATE without one - { - uint8_t seed[SD_RAND_SEED_SIZE]; - nRF54Crypto.begin(); - bool seeded = nRF54Crypto.random(seed, sizeof(seed)); - nRF54Crypto.end(); - VERIFY(seeded, false); - VERIFY_STATUS( sd_rand_seed_set(seed), false ); - } + // sd_ble_enable() fails with INVALID_STATE until the RNG is seeded + VERIFY( seed_softdevice_rng(), false ); /*------------------------------------------------------------------*/ /* SoftDevice Default Configuration depending on the number of @@ -672,9 +673,15 @@ void adafruit_soc_task(void* arg) { (void) arg; + // An unanswered seed request leaves the SoftDevice RNG unseeded, so a failed seed is retried between + // event batches rather than inline, where it would hold up flash completions. + bool seed_pending = false; + while (1) { - if ( xSemaphoreTake(Bluefruit._soc_event_sem, portMAX_DELAY) ) + if ( seed_pending ) seed_pending = !seed_softdevice_rng(); + + if ( xSemaphoreTake(Bluefruit._soc_event_sem, seed_pending ? pdMS_TO_TICKS(10) : portMAX_DELAY) ) { uint32_t soc_evt; uint32_t err = ERROR_NONE; @@ -694,17 +701,7 @@ void adafruit_soc_task(void* arg) break; case NRF_EVT_RAND_SEED_REQUEST: - { - // S145 requires the application to seed the RNG - uint8_t seed[SD_RAND_SEED_SIZE]; - // Use FICR device ID and GRTC counter as entropy source - uint32_t* seed32 = (uint32_t*)seed; - for (uint32_t i = 0; i < SD_RAND_SEED_SIZE / 4; i++) - { - seed32[i] = NRF_FICR->INFO.DEVICEID[i & 1] ^ (uint32_t)(NRF_GRTC->SYSCOUNTER[0].SYSCOUNTERL + i); - } - sd_rand_seed_set(seed); - } + seed_pending = true; break; default: break; diff --git a/libraries/nRF54Crypto/src/nRF54Crypto.cpp b/libraries/nRF54Crypto/src/nRF54Crypto.cpp index 64a6a8d..8218f83 100644 --- a/libraries/nRF54Crypto/src/nRF54Crypto.cpp +++ b/libraries/nRF54Crypto/src/nRF54Crypto.cpp @@ -45,18 +45,32 @@ nRF54CryptoClass nRF54Crypto; static bool _rng_started = false; +// Enable with 1x 128-bit block for AES conditioning +static void cracen_rng_control(bool soft_reset) +{ + nrf_cracen_rng_control_t cfg = { 0 }; + cfg.enable = true; + cfg.number_128_blocks = 1; + cfg.soft_reset = soft_reset; + nrf_cracen_rng_control_set(NRF_CRACENCORE, &cfg); +} + +// A halted generator needs a soft reset before it produces again. Done in place rather than through +// stop/start, so a reader on another task never finds the RNG disabled under it. +static void cracen_rng_reset_if_halted(void) +{ + if (nrf_cracen_rng_fsm_state_get(NRF_CRACENCORE) != NRF_CRACEN_RNG_FSM_STATE_ERROR) return; + cracen_rng_control(true); + cracen_rng_control(false); +} + static bool cracen_rng_start(void) { if (_rng_started) return true; // Enable CRACEN RNG module nrf_cracen_module_enable(NRF_CRACEN, NRF_CRACEN_MODULE_RNG_MASK); - - // Configure RNG: enable with 1x 128-bit block for AES conditioning - nrf_cracen_rng_control_t cfg = { 0 }; - cfg.enable = true; - cfg.number_128_blocks = 1; - nrf_cracen_rng_control_set(NRF_CRACENCORE, &cfg); + cracen_rng_control(false); // Wait for the FSM to leave RESET/STARTUP; a full FIFO parks it in IDLE_STANDBY (rings off) uint32_t timeout = RNG_TIMEOUT; @@ -68,13 +82,7 @@ static bool cracen_rng_start(void) _rng_started = true; return true; } - if (state == NRF_CRACEN_RNG_FSM_STATE_ERROR) { - // A halted generator needs a soft reset before it restarts - cfg.soft_reset = true; - nrf_cracen_rng_control_set(NRF_CRACENCORE, &cfg); - cfg.soft_reset = false; - nrf_cracen_rng_control_set(NRF_CRACENCORE, &cfg); - } + cracen_rng_reset_if_halted(); } return false; } @@ -92,21 +100,34 @@ static void cracen_rng_stop(void) _rng_started = false; } +// Readers run on different tasks (SoftDevice seed requests, LESC key generation). Another reader, or +// a reset clearing the FIFO, between the level check and the read would hand out a word that is not +// random, so the two happen in one critical section. Waiting for data stays outside it. +static bool cracen_rng_word(uint32_t *word) +{ + taskENTER_CRITICAL(); + bool got = nrf_cracen_rng_fifo_level_get(NRF_CRACENCORE) > 0; + if (got) { + *word = nrf_cracen_rng_fifo_get(NRF_CRACENCORE); + } else { + cracen_rng_reset_if_halted(); + } + taskEXIT_CRITICAL(); + return got; +} + static bool cracen_rng_fill(uint8_t *dest, size_t len) { if (!_rng_started) return false; size_t offset = 0; while (offset < len) { - // Wait for FIFO to have data + uint32_t word; uint32_t timeout = RNG_TIMEOUT; - while (nrf_cracen_rng_fifo_level_get(NRF_CRACENCORE) == 0) { + while (!cracen_rng_word(&word)) { if (--timeout == 0) return false; } - // Read a 32-bit random word - uint32_t word = nrf_cracen_rng_fifo_get(NRF_CRACENCORE); - // Copy bytes (handle partial word at end) size_t remaining = len - offset; size_t to_copy = (remaining < 4) ? remaining : 4; From 2dd55976cbcc277a997853948a366390a9faf05e Mon Sep 17 00:00:00 2001 From: Carlos Valdes Date: Mon, 5 Oct 2026 10:06:34 +0200 Subject: [PATCH 02/10] nRF54L15: fix the tickless idle hang and five more bring-up issues (#1) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Fix tickless idle on the 52-bit GRTC, GPIOTE per port, and the SYSCOUNTER read port_cmsis_systick.c: - grtc_cc_set() wrote CCH = 0. The SYSCOUNTER is 52 bits at 1 MHz and keeps running across soft resets, so it passes 2^32 about 71 minutes after power-on; from then on every compare lands in the past and never fires. The tick only survived while some other interrupt woke the CPU, and the first idle sleep after that never ended. Rebuild the high word from the live counter, carrying when the 32-bit target wrapped. - grtc_counter_get() read SYSCOUNTERL without checking validity. SYSCOUNTERH is latched by the read of SYSCOUNTERL and its reset value already has BUSY set, so re-read the pair until BUSY clears (as nrfx does). A junk read here fed the tick catch-up below. - The tick ISR catch-up had no bound: a bogus counter read stepped the tick by hours. Anything beyond a few seconds is treated as one tick and the tick base is re-anchored; genuine long sleeps are accounted in vPortSuppressTicksAndSleep. WInterrupts.c: - attachInterrupt() only ever used GPIOTE20, but nRF54L routes P0 to GPIOTE30 (4 channels) and P1 to GPIOTE20 (8 channels); P2 has no GPIOTE at all. Pick the instance by port, keep per-instance channel maps, serve both IRQ lines, and return 0 for P2 pins. Verified on an nRF54L15-DK with an SX1262 (Meshtastic): boots cold and warm, tick tracks wall clock, DIO1 interrupts on P0.00. * Bound the SoftDevice flash-completion wait and cap the tickless idle product flash_nrf5x.c: wait_for_async_flash_op_completion() blocked on the completion semaphore with portMAX_DELAY. A lost NRF_EVT_FLASH_OPERATION_* event (seen on nRF54L15 during a BLE connection) parked the writing task forever and, with every task blocked, the tickless idle put the CPU to sleep until an arbitrary wake-up: the firmware froze, BLE included. Wait in bounded slices, drain sd_evt_get() from the waiting task so the completion cannot get stuck behind a task that is not running, hand other SoC events to a weak application hook, and report NRF_ERROR_TIMEOUT when nothing arrives. port_cmsis_systick.c: clamp xExpectedIdleTime before multiplying by the GRTC ticks per systick; with every task blocked the product wrapped and the wake-up compare landed anywhere. * Write the InternalFS pages in place on RRAM and report flash failures to LittleFS The page cache flushed every 256-byte LittleFS block as erase-then-program of its 4 KB page: 32 sd_flash_write() calls filling the page with 0xFF (the nRF54L RRAM has no erase, s145 has no sd_flash_page_erase) followed by the whole page again. A hang or reset between the two left the page at 0xFF; when the page was page 0 both LittleFS superblocks vanished and the next boot reformatted the filesystem (seen on an nRF54L15-DK: the node came back unconfigured with a new identity after an overnight freeze). Flush only the 128-byte chunks that differ from the flash, in place: the other blocks sharing the page are never rewritten, so an interrupted flush can only damage the chunk in flight, which is the power-loss model LittleFS is built for. A boot now costs 11 operations instead of 34 per page. Propagate failures instead of ignoring them: flash_cache_flush() returns false, flash_cache_write() -1 when the flush it forced failed, and the LittleFS prog/ sync callbacks return LFS_ERR_IO so the caller learns the write did not happen. Create the completion semaphore on first use (fal_erase() was the only place that did, and it no longer runs before every program). Re-issue BUSY 20 times 5 ms apart and a lost completion 3 times; any other SoftDevice error is final. Keep a flight recorder (flash_nrf5x_stats) of the SoftDevice flash operations: counts of issued, completed, self-drained, timed-out and failed operations, the last destination and the longest wait, and an in_flight flag, so a stuck firmware can be read from the debugger by symbol. * Sleep the tickless idle with WFI instead of a WFE/ISPR loop WFI completes on any enabled pending interrupt regardless of PRIMASK, so the idle sleep no longer depends on SEVONPEND and on the event latch being in the right state when the loop re-checks ISPR. Same pattern Zephyr uses on this part. * Run the FreeRTOS tick on the application core's GRTC domain The tick used SYSCOUNTER[0], INTEN0 and GRTC_0_IRQn, which belong to domain 0, the FLPR's. The secure application core is domain 2 (GRTC_IRQ_GROUP in nrf54l15_interim.h), and the SoftDevice already uses group 3 for its own compares. Use the SYSCOUNTER index, INTEN group and IRQ of domain 2, and fail the build if the MDK's GRTC_IRQ_GROUP ever disagrees. This is a correctness fix on its own: it did not cure the idle hang seen on the nRF54L15-DK, whose cause was the GRTC sleep timing (next commit). * Set the GRTC sleep timing that nrfx_grtc_init() would Nothing on this core calls nrfx_grtc_init(), so TIMEOUT and WAKETIME kept their reset values, 0 and 1: the SYSCOUNTER stopped the moment the CPU slept and had a single 32 kHz cycle to wake up before a compare. On the nRF54L15-DK the core hung in the tickless idle WFI with the SYSCOUNTER stopped, every 40 min to 3.5 h. In all five hangs caught live, a SoftDevice compare sat 1.5-2 of those cycles past the stop and never fired, and neither did the tick compare 19.5 ms later. Write the values of NRFX_GRTC_SLEEP_DEFAULT_CONFIG (TIMEOUT 5, WAKETIME 4), which is what Zephyr applies on this part. Do it outside the start path, like AUTOEN, since the GRTC survives soft resets. * Cap the tickless idle at half the GRTC compare range grtc_cc_set() places a 32-bit target in the right 2^32 epoch of the 52-bit compare by carrying into the high word when the target wrapped, and it decides that from (int32_t)(val - lo) > 0. That only holds for targets less than 2^31 GRTC ticks ahead. The tickless idle allowed up to 2^32 ticks, about 71 min at 1 MHz, so with every task blocked forever a wrapped target further than ~35 min out kept the current high word, sat in the past, and the CPU slept until some other interrupt woke it. Cap the idle at 0x7FFFFFFF GRTC ticks instead of 0xFFFFFFFF. * Keep a page that failed to flush in the InternalFS cache When the flush forced by a page switch failed, flash_cache_flush() dropped the cached page and flash_cache_write() loaded the next one over it. That page held writes LittleFS had already been told were done, so they were lost, while the write that triggered the flush was cached anyway and could reach flash later despite having returned LFS_ERR_IO. Leave the page cached when its flush fails and return -1 before taking the new write, so the next flush (or sync) writes it again; on RRAM only the chunks that still differ are sent. With that, a failed write in the erase callback no longer stores its 0xFF, so the erase now reports LFS_ERR_IO like prog instead of claiming the block was erased. * Answer RNG seed requests the flash driver drains While it waits for a flash completion, the InternalFS driver drains the SoC event queue itself and passes every non-flash event to flash_nrf5x_soc_event_hook(), which nothing defined. A seed request pulled out there was dropped and never reached adafruit_soc_task(), so sd_rand_seed_set() was not called. Give Bluefruit54Lib a weak default for the hook that seeds the RNG through the same function as the SOC task. It is weak so that an application that reads the SoC events itself (Meshtastic does) can still define its own without a duplicate symbol. * Settle a timed-out flash write before issuing the next one When the wait for a SoftDevice flash completion timed out, the write could still be running. Its completion then arrived later, as a give on the semaphore or as an event in the SoC queue, and the next write (the retry, or any later one) took it for its own and reported success while it was still being written. Count accepted operations and completions instead of taking the semaphore once per operation. The SoftDevice runs one operation at a time and reports them in order, so all accepted operations are finished once the counts match, and the last completion is the current write's. Before issuing, wait (with the same 2 s bound) for any operation left outstanding. If its completion still does not come, issue anyway: BUSY means it is still running and goes through the usual BUSY retries, which no longer wait again; acceptance means it finished and another reader of the SoC queue took its completion without passing it on, so it is written off and counted in flash_nrf5x_stats.written_off. The one case left is a completion counted in the instant between that last check and sd_flash_write(), after it had been missing for more than 2 s. It can end one wait early; the surplus count is then dropped. * Count a flash write as done only once its data has landed After a completion was written off as lost, it could still arrive: the SoftDevice accepting the next write proves the earlier one finished, not that another reader consumed its completion. Counted late, it made the counts match while the new write was still running, so the driver returned success early. The cache could then load another page into the buffer the SoftDevice was still reading from, and write that data at the wrong address. Also require the destination to read back as the source before the wait for a write succeeds. The cache only programs chunks that differ from the flash, so a match means the SoftDevice has written them and is done reading the buffer. A stray completion can now only make the wait look again, never end it early. If every completion is in, the last one reported a failure and the data is not there, the write fails and is retried as before. The only write a match cannot vouch for is an erase chunk over flash that is already 0xFF; its source is a constant, so finishing it later cannot write anything else. * Run the GRTC tick on 64-bit counter and compare values grtc_cc_set() rebuilt the high word of a 32-bit target from the live counter. It only ever carried into the next epoch, never borrowed: a target slightly behind the counter when the low word had just wrapped (the tick ISR held off for more than a period by a SoftDevice interrupt) landed about 2^32 us, 71.6 min, ahead, and the tick stopped. Its SYSCOUNTERH read also ignored BUSY and OVERFLOW. Read the counter with nrfy_grtc_sys_counter_get(), which retries on both and reads this core's domain, and arm compares with the full value through nrfy_grtc_sys_counter_cc_set(). A target already in the past now fires at once; the spurious event that writing CCL before CCH can raise is dropped while the target is still ahead, as nrfx does. That removes the epoch reconstruction, the hand-rolled counter read and the 2^31 cap on the tickless idle. Ticks now stay on a grid (grtc_next_tick) instead of being re-armed one period after the ISR ran. A backlog beyond 4 s is no longer dropped by re-anchoring the tick base: it is caught up 4 s per interrupt and counted in grtc_tick_backlogs. The tickless idle wakes on the grid and never steps past the unblock tick; ticks beyond it are caught up by the tick interrupt. Write the sleep configuration the way nrfx_grtc_sleep_configure() does, with the SYSCOUNTER stopped, using NRFX_GRTC_SLEEP_DEFAULT_CONFIG instead of literal 5/4. The nrfx GRTC driver itself is not enabled in this core. * Mask SoftwareSerial's RX interrupt on the GPIOTE that serves its pin attachInterrupt() returns a channel mask of GPIOTE30 for P0 pins and of GPIOTE20 for P1 pins, but SoftwareSerial applied it to NRF_GPIOTE, which nrf54l_compat.h aliases to GPIOTE20. With the RX pin on P0, write() masked GPIOTE20 channel N instead of the RX channel: the RX edge interrupt kept firing during TX, and whatever P1 pin owned that channel was masked during every byte. Add digitalPinToGpiote() to WInterrupts, using the same pin mapping as attachInterrupt(), and have SoftwareSerial keep the instance its RX pin uses. * Tighten the InternalFS flash retry policy and bound a failing page Write only the words of a chunk that differ from the flash, first to last. A chunk ending in words that already matched could otherwise read back equal while the SoftDevice still had those words to write and was still reading the buffer, so a late completion could end the wait before it was done and the cache could reload the buffer under it. A range already in place is now not written at all, which also covers erasing flash that is already 0xFF. Give each chunk a total budget of 2 s, every attempt and wait included, instead of 2 s per wait: with retries one failing chunk could hold the filesystem lock for about 10 s. Settling an operation left outstanding by a timeout may use up to 0.5 s of it. A failure reported by the SoftDevice (NRF_EVT_FLASH_OPERATION_ERROR, which it raises when it cannot fit the write around radio activity) is now retried like BUSY, up to MAX_RETRY times with a pause, instead of three times back to back, and it counts in flash_nrf5x_stats.errors. A page whose flush keeps failing is dropped after three failures in a row. Kept for ever, it refused every write to any other page, including the erases of InternalFS.format(), and its data does not survive a reboot anyway. Drop the NOR erase-then-program flush: the only flash_cache_t writes in place. --------- Co-authored-by: Thomas Göttgens --- cores/nRF5/WInterrupts.c | 161 ++++++--- cores/nRF5/WInterrupts.h | 8 + cores/nRF5/freertos/config/FreeRTOSConfig.h | 4 +- .../CMSIS/nrf54l/port_cmsis_systick.c | 173 ++++++---- .../portable/CMSIS/nrf54l/portmacro_cmsis.h | 16 +- libraries/Bluefruit54Lib/src/bluefruit.cpp | 30 +- .../src/InternalFileSystem.cpp | 10 +- .../InternalFileSytem/src/flash/flash_cache.c | 53 ++- .../InternalFileSytem/src/flash/flash_cache.h | 12 +- .../InternalFileSytem/src/flash/flash_nrf5x.c | 323 ++++++++++++++---- .../InternalFileSytem/src/flash/flash_nrf5x.h | 25 +- libraries/SoftwareSerial/SoftwareSerial.cpp | 16 +- libraries/SoftwareSerial/SoftwareSerial.h | 2 + 13 files changed, 607 insertions(+), 226 deletions(-) diff --git a/cores/nRF5/WInterrupts.c b/cores/nRF5/WInterrupts.c index 7b0288b..47cf799 100644 --- a/cores/nRF5/WInterrupts.c +++ b/cores/nRF5/WInterrupts.c @@ -23,13 +23,17 @@ #include "wiring_private.h" #include "nrf_gpiote.h" -/* The HAL enables interrupts on INTENSET, so the IRQ line and handler must match that group. */ -#define GPIOTE_IRQn NRFX_CONCAT_3(GPIOTE20_, GPIOTE_IRQ_GROUP, _IRQn) -#define GPIOTE_IRQHandler NRFX_CONCAT_3(GPIOTE20_, GPIOTE_IRQ_GROUP, _IRQHandler) - #include -/* nRF54L GPIOTE20 has 8 channels */ +/* nRF54L routes each GPIO port to one GPIOTE instance: P0 -> GPIOTE30 (4 channels), P1 -> GPIOTE20 + * (8 channels). P2, the fast port, has no GPIOTE at all, so no pin on it can raise an interrupt. + * The HAL enables interrupts on INTENSET, so each instance's IRQ line and handler + * must belong to that group. */ +#define GPIOTE20_IRQn_GRP NRFX_CONCAT_3(GPIOTE20_, GPIOTE_IRQ_GROUP, _IRQn) +#define GPIOTE20_IRQHandler_GRP NRFX_CONCAT_3(GPIOTE20_, GPIOTE_IRQ_GROUP, _IRQHandler) +#define GPIOTE30_IRQn_GRP NRFX_CONCAT_3(GPIOTE30_, GPIOTE_IRQ_GROUP, _IRQn) +#define GPIOTE30_IRQHandler_GRP NRFX_CONCAT_3(GPIOTE30_, GPIOTE_IRQ_GROUP, _IRQHandler) + #define NUMBER_OF_GPIO_TE 8 #ifdef GPIOTE_CONFIG_PORT_Msk @@ -38,43 +42,66 @@ #define GPIOTE_CONFIG_PORT_PIN_Msk GPIOTE_CONFIG_PSEL_Msk #endif -static voidFuncPtr callbacksInt[NUMBER_OF_GPIO_TE]; -static bool callbackDeferred[NUMBER_OF_GPIO_TE]; -static int8_t channelMap[NUMBER_OF_GPIO_TE]; -static int enabled = 0; +typedef struct { + NRF_GPIOTE_Type *reg; + IRQn_Type irqn; + uint8_t nChannels; + bool enabled; + voidFuncPtr callbacksInt[NUMBER_OF_GPIO_TE]; + bool callbackDeferred[NUMBER_OF_GPIO_TE]; + int8_t channelMap[NUMBER_OF_GPIO_TE]; +} gpiote_instance_t; + +static gpiote_instance_t gpiote20 = { NRF_GPIOTE20, GPIOTE20_IRQn_GRP, 8, false, {0}, {0}, {0} }; +static gpiote_instance_t gpiote30 = { NRF_GPIOTE30, GPIOTE30_IRQn_GRP, 4, false, {0}, {0}, {0} }; + +/* Pick the GPIOTE that can see this GPIO pin, or NULL when none can (P2). */ +static gpiote_instance_t *instanceForPin(uint32_t pin) +{ + switch (pin >> 5) { + case 0: return &gpiote30; + case 1: return &gpiote20; + default: return NULL; + } +} /* Configure I/O interrupt sources */ -static void __initialize() +static void __initialize(gpiote_instance_t *inst) { - memset(callbacksInt, 0, sizeof(callbacksInt)); - memset(channelMap, -1, sizeof(channelMap)); - memset(callbackDeferred, 0, sizeof(callbackDeferred)); - - NVIC_DisableIRQ(GPIOTE_IRQn); - NVIC_ClearPendingIRQ(GPIOTE_IRQn); - NVIC_SetPriority(GPIOTE_IRQn, 3); - NVIC_EnableIRQ(GPIOTE_IRQn); + memset(inst->callbacksInt, 0, sizeof(inst->callbacksInt)); + memset(inst->channelMap, -1, sizeof(inst->channelMap)); + memset(inst->callbackDeferred, 0, sizeof(inst->callbackDeferred)); + + NVIC_DisableIRQ(inst->irqn); + NVIC_ClearPendingIRQ(inst->irqn); + NVIC_SetPriority(inst->irqn, 3); + NVIC_EnableIRQ(inst->irqn); } /* * \brief Specifies a named Interrupt Service Routine (ISR) to call when an interrupt occurs. * Replaces any previous function that was attached to the interrupt. * - * \return Interrupt Mask + * \return Interrupt Mask, 0 when the pin cannot raise interrupts or no channel is free */ int attachInterrupt(uint32_t pin, voidFuncPtr callback, uint32_t mode) { - if (!enabled) { - __initialize(); - enabled = 1; - } - if (pin >= PINS_COUNT) { return 0; } pin = g_ADigitalPinMap[pin]; + gpiote_instance_t *inst = instanceForPin(pin); + if (inst == NULL) { + return 0; // P2 has no GPIOTE + } + + if (!inst->enabled) { + __initialize(inst); + inst->enabled = true; + } + bool deferred = (mode & ISR_DEFERRED) ? true : false; mode &= ~ISR_DEFERRED; @@ -98,7 +125,7 @@ int attachInterrupt(uint32_t pin, voidFuncPtr callback, uint32_t mode) } // All information for the configuration is known, except the prior values - // of the config register. Pre-compute the mask and new bits for later use. + // of the config register. Pre-compute the mask and new bits for later use: // CONFIG[n] = (CONFIG[n] & oldRegMask) | newRegBits; // // Three fields are configured here: PORT/PIN, POLARITY, MODE @@ -112,17 +139,17 @@ int attachInterrupt(uint32_t pin, voidFuncPtr callback, uint32_t mode) int newChannel = 0; // Find channel where pin is already assigned, if any - for (int i = 0; i < NUMBER_OF_GPIO_TE; i++) { - if ((uint32_t)channelMap[i] != pin) continue; + for (int i = 0; i < inst->nChannels; i++) { + if ((uint32_t)inst->channelMap[i] != pin) continue; ch = i; break; } // else, find one not already mapped and also not in use by others if (ch == -1) { - for (int i = 0; i < NUMBER_OF_GPIO_TE; i++) { - if (channelMap[i] != -1) continue; - if (nrf_gpiote_te_is_enabled(NRF_GPIOTE, i)) continue; - + for (int i = 0; i < inst->nChannels; i++) { + if (inst->channelMap[i] != -1) continue; + if (nrf_gpiote_te_is_enabled(inst->reg, i)) continue; + ch = i; newChannel = 1; break; @@ -133,28 +160,37 @@ int attachInterrupt(uint32_t pin, voidFuncPtr callback, uint32_t mode) return 0; // no channel available } - channelMap[ch] = pin; // harmless for existing channel - callbacksInt[ch] = callback; // caller might be updating this for existing channel - callbackDeferred[ch] = deferred; // caller might be updating this for existing channel + inst->channelMap[ch] = pin; // harmless for existing channel + inst->callbacksInt[ch] = callback; // caller might be updating this for existing channel + inst->callbackDeferred[ch] = deferred; // caller might be updating this for existing channel - uint32_t tmp = NRF_GPIOTE->CONFIG[ch]; + uint32_t tmp = inst->reg->CONFIG[ch]; tmp &= oldRegMask; tmp |= newRegBits; // for existing channel, effectively updates only the polarity - NRF_GPIOTE->CONFIG[ch] = tmp; + inst->reg->CONFIG[ch] = tmp; // For a new channel, additionally ensure no old events existed, and enable the interrupt if (newChannel) { - NRF_GPIOTE->EVENTS_IN[ch] = 0; + inst->reg->EVENTS_IN[ch] = 0; // nRF54L GPIOTE splits INTENSET/INTENCLR per IRQ group (INTENSET0, // INTENSET1, ...). The HAL routes to the right one based on // NRF_GPIOTE_IRQ_GROUP from the MDK interim header. - nrf_gpiote_int_enable(NRF_GPIOTE, (1 << ch)); + nrf_gpiote_int_enable(inst->reg, (1 << ch)); } // Finally, indicate to caller the allocated / updated channel return (1 << ch); } +NRF_GPIOTE_Type *digitalPinToGpiote(uint32_t pin) +{ + if (pin >= PINS_COUNT) { + return NULL; + } + gpiote_instance_t *inst = instanceForPin(g_ADigitalPinMap[pin]); + return inst ? inst->reg : NULL; +} + /* * \brief Turns off the given interrupt. */ @@ -166,22 +202,27 @@ void detachInterrupt(uint32_t pin) pin = g_ADigitalPinMap[pin]; - for (int ch = 0; ch < NUMBER_OF_GPIO_TE; ch++) { - if ((uint32_t)channelMap[ch] == pin) { - nrf_gpiote_int_disable(NRF_GPIOTE, (1 << ch)); - NRF_GPIOTE->CONFIG[ch] = 0; - NRF_GPIOTE->EVENTS_IN[ch] = 0; // clear any final events + gpiote_instance_t *inst = instanceForPin(pin); + if (inst == NULL || !inst->enabled) { + return; + } + + for (int ch = 0; ch < inst->nChannels; ch++) { + if ((uint32_t)inst->channelMap[ch] == pin) { + nrf_gpiote_int_disable(inst->reg, (1 << ch)); + inst->reg->CONFIG[ch] = 0; + inst->reg->EVENTS_IN[ch] = 0; // clear any final events // now cleanup the rest of the use of the channel - channelMap[ch] = -1; - callbacksInt[ch] = NULL; - callbackDeferred[ch] = false; + inst->channelMap[ch] = -1; + inst->callbacksInt[ch] = NULL; + inst->callbackDeferred[ch] = false; break; } } } -void GPIOTE_IRQHandler() +static void gpiote_irq(gpiote_instance_t *inst) { #if CFG_SYSVIEW SEGGER_SYSVIEW_RecordEnterISR(); @@ -189,28 +230,28 @@ void GPIOTE_IRQHandler() // Read this once (not 8x), as it's a volatile read // across the AHB, which adds up to 3 cycles. - uint32_t const enabledInterruptMask = nrf_gpiote_int_enable_check(NRF_GPIOTE, ~0u); - for (int ch = 0; ch < NUMBER_OF_GPIO_TE; ch++) { + uint32_t const enabledInterruptMask = nrf_gpiote_int_enable_check(inst->reg, ~0u); + for (int ch = 0; ch < inst->nChannels; ch++) { // only process where the interrupt is enabled and the event register is set // check interrupt enabled mask first, as already read that IOM value, to // reduce delays from AHB (16MHz) reads. if ( 0 == (enabledInterruptMask & (1 << ch))) continue; - if ( 0 == NRF_GPIOTE->EVENTS_IN[ch]) continue; + if ( 0 == inst->reg->EVENTS_IN[ch]) continue; // If the event was set and interrupts are enabled, // call the callback function only if it exists, // but ALWAYS clear the event to prevent an interrupt storm. - if (channelMap[ch] != -1 && callbacksInt[ch]) { - if ( callbackDeferred[ch] ) { + if (inst->channelMap[ch] != -1 && inst->callbacksInt[ch]) { + if ( inst->callbackDeferred[ch] ) { // Adafruit defer callback to non-isr if configured so - ada_callback(NULL, 0, callbacksInt[ch]); + ada_callback(NULL, 0, inst->callbacksInt[ch]); } else { - callbacksInt[ch](); + inst->callbacksInt[ch](); } } // clear the event - NRF_GPIOTE->EVENTS_IN[ch] = 0; + inst->reg->EVENTS_IN[ch] = 0; } // Ensure event clear completes before ISR returns __DSB(); __NOP();__NOP();__NOP();__NOP(); @@ -219,3 +260,13 @@ void GPIOTE_IRQHandler() SEGGER_SYSVIEW_RecordExitISR(); #endif } + +void GPIOTE20_IRQHandler_GRP(void) +{ + gpiote_irq(&gpiote20); +} + +void GPIOTE30_IRQHandler_GRP(void) +{ + gpiote_irq(&gpiote30); +} diff --git a/cores/nRF5/WInterrupts.h b/cores/nRF5/WInterrupts.h index 3e93d17..2ca7e3c 100644 --- a/cores/nRF5/WInterrupts.h +++ b/cores/nRF5/WInterrupts.h @@ -20,6 +20,7 @@ #define _WIRING_INTERRUPTS_ #include +#include "nrf.h" #ifdef __cplusplus extern "C" { @@ -46,6 +47,13 @@ typedef void (*voidFuncPtr)(void); */ int attachInterrupt(uint32_t pin, voidFuncPtr callback, uint32_t mode); +/* + * \brief The GPIOTE instance that serves the given pin, or NULL when none does: P0 pins use + * GPIOTE30, P1 pins GPIOTE20, and P2 has no GPIOTE. The mask returned by + * attachInterrupt() refers to this instance's channels. + */ +NRF_GPIOTE_Type *digitalPinToGpiote(uint32_t pin); + /* * \brief Turns off the given interrupt. */ diff --git a/cores/nRF5/freertos/config/FreeRTOSConfig.h b/cores/nRF5/freertos/config/FreeRTOSConfig.h index 7f11150..492e50b 100644 --- a/cores/nRF5/freertos/config/FreeRTOSConfig.h +++ b/cores/nRF5/freertos/config/FreeRTOSConfig.h @@ -182,8 +182,8 @@ standard names - or at least those used in the unmodified vector table. */ /* GRTC SYSCOUNTER runs at 1 MHz */ #define configSYSTICK_CLOCK_HZ ( 1000000UL ) -/* The tick uses GRTC IRQ group 0 (INTENSET0 in port_cmsis_systick.c); the SoftDevice owns group 3. */ -#define xPortSysTickHandler GRTC_0_IRQHandler +/* The tick uses the application core's GRTC group 2 (portNRF_GRTC_DOMAIN); the SoftDevice owns group 3. */ +#define xPortSysTickHandler GRTC_2_IRQHandler /* CM33 TrustZone / MPU not used */ #define configENABLE_TRUSTZONE 0 diff --git a/cores/nRF5/freertos/portable/CMSIS/nrf54l/port_cmsis_systick.c b/cores/nRF5/freertos/portable/CMSIS/nrf54l/port_cmsis_systick.c index f166c5f..3170da5 100644 --- a/cores/nRF5/freertos/portable/CMSIS/nrf54l/port_cmsis_systick.c +++ b/cores/nRF5/freertos/portable/CMSIS/nrf54l/port_cmsis_systick.c @@ -28,6 +28,7 @@ /* Scheduler includes. */ #include "nrfy_grtc.h" +#include "nrfx_grtc.h" #include "FreeRTOS.h" #include "task.h" #include "nrf_nvic.h" @@ -51,20 +52,27 @@ /*-----------------------------------------------------------*/ -/* Read the low 32 bits of the GRTC SYSCOUNTER. - * SYSCOUNTER is 52-bit but we only need 32-bit for tick counting. */ -static inline uint32_t grtc_counter_get(void) +/* Read the 52-bit GRTC SYSCOUNTER of this core's domain (NRF_GRTC_DOMAIN_INDEX is GRTC_IRQ_GROUP, + * which the #error in portmacro_cmsis.h ties to portNRF_GRTC_DOMAIN). nrfy re-reads until BUSY and + * OVERFLOW clear: after an idle sleep the counter is not readable until it settles. Working in + * 64 bits leaves no 2^32 epoch to reconstruct, although the counter passes 2^32 about 71 min after + * power-on and keeps running across soft resets. */ +static inline uint64_t grtc_counter_get(void) { - /* Reading SYSCOUNTERL latches SYSCOUNTERH for coherent 64-bit read, - * but we only need the low 32 bits. */ - return (uint32_t)(portNRF_GRTC_REG->SYSCOUNTER[0].SYSCOUNTERL); + return nrfy_grtc_sys_counter_get(portNRF_GRTC_REG); } -/* Set compare channel value */ -static inline void grtc_cc_set(uint32_t cc_channel, uint32_t val) +/* Arm a compare channel at an absolute SYSCOUNTER value. A target already in the past raises the + * event at once, which is what a late tick needs. Writing CCL before CCH can also form a value in + * the past for a moment; drop that spurious event while the target itself is still ahead, as + * nrfx_grtc_syscounter_cc_abs_set(..., safe_setting = true) does. */ +static inline void grtc_cc_set(uint32_t cc_channel, uint64_t val) { - portNRF_GRTC_REG->CC[cc_channel].CCL = val; - portNRF_GRTC_REG->CC[cc_channel].CCH = 0; /* High word = 0 for 32-bit compare */ + nrfy_grtc_sys_counter_cc_set(portNRF_GRTC_REG, cc_channel, val); + if (nrfy_grtc_sys_counter_compare_event_check(portNRF_GRTC_REG, cc_channel) && (val > grtc_counter_get())) + { + nrfy_grtc_sys_counter_compare_event_clear(portNRF_GRTC_REG, cc_channel); + } } /* Clear compare event */ @@ -79,19 +87,26 @@ static inline void grtc_event_compare_clear(uint32_t cc_channel) /* Enable compare interrupt for channel */ static inline void grtc_int_compare_enable(uint32_t cc_channel) { - portNRF_GRTC_REG->INTENSET0 = (1UL << cc_channel); + portNRF_GRTC_REG->portNRF_GRTC_INTENSET = (1UL << cc_channel); } /* Disable compare interrupt for channel */ static inline void grtc_int_compare_disable(uint32_t cc_channel) { - portNRF_GRTC_REG->INTENCLR0 = (1UL << cc_channel); + portNRF_GRTC_REG->portNRF_GRTC_INTENCLR = (1UL << cc_channel); } /*-----------------------------------------------------------*/ -// SYSCOUNTER value at scheduler start; the counter survives resets, so ticks count from here. -static uint32_t grtc_tick_base; +/* SYSCOUNTER value at which the next OS tick falls due. Ticks stay on this grid, which starts with + * the scheduler, instead of being re-armed one period after whenever the interrupt happened to run. */ +static uint64_t grtc_next_tick; + +/* A backlog (core halted in a debugger, a long critical section) is caught up at most this many + * ticks per interrupt, so the ISR stays short; the compare is then left behind and fires again. */ +#define portNRF_GRTC_CATCHUP_MAX ((TickType_t)(4 * configTICK_RATE_HZ)) +/* Interrupts that found more than portNRF_GRTC_CATCHUP_MAX ticks due; readable from a debugger. */ +static volatile uint32_t grtc_tick_backlogs; void xPortSysTickHandler( void ) { @@ -103,22 +118,31 @@ void xPortSysTickHandler( void ) BaseType_t switch_req = pdFALSE; uint32_t isrstate = portSET_INTERRUPT_MASK_FROM_ISR(); - uint32_t systick_counter = grtc_counter_get(); + uint64_t const now = grtc_counter_get(); if (configUSE_DISABLE_TICK_AUTO_CORRECTION_DEBUG == 0) { - /* Auto-correct missed ticks. + /* Auto-correct missed ticks: every grid tick that has fallen due. * GRTC runs at configSYSTICK_CLOCK_HZ (1 MHz). * Each OS tick = portNRF_GRTC_TICKS_PER_SYSTICK GRTC ticks. */ - TickType_t diff; - uint32_t expected_counter = grtc_tick_base + xTaskGetTickCount() * portNRF_GRTC_TICKS_PER_SYSTICK; - diff = (systick_counter - expected_counter) / portNRF_GRTC_TICKS_PER_SYSTICK; + TickType_t diff = 0; + if (now >= grtc_next_tick) + { + diff = (TickType_t)((now - grtc_next_tick) / portNRF_GRTC_TICKS_PER_SYSTICK) + 1; + } /* At most 1 step if scheduler is suspended */ if ((diff > 1) && (xTaskGetSchedulerState() != taskSCHEDULER_RUNNING)) { diff = 1; } + else if (diff > portNRF_GRTC_CATCHUP_MAX) + { + grtc_tick_backlogs++; + diff = portNRF_GRTC_CATCHUP_MAX; + } + + grtc_next_tick += (uint64_t)diff * portNRF_GRTC_TICKS_PER_SYSTICK; while ((diff--) > 0) { switch_req |= xTaskIncrementTick(); @@ -127,11 +151,18 @@ void xPortSysTickHandler( void ) else { switch_req = xTaskIncrementTick(); + grtc_next_tick = now + portNRF_GRTC_TICKS_PER_SYSTICK; } - /* Schedule next compare */ + /* Schedule next compare: the next tick on the grid. When ticks are still due it lies in the past + * and fires at once, except while the scheduler is suspended, which would only spin through here: + * then wait one tick period. */ { - uint32_t next_cc = grtc_counter_get() + portNRF_GRTC_TICKS_PER_SYSTICK; + uint64_t next_cc = grtc_next_tick; + if ((next_cc <= now) && (xTaskGetSchedulerState() == taskSCHEDULER_SUSPENDED)) + { + next_cc = now + portNRF_GRTC_TICKS_PER_SYSTICK; + } grtc_cc_set(portNRF_GRTC_CC_CH, next_cc); } @@ -168,16 +199,36 @@ void vPortSetupTimerInterrupt( void ) nrfy_grtc_prepare(NRF_GRTC, true); nrfy_grtc_sys_counter_start(NRF_GRTC, true); } - /* The GRTC survives soft resets, so set this outside the start path: sd_softdevice_enable() requires AUTOEN. */ - nrf_grtc_sys_counter_auto_mode_set(NRF_GRTC, true); + /* The GRTC survives soft resets, so configure its sleep outside the start path. Nothing else does: + * nrfx_grtc_init() would, but nothing calls it and the nrfx GRTC driver is not enabled here. At the + * reset values, TIMEOUT 0 and WAKETIME 1, the SYSCOUNTER stops as soon as the CPU sleeps and gets a + * single 32 kHz cycle to wake up before a compare. Every idle hang caught on the DK had a SoftDevice + * compare 1.5-2 of those cycles past the stop that never fired, and no later one fired either, so the + * CPU slept until the watchdog. Apply NRFX_GRTC_SLEEP_DEFAULT_CONFIG (TIMEOUT 5, WAKETIME 4, and + * AUTOEN, which sd_softdevice_enable() requires) the way nrfx_grtc_sleep_configure() does, with the + * SYSCOUNTER stopped for the write. The SoftDevice is not enabled yet when the scheduler starts. */ + { + nrfx_grtc_sleep_config_t const sleep_cfg = NRFX_GRTC_SLEEP_DEFAULT_CONFIG; + bool const active = nrfy_grtc_sys_counter_check(NRF_GRTC); + if (active) + { + nrfy_grtc_sys_counter_set(NRF_GRTC, false); + } + nrfy_grtc_sys_counter_auto_mode_set(NRF_GRTC, sleep_cfg.auto_mode); + nrfy_grtc_timeout_set(NRF_GRTC, sleep_cfg.timeout); + nrfy_grtc_waketime_set(NRF_GRTC, sleep_cfg.waketime); + if (active) + { + nrfy_grtc_sys_counter_set(NRF_GRTC, true); + } + } /* Clear any pending event */ grtc_event_compare_clear(portNRF_GRTC_CC_CH); - /* Set first compare value */ - uint32_t now = grtc_counter_get(); - grtc_tick_base = now; - grtc_cc_set(portNRF_GRTC_CC_CH, now + portNRF_GRTC_TICKS_PER_SYSTICK); + /* First tick one period from now: the grid starts here */ + grtc_next_tick = grtc_counter_get() + portNRF_GRTC_TICKS_PER_SYSTICK; + grtc_cc_set(portNRF_GRTC_CC_CH, grtc_next_tick); /* Enable compare interrupt */ grtc_int_compare_enable(portNRF_GRTC_CC_CH); @@ -190,13 +241,7 @@ void vPortSetupTimerInterrupt( void ) void vPortSuppressTicksAndSleep( TickType_t xExpectedIdleTime ) { - TickType_t enterTime; - - /* Make sure the expected idle time does not overflow the counter. */ - if ( xExpectedIdleTime > portNRF_GRTC_MAXTICKS - configEXPECTED_IDLE_TIME_BEFORE_SLEEP ) - { - xExpectedIdleTime = portNRF_GRTC_MAXTICKS - configEXPECTED_IDLE_TIME_BEFORE_SLEEP; - } + /* No cap on xExpectedIdleTime: in 64 bits even portMAX_DELAY ticks (~49 days) fit the 52-bit compare. */ /* Block all the interrupts globally */ #ifdef SOFTDEVICE_PRESENT @@ -209,20 +254,19 @@ void vPortSuppressTicksAndSleep( TickType_t xExpectedIdleTime ) __disable_irq(); #endif - enterTime = grtc_counter_get(); - if ( eTaskConfirmSleepModeStatus() != eAbortSleep ) { TickType_t xModifiableIdleTime; - /* Convert OS ticks to GRTC ticks for wakeup time */ - uint32_t wakeupTime = (enterTime + xExpectedIdleTime * portNRF_GRTC_TICKS_PER_SYSTICK) & portNRF_GRTC_MAXTICKS; + /* Wake on the grid, when the tick that unblocks a task falls due: the next tick is due at + * grtc_next_tick and xExpectedIdleTime counts it. */ + uint64_t const wakeupTime = grtc_next_tick + (uint64_t)(xExpectedIdleTime - 1) * portNRF_GRTC_TICKS_PER_SYSTICK; /* Disable periodic tick interrupt, use compare for wakeup */ grtc_int_compare_disable(portNRF_GRTC_CC_CH); - /* Configure compare for wakeup */ - grtc_cc_set(portNRF_GRTC_CC_CH, wakeupTime); + /* Configure compare for wakeup. Clear first: grtc_cc_set() keeps the event of a target already passed. */ grtc_event_compare_clear(portNRF_GRTC_CC_CH); + grtc_cc_set(portNRF_GRTC_CC_CH, wakeupTime); grtc_int_compare_enable(portNRF_GRTC_CC_CH); __DSB(); @@ -246,12 +290,15 @@ void vPortSuppressTicksAndSleep( TickType_t xExpectedIdleTime ) #endif { - /* S145 does not provide sd_app_evt_wait(); use WFE directly. - * BASEPRI cannot be used because it would prevent WFE from waking up. */ - do{ - __WFE(); - } while (0 == (NVIC->ISPR[0] | NVIC->ISPR[1] | NVIC->ISPR[2] | NVIC->ISPR[3] - | NVIC->ISPR[4] | NVIC->ISPR[5] | NVIC->ISPR[6] | NVIC->ISPR[7])); + /* S145 does not provide sd_app_evt_wait(), so the idle task sleeps on its own with + * PRIMASK set. Use WFI, not WFE: WFI completes as soon as an enabled interrupt is + * pending, PRIMASK or not, which is exactly the wake-up this sleep needs. WFE only + * wakes through SEVONPEND turning the pending transition into an event, and on the + * nRF54L15 the core has been found asleep for hours in that WFE with the GRTC tick and + * SoftDevice interrupts pending and SEVONPEND set. Zephyr sleeps the same way on this + * part (arch_cpu_idle: cpsid i / BASEPRI 0 / wfi / cpsie i). BASEPRI still cannot be + * used for the masking because it would keep WFI from waking. */ + __WFI(); } } configPOST_SLEEP_PROCESSING( xExpectedIdleTime ); @@ -261,26 +308,30 @@ void vPortSuppressTicksAndSleep( TickType_t xExpectedIdleTime ) /* Correct the system ticks */ { - TickType_t diff; - TickType_t exitTime; - - exitTime = grtc_counter_get(); - /* Convert GRTC ticks back to OS ticks */ - diff = ((exitTime - enterTime) & portNRF_GRTC_MAXTICKS) / portNRF_GRTC_TICKS_PER_SYSTICK; - - /* Re-enable periodic tick via compare */ - uint32_t next_cc = grtc_counter_get() + portNRF_GRTC_TICKS_PER_SYSTICK; - grtc_cc_set(portNRF_GRTC_CC_CH, next_cc); - grtc_event_compare_clear(portNRF_GRTC_CC_CH); - grtc_int_compare_enable(portNRF_GRTC_CC_CH); - - /* It is important that we clear pending here so that our corrections are latest and in sync with tick_interrupt handler */ - NVIC_ClearPendingIRQ(portNRF_GRTC_IRQn); + /* Whole grid ticks that fell due while asleep */ + TickType_t diff = 0; + uint64_t const exitTime = grtc_counter_get(); + if (exitTime >= grtc_next_tick) + { + diff = (TickType_t)((exitTime - grtc_next_tick) / portNRF_GRTC_TICKS_PER_SYSTICK) + 1; + } + /* vTaskStepTick() must not pass the unblock tick. Ticks beyond it stay due, and the tick + * compare armed below in the past catches them up at once. */ if ((configUSE_TICKLESS_IDLE_SIMPLE_DEBUG) && (diff > xExpectedIdleTime)) { diff = xExpectedIdleTime; } + grtc_next_tick += (uint64_t)diff * portNRF_GRTC_TICKS_PER_SYSTICK; + + /* It is important that we clear pending here so that our corrections are latest and in sync with + * tick_interrupt handler. Done before re-arming: a compare armed in the past must still fire. */ + grtc_event_compare_clear(portNRF_GRTC_CC_CH); + NVIC_ClearPendingIRQ(portNRF_GRTC_IRQn); + + /* Re-enable periodic tick via compare */ + grtc_cc_set(portNRF_GRTC_CC_CH, grtc_next_tick); + grtc_int_compare_enable(portNRF_GRTC_CC_CH); BaseType_t switch_req = pdFALSE; diff --git a/cores/nRF5/freertos/portable/CMSIS/nrf54l/portmacro_cmsis.h b/cores/nRF5/freertos/portable/CMSIS/nrf54l/portmacro_cmsis.h index 9a809d3..32d8289 100644 --- a/cores/nRF5/freertos/portable/CMSIS/nrf54l/portmacro_cmsis.h +++ b/cores/nRF5/freertos/portable/CMSIS/nrf54l/portmacro_cmsis.h @@ -78,12 +78,20 @@ typedef unsigned long UBaseType_t; /* GRTC configuration for nRF54L FreeRTOS tick */ #define portNRF_GRTC_REG NRF_GRTC #define portNRF_GRTC_CC_CH 4 -/* IRQ group 0, matching the INTENSET0/INTENCLR0 writes in port_cmsis_systick.c */ -#define portNRF_GRTC_IRQn GRTC_0_IRQn +/* The tick must use the GRTC domain of the CPU it runs on: SYSCOUNTER index, INTEN group and IRQ. + * The SYSCOUNTER sleeps along with the CPU and only wakes ahead of a compare armed in the sleeping + * domain's own group. With group 0 (the FLPR's) the tick compare did not wake it: the core stayed + * in the idle WFI with the counter stopped until a debugger read woke it. The secure application + * core is domain 2 (GRTC_IRQ_GROUP in nrf54l15_interim.h); the SoftDevice owns group 3. */ +#define portNRF_GRTC_DOMAIN 2 +#define portNRF_GRTC_INTENSET INTENSET2 +#define portNRF_GRTC_INTENCLR INTENCLR2 +#define portNRF_GRTC_IRQn GRTC_2_IRQn +#if defined(GRTC_IRQ_GROUP) && (GRTC_IRQ_GROUP != portNRF_GRTC_DOMAIN) +#error "portNRF_GRTC_DOMAIN does not match this core's GRTC_IRQ_GROUP" +#endif /* GRTC SYSCOUNTER runs at 1 MHz (not LFCLK). configSYSTICK_CLOCK_HZ = 1000000 */ #define portNRF_GRTC_TICKS_PER_SYSTICK ( configSYSTICK_CLOCK_HZ / configTICK_RATE_HZ ) -/* 32-bit compare window */ -#define portNRF_GRTC_MAXTICKS (0xFFFFFFFFUL) /*-----------------------------------------------------------*/ /* Scheduler utilities. */ diff --git a/libraries/Bluefruit54Lib/src/bluefruit.cpp b/libraries/Bluefruit54Lib/src/bluefruit.cpp index 3b05fcc..6408faa 100644 --- a/libraries/Bluefruit54Lib/src/bluefruit.cpp +++ b/libraries/Bluefruit54Lib/src/bluefruit.cpp @@ -666,6 +666,21 @@ extern "C" void SD_EVT_IRQHandler(void) #endif } +// An unanswered seed request leaves the SoftDevice RNG unseeded, so the SOC task retries a failed seed +// between event batches rather than inline, where it would hold up flash completions. +static volatile bool _seed_pending = false; + +// The InternalFS flash driver drains the SoC event queue while it waits for a flash completion and +// hands every other event to this hook, so a seed request it pulls out never reaches the SOC task +// below: pass it on, waking the task through SD_EVT_IRQn. Weak: an application that reads the SoC +// events itself can take them over. +extern "C" __attribute__((weak)) void flash_nrf5x_soc_event_hook(uint32_t soc_evt) +{ + if ( soc_evt != NRF_EVT_RAND_SEED_REQUEST ) return; + _seed_pending = true; + NVIC_SetPendingIRQ(SD_EVT_IRQn); +} + /** * Handle SOC event such as FLASH operation */ @@ -673,15 +688,16 @@ void adafruit_soc_task(void* arg) { (void) arg; - // An unanswered seed request leaves the SoftDevice RNG unseeded, so a failed seed is retried between - // event batches rather than inline, where it would hold up flash completions. - bool seed_pending = false; - while (1) { - if ( seed_pending ) seed_pending = !seed_softdevice_rng(); + // Cleared first, so a request the flash hook passes on meanwhile is not lost + if ( _seed_pending ) + { + _seed_pending = false; + if ( !seed_softdevice_rng() ) _seed_pending = true; + } - if ( xSemaphoreTake(Bluefruit._soc_event_sem, seed_pending ? pdMS_TO_TICKS(10) : portMAX_DELAY) ) + if ( xSemaphoreTake(Bluefruit._soc_event_sem, _seed_pending ? pdMS_TO_TICKS(10) : portMAX_DELAY) ) { uint32_t soc_evt; uint32_t err = ERROR_NONE; @@ -701,7 +717,7 @@ void adafruit_soc_task(void* arg) break; case NRF_EVT_RAND_SEED_REQUEST: - seed_pending = true; + _seed_pending = true; break; default: break; diff --git a/libraries/InternalFileSytem/src/InternalFileSystem.cpp b/libraries/InternalFileSytem/src/InternalFileSystem.cpp index 2856b2c..6f462e0 100644 --- a/libraries/InternalFileSytem/src/InternalFileSystem.cpp +++ b/libraries/InternalFileSytem/src/InternalFileSystem.cpp @@ -59,7 +59,9 @@ static int _internal_flash_prog (const struct lfs_config *c, lfs_block_t block, (void) c; uint32_t addr = lba2addr(block) + off; - VERIFY( flash_nrf5x_write(addr, buffer, size), -1) + // A short or negative count means a page flush failed: tell LittleFS instead of pretending + // the block was programmed (it then reports the error to the caller and keeps the old state). + if ( flash_nrf5x_write(addr, buffer, size) != (int) size ) return LFS_ERR_IO; return 0; } @@ -77,7 +79,8 @@ static int _internal_flash_erase (const struct lfs_config *c, lfs_block_t block) // implement as write 0xff to whole block address for(int i=0; i cache_addr ) { - flash_cache_flush(fc); + // The old page holds writes already reported as done: keep it cached rather than replace it + if ( !flash_cache_flush(fc) ) return -1; fc->cache_addr = page_addr; // read a whole page from flash @@ -73,26 +77,51 @@ int flash_cache_write (flash_cache_t* fc, uint32_t dst, void const * src, uint32 dst += wr_bytes; } - return len - remain; + return (int) (len - remain); } -void flash_cache_flush (flash_cache_t* fc) +bool flash_cache_flush (flash_cache_t* fc) { - if ( fc->cache_addr == FLASH_CACHE_INVALID_ADDR ) return; + if ( fc->cache_addr == FLASH_CACHE_INVALID_ADDR ) return true; + + uint32_t const page = fc->cache_addr; + bool ok = true; + + // Written in place (RRAM): send only the chunks that differ from the flash. The rest of the + // page is never touched, so an interrupted flush can only damage the chunk in flight, not the + // other 15 LittleFS blocks sharing the page (with erase-then-program a hang mid-page left the + // whole page, superblocks included, at 0xFF). + bool led = false; - // skip erase & program if verify() exists, and memory matches - if ( !(fc->verify && fc->verify(fc->cache_addr, fc->cache_buf, FLASH_CACHE_SIZE)) ) + for ( uint32_t off = 0; off < FLASH_CACHE_SIZE; off += FLASH_CACHE_WRITE_CHUNK ) { - // indicator TODO allow to disable flash indicator - ledOn(LED_BUILTIN); + if ( fc->verify && fc->verify(page + off, fc->cache_buf + off, FLASH_CACHE_WRITE_CHUNK) ) continue; - fc->erase(fc->cache_addr); - fc->program(fc->cache_addr, fc->cache_buf, FLASH_CACHE_SIZE); + if ( !led ) + { + ledOn(LED_BUILTIN); + led = true; + } - ledOff(LED_BUILTIN); + if ( fc->program(page + off, fc->cache_buf + off, FLASH_CACHE_WRITE_CHUNK) != FLASH_CACHE_WRITE_CHUNK ) + { + ok = false; + break; + } } - fc->cache_addr = FLASH_CACHE_INVALID_ADDR; + if ( led ) ledOff(LED_BUILTIN); + + // On failure the page stays cached, so its data is neither lost nor read back stale, and the + // next flush writes it again (only the chunks that still differ). A page that keeps failing is + // given up, though: it would refuse every write to any other page, InternalFS.format()'s erases + // included, and its data does not survive a reboot anyway. + if ( ok || ++fc->flush_failures >= FLASH_CACHE_MAX_FLUSH_FAILURES ) + { + fc->cache_addr = FLASH_CACHE_INVALID_ADDR; + fc->flush_failures = 0; + } + return ok; } int flash_cache_read (flash_cache_t* fc, void* dst, uint32_t addr, uint32_t count) diff --git a/libraries/InternalFileSytem/src/flash/flash_cache.h b/libraries/InternalFileSytem/src/flash/flash_cache.h index bb42ba6..fa6a0ca 100644 --- a/libraries/InternalFileSytem/src/flash/flash_cache.h +++ b/libraries/InternalFileSytem/src/flash/flash_cache.h @@ -31,23 +31,31 @@ #define FLASH_CACHE_SIZE 4096 // must be a erasable page size #define FLASH_CACHE_INVALID_ADDR 0xffffffff +// Granularity of a flush: only the chunks of the cached page that differ from the flash are +// written, in place. Must be a multiple of 4 and divide FLASH_CACHE_SIZE. +#define FLASH_CACHE_WRITE_CHUNK 128 + typedef struct { - bool (*erase) (uint32_t addr); uint32_t (*program) (uint32_t dst, void const * src, uint32_t len); uint32_t (*read) (void* dst, uint32_t src, uint32_t len); bool (*verify) (uint32_t addr, void const * buf, uint32_t len); uint32_t cache_addr; uint8_t* cache_buf; + uint8_t flush_failures; // consecutive failed flushes of the cached page } flash_cache_t; #ifdef __cplusplus extern "C" { #endif +// Returns count, or -1 when a page flush forced by this write failed. The page that could not be +// flushed stays cached, and the data of this write is not taken (part of it may be, when it spans pages). int flash_cache_write (flash_cache_t* fc, uint32_t dst, void const *src, uint32_t count); -void flash_cache_flush (flash_cache_t* fc); +// Returns false when the page could not be written completely. It then stays cached for the next +// flush, up to FLASH_CACHE_MAX_FLUSH_FAILURES failures in a row, after which it is dropped. +bool flash_cache_flush (flash_cache_t* fc); int flash_cache_read (flash_cache_t* fc, void* dst, uint32_t addr, uint32_t count); #ifdef __cplusplus diff --git a/libraries/InternalFileSytem/src/flash/flash_nrf5x.c b/libraries/InternalFileSytem/src/flash/flash_nrf5x.c index 1f1487b..30c5d46 100644 --- a/libraries/InternalFileSytem/src/flash/flash_nrf5x.c +++ b/libraries/InternalFileSytem/src/flash/flash_nrf5x.c @@ -43,52 +43,200 @@ extern uint32_t __flash_arduino_end[]; static SemaphoreHandle_t _sem = NULL; static uint32_t _flash_op_result = NRF_EVT_FLASH_OPERATION_SUCCESS; +// SoftDevice flash operations accepted, and completions seen for them. The SoftDevice runs one +// operation at a time and reports each one in order, so every accepted operation has finished once +// the two counts match; the semaphore only wakes the waiting task. Counting, instead of taking the +// semaphore once per operation, keeps the completion of a write whose wait timed out from being +// taken for the completion of the next one. +static volatile uint32_t _ops_issued = 0; +static volatile uint32_t _ops_completed = 0; + +flash_nrf5x_stats_t flash_nrf5x_stats; + +// The completion semaphore used to be created by fal_erase() only, which always ran before +// fal_program(); with in-place writes a program can be the first operation, so create it here. +static bool ensure_sem (void) +{ + if ( _sem == NULL ) { + _sem = xSemaphoreCreateBinary(); + } + return _sem != NULL; +} + +static void flash_op_completed (uint32_t event) +{ + // Record the result, for consumption by fal_erase or fal_program + // Used to reattempt failed operations + _flash_op_result = event; + __atomic_fetch_add(&_ops_completed, 1, __ATOMIC_SEQ_CST); +} + void flash_nrf5x_event_cb (uint32_t event) { if ( _sem ) { - // Record the result, for consumption by fal_erase or fal_program - // Used to reattempt failed operations - _flash_op_result = event; + flash_op_completed(event); + flash_nrf5x_stats.completed++; // Signal to fal_erase or fal_program that our async flash op is now complete xSemaphoreGive(_sem); } } -// How many retry attempts when performing flash operations +// Accepted operations whose completion has not been seen yet +static uint32_t flash_ops_outstanding (void) +{ + taskENTER_CRITICAL(); + int32_t n = (int32_t) (_ops_issued - _ops_completed); + // More completions than operations: one that sd_flash_write_wait() wrote off did report after all + if ( n < 0 ) { + _ops_completed = _ops_issued; + n = 0; + } + taskEXIT_CRITICAL(); + return (uint32_t) n; +} + +// How many times an operation the SoftDevice reports BUSY, or could not fit around the radio, is +// re-issued (5 ms apart) #define MAX_RETRY 20 +// How many times an operation whose completion never arrived is re-issued +#define MAX_TIMEOUT_RETRY 3 -// When soft device is enabled, flash ops are async -// Eventual success is reported via callback, which we await -static uint32_t wait_for_async_flash_op_completion(uint32_t initial_result) -{ - // If initial result not NRF_SUCCESS, no need to await callback - // We will pass the initial result (failure) straight through - int32_t result = initial_result; +// Total time one chunk may take, every attempt and wait included. The filesystem lock is held all +// along, so this, not the number of attempts, is what bounds the stall. +#define FLASH_NRF5X_CHUNK_BUDGET_MS 2000 +// Part of it that settling an operation left outstanding by a timeout may use +#define FLASH_NRF5X_SETTLE_MS 500 +#define FLASH_NRF5X_WAIT_SLICE_MS 20 + +// The SoftDevice reported NRF_EVT_FLASH_OPERATION_ERROR: it could not fit the write around radio +// activity. Retried like BUSY, after a pause. +#define FLASH_NRF5X_ERR_SD_FAILED NRF_ERROR_INTERNAL - // Operation was queued successfully - if (initial_result == NRF_SUCCESS) { +static bool deadline_passed (TickType_t deadline) +{ + return (int32_t) (xTaskGetTickCount() - deadline) >= 0; +} - // Wait for result via callback - xSemaphoreTake(_sem, portMAX_DELAY); +// Application hook for SoC events drained here that are not flash completions +// (power-failure warning, RNG seed request, ...). Bluefruit54Lib provides a weak default that +// answers the RNG seed request; an application can override it. Absent, they are dropped. +void flash_nrf5x_soc_event_hook(uint32_t event) __attribute__((weak)); - // If completed successfully - if (_flash_op_result == NRF_EVT_FLASH_OPERATION_SUCCESS) { - result = NRF_SUCCESS; +// Pull the pending SoC events ourselves: flash completions are counted, the rest go to the hook +static void drain_soc_events (void) +{ + uint32_t evt; + while (sd_evt_get(&evt) == NRF_SUCCESS) { + if (evt == NRF_EVT_FLASH_OPERATION_SUCCESS || evt == NRF_EVT_FLASH_OPERATION_ERROR) { + flash_op_completed(evt); + flash_nrf5x_stats.drained++; + } else if (flash_nrf5x_soc_event_hook) { + flash_nrf5x_soc_event_hook(evt); } + } +} + +// The write just issued has landed once dst reads back as src. flash_words_program() trims every +// write to start and end on a word that differs from the flash, so a match means the SoftDevice has +// written the range through its last word (it writes in address order) and is done reading src, +// which the caller is about to reuse. The counts alone cannot prove it: a completion that +// sd_flash_write_wait() wrote off as lost may still arrive and make them match early. +static bool flash_write_landed (uint32_t dst, uint32_t const * src, uint32_t n_words) +{ + return src == NULL || memcmp((void const *) dst, src, n_words * 4) == 0; +} - // If general failure. - else if (_flash_op_result == NRF_EVT_FLASH_OPERATION_ERROR) { - result = NRF_ERROR_TIMEOUT; +typedef enum +{ + FLASH_WAIT_DONE, + FLASH_WAIT_FAILED, // every completion is in, the last one reported a failure and the data is not there + FLASH_WAIT_TIMEOUT, +} flash_wait_t; + +// Wait until every accepted operation has reported its completion and, when src is given, the write +// just issued has landed; but never past the deadline: the SoC event normally arrives through the +// SoftDevice event task, yet a lost event would otherwise park the calling task forever (seen on +// nRF54L15 during a BLE connection: the whole firmware froze in an unbounded take here). Drain the +// SoC event queue ourselves while waiting so the completion cannot get stuck behind a task that is +// not running, and hand any other SoC event to the application hook. +static flash_wait_t wait_flash_ops (TickType_t deadline, uint32_t dst, uint32_t const * src, uint32_t n_words) +{ + for (;;) { + if ( !flash_ops_outstanding() ) { + if ( flash_write_landed(dst, src, n_words) ) return FLASH_WAIT_DONE; + if ( _flash_op_result == NRF_EVT_FLASH_OPERATION_ERROR ) return FLASH_WAIT_FAILED; } + if ( deadline_passed(deadline) ) return FLASH_WAIT_TIMEOUT; + // Only a wake-up: a give left by a completion counted earlier costs one more pass + if (xSemaphoreTake(_sem, pdMS_TO_TICKS(FLASH_NRF5X_WAIT_SLICE_MS)) == pdTRUE) continue; + drain_soc_events(); + } +} - // If this assert triggers, we need to implement a new NRF_SOC_EVTS value - else { - assert(false); +// Set once settling an outstanding write timed out, so the retries after BUSY do not wait again +static bool _settle_given_up = false; + +// When soft device is enabled, flash ops are async +// Eventual success is reported via callback, which we await +static uint32_t sd_flash_write_wait (TickType_t deadline, uint32_t dst, uint32_t const * src, uint32_t n_words) +{ + flash_nrf5x_stats.in_flight = 1; + + // A write whose wait timed out may still be running, or its completion may still be on its way: + // settle it first, so that completion cannot be taken for the one of the write issued here. + uint32_t owed = flash_ops_outstanding(); + if (owed) { + TickType_t settle_deadline = xTaskGetTickCount() + pdMS_TO_TICKS(FLASH_NRF5X_SETTLE_MS); + if ((int32_t) (settle_deadline - deadline) > 0) settle_deadline = deadline; + if (!_settle_given_up && wait_flash_ops(settle_deadline, 0, NULL, 0) == FLASH_WAIT_DONE) { + owed = 0; + } else { + // Settling timed out, now or before a BUSY retry: take only what is already queued + drain_soc_events(); + owed = flash_ops_outstanding(); + _settle_given_up = true; } } - return result; + // Counted before the call: the completion can arrive before sd_flash_write() returns + _ops_issued++; + uint32_t result = sd_flash_write((uint32_t*) dst, src, n_words); + + // Not queued (BUSY while an earlier write still runs, or a real error): no completion will follow + if (result != NRF_SUCCESS) { + _ops_issued--; + flash_nrf5x_stats.in_flight = 0; + return result; + } + + _settle_given_up = false; + if (owed) { + // Accepted, so the earlier writes are finished, since the SoftDevice runs one at a time: another + // reader of the SoC event queue most likely took their completions without passing them on. + // Write them off. One that was only late may still arrive and make the counts match early, + // which is why the wait below also requires the data to have landed. + __atomic_fetch_add(&_ops_completed, owed, __ATOMIC_SEQ_CST); + flash_nrf5x_stats.written_off += owed; + } + + flash_wait_t const waited = wait_flash_ops(deadline, dst, src, n_words); + flash_nrf5x_stats.in_flight = 0; + if (waited == FLASH_WAIT_TIMEOUT) { + // Left outstanding: the next write settles it before it is issued + flash_nrf5x_stats.timeouts++; + return NRF_ERROR_TIMEOUT; + } + + if (waited == FLASH_WAIT_FAILED) { + return FLASH_NRF5X_ERR_SD_FAILED; + } + + flash_nrf5x_stats.last_ticks = xTaskGetTickCount() - flash_nrf5x_stats.last_start; + if (flash_nrf5x_stats.last_ticks > flash_nrf5x_stats.max_ticks) { + flash_nrf5x_stats.max_ticks = flash_nrf5x_stats.last_ticks; + } + return NRF_SUCCESS; } // sd_flash_write() is a SoftDevice SVC; without the SoftDevice the RRAM controller is driven directly. @@ -107,10 +255,62 @@ static uint32_t rram_write(uint32_t dst, uint32_t const * src, uint32_t n_words) return NRF_SUCCESS; } -static uint32_t flash_words_write(bool sd_en, uint32_t dst, uint32_t const * src, uint32_t n_words) +static uint32_t flash_words_write(bool sd_en, uint32_t dst, uint32_t const * src, uint32_t n_words, TickType_t deadline) { - if ( !sd_en ) return rram_write(dst, src, n_words); - return wait_for_async_flash_op_completion(sd_flash_write((uint32_t*) dst, src, n_words)); + flash_nrf5x_stats.ops++; + flash_nrf5x_stats.last_addr = dst; + flash_nrf5x_stats.last_words = n_words; + flash_nrf5x_stats.last_start = xTaskGetTickCount(); + + uint32_t result; + if ( !sd_en ) { + result = rram_write(dst, src, n_words); + } else { + result = sd_flash_write_wait(deadline, dst, src, n_words); + } + + flash_nrf5x_stats.last_result = result; + if ( result != NRF_SUCCESS && result != NRF_ERROR_TIMEOUT ) { + flash_nrf5x_stats.errors++; + } + return result; +} + +// One flash operation with the retry policy, within FLASH_NRF5X_CHUNK_BUDGET_MS in total: BUSY (a +// previous operation may still be running inside the SoftDevice) and a SoftDevice-reported failure +// (no room around the radio) are re-issued after a short pause, a lost completion is re-issued a few +// times, any other error is final because it will not fix itself (bad address, forbidden area). +static uint32_t flash_words_write_retry(bool sd_en, uint32_t dst, uint32_t const * src, uint32_t n_words) +{ + TickType_t const deadline = xTaskGetTickCount() + pdMS_TO_TICKS(FLASH_NRF5X_CHUNK_BUDGET_MS); + uint32_t err; + uint8_t retries = 0, timeouts = 0; + + for (;;) { + err = flash_words_write(sd_en, dst, src, n_words, deadline); + if ( err == NRF_SUCCESS || deadline_passed(deadline) ) return err; + if ( (err == NRF_ERROR_BUSY || err == FLASH_NRF5X_ERR_SD_FAILED) && ++retries < MAX_RETRY ) { + delay(5); + continue; + } + if ( err == NRF_ERROR_TIMEOUT && ++timeouts < MAX_TIMEOUT_RETRY ) continue; + return err; + } +} + +// Write only the words that differ from the flash, from the first to the last one. Besides sparing +// the RRAM, every write then starts and ends on a word it changes, which is what lets the data check +// in wait_flash_ops() vouch for it; a range already in place is not written at all. +static uint32_t flash_words_program(bool sd_en, uint32_t dst, uint32_t const * src, uint32_t n_words) +{ + uint32_t const * flash = (uint32_t const *) dst; + uint32_t first = 0, last = n_words; + + while ( first < n_words && flash[first] == src[first] ) first++; + if ( first == n_words ) return NRF_SUCCESS; + while ( flash[last - 1] == src[last - 1] ) last--; + + return flash_words_write_retry(sd_en, dst + 4 * first, src + first, last - first); } // Flash Abstraction Layer @@ -123,7 +323,8 @@ static uint8_t _cache_buffer[FLASH_CACHE_SIZE] __attribute__((aligned(4))); static flash_cache_t _cache = { - .erase = fal_erase, + // RRAM is written in place: the cache flushes only the chunks that changed. + // fal_erase stays available through flash_nrf5x_erase() for the filesystem format path. .program = fal_program, .read = fal_read, .verify = fal_verify, @@ -135,9 +336,11 @@ static flash_cache_t _cache = //--------------------------------------------------------------------+ // Application API //--------------------------------------------------------------------+ -void flash_nrf5x_flush (void) +bool flash_nrf5x_flush (void) { - flash_cache_flush(&_cache); + bool ok = flash_cache_flush(&_cache); + if ( !ok ) flash_nrf5x_stats.flush_failed++; + return ok; } int flash_nrf5x_write (uint32_t dst, void const * src, uint32_t len) @@ -171,11 +374,7 @@ bool flash_nrf5x_erase(uint32_t addr) // writing 0xFF to the entire page via sd_flash_write. static bool fal_erase (uint32_t addr) { - // Init semaphore for first call - if ( _sem == NULL ) { - _sem = xSemaphoreCreateBinary(); - VERIFY(_sem); - } + VERIFY(ensure_sem()); uint8_t sd_en = 0; (void) sd_softdevice_is_enabled(&sd_en); @@ -192,18 +391,8 @@ static bool fal_erase (uint32_t addr) while (remaining > 0) { uint32_t wr_bytes = (remaining < chunk_bytes) ? remaining : chunk_bytes; - uint32_t err; - - for (uint8_t attempt = 0; attempt < MAX_RETRY; ++attempt) { - err = flash_words_write(sd_en, dst, ff_buf, wr_bytes / 4); - if (err == NRF_SUCCESS) { - break; - } - if (err == NRF_ERROR_BUSY) { - delay(1); - } - } - VERIFY_STATUS(err, false); + + VERIFY_STATUS(flash_words_program(sd_en, dst, ff_buf, wr_bytes / 4), false); dst += wr_bytes; remaining -= wr_bytes; @@ -214,38 +403,28 @@ static bool fal_erase (uint32_t addr) static uint32_t fal_program (uint32_t dst, void const * src, uint32_t len) { + VERIFY(ensure_sem(), 0); + // wait for async event if SD is enabled uint8_t sd_en = 0; (void) sd_softdevice_is_enabled(&sd_en); - uint32_t err; + // Written in slices no larger than the RRAMC write buffer; the cache normally hands over one + // FLASH_CACHE_WRITE_CHUNK at a time, the format path a whole page. + uint8_t const * src8 = (uint8_t const *) src; + uint32_t const chunk_bytes = FLASH_CACHE_WRITE_CHUNK; + uint32_t written = 0; - // Write in two halves to avoid potential SoftDevice issues with large writes - // Write first half - for (uint8_t attempt = 0; attempt < MAX_RETRY; ++attempt) { - err = flash_words_write(sd_en, dst, (uint32_t const *) src, len/8); - if (err == NRF_SUCCESS) { - break; - } - if (err == NRF_ERROR_BUSY) { - delay(1); - } - } - VERIFY_STATUS(err, 0); + while (written < len) + { + uint32_t wr_bytes = (len - written < chunk_bytes) ? (len - written) : chunk_bytes; - // Write second half - for (uint8_t attempt = 0; attempt < MAX_RETRY; ++attempt) { - err = flash_words_write(sd_en, dst + len/2, (uint32_t const *) (src + len/2), len/8); - if (err == NRF_SUCCESS) { - break; - } - if (err == NRF_ERROR_BUSY) { - delay(1); - } + VERIFY_STATUS(flash_words_program(sd_en, dst + written, (uint32_t const *) (src8 + written), wr_bytes / 4), written); + + written += wr_bytes; } - VERIFY_STATUS(err, 0); - return len; + return written; } static uint32_t fal_read (void* dst, uint32_t src, uint32_t len) diff --git a/libraries/InternalFileSytem/src/flash/flash_nrf5x.h b/libraries/InternalFileSytem/src/flash/flash_nrf5x.h index 9fc25a4..0c0fc1d 100644 --- a/libraries/InternalFileSytem/src/flash/flash_nrf5x.h +++ b/libraries/InternalFileSytem/src/flash/flash_nrf5x.h @@ -33,9 +33,32 @@ extern "C" { #endif -void flash_nrf5x_flush (void); +// Returns false when the cached page could not be written completely. +bool flash_nrf5x_flush (void); bool flash_nrf5x_erase(uint32_t addr); +// Flight recorder of the SoftDevice flash operations, meant to be read from a debugger while the +// firmware is stuck (the counters live in RAM and survive a hang; a debugger reads them by symbol). +typedef struct +{ + uint32_t ops; // operations handed to the SoftDevice (or the RRAMC without it) + uint32_t completed; // completions received through the SoftDevice event path + uint32_t drained; // completions this driver had to pull with sd_evt_get() itself + uint32_t timeouts; // waits that hit the bound without any completion + uint32_t errors; // operations the SoftDevice refused or reported as failed + uint32_t flush_failed; // page flushes reported to the filesystem as an I/O error + uint32_t in_flight; // 1 while a task waits for a completion: a hang shows here + uint32_t last_addr; // destination of the last operation + uint32_t last_words; // its length, 32-bit words + uint32_t last_start; // RTOS tick when it was issued + uint32_t last_ticks; // duration of the last completed operation, RTOS ticks + uint32_t max_ticks; // longest completed operation + uint32_t last_result; // NRF_SUCCESS or the last error code + uint32_t written_off; // completions given up as lost once the SoftDevice accepted a later operation +} flash_nrf5x_stats_t; + +extern flash_nrf5x_stats_t flash_nrf5x_stats; + int flash_nrf5x_write (uint32_t dst, void const * src, uint32_t len); int flash_nrf5x_read (void* dst, uint32_t src, uint32_t len); diff --git a/libraries/SoftwareSerial/SoftwareSerial.cpp b/libraries/SoftwareSerial/SoftwareSerial.cpp index a72c645..7eba812 100644 --- a/libraries/SoftwareSerial/SoftwareSerial.cpp +++ b/libraries/SoftwareSerial/SoftwareSerial.cpp @@ -40,6 +40,8 @@ SoftwareSerial::SoftwareSerial(uint8_t receivePin, uint8_t transmitPin, bool inv { _receivePin = receivePin; _transmitPin = transmitPin; + _intMask = 0; + _gpiote = NULL; } @@ -85,6 +87,8 @@ bool SoftwareSerial::listen() _receive_buffer_head = _receive_buffer_tail = 0; active_object = this; + // attachInterrupt() returns a channel mask of the GPIOTE that serves the RX pin's port + _gpiote = digitalPinToGpiote(_receivePin); if(_inverse_logic) //Start bit high _intMask = attachInterrupt(_receivePin, handle_interrupt, RISING); @@ -157,7 +161,7 @@ size_t SoftwareSerial::write(uint8_t b) if (inv) b = ~b; // turn off interrupts for a clean txmit - nrf_gpiote_int_disable(NRF_GPIOTE, _intMask); + if (_gpiote) nrf_gpiote_int_disable(_gpiote, _intMask); // Write the start bit if (inv) *reg |= reg_mask; @@ -185,7 +189,7 @@ size_t SoftwareSerial::write(uint8_t b) else *reg |= reg_mask; - nrf_gpiote_int_enable(NRF_GPIOTE, _intMask); + if (_gpiote) nrf_gpiote_int_enable(_gpiote, _intMask); delayMicroseconds(delay); @@ -197,11 +201,11 @@ void SoftwareSerial::flush() if (!isListening()) return; - nrf_gpiote_int_disable(NRF_GPIOTE, _intMask); + if (_gpiote) nrf_gpiote_int_disable(_gpiote, _intMask); _receive_buffer_head = _receive_buffer_tail = 0; - nrf_gpiote_int_enable(NRF_GPIOTE, _intMask); + if (_gpiote) nrf_gpiote_int_enable(_gpiote, _intMask); } int SoftwareSerial::peek() @@ -229,7 +233,7 @@ void SoftwareSerial::recv() if (_inverse_logic ? rx_pin_read() : !rx_pin_read()) { - nrf_gpiote_int_disable(NRF_GPIOTE, _intMask); + if (_gpiote) nrf_gpiote_int_disable(_gpiote, _intMask); // Wait approximately 1/2 of a bit width to "center" the sample delayMicroseconds(_rx_delay_centering); @@ -287,7 +291,7 @@ void SoftwareSerial::recv() // skip the stop bit delayMicroseconds(_rx_delay_stopbit); - nrf_gpiote_int_enable(NRF_GPIOTE, _intMask); + if (_gpiote) nrf_gpiote_int_enable(_gpiote, _intMask); } } diff --git a/libraries/SoftwareSerial/SoftwareSerial.h b/libraries/SoftwareSerial/SoftwareSerial.h index e6ad923..1a69486 100644 --- a/libraries/SoftwareSerial/SoftwareSerial.h +++ b/libraries/SoftwareSerial/SoftwareSerial.h @@ -24,6 +24,7 @@ #include #include #include +#include /****************************************************************************** * Definitions @@ -42,6 +43,7 @@ class SoftwareSerial : public Stream uint32_t _transmitBitMask; volatile uint32_t* _transmitPortRegister; volatile uint32_t _intMask; + NRF_GPIOTE_Type* _gpiote; // the instance _intMask refers to (by the RX pin's port), NULL on P2 // Expressed as 4-cycle delays (must never be 0!) uint16_t _rx_delay_centering; From 19afa3b686b4a6583b4d2fd5b506b0a94f2eba83 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20G=C3=B6ttgens?= Date: Mon, 5 Oct 2026 10:33:10 +0200 Subject: [PATCH 03/10] Place the FreeRTOS tick grid on exact 1/1024 s boundaries (#3) The GRTC tick grid advanced by portNRF_GRTC_TICKS_PER_SYSTICK, 10^6 / 1024 truncated to 976 us, so 1024 ticks took 999424 us. The tick count, and millis() derived from it, ran 576 ppm fast, about 50 s a day. Count grid ticks from the base set at scheduler start and place tick n at base + ceil(n * 10^6 / configTICK_RATE_HZ) us. Rounding up makes a compare armed at tick n see exactly n ticks due when it fires. --- .../CMSIS/nrf54l/port_cmsis_systick.c | 64 +++++++++++++------ 1 file changed, 44 insertions(+), 20 deletions(-) diff --git a/cores/nRF5/freertos/portable/CMSIS/nrf54l/port_cmsis_systick.c b/cores/nRF5/freertos/portable/CMSIS/nrf54l/port_cmsis_systick.c index 3170da5..4f4d6d9 100644 --- a/cores/nRF5/freertos/portable/CMSIS/nrf54l/port_cmsis_systick.c +++ b/cores/nRF5/freertos/portable/CMSIS/nrf54l/port_cmsis_systick.c @@ -98,9 +98,30 @@ static inline void grtc_int_compare_disable(uint32_t cc_channel) /*-----------------------------------------------------------*/ -/* SYSCOUNTER value at which the next OS tick falls due. Ticks stay on this grid, which starts with - * the scheduler, instead of being re-armed one period after whenever the interrupt happened to run. */ -static uint64_t grtc_next_tick; +/* OS ticks stay on a grid that starts with the scheduler, instead of being re-armed one period after + * whenever the interrupt happened to run. Grid tick n falls at grtc_tick_base + ceil(n * 10^6 / + * configTICK_RATE_HZ) us: 10^6 / 1024 is not an integer, and stepping by the truncated + * portNRF_GRTC_TICKS_PER_SYSTICK (976 us) ran the tick count, and millis() with it, 576 ppm fast, + * about 50 s a day. Rounding up makes tick n due exactly when grtc_ticks_due() counts it. */ +static uint64_t grtc_tick_base; +/* Grid ticks accounted so far; the next one falls due at grtc_tick_time(grtc_ticks + 1) */ +static uint64_t grtc_ticks; + +static inline uint64_t grtc_tick_offset(uint64_t n) +{ + return (n * configSYSTICK_CLOCK_HZ + configTICK_RATE_HZ - 1) / configTICK_RATE_HZ; +} + +static inline uint64_t grtc_tick_time(uint64_t n) +{ + return grtc_tick_base + grtc_tick_offset(n); +} + +/* Grid ticks that have fallen due by SYSCOUNTER value now */ +static inline uint64_t grtc_ticks_due(uint64_t now) +{ + return (now - grtc_tick_base) * configTICK_RATE_HZ / configSYSTICK_CLOCK_HZ; +} /* A backlog (core halted in a debugger, a long critical section) is caught up at most this many * ticks per interrupt, so the ISR stays short; the compare is then left behind and fires again. */ @@ -123,12 +144,12 @@ void xPortSysTickHandler( void ) if (configUSE_DISABLE_TICK_AUTO_CORRECTION_DEBUG == 0) { /* Auto-correct missed ticks: every grid tick that has fallen due. - * GRTC runs at configSYSTICK_CLOCK_HZ (1 MHz). - * Each OS tick = portNRF_GRTC_TICKS_PER_SYSTICK GRTC ticks. */ + * GRTC runs at configSYSTICK_CLOCK_HZ (1 MHz). */ TickType_t diff = 0; - if (now >= grtc_next_tick) + uint64_t const due = grtc_ticks_due(now); + if (due > grtc_ticks) { - diff = (TickType_t)((now - grtc_next_tick) / portNRF_GRTC_TICKS_PER_SYSTICK) + 1; + diff = (TickType_t)(due - grtc_ticks); } /* At most 1 step if scheduler is suspended */ @@ -142,7 +163,7 @@ void xPortSysTickHandler( void ) diff = portNRF_GRTC_CATCHUP_MAX; } - grtc_next_tick += (uint64_t)diff * portNRF_GRTC_TICKS_PER_SYSTICK; + grtc_ticks += diff; while ((diff--) > 0) { switch_req |= xTaskIncrementTick(); @@ -151,14 +172,16 @@ void xPortSysTickHandler( void ) else { switch_req = xTaskIncrementTick(); - grtc_next_tick = now + portNRF_GRTC_TICKS_PER_SYSTICK; + /* Move the grid so that the next tick falls one period from now */ + grtc_ticks++; + grtc_tick_base = now - grtc_tick_offset(grtc_ticks); } /* Schedule next compare: the next tick on the grid. When ticks are still due it lies in the past * and fires at once, except while the scheduler is suspended, which would only spin through here: * then wait one tick period. */ { - uint64_t next_cc = grtc_next_tick; + uint64_t next_cc = grtc_tick_time(grtc_ticks + 1); if ((next_cc <= now) && (xTaskGetSchedulerState() == taskSCHEDULER_SUSPENDED)) { next_cc = now + portNRF_GRTC_TICKS_PER_SYSTICK; @@ -227,8 +250,9 @@ void vPortSetupTimerInterrupt( void ) grtc_event_compare_clear(portNRF_GRTC_CC_CH); /* First tick one period from now: the grid starts here */ - grtc_next_tick = grtc_counter_get() + portNRF_GRTC_TICKS_PER_SYSTICK; - grtc_cc_set(portNRF_GRTC_CC_CH, grtc_next_tick); + grtc_tick_base = grtc_counter_get(); + grtc_ticks = 0; + grtc_cc_set(portNRF_GRTC_CC_CH, grtc_tick_time(1)); /* Enable compare interrupt */ grtc_int_compare_enable(portNRF_GRTC_CC_CH); @@ -257,9 +281,9 @@ void vPortSuppressTicksAndSleep( TickType_t xExpectedIdleTime ) if ( eTaskConfirmSleepModeStatus() != eAbortSleep ) { TickType_t xModifiableIdleTime; - /* Wake on the grid, when the tick that unblocks a task falls due: the next tick is due at - * grtc_next_tick and xExpectedIdleTime counts it. */ - uint64_t const wakeupTime = grtc_next_tick + (uint64_t)(xExpectedIdleTime - 1) * portNRF_GRTC_TICKS_PER_SYSTICK; + /* Wake on the grid, when the tick that unblocks a task falls due: xExpectedIdleTime counts + * the next tick, grtc_ticks + 1. */ + uint64_t const wakeupTime = grtc_tick_time(grtc_ticks + xExpectedIdleTime); /* Disable periodic tick interrupt, use compare for wakeup */ grtc_int_compare_disable(portNRF_GRTC_CC_CH); @@ -310,10 +334,10 @@ void vPortSuppressTicksAndSleep( TickType_t xExpectedIdleTime ) { /* Whole grid ticks that fell due while asleep */ TickType_t diff = 0; - uint64_t const exitTime = grtc_counter_get(); - if (exitTime >= grtc_next_tick) + uint64_t const due = grtc_ticks_due(grtc_counter_get()); + if (due > grtc_ticks) { - diff = (TickType_t)((exitTime - grtc_next_tick) / portNRF_GRTC_TICKS_PER_SYSTICK) + 1; + diff = (TickType_t)(due - grtc_ticks); } /* vTaskStepTick() must not pass the unblock tick. Ticks beyond it stay due, and the tick @@ -322,7 +346,7 @@ void vPortSuppressTicksAndSleep( TickType_t xExpectedIdleTime ) { diff = xExpectedIdleTime; } - grtc_next_tick += (uint64_t)diff * portNRF_GRTC_TICKS_PER_SYSTICK; + grtc_ticks += diff; /* It is important that we clear pending here so that our corrections are latest and in sync with * tick_interrupt handler. Done before re-arming: a compare armed in the past must still fire. */ @@ -330,7 +354,7 @@ void vPortSuppressTicksAndSleep( TickType_t xExpectedIdleTime ) NVIC_ClearPendingIRQ(portNRF_GRTC_IRQn); /* Re-enable periodic tick via compare */ - grtc_cc_set(portNRF_GRTC_CC_CH, grtc_next_tick); + grtc_cc_set(portNRF_GRTC_CC_CH, grtc_tick_time(grtc_ticks + 1)); grtc_int_compare_enable(portNRF_GRTC_CC_CH); BaseType_t switch_req = pdFALSE; From 50c9877ef0502b53853c0ee18e4e63b1a04c05f9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20G=C3=B6ttgens?= Date: Mon, 5 Oct 2026 12:51:33 +0200 Subject: [PATCH 04/10] Stop masking the GPIOTE channel after SoftwareSerial::stopListening() (#4) stopListening() detached the RX interrupt but kept _gpiote and _intMask, so write() on an instance that no longer listens still disabled and re-enabled that channel around every byte. Once another pin is attached to the channel, its interrupt was masked during each transmitted byte. Clear _gpiote, which every mask and unmask is guarded on. --- libraries/SoftwareSerial/SoftwareSerial.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/libraries/SoftwareSerial/SoftwareSerial.cpp b/libraries/SoftwareSerial/SoftwareSerial.cpp index 7eba812..92bd1ed 100644 --- a/libraries/SoftwareSerial/SoftwareSerial.cpp +++ b/libraries/SoftwareSerial/SoftwareSerial.cpp @@ -106,6 +106,8 @@ bool SoftwareSerial::stopListening() if (active_object == this) { detachInterrupt(_receivePin); + // The channel may go to another pin now: write() must stop masking it + _gpiote = NULL; active_object = NULL; return true; } From 79a7ad66459c5c6be56e4d48cf5ae96ed35a7714 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20G=C3=B6ttgens?= Date: Mon, 5 Oct 2026 13:23:58 +0200 Subject: [PATCH 05/10] Count InternalFS cache pages dropped after repeated flush failures (#5) A cached page whose flush fails three times in a row is dropped, and with it writes LittleFS was already told had succeeded. Count each drop in flash_nrf5x_stats.pages_dropped, so the loss shows in the flash flight recorder. --- libraries/InternalFileSytem/src/flash/flash_cache.c | 2 ++ libraries/InternalFileSytem/src/flash/flash_cache.h | 1 + libraries/InternalFileSytem/src/flash/flash_nrf5x.c | 3 ++- libraries/InternalFileSytem/src/flash/flash_nrf5x.h | 1 + 4 files changed, 6 insertions(+), 1 deletion(-) diff --git a/libraries/InternalFileSytem/src/flash/flash_cache.c b/libraries/InternalFileSytem/src/flash/flash_cache.c index dbe288d..16c7496 100644 --- a/libraries/InternalFileSytem/src/flash/flash_cache.c +++ b/libraries/InternalFileSytem/src/flash/flash_cache.c @@ -118,6 +118,8 @@ bool flash_cache_flush (flash_cache_t* fc) // included, and its data does not survive a reboot anyway. if ( ok || ++fc->flush_failures >= FLASH_CACHE_MAX_FLUSH_FAILURES ) { + // Writes already reported as done are lost with it + if ( !ok && fc->pages_dropped ) (*fc->pages_dropped)++; fc->cache_addr = FLASH_CACHE_INVALID_ADDR; fc->flush_failures = 0; } diff --git a/libraries/InternalFileSytem/src/flash/flash_cache.h b/libraries/InternalFileSytem/src/flash/flash_cache.h index fa6a0ca..3414c07 100644 --- a/libraries/InternalFileSytem/src/flash/flash_cache.h +++ b/libraries/InternalFileSytem/src/flash/flash_cache.h @@ -44,6 +44,7 @@ typedef struct uint32_t cache_addr; uint8_t* cache_buf; uint8_t flush_failures; // consecutive failed flushes of the cached page + uint32_t* pages_dropped; // counts pages given up after FLASH_CACHE_MAX_FLUSH_FAILURES, may be NULL } flash_cache_t; #ifdef __cplusplus diff --git a/libraries/InternalFileSytem/src/flash/flash_nrf5x.c b/libraries/InternalFileSytem/src/flash/flash_nrf5x.c index 30c5d46..f1ee2c8 100644 --- a/libraries/InternalFileSytem/src/flash/flash_nrf5x.c +++ b/libraries/InternalFileSytem/src/flash/flash_nrf5x.c @@ -330,7 +330,8 @@ static flash_cache_t _cache = .verify = fal_verify, .cache_addr = FLASH_CACHE_INVALID_ADDR, - .cache_buf = _cache_buffer + .cache_buf = _cache_buffer, + .pages_dropped = &flash_nrf5x_stats.pages_dropped }; //--------------------------------------------------------------------+ diff --git a/libraries/InternalFileSytem/src/flash/flash_nrf5x.h b/libraries/InternalFileSytem/src/flash/flash_nrf5x.h index 0c0fc1d..fdae711 100644 --- a/libraries/InternalFileSytem/src/flash/flash_nrf5x.h +++ b/libraries/InternalFileSytem/src/flash/flash_nrf5x.h @@ -55,6 +55,7 @@ typedef struct uint32_t max_ticks; // longest completed operation uint32_t last_result; // NRF_SUCCESS or the last error code uint32_t written_off; // completions given up as lost once the SoftDevice accepted a later operation + uint32_t pages_dropped; // cached pages given up after repeated flush failures, losing their writes } flash_nrf5x_stats_t; extern flash_nrf5x_stats_t flash_nrf5x_stats; From d6c0877ce36766b5bb6aba0dd81cb9c821138202 Mon Sep 17 00:00:00 2001 From: CValdesS Date: Sat, 10 Oct 2026 17:30:55 +0200 Subject: [PATCH 06/10] libraries: import Adafruit TinyUSB Arduino 3.7.7 unchanged Vendored as released (src, library.properties, LICENSE, README; no examples). It bundles TinyUSB with the DWC2 port for the nRF54LM20 USBHS; the nRF54 port and the fixes it needs follow in separate commits. --- libraries/Adafruit_TinyUSB_Arduino/LICENSE | 21 + libraries/Adafruit_TinyUSB_Arduino/README.md | 90 + .../library.properties | 11 + .../src/Adafruit_TinyUSB.h | 80 + .../src/arduino/Adafruit_TinyUSB_API.cpp | 103 + .../src/arduino/Adafruit_TinyUSB_API.h | 94 + .../src/arduino/Adafruit_USBD_CDC.cpp | 346 +++ .../src/arduino/Adafruit_USBD_CDC.h | 109 + .../src/arduino/Adafruit_USBD_Device.cpp | 527 ++++ .../src/arduino/Adafruit_USBD_Device.h | 167 ++ .../src/arduino/Adafruit_USBD_Interface.cpp | 35 + .../src/arduino/Adafruit_USBD_Interface.h | 56 + .../src/arduino/Adafruit_USBH_Host.cpp | 323 +++ .../src/arduino/Adafruit_USBH_Host.h | 100 + .../src/arduino/cdc/Adafruit_USBH_CDC.cpp | 178 ++ .../src/arduino/cdc/Adafruit_USBH_CDC.h | 86 + .../src/arduino/hid/Adafruit_USBD_HID.cpp | 288 ++ .../src/arduino/hid/Adafruit_USBD_HID.h | 116 + .../src/arduino/midi/Adafruit_USBD_MIDI.cpp | 166 ++ .../src/arduino/midi/Adafruit_USBD_MIDI.h | 71 + .../src/arduino/msc/Adafruit_USBD_MSC.cpp | 279 ++ .../src/arduino/msc/Adafruit_USBD_MSC.h | 128 + .../src/arduino/msc/Adafruit_USBH_MSC.cpp | 152 + .../src/arduino/msc/Adafruit_USBH_MSC.h | 77 + .../ports/ch32/Adafruit_TinyUSB_ch32.cpp | 195 ++ .../src/arduino/ports/ch32/tusb_config_ch32.h | 184 ++ .../ports/esp32/Adafruit_TinyUSB_esp32.cpp | 186 ++ .../arduino/ports/esp32/tusb_config_esp32.h | 197 ++ .../ports/nrf/Adafruit_TinyUSB_nrf.cpp | 143 + .../src/arduino/ports/nrf/tusb_config_nrf.h | 179 ++ .../ports/rp2040/Adafruit_TinyUSB_rp2040.cpp | 147 + .../arduino/ports/rp2040/tusb_config_rp2040.h | 178 ++ .../ports/samd/Adafruit_TinyUSB_samd.cpp | 146 + .../src/arduino/ports/samd/tusb_config_samd.h | 161 ++ .../ports/stm32/Adafruit_TinyUSB_stm32.cpp | 104 + .../arduino/ports/stm32/tusb_config_stm32.h | 76 + .../src/arduino/video/Adafruit_USBD_Video.cpp | 269 ++ .../src/arduino/video/Adafruit_USBD_Video.h | 78 + .../arduino/webusb/Adafruit_USBD_WebUSB.cpp | 322 +++ .../src/arduino/webusb/Adafruit_USBD_WebUSB.h | 81 + .../src/class/audio/audio.h | 1299 +++++++++ .../src/class/audio/audio_device.c | 1905 ++++++++++++ .../src/class/audio/audio_device.h | 474 +++ .../src/class/bth/bth_device.c | 298 ++ .../src/class/bth/bth_device.h | 117 + .../src/class/cdc/cdc.h | 472 +++ .../src/class/cdc/cdc_device.c | 534 ++++ .../src/class/cdc/cdc_device.h | 311 ++ .../src/class/cdc/cdc_host.c | 2554 +++++++++++++++++ .../src/class/cdc/cdc_host.h | 267 ++ .../src/class/cdc/cdc_rndis.h | 301 ++ .../src/class/cdc/serial/ch34x.h | 84 + .../src/class/cdc/serial/cp210x.h | 114 + .../src/class/cdc/serial/ftdi_sio.h | 218 ++ .../src/class/cdc/serial/pl2303.h | 159 + .../src/class/dfu/dfu.h | 119 + .../src/class/dfu/dfu_device.c | 447 +++ .../src/class/dfu/dfu_device.h | 99 + .../src/class/dfu/dfu_rt_device.c | 137 + .../src/class/dfu/dfu_rt_device.h | 55 + .../src/class/hid/hid.h | 2076 ++++++++++++++ .../src/class/hid/hid_device.c | 422 +++ .../src/class/hid/hid_device.h | 673 +++++ .../src/class/hid/hid_host.c | 806 ++++++ .../src/class/hid/hid_host.h | 190 ++ .../src/class/midi/README_midi_host.md | 111 + .../src/class/midi/midi.h | 201 ++ .../src/class/midi/midi_device.c | 571 ++++ .../src/class/midi/midi_device.h | 153 + .../src/class/midi/midi_host.c | 657 +++++ .../src/class/midi/midi_host.h | 198 ++ .../src/class/msc/msc.h | 401 +++ .../src/class/msc/msc_device.c | 968 +++++++ .../src/class/msc/msc_device.h | 170 ++ .../src/class/msc/msc_host.c | 517 ++++ .../src/class/msc/msc_host.h | 138 + .../src/class/mtp/mtp.h | 908 ++++++ .../src/class/mtp/mtp_device.c | 567 ++++ .../src/class/mtp/mtp_device.h | 173 ++ .../src/class/net/ecm_rndis_device.c | 442 +++ .../src/class/net/ncm.h | 178 ++ .../src/class/net/ncm_device.c | 1095 +++++++ .../src/class/net/net_device.h | 127 + .../src/class/printer/printer.h | 62 + .../src/class/printer/printer_device.c | 328 +++ .../src/class/printer/printer_device.h | 151 + .../src/class/usbtmc/usbtmc.h | 343 +++ .../src/class/usbtmc/usbtmc_device.c | 866 ++++++ .../src/class/usbtmc/usbtmc_device.h | 117 + .../src/class/vendor/vendor_device.c | 376 +++ .../src/class/vendor/vendor_device.h | 211 ++ .../src/class/video/video.h | 697 +++++ .../src/class/video/video_device.c | 1527 ++++++++++ .../src/class/video/video_device.h | 119 + .../src/common/tusb_common.h | 407 +++ .../src/common/tusb_compiler.h | 293 ++ .../src/common/tusb_debug.h | 178 ++ .../src/common/tusb_fifo.c | 834 ++++++ .../src/common/tusb_fifo.h | 316 ++ .../src/common/tusb_mcu.h | 756 +++++ .../src/common/tusb_private.h | 193 ++ .../src/common/tusb_types.h | 602 ++++ .../src/common/tusb_verify.h | 138 + .../Adafruit_TinyUSB_Arduino/src/device/dcd.h | 253 ++ .../src/device/usbd.c | 1738 +++++++++++ .../src/device/usbd.h | 1061 +++++++ .../src/device/usbd_pvt.h | 137 + .../Adafruit_TinyUSB_Arduino/src/host/hcd.h | 227 ++ .../Adafruit_TinyUSB_Arduino/src/host/hub.c | 501 ++++ .../Adafruit_TinyUSB_Arduino/src/host/hub.h | 227 ++ .../Adafruit_TinyUSB_Arduino/src/host/usbh.c | 2050 +++++++++++++ .../Adafruit_TinyUSB_Arduino/src/host/usbh.h | 405 +++ .../src/host/usbh_pvt.h | 107 + .../Adafruit_TinyUSB_Arduino/src/osal/osal.h | 108 + .../src/osal/osal_freertos.h | 278 ++ .../src/osal/osal_mynewt.h | 226 ++ .../src/osal/osal_none.h | 219 ++ .../src/osal/osal_pico.h | 186 ++ .../src/osal/osal_rtthread.h | 178 ++ .../src/osal/osal_rtx4.h | 196 ++ .../src/osal/osal_zephyr.h | 156 + .../src/portable/analog/max3421/hcd_max3421.c | 1107 +++++++ .../src/portable/analog/max3421/hcd_max3421.h | 63 + .../src/portable/microchip/samd/dcd_samd.c | 437 +++ .../src/portable/microchip/samd/hcd_samd.c | 761 +++++ .../src/portable/nordic/nrf5x/dcd_nrf5x.c | 1096 +++++++ .../raspberrypi/pio_usb/dcd_pio_usb.c | 224 ++ .../raspberrypi/pio_usb/hcd_pio_usb.c | 226 ++ .../portable/raspberrypi/rp2040/dcd_rp2040.c | 582 ++++ .../portable/raspberrypi/rp2040/hcd_rp2040.c | 704 +++++ .../portable/raspberrypi/rp2040/rp2040_usb.c | 478 +++ .../portable/raspberrypi/rp2040/rp2040_usb.h | 185 ++ .../src/portable/renesas/rusb2/dcd_rusb2.c | 988 +++++++ .../src/portable/renesas/rusb2/hcd_rusb2.c | 827 ++++++ .../src/portable/renesas/rusb2/rusb2_common.c | 116 + .../src/portable/renesas/rusb2/rusb2_common.h | 58 + .../src/portable/renesas/rusb2/rusb2_ra.h | 109 + .../src/portable/renesas/rusb2/rusb2_rx.h | 94 + .../src/portable/renesas/rusb2/rusb2_type.h | 1784 ++++++++++++ .../portable/st/stm32_fsdev/dcd_stm32_fsdev.c | 814 ++++++ .../src/portable/st/stm32_fsdev/fsdev_at32.h | 116 + .../src/portable/st/stm32_fsdev/fsdev_ch32.h | 103 + .../portable/st/stm32_fsdev/fsdev_common.c | 116 + .../portable/st/stm32_fsdev/fsdev_common.h | 456 +++ .../src/portable/st/stm32_fsdev/fsdev_stm32.h | 326 +++ .../portable/st/stm32_fsdev/hcd_stm32_fsdev.c | 835 ++++++ .../src/portable/synopsys/dwc2/dcd_dwc2.c | 1264 ++++++++ .../src/portable/synopsys/dwc2/dwc2_at32.h | 139 + .../src/portable/synopsys/dwc2/dwc2_bcm.h | 102 + .../src/portable/synopsys/dwc2/dwc2_common.c | 277 ++ .../src/portable/synopsys/dwc2/dwc2_common.h | 118 + .../src/portable/synopsys/dwc2/dwc2_efm32.h | 103 + .../src/portable/synopsys/dwc2/dwc2_esp32.h | 200 ++ .../src/portable/synopsys/dwc2/dwc2_gd32.h | 114 + .../src/portable/synopsys/dwc2/dwc2_info.md | 58 + .../src/portable/synopsys/dwc2/dwc2_info.py | 216 ++ .../src/portable/synopsys/dwc2/dwc2_nrf.h | 150 + .../src/portable/synopsys/dwc2/dwc2_stm32.h | 471 +++ .../src/portable/synopsys/dwc2/dwc2_type.h | 2317 +++++++++++++++ .../src/portable/synopsys/dwc2/dwc2_xmc.h | 101 + .../src/portable/synopsys/dwc2/hcd_dwc2.c | 1536 ++++++++++ .../src/portable/wch/ch32_usbfs_reg.h | 175 ++ .../src/portable/wch/ch32_usbhs_reg.h | 395 +++ .../src/portable/wch/dcd_ch32_usbfs.c | 355 +++ .../src/portable/wch/dcd_ch32_usbhs.c | 439 +++ .../src/portable/wch/hcd_ch32_usbfs.c | 679 +++++ libraries/Adafruit_TinyUSB_Arduino/src/tusb.c | 570 ++++ libraries/Adafruit_TinyUSB_Arduino/src/tusb.h | 185 ++ .../src/tusb_config.h | 63 + .../src/tusb_option.h | 855 ++++++ 170 files changed, 68209 insertions(+) create mode 100644 libraries/Adafruit_TinyUSB_Arduino/LICENSE create mode 100644 libraries/Adafruit_TinyUSB_Arduino/README.md create mode 100644 libraries/Adafruit_TinyUSB_Arduino/library.properties create mode 100644 libraries/Adafruit_TinyUSB_Arduino/src/Adafruit_TinyUSB.h create mode 100644 libraries/Adafruit_TinyUSB_Arduino/src/arduino/Adafruit_TinyUSB_API.cpp create mode 100644 libraries/Adafruit_TinyUSB_Arduino/src/arduino/Adafruit_TinyUSB_API.h create mode 100644 libraries/Adafruit_TinyUSB_Arduino/src/arduino/Adafruit_USBD_CDC.cpp create mode 100644 libraries/Adafruit_TinyUSB_Arduino/src/arduino/Adafruit_USBD_CDC.h create mode 100644 libraries/Adafruit_TinyUSB_Arduino/src/arduino/Adafruit_USBD_Device.cpp create mode 100644 libraries/Adafruit_TinyUSB_Arduino/src/arduino/Adafruit_USBD_Device.h create mode 100644 libraries/Adafruit_TinyUSB_Arduino/src/arduino/Adafruit_USBD_Interface.cpp create mode 100644 libraries/Adafruit_TinyUSB_Arduino/src/arduino/Adafruit_USBD_Interface.h create mode 100644 libraries/Adafruit_TinyUSB_Arduino/src/arduino/Adafruit_USBH_Host.cpp create mode 100644 libraries/Adafruit_TinyUSB_Arduino/src/arduino/Adafruit_USBH_Host.h create mode 100644 libraries/Adafruit_TinyUSB_Arduino/src/arduino/cdc/Adafruit_USBH_CDC.cpp create mode 100644 libraries/Adafruit_TinyUSB_Arduino/src/arduino/cdc/Adafruit_USBH_CDC.h create mode 100644 libraries/Adafruit_TinyUSB_Arduino/src/arduino/hid/Adafruit_USBD_HID.cpp create mode 100644 libraries/Adafruit_TinyUSB_Arduino/src/arduino/hid/Adafruit_USBD_HID.h create mode 100644 libraries/Adafruit_TinyUSB_Arduino/src/arduino/midi/Adafruit_USBD_MIDI.cpp create mode 100644 libraries/Adafruit_TinyUSB_Arduino/src/arduino/midi/Adafruit_USBD_MIDI.h create mode 100644 libraries/Adafruit_TinyUSB_Arduino/src/arduino/msc/Adafruit_USBD_MSC.cpp create mode 100644 libraries/Adafruit_TinyUSB_Arduino/src/arduino/msc/Adafruit_USBD_MSC.h create mode 100644 libraries/Adafruit_TinyUSB_Arduino/src/arduino/msc/Adafruit_USBH_MSC.cpp create mode 100644 libraries/Adafruit_TinyUSB_Arduino/src/arduino/msc/Adafruit_USBH_MSC.h create mode 100644 libraries/Adafruit_TinyUSB_Arduino/src/arduino/ports/ch32/Adafruit_TinyUSB_ch32.cpp create mode 100644 libraries/Adafruit_TinyUSB_Arduino/src/arduino/ports/ch32/tusb_config_ch32.h create mode 100644 libraries/Adafruit_TinyUSB_Arduino/src/arduino/ports/esp32/Adafruit_TinyUSB_esp32.cpp create mode 100644 libraries/Adafruit_TinyUSB_Arduino/src/arduino/ports/esp32/tusb_config_esp32.h create mode 100644 libraries/Adafruit_TinyUSB_Arduino/src/arduino/ports/nrf/Adafruit_TinyUSB_nrf.cpp create mode 100644 libraries/Adafruit_TinyUSB_Arduino/src/arduino/ports/nrf/tusb_config_nrf.h create mode 100644 libraries/Adafruit_TinyUSB_Arduino/src/arduino/ports/rp2040/Adafruit_TinyUSB_rp2040.cpp create mode 100644 libraries/Adafruit_TinyUSB_Arduino/src/arduino/ports/rp2040/tusb_config_rp2040.h create mode 100644 libraries/Adafruit_TinyUSB_Arduino/src/arduino/ports/samd/Adafruit_TinyUSB_samd.cpp create mode 100644 libraries/Adafruit_TinyUSB_Arduino/src/arduino/ports/samd/tusb_config_samd.h create mode 100644 libraries/Adafruit_TinyUSB_Arduino/src/arduino/ports/stm32/Adafruit_TinyUSB_stm32.cpp create mode 100644 libraries/Adafruit_TinyUSB_Arduino/src/arduino/ports/stm32/tusb_config_stm32.h create mode 100644 libraries/Adafruit_TinyUSB_Arduino/src/arduino/video/Adafruit_USBD_Video.cpp create mode 100644 libraries/Adafruit_TinyUSB_Arduino/src/arduino/video/Adafruit_USBD_Video.h create mode 100644 libraries/Adafruit_TinyUSB_Arduino/src/arduino/webusb/Adafruit_USBD_WebUSB.cpp create mode 100644 libraries/Adafruit_TinyUSB_Arduino/src/arduino/webusb/Adafruit_USBD_WebUSB.h create mode 100644 libraries/Adafruit_TinyUSB_Arduino/src/class/audio/audio.h create mode 100644 libraries/Adafruit_TinyUSB_Arduino/src/class/audio/audio_device.c create mode 100644 libraries/Adafruit_TinyUSB_Arduino/src/class/audio/audio_device.h create mode 100644 libraries/Adafruit_TinyUSB_Arduino/src/class/bth/bth_device.c create mode 100644 libraries/Adafruit_TinyUSB_Arduino/src/class/bth/bth_device.h create mode 100644 libraries/Adafruit_TinyUSB_Arduino/src/class/cdc/cdc.h create mode 100644 libraries/Adafruit_TinyUSB_Arduino/src/class/cdc/cdc_device.c create mode 100644 libraries/Adafruit_TinyUSB_Arduino/src/class/cdc/cdc_device.h create mode 100644 libraries/Adafruit_TinyUSB_Arduino/src/class/cdc/cdc_host.c create mode 100644 libraries/Adafruit_TinyUSB_Arduino/src/class/cdc/cdc_host.h create mode 100644 libraries/Adafruit_TinyUSB_Arduino/src/class/cdc/cdc_rndis.h create mode 100644 libraries/Adafruit_TinyUSB_Arduino/src/class/cdc/serial/ch34x.h create mode 100644 libraries/Adafruit_TinyUSB_Arduino/src/class/cdc/serial/cp210x.h create mode 100644 libraries/Adafruit_TinyUSB_Arduino/src/class/cdc/serial/ftdi_sio.h create mode 100644 libraries/Adafruit_TinyUSB_Arduino/src/class/cdc/serial/pl2303.h create mode 100644 libraries/Adafruit_TinyUSB_Arduino/src/class/dfu/dfu.h create mode 100644 libraries/Adafruit_TinyUSB_Arduino/src/class/dfu/dfu_device.c create mode 100644 libraries/Adafruit_TinyUSB_Arduino/src/class/dfu/dfu_device.h create mode 100644 libraries/Adafruit_TinyUSB_Arduino/src/class/dfu/dfu_rt_device.c create mode 100644 libraries/Adafruit_TinyUSB_Arduino/src/class/dfu/dfu_rt_device.h create mode 100644 libraries/Adafruit_TinyUSB_Arduino/src/class/hid/hid.h create mode 100644 libraries/Adafruit_TinyUSB_Arduino/src/class/hid/hid_device.c create mode 100644 libraries/Adafruit_TinyUSB_Arduino/src/class/hid/hid_device.h create mode 100644 libraries/Adafruit_TinyUSB_Arduino/src/class/hid/hid_host.c create mode 100644 libraries/Adafruit_TinyUSB_Arduino/src/class/hid/hid_host.h create mode 100644 libraries/Adafruit_TinyUSB_Arduino/src/class/midi/README_midi_host.md create mode 100644 libraries/Adafruit_TinyUSB_Arduino/src/class/midi/midi.h create mode 100644 libraries/Adafruit_TinyUSB_Arduino/src/class/midi/midi_device.c create mode 100644 libraries/Adafruit_TinyUSB_Arduino/src/class/midi/midi_device.h create mode 100644 libraries/Adafruit_TinyUSB_Arduino/src/class/midi/midi_host.c create mode 100644 libraries/Adafruit_TinyUSB_Arduino/src/class/midi/midi_host.h create mode 100644 libraries/Adafruit_TinyUSB_Arduino/src/class/msc/msc.h create mode 100644 libraries/Adafruit_TinyUSB_Arduino/src/class/msc/msc_device.c create mode 100644 libraries/Adafruit_TinyUSB_Arduino/src/class/msc/msc_device.h create mode 100644 libraries/Adafruit_TinyUSB_Arduino/src/class/msc/msc_host.c create mode 100644 libraries/Adafruit_TinyUSB_Arduino/src/class/msc/msc_host.h create mode 100644 libraries/Adafruit_TinyUSB_Arduino/src/class/mtp/mtp.h create mode 100644 libraries/Adafruit_TinyUSB_Arduino/src/class/mtp/mtp_device.c create mode 100644 libraries/Adafruit_TinyUSB_Arduino/src/class/mtp/mtp_device.h create mode 100644 libraries/Adafruit_TinyUSB_Arduino/src/class/net/ecm_rndis_device.c create mode 100644 libraries/Adafruit_TinyUSB_Arduino/src/class/net/ncm.h create mode 100644 libraries/Adafruit_TinyUSB_Arduino/src/class/net/ncm_device.c create mode 100644 libraries/Adafruit_TinyUSB_Arduino/src/class/net/net_device.h create mode 100644 libraries/Adafruit_TinyUSB_Arduino/src/class/printer/printer.h create mode 100644 libraries/Adafruit_TinyUSB_Arduino/src/class/printer/printer_device.c create mode 100644 libraries/Adafruit_TinyUSB_Arduino/src/class/printer/printer_device.h create mode 100644 libraries/Adafruit_TinyUSB_Arduino/src/class/usbtmc/usbtmc.h create mode 100644 libraries/Adafruit_TinyUSB_Arduino/src/class/usbtmc/usbtmc_device.c create mode 100644 libraries/Adafruit_TinyUSB_Arduino/src/class/usbtmc/usbtmc_device.h create mode 100644 libraries/Adafruit_TinyUSB_Arduino/src/class/vendor/vendor_device.c create mode 100644 libraries/Adafruit_TinyUSB_Arduino/src/class/vendor/vendor_device.h create mode 100644 libraries/Adafruit_TinyUSB_Arduino/src/class/video/video.h create mode 100644 libraries/Adafruit_TinyUSB_Arduino/src/class/video/video_device.c create mode 100644 libraries/Adafruit_TinyUSB_Arduino/src/class/video/video_device.h create mode 100644 libraries/Adafruit_TinyUSB_Arduino/src/common/tusb_common.h create mode 100644 libraries/Adafruit_TinyUSB_Arduino/src/common/tusb_compiler.h create mode 100644 libraries/Adafruit_TinyUSB_Arduino/src/common/tusb_debug.h create mode 100644 libraries/Adafruit_TinyUSB_Arduino/src/common/tusb_fifo.c create mode 100644 libraries/Adafruit_TinyUSB_Arduino/src/common/tusb_fifo.h create mode 100644 libraries/Adafruit_TinyUSB_Arduino/src/common/tusb_mcu.h create mode 100644 libraries/Adafruit_TinyUSB_Arduino/src/common/tusb_private.h create mode 100644 libraries/Adafruit_TinyUSB_Arduino/src/common/tusb_types.h create mode 100644 libraries/Adafruit_TinyUSB_Arduino/src/common/tusb_verify.h create mode 100644 libraries/Adafruit_TinyUSB_Arduino/src/device/dcd.h create mode 100644 libraries/Adafruit_TinyUSB_Arduino/src/device/usbd.c create mode 100644 libraries/Adafruit_TinyUSB_Arduino/src/device/usbd.h create mode 100644 libraries/Adafruit_TinyUSB_Arduino/src/device/usbd_pvt.h create mode 100644 libraries/Adafruit_TinyUSB_Arduino/src/host/hcd.h create mode 100644 libraries/Adafruit_TinyUSB_Arduino/src/host/hub.c create mode 100644 libraries/Adafruit_TinyUSB_Arduino/src/host/hub.h create mode 100644 libraries/Adafruit_TinyUSB_Arduino/src/host/usbh.c create mode 100644 libraries/Adafruit_TinyUSB_Arduino/src/host/usbh.h create mode 100644 libraries/Adafruit_TinyUSB_Arduino/src/host/usbh_pvt.h create mode 100644 libraries/Adafruit_TinyUSB_Arduino/src/osal/osal.h create mode 100644 libraries/Adafruit_TinyUSB_Arduino/src/osal/osal_freertos.h create mode 100644 libraries/Adafruit_TinyUSB_Arduino/src/osal/osal_mynewt.h create mode 100644 libraries/Adafruit_TinyUSB_Arduino/src/osal/osal_none.h create mode 100644 libraries/Adafruit_TinyUSB_Arduino/src/osal/osal_pico.h create mode 100644 libraries/Adafruit_TinyUSB_Arduino/src/osal/osal_rtthread.h create mode 100644 libraries/Adafruit_TinyUSB_Arduino/src/osal/osal_rtx4.h create mode 100644 libraries/Adafruit_TinyUSB_Arduino/src/osal/osal_zephyr.h create mode 100644 libraries/Adafruit_TinyUSB_Arduino/src/portable/analog/max3421/hcd_max3421.c create mode 100644 libraries/Adafruit_TinyUSB_Arduino/src/portable/analog/max3421/hcd_max3421.h create mode 100644 libraries/Adafruit_TinyUSB_Arduino/src/portable/microchip/samd/dcd_samd.c create mode 100644 libraries/Adafruit_TinyUSB_Arduino/src/portable/microchip/samd/hcd_samd.c create mode 100644 libraries/Adafruit_TinyUSB_Arduino/src/portable/nordic/nrf5x/dcd_nrf5x.c create mode 100644 libraries/Adafruit_TinyUSB_Arduino/src/portable/raspberrypi/pio_usb/dcd_pio_usb.c create mode 100644 libraries/Adafruit_TinyUSB_Arduino/src/portable/raspberrypi/pio_usb/hcd_pio_usb.c create mode 100644 libraries/Adafruit_TinyUSB_Arduino/src/portable/raspberrypi/rp2040/dcd_rp2040.c create mode 100644 libraries/Adafruit_TinyUSB_Arduino/src/portable/raspberrypi/rp2040/hcd_rp2040.c create mode 100644 libraries/Adafruit_TinyUSB_Arduino/src/portable/raspberrypi/rp2040/rp2040_usb.c create mode 100644 libraries/Adafruit_TinyUSB_Arduino/src/portable/raspberrypi/rp2040/rp2040_usb.h create mode 100644 libraries/Adafruit_TinyUSB_Arduino/src/portable/renesas/rusb2/dcd_rusb2.c create mode 100644 libraries/Adafruit_TinyUSB_Arduino/src/portable/renesas/rusb2/hcd_rusb2.c create mode 100644 libraries/Adafruit_TinyUSB_Arduino/src/portable/renesas/rusb2/rusb2_common.c create mode 100644 libraries/Adafruit_TinyUSB_Arduino/src/portable/renesas/rusb2/rusb2_common.h create mode 100644 libraries/Adafruit_TinyUSB_Arduino/src/portable/renesas/rusb2/rusb2_ra.h create mode 100644 libraries/Adafruit_TinyUSB_Arduino/src/portable/renesas/rusb2/rusb2_rx.h create mode 100644 libraries/Adafruit_TinyUSB_Arduino/src/portable/renesas/rusb2/rusb2_type.h create mode 100644 libraries/Adafruit_TinyUSB_Arduino/src/portable/st/stm32_fsdev/dcd_stm32_fsdev.c create mode 100644 libraries/Adafruit_TinyUSB_Arduino/src/portable/st/stm32_fsdev/fsdev_at32.h create mode 100644 libraries/Adafruit_TinyUSB_Arduino/src/portable/st/stm32_fsdev/fsdev_ch32.h create mode 100644 libraries/Adafruit_TinyUSB_Arduino/src/portable/st/stm32_fsdev/fsdev_common.c create mode 100644 libraries/Adafruit_TinyUSB_Arduino/src/portable/st/stm32_fsdev/fsdev_common.h create mode 100644 libraries/Adafruit_TinyUSB_Arduino/src/portable/st/stm32_fsdev/fsdev_stm32.h create mode 100644 libraries/Adafruit_TinyUSB_Arduino/src/portable/st/stm32_fsdev/hcd_stm32_fsdev.c create mode 100644 libraries/Adafruit_TinyUSB_Arduino/src/portable/synopsys/dwc2/dcd_dwc2.c create mode 100644 libraries/Adafruit_TinyUSB_Arduino/src/portable/synopsys/dwc2/dwc2_at32.h create mode 100644 libraries/Adafruit_TinyUSB_Arduino/src/portable/synopsys/dwc2/dwc2_bcm.h create mode 100644 libraries/Adafruit_TinyUSB_Arduino/src/portable/synopsys/dwc2/dwc2_common.c create mode 100644 libraries/Adafruit_TinyUSB_Arduino/src/portable/synopsys/dwc2/dwc2_common.h create mode 100644 libraries/Adafruit_TinyUSB_Arduino/src/portable/synopsys/dwc2/dwc2_efm32.h create mode 100644 libraries/Adafruit_TinyUSB_Arduino/src/portable/synopsys/dwc2/dwc2_esp32.h create mode 100644 libraries/Adafruit_TinyUSB_Arduino/src/portable/synopsys/dwc2/dwc2_gd32.h create mode 100644 libraries/Adafruit_TinyUSB_Arduino/src/portable/synopsys/dwc2/dwc2_info.md create mode 100644 libraries/Adafruit_TinyUSB_Arduino/src/portable/synopsys/dwc2/dwc2_info.py create mode 100644 libraries/Adafruit_TinyUSB_Arduino/src/portable/synopsys/dwc2/dwc2_nrf.h create mode 100644 libraries/Adafruit_TinyUSB_Arduino/src/portable/synopsys/dwc2/dwc2_stm32.h create mode 100644 libraries/Adafruit_TinyUSB_Arduino/src/portable/synopsys/dwc2/dwc2_type.h create mode 100644 libraries/Adafruit_TinyUSB_Arduino/src/portable/synopsys/dwc2/dwc2_xmc.h create mode 100644 libraries/Adafruit_TinyUSB_Arduino/src/portable/synopsys/dwc2/hcd_dwc2.c create mode 100644 libraries/Adafruit_TinyUSB_Arduino/src/portable/wch/ch32_usbfs_reg.h create mode 100644 libraries/Adafruit_TinyUSB_Arduino/src/portable/wch/ch32_usbhs_reg.h create mode 100644 libraries/Adafruit_TinyUSB_Arduino/src/portable/wch/dcd_ch32_usbfs.c create mode 100644 libraries/Adafruit_TinyUSB_Arduino/src/portable/wch/dcd_ch32_usbhs.c create mode 100644 libraries/Adafruit_TinyUSB_Arduino/src/portable/wch/hcd_ch32_usbfs.c create mode 100644 libraries/Adafruit_TinyUSB_Arduino/src/tusb.c create mode 100644 libraries/Adafruit_TinyUSB_Arduino/src/tusb.h create mode 100644 libraries/Adafruit_TinyUSB_Arduino/src/tusb_config.h create mode 100644 libraries/Adafruit_TinyUSB_Arduino/src/tusb_option.h diff --git a/libraries/Adafruit_TinyUSB_Arduino/LICENSE b/libraries/Adafruit_TinyUSB_Arduino/LICENSE new file mode 100644 index 0000000..db5d5da --- /dev/null +++ b/libraries/Adafruit_TinyUSB_Arduino/LICENSE @@ -0,0 +1,21 @@ +The MIT License (MIT) + +Copyright (c) 2019 Ha Thach for Adafruit Industries + +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. diff --git a/libraries/Adafruit_TinyUSB_Arduino/README.md b/libraries/Adafruit_TinyUSB_Arduino/README.md new file mode 100644 index 0000000..b649711 --- /dev/null +++ b/libraries/Adafruit_TinyUSB_Arduino/README.md @@ -0,0 +1,90 @@ +# Adafruit TinyUSB Library for Arduino + +[![Build Status](https://github.com/adafruit/Adafruit_TinyUSB_Arduino/workflows/Build/badge.svg)](https://github.com/adafruit/Adafruit_TinyUSB_Arduino/actions) [![License](https://img.shields.io/badge/license-MIT-brightgreen.svg)](https://opensource.org/licenses/MIT) + +This library is a Arduino-friendly version of [TinyUSB](https://github.com/hathach/tinyusb) stack. +It is designed with structure and APIs that are easily integrated to an Arduino Core. + +## Features + +### Device Stack + +Supported device class drivers are: + +- Communication (CDC): which is used to implement `Serial` monitor +- Human Interface Device (HID): Generic (In & Out), Keyboard, Mouse, Gamepad etc ... +- Mass Storage Class (MSC): with multiple LUNs +- Musical Instrument Digital Interface (MIDI) +- Video (UVC): work in progress +- WebUSB with vendor specific class + +### Host Stack + +Host stack is available with either addition of MAX3421E hardware (e.g [Host FeatherWing](https://www.adafruit.com/product/5858)) or rp2040 core (thanks to [Pico-PIO-USB](https://github.com/sekigon-gonnoc/Pico-PIO-USB)). Supported class driver are: + +- Communication (CDC): including vendor usb2uart such as FTDI, CP210x, CH34x +- MassStorage class + +Note: Host stack is still work-in-progress + +## Supported Cores + +There are 2 type of supported cores: with and without built-in support for TinyUSB. Built-in support provide seamless integration but requires extra code added to core's source code. Unfortunately it is not always easy or possible to make those modification. + +### Cores with built-in support + +Following core has TinyUSB as either the primary usb stack or selectable via menu `Tools->USB Stack`. You only need to include `` in your sketch to use. + +- [adafruit/Adafruit_nRF52_Arduino](https://github.com/adafruit/Adafruit_nRF52_Arduino) +- [adafruit/ArduinoCore-samd](https://github.com/adafruit/ArduinoCore-samd) +- [earlephilhower/arduino-pico](https://github.com/earlephilhower/arduino-pico) +- [espressif/arduino-esp32](https://github.com/espressif/arduino-esp32) Host mode using MAX3421E controller should work with all chips. Device mode only support S2/S3/P4 and additional Tools menu are needed + - `USB Mode=USB-OTG (TinyUSB)` for S3 and P4 + - `USB CDC On Boot=Enabled`, `USB Firmware MSC On Boot=Disabled`, `USB DFU On Boot=Disabled` +- [openwch/arduino_core_ch32](https://github.com/openwch/arduino_core_ch32) + +Note: For ESP32 port, version before v3.0 requires all descriptors must be specified in usb objects declaration i.e constructors. Therefore all descriptor-related fields must be part of object declaration and descriptor-related API have no effect afterwards. This limitation is not the case for version from v3.0. + +### Cores without built-in support + +Following is cores without built-in support + +- **mbed_rp2040** +- **[stm32duino/Arduino_Core_STM32](https://github.com/stm32duino/Arduino_Core_STM32)** + - Still WIP, only support/tested with F4, only support OTG_FS + +It is still possible to use TinyUSB but with some limits such as: + +- `TinyUSB_Device_Init()` need to be manually called in setup() +- `TinyUSB_Device_Task()` and/or `TinyUSB_Device_FlushCDC()` may (or not) need to be manually called in loop() +- Use `SerialTinyUSB` name instead of Serial for serial monitor +- And there could be more other issues, using on these cores should be considered as experimental + +## Class Driver API + +More document to write ... + +## Porting Guide + +To integrate TinyUSB library to a Arduino core, you will need to make changes to the core for built-in support and library for porting the mcu/platform. + +### Arduino Core Changes + +If possible, making changes to core will allow it to have built-in which make it almost transparent to user sketch + +1. Add this repo as submodule (or have local copy) at your ArduioCore/libraries/Adafruit_TinyUSB_Arduino (much like SPI). +2. Since Serial as CDC is considered as part of the core, we need to have `#include "Adafruit_USBD_CDC.h"` within your `Arduino.h`. For this to work, your `platform.txt` include path need to have `"-I{runtime.platform.path}/libraries/Adafruit_TinyUSB_Arduino/src/arduino"`. +3. In your `main.cpp` before setup() invoke the `TinyUSB_Device_Init(rhport)`. This will initialize usb device hardware and tinyusb stack and also include Serial as an instance of CDC class. +4. `TinyUSB_Device_Task()` must be called whenever there is new USB event. Depending on your core and MCU with or without RTOS. There are many ways to run the task. For example: + - Use USB IRQn to set flag then invoke function later on after exiting IRQ. + - Just invoke function after the loop(), within yield(), and delay() +5. `TinyUSB_Device_FlushCDC()` should also be called often to send out Serial data as well. +6. Note: For low power platform that make use of WFI()/WFE(), extra care is required before mcu go into low power mode. Check out my PR to circuipython for reference https://github.com/adafruit/circuitpython/pull/2956 + +### Library Changes + +In addition to core changes, library need to be ported to your platform. Don't worry, tinyusb stack has already done most of heavy-lifting. You only need to write a few APIs + +1. `TinyUSB_Port_InitDevice()` hardware specific (clock, phy) to enable usb hardware then call tud_init(). This API is called as part of TinyUSB_Device_Init() invocation. +2. `TinyUSB_Port_EnterDFU()` which is called when device need to enter DFU mode, usually by touch1200 feature +3. `TinyUSB_Port_GetSerialNumber()` which is called to get unique MCU Serial ID to used as Serial string descriptor. diff --git a/libraries/Adafruit_TinyUSB_Arduino/library.properties b/libraries/Adafruit_TinyUSB_Arduino/library.properties new file mode 100644 index 0000000..7d5c950 --- /dev/null +++ b/libraries/Adafruit_TinyUSB_Arduino/library.properties @@ -0,0 +1,11 @@ +name=Adafruit TinyUSB Library +version=3.7.7 +author=Adafruit +maintainer=Adafruit +sentence=TinyUSB library for Arduino +paragraph=Support nRF5x, SAMD21, SAMD51, RP2040, ESP32-S2/S3, CH32V +category=Communication +url=https://github.com/adafruit/Adafruit_TinyUSB_Arduino +architectures=* +includes=Adafruit_TinyUSB.h +depends=Adafruit SPIFlash, MIDI Library, SdFat - Adafruit Fork diff --git a/libraries/Adafruit_TinyUSB_Arduino/src/Adafruit_TinyUSB.h b/libraries/Adafruit_TinyUSB_Arduino/src/Adafruit_TinyUSB.h new file mode 100644 index 0000000..7782a79 --- /dev/null +++ b/libraries/Adafruit_TinyUSB_Arduino/src/Adafruit_TinyUSB.h @@ -0,0 +1,80 @@ +/* + * The MIT License (MIT) + * + * Copyright (c) 2019 Ha Thach for Adafruit Industries + * + * 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 ADAFRUIT_TINYUSB_H_ +#define ADAFRUIT_TINYUSB_H_ + +#include "tusb_option.h" + +// Device +#if CFG_TUD_ENABLED + +#include "arduino/Adafruit_USBD_Device.h" + +#if CFG_TUD_CDC +#include "arduino/Adafruit_USBD_CDC.h" +#endif + +#if CFG_TUD_HID +#include "arduino/hid/Adafruit_USBD_HID.h" +#endif + +#if CFG_TUD_MIDI +#include "arduino/midi/Adafruit_USBD_MIDI.h" +#endif + +#if CFG_TUD_MSC +#include "arduino/msc/Adafruit_USBD_MSC.h" +#endif + +#if CFG_TUD_VENDOR +#include "arduino/webusb/Adafruit_USBD_WebUSB.h" +#endif + +#if CFG_TUD_VIDEO +#include "arduino/video/Adafruit_USBD_Video.h" +#endif + +// Initialize device hardware, stack, also Serial as CDC +// Wrapper for TinyUSBDevice.begin(rhport) +void TinyUSB_Device_Init(uint8_t rhport); + +#endif + +// Host +#if CFG_TUH_ENABLED + +#include "arduino/Adafruit_USBH_Host.h" + +#if CFG_TUH_CDC +#include "arduino/cdc/Adafruit_USBH_CDC.h" +#endif + +#if CFG_TUH_MSC +#include "arduino/msc/Adafruit_USBH_MSC.h" +#endif + +#endif + +#endif /* ADAFRUIT_TINYUSB_H_ */ diff --git a/libraries/Adafruit_TinyUSB_Arduino/src/arduino/Adafruit_TinyUSB_API.cpp b/libraries/Adafruit_TinyUSB_Arduino/src/arduino/Adafruit_TinyUSB_API.cpp new file mode 100644 index 0000000..2f27632 --- /dev/null +++ b/libraries/Adafruit_TinyUSB_Arduino/src/arduino/Adafruit_TinyUSB_API.cpp @@ -0,0 +1,103 @@ +/* + * The MIT License (MIT) + * + * Copyright (c) 2019 Ha Thach for Adafruit Industries + * + * 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_option.h" + +#if CFG_TUD_ENABLED || CFG_TUH_ENABLED + +#include "Adafruit_TinyUSB.h" +#include "Arduino.h" + +extern "C" { + +uint32_t tusb_time_millis_api(void) { return millis(); } + +//--------------------------------------------------------------------+ +// Device +//--------------------------------------------------------------------+ +#if CFG_TUD_ENABLED +void TinyUSB_Device_Init(uint8_t rhport) { + // Init USB Device controller and stack + TinyUSBDevice.begin(rhport); +} + +// RP2040 has its own implementation since it needs mutex for dual core +#ifndef ARDUINO_ARCH_RP2040 +void TinyUSB_Device_Task(void) { + // Run tinyusb device task + tud_task(); +} +#endif + +#ifndef ARDUINO_ARCH_ESP32 +void TinyUSB_Device_FlushCDC(void) { + uint8_t const cdc_instance = Adafruit_USBD_CDC::getInstanceCount(); + for (uint8_t instance = 0; instance < cdc_instance; instance++) { + tud_cdc_n_write_flush(instance); + } +} +#endif +#endif // CFG_TUD_ENABLED + +//------------- Debug log with Serial1 -------------// +#if CFG_TUSB_DEBUG && defined(CFG_TUSB_DEBUG_PRINTF) && \ + !defined(ARDUINO_ARCH_ESP32) + +// #define USE_SEGGER_RTT +#ifndef SERIAL_TUSB_DEBUG +#define SERIAL_TUSB_DEBUG Serial1 +#endif + +#ifdef USE_SEGGER_RTT +#include "SEGGER_RTT/RTT/SEGGER_RTT.h" +#endif + +__attribute__((used)) int CFG_TUSB_DEBUG_PRINTF(const char *__restrict format, + ...) { + char buf[256]; + int len; + va_list ap; + va_start(ap, format); + len = vsnprintf(buf, sizeof(buf), format, ap); + +#ifdef USE_SEGGER_RTT + SEGGER_RTT_Write(0, buf, len); +#else + static volatile bool ser_inited = false; + if (!ser_inited) { + ser_inited = true; + SERIAL_TUSB_DEBUG.begin(115200); + // SERIAL_TUSB_DEBUG.begin(921600); + } + SERIAL_TUSB_DEBUG.write(buf); +#endif + + va_end(ap); + return len; +} +#endif // CFG_TUSB_DEBUG + +} // extern C + +#endif // CFG_TUD_ENABLED || CFG_TUH_ENABLED diff --git a/libraries/Adafruit_TinyUSB_Arduino/src/arduino/Adafruit_TinyUSB_API.h b/libraries/Adafruit_TinyUSB_Arduino/src/arduino/Adafruit_TinyUSB_API.h new file mode 100644 index 0000000..77826a6 --- /dev/null +++ b/libraries/Adafruit_TinyUSB_Arduino/src/arduino/Adafruit_TinyUSB_API.h @@ -0,0 +1,94 @@ +/* + * The MIT License (MIT) + * + * Copyright (c) 2019 Ha Thach for Adafruit Industries + * + * 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 ADAFRUIT_TINYUSB_API_H_ +#define ADAFRUIT_TINYUSB_API_H_ + +#include +#include + +// API Version, need to be updated when there is changes for +// TinyUSB_API, USBD_CDC, USBD_Device, USBD_Interface, +#define TINYUSB_API_VERSION 30000 + +#if defined(ARDUINO_ARCH_SAMD) || defined(ARDUINO_NRF52_ADAFRUIT) || \ + defined(ARDUINO_ARCH_ESP32) || defined(ARDUINO_ARCH_CH32) || \ + (defined(ARDUINO_ARCH_RP2040) && !defined(ARDUINO_ARCH_MBED)) +#define TINYUSB_HAS_BUITLTIN_CORE_SUPPORT +#endif + +// Core that has built-in support: Adafruit SAMD, Adafruit nRF, rp2040, esp32 +#if !defined(TINYUSB_HAS_BUITLTIN_CORE_SUPPORT) +#define TINYUSB_NEED_POLLING_TASK +#endif + +// Error message for Core that must select TinyUSB via menu (built-in except +// esp32) +#if !defined(USE_TINYUSB) && (defined(TINYUSB_HAS_BUITLTIN_CORE_SUPPORT) && \ + !defined(ARDUINO_ARCH_ESP32)) +#error TinyUSB is not selected, please select it in "Tools->Menu->USB Stack" +#endif + +//--------------------------------------------------------------------+ +// Core API +// Should be called by BSP Core to initialize, process task +// Weak function allow compile arduino core before linking with this library +//--------------------------------------------------------------------+ +#ifdef __cplusplus +extern "C" { +#endif + +// Called by core/sketch to initialize usb device hardware and stack +// This also initialize Serial as CDC device +void TinyUSB_Device_Init(uint8_t rhport) __attribute__((weak)); + +// Called by core/sketch to handle device event +void TinyUSB_Device_Task(void) __attribute__((weak)); + +// Called by core/sketch to flush write on CDC +void TinyUSB_Device_FlushCDC(void) __attribute__((weak)); + +#ifdef __cplusplus +} +#endif + +//--------------------------------------------------------------------+ +// Port API +// Must be implemented by each BSP core/platform +//--------------------------------------------------------------------+ + +// To enter/reboot to bootloader +// usually when host disconnects cdc at baud 1200 (touch 1200) +void TinyUSB_Port_EnterDFU(void); + +// Init device hardware. +// Called by TinyUSB_Device_Init() +void TinyUSB_Port_InitDevice(uint8_t rhport); + +// Get unique serial number, needed for Serial String Descriptor +// Fill serial_id (raw bytes) and return its length (limit to 16 bytes) +// Note: Serial descriptor can be overwritten by user API +uint8_t TinyUSB_Port_GetSerialNumber(uint8_t serial_id[16]); + +#endif diff --git a/libraries/Adafruit_TinyUSB_Arduino/src/arduino/Adafruit_USBD_CDC.cpp b/libraries/Adafruit_TinyUSB_Arduino/src/arduino/Adafruit_USBD_CDC.cpp new file mode 100644 index 0000000..11c5673 --- /dev/null +++ b/libraries/Adafruit_TinyUSB_Arduino/src/arduino/Adafruit_USBD_CDC.cpp @@ -0,0 +1,346 @@ +/* + * The MIT License (MIT) + * + * Copyright (c) 2019 Ha Thach for Adafruit Industries + * + * 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_option.h" + +// esp32 use built-in core cdc +#if CFG_TUD_CDC && !defined(ARDUINO_ARCH_ESP32) + +#include "Arduino.h" + +#include "Adafruit_TinyUSB_API.h" + +#include "Adafruit_USBD_CDC.h" +#include "Adafruit_USBD_Device.h" + +#ifndef TINYUSB_API_VERSION +#define TINYUSB_API_VERSION 0 +#endif + +// SerialTinyUSB can be macro expanding to "Serial" on supported cores +Adafruit_USBD_CDC SerialTinyUSB; + +uint8_t Adafruit_USBD_CDC::_instance_count = 0; +Adafruit_USBD_CDC::Adafruit_USBD_CDC(void) { _instance = INVALID_INSTANCE; } + +#if CFG_TUD_ENABLED + +uint16_t Adafruit_USBD_CDC::getInterfaceDescriptor(uint8_t itfnum_deprecated, + uint8_t *buf, + uint16_t bufsize) { + (void)itfnum_deprecated; + + // CDC is mostly always existed for DFU + uint8_t itfnum = 0; + uint8_t ep_notif = 0; + uint8_t ep_in = 0; + uint8_t ep_out = 0; + + if (buf) { + itfnum = TinyUSBDevice.allocInterface(2); + ep_notif = TinyUSBDevice.allocEndpoint(TUSB_DIR_IN); + ep_in = TinyUSBDevice.allocEndpoint(TUSB_DIR_IN); + ep_out = TinyUSBDevice.allocEndpoint(TUSB_DIR_OUT); + } + +#if TINYUSB_API_VERSION < 20400 + // backward compatible for core that include pre-2.4.0 TinyUSB + uint8_t _strid = 0; +#endif + + uint16_t const mps = + (TUD_OPT_HIGH_SPEED ? 512 : 64); // TODO actual link speed + uint8_t const desc[] = { + TUD_CDC_DESCRIPTOR(itfnum, _strid, ep_notif, 8, ep_out, ep_in, mps)}; + + uint16_t const len = sizeof(desc); + + // null buffer is used to get the length of descriptor only + if (buf) { + if (bufsize < len) { + return 0; + } + memcpy(buf, desc, len); + } + + return len; +} + +// Baud and config is ignore in CDC +void Adafruit_USBD_CDC::begin(uint32_t baud) { + (void)baud; + + // already called begin() + if (isValid()) { + return; + } + + // too many instances + if (!(_instance_count < CFG_TUD_CDC)) { + return; + } + + _instance = _instance_count++; + this->setStringDescriptor("TinyUSB Serial"); + TinyUSBDevice.addInterface(*this); +} + +void Adafruit_USBD_CDC::begin(uint32_t baud, uint8_t config) { + (void)config; + this->begin(baud); +} + +void Adafruit_USBD_CDC::end(void) { + // Reset configuration descriptor without Serial as CDC + TinyUSBDevice.clearConfiguration(); + _instance_count = 0; + _instance = INVALID_INSTANCE; +} + +uint32_t Adafruit_USBD_CDC::baud(void) { + if (!isValid()) { + return 0; + } + + cdc_line_coding_t coding; + tud_cdc_n_get_line_coding(_instance, &coding); + + return coding.bit_rate; +} + +uint8_t Adafruit_USBD_CDC::stopbits(void) { + if (!isValid()) { + return 0; + } + + cdc_line_coding_t coding; + tud_cdc_n_get_line_coding(_instance, &coding); + + return coding.stop_bits; +} + +uint8_t Adafruit_USBD_CDC::paritytype(void) { + if (!isValid()) { + return 0; + } + + cdc_line_coding_t coding; + tud_cdc_n_get_line_coding(_instance, &coding); + + return coding.parity; +} + +uint8_t Adafruit_USBD_CDC::numbits(void) { + if (!isValid()) { + return 0; + } + + cdc_line_coding_t coding; + tud_cdc_n_get_line_coding(_instance, &coding); + + return coding.data_bits; +} + +int Adafruit_USBD_CDC::dtr(void) { + if (!isValid()) { + return 0; + } + + return tud_cdc_n_connected(_instance); +} + +Adafruit_USBD_CDC::operator bool() { + if (!isValid()) { + return false; + } + + bool ret = tud_cdc_n_connected(_instance); + + // Add an yield to run usb background in case sketch block wait as follows + // while( !Serial ) {} + if (!ret) { + yield(); + } + return ret; +} + +int Adafruit_USBD_CDC::available(void) { + if (!isValid()) { + return 0; + } + + uint32_t count = tud_cdc_n_available(_instance); + + // Add an yield to run usb background in case sketch block wait as follows + // while( !Serial.available() ) {} + if (!count) { + yield(); + } + + return count; +} + +int Adafruit_USBD_CDC::peek(void) { + if (!isValid()) { + return -1; + } + + uint8_t ch; + return tud_cdc_n_peek(_instance, &ch) ? (int)ch : -1; +} + +int Adafruit_USBD_CDC::read(void) { + if (!isValid()) { + return -1; + } + return (int)tud_cdc_n_read_char(_instance); +} + +#if TINYUSB_API_VERSION >= 10700 +size_t Adafruit_USBD_CDC::read(uint8_t *buffer, size_t size) { + if (!isValid()) { + return 0; + } + + return tud_cdc_n_read(_instance, buffer, size); +} +#endif + +void Adafruit_USBD_CDC::flush(void) { + if (!isValid()) { + return; + } + + tud_cdc_n_write_flush(_instance); +} + +size_t Adafruit_USBD_CDC::write(uint8_t ch) { return write(&ch, 1); } + +size_t Adafruit_USBD_CDC::write(const uint8_t *buffer, size_t size) { + if (!isValid()) { + return 0; + } + + size_t remain = size; + while (remain && tud_cdc_n_connected(_instance)) { + size_t wrcount = tud_cdc_n_write(_instance, buffer, remain); + remain -= wrcount; + buffer += wrcount; + + // Write FIFO is full, run usb background to flush + if (remain) { + yield(); + } + } + + return size - remain; +} + +int Adafruit_USBD_CDC::availableForWrite(void) { + if (!isValid()) { + return 0; + } + return tud_cdc_n_write_available(_instance); +} + +extern "C" { + +// Invoked when cdc when line state changed e.g connected/disconnected +// Use to reset to DFU when disconnect with 1200 bps +void tud_cdc_line_state_cb(uint8_t instance, bool dtr, bool rts) { + (void)rts; + + // DTR = false is counted as disconnected + if (!dtr) { + // touch1200 only with first CDC instance (Serial) + if (instance == 0) { + cdc_line_coding_t coding; + tud_cdc_get_line_coding(&coding); + + if (coding.bit_rate == 1200) { + TinyUSB_Port_EnterDFU(); + } + } + } +} +} + +#else + +// Device stack is not enabled (probably in host mode) +#warning "TinyUSB Host selected. No output to Serial will occur!" + +uint16_t Adafruit_USBD_CDC::getInterfaceDescriptor(uint8_t itfnum_deprecated, + uint8_t *buf, + uint16_t bufsize) { + (void)itfnum_deprecated; + (void)buf; + (void)bufsize; + + return 0; +} + +// Baud and config is ignore in CDC +void Adafruit_USBD_CDC::begin(uint32_t baud) { (void)baud; } + +void Adafruit_USBD_CDC::begin(uint32_t baud, uint8_t config) { (void)config; } + +void Adafruit_USBD_CDC::end(void) {} + +uint32_t Adafruit_USBD_CDC::baud(void) { return 0; } + +uint8_t Adafruit_USBD_CDC::stopbits(void) { return 0; } + +uint8_t Adafruit_USBD_CDC::paritytype(void) { return 0; } + +uint8_t Adafruit_USBD_CDC::numbits(void) { return 0; } + +int Adafruit_USBD_CDC::dtr(void) { return 0; } + +Adafruit_USBD_CDC::operator bool() { return false; } + +int Adafruit_USBD_CDC::available(void) { return 0; } + +int Adafruit_USBD_CDC::peek(void) { return -1; } + +int Adafruit_USBD_CDC::read(void) { return -1; } + +size_t Adafruit_USBD_CDC::read(uint8_t *buffer, size_t size) { + (void)buffer; + (void)size; + return 0; +} + +void Adafruit_USBD_CDC::flush(void) {} + +size_t Adafruit_USBD_CDC::write(uint8_t ch) { return -1; } + +size_t Adafruit_USBD_CDC::write(const uint8_t *buffer, size_t size) { + return 0; +} + +int Adafruit_USBD_CDC::availableForWrite(void) { return 0; } + +#endif // CFG_TUD_ENABLED +#endif // CDC + ESP32 diff --git a/libraries/Adafruit_TinyUSB_Arduino/src/arduino/Adafruit_USBD_CDC.h b/libraries/Adafruit_TinyUSB_Arduino/src/arduino/Adafruit_USBD_CDC.h new file mode 100644 index 0000000..9c0952f --- /dev/null +++ b/libraries/Adafruit_TinyUSB_Arduino/src/arduino/Adafruit_USBD_CDC.h @@ -0,0 +1,109 @@ +/* + * The MIT License (MIT) + * + * Copyright (c) 2019 Ha Thach for Adafruit Industries + * + * 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 ADAFRUIT_USBD_CDC_H_ +#define ADAFRUIT_USBD_CDC_H_ + +#include "Adafruit_TinyUSB_API.h" + +#if defined(__cplusplus) + +#if defined(ARDUINO_ARCH_ESP32) + +// For ESP32 use USBCDC as it is compatible +#define Adafruit_USBD_CDC USBCDC +#define SerialTinyUSB Serial + +#else + +#include "Adafruit_USBD_Interface.h" +#include "Stream.h" + +class Adafruit_USBD_CDC : public Stream, public Adafruit_USBD_Interface { +public: + Adafruit_USBD_CDC(void); + + static uint8_t getInstanceCount(void) { return _instance_count; } + + void setPins(uint8_t pin_rx, uint8_t pin_tx) { + (void)pin_rx; + (void)pin_tx; + } + void begin(uint32_t baud); + void begin(uint32_t baud, uint8_t config); + void end(void); + + // return line coding set by host + uint32_t baud(void); + uint8_t stopbits(void); + uint8_t paritytype(void); + uint8_t numbits(void); + int dtr(void); + + // Stream API + virtual int available(void); + virtual int peek(void); + + virtual int read(void); + size_t read(uint8_t *buffer, size_t size); + + virtual void flush(void); + virtual size_t write(uint8_t); + + virtual size_t write(const uint8_t *buffer, size_t size); + size_t write(const char *buffer, size_t size) { + return write((const uint8_t *)buffer, size); + } + + virtual int availableForWrite(void); + using Print::write; // pull in write(str) from Print + operator bool(); + + // from Adafruit_USBD_Interface + virtual uint16_t getInterfaceDescriptor(uint8_t itfnum_deprecated, + uint8_t *buf, uint16_t bufsize); + +private: + enum { INVALID_INSTANCE = 0xffu }; + static uint8_t _instance_count; + + uint8_t _instance; + + bool isValid(void) { return _instance != INVALID_INSTANCE; } +}; + +extern Adafruit_USBD_CDC SerialTinyUSB; + +// Built-in support "Serial" is assigned to TinyUSB CDC +// CH32 defines Serial as alias in WSerial.h +#if defined(USE_TINYUSB) && !defined(ARDUINO_ARCH_CH32) +#define SerialTinyUSB Serial +#endif + +extern Adafruit_USBD_CDC SerialTinyUSB; + +#endif // else of ESP32 +#endif // __cplusplus + +#endif diff --git a/libraries/Adafruit_TinyUSB_Arduino/src/arduino/Adafruit_USBD_Device.cpp b/libraries/Adafruit_TinyUSB_Arduino/src/arduino/Adafruit_USBD_Device.cpp new file mode 100644 index 0000000..b044e15 --- /dev/null +++ b/libraries/Adafruit_TinyUSB_Arduino/src/arduino/Adafruit_USBD_Device.cpp @@ -0,0 +1,527 @@ +/* + * The MIT License (MIT) + * + * Copyright (c) 2019 Ha Thach for Adafruit Industries + * + * 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_option.h" + +#if CFG_TUD_ENABLED + +#include "Adafruit_TinyUSB_API.h" + +#include "Adafruit_USBD_CDC.h" +#include "Adafruit_USBD_Device.h" + +// USB Information can be defined in variant file e.g pins_arduino.h +#include "Arduino.h" + +/* VID, PID, Manufacturer and Product name: + * - For most ports: USB_VID, USB_PID, USB_MANUFACTURER, USB_PRODUCT are + * defined. + * - For ESP32: Default USB_MANUFACTURER is Espressif (instead of Adafruit), + * ARDUINO_BOARD as USB_PRODUCT + * - For mbed core: BOARD_VENDORID, BOARD_PRODUCTID, BOARD_MANUFACTURER, + * BOARD_NAME are defined + */ + +#ifndef USB_VID +#ifdef BOARD_VENDORID +#define USB_VID BOARD_VENDORID +#else +#define USB_VID 0x239a +#endif +#endif + +#ifndef USB_PID +#ifdef BOARD_PRODUCTID +#define USB_PID BOARD_PRODUCTID +#else +#define USB_PID 0xcafe +#endif +#endif + +#ifndef USB_MANUFACTURER +#ifdef BOARD_MANUFACTURER +#define USB_MANUFACTURER BOARD_MANUFACTURER +#elif defined(ARDUINO_ARCH_ESP32) +#define USB_MANUFACTURER "Espressif Systems" +#else +#define USB_MANUFACTURER "Adafruit" +#endif +#endif + +#ifndef USB_PRODUCT +#if defined(ARDUINO_BOARD) +#define USB_PRODUCT ARDUINO_BOARD +#elif defined(BOARD_NAME) +#define USB_PRODUCT BOARD_NAME +#else +#define USB_PRODUCT "Unknown" +#endif +#endif + +#ifndef USB_LANGUAGE +#define USB_LANGUAGE 0x0409 // default is English +#endif + +#ifndef USB_CONFIG_POWER +#define USB_CONFIG_POWER 100 +#endif + +enum { STRID_LANGUAGE = 0, STRID_MANUFACTURER, STRID_PRODUCT, STRID_SERIAL }; + +Adafruit_USBD_Device TinyUSBDevice; + +Adafruit_USBD_Device::Adafruit_USBD_Device(void) { +#if defined(ARDUINO_ARCH_ESP32) && ARDUINO_USB_CDC_ON_BOOT && !ARDUINO_USB_MODE + // auto begin for ESP32 USB OTG Mode with CDC on boot + begin(0); +#endif +} + +void Adafruit_USBD_Device::setConfigurationBuffer(uint8_t *buf, + uint32_t buflen) { + if (buflen < _desc_cfg_maxlen) { + return; + } + + memcpy(buf, _desc_cfg, _desc_cfg_len); + _desc_cfg = buf; + _desc_cfg_maxlen = buflen; +} + +void Adafruit_USBD_Device::setID(uint16_t vid, uint16_t pid) { + _desc_device.idVendor = vid; + _desc_device.idProduct = pid; +} + +void Adafruit_USBD_Device::setVersion(uint16_t bcd) { + _desc_device.bcdUSB = bcd; +} + +void Adafruit_USBD_Device::setDeviceVersion(uint16_t bcd) { + _desc_device.bcdDevice = bcd; +} + +void Adafruit_USBD_Device::setLanguageDescriptor(uint16_t language_id) { + _desc_str_arr[STRID_LANGUAGE] = (const char *)((uint32_t)language_id); +} + +void Adafruit_USBD_Device::setManufacturerDescriptor(const char *s) { + _desc_str_arr[STRID_MANUFACTURER] = s; +} + +void Adafruit_USBD_Device::setProductDescriptor(const char *s) { + _desc_str_arr[STRID_PRODUCT] = s; +} + +void Adafruit_USBD_Device::setSerialDescriptor(const char *s) { + _desc_str_arr[STRID_SERIAL] = s; +} + +// Add a string descriptor to the device's pool +// Return string index +uint8_t Adafruit_USBD_Device::addStringDescriptor(const char *s) { + if (_desc_str_count >= STRING_DESCRIPTOR_MAX || s == NULL) { + return 0; + } + + uint8_t index = _desc_str_count++; + _desc_str_arr[index] = s; + return index; +} + +void Adafruit_USBD_Device::task(void) { + tud_task(); + +#ifdef TINYUSB_NEED_POLLING_TASK + // can also be used with port with built-in support + if (SerialTinyUSB) { + SerialTinyUSB.flush(); + } +#endif +} + +void Adafruit_USBD_Device::clearConfiguration(void) { + tusb_desc_device_t const desc_dev = {.bLength = sizeof(tusb_desc_device_t), + .bDescriptorType = TUSB_DESC_DEVICE, + .bcdUSB = 0x0200, + .bDeviceClass = 0, + .bDeviceSubClass = 0, + .bDeviceProtocol = 0, + .bMaxPacketSize0 = + CFG_TUD_ENDPOINT0_SIZE, + .idVendor = USB_VID, + .idProduct = USB_PID, + .bcdDevice = 0x0100, + .iManufacturer = STRID_MANUFACTURER, + .iProduct = STRID_PRODUCT, + .iSerialNumber = STRID_SERIAL, + .bNumConfigurations = 0x01}; + + _desc_device = desc_dev; + + // Config number, interface count, string index, total length, + // attribute (bit 7 set to 1), power in mA. + // Note: Total Length Interface Number will be updated later + uint8_t const dev_cfg[sizeof(tusb_desc_configuration_t)] = { + TUD_CONFIG_DESCRIPTOR(1, 0, 0, sizeof(tusb_desc_configuration_t), + TUSB_DESC_CONFIG_ATT_REMOTE_WAKEUP | TU_BIT(7), + USB_CONFIG_POWER), + }; + + memcpy(_desc_cfg_buffer, dev_cfg, sizeof(tusb_desc_configuration_t)); + _desc_cfg = _desc_cfg_buffer; + _desc_cfg_maxlen = sizeof(_desc_cfg_buffer); + _desc_cfg_len = sizeof(tusb_desc_configuration_t); + + _itf_count = 0; + _epin_count = _epout_count = 1; + + memset(_desc_str_arr, 0, sizeof(_desc_str_arr)); + _desc_str_arr[STRID_LANGUAGE] = (const char *)((uint32_t)USB_LANGUAGE); + _desc_str_arr[STRID_MANUFACTURER] = USB_MANUFACTURER; + _desc_str_arr[STRID_PRODUCT] = USB_PRODUCT; + _desc_str_arr[STRID_SERIAL] = nullptr; + // STRID_SERIAL is platform dependent + + _desc_str_count = 4; +} + +// Add interface descriptor +// - Interface number will be updated to match current count +// - Endpoint number is updated to be unique +bool Adafruit_USBD_Device::addInterface(Adafruit_USBD_Interface &itf) { + uint8_t *desc = _desc_cfg + _desc_cfg_len; + uint16_t const len = itf.getInterfaceDescriptor( + _itf_count, desc, _desc_cfg_maxlen - _desc_cfg_len); + + if (!len) { + return false; + } + + _desc_cfg_len += len; + + // Update configuration descriptor + tusb_desc_configuration_t *config = (tusb_desc_configuration_t *)_desc_cfg; + config->wTotalLength = _desc_cfg_len; + config->bNumInterfaces = _itf_count; + + return true; +} + +bool Adafruit_USBD_Device::begin(uint8_t rhport) { + clearConfiguration(); + + // Serial is always added by default + // 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) + _desc_device.bDeviceClass = TUSB_CLASS_MISC; + _desc_device.bDeviceSubClass = MISC_SUBCLASS_COMMON; + _desc_device.bDeviceProtocol = MISC_PROTOCOL_IAD; + +#if defined(ARDUINO_ARCH_ESP32) +#if ARDUINO_USB_CDC_ON_BOOT && !ARDUINO_USB_MODE + // follow USBCDC cdc descriptor + uint8_t itfnum = allocInterface(2); + uint8_t strid = addStringDescriptor("TinyUSB Serial"); + uint16_t const mps = + (TUD_OPT_HIGH_SPEED ? 512 : 64); // TODO actual link speed + uint8_t const desc_cdc[TUD_CDC_DESC_LEN] = { + TUD_CDC_DESCRIPTOR(itfnum, strid, 0x85, 64, 0x03, 0x84, mps)}; + + memcpy(_desc_cfg + _desc_cfg_len, desc_cdc, sizeof(desc_cdc)); + _desc_cfg_len += sizeof(desc_cdc); + + // Update configuration descriptor + tusb_desc_configuration_t *config = (tusb_desc_configuration_t *)_desc_cfg; + config->wTotalLength = _desc_cfg_len; + config->bNumInterfaces = _itf_count; +#endif +#else + SerialTinyUSB.begin(115200); + + // Init device hardware and call tusb_init() + TinyUSB_Port_InitDevice(rhport); +#endif + + return true; +} + +bool Adafruit_USBD_Device::isInitialized(uint8_t rhport) { + (void)rhport; + return tud_inited(); +} + +static int strcpy_utf16(const char *s, uint16_t *buf, int bufsize); + +uint8_t Adafruit_USBD_Device::getSerialDescriptor(uint16_t *serial_utf16) { + + if (!_desc_str_arr[STRID_SERIAL]) { + uint8_t serial_id[16] __attribute__((aligned(4))); + uint8_t const serial_len = TinyUSB_Port_GetSerialNumber(serial_id); + + for (uint8_t i = 0; i < serial_len; i++) { + for (uint8_t j = 0; j < 2; j++) { + const char nibble_to_hex[16] = {'0', '1', '2', '3', '4', '5', '6', '7', + '8', '9', 'A', 'B', 'C', 'D', 'E', 'F'}; + + uint8_t nibble = (serial_id[i] >> (j * 4)) & 0xf; + serial_utf16[1 + i * 2 + (1 - j)] = nibble_to_hex[nibble]; // UTF-16-LE + } + } + + return 2 * serial_len; + } else { + return strcpy_utf16(_desc_str_arr[STRID_SERIAL], serial_utf16 + 1, 32); + } +} + +uint16_t const *Adafruit_USBD_Device::descriptor_string_cb(uint8_t index, + uint16_t langid) { + (void)langid; + + uint8_t chr_count; + + switch (index) { + case STRID_LANGUAGE: + _desc_str[1] = ((uint16_t)((uint32_t)_desc_str_arr[STRID_LANGUAGE])); + chr_count = 1; + break; + + case STRID_SERIAL: + chr_count = getSerialDescriptor(_desc_str); + break; + + default: + // Invalid index + if (index >= _desc_str_count) { + return NULL; + } + + chr_count = strcpy_utf16(_desc_str_arr[index], _desc_str + 1, 32); + break; + } + + // first byte is length (including header), second byte is string type + _desc_str[0] = (TUSB_DESC_STRING << 8) | (2 * chr_count + 2); + + return _desc_str; +} + +//--------------------------------------------------------------------+ +// TinyUSB stack callbacks +//--------------------------------------------------------------------+ + +extern "C" { + +// Invoked when received GET DEVICE DESCRIPTOR +// Application return pointer to descriptor +uint8_t const *tud_descriptor_device_cb(void) { + return (uint8_t const *)&TinyUSBDevice._desc_device; +} + +// Invoked when received GET CONFIGURATION DESCRIPTOR +// Application return pointer to descriptor, whose contents must exist long +// enough for transfer to complete +uint8_t const *tud_descriptor_configuration_cb(uint8_t index) { + (void)index; + return TinyUSBDevice._desc_cfg; +} + +// Invoked when received GET STRING DESCRIPTOR request +// Application return pointer to descriptor, whose contents must exist long +// enough for transfer to complete Note: the 0xEE index string is a Microsoft +// OS 1.0 Descriptors. +// https://docs.microsoft.com/en-us/windows-hardware/drivers/usbcon/microsoft-defined-usb-descriptors +uint16_t const *tud_descriptor_string_cb(uint8_t index, uint16_t langid) { + return TinyUSBDevice.descriptor_string_cb(index, langid); +} + +} // extern C + +//--------------------------------------------------------------------+ +// Helper +//--------------------------------------------------------------------+ +constexpr static inline bool isInvalidUtf8Octet(uint8_t t) { + // see bullets in https://tools.ietf.org/html/rfc3629#section-1 + return (t == 0xc0) || (t == 0xC1) || (t >= 0xF5); +} + +// +// This function has an UNWRITTEN CONTRACT that the buffer is either: +// 1. Pre-validated as legal UTF-8, -OR- +// 2. has a trailing zero-value octet/byte/uint8_t (aka null-terminated string) +// +// If the above are not true, this decoder may read past the end of the +// allocated buffer, by up to three bytes. +// +// U+1F47F == 👿 ("IMP") +// == 0001_1111_0100_0111_1111 ==> requires four-byte encoding in UTF-8 +// AABB BBBB CCCC CCDD DDDD ==> 0xF0 0x9F 0x91 0xBF +// +// Example sandwich and safety variables are there to cover the +// two most-common stack layouts for declared variables, in unoptimized +// code, so that the bytes surrounding those allocated for 'evilUTF8' +// are guaranteed to be non-zero and valid UTF8 continuation octets. +// uint8_t safety1 = 0; +// uint8_t sandwich1[4] = { 0x81, 0x82, 0x83, 0x84 }; +// uint8_t evilUTF8[5] = { 0xF0, 0x9F, 0x91, 0xBF, 0xF9 }; +// uint8_t sandwich2[4] = { 0x85, 0x86, 0x87, 0x88 }; +// uint8_t safety2 = 0; +// +// NOTE: evilUTF8 could just contain a single byte 0xF9 .... +// +// Attempting to decode evilUTF8 will progress to whatever is next to it on the +// stack. The above should work when optimizations are turned +// +static int8_t utf8Codepoint(const uint8_t *utf8, uint32_t *codepointp) { + const uint32_t CODEPOINT_LOWEST_SURROGATE_HALF = 0xD800; + const uint32_t CODEPOINT_HIGHEST_SURROGATE_HALF = 0xDFFF; + + *codepointp = 0xFFFD; // always initialize output to known value ... 0xFFFD + // (REPLACEMENT CHARACTER) seems the natural choice + uint32_t codepoint; + int len; + + // The upper bits define both the length of additional bytes for the + // multi-byte encoding, as well as defining how many bits of the first byte + // are included in the codepoint. Each additional byte starts with 0b10xxxxxx, + // encoding six additional bits for the codepoint. + // + // For key summary points, see: + // * https://tools.ietf.org/html/rfc3629#section-3 + // + if (isInvalidUtf8Octet(utf8[0])) { + // do not allow illegal octet sequences (e.g., 0xC0 0x80 + // should NOT decode to NULL) + return -1; + } + + if (utf8[0] < 0x80) { + // characters 0000 0000..0000 007F (up to 7 significant bits) + len = 1; + codepoint = utf8[0]; + } else if ((utf8[0] & 0xe0) == 0xc0) { + // characters 0000 0080..0000 07FF + // (up to 11 significant bits, so first byte encodes five bits) + len = 2; + codepoint = utf8[0] & 0x1f; + } else if ((utf8[0] & 0xf0) == 0xe0) { + // characters 0000 8000..0000 FFFF + // (up to 16 significant bits, so first byte encodes four bits) + len = 3; + codepoint = utf8[0] & 0x0f; + } else if ((utf8[0] & 0xf8) == 0xf0) { + // characters 0001 0000..0010 FFFF + // (up to 21 significantbits, so first byte encodes three bits) + len = 4; + codepoint = utf8[0] & 0x07; + } else { + // UTF-8 is defined to only map to Unicode -- 0x00000000..0x0010FFFF + // 5-byte and 6-byte sequences are not legal per RFC3629 + return -1; + } + + for (int i = 1; i < len; i++) { + if ((utf8[i] & 0xc0) != 0x80) { + // the additional bytes in a valid UTF-8 multi-byte encoding cannot have + // either of the top two bits set This is more restrictive than + // isInvalidUtf8Octet() + return -1; + } + codepoint <<= 6; // each continuation byte adds six bits to the codepoint + codepoint |= utf8[i] & 0x3f; // mask off the top two continuation bits, and + // add the six relevant bits + } + + // explicit validation to prevent overlong encodings + if ((len == 1) && (codepoint > 0x00007F)) { + return -1; + } else if ((len == 2) && ((codepoint < 0x000080) || (codepoint > 0x0007FF))) { + return -1; + } else if ((len == 3) && ((codepoint < 0x000800) || (codepoint > 0x00FFFF))) { + return -1; + } else if ((len == 4) && ((codepoint < 0x010000) || (codepoint > 0x10FFFF))) { + // "You might expect larger code points than U+10FFFF + // to be expressible, but Unicode is limited in Sections 12 + // of RFC3629 to match the limits of UTF-16." -- Wikipedia UTF-8 note + // See https://tools.ietf.org/html/rfc3629#section-12 + return -1; + } + + // high and low surrogate halves (U+D800 through U+DFFF) used by UTF-16 are + // not legal Unicode values ... see RFC 3629. + if ((codepoint >= CODEPOINT_LOWEST_SURROGATE_HALF) && + (codepoint <= CODEPOINT_HIGHEST_SURROGATE_HALF)) { + return -1; + } + + *codepointp = codepoint; + return len; +} + +static int strcpy_utf16(const char *s, uint16_t *buf, int bufsize) { + int i = 0; + int buflen = 0; + + while (s[i] != 0) { + uint32_t codepoint; + int8_t utf8len = utf8Codepoint((const uint8_t *)s + i, &codepoint); + + if (utf8len < 0) { + // Invalid utf8 sequence, skip it + i++; + continue; + } + + i += utf8len; + + if (codepoint <= 0xffff) { + if (buflen == bufsize) + break; + + buf[buflen++] = codepoint; + + } else { + if (buflen + 1 >= bufsize) + break; + + // Surrogate pair + codepoint -= 0x10000; + buf[buflen++] = (codepoint >> 10) + 0xd800; + buf[buflen++] = (codepoint & 0x3ff) + 0xdc00; + } + } + + return buflen; +} + +// TODO just for compiling, will move to DFU specific file +#if CFG_TUD_DFU_RUNTIME && !defined(ARDUINO_ARCH_ESP32) +void tud_dfu_runtime_reboot_to_dfu_cb(void) { + // TinyUSB_Port_EnterDFU(); +} +#endif + +#endif // CFG_TUD_ENABLED diff --git a/libraries/Adafruit_TinyUSB_Arduino/src/arduino/Adafruit_USBD_Device.h b/libraries/Adafruit_TinyUSB_Arduino/src/arduino/Adafruit_USBD_Device.h new file mode 100644 index 0000000..48c4d38 --- /dev/null +++ b/libraries/Adafruit_TinyUSB_Arduino/src/arduino/Adafruit_USBD_Device.h @@ -0,0 +1,167 @@ +/* + * The MIT License (MIT) + * + * Copyright (c) 2019 Ha Thach for Adafruit Industries + * + * 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 ADAFRUIT_USBD_DEVICE_H_ +#define ADAFRUIT_USBD_DEVICE_H_ + +#include "Adafruit_USBD_Interface.h" + +#include "../tusb.h" // use relative path to prevent ESP32 out-of-sync issue + +#ifdef ARDUINO_ARCH_ESP32 +#include "esp32-hal-tinyusb.h" +#endif + +#if CFG_TUD_ENABLED + +class Adafruit_USBD_Device { +private: + enum { STRING_DESCRIPTOR_MAX = 12 }; + + // Device descriptor + tusb_desc_device_t _desc_device __attribute__((aligned(4))); + + // Configuration descriptor + uint8_t *_desc_cfg; + uint8_t _desc_cfg_buffer[256]; + uint16_t _desc_cfg_len; + uint16_t _desc_cfg_maxlen; + + uint8_t _itf_count; + + uint8_t _epin_count; + uint8_t _epout_count; + + // String descriptor + const char *_desc_str_arr[STRING_DESCRIPTOR_MAX]; + uint8_t _desc_str_count; + uint16_t _desc_str[32 + 1]; // up to 32 unicode characters with headers + +public: + Adafruit_USBD_Device(void); + + //------------- Device descriptor -------------// + + // Set VID, PID + void setID(uint16_t vid, uint16_t pid); + + // Set bcdUSB version e.g 1.0, 2.0, 2.1 + void setVersion(uint16_t bcd); + + // Set bcdDevice version + void setDeviceVersion(uint16_t bcd); + + //------------- Configuration descriptor -------------// + + // Add a new interface + bool addInterface(Adafruit_USBD_Interface &itf); + + // Clear/Reset configuration descriptor + void clearConfiguration(void); + + // Set configuration attribute + void setConfigurationAttribute(uint8_t attribute) { + _desc_cfg[offsetof(tusb_desc_configuration_t, bmAttributes)] = attribute; + } + + // Set max power consumption in mA (absolute max is 510ma) + bool setConfigurationMaxPower(uint16_t power_ma) { + if (power_ma > 255 * 2u) { + return false; + } + _desc_cfg[offsetof(tusb_desc_configuration_t, bMaxPower)] = + (uint8_t)(power_ma / 2); + return true; + } + + // Provide user buffer for configuration descriptor, if total length > 256 + void setConfigurationBuffer(uint8_t *buf, uint32_t buflen); + + // Allocate a new interface number + uint8_t allocInterface(uint8_t count = 1) { + uint8_t ret = _itf_count; + _itf_count += count; + return ret; + } + + uint8_t allocEndpoint(uint8_t in) { + uint8_t ret = in ? (0x80 | _epin_count++) : _epout_count++; +#if defined(ARDUINO_ARCH_ESP32) && ARDUINO_USB_CDC_ON_BOOT && !ARDUINO_USB_MODE + // ESP32 reserves 0x03, 0x84, 0x85 for CDC Serial + if (ret == 0x03) { + ret = _epout_count++; + } else if (ret == 0x84 || ret == 0x85) { + // Note: ESP32 does not have this much of EP IN + _epin_count = 6; + ret = 0x86; + } +#endif + return ret; + } + + //------------- String descriptor -------------// + void setLanguageDescriptor(uint16_t language_id); + void setManufacturerDescriptor(const char *s); + void setProductDescriptor(const char *s); + void setSerialDescriptor(const char *s); + uint8_t getSerialDescriptor(uint16_t *serial_utf16); + + uint8_t addStringDescriptor(const char *s); + + //------------- Control -------------// + + bool begin(uint8_t rhport = 0); + bool isInitialized(uint8_t rhport = 0); + void task(void); + + // physical disable/enable pull-up + bool detach(void) { return tud_disconnect(); } + bool attach(void) { return tud_connect(); } + + //------------- status -------------// + bool mounted(void) { return tud_mounted(); } + bool suspended(void) { return tud_suspended(); } + bool ready(void) { return tud_ready(); } + bool remoteWakeup(void) { return tud_remote_wakeup(); } + tusb_speed_t getSpeed(void) { return tud_speed_get(); } + +private: + uint16_t const *descriptor_string_cb(uint8_t index, uint16_t langid); + + friend uint8_t const *tud_descriptor_device_cb(void); + friend uint8_t const *tud_descriptor_configuration_cb(uint8_t index); + friend uint16_t const *tud_descriptor_string_cb(uint8_t index, + uint16_t langid); +}; + +extern Adafruit_USBD_Device TinyUSBDevice; + +// USBDevice has a high chance to conflict with other usb stack +// only define if supported BSP +#ifdef USE_TINYUSB +#define USBDevice TinyUSBDevice +#endif + +#endif /* CFG_TUD_ENABLED */ +#endif /* ADAFRUIT_USBD_DEVICE_H_ */ \ No newline at end of file diff --git a/libraries/Adafruit_TinyUSB_Arduino/src/arduino/Adafruit_USBD_Interface.cpp b/libraries/Adafruit_TinyUSB_Arduino/src/arduino/Adafruit_USBD_Interface.cpp new file mode 100644 index 0000000..eabc757 --- /dev/null +++ b/libraries/Adafruit_TinyUSB_Arduino/src/arduino/Adafruit_USBD_Interface.cpp @@ -0,0 +1,35 @@ +/* + * The MIT License (MIT) + * + * Copyright (c) 2021 Ha Thach (tinyusb.org) for Adafruit Industries + * + * 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_option.h" + +#if CFG_TUD_ENABLED + +#include "Adafruit_USBD_Device.h" + +void Adafruit_USBD_Interface::setStringDescriptor(const char *str) { + _strid = TinyUSBDevice.addStringDescriptor(str); +} + +#endif \ No newline at end of file diff --git a/libraries/Adafruit_TinyUSB_Arduino/src/arduino/Adafruit_USBD_Interface.h b/libraries/Adafruit_TinyUSB_Arduino/src/arduino/Adafruit_USBD_Interface.h new file mode 100644 index 0000000..8457f69 --- /dev/null +++ b/libraries/Adafruit_TinyUSB_Arduino/src/arduino/Adafruit_USBD_Interface.h @@ -0,0 +1,56 @@ +/* + * The MIT License (MIT) + * + * Copyright (c) 2021 Ha Thach (tinyusb.org) for Adafruit Industries + * + * 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 ADAFRUIT_USBD_INTERFACE_H_ +#define ADAFRUIT_USBD_INTERFACE_H_ + +#include +#include + +#if defined(ARDUINO_ARCH_CH32) || defined(CH32V20x) || defined(CH32V30x) +// HACK: required for ch32 core version 1.0.4 or prior, removed when 1.0.5 is +// released +extern "C" void yield(void); +#endif + +class Adafruit_USBD_Interface { +protected: + uint8_t _strid; + +public: + Adafruit_USBD_Interface(void) { _strid = 0; } + + // Get Interface Descriptor + // Fill the descriptor (if buf is not NULL) and return its length + virtual uint16_t getInterfaceDescriptor(uint8_t itfnum_deprecated, + uint8_t *buf, uint16_t bufsize) = 0; + // Get Interface Descriptor Length + uint16_t getInterfaceDescriptorLen() { + return getInterfaceDescriptor(0, NULL, 0); + } + + void setStringDescriptor(const char *str); +}; + +#endif diff --git a/libraries/Adafruit_TinyUSB_Arduino/src/arduino/Adafruit_USBH_Host.cpp b/libraries/Adafruit_TinyUSB_Arduino/src/arduino/Adafruit_USBH_Host.cpp new file mode 100644 index 0000000..cdba773 --- /dev/null +++ b/libraries/Adafruit_TinyUSB_Arduino/src/arduino/Adafruit_USBH_Host.cpp @@ -0,0 +1,323 @@ +/* + * The MIT License (MIT) + * + * Copyright (c) 2022, Ha Thach (tinyusb.org) for Adafruit + * + * 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. + */ + +#ifdef ARDUINO_ARCH_ESP32 +#include "arduino/ports/esp32/tusb_config_esp32.h" +#include "driver/gpio.h" +#include +#endif + +#include "tusb_option.h" + +#if CFG_TUH_ENABLED + +#include "Adafruit_TinyUSB_API.h" +#include "Adafruit_USBH_Host.h" + +Adafruit_USBH_Host *Adafruit_USBH_Host::_instance = NULL; + +Adafruit_USBH_Host::Adafruit_USBH_Host(void) { + Adafruit_USBH_Host::_instance = this; + _rhport = 0; +#if defined(CFG_TUH_MAX3421) && CFG_TUH_MAX3421 + _spi = NULL; + _cs = _intr = _sck = _mosi = _miso = -1; +#endif +} + +#if defined(CFG_TUH_MAX3421) && CFG_TUH_MAX3421 + +// API to read MAX3421's register. Implemented by TinyUSB +extern "C" uint8_t tuh_max3421_reg_read(uint8_t rhport, uint8_t reg, + bool in_isr); + +// API to write MAX3421's register. Implemented by TinyUSB +extern "C" bool tuh_max3421_reg_write(uint8_t rhport, uint8_t reg, uint8_t data, + bool in_isr); + +static void max3421_isr(void); + +#if defined(ARDUINO_ARCH_ESP32) +SemaphoreHandle_t max3421_intr_sem; +static void max3421_intr_task(void *param); +#endif + +Adafruit_USBH_Host::Adafruit_USBH_Host(SPIClass *spi, int8_t cs, int8_t intr) { + Adafruit_USBH_Host::_instance = this; + _rhport = 0; + _spi = spi; + _cs = cs; + _intr = intr; + _sck = _mosi = _miso = -1; +} + +Adafruit_USBH_Host::Adafruit_USBH_Host(SPIClass *spi, int8_t sck, int8_t mosi, + int8_t miso, int8_t cs, int8_t intr) { + Adafruit_USBH_Host::_instance = this; + _rhport = 0; + _spi = spi; + _cs = cs; + _intr = intr; + _sck = sck; + _mosi = mosi; + _miso = miso; +} + +uint8_t Adafruit_USBH_Host::max3421_readRegister(uint8_t reg, bool in_isr) { + return tuh_max3421_reg_read(_rhport, reg, in_isr); +} + +bool Adafruit_USBH_Host::max3421_writeRegister(uint8_t reg, uint8_t data, + bool in_isr) { + return tuh_max3421_reg_write(_rhport, reg, data, in_isr); +} + +#endif + +bool Adafruit_USBH_Host::configure(uint8_t rhport, uint32_t cfg_id, + const void *cfg_param) { + return tuh_configure(rhport, cfg_id, cfg_param); +} + +#ifdef ARDUINO_ARCH_RP2040 +bool Adafruit_USBH_Host::configure_pio_usb(uint8_t rhport, + const void *cfg_param) { + return configure(rhport, TUH_CFGID_RPI_PIO_USB_CONFIGURATION, cfg_param); +} +#endif + +bool Adafruit_USBH_Host::begin(uint8_t rhport) { +#if defined(CFG_TUH_MAX3421) && CFG_TUH_MAX3421 + if (_intr < 0 || _cs < 0 || !_spi) { + return false; + } + + // CS pin + pinMode(_cs, OUTPUT); + digitalWrite(_cs, HIGH); + + // SPI init + // Software SPI is not supported yet +#ifdef ARDUINO_ARCH_ESP32 + // ESP32 SPI assign pins when begin() of declaration as standard API + _spi->begin(_sck, _miso, _mosi, -1); + + // Create an task for executing interrupt handler in thread mode + max3421_intr_sem = xSemaphoreCreateBinary(); + xTaskCreateUniversal(max3421_intr_task, "max3421 intr", 2048, NULL, 5, NULL, + ARDUINO_RUNNING_CORE); +#else + _spi->begin(); +#endif + + // Interrupt pin + pinMode(_intr, INPUT_PULLUP); + attachInterrupt(_intr, max3421_isr, FALLING); +#endif + + _rhport = rhport; + const tusb_rhport_init_t rh_init = { + .role = TUSB_ROLE_HOST, + .speed = TUH_OPT_HIGH_SPEED ? TUSB_SPEED_HIGH : TUSB_SPEED_FULL, + }; + return tusb_init(rhport, &rh_init); +} + +void Adafruit_USBH_Host::task(uint32_t timeout_ms, bool in_isr) { + tuh_task_ext(timeout_ms, in_isr); +} + +// Invoked when received report from device via interrupt endpoint +TU_ATTR_WEAK void tuh_hid_report_received_cb(uint8_t dev_addr, uint8_t instance, + uint8_t const *report, + uint16_t len) { + (void)dev_addr; + (void)instance; + (void)report; + (void)len; +} + +//--------------------------------------------------------------------+ +// USB Host using MAX3421E +//--------------------------------------------------------------------+ +#if CFG_TUH_ENABLED && defined(CFG_TUH_MAX3421) && CFG_TUH_MAX3421 + +#if defined(ARDUINO_ARCH_ESP32) + +// ESP32 out-of-sync +#if ESP_ARDUINO_VERSION < ESP_ARDUINO_VERSION_VAL(2, 0, 14) && \ + !defined(PLATFORMIO) +extern "C" void hcd_int_handler_esp32(uint8_t rhport, bool in_isr); +#define tuh_int_handler_esp32 hcd_int_handler_esp32 + +#else +#define tuh_int_handler_esp32 tuh_int_handler + +#endif + +static void max3421_intr_task(void *param) { + (void)param; + + while (1) { + xSemaphoreTake(max3421_intr_sem, portMAX_DELAY); + tuh_int_handler_esp32(1, false); + } +} + +static void max3421_isr(void) { + BaseType_t xHigherPriorityTaskWoken = pdFALSE; + xSemaphoreGiveFromISR(max3421_intr_sem, &xHigherPriorityTaskWoken); + if (xHigherPriorityTaskWoken) { + portYIELD_FROM_ISR(); + } +} + +#else + +static void max3421_isr(void) { tuh_int_handler(1, true); } + +#endif // ESP32 + +extern "C" { + +void tuh_max3421_spi_cs_api(uint8_t rhport, bool active) { + (void)rhport; + + if (!Adafruit_USBH_Host::_instance) { + return; + } + Adafruit_USBH_Host *host = Adafruit_USBH_Host::_instance; + SPIClass *spi = host->_spi; + + if (active) { + // MAX3421e max clock is 26MHz + // Depending on mcu ports, it may need to be clipped down +#ifdef ARDUINO_ARCH_SAMD + // SAMD 21/51 can only work reliably at 12MHz + uint32_t const max_clock = 12000000ul; +#else + uint32_t const max_clock = 26000000ul; +#endif + + spi->beginTransaction(SPISettings(max_clock, MSBFIRST, SPI_MODE0)); + digitalWrite(Adafruit_USBH_Host::_instance->_cs, LOW); + } else { + spi->endTransaction(); + digitalWrite(Adafruit_USBH_Host::_instance->_cs, HIGH); + } +} + +bool tuh_max3421_spi_xfer_api(uint8_t rhport, uint8_t const *tx_buf, + uint8_t *rx_buf, size_t xfer_bytes) { + (void)rhport; + + if (!Adafruit_USBH_Host::_instance) { + return false; + } + Adafruit_USBH_Host *host = Adafruit_USBH_Host::_instance; + SPIClass *spi = host->_spi; + +#ifdef ARDUINO_ARCH_SAMD + // SAMD cannot use transfer(tx_buf, rx_buf, len) API since it default to use + // DMA. However, since this can be invoked within EIC_Handler (ISR) which may + // have priority higher than DMA ISR. That will cause blocking wait for + // dma_busy not working + for (size_t count = 0; count < xfer_bytes; count++) { + uint8_t data = 0x00; + if (tx_buf) { + data = tx_buf[count]; + } + data = spi->transfer(data); + + if (rx_buf) { + rx_buf[count] = data; + } + } +#elif defined(ARDUINO_ARCH_ESP32) + spi->transferBytes(tx_buf, rx_buf, xfer_bytes); +#else + spi->transfer(tx_buf, rx_buf, xfer_bytes); +#endif + + return true; +} + +void tuh_max3421_int_api(uint8_t rhport, bool enabled) { + (void)rhport; + + if (!Adafruit_USBH_Host::_instance) { + return; + } + Adafruit_USBH_Host *host = Adafruit_USBH_Host::_instance; + (void)host; + +#ifdef ARDUINO_ARCH_SAMD + //--- SAMD51 ---// +#ifdef __SAMD51__ + const IRQn_Type irq = + (IRQn_Type)(EIC_0_IRQn + g_APinDescription[host->_intr].ulExtInt); + + if (enabled) { + NVIC_EnableIRQ(irq); + } else { + NVIC_DisableIRQ(irq); + } +#else + //--- SAMD21 ---// + if (enabled) { + NVIC_EnableIRQ(EIC_IRQn); + } else { + NVIC_DisableIRQ(EIC_IRQn); + } +#endif + +#elif defined(ARDUINO_NRF52_ADAFRUIT) + //--- nRF52 ---// + if (enabled) { + NVIC_EnableIRQ(GPIOTE_IRQn); + } else { + NVIC_DisableIRQ(GPIOTE_IRQn); + } + +#elif defined(ARDUINO_ARCH_ESP32) + //--- ESP32 ---// + if (enabled) { + gpio_intr_enable((gpio_num_t)host->_intr); + } else { + gpio_intr_disable((gpio_num_t)host->_intr); + } + +#elif defined(ARDUINO_ARCH_RP2040) + //--- RP2040 ---// + irq_set_enabled(IO_IRQ_BANK0, enabled); + +#else +#error "MAX3421e host is not supported by this architecture" +#endif +} + +} // extern C +#endif + +#endif diff --git a/libraries/Adafruit_TinyUSB_Arduino/src/arduino/Adafruit_USBH_Host.h b/libraries/Adafruit_TinyUSB_Arduino/src/arduino/Adafruit_USBH_Host.h new file mode 100644 index 0000000..8daa8a7 --- /dev/null +++ b/libraries/Adafruit_TinyUSB_Arduino/src/arduino/Adafruit_USBH_Host.h @@ -0,0 +1,100 @@ +/* + * The MIT License (MIT) + * + * Copyright (c) 2019 Ha Thach for Adafruit Industries + * + * 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 ADAFRUIT_USBH_HOST_H_ +#define ADAFRUIT_USBH_HOST_H_ + +#include "Adafruit_USBD_Interface.h" +#include "tusb.h" +#include + +#ifdef ARDUINO_ARCH_ESP32 +#include "esp32-hal-tinyusb.h" +#endif + +#if defined(CFG_TUH_MAX3421) && CFG_TUH_MAX3421 +extern "C" { +void tuh_max3421_spi_cs_api(uint8_t rhport, bool active); +bool tuh_max3421_spi_xfer_api(uint8_t rhport, uint8_t const *tx_buf, + uint8_t *rx_buf, size_t xfer_bytes); +void tuh_max3421_int_api(uint8_t rhport, bool enabled); +} + +class Adafruit_USBH_Host { +private: + SPIClass *_spi; + int8_t _cs; + int8_t _intr; + + // for esp32 or using softwareSPI + int8_t _sck, _mosi, _miso; + +public: + // constructor for using MAX3421E (host shield) + Adafruit_USBH_Host(SPIClass *spi, int8_t cs, int8_t intr); + Adafruit_USBH_Host(SPIClass *spi, int8_t sck, int8_t mosi, int8_t miso, + int8_t cs, int8_t intr); + + uint8_t max3421_readRegister(uint8_t reg, bool in_isr); + bool max3421_writeRegister(uint8_t reg, uint8_t data, bool in_isr); + bool max3421_writeIOPINS1(uint8_t data, bool in_isr) { + enum { IOPINS1_ADDR = 20u << 3 }; // 0xA0 + return max3421_writeRegister(IOPINS1_ADDR, data, in_isr); + } + bool max3421_writeIOPINS2(uint8_t data, bool in_isr) { + enum { IOPINS2_ADDR = 21u << 3 }; // 0xA8 + return max3421_writeRegister(IOPINS2_ADDR, data, in_isr); + } + +private: + friend void tuh_max3421_spi_cs_api(uint8_t rhport, bool active); + friend bool tuh_max3421_spi_xfer_api(uint8_t rhport, uint8_t const *tx_buf, + uint8_t *rx_buf, size_t xfer_bytes); + friend void tuh_max3421_int_api(uint8_t rhport, bool enabled); +#else + +class Adafruit_USBH_Host { +#endif + +public: + // default constructor + Adafruit_USBH_Host(void); + + bool configure(uint8_t rhport, uint32_t cfg_id, const void *cfg_param); + +#ifdef ARDUINO_ARCH_RP2040 + bool configure_pio_usb(uint8_t rhport, const void *cfg_param); +#endif + + bool begin(uint8_t rhport); + void task(uint32_t timeout_ms = UINT32_MAX, bool in_isr = false); + + //------------- internal usage -------------// + static Adafruit_USBH_Host *_instance; + +private: + uint8_t _rhport; +}; + +#endif diff --git a/libraries/Adafruit_TinyUSB_Arduino/src/arduino/cdc/Adafruit_USBH_CDC.cpp b/libraries/Adafruit_TinyUSB_Arduino/src/arduino/cdc/Adafruit_USBH_CDC.cpp new file mode 100644 index 0000000..1fcafb2 --- /dev/null +++ b/libraries/Adafruit_TinyUSB_Arduino/src/arduino/cdc/Adafruit_USBH_CDC.cpp @@ -0,0 +1,178 @@ +/* + * The MIT License (MIT) + * + * Copyright (c) 2022 Ha Thach (tinyusb.org) for Adafruit Industries + * + * 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. + */ + +// ESP32 out-of-sync +#ifdef ARDUINO_ARCH_ESP32 +#include "arduino/ports/esp32/tusb_config_esp32.h" +#endif + +#include "tusb_option.h" + +#if CFG_TUH_ENABLED && CFG_TUH_MSC + +#include "tusb.h" + +#include "Adafruit_USBH_CDC.h" + +Adafruit_USBH_CDC::Adafruit_USBH_CDC(void) +#ifdef ARDUINO_ARCH_ESP32 + : HardwareSerial(0) +#endif +{ + _idx = TUSB_INDEX_INVALID_8; +} + +void Adafruit_USBH_CDC::begin(unsigned long baudrate) { + // default to index 0 when begin + if (_idx == TUSB_INDEX_INVALID_8) { + _idx = 0; + } + + _baud = baudrate; + if (_baud == 0) { + _baud = 115200; // default, backward compatible with previous API begin(0) + } + + // if already mounted, this will set baudrate + if (mounted()) { + setBaudrate(_baud); + } +} + +void Adafruit_USBH_CDC::begin(unsigned long baudrate, uint16_t config) { + (void)config; // TODO support line coding later + begin(baudrate); +} + +void Adafruit_USBH_CDC::end(void) { _idx = TUSB_INDEX_INVALID_8; } + +bool Adafruit_USBH_CDC::mount(uint8_t idx) { + _idx = idx; + + uint32_t local_baud = baud(); + if (local_baud != _baud) { + return setBaudrate(_baud); + } + + return true; +} + +void Adafruit_USBH_CDC::umount(uint8_t idx) { + if (_idx == idx) { + _idx = TUSB_INDEX_INVALID_8; + } +} + +bool Adafruit_USBH_CDC::connected(void) { return tuh_cdc_connected(_idx); } + +bool Adafruit_USBH_CDC::mounted(void) { return tuh_cdc_mounted(_idx); } +uint32_t Adafruit_USBH_CDC::baud() { + cdc_line_coding_t line_coding; + if (!tuh_cdc_get_local_line_coding(_idx, &line_coding)) { + return 0; + } + return line_coding.bit_rate; +} + +//--------------------------------------------------------------------+ +// Control API +//--------------------------------------------------------------------+ + +bool Adafruit_USBH_CDC::setDtrRts(bool dtr, bool rts, tuh_xfer_cb_t complete_cb, + uintptr_t user_data) { + if (!tuh_cdc_mounted(_idx)) { + return false; + } + + uint16_t const line_state = (dtr ? CDC_CONTROL_LINE_STATE_DTR : 0) | + (rts ? CDC_CONTROL_LINE_STATE_RTS : 0); + + return tuh_cdc_set_control_line_state(_idx, line_state, complete_cb, + user_data); +} + +bool Adafruit_USBH_CDC::setBaudrate(uint32_t baudrate, + tuh_xfer_cb_t complete_cb, + uintptr_t user_data) { + if (!tuh_cdc_mounted(_idx)) { + return false; + } + + if (baud() == baudrate) { + // skip if already matched + return true; + } + + return tuh_cdc_set_baudrate(_idx, baudrate, complete_cb, user_data); +} + +//--------------------------------------------------------------------+ +// Stream API +//--------------------------------------------------------------------+ + +int Adafruit_USBH_CDC::available(void) { + return (int)tuh_cdc_read_available(_idx); +} + +int Adafruit_USBH_CDC::peek(void) { + uint8_t ch; + return tuh_cdc_peek(_idx, &ch) ? (int)ch : -1; +} + +int Adafruit_USBH_CDC::read(void) { + uint8_t ch; + return read(&ch, 1) ? (int)ch : -1; +} + +size_t Adafruit_USBH_CDC::read(uint8_t *buffer, size_t size) { + return tuh_cdc_read(_idx, buffer, size); +} + +void Adafruit_USBH_CDC::flush(void) { (void)tuh_cdc_write_flush(_idx); } + +size_t Adafruit_USBH_CDC::write(uint8_t ch) { return write(&ch, 1); } + +size_t Adafruit_USBH_CDC::write(const uint8_t *buffer, size_t size) { + size_t remain = size; + while (remain && tuh_cdc_mounted(_idx)) { + size_t wrcount = tuh_cdc_write(_idx, buffer, remain); + remain -= wrcount; + buffer += wrcount; + + // Write FIFO is full, run host task while wait for space become available + if (remain) { + tuh_task(); + } + } + + return size - remain; + + return tuh_cdc_write(_idx, buffer, size); +} + +int Adafruit_USBH_CDC::availableForWrite(void) { + return (int)tuh_cdc_write_available(_idx); +} + +#endif diff --git a/libraries/Adafruit_TinyUSB_Arduino/src/arduino/cdc/Adafruit_USBH_CDC.h b/libraries/Adafruit_TinyUSB_Arduino/src/arduino/cdc/Adafruit_USBH_CDC.h new file mode 100644 index 0000000..1f7d593 --- /dev/null +++ b/libraries/Adafruit_TinyUSB_Arduino/src/arduino/cdc/Adafruit_USBH_CDC.h @@ -0,0 +1,86 @@ +/* + * The MIT License (MIT) + * + * Copyright (c) 2022 Ha Thach (tinyusb.org) for Adafruit Industries + * + * 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 ADAFRUIT_USBH_CDC_H_ +#define ADAFRUIT_USBH_CDC_H_ + +#include "HardwareSerial.h" + +class Adafruit_USBH_CDC : public HardwareSerial { +public: + Adafruit_USBH_CDC(void); + + // Set/Get index of cdc interface + void setInterfaceIndex(uint8_t idx) { _idx = idx; } + uint8_t getInterfaceIndex(void) { return _idx; } + + void begin(unsigned long baudrate); + void begin(unsigned long baudrate, uint16_t config); + + bool mount(uint8_t idx); + void umount(uint8_t idx); + + // unbind cdc interface + void end(void); + + // If cdc is mounted + bool mounted(void); + operator bool() { return mounted(); } + + // if cdc's DTR is asserted + bool connected(void); + + // Line encoding + uint32_t baud(); + + //------------- Control API -------------// + bool setDtrRts(bool dtr, bool rts, tuh_xfer_cb_t complete_cb = NULL, + uintptr_t user_data = 0); + bool setBaudrate(uint32_t baudrate, tuh_xfer_cb_t complete_cb = NULL, + uintptr_t user_data = 0); + + //------------- Stream API -------------// + virtual int available(void); + virtual int peek(void); + + virtual int read(void); + size_t read(uint8_t *buffer, size_t size); + + virtual void flush(void); + virtual size_t write(uint8_t ch); + + virtual size_t write(const uint8_t *buffer, size_t size); + size_t write(const char *buffer, size_t size) { + return write((const uint8_t *)buffer, size); + } + + virtual int availableForWrite(void); + using Print::write; // pull in write(str) from Print + +private: + uint8_t _idx; // TinyUSB CDC Interface Index + uint32_t _baud; +}; + +#endif diff --git a/libraries/Adafruit_TinyUSB_Arduino/src/arduino/hid/Adafruit_USBD_HID.cpp b/libraries/Adafruit_TinyUSB_Arduino/src/arduino/hid/Adafruit_USBD_HID.cpp new file mode 100644 index 0000000..0bacb95 --- /dev/null +++ b/libraries/Adafruit_TinyUSB_Arduino/src/arduino/hid/Adafruit_USBD_HID.cpp @@ -0,0 +1,288 @@ +/* + * The MIT License (MIT) + * + * Copyright (c) 2019 Ha Thach for Adafruit Industries + * + * 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_option.h" + +#if CFG_TUD_ENABLED && CFG_TUD_HID + +#include "Adafruit_USBD_HID.h" + +uint8_t const _ascii2keycode[128][2] = {HID_ASCII_TO_KEYCODE}; +static Adafruit_USBD_HID *_hid_instances[CFG_TUD_HID] = {0}; + +uint8_t Adafruit_USBD_HID::_instance_count = 0; + +//------------- IMPLEMENTATION -------------// +Adafruit_USBD_HID::Adafruit_USBD_HID(void) + : Adafruit_USBD_HID(NULL, 0, HID_ITF_PROTOCOL_NONE, 4, false) {} + +Adafruit_USBD_HID::Adafruit_USBD_HID(uint8_t const *desc_report, uint16_t len, + uint8_t protocol, uint8_t interval_ms, + bool has_out_endpoint) { + _instance = INVALID_INSTANCE; + _interval_ms = interval_ms; + _protocol = protocol; + + _out_endpoint = has_out_endpoint; + _mouse_button = 0; + + _desc_report = desc_report; + _desc_report_len = len; + + _get_report_cb = NULL; + _set_report_cb = NULL; +} + +void Adafruit_USBD_HID::setPollInterval(uint8_t interval_ms) { + _interval_ms = interval_ms; +} + +void Adafruit_USBD_HID::setBootProtocol(uint8_t protocol) { + _protocol = protocol; +} + +bool Adafruit_USBD_HID::isOutEndpointEnabled(void) { return _out_endpoint; } + +void Adafruit_USBD_HID::enableOutEndpoint(bool enable) { + _out_endpoint = enable; +} + +void Adafruit_USBD_HID::setReportDescriptor(uint8_t const *desc_report, + uint16_t len) { + _desc_report = desc_report; + _desc_report_len = len; +} + +void Adafruit_USBD_HID::setReportCallback(get_report_callback_t get_report, + set_report_callback_t set_report) { + _get_report_cb = get_report; + _set_report_cb = set_report; +} + +uint16_t Adafruit_USBD_HID::makeItfDesc(uint8_t itfnum, uint8_t *buf, + uint16_t bufsize, uint8_t ep_in, + uint8_t ep_out) { + if (!_desc_report_len) { + return 0; + } + + uint8_t const desc_inout[] = {TUD_HID_INOUT_DESCRIPTOR( + itfnum, _strid, _protocol, _desc_report_len, ep_in, ep_out, + CFG_TUD_HID_EP_BUFSIZE, _interval_ms)}; + uint8_t const desc_in_only[] = { + TUD_HID_DESCRIPTOR(itfnum, _strid, _protocol, _desc_report_len, ep_in, + CFG_TUD_HID_EP_BUFSIZE, _interval_ms)}; + + uint8_t const *desc; + uint16_t len; + + if (_out_endpoint) { + desc = desc_inout; + len = sizeof(desc_inout); + } else { + desc = desc_in_only; + len = sizeof(desc_in_only); + } + + // null buffer is used to get the length of descriptor only + if (buf) { + if (bufsize < len) { + return 0; + } + memcpy(buf, desc, len); + } + + return len; +} + +uint16_t Adafruit_USBD_HID::getInterfaceDescriptor(uint8_t itfnum_deprecated, + uint8_t *buf, + uint16_t bufsize) { + (void)itfnum_deprecated; + + uint8_t itfnum = 0; + uint8_t ep_in = 0; + uint8_t ep_out = 0; + + // null buffer is used to get the length of descriptor only + if (buf) { + itfnum = TinyUSBDevice.allocInterface(1); + ep_in = TinyUSBDevice.allocEndpoint(TUSB_DIR_IN); + + if (_out_endpoint) { + ep_out = TinyUSBDevice.allocEndpoint(TUSB_DIR_OUT); + } + } + + return makeItfDesc(itfnum, buf, bufsize, ep_in, ep_out); +} + +bool Adafruit_USBD_HID::begin(void) { + if (isValid()) { + return true; + } + + if (_instance_count >= CFG_TUD_HID) { + return false; + } + + if (!TinyUSBDevice.addInterface(*this)) { + return false; + } + _instance = _instance_count++; + _hid_instances[_instance] = this; + + return true; +} + +bool Adafruit_USBD_HID::ready(void) { return tud_hid_n_ready(_instance); } + +bool Adafruit_USBD_HID::sendReport(uint8_t report_id, void const *report, + uint8_t len) { + return tud_hid_n_report(_instance, report_id, report, len); +} + +bool Adafruit_USBD_HID::sendReport8(uint8_t report_id, uint8_t num) { + return tud_hid_n_report(_instance, report_id, &num, sizeof(num)); +} + +bool Adafruit_USBD_HID::sendReport16(uint8_t report_id, uint16_t num) { + return tud_hid_n_report(_instance, report_id, &num, sizeof(num)); +} + +bool Adafruit_USBD_HID::sendReport32(uint8_t report_id, uint32_t num) { + return tud_hid_n_report(_instance, report_id, &num, sizeof(num)); +} + +uint8_t Adafruit_USBD_HID::getProtocol() { + return tud_hid_n_get_protocol(_instance); +} + +//------------- TinyUSB callbacks -------------// +extern "C" { + +// Invoked when received GET HID REPORT DESCRIPTOR +// Application return pointer to descriptor, whose contents must exist long +// enough for transfer to complete +uint8_t const *tud_hid_descriptor_report_cb(uint8_t itf) { + Adafruit_USBD_HID *p_hid = _hid_instances[itf]; + + if (!p_hid) { + return NULL; + } + + return p_hid->_desc_report; +} + +// Invoked when received GET_REPORT control request +// Application must fill buffer report's content and return its length. +// Return zero will cause the stack to STALL request +uint16_t tud_hid_get_report_cb(uint8_t itf, uint8_t report_id, + hid_report_type_t report_type, uint8_t *buffer, + uint16_t reqlen) { + Adafruit_USBD_HID *p_hid = _hid_instances[itf]; + + if (!(p_hid && p_hid->_get_report_cb)) { + return 0; + } + + return p_hid->_get_report_cb(report_id, report_type, buffer, reqlen); +} + +// Invoked when received SET_REPORT control request or +// received data on OUT endpoint ( Report ID = 0, Type = 0 ) +void tud_hid_set_report_cb(uint8_t itf, uint8_t report_id, + hid_report_type_t report_type, uint8_t const *buffer, + uint16_t bufsize) { + Adafruit_USBD_HID *p_hid = _hid_instances[itf]; + + if (!(p_hid && p_hid->_set_report_cb)) { + return; + } + + p_hid->_set_report_cb(report_id, report_type, buffer, bufsize); +} + +} // extern "C" + +//--------------------------------------------------------------------+ +// Keyboard +//--------------------------------------------------------------------+ + +bool Adafruit_USBD_HID::keyboardReport(uint8_t report_id, uint8_t modifier, + uint8_t keycode[6]) { + return tud_hid_n_keyboard_report(_instance, report_id, modifier, keycode); +} + +bool Adafruit_USBD_HID::keyboardPress(uint8_t report_id, char ch) { + uint8_t keycode[6] = {0}; + uint8_t modifier = 0; + uint8_t uch = (uint8_t)ch; + + if (_ascii2keycode[uch][0]) { + modifier = KEYBOARD_MODIFIER_LEFTSHIFT; + } + keycode[0] = _ascii2keycode[uch][1]; + + return tud_hid_n_keyboard_report(_instance, report_id, modifier, keycode); +} + +bool Adafruit_USBD_HID::keyboardRelease(uint8_t report_id) { + return tud_hid_n_keyboard_report(_instance, report_id, 0, NULL); +} + +//--------------------------------------------------------------------+ +// Mouse +//--------------------------------------------------------------------+ + +bool Adafruit_USBD_HID::mouseReport(uint8_t report_id, uint8_t buttons, + int8_t x, int8_t y, int8_t vertical, + int8_t horizontal) { + // cache mouse button for other API such as move, scroll + _mouse_button = buttons; + + return tud_hid_n_mouse_report(_instance, report_id, buttons, x, y, vertical, + horizontal); +} + +bool Adafruit_USBD_HID::mouseMove(uint8_t report_id, int8_t x, int8_t y) { + return tud_hid_n_mouse_report(_instance, report_id, _mouse_button, x, y, 0, + 0); +} + +bool Adafruit_USBD_HID::mouseScroll(uint8_t report_id, int8_t scroll, + int8_t pan) { + return tud_hid_n_mouse_report(_instance, report_id, _mouse_button, 0, 0, + scroll, pan); +} + +bool Adafruit_USBD_HID::mouseButtonPress(uint8_t report_id, uint8_t buttons) { + return tud_hid_n_mouse_report(_instance, report_id, buttons, 0, 0, 0, 0); +} + +bool Adafruit_USBD_HID::mouseButtonRelease(uint8_t report_id) { + return tud_hid_n_mouse_report(_instance, report_id, 0, 0, 0, 0, 0); +} + +#endif // CFG_TUD_ENABLED diff --git a/libraries/Adafruit_TinyUSB_Arduino/src/arduino/hid/Adafruit_USBD_HID.h b/libraries/Adafruit_TinyUSB_Arduino/src/arduino/hid/Adafruit_USBD_HID.h new file mode 100644 index 0000000..bf6a012 --- /dev/null +++ b/libraries/Adafruit_TinyUSB_Arduino/src/arduino/hid/Adafruit_USBD_HID.h @@ -0,0 +1,116 @@ +/* + * The MIT License (MIT) + * + * Copyright (c) 2019 Ha Thach for Adafruit Industries + * + * 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 ADAFRUIT_USBD_HID_H_ +#define ADAFRUIT_USBD_HID_H_ + +#include "arduino/Adafruit_USBD_Device.h" + +class Adafruit_USBD_HID : public Adafruit_USBD_Interface { +public: + typedef uint16_t (*get_report_callback_t)(uint8_t report_id, + hid_report_type_t report_type, + uint8_t *buffer, uint16_t reqlen); + typedef void (*set_report_callback_t)(uint8_t report_id, + hid_report_type_t report_type, + uint8_t const *buffer, + uint16_t bufsize); + + enum { INVALID_INSTANCE = 0xffu }; + static uint8_t getInstanceCount(void) { return _instance_count; } + + Adafruit_USBD_HID(void); + Adafruit_USBD_HID(uint8_t const *desc_report, uint16_t len, + uint8_t protocol = HID_ITF_PROTOCOL_NONE, + uint8_t interval_ms = 4, bool has_out_endpoint = false); + + void setPollInterval(uint8_t interval_ms); + void setBootProtocol(uint8_t protocol); // 0: None, 1: Keyboard, 2:Mouse + + void enableOutEndpoint(bool enable); + bool isOutEndpointEnabled(void); + + void setReportDescriptor(uint8_t const *desc_report, uint16_t len); + void setReportCallback(get_report_callback_t get_report, + set_report_callback_t set_report); + + bool begin(void); + bool isValid(void) { return _instance != INVALID_INSTANCE; } + + bool ready(void); + bool sendReport(uint8_t report_id, void const *report, uint8_t len); + + uint8_t getProtocol(); + + // Report helpers + bool sendReport8(uint8_t report_id, uint8_t num); + bool sendReport16(uint8_t report_id, uint16_t num); + bool sendReport32(uint8_t report_id, uint32_t num); + + //------------- Keyboard API -------------// + bool keyboardReport(uint8_t report_id, uint8_t modifier, uint8_t keycode[6]); + bool keyboardPress(uint8_t report_id, char ch); + bool keyboardRelease(uint8_t report_id); + + //------------- Mouse API -------------// + bool mouseReport(uint8_t report_id, uint8_t buttons, int8_t x, int8_t y, + int8_t vertical, int8_t horizontal); + bool mouseMove(uint8_t report_id, int8_t x, int8_t y); + bool mouseScroll(uint8_t report_id, int8_t scroll, int8_t pan); + bool mouseButtonPress(uint8_t report_id, uint8_t buttons); + bool mouseButtonRelease(uint8_t report_id); + + // from Adafruit_USBD_Interface + virtual uint16_t getInterfaceDescriptor(uint8_t itfnum_deprecated, + uint8_t *buf, uint16_t bufsize); + + // internal use only + uint16_t makeItfDesc(uint8_t itfnum, uint8_t *buf, uint16_t bufsize, + uint8_t ep_in, uint8_t ep_out); + +private: + static uint8_t _instance_count; + + uint8_t _instance; + uint8_t _interval_ms; + uint8_t _protocol; + bool _out_endpoint; + uint8_t _mouse_button; + + uint16_t _desc_report_len; + uint8_t const *_desc_report; + + get_report_callback_t _get_report_cb; + set_report_callback_t _set_report_cb; + + friend uint16_t tud_hid_get_report_cb(uint8_t itf, uint8_t report_id, + hid_report_type_t report_type, + uint8_t *buffer, uint16_t reqlen); + friend void tud_hid_set_report_cb(uint8_t itf, uint8_t report_id, + hid_report_type_t report_type, + uint8_t const *buffer, uint16_t bufsize); + friend uint8_t const *tud_hid_descriptor_report_cb(uint8_t itf); +}; + +#endif /* ADAFRUIT_USBD_HID_H_ */ diff --git a/libraries/Adafruit_TinyUSB_Arduino/src/arduino/midi/Adafruit_USBD_MIDI.cpp b/libraries/Adafruit_TinyUSB_Arduino/src/arduino/midi/Adafruit_USBD_MIDI.cpp new file mode 100644 index 0000000..ad9729d --- /dev/null +++ b/libraries/Adafruit_TinyUSB_Arduino/src/arduino/midi/Adafruit_USBD_MIDI.cpp @@ -0,0 +1,166 @@ +/* + * The MIT License (MIT) + * + * Copyright (c) 2019 hathach for Adafruit Industries + * + * 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_option.h" + +#if CFG_TUD_ENABLED && CFG_TUD_MIDI + +#include "Adafruit_USBD_MIDI.h" + +//--------------------------------------------------------------------+ +// MACRO TYPEDEF CONSTANT ENUM DECLARATION +//--------------------------------------------------------------------+ +#define BULK_PACKET_SIZE (TUD_OPT_HIGH_SPEED ? 512 : 64) + +// TODO multiple instances +static Adafruit_USBD_MIDI *_midi_dev = NULL; + +Adafruit_USBD_MIDI::Adafruit_USBD_MIDI(uint8_t n_cables) { + _n_cables = n_cables; + memset(_cable_name_strid, 0, sizeof(_cable_name_strid)); +} + +void Adafruit_USBD_MIDI::setCables(uint8_t n_cables) { _n_cables = n_cables; } + +bool Adafruit_USBD_MIDI::setCableName(uint8_t cable_id, const char *str) { + if (cable_id == 0 || cable_id > sizeof(_cable_name_strid)) { + return false; + } + + uint8_t strid = TinyUSBDevice.addStringDescriptor(str); + _cable_name_strid[cable_id - 1] = strid; + + return strid > 0; +} + +bool Adafruit_USBD_MIDI::begin(void) { + if (!TinyUSBDevice.addInterface(*this)) { + return false; + } + + _midi_dev = this; + return true; +} + +uint16_t Adafruit_USBD_MIDI::getInterfaceDescriptor(uint8_t itfnum_deprecated, + uint8_t *buf, + uint16_t bufsize) { + (void)itfnum_deprecated; + + uint16_t const desc_len = TUD_MIDI_DESC_HEAD_LEN + + TUD_MIDI_DESC_JACK_LEN * _n_cables + + 2 * TUD_MIDI_DESC_EP_LEN(_n_cables); + + // null buffer is used to get the length of descriptor only + if (!buf) { + return desc_len; + } + + if (bufsize < desc_len) { + return 0; + } + + uint8_t itfnum = TinyUSBDevice.allocInterface(2); + uint8_t ep_in = TinyUSBDevice.allocEndpoint(TUSB_DIR_IN); + uint8_t ep_out = TinyUSBDevice.allocEndpoint(TUSB_DIR_OUT); + + uint16_t len = 0; + + // Header + { + uint8_t desc[] = {TUD_MIDI_DESC_HEAD(itfnum, _strid, _n_cables)}; + memcpy(buf + len, desc, sizeof(desc)); + len += sizeof(desc); + } + + // Jack + for (uint8_t i = 1; i <= _n_cables; i++) { + uint8_t jack[] = {TUD_MIDI_DESC_JACK_DESC(i, _cable_name_strid[i - 1])}; + memcpy(buf + len, jack, sizeof(jack)); + len += sizeof(jack); + } + + // Endpoint OUT + jack mapping + { + uint8_t desc[] = {TUD_MIDI_DESC_EP(ep_out, BULK_PACKET_SIZE, _n_cables)}; + memcpy(buf + len, desc, sizeof(desc)); + len += sizeof(desc); + } + + for (uint8_t i = 1; i <= _n_cables; i++) { + uint8_t jack[] = {TUD_MIDI_JACKID_IN_EMB(i)}; + memcpy(buf + len, jack, sizeof(jack)); + len += sizeof(jack); + } + + // Endpoint IN + jack mapping + { + uint8_t desc[] = {TUD_MIDI_DESC_EP(ep_in, BULK_PACKET_SIZE, _n_cables)}; + memcpy(buf + len, desc, sizeof(desc)); + len += sizeof(desc); + } + + for (uint8_t i = 1; i <= _n_cables; i++) { + uint8_t jack[] = {TUD_MIDI_JACKID_OUT_EMB(i)}; + memcpy(buf + len, jack, sizeof(jack)); + len += sizeof(jack); + } + + if (len != desc_len) { + // TODO should throw an error message + return 0; + } + + return desc_len; +} + +int Adafruit_USBD_MIDI::read(void) { + uint8_t ch; + return tud_midi_stream_read(&ch, 1) ? (int)ch : (-1); +} + +size_t Adafruit_USBD_MIDI::write(uint8_t b) { + return tud_midi_stream_write(0, &b, 1); +} + +int Adafruit_USBD_MIDI::available(void) { return tud_midi_available(); } + +int Adafruit_USBD_MIDI::peek(void) { + // MIDI Library does not use peek + return -1; +} + +void Adafruit_USBD_MIDI::flush(void) { + // MIDI Library does not use flush +} + +bool Adafruit_USBD_MIDI::writePacket(const uint8_t packet[4]) { + return tud_midi_packet_write(packet); +} + +bool Adafruit_USBD_MIDI::readPacket(uint8_t packet[4]) { + return tud_midi_packet_read(packet); +} + +#endif // CFG_TUD_ENABLED diff --git a/libraries/Adafruit_TinyUSB_Arduino/src/arduino/midi/Adafruit_USBD_MIDI.h b/libraries/Adafruit_TinyUSB_Arduino/src/arduino/midi/Adafruit_USBD_MIDI.h new file mode 100644 index 0000000..0cce303 --- /dev/null +++ b/libraries/Adafruit_TinyUSB_Arduino/src/arduino/midi/Adafruit_USBD_MIDI.h @@ -0,0 +1,71 @@ +/* + * The MIT License (MIT) + * + * Copyright (c) 2019 hathach for Adafruit Industries + * + * 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 ADAFRUIT_USBD_MIDI_H_ +#define ADAFRUIT_USBD_MIDI_H_ + +#include "Stream.h" +#include "arduino/Adafruit_USBD_Device.h" + +class Adafruit_USBD_MIDI : public Stream, public Adafruit_USBD_Interface { +public: + Adafruit_USBD_MIDI(uint8_t n_cables = 1); + + void setCables(uint8_t n_cables); + + // Set the cable number with a USB device descriptor string + // Note: per MIDI specs cable_id (or jackid/elementid) starting from 1. 0 is + // reserved for undefined ID. + bool setCableName(uint8_t cable_id, const char *str); + bool begin(void); + + // for MIDI library + bool begin(uint32_t baud) { + (void)baud; + return begin(); + } + + // Stream interface to use with MIDI Library + virtual int read(void); + virtual size_t write(uint8_t b); + virtual int available(void); + virtual int peek(void); + virtual void flush(void); + + using Stream::write; + + // Raw MIDI USB packet interface. + bool writePacket(const uint8_t packet[4]); + bool readPacket(uint8_t packet[4]); + + // from Adafruit_USBD_Interface + virtual uint16_t getInterfaceDescriptor(uint8_t itfnum_deprecated, + uint8_t *buf, uint16_t bufsize); + +private: + uint8_t _n_cables; + uint8_t _cable_name_strid[16]; +}; + +#endif /* ADAFRUIT_USBD_MIDI_H_ */ diff --git a/libraries/Adafruit_TinyUSB_Arduino/src/arduino/msc/Adafruit_USBD_MSC.cpp b/libraries/Adafruit_TinyUSB_Arduino/src/arduino/msc/Adafruit_USBD_MSC.cpp new file mode 100644 index 0000000..c23c799 --- /dev/null +++ b/libraries/Adafruit_TinyUSB_Arduino/src/arduino/msc/Adafruit_USBD_MSC.cpp @@ -0,0 +1,279 @@ +/* + * The MIT License (MIT) + * + * Copyright (c) 2019 Ha Thach for Adafruit Industries + * + * 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_option.h" + +#if CFG_TUD_ENABLED && CFG_TUD_MSC + +#include "Adafruit_USBD_MSC.h" + +static Adafruit_USBD_MSC *_msc_dev = NULL; + +Adafruit_USBD_MSC::Adafruit_USBD_MSC(void) { + _maxlun = 1; + memset(_lun_info, 0, sizeof(_lun_info)); +} + +uint16_t Adafruit_USBD_MSC::getInterfaceDescriptor(uint8_t itfnum_deprecated, + uint8_t *buf, + uint16_t bufsize) { + (void)itfnum_deprecated; + + // null buffer is used to get the length of descriptor only + if (!buf) { + return TUD_MSC_DESC_LEN; + } + + uint8_t const itfnum = TinyUSBDevice.allocInterface(1); + uint8_t const ep_in = TinyUSBDevice.allocEndpoint(TUSB_DIR_IN); + uint8_t const ep_out = TinyUSBDevice.allocEndpoint(TUSB_DIR_OUT); + + uint16_t const mps = + (TUD_OPT_HIGH_SPEED ? 512 : 64); // TODO actual link speed + uint8_t const desc[] = { + TUD_MSC_DESCRIPTOR(itfnum, _strid, ep_out, ep_in, mps)}; + uint16_t const len = sizeof(desc); + + if (bufsize < len) { + return 0; + } + memcpy(buf, desc, len); + + return len; +} + +void Adafruit_USBD_MSC::setMaxLun(uint8_t maxlun) { _maxlun = maxlun; } + +uint8_t Adafruit_USBD_MSC::getMaxLun(void) { return _maxlun; } + +void Adafruit_USBD_MSC::setID(uint8_t lun, const char *vendor_id, + const char *product_id, const char *product_rev) { + _lun_info[lun]._inquiry_vid = vendor_id; + _lun_info[lun]._inquiry_pid = product_id; + _lun_info[lun]._inquiry_rev = product_rev; +} + +void Adafruit_USBD_MSC::setCapacity(uint8_t lun, uint32_t block_count, + uint16_t block_size) { + _lun_info[lun].block_count = block_count; + _lun_info[lun].block_size = block_size; +} + +void Adafruit_USBD_MSC::setUnitReady(uint8_t lun, bool ready) { + _lun_info[lun].unit_ready = ready; +} + +void Adafruit_USBD_MSC::setReadWriteCallback(uint8_t lun, read_callback_t rd_cb, + write_callback_t wr_cb, + flush_callback_t fl_cb) { + _lun_info[lun].rd_cb = rd_cb; + _lun_info[lun].wr_cb = wr_cb; + _lun_info[lun].fl_cb = fl_cb; +} + +void Adafruit_USBD_MSC::setStartStopCallback(uint8_t lun, + start_stop_callback_t cb) { + _lun_info[lun].start_stop_cb = cb; +} + +void Adafruit_USBD_MSC::setReadyCallback(uint8_t lun, ready_callback_t cb) { + _lun_info[lun].ready_cb = cb; +} + +void Adafruit_USBD_MSC::setWritableCallback(uint8_t lun, + writable_callback_t cb) { + _lun_info[lun].writable_cb = cb; +} + +bool Adafruit_USBD_MSC::begin(void) { + if (!TinyUSBDevice.addInterface(*this)) { + return false; + } + + _msc_dev = this; + return true; +} + +//------------- TinyUSB callbacks -------------// +extern "C" { + +// Invoked to determine max LUN +uint8_t tud_msc_get_maxlun_cb(void) { + if (!_msc_dev) { + return 0; + } + return _msc_dev->getMaxLun(); +} + +// Invoked when received SCSI_CMD_INQUIRY +// Application fill vendor id, product id and revision with string up to 8, 16, +// 4 characters respectively +void tud_msc_inquiry_cb(uint8_t lun, uint8_t vendor_id[8], + uint8_t product_id[16], uint8_t product_rev[4]) { + if (!_msc_dev) { + return; + } + + // If not set use default ID "Adafruit - Mass Storage - 1.0" + const char *vid = (_msc_dev->_lun_info[lun]._inquiry_vid + ? _msc_dev->_lun_info[lun]._inquiry_vid + : "Adafruit"); + const char *pid = (_msc_dev->_lun_info[lun]._inquiry_pid + ? _msc_dev->_lun_info[lun]._inquiry_pid + : "Mass Storage"); + const char *rev = (_msc_dev->_lun_info[lun]._inquiry_rev + ? _msc_dev->_lun_info[lun]._inquiry_rev + : "1.0"); + + memcpy(vendor_id, vid, tu_min32(strlen(vid), 8)); + memcpy(product_id, pid, tu_min32(strlen(pid), 16)); + memcpy(product_rev, rev, tu_min32(strlen(rev), 4)); +} + +// Invoked when received Test Unit Ready command. +// return true allowing host to read/write this LUN e.g SD card inserted +bool tud_msc_test_unit_ready_cb(uint8_t lun) { + if (!_msc_dev) { + return false; + } + + if (_msc_dev->_lun_info[lun].ready_cb) { + _msc_dev->_lun_info[lun].unit_ready = _msc_dev->_lun_info[lun].ready_cb(); + } + + bool const ret = _msc_dev->_lun_info[lun].unit_ready; + + if (!ret) { + // Addition Sense: 3A-00 is NOT FOUND + tud_msc_set_sense(lun, SCSI_SENSE_NOT_READY, 0x3a, 0x00); + } + + return ret; +} + +// Callback invoked to determine disk's size +void tud_msc_capacity_cb(uint8_t lun, uint32_t *block_count, + uint16_t *block_size) { + if (!_msc_dev) { + return; + } + + *block_count = _msc_dev->_lun_info[lun].block_count; + *block_size = _msc_dev->_lun_info[lun].block_size; +} + +// Callback invoked when received an SCSI command not in built-in list below +// - READ_CAPACITY10, READ_FORMAT_CAPACITY, INQUIRY, MODE_SENSE6, REQUEST_SENSE +// - READ10 and WRITE10 has their own callbacks +int32_t tud_msc_scsi_cb(uint8_t lun, const uint8_t scsi_cmd[16], void *buffer, + uint16_t bufsize) { + const void *response = NULL; + int32_t resplen = 0; + + switch (scsi_cmd[0]) { + + default: + // Set Sense = Invalid Command Operation + tud_msc_set_sense(lun, SCSI_SENSE_ILLEGAL_REQUEST, 0x20, 0x00); + + // negative means error -> tinyusb could stall and/or response with failed + // status + resplen = -1; + break; + } + + // return len must not larger than bufsize + if (resplen > bufsize) { + resplen = bufsize; + } + + // copy response to stack's buffer if any + if (response && (resplen > 0)) { + memcpy(buffer, response, resplen); + } + + return resplen; +} + +// Callback invoked on start/stop +bool tud_msc_start_stop_cb(uint8_t lun, uint8_t power_condition, bool start, + bool load_eject) { + if (!(_msc_dev && _msc_dev->_lun_info[lun].start_stop_cb)) { + return true; + } + + return _msc_dev->_lun_info[lun].start_stop_cb(power_condition, start, + load_eject); +} + +// Callback invoked when received READ10 command. +// Copy disk's data to buffer (up to bufsize) and return number of copied bytes. +int32_t tud_msc_read10_cb(uint8_t lun, uint32_t lba, uint32_t offset, + void *buffer, uint32_t bufsize) { + (void)offset; + + if (!(_msc_dev && _msc_dev->_lun_info[lun].rd_cb)) { + return -1; + } + + return _msc_dev->_lun_info[lun].rd_cb(lba, buffer, bufsize); +} + +// Callback invoked when received WRITE10 command. +// Process data in buffer to disk's storage and return number of written bytes +int32_t tud_msc_write10_cb(uint8_t lun, uint32_t lba, uint32_t offset, + uint8_t *buffer, uint32_t bufsize) { + (void)offset; + + if (!(_msc_dev && _msc_dev->_lun_info[lun].wr_cb)) { + return -1; + } + + return _msc_dev->_lun_info[lun].wr_cb(lba, buffer, bufsize); +} + +// Callback invoked when WRITE10 command is completed (status received and +// accepted by host). used to flush any pending cache. +void tud_msc_write10_complete_cb(uint8_t lun) { + if (!(_msc_dev && _msc_dev->_lun_info[lun].fl_cb)) { + return; + } + + // flush pending cache when write10 is complete + return _msc_dev->_lun_info[lun].fl_cb(); +} + +// Invoked to check if device is writable as part of SCSI WRITE10 +// Default mode is writable +bool tud_msc_is_writable_cb(uint8_t lun) { + if (!(_msc_dev && _msc_dev->_lun_info[lun].writable_cb)) { + return true; + } + + return _msc_dev->_lun_info[lun].writable_cb(); +} + +} // extern "C" + +#endif // CFG_TUD_ENABLED diff --git a/libraries/Adafruit_TinyUSB_Arduino/src/arduino/msc/Adafruit_USBD_MSC.h b/libraries/Adafruit_TinyUSB_Arduino/src/arduino/msc/Adafruit_USBD_MSC.h new file mode 100644 index 0000000..f2c5fc3 --- /dev/null +++ b/libraries/Adafruit_TinyUSB_Arduino/src/arduino/msc/Adafruit_USBD_MSC.h @@ -0,0 +1,128 @@ +/* + * The MIT License (MIT) + * + * Copyright (c) 2019 Ha Thach for Adafruit Industries + * + * 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 ADAFRUIT_USBD_MSC_H_ +#define ADAFRUIT_USBD_MSC_H_ + +#include "arduino/Adafruit_USBD_Device.h" + +class Adafruit_USBD_MSC : public Adafruit_USBD_Interface { +public: + typedef int32_t (*read_callback_t)(uint32_t lba, void *buffer, + uint32_t bufsize); + typedef int32_t (*write_callback_t)(uint32_t lba, uint8_t *buffer, + uint32_t bufsize); + typedef void (*flush_callback_t)(void); + typedef bool (*ready_callback_t)(void); + typedef bool (*writable_callback_t)(void); + typedef bool (*start_stop_callback_t)(uint8_t power_condition, bool start, + bool load_eject); + + Adafruit_USBD_MSC(void); + + bool begin(void); + + void setMaxLun(uint8_t maxlun); + uint8_t getMaxLun(void); + + //------------- Multiple LUN API -------------// + void setID(uint8_t lun, const char *vendor_id, const char *product_id, + const char *product_rev); + void setCapacity(uint8_t lun, uint32_t block_count, uint16_t block_size); + void setUnitReady(uint8_t lun, bool ready); + void setReadWriteCallback(uint8_t lun, read_callback_t rd_cb, + write_callback_t wr_cb, flush_callback_t fl_cb); + void setReadyCallback(uint8_t lun, ready_callback_t cb); + void setWritableCallback(uint8_t lun, writable_callback_t cb); + void setStartStopCallback(uint8_t lun, start_stop_callback_t cb); + + //------------- Single LUN API -------------// + void setID(const char *vendor_id, const char *product_id, + const char *product_rev) { + setID(0, vendor_id, product_id, product_rev); + } + + void setCapacity(uint32_t block_count, uint16_t block_size) { + setCapacity(0, block_count, block_size); + } + + void setUnitReady(bool ready) { setUnitReady(0, ready); } + + void setReadWriteCallback(read_callback_t rd_cb, write_callback_t wr_cb, + flush_callback_t fl_cb) { + setReadWriteCallback(0, rd_cb, wr_cb, fl_cb); + } + + void setReadyCallback(ready_callback_t cb) { setReadyCallback(0, cb); } + void setWritableCallback(writable_callback_t cb) { + setWritableCallback(0, cb); + } + void setStartStopCallback(start_stop_callback_t cb) { + setStartStopCallback(0, cb); + } + + // from Adafruit_USBD_Interface + virtual uint16_t getInterfaceDescriptor(uint8_t itfnum_deprecated, + uint8_t *buf, uint16_t bufsize); + +private: + enum { MAX_LUN = 2 }; // TODO make it configurable + struct { + read_callback_t rd_cb; + write_callback_t wr_cb; + flush_callback_t fl_cb; + ready_callback_t ready_cb; + writable_callback_t writable_cb; + start_stop_callback_t start_stop_cb; + + const char *_inquiry_vid; + const char *_inquiry_pid; + const char *_inquiry_rev; + + uint32_t block_count; + uint16_t block_size; + bool unit_ready; + + } _lun_info[MAX_LUN]; + + uint8_t _maxlun; + + // Make all tinyusb callback friend to access private data + friend void tud_msc_inquiry_cb(uint8_t lun, uint8_t vendor_id[8], + uint8_t product_id[16], + uint8_t product_rev[4]); + friend bool tud_msc_test_unit_ready_cb(uint8_t lun); + friend void tud_msc_capacity_cb(uint8_t lun, uint32_t *block_count, + uint16_t *block_size); + friend int32_t tud_msc_read10_cb(uint8_t lun, uint32_t lba, uint32_t offset, + void *buffer, uint32_t bufsize); + friend int32_t tud_msc_write10_cb(uint8_t lun, uint32_t lba, uint32_t offset, + uint8_t *buffer, uint32_t bufsize); + friend void tud_msc_write10_complete_cb(uint8_t lun); + friend bool tud_msc_is_writable_cb(uint8_t lun); + friend bool tud_msc_start_stop_cb(uint8_t lun, uint8_t power_condition, + bool start, bool load_eject); +}; + +#endif /* ADAFRUIT_USBD_MSC_H_ */ diff --git a/libraries/Adafruit_TinyUSB_Arduino/src/arduino/msc/Adafruit_USBH_MSC.cpp b/libraries/Adafruit_TinyUSB_Arduino/src/arduino/msc/Adafruit_USBH_MSC.cpp new file mode 100644 index 0000000..48f88a8 --- /dev/null +++ b/libraries/Adafruit_TinyUSB_Arduino/src/arduino/msc/Adafruit_USBH_MSC.cpp @@ -0,0 +1,152 @@ +/* + * The MIT License (MIT) + * + * Copyright (c) 2022 Ha Thach (tinyusb.org) for Adafruit Industries + * + * 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. + */ + +// ESP32 out-of-sync +#ifdef ARDUINO_ARCH_ESP32 +#include "arduino/ports/esp32/tusb_config_esp32.h" +#endif + +#include "tusb_option.h" + +#if CFG_TUH_ENABLED && CFG_TUH_MSC + +#include "Adafruit_USBH_MSC.h" +#include "tusb.h" + +#if __has_include("SdFat_Adafruit_Fork.h") + +Adafruit_USBH_MSC_BlockDevice::Adafruit_USBH_MSC_BlockDevice() { + _daddr = _lun = 0; + _busy = false; + _wr_cb = NULL; +} + +bool Adafruit_USBH_MSC_BlockDevice::begin(uint8_t dev_addr) { + _daddr = dev_addr; + return true; +} + +bool Adafruit_USBH_MSC_BlockDevice::setActiveLUN(uint8_t lun) { + _lun = lun; + return true; +} +void Adafruit_USBH_MSC_BlockDevice::setWriteCompleteCallback( + tuh_msc_complete_cb_t cb) { + _wr_cb = cb; +} + +void Adafruit_USBH_MSC_BlockDevice::end(void) { _daddr = _lun = 0; } + +bool Adafruit_USBH_MSC_BlockDevice::mounted(void) { return _daddr > 0; } + +bool Adafruit_USBH_MSC_BlockDevice::isBusy(void) { return _busy; } + +bool Adafruit_USBH_MSC_BlockDevice::wait_for_io(void) { + while (_busy) { + if (tuh_task_event_ready()) { + tuh_task(); + } + } + + return true; +} + +bool Adafruit_USBH_MSC_BlockDevice::_io_complete_cb( + uint8_t dev_addr, tuh_msc_complete_data_t const *cb_data) { + (void)cb_data; + if (dev_addr != _daddr) { + // something wrong occurred, maybe device removed while transferring + return false; + } + + // TODO skip csw status: assuming io is successful + _busy = false; + + switch (cb_data->cbw->command[0]) { + case SCSI_CMD_WRITE_10: + if (_wr_cb) { + _wr_cb(dev_addr, cb_data); + } + break; + } + + return true; +} + +static bool _msc_io_complete_cb(uint8_t dev_addr, + tuh_msc_complete_data_t const *cb_data) { + Adafruit_USBH_MSC_BlockDevice *sdfat_dev = + (Adafruit_USBH_MSC_BlockDevice *)cb_data->user_arg; + sdfat_dev->_io_complete_cb(dev_addr, cb_data); + return true; +} + +uint32_t Adafruit_USBH_MSC_BlockDevice::sectorCount(void) { + return tuh_msc_get_block_count(_daddr, _lun); +} + +bool Adafruit_USBH_MSC_BlockDevice::syncDevice(void) { + // no caching + return true; +} + +bool Adafruit_USBH_MSC_BlockDevice::readSectors(uint32_t block, uint8_t *dst, + size_t ns) { + _busy = true; + if (tuh_msc_read10(_daddr, _lun, dst, block, (uint16_t)ns, + _msc_io_complete_cb, (uintptr_t)this)) { + wait_for_io(); + return true; + } else { + _busy = false; + return false; + } +} + +bool Adafruit_USBH_MSC_BlockDevice::writeSectors(uint32_t block, + const uint8_t *src, + size_t ns) { + _busy = true; + if (tuh_msc_write10(_daddr, _lun, src, block, (uint16_t)ns, + _msc_io_complete_cb, (uintptr_t)this)) { + wait_for_io(); + return true; + } else { + _busy = false; + return false; + } +} + +bool Adafruit_USBH_MSC_BlockDevice::readSector(uint32_t block, uint8_t *dst) { + return readSectors(block, dst, 1); +} + +bool Adafruit_USBH_MSC_BlockDevice::writeSector(uint32_t block, + const uint8_t *src) { + return writeSectors(block, src, 1); +} + +#endif + +#endif diff --git a/libraries/Adafruit_TinyUSB_Arduino/src/arduino/msc/Adafruit_USBH_MSC.h b/libraries/Adafruit_TinyUSB_Arduino/src/arduino/msc/Adafruit_USBH_MSC.h new file mode 100644 index 0000000..d731495 --- /dev/null +++ b/libraries/Adafruit_TinyUSB_Arduino/src/arduino/msc/Adafruit_USBH_MSC.h @@ -0,0 +1,77 @@ +/* + * The MIT License (MIT) + * + * Copyright (c) 2022 Ha Thach (tinyusb.org) for Adafruit Industries + * + * 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 ADAFRUIT_USBH_MSC_H_ +#define ADAFRUIT_USBH_MSC_H_ + +#include "tusb.h" + +// define SdFat host helper class if SdFat library is available +#if __has_include("SdFat_Adafruit_Fork.h") + +#include "SdFat_Adafruit_Fork.h" + +class Adafruit_USBH_MSC_BlockDevice : public FsBlockDeviceInterface { +public: + Adafruit_USBH_MSC_BlockDevice(); + + bool begin(uint8_t dev_addr); + void end(void); + + bool mounted(void); + + // Set active LUN + bool setActiveLUN(uint8_t lun); + void setWriteCompleteCallback(tuh_msc_complete_cb_t cb); + + //------------- SdFat v2 FsBlockDeviceInterface API -------------// + virtual bool isBusy(); + virtual uint32_t sectorCount(); + virtual bool syncDevice(); + + virtual bool readSector(uint32_t block, uint8_t *dst); + virtual bool readSectors(uint32_t block, uint8_t *dst, size_t ns); + virtual bool writeSector(uint32_t block, const uint8_t *src); + virtual bool writeSectors(uint32_t block, const uint8_t *src, size_t ns); + + //------------- Internal APIs -------------// + bool _io_complete_cb(uint8_t dev_addr, + tuh_msc_complete_data_t const *cb_data); + +private: + uint8_t _daddr; + uint8_t _lun; + + // TODO use mutex to prevent race condition or atomic for better + // implementation + volatile bool _busy; + + tuh_msc_complete_cb_t _wr_cb; + + bool wait_for_io(void); +}; + +#endif + +#endif diff --git a/libraries/Adafruit_TinyUSB_Arduino/src/arduino/ports/ch32/Adafruit_TinyUSB_ch32.cpp b/libraries/Adafruit_TinyUSB_Arduino/src/arduino/ports/ch32/Adafruit_TinyUSB_ch32.cpp new file mode 100644 index 0000000..6eaae61 --- /dev/null +++ b/libraries/Adafruit_TinyUSB_Arduino/src/arduino/ports/ch32/Adafruit_TinyUSB_ch32.cpp @@ -0,0 +1,195 @@ +/* + * The MIT License (MIT) + * + * Copyright (c) 2019, hathach for Adafruit + * + * 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_option.h" + +#if CFG_TUD_ENABLED && \ + (defined(ARDUINO_ARCH_CH32) || defined(CH32V20x) || defined(CH32V30x)) + +#include "Arduino.h" +#include "arduino/Adafruit_USBD_Device.h" + +//--------------------------------------------------------------------+ +// MACRO TYPEDEF CONSTANT ENUM DECLARATION +//--------------------------------------------------------------------+ + +//--------------------------------------------------------------------+ +// Forward USB interrupt events to TinyUSB IRQ Handler +//--------------------------------------------------------------------+ +extern "C" { + +// USBD (fsdev) +#if CFG_TUD_WCH_USBIP_FSDEV +__attribute__((interrupt("WCH-Interrupt-fast"))) void +USB_LP_CAN1_RX0_IRQHandler(void) { + tud_int_handler(0); +} + +__attribute__((interrupt("WCH-Interrupt-fast"))) void +USB_HP_CAN1_TX_IRQHandler(void) { + tud_int_handler(0); +} + +__attribute__((interrupt("WCH-Interrupt-fast"))) void +USBWakeUp_IRQHandler(void) { + tud_int_handler(0); +} +#endif + +// USBFS +#if CFG_TUD_WCH_USBIP_USBFS + +#if defined(CH32V10x) || defined(CH32V20x) + +#if defined(CH32V10x) +#define USBHDWakeUp_IRQHandler USBWakeUp_IRQHandler +#endif + +__attribute__((interrupt("WCH-Interrupt-fast"))) void USBHD_IRQHandler(void) { + tud_int_handler(0); +} + +__attribute__((interrupt("WCH-Interrupt-fast"))) void +USBHDWakeUp_IRQHandler(void) { + tud_int_handler(0); +} +#endif + +#ifdef CH32V30x +__attribute__((interrupt("WCH-Interrupt-fast"))) void OTG_FS_IRQHandler(void) { + tud_int_handler(0); +} +#endif + +#endif + +// USBHS +#if CFG_TUD_WCH_USBIP_USBHS +__attribute__((interrupt("WCH-Interrupt-fast"))) void USBHS_IRQHandler(void) { + tud_int_handler(0); +} +#endif + +void yield(void) { + tud_task(); + if (tud_cdc_connected()) { + tud_cdc_write_flush(); + } +} +} + +//--------------------------------------------------------------------+ +// Porting API +//--------------------------------------------------------------------+ + +void TinyUSB_Port_InitDevice(uint8_t rhport) { +#if CFG_TUD_WCH_USBIP_FSDEV || CFG_TUD_WCH_USBIP_USBFS + // Full speed OTG or FSDev + +#if defined(CH32V10x) + EXTEN->EXTEN_CTR |= EXTEN_USBHD_IO_EN; + EXTEN->EXTEN_CTR &= ~EXTEN_USB_5V_SEL; + +#define RCC_AHBPeriph_OTG_FS RCC_AHBPeriph_USBHD +#endif + + uint8_t usb_div; + switch (SystemCoreClock) { +#if defined(CH32V20x) || defined(CH32V30x) + case 48000000: + usb_div = 0; // div1 + break; + case 96000000: + usb_div = 1; // div2 + break; + case 144000000: + usb_div = 2; // div3 + break; +#elif defined(CH32V10x) + case 48000000: + usb_div = RCC_USBCLKSource_PLLCLK_Div1; + break; + case 72000000: + usb_div = RCC_USBCLKSource_PLLCLK_1Div5; + break; +#endif + default: + return; // unsupported + } + +#if defined(CH32V30x) + RCC_OTGFSCLKConfig(usb_div); +#else + RCC_USBCLKConfig(usb_div); +#endif + +#if CFG_TUD_WCH_USBIP_FSDEV + RCC_APB1PeriphClockCmd(RCC_APB1Periph_USB, ENABLE); +#endif + +#if CFG_TUD_WCH_USBIP_USBFS + RCC_AHBPeriphClockCmd(RCC_AHBPeriph_OTG_FS, ENABLE); +#endif +#endif + +#if CFG_TUD_WCH_USBIP_USBHS + // High speed USB: currently require 144MHz HSE, update later + RCC_USBCLK48MConfig(RCC_USBCLK48MCLKSource_USBPHY); + RCC_USBHSPLLCLKConfig(RCC_HSBHSPLLCLKSource_HSE); + RCC_USBHSConfig(RCC_USBPLL_Div2); + RCC_USBHSPLLCKREFCLKConfig(RCC_USBHSPLLCKREFCLK_4M); + RCC_USBHSPHYPLLALIVEcmd(ENABLE); + RCC_AHBPeriphClockCmd(RCC_AHBPeriph_USBHS, ENABLE); +#endif + + const tusb_rhport_init_t rh_init = { + .role = TUSB_ROLE_DEVICE, + .speed = TUD_OPT_HIGH_SPEED ? TUSB_SPEED_HIGH : TUSB_SPEED_FULL, + }; + tusb_init(rhport, &rh_init); +} + +void TinyUSB_Port_EnterDFU(void) { + // Reset to Boot ROM + // Serial1.println("Reset to Boot ROM"); + //__disable_irq(); + // tud_deinit(0); + // + // // define function pointer to BOOT ROM address + // void (*bootloader_entry)(void) = (void (*)(void))0x1FFF8000; + // bootloader_entry(); + // while(1) {} +} + +uint8_t TinyUSB_Port_GetSerialNumber(uint8_t serial_id[16]) { + volatile uint32_t *ch32_uuid = ((volatile uint32_t *)0x1FFFF7E8UL); + uint32_t *serial_32 = (uint32_t *)serial_id; + serial_32[0] = ch32_uuid[0]; + serial_32[1] = ch32_uuid[1]; + serial_32[2] = ch32_uuid[2]; + + return 12; +} + +#endif diff --git a/libraries/Adafruit_TinyUSB_Arduino/src/arduino/ports/ch32/tusb_config_ch32.h b/libraries/Adafruit_TinyUSB_Arduino/src/arduino/ports/ch32/tusb_config_ch32.h new file mode 100644 index 0000000..1591566 --- /dev/null +++ b/libraries/Adafruit_TinyUSB_Arduino/src/arduino/ports/ch32/tusb_config_ch32.h @@ -0,0 +1,184 @@ +/* + * The MIT License (MIT) + * + * Copyright (c) 2018, hathach for Adafruit + * + * 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_CH32_H_ +#define TUSB_CONFIG_CH32_H_ + +#ifdef __cplusplus +extern "C" { +#endif + +//-------------------------------------------------------------------- +// COMMON CONFIGURATION +//-------------------------------------------------------------------- +#if defined(CH32V10x) +#define CFG_TUSB_MCU OPT_MCU_CH32V103 +#warnning "CH32v103 is not working yet" +#elif defined(CH32V20x) +#define CFG_TUSB_MCU OPT_MCU_CH32V20X +#elif defined(CH32V30x) +#define CFG_TUSB_MCU OPT_MCU_CH32V307 +#endif + +#define CFG_TUSB_OS OPT_OS_NONE + +#ifndef CFG_TUSB_DEBUG +#define CFG_TUSB_DEBUG 0 +#endif + +// #define SERIAL_TUSB_DEBUG Serial2 + +// For selectively disable device log (when > CFG_TUSB_DEBUG) +// #define CFG_TUD_LOG_LEVEL 3 +// #define CFG_TUH_LOG_LEVEL 3 + +#define CFG_TUSB_MEM_SECTION +#define CFG_TUSB_MEM_ALIGN __attribute__((aligned(4))) + +#define CFG_TUD_ENABLED 1 + +// #ifdef USE_TINYUSB +//// Enable device stack +// #define CFG_TUD_ENABLED 1 +// +//// Enable host stack with MAX3421E (host shield) +// #define CFG_TUH_ENABLED 1 +// #define CFG_TUH_MAX3421 1 +// +// #else +// #define CFG_TUD_ENABLED 0 +// #define CFG_TUH_ENABLED 0 +// #endif + +//-------------------------------------------------------------------- +// DEVICE CONFIGURATION +//-------------------------------------------------------------------- + +#define CFG_TUD_ENDPOINT0_SIZE 64 + +//------------- CLASS -------------// +#ifndef CFG_TUD_CDC +#define CFG_TUD_CDC 1 +#endif + +#ifndef CFG_TUD_MSC +#define CFG_TUD_MSC 1 +#endif + +#ifndef CFG_TUD_HID +#define CFG_TUD_HID 2 +#endif + +#ifndef CFG_TUD_MIDI +#define CFG_TUD_MIDI 1 +#endif + +#ifndef CFG_TUD_VENDOR +#define CFG_TUD_VENDOR 1 +#endif + +#ifndef CFG_TUD_VIDEO +#define CFG_TUD_VIDEO 0 // number of video control interfaces +#endif + +#ifndef CFG_TUD_VIDEO_STREAMING +#define CFG_TUD_VIDEO_STREAMING 0 // number of video streaming interfaces +#endif + +// video streaming endpoint buffer size +#define CFG_TUD_VIDEO_STREAMING_EP_BUFSIZE 256 + +// CDC FIFO size of TX and RX +#define CFG_TUD_CDC_RX_BUFSIZE (TUD_OPT_HIGH_SPEED ? 512 : 256) +#define CFG_TUD_CDC_TX_BUFSIZE (TUD_OPT_HIGH_SPEED ? 512 : 256) + +// MSC Buffer size of Device Mass storage +#define CFG_TUD_MSC_EP_BUFSIZE 512 + +// HID buffer size Should be sufficient to hold ID (if any) + Data +#define CFG_TUD_HID_EP_BUFSIZE 64 + +// MIDI FIFO size of TX and RX +#define CFG_TUD_MIDI_RX_BUFSIZE 128 +#define CFG_TUD_MIDI_TX_BUFSIZE 128 + +// Vendor FIFO size of TX and RX +#ifndef CFG_TUD_VENDOR_RX_BUFSIZE +#define CFG_TUD_VENDOR_RX_BUFSIZE (TUD_OPT_HIGH_SPEED ? 512 : 64) +#endif + +#ifndef CFG_TUD_VENDOR_TX_BUFSIZE +#define CFG_TUD_VENDOR_TX_BUFSIZE (TUD_OPT_HIGH_SPEED ? 512 : 64) +#endif + +//-------------------------------------------------------------------- +// Host Configuration +//-------------------------------------------------------------------- +#if 0 +// Size of buffer to hold descriptors and other data used for enumeration +#define CFG_TUH_ENUMERATION_BUFSIZE 256 + +// Number of hub devices +#define CFG_TUH_HUB 1 + +// max device support (excluding hub device): 1 hub typically has 4 ports +#define CFG_TUH_DEVICE_MAX (3 * CFG_TUH_HUB + 1) + +// Enable tuh_edpt_xfer() API +// #define CFG_TUH_API_EDPT_XFER 1 + +// Number of mass storage +#define CFG_TUH_MSC 1 + +// Number of HIDs +// typical keyboard + mouse device can have 3,4 HID interfaces +#define CFG_TUH_HID (3 * CFG_TUH_DEVICE_MAX) + +// Number of CDC interfaces +// FTDI and CP210x are not part of CDC class, only to re-use CDC driver API +#define CFG_TUH_CDC 1 +#define CFG_TUH_CDC_FTDI 1 +#define CFG_TUH_CDC_CP210X 1 +#define CFG_TUH_CDC_CH34X 1 + +// RX & TX fifo size +#define CFG_TUH_CDC_RX_BUFSIZE (TUD_OPT_HIGH_SPEED ? 512 : 64) +#define CFG_TUH_CDC_TX_BUFSIZE (TUD_OPT_HIGH_SPEED ? 512 : 64) + +// Set Line Control state on enumeration/mounted: +// DTR ( bit 0), RTS (bit 1) +#define CFG_TUH_CDC_LINE_CONTROL_ON_ENUM 0x03 + +// Set Line Coding on enumeration/mounted, value for cdc_line_coding_t +// bit rate = 115200, 1 stop bit, no parity, 8 bit data width +// This need Pico-PIO-USB at least 0.5.1 +#define CFG_TUH_CDC_LINE_CODING_ON_ENUM \ + { 115200, CDC_LINE_CONDING_STOP_BITS_1, CDC_LINE_CODING_PARITY_NONE, 8 } +#endif + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/libraries/Adafruit_TinyUSB_Arduino/src/arduino/ports/esp32/Adafruit_TinyUSB_esp32.cpp b/libraries/Adafruit_TinyUSB_Arduino/src/arduino/ports/esp32/Adafruit_TinyUSB_esp32.cpp new file mode 100644 index 0000000..252fc01 --- /dev/null +++ b/libraries/Adafruit_TinyUSB_Arduino/src/arduino/ports/esp32/Adafruit_TinyUSB_esp32.cpp @@ -0,0 +1,186 @@ +/* + * The MIT License (MIT) + * + * Copyright (c) 2019, hathach for Adafruit + * + * 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_option.h" + +#if defined(ARDUINO_ARCH_ESP32) && CFG_TUD_ENABLED + +#include + +// ESP32 will use the arduino-esp32 core initialization +// These port API is empty to prevent compilation error + +void TinyUSB_Port_EnterDFU(void) {} + +void TinyUSB_Port_InitDevice(uint8_t rhport) { (void)rhport; } + +uint8_t TinyUSB_Port_GetSerialNumber(uint8_t serial_id[16]) { + (void)serial_id; + return 0; +} + +#if 0 + +// This port implemented is not needed and left here for reference only + +#include "sdkconfig.h" + +#include "soc/soc.h" + +#include "soc/efuse_reg.h" +#include "soc/periph_defs.h" +#include "soc/rtc_cntl_reg.h" + +#include "soc/system_reg.h" +#include "soc/timer_group_struct.h" +#include "soc/usb_periph.h" +#include "soc/usb_reg.h" +#include "soc/usb_struct.h" +#include "soc/usb_wrap_reg.h" +#include "soc/usb_wrap_struct.h" + +#include "hal/usb_hal.h" + +// compiler error with gpio_ll_get_drive_capability() +// invalid conversion from 'uint32_t' {aka 'unsigned int'} to 'gpio_drive_cap_t' +// [-fpermissive] #include "hal/gpio_ll.h" + +#include "freertos/FreeRTOS.h" +#include "freertos/task.h" + +#include "driver/gpio.h" +#include "driver/periph_ctrl.h" + +#include "esp32-hal.h" +#include "esp_rom_gpio.h" + +#include "esp32s2/rom/usb/chip_usb_dw_wrapper.h" +#include "esp32s2/rom/usb/usb_dc.h" +#include "esp32s2/rom/usb/usb_persist.h" + +#include "arduino/Adafruit_TinyUSB_API.h" +#include "arduino/Adafruit_USBD_Device.h" + +//--------------------------------------------------------------------+ +// MACRO TYPEDEF CONSTANT ENUM DECLARATION +//--------------------------------------------------------------------+ + +static void configure_pins(usb_hal_context_t *usb) { + for (const usb_iopin_dsc_t *iopin = usb_periph_iopins; iopin->pin != -1; + ++iopin) { + if ((usb->use_external_phy) || (iopin->ext_phy_only == 0)) { + esp_rom_gpio_pad_select_gpio(iopin->pin); + if (iopin->is_output) { + esp_rom_gpio_connect_out_signal(iopin->pin, iopin->func, false, false); + } else { + esp_rom_gpio_connect_in_signal(iopin->pin, iopin->func, false); + if ((iopin->pin != GPIO_FUNC_IN_LOW) && + (iopin->pin != GPIO_FUNC_IN_HIGH)) { + // workaround for compiler error with including "hal/gpio_ll.h" + // gpio_ll_input_enable(&GPIO, (gpio_num_t) iopin->pin); + PIN_INPUT_ENABLE(GPIO_PIN_MUX_REG[iopin->pin]); + } + } + esp_rom_gpio_pad_unhold(iopin->pin); + } + } + if (!usb->use_external_phy) { + gpio_set_drive_capability((gpio_num_t)USBPHY_DM_NUM, GPIO_DRIVE_CAP_3); + gpio_set_drive_capability((gpio_num_t)USBPHY_DP_NUM, GPIO_DRIVE_CAP_3); + } +} + +//--------------------------------------------------------------------+ +// Porting API +//--------------------------------------------------------------------+ + +#define USBD_STACK_SZ (4096) + +// USB Device Driver task +// This top level thread processes all usb events and invokes callbacks +static void usb_device_task(void *param) { + (void)param; + // RTOS forever loop + while (1) { + tud_task(); + } +} + + +void TinyUSB_Port_InitDevice(uint8_t rhport) { + (void)rhport; + + // Reset USB module + periph_module_reset(PERIPH_USB_MODULE); + periph_module_enable(PERIPH_USB_MODULE); + + usb_hal_context_t hal = {.use_external_phy = false}; + usb_hal_init(&hal); + configure_pins(&hal); + + // reset core, should be in dcd_esp32sx.c (do that later with more proper + // testing) + USB0.grstctl |= USB_CSFTRST; + while ((USB0.grstctl & USB_CSFTRST) == USB_CSFTRST) { + } + + tusb_init(); + + // Create a task for tinyusb device stack + xTaskCreate(usb_device_task, "usbd", USBD_STACK_SZ, NULL, + configMAX_PRIORITIES - 1, NULL); +} + +void TinyUSB_Port_EnterDFU(void) { + // Reset to Bootloader + + // Reset USB Core + USB0.grstctl |= USB_CSFTRST; + while ((USB0.grstctl & USB_CSFTRST) == USB_CSFTRST) { + } + + REG_WRITE(RTC_CNTL_OPTION1_REG, RTC_CNTL_FORCE_DOWNLOAD_BOOT); + esp_restart(); +} + +uint8_t TinyUSB_Port_GetSerialNumber(uint8_t serial_id[16]) { + /* Get the MAC address */ + const uint32_t mac0 = + __builtin_bswap32(REG_GET_FIELD(EFUSE_RD_MAC_SPI_SYS_0_REG, EFUSE_MAC_0)); + const uint16_t mac1 = __builtin_bswap16( + (uint16_t)REG_GET_FIELD(EFUSE_RD_MAC_SPI_SYS_1_REG, EFUSE_MAC_1)); + + memcpy(serial_id, &mac1, 2); + memcpy(serial_id + 2, &mac0, 4); + + return 6; +} + +extern "C" void yield(void) { + TinyUSB_Device_FlushCDC(); + vPortYield(); +} +#endif // commented out + +#endif diff --git a/libraries/Adafruit_TinyUSB_Arduino/src/arduino/ports/esp32/tusb_config_esp32.h b/libraries/Adafruit_TinyUSB_Arduino/src/arduino/ports/esp32/tusb_config_esp32.h new file mode 100644 index 0000000..1fdc7e8 --- /dev/null +++ b/libraries/Adafruit_TinyUSB_Arduino/src/arduino/ports/esp32/tusb_config_esp32.h @@ -0,0 +1,197 @@ +/* + * The MIT License (MIT) + * + * Copyright (c) 2018, hathach for Adafruit + * + * 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_ESP32_H_ +#define TUSB_CONFIG_ESP32_H_ + +#ifdef __cplusplus +extern "C" { +#endif + +// ESP32 Arduino core already integrated tinyusb using lib arduino_tinyusb +// https://github.com/espressif/esp32-arduino-lib-builder/tree/master/components/arduino_tinyusb +// Although it is possible to use .c files in this library for linking instead +// of lib arduino_tinyusb. However, include path of lib arduino_tinyusb is first +// in order. Therefore, changes in this Adafruit TinyUSB library's header are +// not picked up by its .c file. Due to the version difference between the 2 +// libraries, this file is used to make it compatible with ESP32 Arduino core. + +// This file also contains additional configuration for EPS32 in addition to +// tools/esp32-arduino-libs/esp32xx/include/arduino_tinyusb/include/tusb_config.h + +//--------------------------------------------------------------------+ +// ESP32 out-of-sync +//--------------------------------------------------------------------+ +#include "esp_arduino_version.h" +#include "sdkconfig.h" + +#if ESP_ARDUINO_VERSION < ESP_ARDUINO_VERSION_VAL(2, 0, 8) +#error "ESP32 Arduino core version 2.0.8 or later is required" +#endif + +//-------------------------------------------------------------------- +// COMMON CONFIGURATION +// Note: it is possible to use tinyusb + max3421e as host controller +// with no OTG USB MCU such as eps32, c3 etc... +//-------------------------------------------------------------------- + +#if CONFIG_IDF_TARGET_ESP32S2 +#define CFG_TUSB_MCU OPT_MCU_ESP32S2 +#elif CONFIG_IDF_TARGET_ESP32S3 +#define CFG_TUSB_MCU OPT_MCU_ESP32S3 +#elif CONFIG_IDF_TARGET_ESP32P4 +#define CFG_TUSB_MCU OPT_MCU_ESP32P4 +#else +#define CFG_TUSB_MCU OPT_MCU_ESP32 +#endif + +#if CONFIG_IDF_TARGET_ESP32P4 +#define CFG_TUD_MAX_SPEED OPT_MODE_HIGH_SPEED +#else +#define CFG_TUD_MAX_SPEED OPT_MODE_FULL_SPEED +#endif + +#ifndef CFG_TUSB_OS +#define CFG_TUSB_OS OPT_OS_FREERTOS +// clang-format off +#define CFG_TUSB_OS_INC_PATH freertos/ +// clang-format on +#endif + +#ifndef CFG_TUD_LOG_LEVEL +#define CFG_TUD_LOG_LEVEL 2 +#endif + +#ifndef CFG_TUH_LOG_LEVEL +#define CFG_TUH_LOG_LEVEL 2 +#endif + +#ifndef CFG_TUSB_DEBUG +#define CFG_TUSB_DEBUG 0 +#endif + +// For selectively disable device log (when > CFG_TUSB_DEBUG) +// #define CFG_TUD_LOG_LEVEL 3 + +#define CFG_TUSB_MEM_SECTION +#define CFG_TUSB_MEM_ALIGN TU_ATTR_ALIGNED(4) + +//-------------------------------------------------------------------- +// DEVICE CONFIGURATION +// only enabled if USB OTG is supported +//-------------------------------------------------------------------- +#if defined(CONFIG_USB_OTG_SUPPORTED) && CONFIG_USB_OTG_SUPPORTED +#define CFG_TUD_ENABLED 1 +#else +#define CFG_TUD_ENABLED 0 +#endif + +#define CFG_TUD_CDC 2 +#define CFG_TUD_MSC 1 +#define CFG_TUD_HID 2 +#define CFG_TUD_MIDI 1 +#define CFG_TUD_VENDOR 1 +#define CFG_TUD_VIDEO 1 +#define CFG_TUD_VIDEO_STREAMING 1 + +// video streaming endpoint buffer size +#define CFG_TUD_VIDEO_STREAMING_EP_BUFSIZE \ + CONFIG_TINYUSB_VIDEO_STREAMING_BUFSIZE + +// CDC FIFO size of TX and RX +#define CFG_TUD_CDC_RX_BUFSIZE CONFIG_TINYUSB_CDC_RX_BUFSIZE +#define CFG_TUD_CDC_TX_BUFSIZE CONFIG_TINYUSB_CDC_TX_BUFSIZE + +// MSC Buffer size of Device Mass storage +#define CFG_TUD_MSC_EP_BUFSIZE CONFIG_TINYUSB_MSC_BUFSIZE + +// HID buffer size Should be sufficient to hold ID (if any) + Data +#define CFG_TUD_HID_BUFSIZE CONFIG_TINYUSB_HID_BUFSIZE + +// MIDI FIFO size of TX and RX +#define CFG_TUD_MIDI_RX_BUFSIZE CONFIG_TINYUSB_MIDI_RX_BUFSIZE +#define CFG_TUD_MIDI_TX_BUFSIZE CONFIG_TINYUSB_MIDI_TX_BUFSIZE + +// Vendor FIFO size of TX and RX +#define CFG_TUD_VENDOR_RX_BUFSIZE CONFIG_TINYUSB_VENDOR_RX_BUFSIZE +#define CFG_TUD_VENDOR_TX_BUFSIZE CONFIG_TINYUSB_VENDOR_TX_BUFSIZE + +//-------------------------------------------------------------------- +// Host Configuration +//-------------------------------------------------------------------- + +// Enable host stack with MAX3421E (host shield) +#define CFG_TUH_ENABLED 1 +#define CFG_TUH_MAX3421 1 +#define CFG_TUH_MAX_SPEED OPT_MODE_FULL_SPEED + +#ifndef CFG_TUH_MAX3421_ENDPOINT_TOTAL +#define CFG_TUH_MAX3421_ENDPOINT_TOTAL (8 + 4 * (CFG_TUH_DEVICE_MAX - 1)) +#endif + +// Size of buffer to hold descriptors and other data used for enumeration +#define CFG_TUH_ENUMERATION_BUFSIZE 256 + +// Number of hub devices +#define CFG_TUH_HUB 1 + +// max device support (excluding hub device): 1 hub typically has 4 ports +#define CFG_TUH_DEVICE_MAX (3 * CFG_TUH_HUB + 1) + +// Enable tuh_edpt_xfer() API +// #define CFG_TUH_API_EDPT_XFER 1 + +// Number of mass storage +#define CFG_TUH_MSC 1 + +// Number of HIDs +// typical keyboard + mouse device can have 3,4 HID interfaces +#define CFG_TUH_HID (3 * CFG_TUH_DEVICE_MAX) + +// Number of CDC interfaces +// FTDI and CP210x are not part of CDC class, only to re-use CDC driver API +#define CFG_TUH_CDC 1 +#define CFG_TUH_CDC_FTDI 1 +#define CFG_TUH_CDC_CP210X 1 +#define CFG_TUH_CDC_CH34X 1 + +// RX & TX fifo size +#define CFG_TUH_CDC_RX_BUFSIZE 64 +#define CFG_TUH_CDC_TX_BUFSIZE 64 + +// Set Line Control state on enumeration/mounted: +// DTR ( bit 0), RTS (bit 1) +#define CFG_TUH_CDC_LINE_CONTROL_ON_ENUM 0x03 + +// Set Line Coding on enumeration/mounted, value for cdc_line_coding_t +// bit rate = 115200, 1 stop bit, no parity, 8 bit data width +// This need Pico-PIO-USB at least 0.5.1 +#define CFG_TUH_CDC_LINE_CODING_ON_ENUM \ + { 115200, CDC_LINE_CONDING_STOP_BITS_1, CDC_LINE_CODING_PARITY_NONE, 8 } + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/libraries/Adafruit_TinyUSB_Arduino/src/arduino/ports/nrf/Adafruit_TinyUSB_nrf.cpp b/libraries/Adafruit_TinyUSB_Arduino/src/arduino/ports/nrf/Adafruit_TinyUSB_nrf.cpp new file mode 100644 index 0000000..a5558b8 --- /dev/null +++ b/libraries/Adafruit_TinyUSB_Arduino/src/arduino/ports/nrf/Adafruit_TinyUSB_nrf.cpp @@ -0,0 +1,143 @@ +/* + * The MIT License (MIT) + * + * Copyright (c) 2019, hathach for Adafruit + * + * 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_option.h" + +#if defined ARDUINO_NRF52_ADAFRUIT && CFG_TUD_ENABLED + +#include "nrfx.h" +#include "nrfx_power.h" + +#include "Arduino.h" +#include "arduino/Adafruit_USBD_Device.h" + +//--------------------------------------------------------------------+ +// MACRO TYPEDEF CONSTANT ENUM DECLARATION +//--------------------------------------------------------------------+ + +#define USBD_STACK_SZ (200) + +//--------------------------------------------------------------------+ +// Forward USB interrupt events to TinyUSB IRQ Handler +//--------------------------------------------------------------------+ +extern "C" void USBD_IRQHandler(void) { +#if CFG_SYSVIEW + SEGGER_SYSVIEW_RecordEnterISR(); +#endif + + tud_int_handler(0); + +#if CFG_SYSVIEW + SEGGER_SYSVIEW_RecordExitISR(); +#endif +} + +//--------------------------------------------------------------------+ +// Porting API +//--------------------------------------------------------------------+ + +static void usb_hardware_init(void); + +// USB Device Driver task +// This top level thread process all usb events and invoke callbacks +static void usb_device_task(void *param) { + (void)param; + + // Priorities 0, 1, 4 (nRF52) are reserved for SoftDevice + // 2 is highest for application + NVIC_SetPriority(USBD_IRQn, 2); + + // init device on rhport0 + const tusb_rhport_init_t rh_init = { + .role = TUSB_ROLE_DEVICE, + .speed = TUD_OPT_HIGH_SPEED ? TUSB_SPEED_HIGH : TUSB_SPEED_FULL, + }; + tusb_init(0, &rh_init); + + usb_hardware_init(); + + // RTOS forever loop + while (1) { + tud_task(); + TinyUSB_Device_FlushCDC(); + } +} + +void TinyUSB_Port_InitDevice(uint8_t rhport) { + (void)rhport; + + // Create a task for tinyusb device stack + xTaskCreate(usb_device_task, "usbd", USBD_STACK_SZ, NULL, TASK_PRIO_HIGH, + NULL); +} + +void TinyUSB_Port_EnterDFU(void) { + // Reset to Bootloader + enterSerialDfu(); +} + +uint8_t TinyUSB_Port_GetSerialNumber(uint8_t serial_id[16]) { + uint32_t *serial_32 = (uint32_t *)serial_id; + + serial_32[0] = __builtin_bswap32(NRF_FICR->DEVICEID[1]); + serial_32[1] = __builtin_bswap32(NRF_FICR->DEVICEID[0]); + + return 8; +} + +//--------------------------------------------------------------------+ +// Helper +//--------------------------------------------------------------------+ + +// tinyusb function that handles power event (detected, ready, removed) +// We must call it within SD's SOC event handler, or set it as power event +// handler if SD is not enabled. +extern "C" void tusb_hal_nrf_power_event(uint32_t event); + +static void power_event_handler(nrfx_power_usb_evt_t event) { + tusb_hal_nrf_power_event((uint32_t)event); +} + +// Init usb hardware when starting up. Softdevice is not enabled yet +static void usb_hardware_init(void) { + // USB power may already be ready at this time -> no event generated + // We need to invoke the handler based on the status initially + uint32_t usb_reg = NRF_POWER->USBREGSTATUS; + + // Power module init + const nrfx_power_config_t pwr_cfg = {0}; + nrfx_power_init(&pwr_cfg); + + // Register tusb function as USB power handler + const nrfx_power_usbevt_config_t config = {.handler = power_event_handler}; + + nrfx_power_usbevt_init(&config); + nrfx_power_usbevt_enable(); + + if (usb_reg & POWER_USBREGSTATUS_VBUSDETECT_Msk) { + tusb_hal_nrf_power_event(NRFX_POWER_USB_EVT_DETECTED); + } +} + +#endif // USE_TINYUSB diff --git a/libraries/Adafruit_TinyUSB_Arduino/src/arduino/ports/nrf/tusb_config_nrf.h b/libraries/Adafruit_TinyUSB_Arduino/src/arduino/ports/nrf/tusb_config_nrf.h new file mode 100644 index 0000000..bfcd132 --- /dev/null +++ b/libraries/Adafruit_TinyUSB_Arduino/src/arduino/ports/nrf/tusb_config_nrf.h @@ -0,0 +1,179 @@ +/* + * The MIT License (MIT) + * + * Copyright (c) 2018, hathach for Adafruit + * + * 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_NRF_H_ +#define _TUSB_CONFIG_NRF_H_ + +#ifdef __cplusplus +extern "C" { +#endif + +// backward compatible with old nrf52 core +#ifndef pdTICKS_TO_MS +#define pdTICKS_TO_MS(xTimeInTicks) \ + ((TickType_t)(((uint64_t)(xTimeInTicks) * (uint64_t)1000U) / \ + (uint64_t)configTICK_RATE_HZ)) +#endif + +//-------------------------------------------------------------------- +// COMMON CONFIGURATION +//-------------------------------------------------------------------- +#define CFG_TUSB_MCU OPT_MCU_NRF5X +#define CFG_TUSB_OS OPT_OS_FREERTOS + +#ifndef CFG_TUSB_DEBUG +#define CFG_TUSB_DEBUG 0 +#endif + +// For selectively disable device log (when > CFG_TUSB_DEBUG) +// #define CFG_TUD_LOG_LEVEL 3 +// #define CFG_TUH_LOG_LEVEL 3 + +#define CFG_TUSB_MEM_SECTION +#define CFG_TUSB_MEM_ALIGN __attribute__((aligned(4))) + +#ifdef USE_TINYUSB +// Enable device stack +#define CFG_TUD_ENABLED 1 + +// Enable host stack with MAX3421E (host shield) +#define CFG_TUH_ENABLED 1 +#define CFG_TUH_MAX3421 1 + +#else +#define CFG_TUD_ENABLED 0 +#define CFG_TUH_ENABLED 0 +#endif + +//-------------------------------------------------------------------- +// DEVICE CONFIGURATION +//-------------------------------------------------------------------- + +#if CFG_TUD_ENABLED +#define CFG_TUD_ENDPOINT0_SIZE 64 + +//------------- CLASS -------------// +#ifndef CFG_TUD_CDC +#define CFG_TUD_CDC 1 +#endif +#ifndef CFG_TUD_MSC +#define CFG_TUD_MSC 1 +#endif +#ifndef CFG_TUD_HID +#define CFG_TUD_HID 2 +#endif +#ifndef CFG_TUD_MIDI +#define CFG_TUD_MIDI 1 +#endif +#ifndef CFG_TUD_VENDOR +#define CFG_TUD_VENDOR 1 +#endif +#ifndef CFG_TUD_VIDEO +#define CFG_TUD_VIDEO 1 // number of video control interfaces +#endif +#ifndef CFG_TUD_VIDEO_STREAMING +#define CFG_TUD_VIDEO_STREAMING 1 // number of video streaming interfaces +#endif + +// video streaming endpoint buffer size +#define CFG_TUD_VIDEO_STREAMING_EP_BUFSIZE 256 + +// CDC FIFO size of TX and RX +#define CFG_TUD_CDC_RX_BUFSIZE 256 +#define CFG_TUD_CDC_TX_BUFSIZE 256 + +// MSC Buffer size of Device Mass storage +#define CFG_TUD_MSC_EP_BUFSIZE 512 + +// HID buffer size Should be sufficient to hold ID (if any) + Data +#define CFG_TUD_HID_EP_BUFSIZE 64 + +// MIDI FIFO size of TX and RX +#define CFG_TUD_MIDI_RX_BUFSIZE 128 +#define CFG_TUD_MIDI_TX_BUFSIZE 128 + +// Vendor FIFO size of TX and RX +#ifndef CFG_TUD_VENDOR_RX_BUFSIZE +#define CFG_TUD_VENDOR_RX_BUFSIZE 64 +#endif + +#ifndef CFG_TUD_VENDOR_TX_BUFSIZE +#define CFG_TUD_VENDOR_TX_BUFSIZE 64 +#endif + +#endif + +//-------------------------------------------------------------------- +// Host Configuration +//-------------------------------------------------------------------- + +#if CFG_TUH_ENABLED + +// Size of buffer to hold descriptors and other data used for enumeration +#define CFG_TUH_ENUMERATION_BUFSIZE 256 + +// Number of hub devices +#define CFG_TUH_HUB 1 + +// max device support (excluding hub device): 1 hub typically has 4 ports +#define CFG_TUH_DEVICE_MAX (3 * CFG_TUH_HUB + 1) + +// Enable tuh_edpt_xfer() API +// #define CFG_TUH_API_EDPT_XFER 1 + +// Number of mass storage +#define CFG_TUH_MSC 1 + +// Number of HIDs +// typical keyboard + mouse device can have 3,4 HID interfaces +#define CFG_TUH_HID (3 * CFG_TUH_DEVICE_MAX) + +// Number of CDC interfaces +// FTDI and CP210x are not part of CDC class, only to re-use CDC driver API +#define CFG_TUH_CDC 1 +#define CFG_TUH_CDC_FTDI 1 +#define CFG_TUH_CDC_CP210X 1 +#define CFG_TUH_CDC_CH34X 1 + +// RX & TX fifo size +#define CFG_TUH_CDC_RX_BUFSIZE 64 +#define CFG_TUH_CDC_TX_BUFSIZE 64 + +// Set Line Control state on enumeration/mounted: +// DTR ( bit 0), RTS (bit 1) +#define CFG_TUH_CDC_LINE_CONTROL_ON_ENUM 0x03 + +// Set Line Coding on enumeration/mounted, value for cdc_line_coding_t +// bit rate = 115200, 1 stop bit, no parity, 8 bit data width +// This need Pico-PIO-USB at least 0.5.1 +#define CFG_TUH_CDC_LINE_CODING_ON_ENUM \ + { 115200, CDC_LINE_CONDING_STOP_BITS_1, CDC_LINE_CODING_PARITY_NONE, 8 } + +#endif + +#ifdef __cplusplus +} +#endif + +#endif /* _TUSB_CONFIG_NRF_H_ */ diff --git a/libraries/Adafruit_TinyUSB_Arduino/src/arduino/ports/rp2040/Adafruit_TinyUSB_rp2040.cpp b/libraries/Adafruit_TinyUSB_Arduino/src/arduino/ports/rp2040/Adafruit_TinyUSB_rp2040.cpp new file mode 100644 index 0000000..d100556 --- /dev/null +++ b/libraries/Adafruit_TinyUSB_Arduino/src/arduino/ports/rp2040/Adafruit_TinyUSB_rp2040.cpp @@ -0,0 +1,147 @@ +/* + * The MIT License (MIT) + * + * Copyright (c) 2019, hathach for Adafruit + * + * 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_option.h" + +#if defined ARDUINO_ARCH_RP2040 && CFG_TUD_ENABLED + +#include "Arduino.h" + +// mbed old pico-sdk need to wrap with cpp +extern "C" { +#include "hardware/flash.h" +#include "hardware/irq.h" +#include "pico/bootrom.h" +#include "pico/mutex.h" +#include "pico/time.h" +} + +#include "arduino/Adafruit_TinyUSB_API.h" +#include "tusb.h" + +// SDK >= 1.4.0 need to dynamically request the IRQ to avoid conflicts +#if (PICO_SDK_VERSION_MAJOR * 100 + PICO_SDK_VERSION_MINOR) < 104 +#define USB_TASK_IRQ 31 +#else +static unsigned int USB_TASK_IRQ; +#endif + +//--------------------------------------------------------------------+ +// Forward USB interrupt events to TinyUSB IRQ Handler +// rp2040 implementation will install appropriate handler when initializing +// tinyusb. There is no need to forward IRQ from application +//--------------------------------------------------------------------+ + +//--------------------------------------------------------------------+ +// Earle Philhower and mbed specific +//--------------------------------------------------------------------+ + +// mbed use old pico-sdk does not have unique_id +#ifdef ARDUINO_ARCH_MBED + +#define get_unique_id(_serial) flash_get_unique_id(_serial) + +#else + +#include "pico/unique_id.h" +#define get_unique_id(_serial) \ + pico_get_unique_board_id((pico_unique_board_id_t *)_serial); + +#endif + +//--------------------------------------------------------------------+ +// Porting API +//--------------------------------------------------------------------+ + +// Big, global USB mutex, shared with all USB devices to make sure we don't +// have multiple cores updating the TUSB state in parallel +mutex_t __usb_mutex; + +static void usb_task_irq(void) { + // if the mutex is already owned, then we are in user code + // in this file which will do a tud_task itself, so we'll just do nothing + // until the next tick; we won't starve + if (mutex_try_enter(&__usb_mutex, NULL)) { + tud_task(); + mutex_exit(&__usb_mutex); + } +} + +#ifndef PICO_SHARED_IRQ_HANDLER_LOWEST_ORDER_PRIORITY +#define PICO_SHARED_IRQ_HANDLER_LOWEST_ORDER_PRIORITY 0x00 +#endif + +// invoked when there is hardware usb irq, trigger task runner later +static void usb_task_trigger_irq(void) { irq_set_pending(USB_TASK_IRQ); } + +void TinyUSB_Port_InitDevice(uint8_t rhport) { + mutex_init(&__usb_mutex); + + const tusb_rhport_init_t rh_init = { + .role = TUSB_ROLE_DEVICE, + .speed = TUSB_SPEED_FULL, + }; + tusb_init(rhport, &rh_init); + + // soft irq for task runner +#if (PICO_SDK_VERSION_MAJOR * 100 + PICO_SDK_VERSION_MINOR) >= 104 + USB_TASK_IRQ = user_irq_claim_unused(true); +#endif + irq_set_exclusive_handler(USB_TASK_IRQ, usb_task_irq); + irq_set_enabled(USB_TASK_IRQ, true); + + // add shared irq to trigger task runner + irq_add_shared_handler(USBCTRL_IRQ, usb_task_trigger_irq, + PICO_SHARED_IRQ_HANDLER_LOWEST_ORDER_PRIORITY); +} + +void TinyUSB_Port_EnterDFU(void) { + reset_usb_boot(0, 0); + while (1) { + } +} + +uint8_t TinyUSB_Port_GetSerialNumber(uint8_t serial_id[16]) { + get_unique_id(serial_id); + return FLASH_UNIQUE_ID_SIZE_BYTES; +} + +//--------------------------------------------------------------------+ +// Core API +// Implement Core API since rp2040 need mutex for calling tud_task in +// IRQ context +//--------------------------------------------------------------------+ + +extern "C" { + +void TinyUSB_Device_Task(void) { + // Since tud_task() is also invoked in ISR, we need to get the mutex first + if (mutex_try_enter(&__usb_mutex, NULL)) { + tud_task(); + mutex_exit(&__usb_mutex); + } +} +} + +#endif diff --git a/libraries/Adafruit_TinyUSB_Arduino/src/arduino/ports/rp2040/tusb_config_rp2040.h b/libraries/Adafruit_TinyUSB_Arduino/src/arduino/ports/rp2040/tusb_config_rp2040.h new file mode 100644 index 0000000..5ddd4ed --- /dev/null +++ b/libraries/Adafruit_TinyUSB_Arduino/src/arduino/ports/rp2040/tusb_config_rp2040.h @@ -0,0 +1,178 @@ +/* + The MIT License (MIT) + + Copyright (c) 2018, hathach for Adafruit + + 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_RP2040_H_ +#define _TUSB_CONFIG_RP2040_H_ + +#ifdef __cplusplus +extern "C" { +#endif + +//-------------------------------------------------------------------- +// COMMON CONFIGURATION +//-------------------------------------------------------------------- + +#ifdef USE_TINYUSB_HOST +// native as host +#define CFG_TUD_ENABLED 0 +#define CFG_TUH_ENABLED 1 +#define CFG_TUH_RPI_PIO_USB 0 + +#else +// native as device +#define CFG_TUD_ENABLED 1 + +#if __has_include("pio_usb.h") +// Enable host stack with pio-usb if Pico-PIO-USB library is available +#define CFG_TUH_ENABLED 1 +#define CFG_TUH_RPI_PIO_USB 1 + +#else +// Otherwise enable host controller with MAX3421E +#define CFG_TUH_ENABLED 1 +#define CFG_TUH_MAX3421 1 + +#endif // pio_usb.h +#endif // USE_TINYUSB_HOST + +#ifndef CFG_TUSB_MCU +#define CFG_TUSB_MCU OPT_MCU_RP2040 +#endif + +#ifndef CFG_TUSB_OS +#define CFG_TUSB_OS OPT_OS_PICO +#endif + +#ifndef CFG_TUSB_DEBUG +#define CFG_TUSB_DEBUG 0 +#endif + +// For selectively disable device log (when > CFG_TUSB_DEBUG) +// #define CFG_TUD_LOG_LEVEL 3 +// #define CFG_TUH_LOG_LEVEL 3 + +#define CFG_TUSB_MEM_SECTION +#define CFG_TUSB_MEM_ALIGN TU_ATTR_ALIGNED(4) + +//-------------------------------------------------------------------- +// Device Configuration +//-------------------------------------------------------------------- + +#define CFG_TUD_ENDPOINT0_SIZE 64 + +#ifndef CFG_TUD_CDC +#define CFG_TUD_CDC 2 +#endif +#ifndef CFG_TUD_MSC +#define CFG_TUD_MSC 1 +#endif +#ifndef CFG_TUD_HID +#define CFG_TUD_HID 2 +#endif +#ifndef CFG_TUD_MIDI +#define CFG_TUD_MIDI 1 +#endif +#ifndef CFG_TUD_VENDOR +#define CFG_TUD_VENDOR 1 +#endif +#ifndef CFG_TUD_VIDEO +#define CFG_TUD_VIDEO 1 // number of video control interfaces +#endif +#ifndef CFG_TUD_VIDEO_STREAMING +#define CFG_TUD_VIDEO_STREAMING 1 // number of video streaming interfaces +#endif + +// video streaming endpoint buffer size +#define CFG_TUD_VIDEO_STREAMING_EP_BUFSIZE 256 + +// CDC FIFO size of TX and RX +#define CFG_TUD_CDC_RX_BUFSIZE 256 +#define CFG_TUD_CDC_TX_BUFSIZE 256 + +// MSC Buffer size of Device Mass storage +#define CFG_TUD_MSC_EP_BUFSIZE 512 + +// HID buffer size Should be sufficient to hold ID (if any) + Data +#define CFG_TUD_HID_EP_BUFSIZE 64 + +// MIDI FIFO size of TX and RX +#define CFG_TUD_MIDI_RX_BUFSIZE 64 +#define CFG_TUD_MIDI_TX_BUFSIZE 64 + +// Vendor FIFO size of TX and RX +#define CFG_TUD_VENDOR_RX_BUFSIZE 64 +#define CFG_TUD_VENDOR_TX_BUFSIZE 64 + +//-------------------------------------------------------------------- +// Host Configuration +//-------------------------------------------------------------------- + +// Size of buffer to hold descriptors and other data used for enumeration +#define CFG_TUH_ENUMERATION_BUFSIZE 256 + +// Number of hub devices +#define CFG_TUH_HUB 1 + +// max device support (excluding hub device): 1 hub typically has 4 ports +#define CFG_TUH_DEVICE_MAX (3 * CFG_TUH_HUB + 1) + +// enable MIDI Host +#define CFG_TUH_MIDI 1 + +// Enable tuh_edpt_xfer() API +// #define CFG_TUH_API_EDPT_XFER 1 + +// Number of mass storage +#define CFG_TUH_MSC 1 + +// Number of HIDs +// typical keyboard + mouse device can have 3,4 HID interfaces +#define CFG_TUH_HID (3 * CFG_TUH_DEVICE_MAX) + +// Number of CDC interfaces +// FTDI and CP210x are not part of CDC class, only to re-use CDC driver API +#define CFG_TUH_CDC 1 +#define CFG_TUH_CDC_FTDI 1 +#define CFG_TUH_CDC_CP210X 1 +#define CFG_TUH_CDC_CH34X 1 + +// RX & TX fifo size +#define CFG_TUH_CDC_RX_BUFSIZE 128 +#define CFG_TUH_CDC_TX_BUFSIZE 128 + +// Set Line Control state on enumeration/mounted: +// DTR ( bit 0), RTS (bit 1) +#define CFG_TUH_CDC_LINE_CONTROL_ON_ENUM 0x03 + +// Set Line Coding on enumeration/mounted, value for cdc_line_coding_t +// bit rate = 115200, 1 stop bit, no parity, 8 bit data width +// This need Pico-PIO-USB at least 0.5.1 +#define CFG_TUH_CDC_LINE_CODING_ON_ENUM \ + { 115200, CDC_LINE_CODING_STOP_BITS_1, CDC_LINE_CODING_PARITY_NONE, 8 } + +#ifdef __cplusplus +} +#endif + +#endif /* _TUSB_CONFIG_RP2040_H_ */ diff --git a/libraries/Adafruit_TinyUSB_Arduino/src/arduino/ports/samd/Adafruit_TinyUSB_samd.cpp b/libraries/Adafruit_TinyUSB_Arduino/src/arduino/ports/samd/Adafruit_TinyUSB_samd.cpp new file mode 100644 index 0000000..f6fe45e --- /dev/null +++ b/libraries/Adafruit_TinyUSB_Arduino/src/arduino/ports/samd/Adafruit_TinyUSB_samd.cpp @@ -0,0 +1,146 @@ +/* + * The MIT License (MIT) + * + * Copyright (c) 2019, hathach for Adafruit + * + * 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_option.h" + +#if defined ARDUINO_ARCH_SAMD && CFG_TUD_ENABLED + +#include "Arduino.h" +#include // Needed for auto-reset with 1200bps port touch + +#include "arduino/Adafruit_TinyUSB_API.h" +#include "tusb.h" + +//--------------------------------------------------------------------+ +// Forward USB interrupt events to TinyUSB IRQ Handler +//--------------------------------------------------------------------+ +extern "C" { + +#if CFG_TUSB_MCU == OPT_MCU_SAMD51 || CFG_TUSB_MCU == OPT_MCU_SAME5X + +// SAMD51 +void USB_0_Handler(void) { tud_int_handler(0); } +void USB_1_Handler(void) { tud_int_handler(0); } +void USB_2_Handler(void) { tud_int_handler(0); } +void USB_3_Handler(void) { tud_int_handler(0); } + +#elif CFG_TUSB_MCU == OPT_MCU_SAMD21 + +// SAMD21 +void USB_Handler(void) { tud_int_handler(0); } + +#endif + +} // extern C + +//--------------------------------------------------------------------+ +// Porting API +//--------------------------------------------------------------------+ +void TinyUSB_Port_InitDevice(uint8_t rhport) { + (void)rhport; + + /* Enable USB clock */ +#if defined(__SAMD51__) + MCLK->APBBMASK.reg |= MCLK_APBBMASK_USB; + MCLK->AHBMASK.reg |= MCLK_AHBMASK_USB; + + // Set up the USB DP/DN pins + PORT->Group[0].PINCFG[PIN_PA24H_USB_DM].bit.PMUXEN = 1; + PORT->Group[0].PMUX[PIN_PA24H_USB_DM / 2].reg &= + ~(0xF << (4 * (PIN_PA24H_USB_DM & 0x01u))); + PORT->Group[0].PMUX[PIN_PA24H_USB_DM / 2].reg |= + MUX_PA24H_USB_DM << (4 * (PIN_PA24H_USB_DM & 0x01u)); + PORT->Group[0].PINCFG[PIN_PA25H_USB_DP].bit.PMUXEN = 1; + PORT->Group[0].PMUX[PIN_PA25H_USB_DP / 2].reg &= + ~(0xF << (4 * (PIN_PA25H_USB_DP & 0x01u))); + PORT->Group[0].PMUX[PIN_PA25H_USB_DP / 2].reg |= + MUX_PA25H_USB_DP << (4 * (PIN_PA25H_USB_DP & 0x01u)); + + GCLK->PCHCTRL[USB_GCLK_ID].reg = + GCLK_PCHCTRL_GEN_GCLK1_Val | (1 << GCLK_PCHCTRL_CHEN_Pos); + + NVIC_SetPriority(USB_0_IRQn, 0UL); + NVIC_SetPriority(USB_1_IRQn, 0UL); + NVIC_SetPriority(USB_2_IRQn, 0UL); + NVIC_SetPriority(USB_3_IRQn, 0UL); +#else + PM->APBBMASK.reg |= PM_APBBMASK_USB; + + // Set up the USB DP/DN pins + PORT->Group[0].PINCFG[PIN_PA24G_USB_DM].bit.PMUXEN = 1; + PORT->Group[0].PMUX[PIN_PA24G_USB_DM / 2].reg &= + ~(0xF << (4 * (PIN_PA24G_USB_DM & 0x01u))); + PORT->Group[0].PMUX[PIN_PA24G_USB_DM / 2].reg |= + MUX_PA24G_USB_DM << (4 * (PIN_PA24G_USB_DM & 0x01u)); + PORT->Group[0].PINCFG[PIN_PA25G_USB_DP].bit.PMUXEN = 1; + PORT->Group[0].PMUX[PIN_PA25G_USB_DP / 2].reg &= + ~(0xF << (4 * (PIN_PA25G_USB_DP & 0x01u))); + PORT->Group[0].PMUX[PIN_PA25G_USB_DP / 2].reg |= + MUX_PA25G_USB_DP << (4 * (PIN_PA25G_USB_DP & 0x01u)); + + // Put Generic Clock Generator 0 as source for Generic Clock Multiplexer 6 + // (USB reference) + GCLK->CLKCTRL.reg = + GCLK_CLKCTRL_ID(6) | // Generic Clock Multiplexer 6 + GCLK_CLKCTRL_GEN_GCLK0 | // Generic Clock Generator 0 is source + GCLK_CLKCTRL_CLKEN; + while (GCLK->STATUS.bit.SYNCBUSY) { + // blocking wait + } + + NVIC_SetPriority((IRQn_Type)USB_IRQn, 0UL); +#endif + + // Init port 0 as device + const tusb_rhport_init_t rh_init = { + .role = TUSB_ROLE_DEVICE, + .speed = TUSB_SPEED_FULL, + }; + tusb_init(0, &rh_init); +} + +void TinyUSB_Port_EnterDFU(void) { + // Reset to bootloader + initiateReset(250); +} + +uint8_t TinyUSB_Port_GetSerialNumber(uint8_t serial_id[16]) { +#ifdef __SAMD51__ + uint32_t *id_addresses[4] = {(uint32_t *)0x008061FC, (uint32_t *)0x00806010, + (uint32_t *)0x00806014, (uint32_t *)0x00806018}; +#else // samd21 + uint32_t *id_addresses[4] = {(uint32_t *)0x0080A00C, (uint32_t *)0x0080A040, + (uint32_t *)0x0080A044, (uint32_t *)0x0080A048}; +#endif + + uint32_t *serial_32 = (uint32_t *)serial_id; + + for (int i = 0; i < 4; i++) { + *serial_32++ = __builtin_bswap32(*id_addresses[i]); + } + + return 16; +} + +#endif diff --git a/libraries/Adafruit_TinyUSB_Arduino/src/arduino/ports/samd/tusb_config_samd.h b/libraries/Adafruit_TinyUSB_Arduino/src/arduino/ports/samd/tusb_config_samd.h new file mode 100644 index 0000000..9de0902 --- /dev/null +++ b/libraries/Adafruit_TinyUSB_Arduino/src/arduino/ports/samd/tusb_config_samd.h @@ -0,0 +1,161 @@ +/* + * The MIT License (MIT) + * + * Copyright (c) 2018, hathach for Adafruit + * + * 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_SAMD_H_ +#define _TUSB_CONFIG_SAMD_H_ + +#ifdef __cplusplus +extern "C" { +#endif + +//-------------------------------------------------------------------- +// COMMON CONFIGURATION +//-------------------------------------------------------------------- +#ifdef __SAMD51__ +#define CFG_TUSB_MCU OPT_MCU_SAMD51 +#else +#define CFG_TUSB_MCU OPT_MCU_SAMD21 +#endif + +#ifndef CFG_TUSB_OS +#define CFG_TUSB_OS OPT_OS_NONE +#endif + +#ifndef CFG_TUSB_DEBUG +#define CFG_TUSB_DEBUG 0 +#endif + +// For selectively disable device log (when > CFG_TUSB_DEBUG) +// #define CFG_TUD_LOG_LEVEL 3 +// #define CFG_TUH_LOG_LEVEL 3 + +#define CFG_TUSB_MEM_SECTION +#define CFG_TUSB_MEM_ALIGN TU_ATTR_ALIGNED(4) + +// Enable device stack +#define CFG_TUD_ENABLED 1 + +// Enable host stack with MAX3421E (host shield) +#define CFG_TUH_ENABLED 1 +#define CFG_TUH_MAX3421 1 + +//-------------------------------------------------------------------- +// DEVICE CONFIGURATION +//-------------------------------------------------------------------- + +#define CFG_TUD_ENDPOINT0_SIZE 64 + +//------------- CLASS -------------// +#ifndef CFG_TUD_CDC +#define CFG_TUD_CDC 1 +#endif +#ifndef CFG_TUD_MSC +#define CFG_TUD_MSC 1 +#endif +#ifndef CFG_TUD_HID +#define CFG_TUD_HID 2 +#endif +#ifndef CFG_TUD_MIDI +#define CFG_TUD_MIDI 1 +#endif +#ifndef CFG_TUD_VENDOR +#define CFG_TUD_VENDOR 1 +#endif +#ifndef CFG_TUD_VIDEO +#define CFG_TUD_VIDEO 1 // number of video control interfaces +#endif +#ifndef CFG_TUD_VIDEO_STREAMING +#define CFG_TUD_VIDEO_STREAMING 1 // number of video streaming interfaces +#endif + +// video streaming endpoint buffer size +#define CFG_TUD_VIDEO_STREAMING_EP_BUFSIZE 256 + +// CDC FIFO size of TX and RX +#define CFG_TUD_CDC_RX_BUFSIZE 256 +#define CFG_TUD_CDC_TX_BUFSIZE 256 + +// MSC Buffer size of Device Mass storage +#define CFG_TUD_MSC_EP_BUFSIZE 512 + +// HID buffer size Should be sufficient to hold ID (if any) + Data +#define CFG_TUD_HID_EP_BUFSIZE 64 + +// MIDI FIFO size of TX and RX +#define CFG_TUD_MIDI_RX_BUFSIZE 128 +#define CFG_TUD_MIDI_TX_BUFSIZE 128 + +// Vendor FIFO size of TX and RX +#define CFG_TUD_VENDOR_RX_BUFSIZE 64 +#define CFG_TUD_VENDOR_TX_BUFSIZE 64 + +//-------------------------------------------------------------------- +// Host Configuration +//-------------------------------------------------------------------- + +// Size of buffer to hold descriptors and other data used for enumeration +#define CFG_TUH_ENUMERATION_BUFSIZE 256 + +// Number of hub devices +#define CFG_TUH_HUB 1 + +// max device support (excluding hub device): 1 hub typically has 4 ports +#define CFG_TUH_DEVICE_MAX (3 * CFG_TUH_HUB + 1) + +// Enable tuh_edpt_xfer() API +// #define CFG_TUH_API_EDPT_XFER 1 + +// Number of mass storage +#define CFG_TUH_MSC 1 + +// Number of HIDs +// typical keyboard + mouse device can have 3,4 HID interfaces +#define CFG_TUH_HID (3 * CFG_TUH_DEVICE_MAX) + +// Number of CDC interfaces +// FTDI and CP210x are not part of CDC class, only to re-use CDC driver API +#define CFG_TUH_CDC 1 +#define CFG_TUH_CDC_FTDI 1 +#define CFG_TUH_CDC_CP210X 1 +#define CFG_TUH_CDC_CH34X 1 + +// RX & TX fifo size +#define CFG_TUH_CDC_RX_BUFSIZE 64 +#define CFG_TUH_CDC_TX_BUFSIZE 64 + +// Set Line Control state on enumeration/mounted: +// DTR ( bit 0), RTS (bit 1) +#define CFG_TUH_CDC_LINE_CONTROL_ON_ENUM 0x03 + +// Set Line Coding on enumeration/mounted, value for cdc_line_coding_t +// bit rate = 115200, 1 stop bit, no parity, 8 bit data width +// This need Pico-PIO-USB at least 0.5.1 +#define CFG_TUH_CDC_LINE_CODING_ON_ENUM \ + { 115200, CDC_LINE_CONDING_STOP_BITS_1, CDC_LINE_CODING_PARITY_NONE, 8 } + +#ifdef __cplusplus +} +#endif + +#endif /* _TUSB_CONFIG_SAMD_H_ */ diff --git a/libraries/Adafruit_TinyUSB_Arduino/src/arduino/ports/stm32/Adafruit_TinyUSB_stm32.cpp b/libraries/Adafruit_TinyUSB_Arduino/src/arduino/ports/stm32/Adafruit_TinyUSB_stm32.cpp new file mode 100644 index 0000000..e785f70 --- /dev/null +++ b/libraries/Adafruit_TinyUSB_Arduino/src/arduino/ports/stm32/Adafruit_TinyUSB_stm32.cpp @@ -0,0 +1,104 @@ +/* + * The MIT License (MIT) + * + * Copyright (c) 2019, hathach for Adafruit + * + * 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_option.h" + +#if (defined(ARDUINO_ARCH_STM32) || \ + defined(ARDUINO_ARCH_ARDUINO_CORE_STM32)) && \ + CFG_TUD_ENABLED + +#define USE_HAL_DRIVER +#include "Arduino.h" +#include "arduino/Adafruit_TinyUSB_API.h" +#include "stm32f4xx_hal.h" +#include "stm32f4xx_hal_rcc.h" +#include "tusb.h" + +//--------------------------------------------------------------------+ +// Forward USB interrupt events to TinyUSB IRQ Handler +//--------------------------------------------------------------------+ +extern "C" { + +void OTG_FS_IRQHandler(void) { tud_int_handler(0); } + +void yield(void) { + tud_task(); + if (tud_cdc_connected()) { + tud_cdc_write_flush(); + } +} + +} // extern "C" + +//--------------------------------------------------------------------+ +// Porting API +//--------------------------------------------------------------------+ +void TinyUSB_Port_InitDevice(uint8_t rhport) { + (void)rhport; + + // Enable clocks FIRST + __HAL_RCC_GPIOA_CLK_ENABLE(); + __HAL_RCC_USB_OTG_FS_CLK_ENABLE(); + + // Configure USB pins (PA11 = DM, PA12 = DP) + GPIO_InitTypeDef GPIO_InitStruct = {}; + GPIO_InitStruct.Pin = GPIO_PIN_11 | GPIO_PIN_12; + GPIO_InitStruct.Mode = GPIO_MODE_AF_PP; + GPIO_InitStruct.Pull = GPIO_NOPULL; + GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_VERY_HIGH; + GPIO_InitStruct.Alternate = GPIO_AF10_OTG_FS; + HAL_GPIO_Init(GPIOA, &GPIO_InitStruct); + + // Enable USB IRQ + NVIC_SetPriority(OTG_FS_IRQn, 0); + NVIC_EnableIRQ(OTG_FS_IRQn); + + // Disable VBUS sensing (we're bus-powered, don't need it) + USB_OTG_FS->GCCFG |= USB_OTG_GCCFG_NOVBUSSENS; + USB_OTG_FS->GCCFG &= ~USB_OTG_GCCFG_VBUSBSEN; + USB_OTG_FS->GCCFG &= ~USB_OTG_GCCFG_VBUSASEN; + + // Initialize TinyUSB device stack + const tusb_rhport_init_t rh_init = { + .role = TUSB_ROLE_DEVICE, + .speed = TUD_OPT_HIGH_SPEED ? TUSB_SPEED_HIGH : TUSB_SPEED_FULL, + }; + tusb_init(rhport, &rh_init); +} + +void TinyUSB_Port_EnterDFU(void) { + // Optional - implement bootloader entry if needed +} + +uint8_t TinyUSB_Port_GetSerialNumber(uint8_t serial_id[16]) { + volatile uint32_t *uid = + (volatile uint32_t *)0x1FFF7A10; // STM32F411 UID base + uint32_t *serial_32 = (uint32_t *)serial_id; + serial_32[0] = uid[0]; + serial_32[1] = uid[1]; + serial_32[2] = uid[2]; + return 12; +} + +#endif // ARDUINO_ARCH_STM32 \ No newline at end of file diff --git a/libraries/Adafruit_TinyUSB_Arduino/src/arduino/ports/stm32/tusb_config_stm32.h b/libraries/Adafruit_TinyUSB_Arduino/src/arduino/ports/stm32/tusb_config_stm32.h new file mode 100644 index 0000000..9b71945 --- /dev/null +++ b/libraries/Adafruit_TinyUSB_Arduino/src/arduino/ports/stm32/tusb_config_stm32.h @@ -0,0 +1,76 @@ +/* + * The MIT License (MIT) + * + * Copyright (c) 2019, hathach for Adafruit + * + * 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_STM32_H_ +#define TUSB_CONFIG_STM32_H_ + +//-------------------------------------------------------------------- +// COMMON CONFIGURATION +//-------------------------------------------------------------------- +// MCU / OS +#define CFG_TUSB_MCU OPT_MCU_STM32F4 +#define CFG_TUSB_OS OPT_OS_NONE + +// Debug +#ifndef CFG_TUSB_DEBUG +#define CFG_TUSB_DEBUG 0 +#endif + +#define CFG_TUSB_MEM_SECTION +#define CFG_TUSB_MEM_ALIGN __attribute__((aligned(4))) + +//-------------------------------------------------------------------- +// DEVICE CONFIGURATION +//-------------------------------------------------------------------- +#define CFG_TUD_ENABLED 1 +#define CFG_TUD_MAX_SPEED OPT_MODE_FULL_SPEED // TODO some are highspeed +#define CFG_TUD_ENDPOINT0_SIZE 64 + +// Classes +#define CFG_TUD_CDC 1 +#define CFG_TUD_MSC 1 +#define CFG_TUD_HID 1 +#define CFG_TUD_MIDI 1 +#define CFG_TUD_VENDOR 1 + +// Buffer sizes +#define CFG_TUD_CDC_RX_BUFSIZE (TUD_OPT_HIGH_SPEED ? 512 : 64) +#define CFG_TUD_CDC_TX_BUFSIZE (TUD_OPT_HIGH_SPEED ? 512 : 64) +#define CFG_TUD_CDC_EP_BUFSIZE (TUD_OPT_HIGH_SPEED ? 512 : 64) +#define CFG_TUD_MSC_EP_BUFSIZE 512 +#define CFG_TUD_HID_EP_BUFSIZE 64 +#define CFG_TUD_MIDI_RX_BUFSIZE (TUD_OPT_HIGH_SPEED ? 512 : 128) +#define CFG_TUD_MIDI_TX_BUFSIZE (TUD_OPT_HIGH_SPEED ? 512 : 128) +#define CFG_TUD_VENDOR_RX_BUFSIZE (TUD_OPT_HIGH_SPEED ? 512 : 64) +#define CFG_TUD_VENDOR_TX_BUFSIZE (TUD_OPT_HIGH_SPEED ? 512 : 64) + +// Serial Redirect +#define Serial SerialTinyUSB + +//-------------------------------------------------------------------- +// Host Configuration +//-------------------------------------------------------------------- +#define CFG_TUH_ENABLED 0 // disable for now + +#endif diff --git a/libraries/Adafruit_TinyUSB_Arduino/src/arduino/video/Adafruit_USBD_Video.cpp b/libraries/Adafruit_TinyUSB_Arduino/src/arduino/video/Adafruit_USBD_Video.cpp new file mode 100644 index 0000000..34727f4 --- /dev/null +++ b/libraries/Adafruit_TinyUSB_Arduino/src/arduino/video/Adafruit_USBD_Video.cpp @@ -0,0 +1,269 @@ +/* + * The MIT License (MIT) + * + * Copyright (c) 2024 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. + * + * This file is part of the TinyUSB stack. + */ + +#include "tusb_option.h" + +#if CFG_TUD_ENABLED && CFG_TUD_VIDEO && CFG_TUD_VIDEO_STREAMING + +#include "Adafruit_USBD_Video.h" + +#define BULK_PACKET_SIZE (TUD_OPT_HIGH_SPEED ? 512 : 64) + +Adafruit_USBD_Video::Adafruit_USBD_Video(void) { + _vc_id = 0; + memset(&_camera_terminal, 0, sizeof(_camera_terminal)); +} + +bool Adafruit_USBD_Video::addTerminal( + tusb_desc_video_control_camera_terminal_t const *camera_terminal) { + _camera_terminal = *camera_terminal; + + // override constants + _camera_terminal.bLength = sizeof(tusb_desc_video_control_camera_terminal_t); + _camera_terminal.bDescriptorType = TUSB_DESC_CS_INTERFACE; + _camera_terminal.bDescriptorSubType = VIDEO_CS_ITF_VC_INPUT_TERMINAL; + + _camera_terminal.bControlSize = 3; + + return true; +} + +bool Adafruit_USBD_Video::addTerminal( + tusb_desc_video_control_output_terminal_t const *output_terminal) { + _output_terminal = *output_terminal; + + // override constants + _output_terminal.bLength = sizeof(tusb_desc_video_control_output_terminal_t); + _output_terminal.bDescriptorType = TUSB_DESC_CS_INTERFACE; + _output_terminal.bDescriptorSubType = VIDEO_CS_ITF_VC_OUTPUT_TERMINAL; + + return true; +} + +bool Adafruit_USBD_Video::addFormat( + tusb_desc_video_format_uncompressed_t const *format) { + _format.uncompressed = *format; + + // override constants + _format.uncompressed.bLength = sizeof(tusb_desc_video_format_uncompressed_t); + _format.uncompressed.bDescriptorType = TUSB_DESC_CS_INTERFACE; + _format.uncompressed.bDescriptorSubType = VIDEO_CS_ITF_VS_FORMAT_UNCOMPRESSED; + + return true; +} + +bool Adafruit_USBD_Video::addFrame( + tusb_desc_video_frame_uncompressed_continuous_t const *frame) { + _frame.uncompressed_cont = *frame; + + // override constants + _frame.uncompressed_cont.bLength = + sizeof(tusb_desc_video_frame_uncompressed_continuous_t); + _frame.uncompressed_cont.bDescriptorType = TUSB_DESC_CS_INTERFACE; + _frame.uncompressed_cont.bDescriptorSubType = + VIDEO_CS_ITF_VS_FRAME_UNCOMPRESSED; + _frame.uncompressed_cont.bFrameIntervalType = 0; // continuous + + return true; +} + +void Adafruit_USBD_Video::addColorMatching( + tusb_desc_video_streaming_color_matching_t const *color) { + _color_matching = *color; + + // override constants + _color_matching.bLength = sizeof(tusb_desc_video_streaming_color_matching_t); + _color_matching.bDescriptorType = TUSB_DESC_CS_INTERFACE; + _color_matching.bDescriptorSubType = VIDEO_CS_ITF_VS_COLORFORMAT; +} + +bool Adafruit_USBD_Video::begin() { + if (!TinyUSBDevice.addInterface(*this)) { + return false; + } + + return true; +} + +uint16_t Adafruit_USBD_Video::getInterfaceDescriptor(uint8_t itfnum_deprecated, + uint8_t *buf, + uint16_t bufsize) { + (void)itfnum_deprecated; + + uint8_t itfnum = 0; + uint8_t ep_in = 0; + + // check if necessary descriptors are added + if (!(_camera_terminal.bLength && _output_terminal.bLength && + _format.uncompressed.bLength && _frame.uncompressed_cont.bLength && + _color_matching.bLength)) { + return 0; + } + + // Null buf is for length only + if (buf) { + itfnum = TinyUSBDevice.allocInterface(2); + ep_in = TinyUSBDevice.allocEndpoint(TUSB_DIR_IN); + } + + typedef struct TU_ATTR_PACKED { + tusb_desc_interface_t itf; + tusb_desc_video_control_header_1itf_t header; + tusb_desc_video_control_camera_terminal_t camera_terminal; + tusb_desc_video_control_output_terminal_t output_terminal; + } uvc_control_desc_t; + + // hard code for now +#define UVC_CLOCK_FREQUENCY 27000000 + + /* Windows support YUY2 and NV12 + * https://docs.microsoft.com/en-us/windows-hardware/drivers/stream/usb-video-class-driver-overview + */ + typedef struct TU_ATTR_PACKED { + tusb_desc_interface_t itf; + tusb_desc_video_streaming_input_header_1byte_t header; + tusb_desc_video_format_uncompressed_t format; + tusb_desc_video_frame_uncompressed_continuous_t frame; + tusb_desc_video_streaming_color_matching_t color; + + // #if USE_ISO_STREAMING + // // For ISO streaming, USB spec requires to alternate interface + // tusb_desc_interface_t itf_alt; + // #endif + + tusb_desc_endpoint_t ep; + } uvc_streaming_desc_t; + + const tusb_desc_interface_assoc_t desc_iad = { + .bLength = sizeof(tusb_desc_interface_assoc_t), + .bDescriptorType = TUSB_DESC_INTERFACE_ASSOCIATION, + + .bFirstInterface = itfnum, + .bInterfaceCount = 2, + .bFunctionClass = TUSB_CLASS_VIDEO, + .bFunctionSubClass = VIDEO_SUBCLASS_INTERFACE_COLLECTION, + .bFunctionProtocol = VIDEO_ITF_PROTOCOL_UNDEFINED, + .iFunction = 0}; + + uvc_control_desc_t desc_video_control{ + .itf = {.bLength = sizeof(tusb_desc_interface_t), + .bDescriptorType = TUSB_DESC_INTERFACE, + + .bInterfaceNumber = itfnum, + .bAlternateSetting = 0, + .bNumEndpoints = 0, + .bInterfaceClass = TUSB_CLASS_VIDEO, + .bInterfaceSubClass = VIDEO_SUBCLASS_CONTROL, + .bInterfaceProtocol = VIDEO_ITF_PROTOCOL_15, + .iInterface = _strid}, + .header = {.bLength = sizeof(tusb_desc_video_control_header_1itf_t), + .bDescriptorType = TUSB_DESC_CS_INTERFACE, + .bDescriptorSubType = VIDEO_CS_ITF_VC_HEADER, + + .bcdUVC = VIDEO_BCD_1_50, + .wTotalLength = + sizeof(uvc_control_desc_t) - + sizeof(tusb_desc_interface_t), // CS VC descriptors only + .dwClockFrequency = UVC_CLOCK_FREQUENCY, + .bInCollection = 1, + .baInterfaceNr = {(uint8_t)(itfnum + 1)}}, + .camera_terminal = _camera_terminal, + .output_terminal = _output_terminal}; + + uvc_streaming_desc_t desc_video_streaming = { + .itf = + { + .bLength = sizeof(tusb_desc_interface_t), + .bDescriptorType = TUSB_DESC_INTERFACE, + + .bInterfaceNumber = (uint8_t)(itfnum + 1), + .bAlternateSetting = 0, + .bNumEndpoints = 1, // bulk 1, iso 0 + .bInterfaceClass = TUSB_CLASS_VIDEO, + .bInterfaceSubClass = VIDEO_SUBCLASS_STREAMING, + .bInterfaceProtocol = VIDEO_ITF_PROTOCOL_15, + .iInterface = _strid, + }, + .header = {.bLength = + sizeof(tusb_desc_video_streaming_input_header_1byte_t), + .bDescriptorType = TUSB_DESC_CS_INTERFACE, + .bDescriptorSubType = VIDEO_CS_ITF_VS_INPUT_HEADER, + + .bNumFormats = 1, + .wTotalLength = + sizeof(uvc_streaming_desc_t) - + sizeof(tusb_desc_interface_t) - + sizeof(tusb_desc_endpoint_t), // CS VS descriptors only + .bEndpointAddress = ep_in, + .bmInfo = 0, + .bTerminalLink = _output_terminal.bTerminalID, + .bStillCaptureMethod = 0, + .bTriggerSupport = 0, + .bTriggerUsage = 0, + .bControlSize = 1, + .bmaControls = {0}}, + .format = _format.uncompressed, + .frame = _frame.uncompressed_cont, + .color = _color_matching, + .ep = {.bLength = sizeof(tusb_desc_endpoint_t), + .bDescriptorType = TUSB_DESC_ENDPOINT, + + .bEndpointAddress = ep_in, + .bmAttributes = {.xfer = TUSB_XFER_BULK, .sync = 0, .usage = 0}, + .wMaxPacketSize = BULK_PACKET_SIZE, + .bInterval = 1}}; + + uint16_t const len_iad = sizeof(desc_iad); + uint16_t const len_vc = sizeof(desc_video_control); + uint16_t const len_vs = sizeof(desc_video_streaming); + uint16_t const len_total = len_iad + len_vc + len_vs; + + if (buf) { + if (bufsize < len_total) { + return 0; + } + + memcpy(buf, &desc_iad, len_iad); + buf += len_iad; + + memcpy(buf, &desc_video_control, len_vc); + buf += len_vc; + + memcpy(buf, &desc_video_streaming, len_vs); + } + + return len_total; +} + +//--------------------------------------------------------------------+ +// API +//--------------------------------------------------------------------+ + +// bool Adafruit_USBD_Video::isStreaming(uint8_t stream_idx) { +// return tud_video_n_streaming(_vc_id, stream_idx); +// } + +#endif diff --git a/libraries/Adafruit_TinyUSB_Arduino/src/arduino/video/Adafruit_USBD_Video.h b/libraries/Adafruit_TinyUSB_Arduino/src/arduino/video/Adafruit_USBD_Video.h new file mode 100644 index 0000000..635f49d --- /dev/null +++ b/libraries/Adafruit_TinyUSB_Arduino/src/arduino/video/Adafruit_USBD_Video.h @@ -0,0 +1,78 @@ +/* + * The MIT License (MIT) + * + * Copyright (c) 2024 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. + * + * This file is part of the TinyUSB stack. + */ + +#ifndef ADAFRUIT_USBD_VIDEO_H +#define ADAFRUIT_USBD_VIDEO_H + +#include "arduino/Adafruit_USBD_Device.h" + +class Adafruit_USBD_Video : public Adafruit_USBD_Interface { +public: + Adafruit_USBD_Video(void); + + bool begin(); + + //------------- Video Control -------------// + // bool isStreaming(uint8_t stream_idx); + bool + addTerminal(tusb_desc_video_control_camera_terminal_t const *camera_terminal); + bool + addTerminal(tusb_desc_video_control_output_terminal_t const *output_terminal); + + //------------- Video Streaming -------------// + // bool setIsochronousStreaming(bool enabled); + + // Add format descriptor, return format index + bool addFormat(tusb_desc_video_format_uncompressed_t const *format); + bool addFrame(tusb_desc_video_frame_uncompressed_continuous_t const *frame); + void + addColorMatching(tusb_desc_video_streaming_color_matching_t const *color); + + // from Adafruit_USBD_Interface + virtual uint16_t getInterfaceDescriptor(uint8_t itfnum_deprecated, + uint8_t *buf, uint16_t bufsize); + +private: + uint8_t _vc_id; + + tusb_desc_video_control_camera_terminal_t _camera_terminal; + tusb_desc_video_control_output_terminal_t _output_terminal; + + // currently only support 1 format + union { + tusb_desc_video_format_uncompressed_t uncompressed; + tusb_desc_video_format_mjpeg_t mjpeg; + } _format; + + union { + tusb_desc_video_frame_uncompressed_continuous_t uncompressed_cont; + tusb_desc_video_frame_mjpeg_continuous_t mjpeg; + } _frame; + + tusb_desc_video_streaming_color_matching_t _color_matching; +}; + +#endif diff --git a/libraries/Adafruit_TinyUSB_Arduino/src/arduino/webusb/Adafruit_USBD_WebUSB.cpp b/libraries/Adafruit_TinyUSB_Arduino/src/arduino/webusb/Adafruit_USBD_WebUSB.cpp new file mode 100644 index 0000000..435c0d9 --- /dev/null +++ b/libraries/Adafruit_TinyUSB_Arduino/src/arduino/webusb/Adafruit_USBD_WebUSB.cpp @@ -0,0 +1,322 @@ +/* + * The MIT License (MIT) + * + * Copyright (c) 2019 hathach for Adafruit Industries + * + * 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_option.h" + +#if CFG_TUD_ENABLED && CFG_TUD_VENDOR + +#include "Adafruit_USBD_WebUSB.h" +#include "Arduino.h" + +#ifdef ARDUINO_ARCH_ESP32 +#include "USB.h" +#endif + +//--------------------------------------------------------------------+ +// MACRO TYPEDEF CONSTANT ENUM DECLARATION +//--------------------------------------------------------------------+ +#define BULK_PACKET_SIZE (TUD_OPT_HIGH_SPEED ? 512 : 64) + +enum { VENDOR_REQUEST_WEBUSB = 1, VENDOR_REQUEST_MICROSOFT = 2 }; + +// TODO multiple instances +static Adafruit_USBD_WebUSB *_webusb_dev = NULL; + +//--------------------------------------------------------------------+ +// BOS Descriptor +//--------------------------------------------------------------------+ + +/* Microsoft OS 2.0 registry property descriptor +Per MS requirements +https://msdn.microsoft.com/en-us/library/windows/hardware/hh450799(v=vs.85).aspx +device should create DeviceInterfaceGUIDs. It can be done by driver and +in case of real PnP solution device should expose MS "Microsoft OS 2.0 +registry property descriptor". Such descriptor can insert any record +into Windows registry per device/configuration/interface. In our case it +will insert "DeviceInterfaceGUIDs" multistring property. + +GUID is freshly generated and should be OK to use. + +https://developers.google.com/web/fundamentals/native-hardware/build-for-webusb/ +(Section Microsoft OS compatibility descriptors) +*/ + +#define BOS_TOTAL_LEN \ + (TUD_BOS_DESC_LEN + TUD_BOS_WEBUSB_DESC_LEN + TUD_BOS_MICROSOFT_OS_DESC_LEN) + +#define MS_OS_20_DESC_LEN 0xB2 + +// BOS Descriptor is required for webUSB +uint8_t const desc_bos[] = { + // total length, number of device caps + TUD_BOS_DESCRIPTOR(BOS_TOTAL_LEN, 2), + + // Vendor Code, iLandingPage + TUD_BOS_WEBUSB_DESCRIPTOR(VENDOR_REQUEST_WEBUSB, 1), + + // Microsoft OS 2.0 descriptor + TUD_BOS_MS_OS_20_DESCRIPTOR(MS_OS_20_DESC_LEN, VENDOR_REQUEST_MICROSOFT)}; + +uint8_t desc_ms_os_20[] = { + // Set header: length, type, windows version, total length + U16_TO_U8S_LE(0x000A), U16_TO_U8S_LE(MS_OS_20_SET_HEADER_DESCRIPTOR), + U32_TO_U8S_LE(0x06030000), U16_TO_U8S_LE(MS_OS_20_DESC_LEN), + + // Configuration subset header: length, type, configuration index, reserved, + // configuration total length + U16_TO_U8S_LE(0x0008), U16_TO_U8S_LE(MS_OS_20_SUBSET_HEADER_CONFIGURATION), + 0, 0, U16_TO_U8S_LE(MS_OS_20_DESC_LEN - 0x0A), + + // Function Subset header: length, type, first interface, reserved, subset + // length + U16_TO_U8S_LE(0x0008), U16_TO_U8S_LE(MS_OS_20_SUBSET_HEADER_FUNCTION), + 0 /*itf num*/, 0, U16_TO_U8S_LE(MS_OS_20_DESC_LEN - 0x0A - 0x08), + + // MS OS 2.0 Compatible ID descriptor: length, type, compatible ID, sub + // compatible ID + U16_TO_U8S_LE(0x0014), U16_TO_U8S_LE(MS_OS_20_FEATURE_COMPATBLE_ID), 'W', + 'I', 'N', 'U', 'S', 'B', 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, // sub-compatible + + // MS OS 2.0 Registry property descriptor: length, type + U16_TO_U8S_LE(MS_OS_20_DESC_LEN - 0x0A - 0x08 - 0x08 - 0x14), + U16_TO_U8S_LE(MS_OS_20_FEATURE_REG_PROPERTY), U16_TO_U8S_LE(0x0007), + U16_TO_U8S_LE(0x002A), // wPropertyDataType, wPropertyNameLength and + // PropertyName "DeviceInterfaceGUIDs\0" in UTF-16 + 'D', 0x00, 'e', 0x00, 'v', 0x00, 'i', 0x00, 'c', 0x00, 'e', 0x00, 'I', 0x00, + 'n', 0x00, 't', 0x00, 'e', 0x00, 'r', 0x00, 'f', 0x00, 'a', 0x00, 'c', 0x00, + 'e', 0x00, 'G', 0x00, 'U', 0x00, 'I', 0x00, 'D', 0x00, 's', 0x00, 0x00, + 0x00, + U16_TO_U8S_LE(0x0050), // wPropertyDataLength + // bPropertyData: “{975F44D9-0D08-43FD-8B3E-127CA8AFFF9D}”. + '{', 0x00, '9', 0x00, '7', 0x00, '5', 0x00, 'F', 0x00, '4', 0x00, '4', 0x00, + 'D', 0x00, '9', 0x00, '-', 0x00, '0', 0x00, 'D', 0x00, '0', 0x00, '8', 0x00, + '-', 0x00, '4', 0x00, '3', 0x00, 'F', 0x00, 'D', 0x00, '-', 0x00, '8', 0x00, + 'B', 0x00, '3', 0x00, 'E', 0x00, '-', 0x00, '1', 0x00, '2', 0x00, '7', 0x00, + 'C', 0x00, 'A', 0x00, '8', 0x00, 'A', 0x00, 'F', 0x00, 'F', 0x00, 'F', 0x00, + '9', 0x00, 'D', 0x00, '}', 0x00, 0x00, 0x00, 0x00, 0x00}; + +TU_VERIFY_STATIC(sizeof(desc_ms_os_20) == MS_OS_20_DESC_LEN, "Incorrect size"); + +//--------------------------------------------------------------------+ +// IMPLEMENTATION +//--------------------------------------------------------------------+ +Adafruit_USBD_WebUSB::Adafruit_USBD_WebUSB(const void *url) { + _connected = false; + _url = (const uint8_t *)url; + _linestate_cb = NULL; +} + +bool Adafruit_USBD_WebUSB::begin(void) { + if (!TinyUSBDevice.addInterface(*this)) { + return false; + } + + // WebUSB requires USB version at least 2.1 (or 3.x) + TinyUSBDevice.setVersion(0x0210); + + _webusb_dev = this; + return true; +} + +bool Adafruit_USBD_WebUSB::setLandingPage(const void *url) { + _url = (const uint8_t *)url; + return true; +} + +void Adafruit_USBD_WebUSB::setLineStateCallback(linestate_callback_t fp) { + _linestate_cb = fp; +} + +uint16_t Adafruit_USBD_WebUSB::getInterfaceDescriptor(uint8_t itfnum_deprecated, + uint8_t *buf, + uint16_t bufsize) { + (void)itfnum_deprecated; + + if (!buf) { + return TUD_VENDOR_DESC_LEN; + } + + uint8_t const itfnum = TinyUSBDevice.allocInterface(1); + uint8_t const ep_in = TinyUSBDevice.allocEndpoint(TUSB_DIR_IN); + uint8_t const ep_out = TinyUSBDevice.allocEndpoint(TUSB_DIR_OUT); + + uint8_t desc[] = { + TUD_VENDOR_DESCRIPTOR(itfnum, _strid, ep_out, ep_in, BULK_PACKET_SIZE)}; + uint16_t const len = sizeof(desc); + + // null buffer for length only + if (bufsize < len) { + return 0; + } + + memcpy(buf, desc, len); + + // update the bFirstInterface in MS OS 2.0 descriptor + // that is bound to WinUSB driver + desc_ms_os_20[0x0a + 0x08 + 4] = itfnum; + + return len; +} + +bool Adafruit_USBD_WebUSB::connected(void) { + return tud_vendor_mounted() && _connected; +} + +Adafruit_USBD_WebUSB::operator bool() { + // Add an yield to run usb background in case sketch block wait as follows + // while( !webusb ) {} + if (!connected()) { + yield(); + } + + return connected(); +} + +int Adafruit_USBD_WebUSB::available(void) { + uint32_t count = tud_vendor_available(); + + // Add an yield to run usb background in case sketch block wait as follows + // while( !webusb.available() ) {} + if (!count) { + yield(); + } + + return count; +} + +int Adafruit_USBD_WebUSB::read(void) { + uint8_t ch; + return tud_vendor_read(&ch, 1) ? (int)ch : -1; +} + +size_t Adafruit_USBD_WebUSB::read(uint8_t *buffer, size_t size) { + return tud_vendor_read(buffer, size); +} + +size_t Adafruit_USBD_WebUSB::write(uint8_t b) { return this->write(&b, 1); } + +size_t Adafruit_USBD_WebUSB::write(const uint8_t *buffer, size_t size) { + size_t remain = size; + while (remain && _connected) { + size_t wrcount = tud_vendor_write(buffer, remain); + remain -= wrcount; + buffer += wrcount; + + // Write FIFO is full, run usb background to flush + if (remain) { + yield(); + } + } + + return size - remain; +} + +int Adafruit_USBD_WebUSB::peek(void) { + uint8_t ch; + return tud_vendor_peek(&ch) ? (int)ch : -1; +} + +void Adafruit_USBD_WebUSB::flush(void) { tud_vendor_flush(); } + +//--------------------------------------------------------------------+ +// TinyUSB stack callbacks +//--------------------------------------------------------------------+ +extern "C" { + +TU_ATTR_WEAK uint8_t const *tud_descriptor_bos_cb(void) { return desc_bos; } + +// Invoked when a control transfer occurred on an interface of this class +// Driver response accordingly to the request and the transfer stage +// (setup/data/ack) return false to stall control endpoint (e.g unsupported +// request) +TU_ATTR_WEAK bool +tud_vendor_control_xfer_cb(uint8_t rhport, uint8_t stage, + tusb_control_request_t const *request) { + if (!_webusb_dev) { + return false; + } + + // nothing to with DATA & ACK stage + if (stage != CONTROL_STAGE_SETUP) { + return true; + } + + switch (request->bmRequestType_bit.type) { + case TUSB_REQ_TYPE_VENDOR: + switch (request->bRequest) { + case VENDOR_REQUEST_WEBUSB: + // match vendor request in BOS descriptor + // Get landing page url + if (!_webusb_dev->_url) { + return false; + } + return tud_control_xfer(rhport, request, (void *)_webusb_dev->_url, + _webusb_dev->_url[0]); + + case VENDOR_REQUEST_MICROSOFT: + if (request->wIndex == 7) { + // Get Microsoft OS 2.0 compatible descriptor + uint16_t total_len; + memcpy(&total_len, desc_ms_os_20 + 8, 2); + + return tud_control_xfer(rhport, request, (void *)desc_ms_os_20, + total_len); + } else { + return false; + } + + default: + break; + } + break; + + case TUSB_REQ_TYPE_CLASS: + if (request->bRequest == 0x22) { + // Webserial simulate the CDC_REQUEST_SET_CONTROL_LINE_STATE (0x22) to + // connect and disconnect. + _webusb_dev->_connected = (request->wValue != 0); + + // response with status OK + tud_control_status(rhport, request); + + // invoked callback if any (TODO should be done at ACK stage) + if (_webusb_dev->_linestate_cb) { + _webusb_dev->_linestate_cb(_webusb_dev->_connected); + } + + return true; + } + break; + + default: + // stall unknown request + return false; + } + + return true; +} +} + +#endif // CFG_TUD_ENABLED diff --git a/libraries/Adafruit_TinyUSB_Arduino/src/arduino/webusb/Adafruit_USBD_WebUSB.h b/libraries/Adafruit_TinyUSB_Arduino/src/arduino/webusb/Adafruit_USBD_WebUSB.h new file mode 100644 index 0000000..86d5b53 --- /dev/null +++ b/libraries/Adafruit_TinyUSB_Arduino/src/arduino/webusb/Adafruit_USBD_WebUSB.h @@ -0,0 +1,81 @@ +/* + * The MIT License (MIT) + * + * Copyright (c) 2019 hathach for Adafruit Industries + * + * 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 ADAFRUIT_USBD_WEBUSB_H_ +#define ADAFRUIT_USBD_WEBUSB_H_ + +#include "Stream.h" +#include "arduino/Adafruit_USBD_Device.h" + +#define WEBUSB_URL_DEF(_name, _scheme, _url) \ + struct TU_ATTR_PACKED { \ + uint8_t bLength; \ + uint8_t bDescriptorType; \ + uint8_t bScheme; \ + char url[3 + sizeof(_url)]; \ + } const _name = {3 + sizeof(_url) - 1, 3, _scheme, _url} + +class Adafruit_USBD_WebUSB : public Stream, public Adafruit_USBD_Interface { +public: + typedef void (*linestate_callback_t)(bool connected); + Adafruit_USBD_WebUSB(const void *url = NULL); + + bool begin(void); + + bool setLandingPage(const void *url); + void setLineStateCallback(linestate_callback_t fp); + + // Stream API + virtual int available(void); + virtual int peek(void); + + virtual int read(void); + size_t read(uint8_t *buffer, size_t size); + + virtual void flush(void); + virtual size_t write(uint8_t b); + + virtual size_t write(const uint8_t *buffer, size_t size); + size_t write(const char *buffer, size_t size) { + return write((const uint8_t *)buffer, size); + } + + bool connected(void); + operator bool(); + + // from Adafruit_USBD_Interface + virtual uint16_t getInterfaceDescriptor(uint8_t itfnum_deprecated, + uint8_t *buf, uint16_t bufsize); + +private: + bool _connected; + const uint8_t *_url; + linestate_callback_t _linestate_cb; + + // Make all tinyusb callback friend to access private data + friend bool tud_vendor_control_xfer_cb(uint8_t rhport, uint8_t stage, + tusb_control_request_t const *request); +}; + +#endif /* ADAFRUIT_USBD_WEBUSB_H_ */ diff --git a/libraries/Adafruit_TinyUSB_Arduino/src/class/audio/audio.h b/libraries/Adafruit_TinyUSB_Arduino/src/class/audio/audio.h new file mode 100644 index 0000000..cff38cc --- /dev/null +++ b/libraries/Adafruit_TinyUSB_Arduino/src/class/audio/audio.h @@ -0,0 +1,1299 @@ +/* + * The MIT License (MIT) + * + * Copyright (c) 2019 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. + */ + +/** \ingroup group_class + * \defgroup ClassDriver_Audio Audio + * Currently only MIDI subclass is supported + * @{ */ + +#ifndef TUSB_AUDIO_H__ +#define TUSB_AUDIO_H__ + +#include "common/tusb_common.h" + +#ifdef __cplusplus +extern "C" { +#endif + +//--------------------------------------------------------------------+ +// GENERIC AUDIO CLASS CODES (COMMON TO UAC1 AND UAC2) +//--------------------------------------------------------------------+ + +/// A.2 - Audio Function Subclass Codes +typedef enum { + AUDIO_FUNCTION_SUBCLASS_UNDEFINED = 0x00, +} audio_function_subclass_type_t; + +/// A.3 - Audio Function Protocol Codes +typedef enum { + AUDIO_FUNC_PROTOCOL_CODE_UNDEF = 0x00, + AUDIO_FUNC_PROTOCOL_CODE_V1 = 0x00,///< Version 1.0 - same as undefined for backward compatibility + AUDIO_FUNC_PROTOCOL_CODE_V2 = 0x20,///< Version 2.0 +} audio_function_protocol_code_t; + +/// A.5 - Audio Interface Subclass Codes +typedef enum { + AUDIO_SUBCLASS_UNDEFINED = 0x00, + AUDIO_SUBCLASS_CONTROL, ///< Audio Control + AUDIO_SUBCLASS_STREAMING, ///< Audio Streaming + AUDIO_SUBCLASS_MIDI_STREAMING,///< MIDI Streaming +} audio_subclass_type_t; + +/// A.6 - Audio Interface Protocol Codes +typedef enum { + AUDIO_INT_PROTOCOL_CODE_UNDEF = 0x00, + AUDIO_INT_PROTOCOL_CODE_V1 = 0x00,///< Version 1.0 - same as undefined for backward compatibility + AUDIO_INT_PROTOCOL_CODE_V2 = 0x20,///< Version 2.0 +} audio_interface_protocol_code_t; + +/// Terminal Types + +/// 2.1 - Audio Class-Terminal Types +typedef enum { + AUDIO_TERM_TYPE_USB_UNDEFINED = 0x0100, + AUDIO_TERM_TYPE_USB_STREAMING = 0x0101, + AUDIO_TERM_TYPE_USB_VENDOR_SPEC = 0x01FF, +} audio_terminal_type_t; + +/// 2.2 - Audio Class-Input Terminal Types +typedef enum { + AUDIO_TERM_TYPE_IN_UNDEFINED = 0x0200, + AUDIO_TERM_TYPE_IN_GENERIC_MIC = 0x0201, + AUDIO_TERM_TYPE_IN_DESKTOP_MIC = 0x0202, + AUDIO_TERM_TYPE_IN_PERSONAL_MIC = 0x0203, + AUDIO_TERM_TYPE_IN_OMNI_MIC = 0x0204, + AUDIO_TERM_TYPE_IN_ARRAY_MIC = 0x0205, + AUDIO_TERM_TYPE_IN_PROC_ARRAY_MIC = 0x0206, +} audio_terminal_input_type_t; + +/// 2.3 - Audio Class-Output Terminal Types +typedef enum { + AUDIO_TERM_TYPE_OUT_UNDEFINED = 0x0300, + AUDIO_TERM_TYPE_OUT_GENERIC_SPEAKER = 0x0301, + AUDIO_TERM_TYPE_OUT_HEADPHONES = 0x0302, + AUDIO_TERM_TYPE_OUT_HEAD_MNT_DISP_AUIDO = 0x0303, + AUDIO_TERM_TYPE_OUT_DESKTOP_SPEAKER = 0x0304, + AUDIO_TERM_TYPE_OUT_ROOM_SPEAKER = 0x0305, + AUDIO_TERM_TYPE_OUT_COMMUNICATION_SPEAKER = 0x0306, + AUDIO_TERM_TYPE_OUT_LOW_FRQ_EFFECTS_SPEAKER = 0x0307, +} audio_terminal_output_type_t; + +/// Rest is yet to be implemented + +//--------------------------------------------------------------------+ +// USB AUDIO CLASS 1.0 (UAC1) DEFINITIONS +//--------------------------------------------------------------------+ + +/// A.5 - Audio Class-Specific AC Interface Descriptor Subtypes UAC1 +typedef enum { + AUDIO10_CS_AC_INTERFACE_AC_DESCRIPTOR_UNDEF = 0x00, + AUDIO10_CS_AC_INTERFACE_HEADER = 0x01, + AUDIO10_CS_AC_INTERFACE_INPUT_TERMINAL = 0x02, + AUDIO10_CS_AC_INTERFACE_OUTPUT_TERMINAL = 0x03, + AUDIO10_CS_AC_INTERFACE_MIXER_UNIT = 0x04, + AUDIO10_CS_AC_INTERFACE_SELECTOR_UNIT = 0x05, + AUDIO10_CS_AC_INTERFACE_FEATURE_UNIT = 0x06, + AUDIO10_CS_AC_INTERFACE_PROCESSING_UNIT = 0x07, + AUDIO10_CS_AC_INTERFACE_EXTENSION_UNIT = 0x08, +} audio10_cs_ac_interface_subtype_t; + +/// A.6 - Audio Class-Specific AS Interface Descriptor Subtypes UAC1 +typedef enum { + AUDIO10_CS_AS_INTERFACE_AS_DESCRIPTOR_UNDEF = 0x00, + AUDIO10_CS_AS_INTERFACE_AS_GENERAL = 0x01, + AUDIO10_CS_AS_INTERFACE_FORMAT_TYPE = 0x02, +} audio10_cs_as_interface_subtype_t; + +/// A.8 - Audio Class-Specific EP Descriptor Subtypes UAC1 +typedef enum { + AUDIO10_CS_EP_SUBTYPE_UNDEF = 0x00, + AUDIO10_CS_EP_SUBTYPE_GENERAL = 0x01, +} audio10_cs_ep_subtype_t; + +/// A.9 - Audio Class-Specific Request Codes UAC1 +typedef enum { + AUDIO10_CS_REQ_UNDEF = 0x00, + AUDIO10_CS_REQ_SET_CUR = 0x01, + AUDIO10_CS_REQ_GET_CUR = 0x81, + AUDIO10_CS_REQ_SET_MIN = 0x02, + AUDIO10_CS_REQ_GET_MIN = 0x82, + AUDIO10_CS_REQ_SET_MAX = 0x03, + AUDIO10_CS_REQ_GET_MAX = 0x83, + AUDIO10_CS_REQ_SET_RES = 0x04, + AUDIO10_CS_REQ_GET_RES = 0x84, + AUDIO10_CS_REQ_SET_MEM = 0x05, + AUDIO10_CS_REQ_GET_MEM = 0x85, + AUDIO10_CS_REQ_GET_STAT = 0xFF, +} audio10_cs_req_t; + +/// A.10.1 - Terminal Control Selectors UAC1 +typedef enum { + AUDIO10_TE_CTRL_UNDEF = 0x00, + AUDIO10_TE_CTRL_COPY_PROTECT = 0x01, +} audio10_terminal_control_selector_t; + +/// A.10.2 - Feature Unit Control Selectors UAC1 +typedef enum { + AUDIO10_FU_CTRL_UNDEF = 0x00, + AUDIO10_FU_CTRL_MUTE = 0x01, + AUDIO10_FU_CTRL_VOLUME = 0x02, + AUDIO10_FU_CTRL_BASS = 0x03, + AUDIO10_FU_CTRL_MID = 0x04, + AUDIO10_FU_CTRL_TREBLE = 0x05, + AUDIO10_FU_CTRL_GRAPHIC_EQUALIZER = 0x06, + AUDIO10_FU_CTRL_AGC = 0x07, + AUDIO10_FU_CTRL_DELAY = 0x08, + AUDIO10_FU_CTRL_BASS_BOOST = 0x09, + AUDIO10_FU_CTRL_LOUDNESS = 0x0A, +} audio10_feature_unit_control_selector_t; + +/// A.10.3.1 - Up/Down-mix Processing Unit Control Selectors UAC1 +typedef enum { + AUDIO10_UD_CTRL_UNDEF = 0x00, + AUDIO10_UD_CTRL_ENABLE = 0x01, + AUDIO10_UD_CTRL_MODE_SELECT = 0x02, +} audio10_up_down_mix_control_selector_t; + +/// A.10.3.2 - Dolby Prologic Processing Unit Control Selectors UAC1 +typedef enum { + AUDIO10_DP_CTRL_UNDEF = 0x00, + AUDIO10_DP_CTRL_ENABLE = 0x01, + AUDIO10_DP_CTRL_MODE_SELECT = 0x02, +} audio10_dolby_prologic_control_selector_t; + +/// A.10.3.3 - 3D Stereo Extender Processing Unit Control Selectors UAC1 +typedef enum { + AUDIO10_3D_CTRL_UNDEF = 0x00, + AUDIO10_3D_CTRL_ENABLE = 0x01, + AUDIO10_3D_CTRL_SPACIOUSNESS = 0x02, +} audio10_3d_stereo_extender_control_selector_t; + +/// A.10.3.4 - Reverberation Processing Unit Control Selectors UAC1 +typedef enum { + AUDIO10_RV_CTRL_UNDEF = 0x00, + AUDIO10_RV_CTRL_ENABLE = 0x01, + AUDIO10_RV_CTRL_REVERB_LEVEL = 0x02, + AUDIO10_RV_CTRL_REVERB_TIME = 0x03, + AUDIO10_RV_CTRL_REVERB_FEEDBACK = 0x04, +} audio10_reverberation_control_selector_t; + +/// A.10.3.5 - Chorus Processing Unit Control Selectors UAC1 +typedef enum { + AUDIO10_CH_CTRL_UNDEF = 0x00, + AUDIO10_CH_CTRL_ENABLE = 0x01, + AUDIO10_CH_CTRL_CHORUS_LEVEL = 0x02, + AUDIO10_CH_CTRL_CHORUS_RATE = 0x03, + AUDIO10_CH_CTRL_CHORUS_DEPTH = 0x04, +} audio10_chorus_control_selector_t; + +/// A.10.3.6 - Dynamic Range Compressor Processing Unit Control Selectors UAC1 +typedef enum { + AUDIO10_DR_CTRL_UNDEF = 0x00, + AUDIO10_DR_CTRL_ENABLE = 0x01, + AUDIO10_DR_CTRL_COMPRESSION_RATE = 0x02, + AUDIO10_DR_CTRL_MAXAMPL = 0x03, + AUDIO10_DR_CTRL_THRESHOLD = 0x04, + AUDIO10_DR_CTRL_ATTACK_TIME = 0x05, + AUDIO10_DR_CTRL_RELEASE_TIME = 0x06, +} audio10_dynamic_range_compression_control_selector_t; + +/// A.10.4 - Extension Unit Control Selectors UAC1 +typedef enum { + AUDIO10_XU_CTRL_UNDEF = 0x00, + AUDIO10_XU_CTRL_ENABLE = 0x01, +} audio10_extension_unit_control_selector_t; + +/// A.10.5 - Endpoint Control Selectors UAC1 +typedef enum { + AUDIO10_EP_CTRL_UNDEF = 0x00, + AUDIO10_EP_CTRL_SAMPLING_FREQ = 0x01, + AUDIO10_EP_CTRL_PITCH = 0x02, +} audio10_ep_control_selector_t; + +/// Audio Class-Specific AS Isochronous Data EP Attributes UAC1 +typedef enum { + AUDIO10_CS_AS_ISO_DATA_EP_ATT_MAX_PACKETS_ONLY = 0x80, + AUDIO10_CS_AS_ISO_DATA_EP_ATT_NON_MAX_PACKETS_OK = 0x00, + AUDIO10_CS_AS_ISO_DATA_EP_ATT_SAMPLING_FRQ = 0x01, + AUDIO10_CS_AS_ISO_DATA_EP_ATT_PITCH = 0x02, +} audio10_cs_as_iso_data_ep_attribute_t; + +/// Audio Class-Specific AS Isochronous Data EP Lock Delay Units UAC1 +typedef enum { + AUDIO10_CS_AS_ISO_DATA_EP_LOCK_DELAY_UNIT_UNDEFINED = 0x00, + AUDIO10_CS_AS_ISO_DATA_EP_LOCK_DELAY_UNIT_MILLISEC = 0x01, + AUDIO10_CS_AS_ISO_DATA_EP_LOCK_DELAY_UNIT_PCM_SAMPLES = 0x02, +} audio10_cs_as_iso_data_ep_lock_delay_unit_t; + +/// Audio Class-Feature Unit Controls UAC1 +typedef enum { + AUDIO10_FU_CONTROL_BM_MUTE = 1 << 0, + AUDIO10_FU_CONTROL_BM_VOLUME = 1 << 1, + AUDIO10_FU_CONTROL_BM_BASS = 1 << 2, + AUDIO10_FU_CONTROL_BM_MID = 1 << 3, + AUDIO10_FU_CONTROL_BM_TREBLE = 1 << 4, + AUDIO10_FU_CONTROL_BM_GRAPHIC_EQUALIZER = 1 << 5, + AUDIO10_FU_CONTROL_BM_AGC = 1 << 6, + AUDIO10_FU_CONTROL_BM_DELAY = 1 << 7, + AUDIO10_FU_CONTROL_BM_BASS_BOOST = 1 << 8, + AUDIO10_FU_CONTROL_BM_LOUDNESS = 1 << 9, +} audio10_feature_unit_control_bitmap_t; + +/// A.1 - Audio Class-Format Type Codes UAC1 +typedef enum { + AUDIO10_FORMAT_TYPE_UNDEFINED = 0x00, + AUDIO10_FORMAT_TYPE_I = 0x01, + AUDIO10_FORMAT_TYPE_II = 0x02, + AUDIO10_FORMAT_TYPE_III = 0x03, +} audio10_format_type_t; + +// A.1.1 - Audio Class-Audio Data Format Type I UAC1 +typedef enum { + AUDIO10_DATA_FORMAT_TYPE_I_PCM = 0x0001, + AUDIO10_DATA_FORMAT_TYPE_I_PCM8 = 0x0002, + AUDIO10_DATA_FORMAT_TYPE_I_IEEE_FLOAT = 0x0003, + AUDIO10_DATA_FORMAT_TYPE_I_ALAW = 0x0004, + AUDIO10_DATA_FORMAT_TYPE_I_MULAW = 0x0005, +} audio10_data_format_type_I_t; + +// A.1.2 - Audio Class-Audio Data Format Type II UAC1 +typedef enum { + AUDIO10_DATA_FORMAT_TYPE_II_MPEG = 0x1001, + AUDIO10_DATA_FORMAT_TYPE_II_AC3 = 0x1002, +} audio10_data_format_type_II_t; + +// A.1.3 - Audio Class-Audio Data Format Type III UAC1 +typedef enum { + AUDIO10_DATA_FORMAT_TYPE_III_IEC1937_AC3_1 = 0x2001, + AUDIO10_DATA_FORMAT_TYPE_III_IEC1937_MPEG1_L1_1 = 0x2002, + AUDIO10_DATA_FORMAT_TYPE_III_IEC1937_MPEG1_L23_1 = 0x2003, + AUDIO10_DATA_FORMAT_TYPE_III_IEC1937_MPEG2_EXT_1 = 0x2004, + AUDIO10_DATA_FORMAT_TYPE_III_IEC1937_MPEG2_L1_LS_1 = 0x2005, + AUDIO10_DATA_FORMAT_TYPE_III_IEC1937_MPEG2_L23_LS_1 = 0x2006, +} audio10_data_format_type_III_t; + +/// Audio Class-Audio Channel Configuration UAC1 (Table A-7) +typedef enum { + AUDIO10_CHANNEL_CONFIG_NON_PREDEFINED = 0x0000, + AUDIO10_CHANNEL_CONFIG_LEFT_FRONT = 0x0001, + AUDIO10_CHANNEL_CONFIG_RIGHT_FRONT = 0x0002, + AUDIO10_CHANNEL_CONFIG_CENTER_FRONT = 0x0004, + AUDIO10_CHANNEL_CONFIG_LOW_FRQ_EFFECTS = 0x0008, + AUDIO10_CHANNEL_CONFIG_LEFT_SURROUND = 0x0010, + AUDIO10_CHANNEL_CONFIG_RIGHT_SURROUND = 0x0020, + AUDIO10_CHANNEL_CONFIG_LEFT_OF_CENTER = 0x0040, + AUDIO10_CHANNEL_CONFIG_RIGHT_OF_CENTER = 0x0080, + AUDIO10_CHANNEL_CONFIG_SURROUND = 0x0100, + AUDIO10_CHANNEL_CONFIG_SIDE_LEFT = 0x0200, + AUDIO10_CHANNEL_CONFIG_SIDE_RIGHT = 0x0400, + AUDIO10_CHANNEL_CONFIG_TOP = 0x0800, +} audio10_channel_config_t; + + +//--------------------------------------------------------------------+ +// USB AUDIO CLASS 1.0 (UAC1) DESCRIPTORS +//--------------------------------------------------------------------+ + +/// AUDIO Class-Specific AC Interface Header Descriptor UAC1 (4.3.2) +#define audio10_desc_cs_ac_interface_n_t(numInterfaces) \ + struct TU_ATTR_PACKED { \ + uint8_t bLength; /* Size of this descriptor in bytes: 8+n. */ \ + uint8_t bDescriptorType; /* Descriptor Type. Value: TUSB_DESC_CS_INTERFACE. */ \ + uint8_t bDescriptorSubType; /* Descriptor SubType. Value: AUDIO10_CS_AC_INTERFACE_HEADER. */ \ + uint16_t bcdADC; /* Audio Device Class Specification Release Number in Binary-Coded Decimal. Value: 0x0100 for UAC1. */ \ + uint16_t wTotalLength; /* Total number of bytes returned for the class-specific AudioControl interface descriptor. */ \ + uint8_t bInCollection; /* The number of AudioStreaming and MIDIStreaming interfaces in the Audio Interface Collection. */ \ + uint8_t baInterfaceNr[numInterfaces]; /* Interface number of the AudioStreaming or MIDIStreaming interface in the Collection. */ \ + } + +/// AUDIO Input Terminal Descriptor UAC1 (4.3.2.1) +typedef struct TU_ATTR_PACKED { + uint8_t bLength; ///< Size of this descriptor in bytes: 12. + uint8_t bDescriptorType; ///< Descriptor Type. Value: TUSB_DESC_CS_INTERFACE. + uint8_t bDescriptorSubType;///< Descriptor SubType. Value: AUDIO10_CS_AC_INTERFACE_INPUT_TERMINAL. + uint8_t bTerminalID; ///< Constant uniquely identifying the Terminal within the audio function. + uint16_t wTerminalType; ///< Constant characterizing the type of Terminal. + uint8_t bAssocTerminal; ///< ID of the Output Terminal to which this Input Terminal is associated. + uint8_t bNrChannels; ///< Number of logical output channels in the Terminal's output audio channel cluster. + uint16_t wChannelConfig; ///< Describes the spatial location of the logical channels. + uint8_t iChannelNames; ///< Index of a string descriptor, describing the name of the first logical channel. + uint8_t iTerminal; ///< Index of a string descriptor, describing the Input Terminal. +} audio10_desc_input_terminal_t; + +/// AUDIO Output Terminal Descriptor UAC1 (4.3.2.2) +typedef struct TU_ATTR_PACKED { + uint8_t bLength; ///< Size of this descriptor in bytes: 9. + uint8_t bDescriptorType; ///< Descriptor Type. Value: TUSB_DESC_CS_INTERFACE. + uint8_t bDescriptorSubType;///< Descriptor SubType. Value: AUDIO10_CS_AC_INTERFACE_OUTPUT_TERMINAL. + uint8_t bTerminalID; ///< Constant uniquely identifying the Terminal within the audio function. + uint16_t wTerminalType; ///< Constant characterizing the type of Terminal. + uint8_t bAssocTerminal; ///< Constant, identifying the Input Terminal to which this Output Terminal is associated. + uint8_t bSourceID; ///< ID of the Unit or Terminal to which this Terminal is connected. + uint8_t iTerminal; ///< Index of a string descriptor, describing the Output Terminal. +} audio10_desc_output_terminal_t; + +/// AUDIO Mixer Unit Descriptor UAC1 (4.3.2.3) +#define audio10_desc_mixer_unit_n_t(numInputPins, numControlBytes) \ + struct TU_ATTR_PACKED { \ + uint8_t bLength; /* Size of this descriptor in bytes: 10+p+n. */ \ + uint8_t bDescriptorType; /* Descriptor Type. Value: TUSB_DESC_CS_INTERFACE. */ \ + uint8_t bDescriptorSubType; /* Descriptor SubType. Value: AUDIO10_CS_AC_INTERFACE_MIXER_UNIT. */ \ + uint8_t bUnitID; /* Constant uniquely identifying the Unit within the audio function. */ \ + uint8_t bNrInPins; /* Number of Input Pins of this Unit: p. */ \ + uint8_t baSourceID[numInputPins]; /* ID of the Unit or Terminal to which Input Pins of this Mixer Unit are connected. */ \ + uint8_t bNrChannels; /* Number of logical output channels in the Mixer Unit's output audio channel cluster. */ \ + uint16_t wChannelConfig; /* Describes the spatial location of the logical channels. */ \ + uint8_t iChannelNames; /* Index of a string descriptor, describing the name of the first logical channel. */ \ + uint8_t bmControls[numControlBytes]; /* Mixer Unit Controls bitmap. */ \ + uint8_t iMixer; /* Index of a string descriptor, describing the Mixer Unit. */ \ + } + +/// AUDIO Selector Unit Descriptor UAC1 (4.3.2.4) +#define audio10_desc_selector_unit_n_t(numInputPins) \ + struct TU_ATTR_PACKED { \ + uint8_t bLength; /* Size of this descriptor in bytes: 6+p. */ \ + uint8_t bDescriptorType; /* Descriptor Type. Value: TUSB_DESC_CS_INTERFACE. */ \ + uint8_t bDescriptorSubType; /* Descriptor SubType. Value: AUDIO10_CS_AC_INTERFACE_SELECTOR_UNIT. */ \ + uint8_t bUnitID; /* Constant uniquely identifying the Unit within the audio function. */ \ + uint8_t bNrInPins; /* Number of Input Pins of this Unit: p. */ \ + uint8_t baSourceID[numInputPins]; /* ID of the Unit or Terminal to which Input Pins of this Selector Unit are connected. */ \ + uint8_t iSelector; /* Index of a string descriptor, describing the Selector Unit. */ \ + } + +/// AUDIO Feature Unit Descriptor UAC1 (4.3.2.5) +#define audio10_desc_feature_unit_n_t(numChannels, controlSize) \ + struct TU_ATTR_PACKED { \ + uint8_t bLength; /* Size of this descriptor in bytes: 7+(ch+1)*n. */ \ + uint8_t bDescriptorType; /* Descriptor Type. Value: TUSB_DESC_CS_INTERFACE. */ \ + uint8_t bDescriptorSubType; /* Descriptor SubType. Value: AUDIO10_CS_AC_INTERFACE_FEATURE_UNIT. */ \ + uint8_t bUnitID; /* Constant uniquely identifying the Unit within the audio function. */ \ + uint8_t bSourceID; /* ID of the Unit or Terminal to which this Feature Unit is connected. */ \ + uint8_t bControlSize; /* Size in bytes of an element of the bmaControls() array. */ \ + uint8_t bmaControls[(numChannels + 1) * controlSize]; /* Control bitmaps for master + logical channels. */ \ + uint8_t iFeature; /* Index of a string descriptor, describing this Feature Unit. */ \ + } + +/// AUDIO Processing Unit Descriptor UAC1 (4.3.2.6) +#define audio10_desc_processing_unit_n_t(numInputPins, numControlBytes) \ + struct TU_ATTR_PACKED { \ + uint8_t bLength; /* Size of this descriptor in bytes: 13+p+n. */ \ + uint8_t bDescriptorType; /* Descriptor Type. Value: TUSB_DESC_CS_INTERFACE. */ \ + uint8_t bDescriptorSubType; /* Descriptor SubType. Value: AUDIO10_CS_AC_INTERFACE_PROCESSING_UNIT. */ \ + uint8_t bUnitID; /* Constant uniquely identifying the Unit within the audio function. */ \ + uint16_t wProcessType; /* Constant identifying the type of processing this Unit is performing. */ \ + uint8_t bNrInPins; /* Number of Input Pins of this Unit: p. */ \ + uint8_t baSourceID[numInputPins]; /* ID of the Unit or Terminal to which Input Pins of this Processing Unit are connected. */ \ + uint8_t bNrChannels; /* Number of logical output channels in the Processing Unit's output audio channel cluster. */ \ + uint16_t wChannelConfig; /* Describes the spatial location of the logical channels. */ \ + uint8_t iChannelNames; /* Index of a string descriptor, describing the name of the first logical channel. */ \ + uint8_t bControlSize; /* Size in bytes of the bmControls field. */ \ + uint8_t bmControls[numControlBytes]; /* Processing Unit Controls bitmap. */ \ + uint8_t iProcessing; /* Index of a string descriptor, describing the Processing Unit. */ \ + } + +/// AUDIO Extension Unit Descriptor UAC1 (4.3.2.7) +#define audio10_desc_extension_unit_n_t(numInputPins, numControlBytes) \ + struct TU_ATTR_PACKED { \ + uint8_t bLength; /* Size of this descriptor in bytes: 13+p+n. */ \ + uint8_t bDescriptorType; /* Descriptor Type. Value: TUSB_DESC_CS_INTERFACE. */ \ + uint8_t bDescriptorSubType; /* Descriptor SubType. Value: AUDIO10_CS_AC_INTERFACE_EXTENSION_UNIT. */ \ + uint8_t bUnitID; /* Constant uniquely identifying the Unit within the audio function. */ \ + uint16_t wExtensionCode; /* Vendor-specific code identifying the Extension Unit. */ \ + uint8_t bNrInPins; /* Number of Input Pins of this Unit: p. */ \ + uint8_t baSourceID[numInputPins]; /* ID of the Unit or Terminal to which Input Pins of this Extension Unit are connected. */ \ + uint8_t bNrChannels; /* Number of logical output channels in the Extension Unit's output audio channel cluster. */ \ + uint16_t wChannelConfig; /* Describes the spatial location of the logical channels. */ \ + uint8_t iChannelNames; /* Index of a string descriptor, describing the name of the first logical channel. */ \ + uint8_t bControlSize; /* Size in bytes of the bmControls field. */ \ + uint8_t bmControls[numControlBytes]; /* Extension Unit Controls bitmap. */ \ + uint8_t iExtension; /* Index of a string descriptor, describing the Extension Unit. */ \ + } + +/// AUDIO Class-Specific AS Interface Descriptor UAC1 (4.5.2) +typedef struct TU_ATTR_PACKED { + uint8_t bLength; ///< Size of this descriptor in bytes: 7. + uint8_t bDescriptorType; ///< Descriptor Type. Value: TUSB_DESC_CS_INTERFACE. + uint8_t bDescriptorSubType;///< Descriptor SubType. Value: AUDIO10_CS_AS_INTERFACE_AS_GENERAL. + uint8_t bTerminalLink; ///< The Terminal ID of the Terminal to which the endpoint of this interface is connected. + uint8_t bDelay; ///< Expressed in number of frames. + uint16_t wFormatTag; ///< The Audio Data Format that has to be used to communicate with this interface. +} audio10_desc_cs_as_interface_t; + +/// AUDIO Type I Format Type Descriptor UAC1 (2.2.5) +#define audio10_desc_type_I_format_n_t(numSamFreq) \ + struct TU_ATTR_PACKED { \ + uint8_t bLength; /* Size of this descriptor in bytes: 8+(ns*3). */ \ + uint8_t bDescriptorType; /* Descriptor Type. Value: TUSB_DESC_CS_INTERFACE. */ \ + uint8_t bDescriptorSubType; /* Descriptor SubType. Value: AUDIO10_CS_AS_INTERFACE_FORMAT_TYPE. */ \ + uint8_t bFormatType; /* Constant identifying the Format Type the AudioStreaming interface is using. */ \ + uint8_t bNrChannels; /* Indicates the number of physical channels in the audio data stream. */ \ + uint8_t bSubFrameSize; /* The number of bytes occupied by one audio subframe. */ \ + uint8_t bBitResolution; /* The number of effectively used bits from the available bits in an audio subframe. */ \ + uint8_t bSamFreqType; /* Indicates how the sampling frequency can be programmed. */ \ + uint8_t tSamFreq[numSamFreq * 3]; /* Sampling frequency or lower/upper bounds in Hz for the sampling frequency range. */ \ + } + +/// AUDIO Type II Format Type Descriptor UAC1 (2.3.5) +#define audio10_desc_type_II_format_n_t(numSamFreq) \ + struct TU_ATTR_PACKED { \ + uint8_t bLength; /* Size of this descriptor in bytes: 9+(ns*3). */ \ + uint8_t bDescriptorType; /* Descriptor Type. Value: TUSB_DESC_CS_INTERFACE. */ \ + uint8_t bDescriptorSubType; /* Descriptor SubType. Value: AUDIO10_CS_AS_INTERFACE_FORMAT_TYPE. */ \ + uint8_t bFormatType; /* Constant identifying the Format Type the AudioStreaming interface is using. */ \ + uint16_t wMaxBitRate; /* Indicates the maximum number of bits per second this interface can handle. */ \ + uint16_t wSamplesPerFrame; /* Indicates the number of PCM audio samples contained in one encoded audio frame. */ \ + uint8_t bSamFreqType; /* Indicates how the sampling frequency can be programmed. */ \ + uint8_t tSamFreq[numSamFreq * 3]; /* Sampling frequency or lower/upper bounds in Hz for the sampling frequency range. */ \ + } + +/// AUDIO Type III Format Type Descriptor UAC1 (2.4.5) +#define audio10_desc_type_III_format_n_t(numSamFreq) \ + struct TU_ATTR_PACKED { \ + uint8_t bLength; /* Size of this descriptor in bytes: 8+(ns*3). */ \ + uint8_t bDescriptorType; /* Descriptor Type. Value: TUSB_DESC_CS_INTERFACE. */ \ + uint8_t bDescriptorSubType; /* Descriptor SubType. Value: AUDIO10_CS_AS_INTERFACE_FORMAT_TYPE. */ \ + uint8_t bFormatType; /* Constant identifying the Format Type the AudioStreaming interface is using. */ \ + uint8_t bNrChannels; /* Indicates the number of physical channels in the audio data stream. */ \ + uint8_t bSubFrameSize; /* The number of bytes occupied by one audio subframe. */ \ + uint8_t bBitResolution; /* The number of effectively used bits from the available bits in an audio subframe. */ \ + uint8_t bSamFreqType; /* Indicates how the sampling frequency can be programmed. */ \ + uint8_t tSamFreq[numSamFreq * 3]; /* Sampling frequency or lower/upper bounds in Hz for the sampling frequency range. */ \ + } + +/// Standard AS Isochronous Audio Data Endpoint Descriptor UAC1 (4.6.1.1) +typedef struct TU_ATTR_PACKED { + uint8_t bLength; ///< Size of this descriptor in bytes: 9. + uint8_t bDescriptorType; ///< Descriptor Type. Value: TUSB_DESC_ENDPOINT. + uint8_t bEndpointAddress;///< The address of the endpoint on the USB device described by this descriptor. + struct TU_ATTR_PACKED { + uint8_t xfer : 2; // Control, ISO, Bulk, Interrupt + uint8_t sync : 2; // None, Asynchronous, Adaptive, Synchronous + uint8_t usage : 2; // Data, Feedback, Implicit feedback + uint8_t : 2; + } bmAttributes; + uint16_t wMaxPacketSize; ///< Maximum packet size this endpoint is capable of sending or receiving when this configuration is selected. + uint8_t bInterval; ///< Interval for polling endpoint for data transfers. + uint8_t bRefresh; ///< The rate at which the endpoint is refreshed. + uint8_t bSynchAddress; ///< The address of the endpoint used to send synchronization information for the data endpoint. +} audio10_desc_as_iso_data_ep_t; + +/// AUDIO Class-Specific AS Isochronous Audio Data Endpoint Descriptor UAC1 (4.6.1.2) +typedef struct TU_ATTR_PACKED { + uint8_t bLength; ///< Size of this descriptor in bytes: 7. + uint8_t bDescriptorType; ///< Descriptor Type. Value: TUSB_DESC_CS_ENDPOINT. + uint8_t bDescriptorSubType;///< Descriptor SubType. Value: AUDIO10_CS_EP_SUBTYPE_GENERAL. + uint8_t bmAttributes; ///< Bit 0: Sampling Frequency, Bit 1: Pitch, Bit 7: MaxPacketsOnly. + uint8_t bLockDelayUnits; ///< Indicates the units used for the wLockDelay field. + uint16_t wLockDelay; ///< Indicates the time it takes this endpoint to reliably lock its internal clock recovery circuitry. +} audio10_desc_cs_as_iso_data_ep_t; + +/// AUDIO Interrupt Data Message Format UAC1 (3.7.1.2) +typedef struct TU_ATTR_PACKED { + uint8_t bStatusType;///< Indicates the type of status information being reported. + uint8_t bOriginator;///< Indicates the entity that originated this status information. +} audio10_interrupt_data_t; + +//--------------------------------------------------------------------+ +// USB AUDIO CLASS 2.0 (UAC2) DEFINITIONS +//--------------------------------------------------------------------+ + +/// A.7 - Audio Function Category Codes +typedef enum { + AUDIO20_FUNC_UNDEF = 0x00, + AUDIO20_FUNC_DESKTOP_SPEAKER = 0x01, + AUDIO20_FUNC_HOME_THEATER = 0x02, + AUDIO20_FUNC_MICROPHONE = 0x03, + AUDIO20_FUNC_HEADSET = 0x04, + AUDIO20_FUNC_TELEPHONE = 0x05, + AUDIO20_FUNC_CONVERTER = 0x06, + AUDIO20_FUNC_SOUND_RECODER = 0x07, + AUDIO20_FUNC_IO_BOX = 0x08, + AUDIO20_FUNC_MUSICAL_INSTRUMENT = 0x09, + AUDIO20_FUNC_PRO_AUDIO = 0x0A, + AUDIO20_FUNC_AUDIO_VIDEO = 0x0B, + AUDIO20_FUNC_CONTROL_PANEL = 0x0C, + AUDIO20_FUNC_OTHER = 0xFF, +} audio20_function_code_t; + +/// A.9 - Audio Class-Specific AC Interface Descriptor Subtypes UAC2 +typedef enum { + AUDIO20_CS_AC_INTERFACE_AC_DESCRIPTOR_UNDEF = 0x00, + AUDIO20_CS_AC_INTERFACE_HEADER = 0x01, + AUDIO20_CS_AC_INTERFACE_INPUT_TERMINAL = 0x02, + AUDIO20_CS_AC_INTERFACE_OUTPUT_TERMINAL = 0x03, + AUDIO20_CS_AC_INTERFACE_MIXER_UNIT = 0x04, + AUDIO20_CS_AC_INTERFACE_SELECTOR_UNIT = 0x05, + AUDIO20_CS_AC_INTERFACE_FEATURE_UNIT = 0x06, + AUDIO20_CS_AC_INTERFACE_EFFECT_UNIT = 0x07, + AUDIO20_CS_AC_INTERFACE_PROCESSING_UNIT = 0x08, + AUDIO20_CS_AC_INTERFACE_EXTENSION_UNIT = 0x09, + AUDIO20_CS_AC_INTERFACE_CLOCK_SOURCE = 0x0A, + AUDIO20_CS_AC_INTERFACE_CLOCK_SELECTOR = 0x0B, + AUDIO20_CS_AC_INTERFACE_CLOCK_MULTIPLIER = 0x0C, + AUDIO20_CS_AC_INTERFACE_SAMPLE_RATE_CONVERTER = 0x0D, +} audio20_cs_ac_interface_subtype_t; + +/// A.10 - Audio Class-Specific AS Interface Descriptor Subtypes UAC2 +typedef enum { + AUDIO20_CS_AS_INTERFACE_AS_DESCRIPTOR_UNDEF = 0x00, + AUDIO20_CS_AS_INTERFACE_AS_GENERAL = 0x01, + AUDIO20_CS_AS_INTERFACE_FORMAT_TYPE = 0x02, + AUDIO20_CS_AS_INTERFACE_ENCODER = 0x03, + AUDIO20_CS_AS_INTERFACE_DECODER = 0x04, +} audio20_cs_as_interface_subtype_t; + +/// A.11 - Effect Unit Effect Types +typedef enum { + AUDIO20_EFFECT_TYPE_UNDEF = 0x00, + AUDIO20_EFFECT_TYPE_PARAM_EQ_SECTION = 0x01, + AUDIO20_EFFECT_TYPE_REVERBERATION = 0x02, + AUDIO20_EFFECT_TYPE_MOD_DELAY = 0x03, + AUDIO20_EFFECT_TYPE_DYN_RANGE_COMP = 0x04, +} audio20_effect_unit_effect_type_t; + +/// A.12 - Processing Unit Process Types +typedef enum { + AUDIO20_PROCESS_TYPE_UNDEF = 0x00, + AUDIO20_PROCESS_TYPE_UP_DOWN_MIX = 0x01, + AUDIO20_PROCESS_TYPE_DOLBY_PROLOGIC = 0x02, + AUDIO20_PROCESS_TYPE_STEREO_EXTENDER = 0x03, +} audio20_processing_unit_process_type_t; + +/// A.13 - Audio Class-Specific EP Descriptor Subtypes UAC2 +typedef enum { + AUDIO20_CS_EP_SUBTYPE_UNDEF = 0x00, + AUDIO20_CS_EP_SUBTYPE_GENERAL = 0x01, +} audio20_cs_ep_subtype_t; + +/// A.14 - Audio Class-Specific Request Codes UAC2 +typedef enum { + AUDIO20_CS_REQ_UNDEF = 0x00, + AUDIO20_CS_REQ_CUR = 0x01, + AUDIO20_CS_REQ_RANGE = 0x02, + AUDIO20_CS_REQ_MEM = 0x03, +} audio20_cs_req_t; + +/// A.17 - Control Selector Codes UAC2 + +/// A.17.1 - Clock Source Control Selectors +typedef enum { + AUDIO20_CS_CTRL_UNDEF = 0x00, + AUDIO20_CS_CTRL_SAM_FREQ = 0x01, + AUDIO20_CS_CTRL_CLK_VALID = 0x02, +} audio20_clock_src_control_selector_t; + +/// A.17.2 - Clock Selector Control Selectors +typedef enum { + AUDIO20_CX_CTRL_UNDEF = 0x00, + AUDIO20_CX_CTRL_CONTROL = 0x01, +} audio20_clock_sel_control_selector_t; + +/// A.17.3 - Clock Multiplier Control Selectors +typedef enum { + AUDIO20_CM_CTRL_UNDEF = 0x00, + AUDIO20_CM_CTRL_NUMERATOR_CONTROL = 0x01, + AUDIO20_CM_CTRL_DENOMINATOR_CONTROL = 0x02, +} audio20_clock_mul_control_selector_t; + +/// A.17.4 - Terminal Control Selectors UAC2 +typedef enum { + AUDIO20_TE_CTRL_UNDEF = 0x00, + AUDIO20_TE_CTRL_COPY_PROTECT = 0x01, + AUDIO20_TE_CTRL_CONNECTOR = 0x02, + AUDIO20_TE_CTRL_OVERLOAD = 0x03, + AUDIO20_TE_CTRL_CLUSTER = 0x04, + AUDIO20_TE_CTRL_UNDERFLOW = 0x05, + AUDIO20_TE_CTRL_OVERFLOW = 0x06, + AUDIO20_TE_CTRL_LATENCY = 0x07, +} audio20_terminal_control_selector_t; + +/// A.17.5 - Mixer Control Selectors +typedef enum { + AUDIO20_MU_CTRL_UNDEF = 0x00, + AUDIO20_MU_CTRL_MIXER = 0x01, + AUDIO20_MU_CTRL_CLUSTER = 0x02, + AUDIO20_MU_CTRL_UNDERFLOW = 0x03, + AUDIO20_MU_CTRL_OVERFLOW = 0x04, + AUDIO20_MU_CTRL_LATENCY = 0x05, +} audio20_mixer_control_selector_t; + +/// A.17.6 - Selector Control Selectors +typedef enum { + AUDIO20_SU_CTRL_UNDEF = 0x00, + AUDIO20_SU_CTRL_SELECTOR = 0x01, + AUDIO20_SU_CTRL_LATENCY = 0x02, +} audio20_sel_control_selector_t; + +/// A.17.7 - Feature Unit Control Selectors UAC2 +typedef enum { + AUDIO20_FU_CTRL_UNDEF = 0x00, + AUDIO20_FU_CTRL_MUTE = 0x01, + AUDIO20_FU_CTRL_VOLUME = 0x02, + AUDIO20_FU_CTRL_BASS = 0x03, + AUDIO20_FU_CTRL_MID = 0x04, + AUDIO20_FU_CTRL_TREBLE = 0x05, + AUDIO20_FU_CTRL_GRAPHIC_EQUALIZER = 0x06, + AUDIO20_FU_CTRL_AGC = 0x07, + AUDIO20_FU_CTRL_DELAY = 0x08, + AUDIO20_FU_CTRL_BASS_BOOST = 0x09, + AUDIO20_FU_CTRL_LOUDNESS = 0x0A, + AUDIO20_FU_CTRL_INPUT_GAIN = 0x0B, + AUDIO20_FU_CTRL_GAIN_PAD = 0x0C, + AUDIO20_FU_CTRL_INVERTER = 0x0D, + AUDIO20_FU_CTRL_UNDERFLOW = 0x0E, + AUDIO20_FU_CTRL_OVERVLOW = 0x0F, + AUDIO20_FU_CTRL_LATENCY = 0x10, +} audio20_feature_unit_control_selector_t; + +/// A.17.8 Effect Unit Control Selectors + +/// A.17.8.1 Parametric Equalizer Section Effect Unit Control Selectors +typedef enum { + AUDIO20_PE_CTRL_UNDEF = 0x00, + AUDIO20_PE_CTRL_ENABLE = 0x01, + AUDIO20_PE_CTRL_CENTERFREQ = 0x02, + AUDIO20_PE_CTRL_QFACTOR = 0x03, + AUDIO20_PE_CTRL_GAIN = 0x04, + AUDIO20_PE_CTRL_UNDERFLOW = 0x05, + AUDIO20_PE_CTRL_OVERFLOW = 0x06, + AUDIO20_PE_CTRL_LATENCY = 0x07, +} audio20_parametric_equalizer_control_selector_t; + +/// A.17.8.2 Reverberation Effect Unit Control Selectors +typedef enum { + AUDIO20_RV_CTRL_UNDEF = 0x00, + AUDIO20_RV_CTRL_ENABLE = 0x01, + AUDIO20_RV_CTRL_TYPE = 0x02, + AUDIO20_RV_CTRL_LEVEL = 0x03, + AUDIO20_RV_CTRL_TIME = 0x04, + AUDIO20_RV_CTRL_FEEDBACK = 0x05, + AUDIO20_RV_CTRL_PREDELAY = 0x06, + AUDIO20_RV_CTRL_DENSITY = 0x07, + AUDIO20_RV_CTRL_HIFREQ_ROLLOFF = 0x08, + AUDIO20_RV_CTRL_UNDERFLOW = 0x09, + AUDIO20_RV_CTRL_OVERFLOW = 0x0A, + AUDIO20_RV_CTRL_LATENCY = 0x0B, +} audio20_reverberation_effect_control_selector_t; + +/// A.17.8.3 Modulation Delay Effect Unit Control Selectors +typedef enum { + AUDIO20_MD_CTRL_UNDEF = 0x00, + AUDIO20_MD_CTRL_ENABLE = 0x01, + AUDIO20_MD_CTRL_BALANCE = 0x02, + AUDIO20_MD_CTRL_RATE = 0x03, + AUDIO20_MD_CTRL_DEPTH = 0x04, + AUDIO20_MD_CTRL_TIME = 0x05, + AUDIO20_MD_CTRL_FEEDBACK = 0x06, + AUDIO20_MD_CTRL_UNDERFLOW = 0x07, + AUDIO20_MD_CTRL_OVERFLOW = 0x08, + AUDIO20_MD_CTRL_LATENCY = 0x09, +} audio20_modulation_delay_control_selector_t; + +/// A.17.8.4 Dynamic Range Compressor Effect Unit Control Selectors +typedef enum { + AUDIO20_DR_CTRL_UNDEF = 0x00, + AUDIO20_DR_CTRL_ENABLE = 0x01, + AUDIO20_DR_CTRL_COMPRESSION_RATE = 0x02, + AUDIO20_DR_CTRL_MAXAMPL = 0x03, + AUDIO20_DR_CTRL_THRESHOLD = 0x04, + AUDIO20_DR_CTRL_ATTACK_TIME = 0x05, + AUDIO20_DR_CTRL_RELEASE_TIME = 0x06, + AUDIO20_DR_CTRL_UNDERFLOW = 0x07, + AUDIO20_DR_CTRL_OVERFLOW = 0x08, + AUDIO20_DR_CTRL_LATENCY = 0x09, +} audio20_dynamic_range_compression_control_selector_t; + +/// A.17.9 Processing Unit Control Selectors + +/// A.17.9.1 Up/Down-mix Processing Unit Control Selectors +typedef enum { + AUDIO20_UD_CTRL_UNDEF = 0x00, + AUDIO20_UD_CTRL_ENABLE = 0x01, + AUDIO20_UD_CTRL_MODE_SELECT = 0x02, + AUDIO20_UD_CTRL_CLUSTER = 0x03, + AUDIO20_UD_CTRL_UNDERFLOW = 0x04, + AUDIO20_UD_CTRL_OVERFLOW = 0x05, + AUDIO20_UD_CTRL_LATENCY = 0x06, +} audio20_up_down_mix_control_selector_t; + +/// A.17.9.2 Dolby Prologic ™ Processing Unit Control Selectors +typedef enum { + AUDIO20_DP_CTRL_UNDEF = 0x00, + AUDIO20_DP_CTRL_ENABLE = 0x01, + AUDIO20_DP_CTRL_MODE_SELECT = 0x02, + AUDIO20_DP_CTRL_CLUSTER = 0x03, + AUDIO20_DP_CTRL_UNDERFLOW = 0x04, + AUDIO20_DP_CTRL_OVERFLOW = 0x05, + AUDIO20_DP_CTRL_LATENCY = 0x06, +} audio20_dolby_prologic_control_selector_t; + +/// A.17.9.3 Stereo Extender Processing Unit Control Selectors +typedef enum { + AUDIO20_ST_EXT_CTRL_UNDEF = 0x00, + AUDIO20_ST_EXT_CTRL_ENABLE = 0x01, + AUDIO20_ST_EXT_CTRL_WIDTH = 0x02, + AUDIO20_ST_EXT_CTRL_UNDERFLOW = 0x03, + AUDIO20_ST_EXT_CTRL_OVERFLOW = 0x04, + AUDIO20_ST_EXT_CTRL_LATENCY = 0x05, +} audio20_stereo_extender_control_selector_t; + +/// A.17.10 Extension Unit Control Selectors +typedef enum { + AUDIO20_XU_CTRL_UNDEF = 0x00, + AUDIO20_XU_CTRL_ENABLE = 0x01, + AUDIO20_XU_CTRL_CLUSTER = 0x02, + AUDIO20_XU_CTRL_UNDERFLOW = 0x03, + AUDIO20_XU_CTRL_OVERFLOW = 0x04, + AUDIO20_XU_CTRL_LATENCY = 0x05, +} audio20_extension_unit_control_selector_t; + +/// A.17.11 AudioStreaming Interface Control Selectors +typedef enum { + AUDIO20_AS_CTRL_UNDEF = 0x00, + AUDIO20_AS_CTRL_ACT_ALT_SETTING = 0x01, + AUDIO20_AS_CTRL_VAL_ALT_SETTINGS = 0x02, + AUDIO20_AS_CTRL_AUDIO_DATA_FORMAT = 0x03, +} audio20_audiostreaming_interface_control_selector_t; + +/// A.17.12 Encoder Control Selectors +typedef enum { + AUDIO20_EN_CTRL_UNDEF = 0x00, + AUDIO20_EN_CTRL_BIT_RATE = 0x01, + AUDIO20_EN_CTRL_QUALITY = 0x02, + AUDIO20_EN_CTRL_VBR = 0x03, + AUDIO20_EN_CTRL_TYPE = 0x04, + AUDIO20_EN_CTRL_UNDERFLOW = 0x05, + AUDIO20_EN_CTRL_OVERFLOW = 0x06, + AUDIO20_EN_CTRL_ENCODER_ERROR = 0x07, + AUDIO20_EN_CTRL_PARAM1 = 0x08, + AUDIO20_EN_CTRL_PARAM2 = 0x09, + AUDIO20_EN_CTRL_PARAM3 = 0x0A, + AUDIO20_EN_CTRL_PARAM4 = 0x0B, + AUDIO20_EN_CTRL_PARAM5 = 0x0C, + AUDIO20_EN_CTRL_PARAM6 = 0x0D, + AUDIO20_EN_CTRL_PARAM7 = 0x0E, + AUDIO20_EN_CTRL_PARAM8 = 0x0F, +} audio20_encoder_control_selector_t; + +/// A.17.13 Decoder Control Selectors + +/// A.17.13.1 MPEG Decoder Control Selectors +typedef enum { + AUDIO20_MPD_CTRL_UNDEF = 0x00, + AUDIO20_MPD_CTRL_DUAL_CHANNEL = 0x01, + AUDIO20_MPD_CTRL_SECOND_STEREO = 0x02, + AUDIO20_MPD_CTRL_MULTILINGUAL = 0x03, + AUDIO20_MPD_CTRL_DYN_RANGE = 0x04, + AUDIO20_MPD_CTRL_SCALING = 0x05, + AUDIO20_MPD_CTRL_HILO_SCALING = 0x06, + AUDIO20_MPD_CTRL_UNDERFLOW = 0x07, + AUDIO20_MPD_CTRL_OVERFLOW = 0x08, + AUDIO20_MPD_CTRL_DECODER_ERROR = 0x09, +} audio20_MPEG_decoder_control_selector_t; + +/// A.17.13.2 AC-3 Decoder Control Selectors +typedef enum { + AUDIO20_AD_CTRL_UNDEF = 0x00, + AUDIO20_AD_CTRL_MODE = 0x01, + AUDIO20_AD_CTRL_DYN_RANGE = 0x02, + AUDIO20_AD_CTRL_SCALING = 0x03, + AUDIO20_AD_CTRL_HILO_SCALING = 0x04, + AUDIO20_AD_CTRL_UNDERFLOW = 0x05, + AUDIO20_AD_CTRL_OVERFLOW = 0x06, + AUDIO20_AD_CTRL_DECODER_ERROR = 0x07, +} audio20_AC3_decoder_control_selector_t; + +/// A.17.13.3 WMA Decoder Control Selectors +typedef enum { + AUDIO20_WD_CTRL_UNDEF = 0x00, + AUDIO20_WD_CTRL_UNDERFLOW = 0x01, + AUDIO20_WD_CTRL_OVERFLOW = 0x02, + AUDIO20_WD_CTRL_DECODER_ERROR = 0x03, +} audio20_WMA_decoder_control_selector_t; + +/// A.17.13.4 DTS Decoder Control Selectors +typedef enum { + AUDIO20_DD_CTRL_UNDEF = 0x00, + AUDIO20_DD_CTRL_UNDERFLOW = 0x01, + AUDIO20_DD_CTRL_OVERFLOW = 0x02, + AUDIO20_DD_CTRL_DECODER_ERROR = 0x03, +} audio20_DTS_decoder_control_selector_t; + +/// A.17.14 Endpoint Control Selectors +typedef enum { + AUDIO20_EP_CTRL_UNDEF = 0x00, + AUDIO20_EP_CTRL_PITCH = 0x01, + AUDIO20_EP_CTRL_DATA_OVERRUN = 0x02, + AUDIO20_EP_CTRL_DATA_UNDERRUN = 0x03, +} audio20_EP_control_selector_t; + +/// Additional Audio Device Class Codes - Source: Audio Data Formats + +/// A.1 - Audio Class-Format Type Codes UAC2 +typedef enum { + AUDIO20_FORMAT_TYPE_UNDEFINED = 0x00, + AUDIO20_FORMAT_TYPE_I = 0x01, + AUDIO20_FORMAT_TYPE_II = 0x02, + AUDIO20_FORMAT_TYPE_III = 0x03, + AUDIO20_FORMAT_TYPE_IV = 0x04, + AUDIO20_EXT_FORMAT_TYPE_I = 0x81, + AUDIO20_EXT_FORMAT_TYPE_II = 0x82, + AUDIO20_EXT_FORMAT_TYPE_III = 0x83, +} audio20_format_type_t; + +// A.2.1 - Audio Class-Audio Data Format Type I UAC2 +typedef enum { + AUDIO20_DATA_FORMAT_TYPE_I_PCM = 1 << 0, + AUDIO20_DATA_FORMAT_TYPE_I_PCM8 = 1 << 1, + AUDIO20_DATA_FORMAT_TYPE_I_IEEE_FLOAT = 1 << 2, + AUDIO20_DATA_FORMAT_TYPE_I_ALAW = 1 << 3, + AUDIO20_DATA_FORMAT_TYPE_I_MULAW = 1 << 4, + AUDIO20_DATA_FORMAT_TYPE_I_RAW_DATA = 0x80000000u, +} audio20_data_format_type_I_t; + +/// Audio Class-Audio Channel Configuration UAC2 (Table A-11) +typedef enum { + AUDIO20_CHANNEL_CONFIG_NON_PREDEFINED = 0x00000000, + AUDIO20_CHANNEL_CONFIG_FRONT_LEFT = 0x00000001, + AUDIO20_CHANNEL_CONFIG_FRONT_RIGHT = 0x00000002, + AUDIO20_CHANNEL_CONFIG_FRONT_CENTER = 0x00000004, + AUDIO20_CHANNEL_CONFIG_LOW_FRQ_EFFECTS = 0x00000008, + AUDIO20_CHANNEL_CONFIG_BACK_LEFT = 0x00000010, + AUDIO20_CHANNEL_CONFIG_BACK_RIGHT = 0x00000020, + AUDIO20_CHANNEL_CONFIG_FRONT_LEFT_OF_CENTER = 0x00000040, + AUDIO20_CHANNEL_CONFIG_FRONT_RIGHT_OF_CENTER = 0x00000080, + AUDIO20_CHANNEL_CONFIG_BACK_CENTER = 0x00000100, + AUDIO20_CHANNEL_CONFIG_SIDE_LEFT = 0x00000200, + AUDIO20_CHANNEL_CONFIG_SIDE_RIGHT = 0x00000400, + AUDIO20_CHANNEL_CONFIG_TOP_CENTER = 0x00000800, + AUDIO20_CHANNEL_CONFIG_TOP_FRONT_LEFT = 0x00001000, + AUDIO20_CHANNEL_CONFIG_TOP_FRONT_CENTER = 0x00002000, + AUDIO20_CHANNEL_CONFIG_TOP_FRONT_RIGHT = 0x00004000, + AUDIO20_CHANNEL_CONFIG_TOP_BACK_LEFT = 0x00008000, + AUDIO20_CHANNEL_CONFIG_TOP_BACK_CENTER = 0x00010000, + AUDIO20_CHANNEL_CONFIG_TOP_BACK_RIGHT = 0x00020000, + AUDIO20_CHANNEL_CONFIG_TOP_FRONT_LEFT_OF_CENTER = 0x00040000, + AUDIO20_CHANNEL_CONFIG_TOP_FRONT_RIGHT_OF_CENTER = 0x00080000, + AUDIO20_CHANNEL_CONFIG_LEFT_LOW_FRQ_EFFECTS = 0x00100000, + AUDIO20_CHANNEL_CONFIG_RIGHT_LOW_FRQ_EFFECTS = 0x00200000, + AUDIO20_CHANNEL_CONFIG_TOP_SIDE_LEFT = 0x00400000, + AUDIO20_CHANNEL_CONFIG_TOP_SIDE_RIGHT = 0x00800000, + AUDIO20_CHANNEL_CONFIG_BOTTOM_CENTER = 0x01000000, + AUDIO20_CHANNEL_CONFIG_BACK_LEFT_OF_CENTER = 0x02000000, + AUDIO20_CHANNEL_CONFIG_BACK_RIGHT_OF_CENTER = 0x04000000, + AUDIO20_CHANNEL_CONFIG_RAW_DATA = 0x80000000u, +} audio20_channel_config_t; + +/// All remaining definitions are taken from the descriptor descriptions in the UAC2 main specification + +/// Audio Class-Control Values UAC2 +typedef enum { + AUDIO20_CTRL_NONE = 0x00,///< No Host access + AUDIO20_CTRL_R = 0x01, ///< Host read access only + AUDIO20_CTRL_RW = 0x03, ///< Host read write access +} audio20_control_t; + +/// Audio Class-Specific AC Interface Descriptor Controls UAC2 +typedef enum { + AUDIO20_CS_AS_INTERFACE_CTRL_LATENCY_POS = 0, +} audio20_cs_ac_interface_control_pos_t; + +/// Audio Class-Specific AS Interface Descriptor Controls UAC2 +typedef enum { + AUDIO20_CS_AS_INTERFACE_CTRL_ACTIVE_ALT_SET_POS = 0, + AUDIO20_CS_AS_INTERFACE_CTRL_VALID_ALT_SET_POS = 2, +} audio20_cs_as_interface_control_pos_t; + +/// Audio Class-Specific AS Isochronous Data EP Attributes UAC2 +typedef enum { + AUDIO20_CS_AS_ISO_DATA_EP_ATT_MAX_PACKETS_ONLY = 0x80, + AUDIO20_CS_AS_ISO_DATA_EP_ATT_NON_MAX_PACKETS_OK = 0x00, +} audio20_cs_as_iso_data_ep_attribute_t; + +/// Audio Class-Specific AS Isochronous Data EP Controls UAC2 +typedef enum { + AUDIO20_CS_AS_ISO_DATA_EP_CTRL_PITCH_POS = 0, + AUDIO20_CS_AS_ISO_DATA_EP_CTRL_DATA_OVERRUN_POS = 2, + AUDIO20_CS_AS_ISO_DATA_EP_CTRL_DATA_UNDERRUN_POS = 4, +} audio20_cs_as_iso_data_ep_control_pos_t; + +/// Audio Class-Specific AS Isochronous Data EP Lock Delay Units UAC2 +typedef enum { + AUDIO20_CS_AS_ISO_DATA_EP_LOCK_DELAY_UNIT_UNDEFINED = 0x00, + AUDIO20_CS_AS_ISO_DATA_EP_LOCK_DELAY_UNIT_MILLISEC = 0x01, + AUDIO20_CS_AS_ISO_DATA_EP_LOCK_DELAY_UNIT_PCM_SAMPLES = 0x02, +} audio20_cs_as_iso_data_ep_lock_delay_unit_t; + +/// Audio Class-Clock Source Attributes UAC2 +typedef enum { + AUDIO20_CLOCK_SOURCE_ATT_EXT_CLK = 0x00, + AUDIO20_CLOCK_SOURCE_ATT_INT_FIX_CLK = 0x01, + AUDIO20_CLOCK_SOURCE_ATT_INT_VAR_CLK = 0x02, + AUDIO20_CLOCK_SOURCE_ATT_INT_PRO_CLK = 0x03, + AUDIO20_CLOCK_SOURCE_ATT_CLK_SYC_SOF = 0x04, +} audio20_clock_source_attribute_t; + +/// Audio Class-Clock Source Controls UAC2 +typedef enum { + AUDIO20_CLOCK_SOURCE_CTRL_CLK_FRQ_POS = 0, + AUDIO20_CLOCK_SOURCE_CTRL_CLK_VAL_POS = 2, +} audio20_clock_source_control_pos_t; + +/// Audio Class-Clock Selector Controls UAC2 +typedef enum { + AUDIO20_CLOCK_SELECTOR_CTRL_POS = 0, +} audio20_clock_selector_control_pos_t; + +/// Audio Class-Clock Multiplier Controls UAC2 +typedef enum { + AUDIO20_CLOCK_MULTIPLIER_CTRL_NUMERATOR_POS = 0, + AUDIO20_CLOCK_MULTIPLIER_CTRL_DENOMINATOR_POS = 2, +} audio20_clock_multiplier_control_pos_t; + +/// Audio Class-Input Terminal Controls UAC2 +typedef enum { + AUDIO20_IN_TERM_CTRL_CPY_PROT_POS = 0, + AUDIO20_IN_TERM_CTRL_CONNECTOR_POS = 2, + AUDIO20_IN_TERM_CTRL_OVERLOAD_POS = 4, + AUDIO20_IN_TERM_CTRL_CLUSTER_POS = 6, + AUDIO20_IN_TERM_CTRL_UNDERFLOW_POS = 8, + AUDIO20_IN_TERM_CTRL_OVERFLOW_POS = 10, +} audio20_terminal_input_control_pos_t; + +/// Audio Class-Output Terminal Controls UAC2 +typedef enum { + AUDIO20_OUT_TERM_CTRL_CPY_PROT_POS = 0, + AUDIO20_OUT_TERM_CTRL_CONNECTOR_POS = 2, + AUDIO20_OUT_TERM_CTRL_OVERLOAD_POS = 4, + AUDIO20_OUT_TERM_CTRL_UNDERFLOW_POS = 6, + AUDIO20_OUT_TERM_CTRL_OVERFLOW_POS = 8, +} audio20_terminal_output_control_pos_t; + +/// Audio Class-Feature Unit Controls UAC2 +typedef enum { + AUDIO20_FEATURE_UNIT_CTRL_MUTE_POS = 0, + AUDIO20_FEATURE_UNIT_CTRL_VOLUME_POS = 2, + AUDIO20_FEATURE_UNIT_CTRL_BASS_POS = 4, + AUDIO20_FEATURE_UNIT_CTRL_MID_POS = 6, + AUDIO20_FEATURE_UNIT_CTRL_TREBLE_POS = 8, + AUDIO20_FEATURE_UNIT_CTRL_GRAPHIC_EQU_POS = 10, + AUDIO20_FEATURE_UNIT_CTRL_AGC_POS = 12, + AUDIO20_FEATURE_UNIT_CTRL_DELAY_POS = 14, + AUDIO20_FEATURE_UNIT_CTRL_BASS_BOOST_POS = 16, + AUDIO20_FEATURE_UNIT_CTRL_LOUDNESS_POS = 18, + AUDIO20_FEATURE_UNIT_CTRL_INPUT_GAIN_POS = 20, + AUDIO20_FEATURE_UNIT_CTRL_INPUT_GAIN_PAD_POS = 22, + AUDIO20_FEATURE_UNIT_CTRL_PHASE_INV_POS = 24, + AUDIO20_FEATURE_UNIT_CTRL_UNDERFLOW_POS = 26, + AUDIO20_FEATURE_UNIT_CTRL_OVERFLOW_POS = 28, +} audio20_feature_unit_control_pos_t; + +//--------------------------------------------------------------------+ +// USB AUDIO CLASS 2.0 (UAC2) DESCRIPTORS +//--------------------------------------------------------------------+ + +/// AUDIO Channel Cluster Descriptor UAC2 (4.1) +typedef struct TU_ATTR_PACKED { + uint8_t bNrChannels; ///< Number of channels currently connected. + uint32_t bmChannelConfig;///< Bitmap according to 'audio20_channel_config_t' with a 1 set if channel is connected and 0 else. In case channels are non-predefined ignore them here (see UAC2 specification 4.1 Audio Channel Cluster Descriptor. + uint8_t iChannelNames; ///< Index of a string descriptor, describing the name of the first inserted channel with a non-predefined spatial location. +} audio20_desc_channel_cluster_t; + +/// AUDIO Class-Specific AC Interface Header Descriptor UAC2 (4.7.2) +typedef struct TU_ATTR_PACKED { + uint8_t bLength; ///< Size of this descriptor in bytes: 9. + uint8_t bDescriptorType; ///< Descriptor Type. Value: TUSB_DESC_CS_INTERFACE. + uint8_t bDescriptorSubType;///< Descriptor SubType. Value: AUDIO20_CS_AC_INTERFACE_HEADER. + uint16_t bcdADC; ///< Audio Device Class Specification Release Number in Binary-Coded Decimal. Value: U16_TO_U8S_LE(0x0200). + uint8_t bCategory; ///< Constant, indicating the primary use of this audio function, as intended by the manufacturer. See: audio20_function_code_t. + uint16_t wTotalLength; ///< Total number of bytes returned for the class-specific AudioControl interface descriptor. Includes the combined length of this descriptor header and all Clock Source, Unit and Terminal descriptors. + uint8_t bmControls; ///< See: audio20_cs_ac_interface_control_pos_t. +} audio20_desc_cs_ac_interface_t; +TU_VERIFY_STATIC(sizeof(audio20_desc_cs_ac_interface_t) == 9, "size is not correct"); + +/// AUDIO Clock Source Descriptor UAC2 (4.7.2.1) +typedef struct TU_ATTR_PACKED { + uint8_t bLength; ///< Size of this descriptor in bytes: 8. + uint8_t bDescriptorType; ///< Descriptor Type. Value: TUSB_DESC_CS_INTERFACE. + uint8_t bDescriptorSubType;///< Descriptor SubType. Value: AUDIO20_CS_AC_INTERFACE_CLOCK_SOURCE. + uint8_t bClockID; ///< Constant uniquely identifying the Clock Source Entity within the audio function. This value is used in all requests to address this Entity. + uint8_t bmAttributes; ///< See: audio20_clock_source_attribute_t. + uint8_t bmControls; ///< See: audio20_clock_source_control_pos_t. + uint8_t bAssocTerminal; ///< Terminal ID of the Terminal that is associated with this Clock Source. + uint8_t iClockSource; ///< Index of a string descriptor, describing the Clock Source Entity. +} audio20_desc_clock_source_t; + +/// AUDIO Clock Selector Descriptor UAC2 (4.7.2.2) for ONE pin +typedef struct TU_ATTR_PACKED { + uint8_t bLength; ///< Size of this descriptor, in bytes: 7+p. + uint8_t bDescriptorType; ///< Descriptor Type. Value: TUSB_DESC_CS_INTERFACE. + uint8_t bDescriptorSubType;///< Descriptor SubType. Value: AUDIO20_CS_AC_INTERFACE_CLOCK_SELECTOR. + uint8_t bClockID; ///< Constant uniquely identifying the Clock Selector Entity within the audio function. This value is used in all requests to address this Entity. + uint8_t bNrInPins; ///< Number of Input Pins of this Unit: p = 1 thus bNrInPins = 1. + uint8_t baCSourceID; ///< ID of the Clock Entity to which the first Clock Input Pin of this Clock Selector Entity is connected.. + uint8_t bmControls; ///< See: audio20_clock_selector_control_pos_t. + uint8_t iClockSource; ///< Index of a string descriptor, describing the Clock Selector Entity. +} audio20_desc_clock_selector_t; + +/// AUDIO Clock Selector Descriptor (4.7.2.2) for multiple pins +#define audio20_desc_clock_selector_n_t(source_num) \ + struct TU_ATTR_PACKED { \ + uint8_t bLength; \ + uint8_t bDescriptorType; \ + uint8_t bDescriptorSubType; \ + uint8_t bClockID; \ + uint8_t bNrInPins; \ + struct TU_ATTR_PACKED { \ + uint8_t baSourceID; \ + } sourceID[source_num]; \ + uint8_t bmControls; \ + uint8_t iClockSource; \ + } + +/// AUDIO Clock Multiplier Descriptor UAC2 (4.7.2.3) +typedef struct TU_ATTR_PACKED { + uint8_t bLength; ///< Size of this descriptor, in bytes: 7. + uint8_t bDescriptorType; ///< Descriptor Type. Value: TUSB_DESC_CS_INTERFACE. + uint8_t bDescriptorSubType;///< Descriptor SubType. Value: AUDIO20_CS_AC_INTERFACE_CLOCK_MULTIPLIER. + uint8_t bClockID; ///< Constant uniquely identifying the Clock Multiplier Entity within the audio function. This value is used in all requests to address this Entity. + uint8_t bCSourceID; ///< ID of the Clock Entity to which the last Clock Input Pin of this Clock Selector Entity is connected. + uint8_t bmControls; ///< See: audio20_clock_multiplier_control_pos_t. + uint8_t iClockSource; ///< Index of a string descriptor, describing the Clock Multiplier Entity. +} audio20_desc_clock_multiplier_t; + +/// AUDIO Input Terminal Descriptor(4.7.2.4) +typedef struct TU_ATTR_PACKED { + uint8_t bLength; ///< Size of this descriptor, in bytes: 17. + uint8_t bDescriptorType; ///< Descriptor Type. Value: TUSB_DESC_CS_INTERFACE. + uint8_t bDescriptorSubType;///< Descriptor SubType. Value: AUDIO_CS_AC_INTERFACE_INPUT_TERMINAL. + uint8_t bTerminalID; ///< Constant uniquely identifying the Terminal within the audio function. This value is used in all requests to address this terminal. + uint16_t wTerminalType; ///< Constant characterizing the type of Terminal. See: audio_terminal_type_t for USB streaming and audio_terminal_input_type_t for other input types. + uint8_t bAssocTerminal; ///< ID of the Output Terminal to which this Input Terminal is associated. + uint8_t bCSourceID; ///< ID of the Clock Entity to which this Input Terminal is connected. + uint8_t bNrChannels; ///< Number of logical output channels in the Terminal’s output audio channel cluster. + uint32_t bmChannelConfig; ///< Describes the spatial location of the logical channels. See:audio20_channel_config_t. + uint8_t iChannelNames; ///< Index of a string descriptor, describing the name of the first logical channel. + uint16_t bmControls; ///< See: audio_terminal_input_control_pos_t. + uint8_t iTerminal; ///< Index of a string descriptor, describing the Input Terminal. +} audio20_desc_input_terminal_t; + +/// AUDIO Output Terminal Descriptor UAC2 (4.7.2.5) +typedef struct TU_ATTR_PACKED { + uint8_t bLength; ///< Size of this descriptor, in bytes: 12. + uint8_t bDescriptorType; ///< Descriptor Type. Value: TUSB_DESC_CS_INTERFACE. + uint8_t bDescriptorSubType;///< Descriptor SubType. Value: AUDIO20_CS_AC_INTERFACE_OUTPUT_TERMINAL. + uint8_t bTerminalID; ///< Constant uniquely identifying the Terminal within the audio function. This value is used in all requests to address this Terminal. + uint16_t wTerminalType; ///< Constant characterizing the type of Terminal. See: audio20_terminal_type_t for USB streaming and audio20_terminal_output_type_t for other output types. + uint8_t bAssocTerminal; ///< Constant, identifying the Input Terminal to which this Output Terminal is associated. + uint8_t bSourceID; ///< ID of the Unit or Terminal to which this Terminal is connected. + uint8_t bCSourceID; ///< ID of the Clock Entity to which this Output Terminal is connected. + uint16_t bmControls; ///< See: audio20_terminal_output_control_pos_t. + uint8_t iTerminal; ///< Index of a string descriptor, describing the Output Terminal. +} audio20_desc_output_terminal_t; + +/// AUDIO Feature Unit Descriptor UAC2 (4.7.2.8) for ONE channel +typedef struct TU_ATTR_PACKED { + uint8_t bLength; ///< Size of this descriptor, in bytes: 14. + uint8_t bDescriptorType; ///< Descriptor Type. Value: TUSB_DESC_CS_INTERFACE. + uint8_t bDescriptorSubType;///< Descriptor SubType. Value: AUDIO20_CS_AC_INTERFACE_FEATURE_UNIT. + uint8_t bUnitID; ///< Constant uniquely identifying the Unit within the audio function. This value is used in all requests to address this Unit. + uint8_t bSourceID; ///< ID of the Unit or Terminal to which this Feature Unit is connected. + struct TU_ATTR_PACKED { + uint32_t bmaControls;///< See: audio20_feature_unit_control_pos_t. Controls0 is master channel 0 (always present) and Controls1 is logical channel 1. + } controls[2]; + uint8_t iTerminal;///< Index of a string descriptor, describing this Feature Unit. +} audio20_desc_feature_unit_t; + +/// AUDIO Feature Unit Descriptor(4.7.2.8) for multiple channels +#define audio20_desc_feature_unit_n_t(ch_num) \ + struct TU_ATTR_PACKED { \ + uint8_t bLength; /* 6+(ch_num+1)*4 */ \ + uint8_t bDescriptorType; \ + uint8_t bDescriptorSubType; \ + uint8_t bUnitID; \ + uint8_t bSourceID; \ + struct TU_ATTR_PACKED { \ + uint32_t bmaControls; \ + } controls[ch_num + 1]; \ + uint8_t iTerminal; \ + } + +/// AUDIO Class-Specific AS Interface Descriptor(4.9.2) +typedef struct TU_ATTR_PACKED { + uint8_t bLength; ///< Size of this descriptor, in bytes: 16. + uint8_t bDescriptorType; ///< Descriptor Type. Value: TUSB_DESC_CS_INTERFACE. + uint8_t bDescriptorSubType;///< Descriptor SubType. Value: AUDIO20_CS_AS_INTERFACE_AS_GENERAL. + uint8_t bTerminalLink; ///< The Terminal ID of the Terminal to which this interface is connected. + uint8_t bmControls; ///< See: audio20_cs_as_interface_control_pos_t. + uint8_t bFormatType; ///< Constant identifying the Format Type the AudioStreaming interface is using. See: audio20_format_type_t. + uint32_t bmFormats; ///< The Audio Data Format(s) that can be used to communicate with this interface.See: audio20_data_format_type_I_t. + uint8_t bNrChannels; ///< Number of physical channels in the AS Interface audio channel cluster. + uint32_t bmChannelConfig; ///< Describes the spatial location of the physical channels. See: audio20_channel_config_t. + uint8_t iChannelNames; ///< Index of a string descriptor, describing the name of the first physical channel. +} audio20_desc_cs_as_interface_t; + +/// AUDIO Type I Format Type Descriptor(2.3.1.6 - Audio Formats) +typedef struct TU_ATTR_PACKED { + uint8_t bLength; ///< Size of this descriptor, in bytes: 6. + uint8_t bDescriptorType; ///< Descriptor Type. Value: TUSB_DESC_CS_INTERFACE. + uint8_t bDescriptorSubType;///< Descriptor SubType. Value: AUDIO20_CS_AS_INTERFACE_FORMAT_TYPE. + uint8_t bFormatType; ///< Constant identifying the Format Type the AudioStreaming interface is using. Value: AUDIO20_FORMAT_TYPE_I. + uint8_t bSubslotSize; ///< The number of bytes occupied by one audio subslot. Can be 1, 2, 3 or 4. + uint8_t bBitResolution; ///< The number of effectively used bits from the available bits in an audio subslot. +} audio20_desc_type_I_format_t; + +/// AUDIO Class-Specific AS Isochronous Audio Data Endpoint Descriptor(4.10.1.2) +typedef struct TU_ATTR_PACKED { + uint8_t bLength; ///< Size of this descriptor, in bytes: 8. + uint8_t bDescriptorType; ///< Descriptor Type. Value: TUSB_DESC_CS_ENDPOINT. + uint8_t bDescriptorSubType;///< Descriptor SubType. Value: AUDIO20_CS_EP_SUBTYPE_GENERAL. + uint8_t bmAttributes; ///< See: audio20_cs_as_iso_data_ep_attribute_t. + uint8_t bmControls; ///< See: audio20_cs_as_iso_data_ep_control_pos_t. + uint8_t bLockDelayUnits; ///< Indicates the units used for the wLockDelay field. See: audio20_cs_as_iso_data_ep_lock_delay_unit_t. + uint16_t wLockDelay; ///< Indicates the time it takes this endpoint to reliably lock its internal clock recovery circuitry. Units used depend on the value of the bLockDelayUnits field. +} audio20_desc_cs_as_iso_data_ep_t; + +// 5.2.2 Control Request Layout +typedef struct TU_ATTR_PACKED { + union { + struct TU_ATTR_PACKED { + uint8_t recipient : 5;///< Recipient type tusb_request_recipient_t. + uint8_t type : 2; ///< Request type tusb_request_type_t. + uint8_t direction : 1;///< Direction type. tusb_dir_t + } bmRequestType_bit; + + uint8_t bmRequestType; + }; + + uint8_t bRequest;///< Request type audio_cs_req_t + uint8_t bChannelNumber; + uint8_t bControlSelector; + union { + uint8_t bInterface; + uint8_t bEndpoint; + }; + uint8_t bEntityID; + uint16_t wLength; +} audio20_control_request_t; + +//// 5.2.3 Control Request Parameter Block Layout + +// 5.2.3.1 1-byte Control CUR Parameter Block +typedef struct TU_ATTR_PACKED { + int8_t bCur;///< The setting for the CUR attribute of the addressed Control +} audio20_control_cur_1_t; + +// 5.2.3.2 2-byte Control CUR Parameter Block +typedef struct TU_ATTR_PACKED { + int16_t bCur;///< The setting for the CUR attribute of the addressed Control +} audio20_control_cur_2_t; + +// 5.2.3.3 4-byte Control CUR Parameter Block +typedef struct TU_ATTR_PACKED { + int32_t bCur;///< The setting for the CUR attribute of the addressed Control +} audio20_control_cur_4_t; + +// Use the following ONLY for RECEIVED data - compiler does not know how many subranges are defined! Use the #define macros below for predefined lengths. + +// 5.2.3.1 1-byte Control RANGE Parameter Block +#define audio20_control_range_1_n_t(numSubRanges) \ + struct TU_ATTR_PACKED { \ + uint16_t wNumSubRanges; \ + struct TU_ATTR_PACKED { \ + int8_t bMin; /*The setting for the MIN attribute of the nth subrange of the addressed Control*/ \ + int8_t bMax; /*The setting for the MAX attribute of the nth subrange of the addressed Control*/ \ + uint8_t bRes; /*The setting for the RES attribute of the nth subrange of the addressed Control*/ \ + } subrange[numSubRanges]; \ + } + +/// 5.2.3.2 2-byte Control RANGE Parameter Block +#define audio20_control_range_2_n_t(numSubRanges) \ + struct TU_ATTR_PACKED { \ + uint16_t wNumSubRanges; \ + struct TU_ATTR_PACKED { \ + int16_t bMin; /*The setting for the MIN attribute of the nth subrange of the addressed Control*/ \ + int16_t bMax; /*The setting for the MAX attribute of the nth subrange of the addressed Control*/ \ + uint16_t bRes; /*The setting for the RES attribute of the nth subrange of the addressed Control*/ \ + } subrange[numSubRanges]; \ + } + +// 5.2.3.3 4-byte Control RANGE Parameter Block +#define audio20_control_range_4_n_t(numSubRanges) \ + struct TU_ATTR_PACKED { \ + uint16_t wNumSubRanges; \ + struct TU_ATTR_PACKED { \ + int32_t bMin; /*The setting for the MIN attribute of the nth subrange of the addressed Control*/ \ + int32_t bMax; /*The setting for the MAX attribute of the nth subrange of the addressed Control*/ \ + uint32_t bRes; /*The setting for the RES attribute of the nth subrange of the addressed Control*/ \ + } subrange[numSubRanges]; \ + } + +// 6.1 Interrupt Data Message Format +typedef struct TU_ATTR_PACKED { + uint8_t bInfo; + uint8_t bAttribute; + union { + uint16_t wValue; + struct { + uint8_t wValue_cn_or_mcn; + uint8_t wValue_cs; + }; + }; + union { + uint16_t wIndex; + struct { + uint8_t wIndex_ep_or_int; + uint8_t wIndex_entity_id; + }; + }; +} audio20_interrupt_data_t; + +//--------------------------------------------------------------------+ +// APPLICATION HELPER DEFINITIONS +//--------------------------------------------------------------------+ + +// Combined Interrupt Data Message Format for both UAC1 and UAC2 +typedef union { + audio10_interrupt_data_t v1; + audio20_interrupt_data_t v2; +} audio_interrupt_data_t; + +// MIDI1.0 use the same CS AC Interface Descriptor as UAC1 +typedef audio10_desc_cs_ac_interface_n_t(1) midi10_desc_cs_ac_interface_t; + +// UAC1.0 AC Interface Descriptor with 1 interface, used to read fields other than baInterfaceNr +typedef audio10_desc_cs_ac_interface_n_t(1) audio10_desc_cs_ac_interface_1_t; + +/** @} */ + +#ifdef __cplusplus +} +#endif + +#endif + +/** @} */ diff --git a/libraries/Adafruit_TinyUSB_Arduino/src/class/audio/audio_device.c b/libraries/Adafruit_TinyUSB_Arduino/src/class/audio/audio_device.c new file mode 100644 index 0000000..995bf8a --- /dev/null +++ b/libraries/Adafruit_TinyUSB_Arduino/src/class/audio/audio_device.c @@ -0,0 +1,1905 @@ +/* + * The MIT License (MIT) + * + * Copyright (c) 2020 Reinhard Panhuber, Jerzy Kasenberg + * Copyright (c) 2023 HiFiPhile + * + * 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. + */ + +/* + * This driver supports at most one out EP, one in EP, one control EP, and one feedback EP and one alternative interface other than zero. Hence, only one input terminal and one output terminal are support, if you need more adjust the driver! + * It supports multiple TX and RX channels. + * + * In case you need more alternate interfaces, you need to define additional defines for this specific alternate interface. Just define them and set them in the set_interface function. + * + * There are three data flow structures currently implemented, where at least one SW-FIFO is used to decouple the asynchronous processes MCU vs. host + * + * 1. Input data -> SW-FIFO -> MCU USB + * + * The most easiest version, available in case the target MCU can handle the software FIFO (SW-FIFO) and if it is implemented in the device driver (if yes then dcd_edpt_xfer_fifo() is available) + * + * 2. Input data -> SW-FIFO -> Linear buffer -> MCU USB + * + * In case the target MCU can not handle a SW-FIFO, a linear buffer is used. This uses the default function dcd_edpt_xfer(). In this case more memory is required. + * + * 3. (Input data 1 | Input data 2 | ... | Input data N) -> (SW-FIFO 1 | SW-FIFO 2 | ... | SW-FIFO N) -> Linear buffer -> MCU USB + * + * This case is used if you have more channels which need to be combined into one stream. Every channel has its own SW-FIFO. All data is encoded into an Linear buffer. + * + * The same holds in the RX case. + * + * */ + +#include "tusb_option.h" + +#if (CFG_TUD_ENABLED && CFG_TUD_AUDIO) + +//--------------------------------------------------------------------+ +// INCLUDE +//--------------------------------------------------------------------+ +#include "device/usbd.h" +#include "device/usbd_pvt.h" + +#include "audio_device.h" + +//--------------------------------------------------------------------+ +// MACRO CONSTANT TYPEDEF +//--------------------------------------------------------------------+ + +// Declaration of buffers + +// Check for maximum supported numbers +#if CFG_TUD_AUDIO > 3 + #error Maximum number of audio functions restricted to three! +#endif + +// Put swap buffer in USB section only if necessary +#if !CFG_TUD_EDPT_DEDICATED_HWFIFO + #define IN_SW_BUF_MEM_ATTR TU_ATTR_ALIGNED(4) +#else + #define IN_SW_BUF_MEM_ATTR CFG_TUD_MEM_SECTION CFG_TUD_MEM_ALIGN +#endif +#if !CFG_TUD_EDPT_DEDICATED_HWFIFO + #define OUT_SW_BUF_MEM_ATTR TU_ATTR_ALIGNED(4) +#else + #define OUT_SW_BUF_MEM_ATTR CFG_TUD_MEM_SECTION CFG_TUD_MEM_ALIGN +#endif + +// EP IN software buffers +#if CFG_TUD_AUDIO_ENABLE_EP_IN +tu_static IN_SW_BUF_MEM_ATTR struct { + #if CFG_TUD_AUDIO_FUNC_1_EP_IN_SW_BUF_SZ > 0 + TUD_EPBUF_DEF(buf_1, CFG_TUD_AUDIO_FUNC_1_EP_IN_SW_BUF_SZ); + #endif + #if CFG_TUD_AUDIO > 1 && CFG_TUD_AUDIO_FUNC_2_EP_IN_SW_BUF_SZ > 0 + TUD_EPBUF_DEF(buf_2, CFG_TUD_AUDIO_FUNC_2_EP_IN_SW_BUF_SZ); + #endif + #if CFG_TUD_AUDIO > 2 && CFG_TUD_AUDIO_FUNC_3_EP_IN_SW_BUF_SZ > 0 + TUD_EPBUF_DEF(buf_3, CFG_TUD_AUDIO_FUNC_3_EP_IN_SW_BUF_SZ); + #endif +} ep_in_sw_buf; +#endif// CFG_TUD_AUDIO_ENABLE_EP_IN + +// Linear buffer TX in case: +// - target MCU is not capable of handling a ring buffer FIFO e.g. no hardware buffer is available or driver is would need to be changed dramatically OR +#if CFG_TUD_AUDIO_ENABLE_EP_IN && !CFG_TUD_EDPT_DEDICATED_HWFIFO +tu_static CFG_TUD_MEM_SECTION struct { + #if CFG_TUD_AUDIO_FUNC_1_EP_IN_SZ_MAX > 0 + TUD_EPBUF_DEF(buf_1, CFG_TUD_AUDIO_FUNC_1_EP_IN_SZ_MAX); + #endif + #if CFG_TUD_AUDIO > 1 && CFG_TUD_AUDIO_FUNC_2_EP_IN_SZ_MAX > 0 + TUD_EPBUF_DEF(buf_2, CFG_TUD_AUDIO_FUNC_2_EP_IN_SZ_MAX); + #endif + #if CFG_TUD_AUDIO > 2 && CFG_TUD_AUDIO_FUNC_3_EP_IN_SZ_MAX > 0 + TUD_EPBUF_DEF(buf_3, CFG_TUD_AUDIO_FUNC_3_EP_IN_SZ_MAX); + #endif +} lin_buf_in; +#endif// CFG_TUD_AUDIO_ENABLE_EP_IN && !CFG_TUD_EDPT_DEDICATED_HWFIFO + +// EP OUT software buffers +#if CFG_TUD_AUDIO_ENABLE_EP_OUT +tu_static OUT_SW_BUF_MEM_ATTR struct { + #if CFG_TUD_AUDIO_FUNC_1_EP_OUT_SW_BUF_SZ > 0 + TUD_EPBUF_DEF(buf_1, CFG_TUD_AUDIO_FUNC_1_EP_OUT_SW_BUF_SZ); + #endif + #if CFG_TUD_AUDIO > 1 && CFG_TUD_AUDIO_FUNC_2_EP_OUT_SW_BUF_SZ > 0 + TUD_EPBUF_DEF(buf_2, CFG_TUD_AUDIO_FUNC_2_EP_OUT_SW_BUF_SZ); + #endif + #if CFG_TUD_AUDIO > 2 && CFG_TUD_AUDIO_FUNC_3_EP_OUT_SW_BUF_SZ > 0 + TUD_EPBUF_DEF(buf_3, CFG_TUD_AUDIO_FUNC_3_EP_OUT_SW_BUF_SZ); + #endif +} ep_out_sw_buf; +#endif// CFG_TUD_AUDIO_ENABLE_EP_OUT + +// Linear buffer RX in case: +// - target MCU is not capable of handling a ring buffer FIFO e.g. no hardware buffer is available or driver is would need to be changed dramatically OR +#if CFG_TUD_AUDIO_ENABLE_EP_OUT && !CFG_TUD_EDPT_DEDICATED_HWFIFO +tu_static CFG_TUD_MEM_SECTION struct { + #if CFG_TUD_AUDIO_FUNC_1_EP_OUT_SZ_MAX > 0 + TUD_EPBUF_DEF(buf_1, CFG_TUD_AUDIO_FUNC_1_EP_OUT_SZ_MAX); + #endif + #if CFG_TUD_AUDIO > 1 && CFG_TUD_AUDIO_FUNC_2_EP_OUT_SZ_MAX > 0 + TUD_EPBUF_DEF(buf_2, CFG_TUD_AUDIO_FUNC_2_EP_OUT_SZ_MAX); + #endif + #if CFG_TUD_AUDIO > 2 && CFG_TUD_AUDIO_FUNC_3_EP_OUT_SZ_MAX > 0 + TUD_EPBUF_DEF(buf_3, CFG_TUD_AUDIO_FUNC_3_EP_OUT_SZ_MAX); + #endif +} lin_buf_out; +#endif// CFG_TUD_AUDIO_ENABLE_EP_OUT && !CFG_TUD_EDPT_DEDICATED_HWFIFO + +// Control buffer +#if CFG_TUD_AUDIO_CTRL_BUF_SZ > CFG_TUD_ENDPOINT0_BUFSIZE +tu_static CFG_TUD_MEM_ALIGN uint8_t ctrl_buf[CFG_TUD_AUDIO_CTRL_BUF_SZ]; +#endif + +// Aligned buffer for feedback EP +#if CFG_TUD_AUDIO_ENABLE_EP_OUT && CFG_TUD_AUDIO_ENABLE_FEEDBACK_EP +tu_static CFG_TUD_MEM_SECTION struct { + #if CFG_TUD_AUDIO_FUNC_1_EP_OUT_SZ_MAX > 0 + TUD_EPBUF_TYPE_DEF(uint32_t, buf_1); + #endif + #if CFG_TUD_AUDIO > 1 && CFG_TUD_AUDIO_FUNC_2_EP_OUT_SZ_MAX > 0 + TUD_EPBUF_TYPE_DEF(uint32_t, buf_2); + #endif + #if CFG_TUD_AUDIO > 2 && CFG_TUD_AUDIO_FUNC_3_EP_OUT_SZ_MAX > 0 + TUD_EPBUF_TYPE_DEF(uint32_t, buf_3); + #endif +} fb_ep_buf; +#endif + +// Aligned buffer for interrupt EP +#if CFG_TUD_AUDIO_ENABLE_INTERRUPT_EP +tu_static CFG_TUD_MEM_SECTION struct { + TUD_EPBUF_DEF(buf, CFG_TUD_AUDIO_INTERRUPT_EP_SZ); +} int_ep_buf[CFG_TUD_AUDIO]; +#endif + +typedef struct +{ + uint8_t rhport; + uint8_t const *p_desc;// Pointer pointing to Standard AC Interface Descriptor(4.7.1) - Audio Control descriptor defining audio function + uint8_t const *p_desc_as;// Pointer pointing to 1st Standard AS Interface Descriptor(4.9.1) - Audio Streaming descriptor defining audio function + uint16_t desc_length;// Length of audio function descriptor + +#if CFG_TUD_AUDIO_ENABLE_EP_IN + uint8_t ep_in; // TX audio data EP. + uint16_t ep_in_sz; // Current size of TX EP + uint8_t ep_in_as_intf_num;// Corresponding Standard AS Interface Descriptor (4.9.1) belonging to output terminal to which this EP belongs - 0 is invalid (this fits to UAC2 specification since AS interfaces can not have interface number equal to zero) + uint8_t ep_in_alt; // Current alternate setting of TX EP + uint16_t ep_in_fifo_threshold;// Target size for the EP IN FIFO. + #endif + +#if CFG_TUD_AUDIO_ENABLE_EP_OUT + uint8_t ep_out; // Incoming (into uC) audio data EP. + uint16_t ep_out_sz; // Current size of RX EP + uint8_t ep_out_as_intf_num;// Corresponding Standard AS Interface Descriptor (4.9.1) belonging to input terminal to which this EP belongs - 0 is invalid (this fits to UAC2 specification since AS interfaces can not have interface number equal to zero) + uint8_t ep_out_alt; // Current alternate setting of RX EP + #if CFG_TUD_AUDIO_ENABLE_FEEDBACK_EP + uint8_t ep_fb;// Feedback EP. + #endif + +#endif + +#if CFG_TUD_AUDIO_ENABLE_INTERRUPT_EP + uint8_t ep_int;// Audio control interrupt EP. +#endif + + bool mounted;// Device opened + +#if CFG_TUD_AUDIO_ENABLE_FEEDBACK_EP + struct { + uint32_t value; // Feedback value for asynchronous mode (in 16.16 format). + uint32_t min_value;// min value according to UAC2 FMT-2.0 section 2.3.1.1. + uint32_t max_value;// max value according to UAC2 FMT-2.0 section 2.3.1.1. + + uint8_t frame_shift;// bInterval-1 in unit of frame (FS), micro-frame (HS) + uint8_t compute_method; + union { + uint8_t power_of_2;// pre-computed power of 2 shift + float float_const; // pre-computed float constant + + struct { + uint32_t sample_freq; + uint32_t mclk_freq; + } fixed; + + struct { + uint32_t nom_value; // In 16.16 format + uint32_t fifo_lvl_avg; // In 16.16 format + uint16_t fifo_lvl_thr; // fifo level threshold + uint16_t rate_const[2];// pre-computed feedback/fifo_depth rate + } fifo_count; + } compute; + + } feedback; +#endif// CFG_TUD_AUDIO_ENABLE_FEEDBACK_EP + +#if CFG_TUD_AUDIO_ENABLE_EP_IN && CFG_TUD_AUDIO_EP_IN_FLOW_CONTROL + uint32_t sample_rate_tx; + uint16_t packet_sz_tx[3]; + uint8_t bclock_id_tx; + uint8_t interval_tx; + uint8_t format_type_tx; + uint8_t n_channels_tx; + uint8_t n_bytes_per_sample_tx; +#endif + + /*------------- From this point, data is not cleared by bus reset -------------*/ + +// EP Transfer buffers and FIFOs +#if CFG_TUD_AUDIO_ENABLE_EP_OUT + tu_fifo_t ep_out_ff; +#endif + +#if CFG_TUD_AUDIO_ENABLE_EP_IN + tu_fifo_t ep_in_ff; +#endif + +// Linear buffer in case target MCU is not capable of handling a ring buffer FIFO e.g. no hardware buffer is available or driver is would need to be changed dramatically +#if CFG_TUD_AUDIO_ENABLE_EP_OUT && !CFG_TUD_EDPT_DEDICATED_HWFIFO + uint8_t *lin_buf_out; +#endif + +#if CFG_TUD_AUDIO_ENABLE_EP_IN && !CFG_TUD_EDPT_DEDICATED_HWFIFO + uint8_t *lin_buf_in; +#endif + +#if CFG_TUD_AUDIO_ENABLE_FEEDBACK_EP + uint32_t *fb_buf; +#endif +} audiod_function_t; + +#if CFG_TUD_AUDIO_ENABLE_EP_OUT +#define ITF_MEM_RESET_SIZE offsetof(audiod_function_t, ep_out_ff) +#else +#define ITF_MEM_RESET_SIZE offsetof(audiod_function_t, ep_in_ff) +#endif + +//--------------------------------------------------------------------+ +// WEAK FUNCTION STUBS +//--------------------------------------------------------------------+ + +#if CFG_TUD_AUDIO_ENABLE_EP_IN +TU_ATTR_WEAK bool tud_audio_tx_done_isr(uint8_t rhport, uint16_t n_bytes_sent, uint8_t func_id, uint8_t ep_in, uint8_t cur_alt_setting) { + (void) rhport; + (void) n_bytes_sent; + (void) func_id; + (void) ep_in; + (void) cur_alt_setting; + return true; +} + +#endif + +#if CFG_TUD_AUDIO_ENABLE_EP_OUT +TU_ATTR_WEAK bool tud_audio_rx_done_isr(uint8_t rhport, uint16_t n_bytes_received, uint8_t func_id, uint8_t ep_out, uint8_t cur_alt_setting) { + (void) rhport; + (void) n_bytes_received; + (void) func_id; + (void) ep_out; + (void) cur_alt_setting; + return true; +} +#endif + +#if CFG_TUD_AUDIO_ENABLE_EP_OUT && CFG_TUD_AUDIO_ENABLE_FEEDBACK_EP +TU_ATTR_WEAK void tud_audio_feedback_params_cb(uint8_t func_id, uint8_t alt_itf, audio_feedback_params_t *feedback_param) { + (void) func_id; + (void) alt_itf; + feedback_param->method = AUDIO_FEEDBACK_METHOD_DISABLED; +} + +TU_ATTR_WEAK TU_ATTR_FAST_FUNC void tud_audio_feedback_interval_isr(uint8_t func_id, uint32_t frame_number, uint8_t interval_shift) { + (void) func_id; + (void) frame_number; + (void) interval_shift; +} +#endif + +#if CFG_TUD_AUDIO_ENABLE_INTERRUPT_EP +TU_ATTR_WEAK void tud_audio_int_done_cb(uint8_t rhport) { + (void) rhport; +} +#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) { + (void) rhport; + (void) p_request; + return true; +} + +// 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) { + (void) rhport; + (void) p_request; + return true; +} + +// 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) { + (void) rhport; + (void) p_request; + (void) pBuff; + 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 +} + +// 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) { + (void) rhport; + (void) p_request; + (void) pBuff; + 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 +} + +// 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) { + (void) rhport; + (void) p_request; + (void) pBuff; + 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 +} + +// 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) { + (void) rhport; + (void) p_request; + TU_LOG2(" No EP get request callback available!\r\n"); + return false;// Stall +} + +// 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) { + (void) rhport; + (void) p_request; + TU_LOG2(" No interface get request callback available!\r\n"); + return false;// Stall +} + +// 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) { + (void) rhport; + (void) p_request; + TU_LOG2(" No entity get request callback available!\r\n"); + return false;// Stall +} + +//--------------------------------------------------------------------+ +// INTERNAL OBJECT & FUNCTION DECLARATION +//--------------------------------------------------------------------+ +tu_static CFG_TUD_MEM_SECTION audiod_function_t _audiod_fct[CFG_TUD_AUDIO]; + +#if CFG_TUD_AUDIO_ENABLE_EP_OUT +static bool audiod_rx_xfer_isr(uint8_t rhport, audiod_function_t* audio, uint16_t n_bytes_received); +#endif + +#if CFG_TUD_AUDIO_ENABLE_EP_IN +static bool audiod_tx_xfer_isr(uint8_t rhport, audiod_function_t* audio, uint16_t n_bytes_sent); +#endif + +static bool audiod_get_interface(uint8_t rhport, tusb_control_request_t const *p_request); +static bool audiod_set_interface(uint8_t rhport, tusb_control_request_t const *p_request); + +static bool audiod_verify_entity_exists(uint8_t itf, uint8_t entityID, uint8_t *func_id); +static bool audiod_verify_itf_exists(uint8_t itf, uint8_t *func_id); +static bool audiod_verify_ep_exists(uint8_t ep, uint8_t *func_id); +static inline uint8_t audiod_get_audio_fct_idx(audiod_function_t *audio); + +#if CFG_TUD_AUDIO_ENABLE_EP_IN && CFG_TUD_AUDIO_EP_IN_FLOW_CONTROL +static void audiod_parse_flow_control_params(audiod_function_t *audio, uint8_t const *p_desc); +static bool audiod_calc_tx_packet_sz(audiod_function_t *audio); +static uint16_t audiod_tx_packet_size(const uint16_t *nominal_size, uint16_t data_count, uint16_t fifo_depth, uint16_t fifo_threshold, uint16_t max_size); +#endif + +#if CFG_TUD_AUDIO_ENABLE_EP_OUT && CFG_TUD_AUDIO_ENABLE_FEEDBACK_EP +static bool audiod_fb_params_prepare(uint8_t func_id, uint8_t alt); +static void audiod_fb_fifo_count_update(audiod_function_t *audio, uint16_t lvl_new); +#endif + +bool tud_audio_n_mounted(uint8_t func_id) { + TU_VERIFY(func_id < CFG_TUD_AUDIO); + audiod_function_t *audio = &_audiod_fct[func_id]; + + return audio->mounted; +} + +static inline uint8_t* get_ctrl_buffer(void) { + // Use EP0 buffer if it is large enough, otherwise use dedicated buffer + #if CFG_TUD_AUDIO_CTRL_BUF_SZ > CFG_TUD_ENDPOINT0_BUFSIZE + return ctrl_buf; + #else + return usbd_get_ctrl_buf(); + #endif +} + +//--------------------------------------------------------------------+ +// READ API +//--------------------------------------------------------------------+ + +#if CFG_TUD_AUDIO_ENABLE_EP_OUT + +uint16_t tud_audio_n_available(uint8_t func_id) { + TU_VERIFY(func_id < CFG_TUD_AUDIO && _audiod_fct[func_id].p_desc != NULL); + return tu_fifo_count(&_audiod_fct[func_id].ep_out_ff); +} + +uint16_t tud_audio_n_read(uint8_t func_id, void *buffer, uint16_t bufsize) { + TU_VERIFY(func_id < CFG_TUD_AUDIO && _audiod_fct[func_id].p_desc != NULL); + return tu_fifo_read_n(&_audiod_fct[func_id].ep_out_ff, buffer, bufsize); +} + +bool tud_audio_n_clear_ep_out_ff(uint8_t func_id) { + TU_VERIFY(func_id < CFG_TUD_AUDIO && _audiod_fct[func_id].p_desc != NULL); + tu_fifo_clear(&_audiod_fct[func_id].ep_out_ff); + return true; +} + +tu_fifo_t *tud_audio_n_get_ep_out_ff(uint8_t func_id) { + if (func_id < CFG_TUD_AUDIO && _audiod_fct[func_id].p_desc != NULL) { + return &_audiod_fct[func_id].ep_out_ff; + } + return NULL; +} + +static bool audiod_rx_xfer_isr(uint8_t rhport, audiod_function_t* audio, uint16_t n_bytes_received) { + uint8_t idx_audio_fct = audiod_get_audio_fct_idx(audio); + + #if !CFG_TUD_EDPT_DEDICATED_HWFIFO + // Data currently is in linear buffer, copy into EP OUT FIFO + TU_VERIFY(0 < tu_fifo_write_n(&audio->ep_out_ff, audio->lin_buf_out, n_bytes_received)); + + // Schedule for next receive + TU_VERIFY(usbd_edpt_xfer(rhport, audio->ep_out, audio->lin_buf_out, audio->ep_out_sz, true)); + #else + // Data is already placed in EP FIFO, schedule for next receive + TU_VERIFY(usbd_edpt_xfer_fifo(rhport, audio->ep_out, &audio->ep_out_ff, audio->ep_out_sz, true)); + #endif + + #if CFG_TUD_AUDIO_ENABLE_FEEDBACK_EP + if (audio->feedback.compute_method == AUDIO_FEEDBACK_METHOD_FIFO_COUNT) { + audiod_fb_fifo_count_update(audio, tu_fifo_count(&audio->ep_out_ff)); + } + #endif + + // Call a weak callback here - a possibility for user to get informed an audio packet was received and data gets now loaded into EP FIFO + TU_VERIFY(tud_audio_rx_done_isr(rhport, n_bytes_received, idx_audio_fct, audio->ep_out, audio->ep_out_alt)); + + return true; +} + +#endif//CFG_TUD_AUDIO_ENABLE_EP_OUT + +//--------------------------------------------------------------------+ +// WRITE API +//--------------------------------------------------------------------+ + +#if CFG_TUD_AUDIO_ENABLE_EP_IN + +uint16_t tud_audio_n_write(uint8_t func_id, const void *data, uint16_t len) { + TU_VERIFY(func_id < CFG_TUD_AUDIO && _audiod_fct[func_id].p_desc != NULL); + return tu_fifo_write_n(&_audiod_fct[func_id].ep_in_ff, data, len); +} + +bool tud_audio_n_clear_ep_in_ff(uint8_t func_id) { + TU_VERIFY(func_id < CFG_TUD_AUDIO && _audiod_fct[func_id].p_desc != NULL); + tu_fifo_clear(&_audiod_fct[func_id].ep_in_ff); + return true; +} + +tu_fifo_t *tud_audio_n_get_ep_in_ff(uint8_t func_id) { + if (func_id < CFG_TUD_AUDIO && _audiod_fct[func_id].p_desc != NULL) { + return &_audiod_fct[func_id].ep_in_ff; + } + return NULL; +} + +uint16_t tud_audio_n_get_ep_in_fifo_threshold(uint8_t func_id) { + if (func_id < CFG_TUD_AUDIO) return _audiod_fct[func_id].ep_in_fifo_threshold; + return 0; +} + +void tud_audio_n_set_ep_in_fifo_threshold(uint8_t func_id, uint16_t threshold) { + if (func_id < CFG_TUD_AUDIO && threshold < _audiod_fct[func_id].ep_in_ff.depth) { + _audiod_fct[func_id].ep_in_fifo_threshold = threshold; + } +} + +static bool audiod_tx_xfer_isr(uint8_t rhport, audiod_function_t * audio, uint16_t n_bytes_sent) { + uint8_t idx_audio_fct = audiod_get_audio_fct_idx(audio); + + // Only send something if current alternate interface is not 0 as in this case nothing is to be sent due to UAC2 specifications + if (audio->ep_in_alt == 0) { return false; } + + // Send everything in ISO EP FIFO + uint16_t n_bytes_tx; + + #if CFG_TUD_AUDIO_EP_IN_FLOW_CONTROL + // packet_sz_tx is based on total packet size, here we want size for each support buffer. + n_bytes_tx = audiod_tx_packet_size(audio->packet_sz_tx, tu_fifo_count(&audio->ep_in_ff), audio->ep_in_ff.depth, audio->ep_in_fifo_threshold, audio->ep_in_sz); + #else + n_bytes_tx = tu_min16(tu_fifo_count(&audio->ep_in_ff), audio->ep_in_sz);// Limit up to max packet size, more can not be done for ISO + #endif + #if !CFG_TUD_EDPT_DEDICATED_HWFIFO + tu_fifo_read_n(&audio->ep_in_ff, audio->lin_buf_in, n_bytes_tx); + TU_VERIFY(usbd_edpt_xfer(rhport, audio->ep_in, audio->lin_buf_in, n_bytes_tx, true)); + #else + // Send everything in ISO EP FIFO + TU_VERIFY(usbd_edpt_xfer_fifo(rhport, audio->ep_in, &audio->ep_in_ff, n_bytes_tx, true)); + #endif + + // 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 + TU_VERIFY(tud_audio_tx_done_isr(rhport, n_bytes_sent, idx_audio_fct, audio->ep_in, audio->ep_in_alt)); + + return true; +} + +#endif + +//--------------------------------------------------------------------+ +// OTHER API +//--------------------------------------------------------------------+ + +#if CFG_TUD_AUDIO_ENABLE_INTERRUPT_EP +// If no interrupt transmit is pending bytes get written into buffer and a transmit is scheduled - once transmit completed tud_audio_int_done_cb() is called in inform user +bool tud_audio_int_n_write(uint8_t func_id, const audio_interrupt_data_t *data) { + TU_VERIFY(func_id < CFG_TUD_AUDIO && _audiod_fct[func_id].p_desc != NULL); + + TU_VERIFY(_audiod_fct[func_id].ep_int != 0); + + // We write directly into the EP's buffer - abort if previous transfer not complete + TU_VERIFY(usbd_edpt_claim(_audiod_fct[func_id].rhport, _audiod_fct[func_id].ep_int)); + + uint8_t size = tud_audio_n_version(func_id) == 2 ? sizeof(audio20_interrupt_data_t) : sizeof(audio10_interrupt_data_t); + + // INT EP buffer must be large enough + TU_ASSERT(size <= sizeof(int_ep_buf[func_id].buf)); + + // Check length + if (tu_memcpy_s(int_ep_buf[func_id].buf, sizeof(int_ep_buf[func_id].buf), data, size) == 0) { + // Schedule transmit + TU_ASSERT(usbd_edpt_xfer(_audiod_fct[func_id].rhport, _audiod_fct[func_id].ep_int, int_ep_buf[func_id].buf, size, false)); + } else { + // Release endpoint since we don't make any transfer + usbd_edpt_release(_audiod_fct[func_id].rhport, _audiod_fct[func_id].ep_int); + } + + return true; +} +#endif + +#if CFG_TUD_AUDIO_ENABLE_EP_OUT && CFG_TUD_AUDIO_ENABLE_FEEDBACK_EP +// This function is called once a transmit of a feedback packet was successfully completed. Here, we get the next feedback value to be sent +static inline bool audiod_fb_send(uint8_t func_id, bool is_isr) { + audiod_function_t *audio = &_audiod_fct[func_id]; + uint8_t uac_version = tud_audio_n_version(func_id); + // Format the feedback value + if (uac_version == 1) { + uint8_t *fb = (uint8_t *) audio->fb_buf; + + // For FS format is 10.14 + *(fb++) = (audio->feedback.value >> 2) & 0xFF; + *(fb++) = (audio->feedback.value >> 10) & 0xFF; + *(fb++) = (audio->feedback.value >> 18) & 0xFF; + *fb = 0; + } else { + *audio->fb_buf = audio->feedback.value; + } + + return usbd_edpt_xfer(audio->rhport, audio->ep_fb, (uint8_t *) audio->fb_buf, uac_version == 1 ? 3 : 4, is_isr); +} + +uint32_t tud_audio_feedback_update(uint8_t func_id, uint32_t cycles) { + audiod_function_t *audio = &_audiod_fct[func_id]; + uint32_t feedback; + + switch (audio->feedback.compute_method) { + case AUDIO_FEEDBACK_METHOD_FREQUENCY_POWER_OF_2: + feedback = (cycles << audio->feedback.compute.power_of_2); + break; + + case AUDIO_FEEDBACK_METHOD_FREQUENCY_FLOAT: + feedback = (uint32_t) ((float) cycles * audio->feedback.compute.float_const); + break; + + case AUDIO_FEEDBACK_METHOD_FREQUENCY_FIXED: { + uint64_t fb64 = (((uint64_t) cycles) * audio->feedback.compute.fixed.sample_freq) << (16 - (audio->feedback.frame_shift - 1)); + feedback = (uint32_t) (fb64 / audio->feedback.compute.fixed.mclk_freq); + } break; + + default: + return 0; + } + + // For Windows: https://docs.microsoft.com/en-us/windows-hardware/drivers/audio/usb-2-0-audio-drivers + // The size of isochronous packets created by the device must be within the limits specified in FMT-2.0 section 2.3.1.1. + // This means that the deviation of actual packet size from nominal size must not exceed +/- one audio slot + // (audio slot = channel count samples). + if (feedback > audio->feedback.max_value) { + feedback = audio->feedback.max_value; + } + if (feedback < audio->feedback.min_value) { + feedback = audio->feedback.min_value; + } + + tud_audio_n_fb_set(func_id, feedback); + + return feedback; +} + +bool tud_audio_n_fb_set(uint8_t func_id, uint32_t feedback) { + TU_VERIFY(func_id < CFG_TUD_AUDIO && _audiod_fct[func_id].p_desc != NULL); + + _audiod_fct[func_id].feedback.value = feedback; + + return true; +} +#endif + +uint8_t tud_audio_n_version(uint8_t func_id) { + TU_VERIFY(func_id < CFG_TUD_AUDIO && _audiod_fct[func_id].p_desc != NULL); + + uint8_t bIntfProtocol = ((tusb_desc_interface_t const *)_audiod_fct[func_id].p_desc)->bInterfaceProtocol; + + if (bIntfProtocol == AUDIO_INT_PROTOCOL_CODE_V1) { + return 1; + } else if (bIntfProtocol == AUDIO_INT_PROTOCOL_CODE_V2) { + return 2; + } else { + return 0; // Unknown version + } +} + +//--------------------------------------------------------------------+ +// USBD Driver API +//--------------------------------------------------------------------+ +void audiod_init(void) { + tu_memclr(_audiod_fct, sizeof(_audiod_fct)); + + for (uint8_t i = 0; i < CFG_TUD_AUDIO; i++) { + audiod_function_t *audio = &_audiod_fct[i]; + + // Initialize IN EP FIFO if required +#if CFG_TUD_AUDIO_ENABLE_EP_IN + switch (i) { + #if CFG_TUD_AUDIO_FUNC_1_EP_IN_SW_BUF_SZ > 0 + case 0: + tu_fifo_config(&audio->ep_in_ff, ep_in_sw_buf.buf_1, CFG_TUD_AUDIO_FUNC_1_EP_IN_SW_BUF_SZ, true); + break; + #endif + #if CFG_TUD_AUDIO > 1 && CFG_TUD_AUDIO_FUNC_2_EP_IN_SW_BUF_SZ > 0 + case 1: + tu_fifo_config(&audio->ep_in_ff, ep_in_sw_buf.buf_2, CFG_TUD_AUDIO_FUNC_2_EP_IN_SW_BUF_SZ, true); + break; + #endif + #if CFG_TUD_AUDIO > 2 && CFG_TUD_AUDIO_FUNC_3_EP_IN_SW_BUF_SZ > 0 + case 2: + tu_fifo_config(&audio->ep_in_ff, ep_in_sw_buf.buf_3, CFG_TUD_AUDIO_FUNC_3_EP_IN_SW_BUF_SZ, true); + break; + #endif + } + + // Initialize linear buffers + #if !CFG_TUD_EDPT_DEDICATED_HWFIFO + switch (i) { + #if CFG_TUD_AUDIO_FUNC_1_EP_IN_SZ_MAX > 0 + case 0: + audio->lin_buf_in = lin_buf_in.buf_1; + break; + #endif + #if CFG_TUD_AUDIO > 1 && CFG_TUD_AUDIO_FUNC_2_EP_IN_SZ_MAX > 0 + case 1: + audio->lin_buf_in = lin_buf_in.buf_2; + break; + #endif + #if CFG_TUD_AUDIO > 2 && CFG_TUD_AUDIO_FUNC_3_EP_IN_SZ_MAX > 0 + case 2: + audio->lin_buf_in = lin_buf_in.buf_3; + break; + #endif + } + #endif// !CFG_TUD_EDPT_DEDICATED_HWFIFO +#endif// CFG_TUD_AUDIO_ENABLE_EP_IN + + // Initialize OUT EP FIFO if required +#if CFG_TUD_AUDIO_ENABLE_EP_OUT + switch (i) { + #if CFG_TUD_AUDIO_FUNC_1_EP_OUT_SW_BUF_SZ > 0 + case 0: + tu_fifo_config(&audio->ep_out_ff, ep_out_sw_buf.buf_1, CFG_TUD_AUDIO_FUNC_1_EP_OUT_SW_BUF_SZ, true); + break; + #endif + #if CFG_TUD_AUDIO > 1 && CFG_TUD_AUDIO_FUNC_2_EP_OUT_SW_BUF_SZ > 0 + case 1: + tu_fifo_config(&audio->ep_out_ff, ep_out_sw_buf.buf_2, CFG_TUD_AUDIO_FUNC_2_EP_OUT_SW_BUF_SZ, true); + break; + #endif + #if CFG_TUD_AUDIO > 2 && CFG_TUD_AUDIO_FUNC_3_EP_OUT_SW_BUF_SZ > 0 + case 2: + tu_fifo_config(&audio->ep_out_ff, ep_out_sw_buf.buf_3, CFG_TUD_AUDIO_FUNC_3_EP_OUT_SW_BUF_SZ, true); + break; + #endif + } + + #if !CFG_TUD_EDPT_DEDICATED_HWFIFO + // Initialize linear buffers + switch (i) { + #if CFG_TUD_AUDIO_FUNC_1_EP_OUT_SZ_MAX > 0 + case 0: + audio->lin_buf_out = lin_buf_out.buf_1; + break; + #endif + #if CFG_TUD_AUDIO > 1 && CFG_TUD_AUDIO_FUNC_2_EP_OUT_SZ_MAX > 0 + case 1: + audio->lin_buf_out = lin_buf_out.buf_2; + break; + #endif + #if CFG_TUD_AUDIO > 2 && CFG_TUD_AUDIO_FUNC_3_EP_OUT_SZ_MAX > 0 + case 2: + audio->lin_buf_out = lin_buf_out.buf_3; + break; + #endif + } + #endif// !CFG_TUD_EDPT_DEDICATED_HWFIFO +#endif// CFG_TUD_AUDIO_ENABLE_EP_OUT + +#if CFG_TUD_AUDIO_ENABLE_FEEDBACK_EP + switch (i) { + #if CFG_TUD_AUDIO_FUNC_1_EP_OUT_SZ_MAX > 0 + case 0: + audio->fb_buf = &fb_ep_buf.buf_1; + break; + #endif + #if CFG_TUD_AUDIO > 1 && CFG_TUD_AUDIO_FUNC_2_EP_OUT_SZ_MAX > 0 + case 1: + audio->fb_buf = &fb_ep_buf.buf_2; + break; + #endif + #if CFG_TUD_AUDIO > 2 && CFG_TUD_AUDIO_FUNC_3_EP_OUT_SZ_MAX > 0 + case 2: + audio->fb_buf = &fb_ep_buf.buf_3; + break; + #endif + } +#endif// CFG_TUD_AUDIO_ENABLE_FEEDBACK_EP + } +} + +bool audiod_deinit(void) { + return false;// TODO not implemented yet +} + +void audiod_reset(uint8_t rhport) { + (void) rhport; + + for (uint8_t i = 0; i < CFG_TUD_AUDIO; i++) { + audiod_function_t *audio = &_audiod_fct[i]; + tu_memclr(audio, ITF_MEM_RESET_SIZE); + +#if CFG_TUD_AUDIO_ENABLE_EP_IN + tu_fifo_clear(&audio->ep_in_ff); +#endif + +#if CFG_TUD_AUDIO_ENABLE_EP_OUT + tu_fifo_clear(&audio->ep_out_ff); +#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, 0); + + // Verify version is correct - this check can be omitted + TU_VERIFY(itf_desc->bInterfaceProtocol == AUDIO_INT_PROTOCOL_CODE_V1 || + itf_desc->bInterfaceProtocol == AUDIO_INT_PROTOCOL_CODE_V2, 0); + + // Verify 2nd interface descriptor is Audio Streaming to avoid mess with MIDI class + // Audio Control interface is followed by Audio Streaming interface(s) + // MIDI class also starts with Audio Control but is followed by MIDI Streaming + { + uint8_t const *p_desc = (uint8_t const *) itf_desc; + uint8_t const *p_desc_end = p_desc + max_len; + + // Advance to next interface descriptor + p_desc = tu_desc_next(p_desc); + while (tu_desc_in_bounds(p_desc, p_desc_end) && tu_desc_type(p_desc) != TUSB_DESC_INTERFACE) { + p_desc = tu_desc_next(p_desc); + } + + // Verify next interface is Audio Streaming (subclass 2), not MIDI Streaming (subclass 3) + if (p_desc_end - p_desc >= (int)sizeof(tusb_desc_interface_t)) { + tusb_desc_interface_t const *next_itf = (tusb_desc_interface_t const *) p_desc; + TU_VERIFY(next_itf->bInterfaceClass == TUSB_CLASS_AUDIO && + next_itf->bInterfaceSubClass == AUDIO_SUBCLASS_STREAMING, 0); + } else { + // No further interface found or not enough bytes for interface descriptor + return 0; + } + } + + // Verify interrupt control EP is enabled if demanded by descriptor + TU_ASSERT(itf_desc->bNumEndpoints <= 1, 0);// 0 or 1 EPs are allowed + if (itf_desc->bNumEndpoints == 1) { + TU_ASSERT(CFG_TUD_AUDIO_ENABLE_INTERRUPT_EP, 0); + } + + // Alternate setting MUST be zero - this check can be omitted + TU_VERIFY(itf_desc->bAlternateSetting == 0, 0); + + // Find available audio driver interface + uint8_t i; + for (i = 0; i < CFG_TUD_AUDIO; i++) { + if (!_audiod_fct[i].p_desc) { + _audiod_fct[i].p_desc = (uint8_t const *) itf_desc;// Save pointer to AC descriptor which is by specification always the first one + _audiod_fct[i].rhport = rhport; + + // Calculate descriptor length + { + uint8_t const *p_desc = (uint8_t const *) itf_desc; + uint8_t const *p_desc_end = p_desc + max_len; + uint16_t total_len = sizeof(tusb_desc_interface_t); + // Skip Standard AC interface descriptor + p_desc = tu_desc_next(p_desc); + while (p_desc_end - p_desc > 0) { + // Stop if: + // - Non audio streaming interface descriptor found + // - IAD found + if ((tu_desc_type(p_desc) == TUSB_DESC_INTERFACE && + !(((tusb_desc_interface_t const *) p_desc)->bInterfaceClass == TUSB_CLASS_AUDIO && ((tusb_desc_interface_t const *) p_desc)->bInterfaceSubClass == AUDIO_SUBCLASS_STREAMING)) + || tu_desc_type(p_desc) == TUSB_DESC_INTERFACE_ASSOCIATION) { + break; + } else if (tu_desc_type(p_desc) == TUSB_DESC_INTERFACE && ((tusb_desc_interface_t const *) p_desc)->bInterfaceSubClass == AUDIO_SUBCLASS_STREAMING) { + if (_audiod_fct[i].p_desc_as == NULL) { + _audiod_fct[i].p_desc_as = p_desc; + } + } else { + // nothing to do + } + total_len += p_desc[0]; + p_desc = tu_desc_next(p_desc); + } + _audiod_fct[i].desc_length = total_len; + } + +#ifdef TUP_DCD_EDPT_ISO_ALLOC + { + #if CFG_TUD_AUDIO_ENABLE_EP_IN + uint8_t ep_in = 0; + uint16_t ep_in_size = 0; + #endif + + #if CFG_TUD_AUDIO_ENABLE_EP_OUT + uint8_t ep_out = 0; + uint16_t ep_out_size = 0; + #endif + + #if CFG_TUD_AUDIO_ENABLE_FEEDBACK_EP + uint8_t ep_fb = 0; + #endif + uint8_t const *p_desc = _audiod_fct[i].p_desc; + uint8_t const *p_desc_end = p_desc + _audiod_fct[i].desc_length; + // Condition modified from p_desc < p_desc_end to prevent gcc>=12 strict-overflow warning + while (p_desc_end - p_desc > 0) { + if (tu_desc_type(p_desc) == TUSB_DESC_ENDPOINT) { + // Unified UAC1/UAC2 endpoint processing + tusb_desc_endpoint_t const *desc_ep = (tusb_desc_endpoint_t const *) p_desc; + bool is_feedback_ep = false; + bool is_data_ep = false; + + if (tud_audio_n_version(i) == 1) { + // UAC1: Use bRefresh field to distinguish endpoint types + audio10_desc_as_iso_data_ep_t const *desc_ep_uac1 = (audio10_desc_as_iso_data_ep_t const *) p_desc; + is_data_ep = (desc_ep_uac1->bmAttributes.sync != TUSB_ISO_EP_ATT_NO_SYNC); + is_feedback_ep = (desc_ep_uac1->bmAttributes.sync == TUSB_ISO_EP_ATT_NO_SYNC); + } else { + // UAC2: Use bmAttributes.usage to distinguish endpoint types + is_data_ep = (desc_ep->bmAttributes.usage == 0 || desc_ep->bmAttributes.usage == 2); + is_feedback_ep = (desc_ep->bmAttributes.usage == 1); + } + + #if CFG_TUD_AUDIO_ENABLE_FEEDBACK_EP + // Explicit feedback EP + if (is_feedback_ep) { + ep_fb = desc_ep->bEndpointAddress; + } + #else + (void) is_feedback_ep; + #endif + #if CFG_TUD_AUDIO_ENABLE_EP_IN + // Data or data with implicit feedback IN EP + if (tu_edpt_dir(desc_ep->bEndpointAddress) == TUSB_DIR_IN && is_data_ep) { + ep_in = desc_ep->bEndpointAddress; + ep_in_size = TU_MAX(tu_edpt_packet_size(desc_ep), ep_in_size); + } + #endif + #if CFG_TUD_AUDIO_ENABLE_EP_OUT + // Data OUT EP + if (tu_edpt_dir(desc_ep->bEndpointAddress) == TUSB_DIR_OUT && is_data_ep) { + ep_out = desc_ep->bEndpointAddress; + ep_out_size = TU_MAX(tu_edpt_packet_size(desc_ep), ep_out_size); + } + #endif + } + + p_desc = tu_desc_next(p_desc); + } + + #if CFG_TUD_AUDIO_ENABLE_EP_IN + if (ep_in != 0) { + usbd_edpt_iso_alloc(rhport, ep_in, ep_in_size); + } + #endif + + #if CFG_TUD_AUDIO_ENABLE_EP_OUT + if (ep_out != 0) { + usbd_edpt_iso_alloc(rhport, ep_out, ep_out_size); + } + #endif + + #if CFG_TUD_AUDIO_ENABLE_FEEDBACK_EP + if (ep_fb != 0) { + usbd_edpt_iso_alloc(rhport, ep_fb, 4); + } + #endif + } +#endif// TUP_DCD_EDPT_ISO_ALLOC + +#if CFG_TUD_AUDIO_ENABLE_EP_IN && CFG_TUD_AUDIO_EP_IN_FLOW_CONTROL + { + uint8_t const *p_desc = _audiod_fct[i].p_desc; + uint8_t const *p_desc_end = p_desc + _audiod_fct[i].desc_length; + // Condition modified from p_desc < p_desc_end to prevent gcc>=12 strict-overflow warning + while (p_desc_end - p_desc > 0) { + if (tu_desc_type(p_desc) == TUSB_DESC_ENDPOINT) { + tusb_desc_endpoint_t const *desc_ep = (tusb_desc_endpoint_t const *) p_desc; + if (desc_ep->bmAttributes.xfer == TUSB_XFER_ISOCHRONOUS) { + // For data or data with implicit feedback IN EP + // For UAC1 this is always the case since there is no usage field + if (tu_edpt_dir(desc_ep->bEndpointAddress) == TUSB_DIR_IN + && (desc_ep->bmAttributes.usage == 0 || desc_ep->bmAttributes.usage == 2)) { + _audiod_fct[i].interval_tx = desc_ep->bInterval; + } + } + } else if (tud_audio_n_version(i) == 2 && + tu_desc_type(p_desc) == TUSB_DESC_CS_INTERFACE && tu_desc_subtype(p_desc) == AUDIO20_CS_AC_INTERFACE_OUTPUT_TERMINAL) { + // For UAC2 only, UAC1 doesn't have a clock source + if (tu_unaligned_read16(p_desc + 4) == AUDIO_TERM_TYPE_USB_STREAMING) { + _audiod_fct[i].bclock_id_tx = p_desc[8]; + } + } else { + // nothing to do + } + p_desc = tu_desc_next(p_desc); + } + } +#endif// CFG_TUD_AUDIO_EP_IN_FLOW_CONTROL + +#if CFG_TUD_AUDIO_ENABLE_INTERRUPT_EP + { + uint8_t const *p_desc = _audiod_fct[i].p_desc; + uint8_t const *p_desc_end = p_desc + _audiod_fct[i].desc_length; + // Condition modified from p_desc < p_desc_end to prevent gcc>=12 strict-overflow warning + while (p_desc_end - p_desc > 0) { + // For each endpoint + if (tu_desc_type(p_desc) == TUSB_DESC_ENDPOINT) { + tusb_desc_endpoint_t const *desc_ep = (tusb_desc_endpoint_t const *) p_desc; + uint8_t const ep_addr = desc_ep->bEndpointAddress; + // If endpoint is input-direction and interrupt-type + if (tu_edpt_dir(ep_addr) == TUSB_DIR_IN && desc_ep->bmAttributes.xfer == TUSB_XFER_INTERRUPT) { + // Store endpoint number and open endpoint + _audiod_fct[i].ep_int = ep_addr; + TU_ASSERT(usbd_edpt_open(_audiod_fct[i].rhport, desc_ep)); + } + } + p_desc = tu_desc_next(p_desc); + } + } +#endif + + _audiod_fct[i].mounted = true; + 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) + uint16_t drv_len = _audiod_fct[i].desc_length; + + return drv_len; +} + +static bool audiod_get_interface(uint8_t rhport, tusb_control_request_t const *p_request) { + uint8_t const itf = tu_u16_low(p_request->wIndex); + + // Find index of audio streaming interface + uint8_t func_id; + TU_VERIFY(audiod_verify_itf_exists(itf, &func_id)); + + // Default to 0 if interface not yet activated + uint8_t alt = 0; +#if CFG_TUD_AUDIO_ENABLE_EP_IN + if (_audiod_fct[func_id].ep_in_as_intf_num == itf) { + alt = _audiod_fct[func_id].ep_in_alt; + } +#endif +#if CFG_TUD_AUDIO_ENABLE_EP_OUT + if (_audiod_fct[func_id].ep_out_as_intf_num == itf) { + alt = _audiod_fct[func_id].ep_out_alt; + } +#endif + + TU_VERIFY(tud_control_xfer(rhport, p_request, &alt, 1)); + + TU_LOG2(" Get itf: %u - current alt: %u\r\n", itf, alt); + + return true; +} + +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 func_id; + TU_VERIFY(audiod_verify_itf_exists(itf, &func_id)); + + audiod_function_t *audio = &_audiod_fct[func_id]; + +// 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_ENABLE_EP_IN + if (audio->ep_in_as_intf_num == itf) { + audio->ep_in_as_intf_num = 0; + audio->ep_in_alt = 0; + #ifndef TUP_DCD_EDPT_ISO_ALLOC + usbd_edpt_close(rhport, audio->ep_in); + #endif + + // Clear FIFOs, since data is no longer valid + tu_fifo_clear(&audio->ep_in_ff); + + // Invoke callback - can be used to stop data sampling + TU_VERIFY(tud_audio_set_itf_close_ep_cb(rhport, p_request)); + + audio->ep_in = 0;// Necessary? + + #if CFG_TUD_AUDIO_EP_IN_FLOW_CONTROL + audio->packet_sz_tx[0] = 0; + audio->packet_sz_tx[1] = 0; + audio->packet_sz_tx[2] = 0; + #endif + } +#endif// CFG_TUD_AUDIO_ENABLE_EP_IN + +#if CFG_TUD_AUDIO_ENABLE_EP_OUT + if (audio->ep_out_as_intf_num == itf) { + audio->ep_out_as_intf_num = 0; + audio->ep_out_alt = 0; + #ifndef TUP_DCD_EDPT_ISO_ALLOC + usbd_edpt_close(rhport, audio->ep_out); + #endif + + // Clear FIFOs, since data is no longer valid + tu_fifo_clear(&audio->ep_out_ff); + + // Invoke callback - can be used to stop data sampling + TU_VERIFY(tud_audio_set_itf_close_ep_cb(rhport, p_request)); + + audio->ep_out = 0;// Necessary? + + // Close corresponding feedback EP + #if CFG_TUD_AUDIO_ENABLE_FEEDBACK_EP + #ifndef TUP_DCD_EDPT_ISO_ALLOC + usbd_edpt_close(rhport, audio->ep_fb); + #endif + audio->ep_fb = 0; + tu_memclr(&audio->feedback, sizeof(audio->feedback)); + #endif + } +#endif// CFG_TUD_AUDIO_ENABLE_EP_OUT + + // Open new EP if necessary - EPs are only to be closed or opened for AS interfaces - Look for AS interface with correct alternate interface + + uint8_t const *p_desc = audio->p_desc_as; + // Get pointer at end + uint8_t const *p_desc_end = audio->p_desc + audio->desc_length; + + // p_desc starts at required interface with alternate setting zero + // Condition modified from p_desc < p_desc_end to prevent gcc>=12 strict-overflow warning + while (p_desc_end - p_desc > 0) { + // Find correct interface + if (tu_desc_type(p_desc) == TUSB_DESC_INTERFACE && ((tusb_desc_interface_t const *) p_desc)->bInterfaceNumber == itf && ((tusb_desc_interface_t const *) p_desc)->bAlternateSetting == alt) { +#if CFG_TUD_AUDIO_ENABLE_EP_IN && CFG_TUD_AUDIO_EP_IN_FLOW_CONTROL + uint8_t const *p_desc_parse_for_params = p_desc; +#endif + // From this point forward follow the EP descriptors associated to the current alternate setting interface - Open EPs if necessary + uint8_t foundEPs = 0, nEps = ((tusb_desc_interface_t const *) p_desc)->bNumEndpoints; + // Condition modified from p_desc < p_desc_end to prevent gcc>=12 strict-overflow warning + while (foundEPs < nEps && (p_desc_end - p_desc > 0)) { + if (tu_desc_type(p_desc) == TUSB_DESC_ENDPOINT) { + tusb_desc_endpoint_t const *desc_ep = (tusb_desc_endpoint_t const *) p_desc; +#ifdef TUP_DCD_EDPT_ISO_ALLOC + TU_ASSERT(usbd_edpt_iso_activate(rhport, desc_ep)); +#else + TU_ASSERT(usbd_edpt_open(rhport, desc_ep)); +#endif + uint8_t const ep_addr = desc_ep->bEndpointAddress; + + bool is_feedback_ep = false; + bool is_data_ep = false; + + if (tud_audio_n_version(func_id) == 1) { + // UAC1: Use bRefresh field to distinguish endpoint types + audio10_desc_as_iso_data_ep_t const *desc_ep_uac1 = (audio10_desc_as_iso_data_ep_t const *) p_desc; + is_data_ep = (desc_ep_uac1->bmAttributes.sync != TUSB_ISO_EP_ATT_NO_SYNC); + is_feedback_ep = (desc_ep_uac1->bmAttributes.sync == TUSB_ISO_EP_ATT_NO_SYNC); + } else { + // UAC2: Use bmAttributes.usage to distinguish endpoint types + is_data_ep = (desc_ep->bmAttributes.usage == 0 || desc_ep->bmAttributes.usage == 2); + is_feedback_ep = (desc_ep->bmAttributes.usage == 1); + } + + //TODO: We need to set EP non busy since this is not taken care of right now in ep_close() - THIS IS A WORKAROUND! + usbd_edpt_clear_stall(rhport, ep_addr); + +#if CFG_TUD_AUDIO_ENABLE_EP_IN + // For data or data with implicit feedback IN EP + if (tu_edpt_dir(ep_addr) == TUSB_DIR_IN && is_data_ep) + { + // Save address + audio->ep_in = ep_addr; + audio->ep_in_as_intf_num = itf; + audio->ep_in_alt = alt; + audio->ep_in_sz = tu_edpt_packet_size(desc_ep); + // Set the default EP IN FIFO threshold to half fifo depth. + audio->ep_in_fifo_threshold = audio->ep_in_ff.depth / 2; + + // If flow control is enabled, parse for the corresponding parameters - doing this here means only AS interfaces with EPs get scanned for parameters + #if CFG_TUD_AUDIO_EP_IN_FLOW_CONTROL + audiod_parse_flow_control_params(audio, p_desc_parse_for_params); + #endif + // Schedule first transmit if alternate interface is not zero, as sample data is available a ZLP is loaded + #if !CFG_TUD_EDPT_DEDICATED_HWFIFO + TU_VERIFY(usbd_edpt_xfer(rhport, audio->ep_in, audio->lin_buf_in, 0, false)); + #else + // Send everything in ISO EP FIFO + TU_VERIFY(usbd_edpt_xfer_fifo(rhport, audio->ep_in, &audio->ep_in_ff, 0, false)); + #endif + } +#endif// CFG_TUD_AUDIO_ENABLE_EP_IN + +#if CFG_TUD_AUDIO_ENABLE_EP_OUT + // Checking usage not necessary + if (tu_edpt_dir(ep_addr) == TUSB_DIR_OUT && is_data_ep) { + // Save address + audio->ep_out = ep_addr; + audio->ep_out_as_intf_num = itf; + audio->ep_out_alt = alt; + audio->ep_out_sz = tu_edpt_packet_size(desc_ep); + + // Prepare for incoming data + #if !CFG_TUD_EDPT_DEDICATED_HWFIFO + TU_VERIFY(usbd_edpt_xfer(rhport, audio->ep_out, audio->lin_buf_out, audio->ep_out_sz, false)); + #else + TU_VERIFY(usbd_edpt_xfer_fifo(rhport, audio->ep_out, &audio->ep_out_ff, audio->ep_out_sz, false)); + #endif + } + + #if CFG_TUD_AUDIO_ENABLE_FEEDBACK_EP + // Check if usage is explicit data feedback + if (is_feedback_ep) { + audio->ep_fb = ep_addr; + audio->feedback.frame_shift = desc_ep->bInterval - 1; + // Schedule first feedback transmit + audiod_fb_send(func_id, false); + } + #else + (void) is_feedback_ep; + #endif +#else + (void) is_feedback_ep; +#endif// CFG_TUD_AUDIO_ENABLE_EP_OUT + + foundEPs += 1; + } + p_desc = tu_desc_next(p_desc); + } + + TU_VERIFY(foundEPs == nEps); + + // Invoke one callback for a final set interface + TU_VERIFY(tud_audio_set_itf_cb(rhport, p_request)); + +#if CFG_TUD_AUDIO_ENABLE_FEEDBACK_EP + // Prepare feedback computation parameters + TU_VERIFY(audiod_fb_params_prepare(func_id, alt)); +#endif// CFG_TUD_AUDIO_ENABLE_FEEDBACK_EP + + // We are done - abort loop + break; + } + + // Moving forward + p_desc = tu_desc_next(p_desc); + } + +#if CFG_TUD_AUDIO_ENABLE_FEEDBACK_EP + // Disable SOF interrupt if no driver has any enabled feedback EP + bool enable_sof = false; + for (uint8_t i = 0; i < CFG_TUD_AUDIO; i++) { + if (_audiod_fct[i].ep_fb != 0 && + (_audiod_fct[i].feedback.compute_method == AUDIO_FEEDBACK_METHOD_FREQUENCY_FIXED || + _audiod_fct[i].feedback.compute_method == AUDIO_FEEDBACK_METHOD_FREQUENCY_FLOAT || + _audiod_fct[i].feedback.compute_method == AUDIO_FEEDBACK_METHOD_FREQUENCY_POWER_OF_2)) { + enable_sof = true; + break; + } + } + usbd_sof_enable(rhport, SOF_CONSUMER_AUDIO, enable_sof); +#endif + +#if CFG_TUD_AUDIO_ENABLE_EP_IN && CFG_TUD_AUDIO_EP_IN_FLOW_CONTROL + audiod_calc_tx_packet_sz(audio); +#endif + + 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) +static 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 func_id; + + switch (p_request->bmRequestType_bit.recipient) { + case TUSB_REQ_RCPT_INTERFACE: { + uint8_t itf = TU_U16_LOW(p_request->wIndex); + uint8_t entityID = TU_U16_HIGH(p_request->wIndex); + + if (entityID != 0) { + // Check if entity is present and get corresponding driver index + TU_VERIFY(audiod_verify_entity_exists(itf, entityID, &func_id)); + +#if CFG_TUD_AUDIO_ENABLE_EP_IN && CFG_TUD_AUDIO_EP_IN_FLOW_CONTROL + if (tud_audio_n_version(func_id) == 2) { + uint8_t ctrlSel = TU_U16_HIGH(p_request->wValue); + if (_audiod_fct[func_id].bclock_id_tx == entityID && ctrlSel == AUDIO20_CS_CTRL_SAM_FREQ && p_request->bRequest == AUDIO20_CS_REQ_CUR) { + _audiod_fct[func_id].sample_rate_tx = tu_unaligned_read32(get_ctrl_buffer()); + audiod_calc_tx_packet_sz(&_audiod_fct[func_id]); + } + } +#endif + + // Invoke callback + return tud_audio_set_req_entity_cb(rhport, p_request, get_ctrl_buffer()); + } else { + // Find index of audio driver structure and verify interface really exists + TU_VERIFY(audiod_verify_itf_exists(itf, &func_id)); + + // Invoke callback + return tud_audio_set_req_itf_cb(rhport, p_request, get_ctrl_buffer()); + } + } break; + + case TUSB_REQ_RCPT_ENDPOINT: { + uint8_t ep = TU_U16_LOW(p_request->wIndex); + + // Check if entity is present and get corresponding driver index + TU_VERIFY(audiod_verify_ep_exists(ep, &func_id)); + +#if CFG_TUD_AUDIO_ENABLE_EP_IN && CFG_TUD_AUDIO_EP_IN_FLOW_CONTROL + if (tud_audio_n_version(func_id) == 1) { + if (_audiod_fct[func_id].ep_in == ep) { + uint8_t ctrlSel = TU_U16_HIGH(p_request->wValue); + if (ctrlSel == AUDIO10_EP_CTRL_SAMPLING_FREQ && p_request->bRequest == AUDIO10_CS_REQ_SET_CUR) { + _audiod_fct[func_id].sample_rate_tx = tu_unaligned_read32(get_ctrl_buffer()) & 0x00FFFFFF; + audiod_calc_tx_packet_sz(&_audiod_fct[func_id]); + } + } + } +#endif + + // Invoke callback + bool ret = tud_audio_set_req_ep_cb(rhport, p_request, get_ctrl_buffer()); + +#if CFG_TUD_AUDIO_ENABLE_EP_OUT && CFG_TUD_AUDIO_ENABLE_FEEDBACK_EP + if (ret && tud_audio_n_version(func_id) == 1) { + if (_audiod_fct[func_id].ep_out == ep) { + uint8_t ctrlSel = TU_U16_HIGH(p_request->wValue); + if (ctrlSel == AUDIO10_EP_CTRL_SAMPLING_FREQ && p_request->bRequest == AUDIO10_CS_REQ_SET_CUR) { + audiod_fb_params_prepare(func_id, _audiod_fct[func_id].ep_out_alt); + } + } + } +#endif + return ret; + } break; + // Unknown/Unsupported recipient + default: + TU_BREAKPOINT(); + return false; + } + } + return true; +} + +// Handle class control request +// return false to stall control endpoint (e.g unsupported request) +static 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); + + case TUSB_REQ_CLEAR_FEATURE: + return true; + + // 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 func_id; + + // Conduct checks which depend on the recipient + switch (p_request->bmRequestType_bit.recipient) { + case TUSB_REQ_RCPT_INTERFACE: { + 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, &func_id)); + + // 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) { + return tud_audio_get_req_entity_cb(rhport, p_request); + } + } else { + // Find index of audio driver structure and verify interface really exists + TU_VERIFY(audiod_verify_itf_exists(itf, &func_id)); + + // 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) { + return tud_audio_get_req_itf_cb(rhport, p_request); + } + } + } break; + + case TUSB_REQ_RCPT_ENDPOINT: { + 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, &func_id)); + + // 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) { + return tud_audio_get_req_ep_cb(rhport, p_request); + } + } 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, get_ctrl_buffer(), CFG_TUD_AUDIO_CTRL_BUF_SZ)); + return true; + } + + // There went something wrong - unsupported control request type + TU_BREAKPOINT(); + return false; +} + +bool audiod_control_xfer_cb(uint8_t rhport, uint8_t stage, tusb_control_request_t const *request) { + if (stage == CONTROL_STAGE_SETUP) { + return audiod_control_request(rhport, request); + } else if (stage == CONTROL_STAGE_DATA) { + return audiod_control_complete(rhport, request); + } else { + // nothing to do + } + + return true; +} + +bool audiod_xfer_cb(uint8_t rhport, uint8_t ep_addr, xfer_result_t result, uint32_t xferred_bytes) { + (void) result; + (void) xferred_bytes; + + #if CFG_TUD_AUDIO_ENABLE_INTERRUPT_EP + // Search for interface belonging to given end point address and proceed as required + for (uint8_t func_id = 0; func_id < CFG_TUD_AUDIO; func_id++) { + audiod_function_t *audio = &_audiod_fct[func_id]; + + // Data transmission of control interrupt finished + if (audio->ep_int == 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 ??? + + // I assume here, that things above are handled by PHY + // All transmission is done - what remains to do is to inform job was completed + + tud_audio_int_done_cb(rhport); + return true; + } + + } + #else + (void) rhport; + (void) ep_addr; + #endif + + return false; +} + +bool audiod_xfer_isr(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 + for (uint8_t func_id = 0; func_id < CFG_TUD_AUDIO; func_id++) + { + audiod_function_t* audio = &_audiod_fct[func_id]; + +#if CFG_TUD_AUDIO_ENABLE_EP_IN + + // Data transmission of audio packet finished + if (audio->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 + audiod_tx_xfer_isr(rhport, audio, (uint16_t) xferred_bytes); + return true; + } +#endif + +#if CFG_TUD_AUDIO_ENABLE_EP_OUT + // New audio packet received + if (audio->ep_out == ep_addr) { + audiod_rx_xfer_isr(rhport, audio, (uint16_t) xferred_bytes); + return true; + } + #if CFG_TUD_AUDIO_ENABLE_FEEDBACK_EP + // Transmission of feedback EP finished + if (audio->ep_fb == ep_addr) { + // Schedule a transmit with the new value if EP is not busy + // Schedule next transmission - value is changed bytud_audio_n_fb_set() in the meantime or the old value gets sent + audiod_fb_send(func_id, true); + return true; + } + #endif +#endif + } + + return false; +} + +#if CFG_TUD_AUDIO_ENABLE_EP_OUT && CFG_TUD_AUDIO_ENABLE_FEEDBACK_EP + +static bool audiod_fb_params_prepare(uint8_t func_id, uint8_t alt) { + audiod_function_t *audio = &_audiod_fct[func_id]; + + // Prepare feedback computation if endpoint is available + if (audio->ep_fb != 0) { + audio_feedback_params_t fb_param = {0}; + + tud_audio_feedback_params_cb(func_id, alt, &fb_param); + audio->feedback.compute_method = fb_param.method; + + // Minimal/Maximum value in 16.16 format for full speed (1ms per frame) or high speed (125 us per frame) + uint32_t const frame_div = (TUSB_SPEED_FULL == tud_speed_get()) ? 1000 : 8000; + audio->feedback.min_value = ((fb_param.sample_freq - 1) / frame_div) << 16; + audio->feedback.max_value = (fb_param.sample_freq / frame_div + 1) << 16; + + switch (fb_param.method) { + case AUDIO_FEEDBACK_METHOD_FREQUENCY_FIXED: + case AUDIO_FEEDBACK_METHOD_FREQUENCY_FLOAT: + case AUDIO_FEEDBACK_METHOD_FREQUENCY_POWER_OF_2: { + // Check if frame interval is within sane limits + // The interval value n_frames was taken from the descriptors within audiod_set_interface() + + // n_frames_min is ceil(2^10 * f_s / f_m) for full speed and ceil(2^13 * f_s / f_m) for high speed + // this lower limit ensures the measures feedback value has sufficient precision + uint32_t const k = (TUSB_SPEED_FULL == tud_speed_get()) ? 10 : 13; + uint32_t const n_frame = (1UL << audio->feedback.frame_shift); + + if ((((1UL << k) * fb_param.sample_freq / fb_param.frequency.mclk_freq) + 1) > n_frame) { + TU_LOG1(" UAC2 feedback interval too small\r\n"); + TU_BREAKPOINT(); + return false; + } + + // Check if parameters really allow for a power of two division + if ((fb_param.frequency.mclk_freq % fb_param.sample_freq) == 0 && tu_is_power_of_two(fb_param.frequency.mclk_freq / fb_param.sample_freq)) { + audio->feedback.compute_method = AUDIO_FEEDBACK_METHOD_FREQUENCY_POWER_OF_2; + audio->feedback.compute.power_of_2 = (uint8_t) (16 - (audio->feedback.frame_shift - 1) - tu_log2(fb_param.frequency.mclk_freq / fb_param.sample_freq)); + } else if (audio->feedback.compute_method == AUDIO_FEEDBACK_METHOD_FREQUENCY_FLOAT) { + audio->feedback.compute.float_const = (float) fb_param.sample_freq / (float) fb_param.frequency.mclk_freq * (1UL << (16 - (audio->feedback.frame_shift - 1))); + } else { + audio->feedback.compute.fixed.sample_freq = fb_param.sample_freq; + audio->feedback.compute.fixed.mclk_freq = fb_param.frequency.mclk_freq; + } + } break; + + case AUDIO_FEEDBACK_METHOD_FIFO_COUNT: { + // Determine FIFO threshold + uint16_t fifo_threshold = fb_param.fifo_count.fifo_threshold ? fb_param.fifo_count.fifo_threshold : tu_fifo_depth(&audio->ep_out_ff) / 2; + audio->feedback.compute.fifo_count.fifo_lvl_thr = fifo_threshold; + audio->feedback.compute.fifo_count.fifo_lvl_avg = ((uint32_t) fifo_threshold) << 16; + // Avoid 64bit division + uint32_t nominal = ((fb_param.sample_freq / 100) << 16) / (frame_div / 100); + audio->feedback.compute.fifo_count.nom_value = nominal; + audio->feedback.compute.fifo_count.rate_const[0] = (uint16_t) ((audio->feedback.max_value - nominal) / fifo_threshold); + audio->feedback.compute.fifo_count.rate_const[1] = (uint16_t) ((nominal - audio->feedback.min_value) / fifo_threshold); + // On HS feedback is more sensitive since packet size can vary every MSOF, could cause instability + if (tud_speed_get() == TUSB_SPEED_HIGH) { + audio->feedback.compute.fifo_count.rate_const[0] /= 8; + audio->feedback.compute.fifo_count.rate_const[1] /= 8; + } + } break; + + // nothing to do + default: + break; + } + } + + return true; +} + +static void audiod_fb_fifo_count_update(audiod_function_t *audio, uint16_t lvl_new) { + /* Low-pass (averaging) filter */ + uint32_t lvl = audio->feedback.compute.fifo_count.fifo_lvl_avg; + lvl = (uint32_t) (((uint64_t) lvl * 63 + ((uint32_t) lvl_new << 16)) >> 6); + audio->feedback.compute.fifo_count.fifo_lvl_avg = lvl; + + uint32_t const ff_lvl = lvl >> 16; + uint16_t const ff_thr = audio->feedback.compute.fifo_count.fifo_lvl_thr; + uint16_t const *rate = audio->feedback.compute.fifo_count.rate_const; + + uint32_t feedback; + + if (ff_lvl < ff_thr) { + feedback = audio->feedback.compute.fifo_count.nom_value + (ff_thr - ff_lvl) * rate[0]; + } else { + feedback = audio->feedback.compute.fifo_count.nom_value - (ff_lvl - ff_thr) * rate[1]; + } + + if (feedback > audio->feedback.max_value) { + feedback = audio->feedback.max_value; + } + if (feedback < audio->feedback.min_value) { + feedback = audio->feedback.min_value; + } + audio->feedback.value = feedback; +} + +#endif + +TU_ATTR_FAST_FUNC void audiod_sof_isr(uint8_t rhport, uint32_t frame_count) { + (void) rhport; + (void) frame_count; + +#if CFG_TUD_AUDIO_ENABLE_EP_OUT && CFG_TUD_AUDIO_ENABLE_FEEDBACK_EP + // Determine feedback value - The feedback method is described in 5.12.4.2 of the USB 2.0 spec + // Boiled down, the feedback value Ff = n_samples / (micro)frame. + // Since an accuracy of less than 1 Sample / second is desired, at least n_frames = ceil(2^K * f_s / f_m) frames need to be measured, where K = 10 for full speed and K = 13 for high speed, f_s is the sampling frequency e.g. 48 kHz and f_m is the cpu clock frequency e.g. 100 MHz (or any other master clock whose clock count is available and locked to f_s) + // The update interval in the (4.10.2.1) Feedback Endpoint Descriptor must be less or equal to 2^(K - P), where P = min( ceil(log2(f_m / f_s)), K) + // feedback = n_cycles / n_frames * f_s / f_m in 16.16 format, where n_cycles are the number of main clock cycles within fb_n_frames + + // Iterate over audio functions and set feedback value + for (uint8_t i = 0; i < CFG_TUD_AUDIO; i++) { + audiod_function_t *audio = &_audiod_fct[i]; + + if (audio->ep_fb != 0) { + // HS shift need to be adjusted since SOF event is generated for frame only + uint8_t const hs_adjust = (TUSB_SPEED_HIGH == tud_speed_get()) ? 3 : 0; + uint32_t const interval = 1UL << (audio->feedback.frame_shift - hs_adjust); + if (0 == (frame_count & (interval - 1))) { + tud_audio_feedback_interval_isr(i, frame_count, audio->feedback.frame_shift); + } + } + } +#endif// CFG_TUD_AUDIO_ENABLE_EP_OUT && CFG_TUD_AUDIO_ENABLE_FEEDBACK_EP +} + +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 func_id; + 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: { + 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, &func_id)); + } else { + // Find index of audio driver structure and verify interface really exists + TU_VERIFY(audiod_verify_itf_exists(itf, &func_id)); + } + } break; + + case TUSB_REQ_RCPT_ENDPOINT: { + 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, &func_id)); + } break; + + // Unknown/Unsupported recipient + default: + TU_LOG2(" Unsupported recipient: %d\r\n", p_request->bmRequestType_bit.recipient); + TU_BREAKPOINT(); + return false; + } + + // Copy into buffer + TU_VERIFY(0 == tu_memcpy_s(get_ctrl_buffer(), CFG_TUD_AUDIO_CTRL_BUF_SZ, data, (size_t) len)); + +#if CFG_TUD_AUDIO_ENABLE_EP_IN && CFG_TUD_AUDIO_EP_IN_FLOW_CONTROL + if (tud_audio_n_version(func_id) == 2) { + // Find data for sampling_frequency_control + if (p_request->bmRequestType_bit.type == TUSB_REQ_TYPE_CLASS && p_request->bmRequestType_bit.recipient == TUSB_REQ_RCPT_INTERFACE) { + uint8_t entityID = TU_U16_HIGH(p_request->wIndex); + uint8_t ctrlSel = TU_U16_HIGH(p_request->wValue); + if (_audiod_fct[func_id].bclock_id_tx == entityID && ctrlSel == AUDIO20_CS_CTRL_SAM_FREQ && p_request->bRequest == AUDIO20_CS_REQ_CUR) { + _audiod_fct[func_id].sample_rate_tx = tu_unaligned_read32(get_ctrl_buffer()); + audiod_calc_tx_packet_sz(&_audiod_fct[func_id]); + } + } + } +#endif + + // Schedule transmit + return tud_control_xfer(rhport, p_request, get_ctrl_buffer(), len); +} + +// 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 *func_id) { + 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_fct[i].p_desc && ((tusb_desc_interface_t const *) _audiod_fct[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_fct[i].p_desc);// Points to CS AC descriptor + p_desc = tu_desc_next(p_desc);// Get past CS AC descriptor + + while (_audiod_fct[i].p_desc_as - p_desc > 0) { + // Entity IDs are always at offset 3 + if (p_desc[3] == entityID) { + *func_id = i; + return true; + } + p_desc = tu_desc_next(p_desc); + } + } + } + return false; +} + +static bool audiod_verify_itf_exists(uint8_t itf, uint8_t *func_id) { + uint8_t i; + for (i = 0; i < CFG_TUD_AUDIO; i++) { + if (_audiod_fct[i].p_desc != NULL) { + // Get pointer at beginning and end + uint8_t const *p_desc = _audiod_fct[i].p_desc; + uint8_t const *p_desc_end = _audiod_fct[i].p_desc + _audiod_fct[i].desc_length; + // Condition modified from p_desc < p_desc_end to prevent gcc>=12 strict-overflow warning + while (p_desc_end - p_desc > 0) { + if (tu_desc_type(p_desc) == TUSB_DESC_INTERFACE && ((tusb_desc_interface_t const *)p_desc)->bInterfaceNumber == itf) { + *func_id = i; + return true; + } + p_desc = tu_desc_next(p_desc); + } + } + } + return false; +} + +static bool audiod_verify_ep_exists(uint8_t ep, uint8_t *func_id) { + uint8_t i; + for (i = 0; i < CFG_TUD_AUDIO; i++) { + if (_audiod_fct[i].p_desc) { + // Get pointer at end + uint8_t const *p_desc_end = _audiod_fct[i].p_desc + _audiod_fct[i].desc_length; + + // Advance past AC descriptors - EP we look for are streaming EPs + uint8_t const *p_desc = _audiod_fct[i].p_desc_as; + + // Condition modified from p_desc < p_desc_end to prevent gcc>=12 strict-overflow warning + while (p_desc_end - p_desc > 0) { + if (tu_desc_type(p_desc) == TUSB_DESC_ENDPOINT && ((tusb_desc_endpoint_t const *) p_desc)->bEndpointAddress == ep) { + *func_id = i; + return true; + } + p_desc = tu_desc_next(p_desc); + } + } + } + return false; +} + +#if CFG_TUD_AUDIO_ENABLE_EP_IN && CFG_TUD_AUDIO_EP_IN_FLOW_CONTROL +static void audiod_parse_flow_control_params(audiod_function_t *audio, uint8_t const *p_desc) { + + p_desc = tu_desc_next(p_desc);// Exclude standard AS interface descriptor of current alternate interface descriptor + + if (tud_audio_n_version(audiod_get_audio_fct_idx(audio)) == 1) { + p_desc = tu_desc_next(p_desc);// Exclude Class-Specific AS Interface Descriptor(4.5.2) to get to format type descriptor + if (tu_desc_type(p_desc) == TUSB_DESC_CS_INTERFACE && tu_desc_subtype(p_desc) == AUDIO10_CS_AS_INTERFACE_FORMAT_TYPE) { + audio->format_type_tx = ((audio10_desc_type_I_format_n_t(1) const *) p_desc)->bFormatType; + if (audio->format_type_tx == AUDIO10_FORMAT_TYPE_I) { + audio->n_channels_tx = ((audio10_desc_type_I_format_n_t(1) const *) p_desc)->bNrChannels; + audio->n_bytes_per_sample_tx = ((audio10_desc_type_I_format_n_t(1) const *) p_desc)->bSubFrameSize; + // Save sample rate - needed when EP doesn't support setting sample rate + audio->sample_rate_tx = tu_unaligned_read32(((audio10_desc_type_I_format_n_t(1) const *) p_desc)->tSamFreq) & 0x00FFFFFF; + } + } + } else { + // Look for a Class-Specific AS Interface Descriptor(4.9.2) to verify format type and format and also to get number of physical channels + if (tu_desc_type(p_desc) == TUSB_DESC_CS_INTERFACE && tu_desc_subtype(p_desc) == AUDIO20_CS_AS_INTERFACE_AS_GENERAL) { + audio->n_channels_tx = ((audio20_desc_cs_as_interface_t const *) p_desc)->bNrChannels; + audio->format_type_tx = ((audio20_desc_cs_as_interface_t const *) p_desc)->bFormatType; + // Look for a Type I Format Type Descriptor(2.3.1.6 - Audio Formats) + p_desc = tu_desc_next(p_desc); + if (tu_desc_type(p_desc) == TUSB_DESC_CS_INTERFACE && tu_desc_subtype(p_desc) == AUDIO20_CS_AS_INTERFACE_FORMAT_TYPE && ((audio20_desc_type_I_format_t const *) p_desc)->bFormatType == AUDIO20_FORMAT_TYPE_I) { + audio->n_bytes_per_sample_tx = ((audio20_desc_type_I_format_t const *) p_desc)->bSubslotSize; + } + } + } +} + +static bool audiod_calc_tx_packet_sz(audiod_function_t *audio) { + // AUDIO20_FORMAT_TYPE_I = AUDIO10_FORMAT_TYPE_I + TU_VERIFY(audio->format_type_tx == AUDIO20_FORMAT_TYPE_I); + TU_VERIFY(audio->n_channels_tx); + TU_VERIFY(audio->n_bytes_per_sample_tx); + TU_VERIFY(audio->interval_tx); + TU_VERIFY(audio->sample_rate_tx); + + const uint8_t interval = (tud_speed_get() == TUSB_SPEED_FULL) ? audio->interval_tx : 1 << (audio->interval_tx - 1); + + const uint16_t sample_normimal = (uint16_t) (audio->sample_rate_tx * interval / ((tud_speed_get() == TUSB_SPEED_FULL) ? 1000 : 8000)); + const uint16_t sample_reminder = (uint16_t) (audio->sample_rate_tx * interval % ((tud_speed_get() == TUSB_SPEED_FULL) ? 1000 : 8000)); + + const uint16_t packet_sz_tx_min = (uint16_t) ((sample_normimal - 1) * audio->n_channels_tx * audio->n_bytes_per_sample_tx); + const uint16_t packet_sz_tx_norm = (uint16_t) (sample_normimal * audio->n_channels_tx * audio->n_bytes_per_sample_tx); + const uint16_t packet_sz_tx_max = (uint16_t) ((sample_normimal + 1) * audio->n_channels_tx * audio->n_bytes_per_sample_tx); + + // Endpoint size must larger than packet size + TU_ASSERT(packet_sz_tx_max <= audio->ep_in_sz); + + // Frmt20.pdf 2.3.1.1 USB Packets + if (sample_reminder) { + // All virtual frame packets must either contain INT(nav) audio slots (small VFP) or INT(nav)+1 (large VFP) audio slots + audio->packet_sz_tx[0] = packet_sz_tx_norm; + audio->packet_sz_tx[1] = packet_sz_tx_norm; + audio->packet_sz_tx[2] = packet_sz_tx_max; + } else { + // In the case where nav = INT(nav), ni may vary between INT(nav)-1 (small VFP), INT(nav) + // (medium VFP) and INT(nav)+1 (large VFP). + audio->packet_sz_tx[0] = packet_sz_tx_min; + audio->packet_sz_tx[1] = packet_sz_tx_norm; + audio->packet_sz_tx[2] = packet_sz_tx_max; + } + + return true; +} + +static uint16_t audiod_tx_packet_size(const uint16_t *nominal_size, uint16_t data_count, uint16_t fifo_depth, uint16_t fifo_threshold, uint16_t max_depth) { + // Flow control need a FIFO size of at least 4*Navg + if (nominal_size[1] && nominal_size[1] <= fifo_depth * 4) { + // Use blackout to prioritize normal size packet + static int ctrl_blackout = 0; + uint16_t packet_size; + uint16_t slot_size = nominal_size[2] - nominal_size[1]; + if (data_count < nominal_size[0]) { + // If you get here frequently, then your I2S clock deviation is too big ! + packet_size = 0; + } else if (data_count < (fifo_threshold - slot_size) && !ctrl_blackout) { + packet_size = nominal_size[0]; + ctrl_blackout = 10; + } else if (data_count > (fifo_threshold + slot_size) && !ctrl_blackout) { + packet_size = nominal_size[2]; + if (nominal_size[0] == nominal_size[1]) { + // nav > INT(nav), eg. 44.1k, 88.2k + ctrl_blackout = 0; + } else { + // nav = INT(nav), eg. 48k, 96k + ctrl_blackout = 10; + } + } else { + packet_size = nominal_size[1]; + if (ctrl_blackout) { + ctrl_blackout--; + } + } + // Normally this cap is not necessary + return tu_min16(packet_size, max_depth); + } else { + return tu_min16(data_count, max_depth); + } +} + +#endif + +// No security checks here - internal function only which should always succeed +static inline uint8_t audiod_get_audio_fct_idx(audiod_function_t *audio) { + return (uint8_t) (audio - _audiod_fct); +} + +#endif // (CFG_TUD_ENABLED && CFG_TUD_AUDIO) diff --git a/libraries/Adafruit_TinyUSB_Arduino/src/class/audio/audio_device.h b/libraries/Adafruit_TinyUSB_Arduino/src/class/audio/audio_device.h new file mode 100644 index 0000000..1e0c469 --- /dev/null +++ b/libraries/Adafruit_TinyUSB_Arduino/src/class/audio/audio_device.h @@ -0,0 +1,474 @@ +/* + * The MIT License (MIT) + * + * Copyright (c) 2020 Ha Thach (tinyusb.org) + * Copyright (c) 2020 Reinhard Panhuber + * Copyright (c) 2023 HiFiPhile + * + * 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 "audio.h" + +//--------------------------------------------------------------------+ +// Class Driver Configuration +//--------------------------------------------------------------------+ + +// All sizes are in bytes! + +// 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_SZ +#define CFG_TUD_AUDIO_CTRL_BUF_SZ 64 +#endif + +// End point sizes IN BYTES - Limits: Full Speed <= 1023, High Speed <= 1024 +#ifndef CFG_TUD_AUDIO_ENABLE_EP_IN +#define CFG_TUD_AUDIO_ENABLE_EP_IN 0 // TX +#endif + +#ifndef CFG_TUD_AUDIO_ENABLE_EP_OUT +#define CFG_TUD_AUDIO_ENABLE_EP_OUT 0 // RX +#endif + +// Maximum EP sizes for all alternate AS interface settings - used for checks and buffer allocation +#if CFG_TUD_AUDIO_ENABLE_EP_IN +#ifndef CFG_TUD_AUDIO_FUNC_1_EP_IN_SZ_MAX +#error You must tell the driver the biggest EP IN size! +#endif +#if CFG_TUD_AUDIO > 1 +#ifndef CFG_TUD_AUDIO_FUNC_2_EP_IN_SZ_MAX +#error You must tell the driver the biggest EP IN size! +#endif +#endif +#if CFG_TUD_AUDIO > 2 +#ifndef CFG_TUD_AUDIO_FUNC_3_EP_IN_SZ_MAX +#error You must tell the driver the biggest EP IN size! +#endif +#endif +#endif // CFG_TUD_AUDIO_ENABLE_EP_IN + +#if CFG_TUD_AUDIO_ENABLE_EP_OUT +#ifndef CFG_TUD_AUDIO_FUNC_1_EP_OUT_SZ_MAX +#error You must tell the driver the biggest EP OUT size! +#endif +#if CFG_TUD_AUDIO > 1 +#ifndef CFG_TUD_AUDIO_FUNC_2_EP_OUT_SZ_MAX +#error You must tell the driver the biggest EP OUT size! +#endif +#endif +#if CFG_TUD_AUDIO > 2 +#ifndef CFG_TUD_AUDIO_FUNC_3_EP_OUT_SZ_MAX +#error You must tell the driver the biggest EP OUT size! +#endif +#endif +#endif // CFG_TUD_AUDIO_ENABLE_EP_OUT + +// Software EP FIFO buffer sizes - must be >= max EP SIZEs! +#ifndef CFG_TUD_AUDIO_FUNC_1_EP_IN_SW_BUF_SZ +#define CFG_TUD_AUDIO_FUNC_1_EP_IN_SW_BUF_SZ 0 +#endif +#ifndef CFG_TUD_AUDIO_FUNC_2_EP_IN_SW_BUF_SZ +#define CFG_TUD_AUDIO_FUNC_2_EP_IN_SW_BUF_SZ 0 +#endif +#ifndef CFG_TUD_AUDIO_FUNC_3_EP_IN_SW_BUF_SZ +#define CFG_TUD_AUDIO_FUNC_3_EP_IN_SW_BUF_SZ 0 +#endif + +#ifndef CFG_TUD_AUDIO_FUNC_1_EP_OUT_SW_BUF_SZ +#define CFG_TUD_AUDIO_FUNC_1_EP_OUT_SW_BUF_SZ 0 +#endif +#ifndef CFG_TUD_AUDIO_FUNC_2_EP_OUT_SW_BUF_SZ +#define CFG_TUD_AUDIO_FUNC_2_EP_OUT_SW_BUF_SZ 0 +#endif +#ifndef CFG_TUD_AUDIO_FUNC_3_EP_OUT_SW_BUF_SZ +#define CFG_TUD_AUDIO_FUNC_3_EP_OUT_SW_BUF_SZ 0 +#endif + +#if CFG_TUD_AUDIO_ENABLE_EP_IN +#if CFG_TUD_AUDIO_FUNC_1_EP_IN_SW_BUF_SZ < CFG_TUD_AUDIO_FUNC_1_EP_IN_SZ_MAX +#error EP software buffer size MUST BE at least as big as maximum EP size +#endif + +#if CFG_TUD_AUDIO > 1 +#if CFG_TUD_AUDIO_FUNC_2_EP_IN_SW_BUF_SZ < CFG_TUD_AUDIO_FUNC_2_EP_IN_SZ_MAX +#error EP software buffer size MUST BE at least as big as maximum EP size +#endif +#endif + +#if CFG_TUD_AUDIO > 2 +#if CFG_TUD_AUDIO_FUNC_3_EP_IN_SW_BUF_SZ < CFG_TUD_AUDIO_FUNC_3_EP_IN_SZ_MAX +#error EP software buffer size MUST BE at least as big as maximum EP size +#endif +#endif +#endif + +#if CFG_TUD_AUDIO_ENABLE_EP_OUT +#if CFG_TUD_AUDIO_FUNC_1_EP_OUT_SW_BUF_SZ < CFG_TUD_AUDIO_FUNC_1_EP_OUT_SZ_MAX +#error EP software buffer size MUST BE at least as big as maximum EP size +#endif + +#if CFG_TUD_AUDIO > 1 +#if CFG_TUD_AUDIO_FUNC_2_EP_OUT_SW_BUF_SZ < CFG_TUD_AUDIO_FUNC_2_EP_OUT_SZ_MAX +#error EP software buffer size MUST BE at least as big as maximum EP size +#endif +#endif + +#if CFG_TUD_AUDIO > 2 +#if CFG_TUD_AUDIO_FUNC_3_EP_OUT_SW_BUF_SZ < CFG_TUD_AUDIO_FUNC_3_EP_OUT_SZ_MAX +#error EP software buffer size MUST BE at least as big as maximum EP size +#endif +#endif +#endif + +// (For TYPE-I format only) Flow control is necessary to allow IN ep send correct amount of data, unless it's a +// virtual device where data is perfectly synchronized to USB clock. +#ifndef CFG_TUD_AUDIO_EP_IN_FLOW_CONTROL +#define CFG_TUD_AUDIO_EP_IN_FLOW_CONTROL 1 +#endif + +// Enable/disable feedback EP (required for asynchronous RX applications) +#ifndef CFG_TUD_AUDIO_ENABLE_FEEDBACK_EP +#define CFG_TUD_AUDIO_ENABLE_FEEDBACK_EP 0 // Feedback - 0 or 1 +#endif + +// Enable/disable interrupt EP (required for notifying host of control changes) +#ifndef CFG_TUD_AUDIO_ENABLE_INTERRUPT_EP +#define CFG_TUD_AUDIO_ENABLE_INTERRUPT_EP 0 // Feedback - 0 or 1 +#endif + +// Audio control interrupt EP - 6 Bytes according to UAC 2 specification (p. 74) +#define CFG_TUD_AUDIO_INTERRUPT_EP_SZ 6 + +#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 func_id); +uint8_t tud_audio_n_version(uint8_t func_id); + +#if CFG_TUD_AUDIO_ENABLE_EP_OUT +uint16_t tud_audio_n_available (uint8_t func_id); +uint16_t tud_audio_n_read (uint8_t func_id, void* buffer, uint16_t bufsize); +bool tud_audio_n_clear_ep_out_ff (uint8_t func_id); +tu_fifo_t* tud_audio_n_get_ep_out_ff (uint8_t func_id); +#endif + +#if CFG_TUD_AUDIO_ENABLE_EP_IN +uint16_t tud_audio_n_write (uint8_t func_id, const void * data, uint16_t len); +bool tud_audio_n_clear_ep_in_ff (uint8_t func_id); +tu_fifo_t* tud_audio_n_get_ep_in_ff (uint8_t func_id); +uint16_t tud_audio_n_get_ep_in_fifo_threshold(uint8_t func_id); +void tud_audio_n_set_ep_in_fifo_threshold(uint8_t func_id, uint16_t threshold); +#endif + +#if CFG_TUD_AUDIO_ENABLE_INTERRUPT_EP +bool tud_audio_int_n_write (uint8_t func_id, const audio_interrupt_data_t * data); +#endif + +//--------------------------------------------------------------------+ +// Application API (Interface0) +//--------------------------------------------------------------------+ +static inline bool tud_audio_mounted (void); +static inline uint8_t tud_audio_version (void); + +#if CFG_TUD_AUDIO_ENABLE_EP_OUT +static inline uint16_t tud_audio_available (void); +static inline bool tud_audio_clear_ep_out_ff (void); +static inline uint16_t tud_audio_read (void* buffer, uint16_t bufsize); +static inline tu_fifo_t* tud_audio_get_ep_out_ff (void); +#endif + +#if CFG_TUD_AUDIO_ENABLE_EP_IN +static inline uint16_t tud_audio_write (const void * data, uint16_t len); +static inline bool tud_audio_clear_ep_in_ff (void); +static inline tu_fifo_t* tud_audio_get_ep_in_ff (void); +#endif + +// INT CTR API + +#if CFG_TUD_AUDIO_ENABLE_INTERRUPT_EP +static inline bool tud_audio_int_write (const audio_interrupt_data_t * data); +#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 +//--------------------------------------------------------------------+ + +#if CFG_TUD_AUDIO_ENABLE_EP_IN +// Invoked in ISR context once an audio packet was sent successfully. +// Normally this function is not needed, since the data transfer should be driven by audio clock (i.e. I2S clock), +// call tud_audio_write() in I2S receive callback. +bool tud_audio_tx_done_isr(uint8_t rhport, uint16_t n_bytes_sent, uint8_t func_id, uint8_t ep_in, + uint8_t cur_alt_setting); +#endif + +#if CFG_TUD_AUDIO_ENABLE_EP_OUT +// Invoked in ISR context once an audio packet was received successfully. +// Normally this function is not needed, since the data transfer should be driven by audio clock (i.e. I2S clock), +// call tud_audio_read() in I2S transmit callback. +bool tud_audio_rx_done_isr(uint8_t rhport, uint16_t n_bytes_received, uint8_t func_id, uint8_t ep_out, + uint8_t cur_alt_setting); +#endif + +#if CFG_TUD_AUDIO_ENABLE_EP_OUT && CFG_TUD_AUDIO_ENABLE_FEEDBACK_EP + +// Note about feedback calculation +// +// Option 1 - AUDIO_FEEDBACK_METHOD_FIFO_COUNT +// Feedback value is calculated within the audio driver by regulating the FIFO level to half fill. +// Advantage: No SOF interrupt is enabled, hence the CPU need not to handle an ISR every 1ms or 125us and thus +// less CPU load, well tested (Windows, Linux, OSX) with a reliable result so far. +// Disadvantage: A FIFO of minimal 4 frames is needed to compensate for jitter, an average delay of 2 frames is +// introduced. +// +// Option 2 - AUDIO_FEEDBACK_METHOD_FREQUENCY_FIXED / AUDIO_FEEDBACK_METHOD_FREQUENCY_FLOAT +// Feedback value is calculated within the audio driver by use of SOF interrupt. The driver needs information +// about the master clock f_m from which the audio sample frequency f_s is derived, f_s itself, and the cycle +// count of f_m at time of the SOF interrupt (e.g. by use of a hardware counter). +// See tud_audio_set_fb_params() and tud_audio_feedback_update() +// Advantage: Reduced jitter in the feedback value computation, hence, the receive FIFO can be smaller and thus a +// smaller delay is possible. +// Disadvantage: higher CPU load due to SOF ISR handling every frame i.e. 1ms or 125us. (The most critical point +// is the reading of the cycle counter value of f_m. It is read from within the SOF ISR - see: audiod_sof() -, +// hence, the ISR must has a high priority such that no software dependent "random" delay i.e. jitter is +// introduced). Long-term drift will cause the FIFO under/overflow, you still needs to correct it somehow. +// +// Option 3 - manual +// Determined by the user itself and set by use of tud_audio_n_fb_set(). The feedback value may be determined +// e.g. from some fill status of some FIFO buffer. +// Advantage: No ISR interrupt is enabled, hence the CPU need not to handle an ISR every 1ms or 125us and thus +// less CPU load. +// Disadvantage: typically a larger FIFO is needed to compensate for jitter (e.g. 6 frames), i.e. a larger delay +// is introduced. + + +// This function is used to provide data rate feedback from an asynchronous sink. Feedback value will be sent at +// FB endpoint interval till it's changed. +// +// The feedback format is specified to be 16.16 for HS and 10.14 for FS devices (see Universal Serial Bus +// Specification Revision 2.0 5.12.4.2). For simplicity, this function always uses 16.16 format. For FS devices, +// the driver will automatically convert the value to 10.14 format. +// +// Note that due to a bug in its USB Audio 2.0 driver, Windows currently requires 16.16 format for _all_ USB 2.0 +// devices. On Linux and it seems the driver can work with either format. +// +// Feedback value can be determined from within the SOF ISR of the audio driver. This should reduce jitter. If the +// feature is used, the user can not set the feedback value. +// +// Determine feedback value - The feedback method is described in 5.12.4.2 of the USB 2.0 spec +// Boiled down, the feedback value Ff = n_samples / (micro)frame. +// Since an accuracy of less than 1 Sample / second is desired, at least n_frames = ceil(2^K * f_s / f_m) frames +// need to be measured, where K = 10 for full speed and K = 13 for high speed, f_s is the sampling frequency +// e.g. 48 kHz and f_m is the cpu clock frequency e.g. 100 MHz (or any other master clock whose clock count is +// available and locked to f_s) +// The update interval in the (4.10.2.1) Feedback Endpoint Descriptor must be less or equal to 2^(K - P), where +// P = min( ceil(log2(f_m / f_s)), K) +// feedback = n_cycles / n_frames * f_s / f_m in 16.16 format, where n_cycles are the number of main clock cycles +// within fb_n_frames +bool tud_audio_n_fb_set(uint8_t func_id, uint32_t feedback); + +// Update feedback value with passed MCLK cycles since last time this update function is called. +// Typically called within tud_audio_sof_isr(). Required tud_audio_feedback_params_cb() is implemented +// This function will also call tud_audio_feedback_set() +// return feedback value in 16.16 for reference (0 for error) +// Example : +// binterval=3 (4ms); FS = 48kHz; MCLK = 12.288MHz +// In 4 SOF MCLK counted 49152 cycles +uint32_t tud_audio_feedback_update(uint8_t func_id, uint32_t cycles); + +enum { + AUDIO_FEEDBACK_METHOD_DISABLED, + AUDIO_FEEDBACK_METHOD_FREQUENCY_FIXED, + AUDIO_FEEDBACK_METHOD_FREQUENCY_FLOAT, + AUDIO_FEEDBACK_METHOD_FREQUENCY_POWER_OF_2, // For driver internal use only + AUDIO_FEEDBACK_METHOD_FIFO_COUNT +}; + +typedef struct { + uint8_t method; + uint32_t sample_freq; // sample frequency in Hz + + union { + struct { + uint32_t mclk_freq; // Main clock frequency in Hz i.e. master clock to which sample clock is based on + } frequency; + struct { + uint16_t fifo_threshold; // Target FIFO threshold level, default to half FIFO if not set + } fifo_count; + }; +} audio_feedback_params_t; + +// Invoked when needed to set feedback parameters +void tud_audio_feedback_params_cb(uint8_t func_id, uint8_t alt_itf, audio_feedback_params_t* feedback_param); + +// Callback in ISR context, invoked periodically according to feedback endpoint bInterval. +// Could be used to compute and update feedback value, should be placed in RAM if possible +// frame_number : current SOF count +// interval_shift: number of bit shift i.e log2(interval) from Feedback endpoint descriptor +TU_ATTR_FAST_FUNC void tud_audio_feedback_interval_isr(uint8_t func_id, uint32_t frame_number, uint8_t interval_shift); +#endif // CFG_TUD_AUDIO_ENABLE_EP_OUT && CFG_TUD_AUDIO_ENABLE_FEEDBACK_EP + +#if CFG_TUD_AUDIO_ENABLE_INTERRUPT_EP +// Invoked when an interrupt notification transfer is complete +void tud_audio_int_done_cb(uint8_t rhport); +#endif + +// Invoked when audio set interface request received +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 +bool tud_audio_set_itf_close_ep_cb(uint8_t rhport, tusb_control_request_t const * p_request); + +// backward compatible for typo +#define tud_audio_set_itf_close_EP_cb tud_audio_set_itf_close_ep_cb + +// 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); + +// 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); + +// 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); + +// 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); + +// 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); + +// 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); + +//--------------------------------------------------------------------+ +// Inline Functions +//--------------------------------------------------------------------+ + +TU_ATTR_ALWAYS_INLINE static inline bool tud_audio_mounted(void) { + return tud_audio_n_mounted(0); +} + +TU_ATTR_ALWAYS_INLINE static inline uint8_t tud_audio_version(void) { + return tud_audio_n_version(0); +} + +#if CFG_TUD_AUDIO_ENABLE_EP_OUT + +TU_ATTR_ALWAYS_INLINE static inline uint16_t tud_audio_available(void) { + return tud_audio_n_available(0); +} + +TU_ATTR_ALWAYS_INLINE static inline uint16_t tud_audio_read(void* buffer, uint16_t bufsize) { + return tud_audio_n_read(0, buffer, bufsize); +} + +TU_ATTR_ALWAYS_INLINE static inline bool tud_audio_clear_ep_out_ff(void) { + return tud_audio_n_clear_ep_out_ff(0); +} + +TU_ATTR_ALWAYS_INLINE static inline tu_fifo_t* tud_audio_get_ep_out_ff(void) { + return tud_audio_n_get_ep_out_ff(0); +} + +#endif + +#if CFG_TUD_AUDIO_ENABLE_EP_IN + +TU_ATTR_ALWAYS_INLINE static inline uint16_t tud_audio_write(const void * data, uint16_t len) { + return tud_audio_n_write(0, data, len); +} + +TU_ATTR_ALWAYS_INLINE static inline bool tud_audio_clear_ep_in_ff(void) { + return tud_audio_n_clear_ep_in_ff(0); +} + +TU_ATTR_ALWAYS_INLINE static inline tu_fifo_t* tud_audio_get_ep_in_ff(void) { + return tud_audio_n_get_ep_in_ff(0); +} + +TU_ATTR_ALWAYS_INLINE static inline uint16_t tud_audio_get_ep_in_fifo_threshold(void) +{ + return tud_audio_n_get_ep_in_fifo_threshold(0); +} + +TU_ATTR_ALWAYS_INLINE static inline void tud_audio_set_ep_in_fifo_threshold(uint16_t threshold) +{ + tud_audio_n_set_ep_in_fifo_threshold(0, threshold); +} + +#endif + +#if CFG_TUD_AUDIO_ENABLE_INTERRUPT_EP +TU_ATTR_ALWAYS_INLINE static inline bool tud_audio_int_write(const audio_interrupt_data_t * data) { + return tud_audio_int_n_write(0, data); +} +#endif + +#if CFG_TUD_AUDIO_ENABLE_EP_OUT && CFG_TUD_AUDIO_ENABLE_FEEDBACK_EP +TU_ATTR_ALWAYS_INLINE static inline bool tud_audio_fb_set(uint32_t feedback) { + return tud_audio_n_fb_set(0, feedback); +} +#endif + +//--------------------------------------------------------------------+ +// Internal Class Driver API +//--------------------------------------------------------------------+ +void audiod_init (void); +bool audiod_deinit (void); +void audiod_reset (uint8_t rhport); +uint16_t audiod_open (uint8_t rhport, tusb_desc_interface_t const * itf_desc, uint16_t max_len); +bool audiod_control_xfer_cb(uint8_t rhport, uint8_t stage, tusb_control_request_t const * request); +bool audiod_xfer_cb (uint8_t rhport, uint8_t edpt_addr, xfer_result_t result, uint32_t xferred_bytes); +bool audiod_xfer_isr (uint8_t rhport, uint8_t edpt_addr, xfer_result_t result, uint32_t xferred_bytes); +void audiod_sof_isr (uint8_t rhport, uint32_t frame_count); + +#ifdef __cplusplus +} +#endif + +#endif /* TUSB_AUDIO_DEVICE_H_ */ + +/** @} */ +/** @} */ diff --git a/libraries/Adafruit_TinyUSB_Arduino/src/class/bth/bth_device.c b/libraries/Adafruit_TinyUSB_Arduino/src/class/bth/bth_device.c new file mode 100644 index 0000000..b679d6a --- /dev/null +++ b/libraries/Adafruit_TinyUSB_Arduino/src/class/bth/bth_device.c @@ -0,0 +1,298 @@ +/* + * The MIT License (MIT) + * + * Copyright (c) 2020 Jerzy Kasenberg + * + * 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. + */ + +#include "tusb_option.h" + +#if (CFG_TUD_ENABLED && CFG_TUD_BTH) + +//--------------------------------------------------------------------+ +// INCLUDE +//--------------------------------------------------------------------+ +#include "bth_device.h" +#include + +//--------------------------------------------------------------------+ +// MACRO CONSTANT TYPEDEF +//--------------------------------------------------------------------+ +typedef struct { + uint8_t itf_num; + uint8_t ep_ev; + uint8_t ep_acl_in; + uint16_t ep_acl_in_pkt_sz; + uint8_t ep_acl_out; + uint8_t ep_voice[2];// Not used yet + uint8_t ep_voice_size[2][CFG_TUD_BTH_ISO_ALT_COUNT]; + + // Previous amount of bytes sent when issuing ZLP + uint32_t prev_xferred_bytes; +} btd_interface_t; + +typedef struct { + TUD_EPBUF_DEF(epout_buf, CFG_TUD_BTH_DATA_EPSIZE); + TUD_EPBUF_TYPE_DEF(bt_hci_cmd_t, hci_cmd); +} btd_epbuf_t; + +//--------------------------------------------------------------------+ +// INTERNAL OBJECT & FUNCTION DECLARATION +//--------------------------------------------------------------------+ +static btd_interface_t _btd_itf; +CFG_TUD_MEM_SECTION static btd_epbuf_t _btd_epbuf; + +static bool bt_tx_data(uint8_t ep, void *data, uint16_t len) { + uint8_t const rhport = 0; + + // skip if previous transfer not complete + TU_VERIFY(!usbd_edpt_busy(rhport, ep)); + + TU_ASSERT(usbd_edpt_xfer(rhport, ep, data, len, false)); + + return true; +} + +//--------------------------------------------------------------------+ +// Weak stubs: invoked if no strong implementation is available +//--------------------------------------------------------------------+ +TU_ATTR_WEAK void tud_bt_hci_cmd_cb(void *hci_cmd, size_t cmd_len) { + (void) hci_cmd; + (void) cmd_len; +} + +TU_ATTR_WEAK void tud_bt_acl_data_received_cb(void *acl_data, uint16_t data_len) { + (void) acl_data; + (void) data_len; +} + +TU_ATTR_WEAK void tud_bt_event_sent_cb(uint16_t sent_bytes) { + (void) sent_bytes; +} + +TU_ATTR_WEAK void tud_bt_acl_data_sent_cb(uint16_t sent_bytes) { + (void) sent_bytes; +} + +//--------------------------------------------------------------------+ +// READ API +//--------------------------------------------------------------------+ + + +//--------------------------------------------------------------------+ +// WRITE API +//--------------------------------------------------------------------+ + +bool tud_bt_event_send(void *event, uint16_t event_len) { + return bt_tx_data(_btd_itf.ep_ev, event, event_len); +} + +bool tud_bt_acl_data_send(void *event, uint16_t event_len) { + return bt_tx_data(_btd_itf.ep_acl_in, event, event_len); +} + +//--------------------------------------------------------------------+ +// USBD Driver API +//--------------------------------------------------------------------+ +void btd_init(void) { + tu_memclr(&_btd_itf, sizeof(_btd_itf)); +} + +bool btd_deinit(void) { + return true; +} + +void btd_reset(uint8_t rhport) { + (void) rhport; +} + +uint16_t btd_open(uint8_t rhport, tusb_desc_interface_t const *itf_desc, uint16_t max_len) { + tusb_desc_endpoint_t const *desc_ep; + uint16_t drv_len = 0; + // Size of single alternative of ISO interface + const uint16_t iso_alt_itf_size = sizeof(tusb_desc_interface_t) + 2 * sizeof(tusb_desc_endpoint_t); + // Size of hci interface + const uint16_t hci_itf_size = sizeof(tusb_desc_interface_t) + 3 * sizeof(tusb_desc_endpoint_t); + // Ensure this is BT Primary Controller + TU_VERIFY(TUSB_CLASS_WIRELESS_CONTROLLER == itf_desc->bInterfaceClass && + TUD_BT_APP_SUBCLASS == itf_desc->bInterfaceSubClass && + TUD_BT_PROTOCOL_PRIMARY_CONTROLLER == itf_desc->bInterfaceProtocol, + 0); + + TU_ASSERT(itf_desc->bNumEndpoints == 3 && max_len >= hci_itf_size); + + _btd_itf.itf_num = itf_desc->bInterfaceNumber; + + desc_ep = (tusb_desc_endpoint_t const *) tu_desc_next(itf_desc); + + TU_ASSERT(TUSB_DESC_ENDPOINT == desc_ep->bDescriptorType && TUSB_XFER_INTERRUPT == desc_ep->bmAttributes.xfer, 0); + TU_ASSERT(usbd_edpt_open(rhport, desc_ep), 0); + _btd_itf.ep_ev = desc_ep->bEndpointAddress; + + desc_ep = (tusb_desc_endpoint_t const *) tu_desc_next(desc_ep); + + // Open endpoint pair + TU_ASSERT(usbd_open_edpt_pair(rhport, (uint8_t const *) desc_ep, 2, + TUSB_XFER_BULK, &_btd_itf.ep_acl_out, + &_btd_itf.ep_acl_in), + 0); + + // Save acl in endpoint max packet size + tusb_desc_endpoint_t const *desc_ep_acl_in = desc_ep; + for (size_t p = 0; p < 2; p++) { + if (tu_edpt_dir(desc_ep_acl_in->bEndpointAddress) == TUSB_DIR_IN) { + _btd_itf.ep_acl_in_pkt_sz = tu_edpt_packet_size(desc_ep_acl_in); + break; + } + desc_ep_acl_in = (tusb_desc_endpoint_t const *) tu_desc_next(desc_ep_acl_in); + } + + itf_desc = (tusb_desc_interface_t const *) tu_desc_next(tu_desc_next(desc_ep)); + + // Prepare for incoming data from host + TU_ASSERT(usbd_edpt_xfer(rhport, _btd_itf.ep_acl_out, _btd_epbuf.epout_buf, CFG_TUD_BTH_DATA_EPSIZE, false), 0); + + drv_len = hci_itf_size; + + // Ensure this is still BT Primary Controller + TU_ASSERT(TUSB_CLASS_WIRELESS_CONTROLLER == itf_desc->bInterfaceClass && + TUD_BT_APP_SUBCLASS == itf_desc->bInterfaceSubClass && + TUD_BT_PROTOCOL_PRIMARY_CONTROLLER == itf_desc->bInterfaceProtocol, + 0); + TU_ASSERT(itf_desc->bNumEndpoints == 2 && max_len >= iso_alt_itf_size + drv_len); + + uint8_t dir; + + desc_ep = (tusb_desc_endpoint_t const *) tu_desc_next(itf_desc); + TU_ASSERT(itf_desc->bAlternateSetting < CFG_TUD_BTH_ISO_ALT_COUNT, 0); + TU_ASSERT(desc_ep->bDescriptorType == TUSB_DESC_ENDPOINT, 0); + dir = tu_edpt_dir(desc_ep->bEndpointAddress); + _btd_itf.ep_voice[dir] = desc_ep->bEndpointAddress; + // Store endpoint size for alternative + _btd_itf.ep_voice_size[dir][itf_desc->bAlternateSetting] = (uint8_t) tu_edpt_packet_size(desc_ep); + + desc_ep = (tusb_desc_endpoint_t const *) tu_desc_next(desc_ep); + TU_ASSERT(desc_ep->bDescriptorType == TUSB_DESC_ENDPOINT, 0); + dir = tu_edpt_dir(desc_ep->bEndpointAddress); + _btd_itf.ep_voice[dir] = desc_ep->bEndpointAddress; + // Store endpoint size for alternative + _btd_itf.ep_voice_size[dir][itf_desc->bAlternateSetting] = (uint8_t) tu_edpt_packet_size(desc_ep); + drv_len += iso_alt_itf_size; + + for (int i = 1; i < CFG_TUD_BTH_ISO_ALT_COUNT && drv_len + iso_alt_itf_size <= max_len; ++i) { + // Make sure rest of alternatives matches + itf_desc = (tusb_desc_interface_t const *) tu_desc_next(desc_ep); + if (itf_desc->bDescriptorType != TUSB_DESC_INTERFACE || + TUSB_CLASS_WIRELESS_CONTROLLER != itf_desc->bInterfaceClass || + TUD_BT_APP_SUBCLASS != itf_desc->bInterfaceSubClass || + TUD_BT_PROTOCOL_PRIMARY_CONTROLLER != itf_desc->bInterfaceProtocol) { + // Not an Iso interface instance + break; + } + TU_ASSERT(itf_desc->bAlternateSetting < CFG_TUD_BTH_ISO_ALT_COUNT, 0); + + desc_ep = (tusb_desc_endpoint_t const *) tu_desc_next(itf_desc); + dir = tu_edpt_dir(desc_ep->bEndpointAddress); + // Verify that alternative endpoint are same as first ones + TU_ASSERT(desc_ep->bDescriptorType == TUSB_DESC_ENDPOINT && + _btd_itf.ep_voice[dir] == desc_ep->bEndpointAddress, + 0); + _btd_itf.ep_voice_size[dir][itf_desc->bAlternateSetting] = (uint8_t) tu_edpt_packet_size(desc_ep); + + desc_ep = (tusb_desc_endpoint_t const *) tu_desc_next(desc_ep); + dir = tu_edpt_dir(desc_ep->bEndpointAddress); + // Verify that alternative endpoint are same as first ones + TU_ASSERT(desc_ep->bDescriptorType == TUSB_DESC_ENDPOINT && + _btd_itf.ep_voice[dir] == desc_ep->bEndpointAddress, + 0); + _btd_itf.ep_voice_size[dir][itf_desc->bAlternateSetting] = (uint8_t) tu_edpt_packet_size(desc_ep); + drv_len += iso_alt_itf_size; + } + + return drv_len; +} + +// Invoked when a control transfer occurred on an interface of this class +// Driver response accordingly to the request and the transfer stage (setup/data/ack) +// return false to stall control endpoint (e.g unsupported request) +bool btd_control_xfer_cb(uint8_t rhport, uint8_t stage, tusb_control_request_t const *request) { + (void) rhport; + + if (stage == CONTROL_STAGE_SETUP) { + if (request->bmRequestType_bit.type == TUSB_REQ_TYPE_CLASS && + request->bmRequestType_bit.recipient == TUSB_REQ_RCPT_DEVICE) { + // HCI command packet addressing for single function Primary Controllers + // also compatible with historical mode if enabled + TU_VERIFY((request->bRequest == 0 && request->wValue == 0 && request->wIndex == 0) || + (CFG_TUD_BTH_HISTORICAL_COMPATIBLE && request->bRequest == 0xe0)); + } else if (request->bmRequestType_bit.recipient == TUSB_REQ_RCPT_INTERFACE) { + if (request->bRequest == TUSB_REQ_SET_INTERFACE && _btd_itf.itf_num + 1 == request->wIndex) { + // TODO: Set interface it would involve changing size of endpoint size + } else { + // HCI command packet for Primary Controller function in a composite device + TU_VERIFY(request->bRequest == 0 && request->wValue == 0 && request->wIndex == _btd_itf.itf_num); + } + } else + return false; + + return tud_control_xfer(rhport, request, &_btd_epbuf.hci_cmd, sizeof(bt_hci_cmd_t)); + } else if (stage == CONTROL_STAGE_DATA) { + // Handle class request only + TU_VERIFY(request->bmRequestType_bit.type == TUSB_REQ_TYPE_CLASS); + + tud_bt_hci_cmd_cb(&_btd_epbuf.hci_cmd, tu_min16(request->wLength, sizeof(bt_hci_cmd_t))); + } + + return true; +} + +bool btd_xfer_cb(uint8_t rhport, uint8_t ep_addr, xfer_result_t result, + uint32_t xferred_bytes) { + // received new data from host + if (ep_addr == _btd_itf.ep_acl_out) { + tud_bt_acl_data_received_cb(_btd_epbuf.epout_buf, xferred_bytes); + + // prepare for next data + TU_ASSERT(usbd_edpt_xfer(rhport, _btd_itf.ep_acl_out, _btd_epbuf.epout_buf, CFG_TUD_BTH_DATA_EPSIZE, false)); + } else if (ep_addr == _btd_itf.ep_ev) { + tud_bt_event_sent_cb((uint16_t) xferred_bytes); + } else if (ep_addr == _btd_itf.ep_acl_in) { + if ((result == XFER_RESULT_SUCCESS) && (xferred_bytes > 0) && + ((xferred_bytes & (_btd_itf.ep_acl_in_pkt_sz - 1)) == 0)) { + // Save number of transferred bytes + _btd_itf.prev_xferred_bytes = xferred_bytes; + + // Send zero-length packet + tud_bt_acl_data_send(NULL, 0); + } else { + if (xferred_bytes == 0) { + xferred_bytes = _btd_itf.prev_xferred_bytes; + _btd_itf.prev_xferred_bytes = 0; + } + tud_bt_acl_data_sent_cb((uint16_t) xferred_bytes); + } + } + + return true; +} + +#endif diff --git a/libraries/Adafruit_TinyUSB_Arduino/src/class/bth/bth_device.h b/libraries/Adafruit_TinyUSB_Arduino/src/class/bth/bth_device.h new file mode 100644 index 0000000..89a056d --- /dev/null +++ b/libraries/Adafruit_TinyUSB_Arduino/src/class/bth/bth_device.h @@ -0,0 +1,117 @@ +/* + * The MIT License (MIT) + * + * Copyright (c) 2020 Jerzy Kasenberg + * + * 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_BTH_DEVICE_H_ +#define TUSB_BTH_DEVICE_H_ + +#include +#include + +//--------------------------------------------------------------------+ +// Class Driver Configuration +//--------------------------------------------------------------------+ +#ifndef CFG_TUD_BTH_EVENT_EPSIZE +#define CFG_TUD_BTH_EVENT_EPSIZE 16 +#endif + +#ifndef CFG_TUD_BTH_DATA_EPSIZE +#define CFG_TUD_BTH_DATA_EPSIZE 64 +#endif + +// Allow BTH class to work in historically compatibility mode where the bRequest is always 0xe0. +// See Bluetooth Core v5.3, Vol. 4, Part B, Section 2.2 +#ifndef CFG_TUD_BTH_HISTORICAL_COMPATIBLE +#define CFG_TUD_BTH_HISTORICAL_COMPATIBLE 0 +#endif + +typedef struct TU_ATTR_PACKED +{ + uint16_t op_code; + uint8_t param_length; + uint8_t param[255]; +} bt_hci_cmd_t; + +#ifdef __cplusplus + extern "C" { +#endif + +//--------------------------------------------------------------------+ +// Application Callback API (weak is optional) +//--------------------------------------------------------------------+ + +// Invoked when HCI command was received over USB from Bluetooth host. +// Detailed format is described in Bluetooth core specification Vol 2, +// Part E, 5.4.1. +// Length of the command is from 3 bytes (2 bytes for OpCode, +// 1 byte for parameter total length) to 258. +void tud_bt_hci_cmd_cb(void *hci_cmd, size_t cmd_len); + +// Invoked when ACL data was received over USB from Bluetooth host. +// Detailed format is described in Bluetooth core specification Vol 2, +// Part E, 5.4.2. +// Length is from 4 bytes, (12 bits for Handle, 4 bits for flags +// and 16 bits for data total length) to endpoint size. +void tud_bt_acl_data_received_cb(void *acl_data, uint16_t data_len); + +// Called when event sent with tud_bt_event_send() was delivered to BT stack. +// Controller can release/reuse buffer with Event packet at this point. +void tud_bt_event_sent_cb(uint16_t sent_bytes); + +// Called when ACL data that was sent with tud_bt_acl_data_send() +// was delivered to BT stack. +// Controller can release/reuse buffer with ACL packet at this point. +void tud_bt_acl_data_sent_cb(uint16_t sent_bytes); + +// Bluetooth controller calls this function when it wants to send even packet +// as described in Bluetooth core specification Vol 2, Part E, 5.4.4. +// Event has at least 2 bytes, first is Event code second contains parameter +// total length. Controller can release/reuse event memory after +// tud_bt_event_sent_cb() is called. +bool tud_bt_event_send(void *event, uint16_t event_len); + +// Bluetooth controller calls this to send ACL data packet +// as described in Bluetooth core specification Vol 2, Part E, 5.4.2 +// Minimum length is 4 bytes, (12 bits for Handle, 4 bits for flags +// and 16 bits for data total length). Upper limit is not limited +// to endpoint size since buffer is allocate by controller +// and must not be reused till tud_bt_acl_data_sent_cb() is called. +bool tud_bt_acl_data_send(void *acl_data, uint16_t data_len); + +//--------------------------------------------------------------------+ +// Internal Class Driver API +//--------------------------------------------------------------------+ +void btd_init (void); +bool btd_deinit (void); +void btd_reset (uint8_t rhport); +uint16_t btd_open (uint8_t rhport, tusb_desc_interface_t const * itf_desc, uint16_t max_len); +bool btd_control_xfer_cb (uint8_t rhport, uint8_t stage, tusb_control_request_t const *request); +bool btd_xfer_cb (uint8_t rhport, uint8_t edpt_addr, xfer_result_t result, uint32_t xferred_bytes); + +#ifdef __cplusplus + } +#endif + +#endif /* TUSB_BTH_DEVICE_H_ */ diff --git a/libraries/Adafruit_TinyUSB_Arduino/src/class/cdc/cdc.h b/libraries/Adafruit_TinyUSB_Arduino/src/class/cdc/cdc.h new file mode 100644 index 0000000..679723b --- /dev/null +++ b/libraries/Adafruit_TinyUSB_Arduino/src/class/cdc/cdc.h @@ -0,0 +1,472 @@ +/* + * 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. + * + * This file is part of the TinyUSB stack. + */ + +/** \ingroup group_class + * \defgroup ClassDriver_CDC Communication Device Class (CDC) + * Currently only Abstract Control Model subclass is supported + * @{ */ + +#ifndef TUSB_CDC_H__ +#define TUSB_CDC_H__ + +#include "common/tusb_common.h" + +#ifdef __cplusplus + extern "C" { +#endif + +/** \defgroup ClassDriver_CDC_Common Common Definitions + * @{ */ + +//--------------------------------------------------------------------+ +// CDC Communication Interface Class +//--------------------------------------------------------------------+ + +/// Communication Interface Subclass Codes +typedef enum +{ + CDC_COMM_SUBCLASS_DIRECT_LINE_CONTROL_MODEL = 0x01 , ///< Direct Line Control Model [USBPSTN1.2] + CDC_COMM_SUBCLASS_ABSTRACT_CONTROL_MODEL = 0x02 , ///< Abstract Control Model [USBPSTN1.2] + CDC_COMM_SUBCLASS_TELEPHONE_CONTROL_MODEL = 0x03 , ///< Telephone Control Model [USBPSTN1.2] + CDC_COMM_SUBCLASS_MULTICHANNEL_CONTROL_MODEL = 0x04 , ///< Multi-Channel Control Model [USBISDN1.2] + CDC_COMM_SUBCLASS_CAPI_CONTROL_MODEL = 0x05 , ///< CAPI Control Model [USBISDN1.2] + CDC_COMM_SUBCLASS_ETHERNET_CONTROL_MODEL = 0x06 , ///< Ethernet Networking Control Model [USBECM1.2] + CDC_COMM_SUBCLASS_ATM_NETWORKING_CONTROL_MODEL = 0x07 , ///< ATM Networking Control Model [USBATM1.2] + CDC_COMM_SUBCLASS_WIRELESS_HANDSET_CONTROL_MODEL = 0x08 , ///< Wireless Handset Control Model [USBWMC1.1] + CDC_COMM_SUBCLASS_DEVICE_MANAGEMENT = 0x09 , ///< Device Management [USBWMC1.1] + CDC_COMM_SUBCLASS_MOBILE_DIRECT_LINE_MODEL = 0x0A , ///< Mobile Direct Line Model [USBWMC1.1] + CDC_COMM_SUBCLASS_OBEX = 0x0B , ///< OBEX [USBWMC1.1] + CDC_COMM_SUBCLASS_ETHERNET_EMULATION_MODEL = 0x0C , ///< Ethernet Emulation Model [USBEEM1.0] + CDC_COMM_SUBCLASS_NETWORK_CONTROL_MODEL = 0x0D ///< Network Control Model [USBNCM1.0] +} cdc_comm_sublcass_type_t; + +/// Communication Interface Protocol Codes +typedef enum +{ + CDC_COMM_PROTOCOL_NONE = 0x00 , ///< No specific protocol + CDC_COMM_PROTOCOL_ATCOMMAND = 0x01 , ///< AT Commands: V.250 etc + CDC_COMM_PROTOCOL_ATCOMMAND_PCCA_101 = 0x02 , ///< AT Commands defined by PCCA-101 + CDC_COMM_PROTOCOL_ATCOMMAND_PCCA_101_AND_ANNEXO = 0x03 , ///< AT Commands defined by PCCA-101 & Annex O + CDC_COMM_PROTOCOL_ATCOMMAND_GSM_707 = 0x04 , ///< AT Commands defined by GSM 07.07 + CDC_COMM_PROTOCOL_ATCOMMAND_3GPP_27007 = 0x05 , ///< AT Commands defined by 3GPP 27.007 + CDC_COMM_PROTOCOL_ATCOMMAND_CDMA = 0x06 , ///< AT Commands defined by TIA for CDMA + CDC_COMM_PROTOCOL_ETHERNET_EMULATION_MODEL = 0x07 ///< Ethernet Emulation Model +} cdc_comm_protocol_type_t; + +//------------- SubType Descriptor in COMM Functional Descriptor -------------// +/// Communication Interface SubType Descriptor +typedef enum +{ + CDC_FUNC_DESC_HEADER = 0x00 , ///< Header Functional Descriptor, which marks the beginning of the concatenated set of functional descriptors for the interface. + CDC_FUNC_DESC_CALL_MANAGEMENT = 0x01 , ///< Call Management Functional Descriptor. + CDC_FUNC_DESC_ABSTRACT_CONTROL_MANAGEMENT = 0x02 , ///< Abstract Control Management Functional Descriptor. + CDC_FUNC_DESC_DIRECT_LINE_MANAGEMENT = 0x03 , ///< Direct Line Management Functional Descriptor. + CDC_FUNC_DESC_TELEPHONE_RINGER = 0x04 , ///< Telephone Ringer Functional Descriptor. + CDC_FUNC_DESC_TELEPHONE_CALL_AND_LINE_STATE_REPORTING_CAPACITY = 0x05 , ///< Telephone Call and Line State Reporting Capabilities Functional Descriptor. + CDC_FUNC_DESC_UNION = 0x06 , ///< Union Functional Descriptor + CDC_FUNC_DESC_COUNTRY_SELECTION = 0x07 , ///< Country Selection Functional Descriptor + CDC_FUNC_DESC_TELEPHONE_OPERATIONAL_MODES = 0x08 , ///< Telephone Operational ModesFunctional Descriptor + CDC_FUNC_DESC_USB_TERMINAL = 0x09 , ///< USB Terminal Functional Descriptor + CDC_FUNC_DESC_NETWORK_CHANNEL_TERMINAL = 0x0A , ///< Network Channel Terminal Descriptor + CDC_FUNC_DESC_PROTOCOL_UNIT = 0x0B , ///< Protocol Unit Functional Descriptor + CDC_FUNC_DESC_EXTENSION_UNIT = 0x0C , ///< Extension Unit Functional Descriptor + CDC_FUNC_DESC_MULTICHANEL_MANAGEMENT = 0x0D , ///< Multi-Channel Management Functional Descriptor + CDC_FUNC_DESC_CAPI_CONTROL_MANAGEMENT = 0x0E , ///< CAPI Control Management Functional Descriptor + CDC_FUNC_DESC_ETHERNET_NETWORKING = 0x0F , ///< Ethernet Networking Functional Descriptor + CDC_FUNC_DESC_ATM_NETWORKING = 0x10 , ///< ATM Networking Functional Descriptor + CDC_FUNC_DESC_WIRELESS_HANDSET_CONTROL_MODEL = 0x11 , ///< Wireless Handset Control Model Functional Descriptor + CDC_FUNC_DESC_MOBILE_DIRECT_LINE_MODEL = 0x12 , ///< Mobile Direct Line Model Functional Descriptor + CDC_FUNC_DESC_MOBILE_DIRECT_LINE_MODEL_DETAIL = 0x13 , ///< MDLM Detail Functional Descriptor + CDC_FUNC_DESC_DEVICE_MANAGEMENT_MODEL = 0x14 , ///< Device Management Model Functional Descriptor + CDC_FUNC_DESC_OBEX = 0x15 , ///< OBEX Functional Descriptor + CDC_FUNC_DESC_COMMAND_SET = 0x16 , ///< Command Set Functional Descriptor + CDC_FUNC_DESC_COMMAND_SET_DETAIL = 0x17 , ///< Command Set Detail Functional Descriptor + CDC_FUNC_DESC_TELEPHONE_CONTROL_MODEL = 0x18 , ///< Telephone Control Model Functional Descriptor + CDC_FUNC_DESC_OBEX_SERVICE_IDENTIFIER = 0x19 , ///< OBEX Service Identifier Functional Descriptor + CDC_FUNC_DESC_NCM = 0x1A , ///< NCM Functional Descriptor +}cdc_func_desc_type_t; + +//--------------------------------------------------------------------+ +// CDC Data Interface Class +//--------------------------------------------------------------------+ + +// SUBCLASS code of Data Interface is not used and should/must be zero + +// Data Interface Protocol Codes +typedef enum{ + CDC_DATA_PROTOCOL_ISDN_BRI = 0x30, ///< Physical interface protocol for ISDN BRI + CDC_DATA_PROTOCOL_HDLC = 0x31, ///< HDLC + CDC_DATA_PROTOCOL_TRANSPARENT = 0x32, ///< Transparent + CDC_DATA_PROTOCOL_Q921_MANAGEMENT = 0x50, ///< Management protocol for Q.921 data link protocol + CDC_DATA_PROTOCOL_Q921_DATA_LINK = 0x51, ///< Data link protocol for Q.931 + CDC_DATA_PROTOCOL_Q921_TEI_MULTIPLEXOR = 0x52, ///< TEI-multiplexor for Q.921 data link protocol + CDC_DATA_PROTOCOL_V42BIS_DATA_COMPRESSION = 0x90, ///< Data compression procedures + CDC_DATA_PROTOCOL_EURO_ISDN = 0x91, ///< Euro-ISDN protocol control + CDC_DATA_PROTOCOL_V24_RATE_ADAPTION_TO_ISDN = 0x92, ///< V.24 rate adaptation to ISDN + CDC_DATA_PROTOCOL_CAPI_COMMAND = 0x93, ///< CAPI Commands + CDC_DATA_PROTOCOL_HOST_BASED_DRIVER = 0xFD, ///< Host based driver. Note: This protocol code should only be used in messages between host and device to identify the host driver portion of a protocol stack. + CDC_DATA_PROTOCOL_IN_PROTOCOL_UNIT_FUNCTIONAL_DESCRIPTOR = 0xFE ///< The protocol(s) are described using a ProtocolUnit Functional Descriptors on Communications Class Interface +}cdc_data_protocol_type_t; + +//--------------------------------------------------------------------+ +// Management Element Request (Control Endpoint) +//--------------------------------------------------------------------+ + +/// Communication Interface Management Element Request Codes +typedef enum { + CDC_REQUEST_SEND_ENCAPSULATED_COMMAND = 0x00, ///< is used to issue a command in the format of the supported control protocol of the Communications Class interface + CDC_REQUEST_GET_ENCAPSULATED_RESPONSE = 0x01, ///< is used to request a response in the format of the supported control protocol of the Communications Class interface. + CDC_REQUEST_SET_COMM_FEATURE = 0x02, + CDC_REQUEST_GET_COMM_FEATURE = 0x03, + CDC_REQUEST_CLEAR_COMM_FEATURE = 0x04, + + CDC_REQUEST_SET_AUX_LINE_STATE = 0x10, + CDC_REQUEST_SET_HOOK_STATE = 0x11, + CDC_REQUEST_PULSE_SETUP = 0x12, + CDC_REQUEST_SEND_PULSE = 0x13, + CDC_REQUEST_SET_PULSE_TIME = 0x14, + CDC_REQUEST_RING_AUX_JACK = 0x15, + + CDC_REQUEST_SET_LINE_CODING = 0x20, + CDC_REQUEST_GET_LINE_CODING = 0x21, + CDC_REQUEST_SET_CONTROL_LINE_STATE = 0x22, + CDC_REQUEST_SEND_BREAK = 0x23, + + CDC_REQUEST_SET_RINGER_PARMS = 0x30, + CDC_REQUEST_GET_RINGER_PARMS = 0x31, + CDC_REQUEST_SET_OPERATION_PARMS = 0x32, + CDC_REQUEST_GET_OPERATION_PARMS = 0x33, + CDC_REQUEST_SET_LINE_PARMS = 0x34, + CDC_REQUEST_GET_LINE_PARMS = 0x35, + CDC_REQUEST_DIAL_DIGITS = 0x36, + CDC_REQUEST_SET_UNIT_PARAMETER = 0x37, + CDC_REQUEST_GET_UNIT_PARAMETER = 0x38, + CDC_REQUEST_CLEAR_UNIT_PARAMETER = 0x39, + CDC_REQUEST_GET_PROFILE = 0x3A, + + CDC_REQUEST_SET_ETHERNET_MULTICAST_FILTERS = 0x40, + CDC_REQUEST_SET_ETHERNET_POWER_MANAGEMENT_PATTERN_FILTER = 0x41, + CDC_REQUEST_GET_ETHERNET_POWER_MANAGEMENT_PATTERN_FILTER = 0x42, + CDC_REQUEST_SET_ETHERNET_PACKET_FILTER = 0x43, + CDC_REQUEST_GET_ETHERNET_STATISTIC = 0x44, + + CDC_REQUEST_SET_ATM_DATA_FORMAT = 0x50, + CDC_REQUEST_GET_ATM_DEVICE_STATISTICS = 0x51, + CDC_REQUEST_SET_ATM_DEFAULT_VC = 0x52, + CDC_REQUEST_GET_ATM_VC_STATISTICS = 0x53, + + CDC_REQUEST_MDLM_SEMANTIC_MODEL = 0x60, +} cdc_management_request_t; + +typedef enum { + CDC_CONTROL_LINE_STATE_DTR = 0x01, + CDC_CONTROL_LINE_STATE_RTS = 0x02, +} cdc_control_line_state_t; + +typedef enum { + CDC_LINE_CODING_STOP_BITS_1 = 0, // 1 bit + CDC_LINE_CODING_STOP_BITS_1_5 = 1, // 1.5 bits + CDC_LINE_CODING_STOP_BITS_2 = 2, // 2 bits +} cdc_line_coding_stopbits_t; + +#define CDC_LINE_CODING_STOP_BITS_TEXT(STOP_BITS) ( \ + (STOP_BITS) == CDC_LINE_CODING_STOP_BITS_1 ? "1" : \ + (STOP_BITS) == CDC_LINE_CODING_STOP_BITS_1_5 ? "1.5" : \ + (STOP_BITS) == CDC_LINE_CODING_STOP_BITS_2 ? "2" : "?" ) + +// TODO Backward compatible for typos. Maybe removed in the future release +#define CDC_LINE_CONDING_STOP_BITS_1 CDC_LINE_CODING_STOP_BITS_1 +#define CDC_LINE_CONDING_STOP_BITS_1_5 CDC_LINE_CODING_STOP_BITS_1_5 +#define CDC_LINE_CONDING_STOP_BITS_2 CDC_LINE_CODING_STOP_BITS_2 + +typedef enum { + CDC_LINE_CODING_PARITY_NONE = 0, + CDC_LINE_CODING_PARITY_ODD = 1, + CDC_LINE_CODING_PARITY_EVEN = 2, + CDC_LINE_CODING_PARITY_MARK = 3, + CDC_LINE_CODING_PARITY_SPACE = 4, +} cdc_line_coding_parity_t; + +#define CDC_LINE_CODING_PARITY_CHAR(PARITY) ( \ + (PARITY) == CDC_LINE_CODING_PARITY_NONE ? 'N' : \ + (PARITY) == CDC_LINE_CODING_PARITY_ODD ? 'O' : \ + (PARITY) == CDC_LINE_CODING_PARITY_EVEN ? 'E' : \ + (PARITY) == CDC_LINE_CODING_PARITY_MARK ? 'M' : \ + (PARITY) == CDC_LINE_CODING_PARITY_SPACE ? 'S' : '?' ) + +//--------------------------------------------------------------------+ +// Management Element Notification (Notification Endpoint) +//--------------------------------------------------------------------+ + +#define CDC_REQ_TYPE_NOTIF 0xA1 ///< Direction IN; Type Class; Recipient Interface + +/// 6.3 Notification Codes +typedef enum { + CDC_NOTIF_NETWORK_CONNECTION = 0x00, // notify the host about network connection status. + CDC_NOTIF_RESPONSE_AVAILABLE = 0x01, // notify the host that a response is available. + CDC_NOTIF_AUX_JACK_HOOK_STATE = 0x08, + CDC_NOTIF_RING_DETECT = 0x09, + CDC_NOTIF_SERIAL_STATE = 0x20, + CDC_NOTIF_CALL_STATE_CHANGE = 0x28, + CDC_NOTIF_LINE_STATE_CHANGE = 0x29, + CDC_NOTIF_CONNECTION_SPEED_CHANGE = 0x2A, // notify the host-networking driver that a change in either the upstream or the downstream bit rate of the connection has occurred + CDC_NOTIF_MDLM_SEMANTIC_MODEL_NOTIFICATION = 0x40, +} cdc_notify_request_t; + +//--------------------------------------------------------------------+ +// Class Specific Functional Descriptor (Communication Interface) +//--------------------------------------------------------------------+ + +// Start of all packed definitions for compiler without per-type packed +TU_ATTR_PACKED_BEGIN +TU_ATTR_BIT_FIELD_ORDER_BEGIN + +/// Header Functional Descriptor (Communication Interface) +typedef struct TU_ATTR_PACKED { + uint8_t bLength ; ///< Size of this descriptor in bytes. + uint8_t bDescriptorType ; ///< Descriptor Type, must be Class-Specific + uint8_t bDescriptorSubType ; ///< Descriptor SubType one of above CDC_FUNC_DESC_ + uint16_t bcdCDC ; ///< CDC release number in Binary-Coded Decimal +}cdc_desc_func_header_t; + +/// Union Functional Descriptor (Communication Interface) +typedef struct TU_ATTR_PACKED { + uint8_t bLength ; ///< Size of this descriptor in bytes. + uint8_t bDescriptorType ; ///< Descriptor Type, must be Class-Specific + uint8_t bDescriptorSubType ; ///< Descriptor SubType one of above CDC_FUCN_DESC_ + uint8_t bControlInterface ; ///< Interface number of Communication Interface + uint8_t bSubordinateInterface ; ///< Array of Interface number of Data Interface +}cdc_desc_func_union_t; + +#define cdc_desc_func_union_n_t(no_slave)\ + struct TU_ATTR_PACKED { \ + uint8_t bLength ;\ + uint8_t bDescriptorType ;\ + uint8_t bDescriptorSubType ;\ + uint8_t bControlInterface ;\ + uint8_t bSubordinateInterface[no_slave] ;\ +} + +/// Country Selection Functional Descriptor (Communication Interface) +typedef struct TU_ATTR_PACKED { + uint8_t bLength ; ///< Size of this descriptor in bytes. + uint8_t bDescriptorType ; ///< Descriptor Type, must be Class-Specific + uint8_t bDescriptorSubType ; ///< Descriptor SubType one of above CDC_FUCN_DESC_ + uint8_t iCountryCodeRelDate ; ///< Index of a string giving the release date for the implemented ISO 3166 Country Codes. + uint16_t wCountryCode ; ///< Country code in the format as defined in [ISO3166], release date as specified inoffset 3 for the first supported country. +} cdc_desc_func_country_selection_t; + +#define cdc_desc_func_country_selection_n_t(no_country) \ + struct TU_ATTR_PACKED { \ + uint8_t bLength ;\ + uint8_t bDescriptorType ;\ + uint8_t bDescriptorSubType ;\ + uint8_t iCountryCodeRelDate ;\ + uint16_t wCountryCode[no_country] ;\ +} + +//--------------------------------------------------------------------+ +// PUBLIC SWITCHED TELEPHONE NETWORK (PSTN) SUBCLASS +//--------------------------------------------------------------------+ + +/// \brief Call Management Functional Descriptor +/// \details This functional descriptor describes the processing of calls for the Communications Class interface. +typedef struct TU_ATTR_PACKED { + uint8_t bLength ; ///< Size of this descriptor in bytes. + uint8_t bDescriptorType ; ///< Descriptor Type, must be Class-Specific + uint8_t bDescriptorSubType ; ///< Descriptor SubType one of above CDC_FUCN_DESC_ + + struct { + uint8_t handle_call : 1; ///< 0 - Device sends/receives call management information only over the Communications Class interface. 1 - Device can send/receive call management information over a Data Class interface. + uint8_t send_recv_call : 1; ///< 0 - Device does not handle call management itself. 1 - Device handles call management itself. + uint8_t TU_RESERVED : 6; + } bmCapabilities; + + uint8_t bDataInterface; +}cdc_desc_func_call_management_t; + +typedef struct TU_ATTR_PACKED { + uint8_t support_comm_request : 1; ///< Device supports the request combination of Set_Comm_Feature, Clear_Comm_Feature, and Get_Comm_Feature. + uint8_t support_line_request : 1; ///< Device supports the request combination of Set_Line_Coding, Set_Control_Line_State, Get_Line_Coding, and the notification Serial_State. + uint8_t support_send_break : 1; ///< Device supports the request Send_Break + uint8_t support_notification_network_connection : 1; ///< Device supports the notification Network_Connection. + uint8_t TU_RESERVED : 4; +}cdc_acm_capability_t; + +TU_VERIFY_STATIC(sizeof(cdc_acm_capability_t) == 1, "mostly problem with compiler"); + +/// Abstract Control Management Functional Descriptor +/// This functional descriptor describes the commands supported by by the Communications Class interface with SubClass code of \ref CDC_COMM_SUBCLASS_ABSTRACT_CONTROL_MODEL +typedef struct TU_ATTR_PACKED { + uint8_t bLength ; ///< Size of this descriptor in bytes. + uint8_t bDescriptorType ; ///< Descriptor Type, must be Class-Specific + uint8_t bDescriptorSubType ; ///< Descriptor SubType one of above CDC_FUCN_DESC_ + cdc_acm_capability_t bmCapabilities ; +}cdc_desc_func_acm_t; + +/// \brief Direct Line Management Functional Descriptor +/// \details This functional descriptor describes the commands supported by the Communications Class interface with SubClass code of \ref CDC_FUNC_DESC_DIRECT_LINE_MANAGEMENT +typedef struct TU_ATTR_PACKED { + uint8_t bLength ; ///< Size of this descriptor in bytes. + uint8_t bDescriptorType ; ///< Descriptor Type, must be Class-Specific + uint8_t bDescriptorSubType ; ///< Descriptor SubType one of above CDC_FUCN_DESC_ + struct { + uint8_t require_pulse_setup : 1; ///< Device requires extra Pulse_Setup request during pulse dialing sequence to disengage holding circuit. + uint8_t support_aux_request : 1; ///< Device supports the request combination of Set_Aux_Line_State, Ring_Aux_Jack, and notification Aux_Jack_Hook_State. + uint8_t support_pulse_request : 1; ///< Device supports the request combination of Pulse_Setup, Send_Pulse, and Set_Pulse_Time. + uint8_t TU_RESERVED : 5; + } bmCapabilities; +}cdc_desc_func_direct_line_management_t; + +/// \brief Telephone Ringer Functional Descriptor +/// \details The Telephone Ringer functional descriptor describes the ringer capabilities supported by the Communications Class interface, +/// with the SubClass code of \ref CDC_COMM_SUBCLASS_TELEPHONE_CONTROL_MODEL +typedef struct TU_ATTR_PACKED +{ + uint8_t bLength ; ///< Size of this descriptor in bytes. + uint8_t bDescriptorType ; ///< Descriptor Type, must be Class-Specific + uint8_t bDescriptorSubType ; ///< Descriptor SubType one of above CDC_FUCN_DESC_ + uint8_t bRingerVolSteps ; + uint8_t bNumRingerPatterns ; +}cdc_desc_func_telephone_ringer_t; + +/// \brief Telephone Operational Modes Functional Descriptor +/// \details The Telephone Operational Modes functional descriptor describes the operational modes supported by +/// the Communications Class interface, with the SubClass code of \ref CDC_COMM_SUBCLASS_TELEPHONE_CONTROL_MODEL +typedef struct TU_ATTR_PACKED +{ + uint8_t bLength ; ///< Size of this descriptor in bytes. + uint8_t bDescriptorType ; ///< Descriptor Type, must be Class-Specific + uint8_t bDescriptorSubType ; ///< Descriptor SubType one of above CDC_FUCN_DESC_ + struct { + uint8_t simple_mode : 1; + uint8_t standalone_mode : 1; + uint8_t computer_centric_mode : 1; + uint8_t TU_RESERVED : 5; + } bmCapabilities; +}cdc_desc_func_telephone_operational_modes_t; + +/// \brief Telephone Call and Line State Reporting Capabilities Descriptor +/// \details The Telephone Call and Line State Reporting Capabilities functional descriptor describes the abilities of a +/// telephone device to report optional call and line states. +typedef struct TU_ATTR_PACKED +{ + uint8_t bLength ; ///< Size of this descriptor in bytes. + uint8_t bDescriptorType ; ///< Descriptor Type, must be Class-Specific + uint8_t bDescriptorSubType ; ///< Descriptor SubType one of above CDC_FUCN_DESC_ + struct { + uint32_t interrupted_dialtone : 1; ///< 0 : Reports only dialtone (does not differentiate between normal and interrupted dialtone). 1 : Reports interrupted dialtone in addition to normal dialtone + uint32_t ringback_busy_fastbusy : 1; ///< 0 : Reports only dialing state. 1 : Reports ringback, busy, and fast busy states. + uint32_t caller_id : 1; ///< 0 : Does not report caller ID. 1 : Reports caller ID information. + uint32_t incoming_distinctive : 1; ///< 0 : Reports only incoming ringing. 1 : Reports incoming distinctive ringing patterns. + uint32_t dual_tone_multi_freq : 1; ///< 0 : Cannot report dual tone multi-frequency (DTMF) digits input remotely over the telephone line. 1 : Can report DTMF digits input remotely over the telephone line. + uint32_t line_state_change : 1; ///< 0 : Does not support line state change notification. 1 : Does support line state change notification + uint32_t TU_RESERVED0 : 2; + uint32_t TU_RESERVED1 : 16; + uint32_t TU_RESERVED2 : 8; + } bmCapabilities; +}cdc_desc_func_telephone_call_state_reporting_capabilities_t; + +// TODO remove +TU_ATTR_ALWAYS_INLINE static inline uint8_t cdc_functional_desc_typeof(uint8_t const * p_desc) { + return p_desc[2]; +} + +//--------------------------------------------------------------------+ +// Requests +//--------------------------------------------------------------------+ +typedef struct TU_ATTR_PACKED { + uint32_t bit_rate; + uint8_t stop_bits; ///< 0: 1 stop bit - 1: 1.5 stop bits - 2: 2 stop bits + uint8_t parity; ///< 0: None - 1: Odd - 2: Even - 3: Mark - 4: Space + uint8_t data_bits; ///< can be 5, 6, 7, 8 or 16 +} cdc_line_coding_t; + +TU_VERIFY_STATIC(sizeof(cdc_line_coding_t) == 7, "size is not correct"); + +typedef union TU_ATTR_PACKED { + struct TU_ATTR_PACKED { + uint8_t dtr : 1; + uint8_t rts : 1; + uint8_t : 6; + }; + uint8_t value; +} cdc_line_control_state_t; + +TU_VERIFY_STATIC(sizeof(cdc_line_control_state_t) == 1, "size is not correct"); + +//--------------------------------------------------------------------+ +// Notifications +//--------------------------------------------------------------------+ +// PSTN 1.2 section 6.5.4 table 31 +typedef union TU_ATTR_PACKED { + struct TU_ATTR_PACKED { + uint16_t bRxCarrier : 1; // DCD + uint16_t bTxCarrier : 1; // DSR + uint16_t bBreak : 1; // Break Detected + uint16_t bRingSignal : 1; + uint16_t bFraming : 1; + uint16_t bParity : 1; + uint16_t bOverRun : 1; + uint16_t : 9; + }; + struct TU_ATTR_PACKED { + uint16_t dcd : 1; + uint16_t dsr : 1; + uint16_t brk : 1; + uint16_t :13; + }; + uint16_t value; +} cdc_notify_uart_state_t; + +TU_VERIFY_STATIC(sizeof(cdc_notify_uart_state_t) == 2, "size is not correct"); + +// CDC 1.2 section 6.3.3 table 21 +typedef struct TU_ATTR_PACKED { + uint32_t upstream_bitrate; + uint32_t downstream_bitrate; +} cdc_notify_conn_speed_change_t; + +typedef struct TU_ATTR_PACKED { + tusb_control_request_t request; + union { + cdc_notify_uart_state_t serial_state; + cdc_notify_conn_speed_change_t conn_speed_change; + }; +} cdc_notify_msg_t; + +TU_VERIFY_STATIC(sizeof(cdc_notify_msg_t) == 16, "size is not correct"); + +TU_ATTR_PACKED_END // End of all packed definitions +TU_ATTR_BIT_FIELD_ORDER_END + +#ifdef __cplusplus + } +#endif + +#endif + +/** @} */ diff --git a/libraries/Adafruit_TinyUSB_Arduino/src/class/cdc/cdc_device.c b/libraries/Adafruit_TinyUSB_Arduino/src/class/cdc/cdc_device.c new file mode 100644 index 0000000..c499756 --- /dev/null +++ b/libraries/Adafruit_TinyUSB_Arduino/src/class/cdc/cdc_device.c @@ -0,0 +1,534 @@ +/* + * 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. + * + * This file is part of the TinyUSB stack. + */ + +#include "tusb_option.h" + +#if (CFG_TUD_ENABLED && CFG_TUD_CDC) + +#include "device/usbd.h" +#include "device/usbd_pvt.h" + +#include "cdc_device.h" + +// Level where CFG_TUSB_DEBUG must be at least for this driver is logged +#ifndef CFG_TUD_CDC_LOG_LEVEL + #define CFG_TUD_CDC_LOG_LEVEL CFG_TUD_LOG_LEVEL +#endif + +#define TU_LOG_DRV(...) TU_LOG(CFG_TUD_CDC_LOG_LEVEL, __VA_ARGS__) + +//--------------------------------------------------------------------+ +// MACRO CONSTANT TYPEDEF +//--------------------------------------------------------------------+ +typedef struct { + uint8_t rhport; + uint8_t itf_num; + uint8_t ep_notify; + uint8_t line_state; // Bit 0: DTR, Bit 1: RTS + + /*------------- From this point, data is not cleared by bus reset -------------*/ + TU_ATTR_ALIGNED(4) cdc_line_coding_t line_coding; + char wanted_char; + + tu_edpt_stream_t tx_stream; + tu_edpt_stream_t rx_stream; + + uint8_t tx_ff_buf[CFG_TUD_CDC_TX_BUFSIZE]; + uint8_t rx_ff_buf[CFG_TUD_CDC_RX_BUFSIZE]; +} cdcd_interface_t; + +#define ITF_MEM_RESET_SIZE offsetof(cdcd_interface_t, line_coding) + +// Skip local EP buffer if dedicated hw FIFO is supported +#if CFG_TUD_EDPT_DEDICATED_HWFIFO == 0 +typedef struct { + TUD_EPBUF_DEF(epout, CFG_TUD_CDC_RX_EPSIZE); + TUD_EPBUF_DEF(epin, CFG_TUD_CDC_TX_EPSIZE); + + #if CFG_TUD_CDC_NOTIFY + TUD_EPBUF_TYPE_DEF(cdc_notify_msg_t, epnotify); + #endif +} cdcd_epbuf_t; + +CFG_TUD_MEM_SECTION static cdcd_epbuf_t _cdcd_epbuf[CFG_TUD_CDC]; +#endif + +//--------------------------------------------------------------------+ +// Weak stubs: invoked if no strong implementation is available +//--------------------------------------------------------------------+ +TU_ATTR_WEAK void tud_cdc_rx_cb(uint8_t itf) { + (void)itf; +} + +TU_ATTR_WEAK void tud_cdc_rx_wanted_cb(uint8_t itf, char wanted_char) { + (void)itf; + (void)wanted_char; +} + +TU_ATTR_WEAK void tud_cdc_tx_complete_cb(uint8_t itf) { + (void)itf; +} + +TU_ATTR_WEAK void tud_cdc_notify_complete_cb(uint8_t itf) { + (void)itf; +} + +TU_ATTR_WEAK void tud_cdc_line_state_cb(uint8_t itf, bool dtr, bool rts) { + (void)itf; + (void)dtr; + (void)rts; +} + +TU_ATTR_WEAK void tud_cdc_line_coding_cb(uint8_t itf, const cdc_line_coding_t *p_line_coding) { + (void)itf; + (void)p_line_coding; +} + +TU_ATTR_WEAK void tud_cdc_send_break_cb(uint8_t itf, uint16_t duration_ms) { + (void)itf; + (void)duration_ms; +} + +//--------------------------------------------------------------------+ +// INTERNAL OBJECT & FUNCTION DECLARATION +//--------------------------------------------------------------------+ +static cdcd_interface_t _cdcd_itf[CFG_TUD_CDC]; + +TU_ATTR_ALWAYS_INLINE static inline uint8_t find_cdc_itf(uint8_t ep_addr) { + for (uint8_t idx = 0; idx < CFG_TUD_CDC; idx++) { + const cdcd_interface_t *p_cdc = &_cdcd_itf[idx]; + if (ep_addr == p_cdc->rx_stream.ep_addr || ep_addr == p_cdc->tx_stream.ep_addr || + (ep_addr == p_cdc->ep_notify && ep_addr != 0)) { + return idx; + } + } + return TUSB_INDEX_INVALID_8; +} + +//--------------------------------------------------------------------+ +// APPLICATION API +//--------------------------------------------------------------------+ +bool tud_cdc_n_ready(uint8_t itf) { + TU_VERIFY(itf < CFG_TUD_CDC); + TU_VERIFY(tud_ready()); + const cdcd_interface_t *p_cdc = &_cdcd_itf[itf]; + + const bool in_opened = tu_edpt_stream_is_opened(&p_cdc->tx_stream); + const bool out_opened = tu_edpt_stream_is_opened(&p_cdc->rx_stream); + return in_opened && out_opened; +} + +bool tud_cdc_n_connected(uint8_t itf) { + TU_VERIFY(itf < CFG_TUD_CDC); + TU_VERIFY(tud_ready()); + // DTR (bit 0) active is considered as connected + return tu_bit_test(_cdcd_itf[itf].line_state, 0); +} + +uint8_t tud_cdc_n_get_line_state(uint8_t itf) { + TU_VERIFY(itf < CFG_TUD_CDC, 0); + return _cdcd_itf[itf].line_state; +} + +void tud_cdc_n_get_line_coding(uint8_t itf, cdc_line_coding_t *coding) { + TU_VERIFY(itf < CFG_TUD_CDC, ); + (*coding) = _cdcd_itf[itf].line_coding; +} + +#if CFG_TUD_CDC_NOTIFY +bool tud_cdc_n_notify_msg(uint8_t itf, cdc_notify_msg_t *msg) { + TU_VERIFY(itf < CFG_TUD_CDC); + const cdcd_interface_t *p_cdc = &_cdcd_itf[itf]; + TU_VERIFY(tud_ready() && p_cdc->ep_notify != 0); + TU_VERIFY(usbd_edpt_claim(p_cdc->rhport, p_cdc->ep_notify)); + + #if CFG_TUD_EDPT_DEDICATED_HWFIFO + cdc_notify_msg_t *msg_epbuf = msg; + #else + cdc_notify_msg_t *msg_epbuf = &_cdcd_epbuf[itf].epnotify; + *msg_epbuf = *msg; + #endif + + msg_epbuf->request.wIndex = p_cdc->itf_num; + + return usbd_edpt_xfer(p_cdc->rhport, p_cdc->ep_notify, (uint8_t *)msg_epbuf, 8 + msg_epbuf->request.wLength, false); +} +#endif + +void tud_cdc_n_set_wanted_char(uint8_t itf, char wanted) { + TU_VERIFY(itf < CFG_TUD_CDC, ); + _cdcd_itf[itf].wanted_char = wanted; +} + +//--------------------------------------------------------------------+ +// READ API +//--------------------------------------------------------------------+ +uint32_t tud_cdc_n_available(uint8_t itf) { + TU_VERIFY(itf < CFG_TUD_CDC, 0); + return tu_edpt_stream_read_available(&_cdcd_itf[itf].rx_stream); +} + +uint32_t tud_cdc_n_read(uint8_t itf, void* buffer, uint32_t bufsize) { + TU_VERIFY(itf < CFG_TUD_CDC, 0); + cdcd_interface_t *p_cdc = &_cdcd_itf[itf]; + return tu_edpt_stream_read(&p_cdc->rx_stream, buffer, bufsize); +} + +bool tud_cdc_n_peek(uint8_t itf, uint8_t *chr) { + TU_VERIFY(itf < CFG_TUD_CDC); + return tu_edpt_stream_peek(&_cdcd_itf[itf].rx_stream, chr); +} + +void tud_cdc_n_read_flush(uint8_t itf) { + TU_VERIFY(itf < CFG_TUD_CDC, ); + cdcd_interface_t *p_cdc = &_cdcd_itf[itf]; + tu_edpt_stream_clear(&p_cdc->rx_stream); + tu_edpt_stream_read_xfer(&p_cdc->rx_stream); +} + +//--------------------------------------------------------------------+ +// WRITE API +//--------------------------------------------------------------------+ +uint32_t tud_cdc_n_write(uint8_t itf, const void* buffer, uint32_t bufsize) { + TU_VERIFY(itf < CFG_TUD_CDC, 0); + cdcd_interface_t *p_cdc = &_cdcd_itf[itf]; + return tu_edpt_stream_write(&p_cdc->tx_stream, buffer, bufsize); +} + +uint32_t tud_cdc_n_write_flush(uint8_t itf) { + TU_VERIFY(itf < CFG_TUD_CDC, 0); + cdcd_interface_t *p_cdc = &_cdcd_itf[itf]; + return tu_edpt_stream_write_xfer(&p_cdc->tx_stream); +} + +uint32_t tud_cdc_n_write_available(uint8_t itf) { + TU_VERIFY(itf < CFG_TUD_CDC, 0); + cdcd_interface_t *p_cdc = &_cdcd_itf[itf]; + return tu_edpt_stream_write_available(&p_cdc->tx_stream); +} + +bool tud_cdc_n_write_clear(uint8_t itf) { + TU_VERIFY(itf < CFG_TUD_CDC); + cdcd_interface_t *p_cdc = &_cdcd_itf[itf]; + tu_edpt_stream_clear(&p_cdc->tx_stream); + return true; +} + +//--------------------------------------------------------------------+ +// USBD Driver API +//--------------------------------------------------------------------+ +void cdcd_init(void) { + tu_memclr(_cdcd_itf, sizeof(_cdcd_itf)); + for (uint8_t i = 0; i < CFG_TUD_CDC; i++) { + cdcd_interface_t *p_cdc = &_cdcd_itf[i]; + p_cdc->wanted_char = (char) -1; + + // default line coding is : stop bit = 1, parity = none, data bits = 8 + p_cdc->line_coding.bit_rate = 115200; + p_cdc->line_coding.stop_bits = 0; + p_cdc->line_coding.parity = 0; + p_cdc->line_coding.data_bits = 8; + + #if CFG_TUD_EDPT_DEDICATED_HWFIFO + uint8_t *epout_buf = NULL; + uint8_t *epin_buf = NULL; + #else + uint8_t *epout_buf = _cdcd_epbuf[i].epout; + uint8_t *epin_buf = _cdcd_epbuf[i].epin; + #endif + + tu_edpt_stream_init(&p_cdc->rx_stream, false, false, false, p_cdc->rx_ff_buf, CFG_TUD_CDC_RX_BUFSIZE, epout_buf); + + // TX fifo can be configured to change to overwritable if not connected (DTR bit not set). Without DTR we do not + // know if data is actually polled by terminal. This way the most current data is prioritized. + // Default: is overwritable + tu_edpt_stream_init(&p_cdc->tx_stream, false, true, CFG_TUD_CDC_TX_OVERWRITABLE_IF_NOT_CONNECTED, p_cdc->tx_ff_buf, + CFG_TUD_CDC_TX_BUFSIZE, epin_buf); + } +} + +bool cdcd_deinit(void) { + for (uint8_t i = 0; i < CFG_TUD_CDC; i++) { + cdcd_interface_t* p_cdc = &_cdcd_itf[i]; + tu_edpt_stream_deinit(&p_cdc->rx_stream); + tu_edpt_stream_deinit(&p_cdc->tx_stream); + } + return true; +} + +void cdcd_reset(uint8_t rhport) { + (void) rhport; + + for (uint8_t i = 0; i < CFG_TUD_CDC; i++) { + cdcd_interface_t* p_cdc = &_cdcd_itf[i]; + tu_memclr(p_cdc, ITF_MEM_RESET_SIZE); + + tu_fifo_set_overwritable(&p_cdc->tx_stream.ff, CFG_TUD_CDC_TX_OVERWRITABLE_IF_NOT_CONNECTED); // back to default + tu_edpt_stream_close(&p_cdc->rx_stream); + tu_edpt_stream_close(&p_cdc->tx_stream); + } +} + +uint16_t cdcd_open(uint8_t rhport, const tusb_desc_interface_t* itf_desc, uint16_t max_len) { + // Only support ACM subclass + TU_VERIFY(TUSB_CLASS_CDC == itf_desc->bInterfaceClass && + CDC_COMM_SUBCLASS_ABSTRACT_CONTROL_MODEL == itf_desc->bInterfaceSubClass, + 0); + + const uint8_t cdc_id = find_cdc_itf(0); // Find available interface + TU_ASSERT(cdc_id < CFG_TUD_CDC, 0); + cdcd_interface_t *p_cdc = &_cdcd_itf[cdc_id]; + + //------------- Control Interface -------------// + p_cdc->rhport = rhport; + p_cdc->itf_num = itf_desc->bInterfaceNumber; + + const uint8_t *p_desc = (const uint8_t *)itf_desc; + const uint8_t *desc_end = p_desc + max_len; + + // Skip all class-specific descriptor + p_desc = tu_desc_next(itf_desc); + while (tu_desc_in_bounds(p_desc, desc_end) && TUSB_DESC_CS_INTERFACE == tu_desc_type(p_desc)) { + p_desc = tu_desc_next(p_desc); + } + + // notification endpoint (optional) + if (TUSB_DESC_ENDPOINT == tu_desc_type(p_desc)) { + const tusb_desc_endpoint_t* desc_ep = (const tusb_desc_endpoint_t*) p_desc; + TU_ASSERT(usbd_edpt_open(rhport, desc_ep), 0); + p_cdc->ep_notify = desc_ep->bEndpointAddress; + + p_desc = tu_desc_next(p_desc); + } + + //------------- Data Interface (optional) -------------// + if (TUSB_DESC_INTERFACE == tu_desc_type(p_desc)) { + const tusb_desc_interface_t *data_itf_desc = (const tusb_desc_interface_t *)p_desc; + if (TUSB_CLASS_CDC_DATA == data_itf_desc->bInterfaceClass) { + for (uint8_t e = 0; e < data_itf_desc->bNumEndpoints; e++) { + if (!tu_desc_in_bounds(p_desc, desc_end)) { + break; + } + p_desc = tu_desc_next(p_desc); + + const tusb_desc_endpoint_t *desc_ep = (const tusb_desc_endpoint_t *)p_desc; + TU_ASSERT(TUSB_DESC_ENDPOINT == desc_ep->bDescriptorType && TUSB_XFER_BULK == desc_ep->bmAttributes.xfer, 0); + + TU_ASSERT(usbd_edpt_open(rhport, desc_ep), 0); + if (tu_edpt_dir(desc_ep->bEndpointAddress) == TUSB_DIR_IN) { + tu_edpt_stream_t *stream_tx = &p_cdc->tx_stream; + tu_edpt_stream_open(stream_tx, rhport, desc_ep, CFG_TUD_CDC_TX_EPSIZE); + + #if CFG_TUD_CDC_TX_PERSISTENT + tu_edpt_stream_write_xfer(stream_tx); // flush pending data + #else + tu_edpt_stream_clear(stream_tx); + #endif + } else { + tu_edpt_stream_t *stream_rx = &p_cdc->rx_stream; + #if CFG_TUD_CDC_RX_NEED_ZLP + const uint16_t xfer_len = CFG_TUD_CDC_RX_EPSIZE; + #else + const uint16_t xfer_len = tu_edpt_packet_size(desc_ep); + #endif + + tu_edpt_stream_open(stream_rx, rhport, desc_ep, xfer_len); + + #if !CFG_TUD_CDC_RX_PERSISTENT + tu_edpt_stream_clear(stream_rx); + #endif + + TU_ASSERT(tu_edpt_stream_read_xfer(stream_rx) > 0, 0); // prepare for incoming data + } + } + + p_desc = tu_desc_next(p_desc); + } + } + + return (uint16_t)(p_desc - (const uint8_t *)itf_desc); +} + +// Invoked when a control transfer occurred on an interface of this class +// Driver response accordingly to the request and the transfer stage (setup/data/ack) +// return false to stall control endpoint (e.g unsupported request) +bool cdcd_control_xfer_cb(uint8_t rhport, uint8_t stage, const tusb_control_request_t* request) { + // Handle class request only + TU_VERIFY(request->bmRequestType_bit.type == TUSB_REQ_TYPE_CLASS); + + uint8_t itf; + cdcd_interface_t* p_cdc; + + // Identify which interface to use + for (itf = 0; itf < CFG_TUD_CDC; itf++) { + p_cdc = &_cdcd_itf[itf]; + if (p_cdc->itf_num == request->wIndex) { + break; + } + } + TU_VERIFY(itf < CFG_TUD_CDC); + + switch (request->bRequest) { + case CDC_REQUEST_SET_LINE_CODING: + if (stage == CONTROL_STAGE_SETUP) { + TU_LOG_DRV(" Set Line Coding\r\n"); + tud_control_xfer(rhport, request, &p_cdc->line_coding, sizeof(cdc_line_coding_t)); + } else if (stage == CONTROL_STAGE_ACK) { + tud_cdc_line_coding_cb(itf, &p_cdc->line_coding); + } else { + // nothing to do + } + break; + + case CDC_REQUEST_GET_LINE_CODING: + if (stage == CONTROL_STAGE_SETUP) { + TU_LOG_DRV(" Get Line Coding\r\n"); + tud_control_xfer(rhport, request, &p_cdc->line_coding, sizeof(cdc_line_coding_t)); + } + break; + + case CDC_REQUEST_SET_CONTROL_LINE_STATE: + if (stage == CONTROL_STAGE_SETUP) { + tud_control_status(rhport, request); + } else if (stage == CONTROL_STAGE_ACK) { + // CDC PSTN v1.2 section 6.3.12 + // Bit 0: Indicates if DTE is present or not. + // This signal corresponds to V.24 signal 108/2 and RS-232 signal DTR (Data Terminal Ready) + // Bit 1: Carrier control for half-duplex modems. + // This signal corresponds to V.24 signal 105 and RS-232 signal RTS (Request to Send) + bool const dtr = tu_bit_test(request->wValue, 0); + bool const rts = tu_bit_test(request->wValue, 1); + + p_cdc->line_state = (uint8_t) request->wValue; + + // If enabled: fifo overwriting is disabled if DTR bit is set and vice versa + #if CFG_TUD_CDC_TX_OVERWRITABLE_IF_NOT_CONNECTED + const bool is_overwritable = !dtr; + #else + const bool is_overwritable = false; + #endif + + tu_fifo_set_overwritable(&p_cdc->tx_stream.ff, is_overwritable); + TU_LOG_DRV(" Set Control Line State: DTR = %d, RTS = %d\r\n", dtr, rts); + tud_cdc_line_state_cb(itf, dtr, rts); // invoke callback + } else { + // nothing to do + } + break; + + case CDC_REQUEST_SEND_BREAK: + if (stage == CONTROL_STAGE_SETUP) { + tud_control_status(rhport, request); + } else if (stage == CONTROL_STAGE_ACK) { + TU_LOG_DRV(" Send Break\r\n"); + tud_cdc_send_break_cb(itf, request->wValue); + } else { + // nothing to do + } + break; + + default: + return false; // stall unsupported request + } + + return true; +} + +bool cdcd_xfer_cb(uint8_t rhport, uint8_t ep_addr, xfer_result_t result, uint32_t xferred_bytes) { + (void)rhport; + (void)result; + + uint8_t itf = find_cdc_itf(ep_addr); + TU_ASSERT(itf < CFG_TUD_CDC); + cdcd_interface_t *p_cdc = &_cdcd_itf[itf]; + tu_edpt_stream_t *stream_rx = &p_cdc->rx_stream; + tu_edpt_stream_t *stream_tx = &p_cdc->tx_stream; + + // Received new data, move to fifo + if (ep_addr == stream_rx->ep_addr) { + tu_edpt_stream_read_xfer_complete(stream_rx, xferred_bytes); + + // Check for wanted char and invoke wanted callback + if (((signed char)p_cdc->wanted_char) != -1) { + tu_fifo_buffer_info_t buf_info; + tu_fifo_get_read_info(&stream_rx->ff, &buf_info); + + // find backward + uint8_t *ptr; + if (buf_info.wrapped.len > 0) { + ptr = buf_info.wrapped.ptr + buf_info.wrapped.len - 1; // last byte of wrap buffer + } else if (buf_info.linear.len > 0) { + ptr = buf_info.linear.ptr + buf_info.linear.len - 1; // last byte of linear buffer + } else { + ptr = NULL; // no data + } + + if (ptr != NULL) { + for (uint32_t i = 0; i < xferred_bytes; i++) { + if (p_cdc->wanted_char == (char)*ptr) { + tud_cdc_rx_wanted_cb(itf, p_cdc->wanted_char); + break; // only invoke once per transfer, even if multiple wanted chars are present + } + + if (ptr == buf_info.wrapped.ptr) { + ptr = buf_info.linear.ptr + buf_info.linear.len - 1; // last byte of linear buffer + } else if (ptr == buf_info.linear.ptr) { + break; // reached the beginning + } else { + ptr--; + } + } + } + } + + // invoke receive callback if there is still data + if (!tu_edpt_stream_empty(stream_rx)) { + tud_cdc_rx_cb(itf); + } + + tu_edpt_stream_read_xfer(stream_rx); // prepare for more data + } + + // Data sent to host, we continue to fetch from tx fifo to send. + // Note: This will cause incorrect baudrate set in line coding. Though maybe the baudrate is not really important! + if (ep_addr == stream_tx->ep_addr) { + tud_cdc_tx_complete_cb(itf); // invoke callback to possibly refill tx fifo + + if (0 == tu_edpt_stream_write_xfer(stream_tx)) { + // If there is no data left, a ZLP should be sent if needed + tu_edpt_stream_write_zlp_if_needed(stream_tx, xferred_bytes); + } + } + + // Sent notification to host + if (ep_addr == p_cdc->ep_notify) { + tud_cdc_notify_complete_cb(itf); + } + + return true; +} + +#endif diff --git a/libraries/Adafruit_TinyUSB_Arduino/src/class/cdc/cdc_device.h b/libraries/Adafruit_TinyUSB_Arduino/src/class/cdc/cdc_device.h new file mode 100644 index 0000000..9ac6bc5 --- /dev/null +++ b/libraries/Adafruit_TinyUSB_Arduino/src/class/cdc/cdc_device.h @@ -0,0 +1,311 @@ +/* + * 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. + * + * This file is part of the TinyUSB stack. + */ + +#ifndef TUSB_CDC_DEVICE_H_ +#define TUSB_CDC_DEVICE_H_ + +#include "cdc.h" + +#ifdef __cplusplus + extern "C" { +#endif + +//--------------------------------------------------------------------+ +// Class Driver Configuration +//--------------------------------------------------------------------+ +#ifndef CFG_TUD_CDC_NOTIFY + #define CFG_TUD_CDC_NOTIFY 0 +#endif + +#ifndef CFG_TUD_CDC_TX_BUFSIZE + #define CFG_TUD_CDC_TX_BUFSIZE TUD_EPSIZE_BULK_MAX +#endif + +#ifndef CFG_TUD_CDC_RX_BUFSIZE + #define CFG_TUD_CDC_RX_BUFSIZE TUD_EPSIZE_BULK_MAX +#endif + +// EP_BUFSIZE is separated to RX_EPSIZE and TX_EPSIZE +#ifndef CFG_TUD_CDC_RX_EPSIZE + #ifdef CFG_TUD_CDC_EP_BUFSIZE + #define CFG_TUD_CDC_RX_EPSIZE CFG_TUD_CDC_EP_BUFSIZE + #else + #define CFG_TUD_CDC_RX_EPSIZE TUD_EPSIZE_BULK_MAX + #endif +#endif + +#ifndef CFG_TUD_CDC_TX_EPSIZE + #ifdef CFG_TUD_CDC_EP_BUFSIZE + #define CFG_TUD_CDC_TX_EPSIZE CFG_TUD_CDC_EP_BUFSIZE + #else + #define CFG_TUD_CDC_TX_EPSIZE TUD_EPSIZE_BULK_MAX + #endif +#endif + +// Enable multi-packet RX transfer with ZLP termination for better throughput. Requires host support for ZLP. +#ifndef CFG_TUD_CDC_RX_NEED_ZLP + #define CFG_TUD_CDC_RX_NEED_ZLP 0 +#endif + +// Keep rx fifo data even with bus reset or disconnect +#ifndef CFG_TUD_CDC_RX_PERSISTENT + #define CFG_TUD_CDC_RX_PERSISTENT 0 +#endif + +// Keep tx fifo data even with bus reset or disconnect +#ifndef CFG_TUD_CDC_TX_PERSISTENT + #define CFG_TUD_CDC_TX_PERSISTENT 0 +#endif + +// If not connected, tx fifo can be overwritten +#ifndef CFG_TUD_CDC_TX_OVERWRITABLE_IF_NOT_CONNECTED + #define CFG_TUD_CDC_TX_OVERWRITABLE_IF_NOT_CONNECTED 1 +#endif + +// Backward compatible: tud_cdc_configure_t and tud_cdc_configure() are no longer used. +// Configuration is now done via compile-time macros above. +typedef struct { + bool rx_persistent; + bool tx_persistent; + bool tx_overwritabe_if_not_connected; +} tud_cdc_configure_t; + +#define tud_cdc_configure(_cfg) ((void)(_cfg)) +#define tud_cdc_configure_fifo_t tud_cdc_configure_t +#define tud_cdc_configure_fifo(_cfg) ((void)(_cfg)) + +//--------------------------------------------------------------------+ +// Application API (Multiple Ports) i.e. CFG_TUD_CDC > 1 +//--------------------------------------------------------------------+ + +// Check if the interface is ready +bool tud_cdc_n_ready(uint8_t itf); + +// Check if the terminal is connected to this port +bool tud_cdc_n_connected(uint8_t itf); + +// Get the current line state. Bit 0: DTR (Data Terminal Ready), Bit 1: RTS (Request to Send) +uint8_t tud_cdc_n_get_line_state(uint8_t itf); + +// Get current line encoding: bit rate, stop bits parity etc .. +void tud_cdc_n_get_line_coding(uint8_t itf, cdc_line_coding_t* coding); + +// Set special character that will trigger tud_cdc_rx_wanted_cb() callback on receiving +void tud_cdc_n_set_wanted_char(uint8_t itf, char wanted); + +// Get the number of bytes available for reading +uint32_t tud_cdc_n_available(uint8_t itf); + +// Read received bytes +uint32_t tud_cdc_n_read(uint8_t itf, void* buffer, uint32_t bufsize); + +// Read a byte, return -1 if there is none +TU_ATTR_ALWAYS_INLINE static inline int32_t tud_cdc_n_read_char(uint8_t itf) { + uint8_t ch; + return tud_cdc_n_read(itf, &ch, 1) ? (int32_t) ch : -1; +} + +// Clear the received FIFO +void tud_cdc_n_read_flush(uint8_t itf); + +// Get a byte from FIFO without removing it +bool tud_cdc_n_peek(uint8_t itf, uint8_t* ui8); + +// Write bytes to TX FIFO, data may remain in the FIFO for a while +uint32_t tud_cdc_n_write(uint8_t itf, void const* buffer, uint32_t bufsize); + +// Write a byte +TU_ATTR_ALWAYS_INLINE static inline uint32_t tud_cdc_n_write_char(uint8_t itf, char ch) { + return tud_cdc_n_write(itf, &ch, 1); +} + +// Write a null-terminated string +TU_ATTR_ALWAYS_INLINE static inline uint32_t tud_cdc_n_write_str(uint8_t itf, char const* str) { + return tud_cdc_n_write(itf, str, strlen(str)); +} + +// Force sending data if possible, return number of forced bytes +uint32_t tud_cdc_n_write_flush(uint8_t itf); + +// Return the number of bytes (characters) available for writing to TX FIFO buffer in a single n_write operation. +uint32_t tud_cdc_n_write_available(uint8_t itf); + +// Clear the TX FIFO +bool tud_cdc_n_write_clear(uint8_t itf); + +#if CFG_TUD_CDC_NOTIFY +bool tud_cdc_n_notify_msg(uint8_t itf, cdc_notify_msg_t *msg); + +// Send UART status notification: DCD, DSR etc .. +TU_ATTR_ALWAYS_INLINE static inline bool tud_cdc_n_notify_uart_state(uint8_t itf, + const cdc_notify_uart_state_t *state) { + cdc_notify_msg_t notify_msg; + notify_msg.request.bmRequestType = CDC_REQ_TYPE_NOTIF; + notify_msg.request.bRequest = CDC_NOTIF_SERIAL_STATE; + notify_msg.request.wValue = 0; + notify_msg.request.wIndex = 0; // filled later + notify_msg.request.wLength = sizeof(cdc_notify_uart_state_t); + notify_msg.serial_state = *state; + return tud_cdc_n_notify_msg(itf, ¬ify_msg); +} + +// Send connection speed change notification +TU_ATTR_ALWAYS_INLINE static inline bool +tud_cdc_n_notify_conn_speed_change(uint8_t itf, const cdc_notify_conn_speed_change_t *conn_speed_change) { + cdc_notify_msg_t notify_msg; + notify_msg.request.bmRequestType = CDC_REQ_TYPE_NOTIF; + notify_msg.request.bRequest = CDC_NOTIF_CONNECTION_SPEED_CHANGE; + notify_msg.request.wValue = 0; + notify_msg.request.wIndex = 0; // filled later + notify_msg.request.wLength = sizeof(cdc_notify_conn_speed_change_t); + notify_msg.conn_speed_change = *conn_speed_change; + return tud_cdc_n_notify_msg(itf, ¬ify_msg); +} + +TU_ATTR_ALWAYS_INLINE static inline bool tud_cdc_notify_msg(cdc_notify_msg_t *msg) { + return tud_cdc_n_notify_msg(0, msg); +} + +TU_ATTR_ALWAYS_INLINE static inline bool tud_cdc_notify_uart_state(const cdc_notify_uart_state_t* state) { + return tud_cdc_n_notify_uart_state(0, state); +} + +TU_ATTR_ALWAYS_INLINE static inline bool tud_cdc_notify_conn_speed_change(const cdc_notify_conn_speed_change_t* conn_speed_change) { + return tud_cdc_n_notify_conn_speed_change(0, conn_speed_change); +} +#endif + +//--------------------------------------------------------------------+ +// Application API (Single Port) +//--------------------------------------------------------------------+ + +TU_ATTR_ALWAYS_INLINE static inline bool tud_cdc_ready(void) { + return tud_cdc_n_ready(0); +} + +TU_ATTR_ALWAYS_INLINE static inline bool tud_cdc_connected(void) { + return tud_cdc_n_connected(0); +} + +TU_ATTR_ALWAYS_INLINE static inline uint8_t tud_cdc_get_line_state(void) { + return tud_cdc_n_get_line_state(0); +} + +TU_ATTR_ALWAYS_INLINE static inline void tud_cdc_get_line_coding(cdc_line_coding_t* coding) { + tud_cdc_n_get_line_coding(0, coding); +} + +TU_ATTR_ALWAYS_INLINE static inline void tud_cdc_set_wanted_char(char wanted) { + tud_cdc_n_set_wanted_char(0, wanted); +} + +TU_ATTR_ALWAYS_INLINE static inline uint32_t tud_cdc_available(void) { + return tud_cdc_n_available(0); +} + +TU_ATTR_ALWAYS_INLINE static inline int32_t tud_cdc_read_char(void) { + return tud_cdc_n_read_char(0); +} + +TU_ATTR_ALWAYS_INLINE static inline uint32_t tud_cdc_read(void* buffer, uint32_t bufsize) { + return tud_cdc_n_read(0, buffer, bufsize); +} + +TU_ATTR_ALWAYS_INLINE static inline void tud_cdc_read_flush(void) { + tud_cdc_n_read_flush(0); +} + +TU_ATTR_ALWAYS_INLINE static inline bool tud_cdc_peek(uint8_t* ui8) { + return tud_cdc_n_peek(0, ui8); +} + +TU_ATTR_ALWAYS_INLINE static inline uint32_t tud_cdc_write_char(char ch) { + return tud_cdc_n_write_char(0, ch); +} + +TU_ATTR_ALWAYS_INLINE static inline uint32_t tud_cdc_write(void const* buffer, uint32_t bufsize) { + return tud_cdc_n_write(0, buffer, bufsize); +} + +TU_ATTR_ALWAYS_INLINE static inline uint32_t tud_cdc_write_str(char const* str) { + return tud_cdc_n_write_str(0, str); +} + +TU_ATTR_ALWAYS_INLINE static inline uint32_t tud_cdc_write_flush(void) { + return tud_cdc_n_write_flush(0); +} + +TU_ATTR_ALWAYS_INLINE static inline uint32_t tud_cdc_write_available(void) { + return tud_cdc_n_write_available(0); +} + +TU_ATTR_ALWAYS_INLINE static inline bool tud_cdc_write_clear(void) { + return tud_cdc_n_write_clear(0); +} + +//--------------------------------------------------------------------+ +// Application Callback API +//--------------------------------------------------------------------+ + +// Invoked when received new data +void tud_cdc_rx_cb(uint8_t itf); + +// Invoked when received `wanted_char` +void tud_cdc_rx_wanted_cb(uint8_t itf, char wanted_char); + +// Invoked when a TX is complete and therefore space becomes available in TX buffer +void tud_cdc_tx_complete_cb(uint8_t itf); + +// Invoked when a notification is sent to host +void tud_cdc_notify_complete_cb(uint8_t itf); + +// Invoked when line state DTR & RTS are changed via SET_CONTROL_LINE_STATE +void tud_cdc_line_state_cb(uint8_t itf, bool dtr, bool rts); + +// Invoked when line coding is change via SET_LINE_CODING +void tud_cdc_line_coding_cb(uint8_t itf, cdc_line_coding_t const* p_line_coding); + +// Invoked when received send break +// \param[in] itf interface for which send break was received. +// \param[in] duration_ms the length of time, in milliseconds, of the break signal. If a value of FFFFh, then the +// device will send a break until another SendBreak request is received with value 0000h. +void tud_cdc_send_break_cb(uint8_t itf, uint16_t duration_ms); + +//--------------------------------------------------------------------+ +// INTERNAL USBD-CLASS DRIVER API +//--------------------------------------------------------------------+ +void cdcd_init (void); +bool cdcd_deinit (void); +void cdcd_reset (uint8_t rhport); +uint16_t cdcd_open (uint8_t rhport, tusb_desc_interface_t const * itf_desc, uint16_t max_len); +bool cdcd_control_xfer_cb (uint8_t rhport, uint8_t stage, tusb_control_request_t const * request); +bool cdcd_xfer_cb (uint8_t rhport, uint8_t ep_addr, xfer_result_t result, uint32_t xferred_bytes); + +#ifdef __cplusplus + } +#endif + +#endif /* TUSB_CDC_DEVICE_H_ */ diff --git a/libraries/Adafruit_TinyUSB_Arduino/src/class/cdc/cdc_host.c b/libraries/Adafruit_TinyUSB_Arduino/src/class/cdc/cdc_host.c new file mode 100644 index 0000000..62c313b --- /dev/null +++ b/libraries/Adafruit_TinyUSB_Arduino/src/class/cdc/cdc_host.c @@ -0,0 +1,2554 @@ +/* + * 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. + * + * This file is part of the TinyUSB stack. + * + * Contribution + * - Heiko Kuester: add support of CH34x & PL2303, improve support of FTDI & CP210x + */ + +#include "tusb_option.h" + +#include + +#if (CFG_TUH_ENABLED && CFG_TUH_CDC) + +#include "host/usbh.h" +#include "host/usbh_pvt.h" + +#include "cdc_host.h" +#include "serial/ftdi_sio.h" +#include "serial/cp210x.h" +#include "serial/ch34x.h" +#include "serial/pl2303.h" + +// Level where CFG_TUSB_DEBUG must be at least for this driver is logged + #ifndef CFG_TUH_CDC_LOG_LEVEL + #define CFG_TUH_CDC_LOG_LEVEL 2 + #endif + + #define TU_LOG_DRV(...) TU_LOG(CFG_TUH_CDC_LOG_LEVEL, __VA_ARGS__) + #define TU_LOG_CDC(_cdc, _format, ...) \ + TU_LOG_DRV("[:%u:%u] CDCh %s " _format "\r\n", _cdc->daddr, _cdc->bInterfaceNumber, \ + serial_drivers[_cdc->serial_drid].name, ##__VA_ARGS__) + +//--------------------------------------------------------------------+ +// Host CDC Interface +//--------------------------------------------------------------------+ + +typedef struct { + uint8_t daddr; + uint8_t bInterfaceNumber; + uint8_t bInterfaceSubClass; + uint8_t bInterfaceProtocol; + + uint8_t ep_notif; + uint8_t serial_drid; // Serial Driver ID + bool mounted; // Enumeration is complete + + struct { + TU_ATTR_ALIGNED(4) cdc_line_coding_t coding; // Baudrate, stop bits, parity, data width + cdc_line_control_state_t control_state; // DTR, RTS + } line, requested_line; + + tuh_xfer_cb_t user_complete_cb; // required since we handle request internally first + + union { + struct { + cdc_acm_capability_t capability; + } acm; + + #if CFG_TUH_CDC_FTDI + ftdi_private_t ftdi; + #endif + + #if CFG_TUH_CDC_PL2303 + pl2303_private_t pl2303; + #endif + }; + + struct { + tu_edpt_stream_t tx; + tu_edpt_stream_t rx; + + uint8_t tx_ff_buf[CFG_TUH_CDC_TX_BUFSIZE]; + uint8_t rx_ff_buf[CFG_TUH_CDC_RX_BUFSIZE]; + } stream; +} cdch_interface_t; + +typedef struct { + TUH_EPBUF_DEF(tx, CFG_TUH_CDC_TX_EPSIZE); + TUH_EPBUF_DEF(rx, CFG_TUH_CDC_RX_EPSIZE); +} cdch_epbuf_t; + +static cdch_interface_t cdch_data[CFG_TUH_CDC]; +CFG_TUH_MEM_SECTION static cdch_epbuf_t cdch_epbuf[CFG_TUH_CDC]; + +//--------------------------------------------------------------------+ +// Serial Driver +//--------------------------------------------------------------------+ + +// General driver +static void cdch_process_set_config(tuh_xfer_t *xfer); +static void cdch_process_line_state_on_enum(tuh_xfer_t *xfer); // invoked after set config is processed +static void cdch_internal_control_complete(tuh_xfer_t *xfer); +static void cdch_set_line_coding_stage1_baudrate_complete(tuh_xfer_t *xfer); +static void cdch_set_line_coding_stage2_data_format_complete(tuh_xfer_t *xfer); + +//------------- ACM prototypes -------------// +static uint16_t acm_open(uint8_t daddr, const tusb_desc_interface_t *itf_desc, uint16_t max_len); +static bool acm_process_set_config(cdch_interface_t *p_cdc, tuh_xfer_t *xfer); +static void acm_internal_control_complete(cdch_interface_t *p_cdc, tuh_xfer_t *xfer); +static bool acm_set_line_coding(cdch_interface_t *p_cdc, tuh_xfer_cb_t complete_cb, uintptr_t user_data); +static bool acm_set_control_line_state(cdch_interface_t *p_cdc, tuh_xfer_cb_t complete_cb, uintptr_t user_data); + + //------------- FTDI prototypes -------------// + #if CFG_TUH_CDC_FTDI +static uint16_t const ftdi_vid_pid_list[][2] = {CFG_TUH_CDC_FTDI_VID_PID_LIST}; + +static uint16_t ftdi_open(uint8_t daddr, const tusb_desc_interface_t *itf_desc, uint16_t max_len); +static bool ftdi_process_set_config(cdch_interface_t *p_cdc, tuh_xfer_t *xfer); +static void ftdi_internal_control_complete(cdch_interface_t *p_cdc, tuh_xfer_t *xfer); +static bool ftdi_set_baudrate(cdch_interface_t *p_cdc, tuh_xfer_cb_t complete_cb, uintptr_t user_data); +static bool ftdi_set_data_format(cdch_interface_t *p_cdc, tuh_xfer_cb_t complete_cb, uintptr_t user_data); +static bool ftdi_set_modem_ctrl(cdch_interface_t *p_cdc, tuh_xfer_cb_t complete_cb, uintptr_t user_data); + #endif + + //------------- CP210X prototypes -------------// + #if CFG_TUH_CDC_CP210X +static uint16_t const cp210x_vid_pid_list[][2] = {CFG_TUH_CDC_CP210X_VID_PID_LIST}; + +static uint16_t cp210x_open(uint8_t daddr, const tusb_desc_interface_t *itf_desc, uint16_t max_len); +static bool cp210x_process_set_config(cdch_interface_t *p_cdc, tuh_xfer_t *xfer); +static void cp210x_internal_control_complete(cdch_interface_t *p_cdc, tuh_xfer_t *xfer); +static bool cp210x_set_baudrate(cdch_interface_t *p_cdc, tuh_xfer_cb_t complete_cb, uintptr_t user_data); +static bool cp210x_set_data_format(cdch_interface_t *p_cdc, tuh_xfer_cb_t complete_cb, uintptr_t user_data); +static bool cp210x_set_modem_ctrl(cdch_interface_t *p_cdc, tuh_xfer_cb_t complete_cb, uintptr_t user_data); + #endif + + //------------- CH34x prototypes -------------// + #if CFG_TUH_CDC_CH34X +static uint16_t const ch34x_vid_pid_list[][2] = {CFG_TUH_CDC_CH34X_VID_PID_LIST}; + +static uint16_t ch34x_open(uint8_t daddr, const tusb_desc_interface_t *itf_desc, uint16_t max_len); +static bool ch34x_process_set_config(cdch_interface_t *p_cdc, tuh_xfer_t *xfer); +static void ch34x_internal_control_complete(cdch_interface_t *p_cdc, tuh_xfer_t *xfer); +static bool ch34x_set_baudrate(cdch_interface_t *p_cdc, tuh_xfer_cb_t complete_cb, uintptr_t user_data); +static bool ch34x_set_data_format(cdch_interface_t *p_cdc, tuh_xfer_cb_t complete_cb, uintptr_t user_data); +static bool ch34x_set_modem_ctrl(cdch_interface_t *p_cdc, tuh_xfer_cb_t complete_cb, uintptr_t user_data); + #endif + + //------------- PL2303 prototypes -------------// + #if CFG_TUH_CDC_PL2303 +static uint16_t const pl2303_vid_pid_list[][2] = {CFG_TUH_CDC_PL2303_VID_PID_LIST}; +static const pl2303_type_data_t pl2303_type_data[PL2303_TYPE_COUNT] = {PL2303_TYPE_DATA}; + +static uint16_t pl2303_open(uint8_t daddr, const tusb_desc_interface_t *itf_desc, uint16_t max_len); +static bool pl2303_process_set_config(cdch_interface_t *p_cdc, tuh_xfer_t *xfer); +static void pl2303_internal_control_complete(cdch_interface_t *p_cdc, tuh_xfer_t *xfer); +static bool pl2303_set_line_coding(cdch_interface_t *p_cdc, tuh_xfer_cb_t complete_cb, uintptr_t user_data); +static bool pl2303_set_modem_ctrl(cdch_interface_t *p_cdc, tuh_xfer_cb_t complete_cb, uintptr_t user_data); + #endif + +//------------- Common -------------// +enum { + SERIAL_DRIVER_ACM = 0, + +#if CFG_TUH_CDC_FTDI + SERIAL_DRIVER_FTDI, +#endif + +#if CFG_TUH_CDC_CP210X + SERIAL_DRIVER_CP210X, +#endif + +#if CFG_TUH_CDC_CH34X + SERIAL_DRIVER_CH34X, +#endif + +#if CFG_TUH_CDC_PL2303 + SERIAL_DRIVER_PL2303, +#endif + + SERIAL_DRIVER_COUNT +}; + +typedef bool (*serial_driver_func_t)(cdch_interface_t * p_cdc, tuh_xfer_cb_t complete_cb, uintptr_t user_data); + +typedef struct { + const uint16_t (*vid_pid_list)[2]; + const uint16_t vid_pid_count; + uint16_t (*const open)(uint8_t daddr, const tusb_desc_interface_t *itf_desc, uint16_t max_len); + bool (*const process_set_config)(cdch_interface_t *p_cdc, tuh_xfer_t *xfer); + // internal request complete handler to update line state + void (*const request_complete)(cdch_interface_t *p_cdc, tuh_xfer_t *xfer); + + serial_driver_func_t set_control_line_state, set_baudrate, set_data_format, set_line_coding; + + #if CFG_TUSB_DEBUG && CFG_TUSB_DEBUG >= CFG_TUH_CDC_LOG_LEVEL + const char * name; + #endif +} cdch_serial_driver_t; + +#if CFG_TUSB_DEBUG >= CFG_TUH_CDC_LOG_LEVEL + #define DRIVER_NAME_DECLARE(_str) .name = _str +#else + #define DRIVER_NAME_DECLARE(_str) +#endif + +// clang-format off +// Note driver list must be in the same order as SERIAL_DRIVER enum +static const cdch_serial_driver_t serial_drivers[] = { + { + .vid_pid_list = NULL, + .vid_pid_count = 0, + .open = acm_open, + .process_set_config = acm_process_set_config, + .request_complete = acm_internal_control_complete, + .set_control_line_state = acm_set_control_line_state, + .set_baudrate = acm_set_line_coding, + .set_data_format = acm_set_line_coding, + .set_line_coding = acm_set_line_coding, + DRIVER_NAME_DECLARE("ACM") + }, + + #if CFG_TUH_CDC_FTDI + { + .vid_pid_list = ftdi_vid_pid_list, + .vid_pid_count = TU_ARRAY_SIZE(ftdi_vid_pid_list), + .open = ftdi_open, + .process_set_config = ftdi_process_set_config, + .request_complete = ftdi_internal_control_complete, + .set_control_line_state = ftdi_set_modem_ctrl, + .set_baudrate = ftdi_set_baudrate, + .set_data_format = ftdi_set_data_format, + .set_line_coding = NULL, // 2 stage set line coding + DRIVER_NAME_DECLARE("FTDI") + }, + #endif + + #if CFG_TUH_CDC_CP210X + { + .vid_pid_list = cp210x_vid_pid_list, + .vid_pid_count = TU_ARRAY_SIZE(cp210x_vid_pid_list), + .open = cp210x_open, + .process_set_config = cp210x_process_set_config, + .request_complete = cp210x_internal_control_complete, + .set_control_line_state = cp210x_set_modem_ctrl, + .set_baudrate = cp210x_set_baudrate, + .set_data_format = cp210x_set_data_format, + .set_line_coding = NULL, // 2 stage set line coding + DRIVER_NAME_DECLARE("CP210x") + }, + #endif + + #if CFG_TUH_CDC_CH34X + { + .vid_pid_list = ch34x_vid_pid_list, + .vid_pid_count = TU_ARRAY_SIZE(ch34x_vid_pid_list), + .open = ch34x_open, + .process_set_config = ch34x_process_set_config, + .request_complete = ch34x_internal_control_complete, + + .set_control_line_state = ch34x_set_modem_ctrl, + .set_baudrate = ch34x_set_baudrate, + .set_data_format = ch34x_set_data_format, + .set_line_coding = NULL, // 2 stage set line coding + DRIVER_NAME_DECLARE("CH34x") + }, + #endif + + #if CFG_TUH_CDC_PL2303 + { + .vid_pid_list = pl2303_vid_pid_list, + .vid_pid_count = TU_ARRAY_SIZE(pl2303_vid_pid_list), + .open = pl2303_open, + .process_set_config = pl2303_process_set_config, + .request_complete = pl2303_internal_control_complete, + .set_control_line_state = pl2303_set_modem_ctrl, + .set_baudrate = pl2303_set_line_coding, + .set_data_format = pl2303_set_line_coding, + .set_line_coding = pl2303_set_line_coding, + DRIVER_NAME_DECLARE("PL2303") + } + #endif +}; +// clang-format on + +TU_VERIFY_STATIC(TU_ARRAY_SIZE(serial_drivers) == SERIAL_DRIVER_COUNT, "Serial driver count mismatch"); + +//--------------------------------------------------------------------+ +// INTERNAL OBJECT & FUNCTION DECLARATION +//--------------------------------------------------------------------+ +static bool open_ep_stream_pair(cdch_interface_t *p_cdc, const tusb_desc_endpoint_t *desc_ep); + +TU_ATTR_ALWAYS_INLINE static inline cdch_interface_t * get_itf(uint8_t idx) { + TU_ASSERT(idx < CFG_TUH_CDC, NULL); + cdch_interface_t * p_cdc = &cdch_data[idx]; + return (p_cdc->daddr != 0) ? p_cdc : NULL; +} + +TU_ATTR_ALWAYS_INLINE static inline uint8_t get_idx_by_ptr(cdch_interface_t* p_cdc) { + return (uint8_t) (p_cdc - cdch_data); +} + +static inline uint8_t get_idx_by_ep_addr(uint8_t daddr, uint8_t ep_addr) { + for(uint8_t i=0; idaddr == daddr) && + (ep_addr == p_cdc->ep_notif || ep_addr == p_cdc->stream.rx.ep_addr || ep_addr == p_cdc->stream.tx.ep_addr)) { + return i; + } + } + + return TUSB_INDEX_INVALID_8; +} + +// determine the interface from the completed transfer +static cdch_interface_t* get_itf_by_xfer(const tuh_xfer_t * xfer) { + TU_VERIFY(xfer->daddr != 0, NULL); + for(uint8_t i=0; idaddr == xfer->daddr) { + switch (p_cdc->serial_drid) { + #if CFG_TUH_CDC_CP210X + case SERIAL_DRIVER_CP210X: + #endif + case SERIAL_DRIVER_ACM: { + // Driver use wIndex for bInterfaceNumber + const uint8_t itf_num = (uint8_t) tu_le16toh(xfer->setup->wIndex); + if (p_cdc->bInterfaceNumber == itf_num) { + return p_cdc; + } + break; + } + + #if CFG_TUH_CDC_FTDI + case SERIAL_DRIVER_FTDI: { + // FTDI uses wIndex for channel number, if channel is 0 then it is the default channel + const uint8_t channel = (uint8_t) tu_le16toh(xfer->setup->wIndex); + if (p_cdc->ftdi.channel == 0 || p_cdc->ftdi.channel == channel) { + return p_cdc; + } + break; + } + #endif + + #if CFG_TUH_CDC_CH34X + case SERIAL_DRIVER_CH34X: + // ch34x has only one interface + return p_cdc; + #endif + + #if CFG_TUH_CDC_PL2303 + case SERIAL_DRIVER_PL2303: + // pl2303 has only one interface + return p_cdc; + #endif + + default: + break; // unknown driver + } + } + } + + return NULL; +} + +static cdch_interface_t * make_new_itf(uint8_t daddr, tusb_desc_interface_t const * itf_desc) { + for(uint8_t i=0; idaddr = daddr; + p_cdc->bInterfaceNumber = itf_desc->bInterfaceNumber; + p_cdc->bInterfaceSubClass = itf_desc->bInterfaceSubClass; + p_cdc->bInterfaceProtocol = itf_desc->bInterfaceProtocol; + p_cdc->line.coding = (cdc_line_coding_t) { 0, 0, 0, 0 }; + p_cdc->line.control_state.value = 0; + return p_cdc; + } + } + + return NULL; +} + +//--------------------------------------------------------------------+ +// Weak stubs: invoked if no strong implementation is available +//--------------------------------------------------------------------+ +TU_ATTR_WEAK void tuh_cdc_mount_cb(uint8_t idx) { + (void) idx; +} + +TU_ATTR_WEAK void tuh_cdc_umount_cb(uint8_t idx) { + (void) idx; +} + +TU_ATTR_WEAK void tuh_cdc_rx_cb(uint8_t idx) { + (void) idx; +} + +TU_ATTR_WEAK void tuh_cdc_tx_complete_cb(uint8_t idx) { + (void) idx; +} + +//--------------------------------------------------------------------+ +// APPLICATION API +//--------------------------------------------------------------------+ + +uint8_t tuh_cdc_itf_get_index(uint8_t daddr, uint8_t itf_num) { + for (uint8_t i = 0; i < CFG_TUH_CDC; i++) { + const cdch_interface_t * p_cdc = &cdch_data[i]; + if (p_cdc->daddr == daddr && p_cdc->bInterfaceNumber == itf_num) { return i; } + } + return TUSB_INDEX_INVALID_8; +} + +bool tuh_cdc_itf_get_info(uint8_t idx, tuh_itf_info_t * info) { + cdch_interface_t * p_cdc = get_itf(idx); + TU_VERIFY(p_cdc && info); + + info->daddr = p_cdc->daddr; + + // re-construct interface descriptor + tusb_desc_interface_t * desc = &info->desc; + desc->bLength = sizeof(tusb_desc_interface_t); + desc->bDescriptorType = TUSB_DESC_INTERFACE; + + desc->bInterfaceNumber = p_cdc->bInterfaceNumber; + desc->bAlternateSetting = 0; + desc->bNumEndpoints = 2u + (p_cdc->ep_notif ? 1u : 0u); + desc->bInterfaceClass = TUSB_CLASS_CDC; + desc->bInterfaceSubClass = p_cdc->bInterfaceSubClass; + desc->bInterfaceProtocol = p_cdc->bInterfaceProtocol; + desc->iInterface = 0; // not used yet + + return true; +} + +bool tuh_cdc_mounted(uint8_t idx) { + cdch_interface_t * p_cdc = get_itf(idx); + TU_VERIFY(p_cdc); + return p_cdc->mounted; +} + +bool tuh_cdc_get_control_line_state_local(uint8_t idx, uint16_t* line_state) { + cdch_interface_t * p_cdc = get_itf(idx); + TU_VERIFY(p_cdc); + *line_state = p_cdc->line.control_state.value; + return true; +} + +bool tuh_cdc_get_line_coding_local(uint8_t idx, cdc_line_coding_t * line_coding) { + cdch_interface_t * p_cdc = get_itf(idx); + TU_VERIFY(p_cdc); + *line_coding = p_cdc->line.coding; + return true; +} + +//--------------------------------------------------------------------+ +// Write +//--------------------------------------------------------------------+ + +uint32_t tuh_cdc_write(uint8_t idx, void const * buffer, uint32_t bufsize) { + cdch_interface_t * p_cdc = get_itf(idx); + TU_VERIFY(p_cdc); + return tu_edpt_stream_write(&p_cdc->stream.tx, buffer, bufsize); +} + +uint32_t tuh_cdc_write_flush(uint8_t idx) { + cdch_interface_t * p_cdc = get_itf(idx); + TU_VERIFY(p_cdc); + return tu_edpt_stream_write_xfer(&p_cdc->stream.tx); +} + +bool tuh_cdc_write_clear(uint8_t idx) { + cdch_interface_t * p_cdc = get_itf(idx); + TU_VERIFY(p_cdc); + tu_edpt_stream_clear(&p_cdc->stream.tx); + return true; +} + +uint32_t tuh_cdc_write_available(uint8_t idx) { + cdch_interface_t * p_cdc = get_itf(idx); + TU_VERIFY(p_cdc); + return tu_edpt_stream_write_available(&p_cdc->stream.tx); +} + +//--------------------------------------------------------------------+ +// Read +//--------------------------------------------------------------------+ + +uint32_t tuh_cdc_read (uint8_t idx, void * buffer, uint32_t bufsize) { + cdch_interface_t * p_cdc = get_itf(idx); + TU_VERIFY(p_cdc); + return tu_edpt_stream_read(&p_cdc->stream.rx, buffer, bufsize); +} + +uint32_t tuh_cdc_read_available(uint8_t idx) { + cdch_interface_t * p_cdc = get_itf(idx); + TU_VERIFY(p_cdc); + return tu_edpt_stream_read_available(&p_cdc->stream.rx); +} + +bool tuh_cdc_peek(uint8_t idx, uint8_t * ch) { + cdch_interface_t * p_cdc = get_itf(idx); + TU_VERIFY(p_cdc); + return tu_edpt_stream_peek(&p_cdc->stream.rx, ch); +} + +bool tuh_cdc_read_clear (uint8_t idx) { + cdch_interface_t * p_cdc = get_itf(idx); + TU_VERIFY(p_cdc); + + tu_edpt_stream_clear(&p_cdc->stream.rx); + (void)tu_edpt_stream_read_xfer(&p_cdc->stream.rx); + return true; +} + +//--------------------------------------------------------------------+ +// Control Endpoint API +//--------------------------------------------------------------------+ + +bool tuh_cdc_set_control_line_state(uint8_t idx, uint16_t line_state, tuh_xfer_cb_t complete_cb, uintptr_t user_data) { + cdch_interface_t * p_cdc = get_itf(idx); + TU_VERIFY(p_cdc && p_cdc->serial_drid < SERIAL_DRIVER_COUNT); + TU_LOG_CDC(p_cdc, "set control line state dtr = %u rts = %u", p_cdc->requested_line.control_state.dtr, p_cdc->requested_line.control_state.rts); + const cdch_serial_driver_t * driver = &serial_drivers[p_cdc->serial_drid]; + + p_cdc->requested_line.control_state.value = (uint8_t) line_state; + p_cdc->user_complete_cb = complete_cb; + TU_VERIFY(driver->set_control_line_state(p_cdc, complete_cb ? cdch_internal_control_complete : NULL, user_data)); + + if (!complete_cb) { + // blocking, update line state if request was successful + p_cdc->line.control_state.value = (uint8_t) line_state; + } + + return true; +} + +bool tuh_cdc_set_baudrate(uint8_t idx, uint32_t baudrate, tuh_xfer_cb_t complete_cb, uintptr_t user_data) { + cdch_interface_t *p_cdc = get_itf(idx); + TU_VERIFY(p_cdc && p_cdc->serial_drid < SERIAL_DRIVER_COUNT); + TU_LOG_CDC(p_cdc, "set baudrate %lu", baudrate); + const cdch_serial_driver_t *driver = &serial_drivers[p_cdc->serial_drid]; + + p_cdc->requested_line = p_cdc->line; // keep current line coding + p_cdc->requested_line.coding.bit_rate = baudrate; + p_cdc->user_complete_cb = complete_cb; + TU_VERIFY(driver->set_baudrate(p_cdc, complete_cb ? cdch_internal_control_complete : NULL, user_data)); + + if (!complete_cb) { + p_cdc->line.coding.bit_rate = baudrate; + } + + return true; +} + +bool tuh_cdc_set_data_format(uint8_t idx, uint8_t stop_bits, uint8_t parity, uint8_t data_bits, + tuh_xfer_cb_t complete_cb, uintptr_t user_data) { + cdch_interface_t *p_cdc = get_itf(idx); + TU_VERIFY(p_cdc && p_cdc->serial_drid < SERIAL_DRIVER_COUNT); + TU_LOG_CDC(p_cdc, "set data format %u%c%s", + data_bits, CDC_LINE_CODING_PARITY_CHAR(parity), + CDC_LINE_CODING_STOP_BITS_TEXT(stop_bits)); + const cdch_serial_driver_t *driver = &serial_drivers[p_cdc->serial_drid]; + + p_cdc->requested_line = p_cdc->line; // keep current line coding + p_cdc->requested_line.coding.stop_bits = stop_bits; + p_cdc->requested_line.coding.parity = parity; + p_cdc->requested_line.coding.data_bits = data_bits; + + p_cdc->user_complete_cb = complete_cb; + TU_VERIFY(driver->set_data_format(p_cdc, complete_cb ? cdch_internal_control_complete : NULL, user_data)); + + if (!complete_cb) { + // blocking + p_cdc->line.coding.stop_bits = stop_bits; + p_cdc->line.coding.parity = parity; + p_cdc->line.coding.data_bits = data_bits; + } + + return true; +} + +bool tuh_cdc_set_line_coding(uint8_t idx, cdc_line_coding_t const *line_coding, + tuh_xfer_cb_t complete_cb, uintptr_t user_data) { + cdch_interface_t *p_cdc = get_itf(idx); + TU_VERIFY(p_cdc && p_cdc->serial_drid < SERIAL_DRIVER_COUNT); + TU_LOG_CDC(p_cdc, "set line coding %lu %u%c%s", + line_coding->bit_rate, line_coding->data_bits, + CDC_LINE_CODING_PARITY_CHAR(line_coding->parity), + CDC_LINE_CODING_STOP_BITS_TEXT(line_coding->stop_bits)); + cdch_serial_driver_t const *driver = &serial_drivers[p_cdc->serial_drid]; + p_cdc->requested_line.coding = *line_coding; + p_cdc->user_complete_cb = complete_cb; + + if (driver->set_line_coding != NULL) { + // driver support set_line_coding request + TU_VERIFY(driver->set_line_coding(p_cdc, complete_cb ? cdch_internal_control_complete : NULL, user_data)); + + if (!complete_cb) { + p_cdc->line.coding = *line_coding; + } + } else { + // driver does not support set_line_coding and need 2 stage to set baudrate and data format separately + if (complete_cb != NULL) { + // non-blocking + TU_VERIFY(driver->set_baudrate(p_cdc, cdch_set_line_coding_stage1_baudrate_complete, user_data)); + } else { + // blocking + xfer_result_t result = XFER_RESULT_INVALID; + + TU_VERIFY(driver->set_baudrate(p_cdc, NULL, (uintptr_t) &result)); + if (user_data != 0) { + *((xfer_result_t *) user_data) = result; + } + TU_VERIFY(result == XFER_RESULT_SUCCESS); + p_cdc->line.coding.bit_rate = p_cdc->requested_line.coding.bit_rate; // update baudrate + + result = XFER_RESULT_INVALID; + TU_VERIFY(driver->set_data_format(p_cdc, NULL, (uintptr_t) &result)); + if (user_data != 0) { + *((xfer_result_t *) user_data) = result; + } + TU_VERIFY(result == XFER_RESULT_SUCCESS); + p_cdc->line.coding = p_cdc->requested_line.coding; // update data format + } + } + + return true; +} + +//--------------------------------------------------------------------+ +// CLASS-USBH API +//--------------------------------------------------------------------+ + +bool cdch_init(void) { + TU_LOG_DRV("sizeof(cdch_interface_t) = %u\r\n", sizeof(cdch_interface_t)); + tu_memclr(cdch_data, sizeof(cdch_data)); + for (size_t i = 0; i < CFG_TUH_CDC; i++) { + cdch_interface_t *p_cdc = &cdch_data[i]; + cdch_epbuf_t *epbuf = &cdch_epbuf[i]; + TU_ASSERT(tu_edpt_stream_init(&p_cdc->stream.tx, true, true, false, p_cdc->stream.tx_ff_buf, + CFG_TUH_CDC_TX_BUFSIZE, epbuf->tx)); + TU_ASSERT(tu_edpt_stream_init(&p_cdc->stream.rx, true, false, false, p_cdc->stream.rx_ff_buf, + CFG_TUH_CDC_RX_BUFSIZE, epbuf->rx)); + } + + return true; +} + +bool cdch_deinit(void) { + for (size_t i = 0; i < CFG_TUH_CDC; i++) { + cdch_interface_t *p_cdc = &cdch_data[i]; + (void)tu_edpt_stream_deinit(&p_cdc->stream.tx); + (void)tu_edpt_stream_deinit(&p_cdc->stream.rx); + } + return true; +} + +void cdch_close(uint8_t daddr) { + for (uint8_t idx = 0; idx < CFG_TUH_CDC; idx++) { + cdch_interface_t *p_cdc = &cdch_data[idx]; + if (p_cdc->daddr == daddr) { + TU_LOG_CDC(p_cdc, "close"); + tuh_cdc_umount_cb(idx); // invoke callback + + p_cdc->daddr = 0; + p_cdc->bInterfaceNumber = 0; + p_cdc->mounted = false; + tu_edpt_stream_close(&p_cdc->stream.tx); + tu_edpt_stream_close(&p_cdc->stream.rx); + } + } +} + +bool cdch_xfer_cb(uint8_t daddr, uint8_t ep_addr, xfer_result_t event, uint32_t xferred_bytes) { + // TODO handle stall response, retry failed transfer ... + TU_VERIFY(event == XFER_RESULT_SUCCESS); + + uint8_t const idx = get_idx_by_ep_addr(daddr, ep_addr); + cdch_interface_t *p_cdc = get_itf(idx); + TU_ASSERT(p_cdc); + + if (ep_addr == p_cdc->stream.tx.ep_addr) { + tuh_cdc_tx_complete_cb(idx); // invoke transmit complete callback + + if (0 == tu_edpt_stream_write_xfer(&p_cdc->stream.tx)) { + // If there is no data left, a ZLP should be sent if: + // - xferred_bytes is multiple of EP Packet size and not zero + (void)tu_edpt_stream_write_zlp_if_needed(&p_cdc->stream.tx, xferred_bytes); + } + } else if (ep_addr == p_cdc->stream.rx.ep_addr) { + #if CFG_TUH_CDC_FTDI + if (p_cdc->serial_drid == SERIAL_DRIVER_FTDI) { + // FTDI reserve 2 bytes for status + // uint8_t status[2] = {p_cdc->stream.rx.ep_buf[0], p_cdc->stream.rx.ep_buf[1]}; + if (xferred_bytes > 2) { + tu_edpt_stream_read_xfer_complete_with_buf(&p_cdc->stream.rx, p_cdc->stream.rx.ep_buf + 2, xferred_bytes - 2); + + tuh_cdc_rx_cb(idx); // invoke receive callback + } + } else + #endif + { + tu_edpt_stream_read_xfer_complete(&p_cdc->stream.rx, xferred_bytes); + tuh_cdc_rx_cb(idx); // invoke receive callback + } + + // prepare for next transfer if needed + tu_edpt_stream_read_xfer(&p_cdc->stream.rx); + } else if (ep_addr == p_cdc->ep_notif) { + // TODO handle notification endpoint + } else { + return false; + } + + return true; +} + +//--------------------------------------------------------------------+ +// Enumeration +//--------------------------------------------------------------------+ +static bool open_ep_stream_pair(cdch_interface_t *p_cdc, tusb_desc_endpoint_t const *desc_ep) { + for (size_t i = 0; i < 2; i++) { + TU_ASSERT(TUSB_DESC_ENDPOINT == desc_ep->bDescriptorType && TUSB_XFER_BULK == desc_ep->bmAttributes.xfer, 0); + TU_ASSERT(tuh_edpt_open(p_cdc->daddr, desc_ep)); + const uint8_t ep_dir = tu_edpt_dir(desc_ep->bEndpointAddress); + tu_edpt_stream_t *stream = (ep_dir == TUSB_DIR_IN) ? &p_cdc->stream.rx : &p_cdc->stream.tx; + tu_edpt_stream_open(stream, p_cdc->daddr, desc_ep, tu_edpt_packet_size(desc_ep)); + tu_edpt_stream_clear(stream); + + desc_ep = (const tusb_desc_endpoint_t *)tu_desc_next(desc_ep); + } + + return true; +} + +uint16_t cdch_open(uint8_t rhport, uint8_t daddr, const tusb_desc_interface_t *itf_desc, uint16_t max_len) { + (void)rhport; + // For CDC: only support ACM subclass + // Note: Protocol 0xFF can be RNDIS device + if (TUSB_CLASS_CDC == itf_desc->bInterfaceClass && + CDC_COMM_SUBCLASS_ABSTRACT_CONTROL_MODEL == itf_desc->bInterfaceSubClass) { + return acm_open(daddr, itf_desc, max_len); + } else if (SERIAL_DRIVER_COUNT > 1 && + TUSB_CLASS_VENDOR_SPECIFIC == itf_desc->bInterfaceClass) { + uint16_t vid, pid; + TU_VERIFY(tuh_vid_pid_get(daddr, &vid, &pid), 0); + + for (size_t drv = 1; drv < SERIAL_DRIVER_COUNT; drv++) { + const cdch_serial_driver_t *driver = &serial_drivers[drv]; + for (size_t i = 0; i < driver->vid_pid_count; i++) { + if (driver->vid_pid_list[i][0] == vid && driver->vid_pid_list[i][1] == pid) { + const uint16_t drv_len = driver->open(daddr, itf_desc, max_len); + TU_LOG_DRV("[:%u:%u] CDCh %s open %s\r\n", daddr, itf_desc->bInterfaceNumber, driver->name, + drv_len > 0 ? "OK" : "FAILED"); + return drv_len; + } + } + } + } else { + // not supported class + } + + return 0; +} + +static void set_config_complete(cdch_interface_t *p_cdc, bool success) { + if (success) { + const uint8_t idx = get_idx_by_ptr(p_cdc); + p_cdc->mounted = true; + tuh_cdc_mount_cb(idx); + // Prepare for incoming data + tu_edpt_stream_read_xfer(&p_cdc->stream.rx); + } else { + // clear the interface entry + p_cdc->daddr = 0; + p_cdc->bInterfaceNumber = 0; + } + + // notify usbh that driver enumeration is complete + const uint8_t itf_offset = (p_cdc->serial_drid == SERIAL_DRIVER_ACM) ? 1 : 0; + usbh_driver_set_config_complete(p_cdc->daddr, p_cdc->bInterfaceNumber + itf_offset); +} + +bool cdch_set_config(uint8_t daddr, uint8_t itf_num) { + const uint8_t idx = tuh_cdc_itf_get_index(daddr, itf_num); + cdch_interface_t *p_cdc = get_itf(idx); + TU_ASSERT(p_cdc && p_cdc->serial_drid < SERIAL_DRIVER_COUNT); + TU_LOG_CDC(p_cdc, "set config"); + + // fake transfer to kick-off process_set_config() + tusb_control_request_t request; + request.wIndex = tu_htole16((uint16_t)itf_num); + + tuh_xfer_t xfer; + xfer.daddr = daddr; + xfer.ep_addr = 0; + xfer.result = XFER_RESULT_SUCCESS; + xfer.setup = &request; + xfer.complete_cb = NULL; + xfer.buffer = NULL; + xfer.user_data = 0; // initial state 0 + + const cdch_serial_driver_t *driver = &serial_drivers[p_cdc->serial_drid]; + if (!driver->process_set_config(p_cdc, &xfer)) { + set_config_complete(p_cdc, false); + } + + return true; +} + +static void cdch_process_set_config(tuh_xfer_t *xfer) { + cdch_interface_t *p_cdc = get_itf_by_xfer(xfer); + TU_ASSERT(p_cdc && p_cdc->serial_drid < SERIAL_DRIVER_COUNT,); + TU_LOG_DRV(" state = %u\r\n", xfer->user_data); + const cdch_serial_driver_t *driver = &serial_drivers[p_cdc->serial_drid]; + + if (!driver->process_set_config(p_cdc, xfer)) { + set_config_complete(p_cdc, false); + } +} + +// return false if there is no active transfer +static bool set_line_state_on_enum(cdch_interface_t *p_cdc, tuh_xfer_t *xfer) { + enum { + ENUM_SET_LINE_CODING = 0, + ENUM_SET_LINE_CONTROL, + ENUM_SET_LINE_COMPLETE, + }; + #ifdef CFG_TUH_CDC_LINE_CODING_ON_ENUM + const uint8_t idx = get_idx_by_ptr(p_cdc); + #endif + const uintptr_t state = xfer->user_data; + + switch (state) { + case ENUM_SET_LINE_CODING: { + #ifdef CFG_TUH_CDC_LINE_CODING_ON_ENUM + #if CFG_TUH_CDC_CH34X + // ch34x already set line coding in serial init + if (p_cdc->serial_drid != SERIAL_DRIVER_CH34X) + #endif + { + const cdc_line_coding_t line_coding = (cdc_line_coding_t) CFG_TUH_CDC_LINE_CODING_ON_ENUM; + TU_ASSERT(tuh_cdc_set_line_coding(idx, &line_coding, + cdch_process_line_state_on_enum, ENUM_SET_LINE_CONTROL)); + break; + } + #endif + TU_ATTR_FALLTHROUGH; + } + + case ENUM_SET_LINE_CONTROL: + #ifdef CFG_TUH_CDC_LINE_CONTROL_ON_ENUM + TU_ASSERT(tuh_cdc_set_control_line_state(idx, CFG_TUH_CDC_LINE_CONTROL_ON_ENUM, + cdch_process_line_state_on_enum, ENUM_SET_LINE_COMPLETE)); + break; + #else + TU_ATTR_FALLTHROUGH; + #endif + + case ENUM_SET_LINE_COMPLETE: + set_config_complete(p_cdc, true); + break; + + default: + return false; + } + + return true; +} + +static void cdch_process_line_state_on_enum(tuh_xfer_t *xfer) { + cdch_interface_t *p_cdc = get_itf_by_xfer(xfer); + TU_ASSERT(p_cdc && p_cdc->serial_drid < SERIAL_DRIVER_COUNT,); + if (xfer->result != XFER_RESULT_SUCCESS || !set_line_state_on_enum(p_cdc, xfer)) { + set_config_complete(p_cdc, false); + } +} + + +static void cdch_internal_control_complete(tuh_xfer_t *xfer) { + cdch_interface_t *p_cdc = get_itf_by_xfer(xfer); + TU_ASSERT(p_cdc && p_cdc->serial_drid < SERIAL_DRIVER_COUNT,); + TU_LOG_DRV(" request result = %u\r\n", xfer->result); + const cdch_serial_driver_t *driver = &serial_drivers[p_cdc->serial_drid]; + driver->request_complete(p_cdc, xfer); + + // Invoke application callback + xfer->complete_cb = p_cdc->user_complete_cb; + if (xfer->complete_cb != NULL) { + xfer->complete_cb(xfer); + } +} + +static void cdch_set_line_coding_stage1_baudrate_complete(tuh_xfer_t *xfer) { + cdch_interface_t *p_cdc = get_itf_by_xfer(xfer); + TU_ASSERT(p_cdc && p_cdc->serial_drid < SERIAL_DRIVER_COUNT,); + TU_LOG_DRV(" stage1 set baudrate result = %u\r\n", xfer->result); + const cdch_serial_driver_t *driver = &serial_drivers[p_cdc->serial_drid]; + + if (xfer->result == XFER_RESULT_SUCCESS) { + p_cdc->line.coding.bit_rate = p_cdc->requested_line.coding.bit_rate; // update baudrate + TU_ASSERT(driver->set_data_format(p_cdc, cdch_set_line_coding_stage2_data_format_complete, xfer->user_data),); + } else { + xfer->complete_cb = p_cdc->user_complete_cb; + if (xfer->complete_cb != NULL) { + xfer->complete_cb(xfer); + } + } +} + +static void cdch_set_line_coding_stage2_data_format_complete(tuh_xfer_t *xfer) { + cdch_interface_t *p_cdc = get_itf_by_xfer(xfer); + TU_ASSERT(p_cdc && p_cdc->serial_drid < SERIAL_DRIVER_COUNT,); + TU_LOG_DRV(" stage2 set data format result = %u\r\n", xfer->result); + + if (xfer->result == XFER_RESULT_SUCCESS) { + p_cdc->line.coding = p_cdc->requested_line.coding; // update data format + } + + xfer->complete_cb = p_cdc->user_complete_cb; + if (xfer->complete_cb != NULL) { + xfer->complete_cb(xfer); + } +} + +//--------------------------------------------------------------------+ +// ACM +//--------------------------------------------------------------------+ + +// internal control complete to update state such as line state, encoding +static void acm_internal_control_complete(cdch_interface_t *p_cdc, tuh_xfer_t *xfer) { + TU_VERIFY (xfer->result == XFER_RESULT_SUCCESS,); + const tusb_control_request_t * setup = xfer->setup; + + switch (setup->bRequest) { + case CDC_REQUEST_SET_CONTROL_LINE_STATE: + p_cdc->line.control_state = p_cdc->requested_line.control_state; + break; + + case CDC_REQUEST_SET_LINE_CODING: + p_cdc->line.coding = p_cdc->requested_line.coding; + break; + + default: + break; // unknown request + } +} + +static bool acm_set_control_line_state(cdch_interface_t *p_cdc, tuh_xfer_cb_t complete_cb, uintptr_t user_data) { + TU_VERIFY(p_cdc->acm.capability.support_line_request != 0); + + const tusb_control_request_t request = { + .bmRequestType_bit = { + .recipient = TUSB_REQ_RCPT_INTERFACE, + .type = TUSB_REQ_TYPE_CLASS, + .direction = TUSB_DIR_OUT + }, + .bRequest = CDC_REQUEST_SET_CONTROL_LINE_STATE, + .wValue = tu_htole16((uint16_t) p_cdc->requested_line.control_state.value), + .wIndex = tu_htole16((uint16_t) p_cdc->bInterfaceNumber), + .wLength = 0 + }; + + tuh_xfer_t xfer = { + .daddr = p_cdc->daddr, + .ep_addr = 0, + .setup = &request, + .buffer = NULL, + .complete_cb = complete_cb, + .user_data = user_data + }; + + return tuh_control_xfer(&xfer); +} + +static bool acm_set_line_coding(cdch_interface_t *p_cdc, tuh_xfer_cb_t complete_cb, uintptr_t user_data) { + TU_VERIFY(p_cdc->acm.capability.support_line_request != 0); + TU_VERIFY((p_cdc->requested_line.coding.data_bits >= 5 && p_cdc->requested_line.coding.data_bits <= 8) || + p_cdc->requested_line.coding.data_bits == 16); + + tusb_control_request_t const request = { + .bmRequestType_bit = { + .recipient = TUSB_REQ_RCPT_INTERFACE, + .type = TUSB_REQ_TYPE_CLASS, + .direction = TUSB_DIR_OUT + }, + .bRequest = CDC_REQUEST_SET_LINE_CODING, + .wValue = 0, + .wIndex = tu_htole16((uint16_t) p_cdc->bInterfaceNumber), + .wLength = tu_htole16((uint16_t) sizeof(cdc_line_coding_t)) + }; + + // use usbh enum buf to hold line coding since user line_coding variable does not live long enough + uint8_t *enum_buf = usbh_get_enum_buf(); + memcpy(enum_buf, &p_cdc->requested_line.coding, sizeof(cdc_line_coding_t)); + + tuh_xfer_t xfer = { + .daddr = p_cdc->daddr, + .ep_addr = 0, + .setup = &request, + .buffer = enum_buf, + .complete_cb = complete_cb, + .user_data = user_data + }; + + return tuh_control_xfer(&xfer); +} + +//------------- Enumeration -------------// +enum { + CONFIG_ACM_COMPLETE = 0 +}; + +static uint16_t acm_open(uint8_t daddr, const tusb_desc_interface_t *itf_desc, uint16_t max_len) { + const uint8_t *p_desc = (const uint8_t *)itf_desc; + const uint8_t *desc_end = p_desc + max_len; + + cdch_interface_t *p_cdc = make_new_itf(daddr, itf_desc); + TU_VERIFY(p_cdc, 0); + p_cdc->serial_drid = SERIAL_DRIVER_ACM; + + //------------- Control Interface -------------// + p_desc = tu_desc_next(p_desc); + + // Communication Functional Descriptors + while ((p_desc < desc_end) && (TUSB_DESC_CS_INTERFACE == tu_desc_type(p_desc))) { + if (CDC_FUNC_DESC_ABSTRACT_CONTROL_MANAGEMENT == cdc_functional_desc_typeof(p_desc)) { + // save ACM bmCapabilities + p_cdc->acm.capability = ((cdc_desc_func_acm_t const *) p_desc)->bmCapabilities; + } + + p_desc = tu_desc_next(p_desc); + } + + // Open notification endpoint of control interface if any + if (itf_desc->bNumEndpoints == 1) { + TU_ASSERT(TUSB_DESC_ENDPOINT == tu_desc_type(p_desc), 0); + const tusb_desc_endpoint_t *desc_ep = (const tusb_desc_endpoint_t *)p_desc; + TU_ASSERT(tuh_edpt_open(daddr, desc_ep), 0); + p_cdc->ep_notif = desc_ep->bEndpointAddress; + + p_desc = tu_desc_next(p_desc); + } + + //------------- Data Interface (if any) -------------// + if (TUSB_DESC_INTERFACE == tu_desc_type(p_desc)) { + const tusb_desc_interface_t *data_itf = (const tusb_desc_interface_t *)p_desc; + if (data_itf->bInterfaceClass == TUSB_CLASS_CDC_DATA) { + p_desc = tu_desc_next(p_desc); // next to endpoint descriptor + + // data endpoints expected to be in pairs + TU_ASSERT(open_ep_stream_pair(p_cdc, (const tusb_desc_endpoint_t *)p_desc), 0); + p_desc += data_itf->bNumEndpoints * sizeof(tusb_desc_endpoint_t); + } + } + + return (uint16_t)((uintptr_t)p_desc - (uintptr_t)itf_desc); +} + +static bool acm_process_set_config(cdch_interface_t *p_cdc, tuh_xfer_t *xfer) { + TU_ASSERT(xfer->result == XFER_RESULT_SUCCESS); + (void) p_cdc; + const uintptr_t state = xfer->user_data; + + switch (state) { + case CONFIG_ACM_COMPLETE: { + xfer->user_data = 0; // kick-off set line state on enum + cdch_process_line_state_on_enum(xfer); + break; + } + + default: + return false; // invalid state + } + + return true; +} + +//--------------------------------------------------------------------+ +// FTDI +//--------------------------------------------------------------------+ +#if CFG_TUH_CDC_FTDI + +static bool ftdi_determine_type(cdch_interface_t *p_cdc); +static uint32_t ftdi_get_divisor(cdch_interface_t *p_cdc); + +//------------- Control Request -------------// + +// set request without data +static bool ftdi_set_request(cdch_interface_t *p_cdc, uint8_t request, uint8_t requesttype, + uint16_t value, uint16_t index, tuh_xfer_cb_t complete_cb, uintptr_t user_data) { + tusb_control_request_t const request_setup = { + .bmRequestType = requesttype, + .bRequest = request, + .wValue = tu_htole16(value), + .wIndex = tu_htole16(index), + .wLength = 0 + }; + + tuh_xfer_t xfer = { + .daddr = p_cdc->daddr, + .ep_addr = 0, + .setup = &request_setup, + .buffer = NULL, + .complete_cb = complete_cb, + .user_data = user_data + }; + + return tuh_control_xfer(&xfer); +} + +#ifdef CFG_TUH_CDC_FTDI_LATENCY +static int8_t ftdi_write_latency_timer(cdch_interface_t * p_cdc, uint16_t latency, + tuh_xfer_cb_t complete_cb, uintptr_t user_data) { + if (p_cdc->ftdi.chip_type == FTDI_SIO /* || p_cdc->ftdi.chip_type == FT232A */ ) + return FTDI_NOT_POSSIBLE; + return ftdi_set_request(p_cdc, FTDI_SIO_SET_LATENCY_TIMER_REQUEST, FTDI_SIO_SET_LATENCY_TIMER_REQUEST_TYPE, + latency, p_cdc->ftdi.channel, complete_cb, user_data) ? FTDI_REQUESTED : FTDI_FAIL; +} +#endif + +static inline bool ftdi_sio_reset(cdch_interface_t *p_cdc, tuh_xfer_cb_t complete_cb, uintptr_t user_data) { + return ftdi_set_request(p_cdc, FTDI_SIO_RESET_REQUEST, FTDI_SIO_RESET_REQUEST_TYPE, FTDI_SIO_RESET_SIO, + p_cdc->ftdi.channel, complete_cb, user_data); +} + + +//------------- Driver API -------------// + +// internal control complete to update state such as line state, line_coding +static void ftdi_internal_control_complete(cdch_interface_t* p_cdc, tuh_xfer_t *xfer) { + const tusb_control_request_t * setup = xfer->setup; + if (xfer->result == XFER_RESULT_SUCCESS) { + if (setup->bRequest == FTDI_SIO_SET_MODEM_CTRL_REQUEST && + setup->bmRequestType == FTDI_SIO_SET_MODEM_CTRL_REQUEST_TYPE ) { + p_cdc->line.control_state = p_cdc->requested_line.control_state; + } + if (setup->bRequest == FTDI_SIO_SET_DATA_REQUEST && + setup->bmRequestType == FTDI_SIO_SET_DATA_REQUEST_TYPE ) { + p_cdc->line.coding.stop_bits = p_cdc->requested_line.coding.stop_bits; + p_cdc->line.coding.parity = p_cdc->requested_line.coding.parity; + p_cdc->line.coding.data_bits = p_cdc->requested_line.coding.data_bits; + } + if (setup->bRequest == FTDI_SIO_SET_BAUDRATE_REQUEST && + setup->bmRequestType == FTDI_SIO_SET_BAUDRATE_REQUEST_TYPE ) { + p_cdc->line.coding.bit_rate = p_cdc->requested_line.coding.bit_rate; + } + } +} + +static bool ftdi_set_data_format(cdch_interface_t *p_cdc, tuh_xfer_cb_t complete_cb, uintptr_t user_data) { + TU_VERIFY(p_cdc->requested_line.coding.data_bits >= 7 && p_cdc->requested_line.coding.data_bits <= 8, 0); + uint16_t value = (uint16_t) ((p_cdc->requested_line.coding.data_bits & 0xfUL) | // data bit quantity is stored in bits 0-3 + (p_cdc->requested_line.coding.parity & 0x7UL) << 8 | // parity is stored in bits 8-10, same coding + (p_cdc->requested_line.coding.stop_bits & 0x3UL) << 11); // stop bits quantity is stored in bits 11-12, same coding + // not each FTDI supports 1.5 stop bits + return ftdi_set_request(p_cdc, FTDI_SIO_SET_DATA_REQUEST, FTDI_SIO_SET_DATA_REQUEST_TYPE, + value, p_cdc->ftdi.channel, complete_cb, user_data); +} + +static bool ftdi_set_baudrate(cdch_interface_t *p_cdc, tuh_xfer_cb_t complete_cb, uintptr_t user_data) { + uint32_t index_value = ftdi_get_divisor(p_cdc); + TU_VERIFY(index_value != 0); + uint16_t value = (uint16_t) index_value; + uint16_t index = (uint16_t) (index_value >> 16); + if (p_cdc->ftdi.channel != 0) { + index = (uint16_t) ((index << 8) | p_cdc->ftdi.channel); + } + + return ftdi_set_request(p_cdc, FTDI_SIO_SET_BAUDRATE_REQUEST, FTDI_SIO_SET_BAUDRATE_REQUEST_TYPE, + value, index, complete_cb, user_data); +} + +static bool ftdi_set_modem_ctrl(cdch_interface_t *p_cdc, tuh_xfer_cb_t complete_cb, uintptr_t user_data) { + uint16_t line_state = (uint16_t) ((p_cdc->requested_line.control_state.dtr ? FTDI_SIO_SET_DTR_HIGH : FTDI_SIO_SET_DTR_LOW) | + (p_cdc->requested_line.control_state.rts ? FTDI_SIO_SET_RTS_HIGH : FTDI_SIO_SET_RTS_LOW)); + return ftdi_set_request(p_cdc, FTDI_SIO_SET_MODEM_CTRL_REQUEST, FTDI_SIO_SET_MODEM_CTRL_REQUEST_TYPE, + line_state, p_cdc->ftdi.channel, complete_cb ? cdch_internal_control_complete : NULL, user_data); +} + +//------------- Enumeration -------------// +enum { + CONFIG_FTDI_DETERMINE_TYPE = 0, + CONFIG_FTDI_WRITE_LATENCY, + CONFIG_FTDI_SIO_RESET, + CONFIG_FTDI_FLOW_CONTROL, + CONFIG_FTDI_COMPLETE +}; + +static uint16_t ftdi_open(uint8_t daddr, const tusb_desc_interface_t *itf_desc, uint16_t max_len) { + // FTDI Interface includes 1 vendor interface + 2 bulk endpoints + TU_VERIFY(itf_desc->bInterfaceSubClass == 0xff && itf_desc->bInterfaceProtocol == 0xff && + itf_desc->bNumEndpoints == 2, + 0); + const uint16_t drv_len = + (uint16_t)(sizeof(tusb_desc_interface_t) + itf_desc->bNumEndpoints * sizeof(tusb_desc_endpoint_t)); + TU_VERIFY(drv_len <= max_len, 0); + + cdch_interface_t *p_cdc = make_new_itf(daddr, itf_desc); + TU_VERIFY(p_cdc, 0); + + p_cdc->serial_drid = SERIAL_DRIVER_FTDI; + + // endpoint pair + const tusb_desc_endpoint_t *desc_ep = (const tusb_desc_endpoint_t *)tu_desc_next(itf_desc); + + /* NOTE: Some users have programmed FT232R/FT245R devices + * with an endpoint size of 0 !!! */ + TU_ASSERT(desc_ep->wMaxPacketSize != 0, 0); + + TU_ASSERT(open_ep_stream_pair(p_cdc, desc_ep), 0); + + return drv_len; +} + +static bool ftdi_process_set_config(cdch_interface_t *p_cdc, tuh_xfer_t *xfer) { + TU_ASSERT(xfer->result == XFER_RESULT_SUCCESS); + const uintptr_t state = xfer->user_data; + switch (state) { + // from here sequence overtaken from Linux Kernel function ftdi_port_probe() + case CONFIG_FTDI_DETERMINE_TYPE: + // determine type + TU_ASSERT(ftdi_determine_type(p_cdc)); + TU_ATTR_FALLTHROUGH; + + case CONFIG_FTDI_WRITE_LATENCY: + #ifdef CFG_TUH_CDC_FTDI_LATENCY + int8_t result = ftdi_write_latency_timer(p_cdc, CFG_TUH_CDC_FTDI_LATENCY, ftdi_process_config, + CONFIG_FTDI_SIO_RESET); + TU_ASSERT(result != FTDI_FAIL); + if (result == FTDI_REQUESTED) { + break; + }// else FTDI_NOT_POSSIBLE => continue directly with next state + #endif + TU_ATTR_FALLTHROUGH; + + // from here sequence overtaken from Linux Kernel function ftdi_open() + case CONFIG_FTDI_SIO_RESET: + TU_ASSERT(ftdi_sio_reset(p_cdc, cdch_process_set_config, CONFIG_FTDI_FLOW_CONTROL)); + break; + + case CONFIG_FTDI_FLOW_CONTROL: + // disable flow control + TU_ASSERT(ftdi_set_request(p_cdc, FTDI_SIO_SET_FLOW_CTRL_REQUEST, FTDI_SIO_SET_FLOW_CTRL_REQUEST_TYPE, FTDI_SIO_DISABLE_FLOW_CTRL, + p_cdc->ftdi.channel, cdch_process_set_config, CONFIG_FTDI_COMPLETE)); + break; + + case CONFIG_FTDI_COMPLETE: { + xfer->user_data = 0; // kick-off set line state on enum + cdch_process_line_state_on_enum(xfer); + break; + } + + default: + return false; + } + + return true; +} + +//------------- Helper -------------// + +static bool ftdi_determine_type(cdch_interface_t *p_cdc) { + tusb_desc_device_t desc_dev; + TU_VERIFY(tuh_descriptor_get_device_local(p_cdc->daddr, &desc_dev)); + uint16_t const version = desc_dev.bcdDevice; + uint8_t const itf_num = p_cdc->bInterfaceNumber; + + p_cdc->ftdi.chip_type = FTDI_UNKNOWN; + + /* Assume Hi-Speed type */ + p_cdc->ftdi.channel = CHANNEL_A + itf_num; + + switch (version) { + case 0x200: + // FT232A not supported to keep it simple (no extra _read_latency_timer()) not testable + // p_cdc->ftdi.chip_type = FT232A; + // p_cdc->ftdi.baud_base = 48000000 / 2; + // p_cdc->ftdi.channel = 0; + // /* + // * FT232B devices have a bug where bcdDevice gets set to 0x200 + // * when iSerialNumber is 0. Assume it is an FT232B in case the + // * latency timer is readable. + // */ + // if (desc->iSerialNumber == 0 && + // _read_latency_timer(port) >= 0) { + // p_cdc->ftdi.chip_type = FTDI_FT232B; + // } + break; + + case 0x400 : p_cdc->ftdi.chip_type = FTDI_FT232B; p_cdc->ftdi.channel = 0; break; + case 0x500 : p_cdc->ftdi.chip_type = FTDI_FT2232C; break; + case 0x600 : p_cdc->ftdi.chip_type = FTDI_FT232R; p_cdc->ftdi.channel = 0; break; + case 0x700 : p_cdc->ftdi.chip_type = FTDI_FT2232H; break; + case 0x800 : p_cdc->ftdi.chip_type = FTDI_FT4232H; break; + case 0x900 : p_cdc->ftdi.chip_type = FTDI_FT232H; break; + case 0x1000: p_cdc->ftdi.chip_type = FTDI_FTX; break; + case 0x2800: p_cdc->ftdi.chip_type = FTDI_FT2233HP; break; + case 0x2900: p_cdc->ftdi.chip_type = FTDI_FT4233HP; break; + case 0x3000: p_cdc->ftdi.chip_type = FTDI_FT2232HP; break; + case 0x3100: p_cdc->ftdi.chip_type = FTDI_FT4232HP; break; + case 0x3200: p_cdc->ftdi.chip_type = FTDI_FT233HP; break; + case 0x3300: p_cdc->ftdi.chip_type = FTDI_FT232HP; break; + case 0x3600: p_cdc->ftdi.chip_type = FTDI_FT4232HA; break; + + default: + if (version < 0x200) { + p_cdc->ftdi.chip_type = FTDI_SIO; + p_cdc->ftdi.channel = 0; + } + break; + } + + #if CFG_TUSB_DEBUG >= CFG_TUH_CDC_LOG_LEVEL + const char * ftdi_chip_name[] = { FTDI_CHIP_NAMES }; + TU_LOG_CDC(p_cdc, "%s detected (bcdDevice = 0x%04x)", + ftdi_chip_name[p_cdc->ftdi.chip_type], version); + #endif + + return (p_cdc->ftdi.chip_type != FTDI_UNKNOWN); +} + +// FT232A not supported +//static uint32_t ftdi_232am_baud_base_to_divisor(uint32_t baud, uint32_t base) +//{ +// uint32_t divisor; +// /* divisor shifted 3 bits to the left */ +// uint32_t divisor3 = DIV_ROUND_CLOSEST(base, 2 * baud); +// if ((divisor3 & 0x7) == 7) +// divisor3++; /* round x.7/8 up to x+1 */ +// divisor = divisor3 >> 3; +// divisor3 &= 0x7; +// if (divisor3 == 1) +// divisor |= 0xc000; /* +0.125 */ +// else if (divisor3 >= 4) +// divisor |= 0x4000; /* +0.5 */ +// else if (divisor3 != 0) +// divisor |= 0x8000; /* +0.25 */ +// else if (divisor == 1) +// divisor = 0; /* special case for maximum baud rate */ +// return divisor; +//} + +// FT232A not supported +//static inline uint32_t ftdi_232am_baud_to_divisor(uint32_t baud) +//{ +// return ftdi_232am_baud_base_to_divisor(baud, (uint32_t) 48000000); +//} + +static uint32_t ftdi_232bm_baud_base_to_divisor(uint32_t baud, uint32_t base) { + uint8_t divfrac[8] = {0, 3, 2, 4, 1, 5, 6, 7}; + uint32_t divisor; + /* divisor shifted 3 bits to the left */ + uint32_t divisor3 = tu_div_round_nearest(base, 2 * baud); + divisor = divisor3 >> 3; + divisor |= (uint32_t) divfrac[divisor3 & 0x7] << 14; + /* Deal with special cases for highest baud rates. */ + if (divisor == 1) /* 1.0 */ { + divisor = 0; + } else if (divisor == 0x4001) /* 1.5 */ { + divisor = 1; + } else { + // nothing to do + } + return divisor; +} + +static inline uint32_t ftdi_232bm_baud_to_divisor(uint32_t baud) { + return ftdi_232bm_baud_base_to_divisor(baud, 48000000); +} + +static uint32_t ftdi_2232h_baud_base_to_divisor(uint32_t baud, uint32_t base) { + static const unsigned char divfrac[8] = {0, 3, 2, 4, 1, 5, 6, 7}; + uint32_t divisor; + uint32_t divisor3; + + /* hi-speed baud rate is 10-bit sampling instead of 16-bit */ + divisor3 = tu_div_round_nearest(8 * base, 10 * baud); + + divisor = divisor3 >> 3; + divisor |= (uint32_t) divfrac[divisor3 & 0x7] << 14; + /* Deal with special cases for highest baud rates. */ + if (divisor == 1) /* 1.0 */ { + divisor = 0; + } else if (divisor == 0x4001) /* 1.5 */ { + divisor = 1; + } else { + // nothing to do + } + + /* Set this bit to turn off a divide by 2.5 on baud rate generator + * This enables baud rates up to 12Mbaud but cannot reach below 1200 + * baud with this bit set */ + divisor |= 0x00020000; + return divisor; +} + +static inline uint32_t ftdi_2232h_baud_to_divisor(uint32_t baud) { + return ftdi_2232h_baud_base_to_divisor(baud, (uint32_t) 120000000); +} + +static inline uint32_t ftdi_get_divisor(cdch_interface_t *p_cdc) { + uint32_t baud = p_cdc->requested_line.coding.bit_rate; + uint32_t div_value = 0; + TU_VERIFY(baud != 0); + + switch (p_cdc->ftdi.chip_type) { + case FTDI_UNKNOWN: + return 0; + case FTDI_SIO: + switch (baud) { + case 300: div_value = ftdi_sio_b300; break; + case 600: div_value = ftdi_sio_b600; break; + case 1200: div_value = ftdi_sio_b1200; break; + case 2400: div_value = ftdi_sio_b2400; break; + case 4800: div_value = ftdi_sio_b4800; break; + case 9600: div_value = ftdi_sio_b9600; break; + case 19200: div_value = ftdi_sio_b19200; break; + case 38400: div_value = ftdi_sio_b38400; break; + case 57600: div_value = ftdi_sio_b57600; break; + case 115200: div_value = ftdi_sio_b115200; break; + default: + // Baudrate not supported + return 0; + break; + } + break; + // FT232A not supported + // case FT232A: + // if (baud <= 3000000) { + // div_value = ftdi_232am_baud_to_divisor(baud); + // } else { + // // Baud rate too high! + // baud = 9600; + // div_value = ftdi_232am_baud_to_divisor(9600); + // div_okay = false; + // } + // break; + case FTDI_FT232B: + case FTDI_FT2232C: + case FTDI_FT232R: + case FTDI_FTX: + TU_VERIFY(baud <= 3000000); // else Baud rate too high! + div_value = ftdi_232bm_baud_to_divisor(baud); + break; + case FTDI_FT232H: + case FTDI_FT2232H: + case FTDI_FT4232H: + case FTDI_FT4232HA: + case FTDI_FT232HP: + case FTDI_FT233HP: + case FTDI_FT2232HP: + case FTDI_FT2233HP: + case FTDI_FT4232HP: + case FTDI_FT4233HP: + default: + TU_VERIFY(baud <= 12000000); // else Baud rate too high! + if (baud >= 1200) { + div_value = ftdi_2232h_baud_to_divisor(baud); + } else { + div_value = ftdi_232bm_baud_to_divisor(baud); + } + break; + } + + TU_LOG_CDC(p_cdc, "Baudrate divisor = 0x%lu", div_value); + + return div_value; +} + +#endif + +//--------------------------------------------------------------------+ +// CP210x +//--------------------------------------------------------------------+ +#if CFG_TUH_CDC_CP210X + +//------------- Control Request -------------// + +static bool cp210x_set_request(cdch_interface_t * p_cdc, uint8_t command, uint16_t value, + uint8_t * buffer, uint16_t length, tuh_xfer_cb_t complete_cb, uintptr_t user_data) { + tusb_control_request_t const request = { + .bmRequestType_bit = { + .recipient = TUSB_REQ_RCPT_INTERFACE, + .type = TUSB_REQ_TYPE_VENDOR, + .direction = TUSB_DIR_OUT + }, + .bRequest = command, + .wValue = tu_htole16(value), + .wIndex = tu_htole16((uint16_t) p_cdc->bInterfaceNumber), + .wLength = tu_htole16(length) + }; + + // use usbh enum buf since application variable does not live long enough + uint8_t * enum_buf = NULL; + + if (buffer && length > 0) { + enum_buf = usbh_get_enum_buf(); + tu_memcpy_s(enum_buf, CFG_TUH_ENUMERATION_BUFSIZE, buffer, length); + } + + tuh_xfer_t xfer = { + .daddr = p_cdc->daddr, + .ep_addr = 0, + .setup = &request, + .buffer = enum_buf, + .complete_cb = complete_cb, + .user_data = user_data + }; + + return tuh_control_xfer(&xfer); +} + +TU_ATTR_ALWAYS_INLINE static inline bool cp210x_ifc_enable(cdch_interface_t *p_cdc, uint16_t enabled, tuh_xfer_cb_t complete_cb, uintptr_t user_data) { + return cp210x_set_request(p_cdc, CP210X_IFC_ENABLE, enabled, NULL, 0, complete_cb, user_data); +} + +TU_ATTR_ALWAYS_INLINE static inline bool cp210x_set_mhs(cdch_interface_t *p_cdc, tuh_xfer_cb_t complete_cb, uintptr_t user_data) { + // CP210x has the same bit coding + return cp210x_set_request(p_cdc, CP210X_SET_MHS, + (uint16_t) (CP210X_CONTROL_WRITE_DTR | CP210X_CONTROL_WRITE_RTS | p_cdc->requested_line.control_state.value), + NULL, 0, complete_cb, user_data); +} + +//------------- Driver API -------------// + +// internal control complete to update state such as line state, encoding +static void cp210x_internal_control_complete(cdch_interface_t *p_cdc, tuh_xfer_t *xfer) { + TU_VERIFY(xfer->result == XFER_RESULT_SUCCESS,); + switch (xfer->setup->bRequest) { + case CP210X_SET_MHS: + p_cdc->line.control_state = p_cdc->requested_line.control_state; + break; + + case CP210X_SET_LINE_CTL: + p_cdc->line.coding.stop_bits = p_cdc->requested_line.coding.stop_bits; + p_cdc->line.coding.parity = p_cdc->requested_line.coding.parity; + p_cdc->line.coding.data_bits = p_cdc->requested_line.coding.data_bits; + break; + + case CP210X_SET_BAUDRATE: + p_cdc->line.coding.bit_rate = p_cdc->requested_line.coding.bit_rate; + break; + + default: + break; // unsupported request + } +} + +static bool cp210x_set_baudrate(cdch_interface_t *p_cdc, tuh_xfer_cb_t complete_cb, uintptr_t user_data) { + // Not every baud rate is supported. See datasheets and AN205 "CP210x Baud Rate Support" + uint32_t baud_le = tu_htole32(p_cdc->requested_line.coding.bit_rate); + return cp210x_set_request(p_cdc, CP210X_SET_BAUDRATE, 0, (uint8_t *) &baud_le, 4, complete_cb, user_data); +} + +static bool cp210x_set_data_format(cdch_interface_t *p_cdc, tuh_xfer_cb_t complete_cb, uintptr_t user_data) { + TU_VERIFY(p_cdc->requested_line.coding.data_bits >= 5 && p_cdc->requested_line.coding.data_bits <= 8, 0); + uint16_t lcr = (uint16_t) ((p_cdc->requested_line.coding.data_bits & 0xfUL) << 8 | // data bit quantity is stored in bits 8-11 + (p_cdc->requested_line.coding.parity & 0xfUL) << 4 | // parity is stored in bits 4-7, same coding + (p_cdc->requested_line.coding.stop_bits & 0xfUL)); // parity is stored in bits 0-3, same coding + + return cp210x_set_request(p_cdc, CP210X_SET_LINE_CTL, lcr, NULL, 0, complete_cb, user_data); +} + +static bool cp210x_set_modem_ctrl(cdch_interface_t *p_cdc, tuh_xfer_cb_t complete_cb, uintptr_t user_data) { + return cp210x_set_mhs(p_cdc, complete_cb, user_data); +} + +//------------- Enumeration -------------// + +enum { + CONFIG_CP210X_IFC_ENABLE = 0, + CONFIG_CP210X_COMPLETE +}; + +static uint16_t cp210x_open(uint8_t daddr, const tusb_desc_interface_t *itf_desc, uint16_t max_len) { + // CP210x Interface includes 1 vendor interface + 2 bulk endpoints + TU_VERIFY(itf_desc->bInterfaceSubClass == 0 && itf_desc->bInterfaceProtocol == 0 && itf_desc->bNumEndpoints == 2, 0); + const uint16_t drv_len = + (uint16_t)(sizeof(tusb_desc_interface_t) + itf_desc->bNumEndpoints * sizeof(tusb_desc_endpoint_t)); + TU_VERIFY(drv_len <= max_len, 0); + + cdch_interface_t *p_cdc = make_new_itf(daddr, itf_desc); + TU_VERIFY(p_cdc, 0); + + p_cdc->serial_drid = SERIAL_DRIVER_CP210X; + + // data endpoints expected to be in pairs + const tusb_desc_endpoint_t *desc_ep = (const tusb_desc_endpoint_t *)tu_desc_next(itf_desc); + TU_ASSERT(open_ep_stream_pair(p_cdc, desc_ep)); + + return drv_len; +} + +static bool cp210x_process_set_config(cdch_interface_t *p_cdc, tuh_xfer_t *xfer) { + TU_ASSERT(xfer->result == XFER_RESULT_SUCCESS); + const uintptr_t state = xfer->user_data; + + switch (state) { + case CONFIG_CP210X_IFC_ENABLE: + TU_ASSERT(cp210x_ifc_enable(p_cdc, CP210X_UART_ENABLE, cdch_process_set_config, CONFIG_CP210X_COMPLETE)); + break; + + case CONFIG_CP210X_COMPLETE: + xfer->user_data = 0;// kick-off set line state on enum + cdch_process_line_state_on_enum(xfer); + break; + + default: + return false; + } + + return true; +} + +#endif + +//--------------------------------------------------------------------+ +// CH34x (CH340 & CH341) +//--------------------------------------------------------------------+ + +#if CFG_TUH_CDC_CH34X + +static uint8_t ch34x_get_lcr(cdch_interface_t *p_cdc); +static uint16_t ch34x_get_divisor_prescaler(cdch_interface_t *p_cdc); + +//------------- Control Request -------------// + +static bool ch34x_set_request(cdch_interface_t *p_cdc, uint8_t direction, uint8_t request, + uint16_t value, uint16_t index, uint8_t *buffer, uint16_t length, + tuh_xfer_cb_t complete_cb, uintptr_t user_data) { + tusb_control_request_t const request_setup = { + .bmRequestType_bit = { + .recipient = TUSB_REQ_RCPT_DEVICE, + .type = TUSB_REQ_TYPE_VENDOR, + .direction = direction & 0x01u + }, + .bRequest = request, + .wValue = tu_htole16(value), + .wIndex = tu_htole16(index), + .wLength = tu_htole16(length) + }; + + // use usbh enum buf since application variable does not live long enough + uint8_t *enum_buf = NULL; + + if (buffer && length > 0) { + enum_buf = usbh_get_enum_buf(); + if (direction == TUSB_DIR_OUT) { + tu_memcpy_s(enum_buf, CFG_TUH_ENUMERATION_BUFSIZE, buffer, length); + } + } + + tuh_xfer_t xfer = { + .daddr = p_cdc->daddr, + .ep_addr = 0, + .setup = &request_setup, + .buffer = enum_buf, + .complete_cb = complete_cb, + .user_data = user_data + }; + + return tuh_control_xfer(&xfer); +} + +TU_ATTR_ALWAYS_INLINE static inline bool ch34x_control_out(cdch_interface_t *p_cdc, uint8_t request, uint16_t value, uint16_t index, + tuh_xfer_cb_t complete_cb, uintptr_t user_data) { + return ch34x_set_request(p_cdc, TUSB_DIR_OUT, request, value, index, NULL, 0, complete_cb, user_data); +} + +TU_ATTR_ALWAYS_INLINE static inline bool ch34x_control_in(cdch_interface_t *p_cdc, uint8_t request, uint16_t value, uint16_t index, + uint8_t *buffer, uint16_t buffersize, tuh_xfer_cb_t complete_cb, uintptr_t user_data) { + return ch34x_set_request(p_cdc, TUSB_DIR_IN, request, value, index, buffer, buffersize, + complete_cb, user_data); +} + +TU_ATTR_ALWAYS_INLINE static inline bool ch34x_write_reg(cdch_interface_t *p_cdc, uint16_t reg, uint16_t reg_value, + tuh_xfer_cb_t complete_cb, uintptr_t user_data) { + return ch34x_control_out(p_cdc, CH34X_REQ_WRITE_REG, reg, reg_value, complete_cb, user_data); +} + +//static bool ch34x_read_reg_request ( cdch_interface_t * p_cdc, uint16_t reg, +// uint8_t *buffer, uint16_t buffersize, tuh_xfer_cb_t complete_cb, uintptr_t user_data ) +//{ +// return ch34x_control_in ( p_cdc, CH34X_REQ_READ_REG, reg, 0, buffer, buffersize, complete_cb, user_data ); +//} + +//------------- Driver API -------------// + +// internal control complete to update state such as line state, encoding +static void ch34x_internal_control_complete(cdch_interface_t *p_cdc, tuh_xfer_t *xfer) { + TU_VERIFY(xfer->result == XFER_RESULT_SUCCESS,); + switch (xfer->setup->bRequest) { + case CH34X_REQ_WRITE_REG: + // register write request + switch (tu_le16toh(xfer->setup->wValue)) { + case CH34X_REG16_DIVISOR_PRESCALER: + // baudrate + p_cdc->line.coding.bit_rate = p_cdc->requested_line.coding.bit_rate; + break; + + case CH32X_REG16_LCR2_LCR: + // data format + p_cdc->line.coding.stop_bits = p_cdc->requested_line.coding.stop_bits; + p_cdc->line.coding.parity = p_cdc->requested_line.coding.parity; + p_cdc->line.coding.data_bits = p_cdc->requested_line.coding.data_bits; + break; + + default: + break; // unsupported + } + break; + + case CH34X_REQ_MODEM_CTRL: + p_cdc->line.control_state = p_cdc->requested_line.control_state; + break; + + default: + break; // unsupported request + } +} + +static bool ch34x_set_data_format(cdch_interface_t *p_cdc, tuh_xfer_cb_t complete_cb, uintptr_t user_data) { + const uint8_t lcr = ch34x_get_lcr(p_cdc); + TU_VERIFY(lcr > 0); + return ch34x_write_reg(p_cdc, CH32X_REG16_LCR2_LCR, lcr, complete_cb, user_data); +} + +static bool ch34x_set_baudrate(cdch_interface_t *p_cdc, tuh_xfer_cb_t complete_cb, uintptr_t user_data) { + const uint16_t div_ps = ch34x_get_divisor_prescaler(p_cdc); + TU_VERIFY(div_ps > 0); + return ch34x_write_reg(p_cdc, CH34X_REG16_DIVISOR_PRESCALER, div_ps, complete_cb, user_data); +} + +static bool ch34x_set_modem_ctrl(cdch_interface_t * p_cdc, tuh_xfer_cb_t complete_cb, uintptr_t user_data) { + // CH34x signals are inverted + uint8_t control = ~((p_cdc->requested_line.control_state.rts ? CH34X_BIT_RTS : 0) | + (p_cdc->requested_line.control_state.dtr ? CH34X_BIT_DTR : 0)); + return ch34x_control_out(p_cdc, CH34X_REQ_MODEM_CTRL, control, 0, complete_cb, user_data); +} + +//------------- Enumeration -------------// + +enum { + CONFIG_CH34X_READ_VERSION = 0, + CONFIG_CH34X_SERIAL_INIT, + CONFIG_CH34X_SPECIAL_REG_WRITE, + CONFIG_CH34X_FLOW_CONTROL, + CONFIG_CH34X_COMPLETE +}; + +static uint16_t ch34x_open(uint8_t daddr, const tusb_desc_interface_t *itf_desc, uint16_t max_len) { + // CH34x Interface includes 1 vendor interface + 2 bulk + 1 interrupt endpoints + TU_VERIFY(itf_desc->bNumEndpoints == 3, 0); + const uint16_t drv_len = + (uint16_t)(sizeof(tusb_desc_interface_t) + itf_desc->bNumEndpoints * sizeof(tusb_desc_endpoint_t)); + TU_VERIFY(drv_len <= max_len, 0); + + cdch_interface_t * p_cdc = make_new_itf(daddr, itf_desc); + TU_VERIFY(p_cdc, 0); + + p_cdc->serial_drid = SERIAL_DRIVER_CH34X; + + const tusb_desc_endpoint_t *desc_ep = (const tusb_desc_endpoint_t *)tu_desc_next(itf_desc); + + // data endpoints expected to be in pairs + TU_ASSERT(open_ep_stream_pair(p_cdc, desc_ep), 0); + desc_ep = (const tusb_desc_endpoint_t *)((uintptr_t)desc_ep + 2 * sizeof(tusb_desc_endpoint_t)); + + // Interrupt endpoint: not used for now + TU_ASSERT(TUSB_DESC_ENDPOINT == tu_desc_type(desc_ep) && TUSB_XFER_INTERRUPT == desc_ep->bmAttributes.xfer, 0); + TU_ASSERT(tuh_edpt_open(daddr, desc_ep), 0); + p_cdc->ep_notif = desc_ep->bEndpointAddress; + + return drv_len; +} + +static bool ch34x_process_set_config(cdch_interface_t *p_cdc, tuh_xfer_t *xfer) { + TU_ASSERT(xfer->result == XFER_RESULT_SUCCESS); + const uintptr_t state = xfer->user_data; + + switch (state) { + case CONFIG_CH34X_READ_VERSION: { + uint8_t* enum_buf = usbh_get_enum_buf(); + TU_ASSERT(ch34x_control_in(p_cdc, CH34X_REQ_READ_VERSION, 0, 0, enum_buf, 2, + cdch_process_set_config, CONFIG_CH34X_SERIAL_INIT)); + break; + } + + case CONFIG_CH34X_SERIAL_INIT: { + // handle version read data, set CH34x line coding (incl. baudrate) + uint8_t const version = xfer->buffer[0]; + TU_LOG_CDC(p_cdc, "Chip Version = 0x%02x", version); + // only versions >= 0x30 are tested, below 0x30 seems having other programming + // see drivers from WCH vendor, Linux kernel and FreeBSD + if (version >= 0x30) { + // init CH34x with line coding + p_cdc->requested_line.coding = (cdc_line_coding_t) CFG_TUH_CDC_LINE_CODING_ON_ENUM_CH34X; + uint16_t const div_ps = ch34x_get_divisor_prescaler(p_cdc); + uint8_t const lcr = ch34x_get_lcr(p_cdc); + TU_ASSERT(div_ps != 0 && lcr != 0); + TU_ASSERT(ch34x_control_out(p_cdc, CH34X_REQ_SERIAL_INIT, tu_u16(lcr, 0x9c), div_ps, + cdch_process_set_config, CONFIG_CH34X_SPECIAL_REG_WRITE)); + } + break; + } + + case CONFIG_CH34X_SPECIAL_REG_WRITE: + // overtake line coding and do special reg write, purpose unknown, overtaken from WCH driver + p_cdc->line.coding = (cdc_line_coding_t) CFG_TUH_CDC_LINE_CODING_ON_ENUM_CH34X; + TU_ASSERT(ch34x_write_reg(p_cdc, TU_U16(CH341_REG_0x0F, CH341_REG_0x2C), 0x0007, + cdch_process_set_config, CONFIG_CH34X_FLOW_CONTROL)); + break; + + case CONFIG_CH34X_FLOW_CONTROL: + // no hardware flow control + TU_ASSERT(ch34x_write_reg(p_cdc, TU_U16(CH341_REG_0x27, CH341_REG_0x27), 0x0000, + cdch_process_set_config, CONFIG_CH34X_COMPLETE)); + break; + + case CONFIG_CH34X_COMPLETE: + xfer->user_data = 0; // kick-off set line state on enum + cdch_process_line_state_on_enum(xfer); + break; + + default: + return false; + } + + return true; +} + +//------------- Helper -------------// + +// calculate divisor and prescaler for baudrate, return it as 16-bit combined value +static uint16_t ch34x_get_divisor_prescaler(cdch_interface_t *p_cdc) { + uint32_t const baval = p_cdc->requested_line.coding.bit_rate; + uint8_t a; + uint8_t b; + uint32_t c; + + TU_VERIFY(baval != 0 && baval <= 2000000, 0); + switch (baval) { + case 921600: + a = 0xf3; + b = 7; + break; + + case 307200: + a = 0xd9; + b = 7; + break; + + default: + if (baval > 6000000 / 255) { + b = 3; + c = 6000000; + } else if (baval > 750000 / 255) { + b = 2; + c = 750000; + } else if (baval > 93750 / 255) { + b = 1; + c = 93750; + } else { + b = 0; + c = 11719; + } + a = (uint8_t) (c / baval); + if (a == 0 || a == 0xFF) { + return 0; + } + if ((c / a - baval) > (baval - c / (a + 1))) { + a++; + } + a = (uint8_t) (256 - a); + break; + } + + // reg divisor = a, reg prescaler = b + // According to linux code we need to set bit 7 of UCHCOM_REG_BPS_PRE, + // otherwise the chip will buffer data. + return (uint16_t) ((uint16_t) a << 8 | 0x80 | b); +} + +// calculate lcr value from data coding +static uint8_t ch34x_get_lcr(cdch_interface_t *p_cdc) { + uint8_t const stop_bits = p_cdc->requested_line.coding.stop_bits; + uint8_t const parity = p_cdc->requested_line.coding.parity; + uint8_t const data_bits = p_cdc->requested_line.coding.data_bits; + + uint8_t lcr = CH34X_LCR_ENABLE_RX | CH34X_LCR_ENABLE_TX; + TU_VERIFY(data_bits >= 5 && data_bits <= 8); + lcr |= (uint8_t) (data_bits - 5); + + switch (parity) { + case CDC_LINE_CODING_PARITY_NONE: + break; + + case CDC_LINE_CODING_PARITY_ODD: + lcr |= CH34X_LCR_ENABLE_PAR; + break; + + case CDC_LINE_CODING_PARITY_EVEN: + lcr |= CH34X_LCR_ENABLE_PAR | CH34X_LCR_PAR_EVEN; + break; + + case CDC_LINE_CODING_PARITY_MARK: + lcr |= CH34X_LCR_ENABLE_PAR | CH34X_LCR_MARK_SPACE; + break; + + case CDC_LINE_CODING_PARITY_SPACE: + lcr |= CH34X_LCR_ENABLE_PAR | CH34X_LCR_MARK_SPACE | CH34X_LCR_PAR_EVEN; + break; + + default: + break; // invalid parity + } + + // 1.5 stop bits not supported + TU_VERIFY(stop_bits != CDC_LINE_CODING_STOP_BITS_1_5); + if (stop_bits == CDC_LINE_CODING_STOP_BITS_2) { + lcr |= CH34X_LCR_STOP_BITS_2; + } + + return lcr; +} + +#endif // CFG_TUH_CDC_CH34X + +//--------------------------------------------------------------------+ +// PL2303 +//--------------------------------------------------------------------+ +#if CFG_TUH_CDC_PL2303 + +static pl2303_type_t pl2303_detect_type(cdch_interface_t *p_cdc, uint8_t step); +static bool pl2303_encode_baud_rate(cdch_interface_t *p_cdc, uint8_t buf[PL2303_LINE_CODING_BAUDRATE_BUFSIZE]); + +//------------- Control Request -------------// +static bool pl2303_set_request(cdch_interface_t *p_cdc, uint8_t request, uint8_t requesttype, + uint16_t value, uint16_t index, uint8_t *buffer, uint16_t length, + tuh_xfer_cb_t complete_cb, uintptr_t user_data) { + tusb_control_request_t const request_setup = { + .bmRequestType = requesttype, + .bRequest = request, + .wValue = tu_htole16(value), + .wIndex = tu_htole16(index), + .wLength = tu_htole16(length) + }; + + // use usbh enum buf since application variable does not live long enough + uint8_t *enum_buf = NULL; + + if (buffer && length > 0) { + enum_buf = usbh_get_enum_buf(); + if (request_setup.bmRequestType_bit.direction == TUSB_DIR_OUT) { + tu_memcpy_s(enum_buf, CFG_TUH_ENUMERATION_BUFSIZE, buffer, length); + } + } + + tuh_xfer_t xfer = { + .daddr = p_cdc->daddr, + .ep_addr = 0, + .setup = &request_setup, + .buffer = enum_buf, + .complete_cb = complete_cb, + .user_data = user_data + }; + + return tuh_control_xfer(&xfer); +} + +static bool pl2303_vendor_read(cdch_interface_t *p_cdc, uint16_t value, uint8_t *buf, + tuh_xfer_cb_t complete_cb, uintptr_t user_data) { + uint8_t request = p_cdc->pl2303.type == PL2303_TYPE_HXN ? PL2303_VENDOR_READ_NREQUEST : PL2303_VENDOR_READ_REQUEST; + return pl2303_set_request(p_cdc, request, PL2303_VENDOR_READ_REQUEST_TYPE, value, 0, buf, 1, complete_cb, user_data); +} + +static bool pl2303_vendor_write(cdch_interface_t *p_cdc, uint16_t value, uint16_t index, + tuh_xfer_cb_t complete_cb, uintptr_t user_data) { + uint8_t request = p_cdc->pl2303.type == PL2303_TYPE_HXN ? PL2303_VENDOR_WRITE_NREQUEST : PL2303_VENDOR_WRITE_REQUEST; + return pl2303_set_request(p_cdc, request, PL2303_VENDOR_WRITE_REQUEST_TYPE, value, index, NULL, 0, complete_cb, user_data); +} + +static inline bool pl2303_supports_hx_status(cdch_interface_t *p_cdc, tuh_xfer_cb_t complete_cb, uintptr_t user_data) { + uint8_t buf = 0; + return pl2303_set_request(p_cdc, PL2303_VENDOR_READ_REQUEST, PL2303_VENDOR_READ_REQUEST_TYPE, PL2303_READ_TYPE_HX_STATUS, 0, + &buf, 1, complete_cb, user_data); +} + +//static bool pl2303_get_line_request(cdch_interface_t * p_cdc, uint8_t buf[PL2303_LINE_CODING_BUFSIZE]) { +// return pl2303_set_request(p_cdc, PL2303_GET_LINE_REQUEST, PL2303_GET_LINE_REQUEST_TYPE, 0, 0, buf, PL2303_LINE_CODING_BUFSIZE); +//} + +//static bool pl2303_set_break(cdch_interface_t * p_cdc, bool enable) { +// uint16_t state = enable ? PL2303_BREAK_ON : PL2303_BREAK_OFF; +// return pl2303_set_request(p_cdc, PL2303_BREAK_REQUEST, PL2303_BREAK_REQUEST_TYPE, state, 0, NULL, 0); +//} + +static inline bool +pl2303_clear_halt(cdch_interface_t *p_cdc, uint8_t endp, tuh_xfer_cb_t complete_cb, uintptr_t user_data) { + /* we don't care if it wasn't halted first. in fact some devices + * (like some ibmcam model 1 units) seem to expect hosts to make + * this request for iso endpoints, which can't halt! + */ + return pl2303_set_request( + p_cdc, TUSB_REQ_CLEAR_FEATURE, PL2303_CLEAR_HALT_REQUEST_TYPE, TUSB_REQ_FEATURE_EDPT_HALT, endp, NULL, 0, + complete_cb, user_data); +} + +//------------- Driver API -------------// + +// internal control complete to update state such as line state, encoding +static void pl2303_internal_control_complete(cdch_interface_t *p_cdc, tuh_xfer_t *xfer) { + TU_VERIFY(xfer->result == XFER_RESULT_SUCCESS,); + if (xfer->setup->bRequest == PL2303_SET_LINE_REQUEST && + xfer->setup->bmRequestType == PL2303_SET_LINE_REQUEST_TYPE) { + p_cdc->line.coding = p_cdc->requested_line.coding; + } + if (xfer->setup->bRequest == PL2303_SET_CONTROL_REQUEST && + xfer->setup->bmRequestType == PL2303_SET_CONTROL_REQUEST_TYPE) { + p_cdc->line.control_state = p_cdc->requested_line.control_state; + } +} + +static bool pl2303_set_line_coding(cdch_interface_t *p_cdc, tuh_xfer_cb_t complete_cb, uintptr_t user_data) { + // the caller has to precheck, that the new line coding different than the current, else false returned + uint8_t buf[PL2303_LINE_CODING_BUFSIZE]; + /* + * Some PL2303 are known to lose bytes if you change serial settings + * even to the same values as before. Thus we actually need to filter + * in this specific case. + */ + TU_VERIFY(p_cdc->requested_line.coding.data_bits != p_cdc->line.coding.data_bits || + p_cdc->requested_line.coding.stop_bits != p_cdc->line.coding.stop_bits || + p_cdc->requested_line.coding.parity != p_cdc->line.coding.parity || + p_cdc->requested_line.coding.bit_rate != p_cdc->line.coding.bit_rate ); + + /* For reference buf[6] data bits value */ + TU_VERIFY(p_cdc->requested_line.coding.data_bits >= 5 && p_cdc->requested_line.coding.data_bits <= 8, 0); + buf[6] = p_cdc->requested_line.coding.data_bits; + + /* For reference buf[0]:buf[3] baud rate value */ + TU_VERIFY(pl2303_encode_baud_rate(p_cdc, &buf[0])); + + /* For reference buf[4]=0 is 1 stop bits */ + /* For reference buf[4]=1 is 1.5 stop bits */ + /* For reference buf[4]=2 is 2 stop bits */ + buf[4] = p_cdc->requested_line.coding.stop_bits; // PL2303 has the same coding + + /* For reference buf[5]=0 is none parity */ + /* For reference buf[5]=1 is odd parity */ + /* For reference buf[5]=2 is even parity */ + /* For reference buf[5]=3 is mark parity */ + /* For reference buf[5]=4 is space parity */ + buf[5] = p_cdc->requested_line.coding.parity; // PL2303 has the same coding + + return pl2303_set_request(p_cdc, PL2303_SET_LINE_REQUEST, PL2303_SET_LINE_REQUEST_TYPE, 0, 0, + buf, PL2303_LINE_CODING_BUFSIZE, complete_cb, user_data); +} + +static bool pl2303_set_modem_ctrl(cdch_interface_t *p_cdc, tuh_xfer_cb_t complete_cb, uintptr_t user_data) { + // PL2303 has the same bit coding + return pl2303_set_request(p_cdc, PL2303_SET_CONTROL_REQUEST, PL2303_SET_CONTROL_REQUEST_TYPE, + p_cdc->requested_line.control_state.value, 0, NULL, 0, complete_cb, user_data); +} + +//------------- Enumeration -------------// + +enum { + CONFIG_PL2303_DETECT_TYPE = 0, + CONFIG_PL2303_READ1, + CONFIG_PL2303_WRITE1, + CONFIG_PL2303_READ2, + CONFIG_PL2303_READ3, + CONFIG_PL2303_READ4, + CONFIG_PL2303_WRITE2, + CONFIG_PL2303_READ5, + CONFIG_PL2303_READ6, + CONFIG_PL2303_WRITE3, + CONFIG_PL2303_WRITE4, + CONFIG_PL2303_WRITE5, + CONFIG_PL2303_RESET_ENDP1, + CONFIG_PL2303_RESET_ENDP2, +// CONFIG_PL2303_FLOW_CTRL_READ, +// CONFIG_PL2303_FLOW_CTRL_WRITE, + CONFIG_PL2303_COMPLETE +}; + +static uint16_t pl2303_open(uint8_t daddr, const tusb_desc_interface_t *itf_desc, uint16_t max_len) { + // PL2303 Interface includes 1 vendor interface + 1 interrupt endpoints + 2 bulk + TU_VERIFY(itf_desc->bNumEndpoints == 3, 0); + const uint16_t drv_len = + (uint16_t)(sizeof(tusb_desc_interface_t) + itf_desc->bNumEndpoints * sizeof(tusb_desc_endpoint_t)); + TU_VERIFY(drv_len <= max_len, 0); + + cdch_interface_t *p_cdc = make_new_itf(daddr, itf_desc); + TU_VERIFY(p_cdc, 0); + + p_cdc->serial_drid = SERIAL_DRIVER_PL2303; + p_cdc->pl2303.quirks = 0; + p_cdc->pl2303.supports_hx_status = false; + + tusb_desc_endpoint_t const *desc_ep = (tusb_desc_endpoint_t const *) tu_desc_next(itf_desc); + + // Interrupt endpoint: not used for now + TU_ASSERT(TUSB_DESC_ENDPOINT == tu_desc_type(desc_ep) && TUSB_XFER_INTERRUPT == desc_ep->bmAttributes.xfer, 0); + TU_ASSERT(tuh_edpt_open(daddr, desc_ep), 0); + p_cdc->ep_notif = desc_ep->bEndpointAddress; + desc_ep += 1; + + // data endpoints expected to be in pairs + TU_ASSERT(open_ep_stream_pair(p_cdc, desc_ep), 0); + + return drv_len; +} + +static bool pl2303_process_set_config(cdch_interface_t *p_cdc, tuh_xfer_t *xfer) { + // state CONFIG_PL2303_READ1 may have no success due to expected stall by pl2303_supports_hx_status() + const uintptr_t state = xfer->user_data; + TU_ASSERT(xfer->result == XFER_RESULT_SUCCESS || state == CONFIG_PL2303_READ1); + uint8_t* enum_buf = usbh_get_enum_buf(); + pl2303_type_t type; + + switch (state) { + // from here sequence overtaken from Linux Kernel function pl2303_startup() + case CONFIG_PL2303_DETECT_TYPE: + // get type and quirks (step 1) + type = pl2303_detect_type(p_cdc, 1); + TU_ASSERT(type != PL2303_TYPE_UNKNOWN); + if (type == PL2303_TYPE_NEED_SUPPORTS_HX_STATUS) { + TU_ASSERT(pl2303_supports_hx_status(p_cdc, cdch_process_set_config, CONFIG_PL2303_READ1)); + break; + } + // no transfer triggered and continue with CONFIG_PL2303_READ1 + TU_ATTR_FALLTHROUGH; + + case CONFIG_PL2303_READ1: + // get supports_hx_status, type and quirks (step 2), do special read + // will not be true, if coming directly from previous case + if (xfer->user_data == CONFIG_PL2303_READ1 && xfer->result == XFER_RESULT_SUCCESS) { + p_cdc->pl2303.supports_hx_status = true; + } + type = pl2303_detect_type(p_cdc, 2); // step 2 now with supports_hx_status + TU_ASSERT(type != PL2303_TYPE_UNKNOWN); + TU_LOG_DRV(" PL2303 type detected: %u\r\n", type); + + p_cdc->pl2303.type = type; + p_cdc->pl2303.quirks |= pl2303_type_data[type].quirks; + + // purpose unknown, overtaken from Linux Kernel driver + if (p_cdc->pl2303.type != PL2303_TYPE_HXN) { + TU_ASSERT(pl2303_vendor_read(p_cdc, 0x8484, enum_buf, cdch_process_set_config, CONFIG_PL2303_WRITE1)); + break; + }// else: continue with next step + TU_ATTR_FALLTHROUGH; + + case CONFIG_PL2303_WRITE1: + // purpose unknown, overtaken from Linux Kernel driver + if (p_cdc->pl2303.type != PL2303_TYPE_HXN) { + TU_ASSERT(pl2303_vendor_write(p_cdc, 0x0404, 0, cdch_process_set_config, CONFIG_PL2303_READ2)); + break; + }// else: continue with next step + TU_ATTR_FALLTHROUGH; + + case CONFIG_PL2303_READ2: + // purpose unknown, overtaken from Linux Kernel driver + if (p_cdc->pl2303.type != PL2303_TYPE_HXN) { + TU_ASSERT(pl2303_vendor_read(p_cdc, 0x8484, enum_buf, cdch_process_set_config, CONFIG_PL2303_READ3)); + break; + }// else: continue with next step + TU_ATTR_FALLTHROUGH; + + case CONFIG_PL2303_READ3: + // purpose unknown, overtaken from Linux Kernel driver + if (p_cdc->pl2303.type != PL2303_TYPE_HXN) { + TU_ASSERT(pl2303_vendor_read(p_cdc, 0x8383, enum_buf, cdch_process_set_config, CONFIG_PL2303_READ4)); + break; + }// else: continue with next step + TU_ATTR_FALLTHROUGH; + + case CONFIG_PL2303_READ4: + // purpose unknown, overtaken from Linux Kernel driver + if (p_cdc->pl2303.type != PL2303_TYPE_HXN) { + TU_ASSERT(pl2303_vendor_read(p_cdc, 0x8484, enum_buf, cdch_process_set_config, CONFIG_PL2303_WRITE2)); + break; + }// else: continue with next step + TU_ATTR_FALLTHROUGH; + + case CONFIG_PL2303_WRITE2: + // purpose unknown, overtaken from Linux Kernel driver + if (p_cdc->pl2303.type != PL2303_TYPE_HXN) { + TU_ASSERT(pl2303_vendor_write(p_cdc, 0x0404, 1, cdch_process_set_config, CONFIG_PL2303_READ5)); + break; + }// else: continue with next step + TU_ATTR_FALLTHROUGH; + + case CONFIG_PL2303_READ5: + // purpose unknown, overtaken from Linux Kernel driver + if (p_cdc->pl2303.type != PL2303_TYPE_HXN) { + TU_ASSERT(pl2303_vendor_read(p_cdc, 0x8484, enum_buf, cdch_process_set_config, CONFIG_PL2303_READ6)); + break; + }// else: continue with next step + TU_ATTR_FALLTHROUGH; + + case CONFIG_PL2303_READ6: + // purpose unknown, overtaken from Linux Kernel driver + if (p_cdc->pl2303.type != PL2303_TYPE_HXN) { + TU_ASSERT(pl2303_vendor_read(p_cdc, 0x8383, enum_buf, cdch_process_set_config, CONFIG_PL2303_WRITE3)); + break; + }// else: continue with next step + TU_ATTR_FALLTHROUGH; + + case CONFIG_PL2303_WRITE3: + // purpose unknown, overtaken from Linux Kernel driver + if (p_cdc->pl2303.type != PL2303_TYPE_HXN) { + TU_ASSERT(pl2303_vendor_write(p_cdc, 0, 1, cdch_process_set_config, CONFIG_PL2303_WRITE4)); + break; + }// else: continue with next step + TU_ATTR_FALLTHROUGH; + + case CONFIG_PL2303_WRITE4: + // purpose unknown, overtaken from Linux Kernel driver + if (p_cdc->pl2303.type != PL2303_TYPE_HXN) { + TU_ASSERT(pl2303_vendor_write(p_cdc, 1, 0, cdch_process_set_config, CONFIG_PL2303_WRITE5)); + break; + }// else: continue with next step + TU_ATTR_FALLTHROUGH; + + case CONFIG_PL2303_WRITE5: + // purpose unknown, overtaken from Linux Kernel driver + if (p_cdc->pl2303.type != PL2303_TYPE_HXN) { + uint16_t const windex = (p_cdc->pl2303.quirks & PL2303_QUIRK_LEGACY) ? 0x24 : 0x44; + TU_ASSERT(pl2303_vendor_write(p_cdc, 2, windex, cdch_process_set_config, CONFIG_PL2303_RESET_ENDP1)); + break; + }// else: continue with next step + TU_ATTR_FALLTHROUGH; + + // from here sequence overtaken from Linux Kernel function pl2303_open() + case CONFIG_PL2303_RESET_ENDP1: + // step 1 + if (p_cdc->pl2303.quirks & PL2303_QUIRK_LEGACY) { + TU_ASSERT(pl2303_clear_halt(p_cdc, PL2303_OUT_EP, cdch_process_set_config, CONFIG_PL2303_RESET_ENDP2)); + } else { + /* reset upstream data pipes */ + if (p_cdc->pl2303.type == PL2303_TYPE_HXN) { + TU_ASSERT(pl2303_vendor_write(p_cdc, PL2303_HXN_RESET_REG,// skip CONFIG_PL2303_RESET_ENDP2, no 2nd step + PL2303_HXN_RESET_UPSTREAM_PIPE | PL2303_HXN_RESET_DOWNSTREAM_PIPE, + cdch_process_set_config, CONFIG_PL2303_COMPLETE)); + } else { + pl2303_vendor_write(p_cdc, 8, 0, cdch_process_set_config, CONFIG_PL2303_RESET_ENDP2); + } + } + break; + + case CONFIG_PL2303_RESET_ENDP2: + // step 2 + if (p_cdc->pl2303.quirks & PL2303_QUIRK_LEGACY) { + TU_ASSERT(pl2303_clear_halt(p_cdc, PL2303_IN_EP, cdch_process_set_config, CONFIG_PL2303_COMPLETE)); + } else { + /* reset upstream data pipes */ + if (p_cdc->pl2303.type == PL2303_TYPE_HXN) { + // here nothing to do, only structure of previous step overtaken for better reading and comparison + } else { + TU_ASSERT(pl2303_vendor_write(p_cdc, 9, 0, cdch_process_set_config, CONFIG_PL2303_COMPLETE)); + } + } + break; + + // skipped, because it's not working with each PL230x. flow control can be also set by PL2303 EEPROM Writer Program + // case CONFIG_PL2303_FLOW_CTRL_READ: + // // read flow control register for modify & write back in next step + // if (p_cdc->pl2303.type == PL2303_TYPE_HXN) { + // TU_LOG_P_CDC ( "1\r\n" ); + // TU_ASSERT(pl2303_vendor_read(p_cdc, PL2303_HXN_FLOWCTRL_REG, &buf, + // cdch_process_set_config, CONFIG_PL2303_FLOW_CTRL_WRITE)); + // } else { + // TU_LOG_P_CDC ( "2\r\n" ); + // TU_ASSERT(pl2303_vendor_read(p_cdc, 0, &buf, cdch_process_set_config, CONFIG_PL2303_FLOW_CTRL_WRITE)); + // } + // break; + // + // case CONFIG_PL2303_FLOW_CTRL_WRITE: + // // no flow control + // buf = xfer->buffer[0]; + // if (p_cdc->pl2303.type == PL2303_TYPE_HXN) { + // buf &= (uint8_t) ~PL2303_HXN_FLOWCTRL_MASK; + // buf |= PL2303_HXN_FLOWCTRL_NONE; + // TU_ASSERT(pl2303_vendor_write(p_cdc, PL2303_HXN_FLOWCTRL_REG, buf, + // cdch_process_set_config, CONFIG_PL2303_COMPLETE)); + // } else { + // buf &= (uint8_t) ~PL2303_FLOWCTRL_MASK; + // TU_ASSERT(pl2303_vendor_write(p_cdc, 0, buf, + // cdch_process_set_config, CONFIG_PL2303_COMPLETE)); + // } + // break; + + case CONFIG_PL2303_COMPLETE: + xfer->user_data = 0; // kick-off set line state on enum + cdch_process_line_state_on_enum(xfer); + break; + + default: + return false; + } + + return true; +} + +//------------- Helper -------------// + +static pl2303_type_t pl2303_detect_type(cdch_interface_t *p_cdc, uint8_t step) { + tusb_desc_device_t desc_dev; + TU_VERIFY(tuh_descriptor_get_device_local(p_cdc->daddr, &desc_dev), PL2303_TYPE_UNKNOWN); + + // Legacy PL2303H, variants 0 and 1 (difference unknown). + if (desc_dev.bDeviceClass == 0x02) { + return PL2303_TYPE_H; /* variant 0 */ + } + + if (desc_dev.bMaxPacketSize0 != 0x40) { + if (desc_dev.bDeviceClass == 0x00 || desc_dev.bDeviceClass == 0xff) { + return PL2303_TYPE_H; /* variant 1 */ + } + return PL2303_TYPE_H; /* variant 0 */ + } + + switch (desc_dev.bcdUSB) { + case 0x101: + /* USB 1.0.1? Let's assume they meant 1.1... */ + TU_ATTR_FALLTHROUGH; + case 0x110: + switch (desc_dev.bcdDevice) { + case 0x300: return PL2303_TYPE_HX; + case 0x400: return PL2303_TYPE_HXD; + default: return PL2303_TYPE_HX; + } + break; + + case 0x200: + switch (desc_dev.bcdDevice) { + case 0x100: /* GC */ + case 0x105: + return PL2303_TYPE_HXN; + + case 0x300: /* GT / TA */ + if (step == 1) { + // step 1 trigger pl2303_supports_hx_status() request + return PL2303_TYPE_NEED_SUPPORTS_HX_STATUS; + } else { + // step 2 use supports_hx_status + if (p_cdc->pl2303.supports_hx_status) { + return PL2303_TYPE_TA; + } + } + TU_ATTR_FALLTHROUGH; + case 0x305: + case 0x400: /* GL */ + case 0x405: + return PL2303_TYPE_HXN; + + case 0x500: /* GE / TB */ + if (step == 1) { + // step 1 trigger pl2303_supports_hx_status() request + return PL2303_TYPE_NEED_SUPPORTS_HX_STATUS; + } else { + // step 2 use supports_hx_status + if (p_cdc->pl2303.supports_hx_status) { + return PL2303_TYPE_TB; + } + } + TU_ATTR_FALLTHROUGH; + case 0x505: + case 0x600: /* GS */ + case 0x605: + case 0x700: /* GR */ + case 0x705: + return PL2303_TYPE_HXN; + + default: + break; // unknown device + } + break; + + default: + break; // unknown device + } + + TU_LOG_CDC(p_cdc, "unknown device type bcdUSB = 0x%04x", desc_dev.bcdUSB); + return PL2303_TYPE_UNKNOWN; +} + +/* + * Returns the nearest supported baud rate that can be set directly without + * using divisors. + */ +static uint32_t pl2303_get_supported_baud_rate(uint32_t baud) { + static const uint32_t baud_sup[] = { + 75, 150, 300, 600, 1200, 1800, 2400, 3600, 4800, 7200, 9600, + 14400, 19200, 28800, 38400, 57600, 115200, 230400, 460800, + 614400, 921600, 1228800, 2457600, 3000000, 6000000 + }; + + uint8_t i; + for (i = 0; i < TU_ARRAY_SIZE(baud_sup); ++i) { + if (baud_sup[i] > baud) { + break; + } + } + + if (i == TU_ARRAY_SIZE(baud_sup)) { + baud = baud_sup[i - 1]; + } else if (i > 0 && (baud_sup[i] - baud) > (baud - baud_sup[i - 1])) { + baud = baud_sup[i - 1]; + } else { + baud = baud_sup[i]; + } + + return baud; +} + +/* + * NOTE: If unsupported baud rates are set directly, the PL2303 seems to + * use 9600 baud. + */ +static uint32_t pl2303_encode_baud_rate_direct(uint8_t buf[PL2303_LINE_CODING_BAUDRATE_BUFSIZE], uint32_t baud) { + uint32_t baud_le = tu_htole32(baud); + buf[0] = (uint8_t) ( baud_le & 0xff); + buf[1] = (uint8_t) ((baud_le >> 8) & 0xff); + buf[2] = (uint8_t) ((baud_le >> 16) & 0xff); + buf[3] = (uint8_t) ((baud_le >> 24) & 0xff); + + return baud; +} + +static uint32_t pl2303_encode_baud_rate_divisor(uint8_t buf[PL2303_LINE_CODING_BAUDRATE_BUFSIZE], uint32_t baud) { + uint32_t baseline, mantissa, exponent; + + /* + * Apparently the formula is: + * baudrate = 12M * 32 / (mantissa * 4^exponent) + * where + * mantissa = buf[8:0] + * exponent = buf[11:9] + */ + baseline = 12000000 * 32; + mantissa = baseline / baud; + if (mantissa == 0) { + mantissa = 1; /* Avoid dividing by zero if baud > 32 * 12M. */ + } + exponent = 0; + while (mantissa >= 512) { + if (exponent < 7) { + mantissa >>= 2; /* divide by 4 */ + exponent++; + } else { + /* Exponent is maxed. Trim mantissa and leave. */ + mantissa = 511; + break; + } + } + + buf[3] = 0x80; + buf[2] = 0; + buf[1] = (uint8_t) ((exponent << 1 | mantissa >> 8) & 0xff); + buf[0] = (uint8_t) (mantissa & 0xff); + + /* Calculate and return the exact baud rate. */ + baud = (baseline / mantissa) >> (exponent << 1); + + return baud; +} + +static uint32_t pl2303_encode_baud_rate_divisor_alt(uint8_t buf[PL2303_LINE_CODING_BAUDRATE_BUFSIZE], uint32_t baud) { + uint32_t baseline, mantissa, exponent; + + /* + * Apparently, for the TA version the formula is: + * baudrate = 12M * 32 / (mantissa * 2^exponent) + * where + * mantissa = buf[10:0] + * exponent = buf[15:13 16] + */ + baseline = 12000000 * 32; + mantissa = baseline / baud; + if (mantissa == 0) { + mantissa = 1; /* Avoid dividing by zero if baud > 32 * 12M. */ + } + exponent = 0; + while (mantissa >= 2048) { + if (exponent < 15) { + mantissa >>= 1; /* divide by 2 */ + exponent++; + } else { + /* Exponent is maxed. Trim mantissa and leave. */ + mantissa = 2047; + break; + } + } + + buf[3] = 0x80; + buf[2] = (uint8_t) (exponent & 0x01); + buf[1] = (uint8_t) (((exponent & (uint32_t) ~0x01) << 4 | mantissa >> 8) & 0xff); + buf[0] = (uint8_t) (mantissa & 0xff); + + /* Calculate and return the exact baud rate. */ + baud = (baseline / mantissa) >> exponent; + + return baud; +} + +static bool pl2303_encode_baud_rate(cdch_interface_t *p_cdc, uint8_t buf[PL2303_LINE_CODING_BAUDRATE_BUFSIZE]) { + uint32_t baud = p_cdc->requested_line.coding.bit_rate; + uint32_t baud_sup; + const pl2303_type_data_t* type_data = &pl2303_type_data[p_cdc->pl2303.type]; + + TU_VERIFY(baud && baud <= type_data->max_baud_rate); + /* + * Use direct method for supported baud rates, otherwise use divisors. + * Newer chip types do not support divisor encoding. + */ + if (type_data->no_divisors != 0) { + baud_sup = baud; + } else { + baud_sup = pl2303_get_supported_baud_rate(baud); + } + + if (baud == baud_sup) { + baud = pl2303_encode_baud_rate_direct(buf, baud); + } else if (type_data->alt_divisors != 0) { + baud = pl2303_encode_baud_rate_divisor_alt(buf, baud); + } else { + baud = pl2303_encode_baud_rate_divisor(buf, baud); + } + TU_LOG_CDC(p_cdc, "real baudrate %lu", baud); + + return true; +} + +#endif // CFG_TUH_CDC_PL2303 + +#endif diff --git a/libraries/Adafruit_TinyUSB_Arduino/src/class/cdc/cdc_host.h b/libraries/Adafruit_TinyUSB_Arduino/src/class/cdc/cdc_host.h new file mode 100644 index 0000000..1b1709b --- /dev/null +++ b/libraries/Adafruit_TinyUSB_Arduino/src/class/cdc/cdc_host.h @@ -0,0 +1,267 @@ +/* + * 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. + * + * This file is part of the TinyUSB stack. + */ + +#ifndef TUSB_CDC_HOST_H_ +#define TUSB_CDC_HOST_H_ + +#include "cdc.h" + +#ifdef __cplusplus +extern "C" { +#endif + +//--------------------------------------------------------------------+ +// Class Driver Configuration +//--------------------------------------------------------------------+ + +// RX FIFO size +#ifndef CFG_TUH_CDC_RX_BUFSIZE + #define CFG_TUH_CDC_RX_BUFSIZE TUH_EPSIZE_BULK_MAX +#endif + +// RX Endpoint size +#ifndef CFG_TUH_CDC_RX_EPSIZE + #define CFG_TUH_CDC_RX_EPSIZE TUH_EPSIZE_BULK_MAX +#endif + +// TX FIFO size +#ifndef CFG_TUH_CDC_TX_BUFSIZE + #define CFG_TUH_CDC_TX_BUFSIZE TUH_EPSIZE_BULK_MAX +#endif + +// TX Endpoint size +#ifndef CFG_TUH_CDC_TX_EPSIZE + #define CFG_TUH_CDC_TX_EPSIZE TUH_EPSIZE_BULK_MAX +#endif + +//--------------------------------------------------------------------+ +// Application API +//--------------------------------------------------------------------+ + +// Get Interface index from device address + interface number +// return TUSB_INDEX_INVALID_8 (0xFF) if not found +uint8_t tuh_cdc_itf_get_index(uint8_t daddr, uint8_t itf_num); + +// Get Interface information +// return true if index is correct and interface is currently mounted +bool tuh_cdc_itf_get_info(uint8_t idx, tuh_itf_info_t *info); + +// Check if an interface is mounted +bool tuh_cdc_mounted(uint8_t idx); + +// Get local (cached) line state +// This function should return correct values if tuh_cdc_set_control_line_state() / tuh_cdc_get_control_line_state() +// are invoked previously or CFG_TUH_CDC_LINE_STATE_ON_ENUM is defined. +bool tuh_cdc_get_control_line_state_local(uint8_t idx, uint16_t *line_state); + +// Get current DTR status +TU_ATTR_ALWAYS_INLINE static inline bool tuh_cdc_get_dtr(uint8_t idx) { + uint16_t line_state; + TU_VERIFY(tuh_cdc_get_control_line_state_local(idx, &line_state)); + return (line_state & CDC_CONTROL_LINE_STATE_DTR) != 0; +} + +// Get current RTS status +TU_ATTR_ALWAYS_INLINE static inline bool tuh_cdc_get_rts(uint8_t idx) { + uint16_t line_state; + TU_VERIFY(tuh_cdc_get_control_line_state_local(idx, &line_state)); + return (line_state & CDC_CONTROL_LINE_STATE_RTS) != 0; +} + +// Check if interface is connected (DTR active) +TU_ATTR_ALWAYS_INLINE static inline bool tuh_cdc_connected(uint8_t idx) { + return tuh_cdc_get_dtr(idx); +} + +// Get local (saved/cached) version of line coding. +// This function should return correct values if tuh_cdc_set_line_coding() / tuh_cdc_get_line_coding() +// are invoked previously or CFG_TUH_CDC_LINE_CODING_ON_ENUM is defined. +// NOTE: This function does not make any USB transfer request to device. +bool tuh_cdc_get_line_coding_local(uint8_t idx, cdc_line_coding_t *line_coding); + +#define tuh_cdc_get_local_line_coding tuh_cdc_get_line_coding_local // backward compatibility + +//--------------------------------------------------------------------+ +// Write API +//--------------------------------------------------------------------+ + +// Get the number of bytes available for writing +uint32_t tuh_cdc_write_available(uint8_t idx); + +// Write to cdc interface +uint32_t tuh_cdc_write(uint8_t idx, const void *buffer, uint32_t bufsize); + +// Force sending data if possible, return number of forced bytes +uint32_t tuh_cdc_write_flush(uint8_t idx); + +// Clear the transmit FIFO +bool tuh_cdc_write_clear(uint8_t idx); + +//--------------------------------------------------------------------+ +// Read API +//--------------------------------------------------------------------+ + +// Get the number of bytes available for reading +uint32_t tuh_cdc_read_available(uint8_t idx); + +// Read from cdc interface +uint32_t tuh_cdc_read(uint8_t idx, void *buffer, uint32_t bufsize); + +// Get a byte from RX FIFO without removing it +bool tuh_cdc_peek(uint8_t idx, uint8_t *ch); + +// Clear the received FIFO +bool tuh_cdc_read_clear(uint8_t idx); + +//--------------------------------------------------------------------+ +// Control Request API +// Each Function will make a USB control transfer request to/from device +// - If complete_cb is provided, the function will return immediately and invoke +// the callback when request is complete. +// - If complete_cb is NULL, the function will block until request is complete. +// In this case, user_data should be usb_xfer_result_t* to hold the transfer result. +//--------------------------------------------------------------------+ + +// Request to Set Control Line State: DTR (bit 0), RTS (bit 1) +bool tuh_cdc_set_control_line_state(uint8_t idx, uint16_t line_state, tuh_xfer_cb_t complete_cb, uintptr_t user_data); + +// Request to Set DTR +TU_ATTR_ALWAYS_INLINE static inline bool tuh_cdc_set_dtr(uint8_t idx, bool dtr_state, tuh_xfer_cb_t complete_cb, + uintptr_t user_data) { + cdc_line_control_state_t line_state = {.dtr = dtr_state}; + line_state.rts = tuh_cdc_get_rts(idx); + return tuh_cdc_set_control_line_state(idx, line_state.value, complete_cb, user_data); +} + +// Request to Set RTS +TU_ATTR_ALWAYS_INLINE static inline bool tuh_cdc_set_rts(uint8_t idx, bool rts_state, tuh_xfer_cb_t complete_cb, + uintptr_t user_data) { + cdc_line_control_state_t line_state = {.rts = rts_state}; + line_state.dtr = tuh_cdc_get_dtr(idx); + return tuh_cdc_set_control_line_state(idx, line_state.value, complete_cb, user_data); +} + +// Request to set baudrate +bool tuh_cdc_set_baudrate(uint8_t idx, uint32_t baudrate, tuh_xfer_cb_t complete_cb, uintptr_t user_data); + +// Request to set data format +bool tuh_cdc_set_data_format(uint8_t idx, uint8_t stop_bits, uint8_t parity, uint8_t data_bits, + tuh_xfer_cb_t complete_cb, uintptr_t user_data); + +// Request to Set Line Coding = baudrate + data format +// Note: only implemented by ACM and CH34x, not supported by FTDI and CP210x yet +bool tuh_cdc_set_line_coding(uint8_t idx, const cdc_line_coding_t *line_coding, tuh_xfer_cb_t complete_cb, + uintptr_t user_data); + +// Request to Get Line Coding (ACM only) +// Should only use if tuh_cdc_set_line_coding() / tuh_cdc_get_line_coding() never got invoked and +// CFG_TUH_CDC_LINE_CODING_ON_ENUM is not defined +// bool tuh_cdc_get_line_coding(uint8_t idx, cdc_line_coding_t* coding); + +// Connect by set both DTR, RTS +TU_ATTR_ALWAYS_INLINE static inline bool tuh_cdc_connect(uint8_t idx, tuh_xfer_cb_t complete_cb, uintptr_t user_data) { + return tuh_cdc_set_control_line_state(idx, CDC_CONTROL_LINE_STATE_DTR | CDC_CONTROL_LINE_STATE_RTS, complete_cb, + user_data); +} + +// Disconnect by clear both DTR, RTS +TU_ATTR_ALWAYS_INLINE static inline bool tuh_cdc_disconnect(uint8_t idx, tuh_xfer_cb_t complete_cb, + uintptr_t user_data) { + return tuh_cdc_set_control_line_state(idx, 0x00, complete_cb, user_data); +} + +//--------------------------------------------------------------------+ +// Control Request Sync API +// Each Function will make a USB control transfer request to/from device the function will block until request is +// complete. The function will return the transfer request result +//--------------------------------------------------------------------+ +TU_ATTR_ALWAYS_INLINE static inline tusb_xfer_result_t tuh_cdc_set_control_line_state_sync(uint8_t idx, + uint16_t line_state) { + TU_API_SYNC(tuh_cdc_set_control_line_state, idx, line_state); +} + +TU_ATTR_ALWAYS_INLINE static inline tusb_xfer_result_t tuh_cdc_set_dtr_sync(uint8_t idx, bool dtr_state) { + TU_API_SYNC(tuh_cdc_set_dtr, idx, dtr_state); +} + +TU_ATTR_ALWAYS_INLINE static inline tusb_xfer_result_t tuh_cdc_set_rts_sync(uint8_t idx, bool rts_state) { + TU_API_SYNC(tuh_cdc_set_rts, idx, rts_state); +} + +TU_ATTR_ALWAYS_INLINE static inline tusb_xfer_result_t tuh_cdc_set_baudrate_sync(uint8_t idx, uint32_t baudrate) { + TU_API_SYNC(tuh_cdc_set_baudrate, idx, baudrate); +} + +TU_ATTR_ALWAYS_INLINE static inline tusb_xfer_result_t tuh_cdc_set_data_format_sync(uint8_t idx, uint8_t stop_bits, + uint8_t parity, uint8_t data_bits) { + TU_API_SYNC(tuh_cdc_set_data_format, idx, stop_bits, parity, data_bits); +} + +TU_ATTR_ALWAYS_INLINE static inline tusb_xfer_result_t +tuh_cdc_set_line_coding_sync(uint8_t idx, const cdc_line_coding_t *line_coding) { + TU_API_SYNC(tuh_cdc_set_line_coding, idx, line_coding); +} + +TU_ATTR_ALWAYS_INLINE static inline tusb_xfer_result_t tuh_cdc_connect_sync(uint8_t idx) { + TU_API_SYNC(tuh_cdc_connect, idx); +} + +TU_ATTR_ALWAYS_INLINE static inline tusb_xfer_result_t tuh_cdc_disconnect_sync(uint8_t idx) { + TU_API_SYNC(tuh_cdc_disconnect, idx); +} + +//--------------------------------------------------------------------+ +// CDC APPLICATION CALLBACKS +//--------------------------------------------------------------------+ + +// Invoked when a device with CDC interface is mounted +// idx is index of cdc interface in the internal pool. +extern void tuh_cdc_mount_cb(uint8_t idx); + +// Invoked when a device with CDC interface is unmounted +extern void tuh_cdc_umount_cb(uint8_t idx); + +// Invoked when received new data +extern void tuh_cdc_rx_cb(uint8_t idx); + +// Invoked when a TX is complete and therefore space becomes available in TX buffer +extern void tuh_cdc_tx_complete_cb(uint8_t idx); + +//--------------------------------------------------------------------+ +// Internal Class Driver API +//--------------------------------------------------------------------+ +bool cdch_init(void); +bool cdch_deinit(void); +uint16_t cdch_open(uint8_t rhport, uint8_t dev_addr, const tusb_desc_interface_t *itf_desc, uint16_t max_len); +bool cdch_set_config(uint8_t dev_addr, uint8_t itf_num); +bool cdch_xfer_cb(uint8_t dev_addr, uint8_t ep_addr, xfer_result_t event, uint32_t xferred_bytes); +void cdch_close(uint8_t dev_addr); + +#ifdef __cplusplus +} +#endif + +#endif /* TUSB_CDC_HOST_H_ */ diff --git a/libraries/Adafruit_TinyUSB_Arduino/src/class/cdc/cdc_rndis.h b/libraries/Adafruit_TinyUSB_Arduino/src/class/cdc/cdc_rndis.h new file mode 100644 index 0000000..fbbd432 --- /dev/null +++ b/libraries/Adafruit_TinyUSB_Arduino/src/class/cdc/cdc_rndis.h @@ -0,0 +1,301 @@ +/* + * 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. + * + * This file is part of the TinyUSB stack. + */ + +/** \ingroup ClassDriver_CDC Communication Device Class (CDC) + * \defgroup CDC_RNDIS Remote Network Driver Interface Specification (RNDIS) + * @{ + * \defgroup CDC_RNDIS_Common Common Definitions + * @{ */ + +#ifndef TUSB_CDC_RNDIS_H_ +#define TUSB_CDC_RNDIS_H_ + +#include "cdc.h" + +#ifdef __cplusplus + extern "C" { +#endif + +#ifdef __CC_ARM +#pragma diag_suppress 66 // Suppress Keil warnings #66-D: enumeration value is out of "int" range +#endif + +/// RNDIS Message Types +typedef enum +{ + RNDIS_MSG_PACKET = 0x00000001UL, ///< The host and device use this to send network data to one another. + + RNDIS_MSG_INITIALIZE = 0x00000002UL, ///< Sent by the host to initialize the device. + RNDIS_MSG_INITIALIZE_CMPLT = 0x80000002UL, ///< Device response to an initialize message. + + RNDIS_MSG_HALT = 0x00000003UL, ///< Sent by the host to halt the device. This does not have a response. It is optional for the device to send this message to the host. + + RNDIS_MSG_QUERY = 0x00000004UL, ///< Sent by the host to send a query OID. + RNDIS_MSG_QUERY_CMPLT = 0x80000004UL, ///< Device response to a query OID. + + RNDIS_MSG_SET = 0x00000005UL, ///< Sent by the host to send a set OID. + RNDIS_MSG_SET_CMPLT = 0x80000005UL, ///< Device response to a set OID. + + RNDIS_MSG_RESET = 0x00000006UL, ///< Sent by the host to perform a soft reset on the device. + RNDIS_MSG_RESET_CMPLT = 0x80000006UL, ///< Device response to reset message. + + RNDIS_MSG_INDICATE_STATUS = 0x00000007UL, ///< Sent by the device to indicate its status or an error when an unrecognized message is received. + + RNDIS_MSG_KEEP_ALIVE = 0x00000008UL, ///< During idle periods, sent every few seconds by the host to check that the device is still responsive. It is optional for the device to send this message to check if the host is active. + RNDIS_MSG_KEEP_ALIVE_CMPLT = 0x80000008UL ///< The device response to a keepalivemessage. The host can respond with this message to a keepalive message from the device when the device implements the optional KeepAliveTimer. +}rndis_msg_type_t; + +/// RNDIS Message Status Values +typedef enum +{ + RNDIS_STATUS_SUCCESS = 0x00000000UL, ///< Success + RNDIS_STATUS_FAILURE = 0xC0000001UL, ///< Unspecified error + RNDIS_STATUS_INVALID_DATA = 0xC0010015UL, ///< Invalid data error + RNDIS_STATUS_NOT_SUPPORTED = 0xC00000BBUL, ///< Unsupported request error + RNDIS_STATUS_MEDIA_CONNECT = 0x4001000BUL, ///< Device is connected to a network medium. + RNDIS_STATUS_MEDIA_DISCONNECT = 0x4001000CUL ///< Device is disconnected from the medium. +}rndis_msg_status_t; + +#ifdef __CC_ARM +#pragma diag_default 66 // return Keil 66 to normal severity +#endif + +//--------------------------------------------------------------------+ +// MESSAGE STRUCTURE +//--------------------------------------------------------------------+ + +//------------- Initialize -------------// +/// \brief Initialize Message +/// \details This message MUST be sent by the host to initialize the device. +typedef struct { + uint32_t type ; ///< Message type, must be \ref RNDIS_MSG_INITIALIZE + uint32_t length ; ///< Message length in bytes, must be 0x18 + uint32_t request_id ; ///< A 32-bit integer value, generated by the host, used to match the host's sent request to the response from the device. + uint32_t major_version ; ///< The major version of the RNDIS Protocol implemented by the host. + uint32_t minor_version ; ///< The minor version of the RNDIS Protocol implemented by the host + uint32_t max_xfer_size ; ///< The maximum size, in bytes, of any single bus data transfer that the host expects to receive from the device. +}rndis_msg_initialize_t; + +/// \brief Initialize Complete Message +/// \details This message MUST be sent by the device in response to an initialize message. +typedef struct { + uint32_t type ; ///< Message Type, must be \ref RNDIS_MSG_INITIALIZE_CMPLT + uint32_t length ; ///< Message length in bytes, must be 0x30 + uint32_t request_id ; ///< A 32-bit integer value from \a request_id field of the \ref rndis_msg_initialize_t to which this message is a response. + uint32_t status ; ///< The initialization status of the device, has value from \ref rndis_msg_status_t + uint32_t major_version ; ///< the highest-numbered RNDIS Protocol version supported by the device. + uint32_t minor_version ; ///< the highest-numbered RNDIS Protocol version supported by the device. + uint32_t device_flags ; ///< MUST be set to 0x000000010. Other values are reserved for future use. + uint32_t medium ; ///< is 0x00 for RNDIS_MEDIUM_802_3 + uint32_t max_packet_per_xfer ; ///< The maximum number of concatenated \ref RNDIS_MSG_PACKET messages that the device can handle in a single bus transfer to it. This value MUST be at least 1. + uint32_t max_xfer_size ; ///< The maximum size, in bytes, of any single bus data transfer that the device expects to receive from the host. + uint32_t packet_alignment_factor ; ///< The byte alignment the device expects for each RNDIS message that is part of a multimessage transfer to it. The value is specified as an exponent of 2; for example, the host uses 2{PacketAlignmentFactor} as the alignment value. + uint32_t reserved[2] ; +} rndis_msg_initialize_cmplt_t; + +//------------- Query -------------// +/// \brief Query Message +/// \details This message MUST be sent by the host to query an OID. +typedef struct { + uint32_t type ; ///< Message Type, must be \ref RNDIS_MSG_QUERY + uint32_t length ; ///< Message length in bytes, including the header and the \a oid_buffer + uint32_t request_id ; ///< A 32-bit integer value, generated by the host, used to match the host's sent request to the response from the device. + uint32_t oid ; ///< The integer value of the host operating system-defined identifier, for the parameter of the device being queried for. + uint32_t buffer_length ; ///< The length, in bytes, of the input data required for the OID query. This MUST be set to 0 when there is no input data associated with the OID. + uint32_t buffer_offset ; ///< The offset, in bytes, from the beginning of \a request_id field where the input data for the query is located in the message. This value MUST be set to 0 when there is no input data associated with the OID. + uint32_t reserved ; + uint8_t oid_buffer[] ; ///< Flexible array contains the input data supplied by the host, required for the OID query request processing by the device, as per the host NDIS specification. +} rndis_msg_query_t, rndis_msg_set_t; + +TU_VERIFY_STATIC(sizeof(rndis_msg_query_t) == 28, "Make sure flexible array member does not affect layout"); + +/// \brief Query Complete Message +/// \details This message MUST be sent by the device in response to a query OID message. +typedef struct { + uint32_t type ; ///< Message Type, must be \ref RNDIS_MSG_QUERY_CMPLT + uint32_t length ; ///< Message length in bytes, including the header and the \a oid_buffer + uint32_t request_id ; ///< A 32-bit integer value from \a request_id field of the \ref rndis_msg_query_t to which this message is a response. + uint32_t status ; ///< The status of processing for the query request, has value from \ref rndis_msg_status_t. + uint32_t buffer_length ; ///< The length, in bytes, of the data in the response to the query. This MUST be set to 0 when there is no OIDInputBuffer. + uint32_t buffer_offset ; ///< The offset, in bytes, from the beginning of \a request_id field where the response data for the query is located in the message. This MUST be set to 0 when there is no \ref oid_buffer. + uint8_t oid_buffer[] ; ///< Flexible array member contains the response data to the OID query request as specified by the host. +} rndis_msg_query_cmplt_t; + +TU_VERIFY_STATIC(sizeof(rndis_msg_query_cmplt_t) == 24, "Make sure flexible array member does not affect layout"); + +//------------- Reset -------------// +/// \brief Reset Message +/// \details This message MUST be sent by the host to perform a soft reset on the device. +typedef struct { + uint32_t type ; ///< Message Type, must be \ref RNDIS_MSG_RESET + uint32_t length ; ///< Message length in bytes, MUST be 0x06 + uint32_t reserved ; +} rndis_msg_reset_t; + +/// \brief Reset Complete Message +/// \details This message MUST be sent by the device in response to a reset message. +typedef struct { + uint32_t type ; ///< Message Type, must be \ref RNDIS_MSG_RESET_CMPLT + uint32_t length ; ///< Message length in bytes, MUST be 0x10 + uint32_t status ; ///< The status of processing for the \ref rndis_msg_reset_t, has value from \ref rndis_msg_status_t. + uint32_t addressing_reset ; ///< This field indicates whether the addressing information, which is the multicast address list or packet filter, has been lost during the reset operation. This MUST be set to 0x00000001 if the device requires that the host to resend addressing information or MUST be set to zero otherwise. +} rndis_msg_reset_cmplt_t; + +//typedef struct { +// uint32_t type; +// uint32_t length; +// uint32_t status; +// uint32_t buffer_length; +// uint32_t buffer_offset; +// uint32_t diagnostic_status; // optional +// uint32_t diagnostic_error_offset; // optional +// uint32_t status_buffer[0]; // optional +//} rndis_msg_indicate_status_t; + +/// \brief Keep Alive Message +/// \details This message MUST be sent by the host to check that device is still responsive. It is optional for the device to send this message to check if the host is active +typedef struct { + uint32_t type ; ///< Message Type + uint32_t length ; ///< Message length in bytes, MUST be 0x10 + uint32_t request_id ; +} rndis_msg_keep_alive_t, rndis_msg_halt_t; + +/// \brief Set Complete Message +/// \brief This message MUST be sent in response to a the request message +typedef struct { + uint32_t type ; ///< Message Type + uint32_t length ; ///< Message length in bytes, MUST be 0x10 + uint32_t request_id ; ///< must be the same as requesting message + uint32_t status ; ///< The status of processing for the request message request by the device to which this message is the response. +} rndis_msg_set_cmplt_t, rndis_msg_keep_alive_cmplt_t; + +/// \brief Packet Data Message +/// \brief This message MUST be used by the host and the device to send network data to one another. +typedef struct { + uint32_t type ; ///< Message Type, must be \ref RNDIS_MSG_PACKET + uint32_t length ; ///< Message length in bytes, The total length of this RNDIS message including the header, payload, and padding. + uint32_t data_offset ; ///< Specifies the offset, in bytes, from the start of this \a data_offset field of this message to the start of the data. This MUST be an integer multiple of 4. + uint32_t data_length ; ///< Specifies the number of bytes in the payload of this message. + uint32_t out_of_band_data_offet ; ///< Specifies the offset, in bytes, of the first out-of-band data record from the start of the DataOffset field in this message. MUST be an integer multiple of 4 when out-of-band data is present or set to 0 otherwise. When there are multiple out-ofband data records, each subsequent record MUST immediately follow the previous out-of-band data record. + uint32_t out_of_band_data_length ; ///< Specifies, in bytes, the total length of the out-of-band data. + uint32_t num_out_of_band_data_elements ; ///< Specifies the number of out-of-band records in this message. + uint32_t per_packet_info_offset ; ///< Specifies the offset, in bytes, of the start of per-packet-info data record from the start of the \a data_offset field in this message. MUST be an integer multiple of 4 when per-packet-info data record is present or MUST be set to 0 otherwise. When there are multiple per-packet-info data records, each subsequent record MUST immediately follow the previous record. + uint32_t per_packet_info_length ; ///< Specifies, in bytes, the total length of per-packetinformation contained in this message. + uint32_t reserved[2] ; + uint32_t payload[0] ; ///< Network data contained in this message. + + // uint8_t padding[0] + // Additional bytes of zeros added at the end of the message to comply with + // the internal and external padding requirements. Internal padding SHOULD be as per the + // specification of the out-of-band data record and per-packet-info data record. The external + //padding size SHOULD be determined based on the PacketAlignmentFactor field specification + //in REMOTE_NDIS_INITIALIZE_CMPLT message by the device, when multiple + //REMOTE_NDIS_PACKET_MSG messages are bundled together in a single bus-native message. + //In this case, all but the very last REMOTE_NDIS_PACKET_MSG MUST respect the + //PacketAlignmentFactor field. + + // rndis_msg_packet_t [0] : (optional) more packet if multiple packet per bus transaction is supported +} rndis_msg_packet_t; + + +typedef struct { + uint32_t size ; ///< Length, in bytes, of this header and appended data and padding. This value MUST be an integer multiple of 4. + uint32_t type ; ///< MUST be as per host operating system specification. + uint32_t offset ; ///< The byte offset from the beginning of this record to the beginning of data. + uint32_t data[0] ; ///< Flexible array contains data +} rndis_msg_out_of_band_data_t, rndis_msg_per_packet_info_t; + +//--------------------------------------------------------------------+ +// NDIS Object ID +//--------------------------------------------------------------------+ + +/// NDIS Object ID +typedef enum +{ + //------------- General Required OIDs -------------// + RNDIS_OID_GEN_SUPPORTED_LIST = 0x00010101, ///< List of supported OIDs + RNDIS_OID_GEN_HARDWARE_STATUS = 0x00010102, ///< Hardware status + RNDIS_OID_GEN_MEDIA_SUPPORTED = 0x00010103, ///< Media types supported (encoded) + RNDIS_OID_GEN_MEDIA_IN_USE = 0x00010104, ///< Media types in use (encoded) + RNDIS_OID_GEN_MAXIMUM_LOOKAHEAD = 0x00010105, ///< + RNDIS_OID_GEN_MAXIMUM_FRAME_SIZE = 0x00010106, ///< Maximum frame size in bytes + RNDIS_OID_GEN_LINK_SPEED = 0x00010107, ///< Link speed in units of 100 bps + RNDIS_OID_GEN_TRANSMIT_BUFFER_SPACE = 0x00010108, ///< Transmit buffer space + RNDIS_OID_GEN_RECEIVE_BUFFER_SPACE = 0x00010109, ///< Receive buffer space + RNDIS_OID_GEN_TRANSMIT_BLOCK_SIZE = 0x0001010A, ///< Minimum amount of storage, in bytes, that a single packet occupies in the transmit buffer space of the NIC + RNDIS_OID_GEN_RECEIVE_BLOCK_SIZE = 0x0001010B, ///< Amount of storage, in bytes, that a single packet occupies in the receive buffer space of the NIC + RNDIS_OID_GEN_VENDOR_ID = 0x0001010C, ///< Vendor NIC code + RNDIS_OID_GEN_VENDOR_DESCRIPTION = 0x0001010D, ///< Vendor network card description + RNDIS_OID_GEN_CURRENT_PACKET_FILTER = 0x0001010E, ///< Current packet filter (encoded) + RNDIS_OID_GEN_CURRENT_LOOKAHEAD = 0x0001010F, ///< Current lookahead size in bytes + RNDIS_OID_GEN_DRIVER_VERSION = 0x00010110, ///< NDIS version number used by the driver + RNDIS_OID_GEN_MAXIMUM_TOTAL_SIZE = 0x00010111, ///< Maximum total packet length in bytes + RNDIS_OID_GEN_PROTOCOL_OPTIONS = 0x00010112, ///< Optional protocol flags (encoded) + RNDIS_OID_GEN_MAC_OPTIONS = 0x00010113, ///< Optional NIC flags (encoded) + RNDIS_OID_GEN_MEDIA_CONNECT_STATUS = 0x00010114, ///< Whether the NIC is connected to the network + RNDIS_OID_GEN_MAXIMUM_SEND_PACKETS = 0x00010115, ///< The maximum number of send packets the driver can accept per call to its MiniportSendPacketsfunction + + //------------- General Optional OIDs -------------// + RNDIS_OID_GEN_VENDOR_DRIVER_VERSION = 0x00010116, ///< Vendor-assigned version number of the driver + RNDIS_OID_GEN_SUPPORTED_GUIDS = 0x00010117, ///< The custom GUIDs (Globally Unique Identifier) supported by the miniport driver + RNDIS_OID_GEN_NETWORK_LAYER_ADDRESSES = 0x00010118, ///< List of network-layer addresses associated with the binding between a transport and the driver + RNDIS_OID_GEN_TRANSPORT_HEADER_OFFSET = 0x00010119, ///< Size of packets' additional headers + RNDIS_OID_GEN_MEDIA_CAPABILITIES = 0x00010201, ///< + RNDIS_OID_GEN_PHYSICAL_MEDIUM = 0x00010202, ///< Physical media supported by the miniport driver (encoded) + + //------------- 802.3 Objects (Ethernet) -------------// + RNDIS_OID_802_3_PERMANENT_ADDRESS = 0x01010101, ///< Permanent station address + RNDIS_OID_802_3_CURRENT_ADDRESS = 0x01010102, ///< Current station address + RNDIS_OID_802_3_MULTICAST_LIST = 0x01010103, ///< Current multicast address list + RNDIS_OID_802_3_MAXIMUM_LIST_SIZE = 0x01010104, ///< Maximum size of multicast address list +} rndis_oid_type_t; + +/// RNDIS Packet Filter Bits \ref RNDIS_OID_GEN_CURRENT_PACKET_FILTER. +typedef enum +{ + RNDIS_PACKET_TYPE_DIRECTED = 0x00000001, ///< Directed packets. Directed packets contain a destination address equal to the station address of the NIC. + RNDIS_PACKET_TYPE_MULTICAST = 0x00000002, ///< Multicast address packets sent to addresses in the multicast address list. + RNDIS_PACKET_TYPE_ALL_MULTICAST = 0x00000004, ///< All multicast address packets, not just the ones enumerated in the multicast address list. + RNDIS_PACKET_TYPE_BROADCAST = 0x00000008, ///< Broadcast packets. + RNDIS_PACKET_TYPE_SOURCE_ROUTING = 0x00000010, ///< All source routing packets. If the protocol driver sets this bit, the NDIS library attempts to act as a source routing bridge. + RNDIS_PACKET_TYPE_PROMISCUOUS = 0x00000020, ///< Specifies all packets regardless of whether VLAN filtering is enabled or not and whether the VLAN identifier matches or not. + RNDIS_PACKET_TYPE_SMT = 0x00000040, ///< SMT packets that an FDDI NIC receives. + RNDIS_PACKET_TYPE_ALL_LOCAL = 0x00000080, ///< All packets sent by installed protocols and all packets indicated by the NIC that is identified by a given NdisBindingHandle. + RNDIS_PACKET_TYPE_GROUP = 0x00001000, ///< Packets sent to the current group address. + RNDIS_PACKET_TYPE_ALL_FUNCTIONAL = 0x00002000, ///< All functional address packets, not just the ones in the current functional address. + RNDIS_PACKET_TYPE_FUNCTIONAL = 0x00004000, ///< Functional address packets sent to addresses included in the current functional address. + RNDIS_PACKET_TYPE_MAC_FRAME = 0x00008000, ///< NIC driver frames that a Token Ring NIC receives. + RNDIS_PACKET_TYPE_NO_LOCAL = 0x00010000, +} rndis_packet_filter_type_t; + +#ifdef __cplusplus + } +#endif + +#endif /* TUSB_CDC_RNDIS_H_ */ + +/** @} */ +/** @} */ diff --git a/libraries/Adafruit_TinyUSB_Arduino/src/class/cdc/serial/ch34x.h b/libraries/Adafruit_TinyUSB_Arduino/src/class/cdc/serial/ch34x.h new file mode 100644 index 0000000..0e08b0a --- /dev/null +++ b/libraries/Adafruit_TinyUSB_Arduino/src/class/cdc/serial/ch34x.h @@ -0,0 +1,84 @@ +/* + * The MIT License (MIT) + * + * Copyright (c) 2023 Heiko Kuester + * + * 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_CH34X_H +#define TUSB_CH34X_H + +// There is no official documentation for the CH34x (CH340, CH341) chips. Reference can be found +// - https://github.com/WCHSoftGroup/ch341ser_linux +// - https://github.com/torvalds/linux/blob/master/drivers/usb/serial/ch341.c +// - https://github.com/freebsd/freebsd-src/blob/main/sys/dev/usb/serial/uchcom.c + +// set line_coding @ enumeration +#ifdef CFG_TUH_CDC_LINE_CODING_ON_ENUM + #define CFG_TUH_CDC_LINE_CODING_ON_ENUM_CH34X CFG_TUH_CDC_LINE_CODING_ON_ENUM +#else // this default is necessary to work properly + #define CFG_TUH_CDC_LINE_CODING_ON_ENUM_CH34X { 9600, CDC_LINE_CONDING_STOP_BITS_1, CDC_LINE_CODING_PARITY_NONE, 8 } +#endif + +// USB requests +#define CH34X_REQ_READ_VERSION 0x5F // dec 95 +#define CH34X_REQ_WRITE_REG 0x9A // dec 154 +#define CH34X_REQ_READ_REG 0x95 // dec 149 +#define CH34X_REQ_SERIAL_INIT 0xA1 // dec 161 +#define CH34X_REQ_MODEM_CTRL 0xA4 // dev 164 + +// registers +#define CH34X_REG_BREAK 0x05 +#define CH34X_REG_PRESCALER 0x12 +#define CH34X_REG_DIVISOR 0x13 +#define CH34X_REG_LCR 0x18 +#define CH34X_REG_LCR2 0x25 +#define CH34X_REG_MCR_MSR 0x06 +#define CH34X_REG_MCR_MSR2 0x07 +#define CH34X_NBREAK_BITS 0x01 + +#define CH341_REG_0x0F 0x0F // undocumented register +#define CH341_REG_0x2C 0x2C // undocumented register +#define CH341_REG_0x27 0x27 // hardware flow control (cts/rts) + +#define CH34X_REG16_DIVISOR_PRESCALER TU_U16(CH34X_REG_DIVISOR, CH34X_REG_PRESCALER) +#define CH32X_REG16_LCR2_LCR TU_U16(CH34X_REG_LCR2, CH34X_REG_LCR) + +// modem control bits +#define CH34X_BIT_RTS (1 << 6) +#define CH34X_BIT_DTR (1 << 5) + +// line control bits +#define CH34X_LCR_ENABLE_RX 0x80 +#define CH34X_LCR_ENABLE_TX 0x40 +#define CH34X_LCR_MARK_SPACE 0x20 +#define CH34X_LCR_PAR_EVEN 0x10 +#define CH34X_LCR_ENABLE_PAR 0x08 +#define CH34X_LCR_PAR_MASK 0x38 // all parity bits +#define CH34X_LCR_STOP_BITS_2 0x04 +#define CH34X_LCR_CS8 0x03 +#define CH34X_LCR_CS7 0x02 +#define CH34X_LCR_CS6 0x01 +#define CH34X_LCR_CS5 0x00 +#define CH34X_LCR_CS_MASK 0x03 // all CSx bits + +#endif // TUSB_CH34X_H diff --git a/libraries/Adafruit_TinyUSB_Arduino/src/class/cdc/serial/cp210x.h b/libraries/Adafruit_TinyUSB_Arduino/src/class/cdc/serial/cp210x.h new file mode 100644 index 0000000..a0eff9e --- /dev/null +++ b/libraries/Adafruit_TinyUSB_Arduino/src/class/cdc/serial/cp210x.h @@ -0,0 +1,114 @@ +/* + * The MIT License (MIT) + * + * Copyright (c) 2023 Ha Thach (thach@tinyusb.org) for Adafruit Industries + * + * 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_CP210X_H +#define TUSB_CP210X_H + +// Protocol details can be found at AN571: CP210x Virtual COM Port Interface +// https://www.silabs.com/documents/public/application-notes/AN571.pdf + +// parts are overtaken from vendors driver +// https://www.silabs.com/documents/public/software/cp210x-3.1.0.tar.gz + +// Config request codes +#define CP210X_IFC_ENABLE 0x00 +#define CP210X_SET_BAUDDIV 0x01 +#define CP210X_GET_BAUDDIV 0x02 +#define CP210X_SET_LINE_CTL 0x03 // Set parity, data bits, stop bits +#define CP210X_GET_LINE_CTL 0x04 +#define CP210X_SET_BREAK 0x05 +#define CP210X_IMM_CHAR 0x06 +#define CP210X_SET_MHS 0x07 // Set DTR, RTS +#define CP210X_GET_MDMSTS 0x08 // Get modem status (DTR, RTS, CTS, DSR, RI, DCD) +#define CP210X_SET_XON 0x09 +#define CP210X_SET_XOFF 0x0A +#define CP210X_SET_EVENTMASK 0x0B +#define CP210X_GET_EVENTMASK 0x0C +#define CP210X_SET_CHAR 0x0D +#define CP210X_GET_CHARS 0x0E +#define CP210X_GET_PROPS 0x0F +#define CP210X_GET_COMM_STATUS 0x10 +#define CP210X_RESET 0x11 +#define CP210X_PURGE 0x12 +#define CP210X_SET_FLOW 0x13 +#define CP210X_GET_FLOW 0x14 +#define CP210X_EMBED_EVENTS 0x15 +#define CP210X_GET_EVENTSTATE 0x16 +#define CP210X_SET_CHARS 0x19 +#define CP210X_GET_BAUDRATE 0x1D +#define CP210X_SET_BAUDRATE 0x1E +#define CP210X_VENDOR_SPECIFIC 0xFF // GPIO, Recipient must be Device + +// SILABSER_IFC_ENABLE_REQUEST_CODE +#define CP210X_UART_ENABLE 0x0001 +#define CP210X_UART_DISABLE 0x0000 + +// SILABSER_SET_BAUDDIV_REQUEST_CODE +#define CP210X_BAUD_RATE_GEN_FREQ 0x384000 + +// SILABSER_SET_LINE_CTL_REQUEST_CODE +#define CP210X_BITS_DATA_MASK 0x0f00 +#define CP210X_BITS_DATA_5 0x0500 +#define CP210X_BITS_DATA_6 0x0600 +#define CP210X_BITS_DATA_7 0x0700 +#define CP210X_BITS_DATA_8 0x0800 +#define CP210X_BITS_DATA_9 0x0900 + +#define CP210X_BITS_PARITY_MASK 0x00f0 +#define CP210X_BITS_PARITY_NONE 0x0000 +#define CP210X_BITS_PARITY_ODD 0x0010 +#define CP210X_BITS_PARITY_EVEN 0x0020 +#define CP210X_BITS_PARITY_MARK 0x0030 +#define CP210X_BITS_PARITY_SPACE 0x0040 + +#define CP210X_BITS_STOP_MASK 0x000f +#define CP210X_BITS_STOP_1 0x0000 +#define CP210X_BITS_STOP_1_5 0x0001 +#define CP210X_BITS_STOP_2 0x0002 + +// SILABSER_SET_BREAK_REQUEST_CODE +#define CP210X_BREAK_ON 0x0001 +#define CP210X_BREAK_OFF 0x0000 + +// SILABSER_SET_MHS_REQUEST_CODE +#define CP210X_MCR_DTR 0x0001 +#define CP210X_MCR_RTS 0x0002 +#define CP210X_MCR_ALL 0x0003 +#define CP210X_MSR_CTS 0x0010 +#define CP210X_MSR_DSR 0x0020 +#define CP210X_MSR_RING 0x0040 +#define CP210X_MSR_DCD 0x0080 +#define CP210X_MSR_ALL 0x00F0 + +#define CP210X_CONTROL_WRITE_DTR 0x0100UL +#define CP210X_CONTROL_WRITE_RTS 0x0200UL + +#define CP210X_LSR_BREAK 0x0001 +#define CP210X_LSR_FRAMING_ERROR 0x0002 +#define CP210X_LSR_HW_OVERRUN 0x0004 +#define CP210X_LSR_QUEUE_OVERRUN 0x0008 +#define CP210X_LSR_PARITY_ERROR 0x0010 +#define CP210X_LSR_ALL 0x001F + +#endif //TUSB_CP210X_H diff --git a/libraries/Adafruit_TinyUSB_Arduino/src/class/cdc/serial/ftdi_sio.h b/libraries/Adafruit_TinyUSB_Arduino/src/class/cdc/serial/ftdi_sio.h new file mode 100644 index 0000000..9bd56ce --- /dev/null +++ b/libraries/Adafruit_TinyUSB_Arduino/src/class/cdc/serial/ftdi_sio.h @@ -0,0 +1,218 @@ +/* + * The MIT License (MIT) + * + * Copyright (c) 2023 Ha Thach (thach@tinyusb.org) for Adafruit Industries + * + * 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_FTDI_SIO_H +#define TUSB_FTDI_SIO_H + +#include + +// Commands +#define FTDI_SIO_RESET 0 // Reset the port +#define FTDI_SIO_MODEM_CTRL 1 // Set the modem control register +#define FTDI_SIO_SET_FLOW_CTRL 2 // Set flow control register +#define FTDI_SIO_SET_BAUD_RATE 3 // Set baud rate +#define FTDI_SIO_SET_DATA 4 // Set the data characteristics of the port +#define FTDI_SIO_GET_MODEM_STATUS 5 // Retrieve current value of modem status register +#define FTDI_SIO_SET_EVENT_CHAR 6 // Set the event character +#define FTDI_SIO_SET_ERROR_CHAR 7 // Set the error character +#define FTDI_SIO_SET_LATENCY_TIMER 9 // Set the latency timer +#define FTDI_SIO_GET_LATENCY_TIMER 10 // Get the latency timer +#define FTDI_SIO_SET_BITMODE 11 // Set bitbang mode +#define FTDI_SIO_READ_PINS 12 // Read immediate value of pins +#define FTDI_SIO_READ_EEPROM 0x90 // Read EEPROM + +// Channel indices for FT2232, FT2232H and FT4232H devices +#define CHANNEL_A 1 +#define CHANNEL_B 2 +#define CHANNEL_C 3 +#define CHANNEL_D 4 + +// Port Identifier Table +#define PIT_DEFAULT 0 // SIOA +#define PIT_SIOA 1 // SIOA +// The device this driver is tested with one has only one port +#define PIT_SIOB 2 // SIOB +#define PIT_PARALLEL 3 // Parallel + +// FTDI_SIO_RESET +#define FTDI_SIO_RESET_REQUEST FTDI_SIO_RESET +#define FTDI_SIO_RESET_REQUEST_TYPE 0x40 +#define FTDI_SIO_RESET_SIO 0 +#define FTDI_SIO_RESET_PURGE_RX 1 +#define FTDI_SIO_RESET_PURGE_TX 2 + +// FTDI_SIO_SET_BAUDRATE +#define FTDI_SIO_SET_BAUDRATE_REQUEST_TYPE 0x40 +#define FTDI_SIO_SET_BAUDRATE_REQUEST 3 + +enum ftdi_sio_baudrate { + ftdi_sio_b300 = 0, + ftdi_sio_b600 = 1, + ftdi_sio_b1200 = 2, + ftdi_sio_b2400 = 3, + ftdi_sio_b4800 = 4, + ftdi_sio_b9600 = 5, + ftdi_sio_b19200 = 6, + ftdi_sio_b38400 = 7, + ftdi_sio_b57600 = 8, + ftdi_sio_b115200 = 9 +}; + +// FTDI_SIO_SET_DATA +#define FTDI_SIO_SET_DATA_REQUEST FTDI_SIO_SET_DATA +#define FTDI_SIO_SET_DATA_REQUEST_TYPE 0x40 +#define FTDI_SIO_SET_DATA_PARITY_NONE (0x0 << 8) +#define FTDI_SIO_SET_DATA_PARITY_ODD (0x1 << 8) +#define FTDI_SIO_SET_DATA_PARITY_EVEN (0x2 << 8) +#define FTDI_SIO_SET_DATA_PARITY_MARK (0x3 << 8) +#define FTDI_SIO_SET_DATA_PARITY_SPACE (0x4 << 8) +#define FTDI_SIO_SET_DATA_STOP_BITS_1 (0x0 << 11) // same coding as ACM +#define FTDI_SIO_SET_DATA_STOP_BITS_15 (0x1 << 11) // 1.5 not supported, for future use? +#define FTDI_SIO_SET_DATA_STOP_BITS_2 (0x2 << 11) +#define FTDI_SIO_SET_BREAK (0x1 << 14) + +// FTDI_SIO_MODEM_CTRL +#define FTDI_SIO_SET_MODEM_CTRL_REQUEST_TYPE 0x40 +#define FTDI_SIO_SET_MODEM_CTRL_REQUEST FTDI_SIO_MODEM_CTRL + +#define FTDI_SIO_SET_DTR_MASK 0x1UL +#define FTDI_SIO_SET_DTR_HIGH ((FTDI_SIO_SET_DTR_MASK << 8) | 1UL) +#define FTDI_SIO_SET_DTR_LOW ((FTDI_SIO_SET_DTR_MASK << 8) | 0UL) +#define FTDI_SIO_SET_RTS_MASK 0x2UL +#define FTDI_SIO_SET_RTS_HIGH ((FTDI_SIO_SET_RTS_MASK << 8) | 2UL) +#define FTDI_SIO_SET_RTS_LOW ((FTDI_SIO_SET_RTS_MASK << 8) | 0UL) + +// FTDI_SIO_SET_FLOW_CTRL +#define FTDI_SIO_SET_FLOW_CTRL_REQUEST_TYPE 0x40 +#define FTDI_SIO_SET_FLOW_CTRL_REQUEST FTDI_SIO_SET_FLOW_CTRL +#define FTDI_SIO_DISABLE_FLOW_CTRL 0x0 +#define FTDI_SIO_RTS_CTS_HS (0x1 << 8) +#define FTDI_SIO_DTR_DSR_HS (0x2 << 8) +#define FTDI_SIO_XON_XOFF_HS (0x4 << 8) + +// FTDI_SIO_GET_LATENCY_TIMER +#define FTDI_SIO_GET_LATENCY_TIMER_REQUEST FTDI_SIO_GET_LATENCY_TIMER +#define FTDI_SIO_GET_LATENCY_TIMER_REQUEST_TYPE 0xC0 + +// FTDI_SIO_SET_LATENCY_TIMER +#define FTDI_SIO_SET_LATENCY_TIMER_REQUEST FTDI_SIO_SET_LATENCY_TIMER +#define FTDI_SIO_SET_LATENCY_TIMER_REQUEST_TYPE 0x40 + +// FTDI_SIO_SET_EVENT_CHAR +#define FTDI_SIO_SET_EVENT_CHAR_REQUEST FTDI_SIO_SET_EVENT_CHAR +#define FTDI_SIO_SET_EVENT_CHAR_REQUEST_TYPE 0x40 + +// FTDI_SIO_GET_MODEM_STATUS +#define FTDI_SIO_GET_MODEM_STATUS_REQUEST_TYPE 0xc0 +#define FTDI_SIO_GET_MODEM_STATUS_REQUEST FTDI_SIO_GET_MODEM_STATUS +#define FTDI_SIO_CTS_MASK 0x10 +#define FTDI_SIO_DSR_MASK 0x20 +#define FTDI_SIO_RI_MASK 0x40 +#define FTDI_SIO_RLSD_MASK 0x80 + +// FTDI_SIO_SET_BITMODE +#define FTDI_SIO_SET_BITMODE_REQUEST_TYPE 0x40 +#define FTDI_SIO_SET_BITMODE_REQUEST FTDI_SIO_SET_BITMODE + +// Possible bitmodes for FTDI_SIO_SET_BITMODE_REQUEST +#define FTDI_SIO_BITMODE_RESET 0x00 +#define FTDI_SIO_BITMODE_CBUS 0x20 + +// FTDI_SIO_READ_PINS +#define FTDI_SIO_READ_PINS_REQUEST_TYPE 0xc0 +#define FTDI_SIO_READ_PINS_REQUEST FTDI_SIO_READ_PINS + +// FTDI_SIO_READ_EEPROM +#define FTDI_SIO_READ_EEPROM_REQUEST_TYPE 0xc0 +#define FTDI_SIO_READ_EEPROM_REQUEST FTDI_SIO_READ_EEPROM + +#define FTDI_FTX_CBUS_MUX_GPIO 0x8 +#define FTDI_FT232R_CBUS_MUX_GPIO 0xa + +#define FTDI_RS0_CTS (1 << 4) +#define FTDI_RS0_DSR (1 << 5) +#define FTDI_RS0_RI (1 << 6) +#define FTDI_RS0_RLSD (1 << 7) + +#define FTDI_RS_DR 1 +#define FTDI_RS_OE (1 << 1) +#define FTDI_RS_PE (1 << 2) +#define FTDI_RS_FE (1 << 3) +#define FTDI_RS_BI (1 << 4) +#define FTDI_RS_THRE (1 << 5) +#define FTDI_RS_TEMT (1 << 6) +#define FTDI_RS_FIFO (1 << 7) + +// chip types and names +typedef enum ftdi_chip_type { + FTDI_SIO = 0, +// FTDI_FT232A, + FTDI_FT232B, + FTDI_FT2232C, + FTDI_FT232R, + FTDI_FT232H, + FTDI_FT2232H, + FTDI_FT4232H, + FTDI_FT4232HA, + FTDI_FT232HP, + FTDI_FT233HP, + FTDI_FT2232HP, + FTDI_FT2233HP, + FTDI_FT4232HP, + FTDI_FT4233HP, + FTDI_FTX, + FTDI_UNKNOWN +} ftdi_chip_type_t; + +#define FTDI_CHIP_NAMES \ + [FTDI_SIO] = "SIO", /* the serial part of FT8U100AX */ \ +/* [FTDI_FT232A] = "FT232A", */ \ + [FTDI_FT232B] = "FT232B", \ + [FTDI_FT2232C] = "FT2232C/D", \ + [FTDI_FT232R] = "FT232R", \ + [FTDI_FT232H] = "FT232H", \ + [FTDI_FT2232H] = "FTDI_FT2232H", \ + [FTDI_FT4232H] = "FT4232H", \ + [FTDI_FT4232HA] = "FT4232HA", \ + [FTDI_FT232HP] = "FT232HP", \ + [FTDI_FT233HP] = "FT233HP", \ + [FTDI_FT2232HP] = "FT2232HP", \ + [FTDI_FT2233HP] = "FT2233HP", \ + [FTDI_FT4232HP] = "FT4232HP", \ + [FTDI_FT4233HP] = "FT4233HP", \ + [FTDI_FTX] = "FT-X", \ + [FTDI_UNKNOWN] = "UNKNOWN" + +// private interface data +typedef struct ftdi_private { + ftdi_chip_type_t chip_type; + uint8_t channel; // channel index, or 0 for legacy types +} ftdi_private_t; + +#define FTDI_OK true +#define FTDI_FAIL false +#define FTDI_NOT_POSSIBLE -1 +#define FTDI_REQUESTED -2 + +#endif //TUSB_FTDI_SIO_H diff --git a/libraries/Adafruit_TinyUSB_Arduino/src/class/cdc/serial/pl2303.h b/libraries/Adafruit_TinyUSB_Arduino/src/class/cdc/serial/pl2303.h new file mode 100644 index 0000000..63910c7 --- /dev/null +++ b/libraries/Adafruit_TinyUSB_Arduino/src/class/cdc/serial/pl2303.h @@ -0,0 +1,159 @@ +/* + * The MIT License (MIT) + * + * Copyright (c) 2024 Heiko Kuester + * + * 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_PL2303_H +#define TUSB_PL2303_H + +#include +#include + +// There is no official documentation for the PL2303 chips. +// Reference can be found +// - https://github.com/torvalds/linux/blob/master/drivers/usb/serial/pl2303.h and +// https://github.com/torvalds/linux/blob/master/drivers/usb/serial/pl2303.c +// - https://github.com/freebsd/freebsd-src/blob/main/sys/dev/usb/serial/uplcom.c + +// quirks +#define PL2303_QUIRK_UART_STATE_IDX0 1 +#define PL2303_QUIRK_LEGACY 2 +#define PL2303_QUIRK_ENDPOINT_HACK 4 + +// requests and bits +#define PL2303_SET_LINE_REQUEST_TYPE 0x21 // class request host to device interface +#define PL2303_SET_LINE_REQUEST 0x20 // dec 32 + +#define PL2303_SET_CONTROL_REQUEST_TYPE 0x21 // class request host to device interface +#define PL2303_SET_CONTROL_REQUEST 0x22 // dec 34 +#define PL2303_CONTROL_DTR 0x01 // dec 1 +#define PL2303_CONTROL_RTS 0x02 // dec 2 + +#define PL2303_BREAK_REQUEST_TYPE 0x21 // class request host to device interface +#define PL2303_BREAK_REQUEST 0x23 // dec 35 +#define PL2303_BREAK_ON 0xffff +#define PL2303_BREAK_OFF 0x0000 + +#define PL2303_GET_LINE_REQUEST_TYPE 0xa1 // class request device to host interface +#define PL2303_GET_LINE_REQUEST 0x21 // dec 33 + +#define PL2303_VENDOR_WRITE_REQUEST_TYPE 0x40 // vendor request host to device interface +#define PL2303_VENDOR_WRITE_REQUEST 0x01 // dec 1 +#define PL2303_VENDOR_WRITE_NREQUEST 0x80 // dec 128 + +#define PL2303_VENDOR_READ_REQUEST_TYPE 0xc0 // vendor request device to host interface +#define PL2303_VENDOR_READ_REQUEST 0x01 // dec 1 +#define PL2303_VENDOR_READ_NREQUEST 0x81 // dec 129 + +#define PL2303_UART_STATE_INDEX 8 +#define PL2303_UART_STATE_MSR_MASK 0x8b +#define PL2303_UART_STATE_TRANSIENT_MASK 0x74 +#define PL2303_UART_DCD 0x01 +#define PL2303_UART_DSR 0x02 +#define PL2303_UART_BREAK_ERROR 0x04 +#define PL2303_UART_RING 0x08 +#define PL2303_UART_FRAME_ERROR 0x10 +#define PL2303_UART_PARITY_ERROR 0x20 +#define PL2303_UART_OVERRUN_ERROR 0x40 +#define PL2303_UART_CTS 0x80 + +#define PL2303_FLOWCTRL_MASK 0xf0 + +#define PL2303_CLEAR_HALT_REQUEST_TYPE 0x02 // standard request host to device endpoint + +// registers via vendor read/write requests +#define PL2303_READ_TYPE_HX_STATUS 0x8080 + +#define PL2303_HXN_RESET_REG 0x07 +#define PL2303_HXN_RESET_UPSTREAM_PIPE 0x02 +#define PL2303_HXN_RESET_DOWNSTREAM_PIPE 0x01 + +#define PL2303_HXN_FLOWCTRL_REG 0x0a +#define PL2303_HXN_FLOWCTRL_MASK 0x1c +#define PL2303_HXN_FLOWCTRL_NONE 0x1c +#define PL2303_HXN_FLOWCTRL_RTS_CTS 0x18 +#define PL2303_HXN_FLOWCTRL_XON_XOFF 0x0c + +// type data +typedef enum pl2303_type { + PL2303_TYPE_H = 0, // 0 + PL2303_TYPE_HX, // 1 + PL2303_TYPE_TA, // 2 + PL2303_TYPE_TB, // 3 + PL2303_TYPE_HXD, // 4 + PL2303_TYPE_HXN, // 5 + PL2303_TYPE_COUNT, + PL2303_TYPE_NEED_SUPPORTS_HX_STATUS, + PL2303_TYPE_UNKNOWN, +} pl2303_type_t; + +typedef struct pl2303_type_data { + uint32_t max_baud_rate; + uint8_t quirks; + uint8_t no_autoxonxoff : 1; + uint8_t no_divisors : 1; + uint8_t alt_divisors : 1; +} pl2303_type_data_t; + +#define PL2303_TYPE_DATA \ + [PL2303_TYPE_H] = { \ + .max_baud_rate = 1228800, .quirks = PL2303_QUIRK_LEGACY, \ + .no_autoxonxoff = 1, .no_divisors = 0, .alt_divisors = 0 \ + }, \ + [PL2303_TYPE_HX] = { \ + .max_baud_rate = 6000000, .quirks = 0, \ + .no_autoxonxoff = 0, .no_divisors = 0, .alt_divisors = 0 \ + }, \ + [PL2303_TYPE_TA] = { \ + .max_baud_rate = 6000000, .quirks = 0, \ + .no_autoxonxoff = 0, .no_divisors = 0, .alt_divisors = 1 \ + }, \ + [PL2303_TYPE_TB] = { \ + .max_baud_rate = 12000000, .quirks = 0, \ + .no_autoxonxoff = 0, .no_divisors = 0, .alt_divisors = 1 \ + }, \ + [PL2303_TYPE_HXD] = { \ + .max_baud_rate = 12000000, .quirks = 0, \ + .no_autoxonxoff = 0, .no_divisors = 0, .alt_divisors = 0 \ + }, \ + [PL2303_TYPE_HXN] = { \ + .max_baud_rate = 12000000, .quirks = 0, \ + .no_autoxonxoff = 0, .no_divisors = 1, .alt_divisors = 0 \ + } + +typedef struct TU_ATTR_PACKED { + pl2303_type_t type; + uint8_t quirks; + bool supports_hx_status; +} pl2303_private_t; + +// buffer sizes for line coding data +#define PL2303_LINE_CODING_BUFSIZE 7 +#define PL2303_LINE_CODING_BAUDRATE_BUFSIZE 4 + +// bulk endpoints +#define PL2303_OUT_EP 0x02 +#define PL2303_IN_EP 0x83 + +#endif // TUSB_PL2303_H diff --git a/libraries/Adafruit_TinyUSB_Arduino/src/class/dfu/dfu.h b/libraries/Adafruit_TinyUSB_Arduino/src/class/dfu/dfu.h new file mode 100644 index 0000000..8cd6365 --- /dev/null +++ b/libraries/Adafruit_TinyUSB_Arduino/src/class/dfu/dfu.h @@ -0,0 +1,119 @@ +/* + * The MIT License (MIT) + * + * Copyright (c) 2021 XMOS LIMITED + * + * 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_DFU_H_ +#define TUSB_DFU_H_ + +#include "common/tusb_common.h" + +#ifdef __cplusplus + extern "C" { +#endif + +//--------------------------------------------------------------------+ +// Common Definitions +//--------------------------------------------------------------------+ + +// DFU Protocol +typedef enum +{ + DFU_PROTOCOL_RT = 0x01, + DFU_PROTOCOL_DFU = 0x02, +} dfu_protocol_type_t; + +// DFU Descriptor Type +typedef enum +{ + DFU_DESC_FUNCTIONAL = 0x21, +} dfu_descriptor_type_t; + +// DFU Requests +typedef enum { + DFU_REQUEST_DETACH = 0, + DFU_REQUEST_DNLOAD = 1, + DFU_REQUEST_UPLOAD = 2, + DFU_REQUEST_GETSTATUS = 3, + DFU_REQUEST_CLRSTATUS = 4, + DFU_REQUEST_GETSTATE = 5, + DFU_REQUEST_ABORT = 6, +} dfu_requests_t; + +// DFU States +typedef enum { + APP_IDLE = 0, + APP_DETACH = 1, + DFU_IDLE = 2, + DFU_DNLOAD_SYNC = 3, + DFU_DNBUSY = 4, + DFU_DNLOAD_IDLE = 5, + DFU_MANIFEST_SYNC = 6, + DFU_MANIFEST = 7, + DFU_MANIFEST_WAIT_RESET = 8, + DFU_UPLOAD_IDLE = 9, + DFU_ERROR = 10, +} dfu_state_t; + +// DFU Status +typedef enum { + DFU_STATUS_OK = 0x00, + DFU_STATUS_ERR_TARGET = 0x01, + DFU_STATUS_ERR_FILE = 0x02, + DFU_STATUS_ERR_WRITE = 0x03, + DFU_STATUS_ERR_ERASE = 0x04, + DFU_STATUS_ERR_CHECK_ERASED = 0x05, + DFU_STATUS_ERR_PROG = 0x06, + DFU_STATUS_ERR_VERIFY = 0x07, + DFU_STATUS_ERR_ADDRESS = 0x08, + DFU_STATUS_ERR_NOTDONE = 0x09, + DFU_STATUS_ERR_FIRMWARE = 0x0A, + DFU_STATUS_ERR_VENDOR = 0x0B, + DFU_STATUS_ERR_USBR = 0x0C, + DFU_STATUS_ERR_POR = 0x0D, + DFU_STATUS_ERR_UNKNOWN = 0x0E, + DFU_STATUS_ERR_STALLEDPKT = 0x0F, +} dfu_status_t; + +#define DFU_ATTR_CAN_DOWNLOAD (1u << 0) +#define DFU_ATTR_CAN_UPLOAD (1u << 1) +#define DFU_ATTR_MANIFESTATION_TOLERANT (1u << 2) +#define DFU_ATTR_WILL_DETACH (1u << 3) + +// DFU Status Request Payload +typedef struct TU_ATTR_PACKED +{ + uint8_t bStatus; + uint8_t bwPollTimeout[3]; + uint8_t bState; + uint8_t iString; +} dfu_status_response_t; + +TU_VERIFY_STATIC( sizeof(dfu_status_response_t) == 6, "size is not correct"); + +#ifdef __cplusplus + } +#endif + +#endif /* TUSB_DFU_H_ */ diff --git a/libraries/Adafruit_TinyUSB_Arduino/src/class/dfu/dfu_device.c b/libraries/Adafruit_TinyUSB_Arduino/src/class/dfu/dfu_device.c new file mode 100644 index 0000000..ee57621 --- /dev/null +++ b/libraries/Adafruit_TinyUSB_Arduino/src/class/dfu/dfu_device.c @@ -0,0 +1,447 @@ +/* + * The MIT License (MIT) + * + * Copyright (c) 2021 XMOS LIMITED + * + * 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. + */ + +#include "tusb_option.h" + +#if (CFG_TUD_ENABLED && CFG_TUD_DFU) + +#include "device/usbd.h" +#include "device/usbd_pvt.h" + +#include "dfu_device.h" + +//--------------------------------------------------------------------+ +// MACRO CONSTANT TYPEDEF +//--------------------------------------------------------------------+ + +// Level where CFG_TUSB_DEBUG must be at least for this driver is logged +#ifndef CFG_TUD_DFU_LOG_LEVEL + #define CFG_TUD_DFU_LOG_LEVEL CFG_TUD_LOG_LEVEL +#endif + +#define TU_LOG_DRV(...) TU_LOG(CFG_TUD_DFU_LOG_LEVEL, __VA_ARGS__) + +//--------------------------------------------------------------------+ +// INTERNAL OBJECT & FUNCTION DECLARATION +//--------------------------------------------------------------------+ +typedef struct { + uint8_t attrs; + uint8_t alt; + uint8_t state; + uint8_t status; + + bool flashing_in_progress; + uint16_t block; + uint16_t length; +} dfu_state_ctx_t; + +static dfu_state_ctx_t _dfu_ctx; + +#if CFG_TUD_DFU_XFER_BUFSIZE > CFG_TUD_ENDPOINT0_BUFSIZE +TU_ATTR_ALIGNED(4) uint8_t _transfer_buf[CFG_TUD_DFU_XFER_BUFSIZE]; +#endif + +static void reset_state(void) { + _dfu_ctx.state = DFU_IDLE; + _dfu_ctx.status = DFU_STATUS_OK; + _dfu_ctx.flashing_in_progress = false; +} + +static inline uint8_t* get_xfer_buffer(void) { + // Use EP0 buffer if it is large enough, otherwise use dedicated buffer + #if CFG_TUD_DFU_XFER_BUFSIZE > CFG_TUD_ENDPOINT0_BUFSIZE + return _transfer_buf; + #else + return usbd_get_ctrl_buf(); + #endif +} + +static bool reply_getstatus(uint8_t rhport, const tusb_control_request_t* request, dfu_state_t state, dfu_status_t status, uint32_t timeout); +static bool process_download_get_status(uint8_t rhport, uint8_t stage, const tusb_control_request_t* request); +static bool process_manifest_get_status(uint8_t rhport, uint8_t stage, const tusb_control_request_t* request); + +//--------------------------------------------------------------------+ +// Weak stubs: invoked if no strong implementation is available +//--------------------------------------------------------------------+ +TU_ATTR_WEAK void tud_dfu_detach_cb(void) { +} + +TU_ATTR_WEAK void tud_dfu_abort_cb(uint8_t alt) { + (void) alt; +} + +TU_ATTR_WEAK uint16_t tud_dfu_upload_cb(uint8_t alt, uint16_t block_num, uint8_t* data, uint16_t length) { + (void) alt; + (void) block_num; + (void) data; + (void) length; + return 0; +} + +//--------------------------------------------------------------------+ +// Debug +//--------------------------------------------------------------------+ +#if CFG_TUSB_DEBUG >= 2 + +tu_static tu_lookup_entry_t const _dfu_request_lookup[] = { + { .key = DFU_REQUEST_DETACH , .data = "DETACH" }, + { .key = DFU_REQUEST_DNLOAD , .data = "DNLOAD" }, + { .key = DFU_REQUEST_UPLOAD , .data = "UPLOAD" }, + { .key = DFU_REQUEST_GETSTATUS, .data = "GETSTATUS" }, + { .key = DFU_REQUEST_CLRSTATUS, .data = "CLRSTATUS" }, + { .key = DFU_REQUEST_GETSTATE , .data = "GETSTATE" }, + { .key = DFU_REQUEST_ABORT , .data = "ABORT" }, +}; + +tu_static tu_lookup_table_t const _dfu_request_table = { + .count = TU_ARRAY_SIZE(_dfu_request_lookup), + .items = _dfu_request_lookup +}; + +tu_static tu_lookup_entry_t const _dfu_state_lookup[] = { + { .key = APP_IDLE , .data = "APP_IDLE" }, + { .key = APP_DETACH , .data = "APP_DETACH" }, + { .key = DFU_IDLE , .data = "IDLE" }, + { .key = DFU_DNLOAD_SYNC , .data = "DNLOAD_SYNC" }, + { .key = DFU_DNBUSY , .data = "DNBUSY" }, + { .key = DFU_DNLOAD_IDLE , .data = "DNLOAD_IDLE" }, + { .key = DFU_MANIFEST_SYNC , .data = "MANIFEST_SYNC" }, + { .key = DFU_MANIFEST , .data = "MANIFEST" }, + { .key = DFU_MANIFEST_WAIT_RESET, .data = "MANIFEST_WAIT_RESET" }, + { .key = DFU_UPLOAD_IDLE , .data = "UPLOAD_IDLE" }, + { .key = DFU_ERROR , .data = "ERROR" }, +}; + +tu_static tu_lookup_table_t const _dfu_state_table = { + .count = TU_ARRAY_SIZE(_dfu_state_lookup), + .items = _dfu_state_lookup +}; + +tu_static tu_lookup_entry_t const _dfu_status_lookup[] = { + { .key = DFU_STATUS_OK , .data = "OK" }, + { .key = DFU_STATUS_ERR_TARGET , .data = "errTARGET" }, + { .key = DFU_STATUS_ERR_FILE , .data = "errFILE" }, + { .key = DFU_STATUS_ERR_WRITE , .data = "errWRITE" }, + { .key = DFU_STATUS_ERR_ERASE , .data = "errERASE" }, + { .key = DFU_STATUS_ERR_CHECK_ERASED , .data = "errCHECK_ERASED" }, + { .key = DFU_STATUS_ERR_PROG , .data = "errPROG" }, + { .key = DFU_STATUS_ERR_VERIFY , .data = "errVERIFY" }, + { .key = DFU_STATUS_ERR_ADDRESS , .data = "errADDRESS" }, + { .key = DFU_STATUS_ERR_NOTDONE , .data = "errNOTDONE" }, + { .key = DFU_STATUS_ERR_FIRMWARE , .data = "errFIRMWARE" }, + { .key = DFU_STATUS_ERR_VENDOR , .data = "errVENDOR" }, + { .key = DFU_STATUS_ERR_USBR , .data = "errUSBR" }, + { .key = DFU_STATUS_ERR_POR , .data = "errPOR" }, + { .key = DFU_STATUS_ERR_UNKNOWN , .data = "errUNKNOWN" }, + { .key = DFU_STATUS_ERR_STALLEDPKT , .data = "errSTALLEDPKT" }, +}; + +tu_static tu_lookup_table_t const _dfu_status_table = { + .count = TU_ARRAY_SIZE(_dfu_status_lookup), + .items = _dfu_status_lookup +}; + +#endif + +//--------------------------------------------------------------------+ +// USBD Driver API +//--------------------------------------------------------------------+ +void dfu_moded_reset(uint8_t rhport) { + (void) rhport; + _dfu_ctx.attrs = 0; + _dfu_ctx.alt = 0; + reset_state(); +} + +void dfu_moded_init(void) { + dfu_moded_reset(0); +} + +bool dfu_moded_deinit(void) { + return true; +} + +uint16_t dfu_moded_open(uint8_t rhport, const tusb_desc_interface_t* itf_desc, uint16_t max_len) { + (void) rhport; + + //------------- Interface (with Alt) descriptor -------------// + const uint8_t itf_num = itf_desc->bInterfaceNumber; + uint8_t alt_count = 0; + + uint16_t drv_len = 0; + TU_VERIFY(itf_desc->bInterfaceSubClass == TUD_DFU_APP_SUBCLASS && itf_desc->bInterfaceProtocol == DFU_PROTOCOL_DFU, 0); + + while(itf_desc->bInterfaceSubClass == TUD_DFU_APP_SUBCLASS && itf_desc->bInterfaceProtocol == DFU_PROTOCOL_DFU) { + TU_ASSERT(max_len > drv_len, 0); + + // Alternate must have the same interface number + TU_ASSERT(itf_desc->bInterfaceNumber == itf_num, 0); + + // Alt should increase by one every time + TU_ASSERT(itf_desc->bAlternateSetting == alt_count, 0); + alt_count++; + + drv_len += tu_desc_len(itf_desc); + itf_desc = (const tusb_desc_interface_t*) tu_desc_next(itf_desc); + } + + //------------- DFU Functional descriptor -------------// + const tusb_desc_dfu_functional_t*func_desc = (const tusb_desc_dfu_functional_t*) itf_desc; + TU_ASSERT(tu_desc_type(func_desc) == TUSB_DESC_FUNCTIONAL, 0); + drv_len += sizeof(tusb_desc_dfu_functional_t); + + _dfu_ctx.attrs = func_desc->bAttributes; + + // CFG_TUD_DFU_XFER_BUFSIZE has to be set to the buffer size used in TUD_DFU_DESCRIPTOR + const uint16_t transfer_size = tu_le16toh( tu_unaligned_read16((const uint8_t*) func_desc + offsetof(tusb_desc_dfu_functional_t, wTransferSize)) ); + TU_ASSERT(transfer_size <= CFG_TUD_DFU_XFER_BUFSIZE, drv_len); + + return drv_len; +} + +// Invoked when a control transfer occurred on an interface of this class +// Driver response accordingly to the request and the transfer stage (setup/data/ack) +// return false to stall control endpoint (e.g unsupported request) +bool dfu_moded_control_xfer_cb(uint8_t rhport, uint8_t stage, const tusb_control_request_t* request) { + TU_VERIFY(request->bmRequestType_bit.recipient == TUSB_REQ_RCPT_INTERFACE); + TU_LOG_DRV(" DFU State : %s, Status: %s\r\n", tu_lookup_find(&_dfu_state_table, _dfu_ctx.state), tu_lookup_find(&_dfu_status_table, _dfu_ctx.status)); + + if (request->bmRequestType_bit.type == TUSB_REQ_TYPE_STANDARD) { + // Standard request include GET/SET_INTERFACE + switch (request->bRequest) { + case TUSB_REQ_SET_INTERFACE: + if (stage == CONTROL_STAGE_SETUP) { + // Switch Alt interface and reset state machine + _dfu_ctx.alt = (uint8_t)request->wValue; + reset_state(); + return tud_control_status(rhport, request); + } + break; + + case TUSB_REQ_GET_INTERFACE: + if (stage == CONTROL_STAGE_SETUP) { + return tud_control_xfer(rhport, request, &_dfu_ctx.alt, 1); + } + break; + + // unsupported request + default: return false; + } + } else if (request->bmRequestType_bit.type == TUSB_REQ_TYPE_CLASS) { + TU_LOG_DRV(" DFU Request: %s\r\n", tu_lookup_find(&_dfu_request_table, request->bRequest)); + + // Class request + switch (request->bRequest) { + case DFU_REQUEST_DETACH: + if (stage == CONTROL_STAGE_SETUP) { + tud_control_status(rhport, request); + } else if (stage == CONTROL_STAGE_ACK) { + tud_dfu_detach_cb(); + } else { + // nothing to do + } + break; + + case DFU_REQUEST_CLRSTATUS: + if (stage == CONTROL_STAGE_SETUP) { + reset_state(); + tud_control_status(rhport, request); + } + break; + + case DFU_REQUEST_GETSTATE: + if (stage == CONTROL_STAGE_SETUP) { + tud_control_xfer(rhport, request, &_dfu_ctx.state, 1); + } + break; + + case DFU_REQUEST_ABORT: + if (stage == CONTROL_STAGE_SETUP) { + reset_state(); + tud_control_status(rhport, request); + } else if (stage == CONTROL_STAGE_ACK) { + tud_dfu_abort_cb(_dfu_ctx.alt); + } else { + // nothing to do + } + break; + + case DFU_REQUEST_UPLOAD: + if (stage == CONTROL_STAGE_SETUP) { + TU_VERIFY(_dfu_ctx.attrs & DFU_ATTR_CAN_UPLOAD); + TU_VERIFY(request->wLength <= CFG_TUD_DFU_XFER_BUFSIZE); + + const uint16_t xfer_len = tud_dfu_upload_cb(_dfu_ctx.alt, request->wValue, get_xfer_buffer(), + request->wLength); + + return tud_control_xfer(rhport, request, get_xfer_buffer(), xfer_len); + } + break; + + case DFU_REQUEST_DNLOAD: + if (stage == CONTROL_STAGE_SETUP) { + TU_VERIFY(_dfu_ctx.attrs & DFU_ATTR_CAN_DOWNLOAD); + TU_VERIFY(_dfu_ctx.state == DFU_IDLE || _dfu_ctx.state == DFU_DNLOAD_IDLE); + TU_VERIFY(request->wLength <= CFG_TUD_DFU_XFER_BUFSIZE); + + // set to true for both download and manifest + _dfu_ctx.flashing_in_progress = true; + + // save block and length for flashing + _dfu_ctx.block = request->wValue; + _dfu_ctx.length = request->wLength; + + if (request->wLength > 0) { + // Download with payload -> transition to DOWNLOAD SYNC + _dfu_ctx.state = DFU_DNLOAD_SYNC; + return tud_control_xfer(rhport, request, get_xfer_buffer(), request->wLength); + } else { + // Download is complete -> transition to MANIFEST SYNC + _dfu_ctx.state = DFU_MANIFEST_SYNC; + return tud_control_status(rhport, request); + } + } + break; + + case DFU_REQUEST_GETSTATUS: + switch (_dfu_ctx.state) { + case DFU_DNLOAD_SYNC: + return process_download_get_status(rhport, stage, request); + break; + + case DFU_MANIFEST_SYNC: + return process_manifest_get_status(rhport, stage, request); + break; + + default: + if (stage == CONTROL_STAGE_SETUP) { + return reply_getstatus(rhport, request, (dfu_state_t) _dfu_ctx.state, (dfu_status_t) _dfu_ctx.status, 0); + } + break; + } + break; + + default: return false; // stall unsupported request + } + } else { + return false; // unsupported request + } + + return true; +} + +void tud_dfu_finish_flashing(uint8_t status) { + _dfu_ctx.flashing_in_progress = false; + + if (status == DFU_STATUS_OK) { + if (_dfu_ctx.state == DFU_DNBUSY) { + _dfu_ctx.state = DFU_DNLOAD_SYNC; + } else if (_dfu_ctx.state == DFU_MANIFEST) { + _dfu_ctx.state = (_dfu_ctx.attrs & DFU_ATTR_MANIFESTATION_TOLERANT) + ? DFU_MANIFEST_SYNC + : DFU_MANIFEST_WAIT_RESET; + } else { + // nothing to do + } + } else { + // failed while flashing, move to dfuError + _dfu_ctx.state = DFU_ERROR; + _dfu_ctx.status = (dfu_status_t)status; + } +} + +static bool process_download_get_status(uint8_t rhport, uint8_t stage, const tusb_control_request_t* request) { + if (stage == CONTROL_STAGE_SETUP) { + // only transition to next state on CONTROL_STAGE_ACK + dfu_state_t next_state; + uint32_t timeout; + + if (_dfu_ctx.flashing_in_progress) { + next_state = DFU_DNBUSY; + timeout = tud_dfu_get_timeout_cb(_dfu_ctx.alt, (uint8_t)next_state); + } else { + next_state = DFU_DNLOAD_IDLE; + timeout = 0; + } + + return reply_getstatus(rhport, request, next_state, (dfu_status_t) _dfu_ctx.status, timeout); + } else if (stage == CONTROL_STAGE_ACK) { + if (_dfu_ctx.flashing_in_progress) { + _dfu_ctx.state = DFU_DNBUSY; + tud_dfu_download_cb(_dfu_ctx.alt, _dfu_ctx.block, get_xfer_buffer(), _dfu_ctx.length); + } else { + _dfu_ctx.state = DFU_DNLOAD_IDLE; + } + } else { + // nothing to do + } + + return true; +} + +static bool process_manifest_get_status(uint8_t rhport, uint8_t stage, const tusb_control_request_t* request) { + if (stage == CONTROL_STAGE_SETUP) { + // only transition to next state on CONTROL_STAGE_ACK + dfu_state_t next_state; + uint32_t timeout; + + if (_dfu_ctx.flashing_in_progress) { + next_state = DFU_MANIFEST; + timeout = tud_dfu_get_timeout_cb(_dfu_ctx.alt, next_state); + } else { + next_state = DFU_IDLE; + timeout = 0; + } + + return reply_getstatus(rhport, request, next_state, (dfu_status_t) _dfu_ctx.status, timeout); + } else if (stage == CONTROL_STAGE_ACK) { + if (_dfu_ctx.flashing_in_progress) { + _dfu_ctx.state = DFU_MANIFEST; + tud_dfu_manifest_cb(_dfu_ctx.alt); + } else { + _dfu_ctx.state = DFU_IDLE; + } + } else { + // nothing to do + } + + return true; +} + +static bool reply_getstatus(uint8_t rhport, const tusb_control_request_t* request, dfu_state_t state, + dfu_status_t status, uint32_t timeout) { + dfu_status_response_t resp; + resp.bStatus = (uint8_t)status; + resp.bwPollTimeout[0] = TU_U32_BYTE0(timeout); + resp.bwPollTimeout[1] = TU_U32_BYTE1(timeout); + resp.bwPollTimeout[2] = TU_U32_BYTE2(timeout); + resp.bState = (uint8_t)state; + resp.iString = 0; + + return tud_control_xfer(rhport, request, &resp, sizeof(dfu_status_response_t)); +} + +#endif diff --git a/libraries/Adafruit_TinyUSB_Arduino/src/class/dfu/dfu_device.h b/libraries/Adafruit_TinyUSB_Arduino/src/class/dfu/dfu_device.h new file mode 100644 index 0000000..b22b4c4 --- /dev/null +++ b/libraries/Adafruit_TinyUSB_Arduino/src/class/dfu/dfu_device.h @@ -0,0 +1,99 @@ +/* + * The MIT License (MIT) + * + * Copyright (c) 2021 XMOS LIMITED + * + * 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_DFU_DEVICE_H_ +#define TUSB_DFU_DEVICE_H_ + +#include "dfu.h" + +#ifdef __cplusplus + extern "C" { +#endif + +//--------------------------------------------------------------------+ +// Class Driver Default Configure & Validation +//--------------------------------------------------------------------+ + +#if !defined(CFG_TUD_DFU_XFER_BUFSIZE) + #error "CFG_TUD_DFU_XFER_BUFSIZE must be defined, it has to be set to the buffer size used in TUD_DFU_DESCRIPTOR" +#endif + +//--------------------------------------------------------------------+ +// Application API +//--------------------------------------------------------------------+ + +// Must be called when the application is done with flashing started by +// tud_dfu_download_cb() and tud_dfu_manifest_cb(). +// status is DFU_STATUS_OK if successful, any other error status will cause state to enter dfuError +void tud_dfu_finish_flashing(uint8_t status); + +//--------------------------------------------------------------------+ +// Application Callback API (weak is optional) +//--------------------------------------------------------------------+ + +// Note: alt is used as the partition number, in order to support multiple partitions like FLASH, EEPROM, etc. + +// Invoked right before tud_dfu_download_cb() (state=DFU_DNBUSY) or tud_dfu_manifest_cb() (state=DFU_MANIFEST) +// Application return timeout in milliseconds (bwPollTimeout) for the next download/manifest operation. +// During this period, USB host won't try to communicate with us. +uint32_t tud_dfu_get_timeout_cb(uint8_t alt, uint8_t state); + +// Invoked when received DFU_DNLOAD (wLength>0) following by DFU_GETSTATUS (state=DFU_DNBUSY) requests +// This callback could be returned before flashing op is complete (async). +// Once finished flashing, application must call tud_dfu_finish_flashing() +void tud_dfu_download_cb (uint8_t alt, uint16_t block_num, uint8_t const *data, uint16_t length); + +// Invoked when download process is complete, received DFU_DNLOAD (wLength=0) following by DFU_GETSTATUS (state=Manifest) +// Application can do checksum, or actual flashing if buffered entire image previously. +// Once finished flashing, application must call tud_dfu_finish_flashing() +void tud_dfu_manifest_cb(uint8_t alt); + +// Invoked when received DFU_UPLOAD request +// Application must populate data with up to length bytes and +// Return the number of written bytes +uint16_t tud_dfu_upload_cb(uint8_t alt, uint16_t block_num, uint8_t* data, uint16_t length); + +// Invoked when a DFU_DETACH request is received +void tud_dfu_detach_cb(void); + +// Invoked when the Host has terminated a download or upload transfer +void tud_dfu_abort_cb(uint8_t alt); + +//--------------------------------------------------------------------+ +// Internal Class Driver API +//--------------------------------------------------------------------+ +void dfu_moded_init(void); +bool dfu_moded_deinit(void); +void dfu_moded_reset(uint8_t rhport); +uint16_t dfu_moded_open(uint8_t rhport, tusb_desc_interface_t const * itf_desc, uint16_t max_len); +bool dfu_moded_control_xfer_cb(uint8_t rhport, uint8_t stage, tusb_control_request_t const * request); + + +#ifdef __cplusplus + } +#endif + +#endif /* TUSB_DFU_MODE_DEVICE_H_ */ diff --git a/libraries/Adafruit_TinyUSB_Arduino/src/class/dfu/dfu_rt_device.c b/libraries/Adafruit_TinyUSB_Arduino/src/class/dfu/dfu_rt_device.c new file mode 100644 index 0000000..a63c23d --- /dev/null +++ b/libraries/Adafruit_TinyUSB_Arduino/src/class/dfu/dfu_rt_device.c @@ -0,0 +1,137 @@ +/* + * The MIT License (MIT) + * + * Copyright (c) 2019 Sylvain Munaut + * + * 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. + */ + +#include "tusb_option.h" + +#if (CFG_TUD_ENABLED && CFG_TUD_DFU_RUNTIME) + +#include "device/usbd.h" +#include "device/usbd_pvt.h" + +#include "dfu_rt_device.h" + +//--------------------------------------------------------------------+ +// MACRO CONSTANT TYPEDEF +//--------------------------------------------------------------------+ + +// Level where CFG_TUSB_DEBUG must be at least for this driver is logged +#ifndef CFG_TUD_DFU_RUNTIME_LOG_LEVEL + #define CFG_TUD_DFU_RUNTIME_LOG_LEVEL CFG_TUD_LOG_LEVEL +#endif + +#define TU_LOG_DRV(...) TU_LOG(CFG_TUD_DFU_RUNTIME_LOG_LEVEL, __VA_ARGS__) + +//--------------------------------------------------------------------+ +// INTERNAL OBJECT & FUNCTION DECLARATION +//--------------------------------------------------------------------+ + +//--------------------------------------------------------------------+ +// USBD Driver API +//--------------------------------------------------------------------+ +void dfu_rtd_init(void) { +} + +bool dfu_rtd_deinit(void) { + return true; +} + +void dfu_rtd_reset(uint8_t rhport) { + (void) rhport; +} + +uint16_t dfu_rtd_open(uint8_t rhport, tusb_desc_interface_t const * itf_desc, uint16_t max_len) +{ + (void) rhport; + (void) max_len; + + // Ensure this is DFU Runtime + TU_VERIFY((itf_desc->bInterfaceSubClass == TUD_DFU_APP_SUBCLASS) && + (itf_desc->bInterfaceProtocol == DFU_PROTOCOL_RT), 0); + + uint8_t const * p_desc = tu_desc_next( itf_desc ); + uint16_t drv_len = sizeof(tusb_desc_interface_t); + + if ( TUSB_DESC_FUNCTIONAL == tu_desc_type(p_desc) ) + { + drv_len += tu_desc_len(p_desc); + p_desc = tu_desc_next(p_desc); + } + + return drv_len; +} + +// Invoked when a control transfer occurred on an interface of this class +// Driver response accordingly to the request and the transfer stage (setup/data/ack) +// return false to stall control endpoint (e.g unsupported request) +bool dfu_rtd_control_xfer_cb(uint8_t rhport, uint8_t stage, tusb_control_request_t const * request) +{ + // nothing to do with DATA or ACK stage + if ( stage != CONTROL_STAGE_SETUP ) return true; + + TU_VERIFY(request->bmRequestType_bit.recipient == TUSB_REQ_RCPT_INTERFACE); + + // dfu-util will try to claim the interface with SET_INTERFACE request before sending DFU request + if ( TUSB_REQ_TYPE_STANDARD == request->bmRequestType_bit.type && + TUSB_REQ_SET_INTERFACE == request->bRequest ) + { + tud_control_status(rhport, request); + return true; + } + + // Handle class request only from here + TU_VERIFY(request->bmRequestType_bit.type == TUSB_REQ_TYPE_CLASS); + + switch (request->bRequest) + { + case DFU_REQUEST_DETACH: + { + TU_LOG_DRV(" DFU RT Request: DETACH\r\n"); + tud_control_status(rhport, request); + tud_dfu_runtime_reboot_to_dfu_cb(); + } + break; + + case DFU_REQUEST_GETSTATUS: + { + TU_LOG_DRV(" DFU RT Request: GETSTATUS\r\n"); + dfu_status_response_t resp; + // Status = OK, Poll timeout is ignored during RT, State = APP_IDLE, IString = 0 + TU_VERIFY(tu_memset_s(&resp, sizeof(resp), 0x00, sizeof(resp))==0); + tud_control_xfer(rhport, request, &resp, sizeof(dfu_status_response_t)); + } + break; + + default: + { + TU_LOG_DRV(" DFU RT Unexpected Request: %d\r\n", request->bRequest); + return false; // stall unsupported request + } + } + + return true; +} + +#endif diff --git a/libraries/Adafruit_TinyUSB_Arduino/src/class/dfu/dfu_rt_device.h b/libraries/Adafruit_TinyUSB_Arduino/src/class/dfu/dfu_rt_device.h new file mode 100644 index 0000000..c4116d8 --- /dev/null +++ b/libraries/Adafruit_TinyUSB_Arduino/src/class/dfu/dfu_rt_device.h @@ -0,0 +1,55 @@ +/* + * The MIT License (MIT) + * + * Copyright (c) 2019 Sylvain Munaut + * + * 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_DFU_RT_DEVICE_H_ +#define TUSB_DFU_RT_DEVICE_H_ + +#include "dfu.h" + +#ifdef __cplusplus + extern "C" { +#endif + +//--------------------------------------------------------------------+ +// Application Callback API (weak is optional) +//--------------------------------------------------------------------+ +// Invoked when a DFU_DETACH request is received and bitWillDetach is set +void tud_dfu_runtime_reboot_to_dfu_cb(void); + +//--------------------------------------------------------------------+ +// Internal Class Driver API +//--------------------------------------------------------------------+ +void dfu_rtd_init(void); +bool dfu_rtd_deinit(void); +void dfu_rtd_reset(uint8_t rhport); +uint16_t dfu_rtd_open(uint8_t rhport, tusb_desc_interface_t const * itf_desc, uint16_t max_len); +bool dfu_rtd_control_xfer_cb(uint8_t rhport, uint8_t stage, tusb_control_request_t const * request); + +#ifdef __cplusplus + } +#endif + +#endif /* TUSB_DFU_RT_DEVICE_H_ */ diff --git a/libraries/Adafruit_TinyUSB_Arduino/src/class/hid/hid.h b/libraries/Adafruit_TinyUSB_Arduino/src/class/hid/hid.h new file mode 100644 index 0000000..0883d95 --- /dev/null +++ b/libraries/Adafruit_TinyUSB_Arduino/src/class/hid/hid.h @@ -0,0 +1,2076 @@ +/* + * 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. + * + * This file is part of the TinyUSB stack. + */ + +/** \ingroup group_class + * \defgroup ClassDriver_HID Human Interface Device (HID) + * @{ */ + +#ifndef TUSB_HID_H_ +#define TUSB_HID_H_ + +#include "common/tusb_common.h" + +#ifdef __cplusplus + extern "C" { +#endif + +//--------------------------------------------------------------------+ +// Common Definitions +//--------------------------------------------------------------------+ +/** \defgroup ClassDriver_HID_Common Common Definitions + * @{ */ + +/// USB HID Descriptor +typedef struct TU_ATTR_PACKED +{ + uint8_t bLength; /**< Numeric expression that is the total size of the HID descriptor */ + uint8_t bDescriptorType; /**< Constant name specifying type of HID descriptor. */ + + uint16_t bcdHID; /**< Numeric expression identifying the HID Class Specification release */ + uint8_t bCountryCode; /**< Numeric expression identifying country code of the localized hardware. */ + uint8_t bNumDescriptors; /**< Numeric expression specifying the number of class descriptors */ + + uint8_t bReportType; /**< Type of HID class report. */ + uint16_t wReportLength; /**< the total size of the Report descriptor. */ +} tusb_hid_descriptor_hid_t; + +/// HID Subclass +typedef enum +{ + HID_SUBCLASS_NONE = 0, ///< No Subclass + HID_SUBCLASS_BOOT = 1 ///< Boot Interface Subclass +}hid_subclass_enum_t; + +/// HID Interface Protocol +typedef enum +{ + HID_ITF_PROTOCOL_NONE = 0, ///< None + HID_ITF_PROTOCOL_KEYBOARD = 1, ///< Keyboard + HID_ITF_PROTOCOL_MOUSE = 2 ///< Mouse +}hid_interface_protocol_enum_t; + +/// HID Descriptor Type +typedef enum +{ + HID_DESC_TYPE_HID = 0x21, ///< HID Descriptor + HID_DESC_TYPE_REPORT = 0x22, ///< Report Descriptor + HID_DESC_TYPE_PHYSICAL = 0x23 ///< Physical Descriptor +}hid_descriptor_enum_t; + +/// HID Request Report Type +typedef enum +{ + HID_REPORT_TYPE_INVALID = 0, + HID_REPORT_TYPE_INPUT, ///< Input + HID_REPORT_TYPE_OUTPUT, ///< Output + HID_REPORT_TYPE_FEATURE ///< Feature +}hid_report_type_t; + +/// HID Class Specific Control Request +typedef enum +{ + HID_REQ_CONTROL_GET_REPORT = 0x01, ///< Get Report + HID_REQ_CONTROL_GET_IDLE = 0x02, ///< Get Idle + HID_REQ_CONTROL_GET_PROTOCOL = 0x03, ///< Get Protocol + HID_REQ_CONTROL_SET_REPORT = 0x09, ///< Set Report + HID_REQ_CONTROL_SET_IDLE = 0x0a, ///< Set Idle + HID_REQ_CONTROL_SET_PROTOCOL = 0x0b ///< Set Protocol +}hid_request_enum_t; + +/// HID Local Code +typedef enum +{ + HID_LOCAL_NotSupported = 0 , ///< NotSupported + HID_LOCAL_Arabic , ///< Arabic + HID_LOCAL_Belgian , ///< Belgian + HID_LOCAL_Canadian_Bilingual , ///< Canadian_Bilingual + HID_LOCAL_Canadian_French , ///< Canadian_French + HID_LOCAL_Czech_Republic , ///< Czech_Republic + HID_LOCAL_Danish , ///< Danish + HID_LOCAL_Finnish , ///< Finnish + HID_LOCAL_French , ///< French + HID_LOCAL_German , ///< German + HID_LOCAL_Greek , ///< Greek + HID_LOCAL_Hebrew , ///< Hebrew + HID_LOCAL_Hungary , ///< Hungary + HID_LOCAL_International , ///< International + HID_LOCAL_Italian , ///< Italian + HID_LOCAL_Japan_Katakana , ///< Japan_Katakana + HID_LOCAL_Korean , ///< Korean + HID_LOCAL_Latin_American , ///< Latin_American + HID_LOCAL_Netherlands_Dutch , ///< Netherlands/Dutch + HID_LOCAL_Norwegian , ///< Norwegian + HID_LOCAL_Persian_Farsi , ///< Persian (Farsi) + HID_LOCAL_Poland , ///< Poland + HID_LOCAL_Portuguese , ///< Portuguese + HID_LOCAL_Russia , ///< Russia + HID_LOCAL_Slovakia , ///< Slovakia + HID_LOCAL_Spanish , ///< Spanish + HID_LOCAL_Swedish , ///< Swedish + HID_LOCAL_Swiss_French , ///< Swiss/French + HID_LOCAL_Swiss_German , ///< Swiss/German + HID_LOCAL_Switzerland , ///< Switzerland + HID_LOCAL_Taiwan , ///< Taiwan + HID_LOCAL_Turkish_Q , ///< Turkish-Q + HID_LOCAL_UK , ///< UK + HID_LOCAL_US , ///< US + HID_LOCAL_Yugoslavia , ///< Yugoslavia + HID_LOCAL_Turkish_F ///< Turkish-F +} hid_local_enum_t; + +// HID protocol value used by GetProtocol / SetProtocol +typedef enum +{ + HID_PROTOCOL_BOOT = 0, + HID_PROTOCOL_REPORT = 1 +} hid_protocol_mode_enum_t; + +/** @} */ + +//--------------------------------------------------------------------+ +// GAMEPAD +//--------------------------------------------------------------------+ +/** \addtogroup ClassDriver_HID_Gamepad Gamepad + * @{ */ + +/* From https://www.kernel.org/doc/html/latest/input/gamepad.html + ____________________________ __ + / [__ZL__] [__ZR__] \ | + / [__ TL __] [__ TR __] \ | Front Triggers + __/________________________________\__ __| + / _ \ | + / /\ __ (N) \ | + / || __ |MO| __ _ _ \ | Main Pad + | <===DP===> |SE| |ST| (W) -|- (E) | | + \ || ___ ___ _ / | + /\ \/ / \ / \ (S) /\ __| + / \________ | LS | ____ | RS | ________/ \ | +| / \ \___/ / \ \___/ / \ | | Control Sticks +| / \_____/ \_____/ \ | __| +| / \ | + \_____/ \_____/ + + |________|______| |______|___________| + D-Pad Left Right Action Pad + Stick Stick + + |_____________| + Menu Pad + + Most gamepads have the following features: + - Action-Pad 4 buttons in diamonds-shape (on the right side) NORTH, SOUTH, WEST and EAST. + - D-Pad (Direction-pad) 4 buttons (on the left side) that point up, down, left and right. + - Menu-Pad Different constellations, but most-times 2 buttons: SELECT - START. + - Analog-Sticks provide freely moveable sticks to control directions, Analog-sticks may also + provide a digital button if you press them. + - Triggers are located on the upper-side of the pad in vertical direction. The upper buttons + are normally named Left- and Right-Triggers, the lower buttons Z-Left and Z-Right. + - Rumble Many devices provide force-feedback features. But are mostly just simple rumble motors. + */ + +/// HID Gamepad Protocol Report. +typedef struct TU_ATTR_PACKED +{ + int8_t x; ///< Delta x movement of left analog-stick + int8_t y; ///< Delta y movement of left analog-stick + int8_t z; ///< Delta z movement of right analog-joystick + int8_t rz; ///< Delta Rz movement of right analog-joystick + int8_t rx; ///< Delta Rx movement of analog left trigger + int8_t ry; ///< Delta Ry movement of analog right trigger + uint8_t hat; ///< Buttons mask for currently pressed buttons in the DPad/hat + uint32_t buttons; ///< Buttons mask for currently pressed buttons +}hid_gamepad_report_t; + +/// Standard Gamepad Buttons Bitmap +typedef enum +{ + GAMEPAD_BUTTON_0 = TU_BIT(0), + GAMEPAD_BUTTON_1 = TU_BIT(1), + GAMEPAD_BUTTON_2 = TU_BIT(2), + GAMEPAD_BUTTON_3 = TU_BIT(3), + GAMEPAD_BUTTON_4 = TU_BIT(4), + GAMEPAD_BUTTON_5 = TU_BIT(5), + GAMEPAD_BUTTON_6 = TU_BIT(6), + GAMEPAD_BUTTON_7 = TU_BIT(7), + GAMEPAD_BUTTON_8 = TU_BIT(8), + GAMEPAD_BUTTON_9 = TU_BIT(9), + GAMEPAD_BUTTON_10 = TU_BIT(10), + GAMEPAD_BUTTON_11 = TU_BIT(11), + GAMEPAD_BUTTON_12 = TU_BIT(12), + GAMEPAD_BUTTON_13 = TU_BIT(13), + GAMEPAD_BUTTON_14 = TU_BIT(14), + GAMEPAD_BUTTON_15 = TU_BIT(15), + GAMEPAD_BUTTON_16 = TU_BIT(16), + GAMEPAD_BUTTON_17 = TU_BIT(17), + GAMEPAD_BUTTON_18 = TU_BIT(18), + GAMEPAD_BUTTON_19 = TU_BIT(19), + GAMEPAD_BUTTON_20 = TU_BIT(20), + GAMEPAD_BUTTON_21 = TU_BIT(21), + GAMEPAD_BUTTON_22 = TU_BIT(22), + GAMEPAD_BUTTON_23 = TU_BIT(23), + GAMEPAD_BUTTON_24 = TU_BIT(24), + GAMEPAD_BUTTON_25 = TU_BIT(25), + GAMEPAD_BUTTON_26 = TU_BIT(26), + GAMEPAD_BUTTON_27 = TU_BIT(27), + GAMEPAD_BUTTON_28 = TU_BIT(28), + GAMEPAD_BUTTON_29 = TU_BIT(29), + GAMEPAD_BUTTON_30 = TU_BIT(30), + GAMEPAD_BUTTON_31 = TU_BIT(31), +}hid_gamepad_button_bm_t; + +/// Standard Gamepad Buttons Naming from Linux input event codes +/// https://github.com/torvalds/linux/blob/master/include/uapi/linux/input-event-codes.h +#define GAMEPAD_BUTTON_A GAMEPAD_BUTTON_0 +#define GAMEPAD_BUTTON_SOUTH GAMEPAD_BUTTON_0 + +#define GAMEPAD_BUTTON_B GAMEPAD_BUTTON_1 +#define GAMEPAD_BUTTON_EAST GAMEPAD_BUTTON_1 + +#define GAMEPAD_BUTTON_C GAMEPAD_BUTTON_2 + +#define GAMEPAD_BUTTON_X GAMEPAD_BUTTON_3 +#define GAMEPAD_BUTTON_NORTH GAMEPAD_BUTTON_3 + +#define GAMEPAD_BUTTON_Y GAMEPAD_BUTTON_4 +#define GAMEPAD_BUTTON_WEST GAMEPAD_BUTTON_4 + +#define GAMEPAD_BUTTON_Z GAMEPAD_BUTTON_5 +#define GAMEPAD_BUTTON_TL GAMEPAD_BUTTON_6 +#define GAMEPAD_BUTTON_TR GAMEPAD_BUTTON_7 +#define GAMEPAD_BUTTON_TL2 GAMEPAD_BUTTON_8 +#define GAMEPAD_BUTTON_TR2 GAMEPAD_BUTTON_9 +#define GAMEPAD_BUTTON_SELECT GAMEPAD_BUTTON_10 +#define GAMEPAD_BUTTON_START GAMEPAD_BUTTON_11 +#define GAMEPAD_BUTTON_MODE GAMEPAD_BUTTON_12 +#define GAMEPAD_BUTTON_THUMBL GAMEPAD_BUTTON_13 +#define GAMEPAD_BUTTON_THUMBR GAMEPAD_BUTTON_14 + +/// Standard Gamepad HAT/DPAD Buttons (from Linux input event codes) +typedef enum +{ + GAMEPAD_HAT_CENTERED = 0, ///< DPAD_CENTERED + GAMEPAD_HAT_UP = 1, ///< DPAD_UP + GAMEPAD_HAT_UP_RIGHT = 2, ///< DPAD_UP_RIGHT + GAMEPAD_HAT_RIGHT = 3, ///< DPAD_RIGHT + GAMEPAD_HAT_DOWN_RIGHT = 4, ///< DPAD_DOWN_RIGHT + GAMEPAD_HAT_DOWN = 5, ///< DPAD_DOWN + GAMEPAD_HAT_DOWN_LEFT = 6, ///< DPAD_DOWN_LEFT + GAMEPAD_HAT_LEFT = 7, ///< DPAD_LEFT + GAMEPAD_HAT_UP_LEFT = 8, ///< DPAD_UP_LEFT +}hid_gamepad_hat_t; + +/// @} + +//--------------------------------------------------------------------+ +// MOUSE +//--------------------------------------------------------------------+ +/** \addtogroup ClassDriver_HID_Mouse Mouse + * @{ */ + +/// Standard HID Boot Protocol Mouse Report. +typedef struct TU_ATTR_PACKED +{ + uint8_t buttons; /**< buttons mask for currently pressed buttons in the mouse. */ + int8_t x; /**< Current delta x movement of the mouse. */ + int8_t y; /**< Current delta y movement on the mouse. */ + int8_t wheel; /**< Current delta wheel movement on the mouse. */ + int8_t pan; // using AC Pan +} hid_mouse_report_t; + + +// Absolute Mouse: same as the Standard (relative) Mouse Report but +// with int16_t instead of int8_t for X and Y coordinates. +typedef struct TU_ATTR_PACKED +{ + uint8_t buttons; /**< buttons mask for currently pressed buttons in the mouse. */ + int16_t x; /**< Current x position of the mouse. */ + int16_t y; /**< Current y position of the mouse. */ + int8_t wheel; /**< Current delta wheel movement on the mouse. */ + int8_t pan; // using AC Pan +} hid_abs_mouse_report_t; + + +/// Standard Mouse Buttons Bitmap +typedef enum +{ + MOUSE_BUTTON_LEFT = TU_BIT(0), ///< Left button + MOUSE_BUTTON_RIGHT = TU_BIT(1), ///< Right button + MOUSE_BUTTON_MIDDLE = TU_BIT(2), ///< Middle button + MOUSE_BUTTON_BACKWARD = TU_BIT(3), ///< Backward button, + MOUSE_BUTTON_FORWARD = TU_BIT(4), ///< Forward button, +}hid_mouse_button_bm_t; + +/// @} + +//--------------------------------------------------------------------+ +// Digitizer Stylus Pen +//--------------------------------------------------------------------+ +/** \addtogroup ClassDriver_HID_Stylus Stylus + * @{ */ + +// Standard Stylus Pen Report. +typedef struct TU_ATTR_PACKED +{ + uint8_t attr; /**< Attribute mask for describing current status of the stylus pen. */ + uint16_t x; /**< Current x position of the mouse. */ + uint16_t y; /**< Current y position of the mouse. */ +} hid_stylus_report_t; + +// Standard Stylus Pen Attributes Bitmap. +typedef enum +{ + STYLUS_ATTR_TIP_SWITCH = TU_BIT(0), ///< Tip switch + STYLUS_ATTR_IN_RANGE = TU_BIT(1), ///< In-range bit. +} hid_stylus_attr_bm_t; + +/// @} + +//--------------------------------------------------------------------+ +// Keyboard +//--------------------------------------------------------------------+ +/** \addtogroup ClassDriver_HID_Keyboard Keyboard + * @{ */ + +/// Standard HID Boot Protocol Keyboard Report. +typedef struct TU_ATTR_PACKED +{ + uint8_t modifier; /**< Keyboard modifier (KEYBOARD_MODIFIER_* masks). */ + uint8_t reserved; /**< Reserved for OEM use, always set to 0. */ + uint8_t keycode[6]; /**< Key codes of the currently pressed keys. */ +} hid_keyboard_report_t; + +/// Keyboard modifier codes bitmap +typedef enum +{ + KEYBOARD_MODIFIER_LEFTCTRL = TU_BIT(0), ///< Left Control + KEYBOARD_MODIFIER_LEFTSHIFT = TU_BIT(1), ///< Left Shift + KEYBOARD_MODIFIER_LEFTALT = TU_BIT(2), ///< Left Alt + KEYBOARD_MODIFIER_LEFTGUI = TU_BIT(3), ///< Left Window + KEYBOARD_MODIFIER_RIGHTCTRL = TU_BIT(4), ///< Right Control + KEYBOARD_MODIFIER_RIGHTSHIFT = TU_BIT(5), ///< Right Shift + KEYBOARD_MODIFIER_RIGHTALT = TU_BIT(6), ///< Right Alt + KEYBOARD_MODIFIER_RIGHTGUI = TU_BIT(7) ///< Right Window +}hid_keyboard_modifier_bm_t; + +typedef enum +{ + KEYBOARD_LED_NUMLOCK = TU_BIT(0), ///< Num Lock LED + KEYBOARD_LED_CAPSLOCK = TU_BIT(1), ///< Caps Lock LED + KEYBOARD_LED_SCROLLLOCK = TU_BIT(2), ///< Scroll Lock LED + KEYBOARD_LED_COMPOSE = TU_BIT(3), ///< Composition Mode + KEYBOARD_LED_KANA = TU_BIT(4) ///< Kana mode +}hid_keyboard_led_bm_t; + +/// @} + +//--------------------------------------------------------------------+ +// HID KEYCODE +//--------------------------------------------------------------------+ +#define HID_KEY_NONE 0x00 +#define HID_KEY_A 0x04 +#define HID_KEY_B 0x05 +#define HID_KEY_C 0x06 +#define HID_KEY_D 0x07 +#define HID_KEY_E 0x08 +#define HID_KEY_F 0x09 +#define HID_KEY_G 0x0A +#define HID_KEY_H 0x0B +#define HID_KEY_I 0x0C +#define HID_KEY_J 0x0D +#define HID_KEY_K 0x0E +#define HID_KEY_L 0x0F +#define HID_KEY_M 0x10 +#define HID_KEY_N 0x11 +#define HID_KEY_O 0x12 +#define HID_KEY_P 0x13 +#define HID_KEY_Q 0x14 +#define HID_KEY_R 0x15 +#define HID_KEY_S 0x16 +#define HID_KEY_T 0x17 +#define HID_KEY_U 0x18 +#define HID_KEY_V 0x19 +#define HID_KEY_W 0x1A +#define HID_KEY_X 0x1B +#define HID_KEY_Y 0x1C +#define HID_KEY_Z 0x1D +#define HID_KEY_1 0x1E +#define HID_KEY_2 0x1F +#define HID_KEY_3 0x20 +#define HID_KEY_4 0x21 +#define HID_KEY_5 0x22 +#define HID_KEY_6 0x23 +#define HID_KEY_7 0x24 +#define HID_KEY_8 0x25 +#define HID_KEY_9 0x26 +#define HID_KEY_0 0x27 +#define HID_KEY_ENTER 0x28 +#define HID_KEY_ESCAPE 0x29 +#define HID_KEY_BACKSPACE 0x2A +#define HID_KEY_TAB 0x2B +#define HID_KEY_SPACE 0x2C +#define HID_KEY_MINUS 0x2D +#define HID_KEY_EQUAL 0x2E +#define HID_KEY_BRACKET_LEFT 0x2F +#define HID_KEY_BRACKET_RIGHT 0x30 +#define HID_KEY_BACKSLASH 0x31 +#define HID_KEY_EUROPE_1 0x32 +#define HID_KEY_SEMICOLON 0x33 +#define HID_KEY_APOSTROPHE 0x34 +#define HID_KEY_GRAVE 0x35 +#define HID_KEY_COMMA 0x36 +#define HID_KEY_PERIOD 0x37 +#define HID_KEY_SLASH 0x38 +#define HID_KEY_CAPS_LOCK 0x39 +#define HID_KEY_F1 0x3A +#define HID_KEY_F2 0x3B +#define HID_KEY_F3 0x3C +#define HID_KEY_F4 0x3D +#define HID_KEY_F5 0x3E +#define HID_KEY_F6 0x3F +#define HID_KEY_F7 0x40 +#define HID_KEY_F8 0x41 +#define HID_KEY_F9 0x42 +#define HID_KEY_F10 0x43 +#define HID_KEY_F11 0x44 +#define HID_KEY_F12 0x45 +#define HID_KEY_PRINT_SCREEN 0x46 +#define HID_KEY_SCROLL_LOCK 0x47 +#define HID_KEY_PAUSE 0x48 +#define HID_KEY_INSERT 0x49 +#define HID_KEY_HOME 0x4A +#define HID_KEY_PAGE_UP 0x4B +#define HID_KEY_DELETE 0x4C +#define HID_KEY_END 0x4D +#define HID_KEY_PAGE_DOWN 0x4E +#define HID_KEY_ARROW_RIGHT 0x4F +#define HID_KEY_ARROW_LEFT 0x50 +#define HID_KEY_ARROW_DOWN 0x51 +#define HID_KEY_ARROW_UP 0x52 +#define HID_KEY_NUM_LOCK 0x53 +#define HID_KEY_KEYPAD_DIVIDE 0x54 +#define HID_KEY_KEYPAD_MULTIPLY 0x55 +#define HID_KEY_KEYPAD_SUBTRACT 0x56 +#define HID_KEY_KEYPAD_ADD 0x57 +#define HID_KEY_KEYPAD_ENTER 0x58 +#define HID_KEY_KEYPAD_1 0x59 +#define HID_KEY_KEYPAD_2 0x5A +#define HID_KEY_KEYPAD_3 0x5B +#define HID_KEY_KEYPAD_4 0x5C +#define HID_KEY_KEYPAD_5 0x5D +#define HID_KEY_KEYPAD_6 0x5E +#define HID_KEY_KEYPAD_7 0x5F +#define HID_KEY_KEYPAD_8 0x60 +#define HID_KEY_KEYPAD_9 0x61 +#define HID_KEY_KEYPAD_0 0x62 +#define HID_KEY_KEYPAD_DECIMAL 0x63 +#define HID_KEY_EUROPE_2 0x64 +#define HID_KEY_APPLICATION 0x65 +#define HID_KEY_POWER 0x66 +#define HID_KEY_KEYPAD_EQUAL 0x67 +#define HID_KEY_F13 0x68 +#define HID_KEY_F14 0x69 +#define HID_KEY_F15 0x6A +#define HID_KEY_F16 0x6B +#define HID_KEY_F17 0x6C +#define HID_KEY_F18 0x6D +#define HID_KEY_F19 0x6E +#define HID_KEY_F20 0x6F +#define HID_KEY_F21 0x70 +#define HID_KEY_F22 0x71 +#define HID_KEY_F23 0x72 +#define HID_KEY_F24 0x73 +#define HID_KEY_EXECUTE 0x74 +#define HID_KEY_HELP 0x75 +#define HID_KEY_MENU 0x76 +#define HID_KEY_SELECT 0x77 +#define HID_KEY_STOP 0x78 +#define HID_KEY_AGAIN 0x79 +#define HID_KEY_UNDO 0x7A +#define HID_KEY_CUT 0x7B +#define HID_KEY_COPY 0x7C +#define HID_KEY_PASTE 0x7D +#define HID_KEY_FIND 0x7E +#define HID_KEY_MUTE 0x7F +#define HID_KEY_VOLUME_UP 0x80 +#define HID_KEY_VOLUME_DOWN 0x81 +#define HID_KEY_LOCKING_CAPS_LOCK 0x82 +#define HID_KEY_LOCKING_NUM_LOCK 0x83 +#define HID_KEY_LOCKING_SCROLL_LOCK 0x84 +#define HID_KEY_KEYPAD_COMMA 0x85 +#define HID_KEY_KEYPAD_EQUAL_SIGN 0x86 +#define HID_KEY_KANJI1 0x87 +#define HID_KEY_KANJI2 0x88 +#define HID_KEY_KANJI3 0x89 +#define HID_KEY_KANJI4 0x8A +#define HID_KEY_KANJI5 0x8B +#define HID_KEY_KANJI6 0x8C +#define HID_KEY_KANJI7 0x8D +#define HID_KEY_KANJI8 0x8E +#define HID_KEY_KANJI9 0x8F +#define HID_KEY_LANG1 0x90 +#define HID_KEY_LANG2 0x91 +#define HID_KEY_LANG3 0x92 +#define HID_KEY_LANG4 0x93 +#define HID_KEY_LANG5 0x94 +#define HID_KEY_LANG6 0x95 +#define HID_KEY_LANG7 0x96 +#define HID_KEY_LANG8 0x97 +#define HID_KEY_LANG9 0x98 +#define HID_KEY_ALTERNATE_ERASE 0x99 +#define HID_KEY_SYSREQ_ATTENTION 0x9A +#define HID_KEY_CANCEL 0x9B +#define HID_KEY_CLEAR 0x9C +#define HID_KEY_PRIOR 0x9D +#define HID_KEY_RETURN 0x9E +#define HID_KEY_SEPARATOR 0x9F +#define HID_KEY_OUT 0xA0 +#define HID_KEY_OPER 0xA1 +#define HID_KEY_CLEAR_AGAIN 0xA2 +#define HID_KEY_CRSEL_PROPS 0xA3 +#define HID_KEY_EXSEL 0xA4 +// RESERVED 0xA5-AF +#define HID_KEY_KEYPAD_00 0xB0 +#define HID_KEY_KEYPAD_000 0xB1 +#define HID_KEY_THOUSANDS_SEPARATOR 0xB2 +#define HID_KEY_DECIMAL_SEPARATOR 0xB3 +#define HID_KEY_CURRENCY_UNIT 0xB4 +#define HID_KEY_CURRENCY_SUBUNIT 0xB5 +#define HID_KEY_KEYPAD_LEFT_PARENTHESIS 0xB6 +#define HID_KEY_KEYPAD_RIGHT_PARENTHESIS 0xB7 +#define HID_KEY_KEYPAD_LEFT_BRACE 0xB8 +#define HID_KEY_KEYPAD_RIGHT_BRACE 0xB9 +#define HID_KEY_KEYPAD_TAB 0xBA +#define HID_KEY_KEYPAD_BACKSPACE 0xBB +#define HID_KEY_KEYPAD_A 0xBC +#define HID_KEY_KEYPAD_B 0xBD +#define HID_KEY_KEYPAD_C 0xBE +#define HID_KEY_KEYPAD_D 0xBF +#define HID_KEY_KEYPAD_E 0xC0 +#define HID_KEY_KEYPAD_F 0xC1 +#define HID_KEY_KEYPAD_XOR 0xC2 +#define HID_KEY_KEYPAD_CARET 0xC3 +#define HID_KEY_KEYPAD_PERCENT 0xC4 +#define HID_KEY_KEYPAD_LESS_THAN 0xC5 +#define HID_KEY_KEYPAD_GREATER_THAN 0xC6 +#define HID_KEY_KEYPAD_AMPERSAND 0xC7 +#define HID_KEY_KEYPAD_DOUBLE_AMPERSAND 0xC8 +#define HID_KEY_KEYPAD_VERTICAL_BAR 0xC9 +#define HID_KEY_KEYPAD_DOUBLE_VERTICAL_BAR 0xCA +#define HID_KEY_KEYPAD_COLON 0xCB +#define HID_KEY_KEYPAD_HASH 0xCC +#define HID_KEY_KEYPAD_SPACE 0xCD +#define HID_KEY_KEYPAD_AT 0xCE +#define HID_KEY_KEYPAD_EXCLAMATION 0xCF +#define HID_KEY_KEYPAD_MEMORY_STORE 0xD0 +#define HID_KEY_KEYPAD_MEMORY_RECALL 0xD1 +#define HID_KEY_KEYPAD_MEMORY_CLEAR 0xD2 +#define HID_KEY_KEYPAD_MEMORY_ADD 0xD3 +#define HID_KEY_KEYPAD_MEMORY_SUBTRACT 0xD4 +#define HID_KEY_KEYPAD_MEMORY_MULTIPLY 0xD5 +#define HID_KEY_KEYPAD_MEMORY_DIVIDE 0xD6 +#define HID_KEY_KEYPAD_PLUS_MINUS 0xD7 +#define HID_KEY_KEYPAD_CLEAR 0xD8 +#define HID_KEY_KEYPAD_CLEAR_ENTRY 0xD9 +#define HID_KEY_KEYPAD_BINARY 0xDA +#define HID_KEY_KEYPAD_OCTAL 0xDB +#define HID_KEY_KEYPAD_DECIMAL_2 0xDC +#define HID_KEY_KEYPAD_HEXADECIMAL 0xDD +// RESERVED 0xDE-DF +#define HID_KEY_CONTROL_LEFT 0xE0 +#define HID_KEY_SHIFT_LEFT 0xE1 +#define HID_KEY_ALT_LEFT 0xE2 +#define HID_KEY_GUI_LEFT 0xE3 +#define HID_KEY_CONTROL_RIGHT 0xE4 +#define HID_KEY_SHIFT_RIGHT 0xE5 +#define HID_KEY_ALT_RIGHT 0xE6 +#define HID_KEY_GUI_RIGHT 0xE7 + + +//--------------------------------------------------------------------+ +// REPORT DESCRIPTOR +//--------------------------------------------------------------------+ + +//------------- ITEM & TAG -------------// +#define HID_REPORT_DATA_0(data) +#define HID_REPORT_DATA_1(data) , data +#define HID_REPORT_DATA_2(data) , U16_TO_U8S_LE(data) +#define HID_REPORT_DATA_3(data) , U32_TO_U8S_LE(data) + +#define HID_REPORT_ITEM(data, tag, type, size) \ + (((tag) << 4) | ((type) << 2) | (size)) HID_REPORT_DATA_##size(data) + +// Report Item Types +enum { + RI_TYPE_MAIN = 0, + RI_TYPE_GLOBAL = 1, + RI_TYPE_LOCAL = 2 +}; + +//------------- Main Items - HID 1.11 section 6.2.2.4 -------------// + +// Report Item Main group +enum { + RI_MAIN_INPUT = 8, + RI_MAIN_OUTPUT = 9, + RI_MAIN_COLLECTION = 10, + RI_MAIN_FEATURE = 11, + RI_MAIN_COLLECTION_END = 12 +}; + +#define HID_INPUT(x) HID_REPORT_ITEM(x, RI_MAIN_INPUT , RI_TYPE_MAIN, 1) +#define HID_OUTPUT(x) HID_REPORT_ITEM(x, RI_MAIN_OUTPUT , RI_TYPE_MAIN, 1) +#define HID_COLLECTION(x) HID_REPORT_ITEM(x, RI_MAIN_COLLECTION , RI_TYPE_MAIN, 1) +#define HID_FEATURE(x) HID_REPORT_ITEM(x, RI_MAIN_FEATURE , RI_TYPE_MAIN, 1) +#define HID_COLLECTION_END HID_REPORT_ITEM(x, RI_MAIN_COLLECTION_END, RI_TYPE_MAIN, 0) + +//------------- Input, Output, Feature - HID 1.11 section 6.2.2.5 -------------// +#define HID_DATA (0<<0) +#define HID_CONSTANT (1<<0) + +#define HID_ARRAY (0<<1) +#define HID_VARIABLE (1<<1) + +#define HID_ABSOLUTE (0<<2) +#define HID_RELATIVE (1<<2) + +#define HID_WRAP_NO (0<<3) +#define HID_WRAP (1<<3) + +#define HID_LINEAR (0<<4) +#define HID_NONLINEAR (1<<4) + +#define HID_PREFERRED_STATE (0<<5) +#define HID_PREFERRED_NO (1<<5) + +#define HID_NO_NULL_POSITION (0<<6) +#define HID_NULL_STATE (1<<6) + +#define HID_NON_VOLATILE (0<<7) +#define HID_VOLATILE (1<<7) + +#define HID_BITFIELD (0<<8) +#define HID_BUFFERED_BYTES (1<<8) + +//------------- Collection Item - HID 1.11 section 6.2.2.6 -------------// +enum { + HID_COLLECTION_PHYSICAL = 0, + HID_COLLECTION_APPLICATION, + HID_COLLECTION_LOGICAL, + HID_COLLECTION_REPORT, + HID_COLLECTION_NAMED_ARRAY, + HID_COLLECTION_USAGE_SWITCH, + HID_COLLECTION_USAGE_MODIFIER +}; + +//------------- Global Items - HID 1.11 section 6.2.2.7 -------------// + +// Report Item Global group +enum { + RI_GLOBAL_USAGE_PAGE = 0, + RI_GLOBAL_LOGICAL_MIN = 1, + RI_GLOBAL_LOGICAL_MAX = 2, + RI_GLOBAL_PHYSICAL_MIN = 3, + RI_GLOBAL_PHYSICAL_MAX = 4, + RI_GLOBAL_UNIT_EXPONENT = 5, + RI_GLOBAL_UNIT = 6, + RI_GLOBAL_REPORT_SIZE = 7, + RI_GLOBAL_REPORT_ID = 8, + RI_GLOBAL_REPORT_COUNT = 9, + RI_GLOBAL_PUSH = 10, + RI_GLOBAL_POP = 11 +}; + +#define HID_USAGE_PAGE(x) HID_REPORT_ITEM(x, RI_GLOBAL_USAGE_PAGE, RI_TYPE_GLOBAL, 1) +#define HID_USAGE_PAGE_N(x, n) HID_REPORT_ITEM(x, RI_GLOBAL_USAGE_PAGE, RI_TYPE_GLOBAL, n) + +#define HID_LOGICAL_MIN(x) HID_REPORT_ITEM(x, RI_GLOBAL_LOGICAL_MIN, RI_TYPE_GLOBAL, 1) +#define HID_LOGICAL_MIN_N(x, n) HID_REPORT_ITEM(x, RI_GLOBAL_LOGICAL_MIN, RI_TYPE_GLOBAL, n) + +#define HID_LOGICAL_MAX(x) HID_REPORT_ITEM(x, RI_GLOBAL_LOGICAL_MAX, RI_TYPE_GLOBAL, 1) +#define HID_LOGICAL_MAX_N(x, n) HID_REPORT_ITEM(x, RI_GLOBAL_LOGICAL_MAX, RI_TYPE_GLOBAL, n) + +#define HID_PHYSICAL_MIN(x) HID_REPORT_ITEM(x, RI_GLOBAL_PHYSICAL_MIN, RI_TYPE_GLOBAL, 1) +#define HID_PHYSICAL_MIN_N(x, n) HID_REPORT_ITEM(x, RI_GLOBAL_PHYSICAL_MIN, RI_TYPE_GLOBAL, n) + +#define HID_PHYSICAL_MAX(x) HID_REPORT_ITEM(x, RI_GLOBAL_PHYSICAL_MAX, RI_TYPE_GLOBAL, 1) +#define HID_PHYSICAL_MAX_N(x, n) HID_REPORT_ITEM(x, RI_GLOBAL_PHYSICAL_MAX, RI_TYPE_GLOBAL, n) + +#define HID_UNIT_EXPONENT(x) HID_REPORT_ITEM(x, RI_GLOBAL_UNIT_EXPONENT, RI_TYPE_GLOBAL, 1) +#define HID_UNIT_EXPONENT_N(x, n) HID_REPORT_ITEM(x, RI_GLOBAL_UNIT_EXPONENT, RI_TYPE_GLOBAL, n) + +#define HID_UNIT(x) HID_REPORT_ITEM(x, RI_GLOBAL_UNIT, RI_TYPE_GLOBAL, 1) +#define HID_UNIT_N(x, n) HID_REPORT_ITEM(x, RI_GLOBAL_UNIT, RI_TYPE_GLOBAL, n) + +#define HID_REPORT_SIZE(x) HID_REPORT_ITEM(x, RI_GLOBAL_REPORT_SIZE, RI_TYPE_GLOBAL, 1) +#define HID_REPORT_SIZE_N(x, n) HID_REPORT_ITEM(x, RI_GLOBAL_REPORT_SIZE, RI_TYPE_GLOBAL, n) + +#define HID_REPORT_ID(x) HID_REPORT_ITEM(x, RI_GLOBAL_REPORT_ID, RI_TYPE_GLOBAL, 1), +#define HID_REPORT_ID_N(x, n) HID_REPORT_ITEM(x, RI_GLOBAL_REPORT_ID, RI_TYPE_GLOBAL, n), + +#define HID_REPORT_COUNT(x) HID_REPORT_ITEM(x, RI_GLOBAL_REPORT_COUNT, RI_TYPE_GLOBAL, 1) +#define HID_REPORT_COUNT_N(x, n) HID_REPORT_ITEM(x, RI_GLOBAL_REPORT_COUNT, RI_TYPE_GLOBAL, n) + +#define HID_PUSH HID_REPORT_ITEM(x, RI_GLOBAL_PUSH, RI_TYPE_GLOBAL, 0) +#define HID_POP HID_REPORT_ITEM(x, RI_GLOBAL_POP, RI_TYPE_GLOBAL, 0) + +//------------- LOCAL ITEMS 6.2.2.8 -------------// + +enum { + RI_LOCAL_USAGE = 0, + RI_LOCAL_USAGE_MIN = 1, + RI_LOCAL_USAGE_MAX = 2, + RI_LOCAL_DESIGNATOR_INDEX = 3, + RI_LOCAL_DESIGNATOR_MIN = 4, + RI_LOCAL_DESIGNATOR_MAX = 5, + // 6 is reserved + RI_LOCAL_STRING_INDEX = 7, + RI_LOCAL_STRING_MIN = 8, + RI_LOCAL_STRING_MAX = 9, + RI_LOCAL_DELIMITER = 10, +}; + +#define HID_USAGE(x) HID_REPORT_ITEM(x, RI_LOCAL_USAGE, RI_TYPE_LOCAL, 1) +#define HID_USAGE_N(x, n) HID_REPORT_ITEM(x, RI_LOCAL_USAGE, RI_TYPE_LOCAL, n) + +#define HID_USAGE_MIN(x) HID_REPORT_ITEM(x, RI_LOCAL_USAGE_MIN, RI_TYPE_LOCAL, 1) +#define HID_USAGE_MIN_N(x, n) HID_REPORT_ITEM(x, RI_LOCAL_USAGE_MIN, RI_TYPE_LOCAL, n) + +#define HID_USAGE_MAX(x) HID_REPORT_ITEM(x, RI_LOCAL_USAGE_MAX, RI_TYPE_LOCAL, 1) +#define HID_USAGE_MAX_N(x, n) HID_REPORT_ITEM(x, RI_LOCAL_USAGE_MAX, RI_TYPE_LOCAL, n) + +//--------------------------------------------------------------------+ +// Usage Table +/* Usage Types Data + Sel Selector Array + SV Static Value Constant, Variable, Absolute + SF Static Flag Constant, Variable, Absolute + DV Dynamic Value Constant, Variable, Absolute + DF Dynamic Flag Constant, Variable, Absolute +*/ +/* Usage Types Collection + NAry Named Array Logical + CA Collection Application Application + CL Collection Logical Logical + CP Collection Physical Physical + US Usage Switch Logical + UM Usage Modifier Logical +*/ +//--------------------------------------------------------------------+ + +/// HID Usage Table - Table 1: Usage Page Summary +enum { + HID_USAGE_PAGE_DESKTOP = 0x01, + HID_USAGE_PAGE_SIMULATE = 0x02, + HID_USAGE_PAGE_VIRTUAL_REALITY = 0x03, + HID_USAGE_PAGE_SPORT = 0x04, + HID_USAGE_PAGE_GAME = 0x05, + HID_USAGE_PAGE_GENERIC_DEVICE = 0x06, + HID_USAGE_PAGE_KEYBOARD = 0x07, + HID_USAGE_PAGE_LED = 0x08, + HID_USAGE_PAGE_BUTTON = 0x09, + HID_USAGE_PAGE_ORDINAL = 0x0a, + HID_USAGE_PAGE_TELEPHONY = 0x0b, + HID_USAGE_PAGE_CONSUMER = 0x0c, + HID_USAGE_PAGE_DIGITIZER = 0x0d, + HID_USAGE_PAGE_PID = 0x0f, + HID_USAGE_PAGE_UNICODE = 0x10, + HID_USAGE_PAGE_SOC = 0x11, + HID_USAGE_PAGE_EYE_AND_HEAD_TRACKERS = 0x12, + // 0x13 is reserved + HID_USAGE_PAGE_AUXILIARY_DISPLAY = 0x14, + // 0x15 - 0x1f is reserved + HID_USAGE_PAGE_SENSORS = 0x20, + // 0x21 - 0x3f is reserved + HID_USAGE_PAGE_MEDICAL_INSTRUMENT = 0x40, + HID_USAGE_PAGE_LIGHTING_AND_ILLUMINATION = 0x59, + HID_USAGE_PAGE_MONITOR = 0x80, // 0x80 - 0x83 + HID_USAGE_PAGE_POWER = 0x84, + HID_USAGE_PAGE_BATTERY = 0x85, + // 0x86 - 0x87 is reserved for Power Device + HID_USAGE_PAGE_BARCODE_SCANNER = 0x8c, + HID_USAGE_PAGE_SCALE = 0x8d, + HID_USAGE_PAGE_MSR = 0x8e, + HID_USAGE_PAGE_CAMERA = 0x90, + HID_USAGE_PAGE_ARCADE = 0x91, + HID_USAGE_PAGE_FIDO = 0xF1D0, // FIDO alliance HID usage page + HID_USAGE_PAGE_VENDOR = 0xFF00 // 0xFF00 - 0xFFFF +}; + +/// HID Usage Table - Table 6: Generic Desktop Page +enum { + HID_USAGE_DESKTOP_POINTER = 0x01, + HID_USAGE_DESKTOP_MOUSE = 0x02, + HID_USAGE_DESKTOP_JOYSTICK = 0x04, + HID_USAGE_DESKTOP_GAMEPAD = 0x05, + HID_USAGE_DESKTOP_KEYBOARD = 0x06, + HID_USAGE_DESKTOP_KEYPAD = 0x07, + HID_USAGE_DESKTOP_MULTI_AXIS_CONTROLLER = 0x08, + HID_USAGE_DESKTOP_TABLET_PC_SYSTEM = 0x09, + HID_USAGE_DESKTOP_X = 0x30, + HID_USAGE_DESKTOP_Y = 0x31, + HID_USAGE_DESKTOP_Z = 0x32, + HID_USAGE_DESKTOP_RX = 0x33, + HID_USAGE_DESKTOP_RY = 0x34, + HID_USAGE_DESKTOP_RZ = 0x35, + HID_USAGE_DESKTOP_SLIDER = 0x36, + HID_USAGE_DESKTOP_DIAL = 0x37, + HID_USAGE_DESKTOP_WHEEL = 0x38, + HID_USAGE_DESKTOP_HAT_SWITCH = 0x39, + HID_USAGE_DESKTOP_COUNTED_BUFFER = 0x3a, + HID_USAGE_DESKTOP_BYTE_COUNT = 0x3b, + HID_USAGE_DESKTOP_MOTION_WAKEUP = 0x3c, + HID_USAGE_DESKTOP_START = 0x3d, + HID_USAGE_DESKTOP_SELECT = 0x3e, + HID_USAGE_DESKTOP_VX = 0x40, + HID_USAGE_DESKTOP_VY = 0x41, + HID_USAGE_DESKTOP_VZ = 0x42, + HID_USAGE_DESKTOP_VBRX = 0x43, + HID_USAGE_DESKTOP_VBRY = 0x44, + HID_USAGE_DESKTOP_VBRZ = 0x45, + HID_USAGE_DESKTOP_VNO = 0x46, + HID_USAGE_DESKTOP_FEATURE_NOTIFICATION = 0x47, + HID_USAGE_DESKTOP_RESOLUTION_MULTIPLIER = 0x48, + HID_USAGE_DESKTOP_SYSTEM_CONTROL = 0x80, + HID_USAGE_DESKTOP_SYSTEM_POWER_DOWN = 0x81, + HID_USAGE_DESKTOP_SYSTEM_SLEEP = 0x82, + HID_USAGE_DESKTOP_SYSTEM_WAKE_UP = 0x83, + HID_USAGE_DESKTOP_SYSTEM_CONTEXT_MENU = 0x84, + HID_USAGE_DESKTOP_SYSTEM_MAIN_MENU = 0x85, + HID_USAGE_DESKTOP_SYSTEM_APP_MENU = 0x86, + HID_USAGE_DESKTOP_SYSTEM_MENU_HELP = 0x87, + HID_USAGE_DESKTOP_SYSTEM_MENU_EXIT = 0x88, + HID_USAGE_DESKTOP_SYSTEM_MENU_SELECT = 0x89, + HID_USAGE_DESKTOP_SYSTEM_MENU_RIGHT = 0x8A, + HID_USAGE_DESKTOP_SYSTEM_MENU_LEFT = 0x8B, + HID_USAGE_DESKTOP_SYSTEM_MENU_UP = 0x8C, + HID_USAGE_DESKTOP_SYSTEM_MENU_DOWN = 0x8D, + HID_USAGE_DESKTOP_SYSTEM_COLD_RESTART = 0x8E, + HID_USAGE_DESKTOP_SYSTEM_WARM_RESTART = 0x8F, + HID_USAGE_DESKTOP_DPAD_UP = 0x90, + HID_USAGE_DESKTOP_DPAD_DOWN = 0x91, + HID_USAGE_DESKTOP_DPAD_RIGHT = 0x92, + HID_USAGE_DESKTOP_DPAD_LEFT = 0x93, + HID_USAGE_DESKTOP_SYSTEM_DOCK = 0xA0, + HID_USAGE_DESKTOP_SYSTEM_UNDOCK = 0xA1, + HID_USAGE_DESKTOP_SYSTEM_SETUP = 0xA2, + HID_USAGE_DESKTOP_SYSTEM_BREAK = 0xA3, + HID_USAGE_DESKTOP_SYSTEM_DEBUGGER_BREAK = 0xA4, + HID_USAGE_DESKTOP_APPLICATION_BREAK = 0xA5, + HID_USAGE_DESKTOP_APPLICATION_DEBUGGER_BREAK = 0xA6, + HID_USAGE_DESKTOP_SYSTEM_SPEAKER_MUTE = 0xA7, + HID_USAGE_DESKTOP_SYSTEM_HIBERNATE = 0xA8, + HID_USAGE_DESKTOP_SYSTEM_DISPLAY_INVERT = 0xB0, + HID_USAGE_DESKTOP_SYSTEM_DISPLAY_INTERNAL = 0xB1, + HID_USAGE_DESKTOP_SYSTEM_DISPLAY_EXTERNAL = 0xB2, + HID_USAGE_DESKTOP_SYSTEM_DISPLAY_BOTH = 0xB3, + HID_USAGE_DESKTOP_SYSTEM_DISPLAY_DUAL = 0xB4, + HID_USAGE_DESKTOP_SYSTEM_DISPLAY_TOGGLE_INT_EXT = 0xB5, + HID_USAGE_DESKTOP_SYSTEM_DISPLAY_SWAP_PRIMARY_SECONDARY = 0xB6, + HID_USAGE_DESKTOP_SYSTEM_DISPLAY_LCD_AUTOSCALE = 0xB7 +}; + +/// HID Usage Table: Consumer Page (0x0C) +/// Only contains controls that supported by Windows (whole list is too long) +enum { + HID_USAGE_CONSUMER_UNASSIGNED = 0x0000, + + // Generic Control + HID_USAGE_CONSUMER_CONTROL = 0x0001, + HID_USAGE_CONSUMER_NUMERIC_KEY_PAD = 0x0002, + HID_USAGE_CONSUMER_PROGRAMMABLE_BUTTONS = 0x0003, + HID_USAGE_CONSUMER_MICROPHONE = 0x0004, + HID_USAGE_CONSUMER_HEADPHONE = 0x0005, + HID_USAGE_CONSUMER_GRAPHIC_EQUALIZER = 0x0006, + // 07-1F Reserved + + HID_USAGE_CONSUMER_PLUS_10 = 0x0020, + HID_USAGE_CONSUMER_PLUS_100 = 0x0021, + HID_USAGE_CONSUMER_AM_PM = 0x0022, + // 23-3F Reserved + + // Power Control + HID_USAGE_CONSUMER_POWER = 0x0030, + HID_USAGE_CONSUMER_RESET = 0x0031, + HID_USAGE_CONSUMER_SLEEP = 0x0032, + + HID_USAGE_CONSUMER_SLEEP_AFTER = 0x0033, + HID_USAGE_CONSUMER_SLEEP_MODE = 0x0034, + HID_USAGE_CONSUMER_ILLUMINATION = 0x0035, + HID_USAGE_CONSUMER_FUNCTION_BUTTONS = 0x0036, + // 37-3F Reserved + HID_USAGE_CONSUMER_MENU = 0x0040, + HID_USAGE_CONSUMER_MENU_PICK = 0x0041, + HID_USAGE_CONSUMER_MENU_UP = 0x0042, + HID_USAGE_CONSUMER_MENU_DOWN = 0x0043, + HID_USAGE_CONSUMER_MENU_LEFT = 0x0044, + HID_USAGE_CONSUMER_MENU_RIGHT = 0x0045, + HID_USAGE_CONSUMER_MENU_ESCAPE = 0x0046, + HID_USAGE_CONSUMER_MENU_VALUE_INCREASE = 0x0047, + HID_USAGE_CONSUMER_MENU_VALUE_DECREASE = 0x0048, + // 49-5F Reserved + HID_USAGE_CONSUMER_DATA_ON_SCREEN = 0x0060, + HID_USAGE_CONSUMER_CLOSED_CAPTION = 0x0061, + HID_USAGE_CONSUMER_CLOSED_CAPTION_SELECT = 0x0062, + HID_USAGE_CONSUMER_VCR_TV = 0x0063, + HID_USAGE_CONSUMER_BROADCAST_MODE = 0x0064, + HID_USAGE_CONSUMER_SNAPSHOT = 0x0065, + HID_USAGE_CONSUMER_STILL = 0x0066, + + // 67-7F Reserved + // Screen Brightness + HID_USAGE_CONSUMER_BRIGHTNESS_INCREMENT = 0x006F, + HID_USAGE_CONSUMER_BRIGHTNESS_DECREMENT = 0x0070, + + // These HID usages operate only on mobile systems (battery powered) and + // require Windows 8 (build 8302 or greater). + HID_USAGE_CONSUMER_WIRELESS_RADIO_CONTROLS = 0x000C, + HID_USAGE_CONSUMER_WIRELESS_RADIO_BUTTONS = 0x00C6, + HID_USAGE_CONSUMER_WIRELESS_RADIO_LED = 0x00C7, + HID_USAGE_CONSUMER_WIRELESS_RADIO_SLIDER_SWITCH = 0x00C8, + + HID_USAGE_CONSUMER_SELECTION = 0x0080, + HID_USAGE_CONSUMER_ASSIGN_SELECTION = 0x0081, + HID_USAGE_CONSUMER_MODE_STEP = 0x0082, + HID_USAGE_CONSUMER_RECALL_LAST = 0x0083, + HID_USAGE_CONSUMER_ENTER_CHANNEL = 0x0084, + HID_USAGE_CONSUMER_ORDER_MOVIE = 0x0085, + HID_USAGE_CONSUMER_CHANNEL = 0x0086, + HID_USAGE_CONSUMER_MEDIA_SELECTION = 0x0087, + HID_USAGE_CONSUMER_MEDIA_SELECT_COMPUTER = 0x0088, + HID_USAGE_CONSUMER_MEDIA_SELECT_TV = 0x0089, + HID_USAGE_CONSUMER_MEDIA_SELECT_WWW = 0x008A, + HID_USAGE_CONSUMER_MEDIA_SELECT_DVD = 0x008B, + HID_USAGE_CONSUMER_MEDIA_SELECT_TELEPHONE = 0x008C, + HID_USAGE_CONSUMER_MEDIA_SELECT_PROGRAM_GUIDE = 0x008D, + HID_USAGE_CONSUMER_MEDIA_SELECT_VIDEO_PHONE = 0x008E, + HID_USAGE_CONSUMER_MEDIA_SELECT_GAMES = 0x008F, + HID_USAGE_CONSUMER_MEDIA_SELECT_MESSAGES = 0x0090, + HID_USAGE_CONSUMER_MEDIA_SELECT_CD = 0x0091, + HID_USAGE_CONSUMER_MEDIA_SELECT_VCR = 0x0092, + HID_USAGE_CONSUMER_MEDIA_SELECT_TUNER = 0x0093, + HID_USAGE_CONSUMER_QUIT = 0x0094, + HID_USAGE_CONSUMER_HELP = 0x0095, + HID_USAGE_CONSUMER_MEDIA_SELECT_TAPE = 0x0096, + HID_USAGE_CONSUMER_MEDIA_SELECT_CABLE = 0x0097, + HID_USAGE_CONSUMER_MEDIA_SELECT_SATELLITE = 0x0098, + HID_USAGE_CONSUMER_MEDIA_SELECT_SECURITY = 0x0099, + HID_USAGE_CONSUMER_MEDIA_SELECT_HOME = 0x009A, + HID_USAGE_CONSUMER_MEDIA_SELECT_CALL = 0x009B, + HID_USAGE_CONSUMER_CHANNEL_INCREMENT = 0x009C, + HID_USAGE_CONSUMER_CHANNEL_DECREMENT = 0x009D, + HID_USAGE_CONSUMER_MEDIA_SELECT_SAP = 0x009E, + // 9F Reserved + HID_USAGE_CONSUMER_VCR_PLUS = 0x00A0, + HID_USAGE_CONSUMER_ONCE = 0x00A1, + HID_USAGE_CONSUMER_DAILY = 0x00A2, + HID_USAGE_CONSUMER_WEEKLY = 0x00A3, + HID_USAGE_CONSUMER_MONTHLY = 0x00A4, + // A5-AF Reserved + + HID_USAGE_CONSUMER_PLAY = 0x00B0, + HID_USAGE_CONSUMER_PAUSE = 0x00B1, + HID_USAGE_CONSUMER_RECORD = 0x00B2, + HID_USAGE_CONSUMER_FAST_FORWARD = 0x00B3, + HID_USAGE_CONSUMER_REWIND = 0x00B4, + HID_USAGE_CONSUMER_SCAN_NEXT_TRACK = 0x00B5, + HID_USAGE_CONSUMER_SCAN_PREVIOUS_TRACK = 0x00B6, + HID_USAGE_CONSUMER_STOP = 0x00B7, + HID_USAGE_CONSUMER_EJECT = 0x00B8, + HID_USAGE_CONSUMER_RANDOM_PLAY = 0x00B9, + HID_USAGE_CONSUMER_SELECT_DISC = 0x00BA, + HID_USAGE_CONSUMER_ENTER_DISC = 0x00BB, + HID_USAGE_CONSUMER_REPEAT = 0x00BC, + HID_USAGE_CONSUMER_TRACKING = 0x00BD, + HID_USAGE_CONSUMER_TRACK_NORMAL = 0x00BE, + HID_USAGE_CONSUMER_SLOW_TRACKING = 0x00BF, + HID_USAGE_CONSUMER_FRAME_FORWARD = 0x00C0, + HID_USAGE_CONSUMER_FRAME_BACK = 0x00C1, + HID_USAGE_CONSUMER_MARK = 0x00C2, + HID_USAGE_CONSUMER_CLEAR_MARK = 0x00C3, + HID_USAGE_CONSUMER_REPEAT_FROM_MARK = 0x00C4, + HID_USAGE_CONSUMER_RETURN_TO_MARK = 0x00C5, + HID_USAGE_CONSUMER_SEARCH_MARK_FORWARD = 0x00C6, + HID_USAGE_CONSUMER_SEARCH_MARK_BACKWARDS = 0x00C7, + HID_USAGE_CONSUMER_COUNTER_RESET = 0x00C8, + HID_USAGE_CONSUMER_SHOW_COUNTER = 0x00C9, + HID_USAGE_CONSUMER_TRACKING_INCREMENT = 0x00CA, + HID_USAGE_CONSUMER_TRACKING_DECREMENT = 0x00CB, + HID_USAGE_CONSUMER_STOP_EJECT = 0x00CC, + + + // Media Control + HID_USAGE_CONSUMER_PLAY_PAUSE = 0x00CD, + + HID_USAGE_CONSUMER_PLAY_SKIP = 0x00CE, + + // CF-DF Reserved + HID_USAGE_CONSUMER_VOLUME = 0x00E0, + HID_USAGE_CONSUMER_BALANCE = 0x00E1, + HID_USAGE_CONSUMER_MUTE = 0x00E2, + HID_USAGE_CONSUMER_BASS = 0x00E3, + HID_USAGE_CONSUMER_TREBLE = 0x00E4, + HID_USAGE_CONSUMER_BASS_BOOST = 0x00E5, + HID_USAGE_CONSUMER_SURROUND_MODE = 0x00E6, + HID_USAGE_CONSUMER_LOUDNESS = 0x00E7, + HID_USAGE_CONSUMER_MPX = 0x00E8, + HID_USAGE_CONSUMER_VOLUME_INCREMENT = 0x00E9, + HID_USAGE_CONSUMER_VOLUME_DECREMENT = 0x00EA, + // EB-EF Reserved + HID_USAGE_CONSUMER_SPEED_SELECT = 0x00F0, + HID_USAGE_CONSUMER_PLAYBACK_SPEED = 0x00F1, + HID_USAGE_CONSUMER_STANDARD_PLAY = 0x00F2, + HID_USAGE_CONSUMER_LONG_PLAY = 0x00F3, + HID_USAGE_CONSUMER_EXTENDED_PLAY = 0x00F4, + HID_USAGE_CONSUMER_SLOW = 0x00F5, + // F6-FF Reserved + HID_USAGE_CONSUMER_FAN_ENABLE = 0x0100, + HID_USAGE_CONSUMER_FAN_SPEED = 0x0101, + HID_USAGE_CONSUMER_LIGHT_ENABLE = 0x0102, + HID_USAGE_CONSUMER_LIGHT_ILLUMINATION_LEVEL = 0x0103, + HID_USAGE_CONSUMER_CLIMATE_CONTROL_ENABLE = 0x0104, + HID_USAGE_CONSUMER_ROOM_TEMPERATURE = 0x0105, + HID_USAGE_CONSUMER_SECURITY_ENABLE = 0x0106, + HID_USAGE_CONSUMER_FIRE_ALARM = 0x0107, + HID_USAGE_CONSUMER_POLICE_ALARM = 0x0108, + HID_USAGE_CONSUMER_PROXIMITY = 0x0109, + HID_USAGE_CONSUMER_MOTION = 0x010A, + HID_USAGE_CONSUMER_DURESS_ALARM = 0x010B, + HID_USAGE_CONSUMER_HOLDUP_ALARM = 0x010C, + HID_USAGE_CONSUMER_MEDICAL_ALARM = 0x010D, + // 10E-14F Reserved + HID_USAGE_CONSUMER_BALANCE_RIGHT = 0x0150, + HID_USAGE_CONSUMER_BALANCE_LEFT = 0x0151, + HID_USAGE_CONSUMER_BASS_INCREMENT = 0x0152, + HID_USAGE_CONSUMER_BASS_DECREMENT = 0x0153, + HID_USAGE_CONSUMER_TREBLE_INCREMENT = 0x0154, + HID_USAGE_CONSUMER_TREBLE_DECREMENT = 0x0155, + + // 156-15F Reserved + HID_USAGE_CONSUMER_SPEAKER_SYSTEM = 0x0160, + HID_USAGE_CONSUMER_CHANNEL_LEFT = 0x0161, + HID_USAGE_CONSUMER_CHANNEL_RIGHT = 0x0162, + HID_USAGE_CONSUMER_CHANNEL_CENTER = 0x0163, + HID_USAGE_CONSUMER_CHANNEL_FRONT = 0x0164, + HID_USAGE_CONSUMER_CHANNEL_CENTER_FRONT = 0x0165, + HID_USAGE_CONSUMER_CHANNEL_SIDE = 0x0166, + HID_USAGE_CONSUMER_CHANNEL_SURROUND = 0x0167, + HID_USAGE_CONSUMER_CHANNEL_LOW_FREQUENCY = 0x0168, + // Enhancement + // CL 15.12.1 + HID_USAGE_CONSUMER_CHANNEL_TOP = 0x0169, + HID_USAGE_CONSUMER_CHANNEL_UNKNOWN = 0x016A, + // 16B-16F Reserved + HID_USAGE_CONSUMER_SUB_CHANNEL = 0x0170, + HID_USAGE_CONSUMER_SUB_CHANNEL_INCREMENT = 0x0171, + HID_USAGE_CONSUMER_SUB_CHANNEL_DECREMENT = 0x0172, + HID_USAGE_CONSUMER_ALTERNATE_AUDIO_INCREMENT = 0x0173, + HID_USAGE_CONSUMER_ALTERNATE_AUDIO_DECREMENT = 0x0174, + // 175-17F Reserved + HID_USAGE_CONSUMER_APPLICATION_LAUNCH_BUTTONS = 0x0180, + HID_USAGE_CONSUMER_AL_LAUNCH_BUTTON_CONFIGURATION = 0x0181, + // Tool + // Sel 15.15 + HID_USAGE_CONSUMER_AL_PROGRAMMABLE_BUTTON = 0x0182, + // Configuration + // Sel 15.15 + HID_USAGE_CONSUMER_AL_CONSUMER_CONTROL_CONFIGURATION = 0x0183, + // Configuration + // Sel 15.15 + HID_USAGE_CONSUMER_AL_WORD_PROCESSOR = 0x0184, + HID_USAGE_CONSUMER_AL_TEXT_EDITOR = 0x0185, + HID_USAGE_CONSUMER_AL_SPREADSHEET = 0x0186, + HID_USAGE_CONSUMER_AL_GRAPHICS_EDITOR = 0x0187, + HID_USAGE_CONSUMER_AL_PRESENTATION_APP = 0x0188, + HID_USAGE_CONSUMER_AL_DATABASE_APP = 0x0189, + HID_USAGE_CONSUMER_AL_EMAIL_READER = 0x018A, + HID_USAGE_CONSUMER_AL_NEWSREADER = 0x018B, + HID_USAGE_CONSUMER_AL_VOICEMAIL = 0x018C, + HID_USAGE_CONSUMER_AL_CONTACTS_ADDRESS_BOOK = 0x018D, + HID_USAGE_CONSUMER_AL_CALENDAR_SCHEDULE = 0x018E, + HID_USAGE_CONSUMER_AL_TASK_PROJECT_MANAGER = 0x018F, + HID_USAGE_CONSUMER_AL_LOG_JOURNAL_TIMECARD = 0x0190, + HID_USAGE_CONSUMER_AL_CHECKBOOK_FINANCE = 0x0191, + HID_USAGE_CONSUMER_AL_CALCULATOR = 0x0192, + HID_USAGE_CONSUMER_AL_A_V_CAPTURE_PLAYBACK = 0x0193, + HID_USAGE_CONSUMER_AL_LOCAL_MACHINE_BROWSER = 0x0194, + HID_USAGE_CONSUMER_AL_LAN_WAN_BROWSER = 0x0195, + HID_USAGE_CONSUMER_AL_INTERNET_BROWSER = 0x0196, + HID_USAGE_CONSUMER_AL_REMOTE_NETWORKING_ISP = 0x0197, + // Connect + // Sel 15.15 + HID_USAGE_CONSUMER_AL_NETWORK_CONFERENCE = 0x0198, + HID_USAGE_CONSUMER_AL_NETWORK_CHAT = 0x0199, + HID_USAGE_CONSUMER_AL_TELEPHONY_DIALER = 0x019A, + HID_USAGE_CONSUMER_AL_LOGON = 0x019B, + HID_USAGE_CONSUMER_AL_LOGOFF = 0x019C, + HID_USAGE_CONSUMER_AL_LOGON_LOGOFF = 0x019D, + HID_USAGE_CONSUMER_AL_TERMINAL_LOCK_SCREENSAVER = 0x019E, + HID_USAGE_CONSUMER_AL_CONTROL_PANEL = 0x019F, + HID_USAGE_CONSUMER_AL_COMMAND_LINE_PROCESSOR_RUN = 0x01A0, + HID_USAGE_CONSUMER_AL_PROCESS_TASK_MANAGER = 0x01A1, + HID_USAGE_CONSUMER_AL_SELECT_TASK_APPLICATION = 0x01A2, + HID_USAGE_CONSUMER_AL_NEXT_TASK_APPLICATION = 0x01A3, + HID_USAGE_CONSUMER_AL_PREVIOUS_TASK_APPLICATION = 0x01A4, + HID_USAGE_CONSUMER_AL_PREEMPTIVE_HALT = 0x01A5, + // Task_Application + // Sel 15.15 + HID_USAGE_CONSUMER_AL_INTEGRATED_HELP_CENTER = 0x01A6, + HID_USAGE_CONSUMER_AL_DOCUMENTS = 0x01A7, + HID_USAGE_CONSUMER_AL_THESAURUS = 0x01A8, + HID_USAGE_CONSUMER_AL_DICTIONARY = 0x01A9, + HID_USAGE_CONSUMER_AL_DESKTOP = 0x01AA, + HID_USAGE_CONSUMER_AL_SPELL_CHECK = 0x01AB, + HID_USAGE_CONSUMER_AL_GRAMMAR_CHECK = 0x01AC, + HID_USAGE_CONSUMER_AL_WIRELESS_STATUS = 0x01AD, + HID_USAGE_CONSUMER_AL_KEYBOARD_LAYOUT = 0x01AE, + HID_USAGE_CONSUMER_AL_VIRUS_PROTECTION = 0x01AF, + HID_USAGE_CONSUMER_AL_ENCRYPTION = 0x01B0, + HID_USAGE_CONSUMER_AL_SCREEN_SAVER = 0x01B1, + HID_USAGE_CONSUMER_AL_ALARMS = 0x01B2, + HID_USAGE_CONSUMER_AL_CLOCK = 0x01B3, + HID_USAGE_CONSUMER_AL_FILE_BROWSER = 0x01B4, + HID_USAGE_CONSUMER_AL_POWER_STATUS = 0x01B5, + HID_USAGE_CONSUMER_AL_IMAGE_BROWSER = 0x01B6, + HID_USAGE_CONSUMER_AL_AUDIO_BROWSER = 0x01B7, + HID_USAGE_CONSUMER_AL_MOVIE_BROWSER = 0x01B8, + HID_USAGE_CONSUMER_AL_DIGITAL_RIGHTS_MANAGER = 0x01B9, + HID_USAGE_CONSUMER_AL_DIGITAL_WALLET = 0x01BA, + // 1BB Reserved + HID_USAGE_CONSUMER_AL_INSTANT_MESSAGING = 0x01BC, + HID_USAGE_CONSUMER_AL_OEM_FEATURES_TIPS_TUTORIAL = 0x01BD, + // Browser + // Sel 15.15 + HID_USAGE_CONSUMER_AL_OEM_HELP = 0x01BE, + HID_USAGE_CONSUMER_AL_ONLINE_COMMUNITY = 0x01BF, + HID_USAGE_CONSUMER_AL_ENTERTAINMENT_CONTENT = 0x01C0, + // Browser + // Sel 15.15 + HID_USAGE_CONSUMER_AL_ONLINE_SHOPPING_BROWSER = 0x01C1, + HID_USAGE_CONSUMER_AL_SMARTCARD_INFORMATION_HELP = 0x01C2, + HID_USAGE_CONSUMER_AL_MARKET_MONITOR_FINANCE = 0x01C3, + // Browser + // Sel 15.15 + HID_USAGE_CONSUMER_AL_CUSTOMIZED_CORPORATE_NEWS = 0x01C4, + // Browser + // Sel 15.15 + HID_USAGE_CONSUMER_AL_ONLINE_ACTIVITY_BROWSER = 0x01C5, + HID_USAGE_CONSUMER_AL_RESEARCH_SEARCH_BROWSER = 0x01C6, + HID_USAGE_CONSUMER_AL_AUDIO_PLAYER = 0x01C7, + // 1C8-1FF Reserved + HID_USAGE_CONSUMER_GENERIC_GUI_APPLICATION = 0x0200, + // ' Controls + // ' + HID_USAGE_CONSUMER_AC_NEW = 0x0201, + HID_USAGE_CONSUMER_AC_OPEN = 0x0202, + HID_USAGE_CONSUMER_AC_CLOSE = 0x0203, + HID_USAGE_CONSUMER_AC_EXIT = 0x0204, + HID_USAGE_CONSUMER_AC_MAXIMIZE = 0x0205, + HID_USAGE_CONSUMER_AC_MINIMIZE = 0x0206, + HID_USAGE_CONSUMER_AC_SAVE = 0x0207, + HID_USAGE_CONSUMER_AC_PRINT = 0x0208, + HID_USAGE_CONSUMER_AC_PROPERTIES = 0x0209, + HID_USAGE_CONSUMER_AC_UNDO = 0x021A, + HID_USAGE_CONSUMER_AC_COPY = 0x021B, + HID_USAGE_CONSUMER_AC_CUT = 0x021C, + HID_USAGE_CONSUMER_AC_PASTE = 0x021D, + HID_USAGE_CONSUMER_AC_SELECT_ALL = 0x021E, + HID_USAGE_CONSUMER_AC_FIND = 0x021F, + HID_USAGE_CONSUMER_AC_FIND_AND_REPLACE = 0x0220, + // Browser/Explorer Specific + HID_USAGE_CONSUMER_AC_SEARCH = 0x0221, + HID_USAGE_CONSUMER_AC_GO_TO = 0x0222, + HID_USAGE_CONSUMER_AC_HOME = 0x0223, + HID_USAGE_CONSUMER_AC_BACK = 0x0224, + HID_USAGE_CONSUMER_AC_FORWARD = 0x0225, + HID_USAGE_CONSUMER_AC_STOP = 0x0226, + HID_USAGE_CONSUMER_AC_REFRESH = 0x0227, + HID_USAGE_CONSUMER_AC_PREVIOUS_LINK = 0x0228, + HID_USAGE_CONSUMER_AC_NEXT_LINK = 0x0229, + HID_USAGE_CONSUMER_AC_BOOKMARKS = 0x022A, + HID_USAGE_CONSUMER_AC_HISTORY = 0x022B, + HID_USAGE_CONSUMER_AC_SUBSCRIPTIONS = 0x022C, + HID_USAGE_CONSUMER_AC_ZOOM_IN = 0x022D, + HID_USAGE_CONSUMER_AC_ZOOM_OUT = 0x022E, + HID_USAGE_CONSUMER_AC_ZOOM = 0x022F, + HID_USAGE_CONSUMER_AC_FULL_SCREEN_VIEW = 0x0230, + HID_USAGE_CONSUMER_AC_NORMAL_VIEW = 0x0231, + HID_USAGE_CONSUMER_AC_VIEW_TOGGLE = 0x0232, + HID_USAGE_CONSUMER_AC_SCROLL_UP = 0x0233, + HID_USAGE_CONSUMER_AC_SCROLL_DOWN = 0x0234, + HID_USAGE_CONSUMER_AC_SCROLL = 0x0235, + HID_USAGE_CONSUMER_AC_PAN_LEFT = 0x0236, + HID_USAGE_CONSUMER_AC_PAN_RIGHT = 0x0237, + // Mouse Horizontal scroll + HID_USAGE_CONSUMER_AC_PAN = 0x0238, + HID_USAGE_CONSUMER_AC_NEW_WINDOW = 0x0239, + HID_USAGE_CONSUMER_AC_TILE_HORIZONTALLY = 0x023A, + HID_USAGE_CONSUMER_AC_TILE_VERTICALLY = 0x023B, + HID_USAGE_CONSUMER_AC_FORMAT = 0x023C, + HID_USAGE_CONSUMER_AC_EDIT = 0x023D, + HID_USAGE_CONSUMER_AC_BOLD = 0x023E, + HID_USAGE_CONSUMER_AC_ITALICS = 0x023F, + HID_USAGE_CONSUMER_AC_UNDERLINE = 0x0240, + HID_USAGE_CONSUMER_AC_STRIKETHROUGH = 0x0241, + HID_USAGE_CONSUMER_AC_SUBSCRIPT = 0x0242, + HID_USAGE_CONSUMER_AC_SUPERSCRIPT = 0x0243, + HID_USAGE_CONSUMER_AC_ALL_CAPS = 0x0244, + HID_USAGE_CONSUMER_AC_ROTATE = 0x0245, + HID_USAGE_CONSUMER_AC_RESIZE = 0x0246, + HID_USAGE_CONSUMER_AC_FLIP_HORIZONTAL = 0x0247, + HID_USAGE_CONSUMER_AC_FLIP_VERTICAL = 0x0248, + HID_USAGE_CONSUMER_AC_MIRROR_HORIZONTAL = 0x0249, + HID_USAGE_CONSUMER_AC_MIRROR_VERTICAL = 0x024A, + HID_USAGE_CONSUMER_AC_FONT_SELECT = 0x024B, + HID_USAGE_CONSUMER_AC_FONT_COLOR = 0x024C, + HID_USAGE_CONSUMER_AC_FONT_SIZE = 0x024D, + HID_USAGE_CONSUMER_AC_JUSTIFY_LEFT = 0x024E, + HID_USAGE_CONSUMER_AC_JUSTIFY_CENTER_H = 0x024F, + HID_USAGE_CONSUMER_AC_JUSTIFY_RIGHT = 0x0250, + HID_USAGE_CONSUMER_AC_JUSTIFY_BLOCK_H = 0x0251, + HID_USAGE_CONSUMER_AC_JUSTIFY_TOP = 0x0252, + HID_USAGE_CONSUMER_AC_JUSTIFY_CENTER_V = 0x0253, + HID_USAGE_CONSUMER_AC_JUSTIFY_BOTTOM = 0x0254, + HID_USAGE_CONSUMER_AC_JUSTIFY_BLOCK_V = 0x0255, + HID_USAGE_CONSUMER_AC_INDENT_DECREASE = 0x0256, + HID_USAGE_CONSUMER_AC_INDENT_INCREASE = 0x0257, + HID_USAGE_CONSUMER_AC_NUMBERED_LIST = 0x0258, + HID_USAGE_CONSUMER_AC_RESTART_NUMBERING = 0x0259, + HID_USAGE_CONSUMER_AC_BULLETED_LIST = 0x025A, + HID_USAGE_CONSUMER_AC_PROMOTE = 0x025B, + HID_USAGE_CONSUMER_AC_DEMOTE = 0x025C, + HID_USAGE_CONSUMER_AC_YES = 0x025D, + HID_USAGE_CONSUMER_AC_NO = 0x025E, + HID_USAGE_CONSUMER_AC_CANCEL = 0x025F, + HID_USAGE_CONSUMER_AC_CATALOG = 0x0260, + HID_USAGE_CONSUMER_AC_BUY_CHECKOUT = 0x0261, + HID_USAGE_CONSUMER_AC_ADD_TO_CART = 0x0262, + HID_USAGE_CONSUMER_AC_EXPAND = 0x0263, + HID_USAGE_CONSUMER_AC_EXPAND_ALL = 0x0264, + HID_USAGE_CONSUMER_AC_COLLAPSE = 0x0265, + HID_USAGE_CONSUMER_AC_COLLAPSE_ALL = 0x0266, + HID_USAGE_CONSUMER_AC_PRINT_PREVIEW = 0x0267, + HID_USAGE_CONSUMER_AC_PASTE_SPECIAL = 0x0268, + HID_USAGE_CONSUMER_AC_INSERT_MODE = 0x0269, + HID_USAGE_CONSUMER_AC_DELETE = 0x026A, + HID_USAGE_CONSUMER_AC_LOCK = 0x026B, + HID_USAGE_CONSUMER_AC_UNLOCK = 0x026C, + HID_USAGE_CONSUMER_AC_PROTECT = 0x026D, + HID_USAGE_CONSUMER_AC_UNPROTECT = 0x026E, + HID_USAGE_CONSUMER_AC_ATTACH_COMMENT = 0x026F, + HID_USAGE_CONSUMER_AC_DELETE_COMMENT = 0x0270, + HID_USAGE_CONSUMER_AC_VIEW_COMMENT = 0x0271, + HID_USAGE_CONSUMER_AC_SELECT_WORD = 0x0272, + HID_USAGE_CONSUMER_AC_SELECT_SENTENCE = 0x0273, + HID_USAGE_CONSUMER_AC_SELECT_PARAGRAPH = 0x0274, + HID_USAGE_CONSUMER_AC_SELECT_COLUMN = 0x0275, + HID_USAGE_CONSUMER_AC_SELECT_ROW = 0x0276, + HID_USAGE_CONSUMER_AC_SELECT_TABLE = 0x0277, + HID_USAGE_CONSUMER_AC_SELECT_OBJECT = 0x0278, + HID_USAGE_CONSUMER_AC_REDO_REPEAT = 0x0279, + HID_USAGE_CONSUMER_AC_SORT = 0x027A, + HID_USAGE_CONSUMER_AC_SORT_ASCENDING = 0x027B, + HID_USAGE_CONSUMER_AC_SORT_DESCENDING = 0x027C, + HID_USAGE_CONSUMER_AC_FILTER = 0x027D, + HID_USAGE_CONSUMER_AC_SET_CLOCK = 0x027E, + HID_USAGE_CONSUMER_AC_VIEW_CLOCK = 0x027F, + HID_USAGE_CONSUMER_AC_SELECT_TIME_ZONE = 0x0280, + HID_USAGE_CONSUMER_AC_EDIT_TIME_ZONES = 0x0281, + HID_USAGE_CONSUMER_AC_SET_ALARM = 0x0282, + HID_USAGE_CONSUMER_AC_CLEAR_ALARM = 0x0283, + HID_USAGE_CONSUMER_AC_SNOOZE_ALARM = 0x0284, + HID_USAGE_CONSUMER_AC_RESET_ALARM = 0x0285, + HID_USAGE_CONSUMER_AC_SYNCHRONIZE = 0x0286, + HID_USAGE_CONSUMER_AC_SEND_RECEIVE = 0x0287, + HID_USAGE_CONSUMER_AC_SEND_TO = 0x0288, + HID_USAGE_CONSUMER_AC_REPLY = 0x0289, + HID_USAGE_CONSUMER_AC_REPLY_ALL = 0x028A, + HID_USAGE_CONSUMER_AC_FORWARD_MSG = 0x028B, + HID_USAGE_CONSUMER_AC_SEND = 0x028C, + HID_USAGE_CONSUMER_AC_ATTACH_FILE = 0x028D, + HID_USAGE_CONSUMER_AC_UPLOAD = 0x028E, + HID_USAGE_CONSUMER_AC_DOWNLOAD_SAVE_TARGET_AS = 0x028F, + HID_USAGE_CONSUMER_AC_SET_BORDERS = 0x0290, + HID_USAGE_CONSUMER_AC_INSERT_ROW = 0x0291, + HID_USAGE_CONSUMER_AC_INSERT_COLUMN = 0x0292, + HID_USAGE_CONSUMER_AC_INSERT_FILE = 0x0293, + HID_USAGE_CONSUMER_AC_INSERT_PICTURE = 0x0294, + HID_USAGE_CONSUMER_AC_INSERT_OBJECT = 0x0295, + HID_USAGE_CONSUMER_AC_INSERT_SYMBOL = 0x0296, + HID_USAGE_CONSUMER_AC_SAVE_AND_CLOSE = 0x0297, + HID_USAGE_CONSUMER_AC_RENAME = 0x0298, + HID_USAGE_CONSUMER_AC_MERGE = 0x0299, + HID_USAGE_CONSUMER_AC_SPLIT = 0x029A, + HID_USAGE_CONSUMER_AC_DISRIBUTE_HORIZONTALLY = 0x029B, + HID_USAGE_CONSUMER_AC_DISTRIBUTE_VERTICALLY = 0x029C, + // 29D-FFFF Reserved + +}; + +/// HID Usage Table: Digitizer Page (0x0D) +enum { + HID_USAGE_DIGITIZER_UNDEFINED = 0x00, + HID_USAGE_DIGITIZER_DIGITIZER = 0x01, // CA + HID_USAGE_DIGITIZER_PEN = 0x02, // CA + HID_USAGE_DIGITIZER_LIGHT_PEN = 0x03, // CA + HID_USAGE_DIGITIZER_TOUCH_SCREEN = 0x04, // CA + HID_USAGE_DIGITIZER_TOUCH_PAD = 0x05, // CA + HID_USAGE_DIGITIZER_WHITEBOARD = 0x06, // CA + HID_USAGE_DIGITIZER_COORDINATE_MEASURING_MACHINE = 0x07, // CA + HID_USAGE_DIGITIZER_3D_DIGITIZER = 0x08, // CA + HID_USAGE_DIGITIZER_STEREO_PLOTTER = 0x09, // CA + HID_USAGE_DIGITIZER_ARTICULATED_ARM = 0x0A, // CA + HID_USAGE_DIGITIZER_ARMATURE = 0x0B, // CA + HID_USAGE_DIGITIZER_MULTIPLE_POINT_DIGITIZER = 0x0C, // CA + HID_USAGE_DIGITIZER_FREE_SPACE_WAND = 0x0D, // CA + HID_USAGE_DIGITIZER_DEVICE_CONFIGURATION = 0x0E, // CA + HID_USAGE_DIGITIZER_CAPACITIVE_HEAT_MAP_DIGITIZER = 0x0F, // CA + // Reserved (0x10 - 0x1F) + HID_USAGE_DIGITIZER_STYLUS = 0x20, // CA/CL + HID_USAGE_DIGITIZER_PUCK = 0x21, // CL + HID_USAGE_DIGITIZER_FINGER = 0x22, // CL + HID_USAGE_DIGITIZER_DEVICE_SETTINGS = 0x23, // CL + HID_USAGE_DIGITIZER_CHARACTER_GESTURE = 0x24, // CL + // Reserved (0x25 - 0x2F) + HID_USAGE_DIGITIZER_TIP_PRESSURE = 0x30, // DV + HID_USAGE_DIGITIZER_BARREL_PRESSURE = 0x31, // DV + HID_USAGE_DIGITIZER_IN_RANGE = 0x32, // MC + HID_USAGE_DIGITIZER_TOUCH = 0x33, // MC + HID_USAGE_DIGITIZER_UNTOUCH = 0x34, // OSC + HID_USAGE_DIGITIZER_TAP = 0x35, // OSC + HID_USAGE_DIGITIZER_QUALITY = 0x36, // DV + HID_USAGE_DIGITIZER_DATA_VALID = 0x37, // MC + HID_USAGE_DIGITIZER_TRANSDUCER_INDEX = 0x38, // DV + HID_USAGE_DIGITIZER_TABLET_FUNCTION_KEYS = 0x39, // CL + HID_USAGE_DIGITIZER_PROGRAM_CHANGE_KEYS = 0x3A, // CL + HID_USAGE_DIGITIZER_BATTERY_STRENGTH = 0x3B, // DV + HID_USAGE_DIGITIZER_INVERT = 0x3C, // MC + HID_USAGE_DIGITIZER_X_TILT = 0x3D, // DV + HID_USAGE_DIGITIZER_Y_TILT = 0x3E, // DV + HID_USAGE_DIGITIZER_AZIMUTH = 0x3F, // DV + HID_USAGE_DIGITIZER_ALTITUDE = 0x40, // DV + HID_USAGE_DIGITIZER_TWIST = 0x41, // DV + HID_USAGE_DIGITIZER_TIP_SWITCH = 0x42, // MC + HID_USAGE_DIGITIZER_SECONDARY_TIP_SWITCH = 0x43, // MC + HID_USAGE_DIGITIZER_BARREL_SWITCH = 0x44, // MC + HID_USAGE_DIGITIZER_ERASER = 0x45, // MC + HID_USAGE_DIGITIZER_TABLET_PICK = 0x46, // MC + HID_USAGE_DIGITIZER_TOUCH_VALID = 0x47, // MC + HID_USAGE_DIGITIZER_WIDTH = 0x48, // DV + HID_USAGE_DIGITIZER_HEIGHT = 0x49, // DV + // Reserved (0x4A - 0x50) + HID_USAGE_DIGITIZER_CONTACT_IDENTIFIER = 0x51, // DV + HID_USAGE_DIGITIZER_DEVICE_MODE = 0x52, // DV + HID_USAGE_DIGITIZER_DEVICE_IDENTIFIER = 0x53, // DV/SV + HID_USAGE_DIGITIZER_CONTACT_COUNT = 0x54, // DV + HID_USAGE_DIGITIZER_CONTACT_COUNT_MAXIMUM = 0x55, // SV + HID_USAGE_DIGITIZER_SCAN_TIME = 0x56, // DV + HID_USAGE_DIGITIZER_SURFACE_SWITCH = 0x57, // DF + HID_USAGE_DIGITIZER_BUTTON_SWITCH = 0x58, // DF + HID_USAGE_DIGITIZER_PAD_TYPE = 0x59, // SF + HID_USAGE_DIGITIZER_TRANSDUCER_SERIAL_NUMBER = 0x5B, // SV + HID_USAGE_DIGITIZER_PREFERRED_COLOR = 0x5C, // DV + HID_USAGE_DIGITIZER_PREFERRED_COLOR_LOCKED = 0x5D, // MC + HID_USAGE_DIGITIZER_PREFERRED_LINE_WIDTH = 0x5E, // DV + HID_USAGE_DIGITIZER_PREFERRED_LINE_WIDTH_LOCKED = 0x5F, // MC + HID_USAGE_DIGITIZER_LATENCY_MODE = 0x60, // DF + HID_USAGE_DIGITIZER_GESTURE_CHARACTER_QUALITY = 0x61, // DV + HID_USAGE_DIGITIZER_CHARACTER_GESTURE_DATA_LENGTH = 0x62, // DV + HID_USAGE_DIGITIZER_CHARACTER_GESTURE_DATA = 0x63, // DV + HID_USAGE_DIGITIZER_GESTURE_CHARACTER_ENCODING = 0x64, // NAry + HID_USAGE_DIGITIZER_UTF8_CHARACTER_GESTURE_ENCODING = 0x65, // Sel + HID_USAGE_DIGITIZER_UTF16_LE_CHARACTER_GESTURE_ENCODING = 0x66, // Sel + HID_USAGE_DIGITIZER_UTF16_BE_CHARACTER_GESTURE_ENCODING = 0x67, // Sel + HID_USAGE_DIGITIZER_UTF32_LE_CHARACTER_GESTURE_ENCODING = 0x68, // Sel + HID_USAGE_DIGITIZER_UTF32_BE_CHARACTER_GESTURE_ENCODING = 0x69, // Sel + HID_USAGE_DIGITIZER_CAPACITIVE_HEAT_MAP_VENDOR_ID = 0x6A, // SV + HID_USAGE_DIGITIZER_CAPACITIVE_HEAT_MAP_VERSION = 0x6B, // SV + HID_USAGE_DIGITIZER_CAPACITIVE_HEAT_MAP_FRAME_DATA = 0x6C, // DV + HID_USAGE_DIGITIZER_GESTURE_CHARACTER_ENABLE = 0x6D, // DF + HID_USAGE_DIGITIZER_TRANSDUCER_SERIAL_NUMBER_PART2 = 0x6E, // SV + HID_USAGE_DIGITIZER_NO_PREFERRED_COLOR = 0x6F, // DF + HID_USAGE_DIGITIZER_PREFERRED_LINE_STYLE = 0x70, // NAry + HID_USAGE_DIGITIZER_PREFERRED_LINE_STYLE_LOCKED = 0x71, // MC + HID_USAGE_DIGITIZER_INK = 0x72, // Sel + HID_USAGE_DIGITIZER_PENCIL = 0x73, // Sel + HID_USAGE_DIGITIZER_HIGHLIGHTER = 0x74, // Sel + HID_USAGE_DIGITIZER_CHISEL_MARKER = 0x75, // Sel + HID_USAGE_DIGITIZER_BRUSH = 0x76, // Sel + HID_USAGE_DIGITIZER_NO_PREFERENCE = 0x77, // Sel + // Reserved (0x78 - 0x7F) + HID_USAGE_DIGITIZER_DIGITIZER_DIAGNOSTIC = 0x80, // CL + HID_USAGE_DIGITIZER_DIGITIZER_ERROR = 0x81, // NAry + HID_USAGE_DIGITIZER_ERR_NORMAL_STATUS = 0x82, // Sel + HID_USAGE_DIGITIZER_ERR_TRANSDUCERS_EXCEEDED = 0x83, // Sel + HID_USAGE_DIGITIZER_ERR_FULL_TRANS_FEATURES_UNAVAILABLE = 0x84, // Sel + HID_USAGE_DIGITIZER_ERR_CHARGE_LOW = 0x85, // Sel + // Reserved (0x86 - 0x8F) + HID_USAGE_DIGITIZER_TRANSDUCER_SOFTWARE_INFO = 0x90, // CL + HID_USAGE_DIGITIZER_TRANSDUCER_VENDOR_ID = 0x91, // SV + HID_USAGE_DIGITIZER_TRANSDUCER_PRODUCT_ID = 0x92, // SV + HID_USAGE_DIGITIZER_DEVICE_SUPPORTED_PROTOCOLS = 0x93, // NAry/CL + HID_USAGE_DIGITIZER_TRANSDUCER_SUPPORTED_PROTOCOLS = 0x94, // NAry/CL + HID_USAGE_DIGITIZER_NO_PROTOCOL = 0x95, // Sel + HID_USAGE_DIGITIZER_WACOM_AES_PROTOCOL = 0x96, // Sel + HID_USAGE_DIGITIZER_USI_PROTOCOL = 0x97, // Sel + HID_USAGE_DIGITIZER_MICROSOFT_PEN_PROTOCOL = 0x98, // Sel + // Reserved (0x99 - 0x9F) + HID_USAGE_DIGITIZER_SUPPORTED_REPORT_RATES = 0xA0, // SV/CL + HID_USAGE_DIGITIZER_REPORT_RATE = 0xA1, // DV + HID_USAGE_DIGITIZER_TRANSDUCER_CONNECTED = 0xA2, // SF + HID_USAGE_DIGITIZER_SWITCH_DISABLED = 0xA3, // Sel + HID_USAGE_DIGITIZER_SWITCH_UNIMPLEMENTED = 0xA4, // Sel + HID_USAGE_DIGITIZER_TRANSDUCER_SWITCHES = 0xA5, // CL + HID_USAGE_DIGITIZER_TRANSDUCER_INDEX_SELECTOR = 0xA6, // DV + // Reserved (0xA7 - 0xAF) + HID_USAGE_DIGITIZER_BUTTON_PRESS_THRESHOLD = 0xB0, // DV + + // Reserved (0xB1 - 0xFFFF) +}; + +/// HID Usage Table: Physical Input Device Page (0x0F) +enum { + HID_USAGE_PID_UNDEFINED = 0x00, + HID_USAGE_PID_PHYSICAL_INPUT_DEVICE = 0x01, + HID_USAGE_PID_NORMAL = 0x20, + HID_USAGE_PID_SET_EFFECT_REPORT = 0x21, + HID_USAGE_PID_EFFECT_PARAMETER_BLOCK_INDEX = 0x22, + HID_USAGE_PID_PARAMETER_BLOCK_OFFSET = 0x23, + HID_USAGE_PID_ROM_FLAG = 0x24, + HID_USAGE_PID_EFFECT_TYPE = 0x25, + HID_USAGE_PID_ET_CONSTANTFORCE = 0x26, + HID_USAGE_PID_ET_RAMP = 0x27, + HID_USAGE_PID_ET_CUSTOMFORCE = 0x28, + HID_USAGE_PID_ET_SQUARE = 0x30, + HID_USAGE_PID_ET_SINE = 0x31, + HID_USAGE_PID_ET_TRIANGLE = 0x32, + HID_USAGE_PID_ET_SAWTOOTH_UP = 0x33, + HID_USAGE_PID_ET_SAWTOOTH_DOWN = 0x34, + HID_USAGE_PID_ET_SPRING = 0x40, + HID_USAGE_PID_ET_DAMPER = 0x41, + HID_USAGE_PID_ET_INERTIA = 0x42, + HID_USAGE_PID_ET_FRICTION = 0x43, + HID_USAGE_PID_DURATION = 0x50, + HID_USAGE_PID_SAMPLE_PERIOD = 0x51, + HID_USAGE_PID_GAIN = 0x52, + HID_USAGE_PID_TRIGGER_BUTTON = 0x53, + HID_USAGE_PID_TRIGGER_REPEAT_INTERVAL = 0x54, + HID_USAGE_PID_AXES_ENABLE = 0x55, + HID_USAGE_PID_DIRECTION_ENABLE = 0x56, + HID_USAGE_PID_DIRECTION = 0x57, + HID_USAGE_PID_TYPE_SPECIFIC_BLOCK_OFFSET = 0x58, + HID_USAGE_PID_BLOCK_TYPE = 0x59, + HID_USAGE_PID_SET_ENVELOPE_REPORT = 0x5a, + HID_USAGE_PID_ATTACK_LEVEL = 0x5b, + HID_USAGE_PID_ATTACK_TIME = 0x5c, + HID_USAGE_PID_FADE_LEVEL = 0x5d, + HID_USAGE_PID_FADE_TIME = 0x5e, + HID_USAGE_PID_SET_CONDITION_REPORT = 0x5f, + HID_USAGE_PID_CENTERPOINT_OFFSET = 0x60, + HID_USAGE_PID_POSITIVE_COEFFICIENT = 0x61, + HID_USAGE_PID_NEGATIVE_COEFFICIENT = 0x62, + HID_USAGE_PID_POSITIVE_SATURATION = 0x63, + HID_USAGE_PID_NEGATIVE_SATURATION = 0x64, + HID_USAGE_PID_DEAD_BAND = 0x65, + HID_USAGE_PID_DOWNLOAD_FORCE_SAMPLE = 0x66, + HID_USAGE_PID_ISOCH_CUSTOMFORCE_ENABLE = 0x67, + HID_USAGE_PID_CUSTOMFORCE_DATA_REPORT = 0x68, + HID_USAGE_PID_CUSTOMFORCE_DATA = 0x69, + HID_USAGE_PID_CUSTOMFORCE_VENDOR_DEFINED_DATA = 0x6a, + HID_USAGE_PID_SET_CUSTOMFORCE_REPORT = 0x6b, + HID_USAGE_PID_CUSTOMFORCE_DATA_OFFSET = 0x6c, + HID_USAGE_PID_SAMPLE_COUNT = 0x6d, + HID_USAGE_PID_SET_PERIODIC_REPORT = 0x6e, + HID_USAGE_PID_OFFSET = 0x6f, + HID_USAGE_PID_MAGNITUDE = 0x70, + HID_USAGE_PID_PHASE = 0x71, + HID_USAGE_PID_PERIOD = 0x72, + HID_USAGE_PID_SET_CONSTANTFORCE_REPORT = 0x73, + HID_USAGE_PID_SET_RAMPFORCE_REPORT = 0x74, + HID_USAGE_PID_RAMP_START = 0x75, + HID_USAGE_PID_RAMP_END = 0x76, + HID_USAGE_PID_EFFECT_OPERATION_REPORT = 0x77, + HID_USAGE_PID_EFFECT_OPERATION = 0x78, + HID_USAGE_PID_OP_EFFECT_START = 0x79, + HID_USAGE_PID_OP_EFFECT_START_SOLO = 0x7a, + HID_USAGE_PID_OP_EFFECT_STOP = 0x7b, + HID_USAGE_PID_LOOP_COUNT = 0x7c, + HID_USAGE_PID_DEVICE_GAIN_REPORT = 0x7d, + HID_USAGE_PID_DEVICE_GAIN = 0x7e, + HID_USAGE_PID_PARAMETER_BLOCK_POOLS_REPORT = 0x7f, + HID_USAGE_PID_RAM_POOL_SIZE = 0x80, + HID_USAGE_PID_ROM_POOL_SIZE = 0x81, + HID_USAGE_PID_ROM_EFFECT_BLOCK_COUNT = 0x82, + HID_USAGE_PID_SIMULTANEOUS_EFFECTS_MAX = 0x83, + HID_USAGE_PID_POOL_ALIGNMENT = 0x84, + HID_USAGE_PID_PARAMETER_BLOCK_MOVE_REPORT = 0x85, + HID_USAGE_PID_MOVE_SOURCE = 0x86, + HID_USAGE_PID_MOVE_DESTINATION = 0x87, + HID_USAGE_PID_MOVE_LENGTH = 0x88, + HID_USAGE_PID_EFFECT_PARAMETER_BLOCK_LOAD_REPORT = 0x89, + HID_USAGE_PID_EFFECT_PARAMETER_BLOCK_LOAD_STATUS = 0x8b, + HID_USAGE_PID_BLOCK_LOAD_SUCCESS = 0x8c, + HID_USAGE_PID_BLOCK_LOAD_FULL = 0x8d, + HID_USAGE_PID_BLOCK_LOAD_ERROR = 0x8e, + HID_USAGE_PID_BLOCK_HANDLE = 0x8f, + HID_USAGE_PID_EFFECT_PARAMETER_BLOCK_FREE_REPORT = 0x90, + HID_USAGE_PID_TYPE_SPECIFIC_BLOCK_HANDLE = 0x91, + HID_USAGE_PID_PID_STATE_REPORT = 0x92, + HID_USAGE_PID_EFFECT_PLAYING = 0x94, + HID_USAGE_PID_PID_DEVICE_CONTROL_REPORT = 0x95, + HID_USAGE_PID_PID_DEVICE_CONTROL = 0x96, + HID_USAGE_PID_DC_ENABLE_ACTUATORS = 0x97, + HID_USAGE_PID_DC_DISABLE_ACTUATORS = 0x98, + HID_USAGE_PID_DC_STOP_ALL_EFFECTS = 0x99, + HID_USAGE_PID_DC_RESET = 0x9a, + HID_USAGE_PID_DC_PAUSE = 0x9b, + HID_USAGE_PID_DC_CONTINUE = 0x9c, + HID_USAGE_PID_DEVICE_PAUSED = 0x9f, + HID_USAGE_PID_ACTUATORS_ENABLED = 0xa0, + HID_USAGE_PID_SAFETY_SWITCH = 0xa4, + HID_USAGE_PID_ACTUATOR_OVERRIDE_SWITCH = 0xa5, + HID_USAGE_PID_ACTUATOR_POWER = 0xa6, + HID_USAGE_PID_START_DELAY = 0xa7, + HID_USAGE_PID_PARAMETER_BLOCK_SIZE = 0xa8, + HID_USAGE_PID_DEVICEMANAGED_POOL = 0xa9, + HID_USAGE_PID_SHARED_PARAMETER_BLOCKS = 0xaa, + HID_USAGE_PID_CREATE_NEW_EFFECT_PARAMETER_BLOCK_REPORT = 0xab, + HID_USAGE_PID_RAM_POOL_AVAILABLE = 0xac, +}; + +/// HID Usage Table - Lighting And Illumination Page (0x59) +enum { + HID_USAGE_LIGHTING_LAMP_ARRAY = 0x01, + HID_USAGE_LIGHTING_LAMP_ARRAY_ATTRIBUTES_REPORT = 0x02, + HID_USAGE_LIGHTING_LAMP_COUNT = 0x03, + HID_USAGE_LIGHTING_BOUNDING_BOX_WIDTH_IN_MICROMETERS = 0x04, + HID_USAGE_LIGHTING_BOUNDING_BOX_HEIGHT_IN_MICROMETERS = 0x05, + HID_USAGE_LIGHTING_BOUNDING_BOX_DEPTH_IN_MICROMETERS = 0x06, + HID_USAGE_LIGHTING_LAMP_ARRAY_KIND = 0x07, + HID_USAGE_LIGHTING_MIN_UPDATE_INTERVAL_IN_MICROSECONDS = 0x08, + HID_USAGE_LIGHTING_LAMP_ATTRIBUTES_REQUEST_REPORT = 0x20, + HID_USAGE_LIGHTING_LAMP_ID = 0x21, + HID_USAGE_LIGHTING_LAMP_ATTRIBUTES_RESPONSE_REPORT = 0x22, + HID_USAGE_LIGHTING_POSITION_X_IN_MICROMETERS = 0x23, + HID_USAGE_LIGHTING_POSITION_Y_IN_MICROMETERS = 0x24, + HID_USAGE_LIGHTING_POSITION_Z_IN_MICROMETERS = 0x25, + HID_USAGE_LIGHTING_LAMP_PURPOSES = 0x26, + HID_USAGE_LIGHTING_UPDATE_LATENCY_IN_MICROSECONDS = 0x27, + HID_USAGE_LIGHTING_RED_LEVEL_COUNT = 0x28, + HID_USAGE_LIGHTING_GREEN_LEVEL_COUNT = 0x29, + HID_USAGE_LIGHTING_BLUE_LEVEL_COUNT = 0x2A, + HID_USAGE_LIGHTING_INTENSITY_LEVEL_COUNT = 0x2B, + HID_USAGE_LIGHTING_IS_PROGRAMMABLE = 0x2C, + HID_USAGE_LIGHTING_INPUT_BINDING = 0x2D, + HID_USAGE_LIGHTING_LAMP_MULTI_UPDATE_REPORT = 0x50, + HID_USAGE_LIGHTING_RED_UPDATE_CHANNEL = 0x51, + HID_USAGE_LIGHTING_GREEN_UPDATE_CHANNEL = 0x52, + HID_USAGE_LIGHTING_BLUE_UPDATE_CHANNEL = 0x53, + HID_USAGE_LIGHTING_INTENSITY_UPDATE_CHANNEL = 0x54, + HID_USAGE_LIGHTING_LAMP_UPDATE_FLAGS = 0x55, + HID_USAGE_LIGHTING_LAMP_RANGE_UPDATE_REPORT = 0x60, + HID_USAGE_LIGHTING_LAMP_ID_START = 0x61, + HID_USAGE_LIGHTING_LAMP_ID_END = 0x62, + HID_USAGE_LIGHTING_LAMP_ARRAY_CONTROL_REPORT = 0x70, + HID_USAGE_LIGHTING_AUTONOMOUS_MODE = 0x71, +}; + +/// HID Usage Table: Power Device Page (0x84) +enum { + HID_USAGE_POWER_UNDEFINED = 0x00, + HID_USAGE_POWER_I_NAME = 0x01, + HID_USAGE_POWER_PRESENT_STATUS = 0x02, + HID_USAGE_POWER_CHANGED_STATUS = 0x03, + HID_USAGE_POWER_UPS = 0x04, + HID_USAGE_POWER_POWER_SUPPLY = 0x05, + // 06-0F Reserved + HID_USAGE_POWER_BATTERY_SYSTEM = 0x10, + HID_USAGE_POWER_BATTERY_SYSTEM_ID = 0x11, + HID_USAGE_POWER_BATTERY = 0x12, + HID_USAGE_POWER_BATTERY_ID = 0x13, + HID_USAGE_POWER_CHARGER = 0x14, + HID_USAGE_POWER_CHARGER_ID = 0x15, + HID_USAGE_POWER_POWER_CONVERTER = 0x16, + HID_USAGE_POWER_POWER_CONVERTER_ID = 0x17, + HID_USAGE_POWER_OUTLET_SYSTEM = 0x18, + HID_USAGE_POWER_OUTLET_SYSTEM_ID = 0x19, + HID_USAGE_POWER_INPUT = 0x1A, + HID_USAGE_POWER_INPUT_ID = 0x1B, + HID_USAGE_POWER_OUTPUT = 0x1C, + HID_USAGE_POWER_OUTPUT_ID = 0x1D, + HID_USAGE_POWER_FLOW = 0x1E, + HID_USAGE_POWER_FLOW_ID = 0x1F, + HID_USAGE_POWER_OUTLET = 0x20, + HID_USAGE_POWER_OUTLET_ID = 0x21, + HID_USAGE_POWER_GANG = 0x22, + HID_USAGE_POWER_GANG_ID = 0x23, + HID_USAGE_POWER_POWER_SUMMARY = 0x24, + HID_USAGE_POWER_POWER_SUMMARY_ID = 0x25, + // 26-2F Reserved + HID_USAGE_POWER_VOLTAGE = 0x30, + HID_USAGE_POWER_CURRENT = 0x31, + HID_USAGE_POWER_FREQUENCY = 0x32, + HID_USAGE_POWER_APPARENT_POWER = 0x33, + HID_USAGE_POWER_ACTIVE_POWER = 0x34, + HID_USAGE_POWER_PERCENT_LOAD = 0x35, + HID_USAGE_POWER_TEMPERATURE = 0x36, + HID_USAGE_POWER_HUMIDITY = 0x37, + HID_USAGE_POWER_BAD_COUNT = 0x38, + // 39-3F Reserved + HID_USAGE_POWER_CONFIG_VOLTAGE = 0x40, + HID_USAGE_POWER_CONFIG_CURRENT = 0x41, + HID_USAGE_POWER_CONFIG_FREQUENCY = 0x42, + HID_USAGE_POWER_CONFIG_APPARENT_POWER = 0x43, + HID_USAGE_POWER_CONFIG_ACTIVE_POWER = 0x44, + HID_USAGE_POWER_CONFIG_PERCENT_LOAD = 0x45, + HID_USAGE_POWER_CONFIG_TEMPERATURE = 0x46, + HID_USAGE_POWER_CONFIG_HUMIDITY = 0x47, + // 48-4F Reserved + HID_USAGE_POWER_SWITCH_ON_CONTROL = 0x50, + HID_USAGE_POWER_SWITCH_OFF_CONTROL = 0x51, + HID_USAGE_POWER_TOGGLE_CONTROL = 0x52, + HID_USAGE_POWER_LOW_VOLTAGE_TRANSFER = 0x53, + HID_USAGE_POWER_HIGH_VOLTAGE_TRANSFER = 0x54, + HID_USAGE_POWER_DELAY_BEFORE_REBOOT = 0x55, + HID_USAGE_POWER_DELAY_BEFORE_STARTUP = 0x56, + HID_USAGE_POWER_DELAY_BEFORE_SHUTDOWN = 0x57, + HID_USAGE_POWER_TEST = 0x58, + HID_USAGE_POWER_MODULE_RESET = 0x59, + HID_USAGE_POWER_AUDIBLE_ALARM_CONTROL = 0x5A, + // 5B-5F Reserved + HID_USAGE_POWER_PRESENT = 0x60, + HID_USAGE_POWER_GOOD = 0x61, + HID_USAGE_POWER_INTERNAL_FAILURE = 0x62, + HID_USAGE_POWER_VOLTAGE_OUT_OF_RANGE = 0x63, + HID_USAGE_POWER_FREQUENCY_OUT_OF_RANGE = 0x64, + HID_USAGE_POWER_OVERLOAD = 0x65, + HID_USAGE_POWER_OVER_CHARGED = 0x66, + HID_USAGE_POWER_OVER_TEMPERATURE = 0x67, + HID_USAGE_POWER_SHUTDOWN_REQUESTED = 0x68, + HID_USAGE_POWER_SHUTDOWN_IMMINENT = 0x69, + // 6A Reserved + HID_USAGE_POWER_SWITCH_ON_OFF = 0x6B, + HID_USAGE_POWER_SWITCHABLE = 0x6C, + HID_USAGE_POWER_USED = 0x6D, + HID_USAGE_POWER_BOOST = 0x6E, + HID_USAGE_POWER_BUCK = 0x6F, + HID_USAGE_POWER_INITIALIZED = 0x70, + HID_USAGE_POWER_TESTED = 0x71, + HID_USAGE_POWER_AWAITING_POWER = 0x72, + HID_USAGE_POWER_COMMUNICATION_LOST = 0x73, + // 74-FC Reserved + HID_USAGE_POWER_I_MANUFACTURER = 0xFD, + HID_USAGE_POWER_I_PRODUCT = 0xFE, + HID_USAGE_POWER_I_SERIAL_NUMBER = 0xFF +}; + +/// HID Usage Table: Battery System Page (0x85) +enum { + HID_USAGE_BATTERY_UNDEFINED = 0x00, + HID_USAGE_BATTERY_SMB_BATTERY_MODE = 0x01, + HID_USAGE_BATTERY_SMB_BATTERY_STATUS = 0x02, + HID_USAGE_BATTERY_SMB_ALARM_WARNING = 0x03, + HID_USAGE_BATTERY_SMB_CHARGER_MODE = 0x04, + HID_USAGE_BATTERY_SMB_CHARGER_STATUS = 0x05, + HID_USAGE_BATTERY_SMB_CHARGER_SPEC_INFO = 0x06, + HID_USAGE_BATTERY_SMB_SELECTOR_STATE = 0x07, + HID_USAGE_BATTERY_SMB_SELECTOR_PRESETS = 0x08, + HID_USAGE_BATTERY_SMB_SELECTOR_INFO = 0x09, + // 0A-0F Reserved + HID_USAGE_BATTERY_OPTIONAL_MFG_FUNCTION_1 = 0x10, + HID_USAGE_BATTERY_OPTIONAL_MFG_FUNCTION_2 = 0x11, + HID_USAGE_BATTERY_OPTIONAL_MFG_FUNCTION_3 = 0x12, + HID_USAGE_BATTERY_OPTIONAL_MFG_FUNCTION_4 = 0x13, + HID_USAGE_BATTERY_OPTIONAL_MFG_FUNCTION_5 = 0x14, + HID_USAGE_BATTERY_CONNECTION_TO_SMBUS = 0x15, + HID_USAGE_BATTERY_OUTPUT_CONNECTION = 0x16, + HID_USAGE_BATTERY_CHARGER_CONNECTION = 0x17, + HID_USAGE_BATTERY_BATTERY_INSERTION = 0x18, + HID_USAGE_BATTERY_USE_NEXT = 0x19, + HID_USAGE_BATTERY_OK_TO_USE = 0x1A, + HID_USAGE_BATTERY_BATTERY_SUPPORTED = 0x1B, + HID_USAGE_BATTERY_SELECTOR_REVISION = 0x1C, + HID_USAGE_BATTERY_CHARGING_INDICATOR = 0x1D, + // 1E-27 Reserved + HID_USAGE_BATTERY_MANUFACTURER_ACCESS = 0x28, + HID_USAGE_BATTERY_REMAINING_CAPACITY_LIMIT = 0x29, + HID_USAGE_BATTERY_REMAINING_TIME_LIMIT = 0x2A, + HID_USAGE_BATTERY_AT_RATE = 0x2B, + HID_USAGE_BATTERY_CAPACITY_MODE = 0x2C, + HID_USAGE_BATTERY_BROADCAST_TO_CHARGER = 0x2D, + HID_USAGE_BATTERY_PRIMARY_BATTERY = 0x2E, + HID_USAGE_BATTERY_CHARGE_CONTROLLER = 0x2F, + // 30-3F Reserved + HID_USAGE_BATTERY_TERMINATE_CHARGE = 0x40, + HID_USAGE_BATTERY_TERMINATE_DISCHARGE = 0x41, + HID_USAGE_BATTERY_BELOW_REMAINING_CAPACITY_LIMIT = 0x42, + HID_USAGE_BATTERY_REMAINING_TIME_LIMIT_EXPIRED = 0x43, + HID_USAGE_BATTERY_CHARGING = 0x44, + HID_USAGE_BATTERY_DISCHARGING = 0x45, + HID_USAGE_BATTERY_FULLY_CHARGED = 0x46, + HID_USAGE_BATTERY_FULLY_DISCHARGED = 0x47, + HID_USAGE_BATTERY_CONDITIONING_FLAG = 0x48, + HID_USAGE_BATTERY_AT_RATE_OK = 0x49, + HID_USAGE_BATTERY_SMB_ERROR_CODE = 0x4A, + HID_USAGE_BATTERY_NEED_REPLACEMENT = 0x4B, + // 4C-5F Reserved + HID_USAGE_BATTERY_AT_RATE_TIME_TO_FULL = 0x60, + HID_USAGE_BATTERY_AT_RATE_TIME_TO_EMPTY = 0x61, + HID_USAGE_BATTERY_AVERAGE_CURRENT = 0x62, + HID_USAGE_BATTERY_MAX_ERROR = 0x63, + HID_USAGE_BATTERY_RELATIVE_STATE_OF_CHARGE = 0x64, + HID_USAGE_BATTERY_ABSOLUTE_STATE_OF_CHARGE = 0x65, + HID_USAGE_BATTERY_REMAINING_CAPACITY = 0x66, + HID_USAGE_BATTERY_FULL_CHARGE_CAPACITY = 0x67, + HID_USAGE_BATTERY_RUN_TIME_TO_EMPTY = 0x68, + HID_USAGE_BATTERY_AVERAGE_TIME_TO_EMPTY = 0x69, + HID_USAGE_BATTERY_AVERAGE_TIME_TO_FULL = 0x6A, + HID_USAGE_BATTERY_CYCLE_COUNT = 0x6B, + // 6C-7F Reserved + HID_USAGE_BATTERY_BATT_PACK_MODEL_LEVEL = 0x80, + HID_USAGE_BATTERY_INTERNAL_CHARGE_CONTROLLER = 0x81, + HID_USAGE_BATTERY_PRIMARY_BATTERY_SUPPORT = 0x82, + HID_USAGE_BATTERY_DESIGN_CAPACITY = 0x83, + HID_USAGE_BATTERY_SPECIFICATION_INFO = 0x84, + HID_USAGE_BATTERY_MANUFACTURER_DATE = 0x85, + HID_USAGE_BATTERY_SERIAL_NUMBER = 0x86, + HID_USAGE_BATTERY_I_MANUFACTURER_NAME = 0x87, + HID_USAGE_BATTERY_I_DEVICE_NAME = 0x88, + HID_USAGE_BATTERY_I_DEVICE_CHEMISTRY = 0x89, + HID_USAGE_BATTERY_MANUFACTURER_DATA = 0x8A, + HID_USAGE_BATTERY_RECHARGEABLE = 0x8B, + HID_USAGE_BATTERY_WARNING_CAPACITY_LIMIT = 0x8C, + HID_USAGE_BATTERY_CAPACITY_GRANULARITY_1 = 0x8D, + HID_USAGE_BATTERY_CAPACITY_GRANULARITY_2 = 0x8E, + HID_USAGE_BATTERY_I_OEMINFORMATION = 0x8F, + // 90-BF Reserved + HID_USAGE_BATTERY_INHIBIT_CHARGE = 0xC0, + HID_USAGE_BATTERY_ENABLE_POLLING = 0xC1, + HID_USAGE_BATTERY_RESET_TO_ZERO = 0xC2, + // C3-CF Reserved + HID_USAGE_BATTERY_AC_PRESENT = 0xD0, + HID_USAGE_BATTERY_BATTERY_PRESENT = 0xD1, + HID_USAGE_BATTERY_POWER_FAIL = 0xD2, + HID_USAGE_BATTERY_ALARM_INHIBITED = 0xD3, + HID_USAGE_BATTERY_THERMISTOR_UNDER_RANGE = 0xD4, + HID_USAGE_BATTERY_THERMISTOR_HOT = 0xD5, + HID_USAGE_BATTERY_THERMISTOR_COLD = 0xD6, + HID_USAGE_BATTERY_THERMISTOR_OVER_RANGE = 0xD7, + HID_USAGE_BATTERY_VOLTAGE_OUT_OF_RANGE = 0xD8, + HID_USAGE_BATTERY_CURRENT_OUT_OF_RANGE = 0xD9, + HID_USAGE_BATTERY_CURRENT_NOT_REGULATED = 0xDA, + HID_USAGE_BATTERY_VOLTAGE_NOT_REGULATED = 0xDB, + HID_USAGE_BATTERY_MASTER_MODE = 0xDC, + // DD-EF Reserved + HID_USAGE_BATTERY_CHARGER_SELECTOR_SUPPORT = 0xF0, + HID_USAGE_BATTERY_CHARGER_SPEC = 0xF1, + HID_USAGE_BATTERY_LEVEL_2 = 0xF2, + HID_USAGE_BATTERY_LEVEL_3 = 0xF3 + // F4-FF Reserved +}; + +/// HID Usage Table: FIDO Alliance Page (0xF1D0) +enum { + HID_USAGE_FIDO_U2FHID = 0x01, // U2FHID usage for top-level collection + HID_USAGE_FIDO_DATA_IN = 0x20, // Raw IN data report + HID_USAGE_FIDO_DATA_OUT = 0x21 // Raw OUT data report +}; + +/*-------------------------------------------------------------------- + * ASCII to KEYCODE Conversion + * Expand to array of [128][2] (shift, keycode) + * + * Usage: example to convert input chr into keyboard report (modifier + keycode) + * + * uint8_t const conv_table[128][2] = { HID_ASCII_TO_KEYCODE }; + * + * uint8_t keycode[6] = { 0 }; + * uint8_t modifier = 0; + * + * if ( conv_table[chr][0] ) modifier = KEYBOARD_MODIFIER_LEFTSHIFT; + * keycode[0] = conv_table[chr][1]; + * tud_hid_keyboard_report(report_id, modifier, keycode); + * + *--------------------------------------------------------------------*/ +#define HID_ASCII_TO_KEYCODE \ + {0, 0 }, /* 0x00 Null */ \ + {0, 0 }, /* 0x01 */ \ + {0, 0 }, /* 0x02 */ \ + {0, 0 }, /* 0x03 */ \ + {0, 0 }, /* 0x04 */ \ + {0, 0 }, /* 0x05 */ \ + {0, 0 }, /* 0x06 */ \ + {0, 0 }, /* 0x07 */ \ + {0, HID_KEY_BACKSPACE }, /* 0x08 Backspace */ \ + {0, HID_KEY_TAB }, /* 0x09 Tab */ \ + {0, HID_KEY_ENTER }, /* 0x0A Line Feed */ \ + {0, 0 }, /* 0x0B */ \ + {0, 0 }, /* 0x0C */ \ + {0, HID_KEY_ENTER }, /* 0x0D CR */ \ + {0, 0 }, /* 0x0E */ \ + {0, 0 }, /* 0x0F */ \ + {0, 0 }, /* 0x10 */ \ + {0, 0 }, /* 0x11 */ \ + {0, 0 }, /* 0x12 */ \ + {0, 0 }, /* 0x13 */ \ + {0, 0 }, /* 0x14 */ \ + {0, 0 }, /* 0x15 */ \ + {0, 0 }, /* 0x16 */ \ + {0, 0 }, /* 0x17 */ \ + {0, 0 }, /* 0x18 */ \ + {0, 0 }, /* 0x19 */ \ + {0, 0 }, /* 0x1A */ \ + {0, HID_KEY_ESCAPE }, /* 0x1B Escape */ \ + {0, 0 }, /* 0x1C */ \ + {0, 0 }, /* 0x1D */ \ + {0, 0 }, /* 0x1E */ \ + {0, 0 }, /* 0x1F */ \ + \ + {0, HID_KEY_SPACE }, /* 0x20 */ \ + {1, HID_KEY_1 }, /* 0x21 ! */ \ + {1, HID_KEY_APOSTROPHE }, /* 0x22 " */ \ + {1, HID_KEY_3 }, /* 0x23 # */ \ + {1, HID_KEY_4 }, /* 0x24 $ */ \ + {1, HID_KEY_5 }, /* 0x25 % */ \ + {1, HID_KEY_7 }, /* 0x26 & */ \ + {0, HID_KEY_APOSTROPHE }, /* 0x27 ' */ \ + {1, HID_KEY_9 }, /* 0x28 ( */ \ + {1, HID_KEY_0 }, /* 0x29 ) */ \ + {1, HID_KEY_8 }, /* 0x2A * */ \ + {1, HID_KEY_EQUAL }, /* 0x2B + */ \ + {0, HID_KEY_COMMA }, /* 0x2C , */ \ + {0, HID_KEY_MINUS }, /* 0x2D - */ \ + {0, HID_KEY_PERIOD }, /* 0x2E . */ \ + {0, HID_KEY_SLASH }, /* 0x2F / */ \ + {0, HID_KEY_0 }, /* 0x30 0 */ \ + {0, HID_KEY_1 }, /* 0x31 1 */ \ + {0, HID_KEY_2 }, /* 0x32 2 */ \ + {0, HID_KEY_3 }, /* 0x33 3 */ \ + {0, HID_KEY_4 }, /* 0x34 4 */ \ + {0, HID_KEY_5 }, /* 0x35 5 */ \ + {0, HID_KEY_6 }, /* 0x36 6 */ \ + {0, HID_KEY_7 }, /* 0x37 7 */ \ + {0, HID_KEY_8 }, /* 0x38 8 */ \ + {0, HID_KEY_9 }, /* 0x39 9 */ \ + {1, HID_KEY_SEMICOLON }, /* 0x3A : */ \ + {0, HID_KEY_SEMICOLON }, /* 0x3B ; */ \ + {1, HID_KEY_COMMA }, /* 0x3C < */ \ + {0, HID_KEY_EQUAL }, /* 0x3D = */ \ + {1, HID_KEY_PERIOD }, /* 0x3E > */ \ + {1, HID_KEY_SLASH }, /* 0x3F ? */ \ + \ + {1, HID_KEY_2 }, /* 0x40 @ */ \ + {1, HID_KEY_A }, /* 0x41 A */ \ + {1, HID_KEY_B }, /* 0x42 B */ \ + {1, HID_KEY_C }, /* 0x43 C */ \ + {1, HID_KEY_D }, /* 0x44 D */ \ + {1, HID_KEY_E }, /* 0x45 E */ \ + {1, HID_KEY_F }, /* 0x46 F */ \ + {1, HID_KEY_G }, /* 0x47 G */ \ + {1, HID_KEY_H }, /* 0x48 H */ \ + {1, HID_KEY_I }, /* 0x49 I */ \ + {1, HID_KEY_J }, /* 0x4A J */ \ + {1, HID_KEY_K }, /* 0x4B K */ \ + {1, HID_KEY_L }, /* 0x4C L */ \ + {1, HID_KEY_M }, /* 0x4D M */ \ + {1, HID_KEY_N }, /* 0x4E N */ \ + {1, HID_KEY_O }, /* 0x4F O */ \ + {1, HID_KEY_P }, /* 0x50 P */ \ + {1, HID_KEY_Q }, /* 0x51 Q */ \ + {1, HID_KEY_R }, /* 0x52 R */ \ + {1, HID_KEY_S }, /* 0x53 S */ \ + {1, HID_KEY_T }, /* 0x55 T */ \ + {1, HID_KEY_U }, /* 0x55 U */ \ + {1, HID_KEY_V }, /* 0x56 V */ \ + {1, HID_KEY_W }, /* 0x57 W */ \ + {1, HID_KEY_X }, /* 0x58 X */ \ + {1, HID_KEY_Y }, /* 0x59 Y */ \ + {1, HID_KEY_Z }, /* 0x5A Z */ \ + {0, HID_KEY_BRACKET_LEFT }, /* 0x5B [ */ \ + {0, HID_KEY_BACKSLASH }, /* 0x5C '\' */ \ + {0, HID_KEY_BRACKET_RIGHT }, /* 0x5D ] */ \ + {1, HID_KEY_6 }, /* 0x5E ^ */ \ + {1, HID_KEY_MINUS }, /* 0x5F _ */ \ + \ + {0, HID_KEY_GRAVE }, /* 0x60 ` */ \ + {0, HID_KEY_A }, /* 0x61 a */ \ + {0, HID_KEY_B }, /* 0x62 b */ \ + {0, HID_KEY_C }, /* 0x63 c */ \ + {0, HID_KEY_D }, /* 0x66 d */ \ + {0, HID_KEY_E }, /* 0x65 e */ \ + {0, HID_KEY_F }, /* 0x66 f */ \ + {0, HID_KEY_G }, /* 0x67 g */ \ + {0, HID_KEY_H }, /* 0x68 h */ \ + {0, HID_KEY_I }, /* 0x69 i */ \ + {0, HID_KEY_J }, /* 0x6A j */ \ + {0, HID_KEY_K }, /* 0x6B k */ \ + {0, HID_KEY_L }, /* 0x6C l */ \ + {0, HID_KEY_M }, /* 0x6D m */ \ + {0, HID_KEY_N }, /* 0x6E n */ \ + {0, HID_KEY_O }, /* 0x6F o */ \ + {0, HID_KEY_P }, /* 0x70 p */ \ + {0, HID_KEY_Q }, /* 0x71 q */ \ + {0, HID_KEY_R }, /* 0x72 r */ \ + {0, HID_KEY_S }, /* 0x73 s */ \ + {0, HID_KEY_T }, /* 0x75 t */ \ + {0, HID_KEY_U }, /* 0x75 u */ \ + {0, HID_KEY_V }, /* 0x76 v */ \ + {0, HID_KEY_W }, /* 0x77 w */ \ + {0, HID_KEY_X }, /* 0x78 x */ \ + {0, HID_KEY_Y }, /* 0x79 y */ \ + {0, HID_KEY_Z }, /* 0x7A z */ \ + {1, HID_KEY_BRACKET_LEFT }, /* 0x7B { */ \ + {1, HID_KEY_BACKSLASH }, /* 0x7C | */ \ + {1, HID_KEY_BRACKET_RIGHT }, /* 0x7D } */ \ + {1, HID_KEY_GRAVE }, /* 0x7E ~ */ \ + {0, HID_KEY_DELETE } /* 0x7F Delete */ \ + +/*-------------------------------------------------------------------- + * KEYCODE to Ascii Conversion + * Expand to array of [128][2] (ascii without shift, ascii with shift) + * + * Usage: example to convert ascii from keycode (key) and shift modifier (shift). + * Here we assume key < 128 ( printable ) + * + * uint8_t const conv_table[128][2] = { HID_KEYCODE_TO_ASCII }; + * char ch = shift ? conv_table[chr][1] : conv_table[chr][0]; + * + *--------------------------------------------------------------------*/ +#define HID_KEYCODE_TO_ASCII \ + {0 , 0 }, /* 0x00 */ \ + {0 , 0 }, /* 0x01 */ \ + {0 , 0 }, /* 0x02 */ \ + {0 , 0 }, /* 0x03 */ \ + {'a' , 'A' }, /* 0x04 */ \ + {'b' , 'B' }, /* 0x05 */ \ + {'c' , 'C' }, /* 0x06 */ \ + {'d' , 'D' }, /* 0x07 */ \ + {'e' , 'E' }, /* 0x08 */ \ + {'f' , 'F' }, /* 0x09 */ \ + {'g' , 'G' }, /* 0x0a */ \ + {'h' , 'H' }, /* 0x0b */ \ + {'i' , 'I' }, /* 0x0c */ \ + {'j' , 'J' }, /* 0x0d */ \ + {'k' , 'K' }, /* 0x0e */ \ + {'l' , 'L' }, /* 0x0f */ \ + {'m' , 'M' }, /* 0x10 */ \ + {'n' , 'N' }, /* 0x11 */ \ + {'o' , 'O' }, /* 0x12 */ \ + {'p' , 'P' }, /* 0x13 */ \ + {'q' , 'Q' }, /* 0x14 */ \ + {'r' , 'R' }, /* 0x15 */ \ + {'s' , 'S' }, /* 0x16 */ \ + {'t' , 'T' }, /* 0x17 */ \ + {'u' , 'U' }, /* 0x18 */ \ + {'v' , 'V' }, /* 0x19 */ \ + {'w' , 'W' }, /* 0x1a */ \ + {'x' , 'X' }, /* 0x1b */ \ + {'y' , 'Y' }, /* 0x1c */ \ + {'z' , 'Z' }, /* 0x1d */ \ + {'1' , '!' }, /* 0x1e */ \ + {'2' , '@' }, /* 0x1f */ \ + {'3' , '#' }, /* 0x20 */ \ + {'4' , '$' }, /* 0x21 */ \ + {'5' , '%' }, /* 0x22 */ \ + {'6' , '^' }, /* 0x23 */ \ + {'7' , '&' }, /* 0x24 */ \ + {'8' , '*' }, /* 0x25 */ \ + {'9' , '(' }, /* 0x26 */ \ + {'0' , ')' }, /* 0x27 */ \ + {'\r' , '\r' }, /* 0x28 */ \ + {'\x1b', '\x1b' }, /* 0x29 */ \ + {'\b' , '\b' }, /* 0x2a */ \ + {'\t' , '\t' }, /* 0x2b */ \ + {' ' , ' ' }, /* 0x2c */ \ + {'-' , '_' }, /* 0x2d */ \ + {'=' , '+' }, /* 0x2e */ \ + {'[' , '{' }, /* 0x2f */ \ + {']' , '}' }, /* 0x30 */ \ + {'\\' , '|' }, /* 0x31 */ \ + {'#' , '~' }, /* 0x32 */ \ + {';' , ':' }, /* 0x33 */ \ + {'\'' , '\"' }, /* 0x34 */ \ + {'`' , '~' }, /* 0x35 */ \ + {',' , '<' }, /* 0x36 */ \ + {'.' , '>' }, /* 0x37 */ \ + {'/' , '?' }, /* 0x38 */ \ + \ + {0 , 0 }, /* 0x39 */ \ + {0 , 0 }, /* 0x3a */ \ + {0 , 0 }, /* 0x3b */ \ + {0 , 0 }, /* 0x3c */ \ + {0 , 0 }, /* 0x3d */ \ + {0 , 0 }, /* 0x3e */ \ + {0 , 0 }, /* 0x3f */ \ + {0 , 0 }, /* 0x40 */ \ + {0 , 0 }, /* 0x41 */ \ + {0 , 0 }, /* 0x42 */ \ + {0 , 0 }, /* 0x43 */ \ + {0 , 0 }, /* 0x44 */ \ + {0 , 0 }, /* 0x45 */ \ + {0 , 0 }, /* 0x46 */ \ + {0 , 0 }, /* 0x47 */ \ + {0 , 0 }, /* 0x48 */ \ + {0 , 0 }, /* 0x49 */ \ + {0 , 0 }, /* 0x4a */ \ + {0 , 0 }, /* 0x4b */ \ + {0 , 0 }, /* 0x4c */ \ + {0 , 0 }, /* 0x4d */ \ + {0 , 0 }, /* 0x4e */ \ + {0 , 0 }, /* 0x4f */ \ + {0 , 0 }, /* 0x50 */ \ + {0 , 0 }, /* 0x51 */ \ + {0 , 0 }, /* 0x52 */ \ + {0 , 0 }, /* 0x53 */ \ + \ + {'/' , '/' }, /* 0x54 */ \ + {'*' , '*' }, /* 0x55 */ \ + {'-' , '-' }, /* 0x56 */ \ + {'+' , '+' }, /* 0x57 */ \ + {'\r' , '\r' }, /* 0x58 */ \ + {'1' , 0 }, /* 0x59 */ \ + {'2' , 0 }, /* 0x5a */ \ + {'3' , 0 }, /* 0x5b */ \ + {'4' , 0 }, /* 0x5c */ \ + {'5' , '5' }, /* 0x5d */ \ + {'6' , 0 }, /* 0x5e */ \ + {'7' , 0 }, /* 0x5f */ \ + {'8' , 0 }, /* 0x60 */ \ + {'9' , 0 }, /* 0x61 */ \ + {'0' , 0 }, /* 0x62 */ \ + {'.' , 0 }, /* 0x63 */ \ + {0 , 0 }, /* 0x64 */ \ + {0 , 0 }, /* 0x65 */ \ + {0 , 0 }, /* 0x66 */ \ + {'=' , '=' }, /* 0x67 */ \ + + +#ifdef __cplusplus + } +#endif + +#endif /* TUSB_HID_H__ */ + +/// @} diff --git a/libraries/Adafruit_TinyUSB_Arduino/src/class/hid/hid_device.c b/libraries/Adafruit_TinyUSB_Arduino/src/class/hid/hid_device.c new file mode 100644 index 0000000..6ee4cd9 --- /dev/null +++ b/libraries/Adafruit_TinyUSB_Arduino/src/class/hid/hid_device.c @@ -0,0 +1,422 @@ +/* + * 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. + * + * This file is part of the TinyUSB stack. + */ + +#include "tusb_option.h" + +#if (CFG_TUD_ENABLED && CFG_TUD_HID) + +//--------------------------------------------------------------------+ +// INCLUDE +//--------------------------------------------------------------------+ +#include "device/usbd.h" +#include "device/usbd_pvt.h" + +#include "hid_device.h" + +//--------------------------------------------------------------------+ +// MACRO CONSTANT TYPEDEF +//--------------------------------------------------------------------+ +typedef struct { + uint8_t itf_num; + uint8_t ep_in; + uint8_t ep_out; // optional Out endpoint + uint8_t itf_protocol; // Boot mouse or keyboard + + uint16_t report_desc_len; + uint8_t protocol_mode; // Boot (0) or Report protocol (1) + uint8_t idle_rate; // up to application to handle idle rate + + // TODO save hid descriptor since host can specifically request this after enumeration + // Note: HID descriptor may be not available from application after enumeration + const tusb_hid_descriptor_hid_t*hid_descriptor; +} hidd_interface_t; + +typedef struct { + TUD_EPBUF_DEF(ctrl , CFG_TUD_HID_EP_BUFSIZE); + TUD_EPBUF_DEF(epin , CFG_TUD_HID_EP_BUFSIZE); + TUD_EPBUF_DEF(epout, CFG_TUD_HID_EP_BUFSIZE); +} hidd_epbuf_t; + +static hidd_interface_t _hidd_itf[CFG_TUD_HID]; +CFG_TUD_MEM_SECTION static hidd_epbuf_t _hidd_epbuf[CFG_TUD_HID]; + +/*------------- Helpers -------------*/ +TU_ATTR_ALWAYS_INLINE static inline uint8_t get_index_by_itfnum(uint8_t itf_num) { + for (uint8_t i = 0; i < CFG_TUD_HID; i++) { + if (itf_num == _hidd_itf[i].itf_num) { + return i; + } + } + return 0xFF; +} + +//--------------------------------------------------------------------+ +// Weak stubs: invoked if no strong implementation is available +//--------------------------------------------------------------------+ +TU_ATTR_WEAK void tud_hid_set_protocol_cb(uint8_t instance, uint8_t protocol) { + (void) instance; + (void) protocol; +} + +TU_ATTR_WEAK bool tud_hid_set_idle_cb(uint8_t instance, uint8_t idle_rate) { + (void) instance; + (void) idle_rate; + return true; +} + +TU_ATTR_WEAK void tud_hid_report_complete_cb(uint8_t instance, uint8_t const* report, uint16_t len) { + (void) instance; + (void) report; + (void) len; +} + +// Invoked when a transfer wasn't successful +TU_ATTR_WEAK void tud_hid_report_failed_cb(uint8_t instance, hid_report_type_t report_type, uint8_t const* report, uint16_t xferred_bytes) { + (void) instance; + (void) report_type; + (void) report; + (void) xferred_bytes; +} + +//--------------------------------------------------------------------+ +// APPLICATION API +//--------------------------------------------------------------------+ +bool tud_hid_n_ready(uint8_t instance) { + uint8_t const rhport = 0; + uint8_t const ep_in = _hidd_itf[instance].ep_in; + return tud_ready() && (ep_in != 0) && !usbd_edpt_busy(rhport, ep_in); +} + +bool tud_hid_n_report(uint8_t instance, uint8_t report_id, void const *report, uint16_t len) { + TU_VERIFY(instance < CFG_TUD_HID); + const uint8_t rhport = 0; + hidd_interface_t *p_hid = &_hidd_itf[instance]; + hidd_epbuf_t *p_epbuf = &_hidd_epbuf[instance]; + + // claim endpoint + TU_VERIFY(usbd_edpt_claim(rhport, p_hid->ep_in)); + + // prepare data + if (report_id) { + p_epbuf->epin[0] = report_id; + TU_VERIFY(0 == tu_memcpy_s(p_epbuf->epin + 1, CFG_TUD_HID_EP_BUFSIZE - 1, report, len)); + len++; + } else { + TU_VERIFY(0 == tu_memcpy_s(p_epbuf->epin, CFG_TUD_HID_EP_BUFSIZE, report, len)); + } + + return usbd_edpt_xfer(rhport, p_hid->ep_in, p_epbuf->epin, len, false); +} + +uint8_t tud_hid_n_interface_protocol(uint8_t instance) { + return _hidd_itf[instance].itf_protocol; +} + +uint8_t tud_hid_n_get_protocol(uint8_t instance) { + return _hidd_itf[instance].protocol_mode; +} + +bool tud_hid_n_keyboard_report(uint8_t instance, uint8_t report_id, uint8_t modifier, const uint8_t keycode[6]) { + hid_keyboard_report_t report; + report.modifier = modifier; + report.reserved = 0; + + if (keycode) { + memcpy(report.keycode, keycode, sizeof(report.keycode)); + } else { + tu_memclr(report.keycode, 6); + } + + return tud_hid_n_report(instance, report_id, &report, sizeof(report)); +} + +bool tud_hid_n_mouse_report(uint8_t instance, uint8_t report_id, + uint8_t buttons, int8_t x, int8_t y, int8_t vertical, int8_t horizontal) { + hid_mouse_report_t report = { + .buttons = buttons, + .x = x, + .y = y, + .wheel = vertical, + .pan = horizontal + }; + + return tud_hid_n_report(instance, report_id, &report, sizeof(report)); +} + +bool tud_hid_n_abs_mouse_report(uint8_t instance, uint8_t report_id, + uint8_t buttons, int16_t x, int16_t y, int8_t vertical, int8_t horizontal) { + hid_abs_mouse_report_t report = { + .buttons = buttons, + .x = x, + .y = y, + .wheel = vertical, + .pan = horizontal + }; + return tud_hid_n_report(instance, report_id, &report, sizeof(report)); +} + +bool tud_hid_n_gamepad_report(uint8_t instance, uint8_t report_id, + int8_t x, int8_t y, int8_t z, int8_t rz, int8_t rx, int8_t ry, uint8_t hat, uint32_t buttons) { + hid_gamepad_report_t report = { + .x = x, + .y = y, + .z = z, + .rz = rz, + .rx = rx, + .ry = ry, + .hat = hat, + .buttons = buttons, + }; + + return tud_hid_n_report(instance, report_id, &report, sizeof(report)); +} + +bool tud_hid_n_stylus_report(uint8_t instance, uint8_t report_id, uint8_t attrs, uint16_t x, uint16_t y) { + hid_stylus_report_t report = { + .attr = attrs, + .x = x, + .y = y, + }; + + return tud_hid_n_report(instance, report_id, &report, sizeof(report)); +} + +//--------------------------------------------------------------------+ +// USBD-CLASS API +//--------------------------------------------------------------------+ +void hidd_init(void) { + hidd_reset(0); +} + +bool hidd_deinit(void) { + return true; +} + +void hidd_reset(uint8_t rhport) { + (void)rhport; + tu_memclr(_hidd_itf, sizeof(_hidd_itf)); +} + +uint16_t hidd_open(uint8_t rhport, tusb_desc_interface_t const *desc_itf, uint16_t max_len) { + TU_VERIFY(TUSB_CLASS_HID == desc_itf->bInterfaceClass, 0); + + // len = interface + hid + n*endpoints + uint16_t const drv_len = (uint16_t) (sizeof(tusb_desc_interface_t) + sizeof(tusb_hid_descriptor_hid_t) + + desc_itf->bNumEndpoints * sizeof(tusb_desc_endpoint_t)); + TU_ASSERT(max_len >= drv_len, 0); + + // Find available interface + hidd_interface_t *p_hid; + uint8_t hid_id; + for (hid_id = 0; hid_id < CFG_TUD_HID; hid_id++) { + p_hid = &_hidd_itf[hid_id]; + if (p_hid->ep_in == 0) { + break; + } + } + TU_ASSERT(hid_id < CFG_TUD_HID, 0); + hidd_epbuf_t *p_epbuf = &_hidd_epbuf[hid_id]; + + uint8_t const *p_desc = (uint8_t const *)desc_itf; + + //------------- HID descriptor -------------// + p_desc = tu_desc_next(p_desc); + TU_ASSERT(HID_DESC_TYPE_HID == tu_desc_type(p_desc), 0); + p_hid->hid_descriptor = (tusb_hid_descriptor_hid_t const *)p_desc; + + //------------- Endpoint Descriptor -------------// + p_desc = tu_desc_next(p_desc); + TU_ASSERT(usbd_open_edpt_pair(rhport, p_desc, desc_itf->bNumEndpoints, TUSB_XFER_INTERRUPT, &p_hid->ep_out, &p_hid->ep_in), 0); + + if (desc_itf->bInterfaceSubClass == HID_SUBCLASS_BOOT) { + p_hid->itf_protocol = desc_itf->bInterfaceProtocol; + } + + p_hid->protocol_mode = HID_PROTOCOL_REPORT; // Per Specs: default is report mode + p_hid->itf_num = desc_itf->bInterfaceNumber; + + // Use offsetof to avoid pointer to the odd/misaligned address + p_hid->report_desc_len = tu_unaligned_read16((uint8_t const *)p_hid->hid_descriptor + offsetof(tusb_hid_descriptor_hid_t, wReportLength)); + + // Prepare for output endpoint + if (p_hid->ep_out) { + TU_ASSERT(usbd_edpt_xfer(rhport, p_hid->ep_out, p_epbuf->epout, CFG_TUD_HID_EP_BUFSIZE, false), drv_len); + } + + return drv_len; +} + +// Invoked when a control transfer occurred on an interface of this class +// Driver response accordingly to the request and the transfer stage (setup/data/ack) +// return false to stall control endpoint (e.g unsupported request) +bool hidd_control_xfer_cb(uint8_t rhport, uint8_t stage, tusb_control_request_t const *request) { + TU_VERIFY(request->bmRequestType_bit.recipient == TUSB_REQ_RCPT_INTERFACE); + + uint8_t const hid_itf = get_index_by_itfnum((uint8_t)request->wIndex); + TU_VERIFY(hid_itf < CFG_TUD_HID); + hidd_interface_t *p_hid = &_hidd_itf[hid_itf]; + hidd_epbuf_t *p_epbuf = &_hidd_epbuf[hid_itf]; + + if (request->bmRequestType_bit.type == TUSB_REQ_TYPE_STANDARD) { + //------------- STD Request -------------// + if (stage == CONTROL_STAGE_SETUP) { + uint8_t const desc_type = tu_u16_high(request->wValue); + // uint8_t const desc_index = tu_u16_low (request->wValue); + + if (request->bRequest == TUSB_REQ_GET_DESCRIPTOR && desc_type == HID_DESC_TYPE_HID) { + TU_VERIFY(p_hid->hid_descriptor); + TU_VERIFY(tud_control_xfer(rhport, request, (void *)(uintptr_t)p_hid->hid_descriptor, p_hid->hid_descriptor->bLength)); + } else if (request->bRequest == TUSB_REQ_GET_DESCRIPTOR && desc_type == HID_DESC_TYPE_REPORT) { + uint8_t const *desc_report = tud_hid_descriptor_report_cb(hid_itf); + tud_control_xfer(rhport, request, (void *)(uintptr_t)desc_report, p_hid->report_desc_len); + } else { + return false; // stall unsupported request + } + } + } else if (request->bmRequestType_bit.type == TUSB_REQ_TYPE_CLASS) { + //------------- Class Specific Request -------------// + switch (request->bRequest) { + case HID_REQ_CONTROL_GET_REPORT: + if (stage == CONTROL_STAGE_SETUP) { + uint8_t const report_type = tu_u16_high(request->wValue); + uint8_t const report_id = tu_u16_low(request->wValue); + + uint8_t* report_buf = p_epbuf->ctrl; + uint16_t req_len = tu_min16(request->wLength, CFG_TUD_HID_EP_BUFSIZE); + uint16_t xferlen = 0; + + // If host request a specific Report ID, add ID to as 1 byte of response + if ((report_id != HID_REPORT_TYPE_INVALID) && (req_len > 1)) { + *report_buf++ = report_id; + req_len--; + xferlen++; + } + + xferlen += tud_hid_get_report_cb(hid_itf, report_id, (hid_report_type_t) report_type, report_buf, req_len); + TU_ASSERT(xferlen > 0); + + tud_control_xfer(rhport, request, p_epbuf->ctrl, xferlen); + } + break; + + case HID_REQ_CONTROL_SET_REPORT: + if (stage == CONTROL_STAGE_SETUP) { + TU_VERIFY(request->wLength <= CFG_TUD_HID_EP_BUFSIZE); + tud_control_xfer(rhport, request, p_epbuf->ctrl, request->wLength); + } else if (stage == CONTROL_STAGE_ACK) { + uint8_t const report_type = tu_u16_high(request->wValue); + uint8_t const report_id = tu_u16_low(request->wValue); + + uint8_t const* report_buf = p_epbuf->ctrl; + uint16_t report_len = tu_min16(request->wLength, CFG_TUD_HID_EP_BUFSIZE); + + // If host request a specific Report ID, extract report ID in buffer before invoking callback + if ((report_id != HID_REPORT_TYPE_INVALID) && (report_len > 1) && (report_id == report_buf[0])) { + report_buf++; + report_len--; + } + + tud_hid_set_report_cb(hid_itf, report_id, (hid_report_type_t) report_type, report_buf, report_len); + } + break; + + case HID_REQ_CONTROL_SET_IDLE: + if (stage == CONTROL_STAGE_SETUP) { + p_hid->idle_rate = tu_u16_high(request->wValue); + TU_VERIFY(tud_hid_set_idle_cb(hid_itf, p_hid->idle_rate)); // stall if false + tud_control_status(rhport, request); + } + break; + + case HID_REQ_CONTROL_GET_IDLE: + if (stage == CONTROL_STAGE_SETUP) { + // TODO idle rate of report + tud_control_xfer(rhport, request, &p_hid->idle_rate, 1); + } + break; + + case HID_REQ_CONTROL_GET_PROTOCOL: + if (stage == CONTROL_STAGE_SETUP) { + tud_control_xfer(rhport, request, &p_hid->protocol_mode, 1); + } + break; + + case HID_REQ_CONTROL_SET_PROTOCOL: + if (stage == CONTROL_STAGE_SETUP) { + tud_control_status(rhport, request); + } else if (stage == CONTROL_STAGE_ACK) { + p_hid->protocol_mode = (uint8_t) request->wValue; + tud_hid_set_protocol_cb(hid_itf, p_hid->protocol_mode); + } + break; + + default: + return false; // stall unsupported request + } + } else { + return false; // stall unsupported request + } + + return true; +} + +bool hidd_xfer_cb(uint8_t rhport, uint8_t ep_addr, xfer_result_t result, uint32_t xferred_bytes) { + uint8_t instance; + hidd_interface_t *p_hid; + + // Identify which interface to use + for (instance = 0; instance < CFG_TUD_HID; instance++) { + p_hid = &_hidd_itf[instance]; + if ((ep_addr == p_hid->ep_out) || (ep_addr == p_hid->ep_in)) { + break; + } + } + TU_ASSERT(instance < CFG_TUD_HID); + hidd_epbuf_t *p_epbuf = &_hidd_epbuf[instance]; + + if (ep_addr == p_hid->ep_in) { + // Input report + if (XFER_RESULT_SUCCESS == result) { + tud_hid_report_complete_cb(instance, p_epbuf->epin, (uint16_t) xferred_bytes); + } else { + tud_hid_report_failed_cb(instance, HID_REPORT_TYPE_INPUT, p_epbuf->epin, (uint16_t) xferred_bytes); + } + } else { + // Output report + if (XFER_RESULT_SUCCESS == result) { + tud_hid_set_report_cb(instance, 0, HID_REPORT_TYPE_OUTPUT, p_epbuf->epout, (uint16_t)xferred_bytes); + } else { + tud_hid_report_failed_cb(instance, HID_REPORT_TYPE_OUTPUT, p_epbuf->epout, (uint16_t) xferred_bytes); + } + + // prepare for new transfer + TU_ASSERT(usbd_edpt_xfer(rhport, p_hid->ep_out, p_epbuf->epout, CFG_TUD_HID_EP_BUFSIZE, false)); + } + + return true; +} + +#endif diff --git a/libraries/Adafruit_TinyUSB_Arduino/src/class/hid/hid_device.h b/libraries/Adafruit_TinyUSB_Arduino/src/class/hid/hid_device.h new file mode 100644 index 0000000..32b5729 --- /dev/null +++ b/libraries/Adafruit_TinyUSB_Arduino/src/class/hid/hid_device.h @@ -0,0 +1,673 @@ +/* + * 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. + * + * This file is part of the TinyUSB stack. + */ + +#ifndef TUSB_HID_DEVICE_H_ +#define TUSB_HID_DEVICE_H_ + +#include "hid.h" + +#ifdef __cplusplus + extern "C" { +#endif + +//--------------------------------------------------------------------+ +// Class Driver Default Configure & Validation +//--------------------------------------------------------------------+ + +#if !defined(CFG_TUD_HID_EP_BUFSIZE) & defined(CFG_TUD_HID_BUFSIZE) + // TODO warn user to use new name later on + // #warning CFG_TUD_HID_BUFSIZE is renamed to CFG_TUD_HID_EP_BUFSIZE, please update to use the new name + #define CFG_TUD_HID_EP_BUFSIZE CFG_TUD_HID_BUFSIZE +#endif + +#ifndef CFG_TUD_HID_EP_BUFSIZE + #define CFG_TUD_HID_EP_BUFSIZE 64 +#endif + +//--------------------------------------------------------------------+ +// Application API (Multiple Instances) i.e. CFG_TUD_HID > 1 +//--------------------------------------------------------------------+ + +// Check if the interface is ready to use +bool tud_hid_n_ready(uint8_t instance); + +// Get interface supported protocol (bInterfaceProtocol) check out hid_interface_protocol_enum_t for possible values +uint8_t tud_hid_n_interface_protocol(uint8_t instance); + +// Get current active protocol: HID_PROTOCOL_BOOT (0) or HID_PROTOCOL_REPORT (1) +uint8_t tud_hid_n_get_protocol(uint8_t instance); + +// Send report to host +bool tud_hid_n_report(uint8_t instance, uint8_t report_id, void const* report, uint16_t len); + +// KEYBOARD: convenient helper to send keyboard report if application +// use template layout report as defined by hid_keyboard_report_t +bool tud_hid_n_keyboard_report(uint8_t instance, uint8_t report_id, uint8_t modifier, const uint8_t keycode[6]); + +// MOUSE: convenient helper to send mouse report if application +// use template layout report as defined by hid_mouse_report_t +bool tud_hid_n_mouse_report(uint8_t instance, uint8_t report_id, uint8_t buttons, int8_t x, int8_t y, int8_t vertical, int8_t horizontal); + +// ABSOLUTE MOUSE: convenient helper to send absolute mouse report if application +// use template layout report as defined by hid_abs_mouse_report_t +bool tud_hid_n_abs_mouse_report(uint8_t instance, uint8_t report_id, uint8_t buttons, int16_t x, int16_t y, int8_t vertical, int8_t horizontal); + +// Gamepad: convenient helper to send gamepad report if application +// use template layout report TUD_HID_REPORT_DESC_GAMEPAD +bool tud_hid_n_gamepad_report(uint8_t instance, uint8_t report_id, int8_t x, int8_t y, int8_t z, int8_t rz, int8_t rx, int8_t ry, uint8_t hat, uint32_t buttons); + +// STYLUS PEN: convenient helper to send absolute stylus pen report if application +bool tud_hid_n_stylus_report(uint8_t instance, uint8_t report_id, uint8_t attrs, uint16_t x, uint16_t y); + +//--------------------------------------------------------------------+ +// Application API (Single Port) +//--------------------------------------------------------------------+ +TU_ATTR_ALWAYS_INLINE static inline bool tud_hid_ready(void) { + return tud_hid_n_ready(0); +} + +TU_ATTR_ALWAYS_INLINE static inline uint8_t tud_hid_interface_protocol(void) { + return tud_hid_n_interface_protocol(0); +} + +TU_ATTR_ALWAYS_INLINE static inline uint8_t tud_hid_get_protocol(void) { + return tud_hid_n_get_protocol(0); +} + +TU_ATTR_ALWAYS_INLINE static inline bool tud_hid_report(uint8_t report_id, void const* report, uint16_t len) { + return tud_hid_n_report(0, report_id, report, len); +} + +TU_ATTR_ALWAYS_INLINE static inline bool tud_hid_keyboard_report(uint8_t report_id, uint8_t modifier, const uint8_t keycode[6]) { + return tud_hid_n_keyboard_report(0, report_id, modifier, keycode); +} + +TU_ATTR_ALWAYS_INLINE static inline bool tud_hid_mouse_report(uint8_t report_id, uint8_t buttons, int8_t x, int8_t y, int8_t vertical, int8_t horizontal) { + return tud_hid_n_mouse_report(0, report_id, buttons, x, y, vertical, horizontal); +} + +TU_ATTR_ALWAYS_INLINE static inline bool tud_hid_abs_mouse_report(uint8_t report_id, uint8_t buttons, int16_t x, int16_t y, int8_t vertical, int8_t horizontal) { + return tud_hid_n_abs_mouse_report(0, report_id, buttons, x, y, vertical, horizontal); +} + +TU_ATTR_ALWAYS_INLINE static inline bool tud_hid_gamepad_report(uint8_t report_id, int8_t x, int8_t y, int8_t z, int8_t rz, int8_t rx, int8_t ry, uint8_t hat, uint32_t buttons) { + return tud_hid_n_gamepad_report(0, report_id, x, y, z, rz, rx, ry, hat, buttons); +} + +TU_ATTR_ALWAYS_INLINE static inline bool tud_hid_stylus_report(uint8_t report_id, uint8_t attrs, uint16_t x, uint16_t y) { + return tud_hid_n_stylus_report(0, report_id, attrs, x, y); +} + +//--------------------------------------------------------------------+ +// Application Callbacks +//--------------------------------------------------------------------+ + +// Invoked when received GET HID REPORT DESCRIPTOR request +// Application return pointer to descriptor, whose contents must exist long enough for transfer to complete +uint8_t const * tud_hid_descriptor_report_cb(uint8_t instance); + +// Invoked when received GET_REPORT control request +// Application must fill buffer report's content and return its length. +// Return zero will cause the stack to STALL request +uint16_t tud_hid_get_report_cb(uint8_t instance, uint8_t report_id, hid_report_type_t report_type, uint8_t* buffer, uint16_t reqlen); + +// Invoked when received SET_REPORT control request or +// received data on OUT endpoint (Report ID = 0, Type = OUTPUT) +void tud_hid_set_report_cb(uint8_t instance, uint8_t report_id, hid_report_type_t report_type, uint8_t const* buffer, uint16_t bufsize); + +// Invoked when received SET_PROTOCOL request +// protocol is either HID_PROTOCOL_BOOT (0) or HID_PROTOCOL_REPORT (1) +void tud_hid_set_protocol_cb(uint8_t instance, uint8_t protocol); + +// Invoked when received SET_IDLE request. return false will stall the request +// - Idle Rate = 0 : only send report if there is changes, i.e. skip duplication +// - Idle Rate > 0 : skip duplication, but send at least 1 report every idle rate (in unit of 4 ms). +bool tud_hid_set_idle_cb(uint8_t instance, uint8_t idle_rate); + +// Invoked when sent REPORT successfully to host +// Application can use this to send the next report +// Note: For composite reports, report[0] is report ID +void tud_hid_report_complete_cb(uint8_t instance, uint8_t const* report, uint16_t len); + +// Invoked when a transfer wasn't successful +void tud_hid_report_failed_cb(uint8_t instance, hid_report_type_t report_type, uint8_t const* report, uint16_t xferred_bytes); + +/* --------------------------------------------------------------------+ + * HID Report Descriptor Template + * + * Convenient for declaring popular HID device (keyboard, mouse, consumer, + * gamepad etc...). Templates take "HID_REPORT_ID(n)" as input, leave + * empty if multiple reports is not used + * + * - Only 1 report: no parameter + * uint8_t const report_desc[] = { TUD_HID_REPORT_DESC_KEYBOARD() }; + * + * - Multiple Reports: "HID_REPORT_ID(ID)" must be passed to template + * uint8_t const report_desc[] = + * { + * TUD_HID_REPORT_DESC_KEYBOARD( HID_REPORT_ID(1) ) , + * TUD_HID_REPORT_DESC_MOUSE ( HID_REPORT_ID(2) ) + * }; + *--------------------------------------------------------------------*/ + +// Keyboard Report Descriptor Template +#define TUD_HID_REPORT_DESC_KEYBOARD(...) \ + HID_USAGE_PAGE ( HID_USAGE_PAGE_DESKTOP ) ,\ + HID_USAGE ( HID_USAGE_DESKTOP_KEYBOARD ) ,\ + HID_COLLECTION ( HID_COLLECTION_APPLICATION ) ,\ + /* Report ID if any */\ + __VA_ARGS__ \ + /* 8 bits Modifier Keys (Shift, Control, Alt) */ \ + HID_USAGE_PAGE ( HID_USAGE_PAGE_KEYBOARD ) ,\ + HID_USAGE_MIN ( 224 ) ,\ + HID_USAGE_MAX ( 231 ) ,\ + HID_LOGICAL_MIN ( 0 ) ,\ + HID_LOGICAL_MAX ( 1 ) ,\ + HID_REPORT_COUNT ( 8 ) ,\ + HID_REPORT_SIZE ( 1 ) ,\ + HID_INPUT ( HID_DATA | HID_VARIABLE | HID_ABSOLUTE ) ,\ + /* 8 bit reserved */ \ + HID_REPORT_COUNT ( 1 ) ,\ + HID_REPORT_SIZE ( 8 ) ,\ + HID_INPUT ( HID_CONSTANT ) ,\ + /* Output 5-bit LED Indicator Kana | Compose | ScrollLock | CapsLock | NumLock */ \ + HID_USAGE_PAGE ( HID_USAGE_PAGE_LED ) ,\ + HID_USAGE_MIN ( 1 ) ,\ + HID_USAGE_MAX ( 5 ) ,\ + HID_REPORT_COUNT ( 5 ) ,\ + HID_REPORT_SIZE ( 1 ) ,\ + HID_OUTPUT ( HID_DATA | HID_VARIABLE | HID_ABSOLUTE ) ,\ + /* led padding */ \ + HID_REPORT_COUNT ( 1 ) ,\ + HID_REPORT_SIZE ( 3 ) ,\ + HID_OUTPUT ( HID_CONSTANT ) ,\ + /* 6-byte Keycodes */ \ + HID_USAGE_PAGE ( HID_USAGE_PAGE_KEYBOARD ) ,\ + HID_USAGE_MIN ( 0 ) ,\ + HID_USAGE_MAX_N ( 255, 2 ) ,\ + HID_LOGICAL_MIN ( 0 ) ,\ + HID_LOGICAL_MAX_N( 255, 2 ) ,\ + HID_REPORT_COUNT ( 6 ) ,\ + HID_REPORT_SIZE ( 8 ) ,\ + HID_INPUT ( HID_DATA | HID_ARRAY | HID_ABSOLUTE ) ,\ + HID_COLLECTION_END \ + +// Mouse Report Descriptor Template +#define TUD_HID_REPORT_DESC_MOUSE(...) \ + HID_USAGE_PAGE ( HID_USAGE_PAGE_DESKTOP ) ,\ + HID_USAGE ( HID_USAGE_DESKTOP_MOUSE ) ,\ + HID_COLLECTION ( HID_COLLECTION_APPLICATION ) ,\ + /* Report ID if any */\ + __VA_ARGS__ \ + HID_USAGE ( HID_USAGE_DESKTOP_POINTER ) ,\ + HID_COLLECTION ( HID_COLLECTION_PHYSICAL ) ,\ + HID_USAGE_PAGE ( HID_USAGE_PAGE_BUTTON ) ,\ + HID_USAGE_MIN ( 1 ) ,\ + HID_USAGE_MAX ( 5 ) ,\ + HID_LOGICAL_MIN ( 0 ) ,\ + HID_LOGICAL_MAX ( 1 ) ,\ + /* Left, Right, Middle, Backward, Forward buttons */ \ + HID_REPORT_COUNT( 5 ) ,\ + HID_REPORT_SIZE ( 1 ) ,\ + HID_INPUT ( HID_DATA | HID_VARIABLE | HID_ABSOLUTE ) ,\ + /* 3 bit padding */ \ + HID_REPORT_COUNT( 1 ) ,\ + HID_REPORT_SIZE ( 3 ) ,\ + HID_INPUT ( HID_CONSTANT ) ,\ + HID_USAGE_PAGE ( HID_USAGE_PAGE_DESKTOP ) ,\ + /* X, Y position [-127, 127] */ \ + HID_USAGE ( HID_USAGE_DESKTOP_X ) ,\ + HID_USAGE ( HID_USAGE_DESKTOP_Y ) ,\ + HID_LOGICAL_MIN ( 0x81 ) ,\ + HID_LOGICAL_MAX ( 0x7f ) ,\ + HID_REPORT_COUNT( 2 ) ,\ + HID_REPORT_SIZE ( 8 ) ,\ + HID_INPUT ( HID_DATA | HID_VARIABLE | HID_RELATIVE ) ,\ + /* Verital wheel scroll [-127, 127] */ \ + HID_USAGE ( HID_USAGE_DESKTOP_WHEEL ) ,\ + HID_LOGICAL_MIN ( 0x81 ) ,\ + HID_LOGICAL_MAX ( 0x7f ) ,\ + HID_REPORT_COUNT( 1 ) ,\ + HID_REPORT_SIZE ( 8 ) ,\ + HID_INPUT ( HID_DATA | HID_VARIABLE | HID_RELATIVE ) ,\ + HID_USAGE_PAGE ( HID_USAGE_PAGE_CONSUMER ), \ + /* Horizontal wheel scroll [-127, 127] */ \ + HID_USAGE_N ( HID_USAGE_CONSUMER_AC_PAN, 2 ), \ + HID_LOGICAL_MIN ( 0x81 ), \ + HID_LOGICAL_MAX ( 0x7f ), \ + HID_REPORT_COUNT( 1 ), \ + HID_REPORT_SIZE ( 8 ), \ + HID_INPUT ( HID_DATA | HID_VARIABLE | HID_RELATIVE ), \ + HID_COLLECTION_END , \ + HID_COLLECTION_END \ + +// Stylus Pen Report Descriptor Template +#define TUD_HID_REPORT_DESC_STYLUS_PEN(...) \ + HID_USAGE_PAGE ( HID_USAGE_PAGE_DIGITIZER ) , \ + HID_USAGE ( HID_USAGE_DIGITIZER_PEN ) , \ + HID_COLLECTION ( HID_COLLECTION_APPLICATION ) , \ + /* Report ID if any */\ + __VA_ARGS__ \ + HID_USAGE ( HID_USAGE_DIGITIZER_STYLUS ), \ + HID_COLLECTION ( HID_COLLECTION_PHYSICAL ), \ + HID_USAGE ( HID_USAGE_DIGITIZER_TIP_SWITCH ), \ + HID_USAGE ( HID_USAGE_DIGITIZER_IN_RANGE ), \ + HID_LOGICAL_MIN ( 0 ), \ + HID_LOGICAL_MAX ( 1 ), \ + HID_REPORT_SIZE ( 1 ), \ + HID_REPORT_COUNT( 2 ), \ + HID_INPUT ( HID_DATA | HID_VARIABLE | HID_ABSOLUTE ), \ + HID_REPORT_SIZE ( 1 ), \ + HID_REPORT_COUNT( 6 ), \ + HID_INPUT ( HID_CONSTANT | HID_ARRAY | HID_ABSOLUTE), \ + HID_USAGE_PAGE ( HID_USAGE_PAGE_DESKTOP ), \ + HID_PHYSICAL_MAX_N( 0x7fff, 2 ), \ + HID_LOGICAL_MAX_N ( 0x7fff, 2 ), \ + HID_REPORT_SIZE ( 16 ), \ + HID_REPORT_COUNT( 1 ), \ + HID_UNIT_EXPONENT( 0x0f ), \ + HID_UNIT ( HID_VARIABLE | HID_NONLINEAR ), \ + HID_PHYSICAL_MIN( 0 ), \ + HID_PHYSICAL_MAX( 0 ), \ + HID_USAGE ( HID_USAGE_DESKTOP_X ), \ + HID_INPUT ( HID_DATA | HID_VARIABLE | HID_ABSOLUTE ), \ + HID_USAGE ( HID_USAGE_DESKTOP_Y ), \ + HID_INPUT ( HID_DATA | HID_VARIABLE | HID_ABSOLUTE ), \ + HID_COLLECTION_END , \ + HID_COLLECTION_END \ + +// Absolute Mouse Report Descriptor Template +#define TUD_HID_REPORT_DESC_ABSMOUSE(...) \ + HID_USAGE_PAGE ( HID_USAGE_PAGE_DESKTOP ) ,\ + HID_USAGE ( HID_USAGE_DESKTOP_MOUSE ) ,\ + HID_COLLECTION ( HID_COLLECTION_APPLICATION ) ,\ + /* Report ID if any */\ + __VA_ARGS__ \ + HID_USAGE ( HID_USAGE_DESKTOP_POINTER ) ,\ + HID_COLLECTION ( HID_COLLECTION_PHYSICAL ) ,\ + HID_USAGE_PAGE ( HID_USAGE_PAGE_BUTTON ) ,\ + HID_USAGE_MIN ( 1 ) ,\ + HID_USAGE_MAX ( 5 ) ,\ + HID_LOGICAL_MIN ( 0 ) ,\ + HID_LOGICAL_MAX ( 1 ) ,\ + /* Left, Right, Middle, Backward, Forward buttons */ \ + HID_REPORT_COUNT( 5 ) ,\ + HID_REPORT_SIZE ( 1 ) ,\ + HID_INPUT ( HID_DATA | HID_VARIABLE | HID_ABSOLUTE ) ,\ + /* 3 bit padding */ \ + HID_REPORT_COUNT( 1 ) ,\ + HID_REPORT_SIZE ( 3 ) ,\ + HID_INPUT ( HID_CONSTANT ) ,\ + HID_USAGE_PAGE ( HID_USAGE_PAGE_DESKTOP ) ,\ + /* X, Y absolute position [0, 32767] */ \ + HID_USAGE ( HID_USAGE_DESKTOP_X ) ,\ + HID_USAGE ( HID_USAGE_DESKTOP_Y ) ,\ + HID_LOGICAL_MIN ( 0x00 ) ,\ + HID_LOGICAL_MAX_N( 0x7FFF, 2 ) ,\ + HID_REPORT_SIZE ( 16 ) ,\ + HID_REPORT_COUNT ( 2 ) ,\ + HID_INPUT ( HID_DATA | HID_VARIABLE | HID_ABSOLUTE ) ,\ + /* Vertical wheel scroll [-127, 127] */ \ + HID_USAGE ( HID_USAGE_DESKTOP_WHEEL ) ,\ + HID_LOGICAL_MIN ( 0x81 ) ,\ + HID_LOGICAL_MAX ( 0x7f ) ,\ + HID_REPORT_COUNT( 1 ) ,\ + HID_REPORT_SIZE ( 8 ) ,\ + HID_INPUT ( HID_DATA | HID_VARIABLE | HID_RELATIVE ) ,\ + HID_USAGE_PAGE ( HID_USAGE_PAGE_CONSUMER ), \ + /* Horizontal wheel scroll [-127, 127] */ \ + HID_USAGE_N ( HID_USAGE_CONSUMER_AC_PAN, 2 ), \ + HID_LOGICAL_MIN ( 0x81 ), \ + HID_LOGICAL_MAX ( 0x7f ), \ + HID_REPORT_COUNT( 1 ), \ + HID_REPORT_SIZE ( 8 ), \ + HID_INPUT ( HID_DATA | HID_VARIABLE | HID_RELATIVE ), \ + HID_COLLECTION_END , \ + HID_COLLECTION_END \ + +// Consumer Control Report Descriptor Template +#define TUD_HID_REPORT_DESC_CONSUMER(...) \ + HID_USAGE_PAGE ( HID_USAGE_PAGE_CONSUMER ) ,\ + HID_USAGE ( HID_USAGE_CONSUMER_CONTROL ) ,\ + HID_COLLECTION ( HID_COLLECTION_APPLICATION ) ,\ + /* Report ID if any */\ + __VA_ARGS__ \ + HID_LOGICAL_MIN ( 0x00 ) ,\ + HID_LOGICAL_MAX_N( 0x03FF, 2 ) ,\ + HID_USAGE_MIN ( 0x00 ) ,\ + HID_USAGE_MAX_N ( 0x03FF, 2 ) ,\ + HID_REPORT_COUNT ( 1 ) ,\ + HID_REPORT_SIZE ( 16 ) ,\ + HID_INPUT ( HID_DATA | HID_ARRAY | HID_ABSOLUTE ) ,\ + HID_COLLECTION_END \ + +/* System Control Report Descriptor Template + * 0x00 - do nothing + * 0x01 - Power Off + * 0x02 - Standby + * 0x03 - Wake Host + */ +#define TUD_HID_REPORT_DESC_SYSTEM_CONTROL(...) \ + HID_USAGE_PAGE ( HID_USAGE_PAGE_DESKTOP ) ,\ + HID_USAGE ( HID_USAGE_DESKTOP_SYSTEM_CONTROL ) ,\ + HID_COLLECTION ( HID_COLLECTION_APPLICATION ) ,\ + /* Report ID if any */\ + __VA_ARGS__ \ + /* 2 bit system power control */ \ + HID_LOGICAL_MIN ( 1 ) ,\ + HID_LOGICAL_MAX ( 3 ) ,\ + HID_REPORT_COUNT ( 1 ) ,\ + HID_REPORT_SIZE ( 2 ) ,\ + HID_USAGE ( HID_USAGE_DESKTOP_SYSTEM_POWER_DOWN ) ,\ + HID_USAGE ( HID_USAGE_DESKTOP_SYSTEM_SLEEP ) ,\ + HID_USAGE ( HID_USAGE_DESKTOP_SYSTEM_WAKE_UP ) ,\ + HID_INPUT ( HID_DATA | HID_ARRAY | HID_ABSOLUTE ) ,\ + /* 6 bit padding */ \ + HID_REPORT_COUNT ( 1 ) ,\ + HID_REPORT_SIZE ( 6 ) ,\ + HID_INPUT ( HID_CONSTANT ) ,\ + HID_COLLECTION_END \ + +// Gamepad Report Descriptor Template +// with 32 buttons, 2 joysticks and 1 hat/dpad with following layout +// | X | Y | Z | Rz | Rx | Ry (1 byte each) | hat/DPAD (1 byte) | Button Map (4 bytes) | +#define TUD_HID_REPORT_DESC_GAMEPAD(...) \ + HID_USAGE_PAGE ( HID_USAGE_PAGE_DESKTOP ) ,\ + HID_USAGE ( HID_USAGE_DESKTOP_GAMEPAD ) ,\ + HID_COLLECTION ( HID_COLLECTION_APPLICATION ) ,\ + /* Report ID if any */\ + __VA_ARGS__ \ + /* 8 bit X, Y, Z, Rz, Rx, Ry (min -127, max 127 ) */ \ + HID_USAGE_PAGE ( HID_USAGE_PAGE_DESKTOP ) ,\ + HID_USAGE ( HID_USAGE_DESKTOP_X ) ,\ + HID_USAGE ( HID_USAGE_DESKTOP_Y ) ,\ + HID_USAGE ( HID_USAGE_DESKTOP_Z ) ,\ + HID_USAGE ( HID_USAGE_DESKTOP_RZ ) ,\ + HID_USAGE ( HID_USAGE_DESKTOP_RX ) ,\ + HID_USAGE ( HID_USAGE_DESKTOP_RY ) ,\ + HID_LOGICAL_MIN ( 0x81 ) ,\ + HID_LOGICAL_MAX ( 0x7f ) ,\ + HID_REPORT_COUNT ( 6 ) ,\ + HID_REPORT_SIZE ( 8 ) ,\ + HID_INPUT ( HID_DATA | HID_VARIABLE | HID_ABSOLUTE ) ,\ + /* 8 bit DPad/Hat Button Map */ \ + HID_USAGE_PAGE ( HID_USAGE_PAGE_DESKTOP ) ,\ + HID_USAGE ( HID_USAGE_DESKTOP_HAT_SWITCH ) ,\ + HID_LOGICAL_MIN ( 1 ) ,\ + HID_LOGICAL_MAX ( 8 ) ,\ + HID_PHYSICAL_MIN ( 0 ) ,\ + HID_PHYSICAL_MAX_N ( 315, 2 ) ,\ + HID_REPORT_COUNT ( 1 ) ,\ + HID_REPORT_SIZE ( 8 ) ,\ + HID_INPUT ( HID_DATA | HID_VARIABLE | HID_ABSOLUTE ) ,\ + /* 32 bit Button Map */ \ + HID_USAGE_PAGE ( HID_USAGE_PAGE_BUTTON ) ,\ + HID_USAGE_MIN ( 1 ) ,\ + HID_USAGE_MAX ( 32 ) ,\ + HID_LOGICAL_MIN ( 0 ) ,\ + HID_LOGICAL_MAX ( 1 ) ,\ + HID_REPORT_COUNT ( 32 ) ,\ + HID_REPORT_SIZE ( 1 ) ,\ + HID_INPUT ( HID_DATA | HID_VARIABLE | HID_ABSOLUTE ) ,\ + HID_COLLECTION_END \ + +// FIDO U2F Authenticator Descriptor Template +// - 1st parameter is report size, which is 64 bytes maximum in U2F +// - 2nd parameter is HID_REPORT_ID(n) (optional) +#define TUD_HID_REPORT_DESC_FIDO_U2F(report_size, ...) \ + HID_USAGE_PAGE_N ( HID_USAGE_PAGE_FIDO, 2 ) ,\ + HID_USAGE ( HID_USAGE_FIDO_U2FHID ) ,\ + HID_COLLECTION ( HID_COLLECTION_APPLICATION ) ,\ + /* Report ID if any */ \ + __VA_ARGS__ \ + /* Usage Data In */ \ + HID_USAGE ( HID_USAGE_FIDO_DATA_IN ) ,\ + HID_LOGICAL_MIN ( 0 ) ,\ + HID_LOGICAL_MAX_N ( 0xff, 2 ) ,\ + HID_REPORT_SIZE ( 8 ) ,\ + HID_REPORT_COUNT ( report_size ) ,\ + HID_INPUT ( HID_DATA | HID_VARIABLE | HID_ABSOLUTE ) ,\ + /* Usage Data Out */ \ + HID_USAGE ( HID_USAGE_FIDO_DATA_OUT ) ,\ + HID_LOGICAL_MIN ( 0 ) ,\ + HID_LOGICAL_MAX_N ( 0xff, 2 ) ,\ + HID_REPORT_SIZE ( 8 ) ,\ + HID_REPORT_COUNT ( report_size ) ,\ + HID_OUTPUT ( HID_DATA | HID_VARIABLE | HID_ABSOLUTE ) ,\ + HID_COLLECTION_END \ + +// HID Generic Input & Output +// - 1st parameter is report size (mandatory) +// - 2nd parameter is report id HID_REPORT_ID(n) (optional) +#define TUD_HID_REPORT_DESC_GENERIC_INOUT(report_size, ...) \ + HID_USAGE_PAGE_N ( HID_USAGE_PAGE_VENDOR, 2 ),\ + HID_USAGE ( 0x01 ),\ + HID_COLLECTION ( HID_COLLECTION_APPLICATION ),\ + /* Report ID if any */\ + __VA_ARGS__ \ + /* Input */ \ + HID_USAGE ( 0x02 ),\ + HID_LOGICAL_MIN ( 0x00 ),\ + HID_LOGICAL_MAX_N ( 0xff, 2 ),\ + HID_REPORT_SIZE ( 8 ),\ + HID_REPORT_COUNT( report_size ),\ + HID_INPUT ( HID_DATA | HID_VARIABLE | HID_ABSOLUTE ),\ + /* Output */ \ + HID_USAGE ( 0x03 ),\ + HID_LOGICAL_MIN ( 0x00 ),\ + HID_LOGICAL_MAX_N ( 0xff, 2 ),\ + HID_REPORT_SIZE ( 8 ),\ + HID_REPORT_COUNT( report_size ),\ + HID_OUTPUT ( HID_DATA | HID_VARIABLE | HID_ABSOLUTE ),\ + HID_COLLECTION_END \ + +// HID Lighting and Illumination Report Descriptor Template +// - 1st parameter is report id (required) +// Creates 6 report ids for lighting HID usages in the following order: +// report_id+0: HID_USAGE_LIGHTING_LAMP_ARRAY_ATTRIBUTES_REPORT +// report_id+1: HID_USAGE_LIGHTING_LAMP_ATTRIBUTES_REQUEST_REPORT +// report_id+2: HID_USAGE_LIGHTING_LAMP_ATTRIBUTES_RESPONSE_REPORT +// report_id+3: HID_USAGE_LIGHTING_LAMP_MULTI_UPDATE_REPORT +// report_id+4: HID_USAGE_LIGHTING_LAMP_RANGE_UPDATE_REPORT +// report_id+5: HID_USAGE_LIGHTING_LAMP_ARRAY_CONTROL_REPORT +#define TUD_HID_REPORT_DESC_LIGHTING(report_id) \ + HID_USAGE_PAGE ( HID_USAGE_PAGE_LIGHTING_AND_ILLUMINATION ),\ + HID_USAGE ( HID_USAGE_LIGHTING_LAMP_ARRAY ),\ + HID_COLLECTION ( HID_COLLECTION_APPLICATION ),\ + /* Lamp Array Attributes Report */ \ + HID_REPORT_ID (report_id ) \ + HID_USAGE ( HID_USAGE_LIGHTING_LAMP_ARRAY_ATTRIBUTES_REPORT ),\ + HID_COLLECTION ( HID_COLLECTION_LOGICAL ),\ + HID_USAGE ( HID_USAGE_LIGHTING_LAMP_COUNT ),\ + HID_LOGICAL_MIN ( 0 ),\ + HID_LOGICAL_MAX_N ( 65535, 3 ),\ + HID_REPORT_SIZE ( 16 ),\ + HID_REPORT_COUNT ( 1 ),\ + HID_FEATURE ( HID_CONSTANT | HID_VARIABLE | HID_ABSOLUTE ),\ + HID_USAGE ( HID_USAGE_LIGHTING_BOUNDING_BOX_WIDTH_IN_MICROMETERS ),\ + HID_USAGE ( HID_USAGE_LIGHTING_BOUNDING_BOX_HEIGHT_IN_MICROMETERS ),\ + HID_USAGE ( HID_USAGE_LIGHTING_BOUNDING_BOX_DEPTH_IN_MICROMETERS ),\ + HID_USAGE ( HID_USAGE_LIGHTING_LAMP_ARRAY_KIND ),\ + HID_USAGE ( HID_USAGE_LIGHTING_MIN_UPDATE_INTERVAL_IN_MICROSECONDS ),\ + HID_LOGICAL_MIN ( 0 ),\ + HID_LOGICAL_MAX_N ( 2147483647, 3 ),\ + HID_REPORT_SIZE ( 32 ),\ + HID_REPORT_COUNT ( 5 ),\ + HID_FEATURE ( HID_CONSTANT | HID_VARIABLE | HID_ABSOLUTE ),\ + HID_COLLECTION_END ,\ + /* Lamp Attributes Request Report */ \ + HID_REPORT_ID ( report_id + 1 ) \ + HID_USAGE ( HID_USAGE_LIGHTING_LAMP_ATTRIBUTES_REQUEST_REPORT ),\ + HID_COLLECTION ( HID_COLLECTION_LOGICAL ),\ + HID_USAGE ( HID_USAGE_LIGHTING_LAMP_ID ),\ + HID_LOGICAL_MIN ( 0 ),\ + HID_LOGICAL_MAX_N ( 65535, 3 ),\ + HID_REPORT_SIZE ( 16 ),\ + HID_REPORT_COUNT ( 1 ),\ + HID_FEATURE ( HID_DATA | HID_VARIABLE | HID_ABSOLUTE ),\ + HID_COLLECTION_END ,\ + /* Lamp Attributes Response Report */ \ + HID_REPORT_ID ( report_id + 2 ) \ + HID_USAGE ( HID_USAGE_LIGHTING_LAMP_ATTRIBUTES_RESPONSE_REPORT ),\ + HID_COLLECTION ( HID_COLLECTION_LOGICAL ),\ + HID_USAGE ( HID_USAGE_LIGHTING_LAMP_ID ),\ + HID_LOGICAL_MIN ( 0 ),\ + HID_LOGICAL_MAX_N ( 65535, 3 ),\ + HID_REPORT_SIZE ( 16 ),\ + HID_REPORT_COUNT ( 1 ),\ + HID_FEATURE ( HID_DATA | HID_VARIABLE | HID_ABSOLUTE ),\ + HID_USAGE ( HID_USAGE_LIGHTING_POSITION_X_IN_MICROMETERS ),\ + HID_USAGE ( HID_USAGE_LIGHTING_POSITION_Y_IN_MICROMETERS ),\ + HID_USAGE ( HID_USAGE_LIGHTING_POSITION_Z_IN_MICROMETERS ),\ + HID_USAGE ( HID_USAGE_LIGHTING_UPDATE_LATENCY_IN_MICROSECONDS ),\ + HID_USAGE ( HID_USAGE_LIGHTING_LAMP_PURPOSES ),\ + HID_LOGICAL_MIN ( 0 ),\ + HID_LOGICAL_MAX_N ( 2147483647, 3 ),\ + HID_REPORT_SIZE ( 32 ),\ + HID_REPORT_COUNT ( 5 ),\ + HID_FEATURE ( HID_DATA | HID_VARIABLE | HID_ABSOLUTE ),\ + HID_USAGE ( HID_USAGE_LIGHTING_RED_LEVEL_COUNT ),\ + HID_USAGE ( HID_USAGE_LIGHTING_GREEN_LEVEL_COUNT ),\ + HID_USAGE ( HID_USAGE_LIGHTING_BLUE_LEVEL_COUNT ),\ + HID_USAGE ( HID_USAGE_LIGHTING_INTENSITY_LEVEL_COUNT ),\ + HID_USAGE ( HID_USAGE_LIGHTING_IS_PROGRAMMABLE ),\ + HID_USAGE ( HID_USAGE_LIGHTING_INPUT_BINDING ),\ + HID_LOGICAL_MIN ( 0 ),\ + HID_LOGICAL_MAX_N ( 255, 2 ),\ + HID_REPORT_SIZE ( 8 ),\ + HID_REPORT_COUNT ( 6 ),\ + HID_FEATURE ( HID_DATA | HID_VARIABLE | HID_ABSOLUTE ),\ + HID_COLLECTION_END ,\ + /* Lamp Multi-Update Report */ \ + HID_REPORT_ID ( report_id + 3 ) \ + HID_USAGE ( HID_USAGE_LIGHTING_LAMP_MULTI_UPDATE_REPORT ),\ + HID_COLLECTION ( HID_COLLECTION_LOGICAL ),\ + HID_USAGE ( HID_USAGE_LIGHTING_LAMP_COUNT ),\ + HID_USAGE ( HID_USAGE_LIGHTING_LAMP_UPDATE_FLAGS ),\ + HID_LOGICAL_MIN ( 0 ),\ + HID_LOGICAL_MAX ( 8 ),\ + HID_REPORT_SIZE ( 8 ),\ + HID_REPORT_COUNT ( 2 ),\ + HID_FEATURE ( HID_DATA | HID_VARIABLE | HID_ABSOLUTE ),\ + HID_USAGE ( HID_USAGE_LIGHTING_LAMP_ID ),\ + HID_LOGICAL_MIN ( 0 ),\ + HID_LOGICAL_MAX_N ( 65535, 3 ),\ + HID_REPORT_SIZE ( 16 ),\ + HID_REPORT_COUNT ( 8 ),\ + HID_FEATURE ( HID_DATA | HID_VARIABLE | HID_ABSOLUTE ),\ + HID_USAGE ( HID_USAGE_LIGHTING_RED_UPDATE_CHANNEL ),\ + HID_USAGE ( HID_USAGE_LIGHTING_GREEN_UPDATE_CHANNEL ),\ + HID_USAGE ( HID_USAGE_LIGHTING_BLUE_UPDATE_CHANNEL ),\ + HID_USAGE ( HID_USAGE_LIGHTING_INTENSITY_UPDATE_CHANNEL ),\ + HID_USAGE ( HID_USAGE_LIGHTING_RED_UPDATE_CHANNEL ),\ + HID_USAGE ( HID_USAGE_LIGHTING_GREEN_UPDATE_CHANNEL ),\ + HID_USAGE ( HID_USAGE_LIGHTING_BLUE_UPDATE_CHANNEL ),\ + HID_USAGE ( HID_USAGE_LIGHTING_INTENSITY_UPDATE_CHANNEL ),\ + HID_USAGE ( HID_USAGE_LIGHTING_RED_UPDATE_CHANNEL ),\ + HID_USAGE ( HID_USAGE_LIGHTING_GREEN_UPDATE_CHANNEL ),\ + HID_USAGE ( HID_USAGE_LIGHTING_BLUE_UPDATE_CHANNEL ),\ + HID_USAGE ( HID_USAGE_LIGHTING_INTENSITY_UPDATE_CHANNEL ),\ + HID_USAGE ( HID_USAGE_LIGHTING_RED_UPDATE_CHANNEL ),\ + HID_USAGE ( HID_USAGE_LIGHTING_GREEN_UPDATE_CHANNEL ),\ + HID_USAGE ( HID_USAGE_LIGHTING_BLUE_UPDATE_CHANNEL ),\ + HID_USAGE ( HID_USAGE_LIGHTING_INTENSITY_UPDATE_CHANNEL ),\ + HID_USAGE ( HID_USAGE_LIGHTING_RED_UPDATE_CHANNEL ),\ + HID_USAGE ( HID_USAGE_LIGHTING_GREEN_UPDATE_CHANNEL ),\ + HID_USAGE ( HID_USAGE_LIGHTING_BLUE_UPDATE_CHANNEL ),\ + HID_USAGE ( HID_USAGE_LIGHTING_INTENSITY_UPDATE_CHANNEL ),\ + HID_USAGE ( HID_USAGE_LIGHTING_RED_UPDATE_CHANNEL ),\ + HID_USAGE ( HID_USAGE_LIGHTING_GREEN_UPDATE_CHANNEL ),\ + HID_USAGE ( HID_USAGE_LIGHTING_BLUE_UPDATE_CHANNEL ),\ + HID_USAGE ( HID_USAGE_LIGHTING_INTENSITY_UPDATE_CHANNEL ),\ + HID_USAGE ( HID_USAGE_LIGHTING_RED_UPDATE_CHANNEL ),\ + HID_USAGE ( HID_USAGE_LIGHTING_GREEN_UPDATE_CHANNEL ),\ + HID_USAGE ( HID_USAGE_LIGHTING_BLUE_UPDATE_CHANNEL ),\ + HID_USAGE ( HID_USAGE_LIGHTING_INTENSITY_UPDATE_CHANNEL ),\ + HID_USAGE ( HID_USAGE_LIGHTING_RED_UPDATE_CHANNEL ),\ + HID_USAGE ( HID_USAGE_LIGHTING_GREEN_UPDATE_CHANNEL ),\ + HID_USAGE ( HID_USAGE_LIGHTING_BLUE_UPDATE_CHANNEL ),\ + HID_USAGE ( HID_USAGE_LIGHTING_INTENSITY_UPDATE_CHANNEL ),\ + HID_LOGICAL_MIN ( 0 ),\ + HID_LOGICAL_MAX_N ( 255, 2 ),\ + HID_REPORT_SIZE ( 8 ),\ + HID_REPORT_COUNT ( 32 ),\ + HID_FEATURE ( HID_DATA | HID_VARIABLE | HID_ABSOLUTE ),\ + HID_COLLECTION_END ,\ + /* Lamp Range Update Report */ \ + HID_REPORT_ID ( report_id + 4 ) \ + HID_USAGE ( HID_USAGE_LIGHTING_LAMP_RANGE_UPDATE_REPORT ),\ + HID_COLLECTION ( HID_COLLECTION_LOGICAL ),\ + HID_USAGE ( HID_USAGE_LIGHTING_LAMP_UPDATE_FLAGS ),\ + HID_LOGICAL_MIN ( 0 ),\ + HID_LOGICAL_MAX ( 8 ),\ + HID_REPORT_SIZE ( 8 ),\ + HID_REPORT_COUNT ( 1 ),\ + HID_FEATURE ( HID_DATA | HID_VARIABLE | HID_ABSOLUTE ),\ + HID_USAGE ( HID_USAGE_LIGHTING_LAMP_ID_START ),\ + HID_USAGE ( HID_USAGE_LIGHTING_LAMP_ID_END ),\ + HID_LOGICAL_MIN ( 0 ),\ + HID_LOGICAL_MAX_N ( 65535, 3 ),\ + HID_REPORT_SIZE ( 16 ),\ + HID_REPORT_COUNT ( 2 ),\ + HID_FEATURE ( HID_DATA | HID_VARIABLE | HID_ABSOLUTE ),\ + HID_USAGE ( HID_USAGE_LIGHTING_RED_UPDATE_CHANNEL ),\ + HID_USAGE ( HID_USAGE_LIGHTING_GREEN_UPDATE_CHANNEL ),\ + HID_USAGE ( HID_USAGE_LIGHTING_BLUE_UPDATE_CHANNEL ),\ + HID_USAGE ( HID_USAGE_LIGHTING_INTENSITY_UPDATE_CHANNEL ),\ + HID_LOGICAL_MIN ( 0 ),\ + HID_LOGICAL_MAX_N ( 255, 2 ),\ + HID_REPORT_SIZE ( 8 ),\ + HID_REPORT_COUNT ( 4 ),\ + HID_FEATURE ( HID_DATA | HID_VARIABLE | HID_ABSOLUTE ),\ + HID_COLLECTION_END ,\ + /* Lamp Array Control Report */ \ + HID_REPORT_ID ( report_id + 5 ) \ + HID_USAGE ( HID_USAGE_LIGHTING_LAMP_ARRAY_CONTROL_REPORT ),\ + HID_COLLECTION ( HID_COLLECTION_LOGICAL ),\ + HID_USAGE ( HID_USAGE_LIGHTING_AUTONOMOUS_MODE ),\ + HID_LOGICAL_MIN ( 0 ),\ + HID_LOGICAL_MAX ( 1 ),\ + HID_REPORT_SIZE ( 8 ),\ + HID_REPORT_COUNT ( 1 ),\ + HID_FEATURE ( HID_DATA | HID_VARIABLE | HID_ABSOLUTE ),\ + HID_COLLECTION_END ,\ + HID_COLLECTION_END \ + +//--------------------------------------------------------------------+ +// Internal Class Driver API +//--------------------------------------------------------------------+ +void hidd_init (void); +bool hidd_deinit (void); +void hidd_reset (uint8_t rhport); +uint16_t hidd_open (uint8_t rhport, tusb_desc_interface_t const * itf_desc, uint16_t max_len); +bool hidd_control_xfer_cb (uint8_t rhport, uint8_t stage, tusb_control_request_t const * request); +bool hidd_xfer_cb (uint8_t rhport, uint8_t ep_addr, xfer_result_t event, uint32_t xferred_bytes); + +#ifdef __cplusplus + } +#endif + +#endif diff --git a/libraries/Adafruit_TinyUSB_Arduino/src/class/hid/hid_host.c b/libraries/Adafruit_TinyUSB_Arduino/src/class/hid/hid_host.c new file mode 100644 index 0000000..fc77042 --- /dev/null +++ b/libraries/Adafruit_TinyUSB_Arduino/src/class/hid/hid_host.c @@ -0,0 +1,806 @@ +/* + * 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. + * + * This file is part of the TinyUSB stack. + */ + +#include "tusb_option.h" + +#if (CFG_TUH_ENABLED && CFG_TUH_HID) + +#include "host/usbh.h" +#include "host/usbh_pvt.h" + +#include "hid_host.h" + +// Level where CFG_TUSB_DEBUG must be at least for this driver is logged +#ifndef CFG_TUH_HID_LOG_LEVEL + #define CFG_TUH_HID_LOG_LEVEL CFG_TUH_LOG_LEVEL +#endif + +#define TU_LOG_DRV(...) TU_LOG(CFG_TUH_HID_LOG_LEVEL, __VA_ARGS__) + +//--------------------------------------------------------------------+ +// MACRO CONSTANT TYPEDEF +//--------------------------------------------------------------------+ +typedef struct { + uint8_t daddr; + uint8_t itf_num; + uint8_t ep_in; + uint8_t ep_out; + + bool mounted; // Enumeration is complete + uint8_t itf_protocol; // None, Keyboard, Mouse + uint8_t protocol_mode; // Boot (0) or Report protocol (1) + + uint8_t report_desc_type; + uint16_t report_desc_len; + + uint16_t epin_size; + uint16_t epout_size; +} hidh_interface_t; + +typedef struct { + TUH_EPBUF_DEF(epin, CFG_TUH_HID_EPIN_BUFSIZE); + TUH_EPBUF_DEF(epout, CFG_TUH_HID_EPOUT_BUFSIZE); +} hidh_epbuf_t; + +static hidh_interface_t _hidh_itf[CFG_TUH_HID]; +CFG_TUH_MEM_SECTION static hidh_epbuf_t _hidh_epbuf[CFG_TUH_HID]; + +static uint8_t _hidh_default_protocol = HID_PROTOCOL_BOOT; + +//--------------------------------------------------------------------+ +// Weak stubs: invoked if no strong implementation is available +//--------------------------------------------------------------------+ +TU_ATTR_WEAK void tuh_hid_mount_cb(uint8_t dev_addr, uint8_t idx, uint8_t const* report_desc, uint16_t desc_len) { + (void) dev_addr; (void) idx; (void) report_desc; (void) desc_len; +} + +TU_ATTR_WEAK void tuh_hid_umount_cb(uint8_t dev_addr, uint8_t idx) { + (void) dev_addr; (void) idx; +} + +TU_ATTR_WEAK void tuh_hid_report_received_cb(uint8_t dev_addr, uint8_t idx, const uint8_t *report, uint16_t len) { + (void) dev_addr; (void) idx; (void) report; (void) len; +} + +TU_ATTR_WEAK void tuh_hid_report_sent_cb(uint8_t dev_addr, uint8_t idx, uint8_t const* report, uint16_t len) { + (void) dev_addr; (void) idx; (void) report; (void) len; +} + +TU_ATTR_WEAK void tuh_hid_get_report_complete_cb(uint8_t dev_addr, uint8_t idx, uint8_t report_id, uint8_t report_type, uint16_t len) { + (void) dev_addr; (void) idx; (void) report_id; (void) report_type; (void) len; +} + +TU_ATTR_WEAK void tuh_hid_set_report_complete_cb(uint8_t dev_addr, uint8_t idx, uint8_t report_id, uint8_t report_type, uint16_t len) { + (void) dev_addr; (void) idx; (void) report_id; (void) report_type; (void) len; +} + +TU_ATTR_WEAK void tuh_hid_set_protocol_complete_cb(uint8_t dev_addr, uint8_t idx, uint8_t protocol) { + (void) dev_addr; (void) idx; (void) protocol; +} + +//--------------------------------------------------------------------+ +// Helper +//--------------------------------------------------------------------+ +TU_ATTR_ALWAYS_INLINE static inline hidh_interface_t* get_hid_itf(uint8_t daddr, uint8_t idx) { + TU_ASSERT(daddr > 0 && idx < CFG_TUH_HID, NULL); + hidh_interface_t* p_hid = &_hidh_itf[idx]; + return (p_hid->daddr == daddr) ? p_hid : NULL; +} + +TU_ATTR_ALWAYS_INLINE static inline hidh_epbuf_t* get_hid_epbuf(uint8_t idx) { + return &_hidh_epbuf[idx]; +} + +// Get instance ID by endpoint address +static uint8_t get_idx_by_epaddr(uint8_t daddr, uint8_t ep_addr) { + for (uint8_t idx = 0; idx < CFG_TUH_HID; idx++) { + hidh_interface_t const* p_hid = &_hidh_itf[idx]; + if (p_hid->daddr == daddr && + (p_hid->ep_in == ep_addr || p_hid->ep_out == ep_addr)) { + return idx; + } + } + return TUSB_INDEX_INVALID_8; +} + +static hidh_interface_t* find_new_itf(void) { + for (uint8_t i = 0; i < CFG_TUH_HID; i++) { + if (_hidh_itf[i].daddr == 0) { + return &_hidh_itf[i]; + } + } + return NULL; +} + +//--------------------------------------------------------------------+ +// Interface API +//--------------------------------------------------------------------+ +uint8_t tuh_hid_itf_get_count(uint8_t daddr) { + uint8_t count = 0; + for (uint8_t i = 0; i < CFG_TUH_HID; i++) { + if (_hidh_itf[i].daddr == daddr) { + count++; + } + } + return count; +} + +uint8_t tuh_hid_itf_get_total_count(void) { + uint8_t count = 0; + for (uint8_t i = 0; i < CFG_TUH_HID; i++) { + if (_hidh_itf[i].daddr != 0) { + count++; + } + } + return count; +} + +bool tuh_hid_mounted(uint8_t daddr, uint8_t idx) { + hidh_interface_t* p_hid = get_hid_itf(daddr, idx); + TU_VERIFY(p_hid); + return p_hid->mounted; +} + +bool tuh_hid_itf_get_info(uint8_t daddr, uint8_t idx, tuh_itf_info_t* info) { + hidh_interface_t* p_hid = get_hid_itf(daddr, idx); + TU_VERIFY(p_hid && info); + + info->daddr = daddr; + + // re-construct descriptor + tusb_desc_interface_t* desc = &info->desc; + desc->bLength = sizeof(tusb_desc_interface_t); + desc->bDescriptorType = TUSB_DESC_INTERFACE; + + desc->bInterfaceNumber = p_hid->itf_num; + desc->bAlternateSetting = 0; + desc->bNumEndpoints = (uint8_t) ((p_hid->ep_in ? 1u : 0u) + (p_hid->ep_out ? 1u : 0u)); + desc->bInterfaceClass = TUSB_CLASS_HID; + desc->bInterfaceSubClass = (p_hid->itf_protocol ? HID_SUBCLASS_BOOT : HID_SUBCLASS_NONE); + desc->bInterfaceProtocol = p_hid->itf_protocol; + desc->iInterface = 0; // not used yet + + return true; +} + +uint8_t tuh_hid_itf_get_index(uint8_t daddr, uint8_t itf_num) { + for (uint8_t idx = 0; idx < CFG_TUH_HID; idx++) { + hidh_interface_t const* p_hid = &_hidh_itf[idx]; + if (p_hid->daddr == daddr && p_hid->itf_num == itf_num) return idx; + } + + return TUSB_INDEX_INVALID_8; +} + +uint8_t tuh_hid_interface_protocol(uint8_t daddr, uint8_t idx) { + hidh_interface_t* p_hid = get_hid_itf(daddr, idx); + return p_hid ? p_hid->itf_protocol : 0; +} + +//--------------------------------------------------------------------+ +// Control Endpoint API +//--------------------------------------------------------------------+ +uint8_t tuh_hid_get_protocol(uint8_t daddr, uint8_t idx) { + hidh_interface_t* p_hid = get_hid_itf(daddr, idx); + return p_hid ? p_hid->protocol_mode : 0; +} + +static void set_protocol_complete(tuh_xfer_t* xfer) { + uint8_t const itf_num = (uint8_t) tu_le16toh(xfer->setup->wIndex); + uint8_t const daddr = xfer->daddr; + uint8_t const idx = tuh_hid_itf_get_index(daddr, itf_num); + + hidh_interface_t* p_hid = get_hid_itf(daddr, idx); + TU_VERIFY(p_hid,); + + if (XFER_RESULT_SUCCESS == xfer->result) { + p_hid->protocol_mode = (uint8_t) tu_le16toh(xfer->setup->wValue); + } + + tuh_hid_set_protocol_complete_cb(daddr, idx, p_hid->protocol_mode); +} + +void tuh_hid_set_default_protocol(uint8_t protocol) { + _hidh_default_protocol = protocol; +} + +static bool hidh_set_protocol(uint8_t daddr, uint8_t itf_num, uint8_t protocol, + tuh_xfer_cb_t complete_cb, uintptr_t user_data) { + TU_LOG_DRV("HID Set Protocol = %d\r\n", protocol); + + tusb_control_request_t const request = { + .bmRequestType_bit = { + .recipient = TUSB_REQ_RCPT_INTERFACE, + .type = TUSB_REQ_TYPE_CLASS, + .direction = TUSB_DIR_OUT + }, + .bRequest = HID_REQ_CONTROL_SET_PROTOCOL, + .wValue = protocol, + .wIndex = itf_num, + .wLength = 0 + }; + + tuh_xfer_t xfer = { + .daddr = daddr, + .ep_addr = 0, + .setup = &request, + .buffer = NULL, + .complete_cb = complete_cb, + .user_data = user_data + }; + + return tuh_control_xfer(&xfer); +} + +bool tuh_hid_set_protocol(uint8_t daddr, uint8_t idx, uint8_t protocol) { + hidh_interface_t* p_hid = get_hid_itf(daddr, idx); + TU_VERIFY(p_hid && p_hid->itf_protocol != HID_ITF_PROTOCOL_NONE); + + return hidh_set_protocol(daddr, p_hid->itf_num, protocol, set_protocol_complete, 0); +} + +static void get_report_complete(tuh_xfer_t* xfer) { + TU_LOG_DRV("HID Get Report complete\r\n"); + + uint8_t const itf_num = (uint8_t) tu_le16toh(xfer->setup->wIndex); + uint8_t const idx = tuh_hid_itf_get_index(xfer->daddr, itf_num); + + uint8_t const report_type = tu_u16_high(xfer->setup->wValue); + uint8_t const report_id = tu_u16_low(xfer->setup->wValue); + + tuh_hid_get_report_complete_cb(xfer->daddr, idx, report_id, report_type, + (xfer->result == XFER_RESULT_SUCCESS) ? xfer->setup->wLength : 0); +} + +bool tuh_hid_get_report(uint8_t daddr, uint8_t idx, uint8_t report_id, uint8_t report_type, void* report, uint16_t len) { + hidh_interface_t* p_hid = get_hid_itf(daddr, idx); + TU_VERIFY(p_hid); + TU_LOG_DRV("HID Get Report: id = %u, type = %u, len = %u\r\n", report_id, report_type, len); + + tusb_control_request_t const request = { + .bmRequestType_bit = { + .recipient = TUSB_REQ_RCPT_INTERFACE, + .type = TUSB_REQ_TYPE_CLASS, + .direction = TUSB_DIR_IN + }, + .bRequest = HID_REQ_CONTROL_GET_REPORT, + .wValue = tu_htole16(tu_u16(report_type, report_id)), + .wIndex = tu_htole16((uint16_t) p_hid->itf_num), + .wLength = len + }; + + tuh_xfer_t xfer = { + .daddr = daddr, + .ep_addr = 0, + .setup = &request, + .buffer = report, + .complete_cb = get_report_complete, + .user_data = 0 + }; + + return tuh_control_xfer(&xfer); +} + +static void set_report_complete(tuh_xfer_t* xfer) { + TU_LOG_DRV("HID Set Report complete\r\n"); + + uint8_t const itf_num = (uint8_t) tu_le16toh(xfer->setup->wIndex); + uint8_t const idx = tuh_hid_itf_get_index(xfer->daddr, itf_num); + + uint8_t const report_type = tu_u16_high(xfer->setup->wValue); + uint8_t const report_id = tu_u16_low(xfer->setup->wValue); + + tuh_hid_set_report_complete_cb(xfer->daddr, idx, report_id, report_type, + (xfer->result == XFER_RESULT_SUCCESS) ? xfer->setup->wLength : 0); +} + +bool tuh_hid_set_report(uint8_t daddr, uint8_t idx, uint8_t report_id, uint8_t report_type, void* report, uint16_t len) { + hidh_interface_t* p_hid = get_hid_itf(daddr, idx); + TU_VERIFY(p_hid); + TU_LOG_DRV("HID Set Report: id = %u, type = %u, len = %u\r\n", report_id, report_type, len); + + tusb_control_request_t const request = { + .bmRequestType_bit = { + .recipient = TUSB_REQ_RCPT_INTERFACE, + .type = TUSB_REQ_TYPE_CLASS, + .direction = TUSB_DIR_OUT + }, + .bRequest = HID_REQ_CONTROL_SET_REPORT, + .wValue = tu_htole16(tu_u16(report_type, report_id)), + .wIndex = tu_htole16((uint16_t) p_hid->itf_num), + .wLength = len + }; + + tuh_xfer_t xfer = { + .daddr = daddr, + .ep_addr = 0, + .setup = &request, + .buffer = report, + .complete_cb = set_report_complete, + .user_data = 0 + }; + + return tuh_control_xfer(&xfer); +} + +static bool hidh_set_idle(uint8_t daddr, uint8_t itf_num, uint16_t idle_rate, + tuh_xfer_cb_t complete_cb, uintptr_t user_data) { + // SET IDLE request, device can stall if not support this request + TU_LOG_DRV("HID Set Idle \r\n"); + + tusb_control_request_t const request = { + .bmRequestType_bit = { + .recipient = TUSB_REQ_RCPT_INTERFACE, + .type = TUSB_REQ_TYPE_CLASS, + .direction = TUSB_DIR_OUT + }, + .bRequest = HID_REQ_CONTROL_SET_IDLE, + .wValue = tu_htole16(idle_rate), + .wIndex = tu_htole16((uint16_t) itf_num), + .wLength = 0 + }; + + tuh_xfer_t xfer = { + .daddr = daddr, + .ep_addr = 0, + .setup = &request, + .buffer = NULL, + .complete_cb = complete_cb, + .user_data = user_data + }; + + return tuh_control_xfer(&xfer); +} + +//--------------------------------------------------------------------+ +// Interrupt Endpoint API +//--------------------------------------------------------------------+ + +// Check if HID interface is ready to receive report +bool tuh_hid_receive_ready(uint8_t dev_addr, uint8_t idx) { + hidh_interface_t* p_hid = get_hid_itf(dev_addr, idx); + TU_VERIFY(p_hid); + return !usbh_edpt_busy(dev_addr, p_hid->ep_in); +} + +bool tuh_hid_receive_report(uint8_t daddr, uint8_t idx) { + hidh_interface_t* p_hid = get_hid_itf(daddr, idx); + TU_VERIFY(p_hid); + hidh_epbuf_t* epbuf = get_hid_epbuf(idx); + + // claim endpoint + TU_VERIFY(usbh_edpt_claim(daddr, p_hid->ep_in)); + + if (!usbh_edpt_xfer(daddr, p_hid->ep_in, epbuf->epin, p_hid->epin_size)) { + usbh_edpt_release(daddr, p_hid->ep_in); + return false; + } + + return true; +} +bool tuh_hid_receive_abort(uint8_t dev_addr, uint8_t idx) { + hidh_interface_t* p_hid = get_hid_itf(dev_addr, idx); + TU_VERIFY(p_hid); + return tuh_edpt_abort_xfer(dev_addr, p_hid->ep_in); +} + +bool tuh_hid_send_ready(uint8_t dev_addr, uint8_t idx) { + hidh_interface_t* p_hid = get_hid_itf(dev_addr, idx); + TU_VERIFY(p_hid); + return !usbh_edpt_busy(dev_addr, p_hid->ep_out); +} + +bool tuh_hid_send_report(uint8_t daddr, uint8_t idx, uint8_t report_id, const void* report, uint16_t len) { + TU_LOG_DRV("HID Send Report %d\r\n", report_id); + + hidh_interface_t* p_hid = get_hid_itf(daddr, idx); + TU_VERIFY(p_hid); + hidh_epbuf_t* epbuf = get_hid_epbuf(idx); + + if (p_hid->ep_out == 0) { + // This HID does not have an out endpoint (other than control) + return false; + } else if (len > CFG_TUH_HID_EPOUT_BUFSIZE || + (report_id != 0 && len > (CFG_TUH_HID_EPOUT_BUFSIZE - 1))) { + // ep_out buffer is not large enough to hold contents + return false; + } + + // claim endpoint + TU_VERIFY(usbh_edpt_claim(daddr, p_hid->ep_out)); + + if (report_id == 0) { + // No report ID in transmission + memcpy(&epbuf->epout[0], report, len); + } else { + epbuf->epout[0] = report_id; + memcpy(&epbuf->epout[1], report, len); + ++len; // 1 more byte for report_id + } + + TU_LOG3_MEM(epbuf->epout, len, 2); + + if (!usbh_edpt_xfer(daddr, p_hid->ep_out, epbuf->epout, len)) { + usbh_edpt_release(daddr, p_hid->ep_out); + return false; + } + + return true; +} + +//--------------------------------------------------------------------+ +// USBH API +//--------------------------------------------------------------------+ +bool hidh_init(void) { + TU_LOG_DRV("sizeof(hidh_interface_t) = %u\r\n", sizeof(hidh_interface_t)); + tu_memclr(_hidh_itf, sizeof(_hidh_itf)); + return true; +} + +bool hidh_deinit(void) { + return true; +} + +bool hidh_xfer_cb(uint8_t daddr, uint8_t ep_addr, xfer_result_t result, uint32_t xferred_bytes) { + (void) result; + + uint8_t const dir = tu_edpt_dir(ep_addr); + uint8_t const idx = get_idx_by_epaddr(daddr, ep_addr); + + hidh_interface_t* p_hid = get_hid_itf(daddr, idx); + TU_VERIFY(p_hid); + hidh_epbuf_t* epbuf = get_hid_epbuf(idx); + + if (dir == TUSB_DIR_IN) { + TU_LOG_DRV(" [idx=%u] Get Report callback\r\n", idx); + TU_LOG3_MEM(epbuf->epin, xferred_bytes, 2); + tuh_hid_report_received_cb(daddr, idx, epbuf->epin, (uint16_t) xferred_bytes); + } else { + tuh_hid_report_sent_cb(daddr, idx, epbuf->epout, (uint16_t) xferred_bytes); + } + + return true; +} + +void hidh_close(uint8_t daddr) { + for (uint8_t i = 0; i < CFG_TUH_HID; i++) { + hidh_interface_t* p_hid = &_hidh_itf[i]; + if (p_hid->daddr == daddr) { + TU_LOG_DRV(" HIDh close addr = %u index = %u\r\n", daddr, i); + tuh_hid_umount_cb(daddr, i); + tu_memclr(p_hid, sizeof(hidh_interface_t)); + } + } +} + +//--------------------------------------------------------------------+ +// Enumeration +//--------------------------------------------------------------------+ +uint16_t hidh_open(uint8_t rhport, uint8_t daddr, const tusb_desc_interface_t *desc_itf, uint16_t max_len) { + (void) rhport; + (void) max_len; + + TU_VERIFY(TUSB_CLASS_HID == desc_itf->bInterfaceClass, 0); + TU_LOG_DRV("[%u] HID opening Interface %u\r\n", daddr, desc_itf->bInterfaceNumber); + + // len = interface + hid + n*endpoints + const uint16_t drv_len = (uint16_t)(sizeof(tusb_desc_interface_t) + sizeof(tusb_hid_descriptor_hid_t) + + desc_itf->bNumEndpoints * sizeof(tusb_desc_endpoint_t)); + TU_ASSERT(drv_len <= max_len, 0); + const uint8_t *p_desc = (const uint8_t *)desc_itf; + + // HID descriptor: mostly right after interface descriptor, in some rare case it might be after endpoint descriptors + p_desc = tu_desc_next(p_desc); + const tusb_hid_descriptor_hid_t *desc_hid; + if (tu_desc_type(p_desc) == HID_DESC_TYPE_HID) { + // HID after interface + desc_hid = (const tusb_hid_descriptor_hid_t *)p_desc; + p_desc = tu_desc_next(p_desc); + } else { + // HID after endpoint + desc_hid = (const tusb_hid_descriptor_hid_t *)(p_desc + sizeof(tusb_desc_endpoint_t) * desc_itf->bNumEndpoints); + TU_ASSERT(tu_desc_type(desc_hid) == HID_DESC_TYPE_HID, 0); + } + + // Allocate new interface + hidh_interface_t *p_hid = find_new_itf(); + TU_ASSERT(p_hid, 0); // not enough interface, try to increase CFG_TUH_HID + p_hid->daddr = daddr; + p_hid->itf_num = desc_itf->bInterfaceNumber; + + // Endpoint Descriptors + for (uint8_t i = 0; i < desc_itf->bNumEndpoints; i++) { + const tusb_desc_endpoint_t *desc_ep = (const tusb_desc_endpoint_t *)p_desc; + TU_ASSERT(TUSB_DESC_ENDPOINT == desc_ep->bDescriptorType, 0); + TU_ASSERT(tuh_edpt_open(daddr, desc_ep), 0); + + if (tu_edpt_dir(desc_ep->bEndpointAddress) == TUSB_DIR_IN) { + p_hid->ep_in = desc_ep->bEndpointAddress; + p_hid->epin_size = tu_edpt_packet_size(desc_ep); + } else { + p_hid->ep_out = desc_ep->bEndpointAddress; + p_hid->epout_size = tu_edpt_packet_size(desc_ep); + } + + p_desc = tu_desc_next(p_desc); + } + + // Assume bNumDescriptors = 1 + p_hid->report_desc_type = desc_hid->bReportType; + // Use offsetof to avoid pointer to the odd/misaligned address + p_hid->report_desc_len = tu_unaligned_read16((uint8_t const*)desc_hid + offsetof(tusb_hid_descriptor_hid_t, wReportLength)); + + // Per HID Specs: default is Report protocol + p_hid->protocol_mode = HID_PROTOCOL_REPORT; + if (HID_SUBCLASS_BOOT == desc_itf->bInterfaceSubClass) { + p_hid->itf_protocol = desc_itf->bInterfaceProtocol; + } + + return drv_len; +} + +//--------------------------------------------------------------------+ +// Set Configure +//--------------------------------------------------------------------+ + +enum { + CONFG_SET_IDLE, + CONFIG_SET_PROTOCOL, + CONFIG_GET_REPORT_DESC, + CONFIG_COMPLETE +}; + +static void config_driver_mount_complete(uint8_t daddr, uint8_t idx, uint8_t const* desc_report, uint16_t desc_len); +static void process_set_config(tuh_xfer_t* xfer); + +bool hidh_set_config(uint8_t daddr, uint8_t itf_num) { + tusb_control_request_t request; + request.wIndex = tu_htole16((uint16_t) itf_num); + + tuh_xfer_t xfer; + xfer.daddr = daddr; + xfer.result = XFER_RESULT_SUCCESS; + xfer.setup = &request; + xfer.user_data = CONFG_SET_IDLE; + + // fake request to kick-off the set config process + process_set_config(&xfer); + + return true; +} + +static void process_set_config(tuh_xfer_t* xfer) { + // Stall is a valid response for SET_IDLE, sometime SET_PROTOCOL as well + // therefore we could ignore its result + if (!(xfer->setup->bRequest == HID_REQ_CONTROL_SET_IDLE || + xfer->setup->bRequest == HID_REQ_CONTROL_SET_PROTOCOL)) { + TU_ASSERT(xfer->result == XFER_RESULT_SUCCESS,); + } + + uintptr_t const state = xfer->user_data; + uint8_t const itf_num = (uint8_t) tu_le16toh(xfer->setup->wIndex); + uint8_t const daddr = xfer->daddr; + + uint8_t const idx = tuh_hid_itf_get_index(daddr, itf_num); + hidh_interface_t* p_hid = get_hid_itf(daddr, idx); + TU_VERIFY(p_hid,); + + switch (state) { + case CONFG_SET_IDLE: { + // Idle rate = 0 mean only report when there are changes + const uint16_t idle_rate = 0; + const uintptr_t next_state = (p_hid->itf_protocol != HID_ITF_PROTOCOL_NONE) + ? CONFIG_SET_PROTOCOL : CONFIG_GET_REPORT_DESC; + hidh_set_idle(daddr, itf_num, idle_rate, process_set_config, next_state); + break; + } + + case CONFIG_SET_PROTOCOL: + #if CFG_TUH_HID_SET_PROTOCOL_ON_ENUM + hidh_set_protocol(daddr, p_hid->itf_num, _hidh_default_protocol, process_set_config, CONFIG_GET_REPORT_DESC); + break; + #else + TU_ATTR_FALLTHROUGH; + #endif + + case CONFIG_GET_REPORT_DESC: + if (xfer->setup->bRequest == HID_REQ_CONTROL_SET_PROTOCOL && xfer->result == XFER_RESULT_SUCCESS) { + p_hid->protocol_mode = (uint8_t) tu_le16toh(xfer->setup->wValue); + } + // Get Report Descriptor if possible + // using usbh enumeration buffer since the report descriptor can be very long + if (p_hid->report_desc_len > CFG_TUH_ENUMERATION_BUFSIZE) { + TU_LOG_DRV("HID Skip Report Descriptor since it is too large %u bytes\r\n", p_hid->report_desc_len); + config_driver_mount_complete(daddr, idx, NULL, 0); + } else { + tuh_descriptor_get_hid_report(daddr, itf_num, p_hid->report_desc_type, 0, + usbh_get_enum_buf(), p_hid->report_desc_len, + process_set_config, CONFIG_COMPLETE); + } + break; + + case CONFIG_COMPLETE: { + const uint8_t *desc_report = usbh_get_enum_buf(); + const uint16_t desc_len = tu_le16toh(xfer->setup->wLength); + + config_driver_mount_complete(daddr, idx, desc_report, desc_len); + break; + } + + default: + break; + } +} + +static void config_driver_mount_complete(uint8_t daddr, uint8_t idx, uint8_t const* desc_report, uint16_t desc_len) { + hidh_interface_t* p_hid = get_hid_itf(daddr, idx); + TU_VERIFY(p_hid,); + p_hid->mounted = true; + + // enumeration is complete + tuh_hid_mount_cb(daddr, idx, desc_report, desc_len); + + // notify usbh that driver enumeration is complete + usbh_driver_set_config_complete(daddr, p_hid->itf_num); +} + +//--------------------------------------------------------------------+ +// Report Descriptor Parser +//--------------------------------------------------------------------+ + +uint8_t tuh_hid_parse_report_descriptor(tuh_hid_report_info_t* report_info_arr, uint8_t arr_count, + uint8_t const* desc_report, uint16_t desc_len) { + // Report Item 6.2.2.2 USB HID 1.11 + union TU_ATTR_PACKED { + uint8_t byte; + struct TU_ATTR_PACKED { + uint8_t size : 2; + uint8_t type : 2; + uint8_t tag : 4; + }; + } header; + + tu_memclr(report_info_arr, arr_count * sizeof(tuh_hid_report_info_t)); + + uint8_t report_num = 0; + tuh_hid_report_info_t* info = report_info_arr; + + // current parsed report count & size from descriptor +// uint8_t ri_report_count = 0; +// uint8_t ri_report_size = 0; + + uint8_t ri_collection_depth = 0; + while (desc_len && report_num < arr_count) { + header.byte = *desc_report++; + desc_len--; + + uint8_t const tag = header.tag; + uint8_t const type = header.type; + uint8_t size = header.size; + if (size == 3) { + size = 4; // HID 1.11 6.2.2.2 3 is 4 bytes + } + + uint8_t const data8 = (size > 0) ? desc_report[0] : 0; + + TU_LOG(3, "tag = %d, type = %d, size = %d, data = ", tag, type, size); + for (uint32_t i = 0; i < size; i++) { + TU_LOG(3, "%02X ", desc_report[i]); + } + TU_LOG(3, "\r\n"); + + switch (type) { + case RI_TYPE_MAIN: + switch (tag) { + case RI_MAIN_INPUT: break; + case RI_MAIN_OUTPUT: break; + case RI_MAIN_FEATURE: break; + case RI_MAIN_COLLECTION: + ri_collection_depth++; + break; + + case RI_MAIN_COLLECTION_END: + ri_collection_depth--; + if (ri_collection_depth == 0) { + info++; + report_num++; + } + break; + + default:break; + } + break; + + case RI_TYPE_GLOBAL: + switch (tag) { + case RI_GLOBAL_USAGE_PAGE: + // only take in account the "usage page" before REPORT ID + if (ri_collection_depth == 0) memcpy(&info->usage_page, desc_report, size); + break; + + case RI_GLOBAL_LOGICAL_MIN: break; + case RI_GLOBAL_LOGICAL_MAX: break; + case RI_GLOBAL_PHYSICAL_MIN: break; + case RI_GLOBAL_PHYSICAL_MAX: break; + + case RI_GLOBAL_REPORT_ID: + info->report_id = data8; + break; + + case RI_GLOBAL_REPORT_SIZE: +// ri_report_size = data8; + break; + + case RI_GLOBAL_REPORT_COUNT: +// ri_report_count = data8; + break; + + case RI_GLOBAL_UNIT_EXPONENT: break; + case RI_GLOBAL_UNIT: break; + case RI_GLOBAL_PUSH: break; + case RI_GLOBAL_POP: break; + + default: break; + } + break; + + case RI_TYPE_LOCAL: + switch (tag) { + case RI_LOCAL_USAGE: + // only take in account the "usage" before starting REPORT ID + if (ri_collection_depth == 0) info->usage = data8; + break; + + case RI_LOCAL_USAGE_MIN: break; + case RI_LOCAL_USAGE_MAX: break; + case RI_LOCAL_DESIGNATOR_INDEX: break; + case RI_LOCAL_DESIGNATOR_MIN: break; + case RI_LOCAL_DESIGNATOR_MAX: break; + case RI_LOCAL_STRING_INDEX: break; + case RI_LOCAL_STRING_MIN: break; + case RI_LOCAL_STRING_MAX: break; + case RI_LOCAL_DELIMITER: break; + default: break; + } + break; + + // error + default: break; + } + + desc_report += size; + desc_len -= size; + } + + for (uint8_t i = 0; i < report_num; i++) { + info = report_info_arr + i; + TU_LOG_DRV("%u: id = %u, usage_page = %u, usage = %u\r\n", i, info->report_id, info->usage_page, info->usage); + } + + return report_num; +} + +#endif diff --git a/libraries/Adafruit_TinyUSB_Arduino/src/class/hid/hid_host.h b/libraries/Adafruit_TinyUSB_Arduino/src/class/hid/hid_host.h new file mode 100644 index 0000000..95ba859 --- /dev/null +++ b/libraries/Adafruit_TinyUSB_Arduino/src/class/hid/hid_host.h @@ -0,0 +1,190 @@ +/* + * 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. + * + * This file is part of the TinyUSB stack. + */ + +#ifndef TUSB_HID_HOST_H_ +#define TUSB_HID_HOST_H_ + +#include "hid.h" + +#ifdef __cplusplus +extern "C" { +#endif + +//--------------------------------------------------------------------+ +// Class Driver Configuration +//--------------------------------------------------------------------+ +// TODO Highspeed interrupt can be up to 512 bytes +#ifndef CFG_TUH_HID_EPIN_BUFSIZE + #define CFG_TUH_HID_EPIN_BUFSIZE 64 +#endif + +#ifndef CFG_TUH_HID_EPOUT_BUFSIZE + #define CFG_TUH_HID_EPOUT_BUFSIZE 64 +#endif + +#ifndef CFG_TUH_HID_SET_PROTOCOL_ON_ENUM + #define CFG_TUH_HID_SET_PROTOCOL_ON_ENUM 1 +#endif + +//--------------------------------------------------------------------+ +// Interface API +//--------------------------------------------------------------------+ +typedef struct { + uint8_t report_id; + uint8_t usage; + uint16_t usage_page; + + // TODO still use the endpoint size for now + // uint8_t in_len; // length of IN report + // uint8_t out_len; // length of OUT report +} tuh_hid_report_info_t; + +// Get the total number of mounted HID interfaces of a device +uint8_t tuh_hid_itf_get_count(uint8_t dev_addr); + +// Get all mounted interfaces across devices +uint8_t tuh_hid_itf_get_total_count(void); + +// backward compatible rename +#define tuh_hid_instance_count tuh_hid_itf_get_count + +// Get Interface information +bool tuh_hid_itf_get_info(uint8_t daddr, uint8_t idx, tuh_itf_info_t *itf_info); + +// Get Interface index from device address + interface number +// return TUSB_INDEX_INVALID_8 (0xFF) if not found +uint8_t tuh_hid_itf_get_index(uint8_t daddr, uint8_t itf_num); + +// Get interface supported protocol (bInterfaceProtocol) check out hid_interface_protocol_enum_t for possible values +uint8_t tuh_hid_interface_protocol(uint8_t dev_addr, uint8_t idx); + +// Check if HID interface is mounted +bool tuh_hid_mounted(uint8_t dev_addr, uint8_t idx); + +// Parse report descriptor into array of report_info struct and return number of reports. +// For complicated report, application should write its own parser. +TU_ATTR_UNUSED uint8_t tuh_hid_parse_report_descriptor(tuh_hid_report_info_t *reports_info_arr, uint8_t arr_count, + const uint8_t *desc_report, uint16_t desc_len); + +//--------------------------------------------------------------------+ +// Control Endpoint API +//--------------------------------------------------------------------+ + +// Get current protocol: HID_PROTOCOL_BOOT (0) or HID_PROTOCOL_REPORT (1) +// Note: Device will be initialized in Boot protocol for simplicity. +// Application can use set_protocol() to switch back to Report protocol. +uint8_t tuh_hid_get_protocol(uint8_t dev_addr, uint8_t idx); + +// Device by default is enumerated in Boot protocol for simplicity. Application +// can use this to modify the default protocol for next enumeration. +void tuh_hid_set_default_protocol(uint8_t protocol); + +// Set protocol to HID_PROTOCOL_BOOT (0) or HID_PROTOCOL_REPORT (1) +// This function is only supported by Boot interface (tuh_n_hid_interface_protocol() != NONE) +bool tuh_hid_set_protocol(uint8_t dev_addr, uint8_t idx, uint8_t protocol); + +// Get Report using control endpoint +// report_type is either Input, Output or Feature, (value from hid_report_type_t) +bool tuh_hid_get_report(uint8_t dev_addr, uint8_t idx, uint8_t report_id, uint8_t report_type, void *report, + uint16_t len); + +// Set Report using control endpoint +// report_type is either Input, Output or Feature, (value from hid_report_type_t) +bool tuh_hid_set_report(uint8_t dev_addr, uint8_t idx, uint8_t report_id, uint8_t report_type, void *report, + uint16_t len); + +//--------------------------------------------------------------------+ +// Interrupt Endpoint API +//--------------------------------------------------------------------+ + +// Check if HID interface is ready to receive report +bool tuh_hid_receive_ready(uint8_t dev_addr, uint8_t idx); + +// Try to receive next report on Interrupt Endpoint. Immediately return +// - true If succeeded, tuh_hid_report_received_cb() callback will be invoked when report is available +// - false if failed to queue the transfer e.g endpoint is busy +bool tuh_hid_receive_report(uint8_t dev_addr, uint8_t idx); + +// Abort receiving report on Interrupt Endpoint +bool tuh_hid_receive_abort(uint8_t dev_addr, uint8_t idx); + +// Check if HID interface is ready to send report +bool tuh_hid_send_ready(uint8_t dev_addr, uint8_t idx); + +// Send report using interrupt endpoint +// If report_id > 0 (composite), it will be sent as 1st byte, then report contents. Otherwise only report content is +// sent. +bool tuh_hid_send_report(uint8_t dev_addr, uint8_t idx, uint8_t report_id, const void *report, uint16_t len); + +//--------------------------------------------------------------------+ +// Callbacks (optional) +//--------------------------------------------------------------------+ + +// Invoked when device with hid interface is mounted +// Report descriptor is also available for use. tuh_hid_parse_report_descriptor() +// can be used to parse common/simple enough descriptor. +// Note: if report descriptor length > CFG_TUH_ENUMERATION_BUFSIZE, it will be skipped +// therefore report_desc = NULL, desc_len = 0 +void tuh_hid_mount_cb(uint8_t dev_addr, uint8_t idx, const uint8_t *report_desc, uint16_t desc_len); + +// Invoked when device with hid interface is un-mounted +void tuh_hid_umount_cb(uint8_t dev_addr, uint8_t idx); + +// Invoked when received report from device via interrupt endpoint +// Note: if there is report ID (composite), it is 1st byte of report +void tuh_hid_report_received_cb(uint8_t dev_addr, uint8_t idx, const uint8_t *report, uint16_t len); + +// Invoked when sent report to device successfully via interrupt endpoint +void tuh_hid_report_sent_cb(uint8_t dev_addr, uint8_t idx, const uint8_t *report, uint16_t len); + +// Invoked when Get Report to device via either control endpoint +// len = 0 indicate there is error in the transfer e.g stalled response +void tuh_hid_get_report_complete_cb(uint8_t dev_addr, uint8_t idx, uint8_t report_id, uint8_t report_type, + uint16_t len); + +// Invoked when Sent Report to device via either control endpoint +// len = 0 indicate there is error in the transfer e.g stalled response +void tuh_hid_set_report_complete_cb(uint8_t dev_addr, uint8_t idx, uint8_t report_id, uint8_t report_type, + uint16_t len); + +// Invoked when Set Protocol request is complete +void tuh_hid_set_protocol_complete_cb(uint8_t dev_addr, uint8_t idx, uint8_t protocol); + +//--------------------------------------------------------------------+ +// Internal Class Driver API +//--------------------------------------------------------------------+ +bool hidh_init(void); +bool hidh_deinit(void); +uint16_t hidh_open(uint8_t rhport, uint8_t dev_addr, const tusb_desc_interface_t *desc_itf, uint16_t max_len); +bool hidh_set_config(uint8_t dev_addr, uint8_t itf_num); +bool hidh_xfer_cb(uint8_t dev_addr, uint8_t ep_addr, xfer_result_t result, uint32_t xferred_bytes); +void hidh_close(uint8_t dev_addr); + +#ifdef __cplusplus +} +#endif + +#endif /* TUSB_HID_HOST_H_ */ diff --git a/libraries/Adafruit_TinyUSB_Arduino/src/class/midi/README_midi_host.md b/libraries/Adafruit_TinyUSB_Arduino/src/class/midi/README_midi_host.md new file mode 100644 index 0000000..efaf7a1 --- /dev/null +++ b/libraries/Adafruit_TinyUSB_Arduino/src/class/midi/README_midi_host.md @@ -0,0 +1,111 @@ +# MIDI HOST DRIVER +This README file contains the design notes and limitations of the +MIDI host driver. + +# MAXIMUM NUMBER OF MIDI DEVICES ATTACHED TO HOST +In this version of the driver, only one MIDI device is supported. This +constraint may change in the future. + +# MAXIMUM NUMBER OF ENDPOINTS +Although the USB MIDI 1.0 Class specification allows an arbitrary number +of endpoints, this driver supports at most one USB BULK DATA IN endpoint +and one USB BULK DATA OUT endpoint. Each endpoint can support up to 16 +virtual cables. If a device has multiple IN endpoints or multiple OUT +endpoints, it will fail to enumerate. + +Most USB MIDI devices contain both an IN endpoint and an OUT endpoint, +but not all do. For example, some USB pedals only support an OUT endpoint. +This driver allows that. + +# PUBLIC API +Applications interact with this driver via 8-bit buffers of MIDI messages +formed using the rules for sending bytes on a 5-pin DIN cable per the +original MIDI 1.0 specification. + +To send a message to a device, the Host application composes a sequence +of status and data bytes in a byte array and calls the API function. +The arguments of the function are a pointer to the byte array, the number +of bytes in the array, and the target virtual cable number 0-15. + +When the host driver receives a message from the device, the host driver +will call a callback function that the host application registers. This +callback function contains a pointer to a message buffer, a message length, +and the virtual cable number of the message buffer. One complete bulk IN +endpoint transfer might contain multiple messages targeted to different +virtual cables. + +# SUBCLASS AUDIO CONTROL +A MIDI device does not absolutely need to have an Audio Control Interface, +unless it adheres to the USB Audio Class 2 spec, but many devices +have them even if the devices do not have an audio streaming interface. +Because this driver does not support audio streaming, the descriptor parser +will skip past any audio control interface and audio streaming interface +and open only the MIDI interface. + +An audio streaming host driver can use this driver by passing a pointer +to the MIDI interface descriptor that is found after the audio streaming +interface to the midih_open() function. That is, an audio streaming host +driver would parse the audio control interface descriptor and then the +audio streaming interface and endpoint descriptors. When the next descriptor +pointer points to a MIDI interface descriptor, call midih_open() with that +descriptor pointer. + +# CLASS SPECIFIC INTERFACE AND REQUESTS +The host driver does not make use of the information in the class specific +interface descriptors. In the future, a public API could be created to +retrieve the string descriptors for the names of each ELEMENT, +IN JACK and OUT JACK, and how the device describes the connections. + +This driver also does not support class specific requests to control +ELEMENT items, nor does it support non-MIDI Streaming bulk endpoints. + +# MIDI CLASS SPECIFIC DESCRIPTOR TOTAL LENGTH FIELD IGNORED +I have observed at least one keyboard by a leading manufacturer that +sets the wTotalLength field of the Class-Specific MS Interface Header +Descriptor to include the length of the MIDIStreaming Endpoint +Descriptors. This is wrong per my reading of the specification. + +# MESSAGE BUFFER DETAILS +Messages buffers composed from USB data received on the IN endpoint will never contain +running status because USB MIDI 1.0 class does not support that. Messages +buffers to be sent to the device on the OUT endpoint may contain running status +(the message might come from a UART data stream from a 5-pin DIN MIDI IN +cable on the host, for example). The driver may in the future correctly compose +4-byte USB MIDI Class packets using the running status if need be. However, +it does not currently do that. Also, use of running status is not a good idea +overall because a single byte error can really mess up the data stream with no +way to recover until the next non-real time status byte is in the message buffer. + +Message buffers to be sent to the device may contain Real time messages +such as MIDI clock. Real time messages may be inserted in the message +byte stream between status and data bytes of another message without disrupting +the running status. However, because MIDI 1.0 class messages are sent +as four byte packets, a real-time message so inserted will be re-ordered +to be sent to the device in a new 4-byte packet immediately before the +interrupted data stream. + +Real time messages the device sends to the host can only appear between +the status byte and data bytes of the message in System Exclusive messages +that are longer than 3 bytes. + +# POORLY FORMED USB MIDI DATA PACKETS FROM THE DEVICE +Some devices do not properly encode the code index number (CIN) for the +MIDI message status byte even though the 3-byte data payload correctly encodes +the MIDI message. This driver looks to the byte after the CIN byte to decide +how many bytes to place in the message buffer. + +Some devices do not properly encode the virtual cable number. If the virtual +cable number in the CIN data byte of the packet is not less than bNumEmbMIDIJack +for that endpoint, then the host driver assumes virtual cable 0 and does not +report an error. + +Some MIDI devices will always send back exactly wMaxPacketSize bytes on +every endpoint even if only one 4-byte packet is required (e.g., NOTE ON). +These devices send packets with 4 packet bytes 0. This driver ignores all +zero packets without reporting an error. + +# ENUMERATION FAILURES +The host may fail to enumerate a device if it has too many endpoints, if it has +if it has a Standard MS Transfer Bulk Data Endpoint Descriptor (not supported), +if it has a poorly formed descriptor, or if the descriptor is too long for +the host to read the whole thing. diff --git a/libraries/Adafruit_TinyUSB_Arduino/src/class/midi/midi.h b/libraries/Adafruit_TinyUSB_Arduino/src/class/midi/midi.h new file mode 100644 index 0000000..cd67640 --- /dev/null +++ b/libraries/Adafruit_TinyUSB_Arduino/src/class/midi/midi.h @@ -0,0 +1,201 @@ +/* + * 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. + * + * This file is part of the TinyUSB stack. + */ + +#ifndef TUSB_MIDI_H_ +#define TUSB_MIDI_H_ + +#include "common/tusb_common.h" + +#ifdef __cplusplus + extern "C" { +#endif + +//--------------------------------------------------------------------+ +// Constants +//--------------------------------------------------------------------+ +enum { + MIDI_VERSION_1_0 = 0x0100, + MIDI_VERSION_2_0 = 0x0200, +}; + +typedef enum { + MIDI_CS_INTERFACE_HEADER = 0x01, + MIDI_CS_INTERFACE_IN_JACK = 0x02, + MIDI_CS_INTERFACE_OUT_JACK = 0x03, + MIDI_CS_INTERFACE_ELEMENT = 0x04, +} midi_cs_interface_subtype_t; + +typedef enum { + MIDI_CS_ENDPOINT_GENERAL = 0x01, + MIDI_CS_ENDPOINT_GENERAL_2_0 = 0x02, +} midi_cs_endpoint_subtype_t; + +typedef enum { + MIDI_JACK_EMBEDDED = 0x01, + MIDI_JACK_EXTERNAL = 0x02 +} midi_jack_type_t; + +typedef enum { + MIDI_CIN_MISC = 0, + MIDI_CIN_CABLE_EVENT = 1, + MIDI_CIN_SYSCOM_2BYTE = 2, // 2 byte system common message e.g MTC, SongSelect + MIDI_CIN_SYSCOM_3BYTE = 3, // 3 byte system common message e.g SPP + MIDI_CIN_SYSEX_START = 4, // SysEx starts or continue + MIDI_CIN_SYSEX_END_1BYTE = 5, // SysEx ends with 1 data, or 1 byte system common message + MIDI_CIN_SYSEX_END_2BYTE = 6, // SysEx ends with 2 data + MIDI_CIN_SYSEX_END_3BYTE = 7, // SysEx ends with 3 data + MIDI_CIN_NOTE_OFF = 8, + MIDI_CIN_NOTE_ON = 9, + MIDI_CIN_POLY_KEYPRESS = 10, + MIDI_CIN_CONTROL_CHANGE = 11, + MIDI_CIN_PROGRAM_CHANGE = 12, + MIDI_CIN_CHANNEL_PRESSURE = 13, + MIDI_CIN_PITCH_BEND_CHANGE = 14, + MIDI_CIN_1BYTE_DATA = 15 +} midi_code_index_number_t; + +// MIDI 1.0 status byte +enum { + //------------- System Exclusive -------------// + MIDI_STATUS_SYSEX_START = 0xF0, + MIDI_STATUS_SYSEX_END = 0xF7, + + //------------- System Common -------------// + MIDI_STATUS_SYSCOM_TIME_CODE_QUARTER_FRAME = 0xF1, + MIDI_STATUS_SYSCOM_SONG_POSITION_POINTER = 0xF2, + MIDI_STATUS_SYSCOM_SONG_SELECT = 0xF3, + // F4, F5 is undefined + MIDI_STATUS_SYSCOM_TUNE_REQUEST = 0xF6, + + //------------- System RealTime -------------// + MIDI_STATUS_SYSREAL_TIMING_CLOCK = 0xF8, + // 0xF9 is undefined + MIDI_STATUS_SYSREAL_START = 0xFA, + MIDI_STATUS_SYSREAL_CONTINUE = 0xFB, + MIDI_STATUS_SYSREAL_STOP = 0xFC, + // 0xFD is undefined + MIDI_STATUS_SYSREAL_ACTIVE_SENSING = 0xFE, + MIDI_STATUS_SYSREAL_SYSTEM_RESET = 0xFF, +}; + +enum { + MIDI_MAX_DATA_VAL = 0x7F, +}; + +//--------------------------------------------------------------------+ +// Class Specific Descriptor +//--------------------------------------------------------------------+ + +/// MIDI Interface Header Descriptor +typedef struct TU_ATTR_PACKED { + uint8_t bLength; ///< Size of this descriptor in bytes. + uint8_t bDescriptorType; ///< must be TUSB_DESC_CS_INTERFACE + uint8_t bDescriptorSubType;///< Descriptor SubType + uint16_t bcdMSC; ///< MidiStreaming SubClass release number in Binary-Coded Decimal + uint16_t wTotalLength; +} midi_desc_header_t; +TU_VERIFY_STATIC(sizeof(midi_desc_header_t) == 7, "size is not correct"); + +/// MIDI In Jack Descriptor +typedef struct TU_ATTR_PACKED { + uint8_t bLength; ///< Size of this descriptor in bytes. + uint8_t bDescriptorType; ///< Descriptor Type, must be Class-Specific + uint8_t bDescriptorSubType;///< Descriptor SubType + uint8_t bJackType; ///< Embedded or External + uint8_t bJackID; ///< Unique ID for MIDI IN Jack + uint8_t iJack; ///< string descriptor +} midi_desc_in_jack_t; +TU_VERIFY_STATIC(sizeof(midi_desc_in_jack_t) == 6, "size is not correct"); + +/// MIDI Out Jack Descriptor with multiple input pins +#define midi_desc_out_jack_n_t(input_num) \ + struct TU_ATTR_PACKED { \ + uint8_t bLength; \ + uint8_t bDescriptorType; \ + uint8_t bDescriptorSubType; \ + uint8_t bJackType; \ + uint8_t bJackID; \ + uint8_t bNrInputPins; \ + struct TU_ATTR_PACKED { \ + uint8_t baSourceID; \ + uint8_t baSourcePin; \ + } input[input_num]; \ + uint8_t iJack; \ + } + +typedef midi_desc_out_jack_n_t(1) midi_desc_out_jack_1in_t; // 1 input +typedef midi_desc_out_jack_1in_t midi_desc_out_jack_t; // backward compatible +TU_VERIFY_STATIC(sizeof(midi_desc_out_jack_1in_t) == 7 + 2 * 1, "size is not correct"); + +/// MIDI Element Descriptor with multiple pins +#define midi_desc_element_n_t(input_num) \ + struct TU_ATTR_PACKED { \ + uint8_t bLength; \ + uint8_t bDescriptorType; \ + uint8_t bDescriptorSubType; \ + uint8_t bElementID; \ + uint8_t bNrInputPins; \ + struct TU_ATTR_PACKED { \ + uint8_t baSourceID; \ + uint8_t baSourcePin; \ + } pins[input_num]; \ + uint8_t bNrOutputPins; \ + uint8_t bInTerminalLink; \ + uint8_t bOutTerminalLink; \ + uint8_t bElCapsSize; \ + uint16_t bmElementCaps; \ + uint8_t iElement; \ + } + +// This descriptor follows the standard bulk data endpoint descriptor +#define midi_desc_cs_endpoint_n_t(jack_num) \ + struct TU_ATTR_PACKED { \ + uint8_t bLength; \ + uint8_t bDescriptorType; \ + uint8_t bDescriptorSubType; \ + uint8_t bNumEmbMIDIJack; \ + uint8_t baAssocJackID[jack_num]; \ + } + +typedef midi_desc_cs_endpoint_n_t() midi_desc_cs_endpoint_t; // empty/flexible jack list +typedef midi_desc_cs_endpoint_n_t(1) midi_desc_cs_endpoint_1jack_t; + +TU_VERIFY_STATIC(sizeof(midi_desc_cs_endpoint_1jack_t) == 4+1, "size is not correct"); + +//--------------------------------------------------------------------+ +// For Internal Driver Use +//--------------------------------------------------------------------+ +typedef struct { + uint8_t buffer[4]; + uint8_t index; + uint8_t total; +} midi_driver_stream_t; + +#ifdef __cplusplus + } +#endif + +#endif diff --git a/libraries/Adafruit_TinyUSB_Arduino/src/class/midi/midi_device.c b/libraries/Adafruit_TinyUSB_Arduino/src/class/midi/midi_device.c new file mode 100644 index 0000000..de4ff5d --- /dev/null +++ b/libraries/Adafruit_TinyUSB_Arduino/src/class/midi/midi_device.c @@ -0,0 +1,571 @@ +/* + * 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. + * + * This file is part of the TinyUSB stack. + */ + +#include "tusb_option.h" + +#if CFG_TUD_ENABLED && CFG_TUD_MIDI + +//--------------------------------------------------------------------+ +// INCLUDE +//--------------------------------------------------------------------+ +#include "device/usbd.h" +#include "device/usbd_pvt.h" + +#include "midi_device.h" + +//--------------------------------------------------------------------+ +// Weak stubs: invoked if no strong implementation is available +//--------------------------------------------------------------------+ +TU_ATTR_WEAK void tud_midi_rx_cb(uint8_t itf) { + (void)itf; +} + +//--------------------------------------------------------------------+ +// MACRO CONSTANT TYPEDEF +//--------------------------------------------------------------------+ +typedef struct { + uint8_t rhport; + uint8_t itf_num; + + // For Stream read()/write() API + // Messages are always 4 bytes long, queue them for reading and writing so the + // callers can use the Stream interface with single-byte read/write calls. + midi_driver_stream_t stream_write; + midi_driver_stream_t stream_read; + + /*------------- From this point, data is not cleared by bus reset -------------*/ + // Endpoint stream + struct { + tu_edpt_stream_t tx; + tu_edpt_stream_t rx; + + uint8_t rx_ff_buf[CFG_TUD_MIDI_RX_BUFSIZE]; + uint8_t tx_ff_buf[CFG_TUD_MIDI_TX_BUFSIZE]; + } ep_stream; +} midid_interface_t; + +#define ITF_MEM_RESET_SIZE offsetof(midid_interface_t, ep_stream) + +static midid_interface_t _midid_itf[CFG_TUD_MIDI]; + + #if CFG_TUD_EDPT_DEDICATED_HWFIFO == 0 +// Endpoint Transfer buffer: not used if dedicated hw FIFO is available +typedef struct { + TUD_EPBUF_DEF(epin, CFG_TUD_MIDI_TX_EPSIZE); + TUD_EPBUF_DEF(epout, CFG_TUD_MIDI_RX_EPSIZE); +} midid_epbuf_t; + +CFG_TUD_MEM_SECTION static midid_epbuf_t _midid_epbuf[CFG_TUD_MIDI]; + #endif + +//--------------------------------------------------------------------+ +// INTERNAL OBJECT & FUNCTION DECLARATION +//--------------------------------------------------------------------+ +bool tud_midi_n_mounted (uint8_t itf) { + midid_interface_t *p_midi = &_midid_itf[itf]; + const bool tx_opened = tu_edpt_stream_is_opened(&p_midi->ep_stream.tx); + const bool rx_opened = tu_edpt_stream_is_opened(&p_midi->ep_stream.rx); + return tx_opened && rx_opened; +} + +//--------------------------------------------------------------------+ +// READ API +//--------------------------------------------------------------------+ +uint32_t tud_midi_n_available(uint8_t itf, uint8_t cable_num) { + (void) cable_num; + const midid_interface_t *p_midi = &_midid_itf[itf]; + const midi_driver_stream_t *stream = &p_midi->stream_read; + const tu_edpt_stream_t *ep_str = &p_midi->ep_stream.rx; + + // when using with packet API stream total & index are both zero + return tu_edpt_stream_read_available(ep_str) + (uint8_t)(stream->total - stream->index); +} + +uint32_t tud_midi_n_stream_read(uint8_t itf, uint8_t cable_num, void *buffer, uint32_t bufsize) { + (void) cable_num; + TU_VERIFY(buffer != NULL && bufsize > 0, 0); + + uint8_t *buf8 = (uint8_t *)buffer; + midid_interface_t *p_midi = &_midid_itf[itf]; + midi_driver_stream_t *stream = &p_midi->stream_read; + + uint32_t total_read = 0; + while (bufsize > 0) { + // Get new packet from fifo, then set packet expected bytes + if (stream->total == 0) { + if (!tud_midi_n_packet_read(itf, stream->buffer)) { + return total_read; // return if there is no more data from fifo + } + + const uint8_t code_index = stream->buffer[0] & 0x0f; + + // MIDI 1.0 Table 4-1: Code Index Number Classifications + switch (code_index) { + case MIDI_CIN_MISC: + case MIDI_CIN_CABLE_EVENT: + // These are reserved and unused, possibly issue somewhere, skip this packet + return 0; + + case MIDI_CIN_SYSEX_END_1BYTE: + case MIDI_CIN_1BYTE_DATA: + stream->total = 1; + break; + + case MIDI_CIN_SYSCOM_2BYTE : + case MIDI_CIN_SYSEX_END_2BYTE : + case MIDI_CIN_PROGRAM_CHANGE : + case MIDI_CIN_CHANNEL_PRESSURE : + stream->total = 2; + break; + + default: + stream->total = 3; + break; + } + } + + // Copy data up to bufsize + const uint8_t count = (uint8_t)tu_min32((uint32_t)(stream->total - stream->index), bufsize); + + // Skip the header (1st byte) in the buffer + TU_VERIFY(0 == tu_memcpy_s(buf8, bufsize, stream->buffer + 1 + stream->index, count)); + + total_read += count; + stream->index += count; + buf8 += count; + bufsize -= count; + + // complete current event packet, reset stream + if (stream->total == stream->index) { + stream->index = 0; + stream->total = 0; + } + } + + return total_read; +} + +// Note: this function shares stream->buffer with tud_midi_n_stream_read(). +// Do not mix calls to both functions on the same interface. +uint32_t tud_midi_n_demux_stream_read(uint8_t itf, uint8_t *p_cable_num, void *buffer, uint32_t bufsize) { + TU_VERIFY(p_cable_num != NULL && buffer != NULL && bufsize > 0, 0); + + midid_interface_t *p_midi = &_midid_itf[itf]; + midi_driver_stream_t *stream = &p_midi->stream_read; + tu_edpt_stream_t *ep_str = &p_midi->ep_stream.rx; + + uint8_t *buf8 = (uint8_t *)buffer; + uint32_t total_read = 0; + + // Initialize to invalid cable so callers can detect "no data" even when + // the return value is 0. + *p_cable_num = 0xff; + + // If there are leftover bytes from a previous partial read, return them first + if (stream->total > 0) { + *p_cable_num = (stream->buffer[0] >> 4) & 0x0f; + const uint8_t count = (uint8_t)tu_min32((uint32_t)(stream->total - stream->index), bufsize); + TU_VERIFY(0 == tu_memcpy_s(buf8, bufsize, stream->buffer + 1 + stream->index, count)); + + total_read += count; + stream->index += count; + buf8 += count; + bufsize -= count; + + if (stream->total == stream->index) { + stream->index = 0; + stream->total = 0; + } + + if (bufsize == 0) { + return total_read; + } + } + + while (bufsize > 0) { + // Peek at next packet header to get cable number without consuming + uint8_t one_byte; + if (!tu_edpt_stream_peek(ep_str, &one_byte)) { + break; + } + + const uint8_t next_cable = (one_byte >> 4) & 0x0f; + + // Stop if cable changed (covers both leftover-originated reads and + // freshly consumed packets — total_read > 0 in either case) + if (total_read > 0 && next_cable != *p_cable_num) { + break; + } + *p_cable_num = next_cable; + + // Consume the packet + if (!tud_midi_n_packet_read(itf, stream->buffer)) { + break; + } + + const uint8_t code_index = stream->buffer[0] & 0x0f; + uint8_t msg_bytes; + + // MIDI 1.0 Table 4-1: Code Index Number Classifications + switch (code_index) { + case MIDI_CIN_MISC: + case MIDI_CIN_CABLE_EVENT: + // Reserved and unused, skip this packet + continue; + + case MIDI_CIN_SYSEX_END_1BYTE: + case MIDI_CIN_1BYTE_DATA: + msg_bytes = 1; + break; + + case MIDI_CIN_SYSCOM_2BYTE: + case MIDI_CIN_SYSEX_END_2BYTE: + case MIDI_CIN_PROGRAM_CHANGE: + case MIDI_CIN_CHANNEL_PRESSURE: + msg_bytes = 2; + break; + + default: + msg_bytes = 3; + break; + } + + const uint8_t count = (uint8_t)tu_min32((uint32_t)msg_bytes, bufsize); + TU_VERIFY(0 == tu_memcpy_s(buf8, bufsize, stream->buffer + 1, count)); + + total_read += count; + buf8 += count; + bufsize -= count; + + if (count < msg_bytes) { + // Output buffer full, save remaining for next call + stream->total = msg_bytes; + stream->index = count; + } + } + + return total_read; +} + +bool tud_midi_n_packet_read(uint8_t itf, uint8_t packet[4]) { + midid_interface_t *p_midi = &_midid_itf[itf]; + tu_edpt_stream_t *ep_str = &p_midi->ep_stream.rx; + return 4 == tu_edpt_stream_read(ep_str, packet, 4); +} + +uint32_t tud_midi_n_packet_read_n(uint8_t itf, uint8_t packets[], uint32_t max_packets) { + midid_interface_t *p_midi = &_midid_itf[itf]; + tu_edpt_stream_t *ep_str = &p_midi->ep_stream.rx; + const uint32_t num_read = tu_edpt_stream_read(ep_str, packets, 4u * max_packets); + return num_read >> 2u; +} + +//--------------------------------------------------------------------+ +// WRITE API +//--------------------------------------------------------------------+ +uint32_t tud_midi_n_stream_write(uint8_t itf, uint8_t cable_num, const uint8_t *buffer, uint32_t bufsize) { + midid_interface_t *p_midi = &_midid_itf[itf]; + midi_driver_stream_t *stream = &p_midi->stream_write; + tu_edpt_stream_t *ep_str = &p_midi->ep_stream.tx; + TU_VERIFY(tu_edpt_stream_is_opened(ep_str), 0); + + uint32_t i = 0; + while (i < bufsize) { + if (tu_edpt_stream_write_available(ep_str) < 4) { + break; + } + + const uint8_t data = buffer[i]; + i++; + + if (stream->index == 0) { + //------------- New event packet -------------// + const uint8_t msg = data >> 4; + + stream->index = 2; + stream->buffer[1] = data; + + // Check to see if we're still in a SysEx transmit. + if (((stream->buffer[0]) & 0xF) == MIDI_CIN_SYSEX_START) { + if (data == MIDI_STATUS_SYSEX_END) { + stream->buffer[0] = (uint8_t)((cable_num << 4) | MIDI_CIN_SYSEX_END_1BYTE); + stream->total = 2; + } else { + stream->total = 4; + } + } else if ((msg >= 0x8 && msg <= 0xB) || msg == 0xE) { + // Channel Voice Messages + stream->buffer[0] = (uint8_t)((cable_num << 4) | msg); + stream->total = 4; + } else if (msg == 0xC || msg == 0xD) { + // Channel Voice Messages, two-byte variants (Program Change and Channel Pressure) + stream->buffer[0] = (uint8_t)((cable_num << 4) | msg); + stream->total = 3; + } else if (msg == 0xf) { + // System message + if (data == MIDI_STATUS_SYSEX_START) { + stream->buffer[0] = MIDI_CIN_SYSEX_START; + stream->total = 4; + } else if (data == MIDI_STATUS_SYSCOM_TIME_CODE_QUARTER_FRAME || data == MIDI_STATUS_SYSCOM_SONG_SELECT) { + stream->buffer[0] = MIDI_CIN_SYSCOM_2BYTE; + stream->total = 3; + } else if (data == MIDI_STATUS_SYSCOM_SONG_POSITION_POINTER) { + stream->buffer[0] = MIDI_CIN_SYSCOM_3BYTE; + stream->total = 4; + } else { + stream->buffer[0] = MIDI_CIN_SYSEX_END_1BYTE; + stream->total = 2; + } + stream->buffer[0] |= (uint8_t)(cable_num << 4); + } else { + // Pack individual bytes if we don't support packing them into words. + stream->buffer[0] = (uint8_t)(cable_num << 4 | 0xf); + stream->buffer[2] = 0; + stream->buffer[3] = 0; + stream->total = 2; // index already set to 2 + } + } else { + //------------- On-going (buffering) packet -------------// + TU_ASSERT(stream->index < 4, i); + stream->buffer[stream->index] = data; + stream->index++; + + // See if this byte ends a SysEx. + if ((stream->buffer[0] & 0xF) == MIDI_CIN_SYSEX_START && data == MIDI_STATUS_SYSEX_END) { + stream->buffer[0] = (uint8_t)((cable_num << 4) | (MIDI_CIN_SYSEX_START + (stream->index - 1))); + stream->total = stream->index; + } + } + + // Send out packet + if (stream->index == stream->total) { + // zeroes unused bytes + for (uint8_t idx = stream->total; idx < 4; idx++) { + stream->buffer[idx] = 0; + } + + const uint32_t count = tu_edpt_stream_write(ep_str, stream->buffer, 4); + + // complete current event packet, reset stream + stream->index = stream->total = 0; + + // FIFO overflown, since we already check fifo remaining. It is probably race condition + TU_ASSERT(count == 4, i); + } + } + + (void)tu_edpt_stream_write_xfer(ep_str); + + return i; +} + +bool tud_midi_n_packet_write (uint8_t itf, const uint8_t packet[4]) { + midid_interface_t *p_midi = &_midid_itf[itf]; + tu_edpt_stream_t *ep_str = &p_midi->ep_stream.tx; + TU_VERIFY(tu_edpt_stream_is_opened(ep_str)); + + TU_VERIFY(tu_edpt_stream_write_available(ep_str) >= 4); + TU_VERIFY(tu_edpt_stream_write(ep_str, packet, 4) > 0); + (void)tu_edpt_stream_write_xfer(ep_str); + + return true; +} + +uint32_t tud_midi_n_packet_write_n(uint8_t itf, const uint8_t packets[], uint32_t n_packets) { + midid_interface_t *p_midi = &_midid_itf[itf]; + tu_edpt_stream_t *ep_str = &p_midi->ep_stream.tx; + TU_VERIFY(tu_edpt_stream_is_opened(ep_str), 0); + + uint32_t n_bytes = tu_edpt_stream_write_available(ep_str); + n_bytes = tu_min32(tu_align4(n_bytes), n_packets << 2u); + + const uint32_t n_write = tu_edpt_stream_write(ep_str, packets, n_bytes); + (void)tu_edpt_stream_write_xfer(ep_str); + + return n_write >> 2u; +} + +//--------------------------------------------------------------------+ +// USBD Driver API +//--------------------------------------------------------------------+ +void midid_init(void) { + tu_memclr(_midid_itf, sizeof(_midid_itf)); + for (uint8_t i = 0; i < CFG_TUD_MIDI; i++) { + midid_interface_t *p_midi = &_midid_itf[i]; + + #if CFG_TUD_EDPT_DEDICATED_HWFIFO + uint8_t *epout_buf = NULL; + uint8_t *epin_buf = NULL; + #else + midid_epbuf_t *p_epbuf = &_midid_epbuf[i]; + uint8_t *epout_buf = p_epbuf->epout; + uint8_t *epin_buf = p_epbuf->epin; + #endif + + tu_edpt_stream_init(&p_midi->ep_stream.rx, false, false, false, p_midi->ep_stream.rx_ff_buf, + CFG_TUD_MIDI_RX_BUFSIZE, epout_buf); + + tu_edpt_stream_init(&p_midi->ep_stream.tx, false, true, false, p_midi->ep_stream.tx_ff_buf, CFG_TUD_MIDI_TX_BUFSIZE, + epin_buf); + } +} + +bool midid_deinit(void) { + for (uint8_t i = 0; i < CFG_TUD_MIDI; i++) { + midid_interface_t *p_midi = &_midid_itf[i]; + tu_edpt_stream_deinit(&p_midi->ep_stream.rx); + tu_edpt_stream_deinit(&p_midi->ep_stream.tx); + } + return true; +} + +void midid_reset(uint8_t rhport) { + (void)rhport; + for (uint8_t i = 0; i < CFG_TUD_MIDI; i++) { + midid_interface_t *p_midi = &_midid_itf[i]; + tu_memclr(p_midi, ITF_MEM_RESET_SIZE); + + tu_edpt_stream_clear(&p_midi->ep_stream.rx); + tu_edpt_stream_close(&p_midi->ep_stream.rx); + + tu_edpt_stream_clear(&p_midi->ep_stream.tx); + tu_edpt_stream_close(&p_midi->ep_stream.tx); + } +} + +TU_ATTR_ALWAYS_INLINE static inline uint8_t find_midi_itf(uint8_t ep_addr) { + for (uint8_t idx = 0; idx < CFG_TUD_MIDI; idx++) { + const midid_interface_t *p_midi = &_midid_itf[idx]; + if (ep_addr == p_midi->ep_stream.rx.ep_addr || ep_addr == p_midi->ep_stream.tx.ep_addr) { + return idx; + } + } + return TUSB_INDEX_INVALID_8; +} + +uint16_t midid_open(uint8_t rhport, const tusb_desc_interface_t *desc_itf, uint16_t max_len) { + const uint8_t *p_desc = (const uint8_t *)desc_itf; + const uint8_t *desc_end = p_desc + max_len; + + // 1st Interface is Audio Control v1 (optional) + if (TUSB_CLASS_AUDIO == desc_itf->bInterfaceClass && + AUDIO_SUBCLASS_CONTROL == desc_itf->bInterfaceSubClass && + AUDIO_FUNC_PROTOCOL_CODE_UNDEF == desc_itf->bInterfaceProtocol) { + p_desc = tu_desc_next(desc_itf); + // Skip Class Specific descriptors + while (tu_desc_in_bounds(p_desc, desc_end) && TUSB_DESC_CS_INTERFACE == tu_desc_type(p_desc)) { + p_desc = tu_desc_next(p_desc); + } + } + + // 2nd Interface is MIDI Streaming + TU_VERIFY(TUSB_DESC_INTERFACE == tu_desc_type(p_desc), 0); + const tusb_desc_interface_t* desc_midi = (const tusb_desc_interface_t*) p_desc; + + TU_VERIFY(TUSB_CLASS_AUDIO == desc_midi->bInterfaceClass && + AUDIO_SUBCLASS_MIDI_STREAMING == desc_midi->bInterfaceSubClass && + AUDIO_FUNC_PROTOCOL_CODE_UNDEF == desc_midi->bInterfaceProtocol, + 0); + + uint8_t idx = find_midi_itf(0); // find unused interface + TU_ASSERT(idx < CFG_TUD_MIDI, 0); + midid_interface_t *p_midi = &_midid_itf[idx]; + + p_midi->rhport = rhport; + p_midi->itf_num = desc_midi->bInterfaceNumber; + (void) p_midi->itf_num; + + p_desc = tu_desc_next(p_desc); + + // Find and open endpoint descriptors + uint8_t found_ep = 0; + while ((found_ep < desc_midi->bNumEndpoints) && tu_desc_in_bounds(p_desc, desc_end)) { + if (TUSB_DESC_ENDPOINT == tu_desc_type(p_desc)) { + const tusb_desc_endpoint_t *desc_ep = (const tusb_desc_endpoint_t *)p_desc; + TU_ASSERT(usbd_edpt_open(rhport, desc_ep), 0); + const uint8_t ep_addr = ((const tusb_desc_endpoint_t *)p_desc)->bEndpointAddress; + + if (tu_edpt_dir(ep_addr) == TUSB_DIR_IN) { + tu_edpt_stream_t *stream_tx = &p_midi->ep_stream.tx; + tu_edpt_stream_open(stream_tx, rhport, desc_ep, CFG_TUD_MIDI_TX_EPSIZE); + tu_edpt_stream_clear(stream_tx); + } else { + tu_edpt_stream_t *stream_rx = &p_midi->ep_stream.rx; + tu_edpt_stream_open(stream_rx, rhport, desc_ep, tu_edpt_packet_size(desc_ep)); + tu_edpt_stream_clear(stream_rx); + TU_ASSERT(tu_edpt_stream_read_xfer(stream_rx) > 0, 0); // prepare to receive data + } + + p_desc = tu_desc_next(p_desc); // skip CS Endpoint descriptor + found_ep++; + } + + p_desc = tu_desc_next(p_desc); + } + + return (uint16_t)(p_desc - (const uint8_t *)desc_itf); +} + +// Invoked when a control transfer occurred on an interface of this class +// Driver response accordingly to the request and the transfer stage (setup/data/ack) +// return false to stall control endpoint (e.g unsupported request) +bool midid_control_xfer_cb(uint8_t rhport, uint8_t stage, const tusb_control_request_t* request) { + (void) rhport; (void) stage; (void) request; + return false; // driver doesn't support any request yet +} + +bool midid_xfer_cb(uint8_t rhport, uint8_t ep_addr, xfer_result_t result, uint32_t xferred_bytes) { + (void)rhport; + (void)result; + + uint8_t idx = find_midi_itf(ep_addr); + TU_ASSERT(idx < CFG_TUD_MIDI); + midid_interface_t *p_midi = &_midid_itf[idx]; + + tu_edpt_stream_t *ep_st_rx = &p_midi->ep_stream.rx; + tu_edpt_stream_t *ep_st_tx = &p_midi->ep_stream.tx; + + if (ep_addr == ep_st_rx->ep_addr) { + // Received new data: put into stream's fifo + if (result == XFER_RESULT_SUCCESS) { + tu_edpt_stream_read_xfer_complete(ep_st_rx, xferred_bytes); + tud_midi_rx_cb(idx); // invoke callback + } + tu_edpt_stream_read_xfer(ep_st_rx); // prepare for next data + } else if (ep_addr == ep_st_tx->ep_addr && result == XFER_RESULT_SUCCESS) { + // sent complete: try to send more if possible + if (0 == tu_edpt_stream_write_xfer(ep_st_tx)) { + // If there is no data left, a ZLP should be sent if needed + (void)tu_edpt_stream_write_zlp_if_needed(ep_st_tx, xferred_bytes); + } + } else { + return false; + } + + return true; +} + +#endif diff --git a/libraries/Adafruit_TinyUSB_Arduino/src/class/midi/midi_device.h b/libraries/Adafruit_TinyUSB_Arduino/src/class/midi/midi_device.h new file mode 100644 index 0000000..57eabec --- /dev/null +++ b/libraries/Adafruit_TinyUSB_Arduino/src/class/midi/midi_device.h @@ -0,0 +1,153 @@ +/* + * 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. + * + * This file is part of the TinyUSB stack. + */ + +#ifndef TUSB_MIDI_DEVICE_H_ +#define TUSB_MIDI_DEVICE_H_ + +#include "class/audio/audio.h" +#include "midi.h" + +//--------------------------------------------------------------------+ +// Class Driver Configuration +//--------------------------------------------------------------------+ + +#ifndef CFG_TUD_MIDI_RX_EPSIZE + #ifdef CFG_TUD_MIDI_EP_BUFSIZE + #define CFG_TUD_MIDI_RX_EPSIZE CFG_TUD_MIDI_EP_BUFSIZE + #else + #define CFG_TUD_MIDI_RX_EPSIZE TUD_EPSIZE_BULK_MAX + #endif +#endif + +#ifndef CFG_TUD_MIDI_TX_EPSIZE + #ifdef CFG_TUD_MIDI_EP_BUFSIZE + #define CFG_TUD_MIDI_TX_EPSIZE CFG_TUD_MIDI_EP_BUFSIZE + #else + #define CFG_TUD_MIDI_TX_EPSIZE TUD_EPSIZE_BULK_MAX + #endif +#endif + +#ifdef __cplusplus +extern "C" { +#endif + +//--------------------------------------------------------------------+ +// Application Callback API (optional) +//--------------------------------------------------------------------+ +void tud_midi_rx_cb(uint8_t itf); + +//--------------------------------------------------------------------+ +// Application API (Multiple Interfaces) +// CFG_TUD_MIDI > 1 +//--------------------------------------------------------------------+ + +// Check if midi interface is mounted +bool tud_midi_n_mounted(uint8_t itf); + +// Get the number of bytes available for reading +uint32_t tud_midi_n_available(uint8_t itf, uint8_t cable_num); + +// Read byte stream (legacy) +uint32_t tud_midi_n_stream_read(uint8_t itf, uint8_t cable_num, void *buffer, uint32_t bufsize); + +// Read byte stream with cable demultiplexing: returns the cable number of the +// data that was read. Reads from a single cable per call; stops when the next +// packet belongs to a different cable so the caller can dispatch per-cable. +// Note: shares internal state with tud_midi_n_stream_read(); do not mix both +// on the same interface. +uint32_t tud_midi_n_demux_stream_read(uint8_t itf, uint8_t *p_cable_num, void *buffer, uint32_t bufsize); + +// Write byte Stream (legacy) +uint32_t tud_midi_n_stream_write(uint8_t itf, uint8_t cable_num, const uint8_t *buffer, uint32_t bufsize); + +// Read an event 4-byte packet +bool tud_midi_n_packet_read(uint8_t itf, uint8_t packet[4]); + +// Read multiple event packets, return number of read packets +uint32_t tud_midi_n_packet_read_n(uint8_t itf, uint8_t packets[], uint32_t max_packets); + +// Write an event 4-byte packet +bool tud_midi_n_packet_write(uint8_t itf, const uint8_t packet[4]); + +// Write multiple event packets, return number of written packets +uint32_t tud_midi_n_packet_write_n(uint8_t itf, const uint8_t packets[], uint32_t n_packets); + +//--------------------------------------------------------------------+ +// Application API (Single Interface) +//--------------------------------------------------------------------+ +TU_ATTR_ALWAYS_INLINE static inline bool tud_midi_mounted(void) { + return tud_midi_n_mounted(0); +} + +TU_ATTR_ALWAYS_INLINE static inline uint32_t tud_midi_available(void) { + return tud_midi_n_available(0, 0); +} + +TU_ATTR_ALWAYS_INLINE static inline uint32_t tud_midi_stream_read(void *buffer, uint32_t bufsize) { + return tud_midi_n_stream_read(0, 0, buffer, bufsize); +} + +TU_ATTR_ALWAYS_INLINE static inline uint32_t +tud_midi_demux_stream_read(uint8_t *p_cable_num, void *buffer, uint32_t bufsize) { + return tud_midi_n_demux_stream_read(0, p_cable_num, buffer, bufsize); +} + +TU_ATTR_ALWAYS_INLINE static inline uint32_t +tud_midi_stream_write(uint8_t cable_num, const uint8_t *buffer, uint32_t bufsize) { + return tud_midi_n_stream_write(0, cable_num, buffer, bufsize); +} + +TU_ATTR_ALWAYS_INLINE static inline bool tud_midi_packet_read(uint8_t packet[4]) { + return tud_midi_n_packet_read(0, packet); +} + +TU_ATTR_ALWAYS_INLINE static inline uint32_t tud_midi_packet_read_n(uint8_t packets[], uint32_t max_packets) { + return tud_midi_n_packet_read_n(0, packets, max_packets); +} + +TU_ATTR_ALWAYS_INLINE static inline bool tud_midi_packet_write(const uint8_t packet[4]) { + return tud_midi_n_packet_write(0, packet); +} + +TU_ATTR_ALWAYS_INLINE static inline uint32_t tud_midi_packet_write_n(const uint8_t packets[], uint32_t n_packets) { + return tud_midi_n_packet_write_n(0, packets, n_packets); +} + +//--------------------------------------------------------------------+ +// Internal Class Driver API +//--------------------------------------------------------------------+ +void midid_init(void); +bool midid_deinit(void); +void midid_reset(uint8_t rhport); +uint16_t midid_open(uint8_t rhport, const tusb_desc_interface_t *itf_desc, uint16_t max_len); +bool midid_control_xfer_cb(uint8_t rhport, uint8_t stage, const tusb_control_request_t *request); +bool midid_xfer_cb(uint8_t rhport, uint8_t edpt_addr, xfer_result_t result, uint32_t xferred_bytes); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/libraries/Adafruit_TinyUSB_Arduino/src/class/midi/midi_host.c b/libraries/Adafruit_TinyUSB_Arduino/src/class/midi/midi_host.c new file mode 100644 index 0000000..a535464 --- /dev/null +++ b/libraries/Adafruit_TinyUSB_Arduino/src/class/midi/midi_host.c @@ -0,0 +1,657 @@ +/* + * 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. + * + * This file is part of the TinyUSB stack. + */ + +#include "tusb_option.h" + +#if (CFG_TUH_ENABLED && CFG_TUH_MIDI) + +#include "host/usbh.h" +#include "host/usbh_pvt.h" + +#include "midi_host.h" + +// Level where CFG_TUSB_DEBUG must be at least for this driver is logged +#ifndef CFG_TUH_MIDI_LOG_LEVEL + #define CFG_TUH_MIDI_LOG_LEVEL CFG_TUH_LOG_LEVEL +#endif + +#define TU_LOG_DRV(...) TU_LOG(CFG_TUH_MIDI_LOG_LEVEL, __VA_ARGS__) + +//--------------------------------------------------------------------+ +// Weak stubs: invoked if no strong implementation is available +//--------------------------------------------------------------------+ +TU_ATTR_WEAK void tuh_midi_descriptor_cb(uint8_t idx, const tuh_midi_descriptor_cb_t * desc_cb_data) { (void) idx; (void) desc_cb_data; } +TU_ATTR_WEAK void tuh_midi_mount_cb(uint8_t idx, const tuh_midi_mount_cb_t* mount_cb_data) { (void) idx; (void) mount_cb_data; } +TU_ATTR_WEAK void tuh_midi_umount_cb(uint8_t idx) { (void) idx; } +TU_ATTR_WEAK void tuh_midi_rx_cb(uint8_t idx, uint32_t xferred_bytes) { (void) idx; (void) xferred_bytes; } +TU_ATTR_WEAK void tuh_midi_tx_cb(uint8_t idx, uint32_t xferred_bytes) { (void) idx; (void) xferred_bytes; } + +//--------------------------------------------------------------------+ +// MACRO CONSTANT TYPEDEF +//--------------------------------------------------------------------+ + +typedef struct { + uint8_t daddr; + uint8_t bInterfaceNumber; // interface number of MIDI streaming + uint8_t iInterface; + uint8_t itf_count; // number of interfaces including Audio Control + MIDI streaming + + uint8_t rx_cable_count; // IN endpoint CS descriptor bNumEmbMIDIJack value + uint8_t tx_cable_count; // OUT endpoint CS descriptor bNumEmbMIDIJack value + + #if CFG_TUH_MIDI_STREAM_API + // For Stream read()/write() API + // Messages are always 4 bytes long, queue them for reading and writing so the + // callers can use the Stream interface with single-byte read/write calls. + midi_driver_stream_t stream_write; + midi_driver_stream_t stream_read; + #endif + + // Endpoint stream + struct { + tu_edpt_stream_t tx; + tu_edpt_stream_t rx; + + uint8_t rx_ff_buf[CFG_TUH_MIDI_RX_BUFSIZE]; + uint8_t tx_ff_buf[CFG_TUH_MIDI_TX_BUFSIZE]; + } ep_stream; + + bool mounted; +}midih_interface_t; + +typedef struct { + TUH_EPBUF_DEF(tx, TUH_EPSIZE_BULK_MAX); + TUH_EPBUF_DEF(rx, TUH_EPSIZE_BULK_MAX); +} midih_epbuf_t; + +static midih_interface_t _midi_host[CFG_TUH_MIDI]; +CFG_TUH_MEM_SECTION static midih_epbuf_t _midi_epbuf[CFG_TUH_MIDI]; + +//--------------------------------------------------------------------+ +// Helper +//--------------------------------------------------------------------+ +TU_ATTR_ALWAYS_INLINE static inline uint8_t find_new_midi_index(void) { + for (uint8_t idx = 0; idx < CFG_TUH_MIDI; idx++) { + if (_midi_host[idx].daddr == 0) { + return idx; + } + } + return TUSB_INDEX_INVALID_8; +} + +static inline uint8_t get_idx_by_ep_addr(uint8_t daddr, uint8_t ep_addr) { + for (uint8_t idx = 0; idx < CFG_TUH_MIDI; idx++) { + const midih_interface_t *p_midi = &_midi_host[idx]; + if ((p_midi->daddr == daddr) && + (ep_addr == p_midi->ep_stream.rx.ep_addr || ep_addr == p_midi->ep_stream.tx.ep_addr)) { + return idx; + } + } + return TUSB_INDEX_INVALID_8; +} + +//--------------------------------------------------------------------+ +// USBH API +//--------------------------------------------------------------------+ +bool midih_init(void) { + tu_memclr(&_midi_host, sizeof(_midi_host)); + for (int inst = 0; inst < CFG_TUH_MIDI; inst++) { + midih_interface_t *p_midi_host = &_midi_host[inst]; + tu_edpt_stream_init(&p_midi_host->ep_stream.rx, true, false, false, + p_midi_host->ep_stream.rx_ff_buf, CFG_TUH_MIDI_RX_BUFSIZE, _midi_epbuf[inst].rx); + tu_edpt_stream_init(&p_midi_host->ep_stream.tx, true, true, false, + p_midi_host->ep_stream.tx_ff_buf, CFG_TUH_MIDI_TX_BUFSIZE, _midi_epbuf[inst].tx); + } + return true; +} + +bool midih_deinit(void) { + for (size_t i = 0; i < CFG_TUH_MIDI; i++) { + midih_interface_t* p_midi = &_midi_host[i]; + tu_edpt_stream_deinit(&p_midi->ep_stream.rx); + tu_edpt_stream_deinit(&p_midi->ep_stream.tx); + } + return true; +} + +void midih_close(uint8_t daddr) { + for (uint8_t idx = 0; idx < CFG_TUH_MIDI; idx++) { + midih_interface_t* p_midi = &_midi_host[idx]; + if (p_midi->daddr == daddr) { + TU_LOG_DRV(" MIDI close addr = %u index = %u\r\n", daddr, idx); + tuh_midi_umount_cb(idx); + + p_midi->bInterfaceNumber = 0; + p_midi->rx_cable_count = 0; + p_midi->tx_cable_count = 0; + p_midi->daddr = 0; + p_midi->mounted = false; +#if CFG_TUH_MIDI_STREAM_API + tu_memclr(&p_midi->stream_read, sizeof(p_midi->stream_read)); + tu_memclr(&p_midi->stream_write, sizeof(p_midi->stream_write)); +#endif + tu_edpt_stream_close(&p_midi->ep_stream.rx); + tu_edpt_stream_close(&p_midi->ep_stream.tx); + } + } +} + +bool midih_xfer_cb(uint8_t dev_addr, uint8_t ep_addr, xfer_result_t result, uint32_t xferred_bytes) { + (void) result; + const uint8_t idx = get_idx_by_ep_addr(dev_addr, ep_addr); + TU_VERIFY(idx < CFG_TUH_MIDI); + midih_interface_t *p_midi = &_midi_host[idx]; + tu_edpt_stream_t *ep_str_rx = &p_midi->ep_stream.rx; + tu_edpt_stream_t *ep_str_tx = &p_midi->ep_stream.tx; + + if (ep_addr == ep_str_rx->ep_addr) { + // receive new data, put it into FIFO and invoke callback if available + // Note: some devices send back all zero packets even if there is no data ready + if (xferred_bytes && !tu_mem_is_zero(ep_str_rx->ep_buf, xferred_bytes)) { + tu_edpt_stream_read_xfer_complete(ep_str_rx, xferred_bytes); + tuh_midi_rx_cb(idx, xferred_bytes); + } + + tu_edpt_stream_read_xfer(ep_str_rx); // prepare for next transfer + } else if (ep_addr == ep_str_tx->ep_addr) { + tuh_midi_tx_cb(idx, xferred_bytes); + + if (0 == tu_edpt_stream_write_xfer(ep_str_tx)) { + // If there is no data left, a ZLP should be sent if + // xferred_bytes is multiple of EP size and not zero + tu_edpt_stream_write_zlp_if_needed(ep_str_tx, xferred_bytes); + } + } + + return true; +} + +//--------------------------------------------------------------------+ +// Enumeration +//--------------------------------------------------------------------+ +uint16_t midih_open(uint8_t rhport, uint8_t dev_addr, const tusb_desc_interface_t *desc_itf, uint16_t max_len) { + (void) rhport; + + TU_VERIFY(TUSB_CLASS_AUDIO == desc_itf->bInterfaceClass, 0); + const uint8_t *desc_start = (const uint8_t *)desc_itf; + const uint8_t *p_desc = desc_start; + const uint8_t *desc_end = desc_start + max_len; + + const uint8_t idx = find_new_midi_index(); + TU_VERIFY(idx < CFG_TUH_MIDI, 0); + midih_interface_t *p_midi = &_midi_host[idx]; + p_midi->itf_count = 0; + + tuh_midi_descriptor_cb_t desc_cb = { 0 }; + desc_cb.jack_num = 0; + + // There can be just a MIDI or an Audio + MIDI interface + // - If there is Audio Control Interface + Audio Header descriptor, then skip it. + // - If there is an Audio Control Interface + Audio Streaming Interface, then ignore the Audio Streaming Interface. + // Future: + // Note that if this driver is used with an USB Audio Streaming host driver, + // then call that driver first. If the MIDI interface comes before the + // audio streaming interface, then the audio driver will have to call this + // driver after parsing the audio control interface and then resume parsing + // the streaming audio interface. + if (AUDIO_SUBCLASS_CONTROL == desc_itf->bInterfaceSubClass) { + TU_VERIFY(max_len > 2 * sizeof(tusb_desc_interface_t) + sizeof(midi10_desc_cs_ac_interface_t), 0); + p_desc = tu_desc_next(p_desc); + TU_VERIFY(tu_desc_type(p_desc) == TUSB_DESC_CS_INTERFACE && + tu_desc_subtype(p_desc) == AUDIO10_CS_AC_INTERFACE_HEADER, + 0); + desc_cb.desc_audio_control = desc_itf; + + p_desc = tu_desc_next(p_desc); + desc_itf = (const tusb_desc_interface_t *)p_desc; + p_midi->itf_count = 1; + // skip non-interface and non-midi streaming descriptors + while (tu_desc_in_bounds(p_desc, desc_end) && (desc_itf->bDescriptorType != TUSB_DESC_INTERFACE || + (desc_itf->bInterfaceClass == TUSB_CLASS_AUDIO && + desc_itf->bInterfaceSubClass != AUDIO_SUBCLASS_MIDI_STREAMING))) { + if (desc_itf->bDescriptorType == TUSB_DESC_INTERFACE && desc_itf->bAlternateSetting == 0) { + p_midi->itf_count++; + } + p_desc = tu_desc_next(p_desc); + desc_itf = (const tusb_desc_interface_t *)p_desc; + } + TU_VERIFY(p_desc < desc_end, 0); + TU_VERIFY(TUSB_CLASS_AUDIO == desc_itf->bInterfaceClass, 0); + } + TU_VERIFY(AUDIO_SUBCLASS_MIDI_STREAMING == desc_itf->bInterfaceSubClass, 0); + + TU_LOG_DRV("MIDI opening Interface %u (addr = %u)\r\n", desc_itf->bInterfaceNumber, dev_addr); + p_midi->bInterfaceNumber = desc_itf->bInterfaceNumber; + p_midi->iInterface = desc_itf->iInterface; + p_midi->itf_count++; + desc_cb.desc_midi = desc_itf; + + bool found_new_interface = false; + do { + p_desc = tu_desc_next(p_desc); + if (!tu_desc_in_bounds(p_desc, desc_end)) { + break; + } + switch (tu_desc_type(p_desc)) { + case TUSB_DESC_INTERFACE: + found_new_interface = true; + break; + + case TUSB_DESC_CS_INTERFACE: + switch (tu_desc_subtype(p_desc)) { + case MIDI_CS_INTERFACE_HEADER: + TU_LOG_DRV(" Interface Header descriptor\r\n"); + desc_cb.desc_header = p_desc; + break; + + case MIDI_CS_INTERFACE_IN_JACK: + case MIDI_CS_INTERFACE_OUT_JACK: { + TU_LOG_DRV(" Jack %s %s descriptor \r\n", + tu_desc_subtype(p_desc) == MIDI_CS_INTERFACE_IN_JACK ? "IN" : "OUT", + p_desc[3] == MIDI_JACK_EXTERNAL ? "External" : "Embedded"); + if (desc_cb.jack_num < TU_ARRAY_SIZE(desc_cb.desc_jack)) { + desc_cb.desc_jack[desc_cb.jack_num++] = p_desc; + } + break; + } + + case MIDI_CS_INTERFACE_ELEMENT: + TU_LOG_DRV(" Element descriptor\r\n"); + desc_cb.desc_element = p_desc; + break; + + default: + TU_LOG_DRV(" Unknown CS Interface sub-type %u\r\n", tu_desc_subtype(p_desc)); + break; + } + break; + + case TUSB_DESC_ENDPOINT: { + const tusb_desc_endpoint_t *p_ep = (const tusb_desc_endpoint_t *) p_desc; + + p_desc = tu_desc_next(p_desc); // next to CS endpoint + TU_VERIFY(tu_desc_in_bounds(p_desc, desc_end), 0); + const midi_desc_cs_endpoint_t *p_csep = (const midi_desc_cs_endpoint_t *) p_desc; + + TU_LOG_DRV(" Endpoint and CS_Endpoint descriptor %02x\r\n", p_ep->bEndpointAddress); + tu_edpt_stream_t *ep_stream; + if (tu_edpt_dir(p_ep->bEndpointAddress) == TUSB_DIR_OUT) { + p_midi->tx_cable_count = p_csep->bNumEmbMIDIJack; + desc_cb.desc_epout = p_ep; + ep_stream = &p_midi->ep_stream.tx; + } else { + p_midi->rx_cable_count = p_csep->bNumEmbMIDIJack; + desc_cb.desc_epin = p_ep; + ep_stream = &p_midi->ep_stream.rx; + } + TU_ASSERT(tuh_edpt_open(dev_addr, p_ep), 0); + tu_edpt_stream_open(ep_stream, dev_addr, p_ep, tu_edpt_packet_size(p_ep)); + tu_edpt_stream_clear(ep_stream); + + break; + } + + default: break; // skip unknown descriptor + } + } while (!found_new_interface); + + desc_cb.desc_midi_total_len = (uint16_t)((uintptr_t)p_desc - (uintptr_t)desc_start); + + p_midi->daddr = dev_addr; + tuh_midi_descriptor_cb(idx, &desc_cb); + + return desc_cb.desc_midi_total_len; +} + +bool midih_set_config(uint8_t dev_addr, uint8_t itf_num) { + uint8_t idx = tuh_midi_itf_get_index(dev_addr, itf_num); + TU_ASSERT(idx < CFG_TUH_MIDI); + midih_interface_t *p_midi = &_midi_host[idx]; + p_midi->mounted = true; + + const tuh_midi_mount_cb_t mount_cb_data = { + .daddr = dev_addr, + .bInterfaceNumber = p_midi->bInterfaceNumber, + .rx_cable_count = p_midi->rx_cable_count, + .tx_cable_count = p_midi->tx_cable_count, + }; + tuh_midi_mount_cb(idx, &mount_cb_data); + + tu_edpt_stream_read_xfer(&p_midi->ep_stream.rx); // prepare for incoming data + + // No special config things to do for MIDI + usbh_driver_set_config_complete(dev_addr, p_midi->bInterfaceNumber); + return true; +} + +//--------------------------------------------------------------------+ +// API +//--------------------------------------------------------------------+ +bool tuh_midi_mounted(uint8_t idx) { + TU_VERIFY(idx < CFG_TUH_MIDI); + midih_interface_t *p_midi = &_midi_host[idx]; + return p_midi->mounted; +} + +uint8_t tuh_midi_itf_get_index(uint8_t daddr, uint8_t itf_num) { + for (uint8_t idx = 0; idx < CFG_TUH_MIDI; idx++) { + const midih_interface_t *p_midi = &_midi_host[idx]; + if (p_midi->daddr == daddr && + (p_midi->bInterfaceNumber == itf_num || + p_midi->bInterfaceNumber == (uint8_t) (itf_num + p_midi->itf_count - 1))) { + return idx; + } + } + return TUSB_INDEX_INVALID_8; +} + +bool tuh_midi_itf_get_info(uint8_t idx, tuh_itf_info_t* info) { + midih_interface_t* p_midi = &_midi_host[idx]; + TU_VERIFY(p_midi && info); + + info->daddr = p_midi->daddr; + + // re-construct descriptor + tusb_desc_interface_t* desc = &info->desc; + desc->bLength = sizeof(tusb_desc_interface_t); + desc->bDescriptorType = TUSB_DESC_INTERFACE; + + desc->bInterfaceNumber = p_midi->bInterfaceNumber; + desc->bAlternateSetting = 0; + desc->bNumEndpoints = 0; + if (tu_edpt_stream_is_opened(&p_midi->ep_stream.tx)) { + desc->bNumEndpoints++; + } + if (tu_edpt_stream_is_opened(&p_midi->ep_stream.rx)) { + desc->bNumEndpoints++; + } + desc->bInterfaceClass = TUSB_CLASS_AUDIO; + desc->bInterfaceSubClass = AUDIO_SUBCLASS_MIDI_STREAMING; + desc->bInterfaceProtocol = 0; + desc->iInterface = p_midi->iInterface; + + return true; +} + +uint8_t tuh_midi_get_tx_cable_count (uint8_t idx) { + TU_VERIFY(idx < CFG_TUH_MIDI); + midih_interface_t *p_midi = &_midi_host[idx]; + TU_VERIFY(p_midi->ep_stream.tx.ep_addr != 0, 0); + return p_midi->tx_cable_count; +} + +uint8_t tuh_midi_get_rx_cable_count (uint8_t idx) { + TU_VERIFY(idx < CFG_TUH_MIDI); + midih_interface_t *p_midi = &_midi_host[idx]; + TU_VERIFY(p_midi->ep_stream.rx.ep_addr != 0, 0); + return p_midi->rx_cable_count; +} + +uint32_t tuh_midi_read_available(uint8_t idx) { + TU_VERIFY(idx < CFG_TUH_MIDI); + midih_interface_t *p_midi = &_midi_host[idx]; + return tu_edpt_stream_read_available(&p_midi->ep_stream.rx); +} + +uint32_t tuh_midi_write_flush(uint8_t idx) { + TU_VERIFY(idx < CFG_TUH_MIDI); + midih_interface_t *p_midi = &_midi_host[idx]; + return tu_edpt_stream_write_xfer(&p_midi->ep_stream.tx); +} + +//--------------------------------------------------------------------+ +// Packet API +//--------------------------------------------------------------------+ +uint32_t tuh_midi_packet_read_n(uint8_t idx, uint8_t* buffer, uint32_t bufsize) { + TU_VERIFY(idx < CFG_TUH_MIDI && buffer && bufsize > 0, 0); + midih_interface_t *p_midi = &_midi_host[idx]; + + uint32_t count4 = tu_min32(bufsize, tu_edpt_stream_read_available(&p_midi->ep_stream.rx)); + count4 = tu_align4(count4); // round down to multiple of 4 + TU_VERIFY(count4 > 0, 0); + return tu_edpt_stream_read(&p_midi->ep_stream.rx, buffer, count4); +} + +uint32_t tuh_midi_packet_write_n(uint8_t idx, const uint8_t* buffer, uint32_t bufsize) { + TU_VERIFY(idx < CFG_TUH_MIDI && buffer && bufsize > 0, 0); + midih_interface_t *p_midi = &_midi_host[idx]; + + const uint32_t bufsize4 = tu_align4(bufsize); + TU_VERIFY(bufsize4 > 0, 0); + return tu_edpt_stream_write(&p_midi->ep_stream.tx, buffer, bufsize4); +} + +//--------------------------------------------------------------------+ +// Stream API +//--------------------------------------------------------------------+ +#if CFG_TUH_MIDI_STREAM_API +uint32_t tuh_midi_stream_write(uint8_t idx, uint8_t cable_num, uint8_t const *buffer, uint32_t bufsize) { + TU_VERIFY(idx < CFG_TUH_MIDI && buffer && bufsize > 0); + midih_interface_t *p_midi = &_midi_host[idx]; + TU_VERIFY(cable_num < p_midi->tx_cable_count); + midi_driver_stream_t *stream = &p_midi->stream_write; + + uint32_t byte_count = 0; + while ((byte_count < bufsize) && (tu_edpt_stream_write_available(&p_midi->ep_stream.tx) >= 4)) { + const uint8_t data = buffer[byte_count]; + byte_count++; + if (data >= MIDI_STATUS_SYSREAL_TIMING_CLOCK) { + // real-time messages need to be sent right away + midi_driver_stream_t streamrt; + streamrt.buffer[0] = (uint8_t)((cable_num << 4) | MIDI_CIN_SYSEX_END_1BYTE); + streamrt.buffer[1] = data; + streamrt.index = 2; + streamrt.total = 2; + const uint32_t count = tu_edpt_stream_write(&p_midi->ep_stream.tx, streamrt.buffer, 4); + TU_ASSERT(count == 4, byte_count); // Check FIFO overflown, since we already check fifo remaining. It is probably race condition + } else if (stream->index == 0) { + //------------- New event packet -------------// + + uint8_t const msg = data >> 4; + + stream->index = 2; + stream->buffer[1] = data; + + // Check to see if we're still in a SysEx transmit. + if ((stream->buffer[0] & 0xF) == MIDI_CIN_SYSEX_START) { + if (data == MIDI_STATUS_SYSEX_END) { + stream->buffer[0] = (uint8_t)((cable_num << 4) | MIDI_CIN_SYSEX_END_1BYTE); + stream->total = 2; + } else { + stream->total = 4; + } + } else if ((msg >= 0x8 && msg <= 0xB) || msg == 0xE) { + // Channel Voice Messages + stream->buffer[0] = (uint8_t) ((cable_num << 4) | msg); + stream->total = 4; + } else if (msg == 0xC || msg == 0xD) { + // Channel Voice Messages, two-byte variants (Program Change and Channel Pressure) + stream->buffer[0] = (uint8_t) ((cable_num << 4) | msg); + stream->total = 3; + } else if (msg == 0xf) { + // System message + if (data == MIDI_STATUS_SYSEX_START) { + stream->buffer[0] = MIDI_CIN_SYSEX_START; + stream->total = 4; + } else if (data == MIDI_STATUS_SYSCOM_TIME_CODE_QUARTER_FRAME || data == MIDI_STATUS_SYSCOM_SONG_SELECT) { + stream->buffer[0] = MIDI_CIN_SYSCOM_2BYTE; + stream->total = 3; + } else if (data == MIDI_STATUS_SYSCOM_SONG_POSITION_POINTER) { + stream->buffer[0] = MIDI_CIN_SYSCOM_3BYTE; + stream->total = 4; + } else { + stream->buffer[0] = MIDI_CIN_SYSEX_END_1BYTE; + stream->total = 2; + } + stream->buffer[0] |= (uint8_t)(cable_num << 4); + } else { + // Pack individual bytes if we don't support packing them into words. + stream->buffer[0] = (uint8_t) (cable_num << 4 | 0xf); + stream->buffer[2] = 0; + stream->buffer[3] = 0; + stream->index = 2; + stream->total = 2; + } + } else { + //------------- On-going (buffering) packet -------------// + TU_ASSERT(stream->index < 4, byte_count); + stream->buffer[stream->index] = data; + stream->index++; + // See if this byte ends a SysEx. + if ((stream->buffer[0] & 0xF) == MIDI_CIN_SYSEX_START && data == MIDI_STATUS_SYSEX_END) { + stream->buffer[0] = (uint8_t)((cable_num << 4) | (MIDI_CIN_SYSEX_START + (stream->index - 1))); + stream->total = stream->index; + } + } + + // Send out packet + if (stream->index >= 2 && stream->index == stream->total) { + // zeroes unused bytes + for (uint8_t i = stream->total; i < 4; i++) { + stream->buffer[i] = 0; + } + TU_LOG3_MEM(stream->buffer, 4, 2); + + const uint32_t count = tu_edpt_stream_write(&p_midi->ep_stream.tx, stream->buffer, 4); + + // complete current event packet, reset stream + stream->index = 0; + stream->total = 0; + + // FIFO overflown, since we already check fifo remaining. It is probably race condition + TU_ASSERT(count == 4, byte_count); + } + } + return byte_count; +} + +uint32_t tuh_midi_stream_read(uint8_t idx, uint8_t *p_cable_num, uint8_t *p_buffer, uint16_t bufsize) { + TU_VERIFY(idx < CFG_TUH_MIDI && p_cable_num && p_buffer && bufsize > 0); + midih_interface_t *p_midi = &_midi_host[idx]; + uint32_t bytes_buffered = 0; + uint8_t one_byte; + if (!tu_edpt_stream_peek(&p_midi->ep_stream.rx, &one_byte)) { + return 0; + } + *p_cable_num = (one_byte >> 4) & 0xf; + uint32_t nread = tu_edpt_stream_read(&p_midi->ep_stream.rx, p_midi->stream_read.buffer, 4); + static uint16_t cable_sysex_in_progress;// bit i is set if received MIDI_STATUS_SYSEX_START but not MIDI_STATUS_SYSEX_END + while (nread == 4 && bytes_buffered < bufsize) { + *p_cable_num = (p_midi->stream_read.buffer[0] >> 4) & 0x0f; + uint8_t bytes_to_add_to_stream = 0; + if (*p_cable_num < p_midi->rx_cable_count) { + // ignore the CIN field; too many devices out there encode this wrong + uint8_t status = p_midi->stream_read.buffer[1]; + uint16_t cable_mask = (uint16_t) (1 << *p_cable_num); + if (status <= MIDI_MAX_DATA_VAL || status == MIDI_STATUS_SYSEX_START) { + if (status == MIDI_STATUS_SYSEX_START) { + cable_sysex_in_progress |= cable_mask; + } + // only add the packet if a sysex message is in progress + if (cable_sysex_in_progress & cable_mask) { + ++bytes_to_add_to_stream; + for (uint8_t i = 2; i < 4; i++) { + if (p_midi->stream_read.buffer[i] <= MIDI_MAX_DATA_VAL) { + ++bytes_to_add_to_stream; + } else if (p_midi->stream_read.buffer[i] == MIDI_STATUS_SYSEX_END) { + ++bytes_to_add_to_stream; + cable_sysex_in_progress &= (uint16_t) ~cable_mask; + i = 4;// force the loop to exit; I hate break statements in loops + } + } + } + else { + // bad packet discard + nread = tu_edpt_stream_read(&p_midi->ep_stream.rx, p_midi->stream_read.buffer, 4); + continue; + } + } else if (status < MIDI_STATUS_SYSEX_START) { + // then it is a channel message either three bytes or two + uint8_t fake_cin = (status & 0xf0) >> 4; + switch (fake_cin) { + case MIDI_CIN_NOTE_OFF: + case MIDI_CIN_NOTE_ON: + case MIDI_CIN_POLY_KEYPRESS: + case MIDI_CIN_CONTROL_CHANGE: + case MIDI_CIN_PITCH_BEND_CHANGE: + bytes_to_add_to_stream = 3; + break; + case MIDI_CIN_PROGRAM_CHANGE: + case MIDI_CIN_CHANNEL_PRESSURE: + bytes_to_add_to_stream = 2; + break; + default: + break;// Should not get this + } + cable_sysex_in_progress &= (uint16_t) ~cable_mask; + } else if (status < MIDI_STATUS_SYSREAL_TIMING_CLOCK) { + switch (status) { + case MIDI_STATUS_SYSCOM_TIME_CODE_QUARTER_FRAME: + case MIDI_STATUS_SYSCOM_SONG_SELECT: + bytes_to_add_to_stream = 2; + break; + case MIDI_STATUS_SYSCOM_SONG_POSITION_POINTER: + bytes_to_add_to_stream = 3; + break; + case MIDI_STATUS_SYSCOM_TUNE_REQUEST: + case MIDI_STATUS_SYSEX_END: + bytes_to_add_to_stream = 1; + break; + default: + break; + } + cable_sysex_in_progress &= (uint16_t) ~cable_mask; + } else { + // Real-time message: can be inserted into a sysex message, + // so do don't clear cable_sysex_in_progress bit + bytes_to_add_to_stream = 1; + } + } + else { + // bad packet discard + nread = tu_edpt_stream_read(&p_midi->ep_stream.rx, p_midi->stream_read.buffer, 4); + continue; + } + + for (uint8_t i = 1; i <= bytes_to_add_to_stream; i++) { + *p_buffer++ = p_midi->stream_read.buffer[i]; + } + bytes_buffered += bytes_to_add_to_stream; + nread = 0; + if (tu_edpt_stream_peek(&p_midi->ep_stream.rx, &one_byte)) { + uint8_t new_cable = (one_byte >> 4) & 0xf; + if (new_cable == *p_cable_num) { + // still on the same cable. Continue reading the stream + nread = tu_edpt_stream_read(&p_midi->ep_stream.rx, p_midi->stream_read.buffer, 4); + } + } + } + + return bytes_buffered; +} +#endif + +#endif diff --git a/libraries/Adafruit_TinyUSB_Arduino/src/class/midi/midi_host.h b/libraries/Adafruit_TinyUSB_Arduino/src/class/midi/midi_host.h new file mode 100644 index 0000000..8fdfd89 --- /dev/null +++ b/libraries/Adafruit_TinyUSB_Arduino/src/class/midi/midi_host.h @@ -0,0 +1,198 @@ +/* + * 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. + * + * This file is part of the TinyUSB stack. + */ + +#ifndef TUSB_MIDI_HOST_H_ +#define TUSB_MIDI_HOST_H_ + +#include "class/audio/audio.h" +#include "midi.h" + +#ifdef __cplusplus +extern "C" { +#endif + +//--------------------------------------------------------------------+ +// Class Driver Configuration +//--------------------------------------------------------------------+ +#ifndef CFG_TUH_MIDI_RX_BUFSIZE + #define CFG_TUH_MIDI_RX_BUFSIZE TUH_EPSIZE_BULK_MAX +#endif + +#ifndef CFG_TUH_MIDI_TX_BUFSIZE + #define CFG_TUH_MIDI_TX_BUFSIZE TUH_EPSIZE_BULK_MAX +#endif + +#ifndef CFG_TUH_MIDI_EP_BUFSIZE + #define CFG_TUH_MIDI_EP_BUFSIZE TUH_EPSIZE_BULK_MAX +#endif + +// Enable the MIDI stream read/write API. Some library can work with raw USB MIDI packet +// Disable this can save driver footprint. +#ifndef CFG_TUH_MIDI_STREAM_API + #define CFG_TUH_MIDI_STREAM_API 1 +#endif + +//--------------------------------------------------------------------+ +// Application Types +//--------------------------------------------------------------------+ +typedef struct { + const tusb_desc_interface_t *desc_audio_control; + const tusb_desc_interface_t *desc_midi; // start of whole midi interface descriptor + uint16_t desc_midi_total_len; + + const uint8_t *desc_header; + const uint8_t *desc_element; + const tusb_desc_endpoint_t *desc_epin; // endpoint IN descriptor, CS_ENDPOINT is right after + const tusb_desc_endpoint_t *desc_epout; // endpoint OUT descriptor, CS_ENDPOINT is right after + + uint8_t jack_num; + const uint8_t *desc_jack[32]; // list of jack descriptors (embedded + external) +} tuh_midi_descriptor_cb_t; + +typedef struct { + uint8_t daddr; + uint8_t bInterfaceNumber; // interface number of MIDI streaming + uint8_t rx_cable_count; + uint8_t tx_cable_count; +} tuh_midi_mount_cb_t; + +//--------------------------------------------------------------------+ +// Application API +//--------------------------------------------------------------------+ + +// Check if MIDI interface is mounted +bool tuh_midi_mounted(uint8_t idx); + +// Get Interface index from device address + interface number +// return TUSB_INDEX_INVALID_8 (0xFF) if not found +uint8_t tuh_midi_itf_get_index(uint8_t daddr, uint8_t itf_num); + +// Get Interface information +// return true if index is correct and interface is currently mounted +bool tuh_midi_itf_get_info(uint8_t idx, tuh_itf_info_t *info); + +// return the number of virtual midi cables on the device's IN endpoint +uint8_t tuh_midi_get_rx_cable_count(uint8_t idx); + +// return the number of virtual midi cables on the device's OUT endpoint +uint8_t tuh_midi_get_tx_cable_count(uint8_t idx); + +// return the raw number of bytes available. +// Note: this is related but not the same as number of stream bytes available. +uint32_t tuh_midi_read_available(uint8_t idx); + +// Send any queued packets to the device if the host hardware is able to do it +// Returns the number of bytes flushed to the host hardware or 0 if +// the host hardware is busy or there is nothing in queue to send. +uint32_t tuh_midi_write_flush(uint8_t idx); + +//--------------------------------------------------------------------+ +// Packet API +//--------------------------------------------------------------------+ + +// Read all available MIDI packets from the connected device +// Return number of bytes read (always multiple of 4) +uint32_t tuh_midi_packet_read_n(uint8_t idx, uint8_t *buffer, uint32_t bufsize); + +// Read a raw MIDI packet from the connected device +// Return true if a packet was returned +TU_ATTR_ALWAYS_INLINE static inline bool tuh_midi_packet_read(uint8_t idx, uint8_t packet[4]) { + return 4 == tuh_midi_packet_read_n(idx, packet, 4); +} + +// Write all 4-byte packets, data is locally buffered and only transferred when buffered bytes +// reach the endpoint packet size or tuh_midi_write_flush() is called +uint32_t tuh_midi_packet_write_n(uint8_t idx, const uint8_t *buffer, uint32_t bufsize); + +// Write a 4-bytes packet to the device. +// Returns true if the packet was successfully queued. +TU_ATTR_ALWAYS_INLINE static inline bool tuh_midi_packet_write(uint8_t idx, const uint8_t packet[4]) { + return 4 == tuh_midi_packet_write_n(idx, packet, 4); +} + +//--------------------------------------------------------------------+ +// Stream API +//--------------------------------------------------------------------+ +#if CFG_TUH_MIDI_STREAM_API + +// Queue a message to the device using stream API. data is locally buffered and only transferred when buffered bytes +// reach the endpoint packet size or tuh_midi_write_flush() is called +// Returns number of bytes was successfully queued. +uint32_t tuh_midi_stream_write(uint8_t idx, uint8_t cable_num, const uint8_t *p_buffer, uint32_t bufsize); + +// Get the MIDI stream from the device. Set the value pointed +// to by p_cable_num to the MIDI cable number intended to receive it. +// The MIDI stream will be stored in the buffer pointed to by p_buffer. +// Return the number of bytes added to the buffer. +// Note that this function ignores the CIN field of the MIDI packet +// because a number of commercial devices out there do not encode +// it properly. +// +// NOTE: this function terminates when it encounters an event whose cable +// number differs from the one being returned. Applications should invoke +// it in a loop until it returns 0 (or until tuh_midi_read_available() +// returns 0) to guarantee the stream FIFO is fully drained per callback. +// Leaving bytes in the FIFO across callbacks can prevent subsequent bulk +// IN transfers from landing. +uint32_t tuh_midi_stream_read(uint8_t idx, uint8_t *p_cable_num, uint8_t *p_buffer, uint16_t bufsize); + +#endif + +//--------------------------------------------------------------------+ +// Callbacks (Weak is optional) +//--------------------------------------------------------------------+ + +// Invoked when MIDI interface is detected in enumeration. Application can copy/parse descriptor if needed. +// Note: may be fired before tuh_midi_mount_cb(), therefore midi interface is not mounted/ready. +void tuh_midi_descriptor_cb(uint8_t idx, const tuh_midi_descriptor_cb_t *desc_cb_data); + +// Invoked when device with MIDI interface is mounted. +void tuh_midi_mount_cb(uint8_t idx, const tuh_midi_mount_cb_t *mount_cb_data); + +// Invoked when device with MIDI interface is un-mounted +void tuh_midi_umount_cb(uint8_t idx); + +// Invoked when received new data +void tuh_midi_rx_cb(uint8_t idx, uint32_t xferred_bytes); + +// Invoked when a TX is complete and therefore space becomes available in TX buffer +void tuh_midi_tx_cb(uint8_t idx, uint32_t xferred_bytes); + +//--------------------------------------------------------------------+ +// Internal Class Driver API +//--------------------------------------------------------------------+ +bool midih_init(void); +bool midih_deinit(void); +uint16_t midih_open(uint8_t rhport, uint8_t dev_addr, const tusb_desc_interface_t *desc_itf, uint16_t max_len); +bool midih_set_config(uint8_t dev_addr, uint8_t itf_num); +bool midih_xfer_cb(uint8_t dev_addr, uint8_t ep_addr, xfer_result_t result, uint32_t xferred_bytes); +void midih_close(uint8_t daddr); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/libraries/Adafruit_TinyUSB_Arduino/src/class/msc/msc.h b/libraries/Adafruit_TinyUSB_Arduino/src/class/msc/msc.h new file mode 100644 index 0000000..3b5d4a8 --- /dev/null +++ b/libraries/Adafruit_TinyUSB_Arduino/src/class/msc/msc.h @@ -0,0 +1,401 @@ +/* + * 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. + * + * This file is part of the TinyUSB stack. + */ + +#ifndef TUSB_MSC_H_ +#define TUSB_MSC_H_ + +#include "common/tusb_common.h" + +#ifdef __cplusplus + extern "C" { +#endif + +//--------------------------------------------------------------------+ +// Mass Storage Class Constant +//--------------------------------------------------------------------+ +/// MassStorage Subclass +typedef enum +{ + MSC_SUBCLASS_RBC = 1 , ///< Reduced Block Commands (RBC) T10 Project 1240-D + MSC_SUBCLASS_SFF_MMC , ///< SFF-8020i, MMC-2 (ATAPI). Typically used by a CD/DVD device + MSC_SUBCLASS_QIC , ///< QIC-157. Typically used by a tape device + MSC_SUBCLASS_UFI , ///< UFI. Typically used by Floppy Disk Drive (FDD) device + MSC_SUBCLASS_SFF , ///< SFF-8070i. Can be used by Floppy Disk Drive (FDD) device + MSC_SUBCLASS_SCSI ///< SCSI transparent command set +}msc_subclass_type_t; + +enum { + MSC_CBW_SIGNATURE = 0x43425355, ///< Constant value of 43425355h (little endian) + MSC_CSW_SIGNATURE = 0x53425355 ///< Constant value of 53425355h (little endian) +}; + +/// \brief MassStorage Protocol. +/// \details CBI only approved to use with full-speed floppy disk & should not used with highspeed or device other than floppy +typedef enum +{ + MSC_PROTOCOL_CBI = 0 , ///< Control/Bulk/Interrupt protocol (with command completion interrupt) + MSC_PROTOCOL_CBI_NO_INTERRUPT = 1 , ///< Control/Bulk/Interrupt protocol (without command completion interrupt) + MSC_PROTOCOL_BOT = 0x50 ///< Bulk-Only Transport +}msc_protocol_type_t; + +/// MassStorage Class-Specific Control Request +typedef enum +{ + MSC_REQ_GET_MAX_LUN = 254, ///< The Get Max LUN device request is used to determine the number of logical units supported by the device. Logical Unit Numbers on the device shall be numbered contiguously starting from LUN 0 to a maximum LUN of 15 + MSC_REQ_RESET = 255 ///< This request is used to reset the mass storage device and its associated interface. This class-specific request shall ready the device for the next CBW from the host. +}msc_request_type_t; + +/// \brief Command Block Status Values +/// \details Indicates the success or failure of the command. The device shall set this byte to zero if the command completed +/// successfully. A non-zero value shall indicate a failure during command execution according to the following +typedef enum +{ + MSC_CSW_STATUS_PASSED = 0 , ///< MSC_CSW_STATUS_PASSED + MSC_CSW_STATUS_FAILED , ///< MSC_CSW_STATUS_FAILED + MSC_CSW_STATUS_PHASE_ERROR ///< MSC_CSW_STATUS_PHASE_ERROR +}msc_csw_status_t; + +/// Command Block Wrapper +typedef struct TU_ATTR_PACKED +{ + uint32_t signature; ///< Signature that helps identify this data packet as a CBW. The signature field shall contain the value 43425355h (little endian), indicating a CBW. + uint32_t tag; ///< Tag sent by the host. The device shall echo the contents of this field back to the host in the dCSWTagfield of the associated CSW. The dCSWTagpositively associates a CSW with the corresponding CBW. + uint32_t total_bytes; ///< The number of bytes of data that the host expects to transfer on the Bulk-In or Bulk-Out endpoint (as indicated by the Direction bit) during the execution of this command. If this field is zero, the device and the host shall transfer no data between the CBW and the associated CSW, and the device shall ignore the value of the Direction bit in bmCBWFlags. + uint8_t dir; ///< Bit 7 of this field define transfer direction \n - 0 : Data-Out from host to the device. \n - 1 : Data-In from the device to the host. + uint8_t lun; ///< The device Logical Unit Number (LUN) to which the command block is being sent. For devices that support multiple LUNs, the host shall place into this field the LUN to which this command block is addressed. Otherwise, the host shall set this field to zero. + uint8_t cmd_len; ///< The valid length of the CBWCBin bytes. This defines the valid length of the command block. The only legal values are 1 through 16 + uint8_t command[16]; ///< The command block to be executed by the device. The device shall interpret the first cmd_len bytes in this field as a command block +}msc_cbw_t; + +TU_VERIFY_STATIC(sizeof(msc_cbw_t) == 31, "size is not correct"); + +/// Command Status Wrapper +typedef struct TU_ATTR_PACKED +{ + uint32_t signature ; ///< Signature that helps identify this data packet as a CSW. The signature field shall contain the value 53425355h (little endian), indicating CSW. + uint32_t tag ; ///< The device shall set this field to the value received in the dCBWTag of the associated CBW. + uint32_t data_residue ; ///< For Data-Out the device shall report in the dCSWDataResidue the difference between the amount of data expected as stated in the dCBWDataTransferLength, and the actual amount of data processed by the device. For Data-In the device shall report in the dCSWDataResiduethe difference between the amount of data expected as stated in the dCBWDataTransferLengthand the actual amount of relevant data sent by the device + uint8_t status ; ///< indicates the success or failure of the command. Values from \ref msc_csw_status_t +}msc_csw_t; + +TU_VERIFY_STATIC(sizeof(msc_csw_t) == 13, "size is not correct"); + +//--------------------------------------------------------------------+ +// SCSI Constant +//--------------------------------------------------------------------+ + +/// SCSI Command Operation Code +typedef enum { + SCSI_CMD_TEST_UNIT_READY = 0x00, ///< The SCSI Test Unit Ready command is used to determine if a device is ready to transfer data (read/write), i.e. if a disk has spun up, if a tape is loaded and ready etc. The device does not perform a self-test operation. + SCSI_CMD_INQUIRY = 0x12, ///< The SCSI Inquiry command is used to obtain basic information from a target device. + SCSI_CMD_MODE_SELECT_6 = 0x15, ///< provides a means for the application client to specify medium, logical unit, or peripheral device parameters to the device server. Device servers that implement the MODE SELECT(6) command shall also implement the MODE SENSE(6) command. Application clients should issue MODE SENSE(6) prior to each MODE SELECT(6) to determine supported mode pages, page lengths, and other parameters. + SCSI_CMD_MODE_SENSE_6 = 0x1A, ///< provides a means for a device server to report parameters to an application client. It is a complementary command to the MODE SELECT(6) command. Device servers that implement the MODE SENSE(6) command shall also implement the MODE SELECT(6) command. + SCSI_CMD_START_STOP_UNIT = 0x1B, + SCSI_CMD_PREVENT_ALLOW_MEDIUM_REMOVAL = 0x1E, + SCSI_CMD_READ_CAPACITY_10 = 0x25, ///< The SCSI Read Capacity command is used to obtain data capacity information from a target device. + SCSI_CMD_REQUEST_SENSE = 0x03, ///< The SCSI Request Sense command is part of the SCSI computer protocol standard. This command is used to obtain sense data -- status/error information -- from a target device. + SCSI_CMD_READ_FORMAT_CAPACITY = 0x23, ///< The command allows the Host to request a list of the possible format capacities for an installed writable media. This command also has the capability to report the writable capacity for a media when it is installed + SCSI_CMD_READ_10 = 0x28, ///< The READ (10) command requests that the device server read the specified logical block(s) and transfer them to the data-in buffer. + SCSI_CMD_WRITE_10 = 0x2A, ///< The WRITE (10) command requests that the device server transfer the specified logical block(s) from the data-out buffer and write them. +}scsi_cmd_type_t; + +/// SCSI Sense Key +typedef enum { + SCSI_SENSE_NONE = 0x00, ///< no specific Sense Key. This would be the case for a successful command + SCSI_SENSE_RECOVERED_ERROR = 0x01, ///< Indicates the last command completed successfully with some recovery action performed by the disc drive. + SCSI_SENSE_NOT_READY = 0x02, ///< Indicates the logical unit addressed cannot be accessed. + SCSI_SENSE_MEDIUM_ERROR = 0x03, ///< Indicates the command terminated with a non-recovered error condition. + SCSI_SENSE_HARDWARE_ERROR = 0x04, ///< Indicates the disc drive detected a nonrecoverable hardware failure while performing the command or during a self test. + SCSI_SENSE_ILLEGAL_REQUEST = 0x05, ///< Indicates an illegal parameter in the command descriptor block or in the additional parameters + SCSI_SENSE_UNIT_ATTENTION = 0x06, ///< Indicates the disc drive may have been reset. + SCSI_SENSE_DATA_PROTECT = 0x07, ///< Indicates that a command that reads or writes the medium was attempted on a block that is protected from this operation. The read or write operation is not performed. + SCSI_SENSE_FIRMWARE_ERROR = 0x08, ///< Vendor specific sense key. + SCSI_SENSE_ABORTED_COMMAND = 0x0b, ///< Indicates the disc drive aborted the command. + SCSI_SENSE_EQUAL = 0x0c, ///< Indicates a SEARCH DATA command has satisfied an equal comparison. + SCSI_SENSE_VOLUME_OVERFLOW = 0x0d, ///< Indicates a buffered peripheral device has reached the end of medium partition and data remains in the buffer that has not been written to the medium. + SCSI_SENSE_MISCOMPARE = 0x0e ///< Indicates that the source data did not match the data read from the medium. +}scsi_sense_key_type_t; + + +typedef enum { + SCSI_PDT_DIRECT_ACCESS = 0x0, + SCSI_PDT_SEQUENTIAL_ACCESS = 0x1, + SCSI_PDT_PRINTER = 0x2, + SCSI_PDT_PROCESSOR = 0x3, + SCSI_PDT_WRITE_ONCE = 0x4, + SCSI_PDT_CD_DVD = 0x5, + SCSI_PDT_SCANNER = 0x6, + SCSI_PDT_OPTICAL_DEVICE = 0x7, + SCSI_PDT_MEDIUM_CHANGER = 0x8, + SCSI_PDT_COMMUNICATIONS = 0x9, // obsolete + SCSI_PDT_RAID = 0x0c, + SCSI_PDT_ENCLOSURE_SERVICES = 0x0d, + SCSI_PDT_SIMPLIFIED_DIRECT_ACCESS = 0x0e, + SCSI_PDT_OPTICAL_CARD_READER = 0x0f, + SCSI_PDT_BRIDGE = 0x10, ///< Bridge device, e.g. USB to SCSI bridge + SCSI_PDT_OBJECT_BASED_STORAGE = 0x11, ///< Object-based storage device + SCSI_PDT_AUTOMATION_DRIVE_INTERFACE = 0x12, ///< Automation/Drive Interface (ADI) device +} scsi_peripheral_device_type_t; + +//--------------------------------------------------------------------+ +// SCSI Primary Command (SPC-4) +//--------------------------------------------------------------------+ + +/// SCSI Test Unit Ready Command +typedef struct TU_ATTR_PACKED +{ + uint8_t cmd_code ; ///< SCSI OpCode for \ref SCSI_CMD_TEST_UNIT_READY + uint8_t lun ; ///< Logical Unit + uint8_t reserved[3] ; + uint8_t control ; +} scsi_test_unit_ready_t; + +TU_VERIFY_STATIC(sizeof(scsi_test_unit_ready_t) == 6, "size is not correct"); + +/// SCSI Inquiry Command +typedef struct TU_ATTR_PACKED +{ + uint8_t cmd_code ; ///< SCSI OpCode for \ref SCSI_CMD_INQUIRY + uint8_t reserved1 ; + uint8_t page_code ; + uint8_t reserved2 ; + uint8_t alloc_length ; ///< specifies the maximum number of bytes that USB host has allocated in the Data-In Buffer. An allocation length of zero specifies that no data shall be transferred. + uint8_t control ; +} scsi_inquiry_t, scsi_request_sense_t; + +TU_VERIFY_STATIC(sizeof(scsi_inquiry_t) == 6, "size is not correct"); + +/// SCSI Inquiry Response Data +typedef struct TU_ATTR_PACKED +{ + uint8_t peripheral_device_type : 5; + uint8_t peripheral_qualifier : 3; + + uint8_t : 7; + uint8_t is_removable : 1; + + uint8_t version; + + uint8_t response_data_format : 4; + uint8_t hierarchical_support : 1; + uint8_t normal_aca : 1; + uint8_t : 2; + + uint8_t additional_length; + + uint8_t protect : 1; + uint8_t : 2; + uint8_t third_party_copy : 1; + uint8_t target_port_group_support : 2; + uint8_t access_control_coordinator : 1; + uint8_t scc_support : 1; + + uint8_t addr16 : 1; + uint8_t : 3; + uint8_t multi_port : 1; + uint8_t : 1; // vendor specific + uint8_t enclosure_service : 1; + uint8_t : 1; + + uint8_t : 1; // vendor specific + uint8_t cmd_que : 1; + uint8_t : 2; + uint8_t sync : 1; + uint8_t wbus16 : 1; + uint8_t : 2; + + uint8_t vendor_id[8] ; ///< 8 bytes of ASCII data identifying the vendor of the product. + uint8_t product_id[16]; ///< 16 bytes of ASCII data defined by the vendor. + uint8_t product_rev[4]; ///< 4 bytes of ASCII data defined by the vendor. +} scsi_inquiry_resp_t; + +TU_VERIFY_STATIC(sizeof(scsi_inquiry_resp_t) == 36, "size is not correct"); + + +typedef struct TU_ATTR_PACKED +{ + uint8_t response_code : 7; ///< 70h - current errors, Fixed Format 71h - deferred errors, Fixed Format + uint8_t valid : 1; + + uint8_t reserved; + + uint8_t sense_key : 4; + uint8_t : 1; + uint8_t ili : 1; ///< Incorrect length indicator + uint8_t end_of_medium : 1; + uint8_t filemark : 1; + + uint32_t information; + uint8_t add_sense_len; + uint32_t command_specific_info; + uint8_t add_sense_code; + uint8_t add_sense_qualifier; + uint8_t field_replaceable_unit_code; + + uint8_t sense_key_specific[3]; ///< sense key specific valid bit is bit 7 of key[0], aka MSB in Big Endian layout + +} scsi_sense_fixed_resp_t; + +TU_VERIFY_STATIC(sizeof(scsi_sense_fixed_resp_t) == 18, "size is not correct"); + +typedef struct TU_ATTR_PACKED +{ + uint8_t cmd_code ; ///< SCSI OpCode for \ref SCSI_CMD_MODE_SENSE_6 + + uint8_t : 3; + uint8_t disable_block_descriptor : 1; + uint8_t : 4; + + uint8_t page_code : 6; + uint8_t page_control : 2; + + uint8_t subpage_code; + uint8_t alloc_length; + uint8_t control; +} scsi_mode_sense6_t; + +TU_VERIFY_STATIC( sizeof(scsi_mode_sense6_t) == 6, "size is not correct"); + +// This is only a Mode parameter header(6). +typedef struct TU_ATTR_PACKED +{ + uint8_t data_len; + uint8_t medium_type; + + uint8_t reserved : 7; + bool write_protected : 1; + + uint8_t block_descriptor_len; +} scsi_mode_sense6_resp_t; + +TU_VERIFY_STATIC( sizeof(scsi_mode_sense6_resp_t) == 4, "size is not correct"); + +typedef struct TU_ATTR_PACKED +{ + uint8_t cmd_code; ///< SCSI OpCode for \ref SCSI_CMD_PREVENT_ALLOW_MEDIUM_REMOVAL + uint8_t reserved[3]; + uint8_t prohibit_removal; + uint8_t control; +} scsi_prevent_allow_medium_removal_t; + +TU_VERIFY_STATIC( sizeof(scsi_prevent_allow_medium_removal_t) == 6, "size is not correct"); + +typedef struct TU_ATTR_PACKED +{ + uint8_t cmd_code; + + uint8_t immded : 1; + uint8_t : 7; + + uint8_t TU_RESERVED; + + uint8_t power_condition_mod : 4; + uint8_t : 4; + + uint8_t start : 1; + uint8_t load_eject : 1; + uint8_t no_flush : 1; + uint8_t : 1; + uint8_t power_condition : 4; + + uint8_t control; +} scsi_start_stop_unit_t; + +TU_VERIFY_STATIC( sizeof(scsi_start_stop_unit_t) == 6, "size is not correct"); + +//--------------------------------------------------------------------+ +// SCSI MMC +//--------------------------------------------------------------------+ +/// SCSI Read Format Capacity: Write Capacity +typedef struct TU_ATTR_PACKED +{ + uint8_t cmd_code; + uint8_t reserved[6]; + uint16_t alloc_length; + uint8_t control; +} scsi_read_format_capacity_t; + +TU_VERIFY_STATIC( sizeof(scsi_read_format_capacity_t) == 10, "size is not correct"); + +typedef struct TU_ATTR_PACKED{ + uint8_t reserved[3]; + uint8_t list_length; /// must be 8*n, length in bytes of formattable capacity descriptor followed it. + + uint32_t block_num; /// Number of Logical Blocks + uint8_t descriptor_type; // 00: reserved, 01 unformatted media , 10 Formatted media, 11 No media present + + uint8_t reserved2; + uint16_t block_size_u16; + +} scsi_read_format_capacity_data_t; + +TU_VERIFY_STATIC( sizeof(scsi_read_format_capacity_data_t) == 12, "size is not correct"); + +//--------------------------------------------------------------------+ +// SCSI Block Command (SBC-3) +// NOTE: All data in SCSI command are in Big Endian +//--------------------------------------------------------------------+ + +/// SCSI Read Capacity 10 Command: Read Capacity +typedef struct TU_ATTR_PACKED +{ + uint8_t cmd_code ; ///< SCSI OpCode for \ref SCSI_CMD_READ_CAPACITY_10 + uint8_t reserved1 ; + uint32_t lba ; ///< The first Logical Block Address (LBA) accessed by this command + uint16_t reserved2 ; + uint8_t partial_medium_indicator ; + uint8_t control ; +} scsi_read_capacity10_t; + +TU_VERIFY_STATIC(sizeof(scsi_read_capacity10_t) == 10, "size is not correct"); + +/// SCSI Read Capacity 10 Response Data +typedef struct { + uint32_t last_lba ; ///< The last Logical Block Address of the device + uint32_t block_size ; ///< Block size in bytes +} scsi_read_capacity10_resp_t; + +TU_VERIFY_STATIC(sizeof(scsi_read_capacity10_resp_t) == 8, "size is not correct"); + +/// SCSI Read 10 Command +typedef struct TU_ATTR_PACKED +{ + uint8_t cmd_code ; ///< SCSI OpCode + uint8_t reserved ; // has LUN according to wiki + uint32_t lba ; ///< The first Logical Block Address (LBA) accessed by this command + uint8_t reserved2 ; + uint16_t block_count ; ///< Number of Blocks used by this command + uint8_t control ; +} scsi_read10_t, scsi_write10_t; + +TU_VERIFY_STATIC(sizeof(scsi_read10_t) == 10, "size is not correct"); +TU_VERIFY_STATIC(sizeof(scsi_write10_t) == 10, "size is not correct"); + +#ifdef __cplusplus + } +#endif + +#endif /* TUSB_MSC_H_ */ diff --git a/libraries/Adafruit_TinyUSB_Arduino/src/class/msc/msc_device.c b/libraries/Adafruit_TinyUSB_Arduino/src/class/msc/msc_device.c new file mode 100644 index 0000000..3766e3a --- /dev/null +++ b/libraries/Adafruit_TinyUSB_Arduino/src/class/msc/msc_device.c @@ -0,0 +1,968 @@ +/* + * 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. + * + * This file is part of the TinyUSB stack. + */ + +#include "tusb_option.h" + +#if (CFG_TUD_ENABLED && CFG_TUD_MSC) + +#include "device/dcd.h" // for faking dcd_event_xfer_complete +#include "device/usbd.h" +#include "device/usbd_pvt.h" + +#include "msc_device.h" + +// Level where CFG_TUSB_DEBUG must be at least for this driver is logged +#ifndef CFG_TUD_MSC_LOG_LEVEL + #define CFG_TUD_MSC_LOG_LEVEL CFG_TUD_LOG_LEVEL +#endif + +#define TU_LOG_DRV(...) TU_LOG(CFG_TUD_MSC_LOG_LEVEL, __VA_ARGS__) + +//--------------------------------------------------------------------+ +// Weak stubs: invoked if no strong implementation is available +//--------------------------------------------------------------------+ +TU_ATTR_WEAK void tud_msc_inquiry_cb(uint8_t lun, uint8_t vendor_id[8], uint8_t product_id[16], uint8_t product_rev[4]) { + (void) lun; (void) vendor_id; (void) product_id; (void) product_rev; +} +TU_ATTR_WEAK uint32_t tud_msc_inquiry2_cb(uint8_t lun, scsi_inquiry_resp_t *inquiry_resp, uint32_t bufsize) { + (void) lun; (void) inquiry_resp; (void) bufsize; + return 0; +} + +//--------------------------------------------------------------------+ +// MACRO CONSTANT TYPEDEF +//--------------------------------------------------------------------+ +enum { + MSC_STAGE_CMD = 0, + MSC_STAGE_DATA, + MSC_STAGE_STATUS, + MSC_STAGE_STATUS_SENT, + MSC_STAGE_NEED_RESET, +}; + +typedef struct { + TU_ATTR_ALIGNED(4) msc_cbw_t cbw; // 31 bytes + uint8_t rhport; + + TU_ATTR_ALIGNED(4) msc_csw_t csw; // 13 bytes + uint8_t itf_num; + uint8_t ep_in; + uint8_t ep_out; + + uint32_t total_len; // byte to be transferred, can be smaller than total_bytes in cbw + uint32_t xferred_len; // numbered of bytes transferred so far in the Data Stage + + // Bulk Only Transfer (BOT) Protocol + uint8_t stage; + + // SCSI Sense Response Data + uint8_t sense_key; + uint8_t add_sense_code; + uint8_t add_sense_qualifier; + + bool pending_io; // pending async IO +}mscd_interface_t; + +static mscd_interface_t _mscd_itf; + +CFG_TUD_MEM_SECTION static struct { + TUD_EPBUF_DEF(buf, CFG_TUD_MSC_EP_BUFSIZE); +} _mscd_epbuf; + +TU_VERIFY_STATIC(CFG_TUD_MSC_EP_BUFSIZE >= 64, "CFG_TUD_MSC_EP_BUFSIZE must be at least 64"); + +//--------------------------------------------------------------------+ +// INTERNAL OBJECT & FUNCTION DECLARATION +//--------------------------------------------------------------------+ +static int32_t proc_builtin_scsi(uint8_t lun, uint8_t const scsi_cmd[16], uint8_t* buffer, uint32_t bufsize); +static void proc_read10_cmd(mscd_interface_t* p_msc); +static void proc_read_io_data(mscd_interface_t* p_msc, int32_t nbytes); +static void proc_write10_cmd(mscd_interface_t* p_msc); +static void proc_write10_host_data(mscd_interface_t* p_msc, uint32_t xferred_bytes); +static void proc_write_io_data(mscd_interface_t* p_msc, uint32_t xferred_bytes, int32_t nbytes); +static bool proc_stage_status(mscd_interface_t* p_msc); + +TU_ATTR_ALWAYS_INLINE static inline bool is_data_in(uint8_t dir) { + return tu_bit_test(dir, 7); +} + +TU_ATTR_ALWAYS_INLINE static inline bool send_csw(mscd_interface_t* p_msc) { + // Data residue is always = host expect - actual transferred + uint8_t rhport = p_msc->rhport; + p_msc->csw.data_residue = p_msc->cbw.total_bytes - p_msc->xferred_len; + p_msc->stage = MSC_STAGE_STATUS_SENT; + memcpy(_mscd_epbuf.buf, &p_msc->csw, sizeof(msc_csw_t)); //-V1086 + return usbd_edpt_xfer(rhport, p_msc->ep_in , _mscd_epbuf.buf, sizeof(msc_csw_t), false); +} + +TU_ATTR_ALWAYS_INLINE static inline bool prepare_cbw(mscd_interface_t* p_msc) { + uint8_t rhport = p_msc->rhport; + p_msc->stage = MSC_STAGE_CMD; + return usbd_edpt_xfer(rhport, p_msc->ep_out, _mscd_epbuf.buf, sizeof(msc_cbw_t), false); +} + +static void fail_scsi_op(mscd_interface_t* p_msc, uint8_t status) { + msc_cbw_t const * p_cbw = &p_msc->cbw; + msc_csw_t * p_csw = &p_msc->csw; + uint8_t rhport = p_msc->rhport; + + p_csw->status = status; + p_csw->data_residue = p_msc->cbw.total_bytes - p_msc->xferred_len; + p_msc->stage = MSC_STAGE_STATUS; + + // failed but sense key is not set: default to Illegal Request + if (p_msc->sense_key == 0) { + (void) tud_msc_set_sense(p_cbw->lun, SCSI_SENSE_ILLEGAL_REQUEST, 0x20, 0x00); + } + + // If there is data stage and not yet complete, stall it + if (p_cbw->total_bytes && p_csw->data_residue) { + if (is_data_in(p_cbw->dir)) { + usbd_edpt_stall(rhport, p_msc->ep_in); + } else { + usbd_edpt_stall(rhport, p_msc->ep_out); + } + } +} + +TU_ATTR_ALWAYS_INLINE static inline uint32_t rdwr10_get_lba(uint8_t const command[]) { + // use offsetof to avoid pointer to the odd/unaligned address + const uint32_t lba = tu_unaligned_read32(command + offsetof(scsi_write10_t, lba)); + return tu_ntohl(lba); // lba is in Big Endian +} + +TU_ATTR_ALWAYS_INLINE static inline uint16_t rdwr10_get_blockcount(msc_cbw_t const* cbw) { + uint16_t const block_count = tu_unaligned_read16(cbw->command + offsetof(scsi_write10_t, block_count)); + return tu_ntohs(block_count); +} + +TU_ATTR_ALWAYS_INLINE static inline uint16_t rdwr10_get_blocksize(msc_cbw_t const* cbw) { + // first extract block count in the command + uint16_t const block_count = rdwr10_get_blockcount(cbw); + if (block_count == 0) { + return 0; // invalid block count + } + return (uint16_t) (cbw->total_bytes / block_count); +} + +static uint8_t rdwr10_validate_cmd(msc_cbw_t const* cbw) { + uint8_t status = MSC_CSW_STATUS_PASSED; + uint16_t const block_count = rdwr10_get_blockcount(cbw); + + if (cbw->total_bytes == 0) { + if (block_count > 0) { + TU_LOG_DRV(" SCSI case 2 (Hn < Di) or case 3 (Hn < Do) \r\n"); + status = MSC_CSW_STATUS_PHASE_ERROR; + } else { + // no data transfer, only exist in complaint test suite + } + } else { + if (SCSI_CMD_READ_10 == cbw->command[0] && !is_data_in(cbw->dir)) { + TU_LOG_DRV(" SCSI case 10 (Ho <> Di)\r\n"); + status = MSC_CSW_STATUS_PHASE_ERROR; + } else if (SCSI_CMD_WRITE_10 == cbw->command[0] && is_data_in(cbw->dir)) { + TU_LOG_DRV(" SCSI case 8 (Hi <> Do)\r\n"); + status = MSC_CSW_STATUS_PHASE_ERROR; + } else if (0 == block_count) { + TU_LOG_DRV(" SCSI case 4 Hi > Dn (READ10) or case 9 Ho > Dn (WRITE10) \r\n"); + status = MSC_CSW_STATUS_FAILED; + } else if (cbw->total_bytes / block_count == 0) { + TU_LOG_DRV(" Computed block size = 0. SCSI case 7 Hi < Di (READ10) or case 13 Ho < Do (WRIT10)\r\n"); + status = MSC_CSW_STATUS_PHASE_ERROR; + } else { + // nothing to do + } + } + + return status; +} + +static bool proc_stage_status(mscd_interface_t *p_msc) { + uint8_t rhport = p_msc->rhport; + msc_cbw_t const *p_cbw = &p_msc->cbw; + + // skip status if epin is currently stalled, will do it when received Clear Stall request + if (!usbd_edpt_stalled(rhport, p_msc->ep_in)) { + if ((p_cbw->total_bytes > p_msc->xferred_len) && is_data_in(p_cbw->dir)) { + // 6.7 The 13 Cases: case 5 (Hi > Di): STALL before status + // TU_LOG_DRV(" SCSI case 5 (Hi > Di): %lu > %lu\r\n", p_cbw->total_bytes, p_msc->xferred_len); + usbd_edpt_stall(rhport, p_msc->ep_in); + } else { + TU_ASSERT(send_csw(p_msc)); + } + } + + #if TU_CHECK_MCU(OPT_MCU_CXD56) + // WORKAROUND: cxd56 has its own nuttx usb stack which does not forward Set/ClearFeature(Endpoint) to DCD. + // There is no way for us to know when EP is un-stall, therefore we will unconditionally un-stall here and + // hope everything will work + if (usbd_edpt_stalled(rhport, p_msc->ep_in)) { + usbd_edpt_clear_stall(rhport, p_msc->ep_in); + send_csw(p_msc); + } + #endif + return true; +} + +//--------------------------------------------------------------------+ +// Weak stubs: invoked if no strong implementation is available +//--------------------------------------------------------------------+ +TU_ATTR_WEAK void tud_msc_read10_complete_cb(uint8_t lun) { + (void) lun; +} + +TU_ATTR_WEAK void tud_msc_write10_complete_cb(uint8_t lun) { + (void) lun; +} + +TU_ATTR_WEAK void tud_msc_scsi_complete_cb(uint8_t lun, uint8_t const scsi_cmd[16]) { + (void) lun; + (void) scsi_cmd; +} + +TU_ATTR_WEAK uint8_t tud_msc_get_maxlun_cb(void) { + return 1; +} + +TU_ATTR_WEAK bool tud_msc_start_stop_cb(uint8_t lun, uint8_t power_condition, bool start, bool load_eject) { + (void) lun; + (void) power_condition; + (void) start; + (void) load_eject; + return true; +} + +TU_ATTR_WEAK bool tud_msc_prevent_allow_medium_removal_cb(uint8_t lun, uint8_t prohibit_removal, uint8_t control) { + (void) lun; + (void) prohibit_removal; + (void) control; + return true; +} + +TU_ATTR_WEAK int32_t tud_msc_request_sense_cb(uint8_t lun, void* buffer, uint16_t bufsize) { + (void) lun; + (void) buffer; + (void) bufsize; + return sizeof(scsi_sense_fixed_resp_t); +} + +TU_ATTR_WEAK bool tud_msc_is_writable_cb(uint8_t lun) { + (void) lun; + return true; +} + +//--------------------------------------------------------------------+ +// Debug +//--------------------------------------------------------------------+ +#if CFG_TUSB_DEBUG >= CFG_TUD_MSC_LOG_LEVEL + +TU_ATTR_UNUSED tu_static tu_lookup_entry_t const _msc_scsi_cmd_lookup[] = { + { .key = SCSI_CMD_TEST_UNIT_READY , .data = "Test Unit Ready" }, + { .key = SCSI_CMD_INQUIRY , .data = "Inquiry" }, + { .key = SCSI_CMD_MODE_SELECT_6 , .data = "Mode_Select 6" }, + { .key = SCSI_CMD_MODE_SENSE_6 , .data = "Mode_Sense 6" }, + { .key = SCSI_CMD_START_STOP_UNIT , .data = "Start Stop Unit" }, + { .key = SCSI_CMD_PREVENT_ALLOW_MEDIUM_REMOVAL , .data = "Prevent/Allow Medium Removal" }, + { .key = SCSI_CMD_READ_CAPACITY_10 , .data = "Read Capacity10" }, + { .key = SCSI_CMD_REQUEST_SENSE , .data = "Request Sense" }, + { .key = SCSI_CMD_READ_FORMAT_CAPACITY , .data = "Read Format Capacity" }, + { .key = SCSI_CMD_READ_10 , .data = "Read10" }, + { .key = SCSI_CMD_WRITE_10 , .data = "Write10" } +}; + +TU_ATTR_UNUSED tu_static tu_lookup_table_t const _msc_scsi_cmd_table = { + .count = TU_ARRAY_SIZE(_msc_scsi_cmd_lookup), + .items = _msc_scsi_cmd_lookup +}; + +#endif + +//--------------------------------------------------------------------+ +// APPLICATION API +//--------------------------------------------------------------------+ +bool tud_msc_set_sense(uint8_t lun, uint8_t sense_key, uint8_t add_sense_code, uint8_t add_sense_qualifier) { + (void) lun; + _mscd_itf.sense_key = sense_key; + _mscd_itf.add_sense_code = add_sense_code; + _mscd_itf.add_sense_qualifier = add_sense_qualifier; + return true; +} + +TU_ATTR_ALWAYS_INLINE static inline void set_sense_medium_not_present(uint8_t lun) { + // default sense is NOT READY, MEDIUM NOT PRESENT + (void) tud_msc_set_sense(lun, SCSI_SENSE_NOT_READY, 0x3A, 0x00); +} + +static void proc_async_io_done(void *bytes_io) { + mscd_interface_t *p_msc = &_mscd_itf; + TU_VERIFY(p_msc->pending_io, ); + const int32_t nbytes = (int32_t) (intptr_t) bytes_io; + const uint8_t cmd = p_msc->cbw.command[0]; + + p_msc->pending_io = false; + switch (cmd) { + case SCSI_CMD_READ_10: + proc_read_io_data(p_msc, nbytes); + break; + + case SCSI_CMD_WRITE_10: + proc_write_io_data(p_msc, (uint32_t) nbytes, nbytes); + break; + + default: break; // nothing to do + } + + // send status if stage is transitioned to STATUS + if (p_msc->stage == MSC_STAGE_STATUS) { + proc_stage_status(p_msc); + } +} + +bool tud_msc_async_io_done(int32_t bytes_io, bool in_isr) { + // Precheck to avoid queueing multiple RW done callback + TU_VERIFY(_mscd_itf.pending_io); + if (bytes_io == 0) { + bytes_io = TUD_MSC_RET_ERROR; // 0 is treated as error, no reason to call this with BUSY here + } + usbd_defer_func(proc_async_io_done, (void *) (intptr_t) bytes_io, in_isr); + return true; +} + +//--------------------------------------------------------------------+ +// USBD Driver API +//--------------------------------------------------------------------+ +void mscd_init(void) { + TU_LOG_INT(CFG_TUD_MSC_LOG_LEVEL, sizeof(mscd_interface_t)); + tu_memclr(&_mscd_itf, sizeof(mscd_interface_t)); +} + +bool mscd_deinit(void) { + return true; // nothing to do +} + +void mscd_reset(uint8_t rhport) { + (void) rhport; + tu_memclr(&_mscd_itf, sizeof(mscd_interface_t)); +} + +uint16_t mscd_open(uint8_t rhport, tusb_desc_interface_t const * itf_desc, uint16_t max_len) { + // only support SCSI's BOT protocol + TU_VERIFY(TUSB_CLASS_MSC == itf_desc->bInterfaceClass && + MSC_SUBCLASS_SCSI == itf_desc->bInterfaceSubClass && + MSC_PROTOCOL_BOT == itf_desc->bInterfaceProtocol, 0); + uint16_t const drv_len = sizeof(tusb_desc_interface_t) + 2*sizeof(tusb_desc_endpoint_t); + TU_ASSERT(max_len >= drv_len, 0); // Max length must be at least 1 interface + 2 endpoints + + mscd_interface_t * p_msc = &_mscd_itf; + p_msc->itf_num = itf_desc->bInterfaceNumber; + p_msc->rhport = rhport; + + // Open endpoint pair + TU_ASSERT(usbd_open_edpt_pair(rhport, tu_desc_next(itf_desc), 2, TUSB_XFER_BULK, &p_msc->ep_out, &p_msc->ep_in), 0); + + // Prepare for Command Block Wrapper + TU_ASSERT(prepare_cbw(p_msc), drv_len); + + return drv_len; +} + +static void proc_bot_reset(mscd_interface_t* p_msc) { + p_msc->stage = MSC_STAGE_CMD; + p_msc->total_len = 0; + p_msc->xferred_len = 0; + p_msc->sense_key = 0; + p_msc->add_sense_code = 0; + p_msc->add_sense_qualifier = 0; +} + +// Invoked when a control transfer occurred on an interface of this class +// Driver response accordingly to the request and the transfer stage (setup/data/ack) +// return false to stall control endpoint (e.g unsupported request) +bool mscd_control_xfer_cb(uint8_t rhport, uint8_t stage, tusb_control_request_t const * request) { + if (stage != CONTROL_STAGE_SETUP) { + return true; // nothing to do with DATA & ACK stage + } + + mscd_interface_t* p_msc = &_mscd_itf; + + // Clear Endpoint Feature (stall) for recovery + if ( TUSB_REQ_TYPE_STANDARD == request->bmRequestType_bit.type && + TUSB_REQ_RCPT_ENDPOINT == request->bmRequestType_bit.recipient && + TUSB_REQ_CLEAR_FEATURE == request->bRequest && + TUSB_REQ_FEATURE_EDPT_HALT == request->wValue ) { + uint8_t const ep_addr = tu_u16_low(request->wIndex); + + if (p_msc->stage == MSC_STAGE_NEED_RESET) { + // reset recovery is required to recover from this stage + // Clear Stall request cannot resolve this -> continue to stall endpoint + usbd_edpt_stall(rhport, ep_addr); + } else { + if (ep_addr == p_msc->ep_in) { + if (p_msc->stage == MSC_STAGE_STATUS) { + // resume sending SCSI status if we are in this stage previously before stalled + TU_ASSERT(send_csw(p_msc)); + } + } else if (ep_addr == p_msc->ep_out) { + if (p_msc->stage == MSC_STAGE_CMD) { + // part of reset recovery (probably due to invalid CBW) -> prepare for new command + // Note: skip if already queued previously + if (usbd_edpt_ready(rhport, p_msc->ep_out)) { + TU_ASSERT(prepare_cbw(p_msc)); + } + } + } else { + // nothing to do + } + } + + return true; + } + + // From this point only handle class request only + TU_VERIFY(request->bmRequestType_bit.type == TUSB_REQ_TYPE_CLASS); + + switch ( request->bRequest ) { + case MSC_REQ_RESET: + TU_LOG_DRV(" MSC BOT Reset\r\n"); + TU_VERIFY(request->wValue == 0 && request->wLength == 0); + proc_bot_reset(p_msc); // driver state reset + tud_control_status(rhport, request); + break; + + case MSC_REQ_GET_MAX_LUN: { + TU_LOG_DRV(" MSC Get Max Lun\r\n"); + TU_VERIFY(request->wValue == 0 && request->wLength == 1); + + uint8_t maxlun = tud_msc_get_maxlun_cb(); + TU_VERIFY(maxlun != 0); + maxlun--; // MAX LUN is minus 1 by specs + tud_control_xfer(rhport, request, &maxlun, 1); + break; + } + + default: return false; // stall unsupported request + } + + return true; +} + +bool mscd_xfer_cb(uint8_t rhport, uint8_t ep_addr, xfer_result_t event, uint32_t xferred_bytes) { + (void) event; + + mscd_interface_t* p_msc = &_mscd_itf; + msc_cbw_t * p_cbw = &p_msc->cbw; + msc_csw_t * p_csw = &p_msc->csw; + + switch (p_msc->stage) { + case MSC_STAGE_CMD: { + //------------- new CBW received -------------// + // Complete IN while waiting for CMD is usually Status of previous SCSI op, ignore it + if (ep_addr != p_msc->ep_out) { + return true; + } + + const uint32_t signature = tu_le32toh(tu_unaligned_read32(_mscd_epbuf.buf)); + + if (!(xferred_bytes == sizeof(msc_cbw_t) && signature == MSC_CBW_SIGNATURE)) { + // BOT 6.6.1 If CBW is not valid stall both endpoints until reset recovery + TU_LOG_DRV(" SCSI CBW is not valid\r\n"); + p_msc->stage = MSC_STAGE_NEED_RESET; + usbd_edpt_stall(rhport, p_msc->ep_in); + usbd_edpt_stall(rhport, p_msc->ep_out); + return false; + } + + memcpy(p_cbw, _mscd_epbuf.buf, sizeof(msc_cbw_t)); + + TU_LOG_DRV(" SCSI Command [Lun%u]: %s\r\n", p_cbw->lun, tu_lookup_find(&_msc_scsi_cmd_table, p_cbw->command[0])); + // TU_LOG_MEM(CFG_TUD_MSC_LOG_LEVEL, p_cbw, xferred_bytes, 2); + + p_csw->signature = MSC_CSW_SIGNATURE; + p_csw->tag = p_cbw->tag; + p_csw->data_residue = 0; + p_csw->status = MSC_CSW_STATUS_PASSED; + + /*------------- Parse command and prepare DATA -------------*/ + p_msc->stage = MSC_STAGE_DATA; + p_msc->total_len = p_cbw->total_bytes; + p_msc->xferred_len = 0; + + // Read10 or Write10 + if ((SCSI_CMD_READ_10 == p_cbw->command[0]) || (SCSI_CMD_WRITE_10 == p_cbw->command[0])) { + uint8_t const status = rdwr10_validate_cmd(p_cbw); + + if (status != MSC_CSW_STATUS_PASSED) { + fail_scsi_op(p_msc, status); + } else if (p_cbw->total_bytes > 0) { + if (SCSI_CMD_READ_10 == p_cbw->command[0]) { + proc_read10_cmd(p_msc); + } else { + proc_write10_cmd(p_msc); + } + } else { + // no data transfer, only exist in complaint test suite + p_msc->stage = MSC_STAGE_STATUS; + } + } else { + // For other SCSI commands + // 1. OUT : queue transfer (invoke app callback after done) + // 2. IN & Zero: Process if is built-in, else Invoke app callback. Skip DATA if zero length + if ((p_cbw->total_bytes > 0) && !is_data_in(p_cbw->dir)) { + if (p_cbw->total_bytes > CFG_TUD_MSC_EP_BUFSIZE) { + TU_LOG_DRV(" SCSI reject non READ10/WRITE10 with large data\r\n"); + fail_scsi_op(p_msc, MSC_CSW_STATUS_FAILED); + } else { + // Didn't check for case 9 (Ho > Dn), which requires examining scsi command first + // but it is OK to just receive data then responded with failed status + TU_ASSERT(usbd_edpt_xfer(rhport, p_msc->ep_out, _mscd_epbuf.buf, (uint16_t) p_msc->total_len, false)); + } + } else { + // First process if it is a built-in commands + int32_t resplen = proc_builtin_scsi(p_cbw->lun, p_cbw->command, _mscd_epbuf.buf, CFG_TUD_MSC_EP_BUFSIZE); + + // Invoke user callback if not built-in + if ((resplen < 0) && (p_msc->sense_key == 0)) { + resplen = tud_msc_scsi_cb(p_cbw->lun, p_cbw->command, _mscd_epbuf.buf, (uint16_t)p_msc->total_len); + } + + if (resplen < 0) { + // unsupported command + TU_LOG_DRV(" SCSI unsupported or failed command\r\n"); + fail_scsi_op(p_msc, MSC_CSW_STATUS_FAILED); + } else if (resplen == 0) { + if (p_cbw->total_bytes > 0) { + // 6.7 The 13 Cases: case 4 (Hi > Dn) + // TU_LOG_DRV(" SCSI case 4 (Hi > Dn): %lu\r\n", p_cbw->total_bytes); + fail_scsi_op(p_msc, MSC_CSW_STATUS_FAILED); + } else { + // case 1 Hn = Dn: all good + p_msc->stage = MSC_STAGE_STATUS; + } + } else { + if (p_cbw->total_bytes == 0) { + // 6.7 The 13 Cases: case 2 (Hn < Di) + // TU_LOG_DRV(" SCSI case 2 (Hn < Di): %lu\r\n", p_cbw->total_bytes); + fail_scsi_op(p_msc, MSC_CSW_STATUS_FAILED); + } else { + // cannot return more than host expect + p_msc->total_len = tu_min32((uint32_t)resplen, p_cbw->total_bytes); + TU_ASSERT(usbd_edpt_xfer(rhport, p_msc->ep_in, _mscd_epbuf.buf, (uint16_t) p_msc->total_len, false)); + } + } + } + } + break; + } + + case MSC_STAGE_DATA: + TU_LOG_DRV(" SCSI Data [Lun%u]\r\n", p_cbw->lun); + TU_ASSERT(xferred_bytes <= CFG_TUD_MSC_EP_BUFSIZE); // sanity check to avoid buffer overflow + // TU_LOG_MEM(CFG_TUD_MSC_LOG_LEVEL, _mscd_epbuf.buf, xferred_bytes, 2); + + if (SCSI_CMD_READ_10 == p_cbw->command[0]) { + p_msc->xferred_len += xferred_bytes; + + if ( p_msc->xferred_len >= p_msc->total_len ) { + // Data Stage is complete + p_msc->stage = MSC_STAGE_STATUS; + }else { + proc_read10_cmd(p_msc); + } + } else if (SCSI_CMD_WRITE_10 == p_cbw->command[0]) { + proc_write10_host_data(p_msc, xferred_bytes); + } else { + p_msc->xferred_len += xferred_bytes; + + // OUT transfer, invoke callback if needed + if ( !is_data_in(p_cbw->dir) ) { + int32_t cb_result = tud_msc_scsi_cb(p_cbw->lun, p_cbw->command, _mscd_epbuf.buf, (uint16_t) p_msc->total_len); + + if ( cb_result < 0 ) { + // unsupported command + TU_LOG_DRV(" SCSI unsupported command\r\n"); + fail_scsi_op(p_msc, MSC_CSW_STATUS_FAILED); + }else { + // TODO haven't implement this scenario any further yet + } + } + + if ( p_msc->xferred_len >= p_msc->total_len ) { + // Data Stage is complete + p_msc->stage = MSC_STAGE_STATUS; + } else { + // This scenario with command that take more than one transfer is already rejected at Command stage + TU_BREAKPOINT(); + } + } + break; + + case MSC_STAGE_STATUS: + // processed immediately after this switch, supposedly to be empty + break; + + case MSC_STAGE_STATUS_SENT: + // Status phase is complete + if ((ep_addr == p_msc->ep_in) && (xferred_bytes == sizeof(msc_csw_t))) { + TU_LOG_DRV(" SCSI Status [Lun%u] = %u\r\n", p_cbw->lun, p_csw->status); + // TU_LOG_MEM(CFG_TUD_MSC_LOG_LEVEL, p_csw, xferred_bytes, 2); + + // Invoke complete callback if defined + // Note: There is racing issue with samd51 + qspi flash testing with arduino + // if complete_cb() is invoked after queuing the status. + switch (p_cbw->command[0]) { + case SCSI_CMD_READ_10: + tud_msc_read10_complete_cb(p_cbw->lun); + break; + + case SCSI_CMD_WRITE_10: + tud_msc_write10_complete_cb(p_cbw->lun); + break; + + default: + tud_msc_scsi_complete_cb(p_cbw->lun, p_cbw->command); + break; + } + + if (!usbd_edpt_stalled(rhport, p_msc->ep_out)) { + TU_ASSERT(prepare_cbw(p_msc)); + } else { + p_msc->stage = MSC_STAGE_CMD; + } + } else { + // Any xfer ended here is considered unknown error, ignore it + TU_LOG1(" Warning expect SCSI Status but received unknown data\r\n"); + } + break; + + default: break; // nothing to do + } + + if (p_msc->stage == MSC_STAGE_STATUS) { + TU_ASSERT(proc_stage_status(p_msc)); + } + + return true; +} + +/*------------------------------------------------------------------*/ +/* SCSI Command Process + *------------------------------------------------------------------*/ + +// return response's length (copied to buffer). Negative if it is not an built-in command or indicate Failed status (CSW) +// In case of a failed status, sense key must be set for reason of failure +static int32_t proc_builtin_scsi(uint8_t lun, uint8_t const scsi_cmd[16], uint8_t* buffer, uint32_t bufsize) { + (void)bufsize; // TODO refractor later + int32_t resplen; + + mscd_interface_t* p_msc = &_mscd_itf; + + switch (scsi_cmd[0]) { + case SCSI_CMD_TEST_UNIT_READY: + resplen = 0; + if (!tud_msc_test_unit_ready_cb(lun)) { + // Failed status response + resplen = -1; + + // set default sense if not set by callback + if (p_msc->sense_key == 0) { + set_sense_medium_not_present(lun); + } + } + break; + + case SCSI_CMD_START_STOP_UNIT: { + resplen = 0; + scsi_start_stop_unit_t const* start_stop = (scsi_start_stop_unit_t const*)scsi_cmd; + if (!tud_msc_start_stop_cb(lun, start_stop->power_condition, start_stop->start, start_stop->load_eject)) { + // Failed status response + resplen = -1; + + // set default sense if not set by callback + if (p_msc->sense_key == 0) { + set_sense_medium_not_present(lun); + } + } + break; + } + + case SCSI_CMD_PREVENT_ALLOW_MEDIUM_REMOVAL: { + resplen = 0; + scsi_prevent_allow_medium_removal_t const* prevent_allow = (scsi_prevent_allow_medium_removal_t const*)scsi_cmd; + if (!tud_msc_prevent_allow_medium_removal_cb(lun, prevent_allow->prohibit_removal, prevent_allow->control)) { + // Failed status response + resplen = -1; + + // set default sense if not set by callback + if (p_msc->sense_key == 0) { + set_sense_medium_not_present(lun); + } + } + break; + } + + case SCSI_CMD_READ_CAPACITY_10: { + uint32_t block_count; + uint32_t block_size; + uint16_t block_size_u16; + + tud_msc_capacity_cb(lun, &block_count, &block_size_u16); + block_size = (uint32_t)block_size_u16; + + // Invalid block size/count from callback, possibly unit is not ready + // stall this request, set sense key to NOT READY + if (block_count == 0 || block_size == 0) { + resplen = -1; + + // set default sense if not set by callback + if (p_msc->sense_key == 0) { + set_sense_medium_not_present(lun); + } + } else { + scsi_read_capacity10_resp_t read_capa10; + + read_capa10.last_lba = tu_htonl(block_count-1); + read_capa10.block_size = tu_htonl(block_size); + + resplen = sizeof(read_capa10); + TU_VERIFY(0 == tu_memcpy_s(buffer, bufsize, &read_capa10, (size_t) resplen)); + } + break; + } + + case SCSI_CMD_READ_FORMAT_CAPACITY: { + scsi_read_format_capacity_data_t read_fmt_capa = { + .list_length = 8, + .block_num = 0, + .descriptor_type = 2, // formatted media + .block_size_u16 = 0 + }; + + uint32_t block_count; + uint16_t block_size; + + tud_msc_capacity_cb(lun, &block_count, &block_size); + + // Invalid block size/count from callback, possibly unit is not ready + // stall this request, set sense key to NOT READY + if (block_count == 0 || block_size == 0) { + resplen = -1; + + // set default sense if not set by callback + if (p_msc->sense_key == 0) { + set_sense_medium_not_present(lun); + } + } else { + read_fmt_capa.block_num = tu_htonl(block_count); + read_fmt_capa.block_size_u16 = tu_htons(block_size); + + resplen = sizeof(read_fmt_capa); + TU_VERIFY(0 == tu_memcpy_s(buffer, bufsize, &read_fmt_capa, (size_t) resplen)); + } + break; + } + + case SCSI_CMD_INQUIRY: { + scsi_inquiry_resp_t *inquiry_rsp = (scsi_inquiry_resp_t *) buffer; + tu_memclr(inquiry_rsp, sizeof(scsi_inquiry_resp_t)); + inquiry_rsp->is_removable = 1; + inquiry_rsp->version = 2; + inquiry_rsp->response_data_format = 2; + inquiry_rsp->additional_length = sizeof(scsi_inquiry_resp_t) - 5; + + resplen = (int32_t) tud_msc_inquiry2_cb(lun, inquiry_rsp, bufsize); + if (resplen == 0) { + // stub callback with no response, use v1 callback + tud_msc_inquiry_cb(lun, inquiry_rsp->vendor_id, inquiry_rsp->product_id, inquiry_rsp->product_rev); + resplen = sizeof(scsi_inquiry_resp_t); + } + break; + } + + case SCSI_CMD_MODE_SENSE_6: { + scsi_mode_sense6_resp_t mode_resp = { + .data_len = 3, + .medium_type = 0, + .write_protected = false, + .reserved = 0, + .block_descriptor_len = 0 // no block descriptor are included + }; + + bool writable = tud_msc_is_writable_cb(lun); + + mode_resp.write_protected = !writable; + + resplen = sizeof(mode_resp); + TU_VERIFY(0 == tu_memcpy_s(buffer, bufsize, &mode_resp, (size_t) resplen)); + break; + } + + case SCSI_CMD_REQUEST_SENSE: { + scsi_sense_fixed_resp_t sense_rsp = { + .response_code = 0x70, // current, fixed format + .valid = 1 + }; + + sense_rsp.add_sense_len = sizeof(scsi_sense_fixed_resp_t) - 8; + sense_rsp.sense_key = (uint8_t)(p_msc->sense_key & 0x0F); + sense_rsp.add_sense_code = p_msc->add_sense_code; + sense_rsp.add_sense_qualifier = p_msc->add_sense_qualifier; + + resplen = sizeof(sense_rsp); + TU_VERIFY(0 == tu_memcpy_s(buffer, bufsize, &sense_rsp, (size_t) resplen)); + + // request sense callback could overwrite the sense data + resplen = tud_msc_request_sense_cb(lun, buffer, (uint16_t)bufsize); + + // Clear sense data after copy + (void) tud_msc_set_sense(lun, 0, 0, 0); + break; + } + + default: resplen = -1; + break; + } + + return resplen; +} + +static void proc_read10_cmd(mscd_interface_t* p_msc) { + msc_cbw_t const* p_cbw = &p_msc->cbw; + uint16_t const block_sz = rdwr10_get_blocksize(p_cbw); // already verified non-zero + TU_VERIFY(block_sz != 0, ); + // Adjust lba & offset with transferred bytes + uint32_t const lba = rdwr10_get_lba(p_cbw->command) + (p_msc->xferred_len / block_sz); + uint32_t const offset = p_msc->xferred_len % block_sz; + + // remaining bytes capped at class buffer + int32_t nbytes = (int32_t)tu_min32(CFG_TUD_MSC_EP_BUFSIZE, p_cbw->total_bytes - p_msc->xferred_len); + + p_msc->pending_io = true; + nbytes = tud_msc_read10_cb(p_cbw->lun, lba, offset, _mscd_epbuf.buf, (uint32_t)nbytes); + if (nbytes != TUD_MSC_RET_ASYNC) { + p_msc->pending_io = false; + proc_read_io_data(p_msc, nbytes); + } +} + +static void proc_read_io_data(mscd_interface_t* p_msc, int32_t nbytes) { + const uint8_t rhport = p_msc->rhport; + if (nbytes > 0) { + TU_ASSERT(usbd_edpt_xfer(rhport, p_msc->ep_in, _mscd_epbuf.buf, (uint16_t) nbytes, false),); + } else { + // nbytes is status + switch (nbytes) { + case TUD_MSC_RET_ERROR: + // error -> endpoint is stalled & status in CSW set to failed + TU_LOG_DRV(" IO read() failed\r\n"); + set_sense_medium_not_present(p_msc->cbw.lun); + fail_scsi_op(p_msc, MSC_CSW_STATUS_FAILED); + break; + + case TUD_MSC_RET_BUSY: + // not ready yet -> fake a transfer complete so that this driver callback will fire again + dcd_event_xfer_complete(rhport, p_msc->ep_in, 0, XFER_RESULT_SUCCESS, false); + break; + + default: break; // nothing to do + } + } +} + +static void proc_write10_cmd(mscd_interface_t* p_msc) { + msc_cbw_t const* p_cbw = &p_msc->cbw; + const bool writable = tud_msc_is_writable_cb(p_cbw->lun); + + if (!writable) { + // Not writable, complete this SCSI op with error + // Sense = Write protected + (void) tud_msc_set_sense(p_cbw->lun, SCSI_SENSE_DATA_PROTECT, 0x27, 0x00); + fail_scsi_op(p_msc, MSC_CSW_STATUS_FAILED); + return; + } + + // remaining bytes capped at class buffer + uint16_t nbytes = (uint16_t)tu_min32(CFG_TUD_MSC_EP_BUFSIZE, p_cbw->total_bytes - p_msc->xferred_len); + // Write10 callback will be called later when usb transfer complete + TU_ASSERT(usbd_edpt_xfer(p_msc->rhport, p_msc->ep_out, _mscd_epbuf.buf, nbytes, false),); +} + +// process new data arrived from WRITE10 +static void proc_write10_host_data(mscd_interface_t* p_msc, uint32_t xferred_bytes) { + msc_cbw_t const* p_cbw = &p_msc->cbw; + uint16_t const block_sz = rdwr10_get_blocksize(p_cbw); // already verified non-zero + TU_VERIFY(block_sz != 0, ); + + // Adjust lba & offset with transferred bytes + uint32_t const lba = rdwr10_get_lba(p_cbw->command) + (p_msc->xferred_len / block_sz); + uint32_t const offset = p_msc->xferred_len % block_sz; + + p_msc->pending_io = true; + int32_t nbytes = tud_msc_write10_cb(p_cbw->lun, lba, offset, _mscd_epbuf.buf, xferred_bytes); + if (nbytes != TUD_MSC_RET_ASYNC) { + p_msc->pending_io = false; + proc_write_io_data(p_msc, xferred_bytes, nbytes); + } +} + +static void proc_write_io_data(mscd_interface_t* p_msc, uint32_t xferred_bytes, int32_t nbytes) { + if (nbytes < 0) { + // nbytes is status + switch (nbytes) { + case TUD_MSC_RET_ERROR: + // IO error -> failed this scsi op + TU_LOG_DRV(" IO write() failed\r\n"); + set_sense_medium_not_present(p_msc->cbw.lun); + fail_scsi_op(p_msc, MSC_CSW_STATUS_FAILED); + break; + + default: break; // nothing to do + } + } else { + if ((uint32_t)nbytes < xferred_bytes) { + // Application consume less than what we got including TUD_MSC_RET_BUSY (0) + const uint32_t left_over = xferred_bytes - (uint32_t)nbytes; + if (nbytes > 0) { + memmove(_mscd_epbuf.buf, _mscd_epbuf.buf + nbytes, left_over); + } + + // fake a transfer complete with adjusted parameters --> callback will be invoked with adjusted parameters + dcd_event_xfer_complete(p_msc->rhport, p_msc->ep_out, left_over, XFER_RESULT_SUCCESS, false); + } else { + // Application consume all bytes in our buffer + p_msc->xferred_len += xferred_bytes; + + if (p_msc->xferred_len >= p_msc->total_len) { + // Data Stage is complete + p_msc->stage = MSC_STAGE_STATUS; + } else { + // prepare to receive more data from host + proc_write10_cmd(p_msc); + } + } + } +} + +#endif diff --git a/libraries/Adafruit_TinyUSB_Arduino/src/class/msc/msc_device.h b/libraries/Adafruit_TinyUSB_Arduino/src/class/msc/msc_device.h new file mode 100644 index 0000000..21e2497 --- /dev/null +++ b/libraries/Adafruit_TinyUSB_Arduino/src/class/msc/msc_device.h @@ -0,0 +1,170 @@ +/* + * 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. + * + * This file is part of the TinyUSB stack. + */ + +#ifndef TUSB_MSC_DEVICE_H_ +#define TUSB_MSC_DEVICE_H_ + +#include "common/tusb_common.h" +#include "msc.h" + +#ifdef __cplusplus + extern "C" { +#endif + +//--------------------------------------------------------------------+ +// Class Driver Configuration +//--------------------------------------------------------------------+ + +#if !defined(CFG_TUD_MSC_EP_BUFSIZE) & defined(CFG_TUD_MSC_BUFSIZE) + // TODO warn user to use new name later on + // #warning CFG_TUD_MSC_BUFSIZE is renamed to CFG_TUD_MSC_EP_BUFSIZE, please update to use the new name + #define CFG_TUD_MSC_EP_BUFSIZE CFG_TUD_MSC_BUFSIZE +#endif + +#ifndef CFG_TUD_MSC_EP_BUFSIZE + #error CFG_TUD_MSC_EP_BUFSIZE must be defined, value of a block size should work well, the more the better +#endif + +// Return value of callback functions +enum { + TUD_MSC_RET_BUSY = 0, // Busy, e.g disk I/O is not ready + TUD_MSC_RET_ERROR = -1, + TUD_MSC_RET_ASYNC = -2, // Asynchronous IO +}; + +TU_VERIFY_STATIC(CFG_TUD_MSC_EP_BUFSIZE < UINT16_MAX, "Size is not correct"); + +//--------------------------------------------------------------------+ +// Application API +//--------------------------------------------------------------------+ + +// Set SCSI sense response +bool tud_msc_set_sense(uint8_t lun, uint8_t sense_key, uint8_t add_sense_code, uint8_t add_sense_qualifier); + +// Called by Application once asynchronous I/O operation is done +// bytes_io is number of bytes in I/O op, typically the bufsize in read/write_cb() or +// TUD_MSC_RET_ERROR (-1) for error. Note TUD_MSC_RET_BUSY (0) will be treated as error as well. +bool tud_msc_async_io_done(int32_t bytes_io, bool in_isr); + +//--------------------------------------------------------------------+ +// Application Callbacks (WEAK is optional) +//--------------------------------------------------------------------+ + +/* + Invoked when received SCSI READ10/WRITE10 command + - Address = lba * BLOCK_SIZE + offset + - offset is only needed if CFG_TUD_MSC_EP_BUFSIZE is smaller than BLOCK_SIZE. + - Application fill the buffer (up to bufsize) with address contents and return number of bytes read or status. + - 0 < ret < bufsize: These bytes are transferred first and callback will be invoked again for remaining data. + - TUD_MSC_RET_BUSY + Application is buys e.g disk I/O not ready. Callback will be invoked again with the same parameters later on. + - TUD_MSC_RET_ERROR + error such as invalid address. This request will be STALLed and scsi command will be failed + - TUD_MSC_RET_ASYNC + Data I/O will be done asynchronously in a background task. Application should return immediately. + tud_msc_async_io_done() must be called once IO/ is done to signal completion. +*/ +int32_t tud_msc_read10_cb (uint8_t lun, uint32_t lba, uint32_t offset, void* buffer, uint32_t bufsize); +int32_t tud_msc_write10_cb (uint8_t lun, uint32_t lba, uint32_t offset, uint8_t* buffer, uint32_t bufsize); + +// Invoked when received SCSI_CMD_INQUIRY, v1, application should use v2 if possible +// Application fill vendor id, product id and revision with string up to 8, 16, 4 characters respectively +void tud_msc_inquiry_cb(uint8_t lun, uint8_t vendor_id[8], uint8_t product_id[16], uint8_t product_rev[4]); + +// Invoked when received SCSI_CMD_INQUIRY, v2 with full inquiry response +// Some inquiry_resp's fields are already filled with default values, application can update them +// Return length of inquiry response, typically sizeof(scsi_inquiry_resp_t) (36 bytes), can be longer if included vendor data. +uint32_t tud_msc_inquiry2_cb(uint8_t lun, scsi_inquiry_resp_t *inquiry_resp, uint32_t bufsize); + +// Invoked when received Test Unit Ready command. +// return true allowing host to read/write this LUN e.g SD card inserted +bool tud_msc_test_unit_ready_cb(uint8_t lun); + +// Invoked when received SCSI_CMD_READ_CAPACITY_10 and SCSI_CMD_READ_FORMAT_CAPACITY to determine the disk size +// Application update block count and block size +void tud_msc_capacity_cb(uint8_t lun, uint32_t* block_count, uint16_t* block_size); + +/** + * Invoked when received an SCSI command not in built-in list below. + * - READ_CAPACITY10, READ_FORMAT_CAPACITY, INQUIRY, TEST_UNIT_READY, START_STOP_UNIT, MODE_SENSE6, REQUEST_SENSE + * - READ10 and WRITE10 has their own callbacks + * + * \param[in] lun Logical unit number + * \param[in] scsi_cmd SCSI command contents which application must examine to response accordingly + * \param[out] buffer Buffer for SCSI Data Stage. + * - For INPUT: application must fill this with response. + * - For OUTPUT it holds the Data from host + * \param[in] bufsize Buffer's length. + * + * \return Actual bytes processed, can be zero for no-data command. + * \retval negative Indicate error e.g unsupported command, tinyusb will \b STALL the corresponding + * endpoint and return failed status in command status wrapper phase. + */ +int32_t tud_msc_scsi_cb (uint8_t lun, uint8_t const scsi_cmd[16], void* buffer, uint16_t bufsize); + +/*------------- Optional callbacks -------------*/ + +// Invoked when received GET_MAX_LUN request, required for multiple LUNs implementation +uint8_t tud_msc_get_maxlun_cb(void); + +// Invoked when received Start Stop Unit command +// - Start = 0 : stopped power mode, if load_eject = 1 : unload disk storage +// - Start = 1 : active mode, if load_eject = 1 : load disk storage +bool tud_msc_start_stop_cb(uint8_t lun, uint8_t power_condition, bool start, bool load_eject); + +//Invoked when we receive the Prevent / Allow Medium Removal command +bool tud_msc_prevent_allow_medium_removal_cb(uint8_t lun, uint8_t prohibit_removal, uint8_t control); + +// Invoked when received REQUEST_SENSE +int32_t tud_msc_request_sense_cb(uint8_t lun, void* buffer, uint16_t bufsize); + +// Invoked when Read10 command is complete +void tud_msc_read10_complete_cb(uint8_t lun); + +// Invoke when Write10 command is complete, can be used to flush flash caching +void tud_msc_write10_complete_cb(uint8_t lun); + +// Invoked when command in tud_msc_scsi_cb is complete +void tud_msc_scsi_complete_cb(uint8_t lun, uint8_t const scsi_cmd[16]); + +// Invoked to check if device is writable as part of SCSI WRITE10 +bool tud_msc_is_writable_cb(uint8_t lun); + +//--------------------------------------------------------------------+ +// Internal Class Driver API +//--------------------------------------------------------------------+ +void mscd_init (void); +bool mscd_deinit (void); +void mscd_reset (uint8_t rhport); +uint16_t mscd_open (uint8_t rhport, tusb_desc_interface_t const * itf_desc, uint16_t max_len); +bool mscd_control_xfer_cb (uint8_t rhport, uint8_t stage, tusb_control_request_t const * p_request); +bool mscd_xfer_cb (uint8_t rhport, uint8_t ep_addr, xfer_result_t event, uint32_t xferred_bytes); + +#ifdef __cplusplus + } +#endif + +#endif /* TUSB_MSC_DEVICE_H_ */ diff --git a/libraries/Adafruit_TinyUSB_Arduino/src/class/msc/msc_host.c b/libraries/Adafruit_TinyUSB_Arduino/src/class/msc/msc_host.c new file mode 100644 index 0000000..6a36c28 --- /dev/null +++ b/libraries/Adafruit_TinyUSB_Arduino/src/class/msc/msc_host.c @@ -0,0 +1,517 @@ +/* + * 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. + * + * This file is part of the TinyUSB stack. + */ + +#include "tusb_option.h" + +#if CFG_TUH_ENABLED && CFG_TUH_MSC + +#include "host/usbh.h" +#include "host/usbh_pvt.h" + +#include "msc_host.h" + +// Level where CFG_TUSB_DEBUG must be at least for this driver is logged +#ifndef CFG_TUH_MSC_LOG_LEVEL + #define CFG_TUH_MSC_LOG_LEVEL CFG_TUH_LOG_LEVEL +#endif + +#define TU_LOG_DRV(...) TU_LOG(CFG_TUH_MSC_LOG_LEVEL, __VA_ARGS__) + +//--------------------------------------------------------------------+ +// MACRO CONSTANT TYPEDEF +//--------------------------------------------------------------------+ +enum { + MSC_STAGE_IDLE = 0, + MSC_STAGE_CMD, + MSC_STAGE_DATA, + MSC_STAGE_STATUS, +}; + +typedef struct { + uint8_t itf_num; + uint8_t ep_in; + uint8_t ep_out; + uint8_t max_lun; + + volatile bool configured; // Receive SET_CONFIGURE + volatile bool mounted; // Enumeration is complete + + // SCSI command data + uint8_t stage; + void* buffer; + tuh_msc_complete_cb_t complete_cb; + uintptr_t complete_arg; + + struct { + uint32_t block_size; + uint32_t block_count; + } capacity[CFG_TUH_MSC_MAXLUN]; +} msch_interface_t; + +typedef struct { + TUH_EPBUF_TYPE_DEF(msc_cbw_t, cbw); + TUH_EPBUF_TYPE_DEF(msc_csw_t, csw); +} msch_epbuf_t; + +static msch_interface_t _msch_itf[CFG_TUH_DEVICE_MAX]; +CFG_TUH_MEM_SECTION static msch_epbuf_t _msch_epbuf[CFG_TUH_DEVICE_MAX]; + +TU_ATTR_ALWAYS_INLINE static inline msch_interface_t* get_itf(uint8_t daddr) { + return &_msch_itf[daddr - 1]; +} + +TU_ATTR_ALWAYS_INLINE static inline msch_epbuf_t* get_epbuf(uint8_t daddr) { + return &_msch_epbuf[daddr - 1]; +} + +//--------------------------------------------------------------------+ +// Weak stubs: invoked if no strong implementation is available +//--------------------------------------------------------------------+ +TU_ATTR_WEAK void tuh_msc_mount_cb(uint8_t dev_addr) { + (void) dev_addr; +} + +TU_ATTR_WEAK void tuh_msc_umount_cb(uint8_t dev_addr) { + (void) dev_addr; +} + +//--------------------------------------------------------------------+ +// PUBLIC API +//--------------------------------------------------------------------+ +uint8_t tuh_msc_get_maxlun(uint8_t dev_addr) { + msch_interface_t* p_msc = get_itf(dev_addr); + return p_msc->max_lun; +} + +uint32_t tuh_msc_get_block_count(uint8_t dev_addr, uint8_t lun) { + msch_interface_t* p_msc = get_itf(dev_addr); + return p_msc->capacity[lun].block_count; +} + +uint32_t tuh_msc_get_block_size(uint8_t dev_addr, uint8_t lun) { + msch_interface_t* p_msc = get_itf(dev_addr); + return p_msc->capacity[lun].block_size; +} + +bool tuh_msc_mounted(uint8_t dev_addr) { + msch_interface_t* p_msc = get_itf(dev_addr); + return p_msc->mounted; +} + +bool tuh_msc_ready(uint8_t dev_addr) { + msch_interface_t* p_msc = get_itf(dev_addr); + TU_VERIFY(p_msc->mounted); + const bool epin_busy = usbh_edpt_busy(dev_addr, p_msc->ep_in); + const bool epout_busy = usbh_edpt_busy(dev_addr, p_msc->ep_out); + return !epin_busy && !epout_busy; +} + +//--------------------------------------------------------------------+ +// PUBLIC API: SCSI COMMAND +//--------------------------------------------------------------------+ +static inline void cbw_init(msc_cbw_t* cbw, uint8_t lun) { + tu_memclr(cbw, sizeof(msc_cbw_t)); + cbw->signature = MSC_CBW_SIGNATURE; + cbw->tag = 0x54555342; // TUSB + cbw->lun = lun; +} + +bool tuh_msc_scsi_command(uint8_t daddr, msc_cbw_t const* cbw, void* data, + tuh_msc_complete_cb_t complete_cb, uintptr_t arg) { + msch_interface_t* p_msc = get_itf(daddr); + TU_VERIFY(p_msc->configured); + + // claim endpoint + TU_VERIFY(usbh_edpt_claim(daddr, p_msc->ep_out)); + msch_epbuf_t* epbuf = get_epbuf(daddr); + + epbuf->cbw = *cbw; + p_msc->buffer = data; + p_msc->complete_cb = complete_cb; + p_msc->complete_arg = arg; + p_msc->stage = MSC_STAGE_CMD; + + if (!usbh_edpt_xfer(daddr, p_msc->ep_out, (uint8_t*) &epbuf->cbw, sizeof(msc_cbw_t))) { + (void) usbh_edpt_release(daddr, p_msc->ep_out); + return false; + } + + return true; +} + +bool tuh_msc_read_capacity(uint8_t dev_addr, uint8_t lun, scsi_read_capacity10_resp_t* response, + tuh_msc_complete_cb_t complete_cb, uintptr_t arg) { + msch_interface_t* p_msc = get_itf(dev_addr); + TU_VERIFY(p_msc->configured); + + msc_cbw_t cbw; + cbw_init(&cbw, lun); + + cbw.total_bytes = sizeof(scsi_read_capacity10_resp_t); + cbw.dir = TUSB_DIR_IN_MASK; + cbw.cmd_len = sizeof(scsi_read_capacity10_t); + cbw.command[0] = SCSI_CMD_READ_CAPACITY_10; + + return tuh_msc_scsi_command(dev_addr, &cbw, response, complete_cb, arg); +} + +bool tuh_msc_inquiry(uint8_t dev_addr, uint8_t lun, scsi_inquiry_resp_t* response, + tuh_msc_complete_cb_t complete_cb, uintptr_t arg) { + msch_interface_t* p_msc = get_itf(dev_addr); + TU_VERIFY(p_msc->mounted); + + msc_cbw_t cbw; + cbw_init(&cbw, lun); + + cbw.total_bytes = sizeof(scsi_inquiry_resp_t); + cbw.dir = TUSB_DIR_IN_MASK; + cbw.cmd_len = sizeof(scsi_inquiry_t); + + scsi_inquiry_t const cmd_inquiry = { + .cmd_code = SCSI_CMD_INQUIRY, + .alloc_length = sizeof(scsi_inquiry_resp_t) + }; + memcpy(cbw.command, &cmd_inquiry, cbw.cmd_len); //-V1086 + + return tuh_msc_scsi_command(dev_addr, &cbw, response, complete_cb, arg); +} + +bool tuh_msc_test_unit_ready(uint8_t dev_addr, uint8_t lun, tuh_msc_complete_cb_t complete_cb, uintptr_t arg) { + msch_interface_t* p_msc = get_itf(dev_addr); + TU_VERIFY(p_msc->configured); + + msc_cbw_t cbw; + cbw_init(&cbw, lun); + + cbw.total_bytes = 0; + cbw.dir = TUSB_DIR_OUT; + cbw.cmd_len = sizeof(scsi_test_unit_ready_t); + cbw.command[0] = SCSI_CMD_TEST_UNIT_READY; + cbw.command[1] = lun; // according to wiki TODO need verification + + return tuh_msc_scsi_command(dev_addr, &cbw, NULL, complete_cb, arg); +} + +bool tuh_msc_request_sense(uint8_t dev_addr, uint8_t lun, void* response, + tuh_msc_complete_cb_t complete_cb, uintptr_t arg) { + msc_cbw_t cbw; + cbw_init(&cbw, lun); + + cbw.total_bytes = 18; // TODO sense response + cbw.dir = TUSB_DIR_IN_MASK; + cbw.cmd_len = sizeof(scsi_request_sense_t); + + scsi_request_sense_t const cmd_request_sense = { + .cmd_code = SCSI_CMD_REQUEST_SENSE, + .alloc_length = 18 + }; + memcpy(cbw.command, &cmd_request_sense, cbw.cmd_len); //-V1086 + + return tuh_msc_scsi_command(dev_addr, &cbw, response, complete_cb, arg); +} + +bool tuh_msc_read10(uint8_t dev_addr, uint8_t lun, void* buffer, uint32_t lba, uint16_t block_count, + tuh_msc_complete_cb_t complete_cb, uintptr_t arg) { + msch_interface_t* p_msc = get_itf(dev_addr); + TU_VERIFY(p_msc->mounted); + + msc_cbw_t cbw; + cbw_init(&cbw, lun); + + cbw.total_bytes = block_count * p_msc->capacity[lun].block_size; + cbw.dir = TUSB_DIR_IN_MASK; + cbw.cmd_len = sizeof(scsi_read10_t); + + scsi_read10_t const cmd_read10 = { + .cmd_code = SCSI_CMD_READ_10, + .lba = tu_htonl(lba), + .block_count = tu_htons(block_count) + }; + memcpy(cbw.command, &cmd_read10, cbw.cmd_len); //-V1086 + + return tuh_msc_scsi_command(dev_addr, &cbw, buffer, complete_cb, arg); +} + +bool tuh_msc_write10(uint8_t dev_addr, uint8_t lun, void const* buffer, uint32_t lba, uint16_t block_count, + tuh_msc_complete_cb_t complete_cb, uintptr_t arg) { + msch_interface_t* p_msc = get_itf(dev_addr); + TU_VERIFY(p_msc->mounted); + + msc_cbw_t cbw; + cbw_init(&cbw, lun); + + cbw.total_bytes = block_count * p_msc->capacity[lun].block_size; + cbw.dir = TUSB_DIR_OUT; + cbw.cmd_len = sizeof(scsi_write10_t); + + scsi_write10_t const cmd_write10 = { + .cmd_code = SCSI_CMD_WRITE_10, + .lba = tu_htonl(lba), + .block_count = tu_htons(block_count) + }; + memcpy(cbw.command, &cmd_write10, cbw.cmd_len); //-V1086 + + return tuh_msc_scsi_command(dev_addr, &cbw, (void*) (uintptr_t) buffer, complete_cb, arg); +} + +#if 0 +// MSC interface Reset (not used now) +bool tuh_msc_reset(uint8_t dev_addr) { + tusb_control_request_t const new_request = { + .bmRequestType_bit = { + .recipient = TUSB_REQ_RCPT_INTERFACE, + .type = TUSB_REQ_TYPE_CLASS, + .direction = TUSB_DIR_OUT + }, + .bRequest = MSC_REQ_RESET, + .wValue = 0, + .wIndex = p_msc->itf_num, + .wLength = 0 + }; + TU_ASSERT( usbh_control_xfer( dev_addr, &new_request, NULL ) ); +} +#endif + +//--------------------------------------------------------------------+ +// CLASS-USBH API +//--------------------------------------------------------------------+ +bool msch_init(void) { + TU_LOG_DRV("sizeof(msch_interface_t) = %u\r\n", sizeof(msch_interface_t)); + TU_LOG_DRV("sizeof(msch_epbuf_t) = %u\r\n", sizeof(msch_epbuf_t)); + tu_memclr(_msch_itf, sizeof(_msch_itf)); + return true; +} + +bool msch_deinit(void) { + return true; +} + +void msch_close(uint8_t dev_addr) { + TU_VERIFY(dev_addr <= CFG_TUH_DEVICE_MAX,); + msch_interface_t* p_msc = get_itf(dev_addr); + TU_VERIFY(p_msc->configured,); + + TU_LOG_DRV(" MSCh close addr = %d\r\n", dev_addr); + + // invoke Application Callback + if (p_msc->mounted) { + tuh_msc_umount_cb(dev_addr); + } + + tu_memclr(p_msc, sizeof(msch_interface_t)); +} + +bool msch_xfer_cb(uint8_t dev_addr, uint8_t ep_addr, xfer_result_t event, uint32_t xferred_bytes) { + msch_interface_t* p_msc = get_itf(dev_addr); + msch_epbuf_t* epbuf = get_epbuf(dev_addr); + msc_cbw_t const * cbw = &epbuf->cbw; + msc_csw_t * csw = &epbuf->csw; + + switch (p_msc->stage) { + case MSC_STAGE_CMD: + // Must be Command Block + TU_ASSERT(ep_addr == p_msc->ep_out && event == XFER_RESULT_SUCCESS && xferred_bytes == sizeof(msc_cbw_t)); + if (cbw->total_bytes && p_msc->buffer) { + // Data stage if any + p_msc->stage = MSC_STAGE_DATA; + uint8_t const ep_data = (cbw->dir & TUSB_DIR_IN_MASK) ? p_msc->ep_in : p_msc->ep_out; + TU_ASSERT(usbh_edpt_xfer(dev_addr, ep_data, p_msc->buffer, (uint16_t) cbw->total_bytes)); + break; + } + TU_ATTR_FALLTHROUGH; // fallthrough to data stage + + case MSC_STAGE_DATA: + // Status stage + p_msc->stage = MSC_STAGE_STATUS; + TU_ASSERT(usbh_edpt_xfer(dev_addr, p_msc->ep_in, (uint8_t*) csw, (uint16_t) sizeof(msc_csw_t))); + break; + + case MSC_STAGE_STATUS: + // SCSI op is complete + p_msc->stage = MSC_STAGE_IDLE; + if (p_msc->complete_cb != NULL) { + tuh_msc_complete_data_t const cb_data = { + .cbw = cbw, + .csw = csw, + .scsi_data = p_msc->buffer, + .user_arg = p_msc->complete_arg + }; + (void) p_msc->complete_cb(dev_addr, &cb_data); + } + break; + + default: + // unknown state + break; + } + + return true; +} + +//--------------------------------------------------------------------+ +// MSC Enumeration +//--------------------------------------------------------------------+ +static void config_get_maxlun_complete(tuh_xfer_t* xfer); +static bool config_test_unit_ready_complete(uint8_t dev_addr, tuh_msc_complete_data_t const* cb_data); +static bool config_request_sense_complete(uint8_t dev_addr, tuh_msc_complete_data_t const* cb_data); +static bool config_read_capacity_complete(uint8_t dev_addr, tuh_msc_complete_data_t const* cb_data); + +uint16_t msch_open(uint8_t rhport, uint8_t dev_addr, const tusb_desc_interface_t *desc_itf, uint16_t max_len) { + (void) rhport; + TU_VERIFY(MSC_SUBCLASS_SCSI == desc_itf->bInterfaceSubClass && MSC_PROTOCOL_BOT == desc_itf->bInterfaceProtocol, 0); + + // msc driver length is fixed + const uint16_t drv_len = + (uint16_t)(sizeof(tusb_desc_interface_t) + desc_itf->bNumEndpoints * sizeof(tusb_desc_endpoint_t)); + TU_ASSERT(drv_len <= max_len, 0); + + msch_interface_t *p_msc = get_itf(dev_addr); + const tusb_desc_endpoint_t *ep_desc = (const tusb_desc_endpoint_t *)tu_desc_next(desc_itf); + + for (uint32_t i = 0; i < 2; i++) { + TU_ASSERT(TUSB_DESC_ENDPOINT == ep_desc->bDescriptorType && TUSB_XFER_BULK == ep_desc->bmAttributes.xfer, 0); + TU_ASSERT(tuh_edpt_open(dev_addr, ep_desc), 0); + + if (TUSB_DIR_IN == tu_edpt_dir(ep_desc->bEndpointAddress)) { + p_msc->ep_in = ep_desc->bEndpointAddress; + } else { + p_msc->ep_out = ep_desc->bEndpointAddress; + } + + ep_desc = (tusb_desc_endpoint_t const*) tu_desc_next(ep_desc); + } + + p_msc->itf_num = desc_itf->bInterfaceNumber; + + return drv_len; +} + +bool msch_set_config(uint8_t daddr, uint8_t itf_num) { + msch_interface_t* p_msc = get_itf(daddr); + TU_ASSERT(p_msc->itf_num == itf_num); + p_msc->configured = true; + + //------------- Get Max Lun -------------// + TU_LOG_DRV("MSC Get Max Lun\r\n"); + tusb_control_request_t const request = { + .bmRequestType_bit = { + .recipient = TUSB_REQ_RCPT_INTERFACE, + .type = TUSB_REQ_TYPE_CLASS, + .direction = TUSB_DIR_IN + }, + .bRequest = MSC_REQ_GET_MAX_LUN, + .wValue = 0, + .wIndex = itf_num, + .wLength = 1 + }; + + uint8_t* enum_buf = usbh_get_enum_buf(); + tuh_xfer_t xfer = { + .daddr = daddr, + .ep_addr = 0, + .setup = &request, + .buffer = enum_buf, + .complete_cb = config_get_maxlun_complete, + .user_data = 0 + }; + TU_ASSERT(tuh_control_xfer(&xfer)); + + return true; +} + +static void config_get_maxlun_complete(tuh_xfer_t* xfer) { + uint8_t const daddr = xfer->daddr; + msch_interface_t* p_msc = get_itf(daddr); + + // MAXLUN's response is minus 1 by specs, STALL means 1 + if (XFER_RESULT_SUCCESS == xfer->result) { + uint8_t* enum_buf = usbh_get_enum_buf(); + p_msc->max_lun = enum_buf[0] + 1; + } else { + p_msc->max_lun = 1; + } + + TU_LOG_DRV(" Max LUN = %u\r\n", p_msc->max_lun); + + // TODO multiple LUN support + TU_LOG_DRV("SCSI Test Unit Ready\r\n"); + uint8_t const lun = 0; + tuh_msc_test_unit_ready(daddr, lun, config_test_unit_ready_complete, 0); +} + +static bool config_test_unit_ready_complete(uint8_t dev_addr, tuh_msc_complete_data_t const* cb_data) { + msc_cbw_t const* cbw = cb_data->cbw; + msc_csw_t const* csw = cb_data->csw; + uint8_t* enum_buf = usbh_get_enum_buf(); + + if (csw->status == 0) { + // Unit is ready, read its capacity + TU_LOG_DRV("SCSI Read Capacity\r\n"); + tuh_msc_read_capacity(dev_addr, cbw->lun, (scsi_read_capacity10_resp_t*) (uintptr_t) enum_buf, + config_read_capacity_complete, 0); + } else { + // Note: During enumeration, some device fails Test Unit Ready and require a few retries + // with Request Sense to start working !! + // TODO limit number of retries + TU_LOG_DRV("SCSI Request Sense\r\n"); + TU_ASSERT(tuh_msc_request_sense(dev_addr, cbw->lun, enum_buf, config_request_sense_complete, 0)); + } + + return true; +} + +static bool config_request_sense_complete(uint8_t dev_addr, tuh_msc_complete_data_t const* cb_data) { + msc_cbw_t const* cbw = cb_data->cbw; + msc_csw_t const* csw = cb_data->csw; + + TU_ASSERT(csw->status == 0); + TU_ASSERT(tuh_msc_test_unit_ready(dev_addr, cbw->lun, config_test_unit_ready_complete, 0)); + return true; +} + +static bool config_read_capacity_complete(uint8_t dev_addr, tuh_msc_complete_data_t const* cb_data) { + msc_cbw_t const* cbw = cb_data->cbw; + msc_csw_t const* csw = cb_data->csw; + TU_ASSERT(csw->status == 0); + msch_interface_t* p_msc = get_itf(dev_addr); + uint8_t* enum_buf = usbh_get_enum_buf(); + + // Capacity response field: Block size and Last LBA are both Big-Endian + scsi_read_capacity10_resp_t* resp = (scsi_read_capacity10_resp_t*) (uintptr_t) enum_buf; + p_msc->capacity[cbw->lun].block_count = (uint32_t) (tu_ntohl(resp->last_lba) + 1u); + p_msc->capacity[cbw->lun].block_size = tu_ntohl(resp->block_size); + + // Mark enumeration is complete + p_msc->mounted = true; + tuh_msc_mount_cb(dev_addr); + + // notify usbh that driver enumeration is complete + usbh_driver_set_config_complete(dev_addr, p_msc->itf_num); + + return true; +} + +#endif diff --git a/libraries/Adafruit_TinyUSB_Arduino/src/class/msc/msc_host.h b/libraries/Adafruit_TinyUSB_Arduino/src/class/msc/msc_host.h new file mode 100644 index 0000000..5dc7c0b --- /dev/null +++ b/libraries/Adafruit_TinyUSB_Arduino/src/class/msc/msc_host.h @@ -0,0 +1,138 @@ +/* + * 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. + * + * This file is part of the TinyUSB stack. + */ + +#ifndef TUSB_MSC_HOST_H_ +#define TUSB_MSC_HOST_H_ + +#include "msc.h" + +#ifdef __cplusplus +extern "C" { +#endif + +//--------------------------------------------------------------------+ +// Class Driver Configuration +//--------------------------------------------------------------------+ + +#ifndef CFG_TUH_MSC_MAXLUN + #define CFG_TUH_MSC_MAXLUN 4 +#endif + +typedef struct { + const msc_cbw_t *cbw; // SCSI command + const msc_csw_t *csw; // SCSI status + void *scsi_data; // SCSI Data + uintptr_t user_arg; // user argument +} tuh_msc_complete_data_t; + +typedef bool (*tuh_msc_complete_cb_t)(uint8_t dev_addr, const tuh_msc_complete_data_t *cb_data); + +//--------------------------------------------------------------------+ +// Application API +//--------------------------------------------------------------------+ + +// Check if device supports MassStorage interface. +// This function true after tuh_msc_mounted_cb() and false after tuh_msc_unmounted_cb() +bool tuh_msc_mounted(uint8_t dev_addr); + +// Check if the interface is currently ready or busy transferring data +bool tuh_msc_ready(uint8_t dev_addr); + +// Get Max Lun +uint8_t tuh_msc_get_maxlun(uint8_t dev_addr); + +// Get number of block +uint32_t tuh_msc_get_block_count(uint8_t dev_addr, uint8_t lun); + +// Get block size in bytes +uint32_t tuh_msc_get_block_size(uint8_t dev_addr, uint8_t lun); + +// Perform a full SCSI command (cbw, data, csw) in non-blocking manner. +// Complete callback is invoked when SCSI op is complete. +// return true if success, false if there is already pending operation. +// NOTE: buffer must be accessible by USB/DMA controller, aligned correctly and multiple of cache line if enabled +bool tuh_msc_scsi_command(uint8_t daddr, const msc_cbw_t *cbw, void *data, tuh_msc_complete_cb_t complete_cb, + uintptr_t arg); + +// Perform SCSI Inquiry command +// Complete callback is invoked when SCSI op is complete. +// NOTE: response must be accessible by USB/DMA controller, aligned correctly and multiple of cache line if enabled +bool tuh_msc_inquiry(uint8_t dev_addr, uint8_t lun, scsi_inquiry_resp_t *response, tuh_msc_complete_cb_t complete_cb, + uintptr_t arg); + +// Perform SCSI Test Unit Ready command +// Complete callback is invoked when SCSI op is complete. +bool tuh_msc_test_unit_ready(uint8_t dev_addr, uint8_t lun, tuh_msc_complete_cb_t complete_cb, uintptr_t arg); + +// Perform SCSI Request Sense 10 command +// Complete callback is invoked when SCSI op is complete. +// NOTE: response must be accessible by USB/DMA controller, aligned correctly and multiple of cache line if enabled +bool tuh_msc_request_sense(uint8_t dev_addr, uint8_t lun, void *response, tuh_msc_complete_cb_t complete_cb, + uintptr_t arg); + +// Perform SCSI Read 10 command. Read n blocks starting from LBA to buffer +// Complete callback is invoked when SCSI op is complete. +// NOTE: buffer must be accessible by USB/DMA controller, aligned correctly and multiple of cache line if enabled +bool tuh_msc_read10(uint8_t dev_addr, uint8_t lun, void *buffer, uint32_t lba, uint16_t block_count, + tuh_msc_complete_cb_t complete_cb, uintptr_t arg); + +// Perform SCSI Write 10 command. Write n blocks starting from LBA to device +// Complete callback is invoked when SCSI op is complete. +// NOTE: buffer must be accessible by USB/DMA controller, aligned correctly and multiple of cache line if enabled +bool tuh_msc_write10(uint8_t dev_addr, uint8_t lun, const void *buffer, uint32_t lba, uint16_t block_count, + tuh_msc_complete_cb_t complete_cb, uintptr_t arg); + +// Perform SCSI Read Capacity 10 command +// Complete callback is invoked when SCSI op is complete. +// Note: during enumeration, host stack already carried out this request. Application can retrieve capacity by +// simply call tuh_msc_get_block_count() and tuh_msc_get_block_size() +bool tuh_msc_read_capacity(uint8_t dev_addr, uint8_t lun, scsi_read_capacity10_resp_t *response, + tuh_msc_complete_cb_t complete_cb, uintptr_t arg); + +//------------- Application Callback -------------// + +// Invoked when a device with MassStorage interface is mounted +void tuh_msc_mount_cb(uint8_t dev_addr); + +// Invoked when a device with MassStorage interface is unmounted +void tuh_msc_umount_cb(uint8_t dev_addr); + +//--------------------------------------------------------------------+ +// Internal Class Driver API +//--------------------------------------------------------------------+ + +bool msch_init(void); +bool msch_deinit(void); +uint16_t msch_open(uint8_t rhport, uint8_t dev_addr, const tusb_desc_interface_t *desc_itf, uint16_t max_len); +bool msch_set_config(uint8_t daddr, uint8_t itf_num); +void msch_close(uint8_t dev_addr); +bool msch_xfer_cb(uint8_t dev_addr, uint8_t ep_addr, xfer_result_t event, uint32_t xferred_bytes); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/libraries/Adafruit_TinyUSB_Arduino/src/class/mtp/mtp.h b/libraries/Adafruit_TinyUSB_Arduino/src/class/mtp/mtp.h new file mode 100644 index 0000000..7b22837 --- /dev/null +++ b/libraries/Adafruit_TinyUSB_Arduino/src/class/mtp/mtp.h @@ -0,0 +1,908 @@ +/* + * The MIT License (MIT) + * + * Copyright (c) 2025 Ennebi Elettronica (https://ennebielettronica.com) + * + * 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_MTP_H_ +#define TUSB_MTP_H_ + +#include "common/tusb_common.h" + +#if (CFG_TUD_ENABLED && CFG_TUD_MTP) + +#ifdef __cplusplus + extern "C" { +#endif + +//--------------------------------------------------------------------+ +// Media Transfer Protocol Class Constant +//--------------------------------------------------------------------+ + +// Media Transfer Protocol Subclass +typedef enum { + MTP_SUBCLASS_STILL_IMAGE = 1 +} mtp_subclass_type_t; + +// MTP Protocol. +typedef enum { + MTP_PROTOCOL_PIMA_15470 = 1, ///< Picture Transfer Protocol (PIMA 15470) +} mtp_protocol_type_t; + +// PTP/MTP protocol phases +typedef enum { + MTP_PHASE_COMMAND = 0, + MTP_PHASE_DATA, + MTP_PHASE_RESPONSE, + MTP_PHASE_ERROR +} mtp_phase_type_t; + +// PTP/MTP Class requests, PIMA 15740-2000: D.5.2 +typedef enum { + MTP_REQ_CANCEL = 0x64, + MTP_REQ_GET_EXT_EVENT_DATA = 0x65, + MTP_REQ_RESET = 0x66, + MTP_REQ_GET_DEVICE_STATUS = 0x67, +} mtp_class_request_t; + +// PTP/MTP Container type +typedef enum { + MTP_CONTAINER_TYPE_UNDEFINED = 0, + MTP_CONTAINER_TYPE_COMMAND_BLOCK = 1, + MTP_CONTAINER_TYPE_DATA_BLOCK = 2, + MTP_CONTAINER_TYPE_RESPONSE_BLOCK = 3, + MTP_CONTAINER_TYPE_EVENT_BLOCK = 4, +} mtp_container_type_t; + +// MTP 1.1 Appendix A: Object formats +typedef enum { + // ---- Base formats ---- + MTP_OBJ_FORMAT_UNDEFINED = 0x3000u, // Undefined object + MTP_OBJ_FORMAT_ASSOCIATION = 0x3001u, // Association (for example, a folder) + MTP_OBJ_FORMAT_SCRIPT = 0x3002u, // Device model-specific script + MTP_OBJ_FORMAT_EXECUTABLE = 0x3003u, // Device model-specific binary executable + MTP_OBJ_FORMAT_TEXT = 0x3004u, // Text file + MTP_OBJ_FORMAT_HTML = 0x3005u, // Hypertext Markup Language file (text) + MTP_OBJ_FORMAT_DPOF = 0x3006u, // Digital Print Order Format file (text) + MTP_OBJ_FORMAT_AIFF = 0x3007u, // Audio clip (AIFF) + MTP_OBJ_FORMAT_WAV = 0x3008u, // Audio clip (WAV) + MTP_OBJ_FORMAT_MP3 = 0x3009u, // MPEG-1 Layer III audio (ISO/IEC 13818-3) + MTP_OBJ_FORMAT_AVI = 0x300Au, // Video clip (AVI) + MTP_OBJ_FORMAT_MPEG = 0x300Bu, // Video clip (MPEG) + MTP_OBJ_FORMAT_ASF = 0x300Cu, // Microsoft Advanced Streaming Format (video) + + // ---- Image formats ---- + MTP_OBJ_FORMAT_UNDEFINED_IMAGE = 0x3800u, // Undefined image object + MTP_OBJ_FORMAT_EXIF_JPEG = 0x3801u, // Exchangeable Image Format, JEIDA standard + MTP_OBJ_FORMAT_TIFF_EP = 0x3802u, // Tag Image File Format for Electronic Photography + MTP_OBJ_FORMAT_FLASHPIX = 0x3803u, // Structured Storage Image Format (FlashPix) + MTP_OBJ_FORMAT_BMP = 0x3804u, // Microsoft Windows Bitmap file + MTP_OBJ_FORMAT_CIFF = 0x3805u, // Canon Camera Image File Format + MTP_OBJ_FORMAT_UNDEFINED_3806 = 0x3806u, // Reserved / Undefined + MTP_OBJ_FORMAT_GIF = 0x3807u, // Graphics Interchange Format + MTP_OBJ_FORMAT_JFIF = 0x3808u, // JPEG File Interchange Format + MTP_OBJ_FORMAT_CD = 0x3809u, // PhotoCD Image Pac + MTP_OBJ_FORMAT_PICT = 0x380Au, // Quickdraw Image Format + MTP_OBJ_FORMAT_PNG = 0x380Bu, // Portable Network Graphics + MTP_OBJ_FORMAT_UNDEFINED_380C = 0x380Cu, // Reserved / Undefined + MTP_OBJ_FORMAT_TIFF = 0x380Du, // Tag Image File Format (baseline) + MTP_OBJ_FORMAT_TIFF_IT = 0x380Eu, // Tag Image File Format for IT (graphic arts) + MTP_OBJ_FORMAT_JP2 = 0x380Fu, // JPEG2000 Baseline File Format + MTP_OBJ_FORMAT_JPX = 0x3810u, // JPEG2000 Extended File Format + + // ---- Firmware & misc ---- + MTP_OBJ_FORMAT_UNDEFINED_FIRMWARE = 0xB802u, // Undefined Firmware + MTP_OBJ_FORMAT_WBMP = 0xB803u, // Wireless Application Protocol Bitmap Format (.wbmp) + MTP_OBJ_FORMAT_WINDOWS_IMAGE = 0xB881u, // Windows Image Format + MTP_OBJ_FORMAT_JPEGXR = 0xB804u, // JPEG XR (.hdp, .jxr, .wdp) + + // ---- Audio formats ---- + MTP_OBJ_FORMAT_UNDEFINED_AUDIO = 0xB900u, // Undefined audio object + MTP_OBJ_FORMAT_WMA = 0xB901u, // Windows Media Audio + MTP_OBJ_FORMAT_OGG = 0xB902u, // OGG container + MTP_OBJ_FORMAT_AAC = 0xB903u, // Advanced Audio Coding (.aac) + MTP_OBJ_FORMAT_AUDIBLE = 0xB904u, // Audible format + MTP_OBJ_FORMAT_FLAC = 0xB906u, // Free Lossless Audio Codec + MTP_OBJ_FORMAT_QCELP = 0xB907u, // Qualcomm Code Excited Linear Prediction (.qcp) + MTP_OBJ_FORMAT_AMR = 0xB908u, // Adaptive Multi-Rate audio (.amr) + + // ---- Video formats ---- + MTP_OBJ_FORMAT_UNDEFINED_VIDEO = 0xB980u, // Undefined video object + MTP_OBJ_FORMAT_WMV = 0xB981u, // Windows Media Video + MTP_OBJ_FORMAT_MP4 = 0xB982u, // MP4 Container (ISO 14496-1) + MTP_OBJ_FORMAT_MP2 = 0xB983u, // MPEG-1 Layer II audio + MTP_OBJ_FORMAT_3GP = 0xB984u, // 3GP Container + MTP_OBJ_FORMAT_3G2 = 0xB985u, // 3GPP2 Container + MTP_OBJ_FORMAT_AVCHD = 0xB986u, // AVCHD (MPEG-4 AVC + Dolby Digital) + MTP_OBJ_FORMAT_ATSC_TS = 0xB987u, // ATSC-compliant MPEG-2 Transport Stream + MTP_OBJ_FORMAT_DVB_TS = 0xB988u, // DVB-compliant MPEG-2 Transport Stream + + // ---- Collections ---- + MTP_OBJ_FORMAT_UNDEFINED_COLLECTION = 0xBA00u, // Undefined collection + MTP_OBJ_FORMAT_ABSTRACT_MULTIMEDIA_ALBUM = 0xBA01u, // Abstract Multimedia Album + MTP_OBJ_FORMAT_ABSTRACT_IMAGE_ALBUM = 0xBA02u, // Abstract Image Album + MTP_OBJ_FORMAT_ABSTRACT_AUDIO_ALBUM = 0xBA03u, // Abstract Audio Album + MTP_OBJ_FORMAT_ABSTRACT_VIDEO_ALBUM = 0xBA04u, // Abstract Video Album + MTP_OBJ_FORMAT_ABSTRACT_AV_PLAYLIST = 0xBA05u, // Abstract Audio & Video Playlist + MTP_OBJ_FORMAT_ABSTRACT_CONTACT_GROUP = 0xBA06u, // Abstract Contact Group + MTP_OBJ_FORMAT_ABSTRACT_MESSAGE_FOLDER = 0xBA07u, // Abstract Message Folder + MTP_OBJ_FORMAT_ABSTRACT_CHAPTERED_PRODUCTION = 0xBA08u, // Abstract Chaptered Production + MTP_OBJ_FORMAT_ABSTRACT_AUDIO_PLAYLIST = 0xBA09u, // Abstract Audio Playlist + MTP_OBJ_FORMAT_ABSTRACT_VIDEO_PLAYLIST = 0xBA0Au, // Abstract Video Playlist + MTP_OBJ_FORMAT_ABSTRACT_MEDIACAST = 0xBA0Bu, // Abstract Mediacast (RSS enclosure) + + // ---- Playlist formats ---- + MTP_OBJ_FORMAT_WPL_PLAYLIST = 0xBA10u, // Windows Media Player Playlist (.wpl) + MTP_OBJ_FORMAT_M3U_PLAYLIST = 0xBA11u, // M3U Playlist + MTP_OBJ_FORMAT_MPL_PLAYLIST = 0xBA12u, // MPL Playlist + MTP_OBJ_FORMAT_ASX_PLAYLIST = 0xBA13u, // ASX Playlist + MTP_OBJ_FORMAT_PLS_PLAYLIST = 0xBA14u, // PLS Playlist + + // ---- Document formats ---- + MTP_OBJ_FORMAT_UNDEFINED_DOC = 0xBA80u, // Undefined Document + MTP_OBJ_FORMAT_ABSTRACT_DOC = 0xBA81u, // Abstract Document + MTP_OBJ_FORMAT_XML_DOC = 0xBA82u, // XML Document + MTP_OBJ_FORMAT_DOC = 0xBA83u, // Microsoft Word Document + MTP_OBJ_FORMAT_MHT_DOC = 0xBA84u, // MHT Compiled HTML Document + MTP_OBJ_FORMAT_XLS = 0xBA85u, // Microsoft Excel Spreadsheet + MTP_OBJ_FORMAT_PPT = 0xBA86u, // Microsoft PowerPoint Presentation + + // ---- Messaging ---- + MTP_OBJ_FORMAT_UNDEFINED_MSG = 0xBB00u, // Undefined Message + MTP_OBJ_FORMAT_ABSTRACT_MSG = 0xBB01u, // Abstract Message + + // ---- Bookmarks ---- + MTP_OBJ_FORMAT_UNDEFINED_BOOKMARK = 0xBB10u, // Undefined Bookmark + MTP_OBJ_FORMAT_ABSTRACT_BOOKMARK = 0xBB11u, // Abstract Bookmark + + // ---- Appointments ---- + MTP_OBJ_FORMAT_UNDEFINED_APPT = 0xBB20u, // Undefined Appointment + MTP_OBJ_FORMAT_ABSTRACT_APPT = 0xBB21u, // Abstract Appointment + MTP_OBJ_FORMAT_VCALENDAR1 = 0xBB22u, // vCalendar 1.0 + + // ---- Tasks ---- + MTP_OBJ_FORMAT_UNDEFINED_TASK = 0xBB40u, // Undefined Task + MTP_OBJ_FORMAT_ABSTRACT_TASK = 0xBB41u, // Abstract Task + MTP_OBJ_FORMAT_ICALENDAR = 0xBB42u, // iCalendar + + // ---- Notes ---- + MTP_OBJ_FORMAT_UNDEFINED_NOTE = 0xBB60u, // Undefined Note + MTP_OBJ_FORMAT_ABSTRACT_NOTE = 0xBB61u, // Abstract Note + + // ---- Contacts ---- + MTP_OBJ_FORMAT_UNDEFINED_CONTACT= 0xBB80u, // Undefined Contact + MTP_OBJ_FORMAT_ABSTRACT_CONTACT = 0xBB81u, // Abstract Contact + MTP_OBJ_FORMAT_VCARD2 = 0xBB82u, // vCard 2.1 + MTP_OBJ_FORMAT_VCARD3 = 0xBB83u, // vCard 3.0 +} mtp_object_formats_t; + +// MTP 1.1 Appendix B: Object Properties +typedef enum { + MTP_OBJ_PROP_STORAGE_ID = 0xDC01u, // StorageID + MTP_OBJ_PROP_OBJECT_FORMAT = 0xDC02u, // Object Format + MTP_OBJ_PROP_PROTECTION_STATUS = 0xDC03u, // Protection Status + MTP_OBJ_PROP_OBJECT_SIZE = 0xDC04u, // Object Size + MTP_OBJ_PROP_ASSOCIATION_TYPE = 0xDC05u, // Association Type + MTP_OBJ_PROP_ASSOCIATION_DESC = 0xDC06u, // Association Description + MTP_OBJ_PROP_OBJECT_FILE_NAME = 0xDC07u, // Object File Name + MTP_OBJ_PROP_DATE_CREATED = 0xDC08u, // Date Created + MTP_OBJ_PROP_DATE_MODIFIED = 0xDC09u, // Date Modified + MTP_OBJ_PROP_KEYWORDS = 0xDC0Au, // Keywords + MTP_OBJ_PROP_PARENT_OBJECT = 0xDC0Bu, // Parent Object + MTP_OBJ_PROP_ALLOWED_FOLDER_CONTENTS = 0xDC0Cu, // Allowed Folder Contents + MTP_OBJ_PROP_HIDDEN = 0xDC0Du, // Hidden + MTP_OBJ_PROP_SYSTEM_OBJECT = 0xDC0Eu, // System Object + // 0xDC0F-0xDC40 is reserved + + MTP_OBJ_PROP_PERSISTENT_UID = 0xDC41u, // Persistent Unique Object Identifier + MTP_OBJ_PROP_SYNC_ID = 0xDC42u, // SyncID + MTP_OBJ_PROP_PROPERTY_BAG = 0xDC43u, // Property Bag + MTP_OBJ_PROP_NAME = 0xDC44u, // Name + MTP_OBJ_PROP_CREATED_BY = 0xDC45u, // Created By + MTP_OBJ_PROP_ARTIST = 0xDC46u, // Artist + MTP_OBJ_PROP_DATE_AUTHORED = 0xDC47u, // Date Authored + MTP_OBJ_PROP_DESCRIPTION = 0xDC48u, // Description + MTP_OBJ_PROP_URL_REFERENCE = 0xDC49u, // URL Reference + MTP_OBJ_PROP_LANGUAGE_LOCALE = 0xDC4Au, // Language-Locale + MTP_OBJ_PROP_COPYRIGHT_INFO = 0xDC4Bu, // Copyright Information + MTP_OBJ_PROP_SOURCE = 0xDC4Cu, // Source + MTP_OBJ_PROP_ORIGIN_LOCATION = 0xDC4Du, // Origin Location + MTP_OBJ_PROP_DATE_ADDED = 0xDC4Eu, // Date Added + MTP_OBJ_PROP_NON_CONSUMABLE = 0xDC4Fu, // Non-Consumable + MTP_OBJ_PROP_CORRUPT_UNPLAYABLE = 0xDC50u, // Corrupt/Unplayable + MTP_OBJ_PROP_PRODUCER_SERIAL_NUMBER = 0xDC51u, // ProducerSerialNumber + // 0xDC52-0xDC80 is reserved + + MTP_OBJ_PROP_REP_SAMPLE_FORMAT = 0xDC81u, // Representative Sample Format + MTP_OBJ_PROP_REP_SAMPLE_SIZE = 0xDC82u, // Representative Sample Size + MTP_OBJ_PROP_REP_SAMPLE_HEIGHT = 0xDC83u, // Representative Sample Height + MTP_OBJ_PROP_REP_SAMPLE_WIDTH = 0xDC84u, // Representative Sample Width + MTP_OBJ_PROP_REP_SAMPLE_DURATION = 0xDC85u, // Representative Sample Duration + MTP_OBJ_PROP_REP_SAMPLE_DATA = 0xDC86u, // Representative Sample Data + MTP_OBJ_PROP_WIDTH = 0xDC87u, // Width + MTP_OBJ_PROP_HEIGHT = 0xDC88u, // Height + MTP_OBJ_PROP_DURATION = 0xDC89u, // Duration + MTP_OBJ_PROP_RATING = 0xDC8Au, // Rating + MTP_OBJ_PROP_TRACK = 0xDC8Bu, // Track + MTP_OBJ_PROP_GENRE = 0xDC8Cu, // Genre + MTP_OBJ_PROP_CREDITS = 0xDC8Du, // Credits + MTP_OBJ_PROP_LYRICS = 0xDC8Eu, // Lyrics + MTP_OBJ_PROP_SUBSCRIPTION_CONTENT_ID = 0xDC8Fu, // Subscription Content ID + MTP_OBJ_PROP_PRODUCED_BY = 0xDC90u, // Produced By + MTP_OBJ_PROP_USE_COUNT = 0xDC91u, // Use Count + MTP_OBJ_PROP_SKIP_COUNT = 0xDC92u, // Skip Count + MTP_OBJ_PROP_LAST_ACCESSED = 0xDC93u, // Last Accessed + MTP_OBJ_PROP_PARENTAL_RATING = 0xDC94u, // Parental Rating + MTP_OBJ_PROP_META_GENRE = 0xDC95u, // Meta Genre + MTP_OBJ_PROP_COMPOSER = 0xDC96u, // Composer + MTP_OBJ_PROP_EFFECTIVE_RATING = 0xDC97u, // Effective Rating + MTP_OBJ_PROP_SUBTITLE = 0xDC98u, // Subtitle + MTP_OBJ_PROP_ORIGINAL_RELEASE_DATE = 0xDC99u, // Original Release Date + MTP_OBJ_PROP_ALBUM_NAME = 0xDC9Au, // Album Name + MTP_OBJ_PROP_ALBUM_ARTIST = 0xDC9Bu, // Album Artist + MTP_OBJ_PROP_MOOD = 0xDC9Cu, // Mood + MTP_OBJ_PROP_DRM_STATUS = 0xDC9Du, // DRM Status + MTP_OBJ_PROP_SUB_DESCRIPTION = 0xDC9Eu, // Sub Description + // 0xDC9F-0xDCD0 is reserved + + MTP_OBJ_PROP_IS_CROPPED = 0xDCD1u, // Is Cropped + MTP_OBJ_PROP_IS_COLOUR_CORRECTED = 0xDCD2u, // Is Colour Corrected + MTP_OBJ_PROP_IMAGE_BIT_DEPTH = 0xDCD3u, // Image Bit Depth + MTP_OBJ_PROP_FNUMBER = 0xDCD4u, // Fnumber (aperture ×100) + MTP_OBJ_PROP_EXPOSURE_TIME = 0xDCD5u, // Exposure Time (sec ×10,000) + MTP_OBJ_PROP_EXPOSURE_INDEX = 0xDCD6u, // Exposure Index (ISO) + // 0xDCD7-0xDCDF is reserved + + MTP_OBJ_PROP_DISPLAY_NAME = 0xDCE0u, // Display Name + MTP_OBJ_PROP_BODY_TEXT = 0xDCE1u, // Body Text + MTP_OBJ_PROP_SUBJECT = 0xDCE2u, // Subject + MTP_OBJ_PROP_PRIORITY = 0xDCE3u, // Priority + // 0xDCE4-0xDCFF is reserved + + MTP_OBJ_PROP_GIVEN_NAME = 0xDD00u, // Given Name + MTP_OBJ_PROP_MIDDLE_NAMES = 0xDD01u, // Middle Names + MTP_OBJ_PROP_FAMILY_NAME = 0xDD02u, // Family Name + MTP_OBJ_PROP_PREFIX = 0xDD03u, // Prefix + MTP_OBJ_PROP_SUFFIX = 0xDD04u, // Suffix + MTP_OBJ_PROP_PHONETIC_GIVEN_NAME = 0xDD05u, // Phonetic Given Name + MTP_OBJ_PROP_PHONETIC_FAMILY_NAME = 0xDD06u, // Phonetic Family Name + MTP_OBJ_PROP_EMAIL_PRIMARY = 0xDD07u, // Email Primary + MTP_OBJ_PROP_EMAIL_PERSONAL_1 = 0xDD08u, // Email Personal 1 + MTP_OBJ_PROP_EMAIL_PERSONAL_2 = 0xDD09u, // Email Personal 2 + MTP_OBJ_PROP_EMAIL_BUSINESS_1 = 0xDD0Au, // Email Business 1 + MTP_OBJ_PROP_EMAIL_BUSINESS_2 = 0xDD0Bu, // Email Business 2 + MTP_OBJ_PROP_EMAIL_OTHERS = 0xDD0Cu, // Email Others + MTP_OBJ_PROP_PHONE_PRIMARY = 0xDD0Du, // Phone Number Primary + MTP_OBJ_PROP_PHONE_PERSONAL_1 = 0xDD0Eu, // Phone Number Personal + MTP_OBJ_PROP_PHONE_PERSONAL_2 = 0xDD0Fu, // Phone Number Personal 2 + MTP_OBJ_PROP_PHONE_BUSINESS_1 = 0xDD10u, // Phone Number Business + MTP_OBJ_PROP_PHONE_BUSINESS_2 = 0xDD11u, // Phone Number Business 2 + MTP_OBJ_PROP_PHONE_MOBILE_1 = 0xDD12u, // Phone Number Mobile + MTP_OBJ_PROP_PHONE_MOBILE_2 = 0xDD13u, // Phone Number Mobile 2 + MTP_OBJ_PROP_FAX_PRIMARY = 0xDD14u, // Fax Number Primary + MTP_OBJ_PROP_FAX_PERSONAL = 0xDD15u, // Fax Number Personal + MTP_OBJ_PROP_FAX_BUSINESS = 0xDD16u, // Fax Number Business + MTP_OBJ_PROP_PAGER_NUMBER = 0xDD17u, // Pager Number + MTP_OBJ_PROP_PHONE_OTHERS = 0xDD18u, // Phone Number Others + MTP_OBJ_PROP_WEB_PRIMARY = 0xDD19u, // Primary Web Address + MTP_OBJ_PROP_WEB_PERSONAL = 0xDD1Au, // Personal Web Address + MTP_OBJ_PROP_WEB_BUSINESS = 0xDD1Bu, // Business Web Address + MTP_OBJ_PROP_IM_ADDRESS_1 = 0xDD1Cu, // Instant Messenger Address + MTP_OBJ_PROP_IM_ADDRESS_2 = 0xDD1Du, // Instant Messenger Address 2 + MTP_OBJ_PROP_IM_ADDRESS_3 = 0xDD1Eu, // Instant Messenger Address 3 + MTP_OBJ_PROP_ADDR_PERSONAL_FULL = 0xDD1Fu, // Postal Address Personal Full + MTP_OBJ_PROP_ADDR_PERSONAL_LINE1 = 0xDD20u, // Postal Address Personal Line 1 + MTP_OBJ_PROP_ADDR_PERSONAL_LINE2 = 0xDD21u, // Postal Address Personal Line 2 + MTP_OBJ_PROP_ADDR_PERSONAL_CITY = 0xDD22u, // Postal Address Personal City + MTP_OBJ_PROP_ADDR_PERSONAL_REGION = 0xDD23u, // Postal Address Personal Region + MTP_OBJ_PROP_ADDR_PERSONAL_POSTAL_CODE = 0xDD24u, // Postal Address Personal Postal Code + MTP_OBJ_PROP_ADDR_PERSONAL_COUNTRY = 0xDD25u, // Postal Address Personal Country + MTP_OBJ_PROP_ADDR_BUSINESS_FULL = 0xDD26u, // Postal Address Business Full + MTP_OBJ_PROP_ADDR_BUSINESS_LINE1 = 0xDD27u, // Postal Address Business Line 1 + MTP_OBJ_PROP_ADDR_BUSINESS_LINE2 = 0xDD28u, // Postal Address Business Line 2 + MTP_OBJ_PROP_ADDR_BUSINESS_CITY = 0xDD29u, // Postal Address Business City + MTP_OBJ_PROP_ADDR_BUSINESS_REGION = 0xDD2Au, // Postal Address Business Region + MTP_OBJ_PROP_ADDR_BUSINESS_POSTAL_CODE = 0xDD2Bu, // Postal Address Business Postal Code + MTP_OBJ_PROP_ADDR_BUSINESS_COUNTRY = 0xDD2Cu, // Postal Address Business Country + MTP_OBJ_PROP_ADDR_OTHER_FULL = 0xDD2Du, // Postal Address Other Full + MTP_OBJ_PROP_ADDR_OTHER_LINE1 = 0xDD2Eu, // Postal Address Other Line 1 + MTP_OBJ_PROP_ADDR_OTHER_LINE2 = 0xDD2Fu, // Postal Address Other Line 2 + MTP_OBJ_PROP_ADDR_OTHER_CITY = 0xDD30u, // Postal Address Other City + MTP_OBJ_PROP_ADDR_OTHER_REGION = 0xDD31u, // Postal Address Other Region + MTP_OBJ_PROP_ADDR_OTHER_POSTAL_CODE = 0xDD32u, // Postal Address Other Postal Code + MTP_OBJ_PROP_ADDR_OTHER_COUNTRY = 0xDD33u, // Postal Address Other Country + MTP_OBJ_PROP_ORGANIZATION_NAME = 0xDD34u, // Organization Name + MTP_OBJ_PROP_PHONETIC_ORG_NAME = 0xDD35u, // Phonetic Organization Name + MTP_OBJ_PROP_ROLE = 0xDD36u, // Role + MTP_OBJ_PROP_BIRTHDATE = 0xDD37u, // Birthdate + // 0xDD38-0xDD3F is reserved + + MTP_OBJ_PROP_MESSAGE_TO = 0xDD40u, // Message To + MTP_OBJ_PROP_MESSAGE_CC = 0xDD41u, // Message CC + MTP_OBJ_PROP_MESSAGE_BCC = 0xDD42u, // Message BCC + MTP_OBJ_PROP_MESSAGE_READ = 0xDD43u, // Message Read + MTP_OBJ_PROP_MESSAGE_RECEIVED_TIME = 0xDD44u, // Message Received Time + MTP_OBJ_PROP_MESSAGE_SENDER = 0xDD45u, // Message Sender + // 0xDD46-0xDD4F is reserved + + MTP_OBJ_PROP_ACTIVITY_BEGIN_TIME = 0xDD50u, // Activity Begin Time + MTP_OBJ_PROP_ACTIVITY_END_TIME = 0xDD51u, // Activity End Time + MTP_OBJ_PROP_ACTIVITY_LOCATION = 0xDD52u, // Activity Location + // 0xDD53 is reserved + MTP_OBJ_PROP_ACTIVITY_REQUIRED_ATTENDEES= 0xDD54u, // Activity Required Attendees + MTP_OBJ_PROP_ACTIVITY_OPTIONAL_ATTENDEES= 0xDD55u, // Activity Optional Attendees + MTP_OBJ_PROP_ACTIVITY_RESOURCES = 0xDD56u, // Activity Resources + MTP_OBJ_PROP_ACTIVITY_ACCEPTED = 0xDD57u, // Activity Accepted + MTP_OBJ_PROP_ACTIVITY_TENTATIVE = 0xDD58u, // Activity Tentative + MTP_OBJ_PROP_ACTIVITY_DECLINED = 0xDD59u, // Activity Declined + MTP_OBJ_PROP_ACTIVITY_REMINDER_TIME = 0xDD5Au, // Activity Reminder Time + MTP_OBJ_PROP_ACTIVITY_OWNER = 0xDD5Bu, // Activity Owner + MTP_OBJ_PROP_ACTIVITY_STATUS = 0xDD5Cu, // Activity Status + MTP_OBJ_PROP_OWNER = 0xDD5Du, // Owner + MTP_OBJ_PROP_EDITOR = 0xDD5Eu, // Editor + MTP_OBJ_PROP_WEBMASTER = 0xDD5Fu, // Webmaster + + MTP_OBJ_PROP_URL_SOURCE = 0xDD60u, // URL Source + MTP_OBJ_PROP_URL_DESTINATION = 0xDD61u, // URL Destination + MTP_OBJ_PROP_TIME_BOOKMARK = 0xDD62u, // Time Bookmark + MTP_OBJ_PROP_OBJECT_BOOKMARK = 0xDD63u, // Object Bookmark + MTP_OBJ_PROP_BYTE_BOOKMARK = 0xDD64u, // Byte Bookmark + // 0xDD65-0xDD6F is reserved + + MTP_OBJ_PROP_LAST_BUILD_DATE = 0xDD70u, // Last Build Date + MTP_OBJ_PROP_TIME_TO_LIVE = 0xDD71u, // Time to Live (minutes) + MTP_OBJ_PROP_MEDIA_GUID = 0xDD72u, // Media GUID + // 0xDD73-0xDDFF is reserved + + // media encoding + MTP_OBJ_PROP_TOTAL_BITRATE = 0xDE91u, // Total BitRate + MTP_OBJ_PROP_BITRATE_TYPE = 0xDE92u, // Bitrate Type + MTP_OBJ_PROP_SAMPLE_RATE = 0xDE93u, // Sample Rate + MTP_OBJ_PROP_NUM_CHANNELS = 0xDE94u, // Number Of Channels + MTP_OBJ_PROP_AUDIO_BITDEPTH = 0xDE95u, // Audio BitDepth + // 0xDE96 is reserved + MTP_OBJ_PROP_SCAN_TYPE = 0xDE97u, // Scan Type + // 0xDE98 is reserved + MTP_OBJ_PROP_AUDIO_WAVE_CODEC = 0xDE99u, // Audio WAVE Codec + MTP_OBJ_PROP_AUDIO_BITRATE = 0xDE9Au, // Audio BitRate + MTP_OBJ_PROP_VIDEO_FOURCC_CODEC = 0xDE9Bu, // Video FourCC Codec + MTP_OBJ_PROP_VIDEO_BITRATE = 0xDE9Cu, // Video BitRate + MTP_OBJ_PROP_FRAMES_PER_KSEC = 0xDE9Du, // Frames Per Thousand Seconds + MTP_OBJ_PROP_KEYFRAME_DISTANCE = 0xDE9Eu, // KeyFrame Distance (ms) + MTP_OBJ_PROP_BUFFER_SIZE = 0xDE9Fu, // Buffer Size + MTP_OBJ_PROP_ENCODING_QUALITY = 0xDEA0u, // Encoding Quality + MTP_OBJ_PROP_ENCODING_PROFILE = 0xDEA1u // Encoding Profile +} mtp_object_properties_t; + + +// MTP 1.1 Appendeix C: Device Properties +typedef enum { + MTP_DEV_PROP_UNDEFINED = 0x5000u, + MTP_DEV_PROP_BATTERY_LEVEL = 0x5001u, + MTP_DEV_PROP_FUNCTIONAL_MODE = 0x5002u, + MTP_DEV_PROP_IMAGE_SIZE = 0x5003u, + MTP_DEV_PROP_COMPRESSION_SETTING = 0x5004u, + MTP_DEV_PROP_WHITE_BALANCE = 0x5005u, + MTP_DEV_PROP_RGB_GAIN = 0x5006u, + MTP_DEV_PROP_F_NUMBER = 0x5007u, + MTP_DEV_PROP_FOCAL_LENGTH = 0x5008u, + MTP_DEV_PROP_FOCUS_DISTANCE = 0x5009u, + MTP_DEV_PROP_FOCUS_MODE = 0x500Au, + MTP_DEV_PROP_EXPOSURE_METERING_MODE = 0x500Bu, + MTP_DEV_PROP_FLASH_MODE = 0x500Cu, + MTP_DEV_PROP_EXPOSURE_TIME = 0x500Du, + MTP_DEV_PROP_EXPOSURE_PROGRAM_MODE = 0x500Eu, + MTP_DEV_PROP_EXPOSURE_INDEX = 0x500Fu, + MTP_DEV_PROP_EXPOSURE_BIAS_COMPENSATION = 0x5010u, + MTP_DEV_PROP_DATE_TIME = 0x5011u, + MTP_DEV_PROP_CAPTURE_DELAY = 0x5012u, + MTP_DEV_PROP_STILL_CAPTURE_MODE = 0x5013u, + MTP_DEV_PROP_CONTRAST = 0x5014u, + MTP_DEV_PROP_SHARPNESS = 0x5015u, + MTP_DEV_PROP_DIGITAL_ZOOM = 0x5016u, + MTP_DEV_PROP_EFFECT_MODE = 0x5017u, + MTP_DEV_PROP_BURST_NUMBER = 0x5018u, + MTP_DEV_PROP_BURST_INTERVAL = 0x5019u, + MTP_DEV_PROP_TIMELAPSE_NUMBER = 0x501Au, + MTP_DEV_PROP_TIMELAPSE_INTERVAL = 0x501Bu, + MTP_DEV_PROP_FOCUS_METERING_MODE = 0x501Cu, + MTP_DEV_PROP_UPLOAD_URL = 0x501Du, + MTP_DEV_PROP_ARTIST = 0x501Eu, + MTP_DEV_PROP_COPYRIGHT_INFO = 0x501Fu, + MTP_DEV_PROP_SYNCHRONIZTION_PARTNER = 0xD401, + MTP_DEV_PROP_DEVICE_FRIENDLY_NAME = 0xD402u, + MTP_DEV_PROP_VOLUME = 0xD403u, + MTP_DEV_PROP_SUPPORTED_FORMATS_ORDERED = 0xD404u, + MTP_DEV_PROP_DEVICE_ICON = 0xD405u, + MTP_DEV_PROP_SECTION_INITIATOR_VERSION_INFO = 0xD406u, + MTP_DEV_PROP_PERCEIVED_DEVICE_TYPE = 0xD407u, + MTP_DEV_PROP_PLAYBACK_RATE = 0xD410u, + MTP_DEV_PROP_PLAYBACK_OBJECT = 0xD411u, + MTP_DEV_PROP_PLAYBACK_CONTAINER_INDEX = 0xD412u, +} mtp_event_properties_t; + +// MTP 1.1 Appendix D: Operations +typedef enum { + MTP_OP_UNDEFINED = 0x1000u, + MTP_OP_GET_DEVICE_INFO = 0x1001u, + MTP_OP_OPEN_SESSION = 0x1002u, + MTP_OP_CLOSE_SESSION = 0x1003u, + MTP_OP_GET_STORAGE_IDS = 0x1004u, + MTP_OP_GET_STORAGE_INFO = 0x1005u, + MTP_OP_GET_NUM_OBJECTS = 0x1006u, + MTP_OP_GET_OBJECT_HANDLES = 0x1007u, + MTP_OP_GET_OBJECT_INFO = 0x1008u, + MTP_OP_GET_OBJECT = 0x1009u, + MTP_OP_GET_THUMB = 0x100Au, + MTP_OP_DELETE_OBJECT = 0x100Bu, + MTP_OP_SEND_OBJECT_INFO = 0x100Cu, + MTP_OP_SEND_OBJECT = 0x100Du, + MTP_OP_INITIATE_CAPTURE = 0x100Eu, + MTP_OP_FORMAT_STORE = 0x100Fu, + MTP_OP_RESET_DEVICE = 0x1010u, + MTP_OP_SELF_TEST = 0x1011u, + MTP_OP_SET_OBJECT_PROTECTION = 0x1012u, + MTP_OP_POWER_DOWN = 0x1013u, + MTP_OP_GET_DEVICE_PROP_DESC = 0x1014u, + MTP_OP_GET_DEVICE_PROP_VALUE = 0x1015u, + MTP_OP_SET_DEVICE_PROP_VALUE = 0x1016u, + MTP_OP_RESET_DEVICE_PROP_VALUE = 0x1017u, + MTP_OP_TERMINATE_OPEN_CAPTURE = 0x1018u, + MTP_OP_MOVE_OBJECT = 0x1019u, + MTP_OP_COPY_OBJECT = 0x101Au, + MTP_OP_GET_PARTIAL_OBJECT = 0x101Bu, + MTP_OP_INITIATE_OPEN_CAPTURE = 0x101Bu, + MTP_OP_GET_OBJECT_PROPS_SUPPORTED = 0x9801u, + MTP_OP_GET_OBJECT_PROP_DESC = 0x9802u, + MTP_OP_GET_OBJECT_PROP_VALUE = 0x9803u, + MTP_OP_SET_OBJECT_PROP_VALUE = 0x9804u, + MTP_OP_GET_OBJECT_PROPLIST = 0x9805u, + MTP_OP_GET_OBJECT_PROP_REFERENCES = 0x9810u, + + MTP_OP_GET_SERVICE_IDS = 0x9301u, + MTP_OP_GET_SERVICE_INFO = 0x9302u, + MTP_OP_GET_SERVICE_CAPABILITIES = 0x9303u, + MTP_OP_GET_SERVICE_PROP_DESC = 0x9304u, + + // Appendix E: Enhanced Operations + MTP_OP_GET_OBJECT_PROP_LIST = 0x9805u, + MTP_OP_SET_OBJECT_PROP_LIST = 0x9806u, + MTP_OP_GET_INTERDEPENDENT_PROP_DESC = 0x9807u, + MTP_OP_SEND_OBJECT_PROP_LIST = 0x9808u, +} mtp_operation_code_t; + +// Appendix F: Responses +typedef enum { + MTP_RESP_UNDEFINED = 0x2000u, + MTP_RESP_OK = 0x2001u, + MTP_RESP_GENERAL_ERROR = 0x2002u, + MTP_RESP_SESSION_NOT_OPEN = 0x2003u, + MTP_RESP_INVALID_TRANSACTION_ID = 0x2004u, + MTP_RESP_OPERATION_NOT_SUPPORTED = 0x2005u, + MTP_RESP_PARAMETER_NOT_SUPPORTED = 0x2006u, + MTP_RESP_INCOMPLETE_TRANSFER = 0x2007u, + MTP_RESP_INVALID_STORAGE_ID = 0x2008u, + MTP_RESP_INVALID_OBJECT_HANDLE = 0x2009u, + MTP_RESP_DEVICE_PROP_NOT_SUPPORTED = 0x200Au, + MTP_RESP_INVALID_OBJECT_FORMAT_CODE = 0x200Bu, + MTP_RESP_STORE_FULL = 0x200Cu, + MTP_RESP_OBJECT_WRITE_PROTECTED = 0x200Du, + MPT_RESC_STORE_READ_ONLY = 0x200Eu, + MTP_RESP_ACCESS_DENIED = 0x200Fu, + MTP_RESP_NO_THUMBNAIL_PRESENT = 0x2010u, + MTP_RESP_SELF_TEST_FAILED = 0x2011u, + MTP_RESP_PARTIAL_DELETION = 0x2012u, + MTP_RESP_STORE_NOT_AVAILABLE = 0x2013u, + MTP_RESP_SPECIFICATION_BY_FORMAT_UNSUPPORTED = 0x2014u, + MTP_RESP_NO_VALID_OBJECTINFO = 0x2015u, + MTP_RESP_INVALID_CODE_FORMAT = 0x2016u, + MTP_RESP_UNKNOWN_VENDOR_CODE = 0x2017u, + MTP_RESP_CAPTURE_ALREADY_TERMINATED = 0x2018u, + MTP_RESP_DEVICE_BUSY = 0x2019u, + MTP_RESP_INVALID_PARENT_OBJECT = 0x201Au, + MTP_RESP_INVALID_DEVICE_PROP_FORMAT = 0x201Bu, + MTP_RESP_INVALID_DEVICE_PROP_VALUE = 0x201Cu, + MTP_RESP_INVALID_PARAMETER = 0x201Du, + MTP_RESP_SESSION_ALREADY_OPEN = 0x201Eu, + MTP_RESP_TRANSACTION_CANCELLED = 0x201Fu, + MTP_RESP_SPEC_OF_DESTINATION_UNSUPPORTED = 0x2020u, + + MTP_RESP_INVALID_OBJECT_PROP_CODE = 0xA801u, + MTP_RESP_INVALID_OBJECT_PROP_FORMAT = 0xA802u, + MTP_RESP_INVALID_OBJECT_PROP_VALUE = 0xA803u, + MTP_RESP_INVALID_OBJECT_REFERENCE = 0xA804u, + MTP_RESP_GROUP_NOT_SUPPORTED = 0xA805u, + MTP_RESP_INVALID_DATASET = 0xA806u, + MTP_RESP_SPEC_BY_GROUP_UNSUPPORTED = 0xA807u, + MTP_RESP_SPEC_BY_DEPTH_UNSUPPORTED = 0xA808u, + MTP_RESP_OBJECT_TOO_LARGE = 0xA809u, + MTP_RESP_OBJECT_PROP_NOT_SUPPORTED = 0xA80Au, +} mtp_response_t; + +// Appendix G: Events +typedef enum { + MTP_EVENT_UNDEFINED = 0x4000, + MTP_EVENT_CANCEL_TRANSACTION = 0x4001, + MTP_EVENT_OBJECT_ADDED = 0x4002, + MTP_EVENT_OBJECT_REMOVED = 0x4003, + MTP_EVENT_STORE_ADDED = 0x4004, + MTP_EVENT_STORE_REMOVED = 0x4005, + MTP_EVENT_DEVICE_PROP_CHANGED = 0x4006, + MTP_EVENT_OBJECT_INFO_CHANGED = 0x4007, + MTP_EVENT_DEVICE_INFO_CHANGED = 0x4008, + MTP_EVENT_REQUEST_OBJECT_TRANSFER = 0x4009, + MTP_EVENT_STORE_FULL = 0x400Au, + MTP_EVENT_DEVICE_RESET = 0x400Bu, + MTP_EVENT_STORAGE_INFO_CHANGED = 0x400Cu, + MTP_EVENT_CAPTURE_COMPLETE = 0x400Du, + MTP_EVENT_UNREPORTED_STATUS = 0x400Eu, + MTP_EVENT_OBJECT_PROP_CHANGED = 0xC801u, + MTP_EVENT_OBJECT_PROP_DESC_CHANGED = 0xC802u, + MTP_EVENT_OBJECT_REFERENCES_CHANGED = 0xC803u, +} mtp_event_code_t; + +// Datatypes +typedef enum { + MTP_DATA_TYPE_UNDEFINED = 0x0000u, + // scalars + MTP_DATA_TYPE_INT8 = 0x0001u, + MTP_DATA_TYPE_UINT8 = 0x0002u, + MTP_DATA_TYPE_INT16 = 0x0003u, + MTP_DATA_TYPE_UINT16 = 0x0004u, + MTP_DATA_TYPE_INT32 = 0x0005u, + MTP_DATA_TYPE_UINT32 = 0x0006u, + MTP_DATA_TYPE_INT64 = 0x0007u, + MTP_DATA_TYPE_UINT64 = 0x0008u, + MTP_DATA_TYPE_INT128 = 0x0009u, + MTP_DATA_TYPE_UINT128 = 0x000Au, + // array + MTP_DATA_TYPE_AINT8 = 0x4001u, + MTP_DATA_TYPE_AUINT8 = 0x4002u, + MTP_DATA_TYPE_AINT16 = 0x4003u, + MTP_DATA_TYPE_AUINT16 = 0x4004u, + MTP_DATA_TYPE_AINT32 = 0x4005u, + MTP_DATA_TYPE_AUINT32 = 0x4006u, + MTP_DATA_TYPE_AINT64 = 0x4007u, + MTP_DATA_TYPE_AUINT64 = 0x4008u, + MTP_DATA_TYPE_AINT128 = 0x4009u, + MTP_DATA_TYPE_AUINT128 = 0x400Au, + MTP_DATA_TYPE_STR = 0xFFFFu, +} mtp_data_type_t; + +// Get/Set +typedef enum { + MTP_MODE_GET = 0x00u, + MTP_MODE_GET_SET = 0x01u, +} mtp_mode_get_set_t; + +typedef enum { + MTP_STORAGE_TYPE_UNDEFINED = 0x0000u, + MTP_STORAGE_TYPE_FIXED_ROM = 0x0001u, + MTP_STORAGE_TYPE_REMOVABLE_ROM = 0x0002u, + MTP_STORAGE_TYPE_FIXED_RAM = 0x0003u, + MTP_STORAGE_TYPE_REMOVABLE_RAM = 0x0004u, +} mtp_storage_type_t; + +typedef enum { + MTP_FILESYSTEM_TYPE_UNDEFINED = 0x0000u, + MTP_FILESYSTEM_TYPE_GENERIC_FLAT = 0x0001u, + MTP_FILESYSTEM_TYPE_GENERIC_HIERARCHICAL = 0x0002u, + MTP_FILESYSTEM_TYPE_DCF = 0x0003u, +} mtp_filesystem_type_t; + +typedef enum { + MTP_ACCESS_CAPABILITY_READ_WRITE = 0x0000u, + MTP_ACCESS_CAPABILITY_READ_ONLY_WITHOUT_OBJECT_DELETION = 0x0001u, + MTP_ACCESS_CAPABILITY_READ_ONLY_WITH_OBJECT_DELETION = 0x0002u, +} mtp_access_capability_t; + +typedef enum { + MTP_PROTECTION_STATUS_NO_PROTECTION = 0x0000u, + MTP_PROTECTION_STATUS_READ_ONLY = 0x0001u, + MTP_PROTECTION_STATUS_READ_ONLY_DATA = 0x8002u, + MTP_PROTECTION_NON_TRANSFERABLE_DATA = 0x8003u, +} mtp_protection_status_t; + +typedef enum { + MTP_ASSOCIATION_UNDEFINED = 0x0000u, + MTP_ASSOCIATION_GENERIC_FOLDER = 0x0001u, + MTP_ASSOCIATION_ALBUM = 0x0002u, + MTP_ASSOCIATION_TIME_SEQUENCE = 0x0003u, + MTP_ASSOCIATION_HORIZONTAL_PANORAMIC = 0x0004u, + MTP_ASSOCIATION_VERTICAL_PANORAMIC = 0x0005u, + MTP_ASSOCIATION_2D_PANORAMIC = 0x0006u, + MTP_ASSOCIATION_ANCILLARY_DATA = 0x0007u, +} mtp_association_t; + +//--------------------------------------------------------------------+ +// Data structures +//--------------------------------------------------------------------+ +typedef struct TU_ATTR_PACKED { + uint32_t len; + uint16_t type; + uint16_t code; + uint32_t transaction_id; +} mtp_container_header_t; +TU_VERIFY_STATIC(sizeof(mtp_container_header_t) == 12, "size is not correct"); + +typedef struct TU_ATTR_PACKED { + mtp_container_header_t header; + uint32_t params[5]; +} mtp_container_command_t; +TU_VERIFY_STATIC(sizeof(mtp_container_command_t) == 32, "size is not correct"); + +// PTP/MTP Generic container +typedef struct TU_ATTR_PACKED { + mtp_container_header_t header; + uint8_t payload[(CFG_TUD_MTP_EP_BUFSIZE - sizeof(mtp_container_header_t))]; +} mtp_generic_container_t; + +typedef struct { + mtp_container_header_t* header; + union { + uint8_t* payload; + uint16_t* payload16; + uint32_t* payload32; + }; + uint32_t payload_bytes; // available bytes for read/write +} mtp_container_info_t; + +typedef struct TU_ATTR_PACKED { + uint16_t code; + uint32_t session_id; + uint32_t transaction_id; + uint32_t params[3]; +} mtp_event_t; +TU_VERIFY_STATIC(sizeof(mtp_event_t) == 22, "size is not correct"); + +#define mtp_string_t(_nchars) \ + struct TU_ATTR_PACKED { \ + uint8_t count; /* in characters including null */ \ + uint16_t utf16[_nchars]; \ + } + +#define mtp_array_t(_type, _count) \ + struct TU_ATTR_PACKED { \ + uint32_t count; \ + _type arr[_count];\ + } + +#define mtp_aint8_t(_count) mtp_array_t(int8_t, _count) +#define mtp_auint16_t(_count) mtp_array_t(uint16_t, _count) +#define mtp_auint32_t(_count) mtp_array_t(uint32_t, _count) +#define mtp_auint64_t(_count) mtp_array_t(uint64_t, _count) + +#define MTP_STORAGE_INFO_STRUCT(_storage_desc_chars, _volume_id_chars) \ + struct TU_ATTR_PACKED { \ + uint16_t storage_type; \ + uint16_t filesystem_type; \ + uint16_t access_capability; \ + uint64_t max_capacity_in_bytes; \ + uint64_t free_space_in_bytes; \ + uint32_t free_space_in_objects; \ + mtp_string_t(_storage_desc_chars) storage_description; \ + mtp_string_t(_volume_id_chars) volume_identifier; \ + } + +// Object Info Dataset without dynamic string: filename, date_created, date_modified, keywords +typedef struct TU_ATTR_PACKED { + uint32_t storage_id; + uint16_t object_format; + uint16_t protection_status; + uint32_t object_compressed_size; + uint16_t thumb_format; + uint32_t thumb_compressed_size; + uint32_t thumb_pix_width; + uint32_t thumb_pix_height; + uint32_t image_pix_width; + uint32_t image_pix_height; + uint32_t image_bit_depth; + uint32_t parent_object; // 0: root + uint16_t association_type; + uint32_t association_desc; + uint32_t sequence_number; + // mtp_string_t() filename + // mtp_string_t() date_created + // mtp_string_t() date_modified + // mtp_string_t() keywords +} mtp_object_info_header_t; + +// Device property desc up to get/set +typedef struct TU_ATTR_PACKED { + uint16_t device_property_code; + uint16_t datatype; + uint8_t get_set; +} mtp_device_prop_desc_header_t; + +// The following fields will be dynamically added to the struct at runtime: +// - wstring factory_def_value; +// - wstring current_value_len; +// - uint8_t form_flag; + +// no form +#define MTP_DEVICE_PROPERTIES_STRUCT(_type) \ + struct TU_ATTR_PACKED { \ + uint16_t device_property_code; \ + uint16_t datatype; \ + uint8_t get_set; \ + _type factory_default; \ + _type current_value; \ + uint8_t form_flag; /* 0: none, 1: range, 2: enum */ \ + }; + +typedef struct TU_ATTR_PACKED { + uint16_t code; + uint32_t transaction_id; +} mtp_request_reset_cancel_data_t; +TU_VERIFY_STATIC(sizeof(mtp_request_reset_cancel_data_t) == 6, "size is not correct"); + +//--------------------------------------------------------------------+ +// Container helper function +// return number of bytes added +//--------------------------------------------------------------------+ + +// return payload buffer for next write +TU_ATTR_ALWAYS_INLINE static inline uint8_t* mtp_container_payload_ptr(mtp_container_info_t* p_container) { + // only 1st packet include header + uint32_t pos = p_container->header->len - sizeof(mtp_container_header_t); + while (pos > CFG_TUD_MTP_EP_BUFSIZE) { + pos -= CFG_TUD_MTP_EP_BUFSIZE; + } + return p_container->payload + pos; +} + +// only add_raw does partial copy +TU_ATTR_ALWAYS_INLINE static inline uint32_t mtp_container_add_raw(mtp_container_info_t* p_container, const void* data, uint32_t len) { + uint8_t* buf = mtp_container_payload_ptr(p_container); + const uint32_t added_len = tu_min32(len, CFG_TUD_MTP_EP_BUFSIZE - p_container->header->len); + if (added_len > 0) { + memcpy(buf, data, added_len); + } + p_container->header->len += len; // always increase len, even partial copy + return added_len; +} + +TU_ATTR_ALWAYS_INLINE static inline uint32_t mtp_container_add_array(mtp_container_info_t* p_container, uint8_t scalar_size, uint32_t count, const void* data) { + const uint32_t added_len = 4 + count * scalar_size; + TU_ASSERT(p_container->header->len + added_len < CFG_TUD_MTP_EP_BUFSIZE, 0); + uint8_t* buf = p_container->payload + p_container->header->len - sizeof(mtp_container_header_t); + + tu_unaligned_write32(buf, count); + p_container->header->len += 4; + buf += 4; + + memcpy(buf, data, count * scalar_size); + p_container->header->len += count * scalar_size; + + return added_len; +} + +TU_ATTR_ALWAYS_INLINE static inline uint32_t mtp_container_add_string(mtp_container_info_t* p_container, uint16_t* utf16) { + uint32_t count = 0; + while (utf16[count] != 0u) { + count++; + } + // MTP strings store length in a single uint8_t, including trailing null. + TU_ASSERT(count < UINT8_MAX, 0); + count++; + + uint8_t* buf = p_container->payload + p_container->header->len - sizeof(mtp_container_header_t); + + if (count == 1) { + // empty string (size only): single zero byte + TU_ASSERT(p_container->header->len + 1 < CFG_TUD_MTP_EP_BUFSIZE, 0); + *buf = 0; + p_container->header->len++; + return 1u; + } + + const uint32_t added_len = 1u + count * 2u; + TU_ASSERT(p_container->header->len + added_len < CFG_TUD_MTP_EP_BUFSIZE, 0); + + *buf++ = (uint8_t) count; + p_container->header->len++; + + memcpy(buf, utf16, 2u * count); + p_container->header->len += 2u * count; + + return added_len; +} + +TU_ATTR_ALWAYS_INLINE static inline uint32_t mtp_container_add_cstring(mtp_container_info_t* p_container, const char* str) { + const size_t cstr_len = strlen(str); + // MTP strings store length in a single uint8_t, including trailing null. + TU_ASSERT(cstr_len < UINT8_MAX, 0); + + const uint32_t count = (uint32_t) cstr_len + 1u; // include null + uint8_t* buf = p_container->payload + p_container->header->len - sizeof(mtp_container_header_t); + + if (count == 1u) { + // empty string (size only): single zero byte + TU_ASSERT(p_container->header->len + 1 < CFG_TUD_MTP_EP_BUFSIZE, 0); + *buf = 0; + p_container->header->len++; + return 1u; + } + + const uint32_t added_len = 1u + 2u * count; + TU_ASSERT(p_container->header->len + added_len < CFG_TUD_MTP_EP_BUFSIZE, 0); + + *buf++ = (uint8_t) count; + p_container->header->len++; + + for (uint32_t i = 0; i < count; i++) { + *buf++ = (uint8_t) str[i]; + *buf++ = 0; + } + p_container->header->len += 2u * count; + + return added_len; +} + +TU_ATTR_ALWAYS_INLINE static inline uint32_t mtp_container_add_uint8(mtp_container_info_t* p_container, uint8_t data) { + return mtp_container_add_raw(p_container, &data, 1); +} + +TU_ATTR_ALWAYS_INLINE static inline uint32_t mtp_container_add_uint16(mtp_container_info_t* p_container, uint16_t data) { + return mtp_container_add_raw(p_container, &data, 2); +} + +TU_ATTR_ALWAYS_INLINE static inline uint32_t mtp_container_add_uint32(mtp_container_info_t* p_container, uint32_t data) { + return mtp_container_add_raw(p_container, &data, 4); +} + +TU_ATTR_ALWAYS_INLINE static inline uint32_t mtp_container_add_uint64(mtp_container_info_t* p_container, uint64_t data) { + return mtp_container_add_raw(p_container, &data, 8); +} + +TU_ATTR_ALWAYS_INLINE static inline uint32_t mtp_container_add_uint128(mtp_container_info_t* p_container, const void* data) { + return mtp_container_add_raw(p_container, data, 16); +} + +TU_ATTR_ALWAYS_INLINE static inline uint32_t mtp_container_add_auint8(mtp_container_info_t* p_container, uint32_t count, const uint8_t* data) { + return mtp_container_add_array(p_container, sizeof(uint8_t), count, data); +} + +TU_ATTR_ALWAYS_INLINE static inline uint32_t mtp_container_add_auint16(mtp_container_info_t* p_container, uint32_t count, const uint16_t* data) { + return mtp_container_add_array(p_container, sizeof(uint16_t), count, data); +} + +TU_ATTR_ALWAYS_INLINE static inline uint32_t mtp_container_add_auint32(mtp_container_info_t* p_container, uint32_t count, const uint32_t* data) { + return mtp_container_add_array(p_container, sizeof(uint32_t), count, data); +} + +//--------------------------------------------------------------------+ +// +//--------------------------------------------------------------------+ +TU_ATTR_ALWAYS_INLINE static inline uint32_t mtp_container_get_string(uint8_t* buf, uint16_t utf16[]) { + size_t nchars = *buf++; + memcpy(utf16, buf, 2u * nchars); + return 1u + 2u * nchars; +} + +#ifdef __cplusplus + } +#endif + +#endif +#endif diff --git a/libraries/Adafruit_TinyUSB_Arduino/src/class/mtp/mtp_device.c b/libraries/Adafruit_TinyUSB_Arduino/src/class/mtp/mtp_device.c new file mode 100644 index 0000000..0da984f --- /dev/null +++ b/libraries/Adafruit_TinyUSB_Arduino/src/class/mtp/mtp_device.c @@ -0,0 +1,567 @@ +/* + * The MIT License (MIT) + * + * Copyright (c) 2025 Ennebi Elettronica (https://ennebielettronica.com) + * + * 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. + */ + +#include "tusb_option.h" + +#if (CFG_TUD_ENABLED && CFG_TUD_MTP) + +//--------------------------------------------------------------------+ +// INCLUDE +//--------------------------------------------------------------------+ +#include "device/dcd.h" +#include "device/usbd.h" +#include "device/usbd_pvt.h" + +#include "mtp_device.h" + +// Level where CFG_TUSB_DEBUG must be at least for this driver is logged +#ifndef CFG_TUD_MTP_LOG_LEVEL + #define CFG_TUD_MTP_LOG_LEVEL CFG_TUD_LOG_LEVEL +#endif + +#define TU_LOG_DRV(...) TU_LOG(CFG_TUD_MTP_LOG_LEVEL, __VA_ARGS__) + +//--------------------------------------------------------------------+ +// Weak stubs: invoked if no strong implementation is available +//--------------------------------------------------------------------+ +TU_ATTR_WEAK bool tud_mtp_request_cancel_cb(tud_mtp_request_cb_data_t* cb_data) { + (void) cb_data; + return false; +} +TU_ATTR_WEAK bool tud_mtp_request_device_reset_cb(tud_mtp_request_cb_data_t* cb_data) { + (void) cb_data; + return false; +} +TU_ATTR_WEAK int32_t tud_mtp_request_get_extended_event_cb(tud_mtp_request_cb_data_t* cb_data) { + (void) cb_data; + return -1; +} +TU_ATTR_WEAK int32_t tud_mtp_request_get_device_status_cb(tud_mtp_request_cb_data_t* cb_data) { + (void) cb_data; + return -1; +} +TU_ATTR_WEAK bool tud_mtp_request_vendor_cb(tud_mtp_request_cb_data_t* cb_data) { + (void) cb_data; + return false; +} +TU_ATTR_WEAK int32_t tud_mtp_command_received_cb(tud_mtp_cb_data_t * cb_data) { + (void) cb_data; + return -1; +} +TU_ATTR_WEAK int32_t tud_mtp_data_xfer_cb(tud_mtp_cb_data_t* cb_data) { + (void) cb_data; + return -1; +} +TU_ATTR_WEAK int32_t tud_mtp_data_complete_cb(tud_mtp_cb_data_t* cb_data) { + (void) cb_data; + return -1; +} +TU_ATTR_WEAK int32_t tud_mtp_response_complete_cb(tud_mtp_cb_data_t* cb_data) { + (void) cb_data; + return -1; +} + +//--------------------------------------------------------------------+ +// STRUCT +//--------------------------------------------------------------------+ +typedef struct { + uint8_t rhport; + uint8_t itf_num; + uint8_t ep_in; + uint8_t ep_out; + + uint8_t ep_event; + uint8_t ep_sz_fs; + // Bulk Only Transfer (BOT) Protocol + uint8_t phase; + + uint32_t total_len; + uint32_t xferred_len; + + uint32_t session_id; + mtp_container_command_t command; + mtp_container_header_t io_header; + + TU_ATTR_ALIGNED(4) uint8_t control_buf[CFG_TUD_MTP_EP_CONTROL_BUFSIZE]; +} mtpd_interface_t; + +typedef struct { + TUD_EPBUF_DEF(buf, CFG_TUD_MTP_EP_BUFSIZE); + TUD_EPBUF_TYPE_DEF(mtp_event_t, buf_event); +} mtpd_epbuf_t; + +//--------------------------------------------------------------------+ +// INTERNAL FUNCTION DECLARATION +//--------------------------------------------------------------------+ +static mtpd_interface_t _mtpd_itf; +CFG_TUD_MEM_SECTION static mtpd_epbuf_t _mtpd_epbuf; + +static void preprocess_cmd(mtpd_interface_t* p_mtp, tud_mtp_cb_data_t* cb_data); + +//--------------------------------------------------------------------+ +// Debug +//--------------------------------------------------------------------+ +#if CFG_TUSB_DEBUG >= CFG_TUD_MTP_LOG_LEVEL + +TU_ATTR_UNUSED static tu_lookup_entry_t const _mpt_op_lookup[] = { +{.key = MTP_OP_UNDEFINED , .data = "Undefined" } , +{.key = MTP_OP_GET_DEVICE_INFO , .data = "GetDeviceInfo" } , +{.key = MTP_OP_OPEN_SESSION , .data = "OpenSession" } , +{.key = MTP_OP_CLOSE_SESSION , .data = "CloseSession" } , +{.key = MTP_OP_GET_STORAGE_IDS , .data = "GetStorageIDs" } , +{.key = MTP_OP_GET_STORAGE_INFO , .data = "GetStorageInfo" } , +{.key = MTP_OP_GET_NUM_OBJECTS , .data = "GetNumObjects" } , +{.key = MTP_OP_GET_OBJECT_HANDLES , .data = "GetObjectHandles" } , +{.key = MTP_OP_GET_OBJECT_INFO , .data = "GetObjectInfo" } , +{.key = MTP_OP_GET_OBJECT , .data = "GetObject" } , +{.key = MTP_OP_GET_THUMB , .data = "GetThumb" } , +{.key = MTP_OP_DELETE_OBJECT , .data = "DeleteObject" } , +{.key = MTP_OP_SEND_OBJECT_INFO , .data = "SendObjectInfo" } , +{.key = MTP_OP_SEND_OBJECT , .data = "SendObject" } , +{.key = MTP_OP_INITIATE_CAPTURE , .data = "InitiateCapture" } , +{.key = MTP_OP_FORMAT_STORE , .data = "FormatStore" } , +{.key = MTP_OP_RESET_DEVICE , .data = "ResetDevice" } , +{.key = MTP_OP_SELF_TEST , .data = "SelfTest" } , +{.key = MTP_OP_SET_OBJECT_PROTECTION , .data = "SetObjectProtection" } , +{.key = MTP_OP_POWER_DOWN , .data = "PowerDown" } , +{.key = MTP_OP_GET_DEVICE_PROP_DESC , .data = "GetDevicePropDesc" } , +{.key = MTP_OP_GET_DEVICE_PROP_VALUE , .data = "GetDevicePropValue" } , +{.key = MTP_OP_SET_DEVICE_PROP_VALUE , .data = "SetDevicePropValue" } , +{.key = MTP_OP_RESET_DEVICE_PROP_VALUE , .data = "ResetDevicePropValue" } , +{.key = MTP_OP_TERMINATE_OPEN_CAPTURE , .data = "TerminateOpenCapture" } , +{.key = MTP_OP_MOVE_OBJECT , .data = "MoveObject" } , +{.key = MTP_OP_COPY_OBJECT , .data = "CopyObject" } , +{.key = MTP_OP_GET_PARTIAL_OBJECT , .data = "GetPartialObject" } , +{.key = MTP_OP_INITIATE_OPEN_CAPTURE , .data = "InitiateOpenCapture" } , +{.key = MTP_OP_GET_OBJECT_PROPS_SUPPORTED , .data = "GetObjectPropsSupported" } , +{.key = MTP_OP_GET_OBJECT_PROP_DESC , .data = "GetObjectPropDesc" } , +{.key = MTP_OP_GET_OBJECT_PROP_VALUE , .data = "GetObjectPropValue" } , +{.key = MTP_OP_SET_OBJECT_PROP_VALUE , .data = "SetObjectPropValue" } , +{.key = MTP_OP_GET_OBJECT_PROPLIST , .data = "GetObjectPropList" } , +{.key = MTP_OP_GET_OBJECT_PROP_REFERENCES , .data = "GetObjectPropReferences" } , +{.key = MTP_OP_GET_SERVICE_IDS , .data = "GetServiceIDs" } , +{.key = MTP_OP_GET_SERVICE_INFO , .data = "GetServiceInfo" } , +{.key = MTP_OP_GET_SERVICE_CAPABILITIES , .data = "GetServiceCapabilities" } , +{.key = MTP_OP_GET_SERVICE_PROP_DESC , .data = "GetServicePropDesc" } , +{.key = MTP_OP_GET_OBJECT_PROP_LIST , .data = "GetObjectPropList" } , +{.key = MTP_OP_SET_OBJECT_PROP_LIST , .data = "SetObjectPropList" } , +{.key = MTP_OP_GET_INTERDEPENDENT_PROP_DESC , .data = "GetInterdependentPropDesc" } , +{.key = MTP_OP_SEND_OBJECT_PROP_LIST , .data = "SendObjectPropList" } +}; + +TU_ATTR_UNUSED static tu_lookup_table_t const _mtp_op_table = { + .count = TU_ARRAY_SIZE(_mpt_op_lookup), + .items = _mpt_op_lookup +}; + +TU_ATTR_UNUSED static const char* _mtp_phase_str[] = { + "Command", + "Data", + "Response", + "Error" +}; + +#endif + + +//--------------------------------------------------------------------+ +// Helper +//--------------------------------------------------------------------+ +static bool prepare_new_command(mtpd_interface_t* p_mtp) { + p_mtp->phase = MTP_PHASE_COMMAND; + return usbd_edpt_xfer(p_mtp->rhport, p_mtp->ep_out, _mtpd_epbuf.buf, CFG_TUD_MTP_EP_BUFSIZE, false); +} + +bool tud_mtp_data_send(mtp_container_info_t *p_container) { + mtpd_interface_t *p_mtp = &_mtpd_itf; + if (p_mtp->phase == MTP_PHASE_COMMAND) { + // 1st data block: header + payload + p_mtp->phase = MTP_PHASE_DATA; + p_mtp->xferred_len = 0; + p_mtp->total_len = p_container->header->len; + + p_container->header->type = MTP_CONTAINER_TYPE_DATA_BLOCK; + p_container->header->transaction_id = p_mtp->command.header.transaction_id; + p_mtp->io_header = *p_container->header; // save header for subsequent data + } + + const uint16_t xact_len = (uint16_t)tu_min32(p_mtp->total_len - p_mtp->xferred_len, CFG_TUD_MTP_EP_BUFSIZE); + + TU_LOG_DRV(" MTP Data IN: xferred_len/total_len=%lu/%lu, xact_len=%u\r\n", p_mtp->xferred_len, p_mtp->total_len, + xact_len); + if (xact_len) { + TU_VERIFY(usbd_edpt_claim(p_mtp->rhport, p_mtp->ep_in)); + TU_ASSERT(usbd_edpt_xfer(p_mtp->rhport, p_mtp->ep_in, _mtpd_epbuf.buf, xact_len, false)); + } + return true; +} + +bool tud_mtp_data_receive(mtp_container_info_t *p_container) { + mtpd_interface_t *p_mtp = &_mtpd_itf; + if (p_mtp->phase == MTP_PHASE_COMMAND) { + // 1st data block: header + payload + p_mtp->phase = MTP_PHASE_DATA; + p_mtp->xferred_len = 0; + p_mtp->total_len = p_container->header->len; + } + + // up to buffer size since 1st packet (with header) may also contain payload + const uint16_t xact_len = CFG_TUD_MTP_EP_BUFSIZE; + + TU_LOG_DRV(" MTP Data OUT: xferred_len/total_len=%lu/%lu, xact_len=%u\r\n", p_mtp->xferred_len, p_mtp->total_len, + xact_len); + TU_VERIFY(usbd_edpt_claim(p_mtp->rhport, p_mtp->ep_out)); + TU_ASSERT(usbd_edpt_xfer(p_mtp->rhport, p_mtp->ep_out, _mtpd_epbuf.buf, xact_len, false)); + return true; +} + +bool tud_mtp_response_send(mtp_container_info_t* p_container) { + mtpd_interface_t* p_mtp = &_mtpd_itf; + p_mtp->phase = MTP_PHASE_RESPONSE; + p_container->header->type = MTP_CONTAINER_TYPE_RESPONSE_BLOCK; + p_container->header->transaction_id = p_mtp->command.header.transaction_id; + TU_VERIFY(usbd_edpt_claim(p_mtp->rhport, p_mtp->ep_in)); + return usbd_edpt_xfer(p_mtp->rhport, p_mtp->ep_in, _mtpd_epbuf.buf, (uint16_t) p_container->header->len, false); +} + +bool tud_mtp_mounted(void) { + mtpd_interface_t* p_mtp = &_mtpd_itf; + return p_mtp->ep_out != 0 && p_mtp->ep_in != 0; +} + +bool tud_mtp_event_send(mtp_event_t* event) { + mtpd_interface_t* p_mtp = &_mtpd_itf; + TU_VERIFY(p_mtp->ep_event != 0); + _mtpd_epbuf.buf_event = *event; + TU_VERIFY(usbd_edpt_claim(p_mtp->rhport, p_mtp->ep_event)); // Claim the endpoint + return usbd_edpt_xfer(p_mtp->rhport, p_mtp->ep_event, (uint8_t*) &_mtpd_epbuf.buf_event, sizeof(mtp_event_t), false); +} + +//--------------------------------------------------------------------+ +// USBD Driver API +//--------------------------------------------------------------------+ +void mtpd_init(void) { + tu_memclr(&_mtpd_itf, sizeof(mtpd_interface_t)); +} + +bool mtpd_deinit(void) { + return true; // nothing to do +} + +void mtpd_reset(uint8_t rhport) { + (void) rhport; + tu_memclr(&_mtpd_itf, sizeof(mtpd_interface_t)); +} + +uint16_t mtpd_open(uint8_t rhport, tusb_desc_interface_t const* itf_desc, uint16_t max_len) { + // only support PIMA 15470 protocol + TU_VERIFY(TUSB_CLASS_IMAGE == itf_desc->bInterfaceClass && + MTP_SUBCLASS_STILL_IMAGE == itf_desc->bInterfaceSubClass && + MTP_PROTOCOL_PIMA_15470 == itf_desc->bInterfaceProtocol, 0); + + // mtp driver length is fixed + const uint16_t mtpd_itf_size = sizeof(tusb_desc_interface_t) + 3 * sizeof(tusb_desc_endpoint_t); + + // Max length must be at least 1 interface + 3 endpoints + TU_ASSERT(itf_desc->bNumEndpoints == 3 && max_len >= mtpd_itf_size); + mtpd_interface_t* p_mtp = &_mtpd_itf; + tu_memclr(p_mtp, sizeof(mtpd_interface_t)); + p_mtp->rhport = rhport; + p_mtp->itf_num = itf_desc->bInterfaceNumber; + + // Open interrupt IN endpoint + const tusb_desc_endpoint_t* ep_desc_int = (const tusb_desc_endpoint_t*) tu_desc_next(itf_desc); + TU_ASSERT(ep_desc_int->bDescriptorType == TUSB_DESC_ENDPOINT && ep_desc_int->bmAttributes.xfer == TUSB_XFER_INTERRUPT, 0); + TU_ASSERT(usbd_edpt_open(rhport, ep_desc_int), 0); + p_mtp->ep_event = ep_desc_int->bEndpointAddress; + + // Open endpoint pair + const tusb_desc_endpoint_t* ep_desc_bulk = (const tusb_desc_endpoint_t*) tu_desc_next(ep_desc_int); + TU_ASSERT(usbd_open_edpt_pair(rhport, (const uint8_t*)ep_desc_bulk, 2, TUSB_XFER_BULK, &p_mtp->ep_out, &p_mtp->ep_in), 0); + TU_ASSERT(prepare_new_command(p_mtp), 0); + + if (tud_speed_get() == TUSB_SPEED_FULL) { + p_mtp->ep_sz_fs = (uint8_t)tu_edpt_packet_size(ep_desc_bulk); + } + + return mtpd_itf_size; +} + +// Invoked when a control transfer occurred on an interface of this class +// Driver response accordingly to the request and the transfer stage (setup/data/ack) +// return false to stall control endpoint (e.g unsupported request) +bool mtpd_control_xfer_cb(uint8_t rhport, uint8_t stage, tusb_control_request_t const* request) { + mtpd_interface_t* p_mtp = &_mtpd_itf; + tud_mtp_request_cb_data_t cb_data = { + .idx = 0, + .stage = stage, + .session_id = p_mtp->session_id, + .request = request, + .buf = p_mtp->control_buf, + .bufsize = request->wLength, + }; + + switch (request->bRequest) { + case MTP_REQ_CANCEL: + TU_LOG_DRV(" MTP request: Cancel\n"); + if (stage == CONTROL_STAGE_SETUP) { + return tud_control_xfer(rhport, request, p_mtp->control_buf, CFG_TUD_MTP_EP_CONTROL_BUFSIZE); + } else if (stage == CONTROL_STAGE_ACK) { + return tud_mtp_request_cancel_cb(&cb_data); + } + break; + + case MTP_REQ_GET_EXT_EVENT_DATA: + TU_LOG_DRV(" MTP request: Get Extended Event Data\n"); + if (stage == CONTROL_STAGE_SETUP) { + const int32_t len = tud_mtp_request_get_extended_event_cb(&cb_data); + TU_VERIFY(len > 0); + return tud_control_xfer(rhport,request, p_mtp->control_buf, (uint16_t) len); + } + break; + + case MTP_REQ_RESET: + TU_LOG_DRV(" MTP request: Device Reset\n"); + // used by the host to return the Still Image Capture Device to the Idle state after the Bulk-pipe has stalled + if (stage == CONTROL_STAGE_SETUP) { + // clear stalled + if (usbd_edpt_stalled(rhport, p_mtp->ep_out)) { + usbd_edpt_clear_stall(rhport, p_mtp->ep_out); + } + if (usbd_edpt_stalled(rhport, p_mtp->ep_in)) { + usbd_edpt_clear_stall(rhport, p_mtp->ep_in); + } + } else if (stage == CONTROL_STAGE_ACK) { + prepare_new_command(p_mtp); + return tud_mtp_request_device_reset_cb(&cb_data); + } + break; + + case MTP_REQ_GET_DEVICE_STATUS: { + TU_LOG_DRV(" MTP request: Get Device Status\n"); + if (stage == CONTROL_STAGE_SETUP) { + const int32_t len = tud_mtp_request_get_device_status_cb(&cb_data); + TU_VERIFY(len > 0); + return tud_control_xfer(rhport, request, p_mtp->control_buf, (uint16_t) len); + } + break; + } + + default: + return tud_mtp_request_vendor_cb(&cb_data); + } + + return true; +} + +// Transfer on bulk endpoints +bool mtpd_xfer_cb(uint8_t rhport, uint8_t ep_addr, xfer_result_t event, uint32_t xferred_bytes) { + if (ep_addr == _mtpd_itf.ep_event) { + // nothing to do + return true; + } + + mtpd_interface_t* p_mtp = &_mtpd_itf; + mtp_generic_container_t* p_container = (mtp_generic_container_t*) _mtpd_epbuf.buf; + +#if CFG_TUSB_DEBUG >= CFG_TUD_MTP_LOG_LEVEL + const uint16_t code = (p_mtp->phase == MTP_PHASE_COMMAND) ? p_container->header.code : p_mtp->command.header.code; + TU_LOG_DRV(" MTP %s: %s phase\r\n", (const char *) tu_lookup_find(&_mtp_op_table, code), + _mtp_phase_str[p_mtp->phase]); +#endif + + const mtp_container_info_t headered_packet = { + .header = &p_container->header, + .payload = p_container->payload, + .payload_bytes = CFG_TUD_MTP_EP_BUFSIZE - sizeof(mtp_container_header_t) + }; + + const mtp_container_info_t headerless_packet = { + .header = &p_mtp->io_header, + .payload = _mtpd_epbuf.buf, + .payload_bytes = CFG_TUD_MTP_EP_BUFSIZE + }; + + tud_mtp_cb_data_t cb_data; + cb_data.idx = 0; + cb_data.phase = p_mtp->phase; + cb_data.session_id = p_mtp->session_id; + cb_data.command_container = &p_mtp->command; + cb_data.io_container = headered_packet; + cb_data.total_xferred_bytes = 0; + cb_data.xfer_result = event; + + switch (p_mtp->phase) { + case MTP_PHASE_COMMAND: { + // received new command + TU_VERIFY(ep_addr == p_mtp->ep_out && p_container->header.type == MTP_CONTAINER_TYPE_COMMAND_BLOCK); + memcpy(&p_mtp->command, p_container, sizeof(mtp_container_command_t)); // save new command + p_container->header.len = sizeof(mtp_container_header_t); // default container to header only + preprocess_cmd(p_mtp, &cb_data); + if (tud_mtp_command_received_cb(&cb_data) < 0) { + p_mtp->phase = MTP_PHASE_ERROR; + } + break; + } + + case MTP_PHASE_DATA: { + p_mtp->xferred_len += xferred_bytes; + cb_data.total_xferred_bytes = p_mtp->xferred_len; + + const bool is_data_in = (ep_addr == p_mtp->ep_in); + // For IN endpoint, threshold is bulk max packet size + // For OUT endpoint, threshold is endpoint buffer size, since we always queue fixed size + uint16_t threshold; + if (is_data_in) { + threshold = (p_mtp->ep_sz_fs > 0) ? p_mtp->ep_sz_fs : 512; // full speed bulk if set + } else { + threshold = CFG_TUD_MTP_EP_BUFSIZE; + } + + // Check completion: ZLP, short packet, or total length reached + const bool is_complete = + (xferred_bytes == 0 || xferred_bytes < threshold || p_mtp->xferred_len >= p_mtp->total_len); + + TU_LOG_DRV(" MTP Data %s CB: xferred_bytes=%lu, xferred_len/total_len=%lu/%lu, is_complete=%d\r\n", + is_data_in ? "IN" : "OUT", xferred_bytes, p_mtp->xferred_len, p_mtp->total_len, is_complete ? 1 : 0); + + // Send/queue ZLP if packet is full-sized but transfer is complete + if (is_complete && xferred_bytes > 0 && !(xferred_bytes & (threshold - 1))) { + TU_LOG_DRV(" queue ZLP\r\n"); + TU_VERIFY(usbd_edpt_claim(p_mtp->rhport, ep_addr)); + TU_ASSERT(usbd_edpt_xfer(p_mtp->rhport, ep_addr, NULL, 0, false)); + return true; + } + + if (is_data_in) { + // Data In + if (is_complete) { + cb_data.io_container.header->len = sizeof(mtp_container_header_t); + tud_mtp_data_complete_cb(&cb_data); + } else { + // 2nd+ packet: payload only + cb_data.io_container = headerless_packet; + tud_mtp_data_xfer_cb(&cb_data); + } + } else { + // Data Out + if (p_mtp->xferred_len == xferred_bytes) { + // 1st OUT packet: header + payload + p_mtp->io_header = p_container->header; // save header for subsequent transaction + cb_data.io_container.payload_bytes = xferred_bytes - sizeof(mtp_container_header_t); + } else { + // 2nd+ packet: payload only + cb_data.io_container = headerless_packet; + cb_data.io_container.payload_bytes = xferred_bytes; + } + tud_mtp_data_xfer_cb(&cb_data); + + if (is_complete) { + // back to header + payload for response + cb_data.io_container = headered_packet; + cb_data.io_container.header->len = sizeof(mtp_container_header_t); + tud_mtp_data_complete_cb(&cb_data); + } + } + break; + } + + case MTP_PHASE_RESPONSE: + // response phase is complete -> prepare for new command + TU_ASSERT(ep_addr == p_mtp->ep_in); + tud_mtp_response_complete_cb(&cb_data); + prepare_new_command(p_mtp); + break; + + case MTP_PHASE_ERROR: + // handled after switch, supposedly to be empty + break; + default: return false; + } + + if (p_mtp->phase == MTP_PHASE_ERROR) { + // stall both IN & OUT endpoints + usbd_edpt_stall(rhport, p_mtp->ep_out); + usbd_edpt_stall(rhport, p_mtp->ep_in); + } + + return true; +} + + +//--------------------------------------------------------------------+ +// MTPD Internal functionality +//--------------------------------------------------------------------+ + +// pre-processed commands +void preprocess_cmd(mtpd_interface_t* p_mtp, tud_mtp_cb_data_t* cb_data) { + switch (p_mtp->command.header.code) { + case MTP_OP_GET_DEVICE_INFO: { + tud_mtp_device_info_t dev_info = { + .standard_version = 100, + .mtp_vendor_extension_id = 6, // MTP specs say 0xFFFFFFFF but libMTP check for value 6 + .mtp_version = 100, + .mtp_extensions = { + .count = sizeof(CFG_TUD_MTP_DEVICEINFO_EXTENSIONS), + .utf16 = { 0 } + }, + .functional_mode = 0x0000, + .supported_operations = { + .count = TU_ARGS_NUM(CFG_TUD_MTP_DEVICEINFO_SUPPORTED_OPERATIONS), + .arr = { CFG_TUD_MTP_DEVICEINFO_SUPPORTED_OPERATIONS } + }, + .supported_events = { + .count = TU_ARGS_NUM(CFG_TUD_MTP_DEVICEINFO_SUPPORTED_EVENTS), + .arr = { CFG_TUD_MTP_DEVICEINFO_SUPPORTED_EVENTS } + }, + .supported_device_properties = { + .count = TU_ARGS_NUM(CFG_TUD_MTP_DEVICEINFO_SUPPORTED_DEVICE_PROPERTIES), + .arr = { CFG_TUD_MTP_DEVICEINFO_SUPPORTED_DEVICE_PROPERTIES } + }, + .capture_formats = { + .count = TU_ARGS_NUM(CFG_TUD_MTP_DEVICEINFO_CAPTURE_FORMATS), + .arr = { CFG_TUD_MTP_DEVICEINFO_CAPTURE_FORMATS } + }, + .playback_formats = { + .count = TU_ARGS_NUM(CFG_TUD_MTP_DEVICEINFO_PLAYBACK_FORMATS), + .arr = { CFG_TUD_MTP_DEVICEINFO_PLAYBACK_FORMATS } + } + }; + + for (uint8_t i=0; i < dev_info.mtp_extensions.count; i++) { + dev_info.mtp_extensions.utf16[i] = (uint16_t)CFG_TUD_MTP_DEVICEINFO_EXTENSIONS[i]; + } + + mtp_container_add_raw(&cb_data->io_container, &dev_info, sizeof(tud_mtp_device_info_t)); + break; + } + + default: + break; + } +} + +#endif diff --git a/libraries/Adafruit_TinyUSB_Arduino/src/class/mtp/mtp_device.h b/libraries/Adafruit_TinyUSB_Arduino/src/class/mtp/mtp_device.h new file mode 100644 index 0000000..f2c5cef --- /dev/null +++ b/libraries/Adafruit_TinyUSB_Arduino/src/class/mtp/mtp_device.h @@ -0,0 +1,173 @@ +/* + * The MIT License (MIT) + * + * Copyright (c) 2025 Ennebi Elettronica (https://ennebielettronica.com) + * + * 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_MTP_DEVICE_H_ +#define TUSB_MTP_DEVICE_H_ + +#include "common/tusb_common.h" +#include "mtp.h" + +#if (CFG_TUD_ENABLED && CFG_TUD_MTP) + +#ifdef __cplusplus + extern "C" { +#endif + +// callback data for Bulk Only Transfer (BOT) protocol +typedef struct { + uint8_t idx; // mtp instance + uint8_t phase; // current phase + uint32_t session_id; + + const mtp_container_command_t* command_container; + mtp_container_info_t io_container; + + tusb_xfer_result_t xfer_result; + uint32_t total_xferred_bytes; // number of bytes transferred so far in this phase +} tud_mtp_cb_data_t; + +// callback data for Control requests +typedef struct { + uint8_t idx; + uint8_t stage; // control stage + + // buffer for data stage + uint16_t bufsize; + uint8_t* buf; + + const tusb_control_request_t* request; + + uint32_t session_id; +} tud_mtp_request_cb_data_t; + +// Number of supported operations, events, device properties, capture formats, playback formats +// and max number of characters for strings manufacturer, model, device_version, serial_number +#define MTP_DEVICE_INFO_STRUCT(_extension_nchars, _op_count, _event_count, _devprop_count, _capture_count, _playback_count) \ + struct TU_ATTR_PACKED { \ + uint16_t standard_version; \ + uint32_t mtp_vendor_extension_id; \ + uint16_t mtp_version; \ + mtp_string_t(_extension_nchars) mtp_extensions; \ + uint16_t functional_mode; \ + mtp_auint16_t(_op_count) supported_operations; \ + mtp_auint16_t(_event_count) supported_events; \ + mtp_auint16_t(_devprop_count) supported_device_properties; \ + mtp_auint16_t(_capture_count) capture_formats; \ + mtp_auint16_t(_playback_count) playback_formats; \ + /* string fields will be added using append function */ \ + /* mtp_string_t() Manufacturer */ \ + /* mtp_string_t() Model */ \ + /* mtp_string_t() Device Version */ \ + /* mtp_string_t() Serial Number */ \ + } + +typedef MTP_DEVICE_INFO_STRUCT( //-V2586 [MISRA-C-18.7] Flexible array members should not be declared + sizeof(CFG_TUD_MTP_DEVICEINFO_EXTENSIONS), TU_ARGS_NUM(CFG_TUD_MTP_DEVICEINFO_SUPPORTED_OPERATIONS), + TU_ARGS_NUM(CFG_TUD_MTP_DEVICEINFO_SUPPORTED_EVENTS), TU_ARGS_NUM(CFG_TUD_MTP_DEVICEINFO_SUPPORTED_DEVICE_PROPERTIES), + TU_ARGS_NUM(CFG_TUD_MTP_DEVICEINFO_CAPTURE_FORMATS), TU_ARGS_NUM(CFG_TUD_MTP_DEVICEINFO_PLAYBACK_FORMATS) +) tud_mtp_device_info_t; + +//--------------------------------------------------------------------+ +// Application API +//--------------------------------------------------------------------+ + +// check if mtp interface is mounted +bool tud_mtp_mounted(void); + +// send data phase +bool tud_mtp_data_send(mtp_container_info_t* p_container); + +// receive data phase +bool tud_mtp_data_receive(mtp_container_info_t* p_container); + +// send response +bool tud_mtp_response_send(mtp_container_info_t* p_container); + +// send event notification on event endpoint +bool tud_mtp_event_send(mtp_event_t* event); + +//--------------------------------------------------------------------+ +// Control request Callbacks +//--------------------------------------------------------------------+ + +// Invoked when received Cancel request. Data is available in callback data's buffer +// return false to stall the request +bool tud_mtp_request_cancel_cb(tud_mtp_request_cb_data_t* cb_data); + +// Invoked when received Device Reset request +// return false to stall the request +bool tud_mtp_request_device_reset_cb(tud_mtp_request_cb_data_t* cb_data); + +// Invoked when received Get Extended Event request. Application fill callback data's buffer for response +// return negative to stall the request +int32_t tud_mtp_request_get_extended_event_cb(tud_mtp_request_cb_data_t* cb_data); + +// Invoked when received Get DeviceStatus request. Application fill callback data's buffer for response +// return negative to stall the request +int32_t tud_mtp_request_get_device_status_cb(tud_mtp_request_cb_data_t* cb_data); + +// Invoked when received vendor-specific request not in the above standard MTP requests +// return false to stall the request +bool tud_mtp_request_vendor_cb(tud_mtp_request_cb_data_t* cb_data); + +//--------------------------------------------------------------------+ +// Bulk only protocol Callbacks +//--------------------------------------------------------------------+ + +// Invoked when new command is received. Application fill the cb_data->io_container and call tud_mtp_data_send() or +// tud_mtp_response_send() for Data or Response phase. +// Return negative to stall the endpoints +int32_t tud_mtp_command_received_cb(tud_mtp_cb_data_t * cb_data); + +// Invoked when a data packet is transferred. If data spans over multiple packets, application can use +// total_xferred_bytes and io_container's payload_bytes to determine the offset and remaining bytes to be transferred. +// Return negative to stall the endpoints +int32_t tud_mtp_data_xfer_cb(tud_mtp_cb_data_t* cb_data); + +// Invoked when all bytes in DATA phase is complete. A response packet is expected +// Return negative to stall the endpoints +int32_t tud_mtp_data_complete_cb(tud_mtp_cb_data_t* cb_data); + +// Invoked when response phase is complete +// Return negative to stall the endpoints +int32_t tud_mtp_response_complete_cb(tud_mtp_cb_data_t* cb_data); + +//--------------------------------------------------------------------+ +// Internal Class Driver API +//--------------------------------------------------------------------+ +void mtpd_init (void); +bool mtpd_deinit (void); +void mtpd_reset (uint8_t rhport); +uint16_t mtpd_open (uint8_t rhport, tusb_desc_interface_t const *itf_desc, uint16_t max_len); +bool mtpd_control_xfer_cb (uint8_t rhport, uint8_t stage, tusb_control_request_t const *p_request); +bool mtpd_xfer_cb (uint8_t rhport, uint8_t ep_addr, xfer_result_t event, uint32_t xferred_bytes); + +#ifdef __cplusplus + } +#endif + +#endif +#endif diff --git a/libraries/Adafruit_TinyUSB_Arduino/src/class/net/ecm_rndis_device.c b/libraries/Adafruit_TinyUSB_Arduino/src/class/net/ecm_rndis_device.c new file mode 100644 index 0000000..643bcfb --- /dev/null +++ b/libraries/Adafruit_TinyUSB_Arduino/src/class/net/ecm_rndis_device.c @@ -0,0 +1,442 @@ +/* + * The MIT License (MIT) + * + * Copyright (c) 2020 Peter Lawrence + * 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. + * + * This file is part of the TinyUSB stack. + */ + +#include "tusb_option.h" + +#if ( CFG_TUD_ENABLED && CFG_TUD_ECM_RNDIS ) + +#include "device/usbd.h" +#include "device/usbd_pvt.h" + +#include "net_device.h" +#include "rndis_protocol.h" + +#define CFG_TUD_NET_PACKET_PREFIX_LEN sizeof(rndis_data_packet_t) +#define CFG_TUD_NET_PACKET_SUFFIX_LEN 0 + +#define NETD_PACKET_SIZE (CFG_TUD_NET_PACKET_PREFIX_LEN + CFG_TUD_NET_MTU + CFG_TUD_NET_PACKET_PREFIX_LEN) +#define NETD_CONTROL_SIZE 120 + +//--------------------------------------------------------------------+ +// MACRO CONSTANT TYPEDEF +//--------------------------------------------------------------------+ +typedef struct { + uint8_t itf_num; // Index number of Management Interface, +1 for Data Interface + uint8_t itf_data_alt; // Alternate setting of Data Interface. 0 : inactive, 1 : active + + uint8_t ep_in; + uint8_t ep_out; + uint16_t ep_size; // bulk endpoint max packet size (IN and OUT assumed equal) + uint8_t ep_notif; + + bool ecm_mode; + + // Endpoint descriptor use to open/close when receiving SetInterface + // TODO since configuration descriptor may not be long-lived memory, we should + // keep a copy of endpoint attribute instead + uint8_t const * ecm_desc_epdata; +} netd_interface_t; + +typedef struct ecm_notify_struct { + tusb_control_request_t header; + uint32_t downlink, uplink; +} ecm_notify_t; + +typedef struct { + TUD_EPBUF_DEF(rx, NETD_PACKET_SIZE); + TUD_EPBUF_DEF(tx, NETD_PACKET_SIZE); + + TUD_EPBUF_DEF(notify, sizeof(ecm_notify_t)); + TUD_EPBUF_DEF(ctrl, NETD_CONTROL_SIZE); +} netd_epbuf_t; + +//--------------------------------------------------------------------+ +// INTERNAL OBJECT & FUNCTION DECLARATION +//--------------------------------------------------------------------+ +static netd_interface_t _netd_itf; +CFG_TUD_MEM_SECTION static netd_epbuf_t _netd_epbuf; +static bool can_xmit; +static bool ecm_link_is_up = true; // Store link state for ECM mode + +//--------------------------------------------------------------------+ +// Weak stubs: invoked if no strong implementation is available +//--------------------------------------------------------------------+ +TU_ATTR_WEAK void tud_network_set_packet_filter_cb(uint16_t packet_filter) { + (void) packet_filter; +} + +void tud_network_recv_renew(void) { + usbd_edpt_xfer(0, _netd_itf.ep_out, _netd_epbuf.rx, NETD_PACKET_SIZE, false); +} + +static void do_in_xfer(uint8_t *buf, uint16_t len) { + can_xmit = false; + usbd_edpt_xfer(0, _netd_itf.ep_in, buf, len, false); +} + +void netd_report(uint8_t *buf, uint16_t len) { + const uint8_t rhport = 0; + len = tu_min16(len, sizeof(ecm_notify_t)); + + if (!usbd_edpt_claim(rhport, _netd_itf.ep_notif)) { + TU_LOG1("ECM: Failed to claim notification endpoint\n"); + return; + } + + memcpy(_netd_epbuf.notify, buf, len); + usbd_edpt_xfer(rhport, _netd_itf.ep_notif, _netd_epbuf.notify, len, false); +} + +//--------------------------------------------------------------------+ +// USBD Driver API +//--------------------------------------------------------------------+ +void netd_init(void) { + tu_memclr(&_netd_itf, sizeof(_netd_itf)); +} + +bool netd_deinit(void) { + return true; +} + +void netd_reset(uint8_t rhport) { + (void) rhport; + netd_init(); +} + +uint16_t netd_open(uint8_t rhport, tusb_desc_interface_t const * itf_desc, uint16_t max_len) { + bool const is_rndis = (TUD_RNDIS_ITF_CLASS == itf_desc->bInterfaceClass && + TUD_RNDIS_ITF_SUBCLASS == itf_desc->bInterfaceSubClass && + TUD_RNDIS_ITF_PROTOCOL == itf_desc->bInterfaceProtocol); + + bool const is_ecm = (TUSB_CLASS_CDC == itf_desc->bInterfaceClass && + CDC_COMM_SUBCLASS_ETHERNET_CONTROL_MODEL == itf_desc->bInterfaceSubClass && + 0x00 == itf_desc->bInterfaceProtocol); + + TU_VERIFY(is_rndis || is_ecm, 0); + + // confirm interface hasn't already been allocated + TU_ASSERT(0 == _netd_itf.ep_notif, 0); + + // sanity check the descriptor + _netd_itf.ecm_mode = is_ecm; + + //------------- Management Interface -------------// + _netd_itf.itf_num = itf_desc->bInterfaceNumber; + + uint16_t drv_len = sizeof(tusb_desc_interface_t); + uint8_t const * p_desc = tu_desc_next( itf_desc ); + + // Communication Functional Descriptors + while (TUSB_DESC_CS_INTERFACE == tu_desc_type(p_desc) && drv_len <= max_len) { + drv_len += tu_desc_len(p_desc); + p_desc = tu_desc_next(p_desc); + } + + // notification endpoint (if any) + if (TUSB_DESC_ENDPOINT == tu_desc_type(p_desc)) { + TU_ASSERT(usbd_edpt_open(rhport, (tusb_desc_endpoint_t const *) p_desc), 0); + + _netd_itf.ep_notif = ((tusb_desc_endpoint_t const*)p_desc)->bEndpointAddress; + + drv_len += tu_desc_len(p_desc); + p_desc = tu_desc_next(p_desc); + } + + //------------- Data Interface -------------// + // - RNDIS Data followed immediately by a pair of endpoints + // - CDC-ECM data interface has 2 alternate settings + // - 0 : zero endpoints for inactive (default) + // - 1 : IN & OUT endpoints for active networking + TU_ASSERT(TUSB_DESC_INTERFACE == tu_desc_type(p_desc), 0); + + do { + tusb_desc_interface_t const * data_itf_desc = (tusb_desc_interface_t const *) p_desc; + TU_ASSERT(TUSB_CLASS_CDC_DATA == data_itf_desc->bInterfaceClass, 0); + + drv_len += tu_desc_len(p_desc); + p_desc = tu_desc_next(p_desc); + } while (_netd_itf.ecm_mode && (TUSB_DESC_INTERFACE == tu_desc_type(p_desc)) && (drv_len <= max_len)); + + // Pair of endpoints + TU_ASSERT(TUSB_DESC_ENDPOINT == tu_desc_type(p_desc), 0); + + // Save the actual bulk endpoint size (IN and OUT assumed equal) + _netd_itf.ep_size = tu_edpt_packet_size((tusb_desc_endpoint_t const *) p_desc); + + if (_netd_itf.ecm_mode) { + // ECM by default is in-active, save the endpoint attribute + // to open later when received setInterface + _netd_itf.ecm_desc_epdata = p_desc; + } else { + // Open endpoint pair for RNDIS + TU_ASSERT(usbd_open_edpt_pair(rhport, p_desc, 2, TUSB_XFER_BULK, &_netd_itf.ep_out, &_netd_itf.ep_in), 0); + + // we are ready to transmit a packet + can_xmit = true; + + // prepare for incoming packets + tud_network_recv_renew(); + } + + drv_len += 2*sizeof(tusb_desc_endpoint_t); + + return drv_len; +} + +static void ecm_report(bool nc) { + ecm_notify_t ecm_notify_nc = { + .header = { + .bmRequestType = 0xA1, + .bRequest = 0, /* NETWORK_CONNECTION aka NetworkConnection */ + .wValue = ecm_link_is_up ? 1 : 0, /* Use current link state */ + .wLength = 0, + }, + }; + + const uint32_t link_bps = (tud_speed_get() == TUSB_SPEED_HIGH) ? 480000000U : 12000000U; + const ecm_notify_t ecm_notify_csc = { + .header = { + .bmRequestType = 0xA1, + .bRequest = 0x2A, /* CONNECTION_SPEED_CHANGE aka ConnectionSpeedChange */ + .wLength = 8, + }, + .downlink = link_bps, + .uplink = link_bps, + }; + + ecm_notify_t notify = (nc) ? ecm_notify_nc : ecm_notify_csc; + notify.header.wIndex = _netd_itf.itf_num; + netd_report((uint8_t *)¬ify, (nc) ? sizeof(notify.header) : sizeof(notify)); +} + +// Invoked when a control transfer occurred on an interface of this class +// Driver response accordingly to the request and the transfer stage (setup/data/ack) +// return false to stall control endpoint (e.g unsupported request) +bool netd_control_xfer_cb (uint8_t rhport, uint8_t stage, tusb_control_request_t const * request) { + if (stage == CONTROL_STAGE_SETUP) { + switch (request->bmRequestType_bit.type) { + case TUSB_REQ_TYPE_STANDARD: + switch (request->bRequest) { + case TUSB_REQ_GET_INTERFACE: { + uint8_t const req_itfnum = (uint8_t)request->wIndex; + TU_VERIFY(_netd_itf.itf_num+1 == req_itfnum); + + tud_control_xfer(rhport, request, &_netd_itf.itf_data_alt, 1); + } + break; + + case TUSB_REQ_SET_INTERFACE: { + uint8_t const req_itfnum = (uint8_t)request->wIndex; + uint8_t const req_alt = (uint8_t)request->wValue; + + // Only valid for Data Interface with Alternate is either 0 or 1 + TU_VERIFY(_netd_itf.itf_num+1 == req_itfnum && req_alt < 2); + + // ACM-ECM only: qequest to enable/disable network activities + TU_VERIFY(_netd_itf.ecm_mode); + + _netd_itf.itf_data_alt = req_alt; + + if (_netd_itf.itf_data_alt) { + // TODO since we don't actually close endpoint + // hack here to not re-open it + if (_netd_itf.ep_in == 0 && _netd_itf.ep_out == 0) { + TU_ASSERT(_netd_itf.ecm_desc_epdata); + TU_ASSERT( + usbd_open_edpt_pair(rhport, _netd_itf.ecm_desc_epdata, 2, TUSB_XFER_BULK, &_netd_itf.ep_out, & + _netd_itf.ep_in)); + + // TODO should be merge with RNDIS's after endpoint opened + // Also should have opposite callback for application to disable network !! + can_xmit = true; // we are ready to transmit a packet + tud_network_recv_renew(); // prepare for incoming packets + } + } else { + // TODO close the endpoint pair + // For now pretend that we did, this should have no harm since host won't try to + // communicate with the endpoints again + // _netd_itf.ep_in = _netd_itf.ep_out = 0 + } + + tud_control_status(rhport, request); + } + break; + + // unsupported request + default: return false; + } + break; + + case TUSB_REQ_TYPE_CLASS: + TU_VERIFY(_netd_itf.itf_num == request->wIndex); + + if (_netd_itf.ecm_mode) { + /* the only required CDC-ECM Management Element Request is SetEthernetPacketFilter */ + if (0x43 /* SET_ETHERNET_PACKET_FILTER */ == request->bRequest) { + tud_network_set_packet_filter_cb(request->wValue); + tud_control_xfer(rhport, request, NULL, 0); + // Only send connection notification if link is up + if (ecm_link_is_up) { + ecm_report(true); + } + } + } else { + if (request->bmRequestType_bit.direction == TUSB_DIR_IN) { + rndis_generic_msg_t* rndis_msg = (rndis_generic_msg_t*)((void*)_netd_epbuf.ctrl); + uint32_t msglen = tu_le32toh(rndis_msg->MessageLength); + TU_ASSERT(msglen <= NETD_CONTROL_SIZE); + tud_control_xfer(rhport, request, _netd_epbuf.ctrl, (uint16_t)msglen); + } else { + tud_control_xfer(rhport, request, _netd_epbuf.ctrl, NETD_CONTROL_SIZE); + } + } + break; + + // unsupported request + default: return false; + } + } else if (stage == CONTROL_STAGE_DATA) { + // Handle RNDIS class control OUT only + if (request->bmRequestType_bit.type == TUSB_REQ_TYPE_CLASS && + request->bmRequestType_bit.direction == TUSB_DIR_OUT && + _netd_itf.itf_num == request->wIndex) { + if (!_netd_itf.ecm_mode) { + rndis_class_set_handler(_netd_epbuf.ctrl, request->wLength); + } + } + } + + return true; +} + +static void handle_incoming_packet(uint32_t len) { + uint8_t* pnt = _netd_epbuf.rx; + uint32_t size = 0; + + if (_netd_itf.ecm_mode) { + size = len; + } else { + rndis_data_packet_t* r = (rndis_data_packet_t*)((void*)pnt); + if (len >= sizeof(rndis_data_packet_t)) { + if ((r->MessageType == REMOTE_NDIS_PACKET_MSG) && (r->MessageLength <= len)) { + if ((r->DataOffset + offsetof(rndis_data_packet_t, DataOffset) + r->DataLength) <= len) { + pnt = &_netd_epbuf.rx[r->DataOffset + offsetof(rndis_data_packet_t, DataOffset)]; + size = r->DataLength; + } + } + } + } + + if (!tud_network_recv_cb(pnt, (uint16_t)size)) { + /* if a buffer was never handled by user code, we must renew on the user's behalf */ + tud_network_recv_renew(); + } +} + +bool netd_xfer_cb(uint8_t rhport, uint8_t ep_addr, xfer_result_t result, uint32_t xferred_bytes) { + (void)rhport; + (void)result; + + /* new packet received */ + if (ep_addr == _netd_itf.ep_out) { + handle_incoming_packet(xferred_bytes); + } + + /* data transmission finished */ + if (ep_addr == _netd_itf.ep_in) { + /* TinyUSB requires the class driver to implement ZLP (since ZLP usage is class-specific) */ + if (xferred_bytes > 0 && 0 == (xferred_bytes & (_netd_itf.ep_size-1))) { + do_in_xfer(NULL, 0); /* a ZLP is needed */ + } else { + /* we're finally finished */ + can_xmit = true; + } + } + + if (_netd_itf.ecm_mode && (ep_addr == _netd_itf.ep_notif)) { + // Notification transfer complete - endpoint is now free + // Don't automatically send speed change notification after link state changes + } + + return true; +} + +bool tud_network_can_xmit(uint16_t size) { + (void)size; + return can_xmit; +} + +void tud_network_xmit(void *ref, uint16_t arg) { + if (!can_xmit) { + return; + } + + uint16_t len = (_netd_itf.ecm_mode) ? 0 : CFG_TUD_NET_PACKET_PREFIX_LEN; + uint8_t* data = _netd_epbuf.tx + len; + + len += tud_network_xmit_cb(data, ref, arg); + + if (!_netd_itf.ecm_mode) { + rndis_data_packet_t *hdr = (rndis_data_packet_t *) ((void*) _netd_epbuf.tx); + memset(hdr, 0, sizeof(rndis_data_packet_t)); + hdr->MessageType = REMOTE_NDIS_PACKET_MSG; + hdr->MessageLength = len; + hdr->DataOffset = sizeof(rndis_data_packet_t) - offsetof(rndis_data_packet_t, DataOffset); + hdr->DataLength = len - sizeof(rndis_data_packet_t); + } + + do_in_xfer(_netd_epbuf.tx, len); +} + +// Set the network link state (up/down) and notify the host +void tud_network_link_state(uint8_t rhport, bool is_up) { + (void)rhport; + + if (_netd_itf.ecm_mode) { + ecm_link_is_up = is_up; + + // For ECM mode, send network connection notification only + // Don't trigger speed change notification for link state changes + ecm_notify_t notify = { + .header = { + .bmRequestType = 0xA1, + .bRequest = 0, /* NETWORK_CONNECTION */ + .wValue = is_up ? 1 : 0, /* 0 = disconnected, 1 = connected */ + .wLength = 0, + }, + }; + notify.header.wIndex = _netd_itf.itf_num; + netd_report((uint8_t *)¬ify, sizeof(notify.header)); + } else { + // For RNDIS mode, we would need to implement RNDIS status indication + // This is more complex and requires RNDIS_INDICATE_STATUS_MSG + // For now, RNDIS doesn't support dynamic link state changes + (void)is_up; + } +} + +#endif diff --git a/libraries/Adafruit_TinyUSB_Arduino/src/class/net/ncm.h b/libraries/Adafruit_TinyUSB_Arduino/src/class/net/ncm.h new file mode 100644 index 0000000..27ff89b --- /dev/null +++ b/libraries/Adafruit_TinyUSB_Arduino/src/class/net/ncm.h @@ -0,0 +1,178 @@ +/* + * The MIT License (MIT) + * + * Copyright (c) 2021, Ha Thach (tinyusb.org) + * Copyright (c) 2024, Hardy Griech + * + * 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_NCM_H_ +#define TUSB_NCM_H_ + +#include "common/tusb_common.h" + +// NTB buffers size for reception side, must be >> MTU to avoid TCP retransmission (driver issue ?) +// Linux use 2048 as minimal size +#ifndef CFG_TUD_NCM_OUT_NTB_MAX_SIZE + #define CFG_TUD_NCM_OUT_NTB_MAX_SIZE 3200 +#endif + +// NTB buffers size for reception side, must be > MTU +// Linux use 2048 as minimal size +#ifndef CFG_TUD_NCM_IN_NTB_MAX_SIZE + #define CFG_TUD_NCM_IN_NTB_MAX_SIZE 3200 +#endif + +// Number of NTB buffers for reception side +// Depending on the configuration, this parameter could be increased with the cost of additional RAM requirements +// On Full-Speed (RP2040) : +// 1 - good performance +// 2 - up to 30% more performance with iperf with small packets +// >2 - no performance gain +// On High-Speed (STM32F7) : +// No performance gain +#ifndef CFG_TUD_NCM_OUT_NTB_N + #define CFG_TUD_NCM_OUT_NTB_N 1 +#endif + +// Number of NTB buffers for transmission side +// Depending on the configuration, this parameter could be increased with the cost of additional RAM requirements +// On Full-Speed (RP2040) : +// 1 - good performance but SystemView shows lost events (on load test) +// 2 - up to 50% more performance with iperf with small packets, "tud_network_can_xmit: request blocked" +// happens from time to time with SystemView +// 3 - "tud_network_can_xmit: request blocked" never happens +// >3 - no performance gain +// On High-Speed (STM32F7) : +// No performance gain +#ifndef CFG_TUD_NCM_IN_NTB_N + #define CFG_TUD_NCM_IN_NTB_N 1 +#endif + +// How many datagrams it is allowed to put into an NTB for transmission side +#ifndef CFG_TUD_NCM_IN_MAX_DATAGRAMS_PER_NTB + #define CFG_TUD_NCM_IN_MAX_DATAGRAMS_PER_NTB 8 +#endif + +// This tells the host how many datagrams it is allowed to put into an NTB +#ifndef CFG_TUD_NCM_OUT_MAX_DATAGRAMS_PER_NTB + #define CFG_TUD_NCM_OUT_MAX_DATAGRAMS_PER_NTB 6 +#endif + +// Table 6.2 Class-Specific Request Codes for Network Control Model subclass +typedef enum +{ + NCM_SET_ETHERNET_MULTICAST_FILTERS = 0x40, + NCM_SET_ETHERNET_POWER_MANAGEMENT_PATTERN_FILTER = 0x41, + NCM_GET_ETHERNET_POWER_MANAGEMENT_PATTERN_FILTER = 0x42, + NCM_SET_ETHERNET_PACKET_FILTER = 0x43, + NCM_GET_ETHERNET_STATISTIC = 0x44, + NCM_GET_NTB_PARAMETERS = 0x80, + NCM_GET_NET_ADDRESS = 0x81, + NCM_SET_NET_ADDRESS = 0x82, + NCM_GET_NTB_FORMAT = 0x83, + NCM_SET_NTB_FORMAT = 0x84, + NCM_GET_NTB_INPUT_SIZE = 0x85, + NCM_SET_NTB_INPUT_SIZE = 0x86, + NCM_GET_MAX_DATAGRAM_SIZE = 0x87, + NCM_SET_MAX_DATAGRAM_SIZE = 0x88, + NCM_GET_CRC_MODE = 0x89, + NCM_SET_CRC_MODE = 0x8A, +} ncm_request_code_t; + +#define NTH16_SIGNATURE 0x484D434E +#define NDP16_SIGNATURE_NCM0 0x304D434E +#define NDP16_SIGNATURE_NCM1 0x314D434E + +typedef struct TU_ATTR_PACKED { + uint16_t wLength; + uint16_t bmNtbFormatsSupported; + uint32_t dwNtbInMaxSize; + uint16_t wNdbInDivisor; + uint16_t wNdbInPayloadRemainder; + uint16_t wNdbInAlignment; + uint16_t wReserved; + uint32_t dwNtbOutMaxSize; + uint16_t wNdbOutDivisor; + uint16_t wNdbOutPayloadRemainder; + uint16_t wNdbOutAlignment; + uint16_t wNtbOutMaxDatagrams; +} ntb_parameters_t; + +typedef struct TU_ATTR_PACKED { + uint32_t dwSignature; + uint16_t wHeaderLength; + uint16_t wSequence; + uint16_t wBlockLength; + uint16_t wNdpIndex; +} nth16_t; + +typedef struct TU_ATTR_PACKED { + uint16_t wDatagramIndex; + uint16_t wDatagramLength; +} ndp16_datagram_t; + +typedef struct TU_ATTR_PACKED { + uint32_t dwSignature; + uint16_t wLength; + uint16_t wNextNdpIndex; + //ndp16_datagram_t datagram[]; +} ndp16_t; + +typedef union TU_ATTR_PACKED { + struct { + nth16_t nth; + ndp16_t ndp; + ndp16_datagram_t ndp_datagram[CFG_TUD_NCM_IN_MAX_DATAGRAMS_PER_NTB + 1]; + }; + uint8_t data[CFG_TUD_NCM_IN_NTB_MAX_SIZE]; +} xmit_ntb_t; + +typedef union TU_ATTR_PACKED { + struct { + nth16_t nth; + // only the header is at a guaranteed position + }; + uint8_t data[CFG_TUD_NCM_OUT_NTB_MAX_SIZE]; +} recv_ntb_t; + +typedef struct { + tusb_control_request_t header; + uint32_t downlink; + uint32_t uplink; +} ncm_notify_t; + +typedef struct TU_ATTR_PACKED { + uint8_t bFunctionLength; + uint8_t bDescriptorType; + uint8_t bDescriptorSubType; + uint16_t bcdNcmVersion; + uint8_t bmCapabilities; +} tusb_desc_cdc_ncm_func_t; + +typedef struct TU_ATTR_PACKED { + uint32_t dwNtbInMaxSize; + uint16_t wNtbInMaxDatagrams; + uint16_t wReserved; +} ncm_ntb_input_size_t; + +#endif diff --git a/libraries/Adafruit_TinyUSB_Arduino/src/class/net/ncm_device.c b/libraries/Adafruit_TinyUSB_Arduino/src/class/net/ncm_device.c new file mode 100644 index 0000000..a78e472 --- /dev/null +++ b/libraries/Adafruit_TinyUSB_Arduino/src/class/net/ncm_device.c @@ -0,0 +1,1095 @@ +/* + * The MIT License (MIT) + * + * Copyright (c) 2019 Ha Thach (tinyusb.org) + * Copyright (c) 2024 Hardy Griech + * Copyright (c) 2020 Jacob Berg Potter + * Copyright (c) 2020 Peter Lawrence + * + * 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. + */ + +/** + * Small Glossary (from the spec) + * -------------- + * Datagram - A collection of bytes forming a single item of information, passed as a unit from source to destination. + * NCM - Network Control Model + * NDP - NCM Datagram Pointer: NTB structure that delineates Datagrams (typically Ethernet frames) within an NTB + * NTB - NCM Transfer Block: a data structure for efficient USB encapsulation of one or more datagrams + * Each NTB is designed to be a single USB transfer + * NTH - NTB Header: a data structure at the front of each NTB, which provides the information needed to validate + * the NTB and begin decoding + * + * Some explanations + * ----------------- + * - rhport is the USB port of the device, in most cases "0" + * - itf_data_alt if != 0 -> data xmit/recv are allowed (see spec) + * - ep_in IN endpoints take data from the device intended to go in to the host (the device transmits) + * - ep_out OUT endpoints send data out of the host to the device (the device receives) + */ + +#include "tusb_option.h" + +#if (CFG_TUD_ENABLED && CFG_TUD_NCM) + +#include "device/usbd.h" +#include "device/usbd_pvt.h" + +#include "ncm.h" +#include "net_device.h" + +// Level where CFG_TUSB_DEBUG must be at least for this driver is logged +#ifndef CFG_TUD_NCM_LOG_LEVEL + #define CFG_TUD_NCM_LOG_LEVEL CFG_TUD_LOG_LEVEL +#endif + +#define TU_LOG_DRV(...) TU_LOG(CFG_TUD_NCM_LOG_LEVEL, __VA_ARGS__) + +// Alignment must be 4 +#define TUD_NCM_ALIGNMENT 4 +// calculate alignment of xmit datagrams within an NTB +#define XMIT_ALIGN_OFFSET(x) ((TUD_NCM_ALIGNMENT - ((x) & (TUD_NCM_ALIGNMENT - 1))) & (TUD_NCM_ALIGNMENT - 1)) + +//----------------------------------------------------------------------------- +// +// Module global things +// +#define XMIT_NTB_N CFG_TUD_NCM_IN_NTB_N +#define RECV_NTB_N CFG_TUD_NCM_OUT_NTB_N + +typedef struct { + // general + uint8_t ep_in; // endpoint for outgoing datagrams (naming is a little bit confusing) + uint8_t ep_out; // endpoint for incoming datagrams (naming is a little bit confusing) + uint8_t ep_notif; // endpoint for notifications + uint8_t itf_num; // interface number + uint8_t itf_data_alt; // ==0 -> no endpoints, i.e. no network traffic, ==1 -> normal operation with two endpoints (spec, chapter 5.3) + uint8_t rhport; // storage of \a rhport because some callbacks are done without it + uint16_t ep_size; // bulk endpoint max packet size (IN and OUT assumed equal) + + // recv handling + recv_ntb_t *recv_free_ntb[RECV_NTB_N]; // free list of recv NTBs + recv_ntb_t *recv_ready_ntb[RECV_NTB_N]; // NTBs waiting for transmission to glue logic (circular buffer) + #if RECV_NTB_N > 1 + uint8_t recv_ready_head; // head index for recv_ready_ntb circular buffer + uint8_t recv_ready_tail; // tail index for recv_ready_ntb circular buffer + uint8_t recv_ready_count; // number of elements in recv_ready_ntb circular buffer + #endif + recv_ntb_t *recv_tinyusb_ntb; // buffer for the running transfer TinyUSB -> driver + recv_ntb_t *recv_glue_ntb; // buffer for the running transfer driver -> glue logic + uint16_t recv_glue_ntb_datagram_ndx; // index into \a recv_glue_ntb_datagram + + // xmit handling + xmit_ntb_t *xmit_free_ntb[XMIT_NTB_N]; // free list of xmit NTBs + xmit_ntb_t *xmit_ready_ntb[XMIT_NTB_N]; // NTBs waiting for transmission to TinyUSB (circular buffer) + #if XMIT_NTB_N > 1 + uint8_t xmit_ready_head; // head index for xmit_ready_ntb circular buffer + uint8_t xmit_ready_tail; // tail index for xmit_ready_ntb circular buffer + uint8_t xmit_ready_count; // number of elements in xmit_ready_ntb circular buffer + #endif + xmit_ntb_t *xmit_tinyusb_ntb; // buffer for the running transfer driver -> TinyUSB + xmit_ntb_t *xmit_glue_ntb; // buffer for the running transfer glue logic -> driver + uint16_t xmit_sequence; // NTB sequence counter + uint16_t xmit_glue_ntb_datagram_ndx; // index into \a xmit_glue_ntb_datagram + + // notification handling + enum { + NOTIFICATION_SPEED, + NOTIFICATION_CONNECTED, + NOTIFICATION_DONE + } notification_xmit_state; // state of notification transmission + bool notification_xmit_is_running; // notification is currently transmitted + bool link_is_up; // current link state + + // host-configured transmit limits + uint8_t bm_capabilities; + uint16_t xmit_max_ntb_size; // maximum NTB size device may send + uint16_t xmit_max_datagrams; // maximum datagrams per NTB device may send + ncm_ntb_input_size_t ntb_input_size; + + // misc + bool tud_network_recv_renew_active; // tud_network_recv_renew() is active (avoid recursive invocations) + bool tud_network_recv_renew_process_again; // tud_network_recv_renew() should process again +} ncm_interface_t; + +typedef struct { + struct { + TUD_EPBUF_TYPE_DEF(recv_ntb_t, ntb); + } recv[RECV_NTB_N]; + + struct { + TUD_EPBUF_TYPE_DEF(xmit_ntb_t, ntb); + } xmit[XMIT_NTB_N]; + + TUD_EPBUF_TYPE_DEF(ncm_notify_t, epnotif); +} ncm_epbuf_t; + +static ncm_interface_t ncm_interface; +CFG_TUD_MEM_SECTION static ncm_epbuf_t ncm_epbuf; + +//--------------------------------------------------------------------+ +// Weak stubs: invoked if no strong implementation is available +//--------------------------------------------------------------------+ +TU_ATTR_WEAK void tud_network_set_packet_filter_cb(uint16_t packet_filter) { + (void) packet_filter; +} + +/** + * This is the NTB parameter structure + * + * \attention + * We are lucky, that byte order is correct + */ +TU_ATTR_ALIGNED(4) static const ntb_parameters_t ntb_parameters = { + .wLength = sizeof(ntb_parameters_t), + .bmNtbFormatsSupported = 0x01,// 16-bit NTB supported + .dwNtbInMaxSize = CFG_TUD_NCM_IN_NTB_MAX_SIZE, + .wNdbInDivisor = 1, + .wNdbInPayloadRemainder = 0, + .wNdbInAlignment = TUD_NCM_ALIGNMENT, + .wReserved = 0, + .dwNtbOutMaxSize = CFG_TUD_NCM_OUT_NTB_MAX_SIZE, + .wNdbOutDivisor = 1, + .wNdbOutPayloadRemainder = 0, + .wNdbOutAlignment = TUD_NCM_ALIGNMENT, + .wNtbOutMaxDatagrams = CFG_TUD_NCM_OUT_MAX_DATAGRAMS_PER_NTB, +}; + +// Some confusing remarks about wNtbOutMaxDatagrams... +// ==1 -> SystemView packets/s goes up to 2000 and events are lost during startup +// ==0 -> SystemView runs fine, iperf shows in wireshark a lot of error +// ==6 -> SystemView runs fine, iperf also +// >6 -> iperf starts to show errors +// -> 6 seems to be the best value. Why? Don't know, perhaps only on my system? +// +// iperf: for MSS in 100 200 400 800 1200 1450 1500; do iperf -c 192.168.14.1 -e -i 1 -M $MSS -l 8192 -P 1; sleep 2; done +// sysview: SYSTICKS_PER_SEC=35000, IDLE_US=1000, PRINT_MOD=1000 +// + +//----------------------------------------------------------------------------- +// +// everything about notifications +// + +/** + * Transmit next notification to the host (if appropriate). + * Notifications are transferred to the host once during connection setup. + */ +static void notification_xmit(uint8_t rhport, bool force_next) { + TU_LOG_DRV("notification_xmit(%d, %d) - %d %d\n", force_next, rhport, ncm_interface.notification_xmit_state, ncm_interface.notification_xmit_is_running); + + if (!force_next && ncm_interface.notification_xmit_is_running) { + return; + } + + if (ncm_interface.notification_xmit_state == NOTIFICATION_SPEED) { + TU_LOG_DRV(" NOTIFICATION_SPEED\n"); + ncm_notify_t notify_speed_change = { + .header = { + .bmRequestType_bit = { + .recipient = TUSB_REQ_RCPT_INTERFACE, + .type = TUSB_REQ_TYPE_CLASS, + .direction = TUSB_DIR_IN + }, + .bRequest = CDC_NOTIF_CONNECTION_SPEED_CHANGE, + .wValue = 0, + .wIndex = ncm_interface.itf_num, + .wLength = 8 + } + }; + if (tud_speed_get() == TUSB_SPEED_HIGH) { + notify_speed_change.downlink = 480000000; + notify_speed_change.uplink = 480000000; + } else { + notify_speed_change.downlink = 12000000; + notify_speed_change.uplink = 12000000; + } + + uint16_t notif_len = sizeof(notify_speed_change.header) + notify_speed_change.header.wLength; + ncm_epbuf.epnotif = notify_speed_change; + usbd_edpt_xfer(rhport, ncm_interface.ep_notif, (uint8_t*) &ncm_epbuf.epnotif, notif_len, false); + + ncm_interface.notification_xmit_state = NOTIFICATION_CONNECTED; + ncm_interface.notification_xmit_is_running = true; + } else if (ncm_interface.notification_xmit_state == NOTIFICATION_CONNECTED) { + TU_LOG_DRV(" NOTIFICATION_CONNECTED\n"); + ncm_notify_t notify_connected = { + .header = { + .bmRequestType_bit = { + .recipient = TUSB_REQ_RCPT_INTERFACE, + .type = TUSB_REQ_TYPE_CLASS, + .direction = TUSB_DIR_IN + }, + .bRequest = CDC_NOTIF_NETWORK_CONNECTION, + .wValue = ncm_interface.link_is_up ? 1 : 0, /* Dynamic link state */ + .wIndex = ncm_interface.itf_num, + .wLength = 0, + }, + }; + + uint16_t notif_len = sizeof(notify_connected.header) + notify_connected.header.wLength; + ncm_epbuf.epnotif = notify_connected; + usbd_edpt_xfer(rhport, ncm_interface.ep_notif, (uint8_t *) &ncm_epbuf.epnotif, notif_len, false); + + ncm_interface.notification_xmit_state = NOTIFICATION_DONE; + ncm_interface.notification_xmit_is_running = true; + } else { + TU_LOG_DRV(" NOTIFICATION_FINISHED\n"); + ncm_interface.notification_xmit_is_running = false; + } +} // notification_xmit + +//----------------------------------------------------------------------------- +// +// everything about packet transmission (driver -> TinyUSB) +// + +/** + * Put NTB into the transmitter free list. + */ +static void xmit_put_ntb_into_free_list(xmit_ntb_t *free_ntb) { + TU_LOG_DRV("xmit_put_ntb_into_free_list() - %p\n", ncm_interface.xmit_tinyusb_ntb); + + if (free_ntb == NULL) { // can happen due to ZLPs + return; + } + + for (int i = 0; i < XMIT_NTB_N; ++i) { + if (ncm_interface.xmit_free_ntb[i] == NULL) { + ncm_interface.xmit_free_ntb[i] = free_ntb; + return; + } + } + TU_LOG_DRV("(EE) xmit_put_ntb_into_free_list - no entry in free list\n");// this should not happen +} // xmit_put_ntb_into_free_list + +/** + * Get an NTB from the free list + */ +static xmit_ntb_t *xmit_get_free_ntb(void) { + TU_LOG_DRV("xmit_get_free_ntb()\n"); + + for (int i = 0; i < XMIT_NTB_N; ++i) { + if (ncm_interface.xmit_free_ntb[i] != NULL) { + xmit_ntb_t *free = ncm_interface.xmit_free_ntb[i]; + ncm_interface.xmit_free_ntb[i] = NULL; + return free; + } + } + return NULL; +} // xmit_get_free_ntb + +/** + * Put a filled NTB into the ready list + */ +static void xmit_put_ntb_into_ready_list(xmit_ntb_t *ready_ntb) { + TU_LOG_DRV("xmit_put_ntb_into_ready_list(%p) %d\n", ready_ntb, ready_ntb->nth.wBlockLength); + +#if XMIT_NTB_N == 1 + ncm_interface.xmit_ready_ntb[0] = ready_ntb; +#else + if (ncm_interface.xmit_ready_count >= XMIT_NTB_N) { + TU_LOG_DRV("(EE) xmit_put_ntb_into_ready_list: ready list full\n");// this should not happen + return; + } + ncm_interface.xmit_ready_ntb[ncm_interface.xmit_ready_head] = ready_ntb; + ncm_interface.xmit_ready_head = (ncm_interface.xmit_ready_head + 1) % XMIT_NTB_N; + ncm_interface.xmit_ready_count++; +#endif +} // xmit_put_ntb_into_ready_list + +/** + * Get the next NTB from the ready list (and remove it from the list). + * If the ready list is empty, return NULL. + */ +static xmit_ntb_t *xmit_get_next_ready_ntb(void) { +#if XMIT_NTB_N == 1 + xmit_ntb_t *r = ncm_interface.xmit_ready_ntb[0]; + ncm_interface.xmit_ready_ntb[0] = NULL; + TU_LOG_DRV("xmit_get_next_ready_ntb: %p\n", r); + return r; +#else + if (ncm_interface.xmit_ready_count == 0) { + return NULL; // empty + } + + xmit_ntb_t *r = ncm_interface.xmit_ready_ntb[ncm_interface.xmit_ready_tail]; + ncm_interface.xmit_ready_tail = (ncm_interface.xmit_ready_tail + 1) % XMIT_NTB_N; + ncm_interface.xmit_ready_count--; + + TU_LOG_DRV("xmit_get_next_ready_ntb: %p\n", r); + return r; +#endif +} // xmit_get_next_ready_ntb + +/** + * Transmit a ZLP if required + * + * \note + * Insertion of the ZLPs is a little bit different then described in the spec. + * But the below implementation actually works. Don't know if this is a spec + * or TinyUSB issue. + * + * \pre + * This must be called from netd_xfer_cb() so that ep_in is ready + */ +static bool xmit_insert_required_zlp(uint8_t rhport, uint32_t xferred_bytes) { + TU_LOG_DRV("xmit_insert_required_zlp(%d,%ld)\n", rhport, xferred_bytes); + + uint16_t const ep_size = ncm_interface.ep_size; + if (xferred_bytes == 0 || (xferred_bytes & (ep_size-1)) != 0) { + return false; + } + + TU_ASSERT(ncm_interface.itf_data_alt == 1, false); + TU_ASSERT(!usbd_edpt_busy(rhport, ncm_interface.ep_in), false); + + TU_LOG_DRV("xmit_insert_required_zlp! (%u)\n", (unsigned) xferred_bytes); + + // start transmission of the ZLP + usbd_edpt_xfer(rhport, ncm_interface.ep_in, NULL, 0, false); + + return true; +} // xmit_insert_required_zlp + +/** + * Start transmission if it there is a waiting packet and if can be done from interface side. + */ +static void xmit_start_if_possible(uint8_t rhport) { + TU_LOG_DRV("xmit_start_if_possible()\n"); + + if (ncm_interface.xmit_tinyusb_ntb != NULL) { + TU_LOG_DRV(" !xmit_start_if_possible 1\n"); + return; + } + if (ncm_interface.itf_data_alt != 1) { + TU_LOG_DRV("(EE) !xmit_start_if_possible 2\n"); + return; + } + if (usbd_edpt_busy(rhport, ncm_interface.ep_in)) { + TU_LOG_DRV(" !xmit_start_if_possible 3\n"); + return; + } + + ncm_interface.xmit_tinyusb_ntb = xmit_get_next_ready_ntb(); + if (ncm_interface.xmit_tinyusb_ntb == NULL) { + if (ncm_interface.xmit_glue_ntb == NULL || ncm_interface.xmit_glue_ntb_datagram_ndx == 0) { + // -> really nothing is waiting + return; + } + ncm_interface.xmit_tinyusb_ntb = ncm_interface.xmit_glue_ntb; + ncm_interface.xmit_glue_ntb = NULL; + } + + #if CFG_TUD_NCM_LOG_LEVEL >= 3 + { + uint16_t len = ncm_interface.xmit_tinyusb_ntb->nth.wBlockLength; + TU_LOG_BUF(3, ncm_interface.xmit_tinyusb_ntb->data[i], len); + } + #endif + + if (ncm_interface.xmit_glue_ntb_datagram_ndx != 1) { + TU_LOG_DRV(">> %d %d\n", ncm_interface.xmit_tinyusb_ntb->nth.wBlockLength, ncm_interface.xmit_glue_ntb_datagram_ndx); + } + + // Kick off an endpoint transfer + usbd_edpt_xfer(0, ncm_interface.ep_in, ncm_interface.xmit_tinyusb_ntb->data, ncm_interface.xmit_tinyusb_ntb->nth.wBlockLength, false); +} // xmit_start_if_possible + +/** + * check if a new datagram fits into the current NTB + */ +static bool xmit_requested_datagram_fits_into_current_ntb(uint16_t datagram_size) { + TU_LOG_DRV("xmit_requested_datagram_fits_into_current_ntb(%d) - %p %p\n", datagram_size, ncm_interface.xmit_tinyusb_ntb, ncm_interface.xmit_glue_ntb); + + if (ncm_interface.xmit_glue_ntb == NULL) { + return false; + } + if (ncm_interface.xmit_glue_ntb_datagram_ndx >= ncm_interface.xmit_max_datagrams) { + return false; + } + if (ncm_interface.xmit_glue_ntb->nth.wBlockLength + datagram_size + (uint32_t)XMIT_ALIGN_OFFSET(datagram_size) > (uint32_t)ncm_interface.xmit_max_ntb_size) { + return false; + } + return true; +} // xmit_requested_datagram_fits_into_current_ntb + +/** + * Setup an NTB for the glue logic + */ +static bool xmit_setup_next_glue_ntb(void) { + TU_LOG_DRV("xmit_setup_next_glue_ntb - %p\n", ncm_interface.xmit_glue_ntb); + + if (ncm_interface.xmit_glue_ntb != NULL) { + // put NTB into waiting list (the new datagram did not fit in) + xmit_put_ntb_into_ready_list(ncm_interface.xmit_glue_ntb); + } + + ncm_interface.xmit_glue_ntb = xmit_get_free_ntb();// get next buffer (if any) + if (ncm_interface.xmit_glue_ntb == NULL) { + TU_LOG_DRV(" xmit_setup_next_glue_ntb - nothing free\n");// should happen rarely + return false; + } + + ncm_interface.xmit_glue_ntb_datagram_ndx = 0; + + xmit_ntb_t *ntb = ncm_interface.xmit_glue_ntb; + + // Fill in NTB header + ntb->nth.dwSignature = NTH16_SIGNATURE; + ntb->nth.wHeaderLength = sizeof(ntb->nth); + ntb->nth.wSequence = ncm_interface.xmit_sequence++; + ntb->nth.wBlockLength = sizeof(ntb->nth) + sizeof(ntb->ndp) + sizeof(ntb->ndp_datagram); + ntb->nth.wNdpIndex = sizeof(ntb->nth); + + // Fill in NDP16 header and terminator + ntb->ndp.dwSignature = NDP16_SIGNATURE_NCM0; + ntb->ndp.wLength = sizeof(ntb->ndp) + sizeof(ntb->ndp_datagram); + ntb->ndp.wNextNdpIndex = 0; + + memset(ntb->ndp_datagram, 0, sizeof(ntb->ndp_datagram)); + return true; +} // xmit_setup_next_glue_ntb + +//----------------------------------------------------------------------------- +// +// all the recv_*() stuff (TinyUSB -> driver -> glue logic) +// + +/** + * Return pointer to an available receive buffer or NULL. + * Returned buffer (if any) has the size \a CFG_TUD_NCM_OUT_NTB_MAX_SIZE. + */ +static recv_ntb_t *recv_get_free_ntb(void) { + TU_LOG_DRV("recv_get_free_ntb()\n"); + + for (int i = 0; i < RECV_NTB_N; ++i) { + if (ncm_interface.recv_free_ntb[i] != NULL) { + recv_ntb_t *free = ncm_interface.recv_free_ntb[i]; + ncm_interface.recv_free_ntb[i] = NULL; + return free; + } + } + return NULL; +} // recv_get_free_ntb + +/** + * Get the next NTB from the ready list (and remove it from the list). + * If the ready list is empty, return NULL. + */ +static recv_ntb_t *recv_get_next_ready_ntb(void) { +#if RECV_NTB_N == 1 + recv_ntb_t *r = ncm_interface.recv_ready_ntb[0]; + ncm_interface.recv_ready_ntb[0] = NULL; + TU_LOG_DRV("recv_get_next_ready_ntb: %p\n", r); + return r; +#else + if (ncm_interface.recv_ready_count == 0) { + return NULL; // empty + } + + recv_ntb_t *r = ncm_interface.recv_ready_ntb[ncm_interface.recv_ready_tail]; + ncm_interface.recv_ready_tail = (ncm_interface.recv_ready_tail + 1) % RECV_NTB_N; + ncm_interface.recv_ready_count--; + + TU_LOG_DRV("recv_get_next_ready_ntb: %p\n", r); + return r; +#endif +} // recv_get_next_ready_ntb + +/** + * Put NTB into the receiver free list. + */ +static void recv_put_ntb_into_free_list(recv_ntb_t *free_ntb) { + TU_LOG_DRV("recv_put_ntb_into_free_list(%p)\n", free_ntb); + + for (int i = 0; i < RECV_NTB_N; ++i) { + if (ncm_interface.recv_free_ntb[i] == NULL) { + ncm_interface.recv_free_ntb[i] = free_ntb; + return; + } + } + TU_LOG_DRV("(EE) recv_put_ntb_into_free_list - no entry in free list\n");// this should not happen +} // recv_put_ntb_into_free_list + +/** + * \a ready_ntb holds a validated NTB, + * put this buffer into the waiting list. + */ +static void recv_put_ntb_into_ready_list(recv_ntb_t *ready_ntb) { + TU_LOG_DRV("recv_put_ntb_into_ready_list(%p) %d\n", ready_ntb, ready_ntb->nth.wBlockLength); + +#if RECV_NTB_N == 1 + ncm_interface.recv_ready_ntb[0] = ready_ntb; +#else + if (ncm_interface.recv_ready_count >= RECV_NTB_N) { + TU_LOG_DRV("(EE) recv_put_ntb_into_ready_list: ready list full\n");// this should not happen + return; + } + ncm_interface.recv_ready_ntb[ncm_interface.recv_ready_head] = ready_ntb; + ncm_interface.recv_ready_head = (ncm_interface.recv_ready_head + 1) % RECV_NTB_N; + ncm_interface.recv_ready_count++; +#endif +} // recv_put_ntb_into_ready_list + +/** + * If possible, start a new reception TinyUSB -> driver. + */ +static void recv_try_to_start_new_reception(uint8_t rhport) { + TU_LOG_DRV("recv_try_to_start_new_reception(%d)\n", rhport); + + if (ncm_interface.itf_data_alt != 1) { + return; + } + if (ncm_interface.recv_tinyusb_ntb != NULL) { + return; + } + if (usbd_edpt_busy(rhport, ncm_interface.ep_out)) { + return; + } + + ncm_interface.recv_tinyusb_ntb = recv_get_free_ntb(); + if (ncm_interface.recv_tinyusb_ntb == NULL) { + return; + } + + // initiate transfer + TU_LOG_DRV(" start reception\n"); + bool r = usbd_edpt_xfer(rhport, ncm_interface.ep_out, ncm_interface.recv_tinyusb_ntb->data, CFG_TUD_NCM_OUT_NTB_MAX_SIZE, false); + if (!r) { + recv_put_ntb_into_free_list(ncm_interface.recv_tinyusb_ntb); + ncm_interface.recv_tinyusb_ntb = NULL; + } +} // recv_try_to_start_new_reception + +/** + * Validate incoming datagram. + * \return true if valid + * + * \note + * \a ndp16->wNextNdpIndex != 0 is not supported + */ +static bool recv_validate_datagram(const recv_ntb_t *ntb, uint32_t len) { + const nth16_t *nth16 = &(ntb->nth); + + TU_LOG_DRV("recv_validate_datagram(%p, %d)\n", ntb, (int) len); + + // check header + if (nth16->wHeaderLength != sizeof(nth16_t)) { + TU_LOG_DRV("(EE) ill nth16 length: %d\n", nth16->wHeaderLength); + return false; + } + if (nth16->dwSignature != NTH16_SIGNATURE) { + TU_LOG_DRV("(EE) ill signature: 0x%08x\n", (unsigned) nth16->dwSignature); + return false; + } + if (len < sizeof(nth16_t) + sizeof(ndp16_t) + 2 * sizeof(ndp16_datagram_t)) { + TU_LOG_DRV("(EE) ill min len: %lu\n", len); + return false; + } + if (nth16->wBlockLength > len) { + TU_LOG_DRV("(EE) ill block length: %d > %lu\n", nth16->wBlockLength, len); + return false; + } + if (nth16->wBlockLength > CFG_TUD_NCM_OUT_NTB_MAX_SIZE) { + TU_LOG_DRV("(EE) ill block length2: %d > %d\n", nth16->wBlockLength, CFG_TUD_NCM_OUT_NTB_MAX_SIZE); + return false; + } + if (nth16->wNdpIndex < sizeof(nth16) || nth16->wNdpIndex > len - (sizeof(ndp16_t) + 2 * sizeof(ndp16_datagram_t))) { + TU_LOG_DRV("(EE) ill position of first ndp: %d (%lu)\n", nth16->wNdpIndex, len); + return false; + } + + // check (first) NDP(16) + const ndp16_t *ndp16 = (const ndp16_t *) (ntb->data + nth16->wNdpIndex); + + if (ndp16->wLength < sizeof(ndp16_t) + 2 * sizeof(ndp16_datagram_t)) { + TU_LOG_DRV("(EE) ill ndp16 length: %d\n", ndp16->wLength); + return false; + } + if (ndp16->dwSignature != NDP16_SIGNATURE_NCM0 && ndp16->dwSignature != NDP16_SIGNATURE_NCM1) { + TU_LOG_DRV("(EE) ill signature: 0x%08x\n", (unsigned) ndp16->dwSignature); + return false; + } + if (ndp16->wNextNdpIndex != 0) { + TU_LOG_DRV("(EE) cannot handle wNextNdpIndex!=0 (%d)\n", ndp16->wNextNdpIndex); + return false; + } + + const ndp16_datagram_t *ndp16_datagram = (const ndp16_datagram_t *) (ntb->data + nth16->wNdpIndex + sizeof(ndp16_t)); + int ndx = 0; + uint16_t max_ndx = (uint16_t) ((ndp16->wLength - sizeof(ndp16_t)) / sizeof(ndp16_datagram_t)); + + if (max_ndx > 2) { // number of datagrams in NTB > 1 + TU_LOG_DRV("<< %d (%d)\n", max_ndx - 1, ntb->nth.wBlockLength); + } + if (ndp16_datagram[max_ndx - 1].wDatagramIndex != 0 || ndp16_datagram[max_ndx - 1].wDatagramLength != 0) { + TU_LOG_DRV(" max_ndx != 0\n"); + return false; + } + while (ndp16_datagram[ndx].wDatagramIndex != 0 && ndp16_datagram[ndx].wDatagramLength != 0) { + TU_LOG_DRV(" << %d %d\n", ndp16_datagram[ndx].wDatagramIndex, ndp16_datagram[ndx].wDatagramLength); + if (ndp16_datagram[ndx].wDatagramIndex > len) { + TU_LOG_DRV("(EE) ill start of datagram[%d]: %d (%lu)\n", ndx, ndp16_datagram[ndx].wDatagramIndex, len); + return false; + } + if (ndp16_datagram[ndx].wDatagramIndex + ndp16_datagram[ndx].wDatagramLength > len) { + TU_LOG_DRV("(EE) ill end of datagram[%d]: %d (%lu)\n", ndx, ndp16_datagram[ndx].wDatagramIndex + ndp16_datagram[ndx].wDatagramLength, len); + return false; + } + ++ndx; + } + + #if CFG_TUD_NCM_LOG_LEVEL >= 3 + TU_LOG_BUF(3, ntb->data[i], len); + #endif + + // -> ntb contains a valid packet structure + // ok... I did not check for garbage within the datagram indices... + return true; +} // recv_validate_datagram + +/** + * Transfer the next (pending) datagram to the glue logic and return receive buffer if empty. + */ +static void recv_transfer_datagram_to_glue_logic(void) { + TU_LOG_DRV("recv_transfer_datagram_to_glue_logic()\n"); + + if (ncm_interface.recv_glue_ntb == NULL) { + ncm_interface.recv_glue_ntb = recv_get_next_ready_ntb(); + TU_LOG_DRV(" new buffer for glue logic: %p\n", ncm_interface.recv_glue_ntb); + ncm_interface.recv_glue_ntb_datagram_ndx = 0; + } + + if (ncm_interface.recv_glue_ntb != NULL) { + const ndp16_datagram_t *ndp16_datagram = (ndp16_datagram_t *) (ncm_interface.recv_glue_ntb->data + ncm_interface.recv_glue_ntb->nth.wNdpIndex + sizeof(ndp16_t)); + + if (ndp16_datagram[ncm_interface.recv_glue_ntb_datagram_ndx].wDatagramIndex == 0) { + TU_LOG_DRV("(EE) SOMETHING WENT WRONG 1\n"); + } else if (ndp16_datagram[ncm_interface.recv_glue_ntb_datagram_ndx].wDatagramLength == 0) { + TU_LOG_DRV("(EE) SOMETHING WENT WRONG 2\n"); + } else { + uint16_t datagramIndex = ndp16_datagram[ncm_interface.recv_glue_ntb_datagram_ndx].wDatagramIndex; + uint16_t datagramLength = ndp16_datagram[ncm_interface.recv_glue_ntb_datagram_ndx].wDatagramLength; + + TU_LOG_DRV(" recv[%d] - %d %d\n", ncm_interface.recv_glue_ntb_datagram_ndx, datagramIndex, datagramLength); + if (tud_network_recv_cb(ncm_interface.recv_glue_ntb->data + datagramIndex, datagramLength)) { + // send datagram successfully to glue logic + TU_LOG_DRV(" OK\n"); + datagramIndex = ndp16_datagram[ncm_interface.recv_glue_ntb_datagram_ndx + 1].wDatagramIndex; + datagramLength = ndp16_datagram[ncm_interface.recv_glue_ntb_datagram_ndx + 1].wDatagramLength; + + if (datagramIndex != 0 && datagramLength != 0) { + // -> next datagram + ++ncm_interface.recv_glue_ntb_datagram_ndx; + } else { + // end of datagrams reached + recv_put_ntb_into_free_list(ncm_interface.recv_glue_ntb); + ncm_interface.recv_glue_ntb = NULL; + } + } + } + } +} // recv_transfer_datagram_to_glue_logic + +//----------------------------------------------------------------------------- +// +// all the tud_network_*() stuff (glue logic -> driver) +// + +/** + * Check if the glue logic is allowed to call tud_network_xmit(). + * This function also fetches a next buffer if required, so that tud_network_xmit() is ready for copy + * and transmission operation. + */ +bool tud_network_can_xmit(uint16_t size) { + TU_LOG_DRV("tud_network_can_xmit(%d)\n", size); + + TU_ASSERT(size <= ncm_interface.xmit_max_ntb_size - (sizeof(nth16_t) + sizeof(ndp16_t) + 2 * sizeof(ndp16_datagram_t)), false); + + if (xmit_requested_datagram_fits_into_current_ntb(size) || xmit_setup_next_glue_ntb()) { + // -> everything is fine + return true; + } + xmit_start_if_possible(ncm_interface.rhport); + TU_LOG_DRV("(II) tud_network_can_xmit: request blocked\n");// could happen if all xmit buffers are full (but should happen rarely) + return false; +} // tud_network_can_xmit + +/** + * Put a datagram into a waiting NTB. + * If currently no transmission is started, then initiate transmission. + */ +void tud_network_xmit(void *ref, uint16_t arg) { + TU_LOG_DRV("tud_network_xmit(%p, %d)\n", ref, arg); + + if (ncm_interface.xmit_glue_ntb == NULL) { + TU_LOG_DRV("(EE) tud_network_xmit: no buffer\n");// must not happen (really) + return; + } + + xmit_ntb_t *ntb = ncm_interface.xmit_glue_ntb; + + // copy new datagram to the end of the current NTB + uint16_t size = tud_network_xmit_cb(ntb->data + ntb->nth.wBlockLength, ref, arg); + + // correct NTB internals + ntb->ndp_datagram[ncm_interface.xmit_glue_ntb_datagram_ndx].wDatagramIndex = ntb->nth.wBlockLength; + ntb->ndp_datagram[ncm_interface.xmit_glue_ntb_datagram_ndx].wDatagramLength = size; + ncm_interface.xmit_glue_ntb_datagram_ndx += 1; + + ntb->nth.wBlockLength += (uint16_t) (size + XMIT_ALIGN_OFFSET(size)); + + if (ntb->nth.wBlockLength > CFG_TUD_NCM_IN_NTB_MAX_SIZE) { + TU_LOG_DRV("(EE) tud_network_xmit: buffer overflow\n"); // must not happen (really) + return; + } + + xmit_start_if_possible(ncm_interface.rhport); +} // tud_network_xmit + +/** + * Keep the receive logic busy and transfer pending packets to the glue logic. + * Avoid recursive calls due to wrong expectations of the net glue logic, + * see https://github.com/hathach/tinyusb/issues/2711 + */ +void tud_network_recv_renew(void) { + TU_LOG_DRV("tud_network_recv_renew()\n"); + + ncm_interface.tud_network_recv_renew_process_again = true; + + if (ncm_interface.tud_network_recv_renew_active) { + TU_LOG_DRV("Re-entrant into tud_network_recv_renew, will process later\n"); + return; + } + + while (ncm_interface.tud_network_recv_renew_process_again) { + ncm_interface.tud_network_recv_renew_process_again = false; + + // If the current function is called within recv_transfer_datagram_to_glue_logic, + // tud_network_recv_renew_process_again will become true, and the loop will run again + // Otherwise the loop will not run again + ncm_interface.tud_network_recv_renew_active = true; + recv_transfer_datagram_to_glue_logic(); + ncm_interface.tud_network_recv_renew_active = false; + } + recv_try_to_start_new_reception(ncm_interface.rhport); +} // tud_network_recv_renew + +/** + * Same as tud_network_recv_renew() but knows \a rhport + */ +static void tud_network_recv_renew_r(uint8_t rhport) { + TU_LOG_DRV("tud_network_recv_renew_r(%d)\n", rhport); + + ncm_interface.rhport = rhport; + tud_network_recv_renew(); +} // tud_network_recv_renew + +/** + * Set the link state and send notification to host + */ +void tud_network_link_state(uint8_t rhport, bool is_up) { + TU_LOG_DRV("tud_network_link_state(%d, %d)\n", rhport, is_up); + + if (ncm_interface.link_is_up == is_up) { + // No change in link state + return; + } + + ncm_interface.link_is_up = is_up; + + // Only send notification if we have an active data interface + if (ncm_interface.itf_data_alt != 1) { + TU_LOG_DRV(" link state notification skipped (interface not active)\n"); + return; + } + + // Reset notification state to send speed change notification first, then link state notification + ncm_interface.notification_xmit_state = NOTIFICATION_SPEED; + + // Trigger notification transmission + notification_xmit(rhport, false); +} + +//----------------------------------------------------------------------------- +// +// all the netd_*() stuff (interface TinyUSB -> driver) +// +/** + * Initialize the driver data structures. + * Might be called several times. + */ +void netd_init(void) { + TU_LOG_DRV("netd_init()\n"); + + memset(&ncm_interface, 0, sizeof(ncm_interface)); + + ncm_interface.xmit_max_ntb_size = CFG_TUD_NCM_IN_NTB_MAX_SIZE; + ncm_interface.xmit_max_datagrams = CFG_TUD_NCM_IN_MAX_DATAGRAMS_PER_NTB; + + for (int i = 0; i < XMIT_NTB_N; ++i) { + ncm_interface.xmit_free_ntb[i] = &ncm_epbuf.xmit[i].ntb; + } + for (int i = 0; i < RECV_NTB_N; ++i) { + ncm_interface.recv_free_ntb[i] = &ncm_epbuf.recv[i].ntb; + } + // Default link state - can be configured via CFG_TUD_NCM_DEFAULT_LINK_UP + #ifdef CFG_TUD_NCM_DEFAULT_LINK_UP + ncm_interface.link_is_up = CFG_TUD_NCM_DEFAULT_LINK_UP; + #else + ncm_interface.link_is_up = true; // Default to link up if not set. + #endif +} // netd_init + +/** + * Deinit driver + */ +bool netd_deinit(void) { + return true; +} + +/** + * Resets the port. + * In this driver this is the same as netd_init() + */ +void netd_reset(uint8_t rhport) { + (void) rhport; + + netd_init(); +} // netd_reset + +/** + * Open the USB interface. + * - parse the USB descriptor \a TUD_CDC_NCM_DESCRIPTOR for itfnum and endpoints + * - a specific order of elements in the descriptor is tested. + * + * \note + * Actually all of the information could be read directly from \a itf_desc, because the + * structure and the values are well known. But we do it this way. + * + * \post + * - \a itf_num set + * - \a ep_notif, \a ep_in and \a ep_out are set + * - USB interface is open + */ +uint16_t netd_open(uint8_t rhport, tusb_desc_interface_t const *itf_desc, uint16_t max_len) { + TU_ASSERT(ncm_interface.ep_notif == 0, 0);// assure that the interface is only opened once + + ncm_interface.itf_num = itf_desc->bInterfaceNumber;// management interface + + uint16_t drv_len = sizeof(tusb_desc_interface_t); + uint8_t const *p_desc = tu_desc_next(itf_desc); + while (tu_desc_type(p_desc) == TUSB_DESC_CS_INTERFACE && drv_len <= max_len) { + if (tu_desc_subtype(p_desc) == CDC_FUNC_DESC_NCM) { + TU_ASSERT(tu_desc_len(p_desc) >= sizeof(tusb_desc_cdc_ncm_func_t), 0); + tusb_desc_cdc_ncm_func_t const *ncm_func = (tusb_desc_cdc_ncm_func_t const *) p_desc; + ncm_interface.bm_capabilities = ncm_func->bmCapabilities; + } + drv_len += tu_desc_len(p_desc); + p_desc = tu_desc_next(p_desc); + } + + // get notification endpoint + TU_ASSERT(tu_desc_type(p_desc) == TUSB_DESC_ENDPOINT, 0); + TU_ASSERT(usbd_edpt_open(rhport, (tusb_desc_endpoint_t const *) p_desc), 0); + ncm_interface.ep_notif = ((tusb_desc_endpoint_t const *) p_desc)->bEndpointAddress; + drv_len += tu_desc_len(p_desc); + p_desc = tu_desc_next(p_desc); + + // skip the following TUSB_DESC_INTERFACE entries (which must be TUSB_CLASS_CDC_DATA) + while (tu_desc_type(p_desc) == TUSB_DESC_INTERFACE && drv_len <= max_len) { + tusb_desc_interface_t const *data_itf_desc = (tusb_desc_interface_t const *) p_desc; + TU_ASSERT(data_itf_desc->bInterfaceClass == TUSB_CLASS_CDC_DATA, 0); + + drv_len += tu_desc_len(p_desc); + p_desc = tu_desc_next(p_desc); + } + + // a TUSB_DESC_ENDPOINT (actually two) must follow, open these endpoints + TU_ASSERT(tu_desc_type(p_desc) == TUSB_DESC_ENDPOINT, 0); + TU_ASSERT(usbd_open_edpt_pair(rhport, p_desc, 2, TUSB_XFER_BULK, &ncm_interface.ep_out, &ncm_interface.ep_in)); + ncm_interface.ep_size = tu_edpt_packet_size((tusb_desc_endpoint_t const *) p_desc); + drv_len += 2 * sizeof(tusb_desc_endpoint_t); + + return drv_len; +} // netd_open + +/** + * Handle TinyUSB requests to process transfer events. + */ +bool netd_xfer_cb(uint8_t rhport, uint8_t ep_addr, xfer_result_t result, uint32_t xferred_bytes) { + (void) result; + + if (ep_addr == ncm_interface.ep_out) { + // new NTB received + // - make the NTB valid + // - if ready transfer datagrams to the glue logic for further processing + // - if there is a free receive buffer, initiate reception + if (!recv_validate_datagram(ncm_interface.recv_tinyusb_ntb, xferred_bytes)) { + // verification failed: ignore NTB and return it to free + TU_LOG_DRV("Invalid datatagram. Ignoring NTB\n"); + recv_put_ntb_into_free_list(ncm_interface.recv_tinyusb_ntb); + } else { + // packet ok -> put it into ready list + recv_put_ntb_into_ready_list(ncm_interface.recv_tinyusb_ntb); + } + ncm_interface.recv_tinyusb_ntb = NULL; + tud_network_recv_renew_r(rhport); + } else if (ep_addr == ncm_interface.ep_in) { + // transmission of an NTB finished + // - free the transmitted NTB buffer + // - insert ZLPs when necessary + // - if there is another transmit NTB waiting, try to start transmission + xmit_put_ntb_into_free_list(ncm_interface.xmit_tinyusb_ntb); + ncm_interface.xmit_tinyusb_ntb = NULL; + if (!xmit_insert_required_zlp(rhport, xferred_bytes)) { + xmit_start_if_possible(rhport); + } + } else if (ep_addr == ncm_interface.ep_notif) { + // next transfer on notification channel + notification_xmit(rhport, true); + } + + return true; +} // netd_xfer_cb + +/** + * Respond to TinyUSB control requests. + * At startup transmission of notification packets are done here. + */ +bool netd_control_xfer_cb(uint8_t rhport, uint8_t stage, tusb_control_request_t const *request) { + + switch (request->bmRequestType_bit.type) { + case TUSB_REQ_TYPE_STANDARD: + if (stage != CONTROL_STAGE_SETUP) { + return true; + } + + switch (request->bRequest) { + case TUSB_REQ_GET_INTERFACE: { + TU_VERIFY(ncm_interface.itf_num + 1 == request->wIndex, false); + + tud_control_xfer(rhport, request, &ncm_interface.itf_data_alt, 1); + } break; + + case TUSB_REQ_SET_INTERFACE: { + TU_VERIFY(ncm_interface.itf_num + 1 == request->wIndex && request->wValue < 2, false); + + ncm_interface.itf_data_alt = (uint8_t) request->wValue; + + if (ncm_interface.itf_data_alt == 1) { + tud_network_recv_renew_r(rhport); + notification_xmit(rhport, false); + } else { + // Reset notification state to send link state update when interface is re-activated + ncm_interface.notification_xmit_state = NOTIFICATION_SPEED; + } + tud_control_status(rhport, request); + } break; + + // unsupported request + default: + return false; + } + break; + + case TUSB_REQ_TYPE_CLASS: + TU_VERIFY(ncm_interface.itf_num == request->wIndex, false); + switch (request->bRequest) { + case NCM_GET_NTB_PARAMETERS: { + if (stage != CONTROL_STAGE_SETUP) { + return true; + } + // transfer NTB parameters to host. + tud_control_xfer(rhport, request, (void *) (uintptr_t) &ntb_parameters, sizeof(ntb_parameters)); + } break; + + case NCM_SET_ETHERNET_PACKET_FILTER: { + if (stage != CONTROL_STAGE_SETUP) { + return true; + } + + // Some hosts issue this request even if ETH_FILTER is not advertised, + // see https://bugzilla.kernel.org/show_bug.cgi?id=217290 + + tud_network_set_packet_filter_cb(request->wValue); + tud_control_xfer(rhport, request, NULL, 0); + } break; + + case NCM_GET_NTB_INPUT_SIZE: { + if (stage != CONTROL_STAGE_SETUP) { + return true; + } + + TU_VERIFY(request->wLength >=4, false); + + uint8_t resp_len = (request->wLength >= 8 && (ncm_interface.bm_capabilities & NCM_NETWORK_CAPS_NTB_INPUT_SIZE)) ? 8 : 4; + + ncm_ntb_input_size_t ntb_input_size = { + .dwNtbInMaxSize = ncm_interface.xmit_max_ntb_size, + .wNtbInMaxDatagrams = ncm_interface.xmit_max_datagrams + }; + tud_control_xfer(rhport, request, &ntb_input_size, resp_len); + } break; + + case NCM_SET_NTB_INPUT_SIZE: { + if (stage == CONTROL_STAGE_SETUP) { + /* wLength == 8 -> the NTB Input Size Structure (if NCM_NETWORK_CAPS_NTB_INPUT_SIZE is set) + wLength == 4 -> dwNtbInMaxSize field of the NTB Input Size Structure. */ + TU_VERIFY(request->wLength == 4 || request->wLength == 8, false); + if (request->wLength == 8) { + TU_VERIFY(ncm_interface.bm_capabilities & NCM_NETWORK_CAPS_NTB_INPUT_SIZE, false); + } + + tu_memclr(&ncm_interface.ntb_input_size, sizeof(ncm_interface.ntb_input_size)); + tud_control_xfer(rhport, request, &ncm_interface.ntb_input_size, request->wLength); + } else if (stage == CONTROL_STAGE_DATA) { + /* CDC-NCM 1.0 Table 6-4, up to NTB16 size */ + const uint32_t requested_size = ncm_interface.ntb_input_size.dwNtbInMaxSize; + if (requested_size < 2048u || requested_size > 65535u) { + return false; + } + ncm_interface.xmit_max_ntb_size = tu_min16(requested_size, CFG_TUD_NCM_IN_NTB_MAX_SIZE); + + if (ncm_interface.ntb_input_size.wNtbInMaxDatagrams == 0 || ncm_interface.ntb_input_size.wNtbInMaxDatagrams > CFG_TUD_NCM_IN_MAX_DATAGRAMS_PER_NTB) { + ncm_interface.xmit_max_datagrams = CFG_TUD_NCM_IN_MAX_DATAGRAMS_PER_NTB; + } else { + ncm_interface.xmit_max_datagrams = ncm_interface.ntb_input_size.wNtbInMaxDatagrams; + } + } + } break; + + // unsupported request + default: + return false; + } + break; + + // unsupported request + default: + return false; + } + + return true; +} // netd_control_xfer_cb + +#endif // ( CFG_TUD_ENABLED && CFG_TUD_NCM ) diff --git a/libraries/Adafruit_TinyUSB_Arduino/src/class/net/net_device.h b/libraries/Adafruit_TinyUSB_Arduino/src/class/net/net_device.h new file mode 100644 index 0000000..332df09 --- /dev/null +++ b/libraries/Adafruit_TinyUSB_Arduino/src/class/net/net_device.h @@ -0,0 +1,127 @@ +/* + * The MIT License (MIT) + * + * Copyright (c) 2020 Peter Lawrence + * 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. + * + * This file is part of the TinyUSB stack. + */ + +#ifndef TUSB_NET_DEVICE_H_ +#define TUSB_NET_DEVICE_H_ + +#include +#include "class/cdc/cdc.h" + +#if CFG_TUD_ECM_RNDIS && CFG_TUD_NCM +#error "Cannot enable both ECM_RNDIS and NCM network drivers" +#endif + +/* Maximum Transmission Unit (in bytes) of the network, including Ethernet header */ +#ifndef CFG_TUD_NET_MTU +#define CFG_TUD_NET_MTU 1514 +#endif + + +// Table 4.3 Data Class Interface Protocol Codes +typedef enum +{ + NCM_DATA_PROTOCOL_NETWORK_TRANSFER_BLOCK = 0x01 +} ncm_data_interface_protocol_code_t; + +// Table 5.2 bmNetworkCapabilities bits +typedef enum { + NCM_NETWORK_CAPS_NONE = 0x00, + NCM_NETWORK_CAPS_ETH_FILTER = (1 << 0), + NCM_NETWORK_CAPS_NET_ADDRESS = (1 << 1), + NCM_NETWORK_CAPS_ENCAP_COMMAND = (1 << 2), + NCM_NETWORK_CAPS_MAX_DATAGRAM_SIZE = (1 << 3), + NCM_NETWORK_CAPS_CRC_MODE = (1 << 4), + NCM_NETWORK_CAPS_NTB_INPUT_SIZE = (1 << 5) +} ncm_network_capabilities_t; + +#ifdef __cplusplus + extern "C" { +#endif + +//--------------------------------------------------------------------+ +// Implemented by Application +//--------------------------------------------------------------------+ +#if CFG_TUD_ECM_RNDIS +extern void rndis_class_set_handler(uint8_t *data, int size); +#endif + +//--------------------------------------------------------------------+ +// Application API +//--------------------------------------------------------------------+ + +// indicate to network driver that client has finished with the packet provided to network_recv_cb() +void tud_network_recv_renew(void); + +// poll network driver for its ability to accept another packet to transmit +bool tud_network_can_xmit(uint16_t size); + +// if network_can_xmit() returns true, network_xmit() can be called once +void tud_network_xmit(void *ref, uint16_t arg); + +//--------------------------------------------------------------------+ +// Application Callbacks (WEAK is optional) +//--------------------------------------------------------------------+ + +// client must provide this: return false if the packet buffer was not accepted +bool tud_network_recv_cb(const uint8_t *src, uint16_t size); + +// client must provide this: copy from network stack packet pointer to dst +uint16_t tud_network_xmit_cb(uint8_t *dst, void *ref, uint16_t arg); + +//------------- ECM/RNDIS -------------// + +// client must provide this: initialize any network state back to the beginning +void tud_network_init_cb(void); + +// client must provide this: 48-bit MAC address +// TODO removed later since it is not part of tinyusb stack +extern uint8_t tud_network_mac_address[6]; + +//------------- NCM -------------// + +// Optional callback: informs the application about host requested packet filter bits +void tud_network_set_packet_filter_cb(uint16_t packet_filter); + +// Set the network link state (up/down) and notify the host +void tud_network_link_state(uint8_t rhport, bool is_up); + +//--------------------------------------------------------------------+ +// INTERNAL USBD-CLASS DRIVER API +//--------------------------------------------------------------------+ +void netd_init (void); +bool netd_deinit (void); +void netd_reset (uint8_t rhport); +uint16_t netd_open (uint8_t rhport, tusb_desc_interface_t const * itf_desc, uint16_t max_len); +bool netd_control_xfer_cb (uint8_t rhport, uint8_t stage, tusb_control_request_t const * request); +bool netd_xfer_cb (uint8_t rhport, uint8_t ep_addr, xfer_result_t result, uint32_t xferred_bytes); +void netd_report (uint8_t *buf, uint16_t len); + +#ifdef __cplusplus + } +#endif + +#endif /* TUSB_NET_DEVICE_H_ */ diff --git a/libraries/Adafruit_TinyUSB_Arduino/src/class/printer/printer.h b/libraries/Adafruit_TinyUSB_Arduino/src/class/printer/printer.h new file mode 100644 index 0000000..09d1a89 --- /dev/null +++ b/libraries/Adafruit_TinyUSB_Arduino/src/class/printer/printer.h @@ -0,0 +1,62 @@ +/* + * The MIT License (MIT) + * + * Copyright (c) 2026 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. + * + * This file is part of the TinyUSB stack. + */ + +#ifndef TUSB_PRINTER_H_ +#define TUSB_PRINTER_H_ + +#include "common/tusb_common.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/// Printer Class Specific Control Request +typedef enum { + TUSB_PRINTER_REQUEST_GET_DEVICE_ID = 0x00, ///< Get device ID + TUSB_PRINTER_REQUEST_GET_PORT_STATUS = 0x01, ///< Get port status + TUSB_PRINTER_REQUEST_SOFT_RESET = 0x02, ///< Soft reset +} tusb_printer_request_type_t; + +/// Printer Port Status (returned by GET_PORT_STATUS request) +/// USB Printer Class spec 1.1, Section 4.2 +typedef union TU_ATTR_PACKED { + uint8_t status; + struct TU_ATTR_PACKED { + uint8_t reserved0 : 3; ///< Reserved (bits 0-2) + uint8_t not_error : 1; ///< 1 = no error, 0 = error + uint8_t selected : 1; ///< 1 = selected (online), 0 = not selected + uint8_t paper_empty : 1; ///< 1 = paper empty, 0 = paper not empty + uint8_t reserved6 : 2; ///< Reserved (bits 6-7) + } status_bm; +} tusb_printer_port_status_t; + +TU_VERIFY_STATIC(sizeof(tusb_printer_port_status_t) == 1, "size is not correct"); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/libraries/Adafruit_TinyUSB_Arduino/src/class/printer/printer_device.c b/libraries/Adafruit_TinyUSB_Arduino/src/class/printer/printer_device.c new file mode 100644 index 0000000..158455f --- /dev/null +++ b/libraries/Adafruit_TinyUSB_Arduino/src/class/printer/printer_device.c @@ -0,0 +1,328 @@ +/* + * The MIT License (MIT) + * + * Copyright (c) 2026 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. + * + * This file is part of the TinyUSB stack. + */ + +#include "tusb_option.h" + +#if (CFG_TUD_ENABLED && CFG_TUD_PRINTER) + +#include "device/usbd.h" +#include "device/usbd_pvt.h" + +#include "printer_device.h" + +//--------------------------------------------------------------------+ +// MACRO CONSTANT TYPEDEF +//--------------------------------------------------------------------+ + +typedef struct { + uint8_t itf_num; + + /*------------- From this point, data is not cleared by bus reset -------------*/ + tu_edpt_stream_t rx_stream; + tu_edpt_stream_t tx_stream; + + uint8_t rx_ff_buf[CFG_TUD_PRINTER_RX_BUFSIZE]; + uint8_t tx_ff_buf[CFG_TUD_PRINTER_TX_BUFSIZE]; +} printer_interface_t; + +#define ITF_MEM_RESET_SIZE offsetof(printer_interface_t, rx_stream) + +#if CFG_TUD_EDPT_DEDICATED_HWFIFO == 0 +typedef struct { + TUD_EPBUF_DEF(epout, CFG_TUD_PRINTER_RX_EPSIZE); + TUD_EPBUF_DEF(epin, CFG_TUD_PRINTER_TX_EPSIZE); +} printer_epbuf_t; + +CFG_TUD_MEM_SECTION static printer_epbuf_t _printer_epbuf[CFG_TUD_PRINTER]; +#endif + +static printer_interface_t _printer_itf[CFG_TUD_PRINTER]; + +//--------------------------------------------------------------------+ +// INTERNAL HELPERS +//--------------------------------------------------------------------+ + +TU_ATTR_ALWAYS_INLINE static inline uint8_t _find_itf(uint8_t ep_addr) { + for (uint8_t i = 0; i < CFG_TUD_PRINTER; i++) { + const printer_interface_t *p = &_printer_itf[i]; + if (ep_addr == p->rx_stream.ep_addr || ep_addr == p->tx_stream.ep_addr) { + return i; + } + } + return TUSB_INDEX_INVALID_8; +} + +//--------------------------------------------------------------------+ +// Weak stubs: invoked if no strong implementation is available +//--------------------------------------------------------------------+ +TU_ATTR_WEAK void tud_printer_rx_cb(uint8_t itf) { + (void)itf; +} + +TU_ATTR_WEAK void tud_printer_tx_complete_cb(uint8_t itf) { + (void)itf; +} + +TU_ATTR_WEAK void tud_printer_request_complete_cb(uint8_t itf, tusb_control_request_t const *request) { + (void)itf; + (void)request; +} + +TU_ATTR_WEAK uint8_t const *tud_printer_get_device_id_cb(uint8_t itf) { + (void)itf; + return NULL; +} + +TU_ATTR_WEAK uint8_t tud_printer_get_port_status_cb(uint8_t itf) { + (void)itf; + return 0x18; // not error, selected, paper not empty +} + +TU_ATTR_WEAK void tud_printer_soft_reset_cb(uint8_t itf) { + (void)itf; +} + +//--------------------------------------------------------------------+ +// READ API +//--------------------------------------------------------------------+ +uint32_t tud_printer_n_read_available(uint8_t itf) { + TU_VERIFY(itf < CFG_TUD_PRINTER, 0); + return tu_edpt_stream_read_available(&_printer_itf[itf].rx_stream); +} + +uint32_t tud_printer_n_read(uint8_t itf, void *buffer, uint32_t bufsize) { + TU_VERIFY(itf < CFG_TUD_PRINTER, 0); + return tu_edpt_stream_read(&_printer_itf[itf].rx_stream, buffer, bufsize); +} + +bool tud_printer_n_peek(uint8_t itf, uint8_t *chr) { + TU_VERIFY(itf < CFG_TUD_PRINTER); + return tu_edpt_stream_peek(&_printer_itf[itf].rx_stream, chr); +} + +void tud_printer_n_read_flush(uint8_t itf) { + TU_VERIFY(itf < CFG_TUD_PRINTER, ); + printer_interface_t *p = &_printer_itf[itf]; + tu_edpt_stream_clear(&p->rx_stream); + tu_edpt_stream_read_xfer(&p->rx_stream); +} + +//--------------------------------------------------------------------+ +// WRITE API +//--------------------------------------------------------------------+ +uint32_t tud_printer_n_write(uint8_t itf, const void *buffer, uint32_t bufsize) { + TU_VERIFY(itf < CFG_TUD_PRINTER, 0); + return tu_edpt_stream_write(&_printer_itf[itf].tx_stream, buffer, bufsize); +} + +uint32_t tud_printer_n_write_flush(uint8_t itf) { + TU_VERIFY(itf < CFG_TUD_PRINTER, 0); + return tu_edpt_stream_write_xfer(&_printer_itf[itf].tx_stream); +} + +uint32_t tud_printer_n_write_available(uint8_t itf) { + TU_VERIFY(itf < CFG_TUD_PRINTER, 0); + return tu_edpt_stream_write_available(&_printer_itf[itf].tx_stream); +} + +bool tud_printer_n_write_clear(uint8_t itf) { + TU_VERIFY(itf < CFG_TUD_PRINTER); + tu_edpt_stream_clear(&_printer_itf[itf].tx_stream); + return true; +} + +//--------------------------------------------------------------------+ +// USBD-CLASS API +//--------------------------------------------------------------------+ +void printerd_init(void) { + tu_memclr(_printer_itf, sizeof(_printer_itf)); + + for (uint8_t i = 0; i < CFG_TUD_PRINTER; i++) { + printer_interface_t *p = &_printer_itf[i]; + + #if CFG_TUD_EDPT_DEDICATED_HWFIFO + uint8_t *epout_buf = NULL; + uint8_t *epin_buf = NULL; + #else + uint8_t *epout_buf = _printer_epbuf[i].epout; + uint8_t *epin_buf = _printer_epbuf[i].epin; + #endif + + tu_edpt_stream_init(&p->rx_stream, false, false, false, + p->rx_ff_buf, CFG_TUD_PRINTER_RX_BUFSIZE, epout_buf); + + tu_edpt_stream_init(&p->tx_stream, false, true, true, + p->tx_ff_buf, CFG_TUD_PRINTER_TX_BUFSIZE, epin_buf); + } +} + +bool printerd_deinit(void) { + for (uint8_t i = 0; i < CFG_TUD_PRINTER; i++) { + printer_interface_t *p = &_printer_itf[i]; + tu_edpt_stream_deinit(&p->rx_stream); + tu_edpt_stream_deinit(&p->tx_stream); + } + return true; +} + +void printerd_reset(uint8_t rhport) { + (void)rhport; + + for (uint8_t i = 0; i < CFG_TUD_PRINTER; i++) { + printer_interface_t *p = &_printer_itf[i]; + tu_memclr(p, ITF_MEM_RESET_SIZE); + tu_edpt_stream_close(&p->rx_stream); + tu_edpt_stream_close(&p->tx_stream); + } +} + +uint16_t printerd_open(uint8_t rhport, const tusb_desc_interface_t *itf_desc, uint16_t max_len) { + TU_VERIFY(TUSB_CLASS_PRINTER == itf_desc->bInterfaceClass, 0); + + // Find available interface slot + uint8_t const printer_id = _find_itf(0); + TU_ASSERT(printer_id < CFG_TUD_PRINTER, 0); + printer_interface_t *p = &_printer_itf[printer_id]; + + p->itf_num = itf_desc->bInterfaceNumber; + + //------------- Endpoints -------------// + const uint8_t *p_desc = (const uint8_t *)itf_desc; + const uint8_t *desc_end = p_desc + max_len; + uint16_t drv_len = sizeof(tusb_desc_interface_t); + + p_desc = tu_desc_next(itf_desc); + for (uint8_t e = 0; e < itf_desc->bNumEndpoints; e++) { + TU_VERIFY(tu_desc_in_bounds(p_desc, desc_end), 0); + const tusb_desc_endpoint_t *desc_ep = (const tusb_desc_endpoint_t *)p_desc; + TU_ASSERT(TUSB_DESC_ENDPOINT == desc_ep->bDescriptorType && TUSB_XFER_BULK == desc_ep->bmAttributes.xfer, 0); + + TU_ASSERT(usbd_edpt_open(rhport, desc_ep), 0); + + if (tu_edpt_dir(desc_ep->bEndpointAddress) == TUSB_DIR_IN) { + tu_edpt_stream_t *stream_tx = &p->tx_stream; + tu_edpt_stream_open(stream_tx, rhport, desc_ep, CFG_TUD_PRINTER_TX_EPSIZE); + tu_edpt_stream_clear(stream_tx); + } else { + tu_edpt_stream_t *stream_rx = &p->rx_stream; + tu_edpt_stream_open(stream_rx, rhport, desc_ep, tu_edpt_packet_size(desc_ep)); + tu_edpt_stream_clear(stream_rx); + TU_ASSERT(tu_edpt_stream_read_xfer(stream_rx) > 0, 0); + } + + drv_len += sizeof(tusb_desc_endpoint_t); + p_desc = tu_desc_next(p_desc); + } + + return drv_len; +} + +bool printerd_control_xfer_cb(uint8_t rhport, uint8_t stage, const tusb_control_request_t *request) { + TU_VERIFY(request->bmRequestType_bit.recipient == TUSB_REQ_RCPT_INTERFACE && + request->bmRequestType_bit.type == TUSB_REQ_TYPE_CLASS); + + // GET_DEVICE_ID: wIndex = (interface_number << 8) | alt_setting + // GET_PORT_STATUS / SOFT_RESET: wIndex = interface_number + uint8_t itf_num; + if (TUSB_PRINTER_REQUEST_GET_DEVICE_ID == request->bRequest) { + itf_num = tu_u16_high(request->wIndex); + } else { + itf_num = tu_u16_low(request->wIndex); + } + + // Find the printer instance index from the USB interface number + uint8_t itf = TUSB_INDEX_INVALID_8; + for (uint8_t i = 0; i < CFG_TUD_PRINTER; i++) { + if (_printer_itf[i].itf_num == itf_num) { + itf = i; + break; + } + } + TU_VERIFY(itf < CFG_TUD_PRINTER); + + // https://www.usb.org/sites/default/files/usbprint11a021811.pdf + if (stage == CONTROL_STAGE_SETUP) { + switch (request->bRequest) { + case TUSB_PRINTER_REQUEST_GET_DEVICE_ID: { + const uint8_t *device_id = tud_printer_get_device_id_cb(itf); + TU_VERIFY(device_id); + const uint16_t total_len = (uint16_t)((device_id[0] << 8) | device_id[1]); + return tud_control_xfer(rhport, request, (void *)(uintptr_t)device_id, total_len); + } + + case TUSB_PRINTER_REQUEST_GET_PORT_STATUS: { + static uint8_t port_status; + port_status = tud_printer_get_port_status_cb(itf); + return tud_control_xfer(rhport, request, &port_status, sizeof(port_status)); + } + + case TUSB_PRINTER_REQUEST_SOFT_RESET: + tud_printer_soft_reset_cb(itf); + tud_control_status(rhport, request); + return true; + + default: + return false; + } + } else if (stage == CONTROL_STAGE_ACK) { + tud_printer_request_complete_cb(itf, request); + } + + return true; +} + +bool printerd_xfer_cb(uint8_t rhport, uint8_t ep_addr, xfer_result_t result, uint32_t xferred_bytes) { + (void)rhport; + (void)result; + + uint8_t const itf = _find_itf(ep_addr); + TU_ASSERT(itf < CFG_TUD_PRINTER); + printer_interface_t *p = &_printer_itf[itf]; + + // Received new data + if (ep_addr == p->rx_stream.ep_addr) { + tu_edpt_stream_read_xfer_complete(&p->rx_stream, xferred_bytes); + + if (!tu_edpt_stream_empty(&p->rx_stream)) { + tud_printer_rx_cb(itf); + } + + tu_edpt_stream_read_xfer(&p->rx_stream); + } + + // Data sent to host + if (ep_addr == p->tx_stream.ep_addr) { + tud_printer_tx_complete_cb(itf); + + if (0 == tu_edpt_stream_write_xfer(&p->tx_stream)) { + tu_edpt_stream_write_zlp_if_needed(&p->tx_stream, xferred_bytes); + } + } + + return true; +} + +#endif diff --git a/libraries/Adafruit_TinyUSB_Arduino/src/class/printer/printer_device.h b/libraries/Adafruit_TinyUSB_Arduino/src/class/printer/printer_device.h new file mode 100644 index 0000000..afde1f0 --- /dev/null +++ b/libraries/Adafruit_TinyUSB_Arduino/src/class/printer/printer_device.h @@ -0,0 +1,151 @@ +/* + * The MIT License (MIT) + * + * Copyright (c) 2026 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. + * + * This file is part of the TinyUSB stack. + */ + +#ifndef TUSB_PRINTER_DEVICE_H_ +#define TUSB_PRINTER_DEVICE_H_ + +#ifdef __cplusplus +extern "C" { +#endif + +#include "printer.h" + +//--------------------------------------------------------------------+ +// Configuration +//--------------------------------------------------------------------+ +#ifndef CFG_TUD_PRINTER_RX_EPSIZE + #define CFG_TUD_PRINTER_RX_EPSIZE TUD_EPSIZE_BULK_MAX +#endif + +#ifndef CFG_TUD_PRINTER_TX_EPSIZE + #define CFG_TUD_PRINTER_TX_EPSIZE TUD_EPSIZE_BULK_MAX +#endif + +//--------------------------------------------------------------------+ +// Application API (Multiple Ports) i.e. CFG_TUD_PRINTER > 1 +//--------------------------------------------------------------------+ + +// Get the number of bytes available for reading +uint32_t tud_printer_n_read_available(uint8_t itf); + +// Read received bytes +uint32_t tud_printer_n_read(uint8_t itf, void *buffer, uint32_t bufsize); + +// Get the number of bytes available for writing +uint32_t tud_printer_n_write_available(uint8_t itf); + +// Clear the received FIFO +void tud_printer_n_read_flush(uint8_t itf); + +// Get a byte from FIFO without removing it +bool tud_printer_n_peek(uint8_t itf, uint8_t *ui8); + +// Write data to host +uint32_t tud_printer_n_write(uint8_t itf, const void *buffer, uint32_t bufsize); + +// Force sending data in the TX FIFO +uint32_t tud_printer_n_write_flush(uint8_t itf); + +// Clear the transmit FIFO +bool tud_printer_n_write_clear(uint8_t itf); + +//--------------------------------------------------------------------+ +// Application API (Single Port) +//--------------------------------------------------------------------+ + +TU_ATTR_ALWAYS_INLINE static inline uint32_t tud_printer_read_available(void) { + return tud_printer_n_read_available(0); +} + +TU_ATTR_ALWAYS_INLINE static inline uint32_t tud_printer_write_available(void) { + return tud_printer_n_write_available(0); +} + +TU_ATTR_ALWAYS_INLINE static inline uint32_t tud_printer_read(void *buffer, uint32_t bufsize) { + return tud_printer_n_read(0, buffer, bufsize); +} + +TU_ATTR_ALWAYS_INLINE static inline void tud_printer_read_flush(void) { + tud_printer_n_read_flush(0); +} + +TU_ATTR_ALWAYS_INLINE static inline bool tud_printer_peek(uint8_t *ui8) { + return tud_printer_n_peek(0, ui8); +} + +TU_ATTR_ALWAYS_INLINE static inline uint32_t tud_printer_write(const void *buffer, uint32_t bufsize) { + return tud_printer_n_write(0, buffer, bufsize); +} + +TU_ATTR_ALWAYS_INLINE static inline uint32_t tud_printer_write_flush(void) { + return tud_printer_n_write_flush(0); +} + +TU_ATTR_ALWAYS_INLINE static inline bool tud_printer_write_clear(void) { + return tud_printer_n_write_clear(0); +} + +//--------------------------------------------------------------------+ +// Application Callback API (weak is optional) +//--------------------------------------------------------------------+ + +// Invoked when received new data +void tud_printer_rx_cb(uint8_t itf); + +// Invoked when last write transfer is completed +void tud_printer_tx_complete_cb(uint8_t itf); + +// Invoked when host requests device ID string (IEEE 1284). +// Application returns pointer to device ID buffer (must remain valid until transfer completes). +// First 2 bytes of returned buffer must contain big-endian length (including the 2 length bytes). +const uint8_t *tud_printer_get_device_id_cb(uint8_t itf); + +// Invoked when host requests port status. +uint8_t tud_printer_get_port_status_cb(uint8_t itf); + +// Invoked when host requests soft reset. +void tud_printer_soft_reset_cb(uint8_t itf); + +// Invoked when a control request is completed (GET_DEVICE_ID, GET_PORT_STATUS, etc.) +void tud_printer_request_complete_cb(uint8_t itf, tusb_control_request_t const *request); + + +//--------------------------------------------------------------------+ +// Internal Class Driver API +//--------------------------------------------------------------------+ +void printerd_init(void); +bool printerd_deinit(void); +void printerd_reset(uint8_t rhport); +uint16_t printerd_open(uint8_t rhport, const tusb_desc_interface_t *itf_desc, uint16_t max_len); +bool printerd_control_xfer_cb(uint8_t rhport, uint8_t stage, const tusb_control_request_t *request); +bool printerd_xfer_cb(uint8_t rhport, uint8_t ep_addr, xfer_result_t event, uint32_t xferred_bytes); + + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/libraries/Adafruit_TinyUSB_Arduino/src/class/usbtmc/usbtmc.h b/libraries/Adafruit_TinyUSB_Arduino/src/class/usbtmc/usbtmc.h new file mode 100644 index 0000000..3bf5e1a --- /dev/null +++ b/libraries/Adafruit_TinyUSB_Arduino/src/class/usbtmc/usbtmc.h @@ -0,0 +1,343 @@ + +/* + * The MIT License (MIT) + * + * Copyright (c) 2019 N Conrad + * + * 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_USBTMC_H__ +#define TUSB_USBTMC_H__ + +#include "common/tusb_common.h" + + +/* Implements USBTMC Revision 1.0, April 14, 2003 + + String descriptors must have a "LANGID=0x409"/US English string. + Characters must be 0x20 (' ') to 0x7E ('~') ASCII, + But MUST not contain: "/:?\* + Also must not have leading or trailing space (' ') + Device descriptor must state USB version 0x0200 or greater + + If USB488DeviceCapabilites.D2 = 1 (SR1), then there must be a INT endpoint. +*/ + +#define USBTMC_VERSION 0x0100 +#define USBTMC_488_VERSION 0x0100 + +typedef enum { + USBTMC_MSGID_DEV_DEP_MSG_OUT = 1u, + USBTMC_MSGID_DEV_DEP_MSG_IN = 2u, + USBTMC_MSGID_VENDOR_SPECIFIC_MSG_OUT = 126u, + USBTMC_MSGID_VENDOR_SPECIFIC_IN = 127u, + USBTMC_MSGID_USB488_TRIGGER = 128u, +} usbtmc_msgid_enum; + +/// \brief Message header (For BULK OUT and BULK IN); 4 bytes +typedef struct TU_ATTR_PACKED +{ + uint8_t MsgID ; ///< Message type ID (usbtmc_msgid_enum) + uint8_t bTag ; ///< Transfer ID 1<=bTag<=255 + uint8_t bTagInverse ; ///< Complement of the tag + uint8_t _reserved ; ///< Must be 0x00 +} usbtmc_msg_header_t; + +typedef struct TU_ATTR_PACKED +{ + usbtmc_msg_header_t header; + uint8_t data[8]; +} usbtmc_msg_generic_t; + +/* Uses on the bulk-out endpoint: */ +// Next 8 bytes are message-specific +typedef struct TU_ATTR_PACKED { + usbtmc_msg_header_t header ; ///< Header + uint32_t TransferSize ; ///< Transfer size; LSB first + struct TU_ATTR_PACKED + { + unsigned int EOM : 1 ; ///< EOM set on last byte + } bmTransferAttributes; + uint8_t _reserved[3]; +} usbtmc_msg_request_dev_dep_out; + +TU_VERIFY_STATIC(sizeof(usbtmc_msg_request_dev_dep_out) == 12u, "struct wrong length"); + +// Next 8 bytes are message-specific +typedef struct TU_ATTR_PACKED +{ + usbtmc_msg_header_t header ; ///< Header + uint32_t TransferSize ; ///< Transfer size; LSB first + struct TU_ATTR_PACKED + { + unsigned int TermCharEnabled : 1 ; ///< "The Bulk-IN transfer must terminate on the specified TermChar."; CAPABILITIES must list TermChar + } bmTransferAttributes; + uint8_t TermChar; + uint8_t _reserved[2]; +} usbtmc_msg_request_dev_dep_in; + +TU_VERIFY_STATIC(sizeof(usbtmc_msg_request_dev_dep_in) == 12u, "struct wrong length"); + +/* Bulk-in headers */ + +typedef struct TU_ATTR_PACKED +{ + usbtmc_msg_header_t header; + uint32_t TransferSize; + struct TU_ATTR_PACKED + { + uint8_t EOM: 1; ///< Last byte of transfer is the end of the message + uint8_t UsingTermChar: 1; ///< Support TermChar && Request.TermCharEnabled && last char in transfer is TermChar + } bmTransferAttributes; + uint8_t _reserved[3]; +} usbtmc_msg_dev_dep_msg_in_header_t; + +TU_VERIFY_STATIC(sizeof(usbtmc_msg_dev_dep_msg_in_header_t) == 12u, "struct wrong length"); + +/* Unsupported vendor things.... Are these ever used?*/ + +typedef struct TU_ATTR_PACKED +{ + usbtmc_msg_header_t header ; ///< Header + uint32_t TransferSize ; ///< Transfer size; LSB first + uint8_t _reserved[4]; +} usbtmc_msg_request_vendor_specific_out; + + +TU_VERIFY_STATIC(sizeof(usbtmc_msg_request_vendor_specific_out) == 12u, "struct wrong length"); + +typedef struct TU_ATTR_PACKED +{ + usbtmc_msg_header_t header ; ///< Header + uint32_t TransferSize ; ///< Transfer size; LSB first + uint8_t _reserved[4]; +} usbtmc_msg_request_vendor_specific_in; + +TU_VERIFY_STATIC(sizeof(usbtmc_msg_request_vendor_specific_in) == 12u, "struct wrong length"); + +// Control request type should use tusb_control_request_t + +/* +typedef struct TU_ATTR_PACKED { + struct { + unsigned int Recipient : 5 ; ///< EOM set on last byte + unsigned int Type : 2 ; ///< EOM set on last byte + unsigned int DirectionToHost : 1 ; ///< 0 is OUT, 1 is IN + } bmRequestType; + uint8_t bRequest ; ///< If bmRequestType.Type = Class, see usmtmc_request_type_enum + uint16_t wValue ; + uint16_t wIndex ; + uint16_t wLength ; // Number of bytes in data stage +} usbtmc_class_specific_control_req; + +*/ +// bulk-in protocol errors +enum { + USBTMC_BULK_IN_ERR_INCOMPLETE_HEADER = 1u, + USBTMC_BULK_IN_ERR_UNSUPPORTED = 2u, + USBTMC_BULK_IN_ERR_BAD_PARAMETER = 3u, + USBTMC_BULK_IN_ERR_DATA_TOO_SHORT = 4u, + USBTMC_BULK_IN_ERR_DATA_TOO_LONG = 5u, +}; +// built-in halt errors +enum { + USBTMC_BULK_IN_ERR = 1u, ///< receives a USBTMC command message that expects a response while a + /// Bulk-IN transfer is in progress +}; + +typedef enum { + USBTMC_bREQUEST_INITIATE_ABORT_BULK_OUT = 1u, + USBTMC_bREQUEST_CHECK_ABORT_BULK_OUT_STATUS = 2u, + USBTMC_bREQUEST_INITIATE_ABORT_BULK_IN = 3u, + USBTMC_bREQUEST_CHECK_ABORT_BULK_IN_STATUS = 4u, + USBTMC_bREQUEST_INITIATE_CLEAR = 5u, + USBTMC_bREQUEST_CHECK_CLEAR_STATUS = 6u, + USBTMC_bREQUEST_GET_CAPABILITIES = 7u, + + USBTMC_bREQUEST_INDICATOR_PULSE = 64u, // Optional + + /****** USBTMC 488 *************/ + USB488_bREQUEST_READ_STATUS_BYTE = 128u, + USB488_bREQUEST_REN_CONTROL = 160u, + USB488_bREQUEST_GO_TO_LOCAL = 161u, + USB488_bREQUEST_LOCAL_LOCKOUT = 162u, + +} usmtmc_request_type_enum; + +typedef enum { + // The last and first valid bNotify1 for use by the USBTMC class specification. + USBTMC_bNOTIFY1_USBTMC_FIRST = 0x00, + USBTMC_bNOTIFY1_USBTMC_LAST = 0x3F, + + // The last and first valid bNotify1 for use by vendors. + USBTMC_bNOTIFY1_VENDOR_SPECIFIC_FIRST = 0x40, + USBTMC_bNOTIFY1_VENDOR_SPECIFIC_LAST = 0x7F, + + // The last and first valid bNotify1 for use by USBTMC subclass specifications. + USBTMC_bNOTIFY1_SUBCLASS_FIRST = 0x80, + USBTMC_bNOTIFY1_SUBCLASS_LAST = 0xFF, + + // From the USB488 Subclass Specification, Section 3.4. + USB488_bNOTIFY1_SRQ = 0x81, +} usbtmc_int_in_payload_format; + +typedef enum { + USBTMC_STATUS_SUCCESS = 0x01, + USBTMC_STATUS_PENDING = 0x02, + USBTMC_STATUS_FAILED = 0x80, + USBTMC_STATUS_TRANSFER_NOT_IN_PROGRESS = 0x81, + USBTMC_STATUS_SPLIT_NOT_IN_PROGRESS = 0x82, + USBTMC_STATUS_SPLIT_IN_PROGRESS = 0x83, + + /****** USBTMC 488 *************/ + USB488_STATUS_INTERRUPT_IN_BUSY = 0x20 +} usbtmc_status_enum; + +/************************************************************ + * Control Responses + */ + +typedef struct TU_ATTR_PACKED { + uint8_t USBTMC_status; ///< usbtmc_status_enum + uint8_t _reserved; + uint16_t bcdUSBTMC; ///< USBTMC_VERSION + + struct TU_ATTR_PACKED + { + unsigned int listenOnly :1; + unsigned int talkOnly :1; + unsigned int supportsIndicatorPulse :1; + } bmIntfcCapabilities; + struct TU_ATTR_PACKED + { + unsigned int canEndBulkInOnTermChar :1; + } bmDevCapabilities; + uint8_t _reserved2[6]; + uint8_t _reserved3[12]; +} usbtmc_response_capabilities_t; + +TU_VERIFY_STATIC(sizeof(usbtmc_response_capabilities_t) == 0x18, "struct wrong length"); + +typedef struct TU_ATTR_PACKED +{ + uint8_t USBTMC_status; + struct TU_ATTR_PACKED + { + unsigned int BulkInFifoBytes :1; + } bmClear; +} usbtmc_get_clear_status_rsp_t; + +TU_VERIFY_STATIC(sizeof(usbtmc_get_clear_status_rsp_t) == 2u, "struct wrong length"); + +// Used for both abort bulk IN and bulk OUT +typedef struct TU_ATTR_PACKED +{ + uint8_t USBTMC_status; + uint8_t bTag; +} usbtmc_initiate_abort_rsp_t; + +TU_VERIFY_STATIC(sizeof(usbtmc_get_clear_status_rsp_t) == 2u, "struct wrong length"); + +// Used for both check_abort_bulk_in_status and check_abort_bulk_out_status +typedef struct TU_ATTR_PACKED +{ + uint8_t USBTMC_status; + struct TU_ATTR_PACKED + { + unsigned int BulkInFifoBytes : 1; ///< Has queued data or a short packet that is queued + } bmAbortBulkIn; + uint8_t _reserved[2]; ///< Must be zero + uint32_t NBYTES_RXD_TXD; +} usbtmc_check_abort_bulk_rsp_t; + +TU_VERIFY_STATIC(sizeof(usbtmc_check_abort_bulk_rsp_t) == 8u, "struct wrong length"); + +typedef struct TU_ATTR_PACKED +{ + uint8_t USBTMC_status; ///< usbtmc_status_enum + uint8_t _reserved; + uint16_t bcdUSBTMC; ///< USBTMC_VERSION + + struct TU_ATTR_PACKED + { + uint8_t listenOnly :1; + uint8_t talkOnly :1; + uint8_t supportsIndicatorPulse :1; + } bmIntfcCapabilities; + + struct TU_ATTR_PACKED + { + uint8_t canEndBulkInOnTermChar :1; + } bmDevCapabilities; + + uint8_t _reserved2[6]; + uint16_t bcdUSB488; + + struct TU_ATTR_PACKED + { + uint8_t supportsTrigger :1; + uint8_t supportsREN_GTL_LLO :1; + uint8_t is488_2 :1; + } bmIntfcCapabilities488; + + struct TU_ATTR_PACKED + { + uint8_t DT1 :1; + uint8_t RL1 :1; + uint8_t SR1 :1; + uint8_t SCPI :1; + } bmDevCapabilities488; + uint8_t _reserved3[8]; +} usbtmc_response_capabilities_488_t; + +TU_VERIFY_STATIC(sizeof(usbtmc_response_capabilities_488_t) == 0x18, "struct wrong length"); + +typedef struct TU_ATTR_PACKED +{ + uint8_t USBTMC_status; + uint8_t bTag; + uint8_t statusByte; +} usbtmc_read_stb_rsp_488_t; + +TU_VERIFY_STATIC(sizeof(usbtmc_read_stb_rsp_488_t) == 3u, "struct wrong length"); + +typedef struct TU_ATTR_PACKED +{ + uint8_t bNotify1; // Must be USB488_bNOTIFY1_SRQ + uint8_t StatusByte; +} usbtmc_srq_interrupt_488_t; + +TU_VERIFY_STATIC(sizeof(usbtmc_srq_interrupt_488_t) == 2u, "struct wrong length"); + +typedef struct TU_ATTR_PACKED +{ + struct TU_ATTR_PACKED + { + unsigned int bTag : 7; + unsigned int one : 1; + } bNotify1; + uint8_t StatusByte; +} usbtmc_read_stb_interrupt_488_t; + +TU_VERIFY_STATIC(sizeof(usbtmc_read_stb_interrupt_488_t) == 2u, "struct wrong length"); + +#endif diff --git a/libraries/Adafruit_TinyUSB_Arduino/src/class/usbtmc/usbtmc_device.c b/libraries/Adafruit_TinyUSB_Arduino/src/class/usbtmc/usbtmc_device.c new file mode 100644 index 0000000..4b0bb01 --- /dev/null +++ b/libraries/Adafruit_TinyUSB_Arduino/src/class/usbtmc/usbtmc_device.c @@ -0,0 +1,866 @@ +/* + * The MIT License (MIT) + * + * Copyright (c) 2019 Nathan Conrad + * + * 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. + */ + +/* + * This library is not fully reentrant, though it is reentrant from the view + * of either the application layer or the USB stack. Due to its locking, + * it is not safe to call its functions from interrupts. + * + * The one exception is that its functions may not be called from the application + * until the USB stack is initialized. This should not be a problem since the + * device shouldn't be sending messages until it receives a request from the + * host. + */ + + +/* + * In the case of single-CPU "no OS", this task is never preempted other than by + * interrupts, and the USBTMC code isn't called by interrupts, so all is OK. For "no OS", + * the mutex structure's main effect is to disable the USB interrupts. + * With an OS, this class driver uses the OSAL to perform locking. The code uses a single lock + * and does not call outside of this class with a lock held, so deadlocks won't happen. + */ + +//Limitations: +// "vendor-specific" commands are handled similar to normal messages, except that the MsgID is changed to "vendor-specific". +// Dealing with "termchar" must be handled by the application layer, +// though additional error checking is does in this module. +// talkOnly and listenOnly are NOT supported. They're not permitted +// in USB488, anyway. + +/* Supported: + * + * Notification pulse + * Trigger + * Read status byte (both by interrupt endpoint and control message) + * + */ + + +// TODO: +// USBTMC 3.2.2 error conditions not strictly followed +// No local lock-out, REN, or GTL. +// Clear message available status byte at the correct time? (488 4.3.1.3) +// Ability to defer status byte transmission +// Transmission of status byte in response to USB488 SRQ condition + +#include "tusb_option.h" + +#if (CFG_TUD_ENABLED && CFG_TUD_USBTMC) + +#include "device/usbd.h" +#include "device/usbd_pvt.h" + +#include "usbtmc_device.h" + +// Buffer size must be an exact multiple of the max packet size for both +// bulk (up to 64 bytes for FS, 512 bytes for HS). In addation, this driver +// imposes a minimum buffer size of 32 bytes. +#define USBTMCD_BUFFER_SIZE (TUD_OPT_HIGH_SPEED ? 512 : 64) + +// Interrupt endpoint buffer size, default to 2 bytes as USB488 specification. +#ifndef CFG_TUD_USBTMC_INT_EP_SIZE + #define CFG_TUD_USBTMC_INT_EP_SIZE 2 +#endif + +/* + * The state machine does not allow simultaneous reading and writing. This is + * consistent with USBTMC. + */ + +typedef enum { + STATE_CLOSED,// Endpoints have not yet been opened since USB reset + STATE_NAK, // Bulk-out endpoint is in NAK state. + STATE_IDLE, // Bulk-out endpoint is waiting for CMD. + STATE_RCV, // Bulk-out is receiving DEV_DEP message + STATE_TX_REQUESTED, + STATE_TX_INITIATED, + STATE_TX_SHORTED, + STATE_CLEARING, + STATE_ABORTING_BULK_IN, + STATE_ABORTING_BULK_IN_SHORTED,// aborting, and short packet has been queued for transmission + STATE_ABORTING_BULK_IN_ABORTED,// aborting, and short packet has been transmitted + STATE_ABORTING_BULK_OUT, + STATE_NUM_STATES +} usbtmcd_state_enum; + +#if (CFG_TUD_USBTMC_ENABLE_488) +typedef usbtmc_response_capabilities_488_t usbtmc_capabilities_specific_t; +#else +typedef usbtmc_response_capabilities_t usbtmc_capabilities_specific_t; +#endif + + +typedef struct +{ + volatile usbtmcd_state_enum state; + + uint8_t itf_id; + uint8_t rhport; + uint8_t ep_bulk_in; + uint8_t ep_bulk_out; + uint8_t ep_int_in; + uint32_t ep_bulk_in_wMaxPacketSize; + uint32_t ep_bulk_out_wMaxPacketSize; + uint32_t transfer_size_remaining;// also used for requested length for bulk IN. + uint32_t transfer_size_sent; // To keep track of data bytes that have been queued in FIFO (not header bytes) + + uint8_t lastBulkOutTag;// used for aborts (mostly) + uint8_t lastBulkInTag; // used for aborts (mostly) + + uint8_t const *devInBuffer;// pointer to application-layer used for transmissions + + usbtmc_capabilities_specific_t const *capabilities; +} usbtmc_interface_state_t; + +typedef struct { + // IN buffer is only used for first packet, not the remainder in order to deal with prepending header + TUD_EPBUF_DEF(epin, USBTMCD_BUFFER_SIZE); + + // OUT buffer receives one packet at a time + TUD_EPBUF_DEF(epout, USBTMCD_BUFFER_SIZE); + + // Buffer int msg + TUD_EPBUF_DEF(epnotif, CFG_TUD_USBTMC_INT_EP_SIZE); +} usbtmc_epbuf_t; + +static usbtmc_interface_state_t usbtmc_state = { + .itf_id = 0xFF, +}; + +CFG_TUD_MEM_SECTION static usbtmc_epbuf_t usbtmc_epbuf; + +// We need all headers to fit in a single packet in this implementation, 32 bytes will fit all standard USBTMC headers +TU_VERIFY_STATIC(USBTMCD_BUFFER_SIZE >= 32u, "USBTMC dev buffer size too small"); + +static bool handle_devMsgOutStart(uint8_t rhport, void *data, size_t len); +static bool handle_devMsgOut(uint8_t rhport, void *data, size_t len, size_t packetLen); + + +// USBTMC Device Callbacks weak implementations +TU_ATTR_WEAK bool tud_usbtmc_notification_complete_cb(void) { + return true; +} + +TU_ATTR_WEAK bool tud_usbtmc_indicator_pulse_cb(tusb_control_request_t const * msg, uint8_t *tmcResult) { + (void) msg; + (void) tmcResult; + return true; +} + +#if (CFG_TUD_USBTMC_ENABLE_488) +TU_ATTR_WEAK bool tud_usbtmc_msg_trigger_cb(usbtmc_msg_generic_t* msg) { + (void) msg; + return true; +} +#endif + +#ifndef NDEBUG +tu_static uint8_t termChar; +#endif + +tu_static uint8_t termCharRequested = false; + +tu_static bool usbtmcVendorSpecificRequested = false; + +#if OSAL_MUTEX_REQUIRED +static OSAL_MUTEX_DEF(usbtmcLockBuffer); +#endif +osal_mutex_t usbtmcLock; + +// Our own private lock, mostly for the state variable. +#define criticalEnter() \ + do { (void) osal_mutex_lock(usbtmcLock, OSAL_TIMEOUT_WAIT_FOREVER); } while (0) +#define criticalLeave() \ + do { (void) osal_mutex_unlock(usbtmcLock); } while (0) + +static bool atomicChangeState(usbtmcd_state_enum expectedState, usbtmcd_state_enum newState) { + bool ret = true; + criticalEnter(); + usbtmcd_state_enum oldState = usbtmc_state.state; + if (oldState == expectedState) { + usbtmc_state.state = newState; + } else { + ret = false; + } + criticalLeave(); + return ret; +} + +// called from app +// We keep a reference to the buffer, so it MUST not change until the app is +// notified that the transfer is complete. +// length of data is specified in the hdr. + +// We can't just send the whole thing at once because we need to concatanate the +// header with the data. +bool tud_usbtmc_transmit_dev_msg_data( + const void *data, size_t len, + bool endOfMessage, + bool usingTermChar) { + const unsigned int txBufLen = USBTMCD_BUFFER_SIZE; + +#ifndef NDEBUG + TU_ASSERT(len > 0u); + TU_ASSERT(len <= usbtmc_state.transfer_size_remaining); + TU_ASSERT(usbtmc_state.transfer_size_sent == 0u); + if (usingTermChar) { + TU_ASSERT(usbtmc_state.capabilities->bmDevCapabilities.canEndBulkInOnTermChar); + TU_ASSERT(termCharRequested); + TU_ASSERT(((uint8_t const *) data)[len - 1u] == termChar); + } +#endif + + TU_VERIFY(usbtmc_state.state == STATE_TX_REQUESTED); + usbtmc_msg_dev_dep_msg_in_header_t *hdr = (usbtmc_msg_dev_dep_msg_in_header_t *) usbtmc_epbuf.epin; + tu_varclr(hdr); + if (usbtmcVendorSpecificRequested) { + hdr->header.MsgID = USBTMC_MSGID_VENDOR_SPECIFIC_IN; + } else { + hdr->header.MsgID = USBTMC_MSGID_DEV_DEP_MSG_IN; + } + hdr->header.bTag = usbtmc_state.lastBulkInTag; + hdr->header.bTagInverse = (uint8_t) ~(usbtmc_state.lastBulkInTag); + hdr->TransferSize = len; + hdr->bmTransferAttributes.EOM = endOfMessage; + hdr->bmTransferAttributes.UsingTermChar = usingTermChar; + + // Copy in the header + const size_t headerLen = sizeof(*hdr); + const size_t dataLen = ((headerLen + hdr->TransferSize) <= txBufLen) ? len : (txBufLen - headerLen); + const size_t packetLen = headerLen + dataLen; + + memcpy((uint8_t *) (usbtmc_epbuf.epin) + headerLen, data, dataLen); + usbtmc_state.transfer_size_remaining = len - dataLen; + usbtmc_state.transfer_size_sent = dataLen; + usbtmc_state.devInBuffer = (uint8_t const *) data + (dataLen); + + bool stateChanged = + atomicChangeState(STATE_TX_REQUESTED, (packetLen >= txBufLen) ? STATE_TX_INITIATED : STATE_TX_SHORTED); + TU_VERIFY(stateChanged); + TU_VERIFY(usbd_edpt_xfer(usbtmc_state.rhport, usbtmc_state.ep_bulk_in, usbtmc_epbuf.epin, (uint16_t) packetLen, false)); + return true; +} + +bool tud_usbtmc_transmit_notification_data(const void *data, size_t len) { +#ifndef NDEBUG + TU_ASSERT(len > 0); + TU_ASSERT(usbtmc_state.ep_int_in != 0); +#endif + TU_VERIFY(usbd_edpt_busy(usbtmc_state.rhport, usbtmc_state.ep_int_in)); + + TU_VERIFY(tu_memcpy_s(usbtmc_epbuf.epnotif, CFG_TUD_USBTMC_INT_EP_SIZE, data, len) == 0); + TU_VERIFY(usbd_edpt_xfer(usbtmc_state.rhport, usbtmc_state.ep_int_in, usbtmc_epbuf.epnotif, (uint16_t) len, false)); + return true; +} + +void usbtmcd_init_cb(void) { + usbtmc_state.capabilities = tud_usbtmc_get_capabilities_cb(); +#ifndef NDEBUG + #if CFG_TUD_USBTMC_ENABLE_488 + // Per USB488 spec: table 8 + TU_ASSERT(!usbtmc_state.capabilities->bmIntfcCapabilities.listenOnly, ); + TU_ASSERT(!usbtmc_state.capabilities->bmIntfcCapabilities.talkOnly, ); + #endif +#endif + + usbtmcLock = osal_mutex_create(&usbtmcLockBuffer); +} + +bool usbtmcd_deinit(void) { +#if OSAL_MUTEX_REQUIRED + osal_mutex_delete(usbtmcLock); +#endif + return true; +} + +uint16_t usbtmcd_open_cb(uint8_t rhport, tusb_desc_interface_t const *itf_desc, uint16_t max_len) { + (void) rhport; + + uint16_t drv_len; + uint8_t const *p_desc; + uint8_t found_endpoints = 0; + + TU_VERIFY(itf_desc->bInterfaceClass == TUD_USBTMC_APP_CLASS, 0); + TU_VERIFY(itf_desc->bInterfaceSubClass == TUD_USBTMC_APP_SUBCLASS, 0); + +#ifndef NDEBUG + // Only 2 or 3 endpoints are allowed for USBTMC. + TU_ASSERT((itf_desc->bNumEndpoints == 2) || (itf_desc->bNumEndpoints == 3), 0); +#endif + + TU_ASSERT(usbtmc_state.state == STATE_CLOSED, 0); + + // Interface + drv_len = 0u; + p_desc = (uint8_t const *) itf_desc; + + usbtmc_state.itf_id = itf_desc->bInterfaceNumber; + usbtmc_state.rhport = rhport; + + while (found_endpoints < itf_desc->bNumEndpoints && drv_len <= max_len) { + if (TUSB_DESC_ENDPOINT == p_desc[DESC_OFFSET_TYPE]) { + tusb_desc_endpoint_t const *ep_desc = (tusb_desc_endpoint_t const *) p_desc; + switch (ep_desc->bmAttributes.xfer) { + case TUSB_XFER_BULK: + // Ensure buffer is an exact multiple of the maxPacketSize + TU_ASSERT((USBTMCD_BUFFER_SIZE % tu_edpt_packet_size(ep_desc)) == 0, 0); + if (tu_edpt_dir(ep_desc->bEndpointAddress) == TUSB_DIR_IN) { + usbtmc_state.ep_bulk_in = ep_desc->bEndpointAddress; + usbtmc_state.ep_bulk_in_wMaxPacketSize = tu_edpt_packet_size(ep_desc); + } else { + usbtmc_state.ep_bulk_out = ep_desc->bEndpointAddress; + usbtmc_state.ep_bulk_out_wMaxPacketSize = tu_edpt_packet_size(ep_desc); + } + + break; + case TUSB_XFER_INTERRUPT: +#ifndef NDEBUG + TU_ASSERT(tu_edpt_dir(ep_desc->bEndpointAddress) == TUSB_DIR_IN, 0); + TU_ASSERT(usbtmc_state.ep_int_in == 0, 0); +#endif + usbtmc_state.ep_int_in = ep_desc->bEndpointAddress; + break; + default: + TU_ASSERT(false, 0); + } + TU_ASSERT(usbd_edpt_open(rhport, ep_desc), 0); + found_endpoints++; + } + + drv_len += tu_desc_len(p_desc); + p_desc = tu_desc_next(p_desc); + } + +// bulk endpoints are required, but interrupt IN is optional +#ifndef NDEBUG + TU_ASSERT(usbtmc_state.ep_bulk_in != 0, 0); + TU_ASSERT(usbtmc_state.ep_bulk_out != 0, 0); + if (itf_desc->bNumEndpoints == 2) { + TU_ASSERT(usbtmc_state.ep_int_in == 0, 0); + } else if (itf_desc->bNumEndpoints == 3) { + TU_ASSERT(usbtmc_state.ep_int_in != 0, 0); + } + #if (CFG_TUD_USBTMC_ENABLE_488) + if (usbtmc_state.capabilities->bmIntfcCapabilities488.is488_2 || + usbtmc_state.capabilities->bmDevCapabilities488.SR1) { + TU_ASSERT(usbtmc_state.ep_int_in != 0, 0); + } + #endif +#endif + atomicChangeState(STATE_CLOSED, STATE_NAK); + tud_usbtmc_open_cb(itf_desc->iInterface); + + return drv_len; +} +// Tell USBTMC class to set its bulk-in EP to ACK so that it can +// receive USBTMC commands. +// Returns false if it was already in an ACK state or is busy +// processing a command (such as a clear). Returns true if it was +// in the NAK state and successfully transitioned to the ACK wait +// state. +bool tud_usbtmc_start_bus_read(void) { + usbtmcd_state_enum oldState = usbtmc_state.state; + switch (oldState) { + // These may transition to IDLE + case STATE_NAK: + case STATE_ABORTING_BULK_IN_ABORTED: + TU_VERIFY(atomicChangeState(oldState, STATE_IDLE)); + break; + // When receiving, let it remain receiving + case STATE_RCV: + break; + default: + return false; + } + TU_VERIFY(usbd_edpt_xfer(usbtmc_state.rhport, usbtmc_state.ep_bulk_out, usbtmc_epbuf.epout, (uint16_t) usbtmc_state.ep_bulk_out_wMaxPacketSize, false)); + return true; +} + +void usbtmcd_reset_cb(uint8_t rhport) { + (void) rhport; + usbtmc_capabilities_specific_t const *capabilities = tud_usbtmc_get_capabilities_cb(); + + criticalEnter(); + tu_varclr(&usbtmc_state); + usbtmc_state.capabilities = capabilities; + usbtmc_state.itf_id = 0xFFu; + criticalLeave(); +} + +static bool handle_devMsgOutStart(uint8_t rhport, void *data, size_t len) { + (void) rhport; + // return true upon failure, as we can assume error is being handled elsewhere. + TU_VERIFY(atomicChangeState(STATE_IDLE, STATE_RCV), true); + usbtmc_state.transfer_size_sent = 0u; + + // must be a header, should have been confirmed before calling here. + usbtmc_msg_request_dev_dep_out *msg = (usbtmc_msg_request_dev_dep_out *) data; + usbtmc_state.transfer_size_remaining = msg->TransferSize; + TU_VERIFY(tud_usbtmc_msgBulkOut_start_cb(msg)); + + TU_VERIFY(handle_devMsgOut(rhport, (uint8_t *) data + sizeof(*msg), len - sizeof(*msg), len)); + usbtmc_state.lastBulkOutTag = msg->header.bTag; + return true; +} + +static bool handle_devMsgOut(uint8_t rhport, void *data, size_t len, size_t packetLen) { + (void) rhport; + // return true upon failure, as we can assume error is being handled elsewhere. + TU_VERIFY(usbtmc_state.state == STATE_RCV, true); + + bool shortPacket = (packetLen < usbtmc_state.ep_bulk_out_wMaxPacketSize); + + // Packet is to be considered complete when we get enough data or at a short packet. + bool atEnd = false; + if (len >= usbtmc_state.transfer_size_remaining || shortPacket) { + atEnd = true; + TU_VERIFY(atomicChangeState(STATE_RCV, STATE_NAK)); + } + + len = tu_min32(len, usbtmc_state.transfer_size_remaining); + + usbtmc_state.transfer_size_remaining -= len; + usbtmc_state.transfer_size_sent += len; + + // App may (should?) call the wait_for_bus() command at this point + if (!tud_usbtmc_msg_data_cb(data, len, atEnd)) { + // TODO: Go to an error state upon failure other than just stalling the EP? + return false; + } + + + return true; +} + +static bool handle_devMsgIn(void *data, size_t len) { + TU_VERIFY(len == sizeof(usbtmc_msg_request_dev_dep_in)); + usbtmc_msg_request_dev_dep_in *msg = (usbtmc_msg_request_dev_dep_in *) data; + bool stateChanged = atomicChangeState(STATE_IDLE, STATE_TX_REQUESTED); + TU_VERIFY(stateChanged); + usbtmc_state.lastBulkInTag = msg->header.bTag; + usbtmc_state.transfer_size_remaining = msg->TransferSize; + usbtmc_state.transfer_size_sent = 0u; + + termCharRequested = msg->bmTransferAttributes.TermCharEnabled; + +#ifndef NDEBUG + termChar = msg->TermChar; +#endif + + if (termCharRequested) + TU_VERIFY(usbtmc_state.capabilities->bmDevCapabilities.canEndBulkInOnTermChar); + + TU_VERIFY(tud_usbtmc_msgBulkIn_request_cb(msg)); + return true; +} + +bool usbtmcd_xfer_cb(uint8_t rhport, uint8_t ep_addr, xfer_result_t result, uint32_t xferred_bytes) { + TU_VERIFY(result == XFER_RESULT_SUCCESS); + //uart_tx_str_sync("TMC XFER CB\r\n"); + if (usbtmc_state.state == STATE_CLEARING) { + return true; /* I think we can ignore everything here */ + } + + if (ep_addr == usbtmc_state.ep_bulk_out) { + usbtmc_msg_generic_t *msg = NULL; + + switch (usbtmc_state.state) { + case STATE_IDLE: { + TU_VERIFY(xferred_bytes >= sizeof(usbtmc_msg_generic_t)); + msg = (usbtmc_msg_generic_t *) (usbtmc_epbuf.epout); + uint8_t invInvTag = (uint8_t) ~(msg->header.bTagInverse); + TU_VERIFY(msg->header.bTag == invInvTag); + TU_VERIFY(msg->header.bTag != 0x00); + + switch (msg->header.MsgID) { + case USBTMC_MSGID_DEV_DEP_MSG_OUT: + usbtmcVendorSpecificRequested = false; + if (!handle_devMsgOutStart(rhport, msg, xferred_bytes)) { + usbd_edpt_stall(rhport, usbtmc_state.ep_bulk_out); + return false; + } + break; + + case USBTMC_MSGID_DEV_DEP_MSG_IN: + usbtmcVendorSpecificRequested = false; + TU_VERIFY(handle_devMsgIn(msg, xferred_bytes)); + break; + +#if (CFG_TUD_USBTMC_ENABLE_488) + case USBTMC_MSGID_USB488_TRIGGER: + // Spec says we halt the EP if we didn't declare we support it. + TU_VERIFY(usbtmc_state.capabilities->bmIntfcCapabilities488.supportsTrigger); + TU_VERIFY(tud_usbtmc_msg_trigger_cb(msg)); + + break; +#endif + case USBTMC_MSGID_VENDOR_SPECIFIC_MSG_OUT: + usbtmcVendorSpecificRequested = true; + if (!handle_devMsgOutStart(rhport, msg, xferred_bytes)) { + usbd_edpt_stall(rhport, usbtmc_state.ep_bulk_out); + return false; + } + break; + + case USBTMC_MSGID_VENDOR_SPECIFIC_IN: + usbtmcVendorSpecificRequested = true; + TU_VERIFY(handle_devMsgIn(msg, xferred_bytes)); + break; + + default: + usbd_edpt_stall(rhport, usbtmc_state.ep_bulk_out); + return false; + } + return true; + } + case STATE_RCV: + if (!handle_devMsgOut(rhport, usbtmc_epbuf.epout, xferred_bytes, xferred_bytes)) { + usbd_edpt_stall(rhport, usbtmc_state.ep_bulk_out); + return false; + } + return true; + + case STATE_ABORTING_BULK_OUT: + // Should be stalled by now, shouldn't have received a packet. + return false; + + case STATE_TX_REQUESTED: + case STATE_TX_INITIATED: + case STATE_ABORTING_BULK_IN: + case STATE_ABORTING_BULK_IN_SHORTED: + case STATE_ABORTING_BULK_IN_ABORTED: + default: + return false; + } + } else if (ep_addr == usbtmc_state.ep_bulk_in) { + switch (usbtmc_state.state) { + case STATE_TX_SHORTED: + TU_VERIFY(atomicChangeState(STATE_TX_SHORTED, STATE_NAK)); + TU_VERIFY(tud_usbtmc_msgBulkIn_complete_cb()); + break; + + case STATE_TX_INITIATED: + if (usbtmc_state.transfer_size_remaining >= USBTMCD_BUFFER_SIZE) { + // Copy buffer to ensure alignment correctness + memcpy(usbtmc_epbuf.epin, usbtmc_state.devInBuffer, USBTMCD_BUFFER_SIZE); + TU_VERIFY(usbd_edpt_xfer(rhport, usbtmc_state.ep_bulk_in, usbtmc_epbuf.epin, USBTMCD_BUFFER_SIZE, false)); + usbtmc_state.devInBuffer += USBTMCD_BUFFER_SIZE; + usbtmc_state.transfer_size_remaining -= USBTMCD_BUFFER_SIZE; + usbtmc_state.transfer_size_sent += USBTMCD_BUFFER_SIZE; + } else// last packet + { + size_t packetLen = usbtmc_state.transfer_size_remaining; + memcpy(usbtmc_epbuf.epin, usbtmc_state.devInBuffer, usbtmc_state.transfer_size_remaining); + usbtmc_state.transfer_size_sent += packetLen; + usbtmc_state.transfer_size_remaining = 0; + usbtmc_state.devInBuffer = NULL; + TU_VERIFY(usbd_edpt_xfer(rhport, usbtmc_state.ep_bulk_in, usbtmc_epbuf.epin, (uint16_t) packetLen, false)); + if (((packetLen % usbtmc_state.ep_bulk_in_wMaxPacketSize) != 0) || (packetLen == 0)) { + usbtmc_state.state = STATE_TX_SHORTED; + } + } + return true; + + case STATE_ABORTING_BULK_IN: + // need to send short packet (ZLP?) + TU_VERIFY(usbd_edpt_xfer(rhport, usbtmc_state.ep_bulk_in, usbtmc_epbuf.epin, (uint16_t) 0u, false)); + usbtmc_state.state = STATE_ABORTING_BULK_IN_SHORTED; + return true; + + case STATE_ABORTING_BULK_IN_SHORTED: + /* Done. :)*/ + usbtmc_state.state = STATE_ABORTING_BULK_IN_ABORTED; + return true; + + default: + TU_ASSERT(false); + } + } else if (ep_addr == usbtmc_state.ep_int_in) { + TU_VERIFY(tud_usbtmc_notification_complete_cb()); + return true; + } + return false; +} + +// Invoked when a control transfer occurred on an interface of this class +// Driver response accordingly to the request and the transfer stage (setup/data/ack) +// return false to stall control endpoint (e.g unsupported request) +bool usbtmcd_control_xfer_cb(uint8_t rhport, uint8_t stage, tusb_control_request_t const *request) { + // nothing to do with DATA and ACK stage + if (stage != CONTROL_STAGE_SETUP) return true; + + uint8_t tmcStatusCode = USBTMC_STATUS_FAILED; +#if (CFG_TUD_USBTMC_ENABLE_488) + uint8_t bTag; +#endif + + if ((request->bmRequestType_bit.type == TUSB_REQ_TYPE_STANDARD) && + (request->bmRequestType_bit.recipient == TUSB_REQ_RCPT_ENDPOINT) && + (request->bRequest == TUSB_REQ_CLEAR_FEATURE) && + (request->wValue == TUSB_REQ_FEATURE_EDPT_HALT)) { + uint32_t ep_addr = (request->wIndex); + + // At this point, a transfer MAY be in progress. Based on USB spec, when clearing bulk EP HALT, + // the EP transfer buffer needs to be cleared and DTOG needs to be reset, even if + // the EP is not halted. The only USBD API interface to do this is to stall and then un-stall the EP. + if (ep_addr == usbtmc_state.ep_bulk_out) { + criticalEnter(); + usbd_edpt_stall(rhport, (uint8_t) ep_addr); + usbd_edpt_clear_stall(rhport, (uint8_t) ep_addr); + usbtmc_state.state = STATE_NAK;// USBD core has placed EP in NAK state for us + criticalLeave(); + tud_usbtmc_bulkOut_clearFeature_cb(); + } else if (ep_addr == usbtmc_state.ep_bulk_in) { + usbd_edpt_stall(rhport, (uint8_t) ep_addr); + usbd_edpt_clear_stall(rhport, (uint8_t) ep_addr); + tud_usbtmc_bulkIn_clearFeature_cb(); + } else if ((usbtmc_state.ep_int_in != 0) && (ep_addr == usbtmc_state.ep_int_in)) { + // Clearing interrupt in EP + usbd_edpt_stall(rhport, (uint8_t) ep_addr); + usbd_edpt_clear_stall(rhport, (uint8_t) ep_addr); + } else { + return false; + } + return true; + } + + // Otherwise, we only handle class requests. + if (request->bmRequestType_bit.type != TUSB_REQ_TYPE_CLASS) { + return false; + } + + // Verification that we own the interface is unneeded since it's been routed to us specifically. + + switch (request->bRequest) { + // USBTMC required requests + case USBTMC_bREQUEST_INITIATE_ABORT_BULK_OUT: { + usbtmc_initiate_abort_rsp_t rsp = { + .bTag = usbtmc_state.lastBulkOutTag, + }; + TU_VERIFY(request->bmRequestType == 0xA2);// in,class,interface + TU_VERIFY(request->wLength == sizeof(rsp)); + TU_VERIFY(request->wIndex == usbtmc_state.ep_bulk_out); + + // wValue is the requested bTag to abort + if (usbtmc_state.state != STATE_RCV) { + rsp.USBTMC_status = USBTMC_STATUS_FAILED; + } else if (usbtmc_state.lastBulkOutTag == (request->wValue & 0x7Fu)) { + rsp.USBTMC_status = USBTMC_STATUS_TRANSFER_NOT_IN_PROGRESS; + } else { + rsp.USBTMC_status = USBTMC_STATUS_SUCCESS; + // Check if we've queued a short packet + criticalEnter(); + usbtmc_state.state = STATE_ABORTING_BULK_OUT; + criticalLeave(); + TU_VERIFY(tud_usbtmc_initiate_abort_bulk_out_cb(&(rsp.USBTMC_status))); + usbd_edpt_stall(rhport, usbtmc_state.ep_bulk_out); + } + TU_VERIFY(tud_control_xfer(rhport, request, (void *) &rsp, sizeof(rsp))); + return true; + } + + case USBTMC_bREQUEST_CHECK_ABORT_BULK_OUT_STATUS: { + usbtmc_check_abort_bulk_rsp_t rsp = { + .USBTMC_status = USBTMC_STATUS_SUCCESS, + .NBYTES_RXD_TXD = usbtmc_state.transfer_size_sent}; + TU_VERIFY(request->bmRequestType == 0xA2);// in,class,EP + TU_VERIFY(request->wLength == sizeof(rsp)); + TU_VERIFY(request->wIndex == usbtmc_state.ep_bulk_out); + TU_VERIFY(tud_usbtmc_check_abort_bulk_out_cb(&rsp)); + TU_VERIFY(tud_control_xfer(rhport, request, (void *) &rsp, sizeof(rsp))); + return true; + } + + case USBTMC_bREQUEST_INITIATE_ABORT_BULK_IN: { + usbtmc_initiate_abort_rsp_t rsp = { + .bTag = usbtmc_state.lastBulkInTag, + }; + TU_VERIFY(request->bmRequestType == 0xA2);// in,class,interface + TU_VERIFY(request->wLength == sizeof(rsp)); + TU_VERIFY(request->wIndex == usbtmc_state.ep_bulk_in); + // wValue is the requested bTag to abort + if ((usbtmc_state.state == STATE_TX_REQUESTED || usbtmc_state.state == STATE_TX_INITIATED) && + usbtmc_state.lastBulkInTag == (request->wValue & 0x7Fu)) { + rsp.USBTMC_status = USBTMC_STATUS_SUCCESS; + usbtmc_state.transfer_size_remaining = 0u; + // Check if we've queued a short packet + criticalEnter(); + usbtmc_state.state = ((usbtmc_state.transfer_size_sent % usbtmc_state.ep_bulk_in_wMaxPacketSize) == 0) ? STATE_ABORTING_BULK_IN : STATE_ABORTING_BULK_IN_SHORTED; + criticalLeave(); + if (usbtmc_state.transfer_size_sent == 0) { + // Send short packet, nothing is in the buffer yet + TU_VERIFY(usbd_edpt_xfer(rhport, usbtmc_state.ep_bulk_in, usbtmc_epbuf.epin, (uint16_t) 0u, false)); + usbtmc_state.state = STATE_ABORTING_BULK_IN_SHORTED; + } + TU_VERIFY(tud_usbtmc_initiate_abort_bulk_in_cb(&(rsp.USBTMC_status))); + } else if ((usbtmc_state.state == STATE_TX_REQUESTED || usbtmc_state.state == STATE_TX_INITIATED)) {// FIXME: Unsure how to check if the OUT endpoint fifo is non-empty.... + rsp.USBTMC_status = USBTMC_STATUS_TRANSFER_NOT_IN_PROGRESS; + } else { + rsp.USBTMC_status = USBTMC_STATUS_FAILED; + } + TU_VERIFY(tud_control_xfer(rhport, request, (void *) &rsp, sizeof(rsp))); + return true; + } + + case USBTMC_bREQUEST_CHECK_ABORT_BULK_IN_STATUS: { + TU_VERIFY(request->bmRequestType == 0xA2);// in,class,EP + TU_VERIFY(request->wLength == 8u); + + usbtmc_check_abort_bulk_rsp_t rsp = + { + .USBTMC_status = USBTMC_STATUS_FAILED, + .bmAbortBulkIn = + { + .BulkInFifoBytes = (usbtmc_state.state != STATE_ABORTING_BULK_IN_ABORTED)}, + .NBYTES_RXD_TXD = usbtmc_state.transfer_size_sent, + }; + TU_VERIFY(tud_usbtmc_check_abort_bulk_in_cb(&rsp)); + criticalEnter(); + switch (usbtmc_state.state) { + case STATE_ABORTING_BULK_IN_ABORTED: + rsp.USBTMC_status = USBTMC_STATUS_SUCCESS; + usbtmc_state.state = STATE_IDLE; + break; + case STATE_ABORTING_BULK_IN: + case STATE_ABORTING_BULK_OUT: + rsp.USBTMC_status = USBTMC_STATUS_PENDING; + break; + default: + break; + } + criticalLeave(); + TU_VERIFY(tud_control_xfer(rhport, request, (void *) &rsp, sizeof(rsp))); + + return true; + } + + case USBTMC_bREQUEST_INITIATE_CLEAR: { + TU_VERIFY(request->bmRequestType == 0xA1);// in,class,interface + TU_VERIFY(request->wLength == sizeof(tmcStatusCode)); + // After receiving an INITIATE_CLEAR request, the device must Halt the Bulk-OUT endpoint, queue the + // control endpoint response shown in Table 31, and clear all input buffers and output buffers. + usbd_edpt_stall(rhport, usbtmc_state.ep_bulk_out); + usbtmc_state.transfer_size_remaining = 0; + criticalEnter(); + usbtmc_state.state = STATE_CLEARING; + criticalLeave(); + TU_VERIFY(tud_usbtmc_initiate_clear_cb(&tmcStatusCode)); + TU_VERIFY(tud_control_xfer(rhport, request, (void *) &tmcStatusCode, sizeof(tmcStatusCode))); + return true; + } + + case USBTMC_bREQUEST_CHECK_CLEAR_STATUS: { + TU_VERIFY(request->bmRequestType == 0xA1);// in,class,interface + usbtmc_get_clear_status_rsp_t clearStatusRsp = {0}; + TU_VERIFY(request->wLength == sizeof(clearStatusRsp)); + + if (usbd_edpt_busy(rhport, usbtmc_state.ep_bulk_in)) { + // Stuff stuck in TX buffer? + clearStatusRsp.bmClear.BulkInFifoBytes = 1; + clearStatusRsp.USBTMC_status = USBTMC_STATUS_PENDING; + } else { + // Let app check if it's clear + TU_VERIFY(tud_usbtmc_check_clear_cb(&clearStatusRsp)); + } + if (clearStatusRsp.USBTMC_status == USBTMC_STATUS_SUCCESS) { + criticalEnter(); + usbtmc_state.state = STATE_IDLE; + criticalLeave(); + } + TU_VERIFY(tud_control_xfer(rhport, request, (void *) &clearStatusRsp, sizeof(clearStatusRsp))); + return true; + } + + case USBTMC_bREQUEST_GET_CAPABILITIES: { + TU_VERIFY(request->bmRequestType == 0xA1);// in,class,interface + TU_VERIFY(request->wLength == sizeof(*(usbtmc_state.capabilities))); + TU_VERIFY(tud_control_xfer(rhport, request, (void *) (uintptr_t) usbtmc_state.capabilities, sizeof(*usbtmc_state.capabilities))); + return true; + } + // USBTMC Optional Requests + + case USBTMC_bREQUEST_INDICATOR_PULSE:// Optional + { + TU_VERIFY(request->bmRequestType == 0xA1);// in,class,interface + TU_VERIFY(request->wLength == sizeof(tmcStatusCode)); + TU_VERIFY(usbtmc_state.capabilities->bmIntfcCapabilities.supportsIndicatorPulse); + TU_VERIFY(tud_usbtmc_indicator_pulse_cb(request, &tmcStatusCode)); + TU_VERIFY(tud_control_xfer(rhport, request, (void *) &tmcStatusCode, sizeof(tmcStatusCode))); + return true; + } +#if (CFG_TUD_USBTMC_ENABLE_488) + + // USB488 required requests + case USB488_bREQUEST_READ_STATUS_BYTE: { + usbtmc_read_stb_rsp_488_t rsp; + TU_VERIFY(request->bmRequestType == 0xA1); // in,class,interface + TU_VERIFY(request->wLength == sizeof(rsp));// in,class,interface + + bTag = request->wValue & 0x7F; + TU_VERIFY(request->bmRequestType == 0xA1); + TU_VERIFY((request->wValue & (~0x7F)) == 0u);// Other bits are required to be zero (USB488v1.0 Table 11) + TU_VERIFY(bTag >= 0x02 && bTag <= 127); + TU_VERIFY(request->wIndex == usbtmc_state.itf_id); + TU_VERIFY(request->wLength == 0x0003); + rsp.bTag = (uint8_t) bTag; + if (usbtmc_state.ep_int_in != 0) { + rsp.statusByte = 0x00;// Use interrupt endpoint, instead. Must be 0x00 (USB488v1.0 4.3.1.2) + if (usbd_edpt_busy(rhport, usbtmc_state.ep_int_in)) { + rsp.USBTMC_status = USB488_STATUS_INTERRUPT_IN_BUSY; + } else { + rsp.USBTMC_status = USBTMC_STATUS_SUCCESS; + usbtmc_read_stb_interrupt_488_t intMsg = + { + .bNotify1 = { + .one = 1, + .bTag = bTag & 0x7Fu, + }, + .StatusByte = tud_usbtmc_get_stb_cb(&(rsp.USBTMC_status))}; + // Must be queued before control request response sent (USB488v1.0 4.3.1.2) + usbd_edpt_xfer(rhport, usbtmc_state.ep_int_in, (void *) &intMsg, sizeof(intMsg), false); + } + } else { + rsp.statusByte = tud_usbtmc_get_stb_cb(&(rsp.USBTMC_status)); + } + TU_VERIFY(tud_control_xfer(rhport, request, (void *) &rsp, sizeof(rsp))); + return true; + } + // USB488 optional requests + case USB488_bREQUEST_REN_CONTROL: + case USB488_bREQUEST_GO_TO_LOCAL: + case USB488_bREQUEST_LOCAL_LOCKOUT: { + TU_VERIFY(request->bmRequestType == 0xA1);// in,class,interface + return false; + } +#endif + + default: + return false; + } +} + +#endif /* CFG_TUD_TSMC */ diff --git a/libraries/Adafruit_TinyUSB_Arduino/src/class/usbtmc/usbtmc_device.h b/libraries/Adafruit_TinyUSB_Arduino/src/class/usbtmc/usbtmc_device.h new file mode 100644 index 0000000..235f127 --- /dev/null +++ b/libraries/Adafruit_TinyUSB_Arduino/src/class/usbtmc/usbtmc_device.h @@ -0,0 +1,117 @@ +/* + * The MIT License (MIT) + * + * Copyright (c) 2019 N Conrad + * + * 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 CLASS_USBTMC_USBTMC_DEVICE_H_ +#define CLASS_USBTMC_USBTMC_DEVICE_H_ + +#include "usbtmc.h" + +// Enable 488 mode by default +#if !defined(CFG_TUD_USBTMC_ENABLE_488) +#define CFG_TUD_USBTMC_ENABLE_488 (1) +#endif + +/*********************************************** + * Functions to be implemented by the class implementation + */ + +// In order to proceed, app must call call tud_usbtmc_start_bus_read(rhport) during or soon after: +// * tud_usbtmc_open_cb +// * tud_usbtmc_msg_data_cb +// * tud_usbtmc_msgBulkIn_complete_cb +// * tud_usbtmc_msg_trigger_cb +// * (successful) tud_usbtmc_check_abort_bulk_out_cb +// * (successful) tud_usbtmc_check_abort_bulk_in_cb +// * (successful) tud_usmtmc_bulkOut_clearFeature_cb + +#if (CFG_TUD_USBTMC_ENABLE_488) +usbtmc_response_capabilities_488_t const * tud_usbtmc_get_capabilities_cb(void); +#else +usbtmc_response_capabilities_t const * tud_usbtmc_get_capabilities_cb(void); +#endif + +void tud_usbtmc_open_cb(uint8_t interface_id); + +bool tud_usbtmc_msgBulkOut_start_cb(usbtmc_msg_request_dev_dep_out const * msgHeader); +// transfer_complete does not imply that a message is complete. +bool tud_usbtmc_msg_data_cb( void *data, size_t len, bool transfer_complete); +void tud_usbtmc_bulkOut_clearFeature_cb(void); // Notice to clear and abort the pending BULK out transfer + +bool tud_usbtmc_msgBulkIn_request_cb(usbtmc_msg_request_dev_dep_in const * request); +bool tud_usbtmc_msgBulkIn_complete_cb(void); +void tud_usbtmc_bulkIn_clearFeature_cb(void); // Notice to clear and abort the pending BULK out transfer + +bool tud_usbtmc_initiate_abort_bulk_in_cb(uint8_t *tmcResult); +bool tud_usbtmc_initiate_abort_bulk_out_cb(uint8_t *tmcResult); +bool tud_usbtmc_initiate_clear_cb(uint8_t *tmcResult); + +bool tud_usbtmc_check_abort_bulk_in_cb(usbtmc_check_abort_bulk_rsp_t *rsp); +bool tud_usbtmc_check_abort_bulk_out_cb(usbtmc_check_abort_bulk_rsp_t *rsp); +bool tud_usbtmc_check_clear_cb(usbtmc_get_clear_status_rsp_t *rsp); + +// The interrupt-IN endpoint buffer was transmitted to the host. Use +// tud_usbtmc_transmit_notification_data to send another notification. +bool tud_usbtmc_notification_complete_cb(void); + +// Indicator pulse should be 0.5 to 1.0 seconds long +bool tud_usbtmc_indicator_pulse_cb(tusb_control_request_t const * msg, uint8_t *tmcResult); + +#if (CFG_TUD_USBTMC_ENABLE_488) +uint8_t tud_usbtmc_get_stb_cb(uint8_t *tmcResult); +bool tud_usbtmc_msg_trigger_cb(usbtmc_msg_generic_t* msg); +#endif + +// Called from app +// +// We keep a reference to the buffer, so it MUST not change until the app is +// notified that the transfer is complete. +bool tud_usbtmc_transmit_dev_msg_data( + const void * data, size_t len, + bool endOfMessage, bool usingTermChar); + +// Buffers a notification to be sent to the host. The data starts +// with the bNotify1 field, see the USBTMC Specification, Table 13. +// +// If the previous notification data has not yet been sent, this +// returns false. +// +// Requires an interrupt endpoint in the interface. +bool tud_usbtmc_transmit_notification_data(const void * data, size_t len); + +bool tud_usbtmc_start_bus_read(void); + + +/* "callbacks" from USB device core */ + +void usbtmcd_init_cb(void); +bool usbtmcd_deinit(void); +uint16_t usbtmcd_open_cb(uint8_t rhport, tusb_desc_interface_t const * itf_desc, uint16_t max_len); +void usbtmcd_reset_cb(uint8_t rhport); +bool usbtmcd_xfer_cb(uint8_t rhport, uint8_t ep_addr, xfer_result_t result, uint32_t xferred_bytes); +bool usbtmcd_control_xfer_cb(uint8_t rhport, uint8_t stage, tusb_control_request_t const * request); + +#endif /* CLASS_USBTMC_USBTMC_DEVICE_H_ */ diff --git a/libraries/Adafruit_TinyUSB_Arduino/src/class/vendor/vendor_device.c b/libraries/Adafruit_TinyUSB_Arduino/src/class/vendor/vendor_device.c new file mode 100644 index 0000000..e1017ba --- /dev/null +++ b/libraries/Adafruit_TinyUSB_Arduino/src/class/vendor/vendor_device.c @@ -0,0 +1,376 @@ +/* + * 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. + * + * This file is part of the TinyUSB stack. + */ + +#include "tusb_option.h" + +#if (CFG_TUD_ENABLED && CFG_TUD_VENDOR) + +#include "device/usbd.h" +#include "device/usbd_pvt.h" + +#include "vendor_device.h" + +//--------------------------------------------------------------------+ +// MACRO CONSTANT TYPEDEF +//--------------------------------------------------------------------+ +typedef struct { + uint8_t rhport; + uint8_t itf_num; + + #if CFG_TUD_VENDOR_TXRX_BUFFERED + /*------------- From this point, data is not cleared by bus reset -------------*/ + tu_edpt_stream_t tx_stream; + tu_edpt_stream_t rx_stream; + uint8_t tx_ff_buf[CFG_TUD_VENDOR_TX_BUFSIZE]; + uint8_t rx_ff_buf[CFG_TUD_VENDOR_RX_BUFSIZE]; + #else + uint8_t ep_in; + uint8_t ep_out; + uint16_t rx_xfer_len; + #endif +} vendord_interface_t; + + #if CFG_TUD_VENDOR_TXRX_BUFFERED + #define ITF_MEM_RESET_SIZE (offsetof(vendord_interface_t, itf_num) + TU_FIELD_SIZE(vendord_interface_t, itf_num)) + #else + #define ITF_MEM_RESET_SIZE sizeof(vendord_interface_t) + #endif + +static vendord_interface_t _vendord_itf[CFG_TUD_VENDOR]; + +// Skip local EP buffer if dedicated hw FIFO is supported or no fifo mode +#if CFG_TUD_EDPT_DEDICATED_HWFIFO == 0 || !CFG_TUD_VENDOR_TXRX_BUFFERED +typedef struct { + TUD_EPBUF_DEF(epout, CFG_TUD_VENDOR_RX_EPSIZE); + TUD_EPBUF_DEF(epin, CFG_TUD_VENDOR_TX_EPSIZE); +} vendord_epbuf_t; + +CFG_TUD_MEM_SECTION static vendord_epbuf_t _vendord_epbuf[CFG_TUD_VENDOR]; +#endif + +//--------------------------------------------------------------------+ +// Weak stubs: invoked if no strong implementation is available +//--------------------------------------------------------------------+ +TU_ATTR_WEAK void tud_vendor_rx_cb(uint8_t idx, const uint8_t *buffer, uint32_t bufsize) { + (void)idx; + (void)buffer; + (void)bufsize; +} + +TU_ATTR_WEAK void tud_vendor_tx_cb(uint8_t idx, uint32_t sent_bytes) { + (void)idx; + (void) sent_bytes; +} + +bool tud_vendor_n_mounted(uint8_t idx) { + TU_VERIFY(idx < CFG_TUD_VENDOR); + vendord_interface_t *p_itf = &_vendord_itf[idx]; + + #if CFG_TUD_VENDOR_TXRX_BUFFERED + return (p_itf->rx_stream.ep_addr != 0) || (p_itf->tx_stream.ep_addr != 0); + #else + return (p_itf->ep_out != 0) || (p_itf->ep_in != 0); + #endif +} + +//--------------------------------------------------------------------+ +// Read API +//--------------------------------------------------------------------+ + #if CFG_TUD_VENDOR_TXRX_BUFFERED +uint32_t tud_vendor_n_available(uint8_t idx) { + TU_VERIFY(idx < CFG_TUD_VENDOR, 0); + vendord_interface_t *p_itf = &_vendord_itf[idx]; + return tu_edpt_stream_read_available(&p_itf->rx_stream); +} + +bool tud_vendor_n_peek(uint8_t idx, uint8_t *u8) { + TU_VERIFY(idx < CFG_TUD_VENDOR); + vendord_interface_t *p_itf = &_vendord_itf[idx]; + return tu_edpt_stream_peek(&p_itf->rx_stream, u8); +} + +uint32_t tud_vendor_n_read(uint8_t idx, void *buffer, uint32_t bufsize) { + TU_VERIFY(idx < CFG_TUD_VENDOR, 0); + vendord_interface_t *p_itf = &_vendord_itf[idx]; + return tu_edpt_stream_read(&p_itf->rx_stream, buffer, bufsize); +} + +void tud_vendor_n_read_flush(uint8_t idx) { + TU_VERIFY(idx < CFG_TUD_VENDOR, ); + vendord_interface_t *p_itf = &_vendord_itf[idx]; + tu_edpt_stream_clear(&p_itf->rx_stream); + tu_edpt_stream_read_xfer(&p_itf->rx_stream); +} + #endif + + #if CFG_TUD_VENDOR_RX_MANUAL_XFER +bool tud_vendor_n_read_xfer(uint8_t idx) { + TU_VERIFY(idx < CFG_TUD_VENDOR); + vendord_interface_t *p_itf = &_vendord_itf[idx]; + + #if CFG_TUD_VENDOR_TXRX_BUFFERED + return tu_edpt_stream_read_xfer(&p_itf->rx_stream); + + #else + // Non-FIFO mode + TU_VERIFY(usbd_edpt_claim(p_itf->rhport, p_itf->ep_out)); + return usbd_edpt_xfer(p_itf->rhport, p_itf->ep_out, _vendord_epbuf[idx].epout, p_itf->rx_xfer_len, false); + #endif +} + #endif + + +//--------------------------------------------------------------------+ +// Write API +//--------------------------------------------------------------------+ +uint32_t tud_vendor_n_write(uint8_t idx, const void *buffer, uint32_t bufsize) { + TU_VERIFY(idx < CFG_TUD_VENDOR, 0); + vendord_interface_t *p_itf = &_vendord_itf[idx]; + + #if CFG_TUD_VENDOR_TXRX_BUFFERED + return tu_edpt_stream_write(&p_itf->tx_stream, buffer, (uint16_t)bufsize); + + #else + // non-fifo mode: direct transfer + TU_VERIFY(usbd_edpt_claim(p_itf->rhport, p_itf->ep_in), 0); + const uint32_t xact_len = tu_min32(bufsize, CFG_TUD_VENDOR_TX_EPSIZE); + memcpy(_vendord_epbuf[idx].epin, buffer, xact_len); + TU_ASSERT(usbd_edpt_xfer(p_itf->rhport, p_itf->ep_in, _vendord_epbuf[idx].epin, (uint16_t)xact_len, false), 0); + return xact_len; + #endif +} + +uint32_t tud_vendor_n_write_available(uint8_t idx) { + TU_VERIFY(idx < CFG_TUD_VENDOR, 0); + vendord_interface_t *p_itf = &_vendord_itf[idx]; + + #if CFG_TUD_VENDOR_TXRX_BUFFERED + return tu_edpt_stream_write_available(&p_itf->tx_stream); + + #else + // Non-FIFO mode + TU_VERIFY(p_itf->ep_in > 0, 0); // must be opened + return usbd_edpt_busy(p_itf->rhport, p_itf->ep_in) ? 0 : CFG_TUD_VENDOR_TX_EPSIZE; + #endif +} + + #if CFG_TUD_VENDOR_TXRX_BUFFERED +uint32_t tud_vendor_n_write_flush(uint8_t idx) { + TU_VERIFY(idx < CFG_TUD_VENDOR, 0); + vendord_interface_t *p_itf = &_vendord_itf[idx]; + return tu_edpt_stream_write_xfer(&p_itf->tx_stream); +} + +bool tud_vendor_n_write_clear(uint8_t idx) { + TU_VERIFY(idx < CFG_TUD_VENDOR, 0); + vendord_interface_t *p_itf = &_vendord_itf[idx]; + tu_edpt_stream_clear(&p_itf->tx_stream); + return true; +} +#endif + +//--------------------------------------------------------------------+ +// USBD Driver API +//--------------------------------------------------------------------+ +void vendord_init(void) { + tu_memclr(_vendord_itf, sizeof(_vendord_itf)); + + #if CFG_TUD_VENDOR_TXRX_BUFFERED + for (uint8_t i = 0; i < CFG_TUD_VENDOR; i++) { + vendord_interface_t *p_itf = &_vendord_itf[i]; + + #if CFG_TUD_EDPT_DEDICATED_HWFIFO + uint8_t *epout_buf = NULL; + uint8_t *epin_buf = NULL; + #else + uint8_t *epout_buf = _vendord_epbuf[i].epout; + uint8_t *epin_buf = _vendord_epbuf[i].epin; + #endif + + uint8_t *rx_ff_buf = p_itf->rx_ff_buf; + tu_edpt_stream_init(&p_itf->rx_stream, false, false, false, rx_ff_buf, CFG_TUD_VENDOR_RX_BUFSIZE, epout_buf); + + uint8_t *tx_ff_buf = p_itf->tx_ff_buf; + tu_edpt_stream_init(&p_itf->tx_stream, false, true, false, tx_ff_buf, CFG_TUD_VENDOR_TX_BUFSIZE, epin_buf); + } + #endif +} + +bool vendord_deinit(void) { + #if CFG_TUD_VENDOR_TXRX_BUFFERED + for (uint8_t i = 0; i < CFG_TUD_VENDOR; i++) { + vendord_interface_t *p_itf = &_vendord_itf[i]; + tu_edpt_stream_deinit(&p_itf->rx_stream); + tu_edpt_stream_deinit(&p_itf->tx_stream); + } + #endif + return true; +} + +void vendord_reset(uint8_t rhport) { + (void) rhport; + + for(uint8_t i=0; irx_stream); + tu_edpt_stream_close(&p_itf->rx_stream); + tu_edpt_stream_clear(&p_itf->tx_stream); + tu_edpt_stream_close(&p_itf->tx_stream); + #endif + } +} + +// Find vendor interface by endpoint address +static uint8_t find_vendor_itf(uint8_t ep_addr) { + for (uint8_t idx = 0; idx < CFG_TUD_VENDOR; idx++) { + const vendord_interface_t *p_vendor = &_vendord_itf[idx]; + if (ep_addr == 0) { + // find unused: require both ep == 0 + #if CFG_TUD_VENDOR_TXRX_BUFFERED + if (p_vendor->rx_stream.ep_addr == 0 && p_vendor->tx_stream.ep_addr == 0) { + return idx; + } + #else + if (p_vendor->ep_out == 0 && p_vendor->ep_in == 0) { + return idx; + } + #endif + } else { + #if CFG_TUD_VENDOR_TXRX_BUFFERED + if (ep_addr == p_vendor->rx_stream.ep_addr || ep_addr == p_vendor->tx_stream.ep_addr) { + return idx; + } + #else + if (ep_addr == p_vendor->ep_out || ep_addr == p_vendor->ep_in) { + return idx; + } + #endif + } + } + return 0xff; +} + +uint16_t vendord_open(uint8_t rhport, const tusb_desc_interface_t *desc_itf, uint16_t max_len) { + TU_VERIFY(TUSB_CLASS_VENDOR_SPECIFIC == desc_itf->bInterfaceClass, 0); + const uint8_t* desc_end = (const uint8_t*)desc_itf + max_len; + const uint8_t* p_desc = tu_desc_next(desc_itf); + + // Find available interface + const uint8_t idx = find_vendor_itf(0); + TU_ASSERT(idx < CFG_TUD_VENDOR, 0); + vendord_interface_t *p_vendor = &_vendord_itf[idx]; + p_vendor->rhport = rhport; + p_vendor->itf_num = desc_itf->bInterfaceNumber; + + while (tu_desc_in_bounds(p_desc, desc_end)) { + const uint8_t desc_type = tu_desc_type(p_desc); + if (desc_type == TUSB_DESC_INTERFACE || desc_type == TUSB_DESC_INTERFACE_ASSOCIATION) { + break; // end of this interface + } else if (desc_type == TUSB_DESC_ENDPOINT) { + const tusb_desc_endpoint_t* desc_ep = (const tusb_desc_endpoint_t*) p_desc; + TU_ASSERT(usbd_edpt_open(rhport, desc_ep)); + + uint16_t rx_xfer_len = CFG_TUD_VENDOR_RX_NEED_ZLP ? CFG_TUD_VENDOR_RX_EPSIZE : tu_edpt_packet_size(desc_ep); + + #if CFG_TUD_VENDOR_TXRX_BUFFERED + // open endpoint stream + if (tu_edpt_dir(desc_ep->bEndpointAddress) == TUSB_DIR_IN) { + tu_edpt_stream_t *tx_stream = &p_vendor->tx_stream; + tu_edpt_stream_open(tx_stream, rhport, desc_ep, CFG_TUD_VENDOR_TX_EPSIZE); + tu_edpt_stream_write_xfer(tx_stream); // flush pending data + } else { + tu_edpt_stream_t *rx_stream = &p_vendor->rx_stream; + tu_edpt_stream_open(rx_stream, rhport, desc_ep, rx_xfer_len); + #if CFG_TUD_VENDOR_RX_MANUAL_XFER == 0 + TU_ASSERT(tu_edpt_stream_read_xfer(rx_stream) > 0, 0); // prepare for incoming data + #endif + } + #else + p_vendor->rx_xfer_len = rx_xfer_len; + // Non-FIFO mode: store endpoint info + if (tu_edpt_dir(desc_ep->bEndpointAddress) == TUSB_DIR_IN) { + p_vendor->ep_in = desc_ep->bEndpointAddress; + } else { + p_vendor->ep_out = desc_ep->bEndpointAddress; + #if CFG_TUD_VENDOR_RX_MANUAL_XFER == 0 + // Prepare for incoming data + TU_ASSERT(usbd_edpt_xfer(rhport, p_vendor->ep_out, _vendord_epbuf[idx].epout, rx_xfer_len, false), 0); + #endif + } + #endif + } + + p_desc = tu_desc_next(p_desc); + } + + return (uint16_t)((uintptr_t)p_desc - (uintptr_t)desc_itf); +} + +bool vendord_xfer_cb(uint8_t rhport, uint8_t ep_addr, xfer_result_t result, uint32_t xferred_bytes) { + (void)rhport; + (void)result; + const uint8_t idx = find_vendor_itf(ep_addr); + TU_VERIFY(idx < CFG_TUD_VENDOR); + vendord_interface_t *p_vendor = &_vendord_itf[idx]; + +#if CFG_TUD_VENDOR_TXRX_BUFFERED + if (ep_addr == p_vendor->rx_stream.ep_addr) { + // Put received data to FIFO + tu_edpt_stream_read_xfer_complete(&p_vendor->rx_stream, xferred_bytes); + tud_vendor_rx_cb(idx, NULL, 0); + #if CFG_TUD_VENDOR_RX_MANUAL_XFER == 0 + tu_edpt_stream_read_xfer(&p_vendor->rx_stream); // prepare next data + #endif + } else if (ep_addr == p_vendor->tx_stream.ep_addr) { + // Send complete + tud_vendor_tx_cb(idx, (uint16_t)xferred_bytes); + + // try to send more if possible + if (0 == tu_edpt_stream_write_xfer(&p_vendor->tx_stream)) { + // If there is no data left, a ZLP should be sent if xferred_bytes is multiple of EP Packet size and not zero + tu_edpt_stream_write_zlp_if_needed(&p_vendor->tx_stream, xferred_bytes); + } + } + #else + if (ep_addr == p_vendor->ep_out) { + // Non-FIFO mode: invoke callback with buffer + tud_vendor_rx_cb(idx, _vendord_epbuf[idx].epout, xferred_bytes); + #if CFG_TUD_VENDOR_RX_MANUAL_XFER == 0 + usbd_edpt_xfer(rhport, p_vendor->ep_out, _vendord_epbuf[idx].epout, p_vendor->rx_xfer_len, false); + #endif + } else if (ep_addr == p_vendor->ep_in) { + // Send complete + tud_vendor_tx_cb(idx, (uint16_t)xferred_bytes); + } + #endif + + return true; +} + +#endif diff --git a/libraries/Adafruit_TinyUSB_Arduino/src/class/vendor/vendor_device.h b/libraries/Adafruit_TinyUSB_Arduino/src/class/vendor/vendor_device.h new file mode 100644 index 0000000..cdca9fd --- /dev/null +++ b/libraries/Adafruit_TinyUSB_Arduino/src/class/vendor/vendor_device.h @@ -0,0 +1,211 @@ +/* + * 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. + * + * This file is part of the TinyUSB stack. + */ + +#ifndef TUSB_VENDOR_DEVICE_H_ +#define TUSB_VENDOR_DEVICE_H_ + +#ifdef __cplusplus +extern "C" { +#endif + +#include "common/tusb_common.h" + +//--------------------------------------------------------------------+ +// Configuration +//--------------------------------------------------------------------+ +#ifndef CFG_TUD_VENDOR_RX_EPSIZE + #ifdef CFG_TUD_VENDOR_EPSIZE + #define CFG_TUD_VENDOR_RX_EPSIZE CFG_TUD_VENDOR_EPSIZE + #else + #define CFG_TUD_VENDOR_RX_EPSIZE TUD_EPSIZE_BULK_MAX + #endif +#endif + +#ifndef CFG_TUD_VENDOR_TX_EPSIZE + #ifdef CFG_TUD_VENDOR_EPSIZE + #define CFG_TUD_VENDOR_TX_EPSIZE CFG_TUD_VENDOR_EPSIZE + #else + #define CFG_TUD_VENDOR_TX_EPSIZE TUD_EPSIZE_BULK_MAX + #endif +#endif + +// RX FIFO can be disabled by setting this value to 0 +#ifndef CFG_TUD_VENDOR_RX_BUFSIZE + #define CFG_TUD_VENDOR_RX_BUFSIZE TUD_EPSIZE_BULK_MAX +#endif + +// TX FIFO can be disabled by setting this value to 0 +#ifndef CFG_TUD_VENDOR_TX_BUFSIZE + #define CFG_TUD_VENDOR_TX_BUFSIZE TUD_EPSIZE_BULK_MAX +#endif + +// Vendor is buffered (FIFO mode) if both TX and RX buffers are configured +// If either is 0, vendor operates in non-buffered (direct transfer) mode +#ifndef CFG_TUD_VENDOR_TXRX_BUFFERED + #define CFG_TUD_VENDOR_TXRX_BUFFERED ((CFG_TUD_VENDOR_RX_BUFSIZE > 0) && (CFG_TUD_VENDOR_TX_BUFSIZE > 0)) +#endif + +// Application will manually schedule RX transfer. This can be useful when using with non-fifo (buffered) mode +// i.e. CFG_TUD_VENDOR_TXRX_BUFFERED = 0 +#ifndef CFG_TUD_VENDOR_RX_MANUAL_XFER + #define CFG_TUD_VENDOR_RX_MANUAL_XFER 0 +#endif + +// Enable multi-packet RX transfer with ZLP termination for better throughput. Requires host support for ZLP. +#ifndef CFG_TUD_VENDOR_RX_NEED_ZLP + #define CFG_TUD_VENDOR_RX_NEED_ZLP 0 +#endif + +//--------------------------------------------------------------------+ +// Application API (Multiple Interfaces) i.e CFG_TUD_VENDOR > 1 +//--------------------------------------------------------------------+ + +// Return whether the vendor interface is mounted +bool tud_vendor_n_mounted(uint8_t idx); + +//------------- RX -------------// +#if CFG_TUD_VENDOR_TXRX_BUFFERED +// Return number of available bytes for reading +uint32_t tud_vendor_n_available(uint8_t idx); + +// Peek a byte from RX buffer +bool tud_vendor_n_peek(uint8_t idx, uint8_t *ui8); + +// Read from RX FIFO +uint32_t tud_vendor_n_read(uint8_t idx, void *buffer, uint32_t bufsize); + +// Flush (clear) RX FIFO +void tud_vendor_n_read_flush(uint8_t idx); +#endif + +#if CFG_TUD_VENDOR_RX_MANUAL_XFER +// Start a new RX transfer to fill the RX FIFO, return false if previous transfer is still ongoing +bool tud_vendor_n_read_xfer(uint8_t idx); +#endif + +//------------- TX -------------// +// Write to TX FIFO. This can be buffered and not sent immediately unless buffered bytes >= USB endpoint size +uint32_t tud_vendor_n_write(uint8_t idx, const void *buffer, uint32_t bufsize); + +// Return number of bytes available for writing in TX FIFO (or endpoint if non-buffered) +uint32_t tud_vendor_n_write_available(uint8_t idx); + +#if CFG_TUD_VENDOR_TXRX_BUFFERED +// Force sending buffered data, return number of bytes sent +uint32_t tud_vendor_n_write_flush(uint8_t idx); + +// Clear the transmit FIFO +bool tud_vendor_n_write_clear(uint8_t idx); +#endif + +// Write a null-terminated string to TX FIFO +TU_ATTR_ALWAYS_INLINE static inline uint32_t tud_vendor_n_write_str(uint8_t idx, const char *str) { + return tud_vendor_n_write(idx, str, strlen(str)); +} + +// backward compatible +#define tud_vendor_n_flush(idx) tud_vendor_n_write_flush(idx) + +//--------------------------------------------------------------------+ +// Application API (Single Port) i.e CFG_TUD_VENDOR = 1 +//--------------------------------------------------------------------+ +TU_ATTR_ALWAYS_INLINE static inline bool tud_vendor_mounted(void) { + return tud_vendor_n_mounted(0); +} + +#if CFG_TUD_VENDOR_TXRX_BUFFERED +TU_ATTR_ALWAYS_INLINE static inline uint32_t tud_vendor_available(void) { + return tud_vendor_n_available(0); +} + +TU_ATTR_ALWAYS_INLINE static inline bool tud_vendor_peek(uint8_t *ui8) { + return tud_vendor_n_peek(0, ui8); +} + +TU_ATTR_ALWAYS_INLINE static inline uint32_t tud_vendor_read(void *buffer, uint32_t bufsize) { + return tud_vendor_n_read(0, buffer, bufsize); +} + +TU_ATTR_ALWAYS_INLINE static inline void tud_vendor_read_flush(void) { + tud_vendor_n_read_flush(0); +} + +TU_ATTR_ALWAYS_INLINE static inline uint32_t tud_vendor_write_flush(void) { + return tud_vendor_n_write_flush(0); +} + +TU_ATTR_ALWAYS_INLINE static inline bool tud_vendor_write_clear(void) { + return tud_vendor_n_write_clear(0); +} +#endif + +#if CFG_TUD_VENDOR_RX_MANUAL_XFER +TU_ATTR_ALWAYS_INLINE static inline bool tud_vendor_read_xfer(void) { + return tud_vendor_n_read_xfer(0); +} +#endif + +TU_ATTR_ALWAYS_INLINE static inline uint32_t tud_vendor_write(const void *buffer, uint32_t bufsize) { + return tud_vendor_n_write(0, buffer, bufsize); +} + +TU_ATTR_ALWAYS_INLINE static inline uint32_t tud_vendor_write_str(const char *str) { + return tud_vendor_n_write_str(0, str); +} + +TU_ATTR_ALWAYS_INLINE static inline uint32_t tud_vendor_write_available(void) { + return tud_vendor_n_write_available(0); +} + +// backward compatible +#define tud_vendor_flush() tud_vendor_write_flush() + +//--------------------------------------------------------------------+ +// Application Callback API (weak is optional) +//--------------------------------------------------------------------+ + +// Invoked when received new data. +// - CFG_TUD_VENDOR_TXRX_BUFFERED = 1: buffer and bufsize must not be used (both NULL,0) since data is in RX FIFO +// - CFG_TUD_VENDOR_TXRX_BUFFERED = 0: Buffer and bufsize are valid +void tud_vendor_rx_cb(uint8_t idx, const uint8_t *buffer, uint32_t bufsize); + +// Invoked when tx transfer is finished +void tud_vendor_tx_cb(uint8_t idx, uint32_t sent_bytes); + +//--------------------------------------------------------------------+ +// Internal Class Driver API +//--------------------------------------------------------------------+ +void vendord_init(void); +bool vendord_deinit(void); +void vendord_reset(uint8_t rhport); +uint16_t vendord_open(uint8_t rhport, const tusb_desc_interface_t *idx_desc, uint16_t max_len); +bool vendord_xfer_cb(uint8_t rhport, uint8_t ep_addr, xfer_result_t event, uint32_t xferred_bytes); + +#ifdef __cplusplus +} +#endif + +#endif /* TUSB_VENDOR_DEVICE_H_ */ diff --git a/libraries/Adafruit_TinyUSB_Arduino/src/class/video/video.h b/libraries/Adafruit_TinyUSB_Arduino/src/class/video/video.h new file mode 100644 index 0000000..5bdf4b8 --- /dev/null +++ b/libraries/Adafruit_TinyUSB_Arduino/src/class/video/video.h @@ -0,0 +1,697 @@ +/* + * The MIT License (MIT) + * + * Copyright (c) 2021 Koji KITAYAMA + * + * 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_VIDEO_H_ +#define TUSB_VIDEO_H_ + +#include "common/tusb_common.h" + +enum { + VIDEO_BCD_1_50 = 0x0150, +}; + +// Table 3-19 Color Matching Descriptor +typedef enum { + VIDEO_COLOR_PRIMARIES_UNDEFINED = 0x00, + VIDEO_COLOR_PRIMARIES_BT709, // sRGB (default) + VIDEO_COLOR_PRIMARIES_BT470_2M, + VIDEO_COLOR_PRIMARIES_BT470_2BG, + VIDEO_COLOR_PRIMARIES_SMPTE170M, + VIDEO_COLOR_PRIMARIES_SMPTE240M, +} video_color_primaries_t; + +// Table 3-19 Color Matching Descriptor +typedef enum { + VIDEO_COLOR_XFER_CH_UNDEFINED = 0x00, + VIDEO_COLOR_XFER_CH_BT709, // default + VIDEO_COLOR_XFER_CH_BT470_2M, + VIDEO_COLOR_XFER_CH_BT470_2BG, + VIDEO_COLOR_XFER_CH_SMPTE170M, + VIDEO_COLOR_XFER_CH_SMPTE240M, + VIDEO_COLOR_XFER_CH_LINEAR, + VIDEO_COLOR_XFER_CH_SRGB, +} video_color_transfer_characteristics_t; + +// Table 3-19 Color Matching Descriptor +typedef enum { + VIDEO_COLOR_COEF_UNDEFINED = 0x00, + VIDEO_COLOR_COEF_BT709, + VIDEO_COLOR_COEF_FCC, + VIDEO_COLOR_COEF_BT470_2BG, + VIDEO_COLOR_COEF_SMPTE170M, // BT.601 default + VIDEO_COLOR_COEF_SMPTE240M, +} video_color_matrix_coefficients_t; + +/* 4.2.1.2 Request Error Code Control */ +typedef enum { + VIDEO_ERROR_NONE = 0, /* The request succeeded. */ + VIDEO_ERROR_NOT_READY, + VIDEO_ERROR_WRONG_STATE, + VIDEO_ERROR_POWER, + VIDEO_ERROR_OUT_OF_RANGE, + VIDEO_ERROR_INVALID_UNIT, + VIDEO_ERROR_INVALID_CONTROL, + VIDEO_ERROR_INVALID_REQUEST, + VIDEO_ERROR_INVALID_VALUE_WITHIN_RANGE, + VIDEO_ERROR_UNKNOWN = 0xFF, +} video_error_code_t; + +/* A.2 Interface Subclass */ +typedef enum { + VIDEO_SUBCLASS_UNDEFINED = 0x00, + VIDEO_SUBCLASS_CONTROL, + VIDEO_SUBCLASS_STREAMING, + VIDEO_SUBCLASS_INTERFACE_COLLECTION, +} video_subclass_type_t; + +/* A.3 Interface Protocol */ +typedef enum { + VIDEO_ITF_PROTOCOL_UNDEFINED = 0x00, + VIDEO_ITF_PROTOCOL_15, +} video_interface_protocol_code_t; + +/* A.5 Class-Specific VideoControl Interface Descriptor Subtypes */ +typedef enum { + VIDEO_CS_ITF_VC_UNDEFINED = 0x00, + VIDEO_CS_ITF_VC_HEADER, + VIDEO_CS_ITF_VC_INPUT_TERMINAL, + VIDEO_CS_ITF_VC_OUTPUT_TERMINAL, + VIDEO_CS_ITF_VC_SELECTOR_UNIT, + VIDEO_CS_ITF_VC_PROCESSING_UNIT, + VIDEO_CS_ITF_VC_EXTENSION_UNIT, + VIDEO_CS_ITF_VC_ENCODING_UNIT, + VIDEO_CS_ITF_VC_MAX, +} video_cs_vc_interface_subtype_t; + +/* A.6 Class-Specific VideoStreaming Interface Descriptor Subtypes */ +typedef enum { + VIDEO_CS_ITF_VS_UNDEFINED = 0x00, + VIDEO_CS_ITF_VS_INPUT_HEADER = 0x01, + VIDEO_CS_ITF_VS_OUTPUT_HEADER = 0x02, + VIDEO_CS_ITF_VS_STILL_IMAGE_FRAME = 0x03, + VIDEO_CS_ITF_VS_FORMAT_UNCOMPRESSED = 0x04, + VIDEO_CS_ITF_VS_FRAME_UNCOMPRESSED = 0x05, + VIDEO_CS_ITF_VS_FORMAT_MJPEG = 0x06, + VIDEO_CS_ITF_VS_FRAME_MJPEG = 0x07, + VIDEO_CS_ITF_VS_FORMAT_MPEG2TS = 0x0A, + VIDEO_CS_ITF_VS_FORMAT_DV = 0x0C, + VIDEO_CS_ITF_VS_COLORFORMAT = 0x0D, + VIDEO_CS_ITF_VS_FORMAT_FRAME_BASED = 0x10, + VIDEO_CS_ITF_VS_FRAME_FRAME_BASED = 0x11, + VIDEO_CS_ITF_VS_FORMAT_STREAM_BASED = 0x12, + VIDEO_CS_ITF_VS_FORMAT_H264 = 0x13, + VIDEO_CS_ITF_VS_FRAME_H264 = 0x14, + VIDEO_CS_ITF_VS_FORMAT_H264_SIMULCAST = 0x15, + VIDEO_CS_ITF_VS_FORMAT_VP8 = 0x16, + VIDEO_CS_ITF_VS_FRAME_VP8 = 0x17, + VIDEO_CS_ITF_VS_FORMAT_VP8_SIMULCAST = 0x18, +} video_cs_vs_interface_subtype_t; + +/* A.7. Class-Specific Endpoint Descriptor Subtypes */ +typedef enum { + VIDEO_CS_EP_UNDEFINED = 0x00, + VIDEO_CS_EP_GENERAL, + VIDEO_CS_EP_ENDPOINT, + VIDEO_CS_EP_INTERRUPT +} video_cs_ep_subtype_t; + +/* A.8 Class-Specific Request Codes */ +typedef enum { + VIDEO_REQUEST_UNDEFINED = 0x00, + VIDEO_REQUEST_SET_CUR = 0x01, + VIDEO_REQUEST_SET_CUR_ALL = 0x11, + VIDEO_REQUEST_GET_CUR = 0x81, + VIDEO_REQUEST_GET_MIN = 0x82, + VIDEO_REQUEST_GET_MAX = 0x83, + VIDEO_REQUEST_GET_RES = 0x84, + VIDEO_REQUEST_GET_LEN = 0x85, + VIDEO_REQUEST_GET_INFO = 0x86, + VIDEO_REQUEST_GET_DEF = 0x87, + VIDEO_REQUEST_GET_CUR_ALL = 0x91, + VIDEO_REQUEST_GET_MIN_ALL = 0x92, + VIDEO_REQUEST_GET_MAX_ALL = 0x93, + VIDEO_REQUEST_GET_RES_ALL = 0x94, + VIDEO_REQUEST_GET_DEF_ALL = 0x97 +} video_control_request_t; + +/* A.9.1 VideoControl Interface Control Selectors */ +typedef enum { + VIDEO_VC_CTL_UNDEFINED = 0x00, + VIDEO_VC_CTL_VIDEO_POWER_MODE, // 0x01 + VIDEO_VC_CTL_REQUEST_ERROR_CODE, // 0x02 +} video_interface_control_selector_t; + +/* A.9.8 VideoStreaming Interface Control Selectors */ +typedef enum { + VIDEO_VS_CTL_UNDEFINED = 0x00, + VIDEO_VS_CTL_PROBE, // 0x01 + VIDEO_VS_CTL_COMMIT, // 0x02 + VIDEO_VS_CTL_STILL_PROBE, // 0x03 + VIDEO_VS_CTL_STILL_COMMIT, // 0x04 + VIDEO_VS_CTL_STILL_IMAGE_TRIGGER, // 0x05 + VIDEO_VS_CTL_STREAM_ERROR_CODE, // 0x06 + VIDEO_VS_CTL_GENERATE_KEY_FRAME, // 0x07 + VIDEO_VS_CTL_UPDATE_FRAME_SEGMENT, // 0x08 + VIDEO_VS_CTL_SYNCH_DELAY_CONTROL, // 0x09 + +} video_interface_streaming_selector_t; + +/* B. Terminal Types */ +typedef enum { + // Terminal + VIDEO_TT_VENDOR_SPECIFIC = 0x0100, + VIDEO_TT_STREAMING = 0x0101, + + // Input + VIDEO_ITT_VENDOR_SPECIFIC = 0x0200, + VIDEO_ITT_CAMERA = 0x0201, + VIDEO_ITT_MEDIA_TRANSPORT_INPUT = 0x0202, + + // Output + VIDEO_OTT_VENDOR_SPECIFIC = 0x0300, + VIDEO_OTT_DISPLAY = 0x0301, + VIDEO_OTT_MEDIA_TRANSPORT_OUTPUT = 0x0302, + + // External + VIDEO_ETT_VENDOR_SPEIFIC = 0x0400, + VIDEO_ETT_COMPOSITE_CONNECTOR = 0x0401, + VIDEO_ETT_SVIDEO_CONNECTOR = 0x0402, + VIDEO_ETT_COMPONENT_CONNECTOR = 0x0403, +} video_terminal_type_t; + +//--------------------------------------------------------------------+ +// Video Control (VC) Descriptors +//--------------------------------------------------------------------+ + +/* 2.3.4.2 */ +#define tusb_desc_video_control_header_nitf_t(_nitf) \ + struct TU_ATTR_PACKED { \ + uint8_t bLength; \ + uint8_t bDescriptorType; \ + uint8_t bDescriptorSubType; \ + uint16_t bcdUVC; \ + uint16_t wTotalLength; \ + uint32_t dwClockFrequency; /* deprecated */ \ + uint8_t bInCollection; \ + uint8_t baInterfaceNr[_nitf]; \ + } + +typedef tusb_desc_video_control_header_nitf_t() tusb_desc_video_control_header_t; //-V2586 incorrectly detected as flexible array +typedef tusb_desc_video_control_header_nitf_t(1) tusb_desc_video_control_header_1itf_t; //-V2586 incorrectly detected as flexible array +typedef tusb_desc_video_control_header_nitf_t(2) tusb_desc_video_control_header_2itf_t; //-V2586 incorrectly detected as flexible array +typedef tusb_desc_video_control_header_nitf_t(3) tusb_desc_video_control_header_3itf_t; //-V2586 incorrectly detected as flexible array +typedef tusb_desc_video_control_header_nitf_t(4) tusb_desc_video_control_header_4itf_t; //-V2586 incorrectly detected as flexible array + +typedef struct TU_ATTR_PACKED { + uint8_t bLength; + uint8_t bDescriptorType; + uint8_t bDescriptorSubType; + uint8_t bTerminalID; + uint16_t wTerminalType; + uint8_t bAssocTerminal; + uint8_t iTerminal; +} tusb_desc_video_control_input_terminal_t; + +TU_VERIFY_STATIC(sizeof(tusb_desc_video_control_input_terminal_t) == 8, "size is not correct"); + +typedef struct TU_ATTR_PACKED { + uint8_t bLength; + uint8_t bDescriptorType; + uint8_t bDescriptorSubType; + uint8_t bTerminalID; + uint16_t wTerminalType; + uint8_t bAssocTerminal; + uint8_t bSourceID; + uint8_t iTerminal; +} tusb_desc_video_control_output_terminal_t; + +TU_VERIFY_STATIC(sizeof(tusb_desc_video_control_output_terminal_t) == 9, "size is not correct"); + +typedef struct TU_ATTR_PACKED { + uint8_t bLength; + uint8_t bDescriptorType; + uint8_t bDescriptorSubType; + uint8_t bTerminalID; + uint16_t wTerminalType; + uint8_t bAssocTerminal; + uint8_t iTerminal; + + uint16_t wObjectiveFocalLengthMin; + uint16_t wObjectiveFocalLengthMax; + uint16_t wOcularFocalLength; + uint8_t bControlSize; + uint8_t bmControls[3]; +} tusb_desc_video_control_camera_terminal_t; + +TU_VERIFY_STATIC(sizeof(tusb_desc_video_control_camera_terminal_t) == 18, "size is not correct"); + +//--------------------------------------------------------------------+ +// Video Streaming (VS) Descriptors +//--------------------------------------------------------------------+ + +/* 3.9.2.1 */ +#define tusb_desc_video_streaming_input_header_nbyte_t(_nb) \ + struct TU_ATTR_PACKED { \ + uint8_t bLength; \ + uint8_t bDescriptorType; \ + uint8_t bDescriptorSubType; \ + uint8_t bNumFormats; /* Number of video payload Format descriptors for this interface */ \ + uint16_t wTotalLength; \ + uint8_t bEndpointAddress; \ + uint8_t bmInfo; /* Bit 0: dynamic format change supported */ \ + uint8_t bTerminalLink; \ + uint8_t bStillCaptureMethod; \ + uint8_t bTriggerSupport; /* Hardware trigger supported */ \ + uint8_t bTriggerUsage; \ + uint8_t bControlSize; /* sizeof of each control item */ \ + uint8_t bmaControls[_nb]; \ + } + +typedef tusb_desc_video_streaming_input_header_nbyte_t() tusb_desc_video_streaming_input_header_t; +typedef tusb_desc_video_streaming_input_header_nbyte_t(1) tusb_desc_video_streaming_input_header_1byte_t; +typedef tusb_desc_video_streaming_input_header_nbyte_t(2) tusb_desc_video_streaming_input_header_2byte_t; +typedef tusb_desc_video_streaming_input_header_nbyte_t(3) tusb_desc_video_streaming_input_header_3byte_t; +typedef tusb_desc_video_streaming_input_header_nbyte_t(4) tusb_desc_video_streaming_input_header_4byte_t; + +/* 3.9.2.2 */ +typedef struct TU_ATTR_PACKED { + uint8_t bLength; + uint8_t bDescriptorType; + uint8_t bDescriptorSubType; + uint8_t bNumFormats; + uint16_t wTotalLength; + uint8_t bEndpointAddress; + uint8_t bTerminalLink; + uint8_t bControlSize; + uint8_t bmaControls[]; +} tusb_desc_video_streaming_output_header_t; + +typedef struct TU_ATTR_PACKED { + uint8_t bLength; + uint8_t bDescriptorType; + uint8_t bDescriptorSubType; + uint8_t bNumFormats; + uint16_t wTotalLength; + uint8_t bEndpointAddress; + union { + struct { + uint8_t bmInfo; + uint8_t bTerminalLink; + uint8_t bStillCaptureMethod; + uint8_t bTriggerSupport; + uint8_t bTriggerUsage; + uint8_t bControlSize; + uint8_t bmaControls[]; + } input; + struct { + uint8_t bEndpointAddress; + uint8_t bTerminalLink; + uint8_t bControlSize; + uint8_t bmaControls[]; + } output; + }; +} tusb_desc_video_streaming_inout_header_t; + +// 3.9.2.6 Color Matching Descriptor +typedef struct TU_ATTR_PACKED { + uint8_t bLength; + uint8_t bDescriptorType; + uint8_t bDescriptorSubType; + uint8_t bColorPrimaries; + uint8_t bTransferCharacteristics; + uint8_t bMatrixCoefficients; +} tusb_desc_video_streaming_color_matching_t; + +TU_VERIFY_STATIC(sizeof(tusb_desc_video_streaming_color_matching_t) == 6, "size is not correct"); + +//--------------------------------------------------------------------+ +// Format and Frame Descriptor +// Note: bFormatIndex & bFrameIndex are 1-based index +//--------------------------------------------------------------------+ + +//------------- Uncompressed -------------// +// Uncompressed payload specs: 3.1.1 format descriptor +typedef struct TU_ATTR_PACKED { + uint8_t bLength; + uint8_t bDescriptorType; + uint8_t bDescriptorSubType; + uint8_t bFormatIndex; + uint8_t bNumFrameDescriptors; // Number of frame descriptors for this format + uint8_t guidFormat[16]; + uint8_t bBitsPerPixel; + uint8_t bDefaultFrameIndex; + uint8_t bAspectRatioX; + uint8_t bAspectRatioY; + uint8_t bmInterlaceFlags; + uint8_t bCopyProtect; +} tusb_desc_video_format_uncompressed_t; + +TU_VERIFY_STATIC(sizeof(tusb_desc_video_format_uncompressed_t) == 27, "size is not correct"); + +// Uncompressed payload specs: 3.1.2 frame descriptor +#define tusb_desc_video_frame_uncompressed_nint_t(_nint) \ + struct TU_ATTR_PACKED { \ + uint8_t bLength; \ + uint8_t bDescriptorType; \ + uint8_t bDescriptorSubType; \ + uint8_t bFrameIndex; \ + uint8_t bmCapabilities; \ + uint16_t wWidth; \ + uint16_t wHeight; \ + uint32_t dwMinBitRate; \ + uint32_t dwMaxBitRate; \ + uint32_t dwMaxVideoFrameBufferSize; /* deprecated in 1.5 */ \ + uint32_t dwDefaultFrameInterval; /* 100ns unit */\ + uint8_t bFrameIntervalType; \ + uint32_t dwFrameInterval[_nint]; \ + } + +typedef tusb_desc_video_frame_uncompressed_nint_t() tusb_desc_video_frame_uncompressed_t; +typedef tusb_desc_video_frame_uncompressed_nint_t(1) tusb_desc_video_frame_uncompressed_1int_t; +typedef tusb_desc_video_frame_uncompressed_nint_t(2) tusb_desc_video_frame_uncompressed_2int_t; +typedef tusb_desc_video_frame_uncompressed_nint_t(3) tusb_desc_video_frame_uncompressed_3int_t; +typedef tusb_desc_video_frame_uncompressed_nint_t(4) tusb_desc_video_frame_uncompressed_4int_t; + +// continuous = 3 intervals: min, max, step +typedef tusb_desc_video_frame_uncompressed_3int_t tusb_desc_video_frame_uncompressed_continuous_t; + +TU_VERIFY_STATIC(sizeof(tusb_desc_video_frame_uncompressed_continuous_t) == 38, "size is not correct"); + +//------------- MJPEG -------------// +// MJPEG payload specs: 3.1.1 format descriptor +typedef struct TU_ATTR_PACKED { + uint8_t bLength; + uint8_t bDescriptorType; + uint8_t bDescriptorSubType; + uint8_t bFormatIndex; + uint8_t bNumFrameDescriptors; + uint8_t bmFlags; // Bit 0: fixed size samples (1 = yes) + uint8_t bDefaultFrameIndex; + uint8_t bAspectRatioX; + uint8_t bAspectRatioY; + uint8_t bmInterlaceFlags; + uint8_t bCopyProtect; +} tusb_desc_video_format_mjpeg_t; + +TU_VERIFY_STATIC(sizeof(tusb_desc_video_format_mjpeg_t) == 11, "size is not correct"); + +// MJPEG payload specs: 3.1.2 frame descriptor (same as uncompressed) +typedef tusb_desc_video_frame_uncompressed_t tusb_desc_video_frame_mjpeg_t; +typedef tusb_desc_video_frame_uncompressed_1int_t tusb_desc_video_frame_mjpeg_1int_t; +typedef tusb_desc_video_frame_uncompressed_2int_t tusb_desc_video_frame_mjpeg_2int_t; +typedef tusb_desc_video_frame_uncompressed_3int_t tusb_desc_video_frame_mjpeg_3int_t; +typedef tusb_desc_video_frame_uncompressed_4int_t tusb_desc_video_frame_mjpeg_4int_t; + +// continuous = 3 intervals: min, max, step +typedef tusb_desc_video_frame_mjpeg_3int_t tusb_desc_video_frame_mjpeg_continuous_t; + +//------------- DV -------------// +// DV payload specs: 3.1.1 +typedef struct TU_ATTR_PACKED { + uint8_t bLength; + uint8_t bDescriptorType; + uint8_t bDescriptorSubType; + uint8_t bFormatIndex; + uint32_t dwMaxVideoFrameBufferSize; /* deprecated */ + uint8_t bFormatType; +} tusb_desc_video_format_dv_t; + +// Frame Based payload specs: 3.1.1 +typedef struct TU_ATTR_PACKED { + uint8_t bLength; + uint8_t bDescriptorType; + uint8_t bDescriptorSubType; + uint8_t bFormatIndex; + uint8_t bNumFrameDescriptors; + uint8_t guidFormat[16]; + uint8_t bBitsPerPixel; + uint8_t bDefaultFrameIndex; + uint8_t bAspectRatioX; + uint8_t bAspectRatioY; + uint8_t bmInterlaceFlags; + uint8_t bCopyProtect; + uint8_t bVaribaleSize; +} tusb_desc_video_format_framebased_t; + +typedef struct TU_ATTR_PACKED { + uint8_t bLength; + uint8_t bDescriptorType; + uint8_t bDescriptorSubType; + uint8_t bFrameIndex; + uint8_t bmCapabilities; + uint16_t wWidth; + uint16_t wHeight; + uint32_t dwMinBitRate; + uint32_t dwMaxBitRate; + uint32_t dwDefaultFrameInterval; + uint8_t bFrameIntervalType; + uint32_t dwBytesPerLine; + uint32_t dwFrameInterval[]; +} tusb_desc_video_frame_framebased_t; + +//--------------------------------------------------------------------+ +// Requests +//--------------------------------------------------------------------+ + +/* 2.4.3.3 */ +typedef struct TU_ATTR_PACKED { + uint8_t bHeaderLength; + union { + uint8_t bmHeaderInfo; + struct { + uint8_t FrameID: 1; + uint8_t EndOfFrame: 1; + uint8_t PresentationTime: 1; + uint8_t SourceClockReference: 1; + uint8_t PayloadSpecific: 1; + uint8_t StillImage: 1; + uint8_t Error: 1; + uint8_t EndOfHeader: 1; + }; + }; +} tusb_video_payload_header_t; + +/* 4.3.1.1 */ +typedef struct TU_ATTR_PACKED { + union { + uint8_t bmHint; + struct TU_ATTR_PACKED { + uint16_t dwFrameInterval: 1; + uint16_t wKeyFrameRatel : 1; + uint16_t wPFrameRate : 1; + uint16_t wCompQuality : 1; + uint16_t wCompWindowSize: 1; + uint16_t : 0; + } Hint; + }; + uint8_t bFormatIndex; + uint8_t bFrameIndex; + uint32_t dwFrameInterval; + uint16_t wKeyFrameRate; + uint16_t wPFrameRate; + uint16_t wCompQuality; + uint16_t wCompWindowSize; + uint16_t wDelay; + uint32_t dwMaxVideoFrameSize; + uint32_t dwMaxPayloadTransferSize; + uint32_t dwClockFrequency; + union { + uint8_t bmFramingInfo; + struct TU_ATTR_PACKED { + uint8_t FrameID : 1; + uint8_t EndOfFrame: 1; + uint8_t EndOfSlice: 1; + uint8_t : 0; + } FramingInfo; + }; + uint8_t bPreferedVersion; + uint8_t bMinVersion; + uint8_t bMaxVersion; + uint8_t bUsage; + uint8_t bBitDepthLuma; + uint8_t bmSettings; + uint8_t bMaxNumberOfRefFramesPlus1; + uint16_t bmRateControlModes; + uint64_t bmLayoutPerStream; +} video_probe_and_commit_control_t; + +TU_VERIFY_STATIC( sizeof(video_probe_and_commit_control_t) == 48, "size is not correct"); + +#define TUD_VIDEO_DESC_IAD_LEN 8 +#define TUD_VIDEO_DESC_STD_VC_LEN 9 +#define TUD_VIDEO_DESC_CS_VC_LEN 12 +#define TUD_VIDEO_DESC_INPUT_TERM_LEN 8 +#define TUD_VIDEO_DESC_OUTPUT_TERM_LEN 9 +#define TUD_VIDEO_DESC_CAMERA_TERM_LEN 18 +#define TUD_VIDEO_DESC_STD_VS_LEN 9 +#define TUD_VIDEO_DESC_CS_VS_IN_LEN 13 +#define TUD_VIDEO_DESC_CS_VS_OUT_LEN 9 +#define TUD_VIDEO_DESC_CS_VS_FMT_UNCOMPR_LEN 27 +#define TUD_VIDEO_DESC_CS_VS_FMT_MJPEG_LEN 11 +#define TUD_VIDEO_DESC_CS_VS_FMT_FRAME_BASED_LEN 28 +#define TUD_VIDEO_DESC_CS_VS_FRM_UNCOMPR_CONT_LEN 38 +#define TUD_VIDEO_DESC_CS_VS_FRM_UNCOMPR_DISC_LEN 26 +#define TUD_VIDEO_DESC_CS_VS_FRM_MJPEG_CONT_LEN 38 +#define TUD_VIDEO_DESC_CS_VS_FRM_MJPEG_DISC_LEN 26 +#define TUD_VIDEO_DESC_CS_VS_FRM_FRAME_BASED_CONT_LEN 38 +#define TUD_VIDEO_DESC_CS_VS_FRM_FRAME_BASED_DISC_LEN 26 +#define TUD_VIDEO_DESC_CS_VS_COLOR_MATCHING_LEN 6 + +/* 2.2 compression formats */ +#define TUD_VIDEO_GUID_YUY2 0x59,0x55,0x59,0x32,0x00,0x00,0x10,0x00,0x80,0x00,0x00,0xAA,0x00,0x38,0x9B,0x71 +#define TUD_VIDEO_GUID_NV12 0x4E,0x56,0x31,0x32,0x00,0x00,0x10,0x00,0x80,0x00,0x00,0xAA,0x00,0x38,0x9B,0x71 +#define TUD_VIDEO_GUID_M420 0x4D,0x34,0x32,0x30,0x00,0x00,0x10,0x00,0x80,0x00,0x00,0xAA,0x00,0x38,0x9B,0x71 +#define TUD_VIDEO_GUID_I420 0x49,0x34,0x32,0x30,0x00,0x00,0x10,0x00,0x80,0x00,0x00,0xAA,0x00,0x38,0x9B,0x71 +#define TUD_VIDEO_GUID_H264 0x48,0x32,0x36,0x34,0x00,0x00,0x10,0x00,0x80,0x00,0x00,0xAA,0x00,0x38,0x9B,0x71 + +#define TUD_VIDEO_DESC_IAD(_firstitf, _nitfs, _stridx) \ + TUD_VIDEO_DESC_IAD_LEN, TUSB_DESC_INTERFACE_ASSOCIATION, \ + _firstitf, _nitfs, TUSB_CLASS_VIDEO, VIDEO_SUBCLASS_INTERFACE_COLLECTION, \ + VIDEO_ITF_PROTOCOL_UNDEFINED, _stridx + +#define TUD_VIDEO_DESC_STD_VC(_itfnum, _nEPs, _stridx) \ + TUD_VIDEO_DESC_STD_VC_LEN, TUSB_DESC_INTERFACE, _itfnum, /* fixed to zero */ 0x00, \ + _nEPs, TUSB_CLASS_VIDEO, VIDEO_SUBCLASS_CONTROL, VIDEO_ITF_PROTOCOL_15, _stridx + +/* 3.7.2 */ +#define TUD_VIDEO_DESC_CS_VC(_bcdUVC, _totallen, _clkfreq, ...) \ + TUD_VIDEO_DESC_CS_VC_LEN + (TU_ARGS_NUM(__VA_ARGS__)), TUSB_DESC_CS_INTERFACE, VIDEO_CS_ITF_VC_HEADER, \ + U16_TO_U8S_LE(_bcdUVC), U16_TO_U8S_LE((_totallen) + TUD_VIDEO_DESC_CS_VC_LEN + (TU_ARGS_NUM(__VA_ARGS__))), \ + U32_TO_U8S_LE(_clkfreq), TU_ARGS_NUM(__VA_ARGS__), __VA_ARGS__ + +/* 3.7.2.1 */ +#define TUD_VIDEO_DESC_INPUT_TERM(_tid, _tt, _at, _stridx) \ + TUD_VIDEO_DESC_INPUT_TERM_LEN, TUSB_DESC_CS_INTERFACE, VIDEO_CS_ITF_VC_INPUT_TERMINAL, \ + _tid, U16_TO_U8S_LE(_tt), _at, _stridx + +/* 3.7.2.2 */ +#define TUD_VIDEO_DESC_OUTPUT_TERM(_tid, _tt, _at, _srcid, _stridx) \ + TUD_VIDEO_DESC_OUTPUT_TERM_LEN, TUSB_DESC_CS_INTERFACE, VIDEO_CS_ITF_VC_OUTPUT_TERMINAL, \ + _tid, U16_TO_U8S_LE(_tt), _at, _srcid, _stridx + +/* 3.7.2.3 */ +#define TUD_VIDEO_DESC_CAMERA_TERM(_tid, _at, _stridx, _focal_min, _focal_max, _focal, _ctls) \ + TUD_VIDEO_DESC_CAMERA_TERM_LEN, TUSB_DESC_CS_INTERFACE, VIDEO_CS_ITF_VC_INPUT_TERMINAL, \ + _tid, U16_TO_U8S_LE(VIDEO_ITT_CAMERA), _at, _stridx, \ + U16_TO_U8S_LE(_focal_min), U16_TO_U8S_LE(_focal_max), U16_TO_U8S_LE(_focal), 3, \ + TU_U32_BYTE0(_ctls), TU_U32_BYTE1(_ctls), TU_U32_BYTE2(_ctls) + +/* 3.9.1 */ +#define TUD_VIDEO_DESC_STD_VS(_itfnum, _alt, _epn, _stridx) \ + TUD_VIDEO_DESC_STD_VS_LEN, TUSB_DESC_INTERFACE, _itfnum, _alt, \ + _epn, TUSB_CLASS_VIDEO, VIDEO_SUBCLASS_STREAMING, VIDEO_ITF_PROTOCOL_15, _stridx + +/* 3.9.2.1 */ +#define TUD_VIDEO_DESC_CS_VS_INPUT(_numfmt, _totallen, _ep, _inf, _termlnk, _sticaptmeth, _trgspt, _trgusg, ...) \ + TUD_VIDEO_DESC_CS_VS_IN_LEN + (_numfmt) * (TU_ARGS_NUM(__VA_ARGS__)), TUSB_DESC_CS_INTERFACE, \ + VIDEO_CS_ITF_VS_INPUT_HEADER, _numfmt, \ + U16_TO_U8S_LE((_totallen) + TUD_VIDEO_DESC_CS_VS_IN_LEN + (_numfmt) * (TU_ARGS_NUM(__VA_ARGS__))), \ + _ep, _inf, _termlnk, _sticaptmeth, _trgspt, _trgusg, (TU_ARGS_NUM(__VA_ARGS__)), __VA_ARGS__ + +/* 3.9.2.2 */ +#define TUD_VIDEO_DESC_CS_VS_OUTPUT(_numfmt, _totallen, _ep, _inf, _termlnk, ...) \ + TUD_VIDEO_DESC_CS_VS_OUT_LEN + (_numfmt) * (TU_ARGS_NUM(__VA_ARGS__)), TUSB_DESC_CS_INTERFACE, \ + VIDEO_CS_ITF_VS_OUTPUT_HEADER, _numfmt, \ + U16_TO_U8S_LE((_totallen) + TUD_VIDEO_DESC_CS_VS_OUT_LEN + (_numfmt) * (TU_ARGS_NUM(__VA_ARGS__))), \ + _ep, _inf, _termlnk, (TU_ARGS_NUM(__VA_ARGS__)), __VA_ARGS__ + +/* Uncompressed 3.1.1 */ +#define TUD_VIDEO_GUID(_g0,_g1,_g2,_g3,_g4,_g5,_g6,_g7,_g8,_g9,_g10,_g11,_g12,_g13,_g14,_g15) _g0,_g1,_g2,_g3,_g4,_g5,_g6,_g7,_g8,_g9,_g10,_g11,_g12,_g13,_g14,_g15 + +#define TUD_VIDEO_DESC_CS_VS_FMT_UNCOMPR(_fmtidx, _numfrmdesc, \ + _guid, _bitsperpix, _frmidx, _asrx, _asry, _interlace, _cp) \ + TUD_VIDEO_DESC_CS_VS_FMT_UNCOMPR_LEN, TUSB_DESC_CS_INTERFACE, VIDEO_CS_ITF_VS_FORMAT_UNCOMPRESSED, \ + _fmtidx, _numfrmdesc, TUD_VIDEO_GUID(_guid), \ + _bitsperpix, _frmidx, _asrx, _asry, _interlace, _cp + +/* Uncompressed 3.1.2 Table 3-3 */ +#define TUD_VIDEO_DESC_CS_VS_FRM_UNCOMPR_CONT(_frmidx, _cap, _width, _height, _minbr, _maxbr, _maxfrmbufsz, _frminterval, _minfrminterval, _maxfrminterval, _frmintervalstep) \ + TUD_VIDEO_DESC_CS_VS_FRM_UNCOMPR_CONT_LEN, TUSB_DESC_CS_INTERFACE, VIDEO_CS_ITF_VS_FRAME_UNCOMPRESSED, \ + _frmidx, _cap, U16_TO_U8S_LE(_width), U16_TO_U8S_LE(_height), U32_TO_U8S_LE(_minbr), U32_TO_U8S_LE(_maxbr), \ + U32_TO_U8S_LE(_maxfrmbufsz), U32_TO_U8S_LE(_frminterval), 0, \ + U32_TO_U8S_LE(_minfrminterval), U32_TO_U8S_LE(_maxfrminterval), U32_TO_U8S_LE(_frmintervalstep) + +/* Uncompressed 3.1.2 Table 3-4 */ +#define TUD_VIDEO_DESC_CS_VS_FRM_UNCOMPR_DISC(_frmidx, _cap, _width, _height, _minbr, _maxbr, _maxfrmbufsz, _frminterval, ...) \ + TUD_VIDEO_DESC_CS_VS_FRM_UNCOMPR_DISC_LEN + (TU_ARGS_NUM(__VA_ARGS__)) * 4, \ + TUSB_DESC_CS_INTERFACE, VIDEO_CS_ITF_VS_FRAME_UNCOMPRESSED, \ + _frmidx, _cap, U16_TO_U8S_LE(_width), U16_TO_U8S_LE(_height), U32_TO_U8S_LE(_minbr), U32_TO_U8S_LE(_maxbr), \ + U32_TO_U8S_LE(_maxfrmbufsz), U32_TO_U8S_LE(_frminterval), (TU_ARGS_NUM(__VA_ARGS__)), __VA_ARGS__ + +/* Motion-JPEG 3.1.1 Table 3-1 */ +#define TUD_VIDEO_DESC_CS_VS_FMT_MJPEG(_fmtidx, _numfrmdesc, _fixed_sz, _frmidx, _asrx, _asry, _interlace, _cp) \ + TUD_VIDEO_DESC_CS_VS_FMT_MJPEG_LEN, TUSB_DESC_CS_INTERFACE, VIDEO_CS_ITF_VS_FORMAT_MJPEG, \ + _fmtidx, _numfrmdesc, _fixed_sz, _frmidx, _asrx, _asry, _interlace, _cp + +/* Motion-JPEG 3.1.1 Table 3-2 and 3-3 */ +#define TUD_VIDEO_DESC_CS_VS_FRM_MJPEG_CONT(_frmidx, _cap, _width, _height, _minbr, _maxbr, _maxfrmbufsz, _frminterval, _minfrminterval, _maxfrminterval, _frmintervalstep) \ + TUD_VIDEO_DESC_CS_VS_FRM_MJPEG_CONT_LEN, TUSB_DESC_CS_INTERFACE, VIDEO_CS_ITF_VS_FRAME_MJPEG, \ + _frmidx, _cap, U16_TO_U8S_LE(_width), U16_TO_U8S_LE(_height), U32_TO_U8S_LE(_minbr), U32_TO_U8S_LE(_maxbr), \ + U32_TO_U8S_LE(_maxfrmbufsz), U32_TO_U8S_LE(_frminterval), 0, \ + U32_TO_U8S_LE(_minfrminterval), U32_TO_U8S_LE(_maxfrminterval), U32_TO_U8S_LE(_frmintervalstep) + +/* Motion-JPEG 3.1.1 Table 3-2 and 3-4 */ +#define TUD_VIDEO_DESC_CS_VS_FRM_MJPEG_DISC(_frmidx, _cap, _width, _height, _minbr, _maxbr, _maxfrmbufsz, _frminterval, ...) \ + TUD_VIDEO_DESC_CS_VS_FRM_MJPEG_DISC_LEN + (TU_ARGS_NUM(__VA_ARGS__)) * 4, \ + TUSB_DESC_CS_INTERFACE, VIDEO_CS_ITF_VS_FRAME_MJPEG, \ + _frmidx, _cap, U16_TO_U8S_LE(_width), U16_TO_U8S_LE(_height), U32_TO_U8S_LE(_minbr), U32_TO_U8S_LE(_maxbr), \ + U32_TO_U8S_LE(_maxfrmbufsz), U32_TO_U8S_LE(_frminterval), (TU_ARGS_NUM(__VA_ARGS__)), __VA_ARGS__ + +/* Motion-Frame-Based 3.1.1 Table 3-1 */ +#define TUD_VIDEO_DESC_CS_VS_FMT_FRAME_BASED(_fmtidx, _numfrmdesc, _guid, _bitsperpix, _frmidx, _asrx, _asry, _interlace, _cp, _variablesize) \ + TUD_VIDEO_DESC_CS_VS_FMT_FRAME_BASED_LEN, TUSB_DESC_CS_INTERFACE, VIDEO_CS_ITF_VS_FORMAT_FRAME_BASED, \ + _fmtidx, _numfrmdesc, TUD_VIDEO_GUID(_guid), _bitsperpix, _frmidx, _asrx, _asry, _interlace, _cp, _variablesize + +/* Motion-Frame-Based 3.1.1 Table 3-2 and 3-3 */ +#define TUD_VIDEO_DESC_CS_VS_FRM_FRAME_BASED_CONT(_frmidx, _cap, _width, _height, _minbr, _maxbr, _frminterval, _bytesperline, _minfrminterval, _maxfrminterval, _frmintervalstep) \ + TUD_VIDEO_DESC_CS_VS_FRM_FRAME_BASED_CONT_LEN, TUSB_DESC_CS_INTERFACE, VIDEO_CS_ITF_VS_FRAME_FRAME_BASED, \ + _frmidx, _cap, U16_TO_U8S_LE(_width), U16_TO_U8S_LE(_height), U32_TO_U8S_LE(_minbr), U32_TO_U8S_LE(_maxbr), \ + U32_TO_U8S_LE(_frminterval), 0, U32_TO_U8S_LE(_bytesperline), \ + U32_TO_U8S_LE(_minfrminterval), U32_TO_U8S_LE(_maxfrminterval), U32_TO_U8S_LE(_frmintervalstep) + +/* Motion-Frame-Based 3.1.1 Table 3-2 and 3-4 */ +#define TUD_VIDEO_DESC_CS_VS_FRM_FRAME_BASED_DISC(_frmidx, _cap, _width, _height, _minbr, _maxbr, _frminterval, _bytesperline, ...) \ + TUD_VIDEO_DESC_CS_VS_FRM_FRAME_BASED_DISC_LEN + (TU_ARGS_NUM(__VA_ARGS__)) * 4, \ + TUSB_DESC_CS_INTERFACE, VIDEO_CS_ITF_VS_FRAME_FRAME_BASED, \ + _frmidx, _cap, U16_TO_U8S_LE(_width), U16_TO_U8S_LE(_height), U32_TO_U8S_LE(_minbr), U32_TO_U8S_LE(_maxbr), \ +U32_TO_U8S_LE(_frminterval), U32_TO_U8S_LE(_bytesperline), (TU_ARGS_NUM(__VA_ARGS__)), __VA_ARGS__ + +/* 3.9.2.6 */ +#define TUD_VIDEO_DESC_CS_VS_COLOR_MATCHING(_color, _trns, _mat) \ + TUD_VIDEO_DESC_CS_VS_COLOR_MATCHING_LEN, \ + TUSB_DESC_CS_INTERFACE, VIDEO_CS_ITF_VS_COLORFORMAT, \ + _color, _trns, _mat + +/* 3.10.1.1 */ +#define TUD_VIDEO_DESC_EP_ISO(_ep, _epsize, _ep_interval) \ + 7, TUSB_DESC_ENDPOINT, _ep, (uint8_t) (TUSB_XFER_ISOCHRONOUS | TUSB_ISO_EP_ATT_ASYNCHRONOUS),\ + U16_TO_U8S_LE(_epsize), _ep_interval + +/* 3.10.1.2 */ +#define TUD_VIDEO_DESC_EP_BULK(_ep, _epsize, _ep_interval) \ + 7, TUSB_DESC_ENDPOINT, _ep, TUSB_XFER_BULK, U16_TO_U8S_LE(_epsize), _ep_interval + +#endif diff --git a/libraries/Adafruit_TinyUSB_Arduino/src/class/video/video_device.c b/libraries/Adafruit_TinyUSB_Arduino/src/class/video/video_device.c new file mode 100644 index 0000000..bbcfe45 --- /dev/null +++ b/libraries/Adafruit_TinyUSB_Arduino/src/class/video/video_device.c @@ -0,0 +1,1527 @@ +/* + * The MIT License (MIT) + * + * Copyright (c) 2021 Koji KITAYAMA + * 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. + * + * This file is part of the TinyUSB stack. + */ + +#include "tusb_option.h" + +#if (CFG_TUD_ENABLED && CFG_TUD_VIDEO && CFG_TUD_VIDEO_STREAMING) + +#include "device/usbd.h" +#include "device/usbd_pvt.h" + +#include "video_device.h" + +// Level where CFG_TUSB_DEBUG must be at least for this driver is logged +#ifndef CFG_TUD_VIDEO_LOG_LEVEL + #define CFG_TUD_VIDEO_LOG_LEVEL CFG_TUD_LOG_LEVEL +#endif + +#define TU_LOG_DRV(...) TU_LOG(CFG_TUD_VIDEO_LOG_LEVEL, __VA_ARGS__) + +//--------------------------------------------------------------------+ +// MACRO CONSTANT TYPEDEF +//--------------------------------------------------------------------+ +#define VS_STATE_PROBING 0 /* Configuration in progress */ +#define VS_STATE_COMMITTED 1 /* Ready for streaming or Streaming via bulk endpoint */ +#define VS_STATE_STREAMING 2 /* Streaming via isochronous endpoint */ + +typedef struct { + tusb_desc_interface_t std; + tusb_desc_video_control_header_t ctl; +} tusb_desc_vc_itf_t; + +typedef struct { + tusb_desc_interface_t std; + tusb_desc_video_streaming_inout_header_t stm; +} tusb_desc_vs_itf_t; + +typedef union { + tusb_desc_video_control_header_t ctl; + tusb_desc_video_streaming_inout_header_t stm; +} tusb_desc_video_itf_hdr_t; + +typedef struct TU_ATTR_PACKED { + uint8_t bLength; + uint8_t bDescriptorType; + uint8_t bDescriptorSubtype; + uint8_t bEntityId; +} tusb_desc_cs_video_entity_itf_t; + +typedef struct TU_ATTR_PACKED { + uint8_t bLength; + uint8_t bDescriptorType; + uint8_t bDescriptorSubtype; + uint16_t wMaxTransferSize; +} tusb_desc_cs_video_vc_ep_t; + +typedef union { + struct TU_ATTR_PACKED { + uint8_t bLength; + uint8_t bDescriptorType; + uint8_t bDescriptorSubType; + uint8_t bFormatIndex; + uint8_t bNumFrameDescriptors; + }; + tusb_desc_video_format_uncompressed_t uncompressed; + tusb_desc_video_format_mjpeg_t mjpeg; + tusb_desc_video_format_framebased_t frame_based; +} tusb_desc_cs_video_fmt_t; + +typedef union { + struct TU_ATTR_PACKED { + uint8_t bLength; + uint8_t bDescriptorType; + uint8_t bDescriptorSubType; + uint8_t bFrameIndex; + uint8_t bmCapabilities; + uint16_t wWidth; + uint16_t wHeight; + }; + tusb_desc_video_frame_uncompressed_t uncompressed; + tusb_desc_video_frame_mjpeg_t mjpeg; + tusb_desc_video_frame_framebased_t frame_based; +} tusb_desc_cs_video_frm_t; + +/* video streaming interface */ +typedef struct TU_ATTR_PACKED { + uint8_t index_vc; /* index of bound video control interface */ + uint8_t index_vs; /* index from the video control interface */ + struct { + uint16_t beg; /* Offset of the beginning of video streaming interface descriptor */ + uint16_t end; /* Offset of the end of video streaming interface descriptor */ + uint16_t cur; /* Offset of the current settings */ + uint16_t ep[2]; /* Offset of endpoint descriptors. 0: streaming, 1: still capture */ + } desc; + uint8_t *buffer; /* frame buffer. assume linear buffer. no support for stride access */ + uint32_t bufsize; /* frame buffer size */ + uint32_t offset; /* offset for the next payload transfer */ + uint32_t max_payload_transfer_size; + uint8_t error_code;/* error code */ + uint8_t state; /* 0:probing 1:committed 2:streaming */ + + video_probe_and_commit_control_t probe_commit_payload; /* Probe and Commit control */ +} videod_streaming_interface_t; + +typedef struct { + TUD_EPBUF_DEF(buf, CFG_TUD_VIDEO_STREAMING_EP_BUFSIZE); +} videod_streaming_epbuf_t; + +/* video control interface */ +typedef struct TU_ATTR_PACKED { + const uint8_t*beg; /* The head of the first video control interface descriptor */ + uint16_t len; /* Byte length of the descriptors */ + uint16_t cur; /* offset for current video control interface */ + uint8_t stm[CFG_TUD_VIDEO_STREAMING]; /* Indices of streaming interface */ + uint8_t error_code; /* error code */ + uint8_t power_mode; +} videod_interface_t; + +//--------------------------------------------------------------------+ +// INTERNAL OBJECT & FUNCTION DECLARATION +//--------------------------------------------------------------------+ +static videod_interface_t _videod_itf[CFG_TUD_VIDEO]; + +static videod_streaming_interface_t _videod_streaming_itf[CFG_TUD_VIDEO_STREAMING]; +CFG_TUD_MEM_SECTION static videod_streaming_epbuf_t _videod_streaming_epbuf[CFG_TUD_VIDEO_STREAMING]; + +static uint8_t const _cap_get = 0x1u; /* support for GET */ +static uint8_t const _cap_get_set = 0x3u; /* support for GET and SET */ + +//--------------------------------------------------------------------+ +// Debug +//--------------------------------------------------------------------+ +#if CFG_TUSB_DEBUG >= CFG_TUD_VIDEO_LOG_LEVEL + +static tu_lookup_entry_t const tu_lookup_video_request[] = { + {.key = VIDEO_REQUEST_UNDEFINED, .data = "Undefined"}, + {.key = VIDEO_REQUEST_SET_CUR, .data = "SetCur"}, + {.key = VIDEO_REQUEST_SET_CUR_ALL, .data = "SetCurAll"}, + {.key = VIDEO_REQUEST_GET_CUR, .data = "GetCur"}, + {.key = VIDEO_REQUEST_GET_MIN, .data = "GetMin"}, + {.key = VIDEO_REQUEST_GET_MAX, .data = "GetMax"}, + {.key = VIDEO_REQUEST_GET_RES, .data = "GetRes"}, + {.key = VIDEO_REQUEST_GET_LEN, .data = "GetLen"}, + {.key = VIDEO_REQUEST_GET_INFO, .data = "GetInfo"}, + {.key = VIDEO_REQUEST_GET_DEF, .data = "GetDef"}, + {.key = VIDEO_REQUEST_GET_CUR_ALL, .data = "GetCurAll"}, + {.key = VIDEO_REQUEST_GET_MIN_ALL, .data = "GetMinAll"}, + {.key = VIDEO_REQUEST_GET_MAX_ALL, .data = "GetMaxAll"}, + {.key = VIDEO_REQUEST_GET_RES_ALL, .data = "GetResAll"}, + {.key = VIDEO_REQUEST_GET_DEF_ALL, .data = "GetDefAll"}, +}; + +static tu_lookup_table_t const tu_table_video_request = { + .count = TU_ARRAY_SIZE(tu_lookup_video_request), + .items = tu_lookup_video_request +}; + +static char const* const tu_str_video_vc_control_selector[] = { + "Undefined", + "Video Power Mode", + "Request Error Code", +}; + +static char const* const tu_str_video_vs_control_selector[] = { + "Undefined", + "Probe", + "Commit", + "Still Probe", + "Still Commit", + "Still Image Trigger", + "Stream Error Code", + "Generate Key Frame", + "Update Frame Segment", + "Sync Delay", +}; + +#endif + +//--------------------------------------------------------------------+ +// Weak stubs: invoked if no strong implementation is available +//--------------------------------------------------------------------+ +TU_ATTR_WEAK void tud_video_frame_xfer_complete_cb(uint_fast8_t ctl_idx, uint_fast8_t stm_idx) { + (void) ctl_idx; + (void) stm_idx; +} + +TU_ATTR_WEAK int tud_video_power_mode_cb(uint_fast8_t ctl_idx, uint8_t power_mod) { + (void) ctl_idx; + (void) power_mod; + return VIDEO_ERROR_NONE; +} + +TU_ATTR_WEAK int tud_video_commit_cb(uint_fast8_t ctl_idx, uint_fast8_t stm_idx, + video_probe_and_commit_control_t const *parameters) { + (void) ctl_idx; + (void) stm_idx; + (void) parameters; + return VIDEO_ERROR_NONE; +} + +TU_ATTR_WEAK void tud_video_prepare_payload_cb(uint_fast8_t ctl_idx, uint_fast8_t stm_idx, tud_video_payload_request_t* request) { + (void) ctl_idx; + (void) stm_idx; + (void) request; +} + +//--------------------------------------------------------------------+ +// +//--------------------------------------------------------------------+ + +/** Get interface number from the interface descriptor + * + * @param[in] desc interface descriptor + * + * @return bInterfaceNumber */ +static inline uint8_t _desc_itfnum(void const *desc) { + return ((uint8_t const*)desc)[2]; +} + +/** Get endpoint address from the endpoint descriptor + * + * @param[in] desc endpoint descriptor + * + * @return bEndpointAddress */ +static inline uint8_t _desc_ep_addr(void const *desc) { + return ((uint8_t const*)desc)[2]; +} + +/** Get instance of streaming interface + * + * @param[in] ctl_idx instance number of video control + * @param[in] stm_idx index number of streaming interface + * + * @return instance */ +static videod_streaming_interface_t* _get_instance_streaming(uint_fast8_t ctl_idx, uint_fast8_t stm_idx) { + videod_interface_t *ctl = &_videod_itf[ctl_idx]; + if (!ctl->beg) { + return NULL; + } + videod_streaming_interface_t *stm = &_videod_streaming_itf[ctl->stm[stm_idx]]; + if (!stm->desc.beg) { + return NULL; + } + return stm; +} + +static tusb_desc_vc_itf_t const* _get_desc_vc(videod_interface_t const *self) { + return (tusb_desc_vc_itf_t const *)(self->beg + self->cur); +} + +static tusb_desc_vs_itf_t const* _get_desc_vs(videod_streaming_interface_t const *self) { + if (!self->desc.cur) { + return NULL; + } + uint8_t const *desc = _videod_itf[self->index_vc].beg; + return (tusb_desc_vs_itf_t const*)(desc + self->desc.cur); +} + +/** Find the first descriptor of a given type + * + * @param[in] beg The head of descriptor byte array. + * @param[in] end The tail of descriptor byte array. + * @param[in] desc_type The target descriptor type. + * + * @return The pointer for interface descriptor. + * @retval end did not found interface descriptor */ +static void const* _find_desc(void const *beg, void const *end, uint_fast8_t desc_type) { + void const *cur = beg; + while ((cur < end) && (desc_type != tu_desc_type(cur))) { + cur = tu_desc_next(cur); + } + return cur; +} + +/** Find the first descriptor of two given types + * + * @param[in] beg The head of descriptor byte array. + * @param[in] end The tail of descriptor byte array. + * @param[in] desc_type_0 The first target descriptor type. + * @param[in] desc_type_1 The second target descriptor type. + * + * @return The pointer for interface descriptor. + * @retval end did not found interface descriptor */ +static void const* _find_desc_2_type(void const *beg, void const *end, uint_fast8_t desc_type_0, uint_fast8_t desc_type_1) +{ + void const *cur = beg; + while ((cur < end) && (desc_type_0 != tu_desc_type(cur)) && (desc_type_1 != tu_desc_type(cur))) { + cur = tu_desc_next(cur); + } + return cur; +} + +/** Find the first descriptor specified by the arguments + * + * @param[in] beg The head of descriptor byte array. + * @param[in] end The tail of descriptor byte array. + * @param[in] desc_type The target descriptor type + * @param[in] element_0 The target element following the desc_type + * @param[in] element_1 The target element following the element_0 + * + * @return The pointer for interface descriptor. + * @retval end did not found interface descriptor */ +static void const* _find_desc_3(void const *beg, void const *end, + uint_fast8_t desc_type, + uint_fast8_t element_0, + uint_fast8_t element_1) { + for (void const *cur = beg; cur < end; cur = _find_desc(cur, end, desc_type)) { + uint8_t const *p = (uint8_t const *)cur; + if ((p[2] == element_0) && (p[3] == element_1)) { + return cur; + } + cur = tu_desc_next(cur); + } + return end; +} + +/** Return the next interface descriptor which has another interface number. + * If there are multiple VC interfaces, there will be an IAD descriptor before + * the next interface descriptor. Check both the IAD descriptor and the interface + * descriptor. + * 3.1 Descriptor Layout Overview + * + * @param[in] beg The head of descriptor byte array. + * @param[in] end The tail of descriptor byte array. + * + * @return The pointer for interface descriptor. + * @retval end did not found interface descriptor */ +static void const* _next_desc_itf(void const *beg, void const *end) { + void const *cur = beg; + uint_fast8_t itfnum = ((tusb_desc_interface_t const*)cur)->bInterfaceNumber; + while ((cur < end) && + (itfnum == ((tusb_desc_interface_t const*)cur)->bInterfaceNumber)) { + cur = _find_desc_2_type(tu_desc_next(cur), end, TUSB_DESC_INTERFACE, TUSB_DESC_INTERFACE_ASSOCIATION); + } + return cur; +} + +/** Find the first interface descriptor with the specified interface number and alternate setting number. + * + * @param[in] beg The head of descriptor byte array. + * @param[in] end The tail of descriptor byte array. + * @param[in] itfnum The target interface number. + * @param[in] altnum The target alternate setting number. + * + * @return The pointer for interface descriptor. + * @retval end did not found interface descriptor */ +static inline uint8_t const* _find_desc_itf(void const *beg, void const *end, uint_fast8_t itfnum, uint_fast8_t altnum) +{ + return (uint8_t const*) _find_desc_3(beg, end, TUSB_DESC_INTERFACE, itfnum, altnum); +} + +/** Find the first endpoint descriptor belonging to the current interface descriptor. + * + * The search range is from `beg` to `end` or the next interface descriptor. + * + * @param[in] beg The head of descriptor byte array. + * @param[in] end The tail of descriptor byte array. + * + * @return The pointer for endpoint descriptor. + * @retval end did not found endpoint descriptor */ +static void const* _find_desc_ep(void const *beg, void const *end) +{ + for (void const *cur = beg; cur < end; cur = tu_desc_next(cur)) { + uint_fast8_t desc_type = tu_desc_type(cur); + if (TUSB_DESC_ENDPOINT == desc_type) { + return cur; + } + if (TUSB_DESC_INTERFACE == desc_type) { + break; + } + } + return end; +} + +/** Return the end of the video control descriptor. */ +static inline void const* _end_of_control_descriptor(void const *desc) +{ + tusb_desc_vc_itf_t const *vc = (tusb_desc_vc_itf_t const *)desc; + return ((uint8_t const*) desc) + vc->std.bLength + tu_le16toh(vc->ctl.wTotalLength); +} + +/** Find the first entity descriptor with the entity ID + * specified by the argument belonging to the current video control descriptor. + * + * @param[in] desc The video control interface descriptor. + * @param[in] entityid The target entity id. + * + * @return The pointer for interface descriptor. + * @retval end did not found interface descriptor */ +static void const* _find_desc_entity(void const *desc, uint_fast8_t entityid) +{ + void const *end = _end_of_control_descriptor(desc); + for (void const *cur = desc; cur < end; cur = _find_desc(cur, end, TUSB_DESC_CS_INTERFACE)) { + tusb_desc_cs_video_entity_itf_t const *itf = (tusb_desc_cs_video_entity_itf_t const *)cur; + if ((VIDEO_CS_ITF_VC_INPUT_TERMINAL <= itf->bDescriptorSubtype + && itf->bDescriptorSubtype < VIDEO_CS_ITF_VC_MAX) + && itf->bEntityId == entityid) { + return itf; + } + cur = tu_desc_next(cur); + } + return end; +} + +/** Return the end of the video streaming descriptor. */ +static inline void const* _end_of_streaming_descriptor(void const *desc) +{ + tusb_desc_vs_itf_t const *vs = (tusb_desc_vs_itf_t const *)desc; + return ((uint8_t const*) desc) + vs->std.bLength + tu_le16toh(vs->stm.wTotalLength); +} + +/** Find the first format descriptor with the specified format number. */ +static inline void const *_find_desc_format(void const *beg, void const *end, uint_fast8_t fmtnum) +{ + for (void const *cur = beg; cur < end; cur = _find_desc(cur, end, TUSB_DESC_CS_INTERFACE)) { + uint8_t const *p = (uint8_t const *)cur; + uint_fast8_t fmt = p[2]; + if ((fmt == VIDEO_CS_ITF_VS_FORMAT_UNCOMPRESSED || + fmt == VIDEO_CS_ITF_VS_FORMAT_MJPEG || + fmt == VIDEO_CS_ITF_VS_FORMAT_DV || + fmt == VIDEO_CS_ITF_VS_FORMAT_FRAME_BASED) && + fmtnum == p[3]) { + return cur; + } + cur = tu_desc_next(cur); + } + return end; +} + +/** Find the first frame descriptor with the specified format number. */ +static inline void const *_find_desc_frame(void const *beg, void const *end, uint_fast8_t frmnum) +{ + for (void const *cur = beg; cur < end; cur = _find_desc(cur, end, TUSB_DESC_CS_INTERFACE)) { + uint8_t const *p = (uint8_t const *)cur; + uint_fast8_t frm = p[2]; + if ((frm == VIDEO_CS_ITF_VS_FRAME_UNCOMPRESSED || + frm == VIDEO_CS_ITF_VS_FRAME_MJPEG || + frm == VIDEO_CS_ITF_VS_FRAME_FRAME_BASED) && + frmnum == p[3]) { + return cur; + } + cur = tu_desc_next(cur); + } + return end; +} + +/** Set uniquely determined values to variables that have not been set + * + * @param[in,out] param Target */ +static bool _update_streaming_parameters(videod_streaming_interface_t const *stm, + video_probe_and_commit_control_t *param) +{ + tusb_desc_vs_itf_t const *vs = _get_desc_vs(stm); + uint_fast8_t fmtnum = param->bFormatIndex; + TU_ASSERT(vs && fmtnum <= vs->stm.bNumFormats); + if (0 == fmtnum) { + if (1 < vs->stm.bNumFormats) { + return true; /* Need to negotiate all variables. */ + } + fmtnum = 1; + param->bFormatIndex = 1; + } + + /* Set the parameters determined by the format */ + param->wKeyFrameRate = 1; + param->wPFrameRate = 0; + param->wCompWindowSize = 1; /* GOP size? */ + param->wDelay = 0; /* milliseconds */ + param->dwClockFrequency = 27000000; /* same as MPEG-2 system time clock */ + param->bmFramingInfo = 0x3; /* enables FrameID and EndOfFrame */ + param->bPreferedVersion = 1; + param->bMinVersion = 1; + param->bMaxVersion = 1; + param->bUsage = 0; + param->bBitDepthLuma = 8; + + void const *end = _end_of_streaming_descriptor(vs); + tusb_desc_cs_video_fmt_t const *fmt = _find_desc_format(tu_desc_next(vs), end, fmtnum); + TU_ASSERT(fmt != end); + + switch (fmt->bDescriptorSubType) { + case VIDEO_CS_ITF_VS_FORMAT_UNCOMPRESSED: + param->wCompQuality = 1; /* 1 to 10000 */ + break; + + case VIDEO_CS_ITF_VS_FORMAT_MJPEG: + break; + + case VIDEO_CS_ITF_VS_FORMAT_FRAME_BASED: + break; + + default: return false; + } + + uint_fast8_t frmnum = param->bFrameIndex; + TU_ASSERT(frmnum <= fmt->bNumFrameDescriptors); + if (0 == frmnum) { + if (1 < fmt->bNumFrameDescriptors) { + return true; + } + frmnum = 1; + param->bFrameIndex = 1; + } + tusb_desc_cs_video_frm_t const *frm = _find_desc_frame(tu_desc_next(fmt), end, frmnum); + TU_ASSERT(frm != end); + + /* Set the parameters determined by the frame */ + uint_fast32_t frame_size = param->dwMaxVideoFrameSize; + if (0 == frame_size) { + switch (fmt->bDescriptorSubType) { + case VIDEO_CS_ITF_VS_FORMAT_UNCOMPRESSED: + frame_size = (uint_fast32_t)frm->wWidth * frm->wHeight * fmt->uncompressed.bBitsPerPixel / 8; + break; + + case VIDEO_CS_ITF_VS_FORMAT_MJPEG: + frame_size = (uint_fast32_t)frm->wWidth * frm->wHeight * 16 / 8; /* YUV422 */ + break; + + case VIDEO_CS_ITF_VS_FORMAT_FRAME_BASED: + frame_size = (uint_fast32_t)frm->wWidth * frm->wHeight * 16 / 8; /* YUV422 */ + break; + + default: break; + } + param->dwMaxVideoFrameSize = frame_size; + } + + uint_fast32_t interval = param->dwFrameInterval; + if (0 == interval) { + if ((1 < frm->uncompressed.bFrameIntervalType) || + ((0 == frm->uncompressed.bFrameIntervalType) && + (frm->uncompressed.dwFrameInterval[1] != frm->uncompressed.dwFrameInterval[0]))) { + return true; + } + interval = frm->uncompressed.dwFrameInterval[0]; + param->dwFrameInterval = interval; + } + uint_fast32_t interval_ms = interval / 10000; + TU_ASSERT(interval_ms != 0); + uint_fast32_t payload_size = (frame_size + interval_ms - 1) / interval_ms + 2; + if (CFG_TUD_VIDEO_STREAMING_EP_BUFSIZE < payload_size) { + payload_size = CFG_TUD_VIDEO_STREAMING_EP_BUFSIZE; + } + param->dwMaxPayloadTransferSize = payload_size; + return true; +} + +/** Set the minimum, maximum, default values or resolutions to variables which need to negotiate with the host + * + * @param[in] request GET_MAX, GET_MIN, GET_RES or GET_DEF + * @param[in,out] param Target + */ +static bool _negotiate_streaming_parameters(videod_streaming_interface_t const *stm, uint_fast8_t request, + video_probe_and_commit_control_t *param) +{ + uint_fast8_t const fmtnum = param->bFormatIndex; + if (0 == fmtnum) { + switch (request) { + case VIDEO_REQUEST_GET_MAX: + if (_get_desc_vs(stm)) + param->bFormatIndex = _get_desc_vs(stm)->stm.bNumFormats; + break; + + case VIDEO_REQUEST_GET_MIN: + case VIDEO_REQUEST_GET_DEF: + param->bFormatIndex = 1; + break; + + default: return false; + } + /* Set the parameters determined by the format */ + param->wKeyFrameRate = 1; + param->wPFrameRate = 0; + param->wCompQuality = 1; /* 1 to 10000 */ + param->wCompWindowSize = 1; /* GOP size? */ + param->wDelay = 0; /* milliseconds */ + param->dwClockFrequency = 27000000; /* same as MPEG-2 system time clock */ + param->bmFramingInfo = 0x3; /* enables FrameID and EndOfFrame */ + param->bPreferedVersion = 1; + param->bMinVersion = 1; + param->bMaxVersion = 1; + param->bUsage = 0; + param->bBitDepthLuma = 8; + return true; + } + + uint_fast8_t frmnum = param->bFrameIndex; + if (0 == frmnum) { + tusb_desc_vs_itf_t const *vs = _get_desc_vs(stm); + TU_ASSERT(vs); + void const *end = _end_of_streaming_descriptor(vs); + tusb_desc_cs_video_fmt_t const *fmt = _find_desc_format(tu_desc_next(vs), end, fmtnum); + switch (request) { + case VIDEO_REQUEST_GET_MAX: + frmnum = fmt->bNumFrameDescriptors; + break; + + case VIDEO_REQUEST_GET_MIN: + frmnum = 1; + break; + + case VIDEO_REQUEST_GET_DEF: + switch (fmt->bDescriptorSubType) { + case VIDEO_CS_ITF_VS_FORMAT_UNCOMPRESSED: + frmnum = fmt->uncompressed.bDefaultFrameIndex; + break; + + case VIDEO_CS_ITF_VS_FORMAT_MJPEG: + frmnum = fmt->mjpeg.bDefaultFrameIndex; + break; + + case VIDEO_CS_ITF_VS_FORMAT_FRAME_BASED: + frmnum = fmt->frame_based.bDefaultFrameIndex; + break; + + default: return false; + } + break; + default: return false; + } + param->bFrameIndex = (uint8_t)frmnum; + /* Set the parameters determined by the frame */ + tusb_desc_cs_video_frm_t const *frm = _find_desc_frame(tu_desc_next(fmt), end, frmnum); + uint_fast32_t frame_size; + switch (fmt->bDescriptorSubType) { + case VIDEO_CS_ITF_VS_FORMAT_UNCOMPRESSED: + frame_size = (uint_fast32_t)frm->wWidth * frm->wHeight * fmt->uncompressed.bBitsPerPixel / 8; + break; + + case VIDEO_CS_ITF_VS_FORMAT_MJPEG: + frame_size = (uint_fast32_t)frm->wWidth * frm->wHeight * 16 / 8; /* YUV422 */ + break; + + case VIDEO_CS_ITF_VS_FORMAT_FRAME_BASED: + frame_size = (uint_fast32_t)frm->wWidth * frm->wHeight * 16 / 8; /* YUV422 */ + break; + + default: return false; + } + param->dwMaxVideoFrameSize = frame_size; + return true; + } + + if (0 == param->dwFrameInterval) { + tusb_desc_vs_itf_t const *vs = _get_desc_vs(stm); + TU_ASSERT(vs); + void const *end = _end_of_streaming_descriptor(vs); + tusb_desc_cs_video_fmt_t const *fmt = _find_desc_format(tu_desc_next(vs), end, fmtnum); + tusb_desc_cs_video_frm_t const *frm = _find_desc_frame(tu_desc_next(fmt), end, frmnum); + + uint_fast32_t interval, interval_ms; + switch (request) { + case VIDEO_REQUEST_GET_MAX: { + uint_fast32_t min_interval, max_interval; + uint_fast8_t num_intervals = frm->uncompressed.bFrameIntervalType; + max_interval = num_intervals ? frm->uncompressed.dwFrameInterval[num_intervals - 1]: frm->uncompressed.dwFrameInterval[1]; + min_interval = frm->uncompressed.dwFrameInterval[0]; + interval = max_interval; + interval_ms = min_interval / 10000; + break; + } + + case VIDEO_REQUEST_GET_MIN: { + uint_fast32_t min_interval, max_interval; + uint_fast8_t num_intervals = frm->uncompressed.bFrameIntervalType; + max_interval = num_intervals ? frm->uncompressed.dwFrameInterval[num_intervals - 1]: frm->uncompressed.dwFrameInterval[1]; + min_interval = frm->uncompressed.dwFrameInterval[0]; + interval = min_interval; + interval_ms = max_interval / 10000; + break; + } + + case VIDEO_REQUEST_GET_DEF: + interval = frm->uncompressed.dwDefaultFrameInterval; + interval_ms = interval / 10000; + break; + + case VIDEO_REQUEST_GET_RES: { + uint_fast8_t num_intervals = frm->uncompressed.bFrameIntervalType; + if (num_intervals) { + interval = 0; + interval_ms = 0; + } else { + interval = frm->uncompressed.dwFrameInterval[2]; + interval_ms = interval / 10000; + } + break; + } + + default: return false; + } + param->dwFrameInterval = interval; + if (0 == interval) { + param->dwMaxPayloadTransferSize = 0; + } else { + uint_fast32_t frame_size = param->dwMaxVideoFrameSize; + uint_fast32_t payload_size; + if (0 == interval_ms) { + payload_size = frame_size + 2; + } else { + payload_size = (frame_size + interval_ms - 1) / interval_ms + 2; + } + if (CFG_TUD_VIDEO_STREAMING_EP_BUFSIZE < payload_size) { + payload_size = CFG_TUD_VIDEO_STREAMING_EP_BUFSIZE; + } + param->dwMaxPayloadTransferSize = payload_size; + } + return true; + } + return true; +} + +/** Close current video control interface. + * + * @param[in,out] self Video control interface context. + * @param[in] altnum The target alternate setting number. */ +static bool _close_vc_itf(uint8_t rhport, videod_interface_t *self) +{ + tusb_desc_vc_itf_t const *vc = _get_desc_vc(self); + + /* The next descriptor after the class-specific VC interface header descriptor. */ + void const *cur = (uint8_t const*)vc + vc->std.bLength + vc->ctl.bLength; + + /* The end of the video control interface descriptor. */ + void const *end = _end_of_control_descriptor(vc); + if (vc->std.bNumEndpoints != 0) { + /* Extend end to cover the standard endpoint and class-specific endpoint descriptors + * that follow wTotalLength */ + end = (uint8_t const*)end + sizeof(tusb_desc_endpoint_t) + sizeof(tusb_desc_cs_video_vc_ep_t); + /* Find the notification endpoint descriptor. */ + cur = _find_desc(cur, end, TUSB_DESC_ENDPOINT); + TU_ASSERT(cur < end); + tusb_desc_endpoint_t const *notif = (tusb_desc_endpoint_t const *)cur; + usbd_edpt_close(rhport, notif->bEndpointAddress); + } + self->cur = 0; + return true; +} + +/** Set the alternate setting to own video control interface. + * + * @param[in,out] self Video control interface context. + * @param[in] altnum The target alternate setting number. */ +static bool _open_vc_itf(uint8_t rhport, videod_interface_t *self, uint_fast8_t altnum) +{ + TU_LOG_DRV(" open VC %d\r\n", altnum); + uint8_t const *beg = self->beg; + uint8_t const *end = beg + self->len; + + /* The first descriptor is a video control interface descriptor. */ + uint8_t const *cur = _find_desc_itf(beg, end, _desc_itfnum(beg), altnum); + TU_LOG_DRV(" cur %" PRId32 "\r\n", (int32_t) (cur - beg)); + TU_VERIFY(cur < end); + + tusb_desc_vc_itf_t const *vc = (tusb_desc_vc_itf_t const *)cur; + TU_LOG_DRV(" bInCollection %d\r\n", vc->ctl.bInCollection); + /* Support for up to 2 streaming interfaces only. */ + TU_ASSERT(vc->ctl.bInCollection <= CFG_TUD_VIDEO_STREAMING); + + /* Update to point the end of the video control interface descriptor. */ + end = _end_of_control_descriptor(cur); + + /* Advance to the next descriptor after the class-specific VC interface header descriptor. */ + cur += vc->std.bLength + vc->ctl.bLength; + TU_LOG_DRV(" bNumEndpoints %d\r\n", vc->std.bNumEndpoints); + /* Open the notification endpoint if it exist. */ + if (vc->std.bNumEndpoints != 0) { + /* Support for 1 endpoint only. */ + TU_VERIFY(1 == vc->std.bNumEndpoints); + /* Extend end to cover the standard endpoint and class-specific endpoint descriptors + * that follow wTotalLength */ + end = (uint8_t const*)end + sizeof(tusb_desc_endpoint_t) + sizeof(tusb_desc_cs_video_vc_ep_t); + /* Find the notification endpoint descriptor. */ + cur = _find_desc(cur, end, TUSB_DESC_ENDPOINT); + TU_VERIFY(cur < end); + tusb_desc_endpoint_t const *notif = (tusb_desc_endpoint_t const *)cur; + /* Open the notification endpoint */ + TU_ASSERT(usbd_edpt_open(rhport, notif)); + } + self->cur = (uint16_t) ((uint8_t const*)vc - beg); + return true; +} + +static bool _init_vs_configuration(videod_streaming_interface_t *stm) { + /* initialize streaming settings */ + stm->state = VS_STATE_PROBING; + stm->max_payload_transfer_size = 0; + video_probe_and_commit_control_t *param = &stm->probe_commit_payload; + tu_memclr(param, sizeof(*param)); + return _update_streaming_parameters(stm, param); +} + +/** Set the alternate setting to own video streaming interface. + * + * @param[in,out] stm Streaming interface context. + * @param[in] altnum The target alternate setting number. */ +static bool _open_vs_itf(uint8_t rhport, videod_streaming_interface_t *stm, uint_fast8_t altnum) +{ + uint_fast8_t i; + TU_LOG_DRV(" reopen VS %d\r\n", altnum); + uint8_t const *desc = _videod_itf[stm->index_vc].beg; + +#ifndef TUP_DCD_EDPT_ISO_ALLOC + /* Close endpoints of previous settings. */ + for (i = 0; i < TU_ARRAY_SIZE(stm->desc.ep); ++i) { + uint_fast16_t ofs_ep = stm->desc.ep[i]; + if (!ofs_ep) break; + tusb_desc_endpoint_t const *ep = (tusb_desc_endpoint_t const*)(desc + ofs_ep); + /* Only ISO endpoints needs to be closed */ + if(ep->bmAttributes.xfer == TUSB_XFER_ISOCHRONOUS) { + stm->desc.ep[i] = 0; + usbd_edpt_close(rhport, ep->bEndpointAddress); + TU_LOG_DRV(" close EP%02x\r\n", ep->bEndpointAddress); + } + } +#endif + + /* clear transfer management information */ + stm->buffer = NULL; + stm->bufsize = 0; + stm->offset = 0; + + /* Find a alternate interface */ + uint8_t const *beg = desc + stm->desc.beg; + uint8_t const *end = desc + stm->desc.end; + uint8_t const *cur = _find_desc_itf(beg, end, _desc_itfnum(beg), altnum); + TU_VERIFY(cur < end); + + uint_fast8_t numeps = ((tusb_desc_interface_t const *)cur)->bNumEndpoints; + TU_ASSERT(numeps <= TU_ARRAY_SIZE(stm->desc.ep)); + stm->desc.cur = (uint16_t)(cur - desc); /* Save the offset of the new settings */ + if (!altnum && (VS_STATE_COMMITTED != stm->state)) { + TU_VERIFY(_init_vs_configuration(stm)); + } + /* Open bulk or isochronous endpoints of the new settings. */ + for (i = 0, cur = tu_desc_next(cur); i < numeps; ++i, cur = tu_desc_next(cur)) { + cur = _find_desc_ep(cur, end); + TU_ASSERT(cur < end); + tusb_desc_endpoint_t const *ep = (tusb_desc_endpoint_t const*)cur; + uint_fast32_t max_size = stm->max_payload_transfer_size; + if (altnum && (TUSB_XFER_ISOCHRONOUS == ep->bmAttributes.xfer)) { + /* FS must be less than or equal to max packet size */ + TU_VERIFY (tu_edpt_packet_size(ep) >= max_size); +#ifdef TUP_DCD_EDPT_ISO_ALLOC + usbd_edpt_iso_activate(rhport, ep); +#else + TU_ASSERT(usbd_edpt_open(rhport, ep)); +#endif + } else { + TU_VERIFY(TUSB_XFER_BULK == ep->bmAttributes.xfer); + TU_ASSERT(usbd_edpt_open(rhport, ep)); + } + stm->desc.ep[i] = (uint16_t) (cur - desc); + TU_LOG_DRV(" open EP%02x\r\n", _desc_ep_addr(cur)); + } + if (altnum != 0) { + stm->state = VS_STATE_STREAMING; + } + TU_LOG_DRV(" done\r\n"); + return true; +} + +/** Prepare the next packet payload. */ +static uint_fast16_t _prepare_in_payload(videod_streaming_interface_t *stm, uint8_t* ep_buf) { + uint_fast16_t remaining = stm->bufsize - stm->offset; + uint_fast16_t hdr_len = ep_buf[0]; + uint_fast16_t pkt_len = stm->max_payload_transfer_size; + if (hdr_len + remaining < pkt_len) { + pkt_len = hdr_len + remaining; + } + TU_ASSERT(pkt_len >= hdr_len); + uint_fast16_t data_len = pkt_len - hdr_len; + if (stm->buffer) { + memcpy(&ep_buf[hdr_len], stm->buffer + stm->offset, data_len); + } else { + tud_video_payload_request_t rqst = { + .buf = &ep_buf[hdr_len], + .length = data_len, + .offset = stm->offset + }; + tud_video_prepare_payload_cb(stm->index_vc, stm->index_vs, &rqst); + } + stm->offset += data_len; + remaining -= data_len; + if (!remaining) { + tusb_video_payload_header_t *hdr = (tusb_video_payload_header_t*) ep_buf; + hdr->EndOfFrame = 1; + } + return hdr_len + data_len; +} + +/** Handle a standard request to the video control interface. */ +static int handle_video_ctl_std_req(uint8_t rhport, uint8_t stage, + tusb_control_request_t const *request, + uint_fast8_t ctl_idx) +{ + TU_LOG_DRV("\r\n"); + switch (request->bRequest) { + case TUSB_REQ_GET_INTERFACE: + if (stage == CONTROL_STAGE_SETUP) + { + TU_VERIFY(1 == request->wLength, VIDEO_ERROR_UNKNOWN); + tusb_desc_vc_itf_t const *vc = _get_desc_vc(&_videod_itf[ctl_idx]); + TU_VERIFY(vc, VIDEO_ERROR_UNKNOWN); + + uint8_t alt_num = vc->std.bAlternateSetting; + + TU_VERIFY(tud_control_xfer(rhport, request, &alt_num, sizeof(alt_num)), VIDEO_ERROR_UNKNOWN); + } + return VIDEO_ERROR_NONE; + + case TUSB_REQ_SET_INTERFACE: + if (stage == CONTROL_STAGE_SETUP) + { + TU_VERIFY(0 == request->wLength, VIDEO_ERROR_UNKNOWN); + TU_VERIFY(_close_vc_itf(rhport, &_videod_itf[ctl_idx]), VIDEO_ERROR_UNKNOWN); + TU_VERIFY(_open_vc_itf(rhport, &_videod_itf[ctl_idx], request->wValue), VIDEO_ERROR_UNKNOWN); + tud_control_status(rhport, request); + } + return VIDEO_ERROR_NONE; + + default: /* Unknown/Unsupported request */ + TU_BREAKPOINT(); + return VIDEO_ERROR_INVALID_REQUEST; + } +} + +static int handle_video_ctl_cs_req(uint8_t rhport, uint8_t stage, + tusb_control_request_t const *request, + uint_fast8_t ctl_idx) +{ + videod_interface_t *self = &_videod_itf[ctl_idx]; + + /* 4.2.1 Interface Control Request */ + uint8_t const ctrl_sel = TU_U16_HIGH(request->wValue); + TU_LOG_DRV("%s_Control(%s)\r\n", tu_str_video_vc_control_selector[ctrl_sel], tu_lookup_find(&tu_table_video_request, request->bRequest)); + + switch (ctrl_sel) { + case VIDEO_VC_CTL_VIDEO_POWER_MODE: + switch (request->bRequest) { + case VIDEO_REQUEST_SET_CUR: + if (stage == CONTROL_STAGE_SETUP) { + TU_VERIFY(1 == request->wLength, VIDEO_ERROR_UNKNOWN); + TU_VERIFY(tud_control_xfer(rhport, request, &self->power_mode, sizeof(self->power_mode)), VIDEO_ERROR_UNKNOWN); + } else if (stage == CONTROL_STAGE_DATA) { + return tud_video_power_mode_cb(ctl_idx, self->power_mode); + } + return VIDEO_ERROR_NONE; + + case VIDEO_REQUEST_GET_CUR: + if (stage == CONTROL_STAGE_SETUP) { + TU_VERIFY(1 == request->wLength, VIDEO_ERROR_UNKNOWN); + TU_VERIFY(tud_control_xfer(rhport, request, &self->power_mode, sizeof(self->power_mode)), VIDEO_ERROR_UNKNOWN); + } + return VIDEO_ERROR_NONE; + + case VIDEO_REQUEST_GET_INFO: + if (stage == CONTROL_STAGE_SETUP) { + TU_VERIFY(1 == request->wLength, VIDEO_ERROR_UNKNOWN); + TU_VERIFY(tud_control_xfer(rhport, request, (uint8_t*)(uintptr_t) &_cap_get_set, sizeof(_cap_get_set)), VIDEO_ERROR_UNKNOWN); + } + return VIDEO_ERROR_NONE; + + default: break; + } + break; + + case VIDEO_VC_CTL_REQUEST_ERROR_CODE: + switch (request->bRequest) { + case VIDEO_REQUEST_GET_CUR: + if (stage == CONTROL_STAGE_SETUP) { + TU_VERIFY(tud_control_xfer(rhport, request, &self->error_code, sizeof(uint8_t)), VIDEO_ERROR_UNKNOWN); + } + return VIDEO_ERROR_NONE; + + case VIDEO_REQUEST_GET_INFO: + if (stage == CONTROL_STAGE_SETUP) { + TU_VERIFY(tud_control_xfer(rhport, request, (uint8_t*)(uintptr_t) &_cap_get, sizeof(_cap_get)), VIDEO_ERROR_UNKNOWN); + } + return VIDEO_ERROR_NONE; + + default: break; + } + break; + + default: break; + } + + /* Unknown/Unsupported request */ + TU_BREAKPOINT(); + return VIDEO_ERROR_INVALID_REQUEST; +} + +static int handle_video_ctl_req(uint8_t rhport, uint8_t stage, + tusb_control_request_t const *request, + uint_fast8_t ctl_idx) +{ + switch (request->bmRequestType_bit.type) { + case TUSB_REQ_TYPE_STANDARD: + return handle_video_ctl_std_req(rhport, stage, request, ctl_idx); + + case TUSB_REQ_TYPE_CLASS: { + uint_fast8_t entity_id = TU_U16_HIGH(request->wIndex); + if (0 == entity_id) { + return handle_video_ctl_cs_req(rhport, stage, request, ctl_idx); + } else { + TU_VERIFY(_find_desc_entity(_get_desc_vc(&_videod_itf[ctl_idx]), entity_id), VIDEO_ERROR_INVALID_REQUEST); + return VIDEO_ERROR_NONE; + } + } + + default: + return VIDEO_ERROR_INVALID_REQUEST; + } +} + +static int handle_video_stm_std_req(uint8_t rhport, uint8_t stage, + tusb_control_request_t const *request, + uint_fast8_t stm_idx) { + TU_LOG_DRV("\r\n"); + videod_streaming_interface_t *self = &_videod_streaming_itf[stm_idx]; + switch (request->bRequest) { + case TUSB_REQ_GET_INTERFACE: + if (stage == CONTROL_STAGE_SETUP) { + TU_VERIFY(1 == request->wLength, VIDEO_ERROR_UNKNOWN); + tusb_desc_vs_itf_t const *vs = _get_desc_vs(self); + TU_VERIFY(vs, VIDEO_ERROR_UNKNOWN); + uint8_t alt_num = vs->std.bAlternateSetting; + + TU_VERIFY(tud_control_xfer(rhport, request, &alt_num, sizeof(alt_num)), VIDEO_ERROR_UNKNOWN); + } + return VIDEO_ERROR_NONE; + + case TUSB_REQ_SET_INTERFACE: + if (stage == CONTROL_STAGE_SETUP) { + TU_VERIFY(_open_vs_itf(rhport, self, request->wValue), VIDEO_ERROR_UNKNOWN); + tud_control_status(rhport, request); + } + return VIDEO_ERROR_NONE; + + default: /* Unknown/Unsupported request */ + TU_BREAKPOINT(); + return VIDEO_ERROR_INVALID_REQUEST; + } +} + +static int handle_video_stm_cs_req(uint8_t rhport, uint8_t stage, + tusb_control_request_t const *request, + uint_fast8_t stm_idx) { + (void)rhport; + videod_streaming_interface_t *stm = &_videod_streaming_itf[stm_idx]; + videod_streaming_epbuf_t *stm_epbuf = &_videod_streaming_epbuf[stm_idx]; + + uint8_t const ctrl_sel = TU_U16_HIGH(request->wValue); + TU_LOG_DRV("%s_Control(%s)\r\n", tu_str_video_vs_control_selector[ctrl_sel], tu_lookup_find(&tu_table_video_request, request->bRequest)); + + /* 4.2.1 Interface Control Request */ + switch (ctrl_sel) { + case VIDEO_VS_CTL_STREAM_ERROR_CODE: + switch (request->bRequest) { + case VIDEO_REQUEST_GET_CUR: + if (stage == CONTROL_STAGE_SETUP) { + /* TODO */ + TU_VERIFY(tud_control_xfer(rhport, request, &stm->error_code, sizeof(uint8_t)), VIDEO_ERROR_UNKNOWN); + } + return VIDEO_ERROR_NONE; + + case VIDEO_REQUEST_GET_INFO: + if (stage == CONTROL_STAGE_SETUP) { + TU_VERIFY(tud_control_xfer(rhport, request, (uint8_t*)(uintptr_t) &_cap_get, sizeof(_cap_get)), VIDEO_ERROR_UNKNOWN); + } + return VIDEO_ERROR_NONE; + + default: break; + } + break; + + case VIDEO_VS_CTL_PROBE: + if (stm->state != VS_STATE_PROBING) { + stm->state = VS_STATE_PROBING; + } + + switch (request->bRequest) { + case VIDEO_REQUEST_SET_CUR: + if (stage == CONTROL_STAGE_SETUP) { + TU_VERIFY(tud_control_xfer(rhport, request, &stm->probe_commit_payload, sizeof(video_probe_and_commit_control_t)), + VIDEO_ERROR_UNKNOWN); + } else if (stage == CONTROL_STAGE_DATA) { + TU_VERIFY(_update_streaming_parameters(stm, &stm->probe_commit_payload), + VIDEO_ERROR_INVALID_VALUE_WITHIN_RANGE); + } else { + // nothing to do + } + return VIDEO_ERROR_NONE; + + case VIDEO_REQUEST_GET_CUR: + if (stage == CONTROL_STAGE_SETUP) { + TU_VERIFY(request->wLength != 0, VIDEO_ERROR_UNKNOWN); + TU_VERIFY(tud_control_xfer(rhport, request, &stm->probe_commit_payload, sizeof(video_probe_and_commit_control_t)), VIDEO_ERROR_UNKNOWN); + } + return VIDEO_ERROR_NONE; + + case VIDEO_REQUEST_GET_MIN: + case VIDEO_REQUEST_GET_MAX: + case VIDEO_REQUEST_GET_RES: + case VIDEO_REQUEST_GET_DEF: + if (stage == CONTROL_STAGE_SETUP) { + TU_VERIFY(request->wLength != 0, VIDEO_ERROR_UNKNOWN); + video_probe_and_commit_control_t tmp = stm->probe_commit_payload; + TU_VERIFY(_negotiate_streaming_parameters(stm, request->bRequest, &tmp), VIDEO_ERROR_INVALID_VALUE_WITHIN_RANGE); + TU_VERIFY(tud_control_xfer(rhport, request, &tmp, sizeof(tmp)), VIDEO_ERROR_UNKNOWN); + } + return VIDEO_ERROR_NONE; + + case VIDEO_REQUEST_GET_LEN: + if (stage == CONTROL_STAGE_SETUP) { + TU_VERIFY(2 == request->wLength, VIDEO_ERROR_UNKNOWN); + uint16_t len = sizeof(video_probe_and_commit_control_t); + TU_VERIFY(tud_control_xfer(rhport, request, (uint8_t*)&len, sizeof(len)), VIDEO_ERROR_UNKNOWN); + } + return VIDEO_ERROR_NONE; + + case VIDEO_REQUEST_GET_INFO: + if (stage == CONTROL_STAGE_SETUP) { + TU_VERIFY(1 == request->wLength, VIDEO_ERROR_UNKNOWN); + TU_VERIFY(tud_control_xfer(rhport, request, (uint8_t*)(uintptr_t)&_cap_get_set, sizeof(_cap_get_set)), VIDEO_ERROR_UNKNOWN); + } + return VIDEO_ERROR_NONE; + + default: break; + } + break; + + case VIDEO_VS_CTL_COMMIT: + switch (request->bRequest) { + case VIDEO_REQUEST_SET_CUR: + if (stage == CONTROL_STAGE_SETUP) { + TU_VERIFY(tud_control_xfer(rhport, request, &stm->probe_commit_payload, sizeof(video_probe_and_commit_control_t)), VIDEO_ERROR_UNKNOWN); + } else if (stage == CONTROL_STAGE_DATA) { + video_probe_and_commit_control_t *param = &stm->probe_commit_payload; + TU_VERIFY(_update_streaming_parameters(stm, param), VIDEO_ERROR_INVALID_VALUE_WITHIN_RANGE); + /* Set the negotiated value */ + stm->max_payload_transfer_size = param->dwMaxPayloadTransferSize; + int ret = tud_video_commit_cb(stm->index_vc, stm->index_vs, param); + if (VIDEO_ERROR_NONE == ret) { + stm->state = VS_STATE_COMMITTED; + stm->buffer = NULL; + stm->bufsize = 0; + stm->offset = 0; + /* initialize payload header */ + tusb_video_payload_header_t *hdr = (tusb_video_payload_header_t*)stm_epbuf->buf; + hdr->bHeaderLength = sizeof(*hdr); + hdr->bmHeaderInfo = 0; + } + } else { + // nothing to do + } + return VIDEO_ERROR_NONE; + + case VIDEO_REQUEST_GET_CUR: + if (stage == CONTROL_STAGE_SETUP) { + TU_VERIFY(request->wLength != 0, VIDEO_ERROR_UNKNOWN); + TU_VERIFY(tud_control_xfer(rhport, request, &stm->probe_commit_payload, sizeof(video_probe_and_commit_control_t)), VIDEO_ERROR_UNKNOWN); + } + return VIDEO_ERROR_NONE; + + case VIDEO_REQUEST_GET_LEN: + if (stage == CONTROL_STAGE_SETUP) { + TU_VERIFY(2 == request->wLength, VIDEO_ERROR_UNKNOWN); + uint16_t len = sizeof(video_probe_and_commit_control_t); + TU_VERIFY(tud_control_xfer(rhport, request, (uint8_t*)&len, sizeof(len)), VIDEO_ERROR_UNKNOWN); + } + return VIDEO_ERROR_NONE; + + case VIDEO_REQUEST_GET_INFO: + if (stage == CONTROL_STAGE_SETUP) { + TU_VERIFY(1 == request->wLength, VIDEO_ERROR_UNKNOWN); + TU_VERIFY(tud_control_xfer(rhport, request, (uint8_t*)(uintptr_t) &_cap_get_set, sizeof(_cap_get_set)), VIDEO_ERROR_UNKNOWN); + } + return VIDEO_ERROR_NONE; + + default: break; + } + break; + + case VIDEO_VS_CTL_STILL_PROBE: + case VIDEO_VS_CTL_STILL_COMMIT: + case VIDEO_VS_CTL_STILL_IMAGE_TRIGGER: + case VIDEO_VS_CTL_GENERATE_KEY_FRAME: + case VIDEO_VS_CTL_UPDATE_FRAME_SEGMENT: + case VIDEO_VS_CTL_SYNCH_DELAY_CONTROL: + /* TODO */ + break; + + default: break; + } + + /* Unknown/Unsupported request */ + TU_BREAKPOINT(); + return VIDEO_ERROR_INVALID_REQUEST; +} + +static int handle_video_stm_req(uint8_t rhport, uint8_t stage, + tusb_control_request_t const *request, + uint_fast8_t stm_idx) { + switch (request->bmRequestType_bit.type) { + case TUSB_REQ_TYPE_STANDARD: + return handle_video_stm_std_req(rhport, stage, request, stm_idx); + + case TUSB_REQ_TYPE_CLASS: + if (0 != TU_U16_HIGH(request->wIndex)) { + return VIDEO_ERROR_INVALID_REQUEST; + } + return handle_video_stm_cs_req(rhport, stage, request, stm_idx); + + default: return VIDEO_ERROR_INVALID_REQUEST; + } +} + +//--------------------------------------------------------------------+ +// APPLICATION API +//--------------------------------------------------------------------+ + +bool tud_video_n_connected(uint_fast8_t ctl_idx) { + TU_ASSERT(ctl_idx < CFG_TUD_VIDEO); + videod_streaming_interface_t *stm = _get_instance_streaming(ctl_idx, 0); + if (stm != NULL) { + return true; + } + return false; +} + +bool tud_video_n_streaming(uint_fast8_t ctl_idx, uint_fast8_t stm_idx) +{ + TU_ASSERT(ctl_idx < CFG_TUD_VIDEO); + TU_ASSERT(stm_idx < CFG_TUD_VIDEO_STREAMING); + videod_streaming_interface_t *stm = _get_instance_streaming(ctl_idx, stm_idx); + if (NULL == stm || 0 == stm->desc.ep[0]) { + return false; + } + if (stm->state == VS_STATE_PROBING) { + return false; + } + + #ifdef TUP_DCD_EDPT_ISO_ALLOC + uint8_t const *desc = _videod_itf[stm->index_vc].beg; + uint_fast16_t ofs_ep = stm->desc.ep[0]; + tusb_desc_endpoint_t const *ep = (tusb_desc_endpoint_t const*)(desc + ofs_ep); + if (ep->bmAttributes.xfer == TUSB_XFER_ISOCHRONOUS) { + if (stm->state == VS_STATE_COMMITTED) { + return false; + } + } +#endif + + return true; +} + +bool tud_video_n_frame_xfer(uint_fast8_t ctl_idx, uint_fast8_t stm_idx, void *buffer, size_t bufsize) { + TU_ASSERT(ctl_idx < CFG_TUD_VIDEO); + TU_ASSERT(stm_idx < CFG_TUD_VIDEO_STREAMING); + + if (0 == bufsize) { + return false; + } + + videod_streaming_interface_t *stm = _get_instance_streaming(ctl_idx, stm_idx); + videod_streaming_epbuf_t *stm_epbuf = &_videod_streaming_epbuf[ctl_idx]; + + if (NULL == stm || 0 == stm->desc.ep[0] || stm->bufsize) { + return false; + } + if (stm->state == VS_STATE_PROBING) { + return false; + } + + /* Find EP address */ + uint8_t const *desc = _videod_itf[stm->index_vc].beg; + uint8_t ep_addr = 0; + for (uint_fast8_t i = 0; i < CFG_TUD_VIDEO_STREAMING; ++i) { + uint_fast16_t ofs_ep = stm->desc.ep[i]; + if (0 == ofs_ep) { + continue; + } + ep_addr = _desc_ep_addr(desc + ofs_ep); + break; + } + if (0 == ep_addr) { + return false; + } + + TU_VERIFY(usbd_edpt_claim(0, ep_addr)); + /* update the packet header */ + tusb_video_payload_header_t *hdr = (tusb_video_payload_header_t*)stm_epbuf->buf; + hdr->FrameID ^= 1; + hdr->EndOfFrame = 0; + /* update the packet data */ + stm->buffer = (uint8_t*)buffer; + stm->bufsize = bufsize; + uint_fast16_t pkt_len = _prepare_in_payload(stm, stm_epbuf->buf); + TU_ASSERT( usbd_edpt_xfer(0, ep_addr, stm_epbuf->buf, (uint16_t) pkt_len, false), 0); + return true; +} + +//--------------------------------------------------------------------+ +// USBD Driver API +//--------------------------------------------------------------------+ +void videod_init(void) { + for (uint_fast8_t i = 0; i < CFG_TUD_VIDEO; ++i) { + videod_interface_t* ctl = &_videod_itf[i]; + tu_memclr(ctl, sizeof(*ctl)); + } + for (uint_fast8_t i = 0; i < CFG_TUD_VIDEO_STREAMING; ++i) { + videod_streaming_interface_t *stm = &_videod_streaming_itf[i]; + tu_memclr(stm, sizeof(videod_streaming_interface_t)); + } +} + +bool videod_deinit(void) { + return true; +} + +void videod_reset(uint8_t rhport) { + (void) rhport; + for (uint_fast8_t i = 0; i < CFG_TUD_VIDEO; ++i) { + videod_interface_t* ctl = &_videod_itf[i]; + tu_memclr(ctl, sizeof(*ctl)); + } + for (uint_fast8_t i = 0; i < CFG_TUD_VIDEO_STREAMING; ++i) { + videod_streaming_interface_t *stm = &_videod_streaming_itf[i]; + tu_memclr(stm, sizeof(videod_streaming_interface_t)); + } +} + +uint16_t videod_open(uint8_t rhport, tusb_desc_interface_t const * itf_desc, uint16_t max_len) { + TU_VERIFY((TUSB_CLASS_VIDEO == itf_desc->bInterfaceClass) && + (VIDEO_SUBCLASS_CONTROL == itf_desc->bInterfaceSubClass) && + (VIDEO_ITF_PROTOCOL_15 == itf_desc->bInterfaceProtocol), 0); + + /* Find available interface */ + videod_interface_t *self = NULL; + uint8_t ctl_idx; + for (ctl_idx = 0; ctl_idx < CFG_TUD_VIDEO; ++ctl_idx) { + if (NULL != _videod_itf[ctl_idx].beg) { + continue; + } + self = &_videod_itf[ctl_idx]; + break; + } + TU_ASSERT(ctl_idx < CFG_TUD_VIDEO, 0); + + uint8_t const *end = (uint8_t const*)itf_desc + max_len; + self->beg = (uint8_t const*) itf_desc; + self->len = max_len; + + /*------------- Video Control Interface -------------*/ + TU_VERIFY(_open_vc_itf(rhport, self, 0), 0); + tusb_desc_vc_itf_t const *vc = _get_desc_vc(self); + uint_fast8_t bInCollection = vc->ctl.bInCollection; + + /* Find the end of the video interface descriptor */ + void const *cur = _next_desc_itf(itf_desc, end); + for (uint8_t stm_idx = 0; stm_idx < bInCollection; ++stm_idx) { + videod_streaming_interface_t *stm = NULL; + /* find free streaming interface handle */ + for (uint8_t i = 0; i < CFG_TUD_VIDEO_STREAMING; ++i) { + if (0 != _videod_streaming_itf[i].desc.beg) { + continue; + } + stm = &_videod_streaming_itf[i]; + self->stm[stm_idx] = i; + break; + } + TU_ASSERT(stm, 0); + stm->index_vc = ctl_idx; + stm->index_vs = stm_idx; + stm->desc.beg = (uint16_t) ((uintptr_t)cur - (uintptr_t)itf_desc); + cur = _next_desc_itf(cur, end); + stm->desc.end = (uint16_t) ((uintptr_t)cur - (uintptr_t)itf_desc); + stm->state = VS_STATE_PROBING; +#ifdef TUP_DCD_EDPT_ISO_ALLOC + /* Allocate ISO endpoints */ + uint16_t ep_size = 0; + uint8_t ep_addr = 0; + uint8_t const *p_desc = (uint8_t const*)itf_desc + stm->desc.beg; + uint8_t const *p_desc_end = (uint8_t const*)itf_desc + stm->desc.end; + while (p_desc < p_desc_end) { + if (tu_desc_type(p_desc) == TUSB_DESC_ENDPOINT) { + tusb_desc_endpoint_t const *desc_ep = (tusb_desc_endpoint_t const *) p_desc; + if (desc_ep->bmAttributes.xfer == TUSB_XFER_ISOCHRONOUS) { + ep_addr = desc_ep->bEndpointAddress; + ep_size = TU_MAX(tu_edpt_packet_size(desc_ep), ep_size); + } + } + p_desc = tu_desc_next(p_desc); + } + if(ep_addr > 0 && ep_size > 0) { + usbd_edpt_iso_alloc(rhport, ep_addr, ep_size); + } +#endif + if (0 == stm_idx && 1 == bInCollection) { + /* If there is only one streaming interface and no alternate settings, + * host may not issue set_interface so open the streaming interface here. */ + uint8_t const *sbeg = (uint8_t const*)itf_desc + stm->desc.beg; + uint8_t const *send = (uint8_t const*)itf_desc + stm->desc.end; + if (send == _find_desc_itf(sbeg, send, _desc_itfnum(sbeg), 1)) { + TU_VERIFY(_open_vs_itf(rhport, stm, 0), 0); + } + } + } + self->len = (uint16_t) ((uintptr_t)cur - (uintptr_t)itf_desc); + return (uint16_t) ((uintptr_t)cur - (uintptr_t)itf_desc); +} + +// Invoked when a control transfer occurred on an interface of this class +// Driver response accordingly to the request and the transfer stage (setup/data/ack) +// return false to stall control endpoint (e.g unsupported request) +bool videod_control_xfer_cb(uint8_t rhport, uint8_t stage, tusb_control_request_t const * request) { + int err; + TU_VERIFY(request->bmRequestType_bit.recipient == TUSB_REQ_RCPT_INTERFACE); + uint_fast8_t itfnum = tu_u16_low(request->wIndex); + /* Identify which control interface to use */ + uint_fast8_t itf; + for (itf = 0; itf < CFG_TUD_VIDEO; ++itf) { + void const *desc = _videod_itf[itf].beg; + if (!desc) { + continue; + } + if (itfnum == _desc_itfnum(desc)) { + break; + } + } + + if (itf < CFG_TUD_VIDEO) { + TU_LOG_DRV(" VC[%d]: ", itf); + err = handle_video_ctl_req(rhport, stage, request, itf); + _videod_itf[itf].error_code = (uint8_t)err; + if (0 != err) { + return false; + } + return true; + } + + /* Identify which streaming interface to use */ + for (itf = 0; itf < CFG_TUD_VIDEO_STREAMING; ++itf) { + videod_streaming_interface_t *stm = &_videod_streaming_itf[itf]; + if (0 == stm->desc.beg) { + continue; + } + uint8_t const *desc = _videod_itf[stm->index_vc].beg; + if (itfnum == _desc_itfnum(desc + stm->desc.beg)) { + break; + } + } + + if (itf < CFG_TUD_VIDEO_STREAMING) { + TU_LOG_DRV(" VS[%d]: ", itf); + err = handle_video_stm_req(rhport, stage, request, itf); + _videod_streaming_itf[itf].error_code = (uint8_t)err; + if (err != 0) { + return false; + } + return true; + } + return false; +} + +bool videod_xfer_cb(uint8_t rhport, uint8_t ep_addr, xfer_result_t result, uint32_t xferred_bytes) { + (void)result; (void)xferred_bytes; + + /* find streaming handle */ + uint_fast8_t itf; + videod_interface_t *ctl; + videod_streaming_interface_t *stm; + for (itf = 0; itf < CFG_TUD_VIDEO_STREAMING; ++itf) { + stm = &_videod_streaming_itf[itf]; + uint_fast16_t const ep_ofs = stm->desc.ep[0]; + if (0 == ep_ofs) { + continue; + } + ctl = &_videod_itf[stm->index_vc]; + uint8_t const *desc = ctl->beg; + if (ep_addr == _desc_ep_addr(desc + ep_ofs)) { + break; + } + } + TU_ASSERT(itf < CFG_TUD_VIDEO_STREAMING); + videod_streaming_epbuf_t *stm_epbuf = &_videod_streaming_epbuf[itf]; + + if (stm->offset < stm->bufsize) { + /* Claim the endpoint */ + TU_VERIFY(usbd_edpt_claim(rhport, ep_addr), 0); + uint_fast16_t pkt_len = _prepare_in_payload(stm, stm_epbuf->buf); + TU_ASSERT(usbd_edpt_xfer(rhport, ep_addr, stm_epbuf->buf, (uint16_t) pkt_len, false), 0); + } else { + stm->buffer = NULL; + stm->bufsize = 0; + stm->offset = 0; + tud_video_frame_xfer_complete_cb(stm->index_vc, stm->index_vs); + } + return true; +} + +#endif diff --git a/libraries/Adafruit_TinyUSB_Arduino/src/class/video/video_device.h b/libraries/Adafruit_TinyUSB_Arduino/src/class/video/video_device.h new file mode 100644 index 0000000..2750bb2 --- /dev/null +++ b/libraries/Adafruit_TinyUSB_Arduino/src/class/video/video_device.h @@ -0,0 +1,119 @@ +/* + * The MIT License (MIT) + * + * Copyright (c) 2019 Ha Thach (tinyusb.org) + * Copyright (c) 2021 Koji KITAYAMA + * + * 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_VIDEO_DEVICE_H_ +#define TUSB_VIDEO_DEVICE_H_ + +#include "common/tusb_common.h" +#include "video.h" + +#ifdef __cplusplus +extern "C" { +#endif + + +//--------------------------------------------------------------------+ +// Payload request +//--------------------------------------------------------------------+ +typedef struct TU_ATTR_PACKED { + void* buf; /* Payload buffer to be filled */ + size_t length; /* Length of the requested data in bytes */ + size_t offset; /* Offset within the frame (in bytes) */ +} tud_video_payload_request_t; + +//--------------------------------------------------------------------+ +// Application API (Multiple Ports) +// CFG_TUD_VIDEO > 1 +//--------------------------------------------------------------------+ + +bool tud_video_n_connected(uint_fast8_t ctl_idx); + +/** Return true if streaming + * + * @param[in] ctl_idx Destination control interface index + * @param[in] stm_idx Destination streaming interface index */ +bool tud_video_n_streaming(uint_fast8_t ctl_idx, uint_fast8_t stm_idx); + +/** Transfer a frame + * + * @param[in] ctl_idx Destination control interface index + * @param[in] stm_idx Destination streaming interface index + * @param[in] buffer Frame buffer. The caller must not use this buffer until the operation is completed. + * @param[in] bufsize Byte size of the frame buffer */ +bool tud_video_n_frame_xfer(uint_fast8_t ctl_idx, uint_fast8_t stm_idx, void *buffer, size_t bufsize); + +/*------------- Optional callbacks -------------*/ +/** Invoked when compeletion of a frame transfer + * + * @param[in] ctl_idx Destination control interface index + * @param[in] stm_idx Destination streaming interface index */ +void tud_video_frame_xfer_complete_cb(uint_fast8_t ctl_idx, uint_fast8_t stm_idx); + +//--------------------------------------------------------------------+ +// Application Callback API (weak is optional) +//--------------------------------------------------------------------+ + +/** Invoked when SET_POWER_MODE request received + * + * @param[in] ctl_idx Destination control interface index + * @param[in] stm_idx Destination streaming interface index + * @return video_error_code_t */ +int tud_video_power_mode_cb(uint_fast8_t ctl_idx, uint8_t power_mod); + +/** Invoked when VS_COMMIT_CONTROL(SET_CUR) request received + * + * @param[in] ctl_idx Destination control interface index + * @param[in] stm_idx Destination streaming interface index + * @param[in] parameters Video streaming parameters + * @return video_error_code_t */ +int tud_video_commit_cb(uint_fast8_t ctl_idx, uint_fast8_t stm_idx, + video_probe_and_commit_control_t const *parameters); + +/** Invoked if buffer is set to NULL (allows bufferless on the fly data generation) + * + * @param[in] ctl_idx Destination control interface index + * @param[in] stm_idx Destination streaming interface index + * @param[out] payload_buf Payload storage buffer (target buffer for requested data) + * @param[in] payload_size Size of payload_buf (requested data size) + * @param[in] offset Current byte offset relative to given bufsize from tud_video_n_frame_xfer (framesize) */ +void tud_video_prepare_payload_cb(uint_fast8_t ctl_idx, uint_fast8_t stm_idx, tud_video_payload_request_t* request); + +//--------------------------------------------------------------------+ +// INTERNAL USBD-CLASS DRIVER API +//--------------------------------------------------------------------+ +void videod_init (void); +bool videod_deinit (void); +void videod_reset (uint8_t rhport); +uint16_t videod_open (uint8_t rhport, tusb_desc_interface_t const * itf_desc, uint16_t max_len); +bool videod_control_xfer_cb(uint8_t rhport, uint8_t stage, tusb_control_request_t const * request); +bool videod_xfer_cb (uint8_t rhport, uint8_t ep_addr, xfer_result_t result, uint32_t xferred_bytes); + +#ifdef __cplusplus + } +#endif + +#endif diff --git a/libraries/Adafruit_TinyUSB_Arduino/src/common/tusb_common.h b/libraries/Adafruit_TinyUSB_Arduino/src/common/tusb_common.h new file mode 100644 index 0000000..6ac1405 --- /dev/null +++ b/libraries/Adafruit_TinyUSB_Arduino/src/common/tusb_common.h @@ -0,0 +1,407 @@ +/* + * 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. + * + * This file is part of the TinyUSB stack. + */ + +#ifndef TUSB_COMMON_H_ +#define TUSB_COMMON_H_ + +#ifdef __cplusplus + extern "C" { +#endif + +//--------------------------------------------------------------------+ +// Macros Helper +//--------------------------------------------------------------------+ +#define TU_ARRAY_SIZE(_arr) ( sizeof(_arr) / sizeof(_arr[0]) ) +#define TU_FIELD_SIZE(_type, _field) (sizeof(((_type *)0)->_field)) +#define TU_MIN(_x, _y) ( ( (_x) < (_y) ) ? (_x) : (_y) ) +#define TU_MAX(_x, _y) ( ( (_x) > (_y) ) ? (_x) : (_y) ) +#define TU_DIV_CEIL(n, d) (((n) + (d) - 1) / (d)) +#define TU_DIV_ROUND_NEAREST(v, d) (((v) + (d)/2) / (d) ) // round to nearest integer + +#define TU_U16(_high, _low) ((uint16_t) ((((uint16_t) (_high)) << 8) | ((uint16_t) (_low)))) +#define TU_U16_HIGH(_u16) ((uint8_t) (((uint16_t) (_u16) >> 8) & 0x00ffu)) +#define TU_U16_LOW(_u16) ((uint8_t) ((uint16_t) (_u16) & 0x00ffu)) +#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 TU_U24(_high, _mid, _low) ((uint32_t) ((((uint32_t) (_high)) << 16) | (((uint32_t) (_mid)) << 8) | ((uint32_t) (_low)))) +#define TU_U24_HIGH(_u24) ((uint8_t) (((uint32_t) (_u24) >> 16) & 0x0000ffu)) +#define TU_U24_MID(_u24) ((uint8_t) (((uint32_t) (_u24) >> 8) & 0x0000ffu)) +#define TU_U24_LOW(_u24) ((uint8_t) ((uint32_t) (_u24) & 0x0000ffu)) +#define U24_TO_U8S_BE(_u24) TU_U24_HIGH(_u24), TU_U24_MID(_u24), TU_U24_LOW(_u24) +#define U24_TO_U8S_LE(_u24) TU_U24_LOW(_u24), TU_U24_MID(_u24), TU_U24_HIGH(_u24) + +#define TU_U32_BYTE3(_u32) ((uint8_t) ((((uint32_t) _u32) >> 24) & 0x000000ff)) // MSB +#define TU_U32_BYTE2(_u32) ((uint8_t) ((((uint32_t) _u32) >> 16) & 0x000000ff)) +#define TU_U32_BYTE1(_u32) ((uint8_t) ((((uint32_t) _u32) >> 8) & 0x000000ff)) +#define TU_U32_BYTE0(_u32) ((uint8_t) (((uint32_t) _u32) & 0x000000ff)) // LSB + +#define U32_TO_U8S_BE(_u32) TU_U32_BYTE3(_u32), TU_U32_BYTE2(_u32), TU_U32_BYTE1(_u32), TU_U32_BYTE0(_u32) +#define U32_TO_U8S_LE(_u32) TU_U32_BYTE0(_u32), TU_U32_BYTE1(_u32), TU_U32_BYTE2(_u32), TU_U32_BYTE3(_u32) + +#define TU_BIT(n) (1UL << (n)) + +// Generate a mask with bit from high (31) to low (0) set, e.g TU_GENMASK(3, 0) = 0b1111 +#define TU_GENMASK(h, l) ( (UINT32_MAX << (l)) & (UINT32_MAX >> (31 - (h))) ) + +//--------------------------------------------------------------------+ +// Includes +//--------------------------------------------------------------------+ + +// Standard Headers +#include +#include +#include +#include +#include + +// Tinyusb Common Headers +#include "tusb_option.h" +#include "tusb_compiler.h" +#include "tusb_verify.h" +#include "tusb_types.h" +#include "tusb_debug.h" + +//--------------------------------------------------------------------+ +// API implemented by application if needed +// TODO move to a more obvious place/file +//--------------------------------------------------------------------+ + +// Get current milliseconds, required by some port/configuration without RTOS +extern uint32_t tusb_time_millis_api(void); + +// Delay in milliseconds, use tusb_time_millis_api() by default. required by some port/configuration with no RTOS +extern void tusb_time_delay_ms_api(uint32_t ms); + +// flush data cache +extern void tusb_app_dcache_flush(uintptr_t addr, uint32_t data_size); + +// invalidate data cache +extern void tusb_app_dcache_invalidate(uintptr_t addr, uint32_t data_size); + +// Optional physical <-> virtual address translation +extern void* tusb_app_virt_to_phys(void *virt_addr); +extern void* tusb_app_phys_to_virt(void *phys_addr); + +//--------------------------------------------------------------------+ +// Internal Inline Functions +//--------------------------------------------------------------------+ + +//------------- Mem -------------// +#define tu_memclr(buffer, size) (void) memset((buffer), 0, (size)) +#define tu_varclr(_var) tu_memclr(_var, sizeof(*(_var))) + +// This is a backport of memset_s from c11 +TU_ATTR_ALWAYS_INLINE static inline int tu_memset_s(void *dest, size_t destsz, int ch, size_t count) { + // Validate parameters + if (dest == NULL) { + return -1; + } + + if (count == 0u) { + return 0; + } + + if (count > destsz) { + return -1; + } + + (void) memset(dest, ch, count); + return 0; +} + +// This is a backport of memcpy_s from c11 +TU_ATTR_ALWAYS_INLINE static inline int tu_memcpy_s(void *dest, size_t destsz, const void *src, size_t count) { + if (dest == NULL) { + return -1; + } + + if (count == 0u) { + return 0; + } + + if (src == NULL) { + return -1; + } + + if (count > destsz) { + return -1; + } + + (void) memcpy(dest, src, count); + return 0; +} + +TU_ATTR_ALWAYS_INLINE static inline bool tu_mem_is_zero(const void *buffer, size_t size) { + const uint8_t* buf8 = (const uint8_t*) buffer; + for (size_t i = 0; i < size; i++) { + if (buf8[i] != 0) { return false; } + } + return true; +} + +TU_ATTR_ALWAYS_INLINE static inline bool tu_mem_is_ff(const void *buffer, size_t size) { + const uint8_t* buf8 = (const uint8_t*) buffer; + for (size_t i = 0; i < size; i++) { + if (buf8[i] != 0xff) { return false; } + } + return true; +} + + +//------------- Bytes -------------// +TU_ATTR_ALWAYS_INLINE static inline uint32_t tu_u32(uint8_t b3, uint8_t b2, uint8_t b1, uint8_t b0) { + return (((uint32_t)b3) << 24) | (((uint32_t)b2) << 16) | (((uint32_t)b1) << 8) | b0; +} + +TU_ATTR_ALWAYS_INLINE static inline uint32_t tu_u32_from_u16(uint16_t high, uint16_t low) { + return (((uint32_t)high) << 16) | low; +} + +TU_ATTR_ALWAYS_INLINE static inline uint16_t tu_u16(uint8_t high, uint8_t low) { + return (uint16_t)((((uint16_t)high) << 8) | low); +} + +TU_ATTR_ALWAYS_INLINE static inline uint8_t tu_u32_byte3(uint32_t ui32) { return TU_U32_BYTE3(ui32); } +TU_ATTR_ALWAYS_INLINE static inline uint8_t tu_u32_byte2(uint32_t ui32) { return TU_U32_BYTE2(ui32); } +TU_ATTR_ALWAYS_INLINE static inline uint8_t tu_u32_byte1(uint32_t ui32) { return TU_U32_BYTE1(ui32); } +TU_ATTR_ALWAYS_INLINE static inline uint8_t tu_u32_byte0(uint32_t ui32) { return TU_U32_BYTE0(ui32); } + +TU_ATTR_ALWAYS_INLINE static inline uint16_t tu_u32_high16(uint32_t ui32) { return (uint16_t) (ui32 >> 16); } +TU_ATTR_ALWAYS_INLINE static inline uint16_t tu_u32_low16 (uint32_t ui32) { return (uint16_t) (ui32 & 0x0000ffffu); } + +TU_ATTR_ALWAYS_INLINE static inline uint8_t tu_u16_high(uint16_t ui16) { return TU_U16_HIGH(ui16); } +TU_ATTR_ALWAYS_INLINE static inline uint8_t tu_u16_low (uint16_t ui16) { return TU_U16_LOW(ui16); } + +//------------- Bits -------------// +TU_ATTR_ALWAYS_INLINE static inline uint32_t tu_bit_set (uint32_t value, uint8_t pos) { return value | TU_BIT(pos); } +TU_ATTR_ALWAYS_INLINE static inline uint32_t tu_bit_clear(uint32_t value, uint8_t pos) { return value & (~TU_BIT(pos)); } +TU_ATTR_ALWAYS_INLINE static inline bool tu_bit_test (uint32_t value, uint8_t pos) { return (value & TU_BIT(pos)) ? true : false; } + +//------------- Min -------------// +TU_ATTR_ALWAYS_INLINE static inline uint8_t tu_min8 (uint8_t x, uint8_t y ) { return (x < y) ? x : y; } +TU_ATTR_ALWAYS_INLINE static inline uint16_t tu_min16 (uint16_t x, uint16_t y) { return (x < y) ? x : y; } +TU_ATTR_ALWAYS_INLINE static inline uint32_t tu_min32 (uint32_t x, uint32_t y) { return (x < y) ? x : y; } + +//------------- Max -------------// +TU_ATTR_ALWAYS_INLINE static inline uint8_t tu_max8 (uint8_t x, uint8_t y ) { return (x > y) ? x : y; } +TU_ATTR_ALWAYS_INLINE static inline uint16_t tu_max16 (uint16_t x, uint16_t y) { return (x > y) ? x : y; } +TU_ATTR_ALWAYS_INLINE static inline uint32_t tu_max32 (uint32_t x, uint32_t y) { return (x > y) ? x : y; } + +//------------- Align -------------// +TU_ATTR_ALWAYS_INLINE static inline uint32_t tu_align(uint32_t value, uint32_t alignment) { + return value & ((uint32_t) ~(alignment-1)); +} + +TU_ATTR_ALWAYS_INLINE static inline uint32_t tu_align4 (uint32_t value) { return (value & 0xFFFFFFFCUL); } +TU_ATTR_ALWAYS_INLINE static inline uint32_t tu_align8 (uint32_t value) { return (value & 0xFFFFFFF8UL); } +TU_ATTR_ALWAYS_INLINE static inline uint32_t tu_align16 (uint32_t value) { return (value & 0xFFFFFFF0UL); } +TU_ATTR_ALWAYS_INLINE static inline uint32_t tu_align32 (uint32_t value) { return (value & 0xFFFFFFE0UL); } +TU_ATTR_ALWAYS_INLINE static inline uint32_t tu_align4k (uint32_t value) { return (value & 0xFFFFF000UL); } +TU_ATTR_ALWAYS_INLINE static inline uint32_t tu_offset4k(uint32_t value) { return (value & 0xFFFUL); } + +TU_ATTR_ALWAYS_INLINE static inline bool tu_is_aligned32(uint32_t value) { return (value & 0x1FUL) == 0; } +TU_ATTR_ALWAYS_INLINE static inline bool tu_is_aligned64(uint64_t value) { return (value & 0x3FUL) == 0; } + +//------------- Mathematics -------------// +TU_ATTR_ALWAYS_INLINE static inline uint32_t tu_div_ceil(uint32_t v, uint32_t d) { return TU_DIV_CEIL(v, d); } +TU_ATTR_ALWAYS_INLINE static inline uint32_t tu_div_round_nearest(uint32_t v, uint32_t d) { return TU_DIV_ROUND_NEAREST(v, d); } + +TU_ATTR_ALWAYS_INLINE static inline uint32_t tu_round_up(uint32_t v, uint32_t f) { return tu_div_ceil(v, f) * f; } + +// log2 of a value is its MSB's position +// TODO use clz TODO remove +TU_ATTR_ALWAYS_INLINE static inline uint8_t tu_log2(uint32_t value) { + uint8_t result = 0; + while ((value >>= 1u) != 0u) { + result++; + } + return result; +} + +//static inline uint8_t tu_log2(uint32_t value) +//{ +// return sizeof(uint32_t) * CHAR_BIT - __builtin_clz(x) - 1; +//} + +TU_ATTR_ALWAYS_INLINE static inline bool tu_is_power_of_two(uint32_t value) { + return (value != 0) && ((value & (value - 1)) == 0); +} + +//------------- Unaligned Access -------------// +#if TUP_ARCH_STRICT_ALIGN + +// Rely on compiler to generate correct code for unaligned access +typedef struct { uint16_t val; } TU_ATTR_PACKED tu_unaligned_uint16_t; +typedef struct { uint32_t val; } TU_ATTR_PACKED tu_unaligned_uint32_t; + +TU_ATTR_ALWAYS_INLINE static inline uint32_t tu_unaligned_read32(const void *mem) { + tu_unaligned_uint32_t const *ua32 = (tu_unaligned_uint32_t const *) mem; + return ua32->val; +} + +TU_ATTR_ALWAYS_INLINE static inline void tu_unaligned_write32(void *mem, uint32_t value) { + tu_unaligned_uint32_t *ua32 = (tu_unaligned_uint32_t *) mem; + ua32->val = value; +} + +TU_ATTR_ALWAYS_INLINE static inline uint16_t tu_unaligned_read16(const void *mem) { + tu_unaligned_uint16_t const *ua16 = (tu_unaligned_uint16_t const *) mem; + return ua16->val; +} + +TU_ATTR_ALWAYS_INLINE static inline void tu_unaligned_write16(void *mem, uint16_t value) { + tu_unaligned_uint16_t *ua16 = (tu_unaligned_uint16_t *) mem; + ua16->val = value; +} + +#elif TUP_MCU_STRICT_ALIGN + +// MCU such as LPC_IP3511 Highspeed cannot access unaligned memory on USB_RAM although it is ARM M4. +// We have to manually pick up bytes since tu_unaligned_uint32_t will still generate unaligned code +// NOTE: volatile cast to memory to prevent compiler to optimize and generate unaligned code +// TODO Big Endian may need minor changes +TU_ATTR_ALWAYS_INLINE static inline uint32_t tu_unaligned_read32(const void* mem) { + volatile uint8_t const* buf8 = (uint8_t const*) mem; + return tu_u32(buf8[3], buf8[2], buf8[1], buf8[0]); +} + +TU_ATTR_ALWAYS_INLINE static inline void tu_unaligned_write32(void* mem, uint32_t value) { + volatile uint8_t* buf8 = (uint8_t*) mem; + buf8[0] = tu_u32_byte0(value); + buf8[1] = tu_u32_byte1(value); + buf8[2] = tu_u32_byte2(value); + buf8[3] = tu_u32_byte3(value); +} + +TU_ATTR_ALWAYS_INLINE static inline uint16_t tu_unaligned_read16(const void* mem) { + volatile uint8_t const* buf8 = (uint8_t const*) mem; + return tu_u16(buf8[1], buf8[0]); +} + +TU_ATTR_ALWAYS_INLINE static inline void tu_unaligned_write16(void* mem, uint16_t value) { + volatile uint8_t* buf8 = (uint8_t*) mem; + buf8[0] = tu_u16_low(value); + buf8[1] = tu_u16_high(value); +} + +#else + +// MCU that could access unaligned memory natively +TU_ATTR_ALWAYS_INLINE static inline uint32_t tu_unaligned_read32(const void *mem) { + return *((uint32_t const *) mem); +} + +TU_ATTR_ALWAYS_INLINE static inline uint16_t tu_unaligned_read16(const void *mem) { + return *((uint16_t const *) mem); +} + +TU_ATTR_ALWAYS_INLINE static inline void tu_unaligned_write32(void *mem, uint32_t value) { + *((uint32_t *) mem) = value; +} + +TU_ATTR_ALWAYS_INLINE static inline void tu_unaligned_write16(void *mem, uint16_t value) { + *((uint16_t *) mem) = value; +} + +#endif + +// scatter read 4 bytes from two buffers (LE). Parameter are not checked +TU_ATTR_ALWAYS_INLINE static inline uint32_t tu_scatter_read32(const uint8_t *buf1, uint8_t len1, const uint8_t *buf2, + uint8_t len2) { + uint32_t result = 0; + uint8_t shift = 0; + + for (uint8_t i = 0; i < len1; ++i) { + result |= ((uint32_t)buf1[i]) << shift; + shift += 8; + } + + for (uint8_t i = 0; i < len2; ++i) { + result |= ((uint32_t)buf2[i]) << shift; + shift += 8; + } + + return result; +} + +// scatter write 4 bytes (LE) to two buffers. Parameter are not checked +TU_ATTR_ALWAYS_INLINE static inline void tu_scatter_write32(uint32_t value, uint8_t *buf1, uint8_t len1, + uint8_t *buf2, uint8_t len2) { + for (uint8_t i = 0; i < len1; ++i) { + buf1[i] = (uint8_t)(value & 0xFF); + value >>= 8; + } + + for (uint8_t i = 0; i < len2; ++i) { + buf2[i] = (uint8_t)(value & 0xFF); + value >>= 8; + } +} + +//--------------------------------------------------------------------+ +// Descriptor helper +//--------------------------------------------------------------------+ + +// return next descriptor +TU_ATTR_ALWAYS_INLINE static inline uint8_t const * tu_desc_next(void const* desc) { + uint8_t const* desc8 = (uint8_t const*) desc; + return desc8 + desc8[DESC_OFFSET_LEN]; +} + +// get descriptor length +TU_ATTR_ALWAYS_INLINE static inline uint8_t tu_desc_len(void const* desc) { + return ((uint8_t const*) desc)[DESC_OFFSET_LEN]; +} + +// get descriptor type +TU_ATTR_ALWAYS_INLINE static inline uint8_t tu_desc_type(void const* desc) { + return ((uint8_t const*) desc)[DESC_OFFSET_TYPE]; +} + +// get descriptor subtype +TU_ATTR_ALWAYS_INLINE static inline uint8_t tu_desc_subtype(void const* desc) { + return ((uint8_t const*) desc)[DESC_OFFSET_SUBTYPE]; +} + +TU_ATTR_ALWAYS_INLINE static inline bool tu_desc_in_bounds(const uint8_t *p_desc, const uint8_t *desc_end) { + return p_desc < desc_end && tu_desc_next(p_desc) <= desc_end; +} + +// find descriptor that match byte1 (type) +uint8_t const * tu_desc_find(uint8_t const* desc, uint8_t const* end, uint8_t byte1); + +// find descriptor that match byte1 (type) and byte2 +uint8_t const * tu_desc_find2(uint8_t const* desc, uint8_t const* end, uint8_t byte1, uint8_t byte2); + +// find descriptor that match byte1 (type) and byte2 +uint8_t const * tu_desc_find3(uint8_t const* desc, uint8_t const* end, uint8_t byte1, uint8_t byte2, uint8_t byte3); + +#ifdef __cplusplus + } +#endif + +#endif /* TUSB_COMMON_H_ */ diff --git a/libraries/Adafruit_TinyUSB_Arduino/src/common/tusb_compiler.h b/libraries/Adafruit_TinyUSB_Arduino/src/common/tusb_compiler.h new file mode 100644 index 0000000..a8971c3 --- /dev/null +++ b/libraries/Adafruit_TinyUSB_Arduino/src/common/tusb_compiler.h @@ -0,0 +1,293 @@ +/* + * 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. + * + * This file is part of the TinyUSB stack. + */ + +#pragma once + +#define TU_TOKEN(x) x +#define TU_STRING(x) #x ///< stringify without expand +#define TU_XSTRING(x) TU_STRING(x) ///< expand then stringify +#define TU_STRCAT(a, b) a##b ///< concat without expand +#define TU_STRCAT3(a, b, c) a##b##c ///< concat without expand +#define TU_XSTRCAT(a, b) TU_STRCAT(a, b) ///< expand then concat +#define TU_XSTRCAT3(a, b, c) TU_STRCAT3(a, b, c) ///< expand then concat 3 tokens + +#define TU_INCLUDE_PATH(_dir,_file) TU_XSTRING( TU_TOKEN(_dir)TU_TOKEN(_file) ) + +#if defined __COUNTER__ && __COUNTER__ != __COUNTER__ + #define TU_COUNTER __COUNTER__ +#else + #define TU_COUNTER __LINE__ +#endif + +// Compile-time Assert +#if defined (__cplusplus) && __cplusplus >= 201103L + #define TU_VERIFY_STATIC static_assert +#elif defined (__STDC_VERSION__) && __STDC_VERSION__ >= 201112L + #define TU_VERIFY_STATIC _Static_assert +#elif defined(__CCRX__) + #define TU_VERIFY_STATIC(const_expr, _mess) typedef char TU_XSTRCAT(_verify_static_, TU_COUNTER)[(const_expr) ? 1 : 0]; +#else + #define TU_VERIFY_STATIC(const_expr, _mess) enum { TU_XSTRCAT(_verify_static_, TU_COUNTER) = 1/(!!(const_expr)) } +#endif + +/* --------------------- Fuzzing types -------------------------------------- */ +#ifdef _FUZZ + #define tu_static static __thread +#else + #define tu_static static +#endif + +// for declaration of reserved field, make use of TU_COUNTER +#define TU_RESERVED TU_XSTRCAT(reserved, TU_COUNTER) + +#define TU_LITTLE_ENDIAN (0x12u) +#define TU_BIG_ENDIAN (0x21u) + +#define TU_BITFIELD_LE (0x34u) +#define TU_BITFIELD_BE (0x43u) + +/*------------------------------------------------------------------*/ +/* Count number of arguments of __VA_ARGS__ + * - reference www.stackoverflow.com/questions/2124339/c-preprocessor-va-args-number-of-arguments + * - TU_GET_NTH_ARG() takes args >= N (64) but only expand to Nth one (64th) + * - TU_NARG_RSEQ_N() is reverse sequential to N to add padding to have + * Nth position is the same as the number of arguments + * - ##__VA_ARGS__ is used to deal with 0 paramerter (swallows comma) + *------------------------------------------------------------------*/ +#if defined(__CCRX__) +#define TU_ARGS_NUM(...) TU_NARG_IMPL(_0, __VA_ARGS__, TU_NARG_RSEQ_N()) +#else +#define TU_ARGS_NUM(...) TU_NARG_IMPL(_0, ##__VA_ARGS__, TU_NARG_RSEQ_N()) +#endif + +#define TU_NARG_IMPL(...) TU_GET_NTH_ARG(__VA_ARGS__) +#define TU_GET_NTH_ARG( \ + _1, _2, _3, _4, _5, _6, _7, _8, _9,_10, \ + _11,_12,_13,_14,_15,_16,_17,_18,_19,_20, \ + _21,_22,_23,_24,_25,_26,_27,_28,_29,_30, \ + _31,_32,_33,_34,_35,_36,_37,_38,_39,_40, \ + _41,_42,_43,_44,_45,_46,_47,_48,_49,_50, \ + _51,_52,_53,_54,_55,_56,_57,_58,_59,_60, \ + _61,_62,_63,N,...) N +#define TU_NARG_RSEQ_N() \ + 62,61,60, \ + 59,58,57,56,55,54,53,52,51,50, \ + 49,48,47,46,45,44,43,42,41,40, \ + 39,38,37,36,35,34,33,32,31,30, \ + 29,28,27,26,25,24,23,22,21,20, \ + 19,18,17,16,15,14,13,12,11,10, \ + 9,8,7,6,5,4,3,2,1,0 + +// Apply a macro X to each of the arguments with a separation/delimiter +#define TU_ARGS_APPLY(_X, _s, ...) TU_XSTRCAT(TU_ARGS_APPLY_, TU_ARGS_NUM(__VA_ARGS__))(_X, _s, __VA_ARGS__) + +#define TU_ARGS_APPLY_1(_X, _s, _a1) _X(_a1) +#define TU_ARGS_APPLY_2(_X, _s, _a1, _a2) _X(_a1) _s _X(_a2) +#define TU_ARGS_APPLY_3(_X, _s, _a1, _a2, _a3) _X(_a1) _s TU_ARGS_APPLY_2(_X, _s, _a2, _a3) +#define TU_ARGS_APPLY_4(_X, _s, _a1, _a2, _a3, _a4) _X(_a1) _s TU_ARGS_APPLY_3(_X, _s, _a2, _a3, _a4) +#define TU_ARGS_APPLY_5(_X, _s, _a1, _a2, _a3, _a4, _a5) _X(_a1) _s TU_ARGS_APPLY_4(_X, _s, _a2, _a3, _a4, _a5) +#define TU_ARGS_APPLY_6(_X, _s, _a1, _a2, _a3, _a4, _a5, _a6) _X(_a1) _s TU_ARGS_APPLY_5(_X, _s, _a2, _a3, _a4, _a5, _a6) +#define TU_ARGS_APPLY_7(_X, _s, _a1, _a2, _a3, _a4, _a5, _a6, _a7) _X(_a1) _s TU_ARGS_APPLY_6(_X, _s, _a2, _a3, _a4, _a5, _a6, _a7) +#define TU_ARGS_APPLY_8(_X, _s, _a1, _a2, _a3, _a4, _a5, _a6, _a7, _a8) _X(_a1) _s TU_ARGS_APPLY_7(_X, _s, _a2, _a3, _a4, _a5, _a6, _a7, _a8) + +// Apply a macro X to each of the arguments and expand the result with comma +#define TU_ARGS_APPLY_EXPAND(_X, ...) TU_XSTRCAT(TU_ARGS_APPLY_EXPAND_, TU_ARGS_NUM(__VA_ARGS__))(_X, __VA_ARGS__) + +#define TU_ARGS_APPLY_EXPAND_1(_X, _a1) _X(_a1) +#define TU_ARGS_APPLY_EXPAND_2(_X, _a1, _a2) _X(_a1), _X(_a2) +#define TU_ARGS_APPLY_EXPAND_3(_X, _a1, _a2, _a3) _X(_a1), TU_ARGS_APPLY_EXPAND_2(_X, _a2, _a3) +#define TU_ARGS_APPLY_EXPAND_4(_X, _a1, _a2, _a3, _a4) _X(_a1), TU_ARGS_APPLY_EXPAND_3(_X, _a2, _a3, _a4) +#define TU_ARGS_APPLY_EXPAND_5(_X, _a1, _a2, _a3, _a4, _a5) _X(_a1), TU_ARGS_APPLY_EXPAND_4(_X, _a2, _a3, _a4, _a5) +#define TU_ARGS_APPLY_EXPAND_6(_X, _a1, _a2, _a3, _a4, _a5, _a6) _X(_a1), TU_ARGS_APPLY_EXPAND_5(_X, _a2, _a3, _a4, _a5, _a6) +#define TU_ARGS_APPLY_EXPAND_7(_X, _a1, _a2, _a3, _a4, _a5, _a6, _a7) _X(_a1), TU_ARGS_APPLY_EXPAND_6(_X, _a2, _a3, _a4, _a5, _a6, _a7) +#define TU_ARGS_APPLY_EXPAND_8(_X, _a1, _a2, _a3, _a4, _a5, _a6, _a7, _a8) _X(_a1), TU_ARGS_APPLY_EXPAND_7(_X, _a2, _a3, _a4, _a5, _a6, _a7, _a8) + +//--------------------------------------------------------------------+ +// Macro for function default arguments +//--------------------------------------------------------------------+ +#define TU_GET_3RD_ARG(arg1, arg2, arg3, ...) arg3 + +// function expand with number of arguments +#define TU_FUNC_OPTIONAL_ARG(func, ...) TU_XSTRCAT(func##_arg, TU_ARGS_NUM(__VA_ARGS__))(__VA_ARGS__) + +//--------------------------------------------------------------------+ +// Compiler Attribute Abstraction +//--------------------------------------------------------------------+ +#if defined(__GNUC__) || defined(__ICCARM__) || defined(__TI_COMPILER_VERSION__) + #if defined(__ICCARM__) + #include // for builtin functions + #endif + + #define TU_ATTR_ALIGNED(Bytes) __attribute__((aligned(Bytes))) + #define TU_ATTR_SECTION(sec_name) __attribute__((section(#sec_name))) + #define TU_ATTR_PACKED __attribute__((packed)) + #define TU_ATTR_WEAK __attribute__((weak)) +// #define TU_ATTR_WEAK_ALIAS(f) __attribute__ ((weak, alias(#f))) + #ifndef TU_ATTR_ALWAYS_INLINE // allow to override for debug + #define TU_ATTR_ALWAYS_INLINE __attribute__((always_inline)) + #endif + #define TU_ATTR_DEPRECATED(mess) __attribute__ ((deprecated(mess))) // warn if function with this attribute is used + #define TU_ATTR_UNUSED __attribute__ ((unused)) // Function/Variable is meant to be possibly unused + #define TU_ATTR_USED __attribute__ ((used)) // Function/Variable is meant to be used + + #define TU_ATTR_PACKED_BEGIN + #define TU_ATTR_PACKED_END + #define TU_ATTR_BIT_FIELD_ORDER_BEGIN + #define TU_ATTR_BIT_FIELD_ORDER_END + + #if (defined(__has_attribute) && __has_attribute(__fallthrough__)) || defined(__TI_COMPILER_VERSION__) + #define TU_ATTR_FALLTHROUGH __attribute__((fallthrough)) + #else + #define TU_ATTR_FALLTHROUGH \ + do { \ + } while (0) /* fallthrough */ + #endif + +// Endian conversion use well-known host to network (big endian) naming +// For TI ARM compiler, __BYTE_ORDER__ is not defined for MSP430 but still LE + #if __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__ || defined(__MSP430__) + #define TU_BYTE_ORDER TU_LITTLE_ENDIAN + #define TU_BITFIELD_ORDER TU_BITFIELD_LE + #else + #define TU_BYTE_ORDER TU_BIG_ENDIAN + #define TU_BITFIELD_ORDER TU_BITFIELD_BE + #endif + + // Unfortunately XC16 doesn't provide builtins for 32bit endian conversion + #if defined(__XC16) + #define TU_BSWAP16(u16) (__builtin_swap(u16)) + #define TU_BSWAP32(u32) ((((u32) & 0xff000000) >> 24) | \ + (((u32) & 0x00ff0000) >> 8) | \ + (((u32) & 0x0000ff00) << 8) | \ + (((u32) & 0x000000ff) << 24)) + #else + #define TU_BSWAP16(u16) (__builtin_bswap16(u16)) + #define TU_BSWAP32(u32) (__builtin_bswap32(u32)) + #endif + + // List of obsolete callback function that is renamed and should not be defined. + // Put it here since only gcc support this pragma + #if !defined(__ARMCC_VERSION) && !defined(__ICCARM__) + #pragma GCC poison tud_vendor_control_request_cb + #endif + +#elif defined(__ICCARM__) + #include + #define TU_ATTR_ALIGNED(Bytes) __attribute__ ((aligned(Bytes))) + #define TU_ATTR_SECTION(sec_name) __attribute__ ((section(#sec_name))) + #define TU_ATTR_PACKED __attribute__ ((packed)) + #define TU_ATTR_WEAK __attribute__ ((weak)) + // #define TU_ATTR_WEAK_ALIAS(f) __attribute__ ((weak, alias(#f))) + #ifndef TU_ATTR_ALWAYS_INLINE // allow to override for debug + #define TU_ATTR_ALWAYS_INLINE __attribute__ ((always_inline)) + #endif + #define TU_ATTR_DEPRECATED(mess) __attribute__ ((deprecated(mess))) // warn if function with this attribute is used + #define TU_ATTR_UNUSED __attribute__ ((unused)) // Function/Variable is meant to be possibly unused + #define TU_ATTR_USED __attribute__ ((used)) // Function/Variable is meant to be used + #define TU_ATTR_FALLTHROUGH do {} while (0) /* fallthrough */ + + #define TU_ATTR_PACKED_BEGIN + #define TU_ATTR_PACKED_END + #define TU_ATTR_BIT_FIELD_ORDER_BEGIN + #define TU_ATTR_BIT_FIELD_ORDER_END + + // Endian conversion use well-known host to network (big endian) naming + #if __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__ + #define TU_BYTE_ORDER TU_LITTLE_ENDIAN + #define TU_BITFIELD_ORDER TU_BITFIELD_LE + #else + #define TU_BYTE_ORDER TU_BIG_ENDIAN + #define TU_BITFIELD_ORDER TU_BITFIELD_BE + #endif + + #define TU_BSWAP16(u16) (__iar_builtin_REV16(u16)) + #define TU_BSWAP32(u32) (__iar_builtin_REV(u32)) + +#elif defined(__CCRX__) + #define TU_ATTR_ALIGNED(Bytes) + #define TU_ATTR_SECTION(sec_name) + #define TU_ATTR_PACKED + #define TU_ATTR_WEAK + // #define TU_ATTR_WEAK_ALIAS(f) + #define TU_ATTR_ALWAYS_INLINE + #define TU_ATTR_DEPRECATED(mess) + #define TU_ATTR_UNUSED + #define TU_ATTR_USED + #define TU_ATTR_FALLTHROUGH do {} while (0) /* fallthrough */ + + #define TU_ATTR_PACKED_BEGIN _Pragma("pack") + #define TU_ATTR_PACKED_END _Pragma("packoption") + #define TU_ATTR_BIT_FIELD_ORDER_BEGIN _Pragma("bit_order right") + #define TU_ATTR_BIT_FIELD_ORDER_END _Pragma("bit_order") + + // Endian conversion use well-known host to network (big endian) naming + #if defined(__LIT) + #define TU_BYTE_ORDER TU_LITTLE_ENDIAN + #define TU_BITFIELD_ORDER TU_BITFIELD_LE + #else + #define TU_BYTE_ORDER TU_BIG_ENDIAN + #define TU_BITFIELD_ORDER TU_BITFIELD_BE + #endif + + #define TU_BSWAP16(u16) ((unsigned short)_builtin_revw((unsigned long)u16)) + #define TU_BSWAP32(u32) (_builtin_revl(u32)) + +#else + #error "Compiler attribute porting is required" +#endif + + +#if (TU_BYTE_ORDER == TU_LITTLE_ENDIAN) + + #define tu_htons(u16) (TU_BSWAP16(u16)) + #define tu_ntohs(u16) (TU_BSWAP16(u16)) + + #define tu_htonl(u32) (TU_BSWAP32(u32)) + #define tu_ntohl(u32) (TU_BSWAP32(u32)) + + #define tu_htole16(u16) (u16) + #define tu_le16toh(u16) (u16) + + #define tu_htole32(u32) (u32) + #define tu_le32toh(u32) (u32) + +#elif (TU_BYTE_ORDER == TU_BIG_ENDIAN) + + #define tu_htons(u16) (u16) + #define tu_ntohs(u16) (u16) + + #define tu_htonl(u32) (u32) + #define tu_ntohl(u32) (u32) + + #define tu_htole16(u16) (TU_BSWAP16(u16)) + #define tu_le16toh(u16) (TU_BSWAP16(u16)) + + #define tu_htole32(u32) (TU_BSWAP32(u32)) + #define tu_le32toh(u32) (TU_BSWAP32(u32)) + +#else + #error Byte order is undefined +#endif diff --git a/libraries/Adafruit_TinyUSB_Arduino/src/common/tusb_debug.h b/libraries/Adafruit_TinyUSB_Arduino/src/common/tusb_debug.h new file mode 100644 index 0000000..ba5b4af --- /dev/null +++ b/libraries/Adafruit_TinyUSB_Arduino/src/common/tusb_debug.h @@ -0,0 +1,178 @@ +/* + * The MIT License (MIT) + * + * Copyright (c) 2022, 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. + * + * This file is part of the TinyUSB stack. + */ + +#ifndef TUSB_DEBUG_H_ +#define TUSB_DEBUG_H_ + +#ifdef __cplusplus + extern "C" { +#endif + +//--------------------------------------------------------------------+ +// Debug +//--------------------------------------------------------------------+ + +// CFG_TUSB_DEBUG for debugging +// 0 : no debug +// 1 : print error +// 2 : print warning +// 3 : print info +#if CFG_TUSB_DEBUG + +// Enum to String for debugging purposes +#if CFG_TUSB_DEBUG >= CFG_TUH_LOG_LEVEL || CFG_TUSB_DEBUG >= CFG_TUD_LOG_LEVEL +extern char const* const tu_str_speed[]; +extern char const* const tu_str_std_request[]; +extern char const* const tu_str_xfer_result[]; +#endif + +void tu_print_mem(void const *buf, uint32_t count, uint8_t indent); + +#ifdef CFG_TUSB_DEBUG_PRINTF + extern int CFG_TUSB_DEBUG_PRINTF(const char *format, ...); + #define tu_printf CFG_TUSB_DEBUG_PRINTF +#else + #include + #define tu_printf(...) (void) printf(__VA_ARGS__) +#endif + +TU_ATTR_ALWAYS_INLINE static inline void tu_print_buf(uint8_t const* buf, uint32_t bufsize) { + for(uint32_t i=0; i= 2 + #define TU_LOG2 TU_LOG1 + #define TU_LOG2_MEM TU_LOG1_MEM + #define TU_LOG2_BUF TU_LOG1_BUF + #define TU_LOG2_INT TU_LOG1_INT + #define TU_LOG2_HEX TU_LOG1_HEX +#endif + +// Log Level 3: Info +#if CFG_TUSB_DEBUG >= 3 + #define TU_LOG3 TU_LOG1 + #define TU_LOG3_MEM TU_LOG1_MEM + #define TU_LOG3_BUF TU_LOG1_BUF + #define TU_LOG3_INT TU_LOG1_INT + #define TU_LOG3_HEX TU_LOG1_HEX +#endif + +typedef struct { + uint32_t key; + const char* data; +} tu_lookup_entry_t; + +typedef struct { + uint16_t count; + tu_lookup_entry_t const* items; +} tu_lookup_table_t; + +static inline const char* tu_lookup_find(tu_lookup_table_t const* p_table, uint32_t key) { + for(uint16_t i=0; icount; i++) { + if (p_table->items[i].key == key) { + return p_table->items[i].data; + } + } + + #ifndef CFG_TUSB_DEBUG_PRINTF + // not found return the key value in hex if no custom printf is defined + static char not_found[11]; + if (snprintf(not_found, sizeof(not_found), "0x%08lX", (unsigned long)key) <= 0) { + not_found[0] = 0; + } + return not_found; + #else + return "NotFound"; + #endif +} + +#endif // CFG_TUSB_DEBUG + +#ifndef TU_LOG + #define TU_LOG(n, ...) + #define TU_LOG_MEM(n, ...) + #define TU_LOG_BUF(n, ...) + #define TU_LOG_INT(n, ...) + #define TU_LOG_HEX(n, ...) + #define TU_LOG_LOCATION() + #define TU_LOG_FAILED() +#endif + +#define TU_LOG0(...) +#define TU_LOG0_MEM(...) +#define TU_LOG0_BUF(...) +#define TU_LOG0_INT(...) +#define TU_LOG0_HEX(...) + +#ifndef TU_LOG1 + #define TU_LOG1(...) + #define TU_LOG1_MEM(...) + #define TU_LOG1_BUF(...) + #define TU_LOG1_INT(...) + #define TU_LOG1_HEX(...) +#endif + +#ifndef TU_LOG2 + #define TU_LOG2(...) + #define TU_LOG2_MEM(...) + #define TU_LOG2_BUF(...) + #define TU_LOG2_INT(...) + #define TU_LOG2_HEX(...) +#endif + +#ifndef TU_LOG3 + #define TU_LOG3(...) + #define TU_LOG3_MEM(...) + #define TU_LOG3_BUF(...) + #define TU_LOG3_INT(...) + #define TU_LOG3_HEX(...) +#endif + +#ifdef __cplusplus + } +#endif + +#endif diff --git a/libraries/Adafruit_TinyUSB_Arduino/src/common/tusb_fifo.c b/libraries/Adafruit_TinyUSB_Arduino/src/common/tusb_fifo.c new file mode 100644 index 0000000..a8ac99f --- /dev/null +++ b/libraries/Adafruit_TinyUSB_Arduino/src/common/tusb_fifo.c @@ -0,0 +1,834 @@ +/* + * The MIT License (MIT) + * + * Copyright (c) 2019 Ha Thach (tinyusb.org) + * Copyright (c) 2020 Reinhard Panhuber - rework to unmasked pointers + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * 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. + */ + +#include "osal/osal.h" +#include "tusb_fifo.h" + +#define TU_FIFO_DBG 0 + + +#if OSAL_MUTEX_REQUIRED + +TU_ATTR_ALWAYS_INLINE static inline void ff_lock(osal_mutex_t mutex) { + if (mutex != NULL) { + osal_mutex_lock(mutex, OSAL_TIMEOUT_WAIT_FOREVER); + } +} + +TU_ATTR_ALWAYS_INLINE static inline void ff_unlock(osal_mutex_t mutex) { + if (mutex != NULL) { + osal_mutex_unlock(mutex); + } +} + +#else + #define ff_lock(_mutex) + #define ff_unlock(_mutex) + +#endif + +//--------------------------------------------------------------------+ +// Setup API +//--------------------------------------------------------------------+ +bool tu_fifo_config(tu_fifo_t *f, void *buffer, uint16_t depth, bool overwritable) { + // Limit index space to 2*depth - this allows for a fast "modulo" calculation + // but limits the maximum depth to 2^16/2 = 2^15 and buffer overflows are detectable + // only if overflow happens once (important for unsupervised DMA applications) + if (depth > 0x8000) { + return false; + } + + ff_lock(f->mutex_wr); + ff_lock(f->mutex_rd); + + f->buffer = (uint8_t *)buffer; + f->depth = depth; + f->overwritable = overwritable; + f->rd_idx = 0u; + f->wr_idx = 0u; + + ff_unlock(f->mutex_wr); + ff_unlock(f->mutex_rd); + + return true; +} + +// clear fifo by resetting read and write indices +void tu_fifo_clear(tu_fifo_t *f) { + ff_lock(f->mutex_wr); + ff_lock(f->mutex_rd); + + f->rd_idx = 0; + f->wr_idx = 0; + + ff_unlock(f->mutex_wr); + ff_unlock(f->mutex_rd); +} + +// Change the fifo overwritable mode +void tu_fifo_set_overwritable(tu_fifo_t *f, bool overwritable) { + if (f->overwritable == overwritable) { + return; + } + + ff_lock(f->mutex_wr); + ff_lock(f->mutex_rd); + + f->overwritable = overwritable; + + ff_unlock(f->mutex_wr); + ff_unlock(f->mutex_rd); +} + +//--------------------------------------------------------------------+ +// Hardware FIFO API +// Support different data access width and address increment scheme +// Can support multiple i.e both 16 and 32-bit data access if needed +//--------------------------------------------------------------------+ +#if CFG_TUSB_FIFO_HWFIFO_API + #if CFG_TUSB_FIFO_HWFIFO_ADDR_STRIDE > 0 + #define HWFIFO_ADDR_NEXT_N(_hwfifo, _const, _n) _hwfifo = (_const volatile void *)((uintptr_t)(_hwfifo) + _n) + #else + #define HWFIFO_ADDR_NEXT_N(_hwfifo, _const, _n) + #endif + + #define HWFIFO_ADDR_NEXT(_hwfifo, _const) HWFIFO_ADDR_NEXT_N(_hwfifo, _const, CFG_TUSB_FIFO_HWFIFO_ADDR_STRIDE) + + // the fixed ratio works since in the only case of dynamic/multiple data_stride (rusb2): addr_stride is 0 + #define HWFIFO_ADDR_DATA_RATIO (CFG_TUSB_FIFO_HWFIFO_ADDR_STRIDE / CFG_TUSB_FIFO_HWFIFO_DATA_STRIDE) + +//------------- Write -------------// + #ifndef CFG_TUSB_FIFO_HWFIFO_CUSTOM_WRITE +TU_ATTR_ALWAYS_INLINE static inline void stride_write(volatile void *hwfifo, const void *src, uint8_t data_stride) { + (void)data_stride; // possible unused + #if CFG_TUSB_FIFO_HWFIFO_DATA_STRIDE & 4 + #if CFG_TUSB_FIFO_HWFIFO_DATA_STRIDE != 4 + if (data_stride == 4) + #endif + { + *((volatile uint32_t *)hwfifo) = tu_unaligned_read32(src); + } + #endif + + #if CFG_TUSB_FIFO_HWFIFO_DATA_STRIDE & 2 + #if CFG_TUSB_FIFO_HWFIFO_DATA_STRIDE != 2 + if (data_stride == 2) + #endif + { + *((volatile uint16_t *)hwfifo) = tu_unaligned_read16(src); + } + #endif + + #if CFG_TUSB_FIFO_HWFIFO_DATA_STRIDE == 1 + *((volatile uint8_t *)hwfifo) = *(const uint8_t *)src; + #endif +} + +// Copy from fifo to fixed address buffer (usually a tx register) with TU_FIFO_FIXED_ADDR_RW32 mode +void tu_hwfifo_write(volatile void *hwfifo, const uint8_t *src, uint16_t len, const tu_hwfifo_access_t *access_mode) { + // Write full available 16/32 bit words to dest + const uint8_t data_stride = (access_mode != NULL) ? access_mode->data_stride : CFG_TUSB_FIFO_HWFIFO_DATA_STRIDE; + while (len >= data_stride) { + stride_write(hwfifo, src, data_stride); + src += data_stride; + len -= data_stride; + HWFIFO_ADDR_NEXT(hwfifo, ); + } + + #if CFG_TUSB_FIFO_HWFIFO_DATA_STRIDE > 1 + #ifdef CFG_TUSB_FIFO_HWFIFO_DATA_ODD_16BIT_ACCESS + // 16-bit access is allowed for odd bytes + if (len >= 2) { + *((volatile uint16_t *)hwfifo) = tu_unaligned_read16(src); + src += 2; + len -= 2; + HWFIFO_ADDR_NEXT_N(hwfifo, , 2); + } + #endif + + #ifdef CFG_TUSB_FIFO_HWFIFO_DATA_ODD_8BIT_ACCESS + // 8-bit access is allowed for odd bytes + while (len > 0) { + *((volatile uint8_t *)hwfifo) = *src++; + len--; + HWFIFO_ADDR_NEXT_N(hwfifo, , 1); + } + #else + + // Write odd bytes i.e 1 byte for 16 bit or 1-3 bytes for 32 bit + if (len > 0) { + uint32_t tmp = 0u; + memcpy(&tmp, src, len); + stride_write(hwfifo, &tmp, data_stride); + HWFIFO_ADDR_NEXT(hwfifo, ); + } + #endif + #endif +} + #endif + +//------------- Read -------------// + #ifndef CFG_TUSB_FIFO_HWFIFO_CUSTOM_READ +TU_ATTR_ALWAYS_INLINE static inline void stride_read(const volatile void *hwfifo, void *dest, uint8_t data_stride) { + (void)data_stride; // possible unused + + #if CFG_TUSB_FIFO_HWFIFO_DATA_STRIDE & 4 + #if CFG_TUSB_FIFO_HWFIFO_DATA_STRIDE != 4 + if (data_stride == 4) + #endif + { + tu_unaligned_write32(dest, *((const volatile uint32_t *)hwfifo)); + } + #endif + + #if CFG_TUSB_FIFO_HWFIFO_DATA_STRIDE & 2 + #if CFG_TUSB_FIFO_HWFIFO_DATA_STRIDE != 2 + if (data_stride == 2) + #endif + { + tu_unaligned_write16(dest, *((const volatile uint16_t *)hwfifo)); + } + #endif + + #if CFG_TUSB_FIFO_HWFIFO_DATA_STRIDE == 1 + *(uint8_t *)dest = *((const volatile uint8_t *)hwfifo); + #endif +} + +void tu_hwfifo_read(const volatile void *hwfifo, uint8_t *dest, uint16_t len, const tu_hwfifo_access_t *access_mode) { + // Reading full available 16/32-bit hwfifo and write to fifo + const uint8_t data_stride = (access_mode != NULL) ? access_mode->data_stride : CFG_TUSB_FIFO_HWFIFO_DATA_STRIDE; + while (len >= data_stride) { + stride_read(hwfifo, dest, data_stride); + dest += data_stride; + len -= data_stride; + HWFIFO_ADDR_NEXT(hwfifo, const); + } + + #if CFG_TUSB_FIFO_HWFIFO_DATA_STRIDE > 1 + #ifdef CFG_TUSB_FIFO_HWFIFO_DATA_ODD_16BIT_ACCESS + // 16-bit access is allowed for odd bytes + if (len >= 2) { + tu_unaligned_write16(dest, *((const volatile uint16_t *)hwfifo)); + dest += 2; + len -= 2; + HWFIFO_ADDR_NEXT_N(hwfifo, const, 2); + } + #endif + + #ifdef CFG_TUSB_FIFO_HWFIFO_DATA_ODD_8BIT_ACCESS + // 8-bit access is allowed for odd bytes + while (len > 0) { + *dest++ = *((const volatile uint8_t *)hwfifo); + len--; + HWFIFO_ADDR_NEXT_N(hwfifo, const, 1); + } + #else + // Read odd bytes i.e 1 byte for 16 bit or 1-3 bytes for 32 bit + if (len > 0) { + uint32_t tmp; + stride_read(hwfifo, &tmp, data_stride); + memcpy(dest, &tmp, len); + HWFIFO_ADDR_NEXT(hwfifo, const); + } + #endif + #endif +} + #endif + +// push to sw fifo from hwfifo +static void hwff_push_n(const tu_fifo_t *f, const void *app_buf, uint16_t n, uint16_t wr_ptr, + const tu_hwfifo_access_t *access_mode) { + uint16_t lin_bytes = f->depth - wr_ptr; + uint16_t wrap_bytes = n - lin_bytes; + uint8_t *ff_buf = f->buffer + wr_ptr; + + const volatile void *hwfifo = (const volatile void *)app_buf; + if (n <= lin_bytes) { + // Linear only case + tu_hwfifo_read(hwfifo, ff_buf, n, access_mode); + } else { + // Wrap around case + #if CFG_TUSB_FIFO_HWFIFO_DATA_STRIDE == 1 + tu_hwfifo_read(hwfifo, ff_buf, lin_bytes, access_mode); // linear part + HWFIFO_ADDR_NEXT_N(hwfifo, const, lin_bytes); + tu_hwfifo_read(hwfifo, f->buffer, wrap_bytes, access_mode); // wrapped part + #else + // Write full words to the linear part of the buffer + const uint8_t data_stride = access_mode->data_stride; + const uint32_t odd_mask = data_stride - 1; + uint16_t lin_even = (uint16_t)(lin_bytes & ~odd_mask); + tu_hwfifo_read(hwfifo, ff_buf, lin_even, access_mode); + HWFIFO_ADDR_NEXT_N(hwfifo, const, lin_even * HWFIFO_ADDR_DATA_RATIO); + ff_buf += lin_even; + + // There could be an odd 1 byte (16bit) or 1-3 bytes (32bit) before the wrap-around boundary + // combine it with the wrapped part to form a full word for data stride + const uint8_t lin_odd = (uint8_t)(lin_bytes & odd_mask); + if (lin_odd > 0) { + const uint8_t wrap_odd = (uint8_t)tu_min16(wrap_bytes, data_stride - lin_odd); + uint8_t buf_temp[4]; + tu_hwfifo_read(hwfifo, buf_temp, lin_odd + wrap_odd, access_mode); + HWFIFO_ADDR_NEXT(hwfifo, const); + + for (uint8_t i = 0; i < lin_odd; ++i) { + ff_buf[i] = buf_temp[i]; + } + for (uint8_t i = 0; i < wrap_odd; ++i) { + f->buffer[i] = buf_temp[lin_odd + i]; + } + + wrap_bytes -= wrap_odd; + ff_buf = f->buffer + wrap_odd; // wrap around + } else { + ff_buf = f->buffer; // wrap around to beginning + } + + // Write data wrapped part + if (wrap_bytes > 0) { + tu_hwfifo_read(hwfifo, ff_buf, wrap_bytes, access_mode); + } + #endif + } +} + +// pull from sw fifo to hwfifo +static void hwff_pull_n(const tu_fifo_t *f, void *app_buf, uint16_t n, uint16_t rd_ptr, + const tu_hwfifo_access_t *access_mode) { + uint16_t lin_bytes = f->depth - rd_ptr; + uint16_t wrap_bytes = n - lin_bytes; // only used if wrapped + const uint8_t *ff_buf = f->buffer + rd_ptr; + + volatile void *hwfifo = (volatile void *)app_buf; + + if (n <= lin_bytes) { + // Linear only case + tu_hwfifo_write(hwfifo, ff_buf, n, access_mode); + } else { + // Wrap around case + #if CFG_TUSB_FIFO_HWFIFO_DATA_STRIDE == 1 + tu_hwfifo_write(hwfifo, ff_buf, lin_bytes, access_mode); // linear part + HWFIFO_ADDR_NEXT_N(hwfifo, , lin_bytes); + tu_hwfifo_write(hwfifo, f->buffer, wrap_bytes, access_mode); // wrapped part + #else + // Read full words from linear part + const uint8_t data_stride = access_mode->data_stride; + const uint32_t odd_mask = data_stride - 1; + uint16_t lin_even = (uint16_t)(lin_bytes & ~odd_mask); + tu_hwfifo_write(hwfifo, ff_buf, lin_even, access_mode); + HWFIFO_ADDR_NEXT_N(hwfifo, , lin_even * HWFIFO_ADDR_DATA_RATIO); + ff_buf += lin_even; + + // There could be odd 1 byte (16bit) or 1-3 bytes (32bit) before the wrap-around boundary + const uint8_t lin_odd = (uint8_t)(lin_bytes & odd_mask); + if (lin_odd > 0) { + const uint8_t wrap_odd = (uint8_t)tu_min16(wrap_bytes, data_stride - lin_odd); + + uint8_t buf_temp[4]; + for (uint8_t i = 0; i < lin_odd; ++i) { + buf_temp[i] = ff_buf[i]; + } + for (uint8_t i = 0; i < wrap_odd; ++i) { + buf_temp[lin_odd + i] = f->buffer[i]; + } + + tu_hwfifo_write(hwfifo, buf_temp, lin_odd + wrap_odd, access_mode); + HWFIFO_ADDR_NEXT(hwfifo, ); + + wrap_bytes -= wrap_odd; + ff_buf = f->buffer + wrap_odd; // wrap around + } else { + ff_buf = f->buffer; // wrap around to beginning + } + + // Read data wrapped part + if (wrap_bytes > 0) { + tu_hwfifo_write(hwfifo, ff_buf, wrap_bytes, access_mode); + } + #endif + } +} +#endif + +//--------------------------------------------------------------------+ +// Pull & Push +// copy data to/from fifo without updating read/write pointers +//--------------------------------------------------------------------+ +// send n items to fifo WITHOUT updating write pointer +static void ff_push_n(const tu_fifo_t *f, const void *app_buf, uint16_t n, uint16_t wr_ptr) { + uint16_t lin_bytes = f->depth - wr_ptr; + uint16_t wrap_bytes = n - lin_bytes; + uint8_t *ff_buf = f->buffer + wr_ptr; + + if (n <= lin_bytes) { + // Linear only case + memcpy(ff_buf, app_buf, n); + } else { + // Wrap around case + memcpy(ff_buf, app_buf, lin_bytes); // linear part + memcpy(f->buffer, ((const uint8_t *)app_buf) + lin_bytes, wrap_bytes); // wrapped part + } +} + +// get n items from fifo WITHOUT updating read pointer +static void ff_pull_n(const tu_fifo_t *f, void *app_buf, uint16_t n, uint16_t rd_ptr) { + uint16_t lin_bytes = f->depth - rd_ptr; + uint16_t wrap_bytes = n - lin_bytes; // only used if wrapped + const uint8_t *ff_buf = f->buffer + rd_ptr; + + // single byte access + if (n <= lin_bytes) { + // Linear only + memcpy(app_buf, ff_buf, n); + } else { + // Wrap around + memcpy(app_buf, ff_buf, lin_bytes); // linear part + memcpy((uint8_t *)app_buf + lin_bytes, f->buffer, wrap_bytes); // wrapped part + } +} + +//--------------------------------------------------------------------+ +// Index Helper +//--------------------------------------------------------------------+ + +// Advance an absolute index +// "absolute" index is only in the range of [0..2*depth) +static uint16_t advance_index(uint16_t depth, uint16_t idx, uint16_t offset) { + // We limit the index space of p such that a correct wrap around happens + // Check for a wrap around or if we are in unused index space - This has to be checked first!! + // We are exploiting the wrap around to the correct index + uint16_t new_idx = (uint16_t)(idx + offset); + if ((idx > new_idx) || (new_idx >= 2 * depth)) { + const uint16_t non_used_index_space = (uint16_t)(UINT16_MAX - (2 * depth - 1)); + new_idx = (uint16_t)(new_idx + non_used_index_space); + } + + return new_idx; +} + +// index to pointer (0..depth-1), simply a modulo with minus. +TU_ATTR_ALWAYS_INLINE static inline uint16_t idx2ptr(uint16_t depth, uint16_t idx) { + // Only run at most 3 times since index is limit in the range of [0..2*depth) + while (idx >= depth) { + idx -= depth; + } + return idx; +} + +// Works on local copies of w +// When an overwritable fifo is overflowed, rd_idx will be re-index so that it forms a full fifo +static uint16_t correct_read_index(tu_fifo_t *f, uint16_t wr_idx) { + uint16_t rd_idx; + if (wr_idx >= f->depth) { + rd_idx = wr_idx - f->depth; + } else { + rd_idx = wr_idx + f->depth; + } + + f->rd_idx = rd_idx; + return rd_idx; +} + +//--------------------------------------------------------------------+ +// n-API +//--------------------------------------------------------------------+ + +// Works on local copies of w and r +// Must be protected by read mutex since in case of an overflow read pointer gets modified +uint16_t tu_fifo_peek_n_access_mode(tu_fifo_t *f, void *p_buffer, uint16_t n, uint16_t wr_idx, uint16_t rd_idx, + const tu_hwfifo_access_t *access_mode) { + uint16_t count = tu_ff_overflow_count(f->depth, wr_idx, rd_idx); + if (count == 0) { + return 0; // nothing to peek + } + + // Check overflow and correct if required + if (count > f->depth) { + rd_idx = correct_read_index(f, wr_idx); + count = f->depth; + } + + if (count < n) { + n = count; // limit to available count + } + + const uint16_t rd_ptr = idx2ptr(f->depth, rd_idx); + +#if CFG_TUSB_FIFO_HWFIFO_API + if (access_mode != NULL) { + hwff_pull_n(f, p_buffer, n, rd_ptr, access_mode); + } else +#endif + { + (void)access_mode; + ff_pull_n(f, p_buffer, n, rd_ptr); + } + + return n; +} + +// Read n items without removing it from the FIFO, correct read pointer if overflowed +uint16_t tu_fifo_peek_n(tu_fifo_t *f, void *p_buffer, uint16_t n) { + ff_lock(f->mutex_rd); + const uint16_t wr_idx = f->wr_idx; + const uint16_t rd_idx = f->rd_idx; + const uint16_t ret = tu_fifo_peek_n_access_mode(f, p_buffer, n, wr_idx, rd_idx, NULL); + ff_unlock(f->mutex_rd); + return ret; +} + +// Read n items from fifo with access mode +uint16_t tu_fifo_read_n_access_mode(tu_fifo_t *f, void *buffer, uint16_t n, const tu_hwfifo_access_t *access_mode) { + ff_lock(f->mutex_rd); + + // Peek the data: f->rd_idx might get modified in case of an overflow so we can not use a local variable + const uint16_t wr_idx = f->wr_idx; + n = tu_fifo_peek_n_access_mode(f, buffer, n, wr_idx, f->rd_idx, access_mode); + f->rd_idx = advance_index(f->depth, f->rd_idx, n); + + ff_unlock(f->mutex_rd); + return n; +} + +// Write n items to fifo with access mode +uint16_t tu_fifo_write_n_access_mode(tu_fifo_t *f, const void *data, uint16_t n, + const tu_hwfifo_access_t *access_mode) { + if (n == 0) { + return 0; + } + + ff_lock(f->mutex_wr); + + uint16_t wr_idx = f->wr_idx; + uint16_t rd_idx = f->rd_idx; + + const uint8_t *buf8 = (const uint8_t *)data; + + TU_LOG(TU_FIFO_DBG, "rd = %3u, wr = %3u, count = %3u, remain = %3u, n = %3u: ", rd_idx, wr_idx, + tu_ff_overflow_count(f->depth, wr_idx, rd_idx), tu_ff_remaining_local(f->depth, wr_idx, rd_idx), n); + + if (!f->overwritable) { + // limit up to full + const uint16_t remain = tu_ff_remaining_local(f->depth, wr_idx, rd_idx); + n = tu_min16(n, remain); + } else { + // In over-writable mode, fifo_write() is allowed even when fifo is full. In such case, + // oldest data in fifo i.e. at read pointer data will be overwritten + // Note: we can modify read buffer contents however we must not modify the read index itself within a write + // function! Since it would end up in a race condition with read functions! + if (n >= f->depth) { + // Only copy last part + if (access_mode == NULL) { + buf8 += (n - f->depth); + } else { + // TODO should read from hw fifo to discard data, however reading an odd number could + // accidentally discard data. + } + + n = f->depth; + + // We start writing at the read pointer's position since we fill the whole buffer + wr_idx = rd_idx; + } else { + const uint16_t overflowable_count = tu_ff_overflow_count(f->depth, wr_idx, rd_idx); + if (overflowable_count + n >= 2 * f->depth) { + // Double overflowed + // Index is bigger than the allowed range [0,2*depth) + // re-position write index to have a full fifo after pushed + wr_idx = advance_index(f->depth, rd_idx, f->depth - n); + + // TODO we should also shift out n bytes from read index since we avoid changing rd index !! + // However memmove() is expensive due to actual copying + wrapping consideration. + // Also race condition could happen anyway if read() is invoke while moving result in corrupted memory + // currently deliberately not implemented --> result in incorrect data read back + } else { + // normal + single overflowed: + // Index is in the range of [0,2*depth) and thus detect and recoverable. Recovering is handled in read() + // Therefore we just increase write index + // we will correct (re-position) read index later on in fifo_read() function + } + } + } + + if (n) { + const uint16_t wr_ptr = idx2ptr(f->depth, wr_idx); + TU_LOG(TU_FIFO_DBG, "actual_n = %u, wr_ptr = %u", n, wr_ptr); + +#if CFG_TUSB_FIFO_HWFIFO_API + if (access_mode != NULL) { + hwff_push_n(f, buf8, n, wr_ptr, access_mode); + } else +#endif + { + ff_push_n(f, buf8, n, wr_ptr); + } + f->wr_idx = advance_index(f->depth, wr_idx, n); + + TU_LOG(TU_FIFO_DBG, "\tnew_wr = %u\r\n", f->wr_idx); + } + + ff_unlock(f->mutex_wr); + + return n; +} + +uint16_t tu_fifo_discard_n(tu_fifo_t *f, uint16_t n) { + const uint16_t count = tu_min16(n, tu_fifo_count(f)); // limit to available count + ff_lock(f->mutex_rd); + f->rd_idx = advance_index(f->depth, f->rd_idx, count); + ff_unlock(f->mutex_rd); + + return count; +} + +//--------------------------------------------------------------------+ +// One API +//--------------------------------------------------------------------+ + +// peek() using local write/read index, correct read index if overflowed +// Be careful, caller must not lock mutex, since this Will also try to lock mutex +static bool ff_peek_local(tu_fifo_t *f, void *buf, uint16_t wr_idx, uint16_t rd_idx) { + const uint16_t ovf_count = tu_ff_overflow_count(f->depth, wr_idx, rd_idx); + if (ovf_count == 0) { + return false; // nothing to peek + } + + // Correct read index if overflow + if (ovf_count > f->depth) { + ff_lock(f->mutex_rd); + rd_idx = correct_read_index(f, wr_idx); + ff_unlock(f->mutex_rd); + } + + const uint16_t rd_ptr = idx2ptr(f->depth, rd_idx); + memcpy(buf, f->buffer + rd_ptr, 1); + + return true; +} + +// Read one element out of the buffer, correct read index if overflowed +bool tu_fifo_read(tu_fifo_t *f, void *buffer) { + // Peek the data + // f->rd_idx might get modified in case of an overflow so we can not use a local variable + const uint16_t wr_idx = f->wr_idx; + const bool ret = ff_peek_local(f, buffer, wr_idx, f->rd_idx); + if (ret) { + ff_lock(f->mutex_rd); + f->rd_idx = advance_index(f->depth, f->rd_idx, 1); + ff_unlock(f->mutex_rd); + } + + return ret; +} + +// Read one item without removing it from the FIFO, correct read index if overflowed +bool tu_fifo_peek(tu_fifo_t *f, void *p_buffer) { + const uint16_t wr_idx = f->wr_idx; + const uint16_t rd_idx = f->rd_idx; + return ff_peek_local(f, p_buffer, wr_idx, rd_idx); +} + +// Write one element into the buffer +bool tu_fifo_write(tu_fifo_t *f, const void *data) { + bool ret; + ff_lock(f->mutex_wr); + + const uint16_t wr_idx = f->wr_idx; + + if (tu_fifo_full(f) && !f->overwritable) { + ret = false; + } else { + const uint16_t wr_ptr = idx2ptr(f->depth, wr_idx); + memcpy(f->buffer + wr_ptr, data, 1); + f->wr_idx = advance_index(f->depth, wr_idx, 1); + ret = true; + } + + ff_unlock(f->mutex_wr); + + return ret; +} + +//--------------------------------------------------------------------+ +// Index API +//--------------------------------------------------------------------+ + +/******************************************************************************/ +/*! + @brief Advance write pointer - intended to be used in combination with DMA. + It is possible to fill the FIFO by use of a DMA in circular mode. Within + DMA ISRs you may update the write pointer to be able to read from the FIFO. + As long as the DMA is the only process writing into the FIFO this is safe + to use. + + USE WITH CARE - WE DO NOT CONDUCT SAFETY CHECKS HERE! + + @param[in] f + Pointer to the FIFO buffer to manipulate + @param[in] n + Number of items the write pointer moves forward + */ +/******************************************************************************/ +void tu_fifo_advance_write_pointer(tu_fifo_t *f, uint16_t n) { + f->wr_idx = advance_index(f->depth, f->wr_idx, n); +} + +// Correct the read index in case tu_fifo_overflow() returned true! +void tu_fifo_correct_read_pointer(tu_fifo_t *f) { + ff_lock(f->mutex_rd); + correct_read_index(f, f->wr_idx); + ff_unlock(f->mutex_rd); +} + +/******************************************************************************/ +/*! + @brief Advance read pointer - intended to be used in combination with DMA. + It is possible to read from the FIFO by use of a DMA in linear mode. Within + DMA ISRs you may update the read pointer to be able to again write into the + FIFO. As long as the DMA is the only process reading from the FIFO this is + safe to use. + + USE WITH CARE - WE DO NOT CONDUCT SAFETY CHECKS HERE! + + @param[in] f + Pointer to the FIFO buffer to manipulate + @param[in] n + Number of items the read pointer moves forward + */ +/******************************************************************************/ +void tu_fifo_advance_read_pointer(tu_fifo_t *f, uint16_t n) { + f->rd_idx = advance_index(f->depth, f->rd_idx, n); +} + +/******************************************************************************/ +/*! + @brief Get read info + + Returns the length and pointer from which bytes can be read in a linear manner. + This is of major interest for DMA transmissions. If returned length is zero the + corresponding pointer is invalid. + The read pointer does NOT get advanced, use tu_fifo_advance_read_pointer() to + do so! + @param[in] f + Pointer to FIFO + @param[out] *info + Pointer to struct which holds the desired infos + */ +/******************************************************************************/ +void tu_fifo_get_read_info(tu_fifo_t *f, tu_fifo_buffer_info_t *info) { + // Operate on temporary values in case they change in between + uint16_t wr_idx = f->wr_idx; + uint16_t rd_idx = f->rd_idx; + + uint16_t cnt = tu_ff_overflow_count(f->depth, wr_idx, rd_idx); + + // Check overflow and correct if required - may happen in case a DMA wrote too fast + if (cnt > f->depth) { + ff_lock(f->mutex_rd); + rd_idx = correct_read_index(f, wr_idx); + ff_unlock(f->mutex_rd); + + cnt = f->depth; + } + + // Check if fifo is empty + if (cnt == 0) { + info->linear.len = 0; + info->wrapped.len = 0; + info->linear.ptr = NULL; + info->wrapped.ptr = NULL; + return; + } + + // Get relative pointers + uint16_t wr_ptr = idx2ptr(f->depth, wr_idx); + uint16_t rd_ptr = idx2ptr(f->depth, rd_idx); + + // Copy pointer to buffer to start reading from + info->linear.ptr = &f->buffer[rd_ptr]; + + // Check if there is a wrap around necessary + if (wr_ptr > rd_ptr) { + // Non wrapping case + info->linear.len = cnt; + + info->wrapped.len = 0; + info->wrapped.ptr = NULL; + } else { + info->linear.len = f->depth - rd_ptr; // Also the case if FIFO was full + + info->wrapped.len = cnt - info->linear.len; + info->wrapped.ptr = f->buffer; + } +} + +/******************************************************************************/ +/*! + @brief Get linear write info + + Returns the length and pointer to which bytes can be written into FIFO in a linear manner. + This is of major interest for DMA transmissions not using circular mode. If a returned length is zero the + corresponding pointer is invalid. The returned lengths summed up are the currently free space in the FIFO. + The write pointer does NOT get advanced, use tu_fifo_advance_write_pointer() to do so! + TAKE CARE TO NOT OVERFLOW THE BUFFER MORE THAN TWO TIMES THE FIFO DEPTH - IT CAN NOT RECOVERE OTHERWISE! + @param[in] f + Pointer to FIFO + @param[out] *info + Pointer to struct which holds the desired infos + */ +/******************************************************************************/ +void tu_fifo_get_write_info(tu_fifo_t *f, tu_fifo_buffer_info_t *info) { + uint16_t wr_idx = f->wr_idx; + uint16_t rd_idx = f->rd_idx; + uint16_t remain = tu_ff_remaining_local(f->depth, wr_idx, rd_idx); + + if (remain == 0) { + info->linear.len = 0; + info->wrapped.len = 0; + info->linear.ptr = NULL; + info->wrapped.ptr = NULL; + return; + } + + // Get relative pointers + uint16_t wr_ptr = idx2ptr(f->depth, wr_idx); + uint16_t rd_ptr = idx2ptr(f->depth, rd_idx); + + // Copy pointer to buffer to start writing to + info->linear.ptr = &f->buffer[wr_ptr]; + + if (wr_ptr < rd_ptr) { + // Non wrapping case + info->linear.len = rd_ptr - wr_ptr; + info->wrapped.len = 0; + info->wrapped.ptr = NULL; + } else { + info->linear.len = f->depth - wr_ptr; + info->wrapped.len = remain - info->linear.len; // Remaining length - n already was limited to remain or FIFO depth + info->wrapped.ptr = f->buffer; // Always start of buffer + } +} diff --git a/libraries/Adafruit_TinyUSB_Arduino/src/common/tusb_fifo.h b/libraries/Adafruit_TinyUSB_Arduino/src/common/tusb_fifo.h new file mode 100644 index 0000000..b31a080 --- /dev/null +++ b/libraries/Adafruit_TinyUSB_Arduino/src/common/tusb_fifo.h @@ -0,0 +1,316 @@ +/* + * The MIT License (MIT) + * + * Copyright (c) 2019 Ha Thach (tinyusb.org) + * Copyright (c) 2020 Reinhard Panhuber - rework to unmasked pointers + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * 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_FIFO_H_ +#define TUSB_FIFO_H_ + +#ifdef __cplusplus +extern "C" { +#endif + +#include "common/tusb_common.h" +#include "osal/osal.h" + +//--------------------------------------------------------------------+ +// Configuration +//--------------------------------------------------------------------+ +// mutex is only needed for RTOS. For OS None, we don't get preempted +#define CFG_FIFO_MUTEX OSAL_MUTEX_REQUIRED + +#define CFG_TUSB_FIFO_HWFIFO_API (CFG_TUD_EDPT_DEDICATED_HWFIFO || CFG_TUH_EDPT_DEDICATED_HWFIFO) + +#ifndef CFG_TUSB_FIFO_HWFIFO_DATA_STRIDE + #define CFG_TUSB_FIFO_HWFIFO_DATA_STRIDE 0 +#endif + +#ifndef CFG_TUSB_FIFO_HWFIFO_ADDR_STRIDE + #define CFG_TUSB_FIFO_HWFIFO_ADDR_STRIDE 0 +#endif + +// Due to the use of unmasked pointers, this FIFO does not suffer from losing +// one item slice. Furthermore, write and read operations are completely +// decoupled as write and read functions do not modify a common state. Henceforth, +// writing or reading from the FIFO within an ISR is safe as long as no other +// process (thread or ISR) interferes. +// Also, this FIFO is ready to be used in combination with a DMA as the write and +// read pointers can be updated from within a DMA ISR. Overflows are detectable +// within a certain number (see tu_fifo_overflow()). + +/* Write/Read "pointer" is in the range of: 0 .. depth - 1, and is used to get the fifo data. + * Write/Read "index" is always in the range of: 0 .. 2*depth-1 + * + * The extra window allow us to determine the fifo state of empty or full with only 2 indices + * Following are examples with depth = 3 + * + * - empty: W = R + * | + * ------------------------- + * | 0 | RW| 2 | 3 | 4 | 5 | + * + * - full 1: W > R + * | + * ------------------------- + * | 0 | R | 2 | 3 | W | 5 | + * + * - full 2: W < R + * | + * ------------------------- + * | 0 | 1 | W | 3 | 4 | R | + * + * - Number of items in the fifo can be determined in either cases: + * - case W >= R: Count = W - R + * - case W < R: Count = 2*depth - (R - W) + * + * In non-overwritable mode, computed Count (in above 2 cases) is at most equal to depth. + * However, in over-writable mode, write index can be repeatedly increased and count can be + * temporarily larger than depth (overflowed condition) e.g + * + * - Overflowed 1: write(3), write(1) + * In this case we will adjust Read index when read()/peek() is called so that count = depth. + * | + * ------------------------- + * | R | 1 | 2 | 3 | W | 5 | + * + * - Double Overflowed i.e index is out of allowed range [0,2*depth) + * This occurs when we continue to write after 1st overflowed to 2nd overflowed. e.g: + * write(3), write(1), write(2) + * This must be prevented since it will cause unrecoverable state, in above example + * if not handled the fifo will be empty instead of continue-to-be full. Since we must not modify + * read index in write() function, which cause race condition. We will re-position write index so that + * after data is written it is a full fifo i.e W = depth - R + * + * re-position W = 1 before write(2) + * Note: we should also move data from mem[3] to read index as well, but deliberately skipped here + * since it is an expensive operation !!! + * | + * ------------------------- + * | R | W | 2 | 3 | 4 | 5 | + * + * perform write(2), result is still a full fifo. + * + * | + * ------------------------- + * | R | 1 | 2 | W | 4 | 5 | + */ +typedef struct { + uint8_t *buffer; // buffer pointer + uint16_t depth; // max items + bool overwritable; // overwritable when full + // 1 byte padding here + + volatile uint16_t wr_idx; // write index + volatile uint16_t rd_idx; // read index + +#if OSAL_MUTEX_REQUIRED + osal_mutex_t mutex_wr; + osal_mutex_t mutex_rd; +#endif +} tu_fifo_t; + +typedef struct { + struct { + uint16_t len; // length + uint8_t *ptr; // buffer pointer + } linear, wrapped; +} tu_fifo_buffer_info_t; + +// Access mode for hardware fifo read/write +typedef struct { + uint8_t data_stride; + uintptr_t param; +} tu_hwfifo_access_t; + +#define TU_FIFO_INIT(_buffer, _depth, _overwritable) \ + { \ + .buffer = _buffer, \ + .depth = _depth, \ + .overwritable = _overwritable, \ + } + +#define TU_FIFO_DEF(_name, _depth, _overwritable) \ + uint8_t _name##_buf[_depth]; \ + tu_fifo_t _name = TU_FIFO_INIT(_name##_buf, _depth, _overwritable) + +// Moving data from tusb_fifo <-> USB hardware FIFOs e.g. STM32s need to use a special stride mode which reads/writes +// data in 2/4 byte chunks from/to a fixed address (USB FIFO register) instead of incrementing the address. For this use +// read/write access_mode with stride_mode = true. The STRIDE DATA and ADDR stride must be configured with +// CFG_TUSB_FIFO_HWFIFO_DATA_STRIDE and CFG_TUSB_FIFO_HWFIFO_ADDR_STRIDE + +//--------------------------------------------------------------------+ +// Setup API +//--------------------------------------------------------------------+ +bool tu_fifo_config(tu_fifo_t *f, void *buffer, uint16_t depth, bool overwritable); +void tu_fifo_set_overwritable(tu_fifo_t *f, bool overwritable); +void tu_fifo_clear(tu_fifo_t *f); + +#if OSAL_MUTEX_REQUIRED +TU_ATTR_ALWAYS_INLINE static inline +void tu_fifo_config_mutex(tu_fifo_t *f, osal_mutex_t wr_mutex, osal_mutex_t rd_mutex) { + f->mutex_wr = wr_mutex; + f->mutex_rd = rd_mutex; +} +#else +#define tu_fifo_config_mutex(_f, _wr_mutex, _rd_mutex) +#endif + +//--------------------------------------------------------------------+ +// Index API +//--------------------------------------------------------------------+ +void tu_fifo_correct_read_pointer(tu_fifo_t *f); + +// Pointer modifications intended to be used in combinations with DMAs. +// USE WITH CARE - NO SAFETY CHECKS CONDUCTED HERE! NOT MUTEX PROTECTED! +void tu_fifo_advance_write_pointer(tu_fifo_t *f, uint16_t n); +void tu_fifo_advance_read_pointer(tu_fifo_t *f, uint16_t n); + +// If you want to read/write from/to the FIFO by use of a DMA, you may need to conduct two copies +// to handle a possible wrapping part. These functions deliver a pointer to start +// reading/writing from/to and a valid linear length along which no wrap occurs. +void tu_fifo_get_read_info(tu_fifo_t *f, tu_fifo_buffer_info_t *info); +void tu_fifo_get_write_info(tu_fifo_t *f, tu_fifo_buffer_info_t *info); + +//--------------------------------------------------------------------+ +// Peek API +// peek() will correct/re-index read pointer in case of an overflowed fifo to form a full fifo +//--------------------------------------------------------------------+ +uint16_t tu_fifo_peek_n_access_mode(tu_fifo_t *f, void *p_buffer, uint16_t n, uint16_t wr_idx, uint16_t rd_idx, + const tu_hwfifo_access_t *access_mode); +bool tu_fifo_peek(tu_fifo_t *f, void *p_buffer); +uint16_t tu_fifo_peek_n(tu_fifo_t *f, void *p_buffer, uint16_t n); + +//--------------------------------------------------------------------+ +// Read API +// peek() + advance read index +//--------------------------------------------------------------------+ +uint16_t tu_fifo_read_n_access_mode(tu_fifo_t *f, void *buffer, uint16_t n, const tu_hwfifo_access_t *access_mode); +bool tu_fifo_read(tu_fifo_t *f, void *buffer); +TU_ATTR_ALWAYS_INLINE static inline uint16_t tu_fifo_read_n(tu_fifo_t *f, void *buffer, uint16_t n) { + return tu_fifo_read_n_access_mode(f, buffer, n, NULL); +} + +// discard first n items from fifo i.e advance read pointer by n with mutex +// return number of discarded items +uint16_t tu_fifo_discard_n(tu_fifo_t *f, uint16_t n); + +//--------------------------------------------------------------------+ +// Write API +//--------------------------------------------------------------------+ +uint16_t tu_fifo_write_n_access_mode(tu_fifo_t *f, const void *data, uint16_t n, const tu_hwfifo_access_t *access_mode); +bool tu_fifo_write(tu_fifo_t *f, const void *data); +TU_ATTR_ALWAYS_INLINE static inline uint16_t tu_fifo_write_n(tu_fifo_t *f, const void *data, uint16_t n) { + return tu_fifo_write_n_access_mode(f, data, n, NULL); +} + +//--------------------------------------------------------------------+ +// Hardware FIFO API +// Special hardware FIFO/Buffer to hold USB data, usually requires certain access method these can be configured with +// CFG_TUSB_FIFO_HWFIFO_DATA_STRIDE (data width) and CFG_TUSB_FIFO_HWFIFO_ADDR_STRIDE (address increment) +// Note: these usually has opposite direction (read/write) to/from our software FIFO (tu_fifo_t) +//--------------------------------------------------------------------+ +TU_ATTR_ALWAYS_INLINE static inline uint16_t tu_hwfifo_write_from_fifo(volatile void *hwfifo, tu_fifo_t *f, uint16_t n, + const tu_hwfifo_access_t *access_mode) { + const tu_hwfifo_access_t default_access = {.data_stride = CFG_TUSB_FIFO_HWFIFO_DATA_STRIDE, .param = 0}; + return tu_fifo_read_n_access_mode(f, (void *)(uintptr_t)hwfifo, n, + (access_mode != NULL) ? access_mode : &default_access); +} + +TU_ATTR_ALWAYS_INLINE static inline uint16_t tu_hwfifo_read_to_fifo(const volatile void *hwfifo, tu_fifo_t *f, + uint16_t n, const tu_hwfifo_access_t *access_mode) { + const tu_hwfifo_access_t default_access = {.data_stride = CFG_TUSB_FIFO_HWFIFO_DATA_STRIDE, .param = 0}; + return tu_fifo_write_n_access_mode(f, (const void *)(uintptr_t)hwfifo, n, + (access_mode != NULL) ? access_mode : &default_access); +} + +#if CFG_TUSB_FIFO_HWFIFO_API +// read from hwfifo to buffer +void tu_hwfifo_read(const volatile void *hwfifo, uint8_t *dest, uint16_t len, const tu_hwfifo_access_t *access_mode); + +// write to hwfifo from buffer with access mode +void tu_hwfifo_write(volatile void *hwfifo, const uint8_t *src, uint16_t len, const tu_hwfifo_access_t *access_mode); + +#endif + +//--------------------------------------------------------------------+ +// Internal Helper Local +// work on local copies of read/write indices in order to only access them once for re-entrancy +//--------------------------------------------------------------------+ +// return overflowable count (index difference), which can be used to determine both fifo count and an overflow state +TU_ATTR_ALWAYS_INLINE static inline uint16_t tu_ff_overflow_count(uint16_t depth, uint16_t wr_idx, uint16_t rd_idx) { + const int32_t diff = (int32_t)wr_idx - (int32_t)rd_idx; + if (diff >= 0) { + return (uint16_t)diff; + } else { + return (uint16_t)(2 * depth + diff); + } +} + +// return remaining slot in fifo +TU_ATTR_ALWAYS_INLINE static inline uint16_t tu_ff_remaining_local(uint16_t depth, uint16_t wr_idx, uint16_t rd_idx) { + const uint16_t ovf_count = tu_ff_overflow_count(depth, wr_idx, rd_idx); + return (depth > ovf_count) ? (depth - ovf_count) : 0; +} + +//--------------------------------------------------------------------+ +// State API +// Following functions are reentrant since they only access read/write indices once, therefore can be used in thread and +// ISRs context without the need of mutexes +//--------------------------------------------------------------------+ +TU_ATTR_ALWAYS_INLINE static inline uint16_t tu_fifo_depth(const tu_fifo_t *f) { + return f->depth; +} + +TU_ATTR_ALWAYS_INLINE static inline bool tu_fifo_empty(const tu_fifo_t *f) { + const uint16_t wr_idx = f->wr_idx; + const uint16_t rd_idx = f->rd_idx; + return wr_idx == rd_idx; +} + +// return number of items in fifo, capped to fifo's depth +TU_ATTR_ALWAYS_INLINE static inline uint16_t tu_fifo_count(const tu_fifo_t *f) { + const uint16_t wr_idx = f->wr_idx; + const uint16_t rd_idx = f->rd_idx; + return tu_min16(tu_ff_overflow_count(f->depth, wr_idx, rd_idx), f->depth); +} + +// check if fifo is full +TU_ATTR_ALWAYS_INLINE static inline bool tu_fifo_full(const tu_fifo_t *f) { + const uint16_t wr_idx = f->wr_idx; + const uint16_t rd_idx = f->rd_idx; + return tu_ff_overflow_count(f->depth, wr_idx, rd_idx) >= f->depth; +} + +TU_ATTR_ALWAYS_INLINE static inline uint16_t tu_fifo_remaining(const tu_fifo_t *f) { + const uint16_t wr_idx = f->wr_idx; + const uint16_t rd_idx = f->rd_idx; + return tu_ff_remaining_local(f->depth, wr_idx, rd_idx); +} + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/libraries/Adafruit_TinyUSB_Arduino/src/common/tusb_mcu.h b/libraries/Adafruit_TinyUSB_Arduino/src/common/tusb_mcu.h new file mode 100644 index 0000000..c85ade4 --- /dev/null +++ b/libraries/Adafruit_TinyUSB_Arduino/src/common/tusb_mcu.h @@ -0,0 +1,756 @@ +/* + * The MIT License (MIT) + * + * Copyright (c) 2021, 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. + * + * This file is part of the TinyUSB stack. + */ + +#pragma once + +//--------------------------------------------------------------------+ +// Port/Platform Specific +// TUP stand for TinyUSB Port/Platform (can be renamed) +//--------------------------------------------------------------------+ + +//------------- Unaligned Memory Access -------------// + +#ifdef __ARM_ARCH + // ARM Architecture set __ARM_FEATURE_UNALIGNED to 1 for mcu supports unaligned access + #if defined(__ARM_FEATURE_UNALIGNED) && __ARM_FEATURE_UNALIGNED == 1 + #define TUP_ARCH_STRICT_ALIGN 0 + #else + #define TUP_ARCH_STRICT_ALIGN 1 + #endif +#else + // TODO default to strict align for others + // Should investigate other architecture such as risv, xtensa, mips for optimal setting + #define TUP_ARCH_STRICT_ALIGN 1 +#endif + +/* USB Controller Attributes for Device, Host or MCU (both) + * - ENDPOINT_MAX: max (logical) number of endpoint + * - ENDPOINT_EXCLUSIVE_NUMBER: endpoint number with different direction IN and OUT aren't allowed, + * e.g EP1 OUT & EP1 IN cannot exist together + * - RHPORT_HIGHSPEED: support highspeed with on-chip PHY + */ + +//--------------------------------------------------------------------+ +// NXP +//--------------------------------------------------------------------+ +#if TU_CHECK_MCU(OPT_MCU_LPC11UXX, OPT_MCU_LPC13XX, OPT_MCU_LPC15XX) + #define TUP_USBIP_IP3511 + #define TUP_DCD_ENDPOINT_MAX 5 + +#elif TU_CHECK_MCU(OPT_MCU_LPC175X_6X, OPT_MCU_LPC177X_8X, OPT_MCU_LPC40XX) + #define TUP_DCD_ENDPOINT_MAX 16 + #define TUP_USBIP_OHCI + #define TUP_USBIP_OHCI_NXP + #define TUP_OHCI_RHPORTS 2 + +#elif TU_CHECK_MCU(OPT_MCU_LPC51UXX) + #define TUP_USBIP_IP3511 + #define TUP_DCD_ENDPOINT_MAX 5 + +#elif TU_CHECK_MCU(OPT_MCU_LPC54) + #include "fsl_device_registers.h" + + // TODO USB0 has 5, USB1 has 6 + #define TUP_USBIP_IP3511 + + #if !defined(LPC54114_cm4_SERIES) && !defined(LPC54114_cm0plus_SERIES) + #define TUP_USBIP_IP3516 + #define TUP_USBIP_OHCI + #define TUP_USBIP_OHCI_NXP + #define TUP_OHCI_RHPORTS 1 // 1 downstream port + #endif + + #define TUP_DCD_ENDPOINT_MAX 6 + +#elif TU_CHECK_MCU(OPT_MCU_LPC55) + // TODO USB0 has 5, USB1 has 6 + #define TUP_USBIP_IP3511 + #define TUP_USBIP_IP3516 + #define TUP_USBIP_OHCI + #define TUP_USBIP_OHCI_NXP + #define TUP_OHCI_RHPORTS 1 // 1 downstream port + + #define TUP_DCD_ENDPOINT_MAX 6 + +#elif TU_CHECK_MCU(OPT_MCU_LPC18XX, OPT_MCU_LPC43XX) + // USB0 has 6 with HS PHY, USB1 has 4 only FS + #define TUP_USBIP_CHIPIDEA_HS + #define TUP_USBIP_EHCI + + #define TUP_DCD_ENDPOINT_MAX 6 + #define TUP_RHPORT_HIGHSPEED 1 + +#elif TU_CHECK_MCU(OPT_MCU_MCXN9) + // USB0 is chipidea FS + #define TUP_USBIP_CHIPIDEA_FS + #define TUP_USBIP_CHIPIDEA_FS_MCX + + // USB1 is chipidea HS + #define TUP_USBIP_CHIPIDEA_HS + #define TUP_USBIP_EHCI + + #define TUP_DCD_ENDPOINT_MAX 8 + #define TUP_RHPORT_HIGHSPEED 1 + +#elif TU_CHECK_MCU(OPT_MCU_MCXA15) + // USB0 is chipidea FS + #define TUP_USBIP_CHIPIDEA_FS + #define TUP_USBIP_CHIPIDEA_FS_MCX + + #define TUP_DCD_ENDPOINT_MAX 16 + +#elif TU_CHECK_MCU(OPT_MCU_RW61X) + // USB0 is chipidea HS + #define TUP_USBIP_CHIPIDEA_HS + #define TUP_USBIP_EHCI + + #define TUP_DCD_ENDPOINT_MAX 8 + #define TUP_RHPORT_HIGHSPEED 1 + +#elif TU_CHECK_MCU(OPT_MCU_MIMXRT1XXX) + #include "fsl_device_registers.h" + + #define TUP_USBIP_CHIPIDEA_HS + #define TUP_USBIP_EHCI + + #define TUP_DCD_ENDPOINT_MAX 8 + #define TUP_RHPORT_HIGHSPEED 1 + + #if __CORTEX_M == 7 + #define CFG_TUD_MEM_DCACHE_ENABLE_DEFAULT 1 + #define CFG_TUH_MEM_DCACHE_ENABLE_DEFAULT 1 + #define CFG_TUSB_MEM_DCACHE_LINE_SIZE_DEFAULT 32 + #endif + +#elif TU_CHECK_MCU(OPT_MCU_KINETIS_KL, OPT_MCU_KINETIS_K32L, OPT_MCU_KINETIS_K) + #define TUP_USBIP_CHIPIDEA_FS + #define TUP_USBIP_CHIPIDEA_FS_KINETIS + #define TUP_DCD_ENDPOINT_MAX 16 + +#elif TU_CHECK_MCU(OPT_MCU_MM32F327X) + #define TUP_DCD_ENDPOINT_MAX 16 + #define TUP_DCD_EDPT_CLOSE_API + +//--------------------------------------------------------------------+ +// Nordic +//--------------------------------------------------------------------+ +#elif TU_CHECK_MCU(OPT_MCU_NRF5X) + // 8 CBI + 1 ISO + #define TUP_DCD_ENDPOINT_MAX 9 + #define TUP_DCD_EDPT_CLOSE_API + +#elif TU_CHECK_MCU(OPT_MCU_NRF54) + #define TUP_USBIP_DWC2 + #define TUP_USBIP_DWC2_NRF + #define TUP_DCD_ENDPOINT_MAX 16 + #define TUP_RHPORT_HIGHSPEED 1 + #define CFG_TUH_DWC2_DMA_ENABLE_DEFAULT 0 + +//--------------------------------------------------------------------+ +// Microchip +//--------------------------------------------------------------------+ +#elif TU_CHECK_MCU(OPT_MCU_SAMD11, OPT_MCU_SAML2X, OPT_MCU_SAMD21) || TU_CHECK_MCU(OPT_MCU_SAMD51, OPT_MCU_SAME5X) + #define TUP_DCD_ENDPOINT_MAX 8 + +#elif TU_CHECK_MCU(OPT_MCU_SAMG) + #define TUP_DCD_ENDPOINT_MAX 6 + #define CFG_TUD_ENDPOINT_ONE_DIRECTION_ONLY 1 + +#elif TU_CHECK_MCU(OPT_MCU_SAMX7X) + #define TUP_DCD_ENDPOINT_MAX 10 + #define TUP_RHPORT_HIGHSPEED 1 + #define CFG_TUD_ENDPOINT_ONE_DIRECTION_ONLY 1 + + // Enable dcache if DMA is enabled + #define CFG_TUD_MEM_DCACHE_ENABLE_DEFAULT CFG_TUD_SAMX7X_DMA_ENABLE + #define CFG_TUSB_MEM_DCACHE_LINE_SIZE_DEFAULT 32 + +#elif TU_CHECK_MCU(OPT_MCU_PIC32MZ) + #define TUP_DCD_ENDPOINT_MAX 8 + #define CFG_TUD_ENDPOINT_ONE_DIRECTION_ONLY 1 + +#elif TU_CHECK_MCU(OPT_MCU_PIC32MX, OPT_MCU_PIC32MM, OPT_MCU_PIC32MK) || TU_CHECK_MCU(OPT_MCU_PIC24, OPT_MCU_DSPIC33) + #define TUP_DCD_ENDPOINT_MAX 16 + #define CFG_TUD_ENDPOINT_ONE_DIRECTION_ONLY 1 + #define TUP_DCD_EDPT_CLOSE_API + +//--------------------------------------------------------------------+ +// ST +//--------------------------------------------------------------------+ +#elif TU_CHECK_MCU(OPT_MCU_STM32C0) + #define TUP_USBIP_FSDEV + #define TUP_USBIP_FSDEV_STM32 + #define TUP_USBIP_FSDEV_DRD + #define CFG_TUSB_FSDEV_PMA_SIZE 2048u + +#elif TU_CHECK_MCU(OPT_MCU_STM32F0) + #define TUP_USBIP_FSDEV + #define TUP_USBIP_FSDEV_STM32 + #define CFG_TUSB_FSDEV_PMA_SIZE 1024u + +#elif TU_CHECK_MCU(OPT_MCU_STM32F1) + // - F102, F103 use fsdev + // - F105, F107 use dwc2 + #if defined(STM32F105x8) || defined(STM32F105xB) || defined(STM32F105xC) || defined(STM32F107xB) || \ + defined(STM32F107xC) + #define TUP_USBIP_DWC2 + #define TUP_USBIP_DWC2_STM32 + #define CFG_TUH_DWC2_DMA_ENABLE_DEFAULT 0 + + #define TUP_DCD_ENDPOINT_MAX 4 + #elif defined(STM32F102x6) || defined(STM32F102xB) || defined(STM32F103x6) || defined(STM32F103xB) || \ + defined(STM32F103xE) || defined(STM32F103xG) + #define TUP_USBIP_FSDEV + #define TUP_USBIP_FSDEV_STM32 + #define CFG_TUSB_FSDEV_PMA_SIZE 512u + #else + #error "Unsupported STM32F1 mcu" + #endif + +#elif TU_CHECK_MCU(OPT_MCU_STM32F2) + #define TUP_USBIP_DWC2 + #define TUP_USBIP_DWC2_STM32 + + // FS has 4 ep, HS has 5 ep + #define TUP_DCD_ENDPOINT_MAX 6 + +#elif TU_CHECK_MCU(OPT_MCU_STM32F3) + #define TUP_USBIP_FSDEV + #define TUP_USBIP_FSDEV_STM32 + + #if defined(STM32F302xB) || defined(STM32F302xC) || defined(STM32F303xB) || defined(STM32F303xC) || \ + defined(STM32F373xC) + // xB, and xC: 512 + #define CFG_TUSB_FSDEV_PMA_SIZE 512u + #elif defined(STM32F302x6) || defined(STM32F302x8) || defined(STM32F302xD) || defined(STM32F302xE) || \ + defined(STM32F303xD) || defined(STM32F303xE) + // x6, x8, xD, and xE: 1024 + LPM Support + #define CFG_TUSB_FSDEV_PMA_SIZE 1024u + #else + #error "Unsupported STM32F3 mcu" + #endif + +#elif TU_CHECK_MCU(OPT_MCU_STM32F4) + #define TUP_USBIP_DWC2 + #define TUP_USBIP_DWC2_STM32 + + // For most mcu, FS has 4, HS has 6. TODO 446/469/479 HS has 9 + #define TUP_DCD_ENDPOINT_MAX 6 + +#elif TU_CHECK_MCU(OPT_MCU_STM32F7) + #define TUP_USBIP_DWC2 + #define TUP_USBIP_DWC2_STM32 + + // FS has 6, HS has 9 + #define TUP_DCD_ENDPOINT_MAX 9 + + // MCU with on-chip HS Phy + #if defined(STM32F723xx) || defined(STM32F730xx) || defined(STM32F733xx) + #define TUP_RHPORT_HIGHSPEED 1 // Port0: FS, Port1: HS + #endif + + // Enable dcache if DMA is enabled + #define CFG_TUD_MEM_DCACHE_ENABLE_DEFAULT CFG_TUD_DWC2_DMA_ENABLE + #define CFG_TUH_MEM_DCACHE_ENABLE_DEFAULT CFG_TUH_DWC2_DMA_ENABLE + #define CFG_TUSB_MEM_DCACHE_LINE_SIZE_DEFAULT 32 + +#elif TU_CHECK_MCU(OPT_MCU_STM32G0) + #define TUP_USBIP_FSDEV + #define TUP_USBIP_FSDEV_STM32 + #define TUP_USBIP_FSDEV_DRD + #define CFG_TUSB_FSDEV_PMA_SIZE 2048u + +#elif TU_CHECK_MCU(OPT_MCU_STM32G4) + // Device controller + #define TUP_USBIP_FSDEV + #define TUP_USBIP_FSDEV_STM32 + #define CFG_TUSB_FSDEV_PMA_SIZE 1024u + + // TypeC controller + #define TUP_USBIP_TYPEC_STM32 + #define TUP_TYPEC_RHPORTS_NUM 1 + +#elif TU_CHECK_MCU(OPT_MCU_STM32H5) + #define TUP_USBIP_FSDEV + #define TUP_USBIP_FSDEV_STM32 + #define TUP_USBIP_FSDEV_DRD + #define CFG_TUSB_FSDEV_PMA_SIZE 2048u + +#elif TU_CHECK_MCU(OPT_MCU_STM32H7) + #define TUP_USBIP_DWC2 + #define TUP_USBIP_DWC2_STM32 + + #define TUP_DCD_ENDPOINT_MAX 9 + + #ifndef CORE_CM4 + // Enable dcache if DMA is enabled + #define CFG_TUD_MEM_DCACHE_ENABLE_DEFAULT CFG_TUD_DWC2_DMA_ENABLE + #define CFG_TUH_MEM_DCACHE_ENABLE_DEFAULT CFG_TUH_DWC2_DMA_ENABLE + #define CFG_TUSB_MEM_DCACHE_LINE_SIZE_DEFAULT 32 + #endif + +#elif TU_CHECK_MCU(OPT_MCU_STM32H7RS, OPT_MCU_STM32N6) + #define TUP_USBIP_DWC2 + #define TUP_USBIP_DWC2_STM32 + + // FS has 6, HS has 9 + #define TUP_DCD_ENDPOINT_MAX 9 + + // MCU with on-chip HS Phy + #define TUP_RHPORT_HIGHSPEED 1 + + // Enable dcache if DMA is enabled + #define CFG_TUD_MEM_DCACHE_ENABLE_DEFAULT CFG_TUD_DWC2_DMA_ENABLE + #define CFG_TUH_MEM_DCACHE_ENABLE_DEFAULT CFG_TUH_DWC2_DMA_ENABLE + #define CFG_TUSB_MEM_DCACHE_LINE_SIZE_DEFAULT 32 + +#elif TU_CHECK_MCU(OPT_MCU_STM32L0) + #define TUP_USBIP_FSDEV + #define TUP_USBIP_FSDEV_STM32 + #define CFG_TUSB_FSDEV_PMA_SIZE 1024u + +#elif TU_CHECK_MCU(OPT_MCU_STM32L1) + #define TUP_USBIP_FSDEV + #define TUP_USBIP_FSDEV_STM32 + #define CFG_TUSB_FSDEV_PMA_SIZE 512u + +#elif TU_CHECK_MCU(OPT_MCU_STM32L4) + // - L4x2, L4x3 use fsdev + // - L4x4, L4x6, L4x7, L4x9 use dwc2 + #if defined(STM32L475xx) || defined(STM32L476xx) || defined(STM32L485xx) || defined(STM32L486xx) || \ + defined(STM32L496xx) || defined(STM32L4A6xx) || defined(STM32L4P5xx) || defined(STM32L4Q5xx) || \ + defined(STM32L4R5xx) || defined(STM32L4R7xx) || defined(STM32L4R9xx) || defined(STM32L4S5xx) || \ + defined(STM32L4S7xx) || defined(STM32L4S9xx) + #define TUP_USBIP_DWC2 + #define TUP_USBIP_DWC2_STM32 + + #define TUP_DCD_ENDPOINT_MAX 6 + #elif defined(STM32L412xx) || defined(STM32L422xx) || defined(STM32L432xx) || defined(STM32L433xx) || \ + defined(STM32L442xx) || defined(STM32L443xx) || defined(STM32L452xx) || defined(STM32L462xx) + #define TUP_USBIP_FSDEV + #define TUP_USBIP_FSDEV_STM32 + #define CFG_TUSB_FSDEV_PMA_SIZE 1024u + #else + #error "Unsupported STM32L4 mcu" + #endif + +#elif TU_CHECK_MCU(OPT_MCU_STM32L5) + #define TUP_USBIP_FSDEV + #define TUP_USBIP_FSDEV_STM32 + #define CFG_TUSB_FSDEV_PMA_SIZE (1024u) + +#elif TU_CHECK_MCU(OPT_MCU_STM32U0) + #define TUP_USBIP_FSDEV + #define TUP_USBIP_FSDEV_STM32 + #define CFG_TUSB_FSDEV_PMA_SIZE 1024u + +#elif TU_CHECK_MCU(OPT_MCU_STM32U3) + #define TUP_USBIP_FSDEV + #define TUP_USBIP_FSDEV_STM32 + #define TUP_USBIP_FSDEV_DRD + #define CFG_TUSB_FSDEV_PMA_SIZE 2048u + +#elif TU_CHECK_MCU(OPT_MCU_STM32U5) + // U535/545 use fsdev + #if defined(STM32U535xx) || defined(STM32U545xx) + #define TUP_USBIP_FSDEV + #define TUP_USBIP_FSDEV_STM32 + #define TUP_USBIP_FSDEV_DRD + #define CFG_TUSB_FSDEV_PMA_SIZE 2048u + #else + #define TUP_USBIP_DWC2 + #define TUP_USBIP_DWC2_STM32 + + // U59x/5Ax/5Fx/5Gx are highspeed with built-in HS PHY + #if defined(STM32U595xx) || defined(STM32U599xx) || defined(STM32U5A5xx) || defined(STM32U5A9xx) || \ + defined(STM32U5F7xx) || defined(STM32U5F9xx) || defined(STM32U5G7xx) || defined(STM32U5G9xx) + #define TUP_DCD_ENDPOINT_MAX 9 + #define TUP_RHPORT_HIGHSPEED 1 + #else + #define TUP_DCD_ENDPOINT_MAX 6 + #endif + #endif + +#elif TU_CHECK_MCU(OPT_MCU_STM32WB) + #define TUP_USBIP_FSDEV + #define TUP_USBIP_FSDEV_STM32 + #define CFG_TUSB_FSDEV_PMA_SIZE 1024u + +#elif TU_CHECK_MCU(OPT_MCU_STM32WBA) + #define TUP_USBIP_DWC2 + #define TUP_USBIP_DWC2_STM32 + #define TUP_DCD_ENDPOINT_MAX 9 + #define TUP_RHPORT_HIGHSPEED 1 + +//--------------------------------------------------------------------+ +// Sony +//--------------------------------------------------------------------+ +#elif TU_CHECK_MCU(OPT_MCU_CXD56) + #define TUP_DCD_ENDPOINT_MAX 7 + #define TUP_RHPORT_HIGHSPEED 1 + #define CFG_TUD_ENDPOINT_ONE_DIRECTION_ONLY 1 + +//--------------------------------------------------------------------+ +// TI +//--------------------------------------------------------------------+ +#elif TU_CHECK_MCU(OPT_MCU_MSP430x5xx) + #define TUP_DCD_ENDPOINT_MAX 8 + +#elif TU_CHECK_MCU(OPT_MCU_MSP432E4, OPT_MCU_TM4C123, OPT_MCU_TM4C129) + #define TUP_USBIP_MUSB + #define TUP_USBIP_MUSB_TI + #define TUP_DCD_ENDPOINT_MAX 8 + +//--------------------------------------------------------------------+ +// ValentyUSB (Litex) +//--------------------------------------------------------------------+ +#elif TU_CHECK_MCU(OPT_MCU_VALENTYUSB_EPTRI) + #define TUP_DCD_ENDPOINT_MAX 16 + +//--------------------------------------------------------------------+ +// Nuvoton +//--------------------------------------------------------------------+ +#elif TU_CHECK_MCU(OPT_MCU_NUC121, OPT_MCU_NUC126) + #define TUP_DCD_ENDPOINT_MAX 8 + +#elif TU_CHECK_MCU(OPT_MCU_NUC120) + #define TUP_DCD_ENDPOINT_MAX 6 + +#elif TU_CHECK_MCU(OPT_MCU_NUC505) + #define TUP_DCD_ENDPOINT_MAX 12 + #define TUP_RHPORT_HIGHSPEED 1 + +//--------------------------------------------------------------------+ +// Espressif +//--------------------------------------------------------------------+ +#elif TU_CHECK_MCU(OPT_MCU_ESP32S2, OPT_MCU_ESP32S3, OPT_MCU_ESP32H4) + #define TUP_USBIP_DWC2 + #define TUP_USBIP_DWC2_ESP32 + #define TUP_DCD_ENDPOINT_MAX 7 // only 5 TX FIFO for endpoint IN + + // clang-format off + #define CFG_TUSB_OS_INC_PATH_DEFAULT freertos/ + // clang-format on + + #if CFG_TUSB_MCU == OPT_MCU_ESP32S3 || CFG_TUSB_MCU == OPT_MCU_ESP32H4 + #define TUP_MCU_MULTIPLE_CORE 1 + #endif + +#elif TU_CHECK_MCU(OPT_MCU_ESP32P4) + #define TUP_USBIP_DWC2 + #define TUP_USBIP_DWC2_ESP32 + #define TUP_RHPORT_HIGHSPEED 1 // port0 FS, port1 HS + #define TUP_DCD_ENDPOINT_MAX 16 // FS 7 ep, HS 16 ep + + // clang-format off + #define CFG_TUSB_OS_INC_PATH_DEFAULT freertos/ + // clang-format on + + #define TUP_MCU_MULTIPLE_CORE 1 + + // Enable dcache if DMA is enabled + #define CFG_TUD_MEM_DCACHE_ENABLE_DEFAULT CFG_TUD_DWC2_DMA_ENABLE + #define CFG_TUH_MEM_DCACHE_ENABLE_DEFAULT CFG_TUH_DWC2_DMA_ENABLE + #define CFG_TUSB_MEM_DCACHE_LINE_SIZE_DEFAULT 64 + +#elif TU_CHECK_MCU(OPT_MCU_ESP32S31) + #define TUP_USBIP_DWC2 + #define TUP_USBIP_DWC2_ESP32 + #define TUP_RHPORT_HIGHSPEED 1 + #define TUP_DCD_ENDPOINT_MAX 16 + + // clang-format off + #define CFG_TUSB_OS_INC_PATH_DEFAULT freertos/ + // clang-format on + + #define TUP_MCU_MULTIPLE_CORE 1 + + // Disable slave if DMA is enabled + #define CFG_TUD_DWC2_SLAVE_ENABLE_DEFAULT !CFG_TUD_DWC2_DMA_ENABLE + #define CFG_TUH_DWC2_SLAVE_ENABLE_DEFAULT !CFG_TUH_DWC2_DMA_ENABLE + +#elif TU_CHECK_MCU(OPT_MCU_ESP32, OPT_MCU_ESP32C2, OPT_MCU_ESP32C3, OPT_MCU_ESP32C5, OPT_MCU_ESP32C6, \ + OPT_MCU_ESP32C61, OPT_MCU_ESP32H2) + #if (CFG_TUD_ENABLED || !(defined(CFG_TUH_MAX3421) && CFG_TUH_MAX3421)) + #error "MCUs are only supported with CFG_TUH_MAX3421 enabled" + #endif + + #define TUP_DCD_ENDPOINT_MAX 0 + + // clang-format off + #define CFG_TUSB_OS_INC_PATH_DEFAULT freertos/ + // clang-format on + + +//--------------------------------------------------------------------+ +// Dialog +//--------------------------------------------------------------------+ +#elif TU_CHECK_MCU(OPT_MCU_DA1469X) + #define TUP_DCD_ENDPOINT_MAX 4 + #define TUP_DCD_EDPT_CLOSE_API + +//--------------------------------------------------------------------+ +// Raspberry Pi +//--------------------------------------------------------------------+ +#elif TU_CHECK_MCU(OPT_MCU_RP2040) + #define TUP_DCD_ENDPOINT_MAX 16 + #define TUP_MCU_MULTIPLE_CORE 1 + + #define TU_ATTR_FAST_FUNC __not_in_flash("tinyusb") + +//--------------------------------------------------------------------+ +// Silabs +//--------------------------------------------------------------------+ +#elif TU_CHECK_MCU(OPT_MCU_EFM32GG) + #define TUP_USBIP_DWC2 + #define TUP_DCD_ENDPOINT_MAX 7 + +//--------------------------------------------------------------------+ +// Renesas +//--------------------------------------------------------------------+ +#elif TU_CHECK_MCU(OPT_MCU_RX63X, OPT_MCU_RX65X, OPT_MCU_RX72N, OPT_MCU_RAXXX) + #define TUP_USBIP_RUSB2 + #define TUP_DCD_ENDPOINT_MAX 10 + +//--------------------------------------------------------------------+ +// GigaDevice +//--------------------------------------------------------------------+ +#elif TU_CHECK_MCU(OPT_MCU_GD32VF103) + #define TUP_USBIP_DWC2 + #define TUP_DCD_ENDPOINT_MAX 4 + +//--------------------------------------------------------------------+ +// Broadcom +//--------------------------------------------------------------------+ +#elif TU_CHECK_MCU(OPT_MCU_BCM2711, OPT_MCU_BCM2835, OPT_MCU_BCM2837) + #define TUP_USBIP_DWC2 + #define TUP_DCD_ENDPOINT_MAX 8 + #define TUP_RHPORT_HIGHSPEED 1 + +//--------------------------------------------------------------------+ +// Infineon +//--------------------------------------------------------------------+ +#elif TU_CHECK_MCU(OPT_MCU_XMC4000) + #define TUP_USBIP_DWC2 + #define TUP_DCD_ENDPOINT_MAX 8 + +//--------------------------------------------------------------------+ +// BridgeTek +//--------------------------------------------------------------------+ +#elif TU_CHECK_MCU(OPT_MCU_FT90X) + #define TUP_DCD_ENDPOINT_MAX 8 + #define TUP_RHPORT_HIGHSPEED 1 + #define CFG_TUD_ENDPOINT_ONE_DIRECTION_ONLY 1 + +#elif TU_CHECK_MCU(OPT_MCU_FT93X) + #define TUP_DCD_ENDPOINT_MAX 16 + #define TUP_RHPORT_HIGHSPEED 1 + #define CFG_TUD_ENDPOINT_ONE_DIRECTION_ONLY 1 + +//--------------------------------------------------------------------+ +// Allwinner +//--------------------------------------------------------------------+ +#elif TU_CHECK_MCU(OPT_MCU_F1C100S) + #define TUP_DCD_ENDPOINT_MAX 4 + #define TUP_DCD_EDPT_CLOSE_API + +//--------------------------------------------------------------------+ +// WCH +//--------------------------------------------------------------------+ +#elif TU_CHECK_MCU(OPT_MCU_CH32F20X) + #define TUP_USBIP_WCH_USBHS + #define TUP_USBIP_WCH_USBFS + + #if !defined(CFG_TUD_WCH_USBIP_USBFS) + #define CFG_TUD_WCH_USBIP_USBFS 0 + #endif + + #if !defined(CFG_TUD_WCH_USBIP_USBHS) + #define CFG_TUD_WCH_USBIP_USBHS (CFG_TUD_WCH_USBIP_USBFS ? 0 : 1) + #endif + + #define TUP_RHPORT_HIGHSPEED CFG_TUD_WCH_USBIP_USBHS + #define TUP_DCD_ENDPOINT_MAX (CFG_TUD_WCH_USBIP_USBHS ? 16 : 8) + + #if CFG_TUD_WCH_USBIP_USBHS + #define TUP_DCD_EDPT_CLOSE_API + #endif + +#elif TU_CHECK_MCU(OPT_MCU_CH32V103) + #define TUP_USBIP_WCH_USBFS + + #if !defined(CFG_TUD_WCH_USBIP_USBFS) + #define CFG_TUD_WCH_USBIP_USBFS 1 + #endif + + #define TUP_DCD_ENDPOINT_MAX 8 + +#elif TU_CHECK_MCU(OPT_MCU_CH32V20X) + // v20x support both port0 FSDEV (USBD) and port1 USBFS + #define TUP_USBIP_WCH_USBFS + + #ifndef CFG_TUH_WCH_USBIP_USBFS + #define CFG_TUH_WCH_USBIP_USBFS 1 + #endif + + #define TUP_USBIP_FSDEV + #define TUP_USBIP_FSDEV_CH32 + #define CFG_TUSB_FSDEV_PMA_SIZE 512u + + // default to FSDEV for device + #if !defined(CFG_TUD_WCH_USBIP_USBFS) + #define CFG_TUD_WCH_USBIP_USBFS 0 + #endif + + #if !defined(CFG_TUD_WCH_USBIP_FSDEV) + #define CFG_TUD_WCH_USBIP_FSDEV (CFG_TUD_WCH_USBIP_USBFS ? 0 : 1) + #endif + + #define TUP_DCD_ENDPOINT_MAX 8 + +#elif TU_CHECK_MCU(OPT_MCU_CH32V307) + // v307 support both FS and HS, default to HS + #define TUP_USBIP_WCH_USBHS + #define TUP_USBIP_WCH_USBFS + + #if !defined(CFG_TUD_WCH_USBIP_USBFS) + #define CFG_TUD_WCH_USBIP_USBFS 0 + #endif + + #if !defined(CFG_TUD_WCH_USBIP_USBHS) + #define CFG_TUD_WCH_USBIP_USBHS (CFG_TUD_WCH_USBIP_USBFS ? 0 : 1) + #endif + + #define TUP_RHPORT_HIGHSPEED CFG_TUD_WCH_USBIP_USBHS + #define TUP_DCD_ENDPOINT_MAX (CFG_TUD_WCH_USBIP_USBHS ? 16 : 8) + + #if CFG_TUD_WCH_USBIP_USBHS + #define TUP_DCD_EDPT_CLOSE_API + #endif + +//--------------------------------------------------------------------+ +// Analog Devices +//--------------------------------------------------------------------+ +#elif TU_CHECK_MCU(OPT_MCU_MAX32650, OPT_MCU_MAX32666, OPT_MCU_MAX32690, OPT_MCU_MAX78002) + #define TUP_USBIP_MUSB + #define TUP_USBIP_MUSB_ADI + #define TUP_DCD_ENDPOINT_MAX 12 + #define TUP_RHPORT_HIGHSPEED 1 + #define CFG_TUD_ENDPOINT_ONE_DIRECTION_ONLY 1 + +//--------------------------------------------------------------------+ +// ArteryTek +//--------------------------------------------------------------------+ +#elif TU_CHECK_MCU(OPT_MCU_AT32F403A_407, OPT_MCU_AT32F413) + #define TUP_USBIP_FSDEV + #define TUP_USBIP_FSDEV_AT32 + #define CFG_TUSB_FSDEV_PMA_SIZE 512u + +#elif TU_CHECK_MCU(OPT_MCU_AT32F415) + #define TUP_USBIP_DWC2 + #define TUP_USBIP_DWC2_AT32 + #define TUP_DCD_ENDPOINT_MAX 4 + +#elif TU_CHECK_MCU(OPT_MCU_AT32F402_405, OPT_MCU_AT32F423, OPT_MCU_AT32F425, OPT_MCU_AT32F435_437, OPT_MCU_AT32F45X) + #define TUP_USBIP_DWC2 + #define TUP_USBIP_DWC2_AT32 + #define TUP_DCD_ENDPOINT_MAX 8 + + // AT32F405xx has on-chip HS PHY + #if defined(AT32F405CBT7) || defined(AT32F405CBU7) || defined(AT32F405CCT7) || defined(AT32F405CCU7) || \ + defined(AT32F405KBU7_4) || defined(AT32F405KCU7_4) || defined(AT32F405RBT7_7) || defined(AT32F405RBT7) || \ + defined(AT32F405RCT7_7) || defined(AT32F405RCT7) + #define TUP_RHPORT_HIGHSPEED 1 // Port0: FS, Port1: HS + #endif + +//--------------------------------------------------------------------+ +// HPMicro +//--------------------------------------------------------------------+ +#elif TU_CHECK_MCU(OPT_MCU_HPM) + #define TUP_USBIP_CHIPIDEA_HS + #define TUP_USBIP_EHCI + + #define TUP_DCD_ENDPOINT_MAX 16 + #define TUP_RHPORT_HIGHSPEED 1 + + #define TU_ATTR_FAST_FUNC __attribute__((section(".fast"))) + +#endif + +// External USB controller +#if defined(CFG_TUH_MAX3421) && CFG_TUH_MAX3421 + #ifndef CFG_TUH_MAX3421_ENDPOINT_TOTAL + #define CFG_TUH_MAX3421_ENDPOINT_TOTAL (8 + 4 * (CFG_TUH_DEVICE_MAX - 1)) + #endif +#endif + + +//--------------------------------------------------------------------+ +// Default Values +//--------------------------------------------------------------------+ + +#if defined(TUP_USBIP_FSDEV) + #define TUP_DCD_ENDPOINT_MAX 8 +#endif + +#ifndef TUP_MCU_MULTIPLE_CORE + #define TUP_MCU_MULTIPLE_CORE 0 +#endif + +#if !defined(TUP_DCD_ENDPOINT_MAX) && defined(CFG_TUD_ENABLED) && CFG_TUD_ENABLED + #warning "TUP_DCD_ENDPOINT_MAX is not defined for this MCU, default to 8" + #define TUP_DCD_ENDPOINT_MAX 8 +#endif + +// Default to fullspeed if not defined +#ifndef TUP_RHPORT_HIGHSPEED + #define TUP_RHPORT_HIGHSPEED 0 +#endif + +// fast function, normally mean placing function in SRAM +#ifndef TU_ATTR_FAST_FUNC + #define TU_ATTR_FAST_FUNC +#endif + +#if defined(TUP_USBIP_IP3511) || defined(TUP_USBIP_RUSB2) + #define TUP_DCD_EDPT_CLOSE_API +#endif + +// USBIP implement dcd_edpt_close() and does not support ISO alloc & activate API +#ifndef TUP_DCD_EDPT_CLOSE_API + #define TUP_DCD_EDPT_ISO_ALLOC +#endif + +// Some USBIPs (SAMG, SAMX7X, PIC32, MAX3266x/MAX78002) cannot assign the same endpoint +// number to both IN and OUT. Default to 0 (same endpoint number may be used for IN and OUT). +#ifndef CFG_TUD_ENDPOINT_ONE_DIRECTION_ONLY + #define CFG_TUD_ENDPOINT_ONE_DIRECTION_ONLY 0 +#endif + +// Backward-compatible alias: legacy code only tests defined(TUD_ENDPOINT_ONE_DIRECTION_ONLY) +#if CFG_TUD_ENDPOINT_ONE_DIRECTION_ONLY && !defined(TUD_ENDPOINT_ONE_DIRECTION_ONLY) + #define TUD_ENDPOINT_ONE_DIRECTION_ONLY +#endif diff --git a/libraries/Adafruit_TinyUSB_Arduino/src/common/tusb_private.h b/libraries/Adafruit_TinyUSB_Arduino/src/common/tusb_private.h new file mode 100644 index 0000000..a31bf7b --- /dev/null +++ b/libraries/Adafruit_TinyUSB_Arduino/src/common/tusb_private.h @@ -0,0 +1,193 @@ +/* + * The MIT License (MIT) + * + * Copyright (c) 2022, 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. + * + * This file is part of the TinyUSB stack. + */ + +#ifndef TUSB_PRIVATE_H +#define TUSB_PRIVATE_H + +// Internal Helper used by Host and Device Stack + +#ifdef __cplusplus + extern "C" { +#endif + +typedef void (*tusb_defer_func_t)(uintptr_t param); + + //--------------------------------------------------------------------+ + // Configuration + //--------------------------------------------------------------------+ + +#define TUP_USBIP_CONTROLLER_NUM 2 +extern tusb_role_t _tusb_rhport_role[TUP_USBIP_CONTROLLER_NUM]; + +//--------------------------------------------------------------------+ +// Endpoint +//--------------------------------------------------------------------+ + +// Endpoint state bits — manipulate the bare uint8_t with these masks. +#define TU_EDPT_STATE_BUSY 0x01u +#define TU_EDPT_STATE_STALLED 0x02u +#define TU_EDPT_STATE_CLAIMED 0x04u + +typedef struct { + uint8_t hwid; // device: rhport, host: daddr + bool is_host; // 1: host, 0: device + uint8_t ep_addr; + // 1 byte padding + + uint16_t mps; + uint16_t xfer_len; + uint8_t *ep_buf; // set to NULL to use xfer_fifo when CFG_TUD_EDPT_DEDICATED_HWFIFO = 1 + tu_fifo_t ff; + + // mutex: read if rx, otherwise write + OSAL_MUTEX_DEF(ff_mutexdef); +}tu_edpt_stream_t; + +//--------------------------------------------------------------------+ +// Endpoint +//--------------------------------------------------------------------+ + +// Check if endpoint descriptor is valid per USB specs if debug is enabled +#if CFG_TUSB_DEBUG +bool tu_edpt_validate(const tusb_desc_endpoint_t *desc_ep, tusb_speed_t speed); +#else +TU_ATTR_ALWAYS_INLINE static inline bool tu_edpt_validate(const tusb_desc_endpoint_t *desc_ep, tusb_speed_t speed) { + (void)speed; + return tu_edpt_packet_size(desc_ep) > 0; +} +#endif + +// Bind drivers to all interfaces and endpoints in the provided configuration descriptor +bool tu_bind_driver_to_ep_itf(uint8_t driver_id, uint8_t ep2drv[][2], uint8_t itf2drv[], uint8_t itf_max, + const uint8_t *p_desc, uint16_t desc_len); + +// Claim an endpoint with provided mutex +bool tu_edpt_claim(volatile uint8_t* ep_state, osal_mutex_t mutex); + +// Release an endpoint with provided mutex +bool tu_edpt_release(volatile uint8_t* ep_state, osal_mutex_t mutex); + +//--------------------------------------------------------------------+ +// Endpoint Stream +//--------------------------------------------------------------------+ + +// Init an endpoint stream +bool tu_edpt_stream_init(tu_edpt_stream_t *s, bool is_host, bool is_tx, bool overwritable, void *ff_buf, + uint16_t ff_bufsize, uint8_t *ep_buf); + +// Deinit an endpoint stream +TU_ATTR_ALWAYS_INLINE static inline void tu_edpt_stream_deinit(tu_edpt_stream_t *s) { + (void)s; +#if OSAL_MUTEX_REQUIRED + if (s->ff.mutex_wr) { + osal_mutex_delete(s->ff.mutex_wr); + } + if (s->ff.mutex_rd) { + osal_mutex_delete(s->ff.mutex_rd); + } +#endif +} + +// Open an endpoint stream +TU_ATTR_ALWAYS_INLINE static inline void tu_edpt_stream_open(tu_edpt_stream_t *s, uint8_t hwid, + const tusb_desc_endpoint_t *desc_ep, uint16_t xfer_len) { + s->hwid = hwid; + s->ep_addr = desc_ep->bEndpointAddress; + s->mps = tu_edpt_packet_size(desc_ep); + s->xfer_len = xfer_len; +} + +TU_ATTR_ALWAYS_INLINE static inline bool tu_edpt_stream_is_opened(const tu_edpt_stream_t *s) { + return s->ep_addr != 0; +} + +TU_ATTR_ALWAYS_INLINE static inline void tu_edpt_stream_close(tu_edpt_stream_t* s) { + s->ep_addr = 0; +} + +TU_ATTR_ALWAYS_INLINE static inline void tu_edpt_stream_clear(tu_edpt_stream_t *s) { + tu_fifo_clear(&s->ff); +} + +TU_ATTR_ALWAYS_INLINE static inline bool tu_edpt_stream_empty(tu_edpt_stream_t *s) { + return tu_fifo_empty(&s->ff); +} + +//--------------------------------------------------------------------+ +// Stream Write +//--------------------------------------------------------------------+ + +// Write to stream +uint32_t tu_edpt_stream_write(tu_edpt_stream_t *s, const void *buffer, uint32_t bufsize); + +// Start an usb transfer if endpoint is not busy. Return number of queued bytes +uint32_t tu_edpt_stream_write_xfer(tu_edpt_stream_t *s); + +// Start an zero-length packet if needed +bool tu_edpt_stream_write_zlp_if_needed(tu_edpt_stream_t *s, uint32_t last_xferred_bytes); + +// Get the number of bytes available for writing to FIFO +// Note: if no fifo, return endpoint size if not busy, 0 otherwise +uint32_t tu_edpt_stream_write_available(tu_edpt_stream_t *s); + +//--------------------------------------------------------------------+ +// Stream Read +//--------------------------------------------------------------------+ + +// Read from stream +uint32_t tu_edpt_stream_read(tu_edpt_stream_t *s, void *buffer, uint32_t bufsize); + +// Start an usb transfer if endpoint is not busy +uint32_t tu_edpt_stream_read_xfer(tu_edpt_stream_t *s); + +// Complete read transfer by writing EP -> FIFO. Must be called in the transfer complete callback +TU_ATTR_ALWAYS_INLINE static inline +void tu_edpt_stream_read_xfer_complete(tu_edpt_stream_t* s, uint32_t xferred_bytes) { + if (s->ep_buf != NULL) { + tu_fifo_write_n(&s->ff, s->ep_buf, (uint16_t)xferred_bytes); + } +} + +// Complete read transfer with provided buffer +TU_ATTR_ALWAYS_INLINE static inline +void tu_edpt_stream_read_xfer_complete_with_buf(tu_edpt_stream_t *s, const void *buf, uint32_t xferred_bytes) { + tu_fifo_write_n(&s->ff, buf, (uint16_t)xferred_bytes); +} + +// Get the number of bytes available for reading +TU_ATTR_ALWAYS_INLINE static inline uint32_t tu_edpt_stream_read_available(const tu_edpt_stream_t *s) { + return (uint32_t) tu_fifo_count(&s->ff); +} + +TU_ATTR_ALWAYS_INLINE static inline bool tu_edpt_stream_peek(tu_edpt_stream_t *s, uint8_t *ch) { + return tu_fifo_peek(&s->ff, ch); +} + +#ifdef __cplusplus + } +#endif + +#endif diff --git a/libraries/Adafruit_TinyUSB_Arduino/src/common/tusb_types.h b/libraries/Adafruit_TinyUSB_Arduino/src/common/tusb_types.h new file mode 100644 index 0000000..959fc12 --- /dev/null +++ b/libraries/Adafruit_TinyUSB_Arduino/src/common/tusb_types.h @@ -0,0 +1,602 @@ +/* + * 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. + * + * This file is part of the TinyUSB stack. + */ + +#ifndef TUSB_TYPES_H_ +#define TUSB_TYPES_H_ + +#include +#include +#include "tusb_compiler.h" + +#ifdef __cplusplus + extern "C" { +#endif + +//------------- Device DCache declaration -------------// +#define TUD_EPBUF_DCACHE_SIZE(_size) (CFG_TUD_MEM_DCACHE_ENABLE ? \ + (TU_DIV_CEIL(_size, CFG_TUD_MEM_DCACHE_LINE_SIZE) * CFG_TUD_MEM_DCACHE_LINE_SIZE) : (_size)) + +// Declare an endpoint buffer with uint8_t[size] +#define TUD_EPBUF_DEF(_name, _size) \ + union { \ + CFG_TUD_MEM_ALIGN uint8_t _name[_size]; \ + TU_ATTR_ALIGNED(CFG_TUD_MEM_DCACHE_ENABLE ? CFG_TUD_MEM_DCACHE_LINE_SIZE : 1) uint8_t _name##_dcache_padding[TUD_EPBUF_DCACHE_SIZE(_size)]; \ + } + +// Declare an endpoint buffer with a type +#define TUD_EPBUF_TYPE_DEF(_type, _name) \ + union { \ + CFG_TUD_MEM_ALIGN _type _name; \ + TU_ATTR_ALIGNED(CFG_TUD_MEM_DCACHE_ENABLE ? CFG_TUD_MEM_DCACHE_LINE_SIZE : 1) uint8_t _name##_dcache_padding[TUD_EPBUF_DCACHE_SIZE(sizeof(_type))]; \ + } + +//------------- Host DCache declaration -------------// +#define TUH_EPBUF_DCACHE_SIZE(_size) (CFG_TUH_MEM_DCACHE_ENABLE ? \ + (TU_DIV_CEIL(_size, CFG_TUH_MEM_DCACHE_LINE_SIZE) * CFG_TUH_MEM_DCACHE_LINE_SIZE) : (_size)) + +// Declare an endpoint buffer with uint8_t[size] +#define TUH_EPBUF_DEF(_name, _size) \ + union { \ + CFG_TUH_MEM_ALIGN uint8_t _name[_size]; \ + TU_ATTR_ALIGNED(CFG_TUH_MEM_DCACHE_ENABLE ? CFG_TUH_MEM_DCACHE_LINE_SIZE : 1) uint8_t _name##_dcache_padding[TUH_EPBUF_DCACHE_SIZE(_size)]; \ + } + +// Declare an endpoint buffer with a type +#define TUH_EPBUF_TYPE_DEF(_type, _name) \ + union { \ + CFG_TUH_MEM_ALIGN _type _name; \ + TU_ATTR_ALIGNED(CFG_TUH_MEM_DCACHE_ENABLE ? CFG_TUH_MEM_DCACHE_LINE_SIZE : 1) uint8_t _name##_dcache_padding[TUH_EPBUF_DCACHE_SIZE(sizeof(_type))]; \ + } + + +/*------------------------------------------------------------------*/ +/* CONSTANTS + *------------------------------------------------------------------*/ + +typedef enum { + TUSB_ROLE_INVALID = 0u, + TUSB_ROLE_DEVICE = 0x1, + TUSB_ROLE_HOST = 0x2, +} tusb_role_t; + +/// defined base on EHCI specs value for Endpoint Speed +typedef enum { + TUSB_SPEED_FULL = 0, + TUSB_SPEED_LOW = 1, + TUSB_SPEED_HIGH = 2, + TUSB_SPEED_AUTO = 0xaa, + TUSB_SPEED_INVALID = 0xff, +} tusb_speed_t; + +/// defined base on USB Specs Endpoint's bmAttributes +typedef enum { + TUSB_XFER_CONTROL = 0, + TUSB_XFER_ISOCHRONOUS = 1, + TUSB_XFER_BULK = 2, + TUSB_XFER_INTERRUPT = 3 +} tusb_xfer_type_t; + +typedef enum { + TUSB_DIR_OUT = 0u, + TUSB_DIR_IN = 1u, +} tusb_dir_t; + +enum { + TUSB_EPNUM_MASK = 0x0F, + TUSB_DIR_IN_MASK = 0x80 +}; + +enum { + TUSB_EPSIZE_BULK_FS = 64, + TUSB_EPSIZE_BULK_HS = 512, + + TUSB_EPSIZE_ISO_FS_MAX = 1023, + TUSB_EPSIZE_ISO_HS_MAX = 1024, +}; + +// Endpoint Bulk size depending on host/device max speed +#define TUD_EPSIZE_BULK_MAX (TUD_OPT_HIGH_SPEED ? 512 : 64) +#define TUH_EPSIZE_BULK_MAX (TUH_OPT_HIGH_SPEED ? 512 : 64) + +/// Isochronous Endpoint Attributes +typedef enum { + TUSB_ISO_EP_ATT_NO_SYNC = 0x00, + TUSB_ISO_EP_ATT_ASYNCHRONOUS = 0x04, + TUSB_ISO_EP_ATT_ADAPTIVE = 0x08, + TUSB_ISO_EP_ATT_SYNCHRONOUS = 0x0C, + TUSB_ISO_EP_ATT_DATA = 0x00, ///< Data End Point + TUSB_ISO_EP_ATT_EXPLICIT_FB = 0x10, ///< Feedback End Point + TUSB_ISO_EP_ATT_IMPLICIT_FB = 0x20, ///< Data endpoint that also serves as an implicit feedback +} tusb_iso_ep_attribute_t; + +/// USB Descriptor Types +typedef enum { + TUSB_DESC_DEVICE = 0x01, + TUSB_DESC_CONFIGURATION = 0x02, + TUSB_DESC_STRING = 0x03, + TUSB_DESC_INTERFACE = 0x04, + TUSB_DESC_ENDPOINT = 0x05, + TUSB_DESC_DEVICE_QUALIFIER = 0x06, + TUSB_DESC_OTHER_SPEED_CONFIG = 0x07, + TUSB_DESC_INTERFACE_POWER = 0x08, + TUSB_DESC_OTG = 0x09, + TUSB_DESC_DEBUG = 0x0A, + TUSB_DESC_INTERFACE_ASSOCIATION = 0x0B, + + TUSB_DESC_BOS = 0x0F, + TUSB_DESC_DEVICE_CAPABILITY = 0x10, + + TUSB_DESC_FUNCTIONAL = 0x21, + + // Class Specific Descriptor + TUSB_DESC_CS_DEVICE = 0x21, + TUSB_DESC_CS_CONFIGURATION = 0x22, + TUSB_DESC_CS_STRING = 0x23, + TUSB_DESC_CS_INTERFACE = 0x24, + TUSB_DESC_CS_ENDPOINT = 0x25, + + TUSB_DESC_SUPERSPEED_ENDPOINT_COMPANION = 0x30, + TUSB_DESC_SUPERSPEED_ISO_ENDPOINT_COMPANION = 0x31 +} tusb_desc_type_t; + +typedef enum { + TUSB_REQ_GET_STATUS = 0 , + TUSB_REQ_CLEAR_FEATURE = 1 , + TUSB_REQ_RESERVED = 2 , + TUSB_REQ_SET_FEATURE = 3 , + TUSB_REQ_RESERVED2 = 4 , + TUSB_REQ_SET_ADDRESS = 5 , + TUSB_REQ_GET_DESCRIPTOR = 6 , + TUSB_REQ_SET_DESCRIPTOR = 7 , + TUSB_REQ_GET_CONFIGURATION = 8 , + TUSB_REQ_SET_CONFIGURATION = 9 , + TUSB_REQ_GET_INTERFACE = 10 , + TUSB_REQ_SET_INTERFACE = 11 , + TUSB_REQ_SYNCH_FRAME = 12 +} tusb_request_code_t; + +typedef enum { + TUSB_REQ_FEATURE_EDPT_HALT = 0, + TUSB_REQ_FEATURE_REMOTE_WAKEUP = 1, + TUSB_REQ_FEATURE_TEST_MODE = 2 +} tusb_request_feature_selector_t; + +typedef enum { + TUSB_REQ_TYPE_STANDARD = 0u, + TUSB_REQ_TYPE_CLASS, + TUSB_REQ_TYPE_VENDOR, + TUSB_REQ_TYPE_INVALID +} tusb_request_type_t; + +typedef enum { + TUSB_REQ_RCPT_DEVICE =0, + TUSB_REQ_RCPT_INTERFACE, + TUSB_REQ_RCPT_ENDPOINT, + TUSB_REQ_RCPT_OTHER +} tusb_request_recipient_t; + +// https://www.usb.org/defined-class-codes +typedef enum { + TUSB_CLASS_UNSPECIFIED = 0 , + TUSB_CLASS_AUDIO = 1 , + TUSB_CLASS_CDC = 2 , + TUSB_CLASS_HID = 3 , + TUSB_CLASS_RESERVED_4 = 4 , + TUSB_CLASS_PHYSICAL = 5 , + TUSB_CLASS_IMAGE = 6 , + TUSB_CLASS_PRINTER = 7 , + TUSB_CLASS_MSC = 8 , + TUSB_CLASS_HUB = 9 , + TUSB_CLASS_CDC_DATA = 10 , + TUSB_CLASS_SMART_CARD = 11 , + TUSB_CLASS_RESERVED_12 = 12 , + TUSB_CLASS_CONTENT_SECURITY = 13 , + TUSB_CLASS_VIDEO = 14 , + TUSB_CLASS_PERSONAL_HEALTHCARE = 15 , + TUSB_CLASS_AUDIO_VIDEO = 16 , + + TUSB_CLASS_DIAGNOSTIC = 0xDC , + TUSB_CLASS_WIRELESS_CONTROLLER = 0xE0 , + TUSB_CLASS_MISC = 0xEF , + TUSB_CLASS_APPLICATION_SPECIFIC = 0xFE , + TUSB_CLASS_VENDOR_SPECIFIC = 0xFF +} tusb_class_code_t; + +typedef enum +{ + MISC_SUBCLASS_COMMON = 2 +}misc_subclass_type_t; + +typedef enum { + MISC_PROTOCOL_IAD = 1 +} misc_protocol_type_t; + +typedef enum { + APP_SUBCLASS_USBTMC = 0x03, + APP_SUBCLASS_DFU_RUNTIME = 0x01 +} app_subclass_type_t; + +typedef enum { + DEVICE_CAPABILITY_WIRELESS_USB = 0x01, + DEVICE_CAPABILITY_USB20_EXTENSION = 0x02, + DEVICE_CAPABILITY_SUPERSPEED_USB = 0x03, + DEVICE_CAPABILITY_CONTAINER_id = 0x04, + DEVICE_CAPABILITY_PLATFORM = 0x05, + DEVICE_CAPABILITY_POWER_DELIVERY = 0x06, + DEVICE_CAPABILITY_BATTERY_INFO = 0x07, + DEVICE_CAPABILITY_PD_CONSUMER_PORT = 0x08, + DEVICE_CAPABILITY_PD_PROVIDER_PORT = 0x09, + DEVICE_CAPABILITY_SUPERSPEED_PLUS = 0x0A, + DEVICE_CAPABILITY_PRECESION_TIME_MEASUREMENT = 0x0B, + DEVICE_CAPABILITY_WIRELESS_USB_EXT = 0x0C, + DEVICE_CAPABILITY_BILLBOARD = 0x0D, + DEVICE_CAPABILITY_AUTHENTICATION = 0x0E, + DEVICE_CAPABILITY_BILLBOARD_EX = 0x0F, + DEVICE_CAPABILITY_CONFIGURATION_SUMMARY = 0x10 +} device_capability_type_t; + +enum { + TUSB_DESC_CONFIG_ATT_REMOTE_WAKEUP = 1 << 5, + TUSB_DESC_CONFIG_ATT_SELF_POWERED = 1 << 6, +}; + +#define TUSB_DESC_CONFIG_POWER_MA(x) ((x)/2) + +// USB 2.0 Spec Table 9-7: Test Mode Selectors +typedef enum { + TUSB_FEATURE_TEST_J = 1, + TUSB_FEATURE_TEST_K = 2, + TUSB_FEATURE_TEST_SE0_NAK = 3, + TUSB_FEATURE_TEST_PACKET = 4, + TUSB_FEATURE_TEST_FORCE_ENABLE = 5, +} tusb_feature_test_mode_t; + +//--------------------------------------------------------------------+ +// +//--------------------------------------------------------------------+ +typedef enum { + XFER_RESULT_SUCCESS = 0, + XFER_RESULT_FAILED, + XFER_RESULT_STALLED, + XFER_RESULT_TIMEOUT, + XFER_RESULT_INVALID +} xfer_result_t; + +#define tusb_xfer_result_t xfer_result_t + +// TODO remove +enum { + DESC_OFFSET_LEN = 0, + DESC_OFFSET_TYPE = 1, + DESC_OFFSET_SUBTYPE = 2 +}; + +enum { + INTERFACE_INVALID_NUMBER = 0xff +}; + +typedef enum { + MS_OS_20_SET_HEADER_DESCRIPTOR = 0x00, + MS_OS_20_SUBSET_HEADER_CONFIGURATION = 0x01, + MS_OS_20_SUBSET_HEADER_FUNCTION = 0x02, + MS_OS_20_FEATURE_COMPATBLE_ID = 0x03, + MS_OS_20_FEATURE_REG_PROPERTY = 0x04, + MS_OS_20_FEATURE_MIN_RESUME_TIME = 0x05, + MS_OS_20_FEATURE_MODEL_ID = 0x06, + MS_OS_20_FEATURE_CCGP_DEVICE = 0x07, + MS_OS_20_FEATURE_VENDOR_REVISION = 0x08 +} microsoft_os_20_type_t; + +enum { + CONTROL_STAGE_IDLE = 0, + CONTROL_STAGE_SETUP, // 1 + CONTROL_STAGE_DATA, // 2 + CONTROL_STAGE_ACK // 3 +}; + +enum { + TUSB_INDEX_INVALID_8 = 0xFF +}; + +enum { + TU_EP0_OUT = 0x00, + TU_EP0_IN = 0x80 +}; + + +//--------------------------------------------------------------------+ +// +//--------------------------------------------------------------------+ +typedef struct { + tusb_role_t role; + tusb_speed_t speed; +} tusb_rhport_init_t; + +typedef struct { + uint16_t len; + uint8_t *buffer; +} tusb_buffer_t; + +//--------------------------------------------------------------------+ +// USB Descriptors +//--------------------------------------------------------------------+ + +// Start of all packed definitions for compiler without per-type packed +TU_ATTR_PACKED_BEGIN +TU_ATTR_BIT_FIELD_ORDER_BEGIN + +/// USB Device Descriptor +typedef struct TU_ATTR_PACKED { + uint8_t bLength ; ///< Size of this descriptor in bytes. + uint8_t bDescriptorType ; ///< DEVICE Descriptor Type. + uint16_t bcdUSB ; ///< BUSB Specification Release Number in Binary-Coded Decimal (i.e., 2.10 is 210H). + uint8_t bDeviceClass ; ///< Class code (assigned by the USB-IF). + uint8_t bDeviceSubClass ; ///< Subclass code (assigned by the USB-IF). + uint8_t bDeviceProtocol ; ///< Protocol code (assigned by the USB-IF). + uint8_t bMaxPacketSize0 ; ///< Maximum packet size for endpoint zero (only 8, 16, 32, or 64 are valid). For HS devices is fixed to 64. + uint16_t idVendor ; ///< Vendor ID (assigned by the USB-IF). + uint16_t idProduct ; ///< Product ID (assigned by the manufacturer). + uint16_t bcdDevice ; ///< Device release number in binary-coded decimal. + uint8_t iManufacturer ; ///< Index of string descriptor describing manufacturer. + uint8_t iProduct ; ///< Index of string descriptor describing product. + uint8_t iSerialNumber ; ///< Index of string descriptor describing the device's serial number. + uint8_t bNumConfigurations ; ///< Number of possible configurations. +} tusb_desc_device_t; + +TU_VERIFY_STATIC( sizeof(tusb_desc_device_t) == 18u, "size is not correct"); + +// USB Binary Device Object Store (BOS) Descriptor +typedef struct TU_ATTR_PACKED { + uint8_t bLength ; ///< Size of this descriptor in bytes + uint8_t bDescriptorType ; ///< CONFIGURATION Descriptor Type + uint16_t wTotalLength ; ///< Total length of data returned for this descriptor + uint8_t bNumDeviceCaps ; ///< Number of device capability descriptors in the BOS +} tusb_desc_bos_t; + +TU_VERIFY_STATIC( sizeof(tusb_desc_bos_t) == 5u, "size is not correct"); + +/// USB Configuration Descriptor +typedef struct TU_ATTR_PACKED { + uint8_t bLength ; ///< Size of this descriptor in bytes + uint8_t bDescriptorType ; ///< CONFIGURATION Descriptor Type + uint16_t wTotalLength ; ///< Total length of data returned for this configuration. Includes the combined length of all descriptors (configuration, interface, endpoint, and class- or vendor-specific) returned for this configuration. + + uint8_t bNumInterfaces ; ///< Number of interfaces supported by this configuration + uint8_t bConfigurationValue ; ///< Value to use as an argument to the SetConfiguration() request to select this configuration. + uint8_t iConfiguration ; ///< Index of string descriptor describing this configuration + uint8_t bmAttributes ; ///< Configuration characteristics \n D7: Reserved (set to one)\n D6: Self-powered \n D5: Remote Wakeup \n D4...0: Reserved (reset to zero) \n D7 is reserved and must be set to one for historical reasons. \n A device configuration that uses power from the bus and a local source reports a non-zero value in bMaxPower to indicate the amount of bus power required and sets D6. The actual power source at runtime may be determined using the GetStatus(DEVICE) request (see USB 2.0 spec Section 9.4.5). \n If a device configuration supports remote wakeup, D5 is set to one. + uint8_t bMaxPower ; ///< Maximum power consumption of the USB device from the bus in this specific configuration when the device is fully operational. Expressed in 2 mA units (i.e., 50 = 100 mA). +} tusb_desc_configuration_t; + +TU_VERIFY_STATIC( sizeof(tusb_desc_configuration_t) == 9u, "size is not correct"); + +/// USB Interface Descriptor +typedef struct TU_ATTR_PACKED { + uint8_t bLength ; ///< Size of this descriptor in bytes + uint8_t bDescriptorType ; ///< INTERFACE Descriptor Type + + uint8_t bInterfaceNumber ; ///< Number of this interface. Zero-based value identifying the index in the array of concurrent interfaces supported by this configuration. + uint8_t bAlternateSetting ; ///< Value used to select this alternate setting for the interface identified in the prior field + uint8_t bNumEndpoints ; ///< Number of endpoints used by this interface (excluding endpoint zero). If this value is zero, this interface only uses the Default Control Pipe. + uint8_t bInterfaceClass ; ///< Class code (assigned by the USB-IF). \li A value of zero is reserved for future standardization. \li If this field is set to FFH, the interface class is vendor-specific. \li All other values are reserved for assignment by the USB-IF. + uint8_t bInterfaceSubClass ; ///< Subclass code (assigned by the USB-IF). \n These codes are qualified by the value of the bInterfaceClass field. \li If the bInterfaceClass field is reset to zero, this field must also be reset to zero. \li If the bInterfaceClass field is not set to FFH, all values are reserved for assignment by the USB-IF. + uint8_t bInterfaceProtocol ; ///< Protocol code (assigned by the USB). \n These codes are qualified by the value of the bInterfaceClass and the bInterfaceSubClass fields. If an interface supports class-specific requests, this code identifies the protocols that the device uses as defined by the specification of the device class. \li If this field is reset to zero, the device does not use a class-specific protocol on this interface. \li If this field is set to FFH, the device uses a vendor-specific protocol for this interface. + uint8_t iInterface ; ///< Index of string descriptor describing this interface +} tusb_desc_interface_t; + +TU_VERIFY_STATIC( sizeof(tusb_desc_interface_t) == 9u, "size is not correct"); + +/// USB Endpoint Descriptor +typedef struct TU_ATTR_PACKED { + uint8_t bLength ; // Size of this descriptor in bytes + uint8_t bDescriptorType ; // ENDPOINT Descriptor Type + + uint8_t bEndpointAddress ; // The address of the endpoint + + struct TU_ATTR_PACKED { +#if (TU_BITFIELD_ORDER == TU_BITFIELD_LE) + uint8_t xfer : 2; // Control, ISO, Bulk, Interrupt + uint8_t sync : 2; // None, Asynchronous, Adaptive, Synchronous + uint8_t usage : 2; // Data, Feedback, Implicit feedback + uint8_t : 2; +#elif (TU_BITFIELD_ORDER == TU_BITFIELD_BE) + uint8_t : 2; + uint8_t usage : 2; + uint8_t sync : 2; + uint8_t xfer : 2; +#else + #error "Please define TU_BITFIELD_ORDER as TU_BITFIELD_LE or TU_BITFIELD_BE" +#endif + } bmAttributes; + + uint16_t wMaxPacketSize ; // Bit 10..0 : max packet size, bit 12..11 additional transaction per highspeed micro-frame + uint8_t bInterval ; // Polling interval, in frames or microframes depending on the operating speed +} tusb_desc_endpoint_t; + +TU_VERIFY_STATIC( sizeof(tusb_desc_endpoint_t) == 7u, "size is not correct"); + +/// USB Other Speed Configuration Descriptor +typedef struct TU_ATTR_PACKED { + uint8_t bLength ; ///< Size of descriptor + uint8_t bDescriptorType ; ///< Other_speed_Configuration Type + uint16_t wTotalLength ; ///< Total length of data returned + + uint8_t bNumInterfaces ; ///< Number of interfaces supported by this speed configuration + uint8_t bConfigurationValue ; ///< Value to use to select configuration + uint8_t iConfiguration ; ///< Index of string descriptor + uint8_t bmAttributes ; ///< Same as Configuration descriptor + uint8_t bMaxPower ; ///< Same as Configuration descriptor +} tusb_desc_other_speed_t; + +/// USB Device Qualifier Descriptor +typedef struct TU_ATTR_PACKED { + uint8_t bLength ; ///< Size of descriptor + uint8_t bDescriptorType ; ///< Device Qualifier Type + uint16_t bcdUSB ; ///< USB specification version number (e.g., 0200H for V2.00) + + uint8_t bDeviceClass ; ///< Class Code + uint8_t bDeviceSubClass ; ///< SubClass Code + uint8_t bDeviceProtocol ; ///< Protocol Code + + uint8_t bMaxPacketSize0 ; ///< Maximum packet size for other speed + uint8_t bNumConfigurations ; ///< Number of Other-speed Configurations + uint8_t bReserved ; ///< Reserved for future use, must be zero +} tusb_desc_device_qualifier_t; + +TU_VERIFY_STATIC( sizeof(tusb_desc_device_qualifier_t) == 10u, "size is not correct"); + +/// USB Interface Association Descriptor (IAD ECN) +typedef struct TU_ATTR_PACKED { + uint8_t bLength ; ///< Size of descriptor + uint8_t bDescriptorType ; ///< Other_speed_Configuration Type + + uint8_t bFirstInterface ; ///< Index of the first associated interface. + uint8_t bInterfaceCount ; ///< Total number of associated interfaces. + + uint8_t bFunctionClass ; ///< Interface class ID. + uint8_t bFunctionSubClass ; ///< Interface subclass ID. + uint8_t bFunctionProtocol ; ///< Interface protocol ID. + + uint8_t iFunction ; ///< Index of the string descriptor describing the interface association. +} tusb_desc_interface_assoc_t; + +TU_VERIFY_STATIC( sizeof(tusb_desc_interface_assoc_t) == 8u, "size is not correct"); + +// USB String Descriptor +typedef struct TU_ATTR_PACKED { + uint8_t bLength ; ///< Size of this descriptor in bytes + uint8_t bDescriptorType ; ///< Descriptor Type + uint16_t utf16le[]; +} tusb_desc_string_t; + +// USB Binary Device Object Store (BOS) +typedef struct TU_ATTR_PACKED { + uint8_t bLength; + uint8_t bDescriptorType ; + uint8_t bDevCapabilityType; + uint8_t bReserved; + uint8_t PlatformCapabilityUUID[16]; + uint8_t CapabilityData[]; +} tusb_desc_bos_platform_t; + +// USB WebUSB URL Descriptor +typedef struct TU_ATTR_PACKED { + uint8_t bLength; + uint8_t bDescriptorType; + uint8_t bScheme; + char url[]; +} tusb_desc_webusb_url_t; + +// DFU Functional Descriptor +typedef struct TU_ATTR_PACKED { + uint8_t bLength; + uint8_t bDescriptorType; + + union { + struct TU_ATTR_PACKED { + uint8_t bitCanDnload : 1; + uint8_t bitCanUpload : 1; + uint8_t bitManifestationTolerant : 1; + uint8_t bitWillDetach : 1; + uint8_t reserved : 4; + } bmAttributes; + + uint8_t bAttributes; + }; + + uint16_t wDetachTimeOut; + uint16_t wTransferSize; + uint16_t bcdDFUVersion; +} tusb_desc_dfu_functional_t; + +//--------------------------------------------------------------------+ +// +//--------------------------------------------------------------------+ + +typedef struct TU_ATTR_PACKED { + union { + struct TU_ATTR_PACKED { +#if (TU_BITFIELD_ORDER == TU_BITFIELD_LE) + uint8_t recipient : 5; ///< Recipient type tusb_request_recipient_t. + uint8_t type : 2; ///< Request type tusb_request_type_t. + uint8_t direction : 1; ///< Direction type. tusb_dir_t +#elif (TU_BITFIELD_ORDER == TU_BITFIELD_BE) + uint8_t direction : 1; ///< Direction type. tusb_dir_t + uint8_t type : 2; ///< Request type tusb_request_type_t. + uint8_t recipient : 5; ///< Recipient type tusb_request_recipient_t. +#else + #error "Please define TU_BITFIELD_ORDER as TU_BITFIELD_LE or TU_BITFIELD_BE" +#endif + } bmRequestType_bit; + + uint8_t bmRequestType; + }; + + uint8_t bRequest; + uint16_t wValue; + uint16_t wIndex; + uint16_t wLength; +} tusb_control_request_t; + +TU_VERIFY_STATIC( sizeof(tusb_control_request_t) == 8u, "size is not correct"); + +TU_ATTR_PACKED_END // End of all packed definitions +TU_ATTR_BIT_FIELD_ORDER_END + +//--------------------------------------------------------------------+ +// Endpoint helper +//--------------------------------------------------------------------+ + +// Get direction from Endpoint address +TU_ATTR_ALWAYS_INLINE static inline tusb_dir_t tu_edpt_dir(uint8_t addr) { + return (addr & TUSB_DIR_IN_MASK) ? TUSB_DIR_IN : TUSB_DIR_OUT; +} + +// Get Endpoint number from address +TU_ATTR_ALWAYS_INLINE static inline uint8_t tu_edpt_number(uint8_t addr) { + return (uint8_t) (addr & TUSB_EPNUM_MASK); +} + +TU_ATTR_ALWAYS_INLINE static inline uint8_t tu_edpt_addr(uint8_t num, uint8_t dir) { + return (uint8_t) (num | (dir == (uint8_t)TUSB_DIR_IN ? (uint8_t)TUSB_DIR_IN_MASK : 0u)); +} + +TU_ATTR_ALWAYS_INLINE static inline uint16_t tu_edpt_packet_size(tusb_desc_endpoint_t const* desc_ep) { + return tu_le16toh(desc_ep->wMaxPacketSize) & 0x7FF; +} + +#if CFG_TUSB_DEBUG +TU_ATTR_ALWAYS_INLINE static inline const char *tu_edpt_type_str(tusb_xfer_type_t t) { + tu_static const char *str[] = {"control", "isochronous", "bulk", "interrupt"}; + return str[t]; +} +#endif + +#ifdef __cplusplus + } +#endif + +#endif // TUSB_TYPES_H_ diff --git a/libraries/Adafruit_TinyUSB_Arduino/src/common/tusb_verify.h b/libraries/Adafruit_TinyUSB_Arduino/src/common/tusb_verify.h new file mode 100644 index 0000000..c0e4d08 --- /dev/null +++ b/libraries/Adafruit_TinyUSB_Arduino/src/common/tusb_verify.h @@ -0,0 +1,138 @@ +/* + * 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. + * + * This file is part of the TinyUSB stack. + */ +#ifndef TUSB_VERIFY_H_ +#define TUSB_VERIFY_H_ + +#include +#include +#include "tusb_option.h" +#include "tusb_compiler.h" + +/*------------------------------------------------------------------*/ +/* This file use an advanced macro technique to mimic the default parameter + * as C++ for the sake of code simplicity. Beware of a headache macro + * manipulation that you are told to stay away. + * + * This contains macros for both VERIFY and ASSERT: + * + * VERIFY: Used when there is an error condition which is not the + * fault of the MCU. For example, bounds checking on data + * sent to the micro over USB should use this function. + * Another example is checking for buffer overflows, where + * returning from the active function causes a NAK. + * + * ASSERT: Used for error conditions that are caused by MCU firmware + * bugs. This is used to discover bugs in the code more + * quickly. One example would be adding assertions in library + * function calls to confirm a function's (untainted) + * parameters are valid. + * + * The difference in behavior is that ASSERT triggers a breakpoint while + * verify does not. + * + * #define TU_VERIFY(cond) if (!cond) return false; + * #define TU_VERIFY(cond,ret) if (!cond) return ret; + * + * #define TU_ASSERT(cond) if (!cond) {TU_MESS_FAILED(); TU_BREAKPOINT(), return false;} + * #define TU_ASSERT(cond,ret) if (!cond) {TU_MESS_FAILED(); TU_BREAKPOINT(), return ret;} + *------------------------------------------------------------------*/ + +#ifdef __cplusplus + extern "C" { +#endif + +//--------------------------------------------------------------------+ +// TU_VERIFY Helper +//--------------------------------------------------------------------+ +#if CFG_TUSB_DEBUG + #define TU_MESS_FAILED() TU_LOG1("%s %d: ASSERT FAILED\r\n", __func__, __LINE__) +#else + #define TU_MESS_FAILED() do {} while (0) +#endif + +// Custom defined application function +#ifdef CFG_TUSB_DEBUG_BREAKPOINT + extern void CFG_TUSB_DEBUG_BREAKPOINT(void); + #define TU_BREAKPOINT() CFG_TUSB_DEBUG_BREAKPOINT() + +// Halt CPU (breakpoint) when hitting error, only apply for Cortex M3, M4, M7, M33. M55 +#elif defined(__ARM_ARCH_7M__) || defined (__ARM_ARCH_7EM__) || defined(__ARM_ARCH_8M_MAIN__) || defined(__ARM_ARCH_8_1M_MAIN__) || \ + defined(__ARM7M__) || defined (__ARM7EM__) || defined(__ARM8M_MAINLINE__) || defined(__ARM8EM_MAINLINE__) + #define TU_BREAKPOINT() do { \ + volatile uint32_t* ARM_CM_DHCSR = ((volatile uint32_t*) 0xE000EDF0UL); /* Cortex M CoreDebug->DHCSR */ \ + if (0u != ((*ARM_CM_DHCSR) & 1UL)) { __asm("BKPT #0\n"); } /* Only halt mcu if debugger is attached */ \ + } while(0) + +#elif defined(__riscv) && !defined(ESP_PLATFORM) + #define TU_BREAKPOINT() do { __asm("ebreak\n"); } while(0) + +#elif defined(_mips) + #define TU_BREAKPOINT() do { __asm("sdbbp 0"); } while (0) + +#else + #define TU_BREAKPOINT() do {} while (0) +#endif + +/*------------------------------------------------------------------*/ +/* TU_VERIFY + * - TU_VERIFY_1ARGS : return false if failed + * - TU_VERIFY_2ARGS : return provided value if failed + *------------------------------------------------------------------*/ +#define TU_VERIFY_DEFINE(_cond, _ret) \ + do { \ + if (!(_cond)) { \ + return _ret; \ + } \ + } while (0) + +#define TU_VERIFY_1ARGS(_cond) TU_VERIFY_DEFINE(_cond, false) +#define TU_VERIFY_2ARGS(_cond, _ret) TU_VERIFY_DEFINE(_cond, _ret) + +#define TU_VERIFY(...) TU_GET_3RD_ARG(__VA_ARGS__, TU_VERIFY_2ARGS, TU_VERIFY_1ARGS, _dummy)(__VA_ARGS__) + +/*------------------------------------------------------------------*/ +/* ASSERT + * basically TU_VERIFY with TU_BREAKPOINT() as handler + * - 1 arg : return false if failed + * - 2 arg : return error if failed + *------------------------------------------------------------------*/ +#define TU_ASSERT_DEFINE(_cond, _ret) \ + do { \ + if ( !(_cond) ) { TU_MESS_FAILED(); TU_BREAKPOINT(); return _ret; } \ + } while(0) + +#define TU_ASSERT_1ARGS(_cond) TU_ASSERT_DEFINE(_cond, false) +#define TU_ASSERT_2ARGS(_cond, _ret) TU_ASSERT_DEFINE(_cond, _ret) + +#ifndef TU_ASSERT +#define TU_ASSERT(...) TU_GET_3RD_ARG(__VA_ARGS__, TU_ASSERT_2ARGS, TU_ASSERT_1ARGS, _dummy)(__VA_ARGS__) +#endif + +#ifdef __cplusplus + } +#endif + +#endif diff --git a/libraries/Adafruit_TinyUSB_Arduino/src/device/dcd.h b/libraries/Adafruit_TinyUSB_Arduino/src/device/dcd.h new file mode 100644 index 0000000..f861eb2 --- /dev/null +++ b/libraries/Adafruit_TinyUSB_Arduino/src/device/dcd.h @@ -0,0 +1,253 @@ +/* + * 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. + * + * This file is part of the TinyUSB stack. + */ + +#ifndef TUSB_DCD_H_ +#define TUSB_DCD_H_ + +#include "common/tusb_common.h" +#include "osal/osal.h" +#include "common/tusb_fifo.h" + +#ifdef __cplusplus + extern "C" { +#endif + +//--------------------------------------------------------------------+ +// MACRO CONSTANT TYPEDEF PROTYPES +//--------------------------------------------------------------------+ + +typedef enum { + DCD_EVENT_INVALID = 0, // 0 + DCD_EVENT_BUS_RESET, // 1 + DCD_EVENT_UNPLUGGED, // 2 + DCD_EVENT_SOF, // 3 + DCD_EVENT_SUSPEND, // 4 TODO LPM Sleep L1 support + DCD_EVENT_RESUME, // 5 + DCD_EVENT_SETUP_RECEIVED, // 6 + DCD_EVENT_XFER_COMPLETE, // 7 + USBD_EVENT_FUNC_CALL, // 8 Not an DCD event, just a convenient way to defer ISR function + DCD_EVENT_COUNT +} dcd_eventid_t; + +typedef struct TU_ATTR_ALIGNED(4) { + uint8_t rhport; + uint8_t event_id; + + union { + // BUS RESET + struct { + tusb_speed_t speed; + } bus_reset; + + // SOF + struct { + uint32_t frame_count; + }sof; + + // SETUP_RECEIVED + tusb_control_request_t setup_received; + + // XFER_COMPLETE + struct { + uint8_t ep_addr; + uint8_t result; + uint32_t len; + }xfer_complete; + + // FUNC_CALL + struct { + void (*func) (void* param); + void* param; + }func_call; + }; +} dcd_event_t; + +//TU_VERIFY_STATIC(sizeof(dcd_event_t) <= 12, "size is not correct"); + +//--------------------------------------------------------------------+ +// Memory API +//--------------------------------------------------------------------+ + +// clean/flush data cache: write cache -> memory. +// Required before an DMA TX transfer to make sure data is in memory +bool dcd_dcache_clean(const void* addr, uint32_t data_size); + +// invalidate data cache: mark cache as invalid, next read will read from memory +// Required BOTH before and after an DMA RX transfer +bool dcd_dcache_invalidate(const void* addr, uint32_t data_size); + +// clean and invalidate data cache +// Required before an DMA transfer where memory is both read/write by DMA +bool dcd_dcache_clean_invalidate(const void* addr, uint32_t data_size); + +//--------------------------------------------------------------------+ +// Controller API +//--------------------------------------------------------------------+ + +// optional dcd configuration, called by tud_configure() +bool dcd_configure(uint8_t rhport, uint32_t cfg_id, const void* cfg_param); + +// Initialize controller to device mode +bool dcd_init(uint8_t rhport, const tusb_rhport_init_t* rh_init); + +// Deinitialize controller, unset device mode. +bool dcd_deinit(uint8_t rhport); + +// Interrupt Handler +void dcd_int_handler(uint8_t rhport); + +// Enable device interrupt +void dcd_int_enable (uint8_t rhport); + +// Disable device interrupt +void dcd_int_disable(uint8_t rhport); + +// Receive Set Address request, mcu port must also include status IN response +void dcd_set_address(uint8_t rhport, uint8_t dev_addr); + +// Wake up host +void dcd_remote_wakeup(uint8_t rhport); + +// Connect by enabling internal pull-up resistor on D+/D- +void dcd_connect(uint8_t rhport); + +// Disconnect by disabling internal pull-up resistor on D+/D- +void dcd_disconnect(uint8_t rhport); + +// Enable/Disable Start-of-frame interrupt. Default is disabled +void dcd_sof_enable(uint8_t rhport, bool en); + +#if CFG_TUD_TEST_MODE +// Put device into a test mode (needs power cycle to quit) +void dcd_enter_test_mode(uint8_t rhport, tusb_feature_test_mode_t test_selector); +#endif +//--------------------------------------------------------------------+ +// Endpoint API +//--------------------------------------------------------------------+ + +// Invoked when a control transfer's status stage is complete. +// May help DCD to prepare for next control transfer, this API is optional. +void dcd_edpt0_status_complete(uint8_t rhport, tusb_control_request_t const * request); + +// Configure endpoint's registers according to descriptor +bool dcd_edpt_open (uint8_t rhport, tusb_desc_endpoint_t const * desc_ep); + +// Close all non-control endpoints, cancel all pending transfers if any. +// Invoked when switching from a non-zero Configuration by SET_CONFIGURE therefore +// required for multiple configuration support. +void dcd_edpt_close_all (uint8_t rhport); + +// Submit a transfer, When complete dcd_event_xfer_complete() is invoked to notify the stack +bool dcd_edpt_xfer (uint8_t rhport, uint8_t ep_addr, uint8_t * buffer, uint16_t total_bytes, bool is_isr); + +// Submit an transfer using fifo, When complete dcd_event_xfer_complete() is invoked to notify the stack +// This API is optional, may be useful for register-based for transferring data. +bool dcd_edpt_xfer_fifo (uint8_t rhport, uint8_t ep_addr, tu_fifo_t * ff, uint16_t total_bytes, bool is_isr); + +// Stall endpoint, any queuing transfer should be removed from endpoint +void dcd_edpt_stall (uint8_t rhport, uint8_t ep_addr); + +// clear stall, data toggle is also reset to DATA0 +// This API never calls with control endpoints, since it is auto cleared when receiving setup packet +void dcd_edpt_clear_stall (uint8_t rhport, uint8_t ep_addr); + +#ifdef TUP_DCD_EDPT_CLOSE_API +// Close an endpoint. +void dcd_edpt_close(uint8_t rhport, uint8_t ep_addr); + +#else + +// Allocate packet buffer used by ISO endpoints +// Some MCU need manual packet buffer allocation, we allocate the largest size to avoid clustering +bool dcd_edpt_iso_alloc(uint8_t rhport, uint8_t ep_addr, uint16_t largest_packet_size); + +// Configure and enable an ISO endpoint according to descriptor +bool dcd_edpt_iso_activate(uint8_t rhport, tusb_desc_endpoint_t const * desc_ep); + +#endif + +//--------------------------------------------------------------------+ +// Event API (implemented by stack) +//--------------------------------------------------------------------+ + +// Called by DCD to notify device stack +extern void dcd_event_handler(dcd_event_t const * event, bool in_isr); + +// helper to send bus signal event +TU_ATTR_ALWAYS_INLINE static inline void dcd_event_bus_signal (uint8_t rhport, dcd_eventid_t eid, bool in_isr) { + dcd_event_t event; + event.rhport = rhport; + event.event_id = eid; + dcd_event_handler(&event, in_isr); +} + +// helper to send bus reset event +TU_ATTR_ALWAYS_INLINE static inline void dcd_event_bus_reset (uint8_t rhport, tusb_speed_t speed, bool in_isr) { + dcd_event_t event; + event.rhport = rhport; + event.event_id = DCD_EVENT_BUS_RESET; + event.bus_reset.speed = speed; + dcd_event_handler(&event, in_isr); +} + +// helper to send setup received +TU_ATTR_ALWAYS_INLINE static inline void dcd_event_setup_received(uint8_t rhport, uint8_t const * setup, bool in_isr) { + dcd_event_t event; + event.rhport = rhport; + event.event_id = DCD_EVENT_SETUP_RECEIVED; + (void) memcpy(&event.setup_received, setup, sizeof(tusb_control_request_t)); + // USB wire format is little-endian. Convert multi-byte fields to host byte order + // so the stack always sees correct values regardless of CPU endianness. + event.setup_received.wValue = tu_le16toh(event.setup_received.wValue); + event.setup_received.wIndex = tu_le16toh(event.setup_received.wIndex); + event.setup_received.wLength = tu_le16toh(event.setup_received.wLength); + dcd_event_handler(&event, in_isr); +} + +// helper to send transfer complete event +TU_ATTR_ALWAYS_INLINE static inline void dcd_event_xfer_complete (uint8_t rhport, uint8_t ep_addr, uint32_t xferred_bytes, uint8_t result, bool in_isr) { + dcd_event_t event; + event.rhport = rhport; + event.event_id = DCD_EVENT_XFER_COMPLETE; + event.xfer_complete.ep_addr = ep_addr; + event.xfer_complete.len = xferred_bytes; + event.xfer_complete.result = result; + dcd_event_handler(&event, in_isr); +} + +TU_ATTR_ALWAYS_INLINE static inline void dcd_event_sof(uint8_t rhport, uint32_t frame_count, bool in_isr) { + dcd_event_t event; + event.rhport = rhport; + event.event_id = DCD_EVENT_SOF; + event.sof.frame_count = frame_count; + dcd_event_handler(&event, in_isr); +} + +#ifdef __cplusplus + } +#endif + +#endif diff --git a/libraries/Adafruit_TinyUSB_Arduino/src/device/usbd.c b/libraries/Adafruit_TinyUSB_Arduino/src/device/usbd.c new file mode 100644 index 0000000..0e58f70 --- /dev/null +++ b/libraries/Adafruit_TinyUSB_Arduino/src/device/usbd.c @@ -0,0 +1,1738 @@ +/* + * 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. + * + * This file is part of the TinyUSB stack. + */ + +#include "tusb_option.h" + +#if CFG_TUD_ENABLED + +#include "device/dcd.h" +#include "tusb.h" +#include "common/tusb_private.h" + +#include "device/usbd.h" +#include "device/usbd_pvt.h" + +//--------------------------------------------------------------------+ +// USBD Configuration +//--------------------------------------------------------------------+ +#ifndef CFG_TUD_TASK_QUEUE_SZ + #define CFG_TUD_TASK_QUEUE_SZ 16 +#endif + +//--------------------------------------------------------------------+ +// Weak stubs: invoked if no strong implementation is available +//--------------------------------------------------------------------+ +TU_ATTR_WEAK void tud_event_hook_cb(uint8_t rhport, uint32_t eventid, bool in_isr) { + (void) rhport; (void) eventid; (void) in_isr; +} + +TU_ATTR_WEAK void tud_sof_cb(uint32_t frame_count) { + (void) frame_count; +} + +TU_ATTR_WEAK uint8_t const* tud_descriptor_bos_cb(void) { + return NULL; +} + +TU_ATTR_WEAK uint8_t const* tud_descriptor_device_qualifier_cb(void) { + return NULL; +} + +TU_ATTR_WEAK uint8_t const* tud_descriptor_other_speed_configuration_cb(uint8_t index) { + (void) index; + return NULL; +} + +TU_ATTR_WEAK void tud_mount_cb(void) { +} + +TU_ATTR_WEAK void tud_umount_cb(void) { +} + +TU_ATTR_WEAK void tud_suspend_cb(bool remote_wakeup_en) { + (void) remote_wakeup_en; +} + +TU_ATTR_WEAK void tud_resume_cb(void) { +} + +TU_ATTR_WEAK bool tud_vendor_control_xfer_cb(uint8_t rhport, uint8_t stage, tusb_control_request_t const* request) { + (void) rhport; (void) stage; (void) request; + return false; +} + +TU_ATTR_WEAK bool dcd_deinit(uint8_t rhport) { + (void) rhport; + return false; +} + +TU_ATTR_WEAK void dcd_connect(uint8_t rhport) { + (void) rhport; +} + +TU_ATTR_WEAK void dcd_disconnect(uint8_t rhport) { + (void) rhport; +} + +TU_ATTR_WEAK bool dcd_dcache_clean(const void* addr, uint32_t data_size) { + (void) addr; (void) data_size; + return true; +} + +TU_ATTR_WEAK bool dcd_dcache_invalidate(const void* addr, uint32_t data_size) { + (void) addr; (void) data_size; + return true; +} + +TU_ATTR_WEAK bool dcd_dcache_clean_invalidate(const void* addr, uint32_t data_size) { + (void) addr; (void) data_size; + return true; +} + +//--------------------------------------------------------------------+ +// Device Data +//--------------------------------------------------------------------+ + +// Per-control-transfer state: populated at process_setup_received() entry, +// consumed asynchronously by usbd_control_xfer_cb() when the EP0 transfer completes. +typedef struct { + tusb_control_request_t request; + uint8_t* buffer; + uint16_t data_len; + uint16_t total_xferred; + usbd_control_xfer_cb_t complete_cb; +} usbd_control_xfer_t; + +typedef struct { + usbd_control_xfer_t ctrl_xfer; + + // Note: these may share an enum state + volatile uint8_t connected; + volatile uint8_t addressed; + volatile uint8_t suspended; + + union { + struct TU_ATTR_PACKED { + uint8_t self_powered : 1; // configuration descriptor's attribute; + uint8_t remote_wakeup_en : 1; // enable/disable by host + }; + uint8_t dev_state_bm; + }; + + uint8_t cfg_num; // current active configuration (0x00 is not configured) + uint8_t speed; + volatile uint8_t sof_consumer; + + uint8_t itf2drv[CFG_TUD_INTERFACE_MAX]; // map interface number to driver (0xff is invalid) + uint8_t ep2drv[CFG_TUD_ENDPPOINT_MAX][2]; // map endpoint to driver ( 0xff is invalid ), can use only 4-bit each + + volatile uint8_t ep_status[CFG_TUD_ENDPPOINT_MAX][2]; +} usbd_device_t; + +static usbd_device_t _usbd_dev; +static volatile uint8_t _usbd_queued_setup; + +CFG_TUD_MEM_SECTION static struct { + TUD_EPBUF_DEF(buf, CFG_TUD_ENDPOINT0_BUFSIZE); +} _ctrl_epbuf; + +//--------------------------------------------------------------------+ +// Class Driver +//--------------------------------------------------------------------+ + #if CFG_TUSB_DEBUG >= CFG_TUD_LOG_LEVEL + #define DRIVER_NAME(_name) _name + #else + #define DRIVER_NAME(_name) NULL + #endif + +// Built-in class drivers +static const usbd_class_driver_t _usbd_driver[] = { + #if CFG_TUD_CDC + { + .name = DRIVER_NAME("CDC"), + .init = cdcd_init, + .deinit = cdcd_deinit, + .reset = cdcd_reset, + .open = cdcd_open, + .control_xfer_cb = cdcd_control_xfer_cb, + .xfer_cb = cdcd_xfer_cb, + .xfer_isr = NULL, + .sof = NULL + }, + #endif + + #if CFG_TUD_MSC + { + .name = DRIVER_NAME("MSC"), + .init = mscd_init, + .deinit = NULL, + .reset = mscd_reset, + .open = mscd_open, + .control_xfer_cb = mscd_control_xfer_cb, + .xfer_cb = mscd_xfer_cb, + .xfer_isr = NULL, + .sof = NULL + }, + #endif + + #if CFG_TUD_HID + { + .name = DRIVER_NAME("HID"), + .init = hidd_init, + .deinit = hidd_deinit, + .reset = hidd_reset, + .open = hidd_open, + .control_xfer_cb = hidd_control_xfer_cb, + .xfer_cb = hidd_xfer_cb, + .xfer_isr = NULL, + .sof = NULL + }, + #endif + + #if CFG_TUD_AUDIO + { + .name = DRIVER_NAME("AUDIO"), + .init = audiod_init, + .deinit = audiod_deinit, + .reset = audiod_reset, + .open = audiod_open, + .control_xfer_cb = audiod_control_xfer_cb, + .xfer_cb = audiod_xfer_cb, + .xfer_isr = audiod_xfer_isr, + .sof = audiod_sof_isr + }, + #endif + + #if CFG_TUD_VIDEO + { + .name = DRIVER_NAME("VIDEO"), + .init = videod_init, + .deinit = videod_deinit, + .reset = videod_reset, + .open = videod_open, + .control_xfer_cb = videod_control_xfer_cb, + .xfer_cb = videod_xfer_cb, + .xfer_isr = NULL, + .sof = NULL + }, + #endif + + #if CFG_TUD_MIDI + { + .name = DRIVER_NAME("MIDI"), + .init = midid_init, + .deinit = midid_deinit, + .open = midid_open, + .reset = midid_reset, + .control_xfer_cb = midid_control_xfer_cb, + .xfer_cb = midid_xfer_cb, + .xfer_isr = NULL, + .sof = NULL + }, + #endif + + #if CFG_TUD_VENDOR + { + .name = DRIVER_NAME("VENDOR"), + .init = vendord_init, + .deinit = vendord_deinit, + .reset = vendord_reset, + .open = vendord_open, + .control_xfer_cb = tud_vendor_control_xfer_cb, + .xfer_cb = vendord_xfer_cb, + .xfer_isr = NULL, + .sof = NULL + }, + #endif + + #if CFG_TUD_USBTMC + { + .name = DRIVER_NAME("TMC"), + .init = usbtmcd_init_cb, + .deinit = usbtmcd_deinit, + .reset = usbtmcd_reset_cb, + .open = usbtmcd_open_cb, + .control_xfer_cb = usbtmcd_control_xfer_cb, + .xfer_cb = usbtmcd_xfer_cb, + .xfer_isr = NULL, + .sof = NULL + }, + #endif + + #if CFG_TUD_DFU_RUNTIME + { + .name = DRIVER_NAME("DFU-RUNTIME"), + .init = dfu_rtd_init, + .deinit = dfu_rtd_deinit, + .reset = dfu_rtd_reset, + .open = dfu_rtd_open, + .control_xfer_cb = dfu_rtd_control_xfer_cb, + .xfer_cb = NULL, + .xfer_isr = NULL, + .sof = NULL + }, + #endif + + #if CFG_TUD_DFU + { + .name = DRIVER_NAME("DFU"), + .init = dfu_moded_init, + .deinit = dfu_moded_deinit, + .reset = dfu_moded_reset, + .open = dfu_moded_open, + .control_xfer_cb = dfu_moded_control_xfer_cb, + .xfer_cb = NULL, + .xfer_isr = NULL, + .sof = NULL + }, + #endif + + #if CFG_TUD_ECM_RNDIS || CFG_TUD_NCM + { + .name = DRIVER_NAME("NET"), + .init = netd_init, + .deinit = netd_deinit, + .reset = netd_reset, + .open = netd_open, + .control_xfer_cb = netd_control_xfer_cb, + .xfer_cb = netd_xfer_cb, + .xfer_isr = NULL, + .sof = NULL, + }, + #endif + + #if CFG_TUD_BTH + { + .name = DRIVER_NAME("BTH"), + .init = btd_init, + .deinit = btd_deinit, + .reset = btd_reset, + .open = btd_open, + .control_xfer_cb = btd_control_xfer_cb, + .xfer_cb = btd_xfer_cb, + .xfer_isr = NULL, + .sof = NULL + }, + #endif + + #if CFG_TUD_MTP + { + .name = DRIVER_NAME("MTP"), + .init = mtpd_init, + .deinit = mtpd_deinit, + .reset = mtpd_reset, + .open = mtpd_open, + .control_xfer_cb = mtpd_control_xfer_cb, + .xfer_cb = mtpd_xfer_cb, + .xfer_isr = NULL, + .sof = NULL + }, + #endif + + #if CFG_TUD_PRINTER + { + .name = DRIVER_NAME("PRINTER"), + .init = printerd_init, + .deinit = printerd_deinit, + .reset = printerd_reset, + .open = printerd_open, + .control_xfer_cb = printerd_control_xfer_cb, + .xfer_cb = printerd_xfer_cb, + .sof = NULL + }, + #endif +}; + +enum { BUILTIN_DRIVER_COUNT = TU_ARRAY_SIZE(_usbd_driver) }; + +// Additional class drivers implemented by application +static const usbd_class_driver_t *_app_driver = NULL; +static uint8_t _app_driver_count = 0; + +#define TOTAL_DRIVER_COUNT ((uint8_t) (_app_driver_count + BUILTIN_DRIVER_COUNT)) + +// virtually joins built-in and application drivers together. +// Application is positioned first to allow overwriting built-in ones. +TU_ATTR_ALWAYS_INLINE static inline usbd_class_driver_t const * get_driver(uint8_t drvid) { + usbd_class_driver_t const *driver = NULL; + if (drvid < _app_driver_count) { + // Application drivers + driver = &_app_driver[drvid]; + } else{ + drvid -= _app_driver_count; + if (drvid < BUILTIN_DRIVER_COUNT) { + driver = &_usbd_driver[drvid]; + } + } + + return driver; +} + +//--------------------------------------------------------------------+ +// DCD Event +//--------------------------------------------------------------------+ +enum { + RHPORT_INVALID = 0xFFu +}; +static uint8_t _usbd_rhport = RHPORT_INVALID; + +static OSAL_SPINLOCK_DEF(_usbd_spin, usbd_int_set); + +// Event queue: usbd_int_set() is used as mutex in OS NONE config +OSAL_QUEUE_DEF(usbd_int_set, _usbd_qdef, CFG_TUD_TASK_QUEUE_SZ, dcd_event_t); +static osal_queue_t _usbd_q; + +// Mutex for claiming endpoint +#if OSAL_MUTEX_REQUIRED + static osal_mutex_def_t _ubsd_mutexdef; + static osal_mutex_t _usbd_mutex; +#else + #define _usbd_mutex NULL +#endif + +TU_ATTR_ALWAYS_INLINE static inline bool queue_event(dcd_event_t const * event, bool in_isr) { + TU_ASSERT(osal_queue_send(_usbd_q, event, in_isr)); + tud_event_hook_cb(event->rhport, event->event_id, in_isr); + return true; +} + +//--------------------------------------------------------------------+ +// Prototypes +//--------------------------------------------------------------------+ +static bool usbd_control_xfer_cb(uint8_t rhport, uint8_t ep_addr, xfer_result_t result, uint32_t xferred_bytes); +static bool process_setup_received(uint8_t rhport, tusb_control_request_t const * p_request); +static bool process_set_config(uint8_t rhport, uint8_t cfg_num); +static bool process_get_descriptor(uint8_t rhport, tusb_control_request_t const * p_request); + +#if CFG_TUD_TEST_MODE +static bool process_test_mode_cb(uint8_t rhport, uint8_t stage, tusb_control_request_t const * request) { + TU_VERIFY(CONTROL_STAGE_ACK == stage); + uint8_t const selector = tu_u16_high(request->wIndex); + TU_LOG_USBD(" Enter Test Mode (test selector index: %d)\r\n", selector); + dcd_enter_test_mode(rhport, (tusb_feature_test_mode_t) selector); + return true; +} +#endif + +//--------------------------------------------------------------------+ +// Weak stubs: invoked if no strong implementation is available +//--------------------------------------------------------------------+ +TU_ATTR_WEAK usbd_class_driver_t const* usbd_app_driver_get_cb(uint8_t* driver_count) { + *driver_count = 0; + return NULL; +} + +TU_ATTR_WEAK bool dcd_edpt_xfer_fifo(uint8_t rhport, uint8_t ep_addr, tu_fifo_t * ff, uint16_t total_bytes, bool is_isr) { + (void) rhport; (void) ep_addr; (void) ff; (void) total_bytes; (void) is_isr; + return false; +} + +TU_ATTR_WEAK bool dcd_configure(uint8_t rhport, uint32_t cfg_id, const void* cfg_param) { + (void) rhport; (void) cfg_id; (void) cfg_param; + return false; +} + +//--------------------------------------------------------------------+ +// Debug +//--------------------------------------------------------------------+ +#if CFG_TUSB_DEBUG >= CFG_TUD_LOG_LEVEL +static char const *const _usbd_event_str[DCD_EVENT_COUNT] = { + "Invalid", + "Bus Reset", + "Unplugged", + "SOF", + "Suspend", + "Resume", + "Setup Received", + "Xfer Complete", + "Func Call" +}; + +#endif + +//--------------------------------------------------------------------+ +// Application API +//--------------------------------------------------------------------+ +tusb_speed_t tud_speed_get(void) { + return (tusb_speed_t) _usbd_dev.speed; +} + +bool tud_connected(void) { + return _usbd_dev.connected; +} + +bool tud_mounted(void) { + return _usbd_dev.cfg_num ? true : false; +} + +bool tud_suspended(void) { + return _usbd_dev.suspended; +} + +bool tud_remote_wakeup(void) { + // only wake up host if this feature is enabled and we are suspended + TU_VERIFY(_usbd_dev.suspended && _usbd_dev.remote_wakeup_en); + dcd_remote_wakeup(_usbd_rhport); + return true; +} + +bool tud_disconnect(void) { + dcd_disconnect(_usbd_rhport); + return true; +} + +bool tud_connect(void) { + dcd_connect(_usbd_rhport); + return true; +} + +void tud_sof_cb_enable(bool en) { + usbd_sof_enable(_usbd_rhport, SOF_CONSUMER_USER, en); +} + +bool tud_inited(void) { + return _usbd_rhport != RHPORT_INVALID; +} + +bool tud_configure(uint8_t rhport, uint32_t cfg_id, const void* cfg_param) { + return dcd_configure(rhport, cfg_id, cfg_param); +} + +bool tud_rhport_init(uint8_t rhport, const tusb_rhport_init_t* rh_init) { + if (tud_inited()) { + return true; // skip if already initialized + } + TU_ASSERT(rh_init); + #if CFG_TUSB_DEBUG >= CFG_TUD_LOG_LEVEL + char const* speed_str = 0; + switch (rh_init->speed) { + case TUSB_SPEED_HIGH: + speed_str = "High"; + break; + case TUSB_SPEED_FULL: + speed_str = "Full"; + break; + case TUSB_SPEED_LOW: + speed_str = "Low"; + break; + case TUSB_SPEED_AUTO: + speed_str = "Auto"; + break; + default: + break; + } + TU_LOG_USBD("USBD init on controller %u, speed = %s\r\n", rhport, speed_str); + TU_LOG_INT(CFG_TUD_LOG_LEVEL, sizeof(usbd_device_t)); + TU_LOG_INT(CFG_TUD_LOG_LEVEL, sizeof(dcd_event_t)); + TU_LOG_INT(CFG_TUD_LOG_LEVEL, sizeof(tu_fifo_t)); + TU_LOG_INT(CFG_TUD_LOG_LEVEL, sizeof(tu_edpt_stream_t)); +#endif + + tu_varclr(&_usbd_dev); + _usbd_queued_setup = 0; + + osal_spin_init(&_usbd_spin); + +#if OSAL_MUTEX_REQUIRED + // Init device mutex + _usbd_mutex = osal_mutex_create(&_ubsd_mutexdef); + TU_ASSERT(_usbd_mutex); +#endif + + // Init device queue & task + _usbd_q = osal_queue_create(&_usbd_qdef); + TU_ASSERT(_usbd_q); + + // Get application driver if available + _app_driver = usbd_app_driver_get_cb(&_app_driver_count); + TU_ASSERT(_app_driver_count + BUILTIN_DRIVER_COUNT <= UINT8_MAX); + + // Init class drivers + for (uint8_t i = 0; i < TOTAL_DRIVER_COUNT; i++) { + usbd_class_driver_t const* driver = get_driver(i); + TU_ASSERT(driver && driver->init); + TU_LOG_USBD("%s init\r\n", driver->name); + driver->init(); + } + + _usbd_rhport = rhport; + + // Init device controller driver + TU_ASSERT(dcd_init(rhport, rh_init)); + dcd_int_enable(rhport); + + return true; +} + +bool tud_deinit(uint8_t rhport) { + if (!tud_inited()) { + return true; // skip if not initialized + } + + TU_LOG_USBD("USBD deinit on controller %u\r\n", rhport); + + const uint8_t cfg_num = _usbd_dev.cfg_num; + + // Deinit device controller driver + dcd_int_disable(rhport); + dcd_disconnect(rhport); + TU_ASSERT(dcd_deinit(rhport)); + + // Deinit class drivers + for (uint8_t i = 0; i < TOTAL_DRIVER_COUNT; i++) { + usbd_class_driver_t const* driver = get_driver(i); + if(driver && driver->deinit) { + TU_LOG_USBD("%s deinit\r\n", driver->name); + driver->deinit(); + } + } + + tu_varclr(&_usbd_dev); // Clear device data + + // Deinit device queue & task + osal_queue_delete(_usbd_q); + _usbd_q = NULL; + +#if OSAL_MUTEX_REQUIRED + // TODO make sure there is no task waiting on this mutex + osal_mutex_delete(_usbd_mutex); + _usbd_mutex = NULL; +#endif + + _usbd_rhport = RHPORT_INVALID; + + if (cfg_num > 0) { + tud_umount_cb(); + } + + return true; +} + +static void configuration_reset(uint8_t rhport) { + for (uint8_t i = 0; i < TOTAL_DRIVER_COUNT; i++) { + usbd_class_driver_t const* driver = get_driver(i); + TU_ASSERT(driver,); + driver->reset(rhport); + } + + tu_varclr(&_usbd_dev); + (void)memset(_usbd_dev.itf2drv, TUSB_INDEX_INVALID_8, sizeof(_usbd_dev.itf2drv)); // invalid mapping + (void)memset(_usbd_dev.ep2drv, TUSB_INDEX_INVALID_8, sizeof(_usbd_dev.ep2drv)); // invalid mapping +} + +static void usbd_reset(uint8_t rhport) { + configuration_reset(rhport); +} + +bool tud_task_event_ready(void) { + TU_VERIFY(tud_inited()); // Skip if stack is not initialized + return !osal_queue_empty(_usbd_q); +} + +//--------------------------------------------------------------------+ +// USBD Task +//--------------------------------------------------------------------+ +/* USB Device Driver task + * This top level thread manages all device controller event and delegates events to class-specific drivers. + * This should be called periodically within the mainloop or rtos thread. + * + int main(void) { + application_init(); + tusb_init(0, TUSB_ROLE_DEVICE); + + while(1) { // the mainloop + application_code(); + tud_task(); // tinyusb device task + } + } + */ +void tud_task_ext(uint32_t timeout_ms, bool in_isr) { + (void) in_isr; // not implemented yet + + // Skip if stack is not initialized + if (!tud_inited()) { + return; + } + + // Loop until there are no more events in the queue or CFG_TUD_TASK_EVENTS_PER_RUN is reached + for (unsigned epr = 0;; epr++) { +#if CFG_TUD_TASK_EVENTS_PER_RUN > 0 + if (epr >= CFG_TUD_TASK_EVENTS_PER_RUN) { + TU_LOG_USBD("USBD event limit (" TU_XSTRING(CFG_TUD_TASK_EVENTS_PER_RUN) ") reached\r\n"); + break; + } +#endif + dcd_event_t event; + if (!osal_queue_receive(_usbd_q, &event, timeout_ms)) { + return; + } + +#if CFG_TUSB_DEBUG >= CFG_TUD_LOG_LEVEL + if (event.event_id == DCD_EVENT_SETUP_RECEIVED) { + TU_LOG_USBD("\r\n"); // extra line for setup + } + TU_LOG_USBD("USBD %s ", event.event_id < DCD_EVENT_COUNT ? _usbd_event_str[event.event_id] : "CORRUPTED"); +#endif + + switch (event.event_id) { + case DCD_EVENT_BUS_RESET: + TU_LOG_USBD(": %s Speed\r\n", tu_str_speed[event.bus_reset.speed]); + usbd_reset(event.rhport); + _usbd_dev.speed = event.bus_reset.speed; + break; + + case DCD_EVENT_UNPLUGGED: + TU_LOG_USBD("\r\n"); + usbd_reset(event.rhport); + tud_umount_cb(); + break; + + case DCD_EVENT_SETUP_RECEIVED: + if (_usbd_queued_setup == 0) { + break; + } + _usbd_queued_setup--; + TU_LOG_BUF(CFG_TUD_LOG_LEVEL, &event.setup_received, 8); + if (_usbd_queued_setup != 0) { + TU_LOG_USBD(" Skipped since there is other SETUP in queue\r\n"); + break; + } + + // Mark as connected after receiving 1st setup packet. + // But it is easier to set it every time instead of wasting time to check then set + _usbd_dev.connected = 1; + + // reset ep state + _usbd_dev.ep_status[0][TUSB_DIR_OUT] = 0; + _usbd_dev.ep_status[0][TUSB_DIR_IN] = 0; + + // Process control request + if (!process_setup_received(event.rhport, &event.setup_received)) { + TU_LOG_USBD(" Stall EP0\r\n"); + // Failed -> stall both control endpoint IN and OUT + dcd_edpt_stall(event.rhport, TU_EP0_OUT); + dcd_edpt_stall(event.rhport, TU_EP0_IN); + } + break; + + case DCD_EVENT_XFER_COMPLETE: { + // Invoke the class callback associated with the endpoint address + uint8_t const ep_addr = event.xfer_complete.ep_addr; + uint8_t const epnum = tu_edpt_number(ep_addr); + uint8_t const ep_dir = tu_edpt_dir(ep_addr); + + TU_LOG_USBD("on EP %02X with %u bytes\r\n", ep_addr, (unsigned int) event.xfer_complete.len); + + // Clear busy + claimed + _usbd_dev.ep_status[epnum][ep_dir] &= (uint8_t) ~(TU_EDPT_STATE_BUSY | TU_EDPT_STATE_CLAIMED); + + if (0 == epnum) { + usbd_control_xfer_cb(event.rhport, ep_addr, (xfer_result_t) event.xfer_complete.result, event.xfer_complete.len); + } else { + usbd_class_driver_t const* driver = get_driver(_usbd_dev.ep2drv[epnum][ep_dir]); + TU_ASSERT(driver,); + + TU_LOG_USBD(" %s xfer callback\r\n", driver->name); + driver->xfer_cb(event.rhport, ep_addr, (xfer_result_t) event.xfer_complete.result, event.xfer_complete.len); + } + break; + } + + case DCD_EVENT_SUSPEND: + // NOTE: When plugging/unplugging device, the D+/D- state are unstable and + // can accidentally meet the SUSPEND condition ( Bus Idle for 3ms ), which result in a series of event + // e.g suspend -> resume -> unplug/plug. Skip suspend/resume if not connected + if (_usbd_dev.connected) { + TU_LOG_USBD(": Remote Wakeup = %u\r\n", _usbd_dev.remote_wakeup_en); + tud_suspend_cb(_usbd_dev.remote_wakeup_en); + } else { + TU_LOG_USBD(" Skipped\r\n"); + } + break; + + case DCD_EVENT_RESUME: + if (_usbd_dev.connected) { + TU_LOG_USBD("\r\n"); + tud_resume_cb(); + } else { + TU_LOG_USBD(" Skipped\r\n"); + } + break; + + case USBD_EVENT_FUNC_CALL: + TU_LOG_USBD("\r\n"); + if (event.func_call.func != NULL) { + event.func_call.func(event.func_call.param); + } + break; + + case DCD_EVENT_SOF: + if (tu_bit_test(_usbd_dev.sof_consumer, SOF_CONSUMER_USER)) { + TU_LOG_USBD("\r\n"); + tud_sof_cb(event.sof.frame_count); + } + break; + + default: + TU_BREAKPOINT(); + break; + } + + // allow to exit tud_task() if there is no event in the next run + timeout_ms = 0; + } +} + +//--------------------------------------------------------------------+ +// Control Endpoint +//--------------------------------------------------------------------+ + +// Weak hook: invoked when the control transfer's status stage completes +TU_ATTR_WEAK void dcd_edpt0_status_complete(uint8_t rhport, const tusb_control_request_t* request) { + (void) rhport; + (void) request; +} + +uint8_t* usbd_get_ctrl_buf(void) { + return _ctrl_epbuf.buf; +} + +// Endpoint used for the Status stage of a control transfer. +// Per USB 2.0 §9.3.1, when wLength == 0 the Direction bit is ignored and the Status +// stage is always IN. Otherwise the Status stage is opposite of the Data stage direction. +TU_ATTR_ALWAYS_INLINE static inline uint8_t status_stage_ep(const tusb_control_request_t* request) { + return (request->wLength != 0 && request->bmRequestType_bit.direction) ? TU_EP0_OUT : TU_EP0_IN; +} + +// Queue ZLP status transaction +TU_ATTR_ALWAYS_INLINE static inline bool status_stage_xact(uint8_t rhport, uint8_t ep_status) { + return usbd_edpt_xfer(rhport, ep_status, NULL, 0, false); +} + +// Queue a transaction in Data Stage. Each transaction has up to Endpoint0's max +// packet size. This function can also transfer a zero-length packet. +static bool data_stage_xact(uint8_t rhport) { + usbd_control_xfer_t* const ctrl_xfer = &_usbd_dev.ctrl_xfer; + const uint16_t xact_len = tu_min16(ctrl_xfer->data_len - ctrl_xfer->total_xferred, CFG_TUD_ENDPOINT0_BUFSIZE); + uint8_t ep_addr = TU_EP0_OUT; + + if (ctrl_xfer->request.bmRequestType_bit.direction == TUSB_DIR_IN) { + ep_addr = TU_EP0_IN; + if (0u != xact_len && ctrl_xfer->buffer != _ctrl_epbuf.buf) { + TU_VERIFY(0 == tu_memcpy_s(_ctrl_epbuf.buf, CFG_TUD_ENDPOINT0_BUFSIZE, ctrl_xfer->buffer, xact_len)); + } + } + + return usbd_edpt_xfer(rhport, ep_addr, xact_len ? _ctrl_epbuf.buf : NULL, xact_len, false); +} + +// Status phase +bool tud_control_status(uint8_t rhport, const tusb_control_request_t* request) { + // _usbd_dev.ctrl_xfer fields are pre-initialized at process_setup_received entry + (void) request; + return status_stage_xact(rhport, status_stage_ep(&_usbd_dev.ctrl_xfer.request)); +} + +// Transmit data to/from the control endpoint. If wLength is zero, a status packet is sent instead. +bool tud_control_xfer(uint8_t rhport, const tusb_control_request_t* request, void* buffer, uint16_t len) { + // _usbd_dev.ctrl_xfer.request and reset fields are pre-initialized at process_setup_received entry + (void) request; + usbd_control_xfer_t* const ctrl_xfer = &_usbd_dev.ctrl_xfer; + ctrl_xfer->buffer = (uint8_t*) buffer; + ctrl_xfer->data_len = tu_min16(len, ctrl_xfer->request.wLength); + + if (ctrl_xfer->request.wLength > 0U) { + if (ctrl_xfer->data_len > 0U) { + TU_ASSERT(buffer); + } + TU_ASSERT(data_stage_xact(rhport)); + } else { + // wLength == 0: Status stage is always IN per USB 2.0 §9.3.1 + TU_ASSERT(status_stage_xact(rhport, TU_EP0_IN)); + } + + return true; +} + +// Callback when a transaction completes on the DATA stage or Status stage of EP0 +static bool usbd_control_xfer_cb(uint8_t rhport, uint8_t ep_addr, xfer_result_t result, uint32_t xferred_bytes) { + (void) result; + usbd_control_xfer_t* const ctrl_xfer = &_usbd_dev.ctrl_xfer; + + // Status Stage complete: ep_addr matches the resolved Status stage endpoint + uint8_t const ep_status = status_stage_ep(&ctrl_xfer->request); + if (ep_addr == ep_status) { + TU_ASSERT(0 == xferred_bytes); + + // invoke optional dcd hook if available + dcd_edpt0_status_complete(rhport, &ctrl_xfer->request); + + if (NULL != ctrl_xfer->complete_cb) { + ctrl_xfer->complete_cb(rhport, CONTROL_STAGE_ACK, &ctrl_xfer->request); + } + + return true; + } + + // Data stage progress + if (ctrl_xfer->request.bmRequestType_bit.direction == TUSB_DIR_OUT) { + TU_VERIFY(ctrl_xfer->buffer); + if (ctrl_xfer->buffer != _ctrl_epbuf.buf) { + memcpy(ctrl_xfer->buffer, _ctrl_epbuf.buf, xferred_bytes); + } + TU_LOG_MEM(CFG_TUD_LOG_LEVEL, ctrl_xfer->buffer, xferred_bytes, 2); + } + + ctrl_xfer->total_xferred += (uint16_t) xferred_bytes; + ctrl_xfer->buffer += xferred_bytes; + + // Data Stage complete when wLength reached or short packet (incl. ZLP) seen + if ((ctrl_xfer->request.wLength == ctrl_xfer->total_xferred) || + (xferred_bytes < CFG_TUD_ENDPOINT0_BUFSIZE)) { + bool is_ok = true; + + if (NULL != ctrl_xfer->complete_cb) { + // Callback can still stall control in status phase, e.g. OUT data doesn't make sense + is_ok = ctrl_xfer->complete_cb(rhport, CONTROL_STAGE_DATA, &ctrl_xfer->request); + } + + if (is_ok) { + TU_ASSERT(status_stage_xact(rhport, ep_status)); + } else { + // Stall both IN and OUT control endpoint + dcd_edpt_stall(rhport, TU_EP0_OUT); + dcd_edpt_stall(rhport, TU_EP0_IN); + } + } else { + // More data to transfer + TU_ASSERT(data_stage_xact(rhport)); + } + + return true; +} + +//--------------------------------------------------------------------+ +// Control Request Parser & Handling +//--------------------------------------------------------------------+ + +// Helper to invoke class driver control request handler +static bool invoke_class_control(uint8_t rhport, usbd_class_driver_t const * driver, tusb_control_request_t const * request) { + _usbd_dev.ctrl_xfer.complete_cb = driver->control_xfer_cb; + TU_LOG_USBD(" %s control request\r\n", driver->name); + return driver->control_xfer_cb(rhport, CONTROL_STAGE_SETUP, request); +} + +// Process a standard request to the device recipient. +static bool process_std_device_request(uint8_t rhport, tusb_control_request_t const * p_request) { + switch (p_request->bRequest) { //-V2520 + case TUSB_REQ_SET_ADDRESS: + // Depending on mcu, status phase could be sent either before or after changing device address, + // or even require stack to not response with status at all + // Therefore DCD must take full responsibility to response and include zlp status packet if needed. + dcd_set_address(rhport, (uint8_t) p_request->wValue); + _usbd_dev.addressed = 1; + return true; + + case TUSB_REQ_GET_CONFIGURATION: { + uint8_t cfg_num = _usbd_dev.cfg_num; + tud_control_xfer(rhport, p_request, &cfg_num, 1); + return true; + } + + case TUSB_REQ_SET_CONFIGURATION: { + uint8_t const cfg_num = (uint8_t) p_request->wValue; + + // Only process if new configure is different + if (_usbd_dev.cfg_num != cfg_num) { + if (_usbd_dev.cfg_num != 0) { + // already configured: need to clear all endpoints and driver first + TU_LOG_USBD(" Clear current Configuration (%u) before switching\r\n", _usbd_dev.cfg_num); + + dcd_sof_enable(rhport, false); + dcd_edpt_close_all(rhport); + + // close all drivers and current configured state except bus speed + const uint8_t speed = _usbd_dev.speed; + configuration_reset(rhport); + + _usbd_dev.speed = speed; // restore speed + } + + _usbd_dev.cfg_num = cfg_num; + + // Handle the new configuration + if (cfg_num == 0) { + tud_umount_cb(); + } else { + if (!process_set_config(rhport, cfg_num)) { + _usbd_dev.cfg_num = 0; + TU_ASSERT(false); + } + tud_mount_cb(); + } + } + + tud_control_status(rhport, p_request); + return true; + } + + case TUSB_REQ_GET_DESCRIPTOR: + return process_get_descriptor(rhport, p_request); + + case TUSB_REQ_SET_FEATURE: + switch (p_request->wValue) { //-V2520 + case TUSB_REQ_FEATURE_REMOTE_WAKEUP: + TU_LOG_USBD(" Enable Remote Wakeup\r\n"); + // Host may enable remote wake up before suspending especially HID device + _usbd_dev.remote_wakeup_en = 1; + tud_control_status(rhport, p_request); + return true; + + #if CFG_TUD_TEST_MODE + case TUSB_REQ_FEATURE_TEST_MODE: { + // Only handle the test mode if supported and valid + TU_VERIFY(0 == tu_u16_low(p_request->wIndex)); + + uint8_t const selector = tu_u16_high(p_request->wIndex); + TU_VERIFY(TUSB_FEATURE_TEST_J <= selector && selector <= TUSB_FEATURE_TEST_FORCE_ENABLE); + + _usbd_dev.ctrl_xfer.complete_cb = process_test_mode_cb; + tud_control_status(rhport, p_request); + return true; + } + #endif + + // Stall unsupported feature selector + default: return false; + } + + case TUSB_REQ_CLEAR_FEATURE: + // Only support remote wakeup for device feature + TU_VERIFY(TUSB_REQ_FEATURE_REMOTE_WAKEUP == p_request->wValue); + TU_LOG_USBD(" Disable Remote Wakeup\r\n"); + + // Host may disable remote wake up after resuming + _usbd_dev.remote_wakeup_en = 0; + tud_control_status(rhport, p_request); + return true; + + case TUSB_REQ_GET_STATUS: { + // Device status bit mask + // - Bit 0: Self Powered TODO must invoke callback to get actual status + // - Bit 1: Remote Wakeup enabled + uint16_t status = (uint16_t) _usbd_dev.dev_state_bm; + tud_control_xfer(rhport, p_request, &status, 2); + return true; + } + + default: + TU_BREAKPOINT(); + return false; + } +} + + +// This handles the actual request and its response. +// Returns false if unable to complete the request, causing caller to stall control endpoints. +static bool process_setup_received(uint8_t rhport, tusb_control_request_t const * p_request) { + // Initialize control transfer state for this request. The request copy must be + // visible to usbd_control_xfer_cb when the (asynchronous) status ZLP completes, + // since the SETUP packet event has already gone out of scope by then. + usbd_control_xfer_t* const ctrl_xfer = &_usbd_dev.ctrl_xfer; + ctrl_xfer->request = *p_request; + ctrl_xfer->buffer = NULL; + ctrl_xfer->total_xferred = 0; + ctrl_xfer->data_len = 0; + ctrl_xfer->complete_cb = NULL; + + p_request = &ctrl_xfer->request; // re-direct request pointer to internal copy (modifiable for hacking) + TU_ASSERT(p_request->bmRequestType_bit.type < TUSB_REQ_TYPE_INVALID); + + // Vendor request + if ( p_request->bmRequestType_bit.type == TUSB_REQ_TYPE_VENDOR ) { + ctrl_xfer->complete_cb = tud_vendor_control_xfer_cb; + return tud_vendor_control_xfer_cb(rhport, CONTROL_STAGE_SETUP, p_request); + } + +#if CFG_TUSB_DEBUG >= CFG_TUD_LOG_LEVEL + if (TUSB_REQ_TYPE_STANDARD == p_request->bmRequestType_bit.type && p_request->bRequest <= TUSB_REQ_SYNCH_FRAME) { + TU_LOG_USBD(" %s", tu_str_std_request[p_request->bRequest]); + if (TUSB_REQ_GET_DESCRIPTOR != p_request->bRequest) { + TU_LOG_USBD("\r\n"); + } + } +#endif + + switch (p_request->bmRequestType_bit.recipient) { //-V2520 + //------------- Device Requests e.g in enumeration -------------// + case TUSB_REQ_RCPT_DEVICE: + if ( TUSB_REQ_TYPE_CLASS == p_request->bmRequestType_bit.type ) { + uint8_t const itf = tu_u16_low(p_request->wIndex); + TU_VERIFY(itf < TU_ARRAY_SIZE(_usbd_dev.itf2drv)); + + usbd_class_driver_t const * driver = get_driver(_usbd_dev.itf2drv[itf]); + TU_VERIFY(driver); + + // forward to class driver: "non-STD request to Interface" + return invoke_class_control(rhport, driver, p_request); + } + + if (TUSB_REQ_TYPE_STANDARD != p_request->bmRequestType_bit.type) { + // Non-standard request is not supported + TU_BREAKPOINT(); + return false; + } + + return process_std_device_request(rhport, p_request); + + //------------- Class/Interface Specific Request -------------// + case TUSB_REQ_RCPT_INTERFACE: { + uint8_t itf; + #if CFG_TUD_PRINTER + // Printer GET_DEVICE_ID has a weird wIndex = interface (high) | alt (low) + // attempt to interpret this as a printer request if matched + if (TUSB_REQ_TYPE_CLASS == p_request->bmRequestType_bit.type && + TUSB_DIR_IN == p_request->bmRequestType_bit.direction && + TUSB_PRINTER_REQUEST_GET_DEVICE_ID == p_request->bRequest) { + itf = tu_u16_high(p_request->wIndex); + if (itf < TU_ARRAY_SIZE(_usbd_dev.itf2drv)) { + const usbd_class_driver_t * driver = get_driver(_usbd_dev.itf2drv[itf]); + if (driver != NULL && driver->control_xfer_cb == printerd_control_xfer_cb) { + if (invoke_class_control(rhport, driver, p_request)) { + return true; + } + } + } + } + #endif + itf = tu_u16_low(p_request->wIndex); + TU_VERIFY(itf < TU_ARRAY_SIZE(_usbd_dev.itf2drv)); + + usbd_class_driver_t const * driver = get_driver(_usbd_dev.itf2drv[itf]); + TU_VERIFY(driver); + + // all requests to Interface (STD or Class) is forwarded to class driver. + // notable requests are: GET HID REPORT DESCRIPTOR, SET_INTERFACE, GET_INTERFACE + if (!invoke_class_control(rhport, driver, p_request)) { + // For GET_INTERFACE and SET_INTERFACE, it is mandatory to respond even if the class + // driver doesn't use alternate settings or implement this + TU_VERIFY(TUSB_REQ_TYPE_STANDARD == p_request->bmRequestType_bit.type); + + // Clear complete callback if driver set since it can also stall the request. + ctrl_xfer->complete_cb = NULL; + + switch (p_request->bRequest) { //-V2520 + case TUSB_REQ_GET_INTERFACE: { + uint8_t alternate = 0; + tud_control_xfer(rhport, p_request, &alternate, 1); + break; + } + + case TUSB_REQ_SET_INTERFACE: + tud_control_status(rhport, p_request); + break; + + default: return false; + } + } + break; + } + + //------------- Endpoint Request -------------// + case TUSB_REQ_RCPT_ENDPOINT: { + uint8_t const ep_addr = tu_u16_low(p_request->wIndex); + uint8_t const ep_num = tu_edpt_number(ep_addr); + uint8_t const ep_dir = tu_edpt_dir(ep_addr); + + TU_ASSERT(ep_num < TU_ARRAY_SIZE(_usbd_dev.ep2drv) ); + usbd_class_driver_t const * driver = get_driver(_usbd_dev.ep2drv[ep_num][ep_dir]); + + if (TUSB_REQ_TYPE_STANDARD != p_request->bmRequestType_bit.type) { + // Forward class request to its driver + TU_VERIFY(driver); + return invoke_class_control(rhport, driver, p_request); + } else { + // Handle STD request to endpoint + switch (p_request->bRequest) { //-V2520 + case TUSB_REQ_GET_STATUS: { + uint16_t status = usbd_edpt_stalled(rhport, ep_addr) ? 0x0001u : 0x0000u; + tud_control_xfer(rhport, p_request, &status, 2); + } + break; + + case TUSB_REQ_CLEAR_FEATURE: + case TUSB_REQ_SET_FEATURE: { + if ( TUSB_REQ_FEATURE_EDPT_HALT == p_request->wValue ) { + if ( TUSB_REQ_CLEAR_FEATURE == p_request->bRequest ) { + usbd_edpt_clear_stall(rhport, ep_addr); + }else { + usbd_edpt_stall(rhport, ep_addr); + } + } + + if (driver != NULL) { + // Some classes such as USBTMC needs to clear/re-init its buffer when receiving CLEAR_FEATURE request + // We will also forward std request targeted endpoint to class drivers as well + + // STD request must always be ACKed regardless of driver returned value + // Also clear complete callback if driver set since it can also stall the request. + (void) invoke_class_control(rhport, driver, p_request); + ctrl_xfer->complete_cb = NULL; + + // skip ZLP status if driver already did that + if (!(_usbd_dev.ep_status[0][TUSB_DIR_IN] & TU_EDPT_STATE_BUSY)) { + tud_control_status(rhport, p_request); + } + } + } + break; + + // Unknown/Unsupported request + default: + TU_BREAKPOINT(); + return false; + } + } + break; + } + + // Unknown recipient + default: + TU_BREAKPOINT(); + return false; + } + + return true; +} + +// Process Set Configure Request +// This function parse configuration descriptor & open drivers accordingly +static bool process_set_config(uint8_t rhport, uint8_t cfg_num) { + // index is cfg_num-1 + const tusb_desc_configuration_t *desc_cfg = + (const tusb_desc_configuration_t *)tud_descriptor_configuration_cb(cfg_num - 1); + TU_ASSERT(desc_cfg != NULL && desc_cfg->bDescriptorType == TUSB_DESC_CONFIGURATION); + + // Parse configuration descriptor + _usbd_dev.self_powered = (desc_cfg->bmAttributes & TUSB_DESC_CONFIG_ATT_SELF_POWERED) ? 1u : 0u; + + // Parse interface descriptor + const uint8_t *p_desc = ((const uint8_t *)desc_cfg) + sizeof(tusb_desc_configuration_t); + const uint8_t *desc_end = ((const uint8_t *)desc_cfg) + tu_le16toh(desc_cfg->wTotalLength); + + while (tu_desc_in_bounds(p_desc, desc_end)) { + // Class will always start with Interface Association (if any) and then Interface descriptor + if (TUSB_DESC_INTERFACE_ASSOCIATION == tu_desc_type(p_desc)) { + p_desc = tu_desc_next(p_desc); // next to Interface + continue; + } + + TU_ASSERT(TUSB_DESC_INTERFACE == tu_desc_type(p_desc)); + const tusb_desc_interface_t *desc_itf = (const tusb_desc_interface_t *)p_desc; + + // Find driver for this interface + const uint16_t remaining_len = (uint16_t)(desc_end - p_desc); + uint8_t drv_id; + for (drv_id = 0; drv_id < TOTAL_DRIVER_COUNT; drv_id++) { + const usbd_class_driver_t *driver = get_driver(drv_id); + TU_ASSERT(driver); + const uint16_t drv_len = driver->open(rhport, desc_itf, remaining_len); + + if ((sizeof(tusb_desc_interface_t) <= drv_len) && (drv_len <= remaining_len)) { + // Open successfully + TU_LOG_USBD(" %s opened\r\n", driver->name); + + // bind found driver to all interfaces and endpoint within drv_len + TU_ASSERT(tu_bind_driver_to_ep_itf(drv_id, _usbd_dev.ep2drv, _usbd_dev.itf2drv, CFG_TUD_INTERFACE_MAX, p_desc, + drv_len)); + + p_desc += drv_len; // next Interface + break; // exit driver find loop + } + } + + // Failed if there is no supported drivers + TU_ASSERT(drv_id < TOTAL_DRIVER_COUNT); + } + + return true; +} + +// return descriptor's buffer and update desc_len +static bool process_get_descriptor(uint8_t rhport, tusb_control_request_t const * p_request) { + tusb_desc_type_t const desc_type = (tusb_desc_type_t) tu_u16_high(p_request->wValue); + uint8_t const desc_index = tu_u16_low( p_request->wValue ); + + switch(desc_type) { //-V2520 + case TUSB_DESC_DEVICE: { + TU_LOG_USBD(" Device\r\n"); + + void *desc_device = (void *)(uintptr_t)tud_descriptor_device_cb(); + TU_ASSERT(desc_device); + + // Only response with exactly 1 Packet if: not addressed and host requested more data than device descriptor has. + // This only happens with the very first get device descriptor and EP0 size = 8 or 16. + if ((CFG_TUD_ENDPOINT0_SIZE < sizeof(tusb_desc_device_t)) && !_usbd_dev.addressed && + p_request->wLength > sizeof(tusb_desc_device_t)) { + // Hack here: we modify the request length to prevent usbd_control response with zlp + // since we are responding with 1 packet & less data than wLength. + ((tusb_control_request_t *)(uintptr_t)p_request)->wLength = CFG_TUD_ENDPOINT0_SIZE; + return tud_control_xfer(rhport, p_request, desc_device, CFG_TUD_ENDPOINT0_SIZE); + } else { + return tud_control_xfer(rhport, p_request, desc_device, sizeof(tusb_desc_device_t)); + } + } + // break; // unreachable + + case TUSB_DESC_BOS: { + TU_LOG_USBD(" BOS\r\n"); + + // requested by host if USB > 2.0 ( i.e 2.1 or 3.x ) + uintptr_t desc_bos = (uintptr_t) tud_descriptor_bos_cb(); + TU_VERIFY(desc_bos != 0); + + // Use offsetof to avoid pointer to the odd/misaligned address + uint16_t const total_len = tu_le16toh( tu_unaligned_read16((const void*) (desc_bos + offsetof(tusb_desc_bos_t, wTotalLength))) ); + + return tud_control_xfer(rhport, p_request, (void*) desc_bos, total_len); + } + // break; // unreachable + + case TUSB_DESC_CONFIGURATION: + case TUSB_DESC_OTHER_SPEED_CONFIG: { + uintptr_t desc_config; + + if ( desc_type == TUSB_DESC_CONFIGURATION ) { + TU_LOG_USBD(" Configuration[%u]\r\n", desc_index); + desc_config = (uintptr_t) tud_descriptor_configuration_cb(desc_index); + TU_ASSERT(desc_config != 0); + }else { + // Host only request this after getting Device Qualifier descriptor + TU_LOG_USBD(" Other Speed Configuration\r\n"); + desc_config = (uintptr_t) tud_descriptor_other_speed_configuration_cb(desc_index); + TU_VERIFY(desc_config != 0); + } + + // Use offsetof to avoid pointer to the odd/misaligned address + uint16_t const total_len = tu_le16toh( tu_unaligned_read16((const void*) (desc_config + offsetof(tusb_desc_configuration_t, wTotalLength))) ); + + return tud_control_xfer(rhport, p_request, (void*) desc_config, total_len); + } + // break; // unreachable + + case TUSB_DESC_STRING: { + TU_LOG_USBD(" String[%u]\r\n", desc_index); + + // String Descriptor always uses the desc set from user + uint8_t const* desc_str = (uint8_t const*) tud_descriptor_string_cb(desc_index, p_request->wIndex); + TU_VERIFY(desc_str); + + // first byte of descriptor is its size + return tud_control_xfer(rhport, p_request, (void*) (uintptr_t) desc_str, tu_desc_len(desc_str)); + } + // break; // unreachable + + case TUSB_DESC_DEVICE_QUALIFIER: { + TU_LOG_USBD(" Device Qualifier\r\n"); + uint8_t const* desc_qualifier = tud_descriptor_device_qualifier_cb(); + TU_VERIFY(desc_qualifier); + return tud_control_xfer(rhport, p_request, (void*) (uintptr_t) desc_qualifier, tu_desc_len(desc_qualifier)); + } + // break; // unreachable + + default: return false; + } +} + +//--------------------------------------------------------------------+ +// DCD Event Handler +//--------------------------------------------------------------------+ +TU_ATTR_FAST_FUNC void dcd_event_handler(dcd_event_t const* event, bool in_isr) { + bool send = false; + switch (event->event_id) { + case DCD_EVENT_UNPLUGGED: + _usbd_dev.connected = 0; + _usbd_dev.addressed = 0; + _usbd_dev.cfg_num = 0; + _usbd_dev.suspended = 0; + send = true; + break; + + case DCD_EVENT_SUSPEND: + // NOTE: When plugging/unplugging device, the D+/D- state are unstable and + // can accidentally meet the SUSPEND condition ( Bus Idle for 3ms ). + // In addition, some MCUs such as SAMD or boards that haven no VBUS detection cannot distinguish + // suspended vs disconnected. We will skip handling SUSPEND/RESUME event if not currently connected + if (_usbd_dev.connected) { + _usbd_dev.suspended = 1; + send = true; + } + break; + + case DCD_EVENT_RESUME: + // skip event if not connected (especially required for SAMD) + if (_usbd_dev.connected) { + _usbd_dev.suspended = 0; + send = true; + } + break; + + case DCD_EVENT_SOF: + // SOF driver handler in ISR context + for (uint8_t i = 0; i < TOTAL_DRIVER_COUNT; i++) { + usbd_class_driver_t const* driver = get_driver(i); + if (driver && driver->sof) { + driver->sof(event->rhport, event->sof.frame_count); + } + } + + // Some MCUs after running dcd_remote_wakeup() does not have way to detect the end of remote wakeup + // which last 1-15 ms. DCD can use SOF as a clear indicator that bus is back to operational + if (_usbd_dev.suspended) { + _usbd_dev.suspended = 0; + + dcd_event_t const event_resume = {.rhport = event->rhport, .event_id = DCD_EVENT_RESUME}; + queue_event(&event_resume, in_isr); + } + + if (tu_bit_test(_usbd_dev.sof_consumer, SOF_CONSUMER_USER)) { + dcd_event_t const event_sof = {.rhport = event->rhport, .event_id = DCD_EVENT_SOF, .sof.frame_count = event->sof.frame_count}; + queue_event(&event_sof, in_isr); + } + break; + + case DCD_EVENT_SETUP_RECEIVED: + _usbd_queued_setup++; + send = true; + break; + + case DCD_EVENT_XFER_COMPLETE: { + // Invoke the class callback associated with the endpoint address + uint8_t const ep_addr = event->xfer_complete.ep_addr; + uint8_t const epnum = tu_edpt_number(ep_addr); + uint8_t const ep_dir = tu_edpt_dir(ep_addr); + + send = true; + if(epnum > 0) { + usbd_class_driver_t const* driver = get_driver(_usbd_dev.ep2drv[epnum][ep_dir]); + + if (driver && driver->xfer_isr) { + // Clear busy + claimed + _usbd_dev.ep_status[epnum][ep_dir] &= (uint8_t) ~(TU_EDPT_STATE_BUSY | TU_EDPT_STATE_CLAIMED); + + send = !driver->xfer_isr(event->rhport, ep_addr, (xfer_result_t) event->xfer_complete.result, event->xfer_complete.len); + + // xfer_isr() is deferred to xfer_cb(), revert busy/claimed status + if (send) { + // set busy + claimed + _usbd_dev.ep_status[epnum][ep_dir] |= (TU_EDPT_STATE_BUSY | TU_EDPT_STATE_CLAIMED); + } + } + } + break; + } + + default: + send = true; + break; + } + + if (send) { + queue_event(event, in_isr); + } +} + +//--------------------------------------------------------------------+ +// USBD API For Class Driver +//--------------------------------------------------------------------+ + +void usbd_int_set(bool enabled) { + if (enabled) { + dcd_int_enable(_usbd_rhport); + } else { + dcd_int_disable(_usbd_rhport); + } +} + +void usbd_spin_lock(bool in_isr) { + osal_spin_lock(&_usbd_spin, in_isr); +} +void usbd_spin_unlock(bool in_isr) { + osal_spin_unlock(&_usbd_spin, in_isr); +} + +// Parse consecutive endpoint descriptors (IN & OUT) +bool usbd_open_edpt_pair(uint8_t rhport, const uint8_t *p_desc, uint8_t ep_count, uint8_t xfer_type, uint8_t *ep_out, + uint8_t *ep_in) { + for (int i = 0; i < ep_count; i++) { + const tusb_desc_endpoint_t *desc_ep = (const tusb_desc_endpoint_t *)p_desc; + + TU_ASSERT(TUSB_DESC_ENDPOINT == desc_ep->bDescriptorType && xfer_type == desc_ep->bmAttributes.xfer); + TU_ASSERT(usbd_edpt_open(rhport, desc_ep)); + + if (tu_edpt_dir(desc_ep->bEndpointAddress) == TUSB_DIR_IN) { + (*ep_in) = desc_ep->bEndpointAddress; + } else { + (*ep_out) = desc_ep->bEndpointAddress; + } + + p_desc = tu_desc_next(p_desc); + } + + return true; +} + +// Helper to defer an isr function +void usbd_defer_func(osal_task_func_t func, void* param, bool in_isr) { + dcd_event_t event = { + .rhport = 0, + .event_id = USBD_EVENT_FUNC_CALL, + }; + event.func_call.func = func; + event.func_call.param = param; + + queue_event(&event, in_isr); +} + +//--------------------------------------------------------------------+ +// USBD Endpoint API +//--------------------------------------------------------------------+ + +bool usbd_edpt_open(uint8_t rhport, tusb_desc_endpoint_t const* desc_ep) { + rhport = _usbd_rhport; + + TU_ASSERT(tu_edpt_number(desc_ep->bEndpointAddress) < CFG_TUD_ENDPPOINT_MAX); + TU_ASSERT(tu_edpt_validate(desc_ep, (tusb_speed_t)_usbd_dev.speed)); + + return dcd_edpt_open(rhport, desc_ep); +} + +bool usbd_edpt_claim(uint8_t rhport, uint8_t ep_addr) { + (void) rhport; + + // TODO add this check later, also make sure we don't starve an out endpoint while suspending + // TU_VERIFY(tud_ready()); + + uint8_t const epnum = tu_edpt_number(ep_addr); + uint8_t const dir = tu_edpt_dir(ep_addr); + return tu_edpt_claim(&_usbd_dev.ep_status[epnum][dir], _usbd_mutex); +} + +bool usbd_edpt_release(uint8_t rhport, uint8_t ep_addr) { + (void) rhport; + + uint8_t const epnum = tu_edpt_number(ep_addr); + uint8_t const dir = tu_edpt_dir(ep_addr); + return tu_edpt_release(&_usbd_dev.ep_status[epnum][dir], _usbd_mutex); +} + +bool usbd_edpt_xfer(uint8_t rhport, uint8_t ep_addr, uint8_t* buffer, uint16_t total_bytes, bool is_isr) { + rhport = _usbd_rhport; + + uint8_t const epnum = tu_edpt_number(ep_addr); + uint8_t const dir = tu_edpt_dir(ep_addr); + + // TODO skip ready() check for now since enumeration also use this API + // TU_VERIFY(tud_ready()); + + TU_LOG_USBD(" Queue EP %02X with %u bytes ...\r\n", ep_addr, total_bytes); +#if CFG_TUD_LOG_LEVEL >= 3 + if(dir == TUSB_DIR_IN) { + TU_LOG_MEM(CFG_TUD_LOG_LEVEL, buffer, total_bytes, 2); + } +#endif + + // Attempt to transfer on a busy endpoint, sound like an race condition ! + TU_ASSERT((_usbd_dev.ep_status[epnum][dir] & TU_EDPT_STATE_BUSY) == 0); + + // Set busy first since the actual transfer can be complete before dcd_edpt_xfer() + // could return and USBD task can preempt and clear the busy + _usbd_dev.ep_status[epnum][dir] |= TU_EDPT_STATE_BUSY; + + if (dcd_edpt_xfer(rhport, ep_addr, buffer, total_bytes, is_isr)) { + return true; + } else { + // DCD error, mark endpoint as ready to allow next transfer + _usbd_dev.ep_status[epnum][dir] &= (uint8_t) ~(TU_EDPT_STATE_BUSY | TU_EDPT_STATE_CLAIMED); + TU_LOG_USBD("FAILED\r\n"); + TU_BREAKPOINT(); + return false; + } +} + +// The number of bytes has to be given explicitly to allow more flexible control of how many +// bytes should be written and second to keep the return value free to give back a boolean +// success message. If total_bytes is too big, the FIFO will copy only what is available +// into the USB buffer! +bool usbd_edpt_xfer_fifo(uint8_t rhport, uint8_t ep_addr, tu_fifo_t* ff, uint16_t total_bytes, bool is_isr) { + #if CFG_TUD_EDPT_DEDICATED_HWFIFO + rhport = _usbd_rhport; + + uint8_t const epnum = tu_edpt_number(ep_addr); + uint8_t const dir = tu_edpt_dir(ep_addr); + + TU_LOG_USBD(" Queue FIFO EP %02X with %u bytes ... ", ep_addr, total_bytes); + + // Attempt to transfer on a busy endpoint, sound like a race condition ! + TU_ASSERT((_usbd_dev.ep_status[epnum][dir] & TU_EDPT_STATE_BUSY) == 0); + + // Set busy first since the actual transfer can be complete before dcd_edpt_xfer() could return + // and usbd task can preempt and clear the busy + _usbd_dev.ep_status[epnum][dir] |= TU_EDPT_STATE_BUSY; + + if (dcd_edpt_xfer_fifo(rhport, ep_addr, ff, total_bytes, is_isr)) { + TU_LOG_USBD("OK\r\n"); + return true; + } else { + // DCD error, mark endpoint as ready to allow next transfer + _usbd_dev.ep_status[epnum][dir] &= (uint8_t) ~(TU_EDPT_STATE_BUSY | TU_EDPT_STATE_CLAIMED); + TU_LOG_USBD("failed\r\n"); + TU_BREAKPOINT(); + return false; + } + #else + (void)rhport; + (void)ep_addr; + (void)ff; + (void)total_bytes; + (void)is_isr; + return false; + #endif +} + +bool usbd_edpt_busy(uint8_t rhport, uint8_t ep_addr) { + (void) rhport; + + uint8_t const epnum = tu_edpt_number(ep_addr); + uint8_t const dir = tu_edpt_dir(ep_addr); + + return (_usbd_dev.ep_status[epnum][dir] & TU_EDPT_STATE_BUSY) != 0; +} + +void usbd_edpt_stall(uint8_t rhport, uint8_t ep_addr) { + rhport = _usbd_rhport; + + uint8_t const epnum = tu_edpt_number(ep_addr); + uint8_t const dir = tu_edpt_dir(ep_addr); + + // only stalled if currently cleared + TU_LOG_USBD(" Stall EP %02X\r\n", ep_addr); + dcd_edpt_stall(rhport, ep_addr); + _usbd_dev.ep_status[epnum][dir] |= (TU_EDPT_STATE_STALLED | TU_EDPT_STATE_BUSY); +} + +void usbd_edpt_clear_stall(uint8_t rhport, uint8_t ep_addr) { + rhport = _usbd_rhport; + + uint8_t const epnum = tu_edpt_number(ep_addr); + uint8_t const dir = tu_edpt_dir(ep_addr); + + // only clear if currently stalled + TU_LOG_USBD(" Clear Stall EP %02X\r\n", ep_addr); + dcd_edpt_clear_stall(rhport, ep_addr); + _usbd_dev.ep_status[epnum][dir] &= (uint8_t) ~(TU_EDPT_STATE_STALLED | TU_EDPT_STATE_BUSY); +} + +bool usbd_edpt_stalled(uint8_t rhport, uint8_t ep_addr) { + (void) rhport; + + uint8_t const epnum = tu_edpt_number(ep_addr); + uint8_t const dir = tu_edpt_dir(ep_addr); + + return (_usbd_dev.ep_status[epnum][dir] & TU_EDPT_STATE_STALLED) != 0; +} + +/** + * usbd_edpt_close will disable an endpoint. + * In progress transfers on this EP may be delivered after this call. + */ +void usbd_edpt_close(uint8_t rhport, uint8_t ep_addr) { +#ifdef TUP_DCD_EDPT_ISO_ALLOC + (void) rhport; (void) ep_addr; + // ISO alloc/activate Should be used instead +#else + rhport = _usbd_rhport; + + TU_LOG_USBD(" CLOSING Endpoint: 0x%02X\r\n", ep_addr); + + uint8_t const epnum = tu_edpt_number(ep_addr); + uint8_t const dir = tu_edpt_dir(ep_addr); + + dcd_edpt_close(rhport, ep_addr); + _usbd_dev.ep_status[epnum][dir] = 0; +#endif + + return; +} + +void usbd_sof_enable(uint8_t rhport, sof_consumer_t consumer, bool en) { + rhport = _usbd_rhport; + + uint8_t consumer_old = _usbd_dev.sof_consumer; + // Keep track how many class instances need the SOF interrupt + if (en) { + _usbd_dev.sof_consumer |= (uint8_t)(1 << consumer); + } else { + _usbd_dev.sof_consumer &= (uint8_t)(~(1 << consumer)); + } + + // Test logically unequal + if(!_usbd_dev.sof_consumer != !consumer_old) { + dcd_sof_enable(rhport, _usbd_dev.sof_consumer); + } +} + +bool usbd_edpt_iso_alloc(uint8_t rhport, uint8_t ep_addr, uint16_t largest_packet_size) { +#ifdef TUP_DCD_EDPT_ISO_ALLOC + rhport = _usbd_rhport; + + TU_ASSERT(tu_edpt_number(ep_addr) < CFG_TUD_ENDPPOINT_MAX); + return dcd_edpt_iso_alloc(rhport, ep_addr, largest_packet_size); +#else + (void) rhport; (void) ep_addr; (void) largest_packet_size; + return false; +#endif +} + +bool usbd_edpt_iso_activate(uint8_t rhport, tusb_desc_endpoint_t const* desc_ep) { +#ifdef TUP_DCD_EDPT_ISO_ALLOC + rhport = _usbd_rhport; + + uint8_t const epnum = tu_edpt_number(desc_ep->bEndpointAddress); + uint8_t const dir = tu_edpt_dir(desc_ep->bEndpointAddress); + + TU_ASSERT(epnum < CFG_TUD_ENDPPOINT_MAX); + TU_ASSERT(tu_edpt_validate(desc_ep, (tusb_speed_t)_usbd_dev.speed)); + + _usbd_dev.ep_status[epnum][dir] = 0; + return dcd_edpt_iso_activate(rhport, desc_ep); +#else + (void) rhport; (void) desc_ep; + return false; +#endif +} + +#endif diff --git a/libraries/Adafruit_TinyUSB_Arduino/src/device/usbd.h b/libraries/Adafruit_TinyUSB_Arduino/src/device/usbd.h new file mode 100644 index 0000000..473e697 --- /dev/null +++ b/libraries/Adafruit_TinyUSB_Arduino/src/device/usbd.h @@ -0,0 +1,1061 @@ +/* + * 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. + * + * This file is part of the TinyUSB stack. + */ + +#ifndef TUSB_USBD_H_ +#define TUSB_USBD_H_ + +#include "common/tusb_common.h" + +#ifdef __cplusplus +extern "C" { +#endif + +// ConfigID for tud_configure() +enum { + TUD_CFGID_INVALID = 0, + TUD_CFGID_DWC2 = 100, +}; + +typedef struct { + uint16_t bm_double_buffered; // bitmap of IN endpoints to be double buffered, only effective for bulk endpoints + bool vbus_sensing; // Vbus pin is used for device connection detection, mandatory for tud_umount_cb() +} tud_configure_dwc2_t; + + #ifndef CFG_TUD_CONFIGURE_DWC2_DEFAULT + #define CFG_TUD_CONFIGURE_DWC2_DEFAULT {.bm_double_buffered = 0, .vbus_sensing = CFG_TUD_VBUS_DETECT_HW} + #endif + +typedef union { + tud_configure_dwc2_t dwc2; +} tud_configure_param_t; + +//--------------------------------------------------------------------+ +// Application API +//--------------------------------------------------------------------+ + +// Configure device stack behavior with dynamic or port-specific parameters. +// Should be called before initialization of the device stack +// - cfg_id : configure ID from TUD_CFGID_* enum values +// - cfg_param: configure data, structure depends on the ID +bool tud_configure(uint8_t rhport, uint32_t cfg_id, const void* cfg_param); + +// New API to replace tud_init() to init device stack on specific roothub port +// Must be called in the same task/context as tud_task() if RTOS is used +bool tud_rhport_init(uint8_t rhport, const tusb_rhport_init_t* rh_init); + +// Init device stack on roothub port +#if TUSB_VERSION_NUMBER > 2000 // 0.20.0 +TU_ATTR_DEPRECATED("Please use tusb_init(rhport, rh_init) instead") +#endif +TU_ATTR_ALWAYS_INLINE static inline bool tud_init (uint8_t rhport) { + const tusb_rhport_init_t rh_init = { + .role = TUSB_ROLE_DEVICE, + .speed = TUD_OPT_HIGH_SPEED ? TUSB_SPEED_HIGH : TUSB_SPEED_FULL + }; + return tud_rhport_init(rhport, &rh_init); +} + +// Deinit device stack on roothub port +// Must be called in the same task/context as tud_task() if RTOS is used +bool tud_deinit(uint8_t rhport); + +// Check if device stack is already initialized +bool tud_inited(void); + +// Task function should be called in main/rtos loop, extended version of tud_task() +// - timeout_ms: millisecond to wait, zero = no wait, 0xFFFFFFFF = wait forever +// - in_isr: if function is called in ISR +void tud_task_ext(uint32_t timeout_ms, bool in_isr); + +// Task function should be called in main/rtos loop +TU_ATTR_ALWAYS_INLINE static inline +void tud_task (void) { + tud_task_ext(UINT32_MAX, false); +} + +// Check if there is pending events need processing by tud_task() +bool tud_task_event_ready(void); + +#ifndef TUSB_DCD_H_ +extern void dcd_int_handler(uint8_t rhport); +#endif + +// Interrupt handler, name alias to DCD +#define tud_int_handler dcd_int_handler + +// Get current bus speed +tusb_speed_t tud_speed_get(void); + +// Check if device is connected (may not mounted/configured yet) +// True if just got out of Bus Reset and received the very first data from host +bool tud_connected(void); + +// Check if device is connected and configured +bool tud_mounted(void); + +// Check if device is suspended +bool tud_suspended(void); + +// Check if device is ready to transfer +TU_ATTR_ALWAYS_INLINE static inline +bool tud_ready(void) { + const bool is_mounted = tud_mounted(); + const bool is_suspended = tud_suspended(); + return is_mounted && !is_suspended; +} + +// Remote wake up host, only if suspended and enabled by host +bool tud_remote_wakeup(void); + +// Enable pull-up resistor on D+ D- +// Return false on unsupported MCUs +bool tud_disconnect(void); + +// Disable pull-up resistor on D+ D- +// Return false on unsupported MCUs +bool tud_connect(void); + +// Enable or disable the Start Of Frame callback support +void tud_sof_cb_enable(bool en); + +// Carry out Data and Status stage of control transfer +// - If len = 0, it is equivalent to sending status only +// - If len > wLength : it will be truncated +bool tud_control_xfer(uint8_t rhport, tusb_control_request_t const * request, void* buffer, uint16_t len); + +// Send STATUS (zero length) packet +bool tud_control_status(uint8_t rhport, tusb_control_request_t const * request); + +//--------------------------------------------------------------------+ +// Application Callbacks +//--------------------------------------------------------------------+ + +// Invoked when received GET DEVICE DESCRIPTOR request +// Application return pointer to descriptor +uint8_t const * tud_descriptor_device_cb(void); + +// Invoked when received GET CONFIGURATION DESCRIPTOR request +// Application return pointer to descriptor, whose contents must exist long enough for transfer to complete +uint8_t const * tud_descriptor_configuration_cb(uint8_t index); + +// 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); + +// Invoked when received GET BOS DESCRIPTOR request +// Application return pointer to descriptor +uint8_t const * tud_descriptor_bos_cb(void); + +// Invoked when received GET DEVICE QUALIFIER DESCRIPTOR request +// Application return pointer to descriptor, whose contents must exist long enough for transfer to complete. +// device_qualifier descriptor describes information about a high-speed capable device that would +// change if the device were operating at the other speed. If not highspeed capable stall this request. +uint8_t const* tud_descriptor_device_qualifier_cb(void); + +// Invoked when received GET OTHER SEED CONFIGURATION DESCRIPTOR request +// Application return pointer to descriptor, whose contents must exist long enough for transfer to complete +// Configuration descriptor in the other speed e.g if high speed then this is for full speed and vice versa +uint8_t const* tud_descriptor_other_speed_configuration_cb(uint8_t index); + +// Invoked when device is mounted (configured) +void tud_mount_cb(void); + +// Invoked when device is unmounted +void tud_umount_cb(void); + +// Invoked when usb bus is suspended +// Within 7ms, device must draw an average of current less than 2.5 mA from bus +void tud_suspend_cb(bool remote_wakeup_en); + +// Invoked when usb bus is resumed +void tud_resume_cb(void); + +// Invoked when there is a new usb event, which need to be processed by tud_task()/tud_task_ext() +void tud_event_hook_cb(uint8_t rhport, uint32_t eventid, bool in_isr); + +// Invoked when a new (micro) frame started +void tud_sof_cb(uint32_t frame_count); + +// Invoked when received control request with VENDOR TYPE +bool tud_vendor_control_xfer_cb(uint8_t rhport, uint8_t stage, tusb_control_request_t const * request); + +//--------------------------------------------------------------------+ +// Binary Device Object Store (BOS) Descriptor Templates +//--------------------------------------------------------------------+ + +#define TUD_BOS_DESC_LEN 5 + +// total length, number of device caps +#define TUD_BOS_DESCRIPTOR(_total_len, _caps_num) \ + 5, TUSB_DESC_BOS, U16_TO_U8S_LE(_total_len), _caps_num + +// Device Capability Platform 128-bit UUID + Data +#define TUD_BOS_PLATFORM_DESCRIPTOR(...) \ + 4+TU_ARGS_NUM(__VA_ARGS__), TUSB_DESC_DEVICE_CAPABILITY, DEVICE_CAPABILITY_PLATFORM, 0x00, __VA_ARGS__ + +//------------- WebUSB BOS Platform -------------// + +// Descriptor Length +#define TUD_BOS_WEBUSB_DESC_LEN 24 + +// Vendor Code, iLandingPage +#define TUD_BOS_WEBUSB_DESCRIPTOR(_vendor_code, _ipage) \ + TUD_BOS_PLATFORM_DESCRIPTOR(TUD_BOS_WEBUSB_UUID, U16_TO_U8S_LE(0x0100), _vendor_code, _ipage) + +#define TUD_BOS_WEBUSB_UUID \ + 0x38, 0xB6, 0x08, 0x34, 0xA9, 0x09, 0xA0, 0x47, \ + 0x8B, 0xFD, 0xA0, 0x76, 0x88, 0x15, 0xB6, 0x65 + +//------------- Microsoft OS 2.0 Platform -------------// +#define TUD_BOS_MICROSOFT_OS_DESC_LEN 28 + +// Total Length of descriptor set, vendor code +#define TUD_BOS_MS_OS_20_DESCRIPTOR(_desc_set_len, _vendor_code) \ + 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, \ + 0x9C, 0xD2, 0x65, 0x9D, 0x9E, 0x64, 0x8A, 0x9F + +//--------------------------------------------------------------------+ +// Configuration Descriptor Templates +//--------------------------------------------------------------------+ + +#define TUD_CONFIG_DESC_LEN (9) + +// Config number, interface count, string index, total length, attribute, power in mA +#define TUD_CONFIG_DESCRIPTOR(config_num, _itfcount, _stridx, _total_len, _attribute, _power_ma) \ + 9, TUSB_DESC_CONFIGURATION, U16_TO_U8S_LE(_total_len), _itfcount, config_num, _stridx, TU_BIT(7) | _attribute, (_power_ma)/2 + +//--------------------------------------------------------------------+ +// CDC Descriptor Templates +//--------------------------------------------------------------------+ + +// Length of template descriptor: 66 bytes +#define TUD_CDC_DESC_LEN (8+9+5+5+4+5+7+9+7+7) + +// CDC Descriptor Template +// Interface number, string index, EP notification address and size, EP data address (out, in) and size. +#define TUD_CDC_DESCRIPTOR(_itfnum, _stridx, _ep_notif, _ep_notif_size, _epout, _epin, _epsize) \ + /* Interface Associate */\ + 8, TUSB_DESC_INTERFACE_ASSOCIATION, _itfnum, 2, TUSB_CLASS_CDC, CDC_COMM_SUBCLASS_ABSTRACT_CONTROL_MODEL, CDC_COMM_PROTOCOL_NONE, 0,\ + /* CDC Control Interface */\ + 9, TUSB_DESC_INTERFACE, _itfnum, 0, 1, TUSB_CLASS_CDC, CDC_COMM_SUBCLASS_ABSTRACT_CONTROL_MODEL, CDC_COMM_PROTOCOL_NONE, _stridx,\ + /* CDC Header */\ + 5, TUSB_DESC_CS_INTERFACE, CDC_FUNC_DESC_HEADER, U16_TO_U8S_LE(0x0120),\ + /* CDC Call */\ + 5, TUSB_DESC_CS_INTERFACE, CDC_FUNC_DESC_CALL_MANAGEMENT, 0, (uint8_t)((_itfnum) + 1),\ + /* CDC ACM: support line request + send break */\ + 4, TUSB_DESC_CS_INTERFACE, CDC_FUNC_DESC_ABSTRACT_CONTROL_MANAGEMENT, 6,\ + /* CDC Union */\ + 5, TUSB_DESC_CS_INTERFACE, CDC_FUNC_DESC_UNION, _itfnum, (uint8_t)((_itfnum) + 1),\ + /* Endpoint Notification */\ + 7, TUSB_DESC_ENDPOINT, _ep_notif, TUSB_XFER_INTERRUPT, U16_TO_U8S_LE(_ep_notif_size), 1,\ + /* CDC Data Interface */\ + 9, TUSB_DESC_INTERFACE, (uint8_t)((_itfnum)+1), 0, 2, TUSB_CLASS_CDC_DATA, 0, 0, 0,\ + /* Endpoint Out */\ + 7, TUSB_DESC_ENDPOINT, _epout, TUSB_XFER_BULK, U16_TO_U8S_LE(_epsize), 0,\ + /* Endpoint In */\ + 7, TUSB_DESC_ENDPOINT, _epin, TUSB_XFER_BULK, U16_TO_U8S_LE(_epsize), 0 + +//--------------------------------------------------------------------+ +// MSC Descriptor Templates +//--------------------------------------------------------------------+ + +// Length of template descriptor: 23 bytes +#define TUD_MSC_DESC_LEN (9 + 7 + 7) + +// Interface number, string index, EP Out & EP In address, EP size +#define TUD_MSC_DESCRIPTOR(_itfnum, _stridx, _epout, _epin, _epsize) \ + /* Interface */\ + 9, TUSB_DESC_INTERFACE, _itfnum, 0, 2, TUSB_CLASS_MSC, MSC_SUBCLASS_SCSI, MSC_PROTOCOL_BOT, _stridx,\ + /* Endpoint Out */\ + 7, TUSB_DESC_ENDPOINT, _epout, TUSB_XFER_BULK, U16_TO_U8S_LE(_epsize), 0,\ + /* Endpoint In */\ + 7, TUSB_DESC_ENDPOINT, _epin, TUSB_XFER_BULK, U16_TO_U8S_LE(_epsize), 0 + +//--------------------------------------------------------------------+ +// Printer Descriptor Templates +//--------------------------------------------------------------------+ + +#define TUD_PRINTER_DESC_LEN (9 + 7 + 7) // one interface, two endpoints + +#define TUD_PRINTER_DESCRIPTOR(_itfnum, _stridx, _epout, _epin, _epsize) \ + /* Interface */\ + 9, TUSB_DESC_INTERFACE, _itfnum, 0, 2, TUSB_CLASS_PRINTER, 1, 2, _stridx,\ + /* Endpoint Out */\ + 7, TUSB_DESC_ENDPOINT, _epout, TUSB_XFER_BULK, U16_TO_U8S_LE(_epsize), 0,\ + /* Endpoint In */\ + 7, TUSB_DESC_ENDPOINT, _epin, TUSB_XFER_BULK, U16_TO_U8S_LE(_epsize), 0 + +//--------------------------------------------------------------------+ +// MTP Descriptor Templates +//--------------------------------------------------------------------+ + +// Length of template descriptor: 30 bytes +#define TUD_MTP_DESC_LEN (9 + 7 + 7 + 7) + +// Interface number, string index, EP event, EP event size, EP event polling, EP Out & EP In address, EP size +#define TUD_MTP_DESCRIPTOR(_itfnum, _stridx, _ep_evt, _ep_evt_size, _ep_evt_polling_interval, _epout, _epin, _epsize) \ + /* Interface */\ + 9, TUSB_DESC_INTERFACE, _itfnum, 0, 3, TUSB_CLASS_IMAGE, MTP_SUBCLASS_STILL_IMAGE, MTP_PROTOCOL_PIMA_15470, _stridx,\ + /* Endpoint Interrupt */\ + 7, TUSB_DESC_ENDPOINT, _ep_evt, TUSB_XFER_INTERRUPT, U16_TO_U8S_LE(_ep_evt_size), _ep_evt_polling_interval,\ + /* Endpoint Out */\ + 7, TUSB_DESC_ENDPOINT, _epout, TUSB_XFER_BULK, U16_TO_U8S_LE(_epsize), 0,\ + /* Endpoint In */\ + 7, TUSB_DESC_ENDPOINT, _epin, TUSB_XFER_BULK, U16_TO_U8S_LE(_epsize), 0 + + +//--------------------------------------------------------------------+ +// HID Descriptor Templates +//--------------------------------------------------------------------+ + +// Length of template descriptor: 25 bytes +#define TUD_HID_DESC_LEN (9 + 9 + 7) + +// HID Input only descriptor +// Interface number, string index, protocol, report descriptor len, EP In address, size & polling interval +#define TUD_HID_DESCRIPTOR(_itfnum, _stridx, _boot_protocol, _report_desc_len, _epin, _epsize, _ep_interval) \ + /* Interface */\ + 9, TUSB_DESC_INTERFACE, _itfnum, 0, 1, TUSB_CLASS_HID, (uint8_t)((_boot_protocol != HID_ITF_PROTOCOL_NONE) ? (uint8_t)HID_SUBCLASS_BOOT : 0u), _boot_protocol, _stridx,\ + /* HID descriptor */\ + 9, HID_DESC_TYPE_HID, U16_TO_U8S_LE(0x0111), 0, 1, HID_DESC_TYPE_REPORT, U16_TO_U8S_LE(_report_desc_len),\ + /* Endpoint In */\ + 7, TUSB_DESC_ENDPOINT, _epin, TUSB_XFER_INTERRUPT, U16_TO_U8S_LE(_epsize), _ep_interval + +// Length of template descriptor: 32 bytes +#define TUD_HID_INOUT_DESC_LEN (9 + 9 + 7 + 7) + +// HID Input & Output descriptor +// Interface number, string index, protocol, report descriptor len, EP OUT & IN address, size & polling interval +#define TUD_HID_INOUT_DESCRIPTOR(_itfnum, _stridx, _boot_protocol, _report_desc_len, _epout, _epin, _epsize, _ep_interval) \ + /* Interface */\ + 9, TUSB_DESC_INTERFACE, _itfnum, 0, 2, TUSB_CLASS_HID, (uint8_t)((_boot_protocol != HID_ITF_PROTOCOL_NONE) ? (uint8_t)HID_SUBCLASS_BOOT : 0u), _boot_protocol, _stridx,\ + /* HID descriptor */\ + 9, HID_DESC_TYPE_HID, U16_TO_U8S_LE(0x0111), 0, 1, HID_DESC_TYPE_REPORT, U16_TO_U8S_LE(_report_desc_len),\ + /* Endpoint Out */\ + 7, TUSB_DESC_ENDPOINT, _epout, TUSB_XFER_INTERRUPT, U16_TO_U8S_LE(_epsize), _ep_interval, \ + /* Endpoint In */\ + 7, TUSB_DESC_ENDPOINT, _epin, TUSB_XFER_INTERRUPT, U16_TO_U8S_LE(_epsize), _ep_interval + +//--------------------------------------------------------------------+ +// MIDI Descriptor Templates +// Note: MIDI v1.0 is based on Audio v1.0 +//--------------------------------------------------------------------+ + +#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_FUNC_PROTOCOL_CODE_UNDEF, _stridx,\ + /* AC Header */\ + 9, TUSB_DESC_CS_INTERFACE, AUDIO10_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_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 + 2 * TUD_MIDI_DESC_EP_LEN(_numcables)) + +#define TUD_MIDI_JACKID_IN_EMB(_cablenum) \ + (uint8_t)(((_cablenum) - 1) * 4 + 1) + +#define TUD_MIDI_JACKID_IN_EXT(_cablenum) \ + (uint8_t)(((_cablenum) - 1) * 4 + 2) + +#define TUD_MIDI_JACKID_OUT_EMB(_cablenum) \ + (uint8_t)(((_cablenum) - 1) * 4 + 3) + +#define TUD_MIDI_JACKID_OUT_EXT(_cablenum) \ + (uint8_t)(((_cablenum) - 1) * 4 + 4) + +#define TUD_MIDI_DESC_JACK_LEN (6 + 6 + 9 + 9) +#define TUD_MIDI_DESC_JACK_DESC(_cablenum, _stridx) \ + /* MS In Jack (Embedded) */\ + 6, TUSB_DESC_CS_INTERFACE, MIDI_CS_INTERFACE_IN_JACK, MIDI_JACK_EMBEDDED, TUD_MIDI_JACKID_IN_EMB(_cablenum), _stridx,\ + /* MS In Jack (External) */\ + 6, TUSB_DESC_CS_INTERFACE, MIDI_CS_INTERFACE_IN_JACK, MIDI_JACK_EXTERNAL, TUD_MIDI_JACKID_IN_EXT(_cablenum), _stridx,\ + /* MS Out Jack (Embedded), connected to In Jack External */\ + 9, TUSB_DESC_CS_INTERFACE, MIDI_CS_INTERFACE_OUT_JACK, MIDI_JACK_EMBEDDED, TUD_MIDI_JACKID_OUT_EMB(_cablenum), 1, TUD_MIDI_JACKID_IN_EXT(_cablenum), 1, _stridx,\ + /* MS Out Jack (External), connected to In Jack Embedded */\ + 9, TUSB_DESC_CS_INTERFACE, MIDI_CS_INTERFACE_OUT_JACK, MIDI_JACK_EXTERNAL, TUD_MIDI_JACKID_OUT_EXT(_cablenum), 1, TUD_MIDI_JACKID_IN_EMB(_cablenum), 1, _stridx + +#define TUD_MIDI_DESC_JACK(_cablenum) TUD_MIDI_DESC_JACK_DESC(_cablenum, 0) + +#define TUD_MIDI_DESC_EP_LEN(_numcables) (9 + 4 + (_numcables)) +#define TUD_MIDI_DESC_EP(_epout, _epsize, _numcables) \ + /* Endpoint: Note Audio v1.0's endpoint has 9 bytes instead of 7 */\ + 9, TUSB_DESC_ENDPOINT, _epout, TUSB_XFER_BULK, U16_TO_U8S_LE(_epsize), 0, 0, 0, \ + /* MS Endpoint (connected to embedded jack) */\ + (uint8_t)(4 + (_numcables)), TUSB_DESC_CS_ENDPOINT, MIDI_CS_ENDPOINT_GENERAL, _numcables + +// Length of template descriptor (88 bytes) +#define TUD_MIDI_DESC_LEN (TUD_MIDI_DESC_HEAD_LEN + TUD_MIDI_DESC_JACK_LEN + TUD_MIDI_DESC_EP_LEN(1) * 2) + +// MIDI simple descriptor +// - 1 Embedded Jack In connected to 1 External Jack Out +// - 1 Embedded Jack out connected to 1 External Jack In +#define TUD_MIDI_DESCRIPTOR(_itfnum, _stridx, _epout, _epin, _epsize) \ + TUD_MIDI_DESC_HEAD(_itfnum, _stridx, 1),\ + TUD_MIDI_DESC_JACK_DESC(1, 0),\ + TUD_MIDI_DESC_EP(_epout, _epsize, 1),\ + TUD_MIDI_JACKID_IN_EMB(1),\ + TUD_MIDI_DESC_EP(_epin, _epsize, 1),\ + TUD_MIDI_JACKID_OUT_EMB(1) + +//--------------------------------------------------------------------+ +// Audio Descriptor Templates +//--------------------------------------------------------------------+ + + +/* Audio v1.0 Descriptor Templates */ + +/* Standard AC Interface Descriptor UAC1 (4.3.1) */ +#define TUD_AUDIO10_DESC_STD_AC_LEN 9 +#define TUD_AUDIO10_DESC_STD_AC(_itfnum, _nEPs, _stridx) \ + TUD_AUDIO10_DESC_STD_AC_LEN, TUSB_DESC_INTERFACE, _itfnum, 0x00, _nEPs, TUSB_CLASS_AUDIO, AUDIO_SUBCLASS_CONTROL, AUDIO_INT_PROTOCOL_CODE_V1, _stridx + +/* Class-Specific AC Interface Header Descriptor UAC1 (4.3.2) */ +#define TUD_AUDIO10_DESC_CS_AC_LEN(_nintfs) (8 + (_nintfs)) +// Class-Specific AC Interface Header descriptor, take list of streaming interface numbers as variable arguments +#define TUD_AUDIO10_DESC_CS_AC(_bcdADC, _totallen, ...) \ + TUD_AUDIO10_DESC_CS_AC_LEN(TU_ARGS_NUM(__VA_ARGS__)), TUSB_DESC_CS_INTERFACE, AUDIO10_CS_AC_INTERFACE_HEADER, U16_TO_U8S_LE(_bcdADC), U16_TO_U8S_LE(_totallen + TUD_AUDIO10_DESC_CS_AC_LEN(TU_ARGS_NUM(__VA_ARGS__))), TU_ARGS_NUM(__VA_ARGS__), __VA_ARGS__ + +/* Input Terminal Descriptor UAC1 (4.3.2.1) */ +#define TUD_AUDIO10_DESC_INPUT_TERM_LEN 12 +#define TUD_AUDIO10_DESC_INPUT_TERM(_termid, _termtype, _assocTerm, _nchannels, _channelcfg, _idxchannelnames, _stridx) \ + TUD_AUDIO10_DESC_INPUT_TERM_LEN, TUSB_DESC_CS_INTERFACE, AUDIO10_CS_AC_INTERFACE_INPUT_TERMINAL, _termid, U16_TO_U8S_LE(_termtype), _assocTerm, _nchannels, U16_TO_U8S_LE(_channelcfg), _idxchannelnames, _stridx + +/* Output Terminal Descriptor UAC1 (4.3.2.2) */ +#define TUD_AUDIO10_DESC_OUTPUT_TERM_LEN 9 +#define TUD_AUDIO10_DESC_OUTPUT_TERM(_termid, _termtype, _assocTerm, _srcid, _stridx) \ + TUD_AUDIO10_DESC_OUTPUT_TERM_LEN, TUSB_DESC_CS_INTERFACE, AUDIO10_CS_AC_INTERFACE_OUTPUT_TERMINAL, _termid, U16_TO_U8S_LE(_termtype), _assocTerm, _srcid, _stridx + +/* Mixer Unit Descriptor UAC1 (4.3.2.3) - One Input Pin */ +#define TUD_AUDIO10_DESC_MIXER_UNIT_ONE_PIN_LEN(_ctrlsize) (11 + (_ctrlsize)) +#define TUD_AUDIO10_DESC_MIXER_UNIT_ONE_PIN(_unitid, _srcid, _nrchannels, _channelcfg, _idxchannelnames, _ctrlsize, _stridx, ...) \ + TUD_AUDIO10_DESC_MIXER_UNIT_ONE_PIN_LEN(_ctrlsize), TUSB_DESC_CS_INTERFACE, AUDIO10_CS_AC_INTERFACE_MIXER_UNIT, _unitid, 1, _srcid, _nrchannels, U16_TO_U8S_LE(_channelcfg), _idxchannelnames, __VA_ARGS__, _stridx + +/* Selector Unit Descriptor UAC1 (4.3.2.4) - One Input Pin */ +#define TUD_AUDIO10_DESC_SELECTOR_UNIT_ONE_PIN_LEN 7 +#define TUD_AUDIO10_DESC_SELECTOR_UNIT_ONE_PIN(_unitid, _srcid, _stridx) \ + TUD_AUDIO10_DESC_SELECTOR_UNIT_ONE_PIN_LEN, TUSB_DESC_CS_INTERFACE, AUDIO10_CS_AC_INTERFACE_SELECTOR_UNIT, _unitid, 1, _srcid, _stridx + +/* Feature Unit Descriptor UAC1 (4.3.2.5) - Variable Channels */ +#define TUD_AUDIO10_DESC_FEATURE_UNIT_LEN(_nchannels) (7 + ((_nchannels) + 1) * 2) +// Feature Unit descriptor, take list of control bitmaps for master channel + each channel as variable arguments +#define TUD_AUDIO10_DESC_FEATURE_UNIT(_unitid, _srcid, _stridx, ...) \ + TUD_AUDIO10_DESC_FEATURE_UNIT_LEN(TU_ARGS_NUM(__VA_ARGS__) - 1), TUSB_DESC_CS_INTERFACE, AUDIO10_CS_AC_INTERFACE_FEATURE_UNIT, _unitid, _srcid, 2, TU_ARGS_APPLY_EXPAND(U16_TO_U8S_LE, __VA_ARGS__), _stridx + +/* Standard AS Interface Descriptor UAC1 (4.5.1) */ +#define TUD_AUDIO10_DESC_STD_AS_LEN 9 +#define TUD_AUDIO10_DESC_STD_AS_INT(_itfnum, _altset, _nEPs, _stridx) \ + TUD_AUDIO10_DESC_STD_AS_LEN, TUSB_DESC_INTERFACE, _itfnum, _altset, _nEPs, TUSB_CLASS_AUDIO, AUDIO_SUBCLASS_STREAMING, AUDIO_INT_PROTOCOL_CODE_V1, _stridx + +/* Class-Specific AS Interface Descriptor UAC1 (4.5.2) */ +#define TUD_AUDIO10_DESC_CS_AS_INT_LEN 7 +#define TUD_AUDIO10_DESC_CS_AS_INT(_termid, _delay, _formattype) \ + TUD_AUDIO10_DESC_CS_AS_INT_LEN, TUSB_DESC_CS_INTERFACE, AUDIO10_CS_AS_INTERFACE_AS_GENERAL, _termid, _delay, U16_TO_U8S_LE(_formattype) + +/* Type I Format Type Descriptor UAC1 (2.2.5) */ +#define TUD_AUDIO10_DESC_TYPE_I_FORMAT_LEN(_nfreqs) (8 + (_nfreqs)*3) +// Type I Format descriptor, take list of sample rates in Hz as variable arguments +#define TUD_AUDIO10_DESC_TYPE_I_FORMAT(_nrchannels, _subframesize, _bitresolution, ...) \ + TUD_AUDIO10_DESC_TYPE_I_FORMAT_LEN(TU_ARGS_NUM(__VA_ARGS__)), TUSB_DESC_CS_INTERFACE, AUDIO10_CS_AS_INTERFACE_FORMAT_TYPE, AUDIO10_FORMAT_TYPE_I, _nrchannels, _subframesize, _bitresolution, TU_ARGS_NUM(__VA_ARGS__), TU_ARGS_APPLY_EXPAND(U24_TO_U8S_LE, __VA_ARGS__) + +/* Standard AS Isochronous Audio Data Endpoint Descriptor UAC1 (4.6.1.1) */ +#define TUD_AUDIO10_DESC_STD_AS_ISO_EP_LEN 9 +#define TUD_AUDIO10_DESC_STD_AS_ISO_EP(_ep, _attr, _maxEPsize, _interval, _sync_ep) \ + TUD_AUDIO10_DESC_STD_AS_ISO_EP_LEN, TUSB_DESC_ENDPOINT, _ep, _attr, U16_TO_U8S_LE(_maxEPsize), _interval, 0x00, _sync_ep + +/* Class-Specific AS Isochronous Audio Data Endpoint Descriptor UAC1 (4.6.1.2) */ +#define TUD_AUDIO10_DESC_CS_AS_ISO_EP_LEN 7 +#define TUD_AUDIO10_DESC_CS_AS_ISO_EP(_attr, _lockdelayunits, _lockdelay) \ + TUD_AUDIO10_DESC_CS_AS_ISO_EP_LEN, TUSB_DESC_CS_ENDPOINT, AUDIO10_CS_EP_SUBTYPE_GENERAL, _attr, _lockdelayunits, U16_TO_U8S_LE(_lockdelay) + +/* Standard AS Isochronous Synch Endpoint Descriptor UAC1 (4.6.2.1) */ +#define TUD_AUDIO10_DESC_STD_AS_ISO_SYNC_EP_LEN 9 +#define TUD_AUDIO10_DESC_STD_AS_ISO_SYNC_EP(_ep, _bRefresh) \ + TUD_AUDIO10_DESC_STD_AS_ISO_SYNC_EP_LEN, TUSB_DESC_ENDPOINT, _ep, TUSB_XFER_ISOCHRONOUS, U16_TO_U8S_LE(3), 1, _bRefresh, 0x00 + +/* Standard AC Interrupt Endpoint Descriptor UAC1 (4.4.2) */ +#define TUD_AUDIO10_DESC_STD_AC_INT_EP_LEN 9 +#define TUD_AUDIO10_DESC_STD_AC_INT_EP(_ep, _interval) \ + TUD_AUDIO10_DESC_STD_AC_INT_EP_LEN, TUSB_DESC_ENDPOINT, _ep, TUSB_XFER_INTERRUPT, U16_TO_U8S_LE(2), _interval, 0x00, 0x00 + +// AUDIO simple descriptor templates for UAC1 + +// AUDIO simple descriptor (UAC1) for 1 microphone input +// - 1 Input Terminal, 1 Feature Unit (Mute and Volume Control), 1 Output Terminal + +#define TUD_AUDIO10_MIC_ONE_CH_DESC_LEN(_nfreqs) (\ + + TUD_AUDIO10_DESC_STD_AC_LEN\ + + TUD_AUDIO10_DESC_CS_AC_LEN(1)\ + + TUD_AUDIO10_DESC_INPUT_TERM_LEN\ + + TUD_AUDIO10_DESC_OUTPUT_TERM_LEN\ + + TUD_AUDIO10_DESC_FEATURE_UNIT_LEN(1)\ + + TUD_AUDIO10_DESC_STD_AS_LEN\ + + TUD_AUDIO10_DESC_STD_AS_LEN\ + + TUD_AUDIO10_DESC_CS_AS_INT_LEN\ + + TUD_AUDIO10_DESC_TYPE_I_FORMAT_LEN(_nfreqs)\ + + TUD_AUDIO10_DESC_STD_AS_ISO_EP_LEN\ + + TUD_AUDIO10_DESC_CS_AS_ISO_EP_LEN) + +#define TUD_AUDIO10_MIC_ONE_CH_DESCRIPTOR(_itfnum, _stridx, _nBytesPerSample, _nBitsUsedPerSample, _epin, _epsize, ...) \ + /* Standard AC Interface Descriptor(4.3.1) */\ + TUD_AUDIO10_DESC_STD_AC(/*_itfnum*/ _itfnum, /*_nEPs*/ 0x00, /*_stridx*/ _stridx),\ + /* Class-Specific AC Interface Header Descriptor(4.3.2) */\ + TUD_AUDIO10_DESC_CS_AC(/*_bcdADC*/ 0x0100, /*_totallen*/ (TUD_AUDIO10_DESC_INPUT_TERM_LEN+TUD_AUDIO10_DESC_OUTPUT_TERM_LEN+TUD_AUDIO10_DESC_FEATURE_UNIT_LEN(1)), /*_itf*/ ((_itfnum)+1)),\ + /* Input Terminal Descriptor(4.3.2.1) */\ + TUD_AUDIO10_DESC_INPUT_TERM(/*_termid*/ 0x01, /*_termtype*/ AUDIO_TERM_TYPE_IN_GENERIC_MIC, /*_assocTerm*/ 0x03, /*_nchannels*/ 0x01, /*_channelcfg*/ AUDIO10_CHANNEL_CONFIG_NON_PREDEFINED, /*_idxchannelnames*/ 0x00, /*_stridx*/ 0x00),\ + /* Output Terminal Descriptor(4.3.2.2) */\ + TUD_AUDIO10_DESC_OUTPUT_TERM(/*_termid*/ 0x03, /*_termtype*/ AUDIO_TERM_TYPE_USB_STREAMING, /*_assocTerm*/ 0x01, /*_srcid*/ 0x02, /*_stridx*/ 0x00),\ + /* Feature Unit Descriptor(4.3.2.5) */\ + TUD_AUDIO10_DESC_FEATURE_UNIT(/*_unitid*/ 0x02, /*_srcid*/ 0x01, /*_stridx*/ 0x00, /*_ctrlmaster*/ (AUDIO10_FU_CONTROL_BM_MUTE | AUDIO10_FU_CONTROL_BM_VOLUME), /*_ctrlch1*/ (AUDIO10_FU_CONTROL_BM_MUTE | AUDIO10_FU_CONTROL_BM_VOLUME)),\ + /* Standard AS Interface Descriptor(4.5.1) */\ + /* Interface 1, Alternate 0 - default alternate setting with 0 bandwidth */\ + TUD_AUDIO10_DESC_STD_AS_INT(/*_itfnum*/ (uint8_t)((_itfnum)+1), /*_altset*/ 0x00, /*_nEPs*/ 0x00, /*_stridx*/ 0x00),\ + /* Standard AS Interface Descriptor(4.5.1) */\ + /* Interface 1, Alternate 1 - alternate interface for data streaming */\ + TUD_AUDIO10_DESC_STD_AS_INT(/*_itfnum*/ (uint8_t)((_itfnum)+1), /*_altset*/ 0x01, /*_nEPs*/ 0x01, /*_stridx*/ 0x00),\ + /* Class-Specific AS Interface Descriptor(4.5.2) */\ + TUD_AUDIO10_DESC_CS_AS_INT(/*_termid*/ 0x03, /*_delay*/ 0x01, /*_formattype*/ AUDIO10_DATA_FORMAT_TYPE_I_PCM),\ + /* Type I Format Type Descriptor(2.2.5) */\ + TUD_AUDIO10_DESC_TYPE_I_FORMAT(/*_nrchannels*/ 0x01, /*_subframesize*/ _nBytesPerSample, /*_bitresolution*/ _nBitsUsedPerSample, /*_freq*/ __VA_ARGS__),\ + /* Standard AS Isochronous Audio Data Endpoint Descriptor(4.6.1.1) */\ + TUD_AUDIO10_DESC_STD_AS_ISO_EP(/*_ep*/ _epin, /*_attr*/ (uint8_t) ((uint8_t)TUSB_XFER_ISOCHRONOUS | (uint8_t)TUSB_ISO_EP_ATT_ASYNCHRONOUS), /*_maxEPsize*/ _epsize, /*_interval*/ 0x01, /* _sync_ep */ 0x00),\ + /* Class-Specific AS Isochronous Audio Data Endpoint Descriptor(4.6.1.2) */\ + TUD_AUDIO10_DESC_CS_AS_ISO_EP(/*_attr*/ AUDIO10_CS_AS_ISO_DATA_EP_ATT_SAMPLING_FRQ, /*_lockdelayunits*/ AUDIO10_CS_AS_ISO_DATA_EP_LOCK_DELAY_UNIT_MILLISEC, /*_lockdelay*/ 0x0001) + +/* Audio v2.0 Descriptor Templates */ + +/* Standard Interface Association Descriptor (IAD) */ +#define TUD_AUDIO20_DESC_IAD_LEN 8 +#define TUD_AUDIO20_DESC_IAD(_firstitf, _nitfs, _stridx) \ + TUD_AUDIO20_DESC_IAD_LEN, TUSB_DESC_INTERFACE_ASSOCIATION, _firstitf, _nitfs, TUSB_CLASS_AUDIO, AUDIO_FUNCTION_SUBCLASS_UNDEFINED, AUDIO_FUNC_PROTOCOL_CODE_V2, _stridx + +/* Standard AC Interface Descriptor(4.7.1) */ +#define TUD_AUDIO20_DESC_STD_AC_LEN 9 +#define TUD_AUDIO20_DESC_STD_AC(_itfnum, _nEPs, _stridx) /* _nEPs is 0 or 1 */\ + TUD_AUDIO20_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_AUDIO20_DESC_CS_AC_LEN 9 +#define TUD_AUDIO20_DESC_CS_AC(_bcdADC, _category, _totallen, _ctrl) /* _bcdADC : Audio Device Class Specification Release Number in Binary-Coded Decimal, _category : see audio20_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_AUDIO20_DESC_CS_AC_LEN, we already do this here*/ \ + TUD_AUDIO20_DESC_CS_AC_LEN, TUSB_DESC_CS_INTERFACE, AUDIO20_CS_AC_INTERFACE_HEADER, U16_TO_U8S_LE(_bcdADC), _category, U16_TO_U8S_LE(_totallen + TUD_AUDIO20_DESC_CS_AC_LEN), _ctrl + +/* Clock Source Descriptor(4.7.2.1) */ +#define TUD_AUDIO20_DESC_CLK_SRC_LEN 8 +#define TUD_AUDIO20_DESC_CLK_SRC(_clkid, _attr, _ctrl, _assocTerm, _stridx) \ + TUD_AUDIO20_DESC_CLK_SRC_LEN, TUSB_DESC_CS_INTERFACE, AUDIO20_CS_AC_INTERFACE_CLOCK_SOURCE, _clkid, _attr, _ctrl, _assocTerm, _stridx + +/* Input Terminal Descriptor(4.7.2.4) */ +#define TUD_AUDIO20_DESC_INPUT_TERM_LEN 17 +#define TUD_AUDIO20_DESC_INPUT_TERM(_termid, _termtype, _assocTerm, _clkid, _nchannelslogical, _channelcfg, _idxchannelnames, _ctrl, _stridx) \ + TUD_AUDIO20_DESC_INPUT_TERM_LEN, TUSB_DESC_CS_INTERFACE, AUDIO20_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_AUDIO20_DESC_OUTPUT_TERM_LEN 12 +#define TUD_AUDIO20_DESC_OUTPUT_TERM(_termid, _termtype, _assocTerm, _srcid, _clkid, _ctrl, _stridx) \ + TUD_AUDIO20_DESC_OUTPUT_TERM_LEN, TUSB_DESC_CS_INTERFACE, AUDIO20_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) */ +#define TUD_AUDIO20_DESC_FEATURE_UNIT_LEN(_nchannels) (6 + ((_nchannels) + 1) * 4) +#define TUD_AUDIO20_DESC_FEATURE_UNIT(_unitid, _srcid, _stridx, ...) \ + TUD_AUDIO20_DESC_FEATURE_UNIT_LEN(TU_ARGS_NUM(__VA_ARGS__) - 1), TUSB_DESC_CS_INTERFACE, AUDIO20_CS_AC_INTERFACE_FEATURE_UNIT, _unitid, _srcid, TU_ARGS_APPLY_EXPAND(U32_TO_U8S_LE, __VA_ARGS__), _stridx + +/* Standard AC Interrupt Endpoint Descriptor(4.8.2.1) */ +#define TUD_AUDIO20_DESC_STD_AC_INT_EP_LEN 7 +#define TUD_AUDIO20_DESC_STD_AC_INT_EP(_ep, _interval) \ + TUD_AUDIO20_DESC_STD_AC_INT_EP_LEN, TUSB_DESC_ENDPOINT, _ep, TUSB_XFER_INTERRUPT, U16_TO_U8S_LE(6), _interval + +/* Standard AS Interface Descriptor(4.9.1) */ +#define TUD_AUDIO20_DESC_STD_AS_LEN 9 +#define TUD_AUDIO20_DESC_STD_AS_INT(_itfnum, _altset, _nEPs, _stridx) \ + TUD_AUDIO20_DESC_STD_AS_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_AUDIO20_DESC_CS_AS_INT_LEN 16 +#define TUD_AUDIO20_DESC_CS_AS_INT(_termid, _ctrl, _formattype, _formats, _nchannelsphysical, _channelcfg, _stridx) \ + TUD_AUDIO20_DESC_CS_AS_INT_LEN, TUSB_DESC_CS_INTERFACE, AUDIO20_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_AUDIO20_DESC_TYPE_I_FORMAT_LEN 6 +#define TUD_AUDIO20_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_AUDIO20_DESC_TYPE_I_FORMAT_LEN, TUSB_DESC_CS_INTERFACE, AUDIO20_CS_AS_INTERFACE_FORMAT_TYPE, AUDIO20_FORMAT_TYPE_I, _subslotsize, _bitresolution + +/* Standard AS Isochronous Audio Data Endpoint Descriptor(4.10.1.1) */ +#define TUD_AUDIO20_DESC_STD_AS_ISO_EP_LEN 7 +#define TUD_AUDIO20_DESC_STD_AS_ISO_EP(_ep, _attr, _maxEPsize, _interval) \ + TUD_AUDIO20_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_AUDIO20_DESC_CS_AS_ISO_EP_LEN 8 +#define TUD_AUDIO20_DESC_CS_AS_ISO_EP(_attr, _ctrl, _lockdelayunit, _lockdelay) \ + TUD_AUDIO20_DESC_CS_AS_ISO_EP_LEN, TUSB_DESC_CS_ENDPOINT, AUDIO20_CS_EP_SUBTYPE_GENERAL, _attr, _ctrl, _lockdelayunit, U16_TO_U8S_LE(_lockdelay) + +/* Standard AS Isochronous Feedback Endpoint Descriptor(4.10.2.1) */ +#define TUD_AUDIO20_DESC_STD_AS_ISO_FB_EP_LEN 7 +#define TUD_AUDIO20_DESC_STD_AS_ISO_FB_EP(_ep, _epsize, _interval) \ + TUD_AUDIO20_DESC_STD_AS_ISO_FB_EP_LEN, TUSB_DESC_ENDPOINT, _ep, (uint8_t) ((uint8_t)TUSB_XFER_ISOCHRONOUS | (uint8_t)TUSB_ISO_EP_ATT_NO_SYNC | (uint8_t)TUSB_ISO_EP_ATT_EXPLICIT_FB), U16_TO_U8S_LE(_epsize), _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_AUDIO20_MIC_ONE_CH_DESC_LEN (TUD_AUDIO20_DESC_IAD_LEN\ + + TUD_AUDIO20_DESC_STD_AC_LEN\ + + TUD_AUDIO20_DESC_CS_AC_LEN\ + + TUD_AUDIO20_DESC_CLK_SRC_LEN\ + + TUD_AUDIO20_DESC_INPUT_TERM_LEN\ + + TUD_AUDIO20_DESC_OUTPUT_TERM_LEN\ + + TUD_AUDIO20_DESC_FEATURE_UNIT_LEN(1)\ + + TUD_AUDIO20_DESC_STD_AS_LEN\ + + TUD_AUDIO20_DESC_STD_AS_LEN\ + + TUD_AUDIO20_DESC_CS_AS_INT_LEN\ + + TUD_AUDIO20_DESC_TYPE_I_FORMAT_LEN\ + + TUD_AUDIO20_DESC_STD_AS_ISO_EP_LEN\ + + TUD_AUDIO20_DESC_CS_AS_ISO_EP_LEN) + +#define TUD_AUDIO20_MIC_ONE_CH_DESC_N_AS_INT 1 // Number of AS interfaces + +#define TUD_AUDIO20_MIC_ONE_CH_DESCRIPTOR(_itfnum, _stridx, _nBytesPerSample, _nBitsUsedPerSample, _epin, _epsize) \ + /* Standard Interface Association Descriptor (IAD) */\ + TUD_AUDIO20_DESC_IAD(/*_firstitf*/ _itfnum, /*_nitfs*/ 0x02, /*_stridx*/ 0x00),\ + /* Standard AC Interface Descriptor(4.7.1) */\ + TUD_AUDIO20_DESC_STD_AC(/*_itfnum*/ _itfnum, /*_nEPs*/ 0x00, /*_stridx*/ _stridx),\ + /* Class-Specific AC Interface Header Descriptor(4.7.2) */\ + TUD_AUDIO20_DESC_CS_AC(/*_bcdADC*/ 0x0200, /*_category*/ AUDIO20_FUNC_MICROPHONE, /*_totallen*/ TUD_AUDIO20_DESC_CLK_SRC_LEN+TUD_AUDIO20_DESC_INPUT_TERM_LEN+TUD_AUDIO20_DESC_OUTPUT_TERM_LEN+TUD_AUDIO20_DESC_FEATURE_UNIT_LEN(1), /*_ctrl*/ AUDIO20_CS_AS_INTERFACE_CTRL_LATENCY_POS),\ + /* Clock Source Descriptor(4.7.2.1) */\ + TUD_AUDIO20_DESC_CLK_SRC(/*_clkid*/ 0x04, /*_attr*/ AUDIO20_CLOCK_SOURCE_ATT_INT_FIX_CLK, /*_ctrl*/ (AUDIO20_CTRL_R << AUDIO20_CLOCK_SOURCE_CTRL_CLK_FRQ_POS), /*_assocTerm*/ 0x01, /*_stridx*/ 0x00),\ + /* Input Terminal Descriptor(4.7.2.4) */\ + TUD_AUDIO20_DESC_INPUT_TERM(/*_termid*/ 0x01, /*_termtype*/ AUDIO_TERM_TYPE_IN_GENERIC_MIC, /*_assocTerm*/ 0x03, /*_clkid*/ 0x04, /*_nchannelslogical*/ 0x01, /*_channelcfg*/ AUDIO20_CHANNEL_CONFIG_NON_PREDEFINED, /*_idxchannelnames*/ 0x00, /*_ctrl*/ AUDIO20_CTRL_R << AUDIO20_IN_TERM_CTRL_CONNECTOR_POS, /*_stridx*/ 0x00),\ + /* Output Terminal Descriptor(4.7.2.5) */\ + TUD_AUDIO20_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_AUDIO20_DESC_FEATURE_UNIT(/*_unitid*/ 0x02, /*_srcid*/ 0x01, /*_stridx*/ 0x00, /*_ctrlch0master*/ AUDIO20_CTRL_RW << AUDIO20_FEATURE_UNIT_CTRL_MUTE_POS | AUDIO20_CTRL_RW << AUDIO20_FEATURE_UNIT_CTRL_VOLUME_POS, /*_ctrlch1*/ AUDIO20_CTRL_RW << AUDIO20_FEATURE_UNIT_CTRL_MUTE_POS | AUDIO20_CTRL_RW << AUDIO20_FEATURE_UNIT_CTRL_VOLUME_POS),\ + /* Standard AS Interface Descriptor(4.9.1) */\ + /* Interface 1, Alternate 0 - default alternate setting with 0 bandwidth */\ + TUD_AUDIO20_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_AUDIO20_DESC_STD_AS_INT(/*_itfnum*/ (uint8_t)((_itfnum)+1), /*_altset*/ 0x01, /*_nEPs*/ 0x01, /*_stridx*/ 0x00),\ + /* Class-Specific AS Interface Descriptor(4.9.2) */\ + TUD_AUDIO20_DESC_CS_AS_INT(/*_termid*/ 0x03, /*_ctrl*/ AUDIO20_CTRL_NONE, /*_formattype*/ AUDIO20_FORMAT_TYPE_I, /*_formats*/ AUDIO20_DATA_FORMAT_TYPE_I_PCM, /*_nchannelsphysical*/ 0x01, /*_channelcfg*/ AUDIO20_CHANNEL_CONFIG_NON_PREDEFINED, /*_stridx*/ 0x00),\ + /* Type I Format Type Descriptor(2.3.1.6 - Audio Formats) */\ + TUD_AUDIO20_DESC_TYPE_I_FORMAT(_nBytesPerSample, _nBitsUsedPerSample),\ + /* Standard AS Isochronous Audio Data Endpoint Descriptor(4.10.1.1) */\ + TUD_AUDIO20_DESC_STD_AS_ISO_EP(/*_ep*/ _epin, /*_attr*/ (uint8_t) ((uint8_t)TUSB_XFER_ISOCHRONOUS | (uint8_t)TUSB_ISO_EP_ATT_ASYNCHRONOUS | (uint8_t)TUSB_ISO_EP_ATT_DATA), /*_maxEPsize*/ _epsize, /*_interval*/ 0x01),\ + /* Class-Specific AS Isochronous Audio Data Endpoint Descriptor(4.10.1.2) */\ + TUD_AUDIO20_DESC_CS_AS_ISO_EP(/*_attr*/ AUDIO20_CS_AS_ISO_DATA_EP_ATT_NON_MAX_PACKETS_OK, /*_ctrl*/ AUDIO20_CTRL_NONE, /*_lockdelayunit*/ AUDIO20_CS_AS_ISO_DATA_EP_LOCK_DELAY_UNIT_UNDEFINED, /*_lockdelay*/ 0x0000) + +// AUDIO simple descriptor (UAC2) for 4 microphone input +// - 1 Input Terminal, 1 Feature Unit (Mute and Volume Control), 1 Output Terminal, 1 Clock Source + +#define TUD_AUDIO20_MIC_FOUR_CH_DESC_LEN (TUD_AUDIO20_DESC_IAD_LEN\ + + TUD_AUDIO20_DESC_STD_AC_LEN\ + + TUD_AUDIO20_DESC_CS_AC_LEN\ + + TUD_AUDIO20_DESC_CLK_SRC_LEN\ + + TUD_AUDIO20_DESC_INPUT_TERM_LEN\ + + TUD_AUDIO20_DESC_OUTPUT_TERM_LEN\ + + TUD_AUDIO20_DESC_FEATURE_UNIT_LEN(4)\ + + TUD_AUDIO20_DESC_STD_AS_LEN\ + + TUD_AUDIO20_DESC_STD_AS_LEN\ + + TUD_AUDIO20_DESC_CS_AS_INT_LEN\ + + TUD_AUDIO20_DESC_TYPE_I_FORMAT_LEN\ + + TUD_AUDIO20_DESC_STD_AS_ISO_EP_LEN\ + + TUD_AUDIO20_DESC_CS_AS_ISO_EP_LEN) + +#define TUD_AUDIO20_MIC_FOUR_CH_DESC_N_AS_INT 1 // Number of AS interfaces + +#define TUD_AUDIO20_MIC_FOUR_CH_DESCRIPTOR(_itfnum, _stridx, _nBytesPerSample, _nBitsUsedPerSample, _epin, _epsize) \ + /* Standard Interface Association Descriptor (IAD) */\ + TUD_AUDIO20_DESC_IAD(/*_firstitf*/ _itfnum, /*_nitfs*/ 0x02, /*_stridx*/ 0x00),\ + /* Standard AC Interface Descriptor(4.7.1) */\ + TUD_AUDIO20_DESC_STD_AC(/*_itfnum*/ _itfnum, /*_nEPs*/ 0x00, /*_stridx*/ _stridx),\ + /* Class-Specific AC Interface Header Descriptor(4.7.2) */\ + TUD_AUDIO20_DESC_CS_AC(/*_bcdADC*/ 0x0200, /*_category*/ AUDIO20_FUNC_MICROPHONE, /*_totallen*/ TUD_AUDIO20_DESC_CLK_SRC_LEN+TUD_AUDIO20_DESC_INPUT_TERM_LEN+TUD_AUDIO20_DESC_OUTPUT_TERM_LEN+TUD_AUDIO20_DESC_FEATURE_UNIT_LEN(4), /*_ctrl*/ AUDIO20_CS_AS_INTERFACE_CTRL_LATENCY_POS),\ + /* Clock Source Descriptor(4.7.2.1) */\ + TUD_AUDIO20_DESC_CLK_SRC(/*_clkid*/ 0x04, /*_attr*/ AUDIO20_CLOCK_SOURCE_ATT_INT_FIX_CLK, /*_ctrl*/ (AUDIO20_CTRL_R << AUDIO20_CLOCK_SOURCE_CTRL_CLK_FRQ_POS), /*_assocTerm*/ 0x01, /*_stridx*/ 0x00),\ + /* Input Terminal Descriptor(4.7.2.4) */\ + TUD_AUDIO20_DESC_INPUT_TERM(/*_termid*/ 0x01, /*_termtype*/ AUDIO_TERM_TYPE_IN_GENERIC_MIC, /*_assocTerm*/ 0x03, /*_clkid*/ 0x04, /*_nchannelslogical*/ 0x04, /*_channelcfg*/ AUDIO20_CHANNEL_CONFIG_NON_PREDEFINED, /*_idxchannelnames*/ 0x00, /*_ctrl*/ AUDIO20_CTRL_R << AUDIO20_IN_TERM_CTRL_CONNECTOR_POS, /*_stridx*/ 0x00),\ + /* Output Terminal Descriptor(4.7.2.5) */\ + TUD_AUDIO20_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_AUDIO20_DESC_FEATURE_UNIT(/*_unitid*/ 0x02, /*_srcid*/ 0x01, /*_stridx*/ 0x00, /*_ctrlch0master*/ AUDIO20_CTRL_RW << AUDIO20_FEATURE_UNIT_CTRL_MUTE_POS | AUDIO20_CTRL_RW << AUDIO20_FEATURE_UNIT_CTRL_VOLUME_POS, /*_ctrlch1*/ AUDIO20_CTRL_RW << AUDIO20_FEATURE_UNIT_CTRL_MUTE_POS | AUDIO20_CTRL_RW << AUDIO20_FEATURE_UNIT_CTRL_VOLUME_POS, /*_ctrlch2*/ AUDIO20_CTRL_RW << AUDIO20_FEATURE_UNIT_CTRL_MUTE_POS | AUDIO20_CTRL_RW << AUDIO20_FEATURE_UNIT_CTRL_VOLUME_POS, /*_ctrlch3*/ AUDIO20_CTRL_RW << AUDIO20_FEATURE_UNIT_CTRL_MUTE_POS | AUDIO20_CTRL_RW << AUDIO20_FEATURE_UNIT_CTRL_VOLUME_POS, /*_ctrlch4*/ AUDIO20_CTRL_RW << AUDIO20_FEATURE_UNIT_CTRL_MUTE_POS | AUDIO20_CTRL_RW << AUDIO20_FEATURE_UNIT_CTRL_VOLUME_POS),\ + /* Standard AS Interface Descriptor(4.9.1) */\ + /* Interface 1, Alternate 0 - default alternate setting with 0 bandwidth */\ + TUD_AUDIO20_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_AUDIO20_DESC_STD_AS_INT(/*_itfnum*/ (uint8_t)((_itfnum)+1), /*_altset*/ 0x01, /*_nEPs*/ 0x01, /*_stridx*/ 0x00),\ + /* Class-Specific AS Interface Descriptor(4.9.2) */\ + TUD_AUDIO20_DESC_CS_AS_INT(/*_termid*/ 0x03, /*_ctrl*/ AUDIO20_CTRL_NONE, /*_formattype*/ AUDIO20_FORMAT_TYPE_I, /*_formats*/ AUDIO20_DATA_FORMAT_TYPE_I_PCM, /*_nchannelsphysical*/ 0x04, /*_channelcfg*/ AUDIO20_CHANNEL_CONFIG_NON_PREDEFINED, /*_stridx*/ 0x00),\ + /* Type I Format Type Descriptor(2.3.1.6 - Audio Formats) */\ + TUD_AUDIO20_DESC_TYPE_I_FORMAT(_nBytesPerSample, _nBitsUsedPerSample),\ + /* Standard AS Isochronous Audio Data Endpoint Descriptor(4.10.1.1) */\ + TUD_AUDIO20_DESC_STD_AS_ISO_EP(/*_ep*/ _epin, /*_attr*/ (uint8_t) ((uint8_t)TUSB_XFER_ISOCHRONOUS | (uint8_t)TUSB_ISO_EP_ATT_ASYNCHRONOUS | (uint8_t)TUSB_ISO_EP_ATT_DATA), /*_maxEPsize*/ _epsize, /*_interval*/ 0x01),\ + /* Class-Specific AS Isochronous Audio Data Endpoint Descriptor(4.10.1.2) */\ + TUD_AUDIO20_DESC_CS_AS_ISO_EP(/*_attr*/ AUDIO20_CS_AS_ISO_DATA_EP_ATT_NON_MAX_PACKETS_OK, /*_ctrl*/ AUDIO20_CTRL_NONE, /*_lockdelayunit*/ AUDIO20_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_AUDIO20_SPEAKER_MONO_FB_DESC_LEN (TUD_AUDIO20_DESC_IAD_LEN\ + + TUD_AUDIO20_DESC_STD_AC_LEN\ + + TUD_AUDIO20_DESC_CS_AC_LEN\ + + TUD_AUDIO20_DESC_CLK_SRC_LEN\ + + TUD_AUDIO20_DESC_INPUT_TERM_LEN\ + + TUD_AUDIO20_DESC_OUTPUT_TERM_LEN\ + + TUD_AUDIO20_DESC_FEATURE_UNIT_LEN(1)\ + + TUD_AUDIO20_DESC_STD_AS_LEN\ + + TUD_AUDIO20_DESC_STD_AS_LEN\ + + TUD_AUDIO20_DESC_CS_AS_INT_LEN\ + + TUD_AUDIO20_DESC_TYPE_I_FORMAT_LEN\ + + TUD_AUDIO20_DESC_STD_AS_ISO_EP_LEN\ + + TUD_AUDIO20_DESC_CS_AS_ISO_EP_LEN\ + + TUD_AUDIO20_DESC_STD_AS_ISO_FB_EP_LEN) + +#define TUD_AUDIO20_SPEAKER_MONO_FB_DESCRIPTOR(_itfnum, _stridx, _nBytesPerSample, _nBitsUsedPerSample, _epout, _epoutsize, _epfb, _epfbsize) \ + /* Standard Interface Association Descriptor (IAD) */\ + TUD_AUDIO20_DESC_IAD(/*_firstitf*/ _itfnum, /*_nitfs*/ 0x02, /*_stridx*/ 0x00),\ + /* Standard AC Interface Descriptor(4.7.1) */\ + TUD_AUDIO20_DESC_STD_AC(/*_itfnum*/ _itfnum, /*_nEPs*/ 0x00, /*_stridx*/ _stridx),\ + /* Class-Specific AC Interface Header Descriptor(4.7.2) */\ + TUD_AUDIO20_DESC_CS_AC(/*_bcdADC*/ 0x0200, /*_category*/ AUDIO20_FUNC_DESKTOP_SPEAKER, /*_totallen*/ TUD_AUDIO20_DESC_CLK_SRC_LEN+TUD_AUDIO20_DESC_INPUT_TERM_LEN+TUD_AUDIO20_DESC_OUTPUT_TERM_LEN+TUD_AUDIO20_DESC_FEATURE_UNIT_LEN(1), /*_ctrl*/ AUDIO20_CS_AS_INTERFACE_CTRL_LATENCY_POS),\ + /* Clock Source Descriptor(4.7.2.1) */\ + TUD_AUDIO20_DESC_CLK_SRC(/*_clkid*/ 0x04, /*_attr*/ AUDIO20_CLOCK_SOURCE_ATT_INT_FIX_CLK, /*_ctrl*/ (AUDIO20_CTRL_R << AUDIO20_CLOCK_SOURCE_CTRL_CLK_FRQ_POS), /*_assocTerm*/ 0x01, /*_stridx*/ 0x00),\ + /* Input Terminal Descriptor(4.7.2.4) */\ + TUD_AUDIO20_DESC_INPUT_TERM(/*_termid*/ 0x01, /*_termtype*/ AUDIO_TERM_TYPE_USB_STREAMING, /*_assocTerm*/ 0x00, /*_clkid*/ 0x04, /*_nchannelslogical*/ 0x01, /*_channelcfg*/ AUDIO20_CHANNEL_CONFIG_NON_PREDEFINED, /*_idxchannelnames*/ 0x00, /*_ctrl*/ 0 * (AUDIO20_CTRL_R << AUDIO20_IN_TERM_CTRL_CONNECTOR_POS), /*_stridx*/ 0x00),\ + /* Output Terminal Descriptor(4.7.2.5) */\ + TUD_AUDIO20_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_AUDIO20_DESC_FEATURE_UNIT(/*_unitid*/ 0x02, /*_srcid*/ 0x01, /*_stridx*/ 0x00, /*_ctrlch0master*/ AUDIO20_CTRL_RW << AUDIO20_FEATURE_UNIT_CTRL_MUTE_POS | AUDIO20_CTRL_RW << AUDIO20_FEATURE_UNIT_CTRL_VOLUME_POS, /*_ctrlch1*/ AUDIO20_CTRL_RW << AUDIO20_FEATURE_UNIT_CTRL_MUTE_POS | AUDIO20_CTRL_RW << AUDIO20_FEATURE_UNIT_CTRL_VOLUME_POS),\ + /* Standard AS Interface Descriptor(4.9.1) */\ + /* Interface 1, Alternate 0 - default alternate setting with 0 bandwidth */\ + TUD_AUDIO20_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_AUDIO20_DESC_STD_AS_INT(/*_itfnum*/ (uint8_t)((_itfnum) + 1), /*_altset*/ 0x01, /*_nEPs*/ 0x02, /*_stridx*/ 0x00),\ + /* Class-Specific AS Interface Descriptor(4.9.2) */\ + TUD_AUDIO20_DESC_CS_AS_INT(/*_termid*/ 0x01, /*_ctrl*/ AUDIO20_CTRL_NONE, /*_formattype*/ AUDIO20_FORMAT_TYPE_I, /*_formats*/ AUDIO20_DATA_FORMAT_TYPE_I_PCM, /*_nchannelsphysical*/ 0x01, /*_channelcfg*/ AUDIO20_CHANNEL_CONFIG_NON_PREDEFINED, /*_stridx*/ 0x00),\ + /* Type I Format Type Descriptor(2.3.1.6 - Audio Formats) */\ + TUD_AUDIO20_DESC_TYPE_I_FORMAT(_nBytesPerSample, _nBitsUsedPerSample),\ + /* Standard AS Isochronous Audio Data Endpoint Descriptor(4.10.1.1) */\ + TUD_AUDIO20_DESC_STD_AS_ISO_EP(/*_ep*/ _epout, /*_attr*/ (uint8_t) ((uint8_t)TUSB_XFER_ISOCHRONOUS | (uint8_t)TUSB_ISO_EP_ATT_ASYNCHRONOUS | (uint8_t)TUSB_ISO_EP_ATT_DATA), /*_maxEPsize*/ _epoutsize, /*_interval*/ 0x01),\ + /* Class-Specific AS Isochronous Audio Data Endpoint Descriptor(4.10.1.2) */\ + TUD_AUDIO20_DESC_CS_AS_ISO_EP(/*_attr*/ AUDIO20_CS_AS_ISO_DATA_EP_ATT_NON_MAX_PACKETS_OK, /*_ctrl*/ AUDIO20_CTRL_NONE, /*_lockdelayunit*/ AUDIO20_CS_AS_ISO_DATA_EP_LOCK_DELAY_UNIT_UNDEFINED, /*_lockdelay*/ 0x0000),\ + /* Standard AS Isochronous Feedback Endpoint Descriptor(4.10.2.1) */\ + TUD_AUDIO20_DESC_STD_AS_ISO_FB_EP(/*_ep*/ _epfb, /*_epsize*/ _epfbsize, /*_interval*/ 1) + +// Calculate wMaxPacketSize of Endpoints +#define TUD_AUDIO_EP_SIZE(_is_highspeed, _maxFrequency, _nBytesPerSample, _nChannels) \ + (((((_maxFrequency) + ((_is_highspeed) ? 7999 : 999)) / ((_is_highspeed) ? 8000 : 1000)) + 1) * (_nBytesPerSample) * (_nChannels)) + + +//--------------------------------------------------------------------+ +// USBTMC/USB488 Descriptor Templates +//--------------------------------------------------------------------+ + +#define TUD_USBTMC_APP_CLASS (TUSB_CLASS_APPLICATION_SPECIFIC) +#define TUD_USBTMC_APP_SUBCLASS 0x03u + +#define TUD_USBTMC_PROTOCOL_STD 0x00u +#define TUD_USBTMC_PROTOCOL_USB488 0x01u + +// Interface number, number of endpoints, EP string index, USB_TMC_PROTOCOL*, bulk-out endpoint ID, +// bulk-in endpoint ID +#define TUD_USBTMC_IF_DESCRIPTOR(_itfnum, _bNumEndpoints, _stridx, _itfProtocol) \ + /* Interface */ \ + 0x09, TUSB_DESC_INTERFACE, _itfnum, 0x00, _bNumEndpoints, TUD_USBTMC_APP_CLASS, TUD_USBTMC_APP_SUBCLASS, _itfProtocol, _stridx + +#define TUD_USBTMC_IF_DESCRIPTOR_LEN 9u + +#define TUD_USBTMC_BULK_DESCRIPTORS(_epout, _epin, _bulk_epsize) \ + /* Endpoint Out */ \ + 7, TUSB_DESC_ENDPOINT, _epout, TUSB_XFER_BULK, U16_TO_U8S_LE(_bulk_epsize), 0u, \ + /* Endpoint In */ \ + 7, TUSB_DESC_ENDPOINT, _epin, TUSB_XFER_BULK, U16_TO_U8S_LE(_bulk_epsize), 0u + +#define TUD_USBTMC_BULK_DESCRIPTORS_LEN (7u+7u) + +/* optional interrupt endpoint */ \ +// _int_pollingInterval : for LS/FS, expressed in frames (1ms each). 16 may be a good number? +#define TUD_USBTMC_INT_DESCRIPTOR(_ep_interrupt, _ep_interrupt_size, _int_pollingInterval ) \ + 7, TUSB_DESC_ENDPOINT, _ep_interrupt, TUSB_XFER_INTERRUPT, U16_TO_U8S_LE(_ep_interrupt_size), _int_pollingInterval + +#define TUD_USBTMC_INT_DESCRIPTOR_LEN (7u) + +//--------------------------------------------------------------------+ +// Vendor Descriptor Templates +//--------------------------------------------------------------------+ + +#define TUD_VENDOR_DESC_LEN (9+7+7) + +// Interface number, string index, EP Out & IN address, EP size +#define TUD_VENDOR_DESCRIPTOR(_itfnum, _stridx, _epout, _epin, _epsize) \ + /* Interface */\ + 9, TUSB_DESC_INTERFACE, _itfnum, 0, 2, TUSB_CLASS_VENDOR_SPECIFIC, 0x00, 0x00, _stridx,\ + /* Endpoint Out */\ + 7, TUSB_DESC_ENDPOINT, _epout, TUSB_XFER_BULK, U16_TO_U8S_LE(_epsize), 0,\ + /* Endpoint In */\ + 7, TUSB_DESC_ENDPOINT, _epin, TUSB_XFER_BULK, U16_TO_U8S_LE(_epsize), 0 + +//--------------------------------------------------------------------+ +// DFU Runtime Descriptor Templates +//--------------------------------------------------------------------+ + +#define TUD_DFU_APP_CLASS (TUSB_CLASS_APPLICATION_SPECIFIC) +#define TUD_DFU_APP_SUBCLASS (APP_SUBCLASS_DFU_RUNTIME) + +// Length of template descriptr: 18 bytes +#define TUD_DFU_RT_DESC_LEN (9 + 9) + +// DFU runtime descriptor +// Interface number, string index, attributes, detach timeout, transfer size +#define TUD_DFU_RT_DESCRIPTOR(_itfnum, _stridx, _attr, _timeout, _xfer_size) \ + /* Interface */ \ + 9, TUSB_DESC_INTERFACE, _itfnum, 0, 0, TUD_DFU_APP_CLASS, TUD_DFU_APP_SUBCLASS, DFU_PROTOCOL_RT, _stridx, \ + /* Function */ \ + 9, DFU_DESC_FUNCTIONAL, _attr, U16_TO_U8S_LE(_timeout), U16_TO_U8S_LE(_xfer_size), U16_TO_U8S_LE(0x0110) + +//--------------------------------------------------------------------+ +// DFU Descriptor Templates +//--------------------------------------------------------------------+ + +// Length of template descriptor: 9 bytes + number of alternatives * 9 +#define TUD_DFU_DESC_LEN(_alt_count) (9 + (_alt_count) * 9) + +// Interface number, Alternate count, starting string index, attributes, detach timeout, transfer size +// Note: Alternate count must be numeric or macro, string index is increased by one for each Alt interface +#define TUD_DFU_DESCRIPTOR(_itfnum, _alt_count, _stridx, _attr, _timeout, _xfer_size) \ + TU_XSTRCAT(TUD_DFU_ALT_,_alt_count)(_itfnum, 0, _stridx), \ + /* Function */ \ + 9, DFU_DESC_FUNCTIONAL, _attr, U16_TO_U8S_LE(_timeout), U16_TO_U8S_LE(_xfer_size), U16_TO_U8S_LE(0x0110) + +#define TUD_DFU_ALT(_itfnum, _alt, _stridx) \ + /* Interface */ \ + 9, TUSB_DESC_INTERFACE, _itfnum, _alt, 0, TUD_DFU_APP_CLASS, TUD_DFU_APP_SUBCLASS, DFU_PROTOCOL_DFU, _stridx + +#define TUD_DFU_ALT_1(_itfnum, _alt_count, _stridx) \ + TUD_DFU_ALT(_itfnum, _alt_count, _stridx) + +#define TUD_DFU_ALT_2(_itfnum, _alt_count, _stridx) \ + TUD_DFU_ALT(_itfnum, _alt_count, _stridx), \ + TUD_DFU_ALT_1(_itfnum, _alt_count+1, _stridx+1) + +#define TUD_DFU_ALT_3(_itfnum, _alt_count, _stridx) \ + TUD_DFU_ALT(_itfnum, _alt_count, _stridx), \ + TUD_DFU_ALT_2(_itfnum, _alt_count+1, _stridx+1) + +#define TUD_DFU_ALT_4(_itfnum, _alt_count, _stridx) \ + TUD_DFU_ALT(_itfnum, _alt_count, _stridx), \ + TUD_DFU_ALT_3(_itfnum, _alt_count+1, _stridx+1) + +#define TUD_DFU_ALT_5(_itfnum, _alt_count, _stridx) \ + TUD_DFU_ALT(_itfnum, _alt_count, _stridx), \ + TUD_DFU_ALT_4(_itfnum, _alt_count+1, _stridx+1) + +#define TUD_DFU_ALT_6(_itfnum, _alt_count, _stridx) \ + TUD_DFU_ALT(_itfnum, _alt_count, _stridx), \ + TUD_DFU_ALT_5(_itfnum, _alt_count+1, _stridx+1) + +#define TUD_DFU_ALT_7(_itfnum, _alt_count, _stridx) \ + TUD_DFU_ALT(_itfnum, _alt_count, _stridx), \ + TUD_DFU_ALT_6(_itfnum, _alt_count+1, _stridx+1) + +#define TUD_DFU_ALT_8(_itfnum, _alt_count, _stridx) \ + TUD_DFU_ALT(_itfnum, _alt_count, _stridx), \ + TUD_DFU_ALT_7(_itfnum, _alt_count+1, _stridx+1) + +//--------------------------------------------------------------------+ +// CDC-ECM Descriptor Templates +//--------------------------------------------------------------------+ + +// Length of template descriptor: 71 bytes +#define TUD_CDC_ECM_DESC_LEN (8+9+5+5+13+7+9+9+7+7) + +// CDC-ECM Descriptor Template +// Interface number, description string index, MAC address string index, EP notification address and size, EP data address (out, in), and size, max segment size. +#define TUD_CDC_ECM_DESCRIPTOR(_itfnum, _desc_stridx, _mac_stridx, _ep_notif, _ep_notif_size, _epout, _epin, _epsize, _maxsegmentsize) \ + /* Interface Association */\ + 8, TUSB_DESC_INTERFACE_ASSOCIATION, _itfnum, 2, TUSB_CLASS_CDC, CDC_COMM_SUBCLASS_ETHERNET_CONTROL_MODEL, 0, 0,\ + /* CDC Control Interface */\ + 9, TUSB_DESC_INTERFACE, _itfnum, 0, 1, TUSB_CLASS_CDC, CDC_COMM_SUBCLASS_ETHERNET_CONTROL_MODEL, 0, _desc_stridx,\ + /* CDC-ECM Header */\ + 5, TUSB_DESC_CS_INTERFACE, CDC_FUNC_DESC_HEADER, U16_TO_U8S_LE(0x0120),\ + /* CDC-ECM Union */\ + 5, TUSB_DESC_CS_INTERFACE, CDC_FUNC_DESC_UNION, _itfnum, (uint8_t)((_itfnum) + 1),\ + /* CDC-ECM Functional Descriptor */\ + 13, TUSB_DESC_CS_INTERFACE, CDC_FUNC_DESC_ETHERNET_NETWORKING, _mac_stridx, 0, 0, 0, 0, U16_TO_U8S_LE(_maxsegmentsize), U16_TO_U8S_LE(0), 0,\ + /* Endpoint Notification */\ + 7, TUSB_DESC_ENDPOINT, _ep_notif, TUSB_XFER_INTERRUPT, U16_TO_U8S_LE(_ep_notif_size), 1,\ + /* CDC Data Interface (default inactive) */\ + 9, TUSB_DESC_INTERFACE, (uint8_t)((_itfnum)+1), 0, 0, TUSB_CLASS_CDC_DATA, 0, 0, 0,\ + /* CDC Data Interface (alternative active) */\ + 9, TUSB_DESC_INTERFACE, (uint8_t)((_itfnum)+1), 1, 2, TUSB_CLASS_CDC_DATA, 0, 0, 0,\ + /* Endpoint In */\ + 7, TUSB_DESC_ENDPOINT, _epin, TUSB_XFER_BULK, U16_TO_U8S_LE(_epsize), 0,\ + /* Endpoint Out */\ + 7, TUSB_DESC_ENDPOINT, _epout, TUSB_XFER_BULK, U16_TO_U8S_LE(_epsize), 0 + +//--------------------------------------------------------------------+ +// RNDIS Descriptor Templates +//--------------------------------------------------------------------+ + +#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 */ +#else +/* 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 +#define TUD_RNDIS_DESC_LEN (8+9+5+5+4+5+7+9+7+7) + +// RNDIS Descriptor Template +// Interface number, string index, EP notification address and size, EP data address (out, in) and size. +#define TUD_RNDIS_DESCRIPTOR(_itfnum, _stridx, _ep_notif, _ep_notif_size, _epout, _epin, _epsize) \ + /* Interface Association */\ + 8, TUSB_DESC_INTERFACE_ASSOCIATION, _itfnum, 2, TUD_RNDIS_ITF_CLASS, TUD_RNDIS_ITF_SUBCLASS, TUD_RNDIS_ITF_PROTOCOL, 0,\ + /* CDC Control Interface */\ + 9, TUSB_DESC_INTERFACE, _itfnum, 0, 1, TUD_RNDIS_ITF_CLASS, TUD_RNDIS_ITF_SUBCLASS, TUD_RNDIS_ITF_PROTOCOL, _stridx,\ + /* CDC-ACM Header */\ + 5, TUSB_DESC_CS_INTERFACE, CDC_FUNC_DESC_HEADER, U16_TO_U8S_LE(0x0110),\ + /* CDC Call Management */\ + 5, TUSB_DESC_CS_INTERFACE, CDC_FUNC_DESC_CALL_MANAGEMENT, 0, (uint8_t)((_itfnum) + 1),\ + /* ACM */\ + 4, TUSB_DESC_CS_INTERFACE, CDC_FUNC_DESC_ABSTRACT_CONTROL_MANAGEMENT, 0,\ + /* CDC Union */\ + 5, TUSB_DESC_CS_INTERFACE, CDC_FUNC_DESC_UNION, _itfnum, (uint8_t)((_itfnum) + 1),\ + /* Endpoint Notification */\ + 7, TUSB_DESC_ENDPOINT, _ep_notif, TUSB_XFER_INTERRUPT, U16_TO_U8S_LE(_ep_notif_size), 1,\ + /* CDC Data Interface */\ + 9, TUSB_DESC_INTERFACE, (uint8_t)((_itfnum)+1), 0, 2, TUSB_CLASS_CDC_DATA, 0, 0, 0,\ + /* Endpoint In */\ + 7, TUSB_DESC_ENDPOINT, _epin, TUSB_XFER_BULK, U16_TO_U8S_LE(_epsize), 0,\ + /* Endpoint Out */\ + 7, TUSB_DESC_ENDPOINT, _epout, TUSB_XFER_BULK, U16_TO_U8S_LE(_epsize), 0 + +//--------------------------------------------------------------------+ +// Bluetooth Radio Descriptor Templates +//--------------------------------------------------------------------+ + +#define TUD_BT_APP_CLASS (TUSB_CLASS_WIRELESS_CONTROLLER) +#define TUD_BT_APP_SUBCLASS 0x01 +#define TUD_BT_PROTOCOL_PRIMARY_CONTROLLER 0x01 +#define TUD_BT_PROTOCOL_AMP_CONTROLLER 0x02 + +// Length of template descriptor: 38 bytes + number of ISO alternatives * 23 +#define TUD_BTH_DESC_LEN (8 + 9 + 7 + 7 + 7 + (CFG_TUD_BTH_ISO_ALT_COUNT) * (9 + 7 + 7)) + +/* Primary Interface */ +#define TUD_BTH_PRI_ITF(_itfnum, _stridx, _ep_evt, _ep_evt_size, _ep_evt_interval, _ep_in, _ep_out, _ep_size) \ + 9, TUSB_DESC_INTERFACE, _itfnum, 0, 3, TUD_BT_APP_CLASS, TUD_BT_APP_SUBCLASS, TUD_BT_PROTOCOL_PRIMARY_CONTROLLER, _stridx, \ + /* Endpoint In for events */ \ + 7, TUSB_DESC_ENDPOINT, _ep_evt, TUSB_XFER_INTERRUPT, U16_TO_U8S_LE(_ep_evt_size), _ep_evt_interval, \ + /* Endpoint In for ACL data */ \ + 7, TUSB_DESC_ENDPOINT, _ep_in, TUSB_XFER_BULK, U16_TO_U8S_LE(_ep_size), 1, \ + /* Endpoint Out for ACL data */ \ + 7, TUSB_DESC_ENDPOINT, _ep_out, TUSB_XFER_BULK, U16_TO_U8S_LE(_ep_size), 1 + +#define TUD_BTH_ISO_ITF(_itfnum, _alt, _ep_in, _ep_out, _n) ,\ + /* Interface with 2 endpoints */ \ + 9, TUSB_DESC_INTERFACE, _itfnum, _alt, 2, TUD_BT_APP_CLASS, TUD_BT_APP_SUBCLASS, TUD_BT_PROTOCOL_PRIMARY_CONTROLLER, 0, \ + /* Isochronous endpoints */ \ + 7, TUSB_DESC_ENDPOINT, _ep_in, TUSB_XFER_ISOCHRONOUS, U16_TO_U8S_LE(_n), 1, \ + 7, TUSB_DESC_ENDPOINT, _ep_out, TUSB_XFER_ISOCHRONOUS, U16_TO_U8S_LE(_n), 1 + +#define _FIRST(a, ...) a +#define _REST(a, ...) __VA_ARGS__ + +#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__)) +#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__)) +#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__)) +#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__)) +#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__)) + +#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__) + +// 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 +// TODO BTH should also use IAD like CDC for composite device +#define TUD_BTH_DESCRIPTOR(_itfnum, _stridx, _ep_evt, _ep_evt_size, _ep_evt_interval, _ep_in, _ep_out, _ep_size,...) \ + /* Interface Associate */\ + 8, TUSB_DESC_INTERFACE_ASSOCIATION, _itfnum, 2, TUD_BT_APP_CLASS, TUD_BT_APP_SUBCLASS, TUD_BT_PROTOCOL_PRIMARY_CONTROLLER, 0,\ + TUD_BTH_PRI_ITF(_itfnum, _stridx, _ep_evt, _ep_evt_size, _ep_evt_interval, _ep_in, _ep_out, _ep_size) \ + TUD_BTH_ISO_ITFS(_itfnum + 1, _ep_in + 1, _ep_out + 1, __VA_ARGS__) + +//--------------------------------------------------------------------+ +// CDC-NCM Descriptor Templates +//--------------------------------------------------------------------+ + +// Length of template descriptor +#define TUD_CDC_NCM_DESC_LEN (8+9+5+5+13+6+7+9+9+7+7) + +// CDC-NCM Descriptor Template +// Interface number, description string index, MAC address string index, EP notification address and size, EP data address (out, in), and size, max segment size, EP notification bInterval, capability. +#define TUD_CDC_NCM_DESCRIPTOR(_itfnum, _desc_stridx, _mac_stridx, _ep_notif, _ep_notif_size, _epout, _epin, _epsize, _maxsegmentsize, _ep_notif_interval, _capability) \ + /* Interface Association */\ + 8, TUSB_DESC_INTERFACE_ASSOCIATION, _itfnum, 2, TUSB_CLASS_CDC, CDC_COMM_SUBCLASS_NETWORK_CONTROL_MODEL, 0, 0,\ + /* CDC Control Interface */\ + 9, TUSB_DESC_INTERFACE, _itfnum, 0, 1, TUSB_CLASS_CDC, CDC_COMM_SUBCLASS_NETWORK_CONTROL_MODEL, 0, _desc_stridx,\ + /* CDC Header */\ + 5, TUSB_DESC_CS_INTERFACE, CDC_FUNC_DESC_HEADER, U16_TO_U8S_LE(0x0110),\ + /* CDC Union */\ + 5, TUSB_DESC_CS_INTERFACE, CDC_FUNC_DESC_UNION, _itfnum, (uint8_t)((_itfnum) + 1),\ + /* CDC Ethernet Networking Descriptor */\ + 13, TUSB_DESC_CS_INTERFACE, CDC_FUNC_DESC_ETHERNET_NETWORKING, _mac_stridx, 0, 0, 0, 0, U16_TO_U8S_LE(_maxsegmentsize), U16_TO_U8S_LE(0), 0, \ + /* CDC-NCM Functional Descriptor */\ + 6, TUSB_DESC_CS_INTERFACE, CDC_FUNC_DESC_NCM, U16_TO_U8S_LE(0x0100), _capability, \ + /* Endpoint Notification */\ + 7, TUSB_DESC_ENDPOINT, _ep_notif, TUSB_XFER_INTERRUPT, U16_TO_U8S_LE(_ep_notif_size), _ep_notif_interval,\ + /* CDC Data Interface (default inactive) */\ + 9, TUSB_DESC_INTERFACE, (uint8_t)((_itfnum)+1), 0, 0, TUSB_CLASS_CDC_DATA, 0, NCM_DATA_PROTOCOL_NETWORK_TRANSFER_BLOCK, 0,\ + /* CDC Data Interface (alternative active) */\ + 9, TUSB_DESC_INTERFACE, (uint8_t)((_itfnum)+1), 1, 2, TUSB_CLASS_CDC_DATA, 0, NCM_DATA_PROTOCOL_NETWORK_TRANSFER_BLOCK, 0,\ + /* Endpoint In */\ + 7, TUSB_DESC_ENDPOINT, _epin, TUSB_XFER_BULK, U16_TO_U8S_LE(_epsize), 0,\ + /* Endpoint Out */\ + 7, TUSB_DESC_ENDPOINT, _epout, TUSB_XFER_BULK, U16_TO_U8S_LE(_epsize), 0 + +#ifdef __cplusplus +} +#endif + +#endif /* TUSB_USBD_H_ */ + +/** @} */ diff --git a/libraries/Adafruit_TinyUSB_Arduino/src/device/usbd_pvt.h b/libraries/Adafruit_TinyUSB_Arduino/src/device/usbd_pvt.h new file mode 100644 index 0000000..be778f9 --- /dev/null +++ b/libraries/Adafruit_TinyUSB_Arduino/src/device/usbd_pvt.h @@ -0,0 +1,137 @@ +/* + * 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. + * + * This file is part of the TinyUSB stack. + */ +#ifndef TUSB_USBD_PVT_H_ +#define TUSB_USBD_PVT_H_ + +#include "osal/osal.h" +#include "common/tusb_fifo.h" +#include "common/tusb_private.h" + +#ifdef __cplusplus + extern "C" { +#endif + +#define TU_LOG_USBD(...) TU_LOG(CFG_TUD_LOG_LEVEL, __VA_ARGS__) + +//--------------------------------------------------------------------+ +// MACRO CONSTANT TYPEDEF PROTYPES +//--------------------------------------------------------------------+ + +typedef enum { + SOF_CONSUMER_USER = 0, + SOF_CONSUMER_AUDIO, +} sof_consumer_t; + +//--------------------------------------------------------------------+ +// Class Driver API +//--------------------------------------------------------------------+ + +typedef struct { + char const* name; + void (* init ) (void); + bool (* deinit ) (void); + void (* reset ) (uint8_t rhport); + uint16_t (* open ) (uint8_t rhport, tusb_desc_interface_t const * desc_intf, uint16_t max_len); + bool (* control_xfer_cb ) (uint8_t rhport, uint8_t stage, tusb_control_request_t const * request); + bool (* xfer_cb ) (uint8_t rhport, uint8_t ep_addr, xfer_result_t result, uint32_t xferred_bytes); + bool (* xfer_isr ) (uint8_t rhport, uint8_t ep_addr, xfer_result_t result, uint32_t xferred_bytes); // optional, return false to defer to xfer_cb() + void (* sof ) (uint8_t rhport, uint32_t frame_count); // optional +} usbd_class_driver_t; + +// Invoked when initializing device stack to get additional class drivers. +// Can be implemented by application to extend/overwrite class driver support. +// Note: The drivers array must be accessible at all time when stack is active +usbd_class_driver_t const* usbd_app_driver_get_cb(uint8_t* driver_count); + +typedef bool (*usbd_control_xfer_cb_t)(uint8_t rhport, uint8_t stage, tusb_control_request_t const * request); + +void usbd_int_set(bool enabled); +void usbd_spin_lock(bool in_isr); +void usbd_spin_unlock(bool in_isr); + +uint8_t* usbd_get_ctrl_buf(void); + +//--------------------------------------------------------------------+ +// USBD Endpoint API +// Note: rhport should be 0 since device stack only support 1 rhport for now +//--------------------------------------------------------------------+ + +// Open an endpoint +bool usbd_edpt_open(uint8_t rhport, tusb_desc_endpoint_t const * desc_ep); + +// Close an endpoint +void usbd_edpt_close(uint8_t rhport, uint8_t ep_addr); + +// Submit a usb transfer +bool usbd_edpt_xfer(uint8_t rhport, uint8_t ep_addr, uint8_t * buffer, uint16_t total_bytes, bool is_isr); + +// Submit a usb ISO transfer by use of a FIFO (ring buffer) - all bytes in FIFO get transmitted +bool usbd_edpt_xfer_fifo(uint8_t rhport, uint8_t ep_addr, tu_fifo_t * ff, uint16_t total_bytes, bool is_isr); + +// Claim an endpoint before submitting a transfer. +// If caller does not make any transfer, it must release endpoint for others. +bool usbd_edpt_claim(uint8_t rhport, uint8_t ep_addr); + +// Release claimed endpoint without submitting a transfer +bool usbd_edpt_release(uint8_t rhport, uint8_t ep_addr); + +// Check if endpoint is busy transferring +bool usbd_edpt_busy(uint8_t rhport, uint8_t ep_addr); + +// Stall endpoint +void usbd_edpt_stall(uint8_t rhport, uint8_t ep_addr); + +// Clear stalled endpoint +void usbd_edpt_clear_stall(uint8_t rhport, uint8_t ep_addr); + +// Check if endpoint is stalled +bool usbd_edpt_stalled(uint8_t rhport, uint8_t ep_addr); + +// Allocate packet buffer used by ISO endpoints +bool usbd_edpt_iso_alloc(uint8_t rhport, uint8_t ep_addr, uint16_t largest_packet_size); + +// Configure and enable an ISO endpoint according to descriptor +bool usbd_edpt_iso_activate(uint8_t rhport, tusb_desc_endpoint_t const * p_endpoint_desc); + +// Check if endpoint is ready (not busy and not stalled) +TU_ATTR_ALWAYS_INLINE static inline +bool usbd_edpt_ready(uint8_t rhport, uint8_t ep_addr) { + const bool is_busy = usbd_edpt_busy(rhport, ep_addr); + const bool is_stalled = usbd_edpt_stalled(rhport, ep_addr); + return !is_busy && !is_stalled; +} + +// Enable SOF interrupt +void usbd_sof_enable(uint8_t rhport, sof_consumer_t consumer, bool en); + +bool usbd_open_edpt_pair(uint8_t rhport, uint8_t const* p_desc, uint8_t ep_count, uint8_t xfer_type, uint8_t* ep_out, uint8_t* ep_in); +void usbd_defer_func(osal_task_func_t func, void *param, bool in_isr); + +#ifdef __cplusplus + } +#endif + +#endif diff --git a/libraries/Adafruit_TinyUSB_Arduino/src/host/hcd.h b/libraries/Adafruit_TinyUSB_Arduino/src/host/hcd.h new file mode 100644 index 0000000..47d672f --- /dev/null +++ b/libraries/Adafruit_TinyUSB_Arduino/src/host/hcd.h @@ -0,0 +1,227 @@ +/* + * 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. + * + * This file is part of the TinyUSB stack. + */ + +#ifndef TUSB_HCD_H_ +#define TUSB_HCD_H_ + +#include "common/tusb_common.h" +#include "osal/osal.h" +#include "common/tusb_fifo.h" + +#ifdef __cplusplus + extern "C" { +#endif + +//--------------------------------------------------------------------+ +// Configuration +//--------------------------------------------------------------------+ + +// Max number of endpoints pair per device +// TODO optimize memory usage +#ifndef CFG_TUH_ENDPOINT_MAX + #define CFG_TUH_ENDPOINT_MAX 16 +// #ifdef TUP_HCD_ENDPOINT_MAX +// #define CFG_TUH_ENDPPOINT_MAX TUP_HCD_ENDPOINT_MAX +// #else +// #define +// #endif +#endif + +//--------------------------------------------------------------------+ +// MACRO CONSTANT TYPEDEF +//--------------------------------------------------------------------+ +typedef enum { + HCD_EVENT_DEVICE_ATTACH, + HCD_EVENT_DEVICE_REMOVE, + HCD_EVENT_XFER_COMPLETE, + + USBH_EVENT_FUNC_CALL, // Not an HCD event + HCD_EVENT_INVALID +} hcd_eventid_t; + +typedef struct { + uint8_t rhport; + uint8_t event_id; + uint8_t dev_addr; + + union { + // Attach, Remove + struct { + uint8_t hub_addr; + uint8_t hub_port; + } connection; + + // XFER_COMPLETE + struct { + uint8_t ep_addr; + uint8_t result; + uint32_t len; + } xfer_complete; + + // FUNC_CALL + struct { + void (*func) (void* param); + void* param; + }func_call; + }; +} hcd_event_t; + +//--------------------------------------------------------------------+ +// Memory API +//--------------------------------------------------------------------+ + +// clean/flush data cache: write cache -> memory. +// Required before an DMA TX transfer to make sure data is in memory +bool hcd_dcache_clean(void const* addr, uint32_t data_size); + +// invalidate data cache: mark cache as invalid, next read will read from memory +// Required BOTH before and after an DMA RX transfer +bool hcd_dcache_invalidate(void const* addr, uint32_t data_size); + +// clean and invalidate data cache +// Required before an DMA transfer where memory is both read/write by DMA +bool hcd_dcache_clean_invalidate(void const* addr, uint32_t data_size); + +//--------------------------------------------------------------------+ +// Controller API +//--------------------------------------------------------------------+ + +// optional hcd configuration, called by tuh_configure() +bool hcd_configure(uint8_t rhport, uint32_t cfg_id, const void* cfg_param); + +// Initialize controller to host mode +bool hcd_init(uint8_t rhport, const tusb_rhport_init_t* rh_init); + +// De-initialize controller +bool hcd_deinit(uint8_t rhport); + +// Interrupt Handler +void hcd_int_handler(uint8_t rhport, bool in_isr); + +// Enable USB interrupt +void hcd_int_enable (uint8_t rhport); + +// Disable USB interrupt +void hcd_int_disable(uint8_t rhport); + +// Get frame number (1ms) +uint32_t hcd_frame_number(uint8_t rhport); + +//--------------------------------------------------------------------+ +// Port API +//--------------------------------------------------------------------+ + +// Get the current connect status of roothub port +bool hcd_port_connect_status(uint8_t rhport); + +// Reset USB bus on the port. Return immediately, bus reset sequence may not be complete. +// Some port would require hcd_port_reset_end() to be invoked after 10ms to complete the reset sequence. +void hcd_port_reset(uint8_t rhport); + +// Complete bus reset sequence, may be required by some controllers +void hcd_port_reset_end(uint8_t rhport); + +// Get port link speed +tusb_speed_t hcd_port_speed_get(uint8_t rhport); + +// HCD closes all opened endpoints belong to this device +void hcd_device_close(uint8_t rhport, uint8_t dev_addr); + +//--------------------------------------------------------------------+ +// Endpoints API +//--------------------------------------------------------------------+ + +// Open an endpoint +// return true if successfully opened or endpoint is currently opened +bool hcd_edpt_open(uint8_t rhport, uint8_t daddr, tusb_desc_endpoint_t const * ep_desc); + +// Close an endpoint +bool hcd_edpt_close(uint8_t rhport, uint8_t daddr, uint8_t ep_addr); + +// Submit a transfer, when complete hcd_event_xfer_complete() must be invoked +bool hcd_edpt_xfer(uint8_t rhport, uint8_t daddr, uint8_t ep_addr, uint8_t * buffer, uint16_t buflen); + +// Abort a queued transfer. Note: it can only abort transfer that has not been started +// Return true if a queued transfer is aborted, false if there is no transfer to abort +bool hcd_edpt_abort_xfer(uint8_t rhport, uint8_t dev_addr, uint8_t ep_addr); + +// Submit a special transfer to send 8-byte Setup Packet, when complete hcd_event_xfer_complete() must be invoked +bool hcd_setup_send(uint8_t rhport, uint8_t daddr, uint8_t const setup_packet[8]); + +// clear stall, data toggle is also reset to DATA0 +bool hcd_edpt_clear_stall(uint8_t rhport, uint8_t dev_addr, uint8_t ep_addr); + +//--------------------------------------------------------------------+ +// USBH implemented API +//--------------------------------------------------------------------+ + +// Called by HCD to notify stack +extern void hcd_event_handler(hcd_event_t const* event, bool in_isr); + +// Helper to send device attach event +TU_ATTR_ALWAYS_INLINE static inline +void hcd_event_device_attach(uint8_t rhport, bool in_isr) { + hcd_event_t event; + event.rhport = rhport; + event.event_id = HCD_EVENT_DEVICE_ATTACH; + event.connection.hub_addr = 0; + event.connection.hub_port = 0; + + hcd_event_handler(&event, in_isr); +} + +// Helper to send device removal event +TU_ATTR_ALWAYS_INLINE static inline +void hcd_event_device_remove(uint8_t rhport, bool in_isr) { + hcd_event_t event; + event.rhport = rhport; + event.event_id = HCD_EVENT_DEVICE_REMOVE; + event.connection.hub_addr = 0; + event.connection.hub_port = 0; + + hcd_event_handler(&event, in_isr); +} + +// Helper to send USB transfer event +TU_ATTR_ALWAYS_INLINE static inline +void hcd_event_xfer_complete(uint8_t dev_addr, uint8_t ep_addr, uint32_t xferred_bytes, xfer_result_t result, bool in_isr) { + hcd_event_t event = { + .rhport = 0, // TODO correct rhport + .event_id = HCD_EVENT_XFER_COMPLETE, + .dev_addr = dev_addr, + }; + event.xfer_complete.ep_addr = ep_addr; + event.xfer_complete.result = result; + event.xfer_complete.len = xferred_bytes; + + hcd_event_handler(&event, in_isr); +} + +#ifdef __cplusplus + } +#endif + +#endif diff --git a/libraries/Adafruit_TinyUSB_Arduino/src/host/hub.c b/libraries/Adafruit_TinyUSB_Arduino/src/host/hub.c new file mode 100644 index 0000000..7c6735e --- /dev/null +++ b/libraries/Adafruit_TinyUSB_Arduino/src/host/hub.c @@ -0,0 +1,501 @@ +/* + * 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. + * + * This file is part of the TinyUSB stack. + */ + +#include "tusb_option.h" + +#if (CFG_TUH_ENABLED && CFG_TUH_HUB) + +#include "hcd.h" +#include "usbh.h" +#include "usbh_pvt.h" +#include "hub.h" + +// Debug level, TUSB_CFG_DEBUG must be at least this level for debug message +#define HUB_DEBUG 2 +#define TU_LOG_DRV(...) TU_LOG(HUB_DEBUG, __VA_ARGS__) + +//--------------------------------------------------------------------+ +// MACRO CONSTANT TYPEDEF +//--------------------------------------------------------------------+ +typedef struct { + uint8_t itf_num; + uint8_t ep_in; + + // from hub descriptor + uint8_t bNbrPorts; + uint8_t bPwrOn2PwrGood_2ms; // port power on to good, in 2ms unit + // uint16_t wHubCharacteristics; + bool mtt; + hub_port_status_response_t port_status; +} hub_interface_t; + +typedef struct { + TUH_EPBUF_DEF(status_change, 4); // interrupt endpoint + TUH_EPBUF_DEF(ctrl_buf, CFG_TUH_HUB_BUFSIZE); +} hub_epbuf_t; + +static tuh_xfer_cb_t user_complete_cb = NULL; +static hub_interface_t hub_itfs[CFG_TUH_HUB]; +CFG_TUH_MEM_SECTION static hub_epbuf_t hub_epbufs[CFG_TUH_HUB]; + + +TU_ATTR_ALWAYS_INLINE static inline hub_interface_t* get_hub_itf(uint8_t daddr) { + return &hub_itfs[daddr-1-CFG_TUH_DEVICE_MAX]; +} + +TU_ATTR_ALWAYS_INLINE static inline hub_epbuf_t* get_hub_epbuf(uint8_t daddr) { + return &hub_epbufs[daddr-1-CFG_TUH_DEVICE_MAX]; +} + +#if CFG_TUSB_DEBUG >= HUB_DEBUG +static char const* const _hub_feature_str[] = { + [HUB_FEATURE_PORT_CONNECTION ] = "PORT_CONNECTION", + [HUB_FEATURE_PORT_ENABLE ] = "PORT_ENABLE", + [HUB_FEATURE_PORT_SUSPEND ] = "PORT_SUSPEND", + [HUB_FEATURE_PORT_OVER_CURRENT ] = "PORT_OVER_CURRENT", + [HUB_FEATURE_PORT_RESET ] = "PORT_RESET", + [HUB_FEATURE_PORT_POWER ] = "PORT_POWER", + [HUB_FEATURE_PORT_LOW_SPEED ] = "PORT_LOW_SPEED", + [HUB_FEATURE_PORT_CONNECTION_CHANGE ] = "PORT_CONNECTION_CHANGE", + [HUB_FEATURE_PORT_ENABLE_CHANGE ] = "PORT_ENABLE_CHANGE", + [HUB_FEATURE_PORT_SUSPEND_CHANGE ] = "PORT_SUSPEND_CHANGE", + [HUB_FEATURE_PORT_OVER_CURRENT_CHANGE ] = "PORT_OVER_CURRENT_CHANGE", + [HUB_FEATURE_PORT_RESET_CHANGE ] = "PORT_RESET_CHANGE", + [HUB_FEATURE_PORT_TEST ] = "PORT_TEST", + [HUB_FEATURE_PORT_INDICATOR ] = "PORT_INDICATOR", +}; +#endif + +//--------------------------------------------------------------------+ +// HUB +//--------------------------------------------------------------------+ +bool hub_port_clear_feature(uint8_t hub_addr, uint8_t hub_port, uint8_t feature, + tuh_xfer_cb_t complete_cb, uintptr_t user_data) { + tusb_control_request_t const request = { + .bmRequestType_bit = { + .recipient = (hub_port == 0) ? TUSB_REQ_RCPT_DEVICE : TUSB_REQ_RCPT_OTHER, + .type = TUSB_REQ_TYPE_CLASS, + .direction = TUSB_DIR_OUT + }, + .bRequest = HUB_REQUEST_CLEAR_FEATURE, + .wValue = feature, + .wIndex = hub_port, + .wLength = 0 + }; + + tuh_xfer_t xfer = { + .daddr = hub_addr, + .ep_addr = 0, + .setup = &request, + .buffer = NULL, + .complete_cb = complete_cb, + .user_data = user_data + }; + + TU_LOG_DRV("HUB Clear Feature: %s, addr = %u port = %u\r\n", _hub_feature_str[feature], hub_addr, hub_port); + TU_ASSERT(tuh_control_xfer(&xfer)); + return true; +} + +bool hub_port_set_feature(uint8_t hub_addr, uint8_t hub_port, uint8_t feature, + tuh_xfer_cb_t complete_cb, uintptr_t user_data) { + tusb_control_request_t const request = { + .bmRequestType_bit = { + .recipient = (hub_port == 0) ? TUSB_REQ_RCPT_DEVICE : TUSB_REQ_RCPT_OTHER, + .type = TUSB_REQ_TYPE_CLASS, + .direction = TUSB_DIR_OUT + }, + .bRequest = HUB_REQUEST_SET_FEATURE, + .wValue = feature, + .wIndex = hub_port, + .wLength = 0 + }; + + tuh_xfer_t xfer = { + .daddr = hub_addr, + .ep_addr = 0, + .setup = &request, + .buffer = NULL, + .complete_cb = complete_cb, + .user_data = user_data + }; + + TU_LOG_DRV("HUB Set Feature: %s, addr = %u port = %u\r\n", _hub_feature_str[feature], hub_addr, hub_port); + TU_ASSERT(tuh_control_xfer(&xfer)); + return true; +} + +static void port_get_status_complete (tuh_xfer_t* xfer) { + if (xfer->result == XFER_RESULT_SUCCESS) { + hub_interface_t* p_hub = get_hub_itf(xfer->daddr); + p_hub->port_status = *((const hub_port_status_response_t *) (uintptr_t) xfer->buffer); + } + + xfer->complete_cb = user_complete_cb; + user_complete_cb = NULL; + if (xfer->complete_cb) { + xfer->complete_cb(xfer); + } +} + +bool hub_port_get_status(uint8_t hub_addr, uint8_t hub_port, void* resp, + tuh_xfer_cb_t complete_cb, uintptr_t user_data) { + tusb_control_request_t const request = { + .bmRequestType_bit = { + .recipient = (hub_port == 0) ? TUSB_REQ_RCPT_DEVICE : TUSB_REQ_RCPT_OTHER, + .type = TUSB_REQ_TYPE_CLASS, + .direction = TUSB_DIR_IN + }, + .bRequest = HUB_REQUEST_GET_STATUS, + .wValue = 0, + .wIndex = hub_port, + .wLength = tu_htole16(4) + }; + + tuh_xfer_t xfer = { + .daddr = hub_addr, + .ep_addr = 0, + .setup = &request, + .buffer = resp, + .complete_cb = complete_cb, + .user_data = user_data + }; + + if (hub_port != 0) { + // intercept complete callback to save port status, ignore resp + hub_epbuf_t* p_epbuf = get_hub_epbuf(hub_addr); + xfer.complete_cb = port_get_status_complete; + xfer.buffer = p_epbuf->ctrl_buf; + user_complete_cb = complete_cb; + } else { + user_complete_cb = NULL; + } + + TU_LOG_DRV("HUB Get Port Status: addr = %u port = %u\r\n", hub_addr, hub_port); + TU_VERIFY(tuh_control_xfer(&xfer)); + return true; +} + +bool hub_port_get_status_local(uint8_t hub_addr, uint8_t hub_port, hub_port_status_response_t* resp) { + (void) hub_port; + hub_interface_t* p_hub = get_hub_itf(hub_addr); + *resp = p_hub->port_status; + return true; +} + +//--------------------------------------------------------------------+ +// CLASS-USBH API (don't require to verify parameters) +//--------------------------------------------------------------------+ +bool hub_init(void) { + tu_memclr(hub_itfs, sizeof(hub_itfs)); + return true; +} + +bool hub_deinit(void) { + return true; +} + +uint16_t hub_open(uint8_t rhport, uint8_t dev_addr, const tusb_desc_interface_t *itf_desc, uint16_t max_len) { + (void)rhport; + TU_VERIFY(TUSB_CLASS_HUB == itf_desc->bInterfaceClass && 0 == itf_desc->bInterfaceSubClass, 0); + + const uint16_t itf_ep_len = sizeof(tusb_desc_interface_t) + sizeof(tusb_desc_endpoint_t); + uint16_t drv_len = itf_ep_len; + TU_ASSERT(drv_len <= max_len, 0); + + hub_interface_t *p_hub = get_hub_itf(dev_addr); + const tusb_desc_interface_t *desc_itf_use = itf_desc; // interface to use for endpoint open + + // Check device descriptor for MTT hub (bDeviceProtocol == 2) + // MTT hub has 2 alt settings: alt 0 is STT (protocol 1), alt 1 is MTT (protocol 2) + // Consume both alt settings and use alt setting 1 for endpoint + tusb_desc_device_t desc_dev; + if (tuh_descriptor_get_device_local(dev_addr, &desc_dev) && desc_dev.bDeviceProtocol == HUB_PROTOCOL_HIGH_SPEED_MTT) { + drv_len += itf_ep_len; + TU_ASSERT(drv_len <= max_len, 0); + const tusb_desc_interface_t *desc_alt1 = (const tusb_desc_interface_t *)((const uint8_t *)itf_desc + itf_ep_len); + TU_ASSERT(desc_alt1->bDescriptorType == TUSB_DESC_INTERFACE && desc_alt1->bInterfaceClass == TUSB_CLASS_HUB && + desc_alt1->bInterfaceProtocol == HUB_PROTOCOL_HIGH_SPEED_MTT, + 0); + p_hub->mtt = true; + desc_itf_use = desc_alt1; + } + + // Interrupt Status endpoint + const tusb_desc_endpoint_t *desc_ep = (const tusb_desc_endpoint_t *)tu_desc_next(desc_itf_use); + TU_ASSERT(TUSB_DESC_ENDPOINT == desc_ep->bDescriptorType && TUSB_XFER_INTERRUPT == desc_ep->bmAttributes.xfer, 0); + TU_ASSERT(tuh_edpt_open(dev_addr, desc_ep), 0); + + p_hub->itf_num = itf_desc->bInterfaceNumber; + p_hub->ep_in = desc_ep->bEndpointAddress; + + return drv_len; +} + +void hub_close(uint8_t dev_addr) { + TU_VERIFY(dev_addr > CFG_TUH_DEVICE_MAX, ); + hub_interface_t* p_hub = get_hub_itf(dev_addr); + + if (p_hub->ep_in) { + TU_LOG_DRV(" HUB close addr = %d\r\n", dev_addr); + tu_memclr(p_hub, sizeof( hub_interface_t)); + } +} + +bool hub_edpt_status_xfer(uint8_t daddr) { + hub_interface_t* p_hub = get_hub_itf(daddr); + hub_epbuf_t* p_epbuf = get_hub_epbuf(daddr); + + TU_VERIFY(usbh_edpt_claim(daddr, p_hub->ep_in)); + if (!usbh_edpt_xfer(daddr, p_hub->ep_in, p_epbuf->status_change, 1)) { + usbh_edpt_release(daddr, p_hub->ep_in); + return false; + } + + return true; +} + +//--------------------------------------------------------------------+ +// Set Configure +//--------------------------------------------------------------------+ +static void config_set_port_power (tuh_xfer_t* xfer); +static void config_port_power_complete (tuh_xfer_t* xfer); +static void config_get_hub_descriptor(tuh_xfer_t* xfer); + +bool hub_set_config(uint8_t daddr, uint8_t itf_num) { + hub_interface_t* p_hub = get_hub_itf(daddr); + TU_ASSERT(itf_num == p_hub->itf_num); + + if (p_hub->mtt) { + // Set Alternate Setting 1 for MTT hub + TU_ASSERT(tuh_interface_set(daddr, itf_num, 1, config_get_hub_descriptor, 0)); + } else { + tuh_xfer_t xfer; + xfer.daddr = daddr; + xfer.ep_addr = 0; + xfer.user_data = 0; + config_get_hub_descriptor(&xfer); + } + + return true; +} + +static void config_get_hub_descriptor(tuh_xfer_t* xfer) { + // Get Hub Descriptor + tusb_control_request_t const request = { + .bmRequestType_bit = { + .recipient = TUSB_REQ_RCPT_DEVICE, + .type = TUSB_REQ_TYPE_CLASS, + .direction = TUSB_DIR_IN + }, + .bRequest = HUB_REQUEST_GET_DESCRIPTOR, + .wValue = 0, + .wIndex = 0, + .wLength = sizeof(hub_desc_cs_t) + }; + + xfer->setup = &request; + xfer->buffer = get_hub_epbuf(xfer->daddr)->ctrl_buf; + xfer->complete_cb = config_set_port_power; + + TU_ASSERT(tuh_control_xfer(xfer), ); +} + +static void config_set_port_power (tuh_xfer_t* xfer) { + TU_ASSERT(XFER_RESULT_SUCCESS == xfer->result, ); + + uint8_t const daddr = xfer->daddr; + hub_interface_t* p_hub = get_hub_itf(daddr); + hub_epbuf_t* p_epbuf = get_hub_epbuf(daddr); + + // only use the number of ports in the hub descriptor + hub_desc_cs_t const* desc_hub = (hub_desc_cs_t const*) p_epbuf->ctrl_buf; + p_hub->bNbrPorts = desc_hub->bNbrPorts; + p_hub->bPwrOn2PwrGood_2ms = desc_hub->bPwrOn2PwrGood; + + // May need to GET_STATUS + + // Set Port Power to be able to detect connection, starting with port 1 + uint8_t const hub_port = 1; + hub_port_set_feature(daddr, hub_port, HUB_FEATURE_PORT_POWER, config_port_power_complete, 0); +} + +static void config_port_power_complete (tuh_xfer_t* xfer) { + TU_ASSERT(XFER_RESULT_SUCCESS == xfer->result, ); + + uint8_t const daddr = xfer->daddr; + hub_interface_t* p_hub = get_hub_itf(daddr); + + if (xfer->setup->wIndex == p_hub->bNbrPorts) { + // All ports are power -> queue notification status endpoint and + // complete the SET CONFIGURATION + if (!hub_edpt_status_xfer(daddr)) { + TU_MESS_FAILED(); + TU_BREAKPOINT(); + } + usbh_driver_set_config_complete(daddr, p_hub->itf_num); + } else { + // power next port + uint8_t const hub_port = (uint8_t) (xfer->setup->wIndex + 1); + hub_port_set_feature(daddr, hub_port, HUB_FEATURE_PORT_POWER, config_port_power_complete, 0); + } +} + +//--------------------------------------------------------------------+ +// Connection Changes +//--------------------------------------------------------------------+ +enum { + STATE_IDLE = 0, + STATE_HUB_STATUS, + STATE_CLEAR_CHANGE, + STATE_CHECK_CONN, + STATE_COMPLETE +}; + +static void process_new_status(tuh_xfer_t* xfer); + +// callback as response of interrupt endpoint polling +bool hub_xfer_cb(uint8_t daddr, uint8_t ep_addr, xfer_result_t result, uint32_t xferred_bytes) { + (void) xferred_bytes; + (void) ep_addr; + + bool processed = false; // true if new status is processed + + if (result == XFER_RESULT_SUCCESS) { + hub_interface_t* p_hub = get_hub_itf(daddr); + hub_epbuf_t *p_epbuf = get_hub_epbuf(daddr); + const uint8_t status_change = p_epbuf->status_change[0]; + TU_LOG_DRV(" Hub Status Change = 0x%02X\r\n", status_change); + + if (status_change == 0) { + // The status change event was neither for the hub, nor for any of its ports. + // This shouldn't happen, but it does with some devices. Re-Initiate the interrupt poll. + processed = false; + } else if (tu_bit_test(status_change, 0)) { + // Hub bit 0 is for the hub device events + processed = hub_get_status(daddr, p_epbuf->ctrl_buf, process_new_status, STATE_HUB_STATUS); + } else { + // Hub bits 1 to n are hub port events + for (uint8_t port=1; port <= p_hub->bNbrPorts; port++) { + if (tu_bit_test(status_change, port)) { + processed = hub_port_get_status(daddr, port, NULL, process_new_status, STATE_CLEAR_CHANGE); + break; // after completely processed one port, we will re-queue the status poll and handle next one + } + } + } + } + + // If new status event is processed: next status pool is queued by usbh.c after handled this request + // Otherwise re-queue the status poll here + if (!processed) { + TU_ASSERT(hub_edpt_status_xfer(daddr)); + } + + return true; +} + +static void process_new_status(tuh_xfer_t* xfer) { + const uint8_t daddr = xfer->daddr; + + if (xfer->result != XFER_RESULT_SUCCESS) { + TU_ASSERT(hub_edpt_status_xfer(daddr),); + return; + } + + const uint8_t port_num = (uint8_t) tu_le16toh(xfer->setup->wIndex); + hub_interface_t *p_hub = get_hub_itf(daddr); + const uintptr_t state = xfer->user_data; + bool processed = false; // true if new status is processed + + switch (state) { + case STATE_HUB_STATUS: { + hub_status_response_t hub_status = *((const hub_status_response_t *) (uintptr_t) xfer->buffer); + TU_LOG_DRV("HUB Got hub status, addr = %u, status = %04x\r\n", daddr, hub_status.change.value); + if (hub_status.change.local_power_source) { + TU_LOG_DRV(" Local Power Change\r\n"); + processed = hub_clear_feature(daddr, HUB_FEATURE_HUB_LOCAL_POWER_CHANGE, + process_new_status, STATE_COMPLETE); + } else if (hub_status.change.over_current) { + TU_LOG_DRV(" Over Current\r\n"); + processed = hub_clear_feature(daddr, HUB_FEATURE_HUB_OVER_CURRENT_CHANGE, + process_new_status, STATE_COMPLETE); + } + break; + } + + case STATE_CLEAR_CHANGE: + // Get port status complete --> clear change + if (p_hub->port_status.change.connection) { + // Connection change + // Port is powered and enabled + //TU_VERIFY(port_status.status_current.port_power && port_status.status_current.port_enable, ); + + // Acknowledge Port Connection Change + processed = hub_port_clear_feature(daddr, port_num, HUB_FEATURE_PORT_CONNECTION_CHANGE, + process_new_status, STATE_CHECK_CONN); + } else if (p_hub->port_status.change.port_enable) { + processed = hub_port_clear_feature(daddr, port_num, HUB_FEATURE_PORT_ENABLE_CHANGE, + process_new_status, STATE_COMPLETE); + } else if (p_hub->port_status.change.suspend) { + processed = hub_port_clear_feature(daddr, port_num, HUB_FEATURE_PORT_SUSPEND_CHANGE, + process_new_status, STATE_COMPLETE); + } else if (p_hub->port_status.change.over_current) { + processed = hub_port_clear_feature(daddr, port_num, HUB_FEATURE_PORT_OVER_CURRENT_CHANGE, + process_new_status, STATE_COMPLETE); + } else if (p_hub->port_status.change.reset) { + processed = hub_port_clear_feature(daddr, port_num, HUB_FEATURE_PORT_RESET_CHANGE, + process_new_status, STATE_COMPLETE); + } + break; + + case STATE_CHECK_CONN: { + const hcd_event_t event = { + .rhport = usbh_get_rhport(daddr), + .event_id = p_hub->port_status.status.connection ? HCD_EVENT_DEVICE_ATTACH : HCD_EVENT_DEVICE_REMOVE, + .connection = { + .hub_addr = daddr, + .hub_port = port_num + } + }; + hcd_event_handler(&event, false); + // skip status for attach event, usbh will do it after handled this enumeration + processed = (event.event_id == HCD_EVENT_DEVICE_ATTACH); + break; + } + + case STATE_COMPLETE: + default: + processed = false; // complete this status, queue next status + break; + + } + + if (!processed) { + TU_ASSERT(hub_edpt_status_xfer(daddr),); + } +} + +#endif diff --git a/libraries/Adafruit_TinyUSB_Arduino/src/host/hub.h b/libraries/Adafruit_TinyUSB_Arduino/src/host/hub.h new file mode 100644 index 0000000..8a7feda --- /dev/null +++ b/libraries/Adafruit_TinyUSB_Arduino/src/host/hub.h @@ -0,0 +1,227 @@ +/* + * 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. + * + * This file is part of the TinyUSB stack. + */ + +#ifndef TUSB_HUB_H_ +#define TUSB_HUB_H_ + +#include "common/tusb_common.h" + +#ifdef __cplusplus + extern "C" { +#endif + +//--------------------------------------------------------------------+ +// Configuration +//--------------------------------------------------------------------+ + +#ifndef CFG_TUH_HUB_BUFSIZE + #define CFG_TUH_HUB_BUFSIZE 12 +#endif + +//--------------------------------------------------------------------+ +// +//--------------------------------------------------------------------+ +enum { + HUB_REQUEST_GET_STATUS = 0 , + HUB_REQUEST_CLEAR_FEATURE = 1 , + // 2 is reserved + HUB_REQUEST_SET_FEATURE = 3 , + // 4-5 are reserved + HUB_REQUEST_GET_DESCRIPTOR = 6 , + HUB_REQUEST_SET_DESCRIPTOR = 7 , + HUB_REQUEST_CLEAR_TT_BUFFER = 8 , + HUB_REQUEST_RESET_TT = 9 , + HUB_REQUEST_GET_TT_STATE = 10 , + HUB_REQUEST_STOP_TT = 11 +}; + +enum { + HUB_FEATURE_HUB_LOCAL_POWER_CHANGE = 0, + HUB_FEATURE_HUB_OVER_CURRENT_CHANGE +}; + +enum{ + HUB_FEATURE_PORT_CONNECTION = 0, + HUB_FEATURE_PORT_ENABLE = 1, + HUB_FEATURE_PORT_SUSPEND = 2, + HUB_FEATURE_PORT_OVER_CURRENT = 3, + HUB_FEATURE_PORT_RESET = 4, + // 5-7 are reserved + HUB_FEATURE_PORT_POWER = 8, + HUB_FEATURE_PORT_LOW_SPEED = 9, + // 10-15 are reserved + HUB_FEATURE_PORT_CONNECTION_CHANGE = 16, + HUB_FEATURE_PORT_ENABLE_CHANGE = 17, + HUB_FEATURE_PORT_SUSPEND_CHANGE = 18, + HUB_FEATURE_PORT_OVER_CURRENT_CHANGE = 19, + HUB_FEATURE_PORT_RESET_CHANGE = 20, + HUB_FEATURE_PORT_TEST = 21, + HUB_FEATURE_PORT_INDICATOR = 22 +}; + +enum { + HUB_CHARS_POWER_GANGED_SWITCHING = 0, + HUB_CHARS_POWER_INDIVIDUAL_SWITCHING = 1, +}; + +enum { + HUB_CHARS_OVER_CURRENT_GLOBAL = 0, + HUB_CHARS_OVER_CURRENT_INDIVIDUAL = 1, +}; + +// Hub Interface Protocol (USB 2.0 spec Table 11-16) +typedef enum { + HUB_PROTOCOL_FULL_SPEED = 0, // Full speed hub + HUB_PROTOCOL_HIGH_SPEED_STT = 1, // Hi-speed hub with single TT + HUB_PROTOCOL_HIGH_SPEED_MTT = 2, // Hi-speed hub with multiple TTs +} hub_protocol_t; + +typedef struct TU_ATTR_PACKED{ + uint8_t bLength ; ///< Size of descriptor + uint8_t bDescriptorType ; ///< Other_speed_Configuration Type + uint8_t bNbrPorts; + uint16_t wHubCharacteristics; + uint8_t bPwrOn2PwrGood; + uint8_t bHubContrCurrent; + uint8_t DeviceRemovable; // bitmap each bit for a port (from bit1) + uint8_t PortPwrCtrlMask; // just for compatibility, should be 0xff +} hub_desc_cs_t; +TU_VERIFY_STATIC(sizeof(hub_desc_cs_t) == 9, "size is not correct"); +TU_VERIFY_STATIC(CFG_TUH_HUB_BUFSIZE >= sizeof(hub_desc_cs_t), "buffer is not big enough"); + +typedef struct TU_ATTR_PACKED { + struct TU_ATTR_PACKED { + uint8_t logical_power_switching_mode : 2; // [0..1] gannged or individual power switching + uint8_t compound_device : 1; // [2] hub is part of compound device + uint8_t over_current_protect_mode : 2; // [3..4] global or individual port over-current protection + uint8_t tt_think_time : 2; // [5..6] TT think time + uint8_t port_indicator_supported : 1; // [7] port indicator supported + }; + uint8_t rsv1; +} hub_characteristics_t; +TU_VERIFY_STATIC(sizeof(hub_characteristics_t) == 2, "size is not correct"); + +// data in response of HUB_REQUEST_GET_STATUS, wIndex = 0 (hub) +typedef struct { + union{ + struct TU_ATTR_PACKED { + uint16_t local_power_source : 1; + uint16_t over_current : 1; + uint16_t : 14; + }; + + uint16_t value; + } status, change; +} hub_status_response_t; +TU_VERIFY_STATIC( sizeof(hub_status_response_t) == 4, "size is not correct"); + +// data in response of HUB_REQUEST_GET_STATUS, wIndex = Port num +typedef struct { + union TU_ATTR_PACKED { + struct TU_ATTR_PACKED { + // Bit 0-4 are for change & status + uint16_t connection : 1; // [0] 0 = no device, 1 = device connected + uint16_t port_enable : 1; // [1] port is enabled + uint16_t suspend : 1; // [2] + uint16_t over_current : 1; // [3] over-current exists + uint16_t reset : 1; // [4] 0 = no reset, 1 = resetting + + // From Bit 5 are for status only + uint16_t rsv5_7 : 3; // [5..7] reserved + uint16_t port_power : 1; // [8] 0 = port is off, 1 = port is on + uint16_t low_speed : 1; // [9] low speed device attached + uint16_t high_speed : 1; // [10] high speed device attached + uint16_t port_test_mode : 1; // [11] port in test mode + uint16_t port_indicator_control : 1; // [12] 0: default color, 1: indicator is software controlled + uint16_t TU_RESERVED : 3; // [13..15] reserved + }; + + uint16_t value; + } status, change; +} hub_port_status_response_t; +TU_VERIFY_STATIC( sizeof(hub_port_status_response_t) == 4, "size is not correct"); + +//--------------------------------------------------------------------+ +// HUB API +//--------------------------------------------------------------------+ + +// Clear port feature +bool hub_port_clear_feature(uint8_t hub_addr, uint8_t hub_port, uint8_t feature, + tuh_xfer_cb_t complete_cb, uintptr_t user_data); + +// Set port feature +bool hub_port_set_feature(uint8_t hub_addr, uint8_t hub_port, uint8_t feature, + tuh_xfer_cb_t complete_cb, uintptr_t user_data); + +// Get port status +// If hub_port != 0, resp is ignored. hub_port_get_status_local() can be used to retrieve the status +bool hub_port_get_status(uint8_t hub_addr, uint8_t hub_port, void *resp, + tuh_xfer_cb_t complete_cb, uintptr_t user_data); + +// Get port status from local cache. This does not send a request to the device +bool hub_port_get_status_local(uint8_t hub_addr, uint8_t hub_port, hub_port_status_response_t* resp); + +// Get status from Interrupt endpoint +bool hub_edpt_status_xfer(uint8_t daddr); + +// Reset a port +TU_ATTR_ALWAYS_INLINE static inline +bool hub_port_reset(uint8_t hub_addr, uint8_t hub_port, tuh_xfer_cb_t complete_cb, uintptr_t user_data) { + return hub_port_set_feature(hub_addr, hub_port, HUB_FEATURE_PORT_RESET, complete_cb, user_data); +} + +// Clear Port Reset Change +TU_ATTR_ALWAYS_INLINE static inline +bool hub_port_clear_reset_change(uint8_t hub_addr, uint8_t hub_port, tuh_xfer_cb_t complete_cb, uintptr_t user_data) { + return hub_port_clear_feature(hub_addr, hub_port, HUB_FEATURE_PORT_RESET_CHANGE, complete_cb, user_data); +} + +// Get Hub status (port = 0) +TU_ATTR_ALWAYS_INLINE static inline +bool hub_get_status(uint8_t hub_addr, void* resp, tuh_xfer_cb_t complete_cb, uintptr_t user_data) { + return hub_port_get_status(hub_addr, 0, resp, complete_cb, user_data); +} + +// Clear Hub feature +TU_ATTR_ALWAYS_INLINE static inline +bool hub_clear_feature(uint8_t hub_addr, uint8_t feature, tuh_xfer_cb_t complete_cb, uintptr_t user_data) { + return hub_port_clear_feature(hub_addr, 0, feature, complete_cb, user_data); +} +//--------------------------------------------------------------------+ +// Internal Class Driver API +//--------------------------------------------------------------------+ +bool hub_init(void); +bool hub_deinit(void); +uint16_t hub_open(uint8_t rhport, uint8_t dev_addr, const tusb_desc_interface_t *itf_desc, uint16_t max_len); +bool hub_set_config(uint8_t daddr, uint8_t itf_num); +bool hub_xfer_cb(uint8_t daddr, uint8_t ep_addr, xfer_result_t event, uint32_t xferred_bytes); +void hub_close(uint8_t dev_addr); + +#ifdef __cplusplus + } +#endif + +#endif diff --git a/libraries/Adafruit_TinyUSB_Arduino/src/host/usbh.c b/libraries/Adafruit_TinyUSB_Arduino/src/host/usbh.c new file mode 100644 index 0000000..490724b --- /dev/null +++ b/libraries/Adafruit_TinyUSB_Arduino/src/host/usbh.c @@ -0,0 +1,2050 @@ +/* + * 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. + * + * This file is part of the TinyUSB stack. + */ + +#include "tusb_option.h" + +#if CFG_TUH_ENABLED + +#include "hcd.h" +#include "tusb.h" +#include "usbh_pvt.h" +#include "hub.h" + +//--------------------------------------------------------------------+ +// Configuration +//--------------------------------------------------------------------+ +#ifndef CFG_TUH_TASK_QUEUE_SZ + #define CFG_TUH_TASK_QUEUE_SZ 16 +#endif + +#ifndef CFG_TUH_INTERFACE_MAX + #define CFG_TUH_INTERFACE_MAX 8 +#endif + +enum { + USBH_CONTROL_RETRY_MAX = 3, +}; + +//--------------------------------------------------------------------+ +// Weak stubs: invoked if no strong implementation is available +//--------------------------------------------------------------------+ +TU_ATTR_WEAK bool hcd_deinit(uint8_t rhport) { + (void) rhport; return false; +} + +TU_ATTR_WEAK bool hcd_configure(uint8_t rhport, uint32_t cfg_id, const void* cfg_param) { + (void) rhport; (void) cfg_id; (void) cfg_param; + return false; +} + +TU_ATTR_WEAK void tuh_enum_descriptor_device_cb(uint8_t daddr, const tusb_desc_device_t *desc_device) { + (void) daddr; (void) desc_device; +} + +TU_ATTR_WEAK bool tuh_enum_descriptor_configuration_cb(uint8_t daddr, uint8_t cfg_index, const tusb_desc_configuration_t *desc_config) { + (void) daddr; (void) cfg_index; (void) desc_config; + return true; +} + +TU_ATTR_WEAK void tuh_event_hook_cb(uint8_t rhport, uint32_t eventid, bool in_isr) { + (void) rhport; (void) eventid; (void) in_isr; +} + +TU_ATTR_WEAK bool hcd_dcache_clean(const void* addr, uint32_t data_size) { + (void) addr; (void) data_size; + return false; +} + +TU_ATTR_WEAK bool hcd_dcache_invalidate(const void* addr, uint32_t data_size) { + (void) addr; (void) data_size; + return false; +} + +TU_ATTR_WEAK bool hcd_dcache_clean_invalidate(const void* addr, uint32_t data_size) { + (void) addr; (void) data_size; + return false; +} + +TU_ATTR_WEAK usbh_class_driver_t const* usbh_app_driver_get_cb(uint8_t* driver_count) { + *driver_count = 0; + return NULL; +} + +TU_ATTR_WEAK void tuh_mount_cb(uint8_t daddr) { + (void) daddr; +} + +TU_ATTR_WEAK void tuh_umount_cb(uint8_t daddr) { + (void) daddr; +} + +//--------------------------------------------------------------------+ +// Data Structure +//--------------------------------------------------------------------+ + +// Device Descriptor (without bLength and bDescriptorType header) +typedef struct TU_ATTR_PACKED { + uint16_t bcdUSB; + uint8_t bDeviceClass; + uint8_t bDeviceSubClass; + uint8_t bDeviceProtocol; + uint8_t bMaxPacketSize0; + uint16_t idVendor; + uint16_t idProduct; + uint16_t bcdDevice; + uint8_t iManufacturer; + uint8_t iProduct; + uint8_t iSerialNumber; + uint8_t bNumConfigurations; +} desc_device_noheader_t; + +TU_VERIFY_STATIC( sizeof(desc_device_noheader_t) == 16u, "size is not correct"); + +typedef struct { + tuh_bus_info_t bus_info; + desc_device_noheader_t desc_device; + + // Device State + struct TU_ATTR_PACKED { + volatile uint8_t connected : 1; // After 1st transfer + volatile uint8_t addressed : 1; // After SET_ADDR + volatile uint8_t configured : 1; // After SET_CONFIG and all drivers are configured + volatile uint8_t suspended : 1; // Bus suspended + // volatile uint8_t removing : 1; // Physically disconnected, waiting to be processed by usbh + }; + + // Endpoint & Interface + uint8_t itf2drv[CFG_TUH_INTERFACE_MAX]; // map interface number to driver (0xff is invalid) + uint8_t ep2drv[CFG_TUH_ENDPOINT_MAX][2]; // map endpoint to driver ( 0xff is invalid ), can use only 4-bit each + + volatile uint8_t ep_status[CFG_TUH_ENDPOINT_MAX][2]; + +#if CFG_TUH_API_EDPT_XFER + // TODO array can be CFG_TUH_ENDPOINT_MAX-1 + struct { + tuh_xfer_cb_t complete_cb; + uintptr_t user_data; + }ep_callback[CFG_TUH_ENDPOINT_MAX][2]; +#endif + +} usbh_device_t; + +// sum of end device + hub +#define TOTAL_DEVICES (CFG_TUH_DEVICE_MAX + CFG_TUH_HUB) + +// all devices excluding zero-address +// hub address start from CFG_TUH_DEVICE_MAX+1 +// TODO: hub can has its own simpler struct to save memory +static usbh_device_t _usbh_devices[TOTAL_DEVICES]; + +// Mutex for claiming endpoint +#if OSAL_MUTEX_REQUIRED +static osal_mutex_def_t _usbh_mutexdef; +static osal_mutex_t _usbh_mutex; +#else +#define _usbh_mutex NULL +#endif + +// Spinlock for interrupt handler +static OSAL_SPINLOCK_DEF(_usbh_spin, usbh_int_set); + +// Event queue: usbh_int_set() is used as mutex in OS NONE config +OSAL_QUEUE_DEF(usbh_int_set, _usbh_qdef, CFG_TUH_TASK_QUEUE_SZ, hcd_event_t); +static osal_queue_t _usbh_q; + + #if CFG_TUH_HUB +// Deferred attachment queue, only needed when using hub +OSAL_QUEUE_DEF(usbh_int_set, _usbh_daqdef, CFG_TUH_HUB, hcd_event_t); +static osal_queue_t _usbh_daq; + #endif + +// Control transfers: since most controllers do not support multiple control transfers +// on multiple devices concurrently and control transfers are not used much except for +// enumeration, we will only execute control transfers one at a time. +typedef struct { + uint8_t* buffer; + tuh_xfer_cb_t complete_cb; + uintptr_t user_data; + + volatile uint8_t stage; + uint8_t daddr; + volatile uint16_t actual_len; + uint8_t failed_count; +} usbh_ctrl_xfer_info_t; + +typedef struct { + tusb_defer_func_t func; + uintptr_t arg; + uint32_t at_ms; +} usbh_call_after_t; + +typedef struct { + uint8_t controller_id; // controller ID + uint8_t enumerating_daddr; // device address of the device being enumerated + uint8_t attach_debouncing_bm; // bitmask for roothub port attach debouncing + tuh_bus_info_t dev0_bus; // bus info for dev0 in enumeration + usbh_ctrl_xfer_info_t ctrl_xfer_info; // control transfer + usbh_call_after_t call_after; +} usbh_data_t; + +static usbh_data_t _usbh_data = { + .controller_id = TUSB_INDEX_INVALID_8, +}; + +typedef struct { + TUH_EPBUF_TYPE_DEF(tusb_control_request_t, request); + TUH_EPBUF_DEF(ctrl, CFG_TUH_ENUMERATION_BUFSIZE); +} usbh_epbuf_t; +CFG_TUH_MEM_SECTION static usbh_epbuf_t _usbh_epbuf; + +//--------------------------------------------------------------------+ +// Class Driver +//--------------------------------------------------------------------+ +#if CFG_TUSB_DEBUG >= CFG_TUH_LOG_LEVEL + #define DRIVER_NAME(_name) _name +#else + #define DRIVER_NAME(_name) NULL +#endif + +static usbh_class_driver_t const usbh_class_drivers[] = { + #if CFG_TUH_CDC + { + .name = DRIVER_NAME("CDC"), + .init = cdch_init, + .deinit = cdch_deinit, + .open = cdch_open, + .set_config = cdch_set_config, + .xfer_cb = cdch_xfer_cb, + .close = cdch_close + }, + #endif + + #if CFG_TUH_MSC + { + .name = DRIVER_NAME("MSC"), + .init = msch_init, + .deinit = msch_deinit, + .open = msch_open, + .set_config = msch_set_config, + .xfer_cb = msch_xfer_cb, + .close = msch_close + }, + #endif + + #if CFG_TUH_HID + { + .name = DRIVER_NAME("HID"), + .init = hidh_init, + .deinit = hidh_deinit, + .open = hidh_open, + .set_config = hidh_set_config, + .xfer_cb = hidh_xfer_cb, + .close = hidh_close + }, + #endif + + #if CFG_TUH_MIDI + { + .name = DRIVER_NAME("MIDI"), + .init = midih_init, + .deinit = midih_deinit, + .open = midih_open, + .set_config = midih_set_config, + .xfer_cb = midih_xfer_cb, + .close = midih_close + }, + #endif + + #if CFG_TUH_HUB + { + .name = DRIVER_NAME("HUB"), + .init = hub_init, + .deinit = hub_deinit, + .open = hub_open, + .set_config = hub_set_config, + .xfer_cb = hub_xfer_cb, + .close = hub_close + }, + #endif + + #if CFG_TUH_VENDOR + { + .name = DRIVER_NAME("VENDOR"), + .init = cush_init, + .deinit = cush_deinit, + .open = cush_open, + .set_config = cush_set_config, + .xfer_cb = cush_isr, + .close = cush_close + } + #endif +}; + +enum { BUILTIN_DRIVER_COUNT = TU_ARRAY_SIZE(usbh_class_drivers) }; + +// Additional class drivers implemented by application +static usbh_class_driver_t const * _app_driver = NULL; +static uint8_t _app_driver_count = 0; + +#define TOTAL_DRIVER_COUNT (_app_driver_count + BUILTIN_DRIVER_COUNT) + +// virtually joins built-in and application drivers together. +// Application is positioned first to allow overwriting built-in ones. +TU_ATTR_ALWAYS_INLINE static inline usbh_class_driver_t const *get_driver(uint8_t drv_id) { + usbh_class_driver_t const *driver = NULL; + if (drv_id < _app_driver_count) { + driver = &_app_driver[drv_id]; + } else { + drv_id -= _app_driver_count; + if (drv_id < BUILTIN_DRIVER_COUNT) { + driver = &usbh_class_drivers[drv_id]; + } + } + + return driver; +} + +//--------------------------------------------------------------------+ +// Function Inline and Prototypes +//--------------------------------------------------------------------+ +static void enum_new_device(hcd_event_t* event); +static void enum_delay_async(uintptr_t state); +static void process_remove_event(hcd_event_t *event); +static void remove_device_tree(uint8_t rhport, uint8_t hub_addr, uint8_t hub_port); +static bool usbh_edpt_control_open(uint8_t dev_addr, uint8_t max_packet_size); +static bool usbh_control_xfer_cb (uint8_t daddr, uint8_t ep_addr, xfer_result_t result, uint32_t xferred_bytes); + +TU_ATTR_ALWAYS_INLINE static inline usbh_device_t* get_device(uint8_t dev_addr) { + TU_VERIFY(dev_addr > 0 && dev_addr <= TOTAL_DEVICES, NULL); + return &_usbh_devices[dev_addr-1]; +} + +TU_ATTR_ALWAYS_INLINE static inline bool is_hub_addr(uint8_t daddr) { + return (CFG_TUH_HUB > 0) && (daddr > CFG_TUH_DEVICE_MAX); //-V560 +} + +TU_ATTR_ALWAYS_INLINE static inline bool queue_event(hcd_event_t const * event, bool in_isr) { + TU_ASSERT(osal_queue_send(_usbh_q, event, in_isr)); + tuh_event_hook_cb(event->rhport, event->event_id, in_isr); + return true; +} + +TU_ATTR_ALWAYS_INLINE static inline void _control_set_xfer_stage(uint8_t stage) { + if (_usbh_data.ctrl_xfer_info.stage != stage) { + (void) osal_mutex_lock(_usbh_mutex, OSAL_TIMEOUT_WAIT_FOREVER); + _usbh_data.ctrl_xfer_info.stage = stage; + (void) osal_mutex_unlock(_usbh_mutex); + } +} + +TU_ATTR_ALWAYS_INLINE static inline bool usbh_setup_send(uint8_t daddr, const uint8_t setup_packet[8]) { + const uint8_t rhport = usbh_get_rhport(daddr); + const bool ret = hcd_setup_send(rhport, daddr, setup_packet); + if (!ret) { + _control_set_xfer_stage(CONTROL_STAGE_IDLE); + } + return ret; +} + +bool usbh_defer_func_ms_async(uint32_t ms, tusb_defer_func_t func, uintptr_t param) { + TU_ASSERT(_usbh_data.call_after.func == NULL); + TU_LOG_USBH("USBH schedule function after %u ms\r\n", (unsigned int)ms); + _usbh_data.call_after.func = func; + _usbh_data.call_after.arg = param; + // add one to ensure we wait at least 'ms' milliseconds + _usbh_data.call_after.at_ms = tusb_time_millis_api() + ms + 1; + return true; +} + +TU_ATTR_ALWAYS_INLINE static inline void usbh_device_close(uint8_t rhport, uint8_t daddr) { + hcd_device_close(rhport, daddr); + + // abort any ongoing control transfer + if (daddr == _usbh_data.ctrl_xfer_info.daddr) { + _control_set_xfer_stage(CONTROL_STAGE_IDLE); + } + + // invalidate if enumerating + if (daddr == _usbh_data.enumerating_daddr) { + _usbh_data.enumerating_daddr = TUSB_INDEX_INVALID_8; + // clear enum delay function of the device being removed + if (_usbh_data.call_after.func == enum_delay_async) { + _usbh_data.call_after.func = NULL; + } + } +} + +//--------------------------------------------------------------------+ +// Device API +//--------------------------------------------------------------------+ +bool tuh_mounted(uint8_t dev_addr) { + usbh_device_t *dev = get_device(dev_addr); + TU_VERIFY(dev); + return dev->configured; +} + +bool tuh_connected(uint8_t daddr) { + if (daddr == 0) { + return _usbh_data.enumerating_daddr == 0; + } else { + const usbh_device_t* dev = get_device(daddr); + TU_VERIFY(dev != NULL); + return dev->connected; + } +} + +bool tuh_vid_pid_get(uint8_t dev_addr, uint16_t *vid, uint16_t *pid) { + *vid = *pid = 0; + + usbh_device_t const *dev = get_device(dev_addr); + TU_VERIFY(dev && dev->addressed && dev->desc_device.idVendor != 0); + + *vid = dev->desc_device.idVendor; + *pid = dev->desc_device.idProduct; + + return true; +} + +bool tuh_descriptor_get_device_local(uint8_t daddr, tusb_desc_device_t* desc_device) { + usbh_device_t *dev = get_device(daddr); + TU_VERIFY(dev && desc_device); + + desc_device->bLength = sizeof(tusb_desc_device_t); + desc_device->bDescriptorType = TUSB_DESC_DEVICE; + memcpy((uint8_t*) desc_device + offsetof(tusb_desc_device_t, bcdUSB), &dev->desc_device, sizeof(desc_device_noheader_t)); + + return true; +} + +tusb_speed_t tuh_speed_get(uint8_t daddr) { + tuh_bus_info_t bus_info; + tuh_bus_info_get(daddr, &bus_info); + return (tusb_speed_t)bus_info.speed; +} + +bool tuh_rhport_is_active(uint8_t rhport) { + return _usbh_data.controller_id == rhport; +} + +bool tuh_rhport_reset_bus(uint8_t rhport, bool active) { + TU_VERIFY(tuh_rhport_is_active(rhport)); + if (active) { + hcd_port_reset(rhport); + } else { + hcd_port_reset_end(rhport); + } + return true; +} + +//--------------------------------------------------------------------+ +// PUBLIC API (Parameter Verification is required) +//--------------------------------------------------------------------+ +bool tuh_configure(uint8_t rhport, uint32_t cfg_id, const void *cfg_param) { + return hcd_configure(rhport, cfg_id, cfg_param); +} + +static void clear_device(usbh_device_t* dev) { + tu_memclr(dev, sizeof(usbh_device_t)); + (void) memset(dev->itf2drv, TUSB_INDEX_INVALID_8, sizeof(dev->itf2drv)); // invalid mapping + (void) memset(dev->ep2drv , TUSB_INDEX_INVALID_8, sizeof(dev->ep2drv )); // invalid mapping +} + +bool tuh_inited(void) { + return _usbh_data.controller_id != TUSB_INDEX_INVALID_8; +} + +bool tuh_rhport_init(uint8_t rhport, const tusb_rhport_init_t* rh_init) { + if (tuh_rhport_is_active(rhport)) { + return true; // skip if already initialized + } +#if CFG_TUSB_DEBUG >= CFG_TUH_LOG_LEVEL + char const* speed_str = 0; + switch (rh_init->speed) { + case TUSB_SPEED_HIGH: + speed_str = "High"; + break; + case TUSB_SPEED_FULL: + speed_str = "Full"; + break; + case TUSB_SPEED_LOW: + speed_str = "Low"; + break; + case TUSB_SPEED_AUTO: + speed_str = "Auto"; + break; + default: + break; + } + TU_LOG_USBH("USBH init on controller %u, speed = %s\r\n", rhport, speed_str); +#endif + + // Init host stack if not already + if (!tuh_inited()) { + TU_LOG_INT_USBH(sizeof(usbh_data_t)); + TU_LOG_INT_USBH(sizeof(usbh_device_t)); + TU_LOG_INT_USBH(sizeof(hcd_event_t)); + TU_LOG_INT_USBH(sizeof(tuh_xfer_t)); + TU_LOG_INT_USBH(sizeof(tu_fifo_t)); + TU_LOG_INT_USBH(sizeof(tu_edpt_stream_t)); + + osal_spin_init(&_usbh_spin); + + // Event queue + _usbh_q = osal_queue_create(&_usbh_qdef); + TU_ASSERT(_usbh_q != NULL); + + #if CFG_TUH_HUB + // Deferred attachment queue + _usbh_daq = osal_queue_create(&_usbh_daqdef); + TU_ASSERT(_usbh_daq != NULL); + #endif + + #if OSAL_MUTEX_REQUIRED + // Init mutex + _usbh_mutex = osal_mutex_create(&_usbh_mutexdef); + TU_ASSERT(_usbh_mutex); + #endif + + // Get application driver if available + _app_driver = usbh_app_driver_get_cb(&_app_driver_count); + + // Device + tu_memclr(_usbh_devices, sizeof(_usbh_devices)); + tu_memclr(&_usbh_data, sizeof(_usbh_data)); + + _usbh_data.controller_id = TUSB_INDEX_INVALID_8; + _usbh_data.enumerating_daddr = TUSB_INDEX_INVALID_8; + + for (uint8_t i = 0; i < TOTAL_DEVICES; i++) { + clear_device(&_usbh_devices[i]); + } + + // Class drivers + for (uint8_t drv_id = 0; drv_id < TOTAL_DRIVER_COUNT; drv_id++) { + usbh_class_driver_t const* driver = get_driver(drv_id); + if (driver != NULL) { + TU_LOG_USBH("%s init\r\n", driver->name); + driver->init(); + } + } + } + + // Init host controller + _usbh_data.controller_id = rhport; + TU_ASSERT(hcd_init(rhport, rh_init)); + hcd_int_enable(rhport); + + return true; +} + +bool tuh_deinit(uint8_t rhport) { + if (!tuh_rhport_is_active(rhport)) { + return true; + } + + // deinit host controller + hcd_int_disable(rhport); + TU_ASSERT(hcd_deinit(rhport)); + _usbh_data.controller_id = TUSB_INDEX_INVALID_8; + + // remove all devices on this rhport (hub_addr = 0, hub_port = 0) + remove_device_tree(rhport, 0, 0); + + // deinit host stack if no controller is active + if (!tuh_inited()) { + // Class drivers + for (uint8_t drv_id = 0; drv_id < TOTAL_DRIVER_COUNT; drv_id++) { + usbh_class_driver_t const* driver = get_driver(drv_id); + if (driver && driver->deinit) { + TU_LOG_USBH("%s deinit\r\n", driver->name); + driver->deinit(); + } + } + + osal_queue_delete(_usbh_q); + _usbh_q = NULL; + + #if CFG_TUH_HUB + osal_queue_delete(_usbh_daq); + _usbh_daq = NULL; + #endif + + #if OSAL_MUTEX_REQUIRED + // TODO make sure there is no task waiting on this mutex + osal_mutex_delete(_usbh_mutex); + _usbh_mutex = NULL; + #endif + } + + return true; +} + +bool tuh_task_event_ready(void) { + if (!tuh_inited()) { + return false; // Skip if tusb stack is not initialized + } + if (!osal_queue_empty(_usbh_q)) { + return true; + } + + #if CFG_TUH_HUB + if (_usbh_data.enumerating_daddr == TUSB_INDEX_INVALID_8 && + !osal_queue_empty(_usbh_daq)) { + return true; + } + #endif + + if (_usbh_data.call_after.func) { + int32_t remain_ms = (int32_t)(_usbh_data.call_after.at_ms - tusb_time_millis_api()); + if (remain_ms <= 0) { + return true; + } + } + + return false; +} + +/* USB Host Driver task + * This top level thread manages all host controller event and delegates events to class-specific drivers. + * This should be called periodically within the mainloop or rtos thread. + * + @code + int main(void) { + application_init(); + tusb_init(0, TUSB_ROLE_HOST); + + while(1) { // the mainloop + application_code(); + tuh_task(); // tinyusb host task + } + } + @endcode + */ +void tuh_task_ext(uint32_t timeout_ms, bool in_isr) { + // Skip if stack is not initialized + if (!tuh_inited()) { + return; + } + + (void) in_isr; // not implemented yet + + // Loop until there are no more events in the queue or CFG_TUH_TASK_EVENTS_PER_RUN is reached + for (unsigned epr = 0;; epr++) { + #if CFG_TUH_TASK_EVENTS_PER_RUN > 0 + if (epr >= CFG_TUH_TASK_EVENTS_PER_RUN) { + TU_LOG_USBH("USBH event limit (" TU_XSTRING(CFG_TUH_TASK_EVENTS_PER_RUN) ") reached\r\n"); + break; + } + #endif + + // Process call_after_ms function if ms is reached + tusb_defer_func_t after_cb = _usbh_data.call_after.func; + if (after_cb) { + int32_t remain_ms = (int32_t)(_usbh_data.call_after.at_ms - tusb_time_millis_api()); + if (remain_ms <= 0) { + // delay expired, run callback now + TU_LOG_USBH("USBH invoke scheduled function\r\n"); + _usbh_data.call_after.func = NULL; + after_cb(_usbh_data.call_after.arg); + } + + // above after_cb() can re-schedule another function, we need to re-check and reduce timeout of + // the main event timeout to make sure we aren't blocking more than call_after remaining ms. + if (_usbh_data.call_after.func != NULL) { + remain_ms = (int32_t) (_usbh_data.call_after.at_ms - tusb_time_millis_api()); + if (remain_ms <= 0) { + timeout_ms = 0; // expired already + } else if (timeout_ms > (uint32_t)remain_ms) { + timeout_ms = (uint32_t)remain_ms; + } + } + } + + hcd_event_t event; + + #if CFG_TUH_HUB + // Get deferred device attachments if none is enumerating + bool has_deferred_attach = false; + if (_usbh_data.enumerating_daddr == TUSB_INDEX_INVALID_8) { + // zero wait to avoid blocking the main event queue + has_deferred_attach = osal_queue_receive(_usbh_daq, &event, 0); + } + + if (!has_deferred_attach) // skip event queue to process deferred attach + #endif + { + if (!osal_queue_receive(_usbh_q, &event, timeout_ms)) { + return; + } + } + + switch (event.event_id) { + case HCD_EVENT_DEVICE_ATTACH: + // Should we miss the hub detach event due to high traffic, Or due to physical debouncing, some devices can + // cause multiple attaches (actually reset) without a detached event. + // Force remove currently mounted with the same bus info (rhport, hub addr, hub port) if exists + process_remove_event(&event); + + // due to the shared control buffer, we must fully complete enumerating one device first. + if (_usbh_data.enumerating_daddr == TUSB_INDEX_INVALID_8) { + // New device attached and we are ready + TU_LOG_USBH("[%u:] USBH Device Attach\r\n", event.rhport); + _usbh_data.enumerating_daddr = 0; // enumerate new device with address 0 + enum_new_device(&event); + } + #if CFG_TUH_HUB + else { + TU_LOG_USBH("[%u:] USBH Defer Attach until current enumeration complete\r\n", event.rhport); + TU_ASSERT(osal_queue_send(_usbh_daq, &event, in_isr), ); + } + #endif + break; + + case HCD_EVENT_DEVICE_REMOVE: + TU_LOG_USBH("[%u:%u:%u] USBH Device Removed\r\n", event.rhport, event.connection.hub_addr, event.connection.hub_port); + process_remove_event(&event); + break; + + case HCD_EVENT_XFER_COMPLETE: { + uint8_t const ep_addr = event.xfer_complete.ep_addr; + uint8_t const epnum = tu_edpt_number(ep_addr); + uint8_t const ep_dir = (uint8_t) tu_edpt_dir(ep_addr); + + TU_LOG_USBH("[:%u] on EP %02X with %u bytes: %s\r\n", + event.dev_addr, ep_addr, (unsigned int) event.xfer_complete.len, tu_str_xfer_result[event.xfer_complete.result]); + + if (event.dev_addr == 0) { + // device 0 only has control endpoint + TU_ASSERT(epnum == 0,); + usbh_control_xfer_cb(event.dev_addr, ep_addr, (xfer_result_t) event.xfer_complete.result, event.xfer_complete.len); + } else { + usbh_device_t* dev = get_device(event.dev_addr); + TU_VERIFY(dev && dev->connected,); + + // clear busy and claimed + dev->ep_status[epnum][ep_dir] &= (uint8_t) ~(TU_EDPT_STATE_BUSY | TU_EDPT_STATE_CLAIMED); + + if (0 == epnum) { + usbh_control_xfer_cb(event.dev_addr, ep_addr, (xfer_result_t) event.xfer_complete.result, event.xfer_complete.len); + } else { + // Prefer application callback over built-in one if available. This occurs when tuh_edpt_xfer() is used + // with enabled driver e.g HID endpoint + #if CFG_TUH_API_EDPT_XFER + tuh_xfer_cb_t const complete_cb = dev->ep_callback[epnum][ep_dir].complete_cb; + if (complete_cb != NULL) { + // re-construct xfer info + tuh_xfer_t xfer = { + .daddr = event.dev_addr, + .ep_addr = ep_addr, + .result = (xfer_result_t)event.xfer_complete.result, + .actual_len = event.xfer_complete.len, + .buflen = 0, // not available + .buffer = NULL, // not available + .complete_cb = complete_cb, + .user_data = dev->ep_callback[epnum][ep_dir].user_data + }; + complete_cb(&xfer); + }else + #endif + { + uint8_t drv_id = dev->ep2drv[epnum][ep_dir]; + usbh_class_driver_t const* driver = get_driver(drv_id); + if (driver != NULL) { + TU_LOG_USBH(" %s xfer callback\r\n", driver->name); + driver->xfer_cb(event.dev_addr, ep_addr, (xfer_result_t) event.xfer_complete.result, + event.xfer_complete.len); + } else { + // no driver/callback responsible for this transfer + TU_ASSERT(false,); + } + } + } + } + break; + } + + case USBH_EVENT_FUNC_CALL: + if (event.func_call.func != NULL) { + event.func_call.func(event.func_call.param); + } + break; + + default: + // unknown event + break; + } + + // allow to exit tuh_task() if there is no event in the next run + timeout_ms = 0; + } +} + +//--------------------------------------------------------------------+ +// Control transfer +//--------------------------------------------------------------------+ + +static void _control_blocking_complete_cb(tuh_xfer_t* xfer) { + // update result + *((xfer_result_t*) xfer->user_data) = xfer->result; +} + +// TODO timeout_ms is not supported yet +bool tuh_control_xfer (tuh_xfer_t* xfer) { + TU_VERIFY(xfer->ep_addr == 0 && xfer->setup); // EP0 with setup packet + const uint8_t daddr = xfer->daddr; + TU_VERIFY(tuh_connected(daddr)); + + usbh_ctrl_xfer_info_t* ctrl_info = &_usbh_data.ctrl_xfer_info; + + TU_VERIFY(ctrl_info->stage == CONTROL_STAGE_IDLE); // pre-check to help reducing mutex lock + (void) osal_mutex_lock(_usbh_mutex, OSAL_TIMEOUT_WAIT_FOREVER); + bool const is_idle = (ctrl_info->stage == CONTROL_STAGE_IDLE); + if (is_idle) { + ctrl_info->stage = CONTROL_STAGE_SETUP; + ctrl_info->daddr = daddr; + ctrl_info->actual_len = 0; + ctrl_info->failed_count = 0; + + ctrl_info->buffer = xfer->buffer; + ctrl_info->complete_cb = xfer->complete_cb; + ctrl_info->user_data = xfer->user_data; + _usbh_epbuf.request = (*xfer->setup); + } + (void) osal_mutex_unlock(_usbh_mutex); + + TU_VERIFY(is_idle); + TU_LOG_USBH("[%u:%u] %s: ", usbh_get_rhport(daddr), daddr, + (xfer->setup->bmRequestType_bit.type == TUSB_REQ_TYPE_STANDARD && xfer->setup->bRequest <= TUSB_REQ_SYNCH_FRAME) ? + tu_str_std_request[xfer->setup->bRequest] : "Class Request"); + TU_LOG_BUF_USBH(xfer->setup, 8); + + if (xfer->complete_cb != NULL) { + TU_ASSERT(usbh_setup_send(daddr, (uint8_t const *) &_usbh_epbuf.request)); + }else { + // blocking if complete callback is not provided + // change callback to internal blocking, and result as user argument + volatile xfer_result_t result = XFER_RESULT_INVALID; + + // use user_data to point to xfer_result_t + ctrl_info->user_data = (uintptr_t) &result; + ctrl_info->complete_cb = _control_blocking_complete_cb; + + TU_ASSERT(usbh_setup_send(daddr, (uint8_t const *) &_usbh_epbuf.request)); + + while (result == XFER_RESULT_INVALID) { + // Note: this can be called within an callback ie. part of tuh_task() + // therefore even with RTOS tuh_task_ext() still need to be invoked + tuh_task_ext(0, false); + // TODO probably some timeout to prevent hanged + } + + // update transfer result, user_data is expected to point to xfer_result_t + if (xfer->user_data != 0) { + *((xfer_result_t*) xfer->user_data) = result; + } + xfer->result = result; + xfer->actual_len = ctrl_info->actual_len; + } + + return true; +} + +static void _control_xfer_complete(uint8_t daddr, xfer_result_t result) { + TU_LOG_USBH("\r\n"); + usbh_ctrl_xfer_info_t* ctrl_info = &_usbh_data.ctrl_xfer_info; + + // duplicate xfer since user can execute control transfer within callback + tusb_control_request_t const request = _usbh_epbuf.request; + tuh_xfer_t xfer_temp = { + .daddr = daddr, + .ep_addr = 0, + .result = result, + .setup = &request, + .actual_len = (uint32_t) ctrl_info->actual_len, + .buffer = ctrl_info->buffer, + .complete_cb = ctrl_info->complete_cb, + .user_data = ctrl_info->user_data + }; + + _control_set_xfer_stage(CONTROL_STAGE_IDLE); + + if (xfer_temp.complete_cb != NULL) { + xfer_temp.complete_cb(&xfer_temp); + } +} + +static bool usbh_control_xfer_cb (uint8_t daddr, uint8_t ep_addr, xfer_result_t result, uint32_t xferred_bytes) { + (void) ep_addr; + + const uint8_t rhport = usbh_get_rhport(daddr); + tusb_control_request_t const * request = &_usbh_epbuf.request; + usbh_ctrl_xfer_info_t* ctrl_info = &_usbh_data.ctrl_xfer_info; + + switch (result) { + case XFER_RESULT_STALLED: + TU_LOG_USBH("[%u:%u] Control STALLED, xferred_bytes = %" PRIu32 "\r\n", rhport, daddr, xferred_bytes); + TU_LOG_BUF_USBH(request, 8); + _control_xfer_complete(daddr, result); + break; + + case XFER_RESULT_FAILED: + if (tuh_connected(daddr) && ctrl_info->failed_count < USBH_CONTROL_RETRY_MAX) { + TU_LOG_USBH("[%u:%u] Control FAILED %u/%u, retrying\r\n", rhport, daddr, ctrl_info->failed_count+1, USBH_CONTROL_RETRY_MAX); + (void) osal_mutex_lock(_usbh_mutex, OSAL_TIMEOUT_WAIT_FOREVER); + ctrl_info->stage = CONTROL_STAGE_SETUP; + ctrl_info->failed_count++; + ctrl_info->actual_len = 0; // reset actual_len + (void) osal_mutex_unlock(_usbh_mutex); + + TU_ASSERT(usbh_setup_send(daddr, (uint8_t const *) request)); + } else { + TU_LOG_USBH("[%u:%u] Control FAILED, xferred_bytes = %" PRIu32 "\r\n", rhport, daddr, xferred_bytes); + TU_LOG_BUF_USBH(request, 8); + _control_xfer_complete(daddr, result); + } + break; + + case XFER_RESULT_SUCCESS: + switch(ctrl_info->stage) { + case CONTROL_STAGE_SETUP: + if (request->wLength > 0) { + // DATA stage: initial data toggle is always 1 + _control_set_xfer_stage(CONTROL_STAGE_DATA); + const uint8_t ep_data = tu_edpt_addr(0, request->bmRequestType_bit.direction); + TU_ASSERT(hcd_edpt_xfer(rhport, daddr, ep_data, ctrl_info->buffer, request->wLength)); + return true; + } + TU_ATTR_FALLTHROUGH; + + case CONTROL_STAGE_DATA: { + if (request->wLength > 0) { + TU_LOG_USBH("[%u:%u] Control data:\r\n", rhport, daddr); + TU_LOG_MEM_USBH(ctrl_info->buffer, xferred_bytes, 2); + } + ctrl_info->actual_len = (uint16_t) xferred_bytes; + + // ACK stage: toggle is always 1 + _control_set_xfer_stage(CONTROL_STAGE_ACK); + const uint8_t ep_status = tu_edpt_addr(0, 1 - request->bmRequestType_bit.direction); + TU_ASSERT(hcd_edpt_xfer(rhport, daddr, ep_status, NULL, 0)); + break; + } + + case CONTROL_STAGE_ACK: { + // Abort all pending transfers if SET_CONFIGURATION request + // NOTE: should we force closing all non-control endpoints in the future? + if (request->bRequest == TUSB_REQ_SET_CONFIGURATION && request->bmRequestType == 0x00) { + for(uint8_t epnum=1; epnumdaddr; + uint8_t const ep_addr = xfer->ep_addr; + + TU_VERIFY(daddr && ep_addr); + TU_VERIFY(usbh_edpt_claim(daddr, ep_addr)); + + if (!usbh_edpt_xfer_with_callback(daddr, ep_addr, xfer->buffer, (uint16_t) xfer->buflen, + xfer->complete_cb, xfer->user_data)) { + usbh_edpt_release(daddr, ep_addr); + return false; + } + + return true; +} + +bool tuh_edpt_abort_xfer(uint8_t daddr, uint8_t ep_addr) { + TU_LOG_USBH("[%u] Aborted transfer on EP %02X\r\n", daddr, ep_addr); + const uint8_t epnum = tu_edpt_number(ep_addr); + const uint8_t dir = tu_edpt_dir(ep_addr); + + if (epnum == 0) { + // Also include dev0 for aborting enumerating + const uint8_t rhport = usbh_get_rhport(daddr); + + // control transfer: only 1 control at a time, check if we are aborting the current one + const usbh_ctrl_xfer_info_t* ctrl_info = &_usbh_data.ctrl_xfer_info; + TU_VERIFY(daddr == ctrl_info->daddr && ctrl_info->stage != CONTROL_STAGE_IDLE); + hcd_edpt_abort_xfer(rhport, daddr, ep_addr); + _control_set_xfer_stage(CONTROL_STAGE_IDLE); // reset control transfer state to idle + } else { + usbh_device_t* dev = get_device(daddr); + TU_VERIFY(dev); + + TU_VERIFY(dev->ep_status[epnum][dir] & TU_EDPT_STATE_BUSY); // non-control skip if not busy + // abort then mark as ready and release endpoint + hcd_edpt_abort_xfer(dev->bus_info.rhport, daddr, ep_addr); + dev->ep_status[epnum][dir] &= (uint8_t) ~TU_EDPT_STATE_BUSY; // clear busy + tu_edpt_release(&dev->ep_status[epnum][dir], _usbh_mutex); + } + + return true; +} + +//--------------------------------------------------------------------+ +// USBH API For Class Driver +//--------------------------------------------------------------------+ + +uint8_t usbh_get_rhport(uint8_t daddr) { + tuh_bus_info_t bus_info; + tuh_bus_info_get(daddr, &bus_info); + return bus_info.rhport; +} + +uint8_t *usbh_get_enum_buf(void) { + return _usbh_epbuf.ctrl; +} + +void usbh_int_set(bool enabled) { + // TODO all host controller if multiple are used since they shared the same event queue + if (enabled) { + hcd_int_enable(_usbh_data.controller_id); + } else { + hcd_int_disable(_usbh_data.controller_id); + } +} + +void usbh_spin_lock(bool in_isr) { + osal_spin_lock(&_usbh_spin, in_isr); +} + +void usbh_spin_unlock(bool in_isr) { + osal_spin_unlock(&_usbh_spin, in_isr); +} + +void usbh_defer_func(osal_task_func_t func, void *param, bool in_isr) { + hcd_event_t event = { 0 }; + event.event_id = USBH_EVENT_FUNC_CALL; + event.func_call.func = func; + event.func_call.param = param; + queue_event(&event, in_isr); +} + +//--------------------------------------------------------------------+ +// Endpoint API +//--------------------------------------------------------------------+ + +// Claim an endpoint for transfer +bool usbh_edpt_claim(uint8_t dev_addr, uint8_t ep_addr) { + // Note: addr0 only use tuh_control_xfer + usbh_device_t* dev = get_device(dev_addr); + TU_ASSERT(dev && dev->connected); + + uint8_t const epnum = tu_edpt_number(ep_addr); + uint8_t const dir = tu_edpt_dir(ep_addr); + + TU_VERIFY(tu_edpt_claim(&dev->ep_status[epnum][dir], _usbh_mutex)); + TU_LOG_USBH("[%u] Claimed EP 0x%02x\r\n", dev_addr, ep_addr); + + return true; +} + +// Release an claimed endpoint due to failed transfer attempt +bool usbh_edpt_release(uint8_t dev_addr, uint8_t ep_addr) { + // Note: addr0 only use tuh_control_xfer + usbh_device_t* dev = get_device(dev_addr); + TU_VERIFY(dev && dev->connected); + + uint8_t const epnum = tu_edpt_number(ep_addr); + uint8_t const dir = tu_edpt_dir(ep_addr); + + TU_VERIFY(tu_edpt_release(&dev->ep_status[epnum][dir], _usbh_mutex)); + TU_LOG_USBH("[%u] Released EP 0x%02x\r\n", dev_addr, ep_addr); + + return true; +} + +// Submit an transfer +bool usbh_edpt_xfer_with_callback(uint8_t dev_addr, uint8_t ep_addr, uint8_t* buffer, uint16_t total_bytes, + tuh_xfer_cb_t complete_cb, uintptr_t user_data) { + (void) complete_cb; + (void) user_data; + + usbh_device_t* dev = get_device(dev_addr); + TU_VERIFY(dev); + + uint8_t const epnum = tu_edpt_number(ep_addr); + uint8_t const dir = tu_edpt_dir(ep_addr); + volatile uint8_t* ep_state = &dev->ep_status[epnum][dir]; + + TU_LOG_USBH(" Queue EP %02X with %u bytes ... \r\n", ep_addr, total_bytes); + + // Attempt to transfer on a busy endpoint, sound like an race condition ! + TU_ASSERT((*ep_state & TU_EDPT_STATE_BUSY) == 0); + + // Set busy first since the actual transfer can be complete before hcd_edpt_xfer() + // could return and USBH task can preempt and clear the busy + *ep_state |= TU_EDPT_STATE_BUSY; + +#if CFG_TUH_API_EDPT_XFER + dev->ep_callback[epnum][dir].complete_cb = complete_cb; + dev->ep_callback[epnum][dir].user_data = user_data; +#endif + + if (hcd_edpt_xfer(dev->bus_info.rhport, dev_addr, ep_addr, buffer, total_bytes)) { + TU_LOG_USBH("OK\r\n"); + return true; + } else { + // HCD error, clear busy and claimed to allow next transfer + *ep_state &= (uint8_t) ~(TU_EDPT_STATE_BUSY | TU_EDPT_STATE_CLAIMED); + TU_LOG1("Failed\r\n"); +// TU_BREAKPOINT(); + return false; + } +} + +static bool usbh_edpt_control_open(uint8_t dev_addr, uint8_t max_packet_size) { + TU_LOG_USBH("[%u:%u] Open EP0 with Size = %u\r\n", usbh_get_rhport(dev_addr), dev_addr, max_packet_size); + tusb_desc_endpoint_t ep0_desc = { + .bLength = sizeof(tusb_desc_endpoint_t), + .bDescriptorType = TUSB_DESC_ENDPOINT, + .bEndpointAddress = 0, + .bmAttributes = { .xfer = TUSB_XFER_CONTROL }, + .wMaxPacketSize = max_packet_size, + .bInterval = 0 + }; + + return hcd_edpt_open(usbh_get_rhport(dev_addr), dev_addr, &ep0_desc); +} + +bool tuh_edpt_open(uint8_t dev_addr, tusb_desc_endpoint_t const* desc_ep) { + // HACK: some device incorrectly always reports 512 bulk regardless of link speed, overwrite descriptor to force 64 + if (desc_ep->bmAttributes.xfer == TUSB_XFER_BULK && tu_edpt_packet_size(desc_ep) > 64 && + tuh_speed_get(dev_addr) == TUSB_SPEED_FULL) { + TU_LOG1(" WARN: EP max packet size is 512 in fullspeed, force to 64\r\n"); + tusb_desc_endpoint_t *hacked_ep = (tusb_desc_endpoint_t *)(uintptr_t)desc_ep; + hacked_ep->wMaxPacketSize = tu_htole16(64); + } + TU_ASSERT(tu_edpt_validate(desc_ep, tuh_speed_get(dev_addr))); + return hcd_edpt_open(usbh_get_rhport(dev_addr), dev_addr, desc_ep); +} + +bool tuh_edpt_close(uint8_t daddr, uint8_t ep_addr) { + TU_VERIFY(0 != tu_edpt_number(ep_addr)); // cannot close EP0 + tuh_edpt_abort_xfer(daddr, ep_addr); // abort any pending transfer + return hcd_edpt_close(usbh_get_rhport(daddr), daddr, ep_addr); +} + +bool usbh_edpt_busy(uint8_t dev_addr, uint8_t ep_addr) { + usbh_device_t* dev = get_device(dev_addr); + TU_VERIFY(dev); + + uint8_t const epnum = tu_edpt_number(ep_addr); + uint8_t const dir = tu_edpt_dir(ep_addr); + + return (dev->ep_status[epnum][dir] & TU_EDPT_STATE_BUSY) != 0; +} + +//--------------------------------------------------------------------+ +// HCD Event Handler +//--------------------------------------------------------------------+ + +bool tuh_bus_info_get(uint8_t daddr, tuh_bus_info_t* bus_info) { + usbh_device_t const* dev = get_device(daddr); + if (dev != NULL) { + *bus_info = dev->bus_info; + } else { + *bus_info = _usbh_data.dev0_bus; + } + return true; +} + +TU_ATTR_FAST_FUNC void hcd_event_handler(hcd_event_t const* event, bool in_isr) { + switch (event->event_id) { + case HCD_EVENT_DEVICE_ATTACH: + case HCD_EVENT_DEVICE_REMOVE: + // Attach debouncing on roothub: skip attach/remove while debouncing delay + if (event->connection.hub_addr == 0) { + if (tu_bit_test(_usbh_data.attach_debouncing_bm, event->rhport)) { + return; + } + + if (event->event_id == HCD_EVENT_DEVICE_ATTACH) { + // No debouncing, set flag if attach event + _usbh_data.attach_debouncing_bm |= TU_BIT(event->rhport); + } + } + break; + + default: + // nothing to do + break; + } + + queue_event(event, in_isr); +} + +//--------------------------------------------------------------------+ +// Descriptors Async +//--------------------------------------------------------------------+ + +// generic helper to get a descriptor +// if blocking, user_data is pointed to xfer_result +TU_ATTR_ALWAYS_INLINE static inline +bool _get_descriptor(uint8_t daddr, uint8_t type, uint8_t index, uint16_t language_id, void* buffer, uint16_t len, + tuh_xfer_cb_t complete_cb, uintptr_t user_data) { + tusb_control_request_t const request = { + .bmRequestType_bit = { + .recipient = TUSB_REQ_RCPT_DEVICE, + .type = TUSB_REQ_TYPE_STANDARD, + .direction = TUSB_DIR_IN + }, + .bRequest = TUSB_REQ_GET_DESCRIPTOR, + .wValue = tu_htole16( TU_U16(type, index) ), + .wIndex = tu_htole16(language_id), + .wLength = tu_htole16(len) + }; + tuh_xfer_t xfer = { + .daddr = daddr, + .ep_addr = 0, + .setup = &request, + .buffer = buffer, + .complete_cb = complete_cb, + .user_data = user_data + }; + + return tuh_control_xfer(&xfer); +} + +bool tuh_descriptor_get(uint8_t daddr, uint8_t type, uint8_t index, void* buffer, uint16_t len, + tuh_xfer_cb_t complete_cb, uintptr_t user_data) { + return _get_descriptor(daddr, type, index, 0x0000, buffer, len, complete_cb, user_data); +} + +bool tuh_descriptor_get_device(uint8_t daddr, void* buffer, uint16_t len, + tuh_xfer_cb_t complete_cb, uintptr_t user_data) { + len = tu_min16(len, sizeof(tusb_desc_device_t)); + return tuh_descriptor_get(daddr, TUSB_DESC_DEVICE, 0, buffer, len, complete_cb, user_data); +} + +bool tuh_descriptor_get_configuration(uint8_t daddr, uint8_t index, void* buffer, uint16_t len, + tuh_xfer_cb_t complete_cb, uintptr_t user_data) { + return tuh_descriptor_get(daddr, TUSB_DESC_CONFIGURATION, index, buffer, len, complete_cb, user_data); +} + +//------------- String Descriptor -------------// +bool tuh_descriptor_get_string(uint8_t daddr, uint8_t index, uint16_t language_id, void* buffer, uint16_t len, + tuh_xfer_cb_t complete_cb, uintptr_t user_data) { + return _get_descriptor(daddr, TUSB_DESC_STRING, index, language_id, buffer, len, complete_cb, user_data); +} + +// Get manufacturer string descriptor +bool tuh_descriptor_get_manufacturer_string(uint8_t daddr, uint16_t language_id, void* buffer, uint16_t len, + tuh_xfer_cb_t complete_cb, uintptr_t user_data) +{ + usbh_device_t const* dev = get_device(daddr); + TU_VERIFY(dev && dev->desc_device.iManufacturer); + return tuh_descriptor_get_string(daddr, dev->desc_device.iManufacturer, language_id, buffer, len, complete_cb, user_data); +} + +// Get product string descriptor +bool tuh_descriptor_get_product_string(uint8_t daddr, uint16_t language_id, void* buffer, uint16_t len, + tuh_xfer_cb_t complete_cb, uintptr_t user_data) { + usbh_device_t const* dev = get_device(daddr); + TU_VERIFY(dev && dev->desc_device.iProduct); + return tuh_descriptor_get_string(daddr, dev->desc_device.iProduct, language_id, buffer, len, complete_cb, user_data); +} + +// Get serial string descriptor +bool tuh_descriptor_get_serial_string(uint8_t daddr, uint16_t language_id, void* buffer, uint16_t len, + tuh_xfer_cb_t complete_cb, uintptr_t user_data) { + usbh_device_t const* dev = get_device(daddr); + TU_VERIFY(dev && dev->desc_device.iSerialNumber); + return tuh_descriptor_get_string(daddr, dev->desc_device.iSerialNumber, language_id, buffer, len, complete_cb, user_data); +} + +// Get HID report descriptor +// if blocking, user_data is pointed to xfer_result +bool tuh_descriptor_get_hid_report(uint8_t daddr, uint8_t itf_num, uint8_t desc_type, uint8_t index, void* buffer, uint16_t len, + tuh_xfer_cb_t complete_cb, uintptr_t user_data) { + TU_LOG_USBH("HID Get Report Descriptor\r\n"); + tusb_control_request_t const request = { + .bmRequestType_bit = { + .recipient = TUSB_REQ_RCPT_INTERFACE, + .type = TUSB_REQ_TYPE_STANDARD, + .direction = TUSB_DIR_IN + }, + .bRequest = TUSB_REQ_GET_DESCRIPTOR, + .wValue = tu_htole16(TU_U16(desc_type, index)), + .wIndex = tu_htole16((uint16_t) itf_num), + .wLength = len + }; + tuh_xfer_t xfer = { + .daddr = daddr, + .ep_addr = 0, + .setup = &request, + .buffer = buffer, + .complete_cb = complete_cb, + .user_data = user_data + }; + + return tuh_control_xfer(&xfer); +} + +bool tuh_address_set(uint8_t daddr, uint8_t new_addr, + tuh_xfer_cb_t complete_cb, uintptr_t user_data) { + TU_LOG_USBH("Set Address = %d\r\n", new_addr); + const tusb_control_request_t request = { + .bmRequestType_bit = { + .recipient = TUSB_REQ_RCPT_DEVICE, + .type = TUSB_REQ_TYPE_STANDARD, + .direction = TUSB_DIR_OUT + }, + .bRequest = TUSB_REQ_SET_ADDRESS, + .wValue = tu_htole16(new_addr), + .wIndex = 0, + .wLength = 0 + }; + tuh_xfer_t xfer = { + .daddr = daddr, + .ep_addr = 0, + .setup = &request, + .buffer = NULL, + .complete_cb = complete_cb, + .user_data = user_data + }; + + TU_ASSERT(tuh_control_xfer(&xfer)); + return true; +} + +bool tuh_configuration_set(uint8_t daddr, uint8_t config_num, + tuh_xfer_cb_t complete_cb, uintptr_t user_data) { + TU_LOG_USBH("Set Configuration = %d\r\n", config_num); + tusb_control_request_t const request = { + .bmRequestType_bit = { + .recipient = TUSB_REQ_RCPT_DEVICE, + .type = TUSB_REQ_TYPE_STANDARD, + .direction = TUSB_DIR_OUT + }, + .bRequest = TUSB_REQ_SET_CONFIGURATION, + .wValue = tu_htole16(config_num), + .wIndex = 0, + .wLength = 0 + }; + tuh_xfer_t xfer = { + .daddr = daddr, + .ep_addr = 0, + .setup = &request, + .buffer = NULL, + .complete_cb = complete_cb, + .user_data = user_data + }; + + return tuh_control_xfer(&xfer); +} + +bool tuh_interface_set(uint8_t daddr, uint8_t itf_num, uint8_t itf_alt, + tuh_xfer_cb_t complete_cb, uintptr_t user_data) { + TU_LOG_USBH("Set Interface %u Alternate %u\r\n", itf_num, itf_alt); + tusb_control_request_t const request = { + .bmRequestType_bit = { + .recipient = TUSB_REQ_RCPT_INTERFACE, + .type = TUSB_REQ_TYPE_STANDARD, + .direction = TUSB_DIR_OUT + }, + .bRequest = TUSB_REQ_SET_INTERFACE, + .wValue = tu_htole16(itf_alt), + .wIndex = tu_htole16(itf_num), + .wLength = 0 + }; + tuh_xfer_t xfer = { + .daddr = daddr, + .ep_addr = 0, + .setup = &request, + .buffer = NULL, + .complete_cb = complete_cb, + .user_data = user_data + }; + + return tuh_control_xfer(&xfer); +} + +//--------------------------------------------------------------------+ +// Detaching +//--------------------------------------------------------------------+ + +// process detach event from rhport:hub_addr:hub_port +static void process_remove_event(hcd_event_t *event) { + if (_usbh_data.enumerating_daddr == 0 && + event->rhport == _usbh_data.dev0_bus.rhport && + event->connection.hub_addr == _usbh_data.dev0_bus.hub_addr && + event->connection.hub_port == _usbh_data.dev0_bus.hub_port) { + // dev0 is unplugged while enumerating (not yet assigned an address) + usbh_device_close(_usbh_data.dev0_bus.rhport, 0); + } else { + remove_device_tree(event->rhport, event->connection.hub_addr, event->connection.hub_port); + } +} + +// remove a device at rhport:hub_addr:hub_port and all of its downstream +static void remove_device_tree(uint8_t rhport, uint8_t hub_addr, uint8_t hub_port) { + // Find the all devices (star-network) under port that is unplugged + #if CFG_TUH_HUB + uint8_t removing_hubs[CFG_TUH_HUB] = { 0 }; + #endif + + do { + for (uint8_t dev_id = 0; dev_id < TOTAL_DEVICES; dev_id++) { + usbh_device_t* dev = &_usbh_devices[dev_id]; + uint8_t const daddr = dev_id + 1u; + + // hub_addr = 0 means roothub, hub_port = 0 means all devices of downstream hub + if (dev->bus_info.rhport == rhport && dev->connected && + (hub_addr == 0 || dev->bus_info.hub_addr == hub_addr) && + (hub_port == 0 || dev->bus_info.hub_port == hub_port)) { + TU_LOG_USBH("[%u:%u:%u] unplugged address = %u\r\n", rhport, hub_addr, hub_port, daddr); + + #if CFG_TUH_HUB + if (is_hub_addr(daddr)) { + TU_LOG_USBH(" is a HUB device %u\r\n", daddr); + removing_hubs[dev_id - CFG_TUH_DEVICE_MAX] = 1; + } else + #endif + { + // Invoke callback before closing driver (maybe call it later ?) + tuh_umount_cb(daddr); + } + + // Close class driver + for (uint8_t drv_id = 0; drv_id < TOTAL_DRIVER_COUNT; drv_id++) { + usbh_class_driver_t const* driver = get_driver(drv_id); + if (driver != NULL) { + driver->close(daddr); + } + } + + usbh_device_close(rhport, daddr); + clear_device(dev); + } + } + +#if CFG_TUH_HUB + // if a hub is removed, we need to remove all of its downstream devices + if (tu_mem_is_zero(removing_hubs, CFG_TUH_HUB)) { + break; + } + + // find a marked hub to process + for (uint8_t h_id = 0; h_id < CFG_TUH_HUB; h_id++) { + if (0 != removing_hubs[h_id]) { + removing_hubs[h_id] = 0; + + // update hub_addr and hub_port for next loop + hub_addr = h_id + 1 + CFG_TUH_DEVICE_MAX; + hub_port = 0; + break; + } + } +#else + break; +#endif + + } while(1); +} + +//--------------------------------------------------------------------+ +// Enumeration Process +// is a lengthy process with a series of control transfer to configure newly attached device. +// NOTE: due to the shared control buffer, we must complete enumerating +// one device before enumerating another one. +//--------------------------------------------------------------------+ +enum { // USB 2.0 specs 7.1.7 for timing + ENUM_DEBOUNCING_DELAY_MS = 150, // T(ATTDB) minimum 100 ms for stable connection + ENUM_RESET_ROOT_DELAY_MS = 50, // T(DRSTr) minimum 50 ms for reset from root port + ENUM_RESET_ROOT_POST_DELAY_MS = 2, // 2 ms delay after root port reset before getting speed/status + ENUM_RESET_HUB_DELAY_MS = 20, // T(DRST) 10-20 ms for hub reset + ENUM_RESET_RECOVERY_DELAY_MS = 10, // T(RSTRCY) minimum 10 ms for reset recovery + ENUM_SET_ADDRESS_RECOVERY_DELAY_MS = 2, // USB 2.0 Spec 9.2.6.3 min is 2 ms +}; + +enum { + ENUM_IDLE, + ENUM_HUB_RERSET, + ENUM_HUB_RESET_COMPLETE, + ENUM_HUB_CLEAR_RESET, + ENUM_HUB_CLEAR_RESET_RETRY, // 2nd attempt waiting for hub reset + ENUM_HUB_CLEAR_RESET_COMPLETE, + ENUM_ADDR0_DEVICE_DESC, + ENUM_SET_ADDR, + ENUM_GET_DEVICE_DESC, + ENUM_GET_STRING_LANGUAGE_ID_LEN, + ENUM_GET_STRING_LANGUAGE_ID, + ENUM_GET_STRING_MANUFACTURER_LEN, + ENUM_GET_STRING_MANUFACTURER, + ENUM_GET_STRING_PRODUCT_LEN, + ENUM_GET_STRING_PRODUCT, + ENUM_GET_STRING_SERIAL_LEN, + ENUM_GET_STRING_SERIAL, + ENUM_GET_9BYTE_CONFIG_DESC, + ENUM_GET_FULL_CONFIG_DESC, + ENUM_SET_CONFIG, + ENUM_CONFIG_DRIVER +}; + +static uint8_t enum_get_new_address(bool is_hub); +static bool enum_parse_configuration_desc(uint8_t dev_addr, const tusb_desc_configuration_t *desc_cfg); +static void enum_full_complete(bool success); +static void process_enumeration(tuh_xfer_t *xfer); + +enum { + ENUM_AFTER_DEBOUNCING_DELAY, + ENUM_AFTER_RESET_ROOT_DELAY, + ENUM_AFTER_RESET_ROOT_POST_DELAY, + ENUM_AFTER_RESET_HUB_DELAY, + ENUM_AFTER_RESET_HUB_DELAY_RETRY, + ENUM_AFTER_RESET_RECOVERY_DELAY, + ENUM_AFTER_SET_ADDRESS_RECOVERY_DELAY, +}; + +// process async delay in enumeration +static void enum_delay_async(uintptr_t state) { + tuh_bus_info_t *dev0_bus = &_usbh_data.dev0_bus; + switch (state) { + case ENUM_AFTER_DEBOUNCING_DELAY: + #if CFG_TUH_HUB + if (dev0_bus->hub_addr != 0) { + // connected via hub + TU_VERIFY(dev0_bus->hub_port != 0, ); + TU_ASSERT(hub_port_get_status(dev0_bus->hub_addr, dev0_bus->hub_port, NULL, process_enumeration, + ENUM_HUB_RERSET), ); + } else + #endif + { + // connected directly to roothub + _usbh_data.attach_debouncing_bm &= (uint8_t)~TU_BIT(dev0_bus->rhport); // clear roothub debouncing delay + if (!hcd_port_connect_status(dev0_bus->rhport)) { + TU_LOG_USBH("Device unplugged while debouncing\r\n"); + enum_full_complete(false); + return; + } + hcd_port_reset(dev0_bus->rhport); // reset port + usbh_defer_func_ms_async(ENUM_RESET_ROOT_DELAY_MS, enum_delay_async, ENUM_AFTER_RESET_ROOT_DELAY); + } + break; + + case ENUM_AFTER_RESET_ROOT_DELAY: + hcd_port_reset_end(dev0_bus->rhport); + usbh_defer_func_ms_async(ENUM_RESET_ROOT_POST_DELAY_MS, enum_delay_async, ENUM_AFTER_RESET_ROOT_POST_DELAY); + break; + + case ENUM_AFTER_RESET_ROOT_POST_DELAY: + if (!hcd_port_connect_status(dev0_bus->rhport)) { + // device unplugged while delaying + enum_full_complete(false); + return; + } + + dev0_bus->speed = hcd_port_speed_get(dev0_bus->rhport); + TU_LOG_USBH("%s Speed\r\n", tu_str_speed[dev0_bus->speed]); + + // fake transfer to kick-off the enumeration process + tuh_xfer_t xfer; + xfer.daddr = 0; + xfer.result = XFER_RESULT_SUCCESS; + xfer.user_data = ENUM_ADDR0_DEVICE_DESC; + process_enumeration(&xfer); + break; + + #if CFG_TUH_HUB + case ENUM_AFTER_RESET_HUB_DELAY: + case ENUM_AFTER_RESET_HUB_DELAY_RETRY: + // get status after reset complete to check for reset change + TU_ASSERT(hub_port_get_status(dev0_bus->hub_addr, dev0_bus->hub_port, NULL, process_enumeration, + state == ENUM_AFTER_RESET_HUB_DELAY ? ENUM_HUB_CLEAR_RESET + : ENUM_HUB_CLEAR_RESET_RETRY), ); + break; + #endif + + case ENUM_AFTER_RESET_RECOVERY_DELAY: + // TODO probably doesn't need to open/close each enumeration + if (!usbh_edpt_control_open(0, 8)) { + TU_LOG_USBH("Failed to open dev0's control endpoint\r\n"); + enum_full_complete(false); // Stop enumeration gracefully + return; + } + // Get first 8 bytes of device descriptor for control endpoint size + TU_LOG_USBH("Get 8 byte of Device Descriptor\r\n"); + TU_ASSERT(tuh_descriptor_get_device(0, _usbh_epbuf.ctrl, 8, process_enumeration, ENUM_SET_ADDR), ); + break; + + case ENUM_AFTER_SET_ADDRESS_RECOVERY_DELAY: { + const uint8_t new_addr = _usbh_data.enumerating_daddr; + usbh_device_t *new_dev = get_device(new_addr); + TU_ASSERT(new_dev, ); + if (!usbh_edpt_control_open(new_addr, new_dev->desc_device.bMaxPacketSize0)) { + TU_LOG_USBH("Failed to open new device's control endpoint\r\n"); + clear_device(new_dev); + enum_full_complete(false); + return; + } + TU_LOG_USBH("Get Device Descriptor\r\n"); + TU_ASSERT(tuh_descriptor_get_device(new_addr, _usbh_epbuf.ctrl, sizeof(tusb_desc_device_t), process_enumeration, + ENUM_GET_STRING_LANGUAGE_ID_LEN), ); + break; + } + + default: + break; + } +} + +// start a new enumeration process +static void enum_new_device(hcd_event_t *event) { + tuh_bus_info_t *dev0_bus = &_usbh_data.dev0_bus; + dev0_bus->rhport = event->rhport; + dev0_bus->hub_addr = event->connection.hub_addr; + dev0_bus->hub_port = event->connection.hub_port; + usbh_defer_func_ms_async(ENUM_DEBOUNCING_DELAY_MS, enum_delay_async, ENUM_AFTER_DEBOUNCING_DELAY); +} + +// process device enumeration +static void process_enumeration(tuh_xfer_t *xfer) { + if (XFER_RESULT_FAILED == xfer->result) { + enum_full_complete(false); // failed to enum + return; + } + + const uint8_t daddr = xfer->daddr; + const uintptr_t state = xfer->user_data; + usbh_device_t *dev = get_device(daddr); + tuh_bus_info_t *dev0_bus = &_usbh_data.dev0_bus; + if (daddr > 0) { + TU_ASSERT(dev != NULL,); + } + uint16_t langid = 0x0409; // default is English + bool is_enum_failed = false; + + switch (state) { + #if CFG_TUH_HUB + case ENUM_HUB_RERSET: { + hub_port_status_response_t port_status; + hub_port_get_status_local(dev0_bus->hub_addr, dev0_bus->hub_port, &port_status); + + if (0 == port_status.status.connection) { + TU_LOG_USBH("Device unplugged from hub while debouncing\r\n"); + is_enum_failed = true; + } else { + TU_ASSERT(hub_port_reset(dev0_bus->hub_addr, dev0_bus->hub_port, process_enumeration, + ENUM_HUB_RESET_COMPLETE), ); + } + break; + } + + case ENUM_HUB_RESET_COMPLETE: + // wait for reset to take effect + usbh_defer_func_ms_async(ENUM_RESET_HUB_DELAY_MS, enum_delay_async, ENUM_AFTER_RESET_HUB_DELAY); + break; + + case ENUM_HUB_CLEAR_RESET: + case ENUM_HUB_CLEAR_RESET_RETRY: { + hub_port_status_response_t port_status; + hub_port_get_status_local(dev0_bus->hub_addr, dev0_bus->hub_port, &port_status); + + if (1 == port_status.change.reset) { + // Acknowledge Port Reset Change + TU_ASSERT(hub_port_clear_reset_change(dev0_bus->hub_addr, dev0_bus->hub_port, process_enumeration, + ENUM_HUB_CLEAR_RESET_COMPLETE), ); + } else if (state == ENUM_HUB_CLEAR_RESET) { + // retry one more time if reset change not set yet + usbh_defer_func_ms_async(ENUM_RESET_HUB_DELAY_MS, enum_delay_async, ENUM_AFTER_RESET_HUB_DELAY_RETRY); + } else { + // retry but still not set --> failed + is_enum_failed = true; + } + break; + } + + case ENUM_HUB_CLEAR_RESET_COMPLETE: { + hub_port_status_response_t port_status; + hub_port_get_status_local(dev0_bus->hub_addr, dev0_bus->hub_port, &port_status); + + if (0 == port_status.status.connection) { + TU_LOG_USBH("Device unplugged from hub (not addressed yet)\r\n"); + is_enum_failed = true; + break; + } + + dev0_bus->speed = (port_status.status.high_speed) ? TUSB_SPEED_HIGH + : (port_status.status.low_speed) ? TUSB_SPEED_LOW + : TUSB_SPEED_FULL; + TU_ATTR_FALLTHROUGH; + } + #endif + + case ENUM_ADDR0_DEVICE_DESC: + usbh_defer_func_ms_async(ENUM_RESET_RECOVERY_DELAY_MS, enum_delay_async, ENUM_AFTER_RESET_RECOVERY_DELAY); + break; + + case ENUM_SET_ADDR: { + const tusb_desc_device_t *desc_device = (const tusb_desc_device_t *) _usbh_epbuf.ctrl; + if (!(desc_device->bDescriptorType == TUSB_DESC_DEVICE && desc_device->bMaxPacketSize0 >= 8)) { + TU_LOG_USBH("Invalid Device descriptor\r\n"); + is_enum_failed = true; + break; + } + + const uint8_t new_addr = enum_get_new_address(desc_device->bDeviceClass == TUSB_CLASS_HUB); + TU_ASSERT(new_addr != 0,); + + usbh_device_t* new_dev = get_device(new_addr); + new_dev->bus_info = *dev0_bus; + new_dev->connected = 1; + new_dev->desc_device.bMaxPacketSize0 = desc_device->bMaxPacketSize0; + + TU_ASSERT(tuh_address_set(0, new_addr, process_enumeration, ENUM_GET_DEVICE_DESC), ); + break; + } + + case ENUM_GET_DEVICE_DESC: { + const uint8_t new_addr = (uint8_t)tu_le16toh(xfer->setup->wValue); + usbh_device_t *new_dev = get_device(new_addr); + TU_ASSERT(new_dev, ); + new_dev->addressed = 1; + _usbh_data.enumerating_daddr = new_addr; + + usbh_device_close(dev0_bus->rhport, 0); // close dev0 + usbh_defer_func_ms_async(ENUM_SET_ADDRESS_RECOVERY_DELAY_MS, enum_delay_async, ENUM_AFTER_SET_ADDRESS_RECOVERY_DELAY); + break; + } + + // For string descriptor (langid, manufacturer, product, serila): always get the first 2 bytes + // to determine the length first. otherwise, some device may have buffer overflow. + case ENUM_GET_STRING_LANGUAGE_ID_LEN: { + // save the received device descriptor + tusb_desc_device_t const *desc_device = (tusb_desc_device_t const *) _usbh_epbuf.ctrl; + + memcpy(&dev->desc_device, (const uint8_t*) desc_device + offsetof(tusb_desc_device_t, bcdUSB), sizeof(desc_device_noheader_t)); + + tuh_enum_descriptor_device_cb(daddr, desc_device); // callback + tuh_descriptor_get_string_langid(daddr, _usbh_epbuf.ctrl, 2, + process_enumeration, ENUM_GET_STRING_LANGUAGE_ID); + break; + } + + case ENUM_GET_STRING_LANGUAGE_ID: { + const uint8_t str_len = xfer->buffer[0]; + tuh_descriptor_get_string_langid(daddr, _usbh_epbuf.ctrl, str_len, + process_enumeration, ENUM_GET_STRING_MANUFACTURER_LEN); + break; + } + + case ENUM_GET_STRING_MANUFACTURER_LEN: { + const tusb_desc_string_t* desc_langid = (const tusb_desc_string_t *) _usbh_epbuf.ctrl; + if (desc_langid->bLength >= 4) { + langid = tu_le16toh(desc_langid->utf16le[0]); // previous request is langid + } + if (dev->desc_device.iManufacturer != 0) { + tuh_descriptor_get_string(daddr, dev->desc_device.iManufacturer, langid, _usbh_epbuf.ctrl, 2, + process_enumeration, ENUM_GET_STRING_MANUFACTURER); + break; + } + TU_ATTR_FALLTHROUGH; + } + + case ENUM_GET_STRING_MANUFACTURER: { + if (dev->desc_device.iManufacturer != 0) { + langid = tu_le16toh(xfer->setup->wIndex); // langid from length's request + const uint8_t str_len = xfer->buffer[0]; + tuh_descriptor_get_string(daddr, dev->desc_device.iManufacturer, langid, _usbh_epbuf.ctrl, str_len, + process_enumeration, ENUM_GET_STRING_PRODUCT_LEN); + break; + } + TU_ATTR_FALLTHROUGH; + } + + case ENUM_GET_STRING_PRODUCT_LEN: { + if (dev->desc_device.iProduct != 0) { + if (state == ENUM_GET_STRING_PRODUCT_LEN) { + langid = tu_le16toh(xfer->setup->wIndex); // get langid from previous setup packet if not fall through + } + tuh_descriptor_get_string( + daddr, dev->desc_device.iProduct, langid, _usbh_epbuf.ctrl, 2, process_enumeration, ENUM_GET_STRING_PRODUCT); + break; + } + TU_ATTR_FALLTHROUGH; + } + + case ENUM_GET_STRING_PRODUCT: { + if (dev->desc_device.iProduct != 0) { + langid = tu_le16toh(xfer->setup->wIndex); // langid from length's request + const uint8_t str_len = xfer->buffer[0]; + tuh_descriptor_get_string(daddr, dev->desc_device.iProduct, langid, _usbh_epbuf.ctrl, str_len, + process_enumeration, ENUM_GET_STRING_SERIAL_LEN); + break; + } + TU_ATTR_FALLTHROUGH; + } + + case ENUM_GET_STRING_SERIAL_LEN: { + if (dev->desc_device.iSerialNumber != 0) { + if (state == ENUM_GET_STRING_SERIAL_LEN) { + langid = tu_le16toh(xfer->setup->wIndex); // get langid from previous setup packet if not fall through + } + tuh_descriptor_get_string( + daddr, dev->desc_device.iSerialNumber, langid, _usbh_epbuf.ctrl, 2, process_enumeration, ENUM_GET_STRING_SERIAL); + break; + } + TU_ATTR_FALLTHROUGH; + } + + case ENUM_GET_STRING_SERIAL: { + if (dev->desc_device.iSerialNumber != 0) { + langid = tu_le16toh(xfer->setup->wIndex); // langid from length's request + const uint8_t str_len = xfer->buffer[0]; + tuh_descriptor_get_string(daddr, dev->desc_device.iSerialNumber, langid, _usbh_epbuf.ctrl, str_len, + process_enumeration, ENUM_GET_9BYTE_CONFIG_DESC); + break; + } + TU_ATTR_FALLTHROUGH; + } + + case ENUM_GET_9BYTE_CONFIG_DESC: { + // Get 9-byte for total length + uint8_t const config_idx = 0; + TU_LOG_USBH("Get Configuration[%u] Descriptor (9 bytes)\r\n", config_idx); + TU_ASSERT(tuh_descriptor_get_configuration(daddr, config_idx, _usbh_epbuf.ctrl, 9, + process_enumeration, ENUM_GET_FULL_CONFIG_DESC),); + break; + } + + case ENUM_GET_FULL_CONFIG_DESC: { + uint8_t const* desc_config = _usbh_epbuf.ctrl; + + // Use offsetof to avoid pointer to the odd/misaligned address + uint16_t const total_len = tu_le16toh(tu_unaligned_read16(desc_config + offsetof(tusb_desc_configuration_t, wTotalLength))); + + // TODO not enough buffer to hold configuration descriptor + TU_ASSERT(total_len <= CFG_TUH_ENUMERATION_BUFSIZE,); + + // Get full configuration descriptor + uint8_t const config_idx = (uint8_t) tu_le16toh(xfer->setup->wIndex); + TU_LOG_USBH("Get Configuration[%u] Descriptor\r\n", config_idx); + TU_ASSERT(tuh_descriptor_get_configuration(daddr, config_idx, _usbh_epbuf.ctrl, total_len, + process_enumeration, ENUM_SET_CONFIG),); + break; + } + + case ENUM_SET_CONFIG: { + uint8_t config_idx = (uint8_t) tu_le16toh(xfer->setup->wIndex); + if (tuh_enum_descriptor_configuration_cb(daddr, config_idx, (const tusb_desc_configuration_t*) _usbh_epbuf.ctrl)) { + TU_ASSERT(tuh_configuration_set(daddr, config_idx+1u, process_enumeration, ENUM_CONFIG_DRIVER),); + } else { + config_idx++; + TU_ASSERT(config_idx < dev->desc_device.bNumConfigurations,); + TU_LOG_USBH("Get Configuration[%u] Descriptor (9 bytes)\r\n", config_idx); + TU_ASSERT(tuh_descriptor_get_configuration(daddr, config_idx, _usbh_epbuf.ctrl, 9, + process_enumeration, ENUM_GET_FULL_CONFIG_DESC),); + } + break; + } + + case ENUM_CONFIG_DRIVER: { + TU_LOG_USBH("Device configured\r\n"); + dev->configured = 1; + + #if CFG_TUH_HUB + // get next hub status now since device can be unplugged before set_configure() is complete + if (_usbh_data.dev0_bus.hub_addr != 0) { + hub_edpt_status_xfer(_usbh_data.dev0_bus.hub_addr); + } + #endif + + // Parse configuration & set up drivers + // driver_open() must not make any usb transfer + TU_ASSERT(enum_parse_configuration_desc(daddr, (tusb_desc_configuration_t*) _usbh_epbuf.ctrl),); + + // Start the Set Configuration process for interfaces (itf = TUSB_INDEX_INVALID_8) + // Since driver can perform control transfer within its set_config, this is done asynchronously. + // The process continue with next interface when class driver complete its sequence with usbh_driver_set_config_complete() + // TODO use separated API instead of using TUSB_INDEX_INVALID_8 + usbh_driver_set_config_complete(daddr, TUSB_INDEX_INVALID_8); + break; + } + + default: + is_enum_failed = true; + break; + } + + if (is_enum_failed) { + enum_full_complete(false); + } +} + +static uint8_t enum_get_new_address(bool is_hub) { + uint8_t start; + uint8_t end; + + if ( is_hub ) { + start = CFG_TUH_DEVICE_MAX; + end = start + CFG_TUH_HUB; + }else { + start = 0; + end = start + CFG_TUH_DEVICE_MAX; + } + + for (uint8_t idx = start; idx < end; idx++) { + if (0 == _usbh_devices[idx].connected) { + return (idx + 1); + } + } + +#if CFG_TUH_HUB + if ( is_hub ) { + TU_LOG1("All addresses are occupied, try to increase CFG_TUH_HUB value.\r\n"); + } +#endif // CFG_TUH_HUB + + return 0; // invalid address +} + +static bool enum_parse_configuration_desc(uint8_t dev_addr, tusb_desc_configuration_t const* desc_cfg) { + usbh_device_t* dev = get_device(dev_addr); + uint16_t const total_len = tu_le16toh(desc_cfg->wTotalLength); + uint8_t const* desc_end = ((uint8_t const*) desc_cfg) + total_len; + uint8_t const* p_desc = tu_desc_next(desc_cfg); + + TU_LOG_USBH("Parsing Configuration descriptor (wTotalLength = %u)\r\n", total_len); + + // parse all interfaces + while (tu_desc_in_bounds(p_desc, desc_end)) { + if (0 == tu_desc_len(p_desc)) { + // A zero-length descriptor indicates that the device is off spec (e.g. wrong wTotalLength). + // Parsed interfaces should still be usable + TU_LOG_USBH("Encountered a zero-length descriptor after %" PRIu32 " bytes\r\n", (uint32_t)p_desc - (uint32_t)desc_cfg); + break; + } + + // skip if not interface + if (TUSB_DESC_INTERFACE != tu_desc_type(p_desc)) { + p_desc = tu_desc_next(p_desc); + continue; + } + const tusb_desc_interface_t *desc_itf = (const tusb_desc_interface_t *)p_desc; + + // uint16_t const drv_len = tu_desc_get_interface_total_len(desc_itf, assoc_itf_count, (uint16_t) + // (desc_end-p_desc)); TU_ASSERT(drv_len >= sizeof(tusb_desc_interface_t)); + + // Find a driver for this interface + const uint16_t remaining_len = (uint16_t)(desc_end - p_desc); + uint8_t drv_id; + for (drv_id = 0; drv_id < TOTAL_DRIVER_COUNT; drv_id++) { + const usbh_class_driver_t *driver = get_driver(drv_id); + if (driver) { + const uint16_t drv_len = driver->open(dev->bus_info.rhport, dev_addr, desc_itf, remaining_len); + if ((sizeof(tusb_desc_interface_t) <= drv_len) && (drv_len <= remaining_len)) { + // open successfully + TU_LOG_USBH(" %s opened\r\n", driver->name); + + // bind found driver to all interfaces and endpoint within drv_len + tu_bind_driver_to_ep_itf(drv_id, dev->ep2drv, dev->itf2drv, CFG_TUH_INTERFACE_MAX, p_desc, drv_len); + + p_desc += drv_len; // next Interface + break; // exit driver find loop + } + } + } + + // no driver found + if (drv_id == TOTAL_DRIVER_COUNT) { + p_desc = tu_desc_next(p_desc); // skip this interface + TU_LOG_USBH("[%u:%u] Interface %u: class = %u subclass = %u protocol = %u is not supported\r\n", + dev->bus_info.rhport, dev_addr, desc_itf->bInterfaceNumber, desc_itf->bInterfaceClass, + desc_itf->bInterfaceSubClass, desc_itf->bInterfaceProtocol); + } + } + + return true; +} + +void usbh_driver_set_config_complete(uint8_t dev_addr, uint8_t itf_num) { + usbh_device_t* dev = get_device(dev_addr); + + for(itf_num++; itf_num < CFG_TUH_INTERFACE_MAX; itf_num++) { + // continue with next valid interface + // IAD binding interface such as CDCs should return itf_num + 1 when complete + // with usbh_driver_set_config_complete() + uint8_t const drv_id = dev->itf2drv[itf_num]; + usbh_class_driver_t const * driver = get_driver(drv_id); + if (driver != NULL) { + TU_LOG_USBH("%s set config: itf = %u\r\n", driver->name, itf_num); + driver->set_config(dev_addr, itf_num); + break; + } + } + + // all interfaces are configured + if (itf_num == CFG_TUH_INTERFACE_MAX) { + enum_full_complete(true); + + if (is_hub_addr(dev_addr)) { + TU_LOG_USBH("HUB address = %u is mounted\r\n", dev_addr); + }else { + // Invoke callback if available + tuh_mount_cb(dev_addr); + } + } +} + +static void enum_full_complete(bool success) { + (void)success; + TU_LOG_USBH("Enumeration complete: success = %u\r\n", success); + + _usbh_data.enumerating_daddr = TUSB_INDEX_INVALID_8; // mark enumeration as complete + _usbh_data.call_after.func = NULL; + + #if CFG_TUH_HUB + // Hub status is already requested in case of successful enumeration + if (!success && _usbh_data.dev0_bus.hub_addr != 0) { + hub_edpt_status_xfer(_usbh_data.dev0_bus.hub_addr); + } + #endif +} + +#endif diff --git a/libraries/Adafruit_TinyUSB_Arduino/src/host/usbh.h b/libraries/Adafruit_TinyUSB_Arduino/src/host/usbh.h new file mode 100644 index 0000000..7ddec35 --- /dev/null +++ b/libraries/Adafruit_TinyUSB_Arduino/src/host/usbh.h @@ -0,0 +1,405 @@ +/* + * 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. + * + * This file is part of the TinyUSB stack. + */ + +#ifndef TUSB_USBH_H_ +#define TUSB_USBH_H_ + +#ifdef __cplusplus + extern "C" { +#endif + +#include "common/tusb_common.h" + +#if CFG_TUH_MAX3421 +#include "portable/analog/max3421/hcd_max3421.h" +#endif + +//--------------------------------------------------------------------+ +// MACRO CONSTANT TYPEDEF +//--------------------------------------------------------------------+ + + + +// forward declaration +struct tuh_xfer_s; +typedef struct tuh_xfer_s tuh_xfer_t; +typedef void (*tuh_xfer_cb_t)(tuh_xfer_t* xfer); + +// Note1: layout and order of this will be changed in near future +// it is advised to initialize it using member name +// Note2: not all field is available/meaningful in callback, +// some info is not saved by usbh to save SRAM +struct tuh_xfer_s { + uint8_t daddr; + uint8_t ep_addr; + uint8_t TU_RESERVED; // reserved + xfer_result_t result; + + uint32_t actual_len; // excluding setup packet + + union { + tusb_control_request_t const* setup; // setup packet pointer if control transfer + uint32_t buflen; // expected length if not control transfer (not available in callback) + }; + + uint8_t* buffer; // not available in callback if not control transfer + tuh_xfer_cb_t complete_cb; + uintptr_t user_data; + + // uint32_t timeout_ms; // place holder, not supported yet +}; + +// Subject to change +typedef struct { + uint8_t daddr; + tusb_desc_interface_t desc; +} tuh_itf_info_t; + +typedef struct { + uint8_t rhport; + uint8_t hub_addr; + uint8_t hub_port; + uint8_t speed; +} tuh_bus_info_t; + +// backward compatibility for hcd_devtree_info_t, maybe removed in the future +#define hcd_devtree_info_t tuh_bus_info_t +#define hcd_devtree_get_info(_daddr, _bus_info) tuh_bus_info_get(_daddr, _bus_info) + +// ConfigID for tuh_configure() +enum { + TUH_CFGID_INVALID = 0, + TUH_CFGID_RPI_PIO_USB_CONFIGURATION = 100, // cfg_param: pio_usb_configuration_t + TUH_CFGID_MAX3421 = 200, + TUH_CFGID_FSDEV = 300, + TUH_CFGID_DWC2 = 400 +}; + +typedef struct { + uint8_t max_nak; // max NAK per endpoint per frame to save CPU/SPI bus usage (0=unlimited) + uint8_t cpuctl; // R16: CPU Control Register + uint8_t pinctl; // R17: Pin Control Register. FDUPSPI bit is ignored +} tuh_configure_max3421_t; + +typedef struct { + uint8_t max_nak; // max NAK per endpoint per frame to save CPU usage (0=unlimited) +} tuh_configure_fsdev_t; + +typedef struct { + bool use_hs_phy; // Always use high-speed ULPI/UTMI phy even when working at full-speed +} tuh_configure_dwc2_t; + +typedef union { + // For TUH_CFGID_RPI_PIO_USB_CONFIGURATION use pio_usb_configuration_t + tuh_configure_max3421_t max3421; + tuh_configure_fsdev_t fsdev; + tuh_configure_dwc2_t dwc2; +} tuh_configure_param_t; + +//--------------------------------------------------------------------+ +// APPLICATION CALLBACK +//--------------------------------------------------------------------+ + +// Invoked when enumeration get device descriptor +// Device is not ready to communicate yet, application can copy the descriptor if needed +void tuh_enum_descriptor_device_cb(uint8_t daddr, const tusb_desc_device_t *desc_device); + +// Invoked when enumeration get configuration descriptor +// For multi-configuration device return false to skip, true to proceed with this configuration (may not be implemented yet) +// Device is not ready to communicate yet, application can copy the descriptor if needed +bool tuh_enum_descriptor_configuration_cb(uint8_t daddr, uint8_t cfg_index, const tusb_desc_configuration_t *desc_config); + +// Invoked when a device is mounted (configured) +void tuh_mount_cb (uint8_t daddr); + +// Invoked when a device failed to mount during enumeration process +// void tuh_mount_failed_cb (uint8_t daddr); + +// Invoked when a device is unmounted (detached) +void tuh_umount_cb(uint8_t daddr); + +// Invoked when there is a new usb event, which need to be processed by tuh_task()/tuh_task_ext() +void tuh_event_hook_cb(uint8_t rhport, uint32_t eventid, bool in_isr); + +//--------------------------------------------------------------------+ +// APPLICATION API +//--------------------------------------------------------------------+ + +// Configure host stack behavior with dynamic or port-specific parameters. +// Should be called before tuh_init() +// - cfg_id : configure ID (TBD) +// - cfg_param: configure data, structure depends on the ID +bool tuh_configure(uint8_t rhport, uint32_t cfg_id, const void* cfg_param); + +// New API to replace tuh_init() to init host stack on specific roothub port +// Must be called in the same task/context as tuh_task() if RTOS is used +bool tuh_rhport_init(uint8_t rhport, const tusb_rhport_init_t* rh_init); + +// Init host stack +#if TUSB_VERSION_NUMBER > 2000 // 0.20.0 +TU_ATTR_DEPRECATED("Please use tusb_init(rhport, rh_init) instead") +#endif +TU_ATTR_ALWAYS_INLINE static inline bool tuh_init(uint8_t rhport) { + const tusb_rhport_init_t rh_init = { + .role = TUSB_ROLE_HOST, + .speed = TUH_OPT_HIGH_SPEED ? TUSB_SPEED_HIGH : TUSB_SPEED_FULL, + }; + return tuh_rhport_init(rhport, &rh_init); +} + +// Deinit host stack on rhport +// Must be called in the same task/context as tuh_task() if RTOS is used +bool tuh_deinit(uint8_t rhport); + +// Check if host stack is already initialized with any roothub ports +// To check if an rhport is initialized, use tuh_rhport_is_active() +bool tuh_inited(void); + +// Task function should be called in main/rtos loop, extended version of tuh_task() +// - timeout_ms: millisecond to wait, zero = no wait, 0xFFFFFFFF = wait forever +// - in_isr: if function is called in ISR +void tuh_task_ext(uint32_t timeout_ms, bool in_isr); + +// Task function should be called in main/rtos loop +TU_ATTR_ALWAYS_INLINE static inline void tuh_task(void) { + tuh_task_ext(UINT32_MAX, false); +} + +// Check if there is pending events need processing by tuh_task() +bool tuh_task_event_ready(void); + +#ifndef TUSB_HCD_H_ +extern void hcd_int_handler(uint8_t rhport, bool in_isr); +#endif + +// Interrupt handler alias to HCD with in_isr as optional parameter +#define _tuh_int_handler_arg0() TU_VERIFY_STATIC(false, "tuh_int_handler() must have 1 or 2 arguments") +#define _tuh_int_handler_arg1(_rhport) hcd_int_handler(_rhport, true) +#define _tuh_int_handler_arg2(_rhport, _in_isr) hcd_int_handler(_rhport, _in_isr) + +// 1st argument is rhport (mandatory), 2nd argument in_isr (optional) +#define tuh_int_handler(...) TU_FUNC_OPTIONAL_ARG(_tuh_int_handler, __VA_ARGS__) + +// Check if roothub port is initialized and active as a host +bool tuh_rhport_is_active(uint8_t rhport); + +// Assert/de-assert Bus Reset signal to roothub port. USB specs: it should last 10-50ms +bool tuh_rhport_reset_bus(uint8_t rhport, bool active); + +//--------------------------------------------------------------------+ +// Device API +//--------------------------------------------------------------------+ + +// Get VID/PID of device +bool tuh_vid_pid_get(uint8_t daddr, uint16_t* vid, uint16_t* pid); + +// Get local (cached) device descriptor once device is enumerated +bool tuh_descriptor_get_device_local(uint8_t daddr, tusb_desc_device_t* desc_device); + +// Get speed of device +tusb_speed_t tuh_speed_get(uint8_t daddr); + +// Check if device is connected and configured +bool tuh_mounted(uint8_t daddr); + +// Check if device is connected which mean device has at least 1 successful transfer +// Note: It may not be addressed/configured/mounted yet +bool tuh_connected(uint8_t daddr); + +// Check if device is suspended +TU_ATTR_ALWAYS_INLINE static inline bool tuh_suspended(uint8_t daddr) { + // TODO implement suspend & resume on host + (void) daddr; + return false; +} + +// Check if device is ready to communicate with +TU_ATTR_ALWAYS_INLINE static inline bool tuh_ready(uint8_t daddr) { + return tuh_mounted(daddr) && !tuh_suspended(daddr); +} + +// Get bus information of device +bool tuh_bus_info_get(uint8_t daddr, tuh_bus_info_t* bus_info); + +//--------------------------------------------------------------------+ +// Transfer API +// Each Function will make a USB transfer request to device. If +// - complete_cb != NULL, the function will return immediately and invoke the callback when request is complete. +// - complete_cb == NULL, the function will block until request is complete. +// In this case, user_data should be tusb_xfer_result_t* to hold the transfer result. +//--------------------------------------------------------------------+ + +// Helper to make Sync API from async one +#define TU_API_SYNC(_async_api, ...) \ + xfer_result_t result = XFER_RESULT_INVALID;\ + TU_VERIFY(_async_api(__VA_ARGS__, NULL, (uintptr_t) &result), XFER_RESULT_TIMEOUT); \ + return result + +// Submit a control transfer +// - async: complete callback invoked when finished. +// - sync : blocking if complete callback is NULL. +bool tuh_control_xfer(tuh_xfer_t* xfer); + +// Submit a bulk/interrupt transfer +// - async: complete callback invoked when finished. +// - sync : blocking if complete callback is NULL. +bool tuh_edpt_xfer(tuh_xfer_t* xfer); + +// Open a non-control endpoint +bool tuh_edpt_open(uint8_t daddr, tusb_desc_endpoint_t const * desc_ep); + +// Close a non-control endpoint, it will abort any pending transfer +bool tuh_edpt_close(uint8_t daddr, uint8_t ep_addr); + +// Abort a queued transfer. Note: it can only abort transfer that has not been started +// Return true if a queued transfer is aborted, false if there is no transfer to abort +bool tuh_edpt_abort_xfer(uint8_t daddr, uint8_t ep_addr); + +// Set Address (control transfer) +bool tuh_address_set(uint8_t daddr, uint8_t new_addr, + tuh_xfer_cb_t complete_cb, uintptr_t user_data); + +// Set Configuration (control transfer) +// config_num = 0 will un-configure device. Note: config_num = config_descriptor_index + 1 +// true on success, false if there is on-going control transfer or incorrect parameters +// if complete_cb == NULL i.e blocking, user_data should be pointed to xfer_reuslt_t* +bool tuh_configuration_set(uint8_t daddr, uint8_t config_num, + tuh_xfer_cb_t complete_cb, uintptr_t user_data); + +// Set Interface (control transfer) +// true on success, false if there is on-going control transfer or incorrect parameters +// if complete_cb == NULL i.e blocking, user_data should be pointed to xfer_reuslt_t* +bool tuh_interface_set(uint8_t daddr, uint8_t itf_num, uint8_t itf_alt, + tuh_xfer_cb_t complete_cb, uintptr_t user_data); + +//--------------------------------------------------------------------+ +// Descriptors Asynchronous (non-blocking) +//--------------------------------------------------------------------+ + +// Get an descriptor (control transfer) +// true on success, false if there is on-going control transfer or incorrect parameters +bool tuh_descriptor_get(uint8_t daddr, uint8_t type, uint8_t index, void* buffer, uint16_t len, + tuh_xfer_cb_t complete_cb, uintptr_t user_data); + +// Get device descriptor (control transfer) +// true on success, false if there is on-going control transfer or incorrect parameters +bool tuh_descriptor_get_device(uint8_t daddr, void* buffer, uint16_t len, + tuh_xfer_cb_t complete_cb, uintptr_t user_data); + +// Get configuration descriptor (control transfer) +// true on success, false if there is on-going control transfer or incorrect parameters +bool tuh_descriptor_get_configuration(uint8_t daddr, uint8_t index, void* buffer, uint16_t len, + tuh_xfer_cb_t complete_cb, uintptr_t user_data); + +// Get HID report descriptor (control transfer) +// true on success, false if there is on-going control transfer or incorrect parameters +bool tuh_descriptor_get_hid_report(uint8_t daddr, uint8_t itf_num, uint8_t desc_type, uint8_t index, void* buffer, uint16_t len, + tuh_xfer_cb_t complete_cb, uintptr_t user_data); + +// Get string descriptor (control transfer) +// true on success, false if there is on-going control transfer or incorrect parameters +// Blocking if complete callback is NULL, in this case 'user_data' must contain xfer_result_t variable +bool tuh_descriptor_get_string(uint8_t daddr, uint8_t index, uint16_t language_id, void* buffer, uint16_t len, + tuh_xfer_cb_t complete_cb, uintptr_t user_data); + +// Get language id string descriptor (control transfer) +TU_ATTR_ALWAYS_INLINE static inline +bool tuh_descriptor_get_string_langid(uint8_t daddr, void* buffer, uint16_t len, + tuh_xfer_cb_t complete_cb, uintptr_t user_data) { + return tuh_descriptor_get_string(daddr, 0, 0, buffer, len, complete_cb, user_data); +} + +// Get manufacturer string descriptor (control transfer) +// true on success, false if there is on-going control transfer or incorrect parameters +bool tuh_descriptor_get_manufacturer_string(uint8_t daddr, uint16_t language_id, void* buffer, uint16_t len, + tuh_xfer_cb_t complete_cb, uintptr_t user_data); + +// Get product string descriptor (control transfer) +// true on success, false if there is on-going control transfer or incorrect parameters +bool tuh_descriptor_get_product_string(uint8_t daddr, uint16_t language_id, void* buffer, uint16_t len, + tuh_xfer_cb_t complete_cb, uintptr_t user_data); + +// Get serial string descriptor (control transfer) +// true on success, false if there is on-going control transfer or incorrect parameters +bool tuh_descriptor_get_serial_string(uint8_t daddr, uint16_t language_id, void* buffer, uint16_t len, + tuh_xfer_cb_t complete_cb, uintptr_t user_data); + +//--------------------------------------------------------------------+ +// Descriptors Synchronous (blocking) +// Sync API which is blocking until transfer is complete. +// return transfer result +//--------------------------------------------------------------------+ + +// Sync version of tuh_descriptor_get() +TU_ATTR_ALWAYS_INLINE static inline tusb_xfer_result_t tuh_descriptor_get_sync(uint8_t daddr, uint8_t type, uint8_t index, void* buffer, uint16_t len) { + TU_API_SYNC(tuh_descriptor_get, daddr, type, index, buffer, len); +} + +// Sync version of tuh_descriptor_get_device() +TU_ATTR_ALWAYS_INLINE static inline tusb_xfer_result_t tuh_descriptor_get_device_sync(uint8_t daddr, void* buffer, uint16_t len) { + TU_API_SYNC(tuh_descriptor_get_device, daddr, buffer, len); +} + +// Sync version of tuh_descriptor_get_configuration() +TU_ATTR_ALWAYS_INLINE static inline tusb_xfer_result_t tuh_descriptor_get_configuration_sync(uint8_t daddr, uint8_t index, void* buffer, uint16_t len) { + TU_API_SYNC(tuh_descriptor_get_configuration, daddr, index, buffer, len); +} + +// Sync version of tuh_descriptor_get_hid_report() +TU_ATTR_ALWAYS_INLINE static inline tusb_xfer_result_t tuh_descriptor_get_hid_report_sync(uint8_t daddr, uint8_t itf_num, uint8_t desc_type, uint8_t index, void* buffer, uint16_t len) { + TU_API_SYNC(tuh_descriptor_get_hid_report, daddr, itf_num, desc_type, index, buffer, len); +} + +// Sync version of tuh_descriptor_get_string() +TU_ATTR_ALWAYS_INLINE static inline tusb_xfer_result_t tuh_descriptor_get_string_sync(uint8_t daddr, uint8_t index, uint16_t language_id, void* buffer, uint16_t len) { + TU_API_SYNC(tuh_descriptor_get_string, daddr, index, language_id, buffer, len); +} + +// Sync version of tuh_descriptor_get_string_langid() +TU_ATTR_ALWAYS_INLINE static inline tusb_xfer_result_t tuh_descriptor_get_string_langid_sync(uint8_t daddr, void* buffer, uint16_t len) { + return tuh_descriptor_get_string_sync(daddr, 0, 0, buffer, len); +} + +// Sync version of tuh_descriptor_get_manufacturer_string() +TU_ATTR_ALWAYS_INLINE static inline tusb_xfer_result_t tuh_descriptor_get_manufacturer_string_sync(uint8_t daddr, uint16_t language_id, void* buffer, uint16_t len) { + TU_API_SYNC(tuh_descriptor_get_manufacturer_string, daddr, language_id, buffer, len); +} + +// Sync version of tuh_descriptor_get_product_string() +TU_ATTR_ALWAYS_INLINE static inline tusb_xfer_result_t tuh_descriptor_get_product_string_sync(uint8_t daddr, uint16_t language_id, void* buffer, uint16_t len) { + TU_API_SYNC(tuh_descriptor_get_product_string, daddr, language_id, buffer, len); +} + +// Sync version of tuh_descriptor_get_serial_string() +TU_ATTR_ALWAYS_INLINE static inline tusb_xfer_result_t tuh_descriptor_get_serial_string_sync(uint8_t daddr, uint16_t language_id, void* buffer, uint16_t len) { + TU_API_SYNC(tuh_descriptor_get_serial_string, daddr, language_id, buffer, len); +} + +#ifdef __cplusplus + } +#endif + +#endif diff --git a/libraries/Adafruit_TinyUSB_Arduino/src/host/usbh_pvt.h b/libraries/Adafruit_TinyUSB_Arduino/src/host/usbh_pvt.h new file mode 100644 index 0000000..adb6a8c --- /dev/null +++ b/libraries/Adafruit_TinyUSB_Arduino/src/host/usbh_pvt.h @@ -0,0 +1,107 @@ +/* + * The MIT License (MIT) + * + * Copyright (c) 2021, 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. + * + * This file is part of the TinyUSB stack. + */ + +#ifndef TUSB_USBH_PVT_H_ +#define TUSB_USBH_PVT_H_ + +#include "osal/osal.h" +#include "common/tusb_fifo.h" +#include "common/tusb_private.h" + +#ifdef __cplusplus + extern "C" { +#endif + +#define TU_LOG_USBH(...) TU_LOG(CFG_TUH_LOG_LEVEL, __VA_ARGS__) +#define TU_LOG_MEM_USBH(...) TU_LOG_MEM(CFG_TUH_LOG_LEVEL, __VA_ARGS__) +#define TU_LOG_BUF_USBH(...) TU_LOG_BUF(CFG_TUH_LOG_LEVEL, __VA_ARGS__) +#define TU_LOG_INT_USBH(...) TU_LOG_INT(CFG_TUH_LOG_LEVEL, __VA_ARGS__) +#define TU_LOG_HEX_USBH(...) TU_LOG_HEX(CFG_TUH_LOG_LEVEL, __VA_ARGS__) + +//--------------------------------------------------------------------+ +// Class Driver API +//--------------------------------------------------------------------+ +typedef struct { + const char *name; + bool (*const init)(void); + bool (*const deinit)(void); + uint16_t (*const open)(uint8_t rhport, uint8_t dev_addr, const tusb_desc_interface_t *itf_desc, uint16_t max_len); + bool (*const set_config)(uint8_t dev_addr, uint8_t itf_num); + bool (*const xfer_cb)(uint8_t dev_addr, uint8_t ep_addr, xfer_result_t result, uint32_t xferred_bytes); + void (*const close)(uint8_t dev_addr); + } usbh_class_driver_t; + +// Invoked when initializing host stack to get additional class drivers. +// Can be implemented by application to extend/overwrite class driver support. +// Note: The drivers array must be accessible at all time when stack is active +usbh_class_driver_t const* usbh_app_driver_get_cb(uint8_t* driver_count); + +// Call by class driver to tell USBH that it has complete the enumeration +void usbh_driver_set_config_complete(uint8_t dev_addr, uint8_t itf_num); + +uint8_t usbh_get_rhport(uint8_t daddr); + +uint8_t* usbh_get_enum_buf(void); + +void usbh_int_set(bool enabled); + +// Invoke this function later in tuh_task() by putting it into task queue +void usbh_defer_func(osal_task_func_t func, void *param, bool in_isr); + +// Schedules a function to be called after certain time asynchronously +bool usbh_defer_func_ms_async(uint32_t ms, tusb_defer_func_t func, uintptr_t param); + +void usbh_spin_lock(bool in_isr); +void usbh_spin_unlock(bool in_isr); + +//--------------------------------------------------------------------+ +// USBH Endpoint API +//--------------------------------------------------------------------+ + +// Submit a usb transfer with callback support, require CFG_TUH_API_EDPT_XFER +bool usbh_edpt_xfer_with_callback(uint8_t dev_addr, uint8_t ep_addr, uint8_t * buffer, uint16_t total_bytes, + tuh_xfer_cb_t complete_cb, uintptr_t user_data); + +TU_ATTR_ALWAYS_INLINE static inline +bool usbh_edpt_xfer(uint8_t dev_addr, uint8_t ep_addr, uint8_t * buffer, uint16_t total_bytes) { + return usbh_edpt_xfer_with_callback(dev_addr, ep_addr, buffer, total_bytes, NULL, 0); +} + +// Claim an endpoint before submitting a transfer. +// If caller does not make any transfer, it must release endpoint for others. +bool usbh_edpt_claim(uint8_t dev_addr, uint8_t ep_addr); + +// Release claimed endpoint without submitting a transfer +bool usbh_edpt_release(uint8_t dev_addr, uint8_t ep_addr); + +// Check if endpoint transferring is complete +bool usbh_edpt_busy(uint8_t dev_addr, uint8_t ep_addr); + +#ifdef __cplusplus + } +#endif + +#endif diff --git a/libraries/Adafruit_TinyUSB_Arduino/src/osal/osal.h b/libraries/Adafruit_TinyUSB_Arduino/src/osal/osal.h new file mode 100644 index 0000000..4840463 --- /dev/null +++ b/libraries/Adafruit_TinyUSB_Arduino/src/osal/osal.h @@ -0,0 +1,108 @@ +/* + * 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. + * + * This file is part of the TinyUSB stack. + */ + +#ifndef TUSB_OSAL_H_ +#define TUSB_OSAL_H_ + +#ifdef __cplusplus + extern "C" { +#endif + +#include "common/tusb_common.h" + +typedef void (*osal_task_func_t)(void* param); + +// Timeout +#define OSAL_TIMEOUT_NOTIMEOUT (0) // Return immediately +#define OSAL_TIMEOUT_NORMAL (10) // Default timeout +#define OSAL_TIMEOUT_WAIT_FOREVER (UINT32_MAX) // Wait forever +#define OSAL_TIMEOUT_CONTROL_XFER OSAL_TIMEOUT_WAIT_FOREVER + +// Mutex is required when using a preempted RTOS or MCU has multiple cores +#if (CFG_TUSB_OS == OPT_OS_NONE) && !TUP_MCU_MULTIPLE_CORE + #define OSAL_MUTEX_REQUIRED 0 + #define OSAL_MUTEX_DEF(_name) uint8_t :0 +#else + #define OSAL_MUTEX_REQUIRED 1 + #define OSAL_MUTEX_DEF(_name) osal_mutex_def_t _name +#endif + +// OS thin implementation +#if CFG_TUSB_OS == OPT_OS_NONE + #include "osal_none.h" +#elif CFG_TUSB_OS == OPT_OS_FREERTOS + #include "osal_freertos.h" +#elif CFG_TUSB_OS == OPT_OS_MYNEWT + #include "osal_mynewt.h" +#elif CFG_TUSB_OS == OPT_OS_PICO + #include "osal_pico.h" +#elif CFG_TUSB_OS == OPT_OS_RTTHREAD + #include "osal_rtthread.h" +#elif CFG_TUSB_OS == OPT_OS_RTX4 + #include "osal_rtx4.h" +#elif CFG_TUSB_OS == OPT_OS_ZEPHYR + #include "osal_zephyr.h" +#elif CFG_TUSB_OS == OPT_OS_THREADX + #include "osal_threadx.h" +#elif CFG_TUSB_OS == OPT_OS_CUSTOM + #include "tusb_os_custom.h" // implemented by application +#else + #error OS is not supported yet +#endif + +/*-------------------------------------------------------------------- + OSAL Porting API + Should be implemented as static inline function in osal_port.h header + uint32_t osal_time_millis(void); + + void osal_spin_init(osal_spinlock_t *ctx); + void osal_spin_lock(osal_spinlock_t *ctx, bool in_isr) + void osal_spin_unlock(osal_spinlock_t *ctx, bool in_isr); + + osal_semaphore_t osal_semaphore_create(osal_semaphore_def_t* semdef); + bool osal_semaphore_delete(osal_semaphore_t semd_hdl); + bool osal_semaphore_post(osal_semaphore_t sem_hdl, bool in_isr); + bool osal_semaphore_wait(osal_semaphore_t sem_hdl, uint32_t msec); + void osal_semaphore_reset(osal_semaphore_t sem_hdl); + + osal_mutex_t osal_mutex_create(osal_mutex_def_t* mdef); + bool osal_mutex_delete(osal_mutex_t mutex_hdl) + bool osal_mutex_lock (osal_mutex_t sem_hdl, uint32_t msec); + bool osal_mutex_unlock(osal_mutex_t mutex_hdl); + + osal_queue_t osal_queue_create(osal_queue_def_t* qdef); + bool osal_queue_delete(osal_queue_t qhdl); + bool osal_queue_receive(osal_queue_t qhdl, void* data, uint32_t msec); + bool osal_queue_send(osal_queue_t qhdl, void const * data, bool in_isr); + bool osal_queue_empty(osal_queue_t qhdl); +--------------------------------------------------------------------------*/ + + +#ifdef __cplusplus + } +#endif + +#endif diff --git a/libraries/Adafruit_TinyUSB_Arduino/src/osal/osal_freertos.h b/libraries/Adafruit_TinyUSB_Arduino/src/osal/osal_freertos.h new file mode 100644 index 0000000..898edd4 --- /dev/null +++ b/libraries/Adafruit_TinyUSB_Arduino/src/osal/osal_freertos.h @@ -0,0 +1,278 @@ +/* + * 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. + * + * This file is part of the TinyUSB stack. + */ + +#ifndef TUSB_OSAL_FREERTOS_H_ +#define TUSB_OSAL_FREERTOS_H_ + +// FreeRTOS Headers +#include TU_INCLUDE_PATH(CFG_TUSB_OS_INC_PATH,FreeRTOS.h) +#include TU_INCLUDE_PATH(CFG_TUSB_OS_INC_PATH,semphr.h) +#include TU_INCLUDE_PATH(CFG_TUSB_OS_INC_PATH,queue.h) +#include TU_INCLUDE_PATH(CFG_TUSB_OS_INC_PATH,task.h) + +#ifdef __cplusplus +extern "C" { +#endif + +//--------------------------------------------------------------------+ +// MACRO CONSTANT TYPEDEF PROTYPES +//--------------------------------------------------------------------+ + +#if configSUPPORT_STATIC_ALLOCATION +typedef StaticSemaphore_t osal_semaphore_def_t; +typedef StaticSemaphore_t osal_mutex_def_t; +#else + +// not used therefore defined to the smallest possible type to save space +typedef uint8_t osal_semaphore_def_t; +typedef uint8_t osal_mutex_def_t; +#endif + +typedef SemaphoreHandle_t osal_semaphore_t; +typedef SemaphoreHandle_t osal_mutex_t; +typedef QueueHandle_t osal_queue_t; + +typedef struct { + uint16_t depth; + uint16_t item_sz; + void* buf; + +#if defined(configQUEUE_REGISTRY_SIZE) && (configQUEUE_REGISTRY_SIZE>0) + char const* name; +#endif + +#if configSUPPORT_STATIC_ALLOCATION + StaticQueue_t sq; +#endif +} osal_queue_def_t; + +#if defined(configQUEUE_REGISTRY_SIZE) && (configQUEUE_REGISTRY_SIZE>0) + #define OSAL_Q_NAME(_name) .name = #_name +#else + #define OSAL_Q_NAME(_name) +#endif + +// _int_set is not used with an RTOS +#define OSAL_QUEUE_DEF(_int_set, _name, _depth, _type) \ + static _type _name##_##buf[_depth];\ + osal_queue_def_t _name = { .depth = _depth, .item_sz = sizeof(_type), .buf = _name##_##buf, OSAL_Q_NAME(_name) } + +//--------------------------------------------------------------------+ +// TASK API +//--------------------------------------------------------------------+ +TU_ATTR_ALWAYS_INLINE static inline uint32_t _osal_ms2tick(uint32_t msec) { + if (msec == OSAL_TIMEOUT_WAIT_FOREVER) { return portMAX_DELAY; } + if (msec == 0) { return 0; } + + uint32_t ticks = pdMS_TO_TICKS(msec); + + // If configTICK_RATE_HZ is less than 1000 and 1 tick > 1 ms, we still need to delay at least 1 tick + if (ticks == 0) { ticks = 1; } + + return ticks; +} + +TU_ATTR_ALWAYS_INLINE static inline void osal_task_delay(uint32_t msec) { + vTaskDelay(pdMS_TO_TICKS(msec)); +} + +TU_ATTR_ALWAYS_INLINE static inline uint32_t osal_time_millis(void) { + return pdTICKS_TO_MS(xTaskGetTickCount()); +} + +//--------------------------------------------------------------------+ +// Spinlock API +//--------------------------------------------------------------------+ +#define OSAL_SPINLOCK_DEF(_name, _int_set) \ + osal_spinlock_t _name + +#ifdef ESP_PLATFORM +// Espressif critical take spinlock as argument and does not use in_isr +typedef portMUX_TYPE osal_spinlock_t; + +TU_ATTR_ALWAYS_INLINE static inline void osal_spin_init(osal_spinlock_t *ctx) { + spinlock_initialize(ctx); +} + +TU_ATTR_ALWAYS_INLINE static inline void osal_spin_lock(osal_spinlock_t *ctx, bool in_isr) { + if (!TUP_MCU_MULTIPLE_CORE && in_isr) { + return; // single core MCU does not need to lock in ISR + } + portENTER_CRITICAL(ctx); +} + +TU_ATTR_ALWAYS_INLINE static inline void osal_spin_unlock(osal_spinlock_t *ctx, bool in_isr) { + if (!TUP_MCU_MULTIPLE_CORE && in_isr) { + return; // single core MCU does not need to lock in ISR + } + portEXIT_CRITICAL(ctx); +} + +#else + +typedef UBaseType_t osal_spinlock_t; + +TU_ATTR_ALWAYS_INLINE static inline void osal_spin_init(osal_spinlock_t *ctx) { + (void) ctx; +} + +TU_ATTR_ALWAYS_INLINE static inline void osal_spin_lock(osal_spinlock_t *ctx, bool in_isr) { + if (in_isr) { + #if TUP_MCU_MULTIPLE_CORE + *ctx = taskENTER_CRITICAL_FROM_ISR(); + #else + (void) ctx; + return; // single core MCU does not need to lock in ISR + #endif + } else { + taskENTER_CRITICAL(); + } +} + +TU_ATTR_ALWAYS_INLINE static inline void osal_spin_unlock(osal_spinlock_t *ctx, bool in_isr) { + if (in_isr) { + #if TUP_MCU_MULTIPLE_CORE + taskEXIT_CRITICAL_FROM_ISR(*ctx); + #else + (void) ctx; + return; // single core MCU does not need to lock in ISR + #endif + } else { + taskEXIT_CRITICAL(); + } +} + +#endif + +//--------------------------------------------------------------------+ +// Semaphore API +//--------------------------------------------------------------------+ +TU_ATTR_ALWAYS_INLINE static inline osal_semaphore_t osal_semaphore_create(osal_semaphore_def_t *semdef) { +#if configSUPPORT_STATIC_ALLOCATION + return xSemaphoreCreateBinaryStatic((StaticSemaphore_t*) semdef); +#else + (void) semdef; + return xSemaphoreCreateBinary(); +#endif +} + +TU_ATTR_ALWAYS_INLINE static inline bool osal_semaphore_delete(osal_semaphore_t semd_hdl) { + vSemaphoreDelete((SemaphoreHandle_t) semd_hdl); + return true; +} + +TU_ATTR_ALWAYS_INLINE static inline bool osal_semaphore_post(osal_semaphore_t sem_hdl, bool in_isr) { + if (!in_isr) { + return xSemaphoreGive(sem_hdl) != 0; + } else { + BaseType_t xHigherPriorityTaskWoken = pdFALSE; + BaseType_t res = xSemaphoreGiveFromISR(sem_hdl, &xHigherPriorityTaskWoken); + portYIELD_FROM_ISR(xHigherPriorityTaskWoken); + return res != 0; + } +} + +TU_ATTR_ALWAYS_INLINE static inline bool osal_semaphore_wait(osal_semaphore_t sem_hdl, uint32_t msec) { + return xSemaphoreTake(sem_hdl, _osal_ms2tick(msec)); +} + +TU_ATTR_ALWAYS_INLINE static inline void osal_semaphore_reset(osal_semaphore_t const sem_hdl) { + xQueueReset(sem_hdl); +} + +//--------------------------------------------------------------------+ +// MUTEX API (priority inheritance) +//--------------------------------------------------------------------+ +TU_ATTR_ALWAYS_INLINE static inline osal_mutex_t osal_mutex_create(osal_mutex_def_t *mdef) { +#if configSUPPORT_STATIC_ALLOCATION + return xSemaphoreCreateMutexStatic(mdef); +#else + (void) mdef; + return xSemaphoreCreateMutex(); +#endif +} + +TU_ATTR_ALWAYS_INLINE static inline bool osal_mutex_delete(osal_mutex_t mutex_hdl) { + vSemaphoreDelete(mutex_hdl); + return true; +} + +TU_ATTR_ALWAYS_INLINE static inline bool osal_mutex_lock(osal_mutex_t mutex_hdl, uint32_t msec) { + return osal_semaphore_wait(mutex_hdl, msec); +} + +TU_ATTR_ALWAYS_INLINE static inline bool osal_mutex_unlock(osal_mutex_t mutex_hdl) { + return xSemaphoreGive(mutex_hdl); +} + +//--------------------------------------------------------------------+ +// QUEUE API +//--------------------------------------------------------------------+ +TU_ATTR_ALWAYS_INLINE static inline osal_queue_t osal_queue_create(osal_queue_def_t* qdef) { + osal_queue_t q; + +#if configSUPPORT_STATIC_ALLOCATION + q = xQueueCreateStatic(qdef->depth, qdef->item_sz, (uint8_t*) qdef->buf, &qdef->sq); +#else + q = xQueueCreate(qdef->depth, qdef->item_sz); +#endif + +#if defined(configQUEUE_REGISTRY_SIZE) && (configQUEUE_REGISTRY_SIZE>0) + vQueueAddToRegistry(q, qdef->name); +#endif + + return q; +} + +TU_ATTR_ALWAYS_INLINE static inline bool osal_queue_delete(osal_queue_t qhdl) { + vQueueDelete(qhdl); + return true; +} + +TU_ATTR_ALWAYS_INLINE static inline bool osal_queue_receive(osal_queue_t qhdl, void* data, uint32_t msec) { + return xQueueReceive(qhdl, data, _osal_ms2tick(msec)); +} + +TU_ATTR_ALWAYS_INLINE static inline bool osal_queue_send(osal_queue_t qhdl, void const *data, bool in_isr) { + if (!in_isr) { + return xQueueSendToBack(qhdl, data, OSAL_TIMEOUT_WAIT_FOREVER) != 0; + } else { + BaseType_t xHigherPriorityTaskWoken = pdFALSE; + BaseType_t res = xQueueSendToBackFromISR(qhdl, data, &xHigherPriorityTaskWoken); + portYIELD_FROM_ISR(xHigherPriorityTaskWoken); + return res != 0; + } +} + +TU_ATTR_ALWAYS_INLINE static inline bool osal_queue_empty(osal_queue_t qhdl) { + return uxQueueMessagesWaiting(qhdl) == 0; +} + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/libraries/Adafruit_TinyUSB_Arduino/src/osal/osal_mynewt.h b/libraries/Adafruit_TinyUSB_Arduino/src/osal/osal_mynewt.h new file mode 100644 index 0000000..335d534 --- /dev/null +++ b/libraries/Adafruit_TinyUSB_Arduino/src/osal/osal_mynewt.h @@ -0,0 +1,226 @@ +/* + * 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. + * + * This file is part of the TinyUSB stack. + */ + +#ifndef OSAL_MYNEWT_H_ +#define OSAL_MYNEWT_H_ + +#include "os/os.h" + +#ifdef __cplusplus + extern "C" { +#endif + +//--------------------------------------------------------------------+ +// TASK API +//--------------------------------------------------------------------+ +TU_ATTR_ALWAYS_INLINE static inline void osal_task_delay(uint32_t msec) { + os_time_delay( os_time_ms_to_ticks32(msec) ); +} + +TU_ATTR_ALWAYS_INLINE static inline uint32_t osal_time_millis(void) { + return os_time_ticks_to_ms32(os_time_get()); +} + +//--------------------------------------------------------------------+ +// Spinlock API +//--------------------------------------------------------------------+ +typedef os_sr_t osal_spinlock_t; + +#define OSAL_SPINLOCK_DEF(_name, _int_set) \ + osal_spinlock_t _name + +TU_ATTR_ALWAYS_INLINE static inline void osal_spin_init(osal_spinlock_t *ctx) { + (void) ctx; +} + +TU_ATTR_ALWAYS_INLINE static inline void osal_spin_lock(osal_spinlock_t *ctx, bool in_isr) { + if (!TUP_MCU_MULTIPLE_CORE && in_isr) { + return; // single core MCU does not need to lock in ISR + } + OS_ENTER_CRITICAL(*ctx); +} + +TU_ATTR_ALWAYS_INLINE static inline void osal_spin_unlock(osal_spinlock_t *ctx, bool in_isr) { + if (!TUP_MCU_MULTIPLE_CORE && in_isr) { + return; // single core MCU does not need to lock in ISR + } + OS_EXIT_CRITICAL(*ctx); +} + +//--------------------------------------------------------------------+ +// Semaphore API +//--------------------------------------------------------------------+ +typedef struct os_sem osal_semaphore_def_t; +typedef struct os_sem* osal_semaphore_t; + +TU_ATTR_ALWAYS_INLINE static inline osal_semaphore_t osal_semaphore_create(osal_semaphore_def_t* semdef) { + return (os_sem_init(semdef, 0) == OS_OK) ? (osal_semaphore_t) semdef : NULL; +} + +TU_ATTR_ALWAYS_INLINE static inline bool osal_semaphore_delete(osal_semaphore_t semd_hdl) { + (void) semd_hdl; + return true; // nothing to do +} + +TU_ATTR_ALWAYS_INLINE static inline bool osal_semaphore_post(osal_semaphore_t sem_hdl, bool in_isr) { + (void) in_isr; + return os_sem_release(sem_hdl) == OS_OK; +} + +TU_ATTR_ALWAYS_INLINE static inline bool osal_semaphore_wait(osal_semaphore_t sem_hdl, uint32_t msec) { + uint32_t const ticks = (msec == OSAL_TIMEOUT_WAIT_FOREVER) ? OS_TIMEOUT_NEVER : os_time_ms_to_ticks32(msec); + return os_sem_pend(sem_hdl, ticks) == OS_OK; +} + +static inline void osal_semaphore_reset(osal_semaphore_t sem_hdl) { + // TODO implement later +} + +//--------------------------------------------------------------------+ +// MUTEX API (priority inheritance) +//--------------------------------------------------------------------+ +typedef struct os_mutex osal_mutex_def_t; +typedef struct os_mutex* osal_mutex_t; + +TU_ATTR_ALWAYS_INLINE static inline osal_mutex_t osal_mutex_create(osal_mutex_def_t* mdef) { + return (os_mutex_init(mdef) == OS_OK) ? (osal_mutex_t) mdef : NULL; +} + +TU_ATTR_ALWAYS_INLINE static inline bool osal_mutex_delete(osal_mutex_t mutex_hdl) { + (void) mutex_hdl; + return true; // nothing to do +} + +TU_ATTR_ALWAYS_INLINE static inline bool osal_mutex_lock(osal_mutex_t mutex_hdl, uint32_t msec) { + uint32_t const ticks = (msec == OSAL_TIMEOUT_WAIT_FOREVER) ? OS_TIMEOUT_NEVER : os_time_ms_to_ticks32(msec); + return os_mutex_pend(mutex_hdl, ticks) == OS_OK; +} + +TU_ATTR_ALWAYS_INLINE static inline bool osal_mutex_unlock(osal_mutex_t mutex_hdl) { + return os_mutex_release(mutex_hdl) == OS_OK; +} + +TU_ATTR_ALWAYS_INLINE static inline os_time_t _osal_ms2tick(uint32_t msec) { + if (msec == OSAL_TIMEOUT_WAIT_FOREVER) { + return OS_TIMEOUT_NEVER; + } + if (msec == 0) { + return 0; + } + + os_time_t ticks = os_time_ms_to_ticks32(msec); + + // If 1 tick > 1 ms, still wait at least 1 tick for non-zero timeout. + if (ticks == 0) { + ticks = 1; + } + + return ticks; +} + +//--------------------------------------------------------------------+ +// QUEUE API +//--------------------------------------------------------------------+ + +// role device/host is used by OS NONE for mutex (disable usb isr) only +#define OSAL_QUEUE_DEF(_int_set, _name, _depth, _type) \ + static _type _name##_##buf[_depth];\ + static struct os_event _name##_##evbuf[_depth];\ + osal_queue_def_t _name = { .depth = _depth, .item_sz = sizeof(_type), .buf = _name##_##buf, .evbuf = _name##_##evbuf};\ + +typedef struct { + uint16_t depth; + uint16_t item_sz; + void* buf; + void* evbuf; + + struct os_mempool mpool; + struct os_mempool epool; + + struct os_eventq evq; +}osal_queue_def_t; + +typedef osal_queue_def_t* osal_queue_t; + +TU_ATTR_ALWAYS_INLINE static inline osal_queue_t osal_queue_create(osal_queue_def_t* qdef) { + if ( OS_OK != os_mempool_init(&qdef->mpool, qdef->depth, qdef->item_sz, qdef->buf, "usb queue") ) return NULL; + if ( OS_OK != os_mempool_init(&qdef->epool, qdef->depth, sizeof(struct os_event), qdef->evbuf, "usb evqueue") ) return NULL; + + os_eventq_init(&qdef->evq); + return (osal_queue_t) qdef; +} + +TU_ATTR_ALWAYS_INLINE static inline bool osal_queue_delete(osal_queue_t qhdl) { + (void) qhdl; + return true; // nothing to do +} + +TU_ATTR_ALWAYS_INLINE static inline bool osal_queue_receive(osal_queue_t qhdl, void* data, uint32_t msec) { + struct os_eventq* evq = &qhdl->evq; + struct os_event* ev = os_eventq_poll(&evq, 1, _osal_ms2tick(msec)); + if (!ev) { + return false; + } + + memcpy(data, ev->ev_arg, qhdl->item_sz); // copy message + os_memblock_put(&qhdl->mpool, ev->ev_arg); // put back mem block + os_memblock_put(&qhdl->epool, ev); // put back ev block + + return true; +} + +static inline bool osal_queue_send(osal_queue_t qhdl, void const * data, bool in_isr) { + (void) in_isr; + + // get a block from mem pool for data + void* ptr = os_memblock_get(&qhdl->mpool); + if (!ptr) return false; + memcpy(ptr, data, qhdl->item_sz); + + // get a block from event pool to put into queue + struct os_event* ev = (struct os_event*) os_memblock_get(&qhdl->epool); + if (!ev) { + os_memblock_put(&qhdl->mpool, ptr); + return false; + } + tu_memclr(ev, sizeof(struct os_event)); + ev->ev_arg = ptr; + + os_eventq_put(&qhdl->evq, ev); + + return true; +} + +TU_ATTR_ALWAYS_INLINE static inline bool osal_queue_empty(osal_queue_t qhdl) { + return STAILQ_EMPTY(&qhdl->evq.evq_list); +} + + +#ifdef __cplusplus + } +#endif + +#endif /* OSAL_MYNEWT_H_ */ diff --git a/libraries/Adafruit_TinyUSB_Arduino/src/osal/osal_none.h b/libraries/Adafruit_TinyUSB_Arduino/src/osal/osal_none.h new file mode 100644 index 0000000..7bf6029 --- /dev/null +++ b/libraries/Adafruit_TinyUSB_Arduino/src/osal/osal_none.h @@ -0,0 +1,219 @@ +/* + * 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. + * + * This file is part of the TinyUSB stack. + */ + +#ifndef TUSB_OSAL_NONE_H_ +#define TUSB_OSAL_NONE_H_ + +#ifdef __cplusplus +extern "C" { +#endif + +// osal_time_millis() is not provided, tusb_time_millis_api() must be implemented by user application + +//--------------------------------------------------------------------+ +// Spinlock API +//--------------------------------------------------------------------+ +// Note: This implementation is designed for bare-metal single-core systems without RTOS. +// - Supports nested locking within the same execution context +// - NOT suitable for true SMP (Symmetric Multi-Processing) systems +// - NOT thread-safe for multi-threaded environments +// - Primarily manages interrupt enable/disable state for critical sections +typedef struct { + void (* interrupt_set)(bool enabled); + uint32_t nested_count; +} osal_spinlock_t; + +// For SMP, spinlock must be locked by hardware, cannot just use interrupt +#define OSAL_SPINLOCK_DEF(_name, _int_set) \ + osal_spinlock_t _name = { .interrupt_set = _int_set, .nested_count = 0 } + +TU_ATTR_ALWAYS_INLINE static inline void osal_spin_init(osal_spinlock_t *ctx) { + (void) ctx; +} + +TU_ATTR_ALWAYS_INLINE static inline void osal_spin_lock(osal_spinlock_t *ctx, bool in_isr) { + // Disable interrupts first to make nested_count increment atomic + if (!in_isr && ctx->nested_count == 0) { + ctx->interrupt_set(false); + } + ctx->nested_count++; +} + +TU_ATTR_ALWAYS_INLINE static inline void osal_spin_unlock(osal_spinlock_t *ctx, bool in_isr) { + if (ctx->nested_count == 0) { + return; // spin is not locked to begin with + } + + ctx->nested_count--; + + // Only re-enable interrupts when fully unlocked + if (!in_isr && ctx->nested_count == 0) { + ctx->interrupt_set(true); + } +} + +//--------------------------------------------------------------------+ +// Binary Semaphore API +//--------------------------------------------------------------------+ +typedef struct { + volatile uint16_t count; +} osal_semaphore_def_t; + +typedef osal_semaphore_def_t* osal_semaphore_t; + +TU_ATTR_ALWAYS_INLINE static inline osal_semaphore_t osal_semaphore_create(osal_semaphore_def_t* semdef) { + semdef->count = 0; + return semdef; +} + +TU_ATTR_ALWAYS_INLINE static inline bool osal_semaphore_delete(osal_semaphore_t semd_hdl) { + (void) semd_hdl; + return true; // nothing to do +} + + +TU_ATTR_ALWAYS_INLINE static inline bool osal_semaphore_post(osal_semaphore_t sem_hdl, bool in_isr) { + (void) in_isr; + sem_hdl->count++; + return true; +} + +// TODO blocking for now +TU_ATTR_ALWAYS_INLINE static inline bool osal_semaphore_wait(osal_semaphore_t sem_hdl, uint32_t msec) { + (void) msec; + + while (sem_hdl->count == 0) {} + sem_hdl->count--; + + return true; +} + +TU_ATTR_ALWAYS_INLINE static inline void osal_semaphore_reset(osal_semaphore_t sem_hdl) { + sem_hdl->count = 0; +} + +//--------------------------------------------------------------------+ +// MUTEX API +// Within tinyusb, mutex is never used in ISR context +//--------------------------------------------------------------------+ +typedef osal_semaphore_def_t osal_mutex_def_t; +typedef osal_semaphore_t osal_mutex_t; + +#if OSAL_MUTEX_REQUIRED +// Note: multiple cores MCUs usually do provide IPC API for mutex +// or we can use std atomic function + +TU_ATTR_ALWAYS_INLINE static inline osal_mutex_t osal_mutex_create(osal_mutex_def_t* mdef) { + mdef->count = 1; + return mdef; +} + +TU_ATTR_ALWAYS_INLINE static inline bool osal_mutex_delete(osal_mutex_t mutex_hdl) { + (void) mutex_hdl; + return true; // nothing to do +} + +TU_ATTR_ALWAYS_INLINE static inline bool osal_mutex_lock (osal_mutex_t mutex_hdl, uint32_t msec) { + return osal_semaphore_wait(mutex_hdl, msec); +} + +TU_ATTR_ALWAYS_INLINE static inline bool osal_mutex_unlock(osal_mutex_t mutex_hdl) { + return osal_semaphore_post(mutex_hdl, false); +} + +#else + +#define osal_mutex_create(_mdef) (NULL) +#define osal_mutex_lock(_mutex_hdl, _ms) (true) +#define osal_mutex_unlock(_mutex_hdl) (true) + +#endif + +//--------------------------------------------------------------------+ +// QUEUE API +//--------------------------------------------------------------------+ +#include "common/tusb_fifo.h" + +typedef struct { + void (* interrupt_set)(bool enabled); + uint16_t item_size; + tu_fifo_t ff; +} osal_queue_def_t; + +typedef osal_queue_def_t* osal_queue_t; + +// _int_set is used as mutex in OS NONE (disable/enable USB ISR) +#define OSAL_QUEUE_DEF(_int_set, _name, _depth, _type) \ + uint8_t _name##_buf[_depth * sizeof(_type)]; \ + osal_queue_def_t _name = {.interrupt_set = _int_set, \ + .item_size = sizeof(_type), \ + .ff = TU_FIFO_INIT(_name##_buf, _depth * sizeof(_type), false)} + +TU_ATTR_ALWAYS_INLINE static inline osal_queue_t osal_queue_create(osal_queue_def_t* qdef) { + tu_fifo_clear(&qdef->ff); + return (osal_queue_t) qdef; +} + +TU_ATTR_ALWAYS_INLINE static inline bool osal_queue_delete(osal_queue_t qhdl) { + (void) qhdl; + return true; // nothing to do +} + +TU_ATTR_ALWAYS_INLINE static inline bool osal_queue_receive(osal_queue_t qhdl, void* data, uint32_t msec) { + (void) msec; // not used, always behave as msec = 0 + + qhdl->interrupt_set(false); + const bool success = (tu_fifo_read_n(&qhdl->ff, data, qhdl->item_size) > 0); + qhdl->interrupt_set(true); + + return success; +} + +TU_ATTR_ALWAYS_INLINE static inline bool osal_queue_send(osal_queue_t qhdl, void const* data, bool in_isr) { + if (!in_isr) { + qhdl->interrupt_set(false); + } + + const bool success = (tu_fifo_write_n(&qhdl->ff, data, qhdl->item_size) > 0); + + if (!in_isr) { + qhdl->interrupt_set(true); + } + + return success; +} + +TU_ATTR_ALWAYS_INLINE static inline bool osal_queue_empty(osal_queue_t qhdl) { + // Skip queue lock/unlock since this function is primarily called + // with interrupt disabled before going into low power mode + return tu_fifo_empty(&qhdl->ff); +} + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/libraries/Adafruit_TinyUSB_Arduino/src/osal/osal_pico.h b/libraries/Adafruit_TinyUSB_Arduino/src/osal/osal_pico.h new file mode 100644 index 0000000..6a0a21b --- /dev/null +++ b/libraries/Adafruit_TinyUSB_Arduino/src/osal/osal_pico.h @@ -0,0 +1,186 @@ +/* + * The MIT License (MIT) + * + * Copyright (c) 2020 Raspberry Pi (Trading) Ltd. + * + * 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_OSAL_PICO_H_ +#define TUSB_OSAL_PICO_H_ + +#include "pico/time.h" +#include "pico/sem.h" +#include "pico/mutex.h" +#include "pico/critical_section.h" + +#ifdef __cplusplus +extern "C" { +#endif + +//--------------------------------------------------------------------+ +// TASK API +//--------------------------------------------------------------------+ +TU_ATTR_ALWAYS_INLINE static inline void osal_task_delay(uint32_t msec) { + sleep_ms(msec); +} + +TU_ATTR_ALWAYS_INLINE static inline uint32_t osal_time_millis(void) { + return to_ms_since_boot(get_absolute_time()); +} + +//--------------------------------------------------------------------+ +// Spinlock API +//--------------------------------------------------------------------+ +typedef critical_section_t osal_spinlock_t; // pico implement critical section with spinlock +#define OSAL_SPINLOCK_DEF(_name, _int_set) osal_spinlock_t _name + +TU_ATTR_ALWAYS_INLINE static inline void osal_spin_init(osal_spinlock_t *ctx) { + critical_section_init(ctx); +} + +TU_ATTR_ALWAYS_INLINE static inline void osal_spin_lock(osal_spinlock_t *ctx, bool in_isr) { + (void)in_isr; + critical_section_enter_blocking(ctx); +} + +TU_ATTR_ALWAYS_INLINE static inline void osal_spin_unlock(osal_spinlock_t *ctx, bool in_isr) { + (void)in_isr; + critical_section_exit(ctx); +} + +//--------------------------------------------------------------------+ +// Binary Semaphore API +//--------------------------------------------------------------------+ +typedef struct semaphore osal_semaphore_def_t, *osal_semaphore_t; + +TU_ATTR_ALWAYS_INLINE static inline osal_semaphore_t osal_semaphore_create(osal_semaphore_def_t *semdef) { + sem_init(semdef, 0, 255); + return semdef; +} + +TU_ATTR_ALWAYS_INLINE static inline bool osal_semaphore_delete(osal_semaphore_t semd_hdl) { + (void)semd_hdl; + return true; // nothing to do +} + +TU_ATTR_ALWAYS_INLINE static inline bool osal_semaphore_post(osal_semaphore_t sem_hdl, bool in_isr) { + (void)in_isr; + return sem_release(sem_hdl); +} + +TU_ATTR_ALWAYS_INLINE static inline bool osal_semaphore_wait(osal_semaphore_t sem_hdl, uint32_t msec) { + return sem_acquire_timeout_ms(sem_hdl, msec); +} + +TU_ATTR_ALWAYS_INLINE static inline void osal_semaphore_reset(osal_semaphore_t sem_hdl) { + sem_reset(sem_hdl, 0); +} + +//--------------------------------------------------------------------+ +// MUTEX API +// Within tinyusb, mutex is never used in ISR context +//--------------------------------------------------------------------+ +typedef struct mutex osal_mutex_def_t, *osal_mutex_t; + +TU_ATTR_ALWAYS_INLINE static inline osal_mutex_t osal_mutex_create(osal_mutex_def_t *mdef) { + mutex_init(mdef); + return mdef; +} + +TU_ATTR_ALWAYS_INLINE static inline bool osal_mutex_delete(osal_mutex_t mutex_hdl) { + (void)mutex_hdl; + return true; // nothing to do +} + +TU_ATTR_ALWAYS_INLINE static inline bool osal_mutex_lock(osal_mutex_t mutex_hdl, uint32_t msec) { + return mutex_enter_timeout_ms(mutex_hdl, msec); +} + +TU_ATTR_ALWAYS_INLINE static inline bool osal_mutex_unlock(osal_mutex_t mutex_hdl) { + mutex_exit(mutex_hdl); + return true; +} + +//--------------------------------------------------------------------+ +// QUEUE API +//--------------------------------------------------------------------+ +#include "common/tusb_fifo.h" + +typedef struct { + uint16_t item_size; + tu_fifo_t ff; + struct critical_section critsec; // osal_queue may be used in IRQs, so need critical section +} osal_queue_def_t; + +typedef osal_queue_def_t *osal_queue_t; + +// role device/host is used by OS NONE for mutex (disable usb isr) only +#define OSAL_QUEUE_DEF(_int_set, _name, _depth, _type) \ + uint8_t _name##_buf[_depth * sizeof(_type)]; \ + osal_queue_def_t _name = {.item_size = sizeof(_type), .ff = TU_FIFO_INIT(_name##_buf, _depth * sizeof(_type), false)} + +TU_ATTR_ALWAYS_INLINE static inline osal_queue_t osal_queue_create(osal_queue_def_t *qdef) { + critical_section_init(&qdef->critsec); + tu_fifo_clear(&qdef->ff); + return (osal_queue_t)qdef; +} + +TU_ATTR_ALWAYS_INLINE static inline bool osal_queue_delete(osal_queue_t qhdl) { + osal_queue_def_t *qdef = (osal_queue_def_t *)qhdl; + critical_section_deinit(&qdef->critsec); + return true; +} + +TU_ATTR_ALWAYS_INLINE static inline bool osal_queue_receive(osal_queue_t qhdl, void *data, uint32_t msec) { + (void)msec; // not used, always behave as msec = 0 + + critical_section_enter_blocking(&qhdl->critsec); + bool success = tu_fifo_read_n(&qhdl->ff, data, qhdl->item_size); + critical_section_exit(&qhdl->critsec); + + return success; +} + +TU_ATTR_ALWAYS_INLINE static inline bool osal_queue_send(osal_queue_t qhdl, const void *data, bool in_isr) { + (void)in_isr; + + critical_section_enter_blocking(&qhdl->critsec); + bool success = tu_fifo_write_n(&qhdl->ff, data, qhdl->item_size); + critical_section_exit(&qhdl->critsec); + + return success; +} + +TU_ATTR_ALWAYS_INLINE static inline bool osal_queue_empty(osal_queue_t qhdl) { + // TODO: revisit; whether this is true or not currently, tu_fifo_empty is a single + // volatile read. + + // Skip queue lock/unlock since this function is primarily called + // with interrupt disabled before going into low power mode + return tu_fifo_empty(&qhdl->ff); +} + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/libraries/Adafruit_TinyUSB_Arduino/src/osal/osal_rtthread.h b/libraries/Adafruit_TinyUSB_Arduino/src/osal/osal_rtthread.h new file mode 100644 index 0000000..f560281 --- /dev/null +++ b/libraries/Adafruit_TinyUSB_Arduino/src/osal/osal_rtthread.h @@ -0,0 +1,178 @@ +/* + * The MIT License (MIT) + * + * Copyright (c) 2020 tfx2001 (2479727366@qq.com) + * Copyright (c) 2020 yekai (2857693944@qq.com) + * + * 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_OSAL_RTTHREAD_H_ +#define TUSB_OSAL_RTTHREAD_H_ + +// RT-Thread Headers +#include "rtthread.h" + +#ifdef __cplusplus +extern "C" { +#endif + +//--------------------------------------------------------------------+ +// TASK API +//--------------------------------------------------------------------+ +TU_ATTR_ALWAYS_INLINE static inline void osal_task_delay(uint32_t msec) { + rt_thread_mdelay(msec); +} + +TU_ATTR_ALWAYS_INLINE static inline uint32_t osal_time_millis(void) { + return (uint32_t)((((uint64_t)rt_tick_get()) * 1000) / RT_TICK_PER_SECOND); +} + +//--------------------------------------------------------------------+ +// Spinlock API +//--------------------------------------------------------------------+ +typedef struct rt_spinlock osal_spinlock_t; + +#define OSAL_SPINLOCK_DEF(_name, _int_set) \ + osal_spinlock_t _name + +TU_ATTR_ALWAYS_INLINE static inline void osal_spin_init(osal_spinlock_t *ctx) { + rt_spin_lock_init(ctx); +} + +TU_ATTR_ALWAYS_INLINE static inline void osal_spin_lock(osal_spinlock_t *ctx, bool in_isr) { + if (!TUP_MCU_MULTIPLE_CORE && in_isr) { + return; // single core MCU does not need to lock in ISR + } + rt_spin_lock(ctx); +} + +TU_ATTR_ALWAYS_INLINE static inline void osal_spin_unlock(osal_spinlock_t *ctx, bool in_isr) { + if (!TUP_MCU_MULTIPLE_CORE && in_isr) { + return; // single core MCU does not need to lock in ISR + } + rt_spin_unlock(ctx); +} + +//--------------------------------------------------------------------+ +// Semaphore API +//--------------------------------------------------------------------+ +typedef struct rt_semaphore osal_semaphore_def_t; +typedef rt_sem_t osal_semaphore_t; + +TU_ATTR_ALWAYS_INLINE static inline +osal_semaphore_t osal_semaphore_create(osal_semaphore_def_t *semdef) { + rt_sem_init(semdef, "tusb", 0, RT_IPC_FLAG_PRIO); + return semdef; +} + +TU_ATTR_ALWAYS_INLINE static inline bool osal_semaphore_delete(osal_semaphore_t semd_hdl) { + return RT_EOK == rt_sem_detach(semd_hdl); +} + +TU_ATTR_ALWAYS_INLINE static inline bool osal_semaphore_post(osal_semaphore_t sem_hdl, bool in_isr) { + (void) in_isr; + return rt_sem_release(sem_hdl) == RT_EOK; +} + +TU_ATTR_ALWAYS_INLINE static inline bool osal_semaphore_wait(osal_semaphore_t sem_hdl, uint32_t msec) { + return rt_sem_take(sem_hdl, rt_tick_from_millisecond((rt_int32_t) msec)) == RT_EOK; +} + +TU_ATTR_ALWAYS_INLINE static inline void osal_semaphore_reset(osal_semaphore_t const sem_hdl) { + rt_sem_control(sem_hdl, RT_IPC_CMD_RESET, 0); +} + +//--------------------------------------------------------------------+ +// MUTEX API (priority inheritance) +//--------------------------------------------------------------------+ +typedef struct rt_mutex osal_mutex_def_t; +typedef rt_mutex_t osal_mutex_t; + +TU_ATTR_ALWAYS_INLINE static inline osal_mutex_t osal_mutex_create(osal_mutex_def_t *mdef) { + rt_mutex_init(mdef, "tusb", RT_IPC_FLAG_PRIO); + return mdef; +} + +TU_ATTR_ALWAYS_INLINE static inline bool osal_mutex_delete(osal_mutex_t mutex_hdl) { + return RT_EOK == rt_mutex_detach(mutex_hdl); +} + +TU_ATTR_ALWAYS_INLINE static inline bool osal_mutex_lock(osal_mutex_t mutex_hdl, uint32_t msec) { + return rt_mutex_take(mutex_hdl, rt_tick_from_millisecond((rt_int32_t) msec)) == RT_EOK; +} + +TU_ATTR_ALWAYS_INLINE static inline bool osal_mutex_unlock(osal_mutex_t mutex_hdl) { + return rt_mutex_release(mutex_hdl) == RT_EOK; +} + +//--------------------------------------------------------------------+ +// QUEUE API +//--------------------------------------------------------------------+ + +// role device/host is used by OS NONE for mutex (disable usb isr) only +#define OSAL_QUEUE_DEF(_int_set, _name, _depth, _type) \ + static _type _name##_##buf[_depth]; \ + osal_queue_def_t _name = { .depth = _depth, .item_sz = sizeof(_type), .buf = _name##_##buf }; + +typedef struct { + uint16_t depth; + uint16_t item_sz; + void *buf; + + struct rt_messagequeue sq; +} osal_queue_def_t; + +typedef rt_mq_t osal_queue_t; + +TU_ATTR_ALWAYS_INLINE static inline osal_queue_t osal_queue_create(osal_queue_def_t *qdef) { + rt_mq_init(&(qdef->sq), "tusb", qdef->buf, qdef->item_sz, + qdef->item_sz * qdef->depth, RT_IPC_FLAG_PRIO); + return &(qdef->sq); +} + +TU_ATTR_ALWAYS_INLINE static inline bool osal_queue_delete(osal_queue_t qhdl) { + return RT_EOK == rt_mq_detach(qhdl); +} + +TU_ATTR_ALWAYS_INLINE static inline bool osal_queue_receive(osal_queue_t qhdl, void *data, uint32_t msec) { + rt_tick_t tick = rt_tick_from_millisecond((rt_int32_t) msec); +#if RT_VERSION_MAJOR >= 5 + return rt_mq_recv(qhdl, data, qhdl->msg_size, tick) > 0; +#else + return rt_mq_recv(qhdl, data, qhdl->msg_size, tick) == RT_EOK; +#endif /* RT_VERSION_MAJOR >= 5 */ +} + +TU_ATTR_ALWAYS_INLINE static inline bool osal_queue_send(osal_queue_t qhdl, void const *data, bool in_isr) { + (void) in_isr; + return rt_mq_send(qhdl, (void *)data, qhdl->msg_size) == RT_EOK; +} + +TU_ATTR_ALWAYS_INLINE static inline bool osal_queue_empty(osal_queue_t qhdl) { + return (qhdl->entry) == 0; +} + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/libraries/Adafruit_TinyUSB_Arduino/src/osal/osal_rtx4.h b/libraries/Adafruit_TinyUSB_Arduino/src/osal/osal_rtx4.h new file mode 100644 index 0000000..e1930c9 --- /dev/null +++ b/libraries/Adafruit_TinyUSB_Arduino/src/osal/osal_rtx4.h @@ -0,0 +1,196 @@ +/* + * The MIT License (MIT) + * + * Copyright (c) 2021 Tian Yunhao (t123yh) + * 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. + * + * This file is part of the TinyUSB stack. + */ + +#ifndef TUSB_OSAL_RTX4_H_ +#define TUSB_OSAL_RTX4_H_ + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +//--------------------------------------------------------------------+ +// TASK API +//--------------------------------------------------------------------+ +TU_ATTR_ALWAYS_INLINE static inline void osal_task_delay(uint32_t msec) { + uint16_t hi = msec >> 16; + uint16_t lo = msec; + while (hi--) { + os_dly_wait(0xFFFE); + } + os_dly_wait(lo); +} + +TU_ATTR_ALWAYS_INLINE static inline uint32_t osal_time_millis(void) { + return os_time_get(); +} + +TU_ATTR_ALWAYS_INLINE static inline uint16_t msec2wait(uint32_t msec) { + if (msec == OSAL_TIMEOUT_WAIT_FOREVER) { + return 0xFFFF; + } else if (msec >= 0xFFFE) { + return 0xFFFE; + } else { + return msec; + } +} + +//--------------------------------------------------------------------+ +// Spinlock API, stub not implemented +//--------------------------------------------------------------------+ +typedef uint8_t osal_spinlock_t; +#define OSAL_SPINLOCK_DEF(_name, _int_set) \ + osal_spinlock_t _name + +TU_ATTR_ALWAYS_INLINE static inline void osal_spin_init(osal_spinlock_t *ctx) { + (void) ctx; +} + +TU_ATTR_ALWAYS_INLINE static inline void osal_spin_lock(osal_spinlock_t *ctx, bool in_isr) { + (void) ctx; (void) in_isr; +} + +TU_ATTR_ALWAYS_INLINE static inline void osal_spin_unlock(osal_spinlock_t *ctx, bool in_isr) { + (void) ctx; (void) in_isr; +} + +//--------------------------------------------------------------------+ +// Semaphore API +//--------------------------------------------------------------------+ +typedef OS_SEM osal_semaphore_def_t; +typedef OS_ID osal_semaphore_t; + +TU_ATTR_ALWAYS_INLINE static inline OS_ID osal_semaphore_create(osal_semaphore_def_t* semdef) { + os_sem_init(semdef, 0); + return semdef; +} + +TU_ATTR_ALWAYS_INLINE static inline bool osal_semaphore_delete(osal_semaphore_t semd_hdl) { + (void) semd_hdl; + return true; // nothing to do +} + +TU_ATTR_ALWAYS_INLINE static inline bool osal_semaphore_post(osal_semaphore_t sem_hdl, bool in_isr) { + if ( !in_isr ) { + os_sem_send(sem_hdl); + } else { + isr_sem_send(sem_hdl); + } + return true; +} + +TU_ATTR_ALWAYS_INLINE static inline bool osal_semaphore_wait (osal_semaphore_t sem_hdl, uint32_t msec) { + return os_sem_wait(sem_hdl, msec2wait(msec)) != OS_R_TMO; +} + +TU_ATTR_ALWAYS_INLINE static inline void osal_semaphore_reset(osal_semaphore_t const sem_hdl) { + // TODO: implement +} + +//--------------------------------------------------------------------+ +// MUTEX API (priority inheritance) +//--------------------------------------------------------------------+ +typedef OS_MUT osal_mutex_def_t; +typedef OS_ID osal_mutex_t; + +TU_ATTR_ALWAYS_INLINE static inline osal_mutex_t osal_mutex_create(osal_mutex_def_t* mdef) { + os_mut_init(mdef); + return mdef; +} + +TU_ATTR_ALWAYS_INLINE static inline bool osal_mutex_delete(osal_mutex_t mutex_hdl) { + (void) mutex_hdl; + return true; // nothing to do +} + +TU_ATTR_ALWAYS_INLINE static inline bool osal_mutex_lock (osal_mutex_t mutex_hdl, uint32_t msec) { + return os_mut_wait(mutex_hdl, msec2wait(msec)) != OS_R_TMO; +} + +TU_ATTR_ALWAYS_INLINE static inline bool osal_mutex_unlock(osal_mutex_t mutex_hdl) { + return os_mut_release(mutex_hdl) == OS_R_OK; +} + +//--------------------------------------------------------------------+ +// QUEUE API +//--------------------------------------------------------------------+ + +// role device/host is used by OS NONE for mutex (disable usb isr) only +#define OSAL_QUEUE_DEF(_int_set, _name, _depth, _type) \ + os_mbx_declare(_name##__mbox, _depth); \ + _declare_box(_name##__pool, sizeof(_type), _depth); \ + osal_queue_def_t _name = { .depth = _depth, .item_sz = sizeof(_type), .pool = _name##__pool, .mbox = _name##__mbox }; + +typedef struct { + uint16_t depth; + uint16_t item_sz; + U32* pool; + U32* mbox; +}osal_queue_def_t; + +typedef osal_queue_def_t* osal_queue_t; + +TU_ATTR_ALWAYS_INLINE static inline osal_queue_t osal_queue_create(osal_queue_def_t* qdef) { + os_mbx_init(qdef->mbox, (qdef->depth + 4) * 4); + _init_box(qdef->pool, ((qdef->item_sz+3)/4)*(qdef->depth) + 3, qdef->item_sz); + return qdef; +} + +TU_ATTR_ALWAYS_INLINE static inline bool osal_queue_receive(osal_queue_t qhdl, void* data, uint32_t msec) { + void* buf; + os_mbx_wait(qhdl->mbox, &buf, msec2wait(msec)); + memcpy(data, buf, qhdl->item_sz); + _free_box(qhdl->pool, buf); + return true; +} + +TU_ATTR_ALWAYS_INLINE static inline bool osal_queue_delete(osal_queue_t qhdl) { + (void) qhdl; + return true; // nothing to do ? +} + +TU_ATTR_ALWAYS_INLINE static inline bool osal_queue_send(osal_queue_t qhdl, void const * data, bool in_isr) { + void* buf = _alloc_box(qhdl->pool); + memcpy(buf, data, qhdl->item_sz); + if ( !in_isr ) { + os_mbx_send(qhdl->mbox, buf, 0xFFFF); + } else { + isr_mbx_send(qhdl->mbox, buf); + } + return true; +} + +TU_ATTR_ALWAYS_INLINE static inline bool osal_queue_empty(osal_queue_t qhdl) { + return os_mbx_check(qhdl->mbox) == qhdl->depth; +} + +#ifdef __cplusplus + } +#endif + +#endif diff --git a/libraries/Adafruit_TinyUSB_Arduino/src/osal/osal_zephyr.h b/libraries/Adafruit_TinyUSB_Arduino/src/osal/osal_zephyr.h new file mode 100644 index 0000000..900ac78 --- /dev/null +++ b/libraries/Adafruit_TinyUSB_Arduino/src/osal/osal_zephyr.h @@ -0,0 +1,156 @@ +/* + * The MIT License (MIT) + * + * Copyright (c) 2025 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. + * + * This file is part of the TinyUSB stack. + */ +#ifndef TUSB_OSAL_ZEPHYR_H +#define TUSB_OSAL_ZEPHYR_H + +#include + +//--------------------------------------------------------------------+ +// TASK API +//--------------------------------------------------------------------+ +TU_ATTR_ALWAYS_INLINE static inline void osal_task_delay(uint32_t msec) { + k_msleep(msec); +} + +TU_ATTR_ALWAYS_INLINE static inline uint32_t osal_time_millis(void) { + return k_uptime_get_32(); +} + +//--------------------------------------------------------------------+ +// Spinlock API +//--------------------------------------------------------------------+ +typedef struct { + struct k_spinlock lock; + k_spinlock_key_t key; +} osal_spinlock_t; + +#define OSAL_SPINLOCK_DEF(_name, _int_set) \ + osal_spinlock_t _name + +TU_ATTR_ALWAYS_INLINE static inline void osal_spin_init(osal_spinlock_t *ctx) { + (void) ctx; +} + +TU_ATTR_ALWAYS_INLINE static inline void osal_spin_lock(osal_spinlock_t *ctx, bool in_isr) { + if (!TUP_MCU_MULTIPLE_CORE && in_isr) { + return; // single core MCU does not need to lock in ISR + } + ctx->key = k_spin_lock(&ctx->lock); +} + +TU_ATTR_ALWAYS_INLINE static inline void osal_spin_unlock(osal_spinlock_t *ctx, bool in_isr) { + if (!TUP_MCU_MULTIPLE_CORE && in_isr) { + return; // single core MCU does not need to lock in ISR + } + k_spin_unlock(&ctx->lock, ctx->key); +} + +//--------------------------------------------------------------------+ +// Binary Semaphore API +//--------------------------------------------------------------------+ +typedef struct k_sem osal_semaphore_def_t, * osal_semaphore_t; + +TU_ATTR_ALWAYS_INLINE static inline osal_semaphore_t osal_semaphore_create(osal_semaphore_def_t* semdef) { + k_sem_init(semdef, 0, 255); + return semdef; +} + +TU_ATTR_ALWAYS_INLINE static inline bool osal_semaphore_delete(osal_semaphore_t semd_hdl) { + (void) semd_hdl; + return true; // nothing to do +} + +TU_ATTR_ALWAYS_INLINE static inline bool osal_semaphore_post(osal_semaphore_t sem_hdl, bool in_isr) { + (void) in_isr; + k_sem_give(sem_hdl); + return true; +} + +TU_ATTR_ALWAYS_INLINE static inline bool osal_semaphore_wait(osal_semaphore_t sem_hdl, uint32_t msec) { + return 0 == k_sem_take(sem_hdl, K_MSEC(msec)); +} + +TU_ATTR_ALWAYS_INLINE static inline void osal_semaphore_reset(osal_semaphore_t sem_hdl) { + k_sem_reset(sem_hdl); +} + +//--------------------------------------------------------------------+ +// MUTEX API +//--------------------------------------------------------------------+ +typedef struct k_mutex osal_mutex_def_t, *osal_mutex_t; + +TU_ATTR_ALWAYS_INLINE static inline osal_mutex_t osal_mutex_create(osal_mutex_def_t* mdef) { + if ( 0 == k_mutex_init(mdef) ) { + return mdef; + } else { + return NULL; + } +} + +TU_ATTR_ALWAYS_INLINE static inline bool osal_mutex_delete(osal_mutex_t mutex_hdl) { + (void) mutex_hdl; + return true; // nothing to do +} + +TU_ATTR_ALWAYS_INLINE static inline bool osal_mutex_lock(osal_mutex_t mutex_hdl, uint32_t msec) { + return 0 == k_mutex_lock(mutex_hdl, K_MSEC(msec)); +} + +TU_ATTR_ALWAYS_INLINE static inline bool osal_mutex_unlock(osal_mutex_t mutex_hdl) { + return 0 == k_mutex_unlock(mutex_hdl); +} + +//--------------------------------------------------------------------+ +// QUEUE API +//--------------------------------------------------------------------+ +typedef struct k_msgq osal_queue_def_t, * osal_queue_t; + +// role device/host is used by OS NONE for mutex (disable usb isr) only +#define OSAL_QUEUE_DEF(_int_set, _name, _depth, _type) K_MSGQ_DEFINE(_name, sizeof(_type), _depth, 4) + +TU_ATTR_ALWAYS_INLINE static inline osal_queue_t osal_queue_create(osal_queue_def_t* qdef) { + // K_MSGQ_DEFINE already initializes the queue + return (osal_queue_t) qdef; +} + +TU_ATTR_ALWAYS_INLINE static inline bool osal_queue_delete(osal_queue_t qhdl) { + (void) qhdl; + return true; +} + +TU_ATTR_ALWAYS_INLINE static inline bool osal_queue_receive(osal_queue_t qhdl, void* data, uint32_t msec) { + return 0 == k_msgq_get(qhdl, data, K_MSEC(msec)); +} + +TU_ATTR_ALWAYS_INLINE static inline bool osal_queue_send(osal_queue_t qhdl, void const* data, bool in_isr) { + return 0 == k_msgq_put(qhdl, data, in_isr ? K_NO_WAIT : K_FOREVER); +} + +TU_ATTR_ALWAYS_INLINE static inline bool osal_queue_empty(osal_queue_t qhdl) { + return 0 == k_msgq_num_used_get(qhdl); +} + +#endif diff --git a/libraries/Adafruit_TinyUSB_Arduino/src/portable/analog/max3421/hcd_max3421.c b/libraries/Adafruit_TinyUSB_Arduino/src/portable/analog/max3421/hcd_max3421.c new file mode 100644 index 0000000..971dbd6 --- /dev/null +++ b/libraries/Adafruit_TinyUSB_Arduino/src/portable/analog/max3421/hcd_max3421.c @@ -0,0 +1,1107 @@ +/* + * The MIT License (MIT) + * + * Copyright (c) 2023 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. + * + * This file is part of the TinyUSB stack. + */ + +#include "tusb_option.h" + +#if CFG_TUH_ENABLED && defined(CFG_TUH_MAX3421) && CFG_TUH_MAX3421 + +#include "host/hcd.h" +#include "host/usbh.h" +#include "host/usbh_pvt.h" + +//--------------------------------------------------------------------+ +// +//--------------------------------------------------------------------+ + +// Command format is +// Reg [7:3] | 0 [2] | Dir [1] | Ack [0] + +enum { + CMDBYTE_WRITE = 0x02, +}; + +enum { + RCVVFIFO_ADDR = 1u << 3, // 0x08 + SNDFIFO_ADDR = 2u << 3, // 0x10 + SUDFIFO_ADDR = 4u << 3, // 0x20 + RCVBC_ADDR = 6u << 3, // 0x30 + SNDBC_ADDR = 7u << 3, // 0x38 + USBIRQ_ADDR = 13u << 3, // 0x68 + USBIEN_ADDR = 14u << 3, // 0x70 + USBCTL_ADDR = 15u << 3, // 0x78 + CPUCTL_ADDR = 16u << 3, // 0x80 + PINCTL_ADDR = 17u << 3, // 0x88 + REVISION_ADDR = 18u << 3, // 0x90 + // 19 is not used + IOPINS1_ADDR = 20u << 3, // 0xA0 + IOPINS2_ADDR = 21u << 3, // 0xA8 + GPINIRQ_ADDR = 22u << 3, // 0xB0 + GPINIEN_ADDR = 23u << 3, // 0xB8 + GPINPOL_ADDR = 24u << 3, // 0xC0 + HIRQ_ADDR = 25u << 3, // 0xC8 + HIEN_ADDR = 26u << 3, // 0xD0 + MODE_ADDR = 27u << 3, // 0xD8 + PERADDR_ADDR = 28u << 3, // 0xE0 + HCTL_ADDR = 29u << 3, // 0xE8 + HXFR_ADDR = 30u << 3, // 0xF0 + HRSL_ADDR = 31u << 3, // 0xF8 +}; + +enum { + USBIRQ_OSCOK_IRQ = 1u << 0, + USBIRQ_NOVBUS_IRQ = 1u << 5, + USBIRQ_VBUS_IRQ = 1u << 6, +}; + +enum { + USBCTL_PWRDOWN = 1u << 4, + USBCTL_CHIPRES = 1u << 5, +}; + +enum { + CPUCTL_IE = 1u << 0, + CPUCTL_PULSEWID0 = 1u << 6, + CPUCTL_PULSEWID1 = 1u << 7, +}; + +enum { + PINCTL_GPXA = 1u << 0, + PINCTL_GPXB = 1u << 1, + PINCTL_POSINT = 1u << 2, + PINCTL_INTLEVEL = 1u << 3, + PINCTL_FDUPSPI = 1u << 4, +}; + +enum { + HIRQ_BUSEVENT_IRQ = 1u << 0, + HIRQ_RWU_IRQ = 1u << 1, + HIRQ_RCVDAV_IRQ = 1u << 2, + HIRQ_SNDBAV_IRQ = 1u << 3, + HIRQ_SUSDN_IRQ = 1u << 4, + HIRQ_CONDET_IRQ = 1u << 5, + HIRQ_FRAME_IRQ = 1u << 6, + HIRQ_HXFRDN_IRQ = 1u << 7, +}; + +enum { + MODE_HOST = 1u << 0, + MODE_LOWSPEED = 1u << 1, + MODE_HUBPRE = 1u << 2, + MODE_SOFKAENAB = 1u << 3, + MODE_SEPIRQ = 1u << 4, + MODE_DELAYISO = 1u << 5, + MODE_DMPULLDN = 1u << 6, + MODE_DPPULLDN = 1u << 7, +}; + +enum { + HCTL_BUSRST = 1u << 0, + HCTL_FRMRST = 1u << 1, + HCTL_SAMPLEBUS = 1u << 2, + HCTL_SIGRSM = 1u << 3, + HCTL_RCVTOG0 = 1u << 4, + HCTL_RCVTOG1 = 1u << 5, + HCTL_SNDTOG0 = 1u << 6, + HCTL_SNDTOG1 = 1u << 7, +}; + +enum { + HXFR_EPNUM_MASK = 0x0f, + HXFR_SETUP = 1u << 4, + HXFR_OUT_NIN = 1u << 5, + HXFR_ISO = 1u << 6, + HXFR_HS = 1u << 7, +}; + +enum { + HRSL_RESULT_MASK = 0x0f, + HRSL_RCVTOGRD = 1u << 4, + HRSL_SNDTOGRD = 1u << 5, + HRSL_KSTATUS = 1u << 6, + HRSL_JSTATUS = 1u << 7, +}; + +enum { + HRSL_SUCCESS = 0, + HRSL_BUSY, + HRSL_BAD_REQ, + HRSL_UNDEF, + HRSL_NAK, + HRSL_STALL, + HRSL_TOG_ERR, + HRSL_WRONG_PID, + HRSL_BAD_BYTECOUNT, + HRSL_PID_ERR, + HRSL_PKT_ERR, + HRSL_CRC_ERR, + HRSL_K_ERR, + HRSL_J_ERR, + HRSL_TIMEOUT, + HRSL_BABBLE, +}; + +enum { + DEFAULT_HIEN = HIRQ_CONDET_IRQ | HIRQ_FRAME_IRQ | HIRQ_HXFRDN_IRQ | HIRQ_RCVDAV_IRQ +}; + +enum { + MAX_NAK_DEFAULT = 1 // Number of NAK per endpoint per usb frame to save CPU/SPI bus usage +}; + +enum { + EP_STATE_IDLE = 0, + EP_STATE_COMPLETE = 1, + EP_STATE_ABORTING = 2, + EP_STATE_ATTEMPT_1 = 3, // Number of attempts to transfer in a frame. Incremented after each NAK + EP_STATE_ATTEMPT_MAX = 15 +}; + +//--------------------------------------------------------------------+ +// +//--------------------------------------------------------------------+ + +typedef struct TU_ATTR_PACKED { + uint8_t ep_num : 4; + uint8_t is_setup : 1; + uint8_t is_out : 1; + uint8_t is_iso : 1; +} hxfr_bm_t; + +TU_VERIFY_STATIC(sizeof(hxfr_bm_t) == 1, "size is not correct"); + +typedef struct { + uint8_t daddr; + + union { + hxfr_bm_t hxfr_bm; + uint8_t hxfr; + }; + + struct TU_ATTR_PACKED { + uint8_t state : 4; + uint8_t data_toggle : 1; + uint16_t packet_size : 11; + }; + + uint16_t total_len; + uint16_t xferred_len; + uint8_t* buf; +} max3421_ep_t; + +TU_VERIFY_STATIC(sizeof(max3421_ep_t) == 12, "size is not correct"); + +typedef struct { + volatile uint16_t frame_count; + + // cached register + uint8_t sndbc; + uint8_t hirq; + uint8_t hien; + uint8_t mode; + uint8_t peraddr; + union { + hxfr_bm_t hxfr_bm; + uint8_t hxfr; + }; + + // owner of data in SNDFIFO, for retrying NAKed without re-writing to FIFO + struct { + uint8_t daddr; + uint8_t hxfr; + }sndfifo_owner; + + bool busy_lock; // busy transferring + +#if OSAL_MUTEX_REQUIRED + OSAL_MUTEX_DEF(spi_mutexdef); + osal_mutex_t spi_mutex; +#endif + + max3421_ep_t ep[CFG_TUH_MAX3421_ENDPOINT_TOTAL]; // [0] is reserved for addr0 +} max3421_data_t; + +static max3421_data_t _hcd_data; + +// max NAK before giving up in a frame. 0 means infinite NAKs +static tuh_configure_max3421_t _tuh_cfg = { + .max_nak = MAX_NAK_DEFAULT, + .cpuctl = 0, // default: INT pulse width = 10.6 us + .pinctl = 0, // default: negative edge interrupt +}; + +//--------------------------------------------------------------------+ +// SPI Commands and Helper +//--------------------------------------------------------------------+ + +#define reg_read tuh_max3421_reg_read +#define reg_write tuh_max3421_reg_write + +static void max3421_spi_lock(uint8_t rhport, bool in_isr) { + // disable interrupt and mutex lock (for pre-emptive RTOS) if not in_isr + if (!in_isr) { + (void) osal_mutex_lock(_hcd_data.spi_mutex, OSAL_TIMEOUT_WAIT_FOREVER); + tuh_max3421_int_api(rhport, false); + } + + // assert CS + tuh_max3421_spi_cs_api(rhport, true); +} + +static void max3421_spi_unlock(uint8_t rhport, bool in_isr) { + // de-assert CS + tuh_max3421_spi_cs_api(rhport, false); + + // mutex unlock and re-enable interrupt + if (!in_isr) { + tuh_max3421_int_api(rhport, true); + (void) osal_mutex_unlock(_hcd_data.spi_mutex); + } +} + +uint8_t tuh_max3421_reg_read(uint8_t rhport, uint8_t reg, bool in_isr) { + uint8_t tx_buf[2] = {reg, 0}; + uint8_t rx_buf[2] = {0, 0}; + + max3421_spi_lock(rhport, in_isr); + bool ret = tuh_max3421_spi_xfer_api(rhport, tx_buf, rx_buf, 2); + max3421_spi_unlock(rhport, in_isr); + + _hcd_data.hirq = rx_buf[0]; + return ret ? rx_buf[1] : 0; +} + +bool tuh_max3421_reg_write(uint8_t rhport, uint8_t reg, uint8_t data, bool in_isr) { + uint8_t tx_buf[2] = {reg | CMDBYTE_WRITE, data}; + uint8_t rx_buf[2] = {0, 0}; + + max3421_spi_lock(rhport, in_isr); + bool ret = tuh_max3421_spi_xfer_api(rhport, tx_buf, rx_buf, 2); + max3421_spi_unlock(rhport, in_isr); + + // HIRQ register since we are in full-duplex mode + _hcd_data.hirq = rx_buf[0]; + + return ret; +} + +//-------------------------------------------------------------------- +// Register helper +//-------------------------------------------------------------------- +TU_ATTR_ALWAYS_INLINE static inline void hirq_write(uint8_t rhport, uint8_t data, bool in_isr) { + reg_write(rhport, HIRQ_ADDR, data, in_isr); + // HIRQ write 1 is clear + _hcd_data.hirq &= (uint8_t) ~data; +} + +TU_ATTR_ALWAYS_INLINE static inline void hien_write(uint8_t rhport, uint8_t data, bool in_isr) { + _hcd_data.hien = data; + reg_write(rhport, HIEN_ADDR, data, in_isr); +} + +TU_ATTR_ALWAYS_INLINE static inline void mode_write(uint8_t rhport, uint8_t data, bool in_isr) { + _hcd_data.mode = data; + reg_write(rhport, MODE_ADDR, data, in_isr); +} + +TU_ATTR_ALWAYS_INLINE static inline void peraddr_write(uint8_t rhport, uint8_t data, bool in_isr) { + if (_hcd_data.peraddr == data) { + return; // no need to change address + } + + _hcd_data.peraddr = data; + reg_write(rhport, PERADDR_ADDR, data, in_isr); +} + +TU_ATTR_ALWAYS_INLINE static inline void hxfr_write(uint8_t rhport, uint8_t data, bool in_isr) { + _hcd_data.hxfr = data; + reg_write(rhport, HXFR_ADDR, data, in_isr); +} + +TU_ATTR_ALWAYS_INLINE static inline void sndbc_write(uint8_t rhport, uint8_t data, bool in_isr) { + _hcd_data.sndbc = data; + reg_write(rhport, SNDBC_ADDR, data, in_isr); +} + +//-------------------------------------------------------------------- +// FIFO access (receive, send, setup) +//-------------------------------------------------------------------- +static void hwfifo_write(uint8_t rhport, uint8_t reg, const uint8_t* buffer, uint8_t len, bool in_isr) { + uint8_t hirq; + reg |= CMDBYTE_WRITE; + + max3421_spi_lock(rhport, in_isr); + + tuh_max3421_spi_xfer_api(rhport, ®, &hirq, 1); + _hcd_data.hirq = hirq; + tuh_max3421_spi_xfer_api(rhport, buffer, NULL, len); + + max3421_spi_unlock(rhport, in_isr); +} + +// Write to SNDFIFO if len > 0 and update SNDBC +TU_ATTR_ALWAYS_INLINE static inline void hwfifo_send(uint8_t rhport, const uint8_t* buffer, uint8_t len, bool in_isr) { + if (len) { + hwfifo_write(rhport, SNDFIFO_ADDR, buffer, len, in_isr); + } + sndbc_write(rhport, len, in_isr); +} + +TU_ATTR_ALWAYS_INLINE static inline void hwfifo_setup(uint8_t rhport, const uint8_t* buffer, bool in_isr) { + hwfifo_write(rhport, SUDFIFO_ADDR, buffer, 8, in_isr); +} + +static void hwfifo_receive(uint8_t rhport, uint8_t * buffer, uint16_t len, bool in_isr) { + uint8_t hirq; + const uint8_t reg = RCVVFIFO_ADDR; + + max3421_spi_lock(rhport, in_isr); + + tuh_max3421_spi_xfer_api(rhport, ®, &hirq, 1); + _hcd_data.hirq = hirq; + tuh_max3421_spi_xfer_api(rhport, NULL, buffer, len); + + max3421_spi_unlock(rhport, in_isr); +} + +//--------------------------------------------------------------------+ +// Endpoint helper +//--------------------------------------------------------------------+ + +static max3421_ep_t* find_ep_not_addr0(uint8_t daddr, uint8_t ep_num, uint8_t ep_dir) { + const uint8_t is_out = 1-ep_dir; + for(size_t i=1; idaddr && ep_num == ep->hxfr_bm.ep_num && (ep_num == 0 || is_out == ep->hxfr_bm.is_out)) { + return ep; + } + } + + return NULL; +} + +// daddr = 0 and ep_num = 0 means find a free (allocate) endpoint +TU_ATTR_ALWAYS_INLINE static inline max3421_ep_t * allocate_ep(void) { + return find_ep_not_addr0(0, 0, 0); +} + +TU_ATTR_ALWAYS_INLINE static inline max3421_ep_t * find_opened_ep(uint8_t daddr, uint8_t ep_num, uint8_t ep_dir) { + if (daddr == 0 && ep_num == 0) { + return &_hcd_data.ep[0]; + }else{ + return find_ep_not_addr0(daddr, ep_num, ep_dir); + } +} + +// free all endpoints belong to device address +static void free_ep(uint8_t daddr) { + for (size_t i=1; idaddr == daddr) { + tu_memclr(ep, sizeof(max3421_ep_t)); + } + } +} + +// Check if endpoint has a queued transfer and not reach max NAK in this frame +TU_ATTR_ALWAYS_INLINE static inline bool is_ep_pending(max3421_ep_t const * ep) { + uint8_t const state = ep->state; + return ep->packet_size && (state >= EP_STATE_ATTEMPT_1) && + (_tuh_cfg.max_nak == 0 || state < EP_STATE_ATTEMPT_1 + _tuh_cfg.max_nak); +} + +// Find the next pending endpoint using round-robin scheduling, starting from next endpoint. +// return NULL if not found +// TODO respect interrupt endpoint's interval +static max3421_ep_t * find_next_pending_ep(max3421_ep_t * cur_ep) { + size_t const idx = (size_t) (cur_ep - _hcd_data.ep); + + // starting from next endpoint + for (size_t i = idx + 1; i < CFG_TUH_MAX3421_ENDPOINT_TOTAL; i++) { + max3421_ep_t* ep = &_hcd_data.ep[i]; + if (is_ep_pending(ep)) { + return ep; + } + } + + // wrap around including current endpoint + for (size_t i = 0; i <= idx; i++) { + max3421_ep_t* ep = &_hcd_data.ep[i]; + if (is_ep_pending(ep)) { + return ep; + } + } + + return NULL; +} + +//--------------------------------------------------------------------+ +// Controller API +//--------------------------------------------------------------------+ + +// optional hcd configuration, called by tuh_configure() +bool hcd_configure(uint8_t rhport, uint32_t cfg_id, const void* cfg_param) { + (void) rhport; + TU_VERIFY(cfg_id == TUH_CFGID_MAX3421 && cfg_param != NULL); + + tuh_configure_param_t const* cfg = (tuh_configure_param_t const*) cfg_param; + _tuh_cfg = cfg->max3421; + _tuh_cfg.max_nak = tu_min8(_tuh_cfg.max_nak, EP_STATE_ATTEMPT_MAX-EP_STATE_ATTEMPT_1); + return true; +} + +// Initialize controller to host mode +bool hcd_init(uint8_t rhport, const tusb_rhport_init_t* rh_init) { + (void) rh_init; + + tuh_max3421_int_api(rhport, false); + + TU_LOG2_INT(sizeof(max3421_ep_t)); + TU_LOG2_INT(sizeof(max3421_data_t)); + TU_LOG2_INT(offsetof(max3421_data_t, ep)); + + tu_memclr(&_hcd_data, sizeof(_hcd_data)); + _hcd_data.peraddr = 0xff; // invalid + +#if OSAL_MUTEX_REQUIRED + _hcd_data.spi_mutex = osal_mutex_create(&_hcd_data.spi_mutexdef); +#endif + + // NOTE: driver does not seem to work without nRST pin signal + + // full duplex, interrupt negative edge + reg_write(rhport, PINCTL_ADDR, _tuh_cfg.pinctl | PINCTL_FDUPSPI, false); + + // v1 is 0x01, v2 is 0x12, v3 is 0x13 + // Note: v1 and v2 has host OUT errata whose workaround is not implemented in this driver + uint8_t const revision = reg_read(rhport, REVISION_ADDR, false); + TU_LOG2_HEX(revision); + TU_ASSERT(revision == 0x01 || revision == 0x12 || revision == 0x13, false); + + // reset + reg_write(rhport, USBCTL_ADDR, USBCTL_CHIPRES, false); + reg_write(rhport, USBCTL_ADDR, 0, false); + while( !(reg_read(rhport, USBIRQ_ADDR, false) & USBIRQ_OSCOK_IRQ) ) { + // wait for oscillator to stabilize + } + + // Mode: Host and DP/DM pull down + mode_write(rhport, MODE_DPPULLDN | MODE_DMPULLDN | MODE_HOST, false); + + // frame reset & bus reset, this will trigger CONDET IRQ if device is already connected + reg_write(rhport, HCTL_ADDR, HCTL_BUSRST | HCTL_FRMRST, false); + + // clear all previously pending IRQ + hirq_write(rhport, 0xff, false); + + // Enable IRQ + hien_write(rhport, DEFAULT_HIEN, false); + + tuh_max3421_int_api(rhport, true); + + // Enable Interrupt pin + reg_write(rhport, CPUCTL_ADDR, _tuh_cfg.cpuctl | CPUCTL_IE, false); + + return true; +} + +bool hcd_deinit(uint8_t rhport) { + (void) rhport; + + // disable interrupt + tuh_max3421_int_api(rhport, false); + + // reset max3421 and power down + reg_write(rhport, USBCTL_ADDR, USBCTL_CHIPRES, false); + reg_write(rhport, USBCTL_ADDR, USBCTL_PWRDOWN, false); + + #if OSAL_MUTEX_REQUIRED + osal_mutex_delete(_hcd_data.spi_mutex); + _hcd_data.spi_mutex = NULL; + #endif + + return true; +} + +// Enable USB interrupt +// Not actually enable GPIO interrupt, just set variable to prevent handler to process +void hcd_int_enable (uint8_t rhport) { + tuh_max3421_int_api(rhport, true); +} + +// Disable USB interrupt +// Not actually disable GPIO interrupt, just set variable to prevent handler to process +void hcd_int_disable(uint8_t rhport) { + tuh_max3421_int_api(rhport, false); +} + +// Get frame number (1ms) +uint32_t hcd_frame_number(uint8_t rhport) { + (void) rhport; + return (uint32_t ) _hcd_data.frame_count; +} + +//--------------------------------------------------------------------+ +// Port API +//--------------------------------------------------------------------+ + +// Get the current connect status of roothub port +bool hcd_port_connect_status(uint8_t rhport) { + (void) rhport; + return (_hcd_data.mode & MODE_SOFKAENAB) ? true : false; +} + +// Reset USB bus on the port. Return immediately, bus reset sequence may not be complete. +// Some port would require hcd_port_reset_end() to be invoked after 10ms to complete the reset sequence. +void hcd_port_reset(uint8_t rhport) { + reg_write(rhport, HCTL_ADDR, HCTL_BUSRST, false); +} + +// Complete bus reset sequence, may be required by some controllers +void hcd_port_reset_end(uint8_t rhport) { + reg_write(rhport, HCTL_ADDR, 0, false); +} + +// Get port link speed +tusb_speed_t hcd_port_speed_get(uint8_t rhport) { + (void) rhport; + return (_hcd_data.mode & MODE_LOWSPEED) ? TUSB_SPEED_LOW : TUSB_SPEED_FULL; +} + +// HCD closes all opened endpoints belong to this device +void hcd_device_close(uint8_t rhport, uint8_t dev_addr) { + (void) rhport; + (void) dev_addr; +} + +//--------------------------------------------------------------------+ +// Endpoints API +//--------------------------------------------------------------------+ + +// Open an endpoint +bool hcd_edpt_open(uint8_t rhport, uint8_t daddr, tusb_desc_endpoint_t const * ep_desc) { + (void) rhport; + + uint8_t const ep_num = tu_edpt_number(ep_desc->bEndpointAddress); + tusb_dir_t const ep_dir = tu_edpt_dir(ep_desc->bEndpointAddress); + + max3421_ep_t * ep; + if (daddr == 0 && ep_num == 0) { + ep = &_hcd_data.ep[0]; + }else { + if (NULL != find_ep_not_addr0(daddr, ep_num, ep_dir)) { + return true; // already opened + } + ep = allocate_ep(); + TU_ASSERT(ep); + ep->daddr = daddr; + ep->hxfr_bm.ep_num = (uint8_t) (ep_num & 0x0f); + ep->hxfr_bm.is_out = (ep_dir == TUSB_DIR_OUT) ? 1 : 0; + ep->hxfr_bm.is_iso = (TUSB_XFER_ISOCHRONOUS == ep_desc->bmAttributes.xfer) ? 1 : 0; + } + + ep->packet_size = (uint16_t) (tu_edpt_packet_size(ep_desc) & 0x7ff); + + return true; +} + +bool hcd_edpt_close(uint8_t rhport, uint8_t daddr, uint8_t ep_addr) { + (void) rhport; + uint8_t const ep_num = tu_edpt_number(ep_addr); + tusb_dir_t const ep_dir = tu_edpt_dir(ep_addr); + max3421_ep_t * ep = find_ep_not_addr0(daddr, ep_num, ep_dir); + + if (!ep) { + return false; // not opened + } + + tu_memclr(ep, sizeof(max3421_ep_t)); + + return true; +} + +/* The microcontroller repeatedly writes the SNDFIFO register R2 to load the FIFO with up to 64 data bytes. + * Then the microcontroller writes the SNDBC register, which this does three things: + * 1. Tells the MAX3421E SIE (Serial Interface Engine) how many bytes in the FIFO to send. + * 2. Connects the SNDFIFO and SNDBC register to the USB logic for USB transmission. + * 3. Clears the SNDBAVIRQ interrupt flag. If the second FIFO is available for µC loading, the SNDBAVIRQ immediately re-asserts. + + +-----------+ + --->| SNDBC-A | + / | SNDFIFO-A | + / +-----------+ + +------+ +-------------+ / +----------+ + | MCU |------>| R2: SNDFIFO |---- << Write R7 Flip >> ---| MAX3241E | + |(hcd) | | R7: SNDBC | / | SIE | + +------+ +-------------+ / +----------+ + +-----------+ / + | SNDBC-B | / + | SNDFIFO-B |<--- + +-----------+ + Note: xact_out() is called when starting a new transfer, continue a transfer (isr) or retry a transfer (NAK) + For NAK retry, we do not need to write to FIFO or SNDBC register again. +*/ +static void xact_out(uint8_t rhport, max3421_ep_t *ep, bool switch_ep, bool in_isr) { + // Page 12: Programming BULK-OUT Transfers + // TODO: double buffering for ISO transfer + if (switch_ep) { + peraddr_write(rhport, ep->daddr, in_isr); + const uint8_t hctl = (ep->data_toggle ? HCTL_SNDTOG1 : HCTL_SNDTOG0); + reg_write(rhport, HCTL_ADDR, hctl, in_isr); + } + + // Only write to sndfifo and sdnbc register if it is not a NAKed retry + if (!(ep->daddr == _hcd_data.sndfifo_owner.daddr && ep->hxfr == _hcd_data.sndfifo_owner.hxfr)) { + // skip SNDBAV IRQ check, overwrite sndfifo if needed + const uint8_t xact_len = (uint8_t) tu_min16(ep->total_len - ep->xferred_len, ep->packet_size); + hwfifo_send(rhport, ep->buf, xact_len, in_isr); + } + _hcd_data.sndfifo_owner.daddr = ep->daddr; + _hcd_data.sndfifo_owner.hxfr = ep->hxfr; + + hxfr_write(rhport, ep->hxfr, in_isr); +} + +static void xact_in(uint8_t rhport, max3421_ep_t *ep, bool switch_ep, bool in_isr) { + // Page 13: Programming BULK-IN Transfers + if (switch_ep) { + peraddr_write(rhport, ep->daddr, in_isr); + + uint8_t const hctl = (ep->data_toggle ? HCTL_RCVTOG1 : HCTL_RCVTOG0); + reg_write(rhport, HCTL_ADDR, hctl, in_isr); + } + + hxfr_write(rhport, ep->hxfr, in_isr); +} + +static void xact_setup(uint8_t rhport, max3421_ep_t *ep, bool in_isr) { + peraddr_write(rhport, ep->daddr, in_isr); + hwfifo_setup(rhport, ep->buf, in_isr); + hxfr_write(rhport, HXFR_SETUP, in_isr); +} + +static void xact_generic(uint8_t rhport, max3421_ep_t *ep, bool switch_ep, bool in_isr) { + if (ep->hxfr_bm.ep_num == 0 ) { + // setup + if (ep->hxfr_bm.is_setup) { + xact_setup(rhport, ep, in_isr); + return; + } + + // status + if (ep->buf == NULL || ep->total_len == 0) { + const uint8_t hxfr = (uint8_t) (HXFR_HS | (ep->hxfr & HXFR_OUT_NIN)); + peraddr_write(rhport, ep->daddr, in_isr); + hxfr_write(rhport, hxfr, in_isr); + return; + } + } + + if (ep->hxfr_bm.is_out) { + xact_out(rhport, ep, switch_ep, in_isr); + }else { + xact_in(rhport, ep, switch_ep, in_isr); + } +} + +// Submit a transfer, when complete hcd_event_xfer_complete() must be invoked +bool hcd_edpt_xfer(uint8_t rhport, uint8_t daddr, uint8_t ep_addr, uint8_t * buffer, uint16_t buflen) { + const uint8_t ep_num = tu_edpt_number(ep_addr); + const uint8_t ep_dir = (uint8_t) tu_edpt_dir(ep_addr); + max3421_ep_t* ep = find_opened_ep(daddr, ep_num, ep_dir); + TU_VERIFY(ep); + + if (ep_num == 0) { + // control transfer can switch direction + ep->hxfr_bm.is_out = ep_dir ? 0 : 1; + ep->hxfr_bm.is_setup = 0; + ep->data_toggle = 1; + } + + ep->buf = buffer; + ep->total_len = buflen; + ep->xferred_len = 0; + ep->state = EP_STATE_ATTEMPT_1; + + bool has_xfer = false; + + usbh_spin_lock(false); + if (!_hcd_data.busy_lock) { + _hcd_data.busy_lock = true; + has_xfer = true; + } + usbh_spin_unlock(false); + + // carry out transfer if not busy + if (has_xfer) { + xact_generic(rhport, ep, true, false); + } + + return true; +} + +bool hcd_edpt_abort_xfer(uint8_t rhport, uint8_t daddr, uint8_t ep_addr) { + uint8_t const ep_num = tu_edpt_number(ep_addr); + uint8_t const ep_dir = (uint8_t) tu_edpt_dir(ep_addr); + max3421_ep_t* ep = find_opened_ep(daddr, ep_num, ep_dir); + TU_VERIFY(ep); + + if (EP_STATE_ATTEMPT_1 <= ep->state && ep->state < EP_STATE_ATTEMPT_MAX) { + hcd_int_disable(rhport); + ep->state = EP_STATE_ABORTING; + hcd_int_enable(rhport); + } + + return true; +} + +// Submit a special transfer to send 8-byte Setup Packet, when complete hcd_event_xfer_complete() must be invoked +bool hcd_setup_send(uint8_t rhport, uint8_t daddr, uint8_t const setup_packet[8]) { + (void) rhport; + + max3421_ep_t* ep = find_opened_ep(daddr, 0, 0); + TU_ASSERT(ep); + + ep->hxfr_bm.is_out = 1; + ep->hxfr_bm.is_setup = 1; + ep->buf = (uint8_t*)(uintptr_t) setup_packet; + ep->total_len = 8; + ep->xferred_len = 0; + ep->state = EP_STATE_ATTEMPT_1; + + bool has_xfer = false; + + usbh_spin_lock(false); + if (!_hcd_data.busy_lock) { + _hcd_data.busy_lock = true; + has_xfer = true; + } + usbh_spin_unlock(false); + + // carry out transfer if not busy + if (has_xfer) { + xact_setup(rhport, ep, false); + } + + return true; +} + +// clear stall, data toggle is also reset to DATA0 +bool hcd_edpt_clear_stall(uint8_t rhport, uint8_t dev_addr, uint8_t ep_addr) { + (void) rhport; + (void) dev_addr; + (void) ep_addr; + + return false; +} + +//--------------------------------------------------------------------+ +// Interrupt Handler +//--------------------------------------------------------------------+ + +static void handle_connect_irq(uint8_t rhport, bool in_isr) { + uint8_t const hrsl = reg_read(rhport, HRSL_ADDR, in_isr); + uint8_t const jk = hrsl & (HRSL_JSTATUS | HRSL_KSTATUS); + + uint8_t new_mode = MODE_DPPULLDN | MODE_DMPULLDN | MODE_HOST; + TU_LOG2_HEX(jk); + + switch(jk) { + case 0x00: // SEO is disconnected + case (HRSL_JSTATUS | HRSL_KSTATUS): // SE1 is illegal + mode_write(rhport, new_mode, in_isr); + + // port reset anyway, this will help to stable bus signal for next connection + reg_write(rhport, HCTL_ADDR, HCTL_BUSRST, in_isr); + hcd_event_device_remove(rhport, in_isr); + reg_write(rhport, HCTL_ADDR, 0, in_isr); + break; + + default: { + // Bus Reset also cause CONDET IRQ, skip if we are already connected and doing bus reset + if ((_hcd_data.hirq & HIRQ_BUSEVENT_IRQ) && (_hcd_data.mode & MODE_SOFKAENAB)) { + break; + } + + // Low speed if (LS = 1 and J-state) or (LS = 0 and K-State) + // However, since we are always in full speed mode, we can just check J-state + if (jk == HRSL_KSTATUS) { + new_mode |= MODE_LOWSPEED; + TU_LOG3("Low speed\r\n"); + }else { + TU_LOG3("Full speed\r\n"); + } + new_mode |= MODE_SOFKAENAB; + mode_write(rhport, new_mode, in_isr); + + // FIXME multiple MAX3421 rootdevice address is not 1 + uint8_t const daddr = 1; + free_ep(daddr); + + hcd_event_device_attach(rhport, in_isr); + break; + } + } +} + +static void xfer_complete_isr(uint8_t rhport, max3421_ep_t *ep, xfer_result_t result, uint8_t hrsl, bool in_isr) { + const uint8_t ep_dir = 1 - ep->hxfr_bm.is_out; + const uint8_t ep_addr = tu_edpt_addr(ep->hxfr_bm.ep_num, ep_dir); + + // save data toggle + if (ep_dir) { + ep->data_toggle = (hrsl & HRSL_RCVTOGRD) ? 1u : 0u; + }else { + ep->data_toggle = (hrsl & HRSL_SNDTOGRD) ? 1u : 0u; + } + + ep->state = EP_STATE_IDLE; + hcd_event_xfer_complete(ep->daddr, ep_addr, ep->xferred_len, result, in_isr); + + // Find next pending endpoint + max3421_ep_t * next_ep = find_next_pending_ep(ep); + if (next_ep) { + xact_generic(rhport, next_ep, true, in_isr); + }else { + // no more pending + usbh_spin_lock(in_isr); + _hcd_data.busy_lock = false; + usbh_spin_unlock(in_isr); + } +} + +static void handle_xfer_done(uint8_t rhport, bool in_isr) { + const uint8_t hrsl = reg_read(rhport, HRSL_ADDR, in_isr); + const uint8_t hresult = hrsl & HRSL_RESULT_MASK; + const uint8_t ep_num = _hcd_data.hxfr_bm.ep_num; + const uint8_t hxfr_type = _hcd_data.hxfr & 0xf0; + const uint8_t ep_dir = ((hxfr_type & HXFR_SETUP) || (hxfr_type & HXFR_OUT_NIN)) ? 0 : 1; + + max3421_ep_t *ep = find_opened_ep(_hcd_data.peraddr, ep_num, ep_dir); + TU_VERIFY(ep, ); + + xfer_result_t xfer_result; + switch(hresult) { + case HRSL_NAK: + if (ep->state == EP_STATE_ABORTING) { + ep->state = EP_STATE_IDLE; + } else { + if (ep_num == 0) { + // control endpoint -> retry immediately and return + hxfr_write(rhport, _hcd_data.hxfr, in_isr); + return; + } + if (EP_STATE_ATTEMPT_1 <= ep->state && ep->state < EP_STATE_ATTEMPT_MAX) { + ep->state++; + } + } + + max3421_ep_t * next_ep = find_next_pending_ep(ep); + if (ep == next_ep) { + // this endpoint is only one pending -> retry immediately + hxfr_write(rhport, _hcd_data.hxfr, in_isr); + } else if (next_ep) { + // switch to next pending endpoint + xact_generic(rhport, next_ep, true, in_isr); + } else { + // no more pending in this frame -> clear busy + usbh_spin_lock(in_isr); + _hcd_data.busy_lock = false; + usbh_spin_unlock(in_isr); + } + return; + + case HRSL_BAD_REQ: + // occurred when initialized without any pending transfer. Skip for now + return; + + case HRSL_SUCCESS: + xfer_result = XFER_RESULT_SUCCESS; + break; + + case HRSL_STALL: + xfer_result = XFER_RESULT_STALLED; + break; + + default: + TU_LOG3("HRSL: %02X\r\n", hrsl); + xfer_result = XFER_RESULT_FAILED; + break; + } + + if (xfer_result != XFER_RESULT_SUCCESS) { + xfer_complete_isr(rhport, ep, xfer_result, hrsl, in_isr); + return; + } + + if (ep_dir) { + // IN transfer: fifo data is already received in RCVDAV IRQ + + // mark control handshake as complete + if (hxfr_type & HXFR_HS) { + ep->state = EP_STATE_COMPLETE; + } + + // short packet or all bytes transferred + if (ep->state == EP_STATE_COMPLETE) { + xfer_complete_isr(rhport, ep, xfer_result, hrsl, in_isr); + }else { + hxfr_write(rhport, _hcd_data.hxfr, in_isr); // more to transfer + } + } else { + // SETUP or OUT transfer + + // clear sndfifo owner since data is sent + _hcd_data.sndfifo_owner.daddr = 0xff; + _hcd_data.sndfifo_owner.hxfr = 0xff; + + uint8_t xact_len; + + if (hxfr_type & HXFR_SETUP) { + xact_len = 8; + } else if (hxfr_type & HXFR_HS) { + xact_len = 0; + } else { + xact_len = _hcd_data.sndbc; + } + + ep->xferred_len += xact_len; + ep->buf += xact_len; + + if (xact_len < ep->packet_size || ep->xferred_len >= ep->total_len) { + xfer_complete_isr(rhport, ep, xfer_result, hrsl, in_isr); + } else { + xact_out(rhport, ep, false, in_isr); // more to transfer + } + } +} + +#if CFG_TUSB_DEBUG >= 3 +void print_hirq(uint8_t hirq) { + TU_LOG3_HEX(hirq); + + if (hirq & HIRQ_HXFRDN_IRQ) TU_LOG3(" HXFRDN"); + if (hirq & HIRQ_FRAME_IRQ) TU_LOG3(" FRAME"); + if (hirq & HIRQ_CONDET_IRQ) TU_LOG3(" CONDET"); + if (hirq & HIRQ_SUSDN_IRQ) TU_LOG3(" SUSDN"); + if (hirq & HIRQ_SNDBAV_IRQ) TU_LOG3(" SNDBAV"); + if (hirq & HIRQ_RCVDAV_IRQ) TU_LOG3(" RCVDAV"); + if (hirq & HIRQ_RWU_IRQ) TU_LOG3(" RWU"); + if (hirq & HIRQ_BUSEVENT_IRQ) TU_LOG3(" BUSEVENT"); + + TU_LOG3("\r\n"); +} +#else + #define print_hirq(hirq) +#endif + +// Interrupt handler +void hcd_int_handler(uint8_t rhport, bool in_isr) { + uint8_t hirq = reg_read(rhport, HIRQ_ADDR, in_isr) & _hcd_data.hien; + if (!hirq) { return; } + // print_hirq(hirq); + + if (hirq & HIRQ_FRAME_IRQ) { + _hcd_data.frame_count++; + + // reset all endpoints nak counter, retry with 1st pending ep. + max3421_ep_t* ep_retry = NULL; + for (size_t i = 0; i < CFG_TUH_MAX3421_ENDPOINT_TOTAL; i++) { + max3421_ep_t* ep = &_hcd_data.ep[i]; + if (ep->packet_size && ep->state > EP_STATE_ATTEMPT_1) { + ep->state = EP_STATE_ATTEMPT_1; + + if (ep_retry == NULL) { + ep_retry = ep; + } + } + } + + // start usb transfer if not busy + if (ep_retry != NULL) { + bool has_xfer = false; + + usbh_spin_lock(in_isr); + if (!_hcd_data.busy_lock) { + _hcd_data.busy_lock = true; + has_xfer = true; + } + usbh_spin_unlock(in_isr); + + if (has_xfer) { + xact_generic(rhport, ep_retry, true, in_isr); + } + } + } + + if (hirq & HIRQ_CONDET_IRQ) { + handle_connect_irq(rhport, in_isr); + } + + // queue more transfer in handle_xfer_done() can cause hirq to be set again while external IRQ may not catch and/or + // not call this handler again. So we need to loop until all IRQ are cleared + while (hirq & (HIRQ_RCVDAV_IRQ | HIRQ_HXFRDN_IRQ)) { + if (hirq & HIRQ_RCVDAV_IRQ) { + const uint8_t ep_num = _hcd_data.hxfr_bm.ep_num; + max3421_ep_t* ep = find_opened_ep(_hcd_data.peraddr, ep_num, 1); + uint8_t xact_len = 0; + + // RCVDAV_IRQ can trigger 2 times (dual buffered) + while (hirq & HIRQ_RCVDAV_IRQ) { + const uint8_t rcvbc = reg_read(rhport, RCVBC_ADDR, in_isr); + xact_len = (uint8_t) tu_min16(rcvbc, ep->total_len - ep->xferred_len); + if (xact_len) { + hwfifo_receive(rhport, ep->buf, xact_len, in_isr); + ep->buf += xact_len; + ep->xferred_len += xact_len; + } + + // ack RCVDVAV IRQ + hirq_write(rhport, HIRQ_RCVDAV_IRQ, in_isr); + hirq = reg_read(rhport, HIRQ_ADDR, in_isr); + } + + if (xact_len < ep->packet_size || ep->xferred_len >= ep->total_len) { + ep->state = EP_STATE_COMPLETE; + } + } + + if (hirq & HIRQ_HXFRDN_IRQ) { + hirq_write(rhport, HIRQ_HXFRDN_IRQ, in_isr); + handle_xfer_done(rhport, in_isr); + } + + hirq = reg_read(rhport, HIRQ_ADDR, in_isr); + } + + // clear all interrupt except SNDBAV_IRQ (never clear by us). Note RCVDAV_IRQ, HXFRDN_IRQ already clear while processing + hirq &= (uint8_t) ~HIRQ_SNDBAV_IRQ; + if (hirq) { + hirq_write(rhport, hirq, in_isr); + } +} + +#endif diff --git a/libraries/Adafruit_TinyUSB_Arduino/src/portable/analog/max3421/hcd_max3421.h b/libraries/Adafruit_TinyUSB_Arduino/src/portable/analog/max3421/hcd_max3421.h new file mode 100644 index 0000000..4631fa2 --- /dev/null +++ b/libraries/Adafruit_TinyUSB_Arduino/src/portable/analog/max3421/hcd_max3421.h @@ -0,0 +1,63 @@ +/* + * The MIT License (MIT) + * + * Copyright (c) 2025 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. + * + * This file is part of the TinyUSB stack. + */ +#ifndef TUSB_HCD_MAX3421_H +#define TUSB_HCD_MAX3421_H + +#ifdef __cplusplus + extern "C" { +#endif + +//--------------------------------------------------------------------+ +// SPI transfer API with MAX3421E are implemented by application +// - spi_cs_api(), spi_xfer_api(), int_api() +//--------------------------------------------------------------------+ + +// API to control MAX3421 SPI CS +extern void tuh_max3421_spi_cs_api(uint8_t rhport, bool active); + +// API to transfer data with MAX3421 SPI +// Either tx_buf or rx_buf can be NULL, which means transfer is write or read only +extern bool tuh_max3421_spi_xfer_api(uint8_t rhport, uint8_t const* tx_buf, uint8_t* rx_buf, size_t xfer_bytes); + +// API to enable/disable MAX3421 INTR pin interrupt +extern void tuh_max3421_int_api(uint8_t rhport, bool enabled); + +//--------------------------------------------------------------------+ +// API for read/write MAX3421 registers +// are implemented by this driver, can be used by application +//--------------------------------------------------------------------+ + +// API to read MAX3421's register. Implemented by TinyUSB +uint8_t tuh_max3421_reg_read(uint8_t rhport, uint8_t reg, bool in_isr); + +// API to write MAX3421's register. Implemented by TinyUSB +bool tuh_max3421_reg_write(uint8_t rhport, uint8_t reg, uint8_t data, bool in_isr); + +#ifdef __cplusplus + } +#endif + +#endif diff --git a/libraries/Adafruit_TinyUSB_Arduino/src/portable/microchip/samd/dcd_samd.c b/libraries/Adafruit_TinyUSB_Arduino/src/portable/microchip/samd/dcd_samd.c new file mode 100644 index 0000000..cdfb00e --- /dev/null +++ b/libraries/Adafruit_TinyUSB_Arduino/src/portable/microchip/samd/dcd_samd.c @@ -0,0 +1,437 @@ +/* + * The MIT License (MIT) + * + * Copyright (c) 2018 Scott Shawcroft for Adafruit Industries + * + * 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. + */ + +#include "tusb_option.h" + +#if CFG_TUD_ENABLED && TU_CHECK_MCU(OPT_MCU_SAMD11, OPT_MCU_SAMD21, OPT_MCU_SAML2X, OPT_MCU_SAMD51, OPT_MCU_SAME5X) + +#include "sam.h" +#include "device/dcd.h" + +/*------------------------------------------------------------------*/ +/* MACRO TYPEDEF CONSTANT ENUM + *------------------------------------------------------------------*/ +static TU_ATTR_ALIGNED(4) UsbDeviceDescBank sram_registers[8][2]; + +// Setup packet is only 8 bytes in length. However under certain scenario, +// USB DMA controller may decide to overwrite/overflow the buffer with +// 2 extra bytes of CRC. From datasheet's "Management of SETUP Transactions" section +// If the number of received data bytes is the maximum data payload specified by +// PCKSIZE.SIZE minus one, only the first CRC data is written to the data buffer. +// If the number of received data is equal or less than the data payload specified +// by PCKSIZE.SIZE minus two, both CRC data bytes are written to the data buffer. +// Therefore we will need to increase it to 10 bytes here. +static TU_ATTR_ALIGNED(4) uint8_t _setup_packet[8+2]; + +// ready for receiving SETUP packet +static inline void prepare_setup(void) +{ + // Only make sure the EP0 OUT buffer is ready + sram_registers[0][0].ADDR.reg = (uint32_t) _setup_packet; + sram_registers[0][0].PCKSIZE.bit.MULTI_PACKET_SIZE = sizeof(tusb_control_request_t); + sram_registers[0][0].PCKSIZE.bit.BYTE_COUNT = 0; +} + +// Setup the control endpoint 0. +static void bus_reset(void) +{ + // Max size of packets is 64 bytes. + UsbDeviceDescBank* bank_out = &sram_registers[0][TUSB_DIR_OUT]; + bank_out->PCKSIZE.bit.SIZE = 0x3; + UsbDeviceDescBank* bank_in = &sram_registers[0][TUSB_DIR_IN]; + bank_in->PCKSIZE.bit.SIZE = 0x3; + + UsbDeviceEndpoint* ep = &USB->DEVICE.DeviceEndpoint[0]; + ep->EPCFG.reg = USB_DEVICE_EPCFG_EPTYPE0(0x1) | USB_DEVICE_EPCFG_EPTYPE1(0x1); + ep->EPINTENSET.reg = USB_DEVICE_EPINTENSET_TRCPT0 | USB_DEVICE_EPINTENSET_TRCPT1 | USB_DEVICE_EPINTENSET_RXSTP; + + // Prepare for setup packet + prepare_setup(); +} + +/*------------------------------------------------------------------*/ +/* Controller API + *------------------------------------------------------------------*/ +bool dcd_init(uint8_t rhport, const tusb_rhport_init_t* rh_init) { + (void) rhport; + (void) rh_init; + + // Reset to get in a clean state. + USB->DEVICE.CTRLA.bit.SWRST = true; + while (USB->DEVICE.SYNCBUSY.bit.SWRST == 0) {} + while (USB->DEVICE.SYNCBUSY.bit.SWRST == 1) {} + + USB->DEVICE.PADCAL.bit.TRANSP = (*((uint32_t*) USB_FUSES_TRANSP_ADDR) & USB_FUSES_TRANSP_Msk) >> USB_FUSES_TRANSP_Pos; + USB->DEVICE.PADCAL.bit.TRANSN = (*((uint32_t*) USB_FUSES_TRANSN_ADDR) & USB_FUSES_TRANSN_Msk) >> USB_FUSES_TRANSN_Pos; + USB->DEVICE.PADCAL.bit.TRIM = (*((uint32_t*) USB_FUSES_TRIM_ADDR) & USB_FUSES_TRIM_Msk) >> USB_FUSES_TRIM_Pos; + + USB->DEVICE.QOSCTRL.bit.CQOS = 3; // High Quality + USB->DEVICE.QOSCTRL.bit.DQOS = 3; // High Quality + + // Configure registers + USB->DEVICE.DESCADD.reg = (uint32_t) &sram_registers; + USB->DEVICE.CTRLB.reg = USB_DEVICE_CTRLB_SPDCONF_FS; + USB->DEVICE.CTRLA.reg = USB_CTRLA_MODE_DEVICE | USB_CTRLA_ENABLE | USB_CTRLA_RUNSTDBY; + while (USB->DEVICE.SYNCBUSY.bit.ENABLE == 1) {} + + USB->DEVICE.INTFLAG.reg |= USB->DEVICE.INTFLAG.reg; // clear pending + USB->DEVICE.INTENSET.reg = /* USB_DEVICE_INTENSET_SOF | */ USB_DEVICE_INTENSET_EORST; + + return true; +} + +#if TU_CHECK_MCU(OPT_MCU_SAMD51, OPT_MCU_SAME5X) +void dcd_int_enable(uint8_t rhport) { + (void) rhport; + NVIC_EnableIRQ(USB_0_IRQn); + NVIC_EnableIRQ(USB_1_IRQn); + NVIC_EnableIRQ(USB_2_IRQn); + NVIC_EnableIRQ(USB_3_IRQn); +} + +void dcd_int_disable(uint8_t rhport) { + (void) rhport; + NVIC_DisableIRQ(USB_3_IRQn); + NVIC_DisableIRQ(USB_2_IRQn); + NVIC_DisableIRQ(USB_1_IRQn); + NVIC_DisableIRQ(USB_0_IRQn); +} + +#elif TU_CHECK_MCU(OPT_MCU_SAMD11, OPT_MCU_SAMD21, OPT_MCU_SAML2X) +void dcd_int_enable(uint8_t rhport) { + (void) rhport; + NVIC_EnableIRQ(USB_IRQn); +} + +void dcd_int_disable(uint8_t rhport) { + (void) rhport; + NVIC_DisableIRQ(USB_IRQn); +} + +#else + +#error "No implementation available for dcd_int_enable / dcd_int_disable" + +#endif + +void dcd_set_address (uint8_t rhport, uint8_t dev_addr) +{ + (void) dev_addr; + + // Response with zlp status + dcd_edpt_xfer(rhport, 0x80, NULL, 0, false); + + // DCD can only set address after status for this request is complete + // do it at dcd_edpt0_status_complete() + + // Enable SUSPEND interrupt since the bus signal D+/D- are stable now. + USB->DEVICE.INTFLAG.reg = USB_DEVICE_INTENCLR_SUSPEND; // clear pending + USB->DEVICE.INTENSET.reg = USB_DEVICE_INTENSET_SUSPEND; +} + +void dcd_remote_wakeup(uint8_t rhport) +{ + (void) rhport; + USB->DEVICE.CTRLB.bit.UPRSM = 1; +} + +// disconnect by disabling internal pull-up resistor on D+/D- +void dcd_disconnect(uint8_t rhport) +{ + (void) rhport; + USB->DEVICE.CTRLB.reg |= USB_DEVICE_CTRLB_DETACH; +} + +// connect by enabling internal pull-up resistor on D+/D- +void dcd_connect(uint8_t rhport) +{ + (void) rhport; + USB->DEVICE.CTRLB.reg &= ~USB_DEVICE_CTRLB_DETACH; +} + +void dcd_sof_enable(uint8_t rhport, bool en) +{ + (void) rhport; + + if (en) { + USB->DEVICE.INTENSET.bit.SOF = 1; + } else { + USB->DEVICE.INTENCLR.bit.SOF = 1; + } +} + +/*------------------------------------------------------------------*/ +/* DCD Endpoint port + *------------------------------------------------------------------*/ + +// Invoked when a control transfer's status stage is complete. +// May help DCD to prepare for next control transfer, this API is optional. +void dcd_edpt0_status_complete(uint8_t rhport, tusb_control_request_t const * request) +{ + (void) rhport; + + if (request->bmRequestType_bit.recipient == TUSB_REQ_RCPT_DEVICE && + request->bmRequestType_bit.type == TUSB_REQ_TYPE_STANDARD && + request->bRequest == TUSB_REQ_SET_ADDRESS ) + { + uint8_t const dev_addr = (uint8_t) request->wValue; + USB->DEVICE.DADD.reg = USB_DEVICE_DADD_DADD(dev_addr) | USB_DEVICE_DADD_ADDEN; + } + + // Just finished status stage, prepare for next setup packet + // Note: we may already prepare setup when queueing the control status. + // but it has no harm to do it again here + prepare_setup(); +} + +bool dcd_edpt_open (uint8_t rhport, tusb_desc_endpoint_t const * desc_edpt) +{ + (void) rhport; + + uint8_t const epnum = tu_edpt_number(desc_edpt->bEndpointAddress); + uint8_t const dir = tu_edpt_dir(desc_edpt->bEndpointAddress); + + UsbDeviceDescBank* bank = &sram_registers[epnum][dir]; + uint32_t size_value = 0; + while (size_value < 7) { + if (1 << (size_value + 3) >= tu_edpt_packet_size(desc_edpt)) { + break; + } + size_value++; + } + + // unsupported endpoint size + if ( size_value == 7 && tu_edpt_packet_size(desc_edpt) > 1023 ) return false; + + bank->PCKSIZE.bit.SIZE = size_value; + + UsbDeviceEndpoint* ep = &USB->DEVICE.DeviceEndpoint[epnum]; + + if ( dir == TUSB_DIR_OUT ) + { + ep->EPCFG.bit.EPTYPE0 = desc_edpt->bmAttributes.xfer + 1; + ep->EPSTATUSCLR.reg = USB_DEVICE_EPSTATUSCLR_STALLRQ0 | USB_DEVICE_EPSTATUSCLR_DTGLOUT; // clear stall & dtoggle + ep->EPINTENSET.bit.TRCPT0 = true; + }else + { + ep->EPCFG.bit.EPTYPE1 = desc_edpt->bmAttributes.xfer + 1; + ep->EPSTATUSCLR.reg = USB_DEVICE_EPSTATUSCLR_STALLRQ1 | USB_DEVICE_EPSTATUSCLR_DTGLIN; // clear stall & dtoggle + ep->EPINTENSET.bit.TRCPT1 = true; + } + + return true; +} + +bool dcd_edpt_iso_alloc(uint8_t rhport, uint8_t ep_addr, uint16_t largest_packet_size) { + (void) rhport; + (void) ep_addr; + (void)largest_packet_size; + return false; +} + +bool dcd_edpt_iso_activate(uint8_t rhport, const tusb_desc_endpoint_t *desc_ep) { + (void)rhport; + (void)desc_ep; + return false; +} + +void dcd_edpt_close_all (uint8_t rhport) +{ + (void) rhport; + // TODO implement dcd_edpt_close_all() +} + +bool dcd_edpt_xfer(uint8_t rhport, uint8_t ep_addr, uint8_t * buffer, uint16_t total_bytes, bool is_isr) +{ + (void) is_isr; + (void) rhport; + + uint8_t const epnum = tu_edpt_number(ep_addr); + uint8_t const dir = tu_edpt_dir(ep_addr); + + UsbDeviceDescBank* bank = &sram_registers[epnum][dir]; + UsbDeviceEndpoint* ep = &USB->DEVICE.DeviceEndpoint[epnum]; + + bank->ADDR.reg = (uint32_t) buffer; + + // A SETUP token can occur immediately after an ZLP Status. + // So make sure we have a valid buffer for setup packet. + // Status = ZLP EP0 with direction opposite to one in the dir bit of current setup + if ( (epnum == 0) && (buffer == NULL) && (total_bytes == 0) && (dir != tu_edpt_dir(_setup_packet[0])) ) { + prepare_setup(); + } + + if ( dir == TUSB_DIR_OUT ) + { + bank->PCKSIZE.bit.MULTI_PACKET_SIZE = total_bytes; + bank->PCKSIZE.bit.BYTE_COUNT = 0; + ep->EPSTATUSCLR.reg = USB_DEVICE_EPSTATUSCLR_BK0RDY; + ep->EPINTFLAG.reg = USB_DEVICE_EPINTFLAG_TRFAIL0; + } else + { + bank->PCKSIZE.bit.MULTI_PACKET_SIZE = 0; + bank->PCKSIZE.bit.BYTE_COUNT = total_bytes; + ep->EPSTATUSSET.reg = USB_DEVICE_EPSTATUSSET_BK1RDY; + ep->EPINTFLAG.reg = USB_DEVICE_EPINTFLAG_TRFAIL1; + } + + return true; +} + +void dcd_edpt_stall (uint8_t rhport, uint8_t ep_addr) +{ + (void) rhport; + + uint8_t const epnum = tu_edpt_number(ep_addr); + UsbDeviceEndpoint* ep = &USB->DEVICE.DeviceEndpoint[epnum]; + + if (tu_edpt_dir(ep_addr) == TUSB_DIR_IN) { + ep->EPSTATUSSET.reg = USB_DEVICE_EPSTATUSSET_STALLRQ1; + } else { + ep->EPSTATUSSET.reg = USB_DEVICE_EPSTATUSSET_STALLRQ0; + } +} + +void dcd_edpt_clear_stall (uint8_t rhport, uint8_t ep_addr) +{ + (void) rhport; + + uint8_t const epnum = tu_edpt_number(ep_addr); + UsbDeviceEndpoint* ep = &USB->DEVICE.DeviceEndpoint[epnum]; + + if (tu_edpt_dir(ep_addr) == TUSB_DIR_IN) { + ep->EPSTATUSCLR.reg = USB_DEVICE_EPSTATUSCLR_STALLRQ1 | USB_DEVICE_EPSTATUSCLR_DTGLIN; + } else { + ep->EPSTATUSCLR.reg = USB_DEVICE_EPSTATUSCLR_STALLRQ0 | USB_DEVICE_EPSTATUSCLR_DTGLOUT; + } +} + +//--------------------------------------------------------------------+ +// Interrupt Handler +//--------------------------------------------------------------------+ +static void maybe_transfer_complete(void) { + uint32_t epints = USB->DEVICE.EPINTSMRY.reg; + + for (uint8_t epnum = 0; epnum < USB_EPT_NUM; epnum++) { + if ((epints & (1 << epnum)) == 0) { + continue; + } + + UsbDeviceEndpoint* ep = &USB->DEVICE.DeviceEndpoint[epnum]; + uint32_t epintflag = ep->EPINTFLAG.reg; + + // Handle IN completions + if ((epintflag & USB_DEVICE_EPINTFLAG_TRCPT1) != 0) { + UsbDeviceDescBank* bank = &sram_registers[epnum][TUSB_DIR_IN]; + uint16_t const total_transfer_size = bank->PCKSIZE.bit.BYTE_COUNT; + + dcd_event_xfer_complete(0, epnum | TUSB_DIR_IN_MASK, total_transfer_size, XFER_RESULT_SUCCESS, true); + + ep->EPINTFLAG.reg = USB_DEVICE_EPINTFLAG_TRCPT1; + } + + // Handle OUT completions + if ((epintflag & USB_DEVICE_EPINTFLAG_TRCPT0) != 0) { + UsbDeviceDescBank* bank = &sram_registers[epnum][TUSB_DIR_OUT]; + uint16_t const total_transfer_size = bank->PCKSIZE.bit.BYTE_COUNT; + + dcd_event_xfer_complete(0, epnum, total_transfer_size, XFER_RESULT_SUCCESS, true); + + ep->EPINTFLAG.reg = USB_DEVICE_EPINTFLAG_TRCPT0; + } + } +} + + +void dcd_int_handler (uint8_t rhport) +{ + (void) rhport; + + uint32_t int_status = USB->DEVICE.INTFLAG.reg & USB->DEVICE.INTENSET.reg; + + // Start of Frame + if ( int_status & USB_DEVICE_INTFLAG_SOF ) + { + USB->DEVICE.INTFLAG.reg = USB_DEVICE_INTFLAG_SOF; + const uint32_t frame = USB->DEVICE.FNUM.bit.FNUM; + dcd_event_sof(0, frame, true); + //dcd_event_bus_signal(0, DCD_EVENT_SOF, true); + } + + // SAMD doesn't distinguish between Suspend and Disconnect state. + // Both condition will cause SUSPEND interrupt triggered. + // To prevent being triggered when D+/D- are not stable, SUSPEND interrupt is only + // enabled when we received SET_ADDRESS request and cleared on Bus Reset + if ( int_status & USB_DEVICE_INTFLAG_SUSPEND ) + { + USB->DEVICE.INTFLAG.reg = USB_DEVICE_INTFLAG_SUSPEND; + + // Enable wakeup interrupt + USB->DEVICE.INTFLAG.reg = USB_DEVICE_INTFLAG_WAKEUP; // clear pending + USB->DEVICE.INTENSET.reg = USB_DEVICE_INTFLAG_WAKEUP; + + dcd_event_bus_signal(0, DCD_EVENT_SUSPEND, true); + } + + // Wakeup interrupt is only enabled when we got suspended. + // Wakeup interrupt will disable itself + if ( int_status & USB_DEVICE_INTFLAG_WAKEUP ) + { + USB->DEVICE.INTFLAG.reg = USB_DEVICE_INTFLAG_WAKEUP; + + // disable wakeup interrupt itself + USB->DEVICE.INTENCLR.reg = USB_DEVICE_INTFLAG_WAKEUP; + dcd_event_bus_signal(0, DCD_EVENT_RESUME, true); + } + + // Enable of Reset + if ( int_status & USB_DEVICE_INTFLAG_EORST ) + { + USB->DEVICE.INTFLAG.reg = USB_DEVICE_INTFLAG_EORST; + + // Disable both suspend and wakeup interrupt + USB->DEVICE.INTENCLR.reg = USB_DEVICE_INTFLAG_WAKEUP | USB_DEVICE_INTFLAG_SUSPEND; + + bus_reset(); + dcd_event_bus_reset(0, TUSB_SPEED_FULL, true); + } + + // Handle SETUP packet + if (USB->DEVICE.DeviceEndpoint[0].EPINTFLAG.bit.RXSTP) + { + // This copies the data elsewhere so we can reuse the buffer. + dcd_event_setup_received(0, _setup_packet, true); + + // Although Setup packet only set RXSTP bit, + // TRCPT0 bit could already be set by previous ZLP OUT Status (not handled until now). + // Since control status complete event is optional, we can just clear TRCPT0 and skip the status event + USB->DEVICE.DeviceEndpoint[0].EPINTFLAG.reg = USB_DEVICE_EPINTFLAG_RXSTP | USB_DEVICE_EPINTFLAG_TRCPT0; + } + + // Handle complete transfer + maybe_transfer_complete(); +} +#endif diff --git a/libraries/Adafruit_TinyUSB_Arduino/src/portable/microchip/samd/hcd_samd.c b/libraries/Adafruit_TinyUSB_Arduino/src/portable/microchip/samd/hcd_samd.c new file mode 100644 index 0000000..0de7dde --- /dev/null +++ b/libraries/Adafruit_TinyUSB_Arduino/src/portable/microchip/samd/hcd_samd.c @@ -0,0 +1,761 @@ +/* + * The MIT License (MIT) + * + * Copyright (c) 2024 ChrisDeadman + * + * 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. + */ + +#include "tusb_option.h" + +#if CFG_TUH_ENABLED && !(defined(CFG_TUH_MAX3421) && CFG_TUH_MAX3421) && \ + TU_CHECK_MCU(OPT_MCU_SAMD11, OPT_MCU_SAMD21, OPT_MCU_SAML2X, OPT_MCU_SAMD51, OPT_MCU_SAME5X) + +#include "host/hcd.h" +#include "sam.h" + +/*------------------------------------------------------------------*/ +/* MACRO TYPEDEF CONSTANT ENUM + *------------------------------------------------------------------*/ +#define USB_HOST_PTYPE_DIS 0x0 +#define USB_HOST_PTYPE_CTRL 0x1 +#define USB_HOST_PTYPE_ISO 0x2 +#define USB_HOST_PTYPE_BULK 0x3 +#define USB_HOST_PTYPE_INT 0x4 +#define USB_HOST_PTYPE_EXT 0x5 + +#define USB_HOST_PCFG_PTOKEN_SETUP 0x0 +#define USB_HOST_PCFG_PTOKEN_IN 0x1 +#define USB_HOST_PCFG_PTOKEN_OUT 0x2 + +#define USB_PCKSIZE_ENUM(size) \ + ((size) >= 1024 ? 7 \ + : (size) >= 1023 ? 7 \ + : (size) > 256 ? 6 \ + : (size) > 128 ? 5 \ + : (size) > 64 ? 4 \ + : (size) > 32 ? 3 \ + : (size) > 16 ? 2 \ + : (size) > 8 ? 1 \ + : 0) + +// Uncomment to use fake frame number. +// Low-Speed devices stall FNUM during enumeration :/ +// #define HCD_SAMD_FAKE_FNUM + +typedef struct { + uint8_t dev_addr; + uint8_t ep_addr; + uint16_t max_packet_size; + uint16_t xfer_length; + uint16_t xfer_remaining; +} usb_pipe_status_t; + +CFG_TUH_MEM_SECTION CFG_TUH_MEM_ALIGN static volatile UsbHostDescriptor usb_pipe_table[USB_PIPE_NUM]; + +CFG_TUH_MEM_SECTION CFG_TUH_MEM_ALIGN static volatile usb_pipe_status_t usb_pipe_status_table[USB_PIPE_NUM]; + +CFG_TUH_MEM_SECTION CFG_TUH_MEM_ALIGN static volatile uint32_t fake_fnum; + +static uint8_t samd_configure_pipe(uint8_t dev_addr, uint8_t ep_addr) +{ + uint8_t pipe; + uint8_t token; + volatile usb_pipe_status_t* pipe_status; + bool same_addr = false; + bool same_ep_addr = false; + + // evaluate pipe token + token = (tu_edpt_dir(ep_addr) == TUSB_DIR_IN) ? USB_HOST_PCFG_PTOKEN_IN + : tu_edpt_number(ep_addr) == 0 ? USB_HOST_PCFG_PTOKEN_SETUP + : USB_HOST_PCFG_PTOKEN_OUT; + + TU_LOG3("samd_configure_pipe(token=%02X, dev_addr=%02X, ep_addr=%02X)=", token, dev_addr, ep_addr); + + // find already allocated pipe + for (pipe = 0; pipe < USB_PIPE_NUM; pipe++) { + pipe_status = &usb_pipe_status_table[pipe]; + same_addr = (pipe_status->dev_addr == dev_addr); + same_ep_addr = (tu_edpt_number(pipe_status->ep_addr) == tu_edpt_number(ep_addr)); + if (same_ep_addr && (same_addr || (tu_edpt_number(ep_addr) == 0))) { + break; + } + } + + // allocate from pool of free pipes + if (pipe >= USB_PIPE_NUM) { + for (pipe = 0; pipe < USB_PIPE_NUM; pipe++) { + pipe_status = &usb_pipe_status_table[pipe]; + // found a free pipe + if (pipe_status->dev_addr >= UINT8_MAX) { + break; + } + } + } + + // no pipe available :( + if (pipe >= USB_PIPE_NUM) { + TU_LOG3("ERR_NO_PIPE\r\n"); + return pipe; + } + TU_LOG3("%d\r\n", pipe); + + // no transfer should be in progress + TU_ASSERT(((USB->HOST.HostPipe[pipe].PCFG.bit.PTYPE == USB_HOST_PTYPE_DIS) || + USB->HOST.HostPipe[pipe].PSTATUS.bit.PFREEZE == 1), + USB_PIPE_NUM); + + // update addr and ep_addr + pipe_status->dev_addr = dev_addr; + pipe_status->ep_addr = ep_addr; + usb_pipe_table[pipe].HostDescBank[0].CTRL_PIPE.bit.PDADDR = dev_addr; + usb_pipe_table[pipe].HostDescBank[0].CTRL_PIPE.bit.PEPNUM = tu_edpt_number(ep_addr); + + // token specific configuration + USB->HOST.HostPipe[pipe].PCFG.bit.PTOKEN = token; + USB->HOST.HostPipe[pipe].PINTENCLR.reg = USB_HOST_PINTENCLR_MASK; + if (token == USB_HOST_PCFG_PTOKEN_SETUP) { + USB->HOST.HostPipe[pipe].PSTATUSCLR.reg = USB_HOST_PSTATUSCLR_DTGL; + USB->HOST.HostPipe[pipe].PSTATUSCLR.reg = USB_HOST_PSTATUSCLR_BK0RDY; + USB->HOST.HostPipe[pipe].PINTENSET.reg = USB_HOST_PINTENSET_TXSTP; + USB->HOST.HostPipe[pipe].PINTENSET.reg = USB_HOST_PINTENSET_STALL; + USB->HOST.HostPipe[pipe].PINTENSET.reg = USB_HOST_PINTENSET_TRFAIL; + USB->HOST.HostPipe[pipe].PINTENSET.reg = USB_HOST_PINTENSET_PERR; + } else if (token == USB_HOST_PCFG_PTOKEN_IN) { + USB->HOST.HostPipe[pipe].PSTATUSSET.reg = USB_HOST_PSTATUSSET_BK0RDY; + USB->HOST.HostPipe[pipe].PINTENSET.reg = USB_HOST_PINTENSET_TRCPT_Msk; + USB->HOST.HostPipe[pipe].PINTENSET.reg = USB_HOST_PINTENSET_STALL; + USB->HOST.HostPipe[pipe].PINTENSET.reg = USB_HOST_PINTENSET_TRFAIL; + USB->HOST.HostPipe[pipe].PINTENSET.reg = USB_HOST_PINTENSET_PERR; + } else { + USB->HOST.HostPipe[pipe].PSTATUSCLR.reg = USB_HOST_PSTATUSCLR_BK0RDY; + USB->HOST.HostPipe[pipe].PINTENSET.reg = USB_HOST_PINTENSET_TRCPT_Msk; + USB->HOST.HostPipe[pipe].PINTENSET.reg = USB_HOST_PINTENSET_STALL; + USB->HOST.HostPipe[pipe].PINTENSET.reg = USB_HOST_PINTENSET_TRFAIL; + USB->HOST.HostPipe[pipe].PINTENSET.reg = USB_HOST_PINTENSET_PERR; + } + + return pipe; +} + +static void samd_free_pipe(uint8_t pipe) +{ + volatile usb_pipe_status_t* pipe_status = &usb_pipe_status_table[pipe]; + pipe_status->dev_addr = UINT8_MAX; + pipe_status->ep_addr = UINT8_MAX; + pipe_status->max_packet_size = 0; + pipe_status->xfer_length = 0; + pipe_status->xfer_remaining = 0; + + USB->HOST.HostPipe[pipe].PSTATUSSET.reg = USB_HOST_PSTATUSSET_PFREEZE; + USB->HOST.HostPipe[pipe].PCFG.reg &= ~USB_HOST_PCFG_PTYPE_Msk; + USB->HOST.HostPipe[pipe].PINTENCLR.reg = USB_HOST_PINTENCLR_MASK; + memset((uint8_t*)(uintptr_t) &usb_pipe_table[pipe], 0, sizeof(usb_pipe_table[pipe])); +} + +static void samd_free_all_pipes(void) +{ + for (uint8_t pipe = 0; pipe < USB_PIPE_NUM; pipe++) { + samd_free_pipe(pipe); + } +} + +static bool samd_on_xfer(uint8_t pipe, xfer_result_t xfer_result) +{ + uint16_t xfer_delta; + bool xfer_complete; + volatile usb_pipe_status_t* pipe_status = &usb_pipe_status_table[pipe]; + + // freeze the pipe + USB->HOST.HostPipe[pipe].PSTATUSSET.reg = USB_HOST_PSTATUSSET_PFREEZE; + + // get number of transferred bytes + if (xfer_result == XFER_RESULT_SUCCESS) { + xfer_delta = usb_pipe_table[pipe].HostDescBank[0].PCKSIZE.bit.BYTE_COUNT; + } else { + xfer_delta = 0; + } + + TU_LOG3("samd_on_xfer(%d, result=%d, xdelta=%d, rem=%d)\r\n", xfer_result, pipe, xfer_delta, pipe_status->xfer_remaining); + + // update pipe status + if (xfer_delta > pipe_status->xfer_remaining) { + xfer_delta = pipe_status->xfer_remaining; + } + pipe_status->xfer_remaining -= xfer_delta; + pipe_status->xfer_length += xfer_delta; + + // last packet handling + if (xfer_delta < pipe_status->max_packet_size) { + pipe_status->xfer_remaining = 0; + } + + // transfer complete + xfer_complete = (xfer_result != XFER_RESULT_SUCCESS) || (pipe_status->xfer_remaining == 0); + if (xfer_complete) { + return true; + } + + // continue receiving + if (tu_edpt_dir(pipe_status->ep_addr) == TUSB_DIR_IN) { + usb_pipe_table[pipe].HostDescBank[0].PCKSIZE.bit.BYTE_COUNT = 0; + USB->HOST.HostPipe[pipe].PSTATUSCLR.reg = USB_HOST_PSTATUSCLR_BK0RDY; + } + // continue sending + else { + usb_pipe_table[pipe].HostDescBank[0].PCKSIZE.bit.BYTE_COUNT = + (pipe_status->xfer_remaining < pipe_status->max_packet_size) ? pipe_status->xfer_remaining + : pipe_status->max_packet_size; + USB->HOST.HostPipe[pipe].PSTATUSSET.reg = USB_HOST_PSTATUSSET_BK0RDY; + } + + // advance packet buffer + usb_pipe_table[pipe].HostDescBank[0].ADDR.reg += xfer_delta; + + // start next transfer + USB->HOST.HostPipe[pipe].PSTATUSCLR.reg = USB_HOST_PSTATUSCLR_PFREEZE; + + return false; +} + +//--------------------------------------------------------------------+ +// Controller API +//--------------------------------------------------------------------+ + +// Interrupt Handler +void hcd_int_handler(uint8_t rhport, bool in_isr) +{ + (void) rhport; + + uint16_t int_flags; + uint8_t pint_flags; + xfer_result_t xfer_result; + volatile usb_pipe_status_t* pipe_status; + + // + // Check INTFLAG + // + int_flags = USB->HOST.INTFLAG.reg; + if (int_flags & USB_HOST_INTFLAG_HSOF) { + USB->HOST.INTFLAG.reg = USB_HOST_INTFLAG_HSOF; + } + if (int_flags & USB_HOST_INTFLAG_RST) { + TU_LOG2("USB_HOST_INTFLAG_RST\r\n"); + USB->HOST.INTFLAG.reg = USB_HOST_INTFLAG_RST; + } + if (int_flags & USB_HOST_INTFLAG_WAKEUP) { + TU_LOG3("USB_HOST_INTFLAG_WAKEUP\r\n"); + USB->HOST.INTFLAG.reg = USB_HOST_INTFLAG_WAKEUP; + } + if (int_flags & USB_HOST_INTFLAG_DNRSM) { + TU_LOG3("USB_HOST_INTFLAG_DNRSM\r\n"); + USB->HOST.INTFLAG.reg = USB_HOST_INTFLAG_DNRSM; + } + if (int_flags & USB_HOST_INTFLAG_UPRSM) { + TU_LOG3("USB_HOST_INTFLAG_UPRSM\r\n"); + USB->HOST.INTFLAG.reg = USB_HOST_INTFLAG_UPRSM; + } + if (int_flags & USB_HOST_INTFLAG_RAMACER) { + TU_LOG1("USB_HOST_INTFLAG_RAMACER\r\n"); + USB->HOST.INTFLAG.reg = USB_HOST_INTFLAG_RAMACER; + } + if (int_flags & USB_HOST_INTFLAG_DCONN) { + USB->HOST.INTFLAG.reg = USB_HOST_INTFLAG_DCONN; + hcd_event_device_attach(rhport, in_isr); + } + if (int_flags & USB_HOST_INTFLAG_DDISC) { + USB->HOST.INTFLAG.reg = USB_HOST_INTFLAG_DDISC; + hcd_event_device_remove(rhport, in_isr); + } + + // handle pipe interrupts + for (uint8_t pipe = 0; pipe < USB_PIPE_NUM; pipe++) { + // get pipe handle + pipe_status = &usb_pipe_status_table[pipe]; + if (pipe_status->dev_addr >= UINT8_MAX) { + continue; + } + + // + // Check PINTFLAG + // + pint_flags = USB->HOST.HostPipe[pipe].PINTFLAG.reg; + xfer_result = XFER_RESULT_INVALID; + if (pint_flags & USB_HOST_PINTFLAG_TRCPT0) { + USB->HOST.HostPipe[pipe].PINTFLAG.reg = USB_HOST_PINTFLAG_TRCPT0; + xfer_result = XFER_RESULT_SUCCESS; + } + if (pint_flags & USB_HOST_PINTFLAG_TRCPT1) { + USB->HOST.HostPipe[pipe].PINTFLAG.reg = USB_HOST_PINTFLAG_TRCPT1; + xfer_result = XFER_RESULT_SUCCESS; + } + if (pint_flags & USB_HOST_PINTFLAG_TXSTP) { + USB->HOST.HostPipe[pipe].PINTFLAG.reg = USB_HOST_PINTFLAG_TXSTP; + xfer_result = XFER_RESULT_SUCCESS; + } + if (pint_flags & USB_HOST_PINTFLAG_STALL) { + TU_LOG2("USB_HOST_PINTFLAG_STALL\r\n"); + USB->HOST.HostPipe[pipe].PINTFLAG.reg = USB_HOST_PINTFLAG_STALL; + xfer_result = XFER_RESULT_STALLED; + } + if (pint_flags & USB_HOST_PINTFLAG_TRFAIL) { + USB->HOST.HostPipe[pipe].PINTFLAG.reg = USB_HOST_PINTFLAG_TRFAIL; + if (usb_pipe_table[pipe].HostDescBank[0].STATUS_BK.reg & USB_HOST_STATUS_BK_ERRORFLOW) { + TU_LOG1("USB_HOST_STATUS_BK_ERRORFLOW\r\n"); + xfer_result = XFER_RESULT_FAILED; + } else if (usb_pipe_table[pipe].HostDescBank[0].STATUS_BK.reg & USB_HOST_STATUS_BK_CRCERR) { + TU_LOG1("USB_HOST_STATUS_BK_CRCERR\r\n"); + xfer_result = XFER_RESULT_FAILED; + } else { + // SAMD Quirk #1: + // Likes to report TRFAIL for no apparent reason -> ignore + } + } + if (pint_flags & USB_HOST_PINTFLAG_PERR) { + USB->HOST.HostPipe[pipe].PINTFLAG.reg = USB_HOST_PINTFLAG_PERR; + // Handled by STATUS_PIPE checks below + } + + // + // Check STATUS_PIPE + // + if (usb_pipe_table[pipe].HostDescBank[0].STATUS_PIPE.reg & USB_HOST_STATUS_PIPE_DTGLER) { + TU_LOG1("USB_HOST_STATUS_PIPE_DTGLER\r\n"); + usb_pipe_table[pipe].HostDescBank[0].STATUS_PIPE.reg &= ~USB_HOST_STATUS_PIPE_DTGLER; + xfer_result = XFER_RESULT_FAILED; + } + if (usb_pipe_table[pipe].HostDescBank[0].STATUS_PIPE.reg & USB_HOST_STATUS_PIPE_DAPIDER) { + TU_LOG1("USB_HOST_STATUS_PIPE_DAPIDER\r\n"); + usb_pipe_table[pipe].HostDescBank[0].STATUS_PIPE.reg &= ~USB_HOST_STATUS_PIPE_DAPIDER; + xfer_result = XFER_RESULT_FAILED; + } + if (usb_pipe_table[pipe].HostDescBank[0].STATUS_PIPE.reg & USB_HOST_STATUS_PIPE_PIDER) { + TU_LOG1("USB_HOST_STATUS_PIPE_PIDER\r\n"); + usb_pipe_table[pipe].HostDescBank[0].STATUS_PIPE.reg &= ~USB_HOST_STATUS_PIPE_PIDER; + xfer_result = XFER_RESULT_FAILED; + } + if (usb_pipe_table[pipe].HostDescBank[0].STATUS_PIPE.reg & USB_HOST_STATUS_PIPE_CRC16ER) { + TU_LOG1("USB_HOST_STATUS_PIPE_CRC16ER\r\n"); + usb_pipe_table[pipe].HostDescBank[0].STATUS_PIPE.reg &= ~USB_HOST_STATUS_PIPE_CRC16ER; + xfer_result = XFER_RESULT_FAILED; + } + if (usb_pipe_table[pipe].HostDescBank[0].STATUS_PIPE.reg & USB_HOST_STATUS_PIPE_TOUTER) { + usb_pipe_table[pipe].HostDescBank[0].STATUS_PIPE.reg &= ~USB_HOST_STATUS_PIPE_TOUTER; + + if ((USB->HOST.HostPipe[pipe].PCFG.bit.PTYPE == USB_HOST_PTYPE_INT) && + (tu_edpt_dir(pipe_status->ep_addr) == TUSB_DIR_IN)) { + // ignore timeouts from INT pipes + } else { + if (xfer_result == XFER_RESULT_INVALID) { + xfer_result = XFER_RESULT_TIMEOUT; + } + } + } + + // prevent PERR from too high error counts, that is handled by TinyUSB anyways + usb_pipe_table[pipe].HostDescBank[0].STATUS_PIPE.bit.ERCNT = 0; + + // no updates + if (xfer_result == XFER_RESULT_INVALID) { + continue; + } + + // continue / complete transfer + if (samd_on_xfer(pipe, xfer_result)) { + hcd_event_xfer_complete(pipe_status->dev_addr, pipe_status->ep_addr, pipe_status->xfer_length, xfer_result, true); + } + } +} + +// Initialize controller to host mode +bool hcd_init(uint8_t rhport, const tusb_rhport_init_t* rh_init) { + TU_ASSERT(rhport == 0); + (void) rh_init; + fake_fnum = 0; + + // reset to get in a clean state. + USB->HOST.CTRLA.bit.SWRST = 1; + while (USB->HOST.SYNCBUSY.bit.SWRST == 0) + ; + while (USB->HOST.SYNCBUSY.bit.SWRST == 1) + ; + + // load pad calibration + USB->HOST.PADCAL.bit.TRANSP = (*((uint32_t*) USB_FUSES_TRANSP_ADDR) & USB_FUSES_TRANSP_Msk) >> USB_FUSES_TRANSP_Pos; + USB->HOST.PADCAL.bit.TRANSN = (*((uint32_t*) USB_FUSES_TRANSN_ADDR) & USB_FUSES_TRANSN_Msk) >> USB_FUSES_TRANSN_Pos; + USB->HOST.PADCAL.bit.TRIM = (*((uint32_t*) USB_FUSES_TRIM_ADDR) & USB_FUSES_TRIM_Msk) >> USB_FUSES_TRIM_Pos; + + USB->HOST.QOSCTRL.bit.CQOS = 3; // High Quality + USB->HOST.QOSCTRL.bit.DQOS = 3; // High Quality + + // configure host-mode + samd_free_all_pipes(); // initializes pipe handles and usb_pipe_table + USB->HOST.DESCADD.reg = (uint32_t) (&usb_pipe_table[0]); + USB->HOST.CTRLB.reg = USB_HOST_CTRLB_SPDCONF_NORMAL | USB_HOST_CTRLB_VBUSOK; + USB->HOST.CTRLA.reg = USB_CTRLA_MODE_HOST | USB_CTRLA_ENABLE | USB_CTRLA_RUNSTDBY; + while (USB->HOST.SYNCBUSY.bit.ENABLE == 1) + ; + + // enable basic USB interrupts + USB->HOST.INTFLAG.reg |= USB->HOST.INTFLAG.reg; // clear pending + USB->HOST.INTENCLR.reg = USB_HOST_INTENCLR_MASK; + USB->HOST.INTENSET.reg = USB_HOST_INTENSET_DCONN; + USB->HOST.INTENSET.reg = USB_HOST_INTENSET_DDISC; + USB->HOST.INTENSET.reg = USB_HOST_INTENSET_WAKEUP; + USB->HOST.INTENSET.reg = USB_HOST_INTENSET_RST; + + return true; +} + +#if TU_CHECK_MCU(OPT_MCU_SAMD51, OPT_MCU_SAME5X) + +// Enable USB interrupt +void hcd_int_enable(uint8_t rhport) +{ + (void) rhport; + NVIC_EnableIRQ(USB_0_IRQn); + NVIC_EnableIRQ(USB_1_IRQn); + NVIC_EnableIRQ(USB_2_IRQn); + NVIC_EnableIRQ(USB_3_IRQn); +} + +// Disable USB interrupt +void hcd_int_disable(uint8_t rhport) +{ + (void) rhport; + NVIC_DisableIRQ(USB_3_IRQn); + NVIC_DisableIRQ(USB_2_IRQn); + NVIC_DisableIRQ(USB_1_IRQn); + NVIC_DisableIRQ(USB_0_IRQn); +} + +#elif TU_CHECK_MCU(OPT_MCU_SAMD11, OPT_MCU_SAMD21, OPT_MCU_SAML2X) + +// Enable USB interrupt +void hcd_int_enable(uint8_t rhport) +{ + (void) rhport; + NVIC_EnableIRQ(USB_IRQn); +} + +// Disable USB interrupt +void hcd_int_disable(uint8_t rhport) +{ + (void) rhport; + NVIC_DisableIRQ(USB_IRQn); +} + +#else + +#error "No implementation available for hcd_int_enable / hcd_int_disable" + +#endif + +// Get frame number (1ms) +uint32_t hcd_frame_number(uint8_t rhport) +{ + (void) rhport; + +// SAMD Quirk #2: +// FNUM is stalled before enumeration of Low-Speed devices. +// internal frame counter can be used as workaround (not very accurate) +#ifdef HCD_SAMD_FAKE_FNUM + uint8_t start, current, prev; + uint8_t loop_count = (USB->HOST.STATUS.bit.SPEED == TUSB_SPEED_HIGH) ? 8 : 1; + for (uint8_t i = 0; i < loop_count; i++) { + start = USB->HOST.FLENHIGH.reg; + current = start; + // wait until wrap-around + prev = current; + while (current <= start) { + current = USB->HOST.FLENHIGH.reg; + if (current > prev) + break; + prev = current; + } + // wait until start is reached again + prev = current; + while (current > start) { + current = USB->HOST.FLENHIGH.reg; + if (current > prev) + break; + prev = current; + } + } + fake_fnum += 1; + return fake_fnum; +#else + return USB->HOST.FNUM.bit.FNUM; +#endif // HCD_SAMD_FAKE_FNUM +} + +//--------------------------------------------------------------------+ +// Port API +//--------------------------------------------------------------------+ + +// Get the current connect status of roothub port +bool hcd_port_connect_status(uint8_t rhport) +{ + TU_ASSERT(rhport == 0); + return USB->HOST.STATUS.bit.LINESTATE != 0; +} + +// Reset USB bus on the port. Return immediately, bus reset sequence may not be +// complete. Some port would require hcd_port_reset_end() to be invoked after 10ms to +// complete the reset sequence. +void hcd_port_reset(uint8_t rhport) +{ + hcd_int_disable(rhport); + samd_free_all_pipes(); + USB->HOST.INTFLAG.reg |= USB->HOST.INTFLAG.reg; // clear pending + USB->HOST.CTRLB.bit.BUSRESET = 1; + fake_fnum = 0; +} + +// Complete bus reset sequence, may be required by some controllers +void hcd_port_reset_end(uint8_t rhport) +{ + while (USB->HOST.INTFLAG.bit.RST == 0) + ; + USB->HOST.INTFLAG.reg = USB_HOST_INTFLAG_RST; + USB->HOST.CTRLB.bit.SOFE = 1; + hcd_int_enable(rhport); +} + +// Get port link speed +tusb_speed_t hcd_port_speed_get(uint8_t rhport) +{ + (void) rhport; + + switch (USB->HOST.STATUS.bit.SPEED) { + case 0: + return TUSB_SPEED_FULL; + case 1: + return TUSB_SPEED_LOW; + case 2: + return TUSB_SPEED_HIGH; + default: + return TUSB_SPEED_INVALID; + } +} + +// HCD closes all opened endpoints belong to this device +void hcd_device_close(uint8_t rhport, uint8_t dev_addr) +{ + (void) rhport; + + for (uint8_t pipe = 0; pipe < USB_PIPE_NUM; pipe++) { + volatile usb_pipe_status_t* pipe_status = &usb_pipe_status_table[pipe]; + if (pipe_status->dev_addr == dev_addr) { + samd_free_pipe(pipe); + } + } +} + +//--------------------------------------------------------------------+ +// Endpoints API +//--------------------------------------------------------------------+ + +// Open an endpoint +bool hcd_edpt_open(uint8_t rhport, uint8_t dev_addr, tusb_desc_endpoint_t const* ep_desc) +{ + TU_ASSERT(rhport == 0); + + uint8_t pipe; + volatile usb_pipe_status_t* pipe_status; + const uint8_t ep_addr = ep_desc->bEndpointAddress; + const uint8_t bmAttributes = (ep_desc->bmAttributes.xfer) | + ((ep_desc->bmAttributes.sync) << 2) | + ((ep_desc->bmAttributes.usage) << 4); + + // configure the pipe + pipe = samd_configure_pipe(dev_addr, ep_addr); + if (pipe >= USB_PIPE_NUM) { + return false; + } + + // initial configuration + pipe_status = &usb_pipe_status_table[pipe]; + USB->HOST.HostPipe[pipe].PCFG.reg &= ~USB_HOST_PCFG_PTYPE_Msk; + USB->HOST.HostPipe[pipe].PCFG.bit.PTYPE = bmAttributes + 1; + USB->HOST.HostPipe[pipe].BINTERVAL.reg = ep_desc->bInterval; + USB->HOST.HostPipe[pipe].PSTATUSCLR.reg = USB_HOST_PSTATUSCLR_DTGL; + USB->HOST.HostPipe[pipe].PINTENCLR.reg = USB_HOST_PINTENCLR_MASK; + pipe_status->max_packet_size = ep_desc->wMaxPacketSize; + usb_pipe_table[pipe].HostDescBank[0].PCKSIZE.bit.SIZE = USB_PCKSIZE_ENUM(pipe_status->max_packet_size); + usb_pipe_table[pipe].HostDescBank[0].PCKSIZE.bit.AUTO_ZLP = 0; + + return true; +} + +// Submit a special transfer to send 8-byte Setup Packet, when complete +// hcd_event_xfer_complete() must be invoked +bool hcd_setup_send(uint8_t rhport, uint8_t dev_addr, uint8_t const setup_packet[8]) +{ + TU_ASSERT(rhport == 0); + + uint8_t pipe; + volatile usb_pipe_status_t* pipe_status; + + // configure the pipe + pipe = samd_configure_pipe(dev_addr, 0); + if (pipe >= USB_PIPE_NUM) { + return false; + } + + // prepare transfer + pipe_status = &usb_pipe_status_table[pipe]; + usb_pipe_table[pipe].HostDescBank[0].ADDR.reg = (uint32_t) setup_packet; + pipe_status->xfer_remaining = 8; + pipe_status->xfer_length = 0; + usb_pipe_table[pipe].HostDescBank[0].PCKSIZE.bit.BYTE_COUNT = 8; + usb_pipe_table[pipe].HostDescBank[0].PCKSIZE.bit.MULTI_PACKET_SIZE = 0; + USB->HOST.HostPipe[pipe].PSTATUSSET.reg = USB_HOST_PSTATUSSET_BK0RDY; + + // clear pending interrupts + USB->HOST.HostPipe[pipe].PINTFLAG.reg |= USB->HOST.HostPipe[pipe].PINTFLAG.reg; + + // begin transfer + USB->HOST.HostPipe[pipe].PSTATUSCLR.reg = USB_HOST_PSTATUSCLR_PFREEZE; + + return true; +} + +// Submit a transfer, when complete hcd_event_xfer_complete() must be invoked +bool hcd_edpt_xfer(uint8_t rhport, uint8_t dev_addr, uint8_t ep_addr, uint8_t* buffer, uint16_t buflen) +{ + TU_ASSERT(rhport == 0); + + uint8_t pipe; + volatile usb_pipe_status_t* pipe_status; + + // configure the pipe + pipe = samd_configure_pipe(dev_addr, ep_addr); + if (pipe >= USB_PIPE_NUM) { + return false; + } + + // prepare transfer + pipe_status = &usb_pipe_status_table[pipe]; + usb_pipe_table[pipe].HostDescBank[0].ADDR.reg = (uint32_t) buffer; + pipe_status->xfer_remaining = buflen; + pipe_status->xfer_length = 0; + // receive data + if (tu_edpt_dir(pipe_status->ep_addr) == TUSB_DIR_IN) { + usb_pipe_table[pipe].HostDescBank[0].PCKSIZE.bit.BYTE_COUNT = 0; + usb_pipe_table[pipe].HostDescBank[0].PCKSIZE.bit.MULTI_PACKET_SIZE = pipe_status->max_packet_size; + USB->HOST.HostPipe[pipe].PSTATUSCLR.reg = USB_HOST_PSTATUSCLR_BK0RDY; + } + // send data + else { + usb_pipe_table[pipe].HostDescBank[0].PCKSIZE.bit.BYTE_COUNT = + (pipe_status->xfer_remaining < pipe_status->max_packet_size) ? pipe_status->xfer_remaining + : pipe_status->max_packet_size; + usb_pipe_table[pipe].HostDescBank[0].PCKSIZE.bit.MULTI_PACKET_SIZE = 0; + USB->HOST.HostPipe[pipe].PSTATUSSET.reg = USB_HOST_PSTATUSSET_BK0RDY; + } + + // clear pending interrupts + USB->HOST.HostPipe[pipe].PINTFLAG.reg |= USB->HOST.HostPipe[pipe].PINTFLAG.reg; + + // begin transfer + USB->HOST.HostPipe[pipe].PSTATUSCLR.reg = USB_HOST_PSTATUSCLR_PFREEZE; + + return true; +} + +// Abort a queued transfer. Note: it can only abort transfer that has not been +// started Return true if a queued transfer is aborted, false if there is no transfer +// to abort +bool hcd_edpt_abort_xfer(uint8_t rhport, uint8_t dev_addr, uint8_t ep_addr) +{ + TU_ASSERT(rhport == 0); + + uint8_t pipe; + volatile usb_pipe_status_t* pipe_status; + + TU_LOG3("hcd_edpt_abort_xfer(dev_addr=%02X, ep_addr=%02X)=", dev_addr, ep_addr); + + // find the pipe + for (pipe = 0; pipe < USB_PIPE_NUM; pipe++) { + pipe_status = &usb_pipe_status_table[pipe]; + if ((pipe_status->dev_addr == dev_addr) && (pipe_status->ep_addr == ep_addr)) { + break; + } + } + + // pipe not found + if (pipe >= USB_PIPE_NUM) { + TU_LOG3("ERR_NO_PIPE\r\n"); + return false; + } + TU_LOG3("%d\r\n", pipe); + + // no transfer in progress + if (USB->HOST.HostPipe[pipe].PSTATUS.bit.PFREEZE == 1) { + return false; + } + + // abort the transfer + USB->HOST.HostPipe[pipe].PSTATUSSET.reg = USB_HOST_PSTATUSSET_PFREEZE; + pipe_status = &usb_pipe_status_table[pipe]; + pipe_status->xfer_length = 0; + pipe_status->xfer_remaining = 0; + + return true; +} + +// clear stall, data toggle is also reset to DATA0 +bool hcd_edpt_clear_stall(uint8_t rhport, uint8_t dev_addr, uint8_t ep_addr) +{ + TU_ASSERT(rhport == 0); + + uint8_t pipe; + volatile usb_pipe_status_t* pipe_status; + + TU_LOG3("hcd_edpt_clear_stall(dev_addr=%02X, ep_addr=%02X)=", dev_addr, ep_addr); + + // find the pipe + for (pipe = 0; pipe < USB_PIPE_NUM; pipe++) { + pipe_status = &usb_pipe_status_table[pipe]; + if ((pipe_status->dev_addr == dev_addr) && (pipe_status->ep_addr == ep_addr)) { + break; + } + } + + // pipe not found + if (pipe >= USB_PIPE_NUM) { + TU_LOG3("ERR_NO_PIPE\r\n"); + return false; + } + TU_LOG3("%d\r\n", pipe); + + // clear pending interrupts + USB->HOST.HostPipe[pipe].PINTFLAG.reg |= USB->HOST.HostPipe[pipe].PINTFLAG.reg; + + // clear stalled state + USB->HOST.HostPipe[pipe].PSTATUSSET.reg = USB_HOST_PSTATUSSET_PFREEZE; + USB->HOST.HostPipe[pipe].PSTATUSCLR.reg = USB_HOST_PSTATUSCLR_DTGL; + + return true; +} + +#endif diff --git a/libraries/Adafruit_TinyUSB_Arduino/src/portable/nordic/nrf5x/dcd_nrf5x.c b/libraries/Adafruit_TinyUSB_Arduino/src/portable/nordic/nrf5x/dcd_nrf5x.c new file mode 100644 index 0000000..befbaa3 --- /dev/null +++ b/libraries/Adafruit_TinyUSB_Arduino/src/portable/nordic/nrf5x/dcd_nrf5x.c @@ -0,0 +1,1096 @@ +/* + * 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. + * + * This file is part of the TinyUSB stack. + */ + +#include "tusb_option.h" + +#if CFG_TUD_ENABLED && CFG_TUSB_MCU == OPT_MCU_NRF5X + +#include + +// Suppress warning caused by nrfx driver +#ifdef __GNUC__ +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wcast-qual" +#pragma GCC diagnostic ignored "-Wcast-align" +#pragma GCC diagnostic ignored "-Wunused-parameter" +#pragma GCC diagnostic ignored "-Wconversion" +#pragma GCC diagnostic ignored "-Wsign-conversion" +#endif + +#include "nrf.h" +#include "nrfx_clock.h" +#include "nrf_erratas.h" + +#ifdef __GNUC__ +#pragma GCC diagnostic pop +#endif + +#include "device/dcd.h" + +// TODO remove later +#include "device/usbd.h" +#include "device/usbd_pvt.h" // to use defer function helper + +#if CFG_TUSB_OS == OPT_OS_MYNEWT +#include "mcu/mcu.h" +#endif + +/* Try to detect nrfx version if not configured with CFG_TUD_NRF_NRFX_VERSION + * nrfx v1 and v2 are concurrently developed. There is no NRFX_VERSION only MDK VERSION which is as follows: + * - v3.0.0: 8.53.1 (conflict with v2.11.0), v3.1.0: 8.55.0 ... + * - v2.11.0: 8.53.1, v2.6.0: 8.44.1, v2.5.0: 8.40.2, v2.4.0: 8.37.0, v2.3.0: 8.35.0, v2.2.0: 8.32.1, v2.1.0: 8.30.2, + * v2.0.0: 8.29.0 + * - v1.9.0: 8.40.3, v1.8.6: 8.35.0 (conflict with v2.3.0), v1.8.5: 8.32.3, v1.8.4: 8.32.1 (conflict with v2.2.0), + * v1.8.2: 8.32.1 (conflict with v2.2.0), v1.8.1: 8.27.1 + * Therefore the check for v1 would be: + * - MDK < 8.29.0 (v2.0), MDK == 8.32.3, 8.40.3 + * - in case of conflict User of those version must upgrade to other 1.x version or set CFG_TUD_NRF_NRFX_VERSION + */ +#ifndef CFG_TUD_NRF_NRFX_VERSION + #define MDK_VERSION (10000 * MDK_MAJOR_VERSION + 100 * MDK_MINOR_VERSION + MDK_MICRO_VERSION) + + #if MDK_VERSION < 82900 || MDK_VERSION == 83203 || MDK_VERSION == 84003 + // nrfx <= 1.8.1, or 1.8.5 or 1.9.0 + #define CFG_TUD_NRF_NRFX_VERSION 1 + #elif MDK_VERSION < 85301 + #define CFG_TUD_NRF_NRFX_VERSION 2 + #elif MDK_VERSION < 87300 + #define CFG_TUD_NRF_NRFX_VERSION 3 + #else + #define CFG_TUD_NRF_NRFX_VERSION 4 + #endif +#endif + +/*------------------------------------------------------------------*/ +/* MACRO TYPEDEF CONSTANT ENUM + *------------------------------------------------------------------*/ +enum { + // Max allowed by USB specs + MAX_PACKET_SIZE = 64, + + // Mask of all END event (IN & OUT) for all endpoints. ENDEPIN0-7, ENDEPOUT0-7, ENDISOIN, ENDISOOUT + EDPT_END_ALL_MASK = (0xff << USBD_INTEN_ENDEPIN0_Pos) | (0xff << USBD_INTEN_ENDEPOUT0_Pos) | + USBD_INTENCLR_ENDISOIN_Msk | USBD_INTEN_ENDISOOUT_Msk +}; + +enum { + EP_ISO_NUM = 8, // Endpoint number is fixed (8) for ISOOUT and ISOIN + EP_CBI_COUNT = 8 // Control Bulk Interrupt endpoints count +}; + +// Transfer Descriptor +typedef struct { + uint8_t* buffer; + uint16_t total_len; + volatile uint16_t actual_len; + uint16_t mps; // max packet size + + // nRF will auto accept OUT packet after DMA is done + // indicate packet is already ACK + volatile bool data_received; + volatile bool started; + + // Set to true when data was transferred from RAM to ISO IN output buffer. + // New data can be put in ISO IN output buffer after SOF. + bool iso_in_transfer_ready; + +} xfer_td_t; + +// Data for managing dcd +static struct { + // All 8 endpoints including control IN & OUT (offset 1) + // +1 for ISO endpoints + xfer_td_t xfer[EP_CBI_COUNT + 1][2]; + + // nRF can only carry one DMA at a time, this is used to guard the access to EasyDMA + atomic_flag dma_running; + + // Track whether sof has been manually enabled + bool sof_enabled; +} _dcd; + +/*------------------------------------------------------------------*/ +/* Control / Bulk / Interrupt (CBI) Transfer + *------------------------------------------------------------------*/ + +// check if we are in ISR +TU_ATTR_ALWAYS_INLINE static inline bool is_in_isr(void) { + return (SCB->ICSR & SCB_ICSR_VECTACTIVE_Msk) ? true : false; +} + +// helper to start DMA +static void start_dma(volatile uint32_t* reg_startep) { + (*reg_startep) = 1; + __ISB(); + __DSB(); + + // TASKS_EP0STATUS, TASKS_EP0RCVOUT seem to need EasyDMA to be available + // However these don't trigger any DMA transfer and got ENDED event subsequently + // Therefore dma_pending is corrected right away + if ((reg_startep == &NRF_USBD->TASKS_EP0STATUS) || (reg_startep == &NRF_USBD->TASKS_EP0RCVOUT)) { + atomic_flag_clear(&_dcd.dma_running); + } +} + +static void edpt_dma_start(volatile uint32_t* reg_startep) { + if (atomic_flag_test_and_set(&_dcd.dma_running)) { + usbd_defer_func((osal_task_func_t)(uintptr_t ) edpt_dma_start, (void*) (uintptr_t) reg_startep, is_in_isr()); + } else { + start_dma(reg_startep); + } +} + +// DMA is complete +static void edpt_dma_end(void) { + atomic_flag_clear(&_dcd.dma_running); +} + +// helper getting td +static inline xfer_td_t* get_td(uint8_t epnum, uint8_t dir) { + return &_dcd.xfer[epnum][dir]; +} + +static void xact_out_dma(uint8_t epnum); + +// Function wraps xact_out_dma which wants uint8_t while usbd_defer_func wants void (*)(void *) +static void xact_out_dma_wrapper(void* epnum) { + xact_out_dma((uint8_t) ((uintptr_t) epnum)); +} + +// Start DMA to move data from Endpoint -> RAM +static void xact_out_dma(uint8_t epnum) { + xfer_td_t* xfer = get_td(epnum, TUSB_DIR_OUT); + uint32_t xact_len; + + // DMA can't be active during read of SIZE.EPOUT or SIZE.ISOOUT, so try to lock, + // If already running defer call regardless if it was called from ISR or task, + if (atomic_flag_test_and_set(&_dcd.dma_running)) { + usbd_defer_func((osal_task_func_t) xact_out_dma_wrapper, (void*) (uint32_t) epnum, is_in_isr()); + return; + } + if (epnum == EP_ISO_NUM) { + xact_len = NRF_USBD->SIZE.ISOOUT; + // If ZERO bit is set, ignore ISOOUT length + if (xact_len & USBD_SIZE_ISOOUT_ZERO_Msk) { + xact_len = 0; + atomic_flag_clear(&_dcd.dma_running); + } else { + if (xfer->started) { + // Trigger DMA move data from Endpoint -> SRAM + NRF_USBD->ISOOUT.PTR = (uint32_t) xfer->buffer; + NRF_USBD->ISOOUT.MAXCNT = xact_len; + + start_dma(&NRF_USBD->TASKS_STARTISOOUT); + } else { + atomic_flag_clear(&_dcd.dma_running); + } + } + } else { + // limit xact len to remaining length + xact_len = tu_min16((uint16_t) NRF_USBD->SIZE.EPOUT[epnum], xfer->total_len - xfer->actual_len); + + // Trigger DMA move data from Endpoint -> SRAM + NRF_USBD->EPOUT[epnum].PTR = (uint32_t) xfer->buffer; + NRF_USBD->EPOUT[epnum].MAXCNT = xact_len; + + start_dma(&NRF_USBD->TASKS_STARTEPOUT[epnum]); + } +} + +// Prepare for a CBI transaction IN, call at the start +// it start DMA to transfer data from RAM -> Endpoint +static void xact_in_dma(uint8_t epnum) { + xfer_td_t* xfer = get_td(epnum, TUSB_DIR_IN); + + // Each transaction is up to Max Packet Size + uint16_t const xact_len = tu_min16(xfer->total_len - xfer->actual_len, xfer->mps); + + NRF_USBD->EPIN[epnum].PTR = (uint32_t) xfer->buffer; + NRF_USBD->EPIN[epnum].MAXCNT = xact_len; + + edpt_dma_start(&NRF_USBD->TASKS_STARTEPIN[epnum]); +} + +//--------------------------------------------------------------------+ +// Controller API +//--------------------------------------------------------------------+ +bool dcd_init(uint8_t rhport, const tusb_rhport_init_t* rh_init) { + (void) rhport; + (void) rh_init; + TU_LOG2("dcd init\r\n"); + return true; +} + +void dcd_int_enable(uint8_t rhport) { + (void) rhport; + NVIC_EnableIRQ(USBD_IRQn); +} + +void dcd_int_disable(uint8_t rhport) { + (void) rhport; + NVIC_DisableIRQ(USBD_IRQn); +} + +void dcd_set_address(uint8_t rhport, uint8_t dev_addr) { + (void) rhport; + (void) dev_addr; + // Set Address is automatically update by hw controller, nothing to do + + // Enable usbevent for suspend and resume detection + // Since the bus signal D+/D- are stable now. + + // Clear current pending first + NRF_USBD->EVENTCAUSE |= NRF_USBD->EVENTCAUSE; + NRF_USBD->EVENTS_USBEVENT = 0; + + NRF_USBD->INTENSET = USBD_INTEN_USBEVENT_Msk; +} + +void dcd_remote_wakeup(uint8_t rhport) { + (void) rhport; + + // Bring controller out of low power mode + // will start wakeup when USBWUALLOWED is set + NRF_USBD->LOWPOWER = 0; +} + +// disconnect by disabling internal pull-up resistor on D+/D- +void dcd_disconnect(uint8_t rhport) { + (void) rhport; + NRF_USBD->USBPULLUP = 0; + + // Disable Pull-up does not trigger Power USB Removed, in fact it have no + // impact on the USB Power status at all -> need to submit unplugged event to the stack. + dcd_event_bus_signal(0, DCD_EVENT_UNPLUGGED, false); +} + +// connect by enabling internal pull-up resistor on D+/D- +void dcd_connect(uint8_t rhport) { + (void) rhport; + NRF_USBD->USBPULLUP = 1; +} + +void dcd_sof_enable(uint8_t rhport, bool en) { + (void) rhport; + if (en) { + _dcd.sof_enabled = true; + NRF_USBD->INTENSET = USBD_INTENSET_SOF_Msk; + } else { + _dcd.sof_enabled = false; + NRF_USBD->INTENCLR = USBD_INTENCLR_SOF_Msk; + } +} + +//--------------------------------------------------------------------+ +// Endpoint API +//--------------------------------------------------------------------+ +bool dcd_edpt_open(uint8_t rhport, tusb_desc_endpoint_t const* desc_edpt) { + (void) rhport; + + uint8_t const ep_addr = desc_edpt->bEndpointAddress; + uint8_t const epnum = tu_edpt_number(ep_addr); + uint8_t const dir = tu_edpt_dir(ep_addr); + + _dcd.xfer[epnum][dir].mps = tu_edpt_packet_size(desc_edpt); + + if (desc_edpt->bmAttributes.xfer != TUSB_XFER_ISOCHRONOUS) { + if (dir == TUSB_DIR_OUT) { + NRF_USBD->INTENSET = TU_BIT(USBD_INTEN_ENDEPOUT0_Pos + epnum); + NRF_USBD->EPOUTEN |= TU_BIT(epnum); + + // Write any value to SIZE register will allow nRF to ACK/accept data + NRF_USBD->SIZE.EPOUT[epnum] = 0; + } else { + NRF_USBD->INTENSET = TU_BIT(USBD_INTEN_ENDEPIN0_Pos + epnum); + NRF_USBD->EPINEN |= TU_BIT(epnum); + } + // clear stall and reset DataToggle + NRF_USBD->EPSTALL = (USBD_EPSTALL_STALL_UnStall << USBD_EPSTALL_STALL_Pos) | ep_addr; + NRF_USBD->DTOGGLE = (USBD_DTOGGLE_VALUE_Data0 << USBD_DTOGGLE_VALUE_Pos) | ep_addr; + } else { + TU_ASSERT(epnum == EP_ISO_NUM); + if (dir == TUSB_DIR_OUT) { + // SPLIT ISO buffer when ISO IN endpoint is already opened. + if (_dcd.xfer[EP_ISO_NUM][TUSB_DIR_IN].mps) NRF_USBD->ISOSPLIT = USBD_ISOSPLIT_SPLIT_HalfIN; + + // Clear old events + NRF_USBD->EVENTS_ENDISOOUT = 0; + + // Clear SOF event in case interrupt was not enabled yet. + if ((NRF_USBD->INTEN & USBD_INTEN_SOF_Msk) == 0) NRF_USBD->EVENTS_SOF = 0; + + // Enable SOF and ISOOUT interrupts, and ISOOUT endpoint. + NRF_USBD->INTENSET = USBD_INTENSET_ENDISOOUT_Msk | USBD_INTENSET_SOF_Msk; + NRF_USBD->EPOUTEN |= USBD_EPOUTEN_ISOOUT_Msk; + } else { + NRF_USBD->EVENTS_ENDISOIN = 0; + + // SPLIT ISO buffer when ISO OUT endpoint is already opened. + if (_dcd.xfer[EP_ISO_NUM][TUSB_DIR_OUT].mps) NRF_USBD->ISOSPLIT = USBD_ISOSPLIT_SPLIT_HalfIN; + + // Clear SOF event in case interrupt was not enabled yet. + if ((NRF_USBD->INTEN & USBD_INTEN_SOF_Msk) == 0) NRF_USBD->EVENTS_SOF = 0; + + // Enable SOF and ISOIN interrupts, and ISOIN endpoint. + NRF_USBD->INTENSET = USBD_INTENSET_ENDISOIN_Msk | USBD_INTENSET_SOF_Msk; + NRF_USBD->EPINEN |= USBD_EPINEN_ISOIN_Msk; + } + } + + __ISB(); + __DSB(); + + return true; +} + +void dcd_edpt_close_all(uint8_t rhport) { + // disable interrupt to prevent race condition + dcd_int_disable(rhport); + + // disable all non-control (bulk + interrupt) endpoints + for (uint8_t ep = 1; ep < EP_CBI_COUNT; ep++) { + NRF_USBD->INTENCLR = TU_BIT(USBD_INTEN_ENDEPOUT0_Pos + ep) | TU_BIT(USBD_INTEN_ENDEPIN0_Pos + ep); + + NRF_USBD->TASKS_STARTEPIN[ep] = 0; + NRF_USBD->TASKS_STARTEPOUT[ep] = 0; + + tu_memclr(_dcd.xfer[ep], 2 * sizeof(xfer_td_t)); + } + + // disable both ISO + NRF_USBD->INTENCLR = USBD_INTENCLR_SOF_Msk | USBD_INTENCLR_ENDISOOUT_Msk | USBD_INTENCLR_ENDISOIN_Msk; + NRF_USBD->ISOSPLIT = USBD_ISOSPLIT_SPLIT_OneDir; + + NRF_USBD->TASKS_STARTISOIN = 0; + NRF_USBD->TASKS_STARTISOOUT = 0; + + tu_memclr(_dcd.xfer[EP_ISO_NUM], 2 * sizeof(xfer_td_t)); + + // de-activate all non-control + NRF_USBD->EPOUTEN = 1UL; + NRF_USBD->EPINEN = 1UL; + + dcd_int_enable(rhport); +} + +void dcd_edpt_close(uint8_t rhport, uint8_t ep_addr) { + (void) rhport; + + uint8_t const epnum = tu_edpt_number(ep_addr); + uint8_t const dir = tu_edpt_dir(ep_addr); + + if (epnum != EP_ISO_NUM) { + // CBI + if (dir == TUSB_DIR_OUT) { + NRF_USBD->INTENCLR = TU_BIT(USBD_INTEN_ENDEPOUT0_Pos + epnum); + NRF_USBD->EPOUTEN &= ~TU_BIT(epnum); + } else { + NRF_USBD->INTENCLR = TU_BIT(USBD_INTEN_ENDEPIN0_Pos + epnum); + NRF_USBD->EPINEN &= ~TU_BIT(epnum); + } + } else { + _dcd.xfer[EP_ISO_NUM][dir].mps = 0; + // ISO + if (dir == TUSB_DIR_OUT) { + NRF_USBD->INTENCLR = USBD_INTENCLR_ENDISOOUT_Msk; + NRF_USBD->EPOUTEN &= ~USBD_EPOUTEN_ISOOUT_Msk; + NRF_USBD->EVENTS_ENDISOOUT = 0; + } else { + NRF_USBD->INTENCLR = USBD_INTENCLR_ENDISOIN_Msk; + NRF_USBD->EPINEN &= ~USBD_EPINEN_ISOIN_Msk; + } + // One of the ISO endpoints closed, no need to split buffers any more. + NRF_USBD->ISOSPLIT = USBD_ISOSPLIT_SPLIT_OneDir; + // When both ISO endpoint are close there is no need for SOF any more. + if (_dcd.xfer[EP_ISO_NUM][TUSB_DIR_IN].mps + _dcd.xfer[EP_ISO_NUM][TUSB_DIR_OUT].mps == 0) + NRF_USBD->INTENCLR = USBD_INTENCLR_SOF_Msk; + } + _dcd.xfer[epnum][dir].started = false; + __ISB(); + __DSB(); +} + +#if 0 +bool dcd_edpt_iso_alloc(uint8_t rhport, uint8_t ep_addr, uint16_t largest_packet_size) { + (void)rhport; + (void)ep_addr; + (void)largest_packet_size; + return false; +} + +bool dcd_edpt_iso_activate(uint8_t rhport, const tusb_desc_endpoint_t *desc_ep) { + (void)rhport; + (void)desc_ep; + return false; +} +#endif + +bool dcd_edpt_xfer(uint8_t rhport, uint8_t ep_addr, uint8_t* buffer, uint16_t total_bytes, bool is_isr) { + (void) rhport; + (void) is_isr; + + uint8_t const epnum = tu_edpt_number(ep_addr); + uint8_t const dir = tu_edpt_dir(ep_addr); + + xfer_td_t* xfer = get_td(epnum, dir); + + TU_ASSERT(!xfer->started); + xfer->buffer = buffer; + xfer->total_len = total_bytes; + xfer->actual_len = 0; + + // Control endpoint with zero-length packet and opposite direction to 1st request byte --> status stage + bool const control_status = (epnum == 0 && total_bytes == 0 && dir != tu_edpt_dir((uint8_t)NRF_USBD->BMREQUESTTYPE)); + + if (control_status) { + // The nRF doesn't interrupt on status transmit so we queue up a success response. + dcd_event_xfer_complete(0, ep_addr, 0, XFER_RESULT_SUCCESS, is_in_isr()); + + // Status Phase also requires EasyDMA has to be available as well !!!! + edpt_dma_start(&NRF_USBD->TASKS_EP0STATUS); + } else if (dir == TUSB_DIR_OUT) { + xfer->started = true; + if (epnum == 0) { + // Accept next Control Out packet. TASKS_EP0RCVOUT also require EasyDMA + edpt_dma_start(&NRF_USBD->TASKS_EP0RCVOUT); + } else { + // started just set, it could start DMA transfer if interrupt was trigger after this line + // code only needs to start transfer (from Endpoint to RAM) when data_received was set + // before started was set. If started is NOT set but data_received is, it means that + // current transfer was already finished and next data is already present in endpoint and + // can be consumed by future transfer + __ISB(); + __DSB(); + if (xfer->data_received && xfer->started) { + // Data is already received previously + // start DMA to copy to SRAM + xfer->data_received = false; + xact_out_dma(epnum); + } else { + // nRF auto accept next Bulk/Interrupt OUT packet + // nothing to do + } + } + } else { + // Start DMA to copy data from RAM -> Endpoint + xact_in_dma(epnum); + } + + return true; +} + +void dcd_edpt_stall(uint8_t rhport, uint8_t ep_addr) { + (void) rhport; + + uint8_t const epnum = tu_edpt_number(ep_addr); + uint8_t const dir = tu_edpt_dir(ep_addr); + + xfer_td_t* xfer = get_td(epnum, dir); + + if (epnum == 0) { + NRF_USBD->TASKS_EP0STALL = 1; + } else if (epnum != EP_ISO_NUM) { + NRF_USBD->EPSTALL = (USBD_EPSTALL_STALL_Stall << USBD_EPSTALL_STALL_Pos) | ep_addr; + + // Note: nRF can auto ACK packet OUT before get stalled. + // There maybe data in endpoint fifo already, we need to pull it out + if ((dir == TUSB_DIR_OUT) && xfer->data_received) { + xfer->data_received = false; + xact_out_dma(epnum); + } + } + + __ISB(); + __DSB(); +} + +void dcd_edpt_clear_stall(uint8_t rhport, uint8_t ep_addr) { + (void) rhport; + uint8_t const epnum = tu_edpt_number(ep_addr); + uint8_t const dir = tu_edpt_dir(ep_addr); + + if (epnum != 0 && epnum != EP_ISO_NUM) { + // reset data toggle to DATA0 + // First write this register with VALUE=Nop to select the endpoint, then either read it to get the status from + // VALUE, or write it again with VALUE=Data0 or Data1 + NRF_USBD->DTOGGLE = ep_addr; + NRF_USBD->DTOGGLE = (USBD_DTOGGLE_VALUE_Data0 << USBD_DTOGGLE_VALUE_Pos) | ep_addr; + + // clear stall + NRF_USBD->EPSTALL = (USBD_EPSTALL_STALL_UnStall << USBD_EPSTALL_STALL_Pos) | ep_addr; + + // Write any value to SIZE register will allow nRF to ACK/accept data + if (dir == TUSB_DIR_OUT) NRF_USBD->SIZE.EPOUT[epnum] = 0; + + __ISB(); + __DSB(); + } +} + +/*------------------------------------------------------------------*/ +/* Interrupt Handler + *------------------------------------------------------------------*/ +static void bus_reset(void) { + // 6.35.6 USB controller automatically disabled all endpoints (except control) + NRF_USBD->EPOUTEN = 1UL; + NRF_USBD->EPINEN = 1UL; + + for (int i = 0; i < 8; i++) { + NRF_USBD->TASKS_STARTEPIN[i] = 0; + NRF_USBD->TASKS_STARTEPOUT[i] = 0; + } + + NRF_USBD->TASKS_STARTISOIN = 0; + NRF_USBD->TASKS_STARTISOOUT = 0; + + // Clear USB Event Interrupt + NRF_USBD->EVENTS_USBEVENT = 0; + NRF_USBD->EVENTCAUSE |= NRF_USBD->EVENTCAUSE; + + // Reset interrupt + NRF_USBD->INTENCLR = NRF_USBD->INTEN; + NRF_USBD->INTENSET = USBD_INTEN_USBRESET_Msk | USBD_INTEN_USBEVENT_Msk | USBD_INTEN_EPDATA_Msk | + USBD_INTEN_EP0SETUP_Msk | USBD_INTEN_EP0DATADONE_Msk | USBD_INTEN_ENDEPIN0_Msk | + USBD_INTEN_ENDEPOUT0_Msk; + + tu_varclr(&_dcd); + _dcd.xfer[0][TUSB_DIR_IN].mps = MAX_PACKET_SIZE; + _dcd.xfer[0][TUSB_DIR_OUT].mps = MAX_PACKET_SIZE; +} + +void dcd_int_handler(uint8_t rhport) { + (void) rhport; + + uint32_t const inten = NRF_USBD->INTEN; + uint32_t int_status = 0; + + volatile uint32_t* regevt = &NRF_USBD->EVENTS_USBRESET; + + for (uint8_t i = 0; i < USBD_INTEN_EPDATA_Pos + 1; i++) { + if (tu_bit_test(inten, i) && regevt[i]) { + int_status |= TU_BIT(i); + + // event clear + regevt[i] = 0; + __ISB(); + __DSB(); + } + } + + if (int_status & USBD_INTEN_USBRESET_Msk) { + bus_reset(); + dcd_event_bus_reset(0, TUSB_SPEED_FULL, true); + } + + // ISOIN: Data was moved to endpoint buffer, client will be notified in SOF + if (int_status & USBD_INTEN_ENDISOIN_Msk) { + xfer_td_t* xfer = get_td(EP_ISO_NUM, TUSB_DIR_IN); + + xfer->actual_len = NRF_USBD->ISOIN.AMOUNT; + // Data transferred from RAM to endpoint output buffer. + // Next transfer can be scheduled after SOF. + xfer->iso_in_transfer_ready = true; + } + + if (int_status & USBD_INTEN_SOF_Msk) { + bool iso_enabled = false; + + // ISOOUT: Transfer data gathered in previous frame from buffer to RAM + if (NRF_USBD->EPOUTEN & USBD_EPOUTEN_ISOOUT_Msk) { + iso_enabled = true; + // Transfer from endpoint to RAM only if data is not corrupted + if ((int_status & USBD_INTEN_USBEVENT_Msk) == 0 || + (NRF_USBD->EVENTCAUSE & USBD_EVENTCAUSE_ISOOUTCRC_Msk) == 0) { + xact_out_dma(EP_ISO_NUM); + } + } + + // ISOIN: Notify client that data was transferred + if (NRF_USBD->EPINEN & USBD_EPINEN_ISOIN_Msk) { + iso_enabled = true; + + xfer_td_t* xfer = get_td(EP_ISO_NUM, TUSB_DIR_IN); + if (xfer->iso_in_transfer_ready) { + xfer->iso_in_transfer_ready = false; + dcd_event_xfer_complete(0, EP_ISO_NUM | TUSB_DIR_IN_MASK, xfer->actual_len, XFER_RESULT_SUCCESS, true); + } + } + + if (!iso_enabled && !_dcd.sof_enabled) { + // SOF interrupt not manually enabled and ISO endpoint is not used, + // SOF is only enabled one-time for remote wakeup so we disable it now + + NRF_USBD->INTENCLR = USBD_INTENCLR_SOF_Msk; + } + + const uint32_t frame = NRF_USBD->FRAMECNTR; + dcd_event_sof(0, frame, true); + //dcd_event_bus_signal(0, DCD_EVENT_SOF, true); + } + + if (int_status & USBD_INTEN_USBEVENT_Msk) { + TU_LOG(3, "EVENTCAUSE = 0x%04" PRIX32 "\r\n", NRF_USBD->EVENTCAUSE); + + enum { + EVT_CAUSE_MASK = USBD_EVENTCAUSE_SUSPEND_Msk | USBD_EVENTCAUSE_RESUME_Msk | USBD_EVENTCAUSE_USBWUALLOWED_Msk | + USBD_EVENTCAUSE_ISOOUTCRC_Msk + }; + uint32_t const evt_cause = NRF_USBD->EVENTCAUSE & EVT_CAUSE_MASK; + NRF_USBD->EVENTCAUSE = evt_cause; // clear interrupt + + if (evt_cause & USBD_EVENTCAUSE_SUSPEND_Msk) { + // Put controller into low power mode + // Leave HFXO disable to application, since it may be used by other peripherals + NRF_USBD->LOWPOWER = 1; + + dcd_event_bus_signal(0, DCD_EVENT_SUSPEND, true); + } + + if (evt_cause & USBD_EVENTCAUSE_USBWUALLOWED_Msk) { + // USB is out of low power mode, and wakeup is allowed + // Initiate RESUME signal + NRF_USBD->DPDMVALUE = USBD_DPDMVALUE_STATE_Resume; + NRF_USBD->TASKS_DPDMDRIVE = 1; + + // There is no Resume interrupt for remote wakeup, enable SOF for to report bus ready state + // Clear SOF event in case interrupt was not enabled yet. + if ((NRF_USBD->INTEN & USBD_INTEN_SOF_Msk) == 0) NRF_USBD->EVENTS_SOF = 0; + NRF_USBD->INTENSET = USBD_INTENSET_SOF_Msk; + } + + if (evt_cause & USBD_EVENTCAUSE_RESUME_Msk) { + dcd_event_bus_signal(0, DCD_EVENT_RESUME, true); + } + } + + // Setup tokens are specific to the Control endpoint. + if (int_status & USBD_INTEN_EP0SETUP_Msk) { + uint8_t const setup[8] = { + NRF_USBD->BMREQUESTTYPE, NRF_USBD->BREQUEST, NRF_USBD->WVALUEL, NRF_USBD->WVALUEH, + NRF_USBD->WINDEXL, NRF_USBD->WINDEXH, NRF_USBD->WLENGTHL, NRF_USBD->WLENGTHH + }; + + // nrf5x hw auto handle set address, there is no need to inform usb stack + tusb_control_request_t const* request = (tusb_control_request_t const*) setup; + + if (!(TUSB_REQ_RCPT_DEVICE == request->bmRequestType_bit.recipient && + TUSB_REQ_TYPE_STANDARD == request->bmRequestType_bit.type && + TUSB_REQ_SET_ADDRESS == request->bRequest)) { + dcd_event_setup_received(0, setup, true); + } + } + + if (int_status & EDPT_END_ALL_MASK) { + // DMA complete move data from SRAM <-> Endpoint + // Must before endpoint transfer handling + edpt_dma_end(); + } + + //--------------------------------------------------------------------+ + /* Control/Bulk/Interrupt (CBI) Transfer + * + * Data flow is: + * (bus) (dma) + * Host <-------> Endpoint <-------> RAM + * + * For CBI OUT: + * - Host -> Endpoint + * EPDATA (or EP0DATADONE) interrupted, check EPDATASTATUS.EPOUT[i] + * to start DMA. For Bulk/Interrupt, this step can occur automatically (without sw), + * which means data may or may not be ready (out_received flag). + * - Endpoint -> RAM + * ENDEPOUT[i] interrupted, transaction complete, sw prepare next transaction + * + * For CBI IN: + * - RAM -> Endpoint + * ENDEPIN[i] interrupted indicate DMA is complete. HW will start + * to move data to host + * - Endpoint -> Host + * EPDATA (or EP0DATADONE) interrupted, check EPDATASTATUS.EPIN[i]. + * Transaction is complete, sw prepare next transaction + * + * Note: in both Control In and Out of Data stage from Host <-> Endpoint + * EP0DATADONE will be set as interrupt source + */ + //--------------------------------------------------------------------+ + + /* CBI OUT: Endpoint -> SRAM (aka transaction complete) + * Note: Since nRF controller auto ACK next packet without SW awareness + * We must handle this stage before Host -> Endpoint just in case 2 event happens at once + * + * ISO OUT: Transaction must fit in single packet, it can be shorter then total + * len if Host decides to sent fewer bytes, it this case transaction is also + * complete and next transfer is not initiated here like for CBI. + */ + for (uint8_t epnum = 0; epnum < EP_CBI_COUNT + 1; epnum++) { + if (tu_bit_test(int_status, USBD_INTEN_ENDEPOUT0_Pos + epnum)) { + xfer_td_t* xfer = get_td(epnum, TUSB_DIR_OUT); + uint16_t const xact_len = NRF_USBD->EPOUT[epnum].AMOUNT; + + xfer->buffer += xact_len; + xfer->actual_len += xact_len; + + // Transfer complete if transaction len < Max Packet Size or total len is transferred + if ((epnum != EP_ISO_NUM) && (xact_len == xfer->mps) && (xfer->actual_len < xfer->total_len)) { + if (epnum == 0) { + // Accept next Control Out packet. TASKS_EP0RCVOUT also require EasyDMA + edpt_dma_start(&NRF_USBD->TASKS_EP0RCVOUT); + } else { + // nRF auto accept next Bulk/Interrupt OUT packet + // nothing to do + } + } else { + TU_ASSERT(xfer->started,); + xfer->total_len = xfer->actual_len; + xfer->started = false; + + // CBI OUT complete + dcd_event_xfer_complete(0, epnum, xfer->actual_len, XFER_RESULT_SUCCESS, true); + } + } + + // Ended event for CBI IN : nothing to do + } + + // Endpoint <-> Host ( In & OUT ) + if (int_status & (USBD_INTEN_EPDATA_Msk | USBD_INTEN_EP0DATADONE_Msk)) { + uint32_t data_status = NRF_USBD->EPDATASTATUS; + NRF_USBD->EPDATASTATUS = data_status; + __ISB(); + __DSB(); + + // EP0DATADONE is set with either Control Out on IN Data + // Since EPDATASTATUS cannot be used to determine whether it is control OUT or IN. + // We will use BMREQUESTTYPE in setup packet to determine the direction + bool const is_control_in = (int_status & USBD_INTEN_EP0DATADONE_Msk) && (NRF_USBD->BMREQUESTTYPE & TUSB_DIR_IN_MASK); + bool const is_control_out = (int_status & USBD_INTEN_EP0DATADONE_Msk) && !(NRF_USBD->BMREQUESTTYPE & TUSB_DIR_IN_MASK); + + // CBI In: Endpoint -> Host (transaction complete) + for (uint8_t epnum = 0; epnum < EP_CBI_COUNT; epnum++) { + if (tu_bit_test(data_status, epnum) || (epnum == 0 && is_control_in)) { + xfer_td_t* xfer = get_td(epnum, TUSB_DIR_IN); + uint8_t const xact_len = NRF_USBD->EPIN[epnum].AMOUNT; + + xfer->buffer += xact_len; + xfer->actual_len += xact_len; + + if (xfer->actual_len < xfer->total_len) { + // Start DMA to copy next data packet + xact_in_dma(epnum); + } else { + // CBI IN complete + dcd_event_xfer_complete(0, epnum | TUSB_DIR_IN_MASK, xfer->actual_len, XFER_RESULT_SUCCESS, true); + } + } + } + + // CBI OUT: Host -> Endpoint + for (uint8_t epnum = 0; epnum < EP_CBI_COUNT; epnum++) { + if (tu_bit_test(data_status, 16 + epnum) || (epnum == 0 && is_control_out)) { + xfer_td_t* xfer = get_td(epnum, TUSB_DIR_OUT); + + if (xfer->started && xfer->actual_len < xfer->total_len) { + xact_out_dma(epnum); + } else { + // Data overflow !!! Nah, nRF will auto accept next Bulk/Interrupt OUT packet + // Mark this endpoint with data received + xfer->data_received = true; + } + } + } + } +} + +//--------------------------------------------------------------------+ +// HFCLK helper +//--------------------------------------------------------------------+ +#ifdef SOFTDEVICE_PRESENT + +// For enable/disable hfclk with SoftDevice +#include "nrf_mbr.h" +#include "nrf_sdm.h" +#include "nrf_soc.h" + +#ifndef SD_MAGIC_NUMBER + #define SD_MAGIC_NUMBER 0x51B1E5DB +#endif + +TU_ATTR_ALWAYS_INLINE static inline bool is_sd_existed(void) { + return *((uint32_t*)(SOFTDEVICE_INFO_STRUCT_ADDRESS+4)) == SD_MAGIC_NUMBER; +} + +// check if SD is existed and enabled +TU_ATTR_ALWAYS_INLINE static inline bool is_sd_enabled(void) { + if ( !is_sd_existed() ) return false; + uint8_t sd_en = false; + (void) sd_softdevice_is_enabled(&sd_en); + return sd_en; +} +#endif + +static bool hfclk_running(void) { + #ifdef SOFTDEVICE_PRESENT + if (is_sd_enabled()) { + uint32_t is_running = 0; + (void)sd_clock_hfclk_is_running(&is_running); + return (is_running ? true : false); + } + #endif + + #if CFG_TUD_NRF_NRFX_VERSION == 1 + return nrf_clock_hf_is_running(NRF_CLOCK_HFCLK_HIGH_ACCURACY); + #else + return nrf_clock_is_running(NRF_CLOCK, NRF_CLOCK_DOMAIN_HFCLK, NULL); + #endif +} + +static void hfclk_enable(void) { +#if CFG_TUSB_OS == OPT_OS_MYNEWT + usb_clock_request(); + return; +#else + + // already running, nothing to do + if (hfclk_running()) { + return; + } + + #ifdef SOFTDEVICE_PRESENT + if (is_sd_enabled()) { + (void)sd_clock_hfclk_request(); + return; + } + #endif + + #if CFG_TUD_NRF_NRFX_VERSION == 1 + nrf_clock_event_clear(NRF_CLOCK_EVENT_HFCLKSTARTED); + nrf_clock_task_trigger(NRF_CLOCK_TASK_HFCLKSTART); + #else + nrf_clock_event_clear(NRF_CLOCK, NRF_CLOCK_EVENT_HFCLKSTARTED); + nrf_clock_task_trigger(NRF_CLOCK, NRF_CLOCK_TASK_HFCLKSTART); + #endif +#endif +} + +static void hfclk_disable(void) { +#if CFG_TUSB_OS == OPT_OS_MYNEWT + usb_clock_release(); + return; +#else + +#ifdef SOFTDEVICE_PRESENT + if ( is_sd_enabled() ) { + (void)sd_clock_hfclk_release(); + return; + } +#endif + +#if CFG_TUD_NRF_NRFX_VERSION == 1 + nrf_clock_task_trigger(NRF_CLOCK_TASK_HFCLKSTOP); +#else + nrf_clock_task_trigger(NRF_CLOCK, NRF_CLOCK_TASK_HFCLKSTOP); +#endif +#endif +} + +// Power & Clock Peripheral on nRF5x to manage USB +// +// USB Bus power is managed by Power module, there are 3 VBUS power events: +// Detected, Ready, Removed. Upon these power events, This function will +// enable ( or disable ) usb & hfclk peripheral, set the usb pin pull up +// accordingly to the controller Startup/Standby Sequence in USBD 51.4 specs. +// +// Therefore this function must be called to handle USB power event by +// - nrfx_power_usbevt_init() : if Softdevice is not used or enabled +// - SoftDevice SOC event : if SD is used and enabled +void tusb_hal_nrf_power_event(uint32_t event); +void tusb_hal_nrf_power_event(uint32_t event) { + // Value is chosen to be as same as NRFX_POWER_USB_EVT_* in nrfx_power.h + enum { + USB_EVT_DETECTED = 0, + USB_EVT_REMOVED = 1, + USB_EVT_READY = 2 + }; + +#if CFG_TUSB_DEBUG >= 3 + const char* const power_evt_str[] = {"Detected", "Removed", "Ready"}; + TU_LOG(3, "Power USB event: %s\r\n", power_evt_str[event]); +#endif + + switch (event) { + case USB_EVT_DETECTED: + if (!NRF_USBD->ENABLE) { + // Prepare for receiving READY event: disable interrupt since we will blocking wait + NRF_USBD->INTENCLR = USBD_INTEN_USBEVENT_Msk; + NRF_USBD->EVENTCAUSE = USBD_EVENTCAUSE_READY_Msk; + __ISB(); + __DSB(); // for sync + +#ifdef NRF52_SERIES // NRF53 does not need this errata + // ERRATA 171, 187, 166 + if (nrf52_errata_187()) { + // CRITICAL_REGION_ENTER(); + if (*((volatile uint32_t*) (0x4006EC00)) == 0x00000000) { + *((volatile uint32_t*) (0x4006EC00)) = 0x00009375; + *((volatile uint32_t*) (0x4006ED14)) = 0x00000003; + *((volatile uint32_t*) (0x4006EC00)) = 0x00009375; + } else { + *((volatile uint32_t*) (0x4006ED14)) = 0x00000003; + } + // CRITICAL_REGION_EXIT(); + } + + if (nrf52_errata_171()) { + // CRITICAL_REGION_ENTER(); + if (*((volatile uint32_t*) (0x4006EC00)) == 0x00000000) { + *((volatile uint32_t*) (0x4006EC00)) = 0x00009375; + *((volatile uint32_t*) (0x4006EC14)) = 0x000000C0; + *((volatile uint32_t*) (0x4006EC00)) = 0x00009375; + } else { + *((volatile uint32_t*) (0x4006EC14)) = 0x000000C0; + } + // CRITICAL_REGION_EXIT(); + } +#endif + + // Enable the peripheral (will cause Ready event) + NRF_USBD->ENABLE = 1; + __ISB(); + __DSB(); // for sync + + // Enable HFCLK + hfclk_enable(); + } + break; + + case USB_EVT_READY: + // Skip if pull-up is enabled and HCLK is already running. + // Application probably call this more than necessary. + if (NRF_USBD->USBPULLUP && hfclk_running()) break; + + // Waiting for USBD peripheral enabled + while (!(USBD_EVENTCAUSE_READY_Msk & NRF_USBD->EVENTCAUSE)) {} + + NRF_USBD->EVENTCAUSE = USBD_EVENTCAUSE_READY_Msk; + __ISB(); + __DSB(); // for sync + +#ifdef NRF52_SERIES + if (nrf52_errata_171()) { + // CRITICAL_REGION_ENTER(); + if (*((volatile uint32_t*) (0x4006EC00)) == 0x00000000) { + *((volatile uint32_t*) (0x4006EC00)) = 0x00009375; + *((volatile uint32_t*) (0x4006EC14)) = 0x00000000; + *((volatile uint32_t*) (0x4006EC00)) = 0x00009375; + } else { + *((volatile uint32_t*) (0x4006EC14)) = 0x00000000; + } + + // CRITICAL_REGION_EXIT(); + } + + if (nrf52_errata_187()) { + // CRITICAL_REGION_ENTER(); + if (*((volatile uint32_t*) (0x4006EC00)) == 0x00000000) { + *((volatile uint32_t*) (0x4006EC00)) = 0x00009375; + *((volatile uint32_t*) (0x4006ED14)) = 0x00000000; + *((volatile uint32_t*) (0x4006EC00)) = 0x00009375; + } else { + *((volatile uint32_t*) (0x4006ED14)) = 0x00000000; + } + // CRITICAL_REGION_EXIT(); + } + + if (nrf52_errata_166()) { + *((volatile uint32_t*) (NRF_USBD_BASE + 0x800)) = 0x7E3; + *((volatile uint32_t*) (NRF_USBD_BASE + 0x804)) = 0x40; + + __ISB(); + __DSB(); + } +#endif + + // ISO buffer Lower half for IN, upper half for OUT + NRF_USBD->ISOSPLIT = USBD_ISOSPLIT_SPLIT_HalfIN; + + // Enable bus-reset interrupt + NRF_USBD->INTENSET = USBD_INTEN_USBRESET_Msk; + + // Enable interrupt, priorities should be set by application + NVIC_ClearPendingIRQ(USBD_IRQn); + + // Don't enable USBD interrupt yet, if dcd_init() did not finish yet + // Interrupt will be enabled by tud_init(), when USB stack is ready + // to handle interrupts. + if (tud_inited()) { + NVIC_EnableIRQ(USBD_IRQn); + } + + // Ensure HFCLK is requested in the current context. The hfclk_enable() in + // USB_EVT_DETECTED may have been pre-SoftDevice. After Softdevice is + // enabled, HFXO is physically off again. So any caller that fires + // USB_EVT_READY post-SD would hang here. + hfclk_enable(); + + // Wait for HFCLK + while (!hfclk_running()) {} + + // Enable pull up + NRF_USBD->USBPULLUP = 1; + __ISB(); + __DSB(); // for sync + break; + + case USB_EVT_REMOVED: + if (NRF_USBD->ENABLE) { + // Abort all transfers + + // Disable pull up + NRF_USBD->USBPULLUP = 0; + __ISB(); + __DSB(); // for sync + + // Disable Interrupt + NVIC_DisableIRQ(USBD_IRQn); + + // disable all interrupt + NRF_USBD->INTENCLR = NRF_USBD->INTEN; + + NRF_USBD->ENABLE = 0; + __ISB(); + __DSB(); // for sync + + hfclk_disable(); + + dcd_event_bus_signal(0, DCD_EVENT_UNPLUGGED, is_in_isr()); + } + break; + + default: + break; + } +} +#endif diff --git a/libraries/Adafruit_TinyUSB_Arduino/src/portable/raspberrypi/pio_usb/dcd_pio_usb.c b/libraries/Adafruit_TinyUSB_Arduino/src/portable/raspberrypi/pio_usb/dcd_pio_usb.c new file mode 100644 index 0000000..78df2b6 --- /dev/null +++ b/libraries/Adafruit_TinyUSB_Arduino/src/portable/raspberrypi/pio_usb/dcd_pio_usb.c @@ -0,0 +1,224 @@ +/* + * The MIT License (MIT) + * + * Copyright (c) 2018, hathach (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. + * + * This file is part of the TinyUSB stack. + */ + +#include "tusb_option.h" + +#if CFG_TUD_ENABLED && (CFG_TUSB_MCU == OPT_MCU_RP2040) && CFG_TUD_RPI_PIO_USB + +#include "pico.h" +#include "pio_usb.h" +#include "pio_usb_ll.h" + +#include "device/dcd.h" + +//--------------------------------------------------------------------+ +// MACRO TYPEDEF CONSTANT ENUM DECLARATION +//--------------------------------------------------------------------+ + +#define RHPORT_OFFSET 1 +#define RHPORT_PIO(_x) ((_x)-RHPORT_OFFSET) + +//------------- -------------// +static usb_device_t *usb_device = NULL; +static usb_descriptor_buffers_t desc; + +/*------------------------------------------------------------------*/ +/* Device API + *------------------------------------------------------------------*/ + +// Initialize controller to device mode +bool dcd_init(uint8_t rhport, const tusb_rhport_init_t* rh_init) { + (void) rhport; + (void) rh_init; + static pio_usb_configuration_t config = PIO_USB_DEFAULT_CONFIG; + usb_device = pio_usb_device_init(&config, &desc); + + return true; +} + +// Enable device interrupt +void dcd_int_enable (uint8_t rhport) +{ + (void) rhport; +} + +// Disable device interrupt +void dcd_int_disable (uint8_t rhport) +{ + (void) rhport; +} + +// Receive Set Address request, mcu port must also include status IN response +void dcd_set_address (uint8_t rhport, uint8_t dev_addr) +{ + // must be called before queuing status + pio_usb_device_set_address(dev_addr); + dcd_edpt_xfer(rhport, 0x80, NULL, 0, false); +} + +// Wake up host +void dcd_remote_wakeup (uint8_t rhport) +{ + (void) rhport; +} + +// Connect by enabling internal pull-up resistor on D+/D- +void dcd_connect(uint8_t rhport) +{ + (void) rhport; +} + +// Disconnect by disabling internal pull-up resistor on D+/D- +void dcd_disconnect(uint8_t rhport) +{ + (void) rhport; +} + +//--------------------------------------------------------------------+ +// Endpoint API +//--------------------------------------------------------------------+ + +// Configure endpoint's registers according to descriptor +bool dcd_edpt_open (uint8_t rhport, tusb_desc_endpoint_t const * desc_ep) +{ + (void) rhport; + return pio_usb_device_endpoint_open((uint8_t const*) desc_ep); +} + +void dcd_edpt_close_all (uint8_t rhport) +{ + (void) rhport; +} + +bool dcd_edpt_iso_alloc(uint8_t rhport, uint8_t ep_addr, uint16_t largest_packet_size) { + (void)rhport; + (void)ep_addr; + (void)largest_packet_size; + return false; +} + +bool dcd_edpt_iso_activate(uint8_t rhport, const tusb_desc_endpoint_t *desc_ep) { + (void)rhport; + (void)desc_ep; + return false; +} + +// Submit a transfer, When complete dcd_event_xfer_complete() is invoked to notify the stack +bool dcd_edpt_xfer(uint8_t rhport, uint8_t ep_addr, uint8_t * buffer, uint16_t total_bytes, bool is_isr) +{ + (void) is_isr; + (void) rhport; + endpoint_t *ep = pio_usb_device_get_endpoint_by_address(ep_addr); + return pio_usb_ll_transfer_start(ep, buffer, total_bytes); +} + +// Submit a transfer where is managed by FIFO, When complete dcd_event_xfer_complete() is invoked to notify the stack - optional, however, must be listed in usbd.c +//bool dcd_edpt_xfer_fifo(uint8_t rhport, uint8_t ep_addr, tu_fifo_t * ff, uint16_t total_bytes, bool is_isr) +//{ +// (void) rhport; +// (void) ep_addr; +// (void) ff; +// (void) total_bytes; +// return false; +//} + +// Stall endpoint +void dcd_edpt_stall (uint8_t rhport, uint8_t ep_addr) +{ + (void) rhport; + endpoint_t *ep = pio_usb_device_get_endpoint_by_address(ep_addr); + ep->has_transfer = false; + ep->stalled = true; +} + +// clear stall, data toggle is also reset to DATA0 +void dcd_edpt_clear_stall (uint8_t rhport, uint8_t ep_addr) +{ + (void) rhport; + endpoint_t *ep = pio_usb_device_get_endpoint_by_address(ep_addr); + ep->data_id = 0; + ep->stalled = false; +} + +//--------------------------------------------------------------------+ +// +//--------------------------------------------------------------------+ + +static void __no_inline_not_in_flash_func(handle_endpoint_irq)(uint8_t tu_rhport, xfer_result_t result, volatile uint32_t* ep_reg) +{ + const uint32_t ep_all = *ep_reg; + + for(uint8_t ep_idx = 0; ep_idx < PIO_USB_EP_POOL_CNT; ep_idx++) + { + uint32_t const mask = (1u << ep_idx); + + if (ep_all & mask) + { + endpoint_t* ep = PIO_USB_ENDPOINT(ep_idx); + dcd_event_xfer_complete(tu_rhport, ep->ep_num, ep->actual_len, result, true); + } + } + + // clear all + (*ep_reg) &= ~ep_all; +} + +// IRQ Handler +void __no_inline_not_in_flash_func(pio_usb_device_irq_handler)(uint8_t root_id) +{ + uint8_t const tu_rhport = root_id + 1; + root_port_t* rport = PIO_USB_ROOT_PORT(root_id); + uint32_t const ints = rport->ints; + + if (ints & PIO_USB_INTS_RESET_END_BITS) + { + dcd_event_bus_reset(tu_rhport, TUSB_SPEED_FULL, true); + } + + if (ints & PIO_USB_INTS_SETUP_REQ_BITS) + { + dcd_event_setup_received(tu_rhport, rport->setup_packet, true); + } + + if ( ints & PIO_USB_INTS_ENDPOINT_COMPLETE_BITS ) + { + handle_endpoint_irq(tu_rhport, XFER_RESULT_SUCCESS, &rport->ep_complete); + } + + if ( ints & PIO_USB_INTS_ENDPOINT_STALLED_BITS ) + { + handle_endpoint_irq(tu_rhport, XFER_RESULT_STALLED, &rport->ep_stalled); + } + + if ( ints & PIO_USB_INTS_ENDPOINT_ERROR_BITS ) + { + handle_endpoint_irq(tu_rhport, XFER_RESULT_FAILED, &rport->ep_error); + } + + // clear all + rport->ints &= ~ints; +} +#endif diff --git a/libraries/Adafruit_TinyUSB_Arduino/src/portable/raspberrypi/pio_usb/hcd_pio_usb.c b/libraries/Adafruit_TinyUSB_Arduino/src/portable/raspberrypi/pio_usb/hcd_pio_usb.c new file mode 100644 index 0000000..90eb920 --- /dev/null +++ b/libraries/Adafruit_TinyUSB_Arduino/src/portable/raspberrypi/pio_usb/hcd_pio_usb.c @@ -0,0 +1,226 @@ +/* + * The MIT License (MIT) + * + * Copyright (c) 2021 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. + * + * This file is part of the TinyUSB stack. + */ + +#include "tusb_option.h" + +#if CFG_TUH_ENABLED && (CFG_TUSB_MCU == OPT_MCU_RP2040) && CFG_TUH_RPI_PIO_USB + +#include "pico.h" + +#include "pio_usb.h" + +#ifdef __GNUC__ +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wsign-conversion" +#endif + +#include "pio_usb_ll.h" + +#ifdef __GNUC__ +#pragma GCC diagnostic pop +#endif + +//--------------------------------------------------------------------+ +// INCLUDE +//--------------------------------------------------------------------+ +#include "osal/osal.h" + +#include "host/hcd.h" +#include "host/usbh.h" + +#define RHPORT_OFFSET 1 +#define RHPORT_PIO(_x) ((_x)-RHPORT_OFFSET) + +static pio_usb_configuration_t pio_host_cfg = PIO_USB_DEFAULT_CONFIG; + +//--------------------------------------------------------------------+ +// HCD API +//--------------------------------------------------------------------+ +bool hcd_configure(uint8_t rhport, uint32_t cfg_id, const void *cfg_param) { + (void) rhport; + TU_VERIFY(cfg_id == TUH_CFGID_RPI_PIO_USB_CONFIGURATION); + memcpy(&pio_host_cfg, cfg_param, sizeof(pio_usb_configuration_t)); + return true; +} + +bool hcd_init(uint8_t rhport, const tusb_rhport_init_t* rh_init) { + (void) rhport; + (void) rh_init; + + // To run USB SOF interrupt in core1, call this init in core1 + pio_usb_host_init(&pio_host_cfg); + + return true; +} + +void hcd_port_reset(uint8_t rhport) { + uint8_t const pio_rhport = RHPORT_PIO(rhport); + pio_usb_host_port_reset_start(pio_rhport); +} + +void hcd_port_reset_end(uint8_t rhport) { + uint8_t const pio_rhport = RHPORT_PIO(rhport); + pio_usb_host_port_reset_end(pio_rhport); +} + +bool hcd_port_connect_status(uint8_t rhport) { + uint8_t const pio_rhport = RHPORT_PIO(rhport); + + root_port_t *root = PIO_USB_ROOT_PORT(pio_rhport); + port_pin_status_t line_state = pio_usb_bus_get_line_state(root); + + return line_state != PORT_PIN_SE0; +} + +tusb_speed_t hcd_port_speed_get(uint8_t rhport) { + // TODO determine link speed + uint8_t const pio_rhport = RHPORT_PIO(rhport); + return PIO_USB_ROOT_PORT(pio_rhport)->is_fullspeed ? TUSB_SPEED_FULL : TUSB_SPEED_LOW; +} + +// Close all opened endpoint belong to this device +void hcd_device_close(uint8_t rhport, uint8_t dev_addr) { + uint8_t const pio_rhport = RHPORT_PIO(rhport); + pio_usb_host_close_device(pio_rhport, dev_addr); +} + +uint32_t hcd_frame_number(uint8_t rhport) { + (void) rhport; + return pio_usb_host_get_frame_number(); +} + +void hcd_int_enable(uint8_t rhport) { + (void) rhport; +} + +void hcd_int_disable(uint8_t rhport) { + (void) rhport; +} + +//--------------------------------------------------------------------+ +// Endpoint API +//--------------------------------------------------------------------+ + +bool hcd_edpt_open(uint8_t rhport, uint8_t dev_addr, tusb_desc_endpoint_t const *desc_ep) { + tuh_bus_info_t bus_info; + tuh_bus_info_get(dev_addr, &bus_info); + bool const need_pre = (bus_info.hub_addr && bus_info.speed == TUSB_SPEED_LOW); + + uint8_t const pio_rhport = RHPORT_PIO(rhport); + return pio_usb_host_endpoint_open(pio_rhport, dev_addr, (uint8_t const *) desc_ep, need_pre); +} + +bool hcd_edpt_close(uint8_t rhport, uint8_t daddr, uint8_t ep_addr) { + uint8_t const pio_rhport = RHPORT_PIO(rhport); + return pio_usb_host_endpoint_close(pio_rhport, daddr, ep_addr); +} + +bool hcd_edpt_xfer(uint8_t rhport, uint8_t dev_addr, uint8_t ep_addr, uint8_t *buffer, uint16_t buflen) { + uint8_t const pio_rhport = RHPORT_PIO(rhport); + return pio_usb_host_endpoint_transfer(pio_rhport, dev_addr, ep_addr, buffer, buflen); +} + +bool hcd_edpt_abort_xfer(uint8_t rhport, uint8_t dev_addr, uint8_t ep_addr) { + uint8_t const pio_rhport = RHPORT_PIO(rhport); + return pio_usb_host_endpoint_abort_transfer(pio_rhport, dev_addr, ep_addr); +} + +bool hcd_setup_send(uint8_t rhport, uint8_t dev_addr, uint8_t const setup_packet[8]) { + uint8_t const pio_rhport = RHPORT_PIO(rhport); + return pio_usb_host_send_setup(pio_rhport, dev_addr, setup_packet); +} + +//bool hcd_edpt_busy(uint8_t dev_addr, uint8_t ep_addr) +//{ +// // EPX is shared, so multiple device addresses and endpoint addresses share that +// // so if any transfer is active on epx, we are busy. Interrupt endpoints have their own +// // EPX so ep->active will only be busy if there is a pending transfer on that interrupt endpoint +// // on that device +// pico_trace("hcd_edpt_busy dev addr %d ep_addr 0x%x\r\n", dev_addr, ep_addr); +// struct hw_endpoint *ep = get_dev_ep(dev_addr, ep_addr); +// assert(ep); +// bool busy = ep->active; +// pico_trace("busy == %d\r\n", busy); +// return busy; +//} + +bool hcd_edpt_clear_stall(uint8_t rhport, uint8_t dev_addr, uint8_t ep_addr) { + (void) rhport; + (void) dev_addr; + (void) ep_addr; + + return true; +} + +static void __no_inline_not_in_flash_func(handle_endpoint_irq)(root_port_t *rport, xfer_result_t result, + volatile uint32_t *ep_reg) { + (void) rport; + const uint32_t ep_all = *ep_reg; + + for ( uint8_t ep_idx = 0; ep_idx < PIO_USB_EP_POOL_CNT; ep_idx++ ) { + uint32_t const mask = (1u << ep_idx); + + if ( ep_all & mask ) { + endpoint_t * ep = PIO_USB_ENDPOINT(ep_idx); + hcd_event_xfer_complete(ep->dev_addr, ep->ep_num, ep->actual_len, result, true); + } + } + + // clear all + (*ep_reg) &= ~ep_all; +} + +// IRQ Handler +void __no_inline_not_in_flash_func(pio_usb_host_irq_handler)(uint8_t root_id) { + uint8_t const tu_rhport = root_id + 1; + root_port_t *rport = PIO_USB_ROOT_PORT(root_id); + uint32_t const ints = rport->ints; + + if ( ints & PIO_USB_INTS_ENDPOINT_COMPLETE_BITS ) { + handle_endpoint_irq(rport, XFER_RESULT_SUCCESS, &rport->ep_complete); + } + + if ( ints & PIO_USB_INTS_ENDPOINT_STALLED_BITS ) { + handle_endpoint_irq(rport, XFER_RESULT_STALLED, &rport->ep_stalled); + } + + if ( ints & PIO_USB_INTS_ENDPOINT_ERROR_BITS ) { + handle_endpoint_irq(rport, XFER_RESULT_FAILED, &rport->ep_error); + } + + if ( ints & PIO_USB_INTS_CONNECT_BITS ) { + hcd_event_device_attach(tu_rhport, true); + } + + if ( ints & PIO_USB_INTS_DISCONNECT_BITS ) { + hcd_event_device_remove(tu_rhport, true); + } + + // clear all + rport->ints &= ~ints; +} + +#endif diff --git a/libraries/Adafruit_TinyUSB_Arduino/src/portable/raspberrypi/rp2040/dcd_rp2040.c b/libraries/Adafruit_TinyUSB_Arduino/src/portable/raspberrypi/rp2040/dcd_rp2040.c new file mode 100644 index 0000000..2b6bbc4 --- /dev/null +++ b/libraries/Adafruit_TinyUSB_Arduino/src/portable/raspberrypi/rp2040/dcd_rp2040.c @@ -0,0 +1,582 @@ +/* + * The MIT License (MIT) + * + * Copyright (c) 2020 Raspberry Pi (Trading) Ltd. + * + * 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. + */ + +#include "tusb_option.h" + +#if CFG_TUD_ENABLED && (CFG_TUSB_MCU == OPT_MCU_RP2040) && !CFG_TUD_RPI_PIO_USB + +#include "pico.h" +#include "hardware/sync.h" +#include "rp2040_usb.h" + +#if TUD_OPT_RP2040_USB_DEVICE_ENUMERATION_FIX +#include "pico/fix/rp2040_usb_device_enumeration.h" +#endif + +#include "device/dcd.h" + +// Current implementation force vbus detection as always present, causing device think it is always plugged into host. +// Therefore, it cannot detect disconnect event, mistaken it as suspend. +// Note: won't work if change to 0 (for now) + #define FORCE_VBUS_DETECT 1 + + #define USB_INTS_ERROR_BITS \ + (USB_INTS_ERROR_DATA_SEQ_BITS | USB_INTS_ERROR_BIT_STUFF_BITS | USB_INTS_ERROR_CRC_BITS | \ + USB_INTS_ERROR_RX_OVERFLOW_BITS | USB_INTS_ERROR_RX_TIMEOUT_BITS) + +/*------------------------------------------------------------------*/ +/* Low level controller + *------------------------------------------------------------------*/ +// HW buffer pointer from USB buffer space (max 3840 bytes) +static uint8_t *hw_buffer_ptr; + +// USB_MAX_ENDPOINTS Endpoints, direction TUSB_DIR_OUT for out and TUSB_DIR_IN for in. +static struct hw_endpoint hw_endpoints[USB_MAX_ENDPOINTS][2]; + +// SOF may be used by remote wakeup as RESUME, this indicates whether SOF is actually used by usbd +static bool _sof_enable = false; + +TU_ATTR_ALWAYS_INLINE static inline hw_endpoint_t *hw_endpoint_get(uint8_t epnum, tusb_dir_t dir) { + return &hw_endpoints[epnum][dir]; +} + +TU_ATTR_ALWAYS_INLINE static inline hw_endpoint_t *hw_endpoint_get_by_addr(uint8_t ep_addr) { + const uint8_t num = tu_edpt_number(ep_addr); + const tusb_dir_t dir = tu_edpt_dir(ep_addr); + return hw_endpoint_get(num, dir); +} + +TU_ATTR_ALWAYS_INLINE static inline io_rw_32 *get_ep_ctrl(uint8_t epnum, tusb_dir_t dir) { + if (epnum == 0) { + // EP0 has no endpoint control register because the buffer offsets are fixed and always enabled + return NULL; + } + struct usb_device_dpram_ep_ctrl *ep_ctrl = &usb_dpram->ep_ctrl[epnum - 1]; + return (dir == TUSB_DIR_IN) ? &ep_ctrl->in : &ep_ctrl->out; +} + +TU_ATTR_ALWAYS_INLINE static inline io_rw_32 *get_buf_ctrl(uint8_t epnum, tusb_dir_t dir) { + struct usb_device_dpram_ep_buf_ctrl *buf_ctrl = &usb_dpram->ep_buf_ctrl[epnum]; + return (dir == TUSB_DIR_IN) ? &buf_ctrl->in : &buf_ctrl->out; +} + +// Init and enable endpoint +static void hw_endpoint_open(uint8_t ep_addr, uint16_t wMaxPacketSize, uint8_t transfer_type, bool ep_enabled) { + const uint8_t epnum = tu_edpt_number(ep_addr); + const tusb_dir_t dir = tu_edpt_dir(ep_addr); + + hw_endpoint_t *ep = hw_endpoint_get(epnum, dir); + ep->ep_addr = ep_addr; + ep->next_pid = 0u; + ep->max_packet_size = wMaxPacketSize; + + // Clear existing buffer control state + io_rw_32 *buf_reg = get_buf_ctrl(epnum, dir); + *buf_reg = 0; + + // allocated hw buffer + if (epnum == 0) { + // Buffer offset is fixed (2 buffer allocated). + // Note: Only single buffer for EP since Double buffered RX can be troublesome with future data. + ep->dpram_buf = (uint8_t *)&usb_dpram->ep0_buf_a[0]; + } else { + uint32_t ep_ctrl = EP_CTRL_INTERRUPT_PER_BUFFER | ((uint32_t)transfer_type << EP_CTRL_BUFFER_TYPE_LSB); + + // round up size to multiple of 64 + uint16_t size = (uint16_t)tu_round_up(wMaxPacketSize, 64); + + // double buffered Bulk endpoint + if (transfer_type == TUSB_XFER_BULK) { + size *= 2u; + #if CFG_TUSB_RP2_ERRATA_E15 + if (dir == TUSB_DIR_IN) { + ep->e15_bulk_in = true; + } + #endif + } + + // assign buffer + ep->dpram_buf = hw_buffer_ptr; + hw_buffer_ptr += size; + + ep_ctrl |= hw_data_offset(ep->dpram_buf); + if (ep_enabled) { + ep_ctrl |= EP_CTRL_ENABLE_BITS; + } + + *get_ep_ctrl(epnum, dir) = ep_ctrl; + + hard_assert(hw_buffer_ptr < usb_dpram->epx_data + sizeof(usb_dpram->epx_data)); + pico_info(" Allocated %d bytes (0x%p)\r\n", size, ep->dpram_buf); + } +} + +static void hw_endpoint_abort_xfer(struct hw_endpoint* ep) { + // Abort any pending transfer + const uint8_t dir = (uint8_t)tu_edpt_dir(ep->ep_addr); + const uint8_t epnum = tu_edpt_number(ep->ep_addr); + const uint32_t abort_mask = TU_BIT((epnum << 1) | (dir ? 0 : 1)); + + // Due to Errata RP2040-E2: ABORT flag is only applicable for B2 and later (unusable for B0, B1). + // Which means we are not guaranteed to safely abort pending transfer on B0 and B1. + if (rp2040_chip_version() >= 2) { + usb_hw_set->abort = abort_mask; + while ((usb_hw->abort_done & abort_mask) != abort_mask) {} + } + + io_rw_32 *buf_reg = get_buf_ctrl(epnum, dir); + *buf_reg = 0; // clear buffer control + rp2usb_reset_transfer(ep); + + if (rp2040_chip_version() >= 2) { + usb_hw_clear->abort_done = abort_mask; + usb_hw_clear->abort = abort_mask; + } +} + +static void __tusb_irq_path_func(handle_hw_buff_status)(void) { + uint32_t buf_status = usb_hw->buf_status; + pico_trace("buf_status = 0x%08lx\r\n", buf_status); + while (buf_status) { + // ctz/clz is faster than loop which has only a few bit set in general + const uint8_t i = (uint8_t) __builtin_ctz(buf_status); + const uint32_t bit = TU_BIT(i); + + // IN transfer for even i, OUT transfer for odd i + const uint8_t epnum = i >> 1u; + const tusb_dir_t dir = (i & 1u) ? TUSB_DIR_OUT : TUSB_DIR_IN; + hw_endpoint_t *ep = hw_endpoint_get(epnum, dir); + io_rw_32 *ep_reg = get_ep_ctrl(epnum, dir); + io_rw_32 *buf_reg = get_buf_ctrl(epnum, dir); + + // Double-buffered: if both buffers completed at once, buf_status re-sets + // immediately after clearing (datasheet Table 406). Process the second buffer too. + while (usb_hw->buf_status & bit) { + const uint8_t buf_id = (usb_hw->buf_cpu_should_handle & bit) ? 1 : 0; // before clear buf_status + usb_hw_clear->buf_status = bit; + buf_status &= ~bit; + + if (rp2usb_xfer_continue(ep, ep_reg, buf_reg, buf_id, dir == TUSB_DIR_OUT)) { + const uint16_t xferred_len = ep->xferred_len; + rp2usb_reset_transfer(ep); + dcd_event_xfer_complete(0, ep->ep_addr, xferred_len, XFER_RESULT_SUCCESS, true); + } + } + } +} + +TU_ATTR_ALWAYS_INLINE static inline void reset_ep0(void) { + // If we have finished this transfer on EP0 set pid back to 1 for next + // setup transfer. Also clear a stall in case + for (uint8_t dir = 0; dir < 2; dir++) { + struct hw_endpoint *ep = hw_endpoint_get(0, dir); + ep->next_pid = 1u; + if (ep->state == EPSTATE_ACTIVE) { + hw_endpoint_abort_xfer(ep); // Abort any pending transfer per USB specs + } + } +} + +static void __tusb_irq_path_func(reset_non_control_endpoints)(void) { + // Disable all non-control + for (uint8_t i = 0; i < USB_MAX_ENDPOINTS - 1; i++) { + usb_dpram->ep_ctrl[i].in = 0; + usb_dpram->ep_ctrl[i].out = 0; + } + + // clear non-control hw endpoints + tu_memclr(hw_endpoints[1], sizeof(hw_endpoints) - 2 * sizeof(hw_endpoint_t)); + + // reclaim buffer space + hw_buffer_ptr = &usb_dpram->epx_data[0]; +} + +static void __tusb_irq_path_func(dcd_rp2040_irq)(void) { + const uint32_t status = usb_hw->ints; + + if (status & USB_INTF_DEV_SOF_BITS) { + uint32_t sof_count = usb_hw->sof_rd & USB_SOF_RD_BITS; // clear interrupt by reading SOF_RD + + #if CFG_TUSB_RP2_ERRATA_E15 + e15_last_sof = time_us_32(); // timing critical + #endif + + dcd_event_sof(0, sof_count, true); + } + + // xfer events are handled before setup req. So if a transfer completes immediately + // before closing the EP, the events will be delivered in same order. + if (status & USB_INTS_BUFF_STATUS_BITS) { + handle_hw_buff_status(); + } + + if (status & USB_INTS_SETUP_REQ_BITS) { + const uint8_t *setup = remove_volatile_cast(const uint8_t *, &usb_dpram->setup_packet); + + // reset pid to both 1 (data and ack) + reset_ep0(); + + // Pass setup packet to tiny usb + dcd_event_setup_received(0, setup, true); + usb_hw_clear->sie_status = USB_SIE_STATUS_SETUP_REC_BITS; + } + + // Errata 15 workaround for Device Bulk-In endpoint, must be after BUF_STATUS interrupt to sync buf control first + if (status & USB_INTF_DEV_SOF_BITS) { + bool keep_sof_alive = false; + + #if CFG_TUSB_RP2_ERRATA_E15 + for (uint8_t i = 0; i < USB_MAX_ENDPOINTS; i++) { + struct hw_endpoint *ep = hw_endpoint_get(i, TUSB_DIR_IN); + + // Active Bulk IN endpoint requires SOF + if (ep->e15_bulk_in && ep->state >= EPSTATE_ACTIVE) { + keep_sof_alive = true; + hw_endpoint_lock_update(ep, 1); + + if (ep->state == EPSTATE_PENDING) { + ep->state = EPSTATE_ACTIVE; + + io_rw_32 *buf_reg32 = get_buf_ctrl(i, TUSB_DIR_IN); + io_rw_16 *buf_reg16 = (io_rw_16 *)buf_reg32; + + // Check each buffer half: idle when both FULL and AVAIL are clear. + // Use 16-bit writes to avoid clobbering the other half (DPSRAM concurrent access). + enum { + BUSY_MASK = USB_BUF_CTRL_FULL | USB_BUF_CTRL_AVAIL + }; + + const bool buf0_idle = !(buf_reg16[0] & BUSY_MASK); + const bool buf1_idle = (ep->remaining_len > 0) && !(buf_reg16[1] & BUSY_MASK); + + if (buf0_idle && buf1_idle) { + // both are idle, start fresh + io_rw_32 *ep_reg = get_ep_ctrl(i, TUSB_DIR_IN); + rp2usb_buffer_start(ep, ep_reg, buf_reg32, false); + } else if (buf0_idle) { + uint16_t buf0 = bufctrl_prepare16(ep, ep->dpram_buf, false); + bufctrl_write16(buf_reg16, buf0); + } else if (buf1_idle) { + uint16_t buf1 = bufctrl_prepare16(ep, ep->dpram_buf + 64, false); + bufctrl_write16(buf_reg16 + 1, buf1); + } + } + + hw_endpoint_lock_update(ep, -1); + } + } + #endif + + // disable SOF interrupt if it is used for RESUME in remote wakeup + if (!keep_sof_alive && !_sof_enable) { + usb_hw_clear->inte = USB_INTS_DEV_SOF_BITS; + } + } + + #if FORCE_VBUS_DETECT == 0 + // Since we force VBUS detect On, device will always think it is connected and + // couldn't distinguish between disconnect and suspend + if (status & USB_INTS_DEV_CONN_DIS_BITS) { + if (usb_hw->sie_status & USB_SIE_STATUS_CONNECTED_BITS) { + // Connected: nothing to do + } else { + // Disconnected + dcd_event_bus_signal(0, DCD_EVENT_UNPLUGGED, true); + } + usb_hw_clear->sie_status = USB_SIE_STATUS_CONNECTED_BITS; + } +#endif + + // SE0 for 2.5 us or more (will last at least 10ms) + if (status & USB_INTS_BUS_RESET_BITS) { + pico_trace("BUS RESET\r\n"); + usb_hw->dev_addr_ctrl = 0; + reset_non_control_endpoints(); + dcd_event_bus_reset(0, TUSB_SPEED_FULL, true); + usb_hw_clear->sie_status = USB_SIE_STATUS_BUS_RESET_BITS; + + #if TUD_OPT_RP2040_USB_DEVICE_ENUMERATION_FIX + // Only run enumeration workaround if pull up is enabled + if (usb_hw->sie_ctrl & USB_SIE_CTRL_PULLUP_EN_BITS) { + rp2040_usb_device_enumeration_fix(); + } + #endif + } + + /* Note from pico datasheet 4.1.2.6.4 (v1.2) + * If you enable the suspend interrupt, it is likely you will see a suspend interrupt when + * the device is first connected but the bus is idle. The bus can be idle for a few ms before + * the host begins sending start of frame packets. You will also see a suspend interrupt + * when the device is disconnected if you do not have a VBUS detect circuit connected. This is + * because without VBUS detection, it is impossible to tell the difference between + * being disconnected and suspended. + */ + if (status & USB_INTS_DEV_SUSPEND_BITS) { + dcd_event_bus_signal(0, DCD_EVENT_SUSPEND, true); + usb_hw_clear->sie_status = USB_SIE_STATUS_SUSPENDED_BITS; + } + + if (status & USB_INTS_DEV_RESUME_FROM_HOST_BITS) { + dcd_event_bus_signal(0, DCD_EVENT_RESUME, true); + usb_hw_clear->sie_status = USB_SIE_STATUS_RESUME_BITS; + } + +} + +/*------------------------------------------------------------------*/ +/* Controller API + *------------------------------------------------------------------*/ + +// older SDK +#ifndef PICO_SHARED_IRQ_HANDLER_HIGHEST_ORDER_PRIORITY +#define PICO_SHARED_IRQ_HANDLER_HIGHEST_ORDER_PRIORITY 0xff +#endif + +bool dcd_init(uint8_t rhport, const tusb_rhport_init_t* rh_init) { + (void) rh_init; + assert(rhport == 0); + + // TU_LOG(1, "Chip Version B%u\r\n", rp2040_chip_version()); + + // Reset hardware to default state + rp2usb_init(); + + #if FORCE_VBUS_DETECT + // Force VBUS detect so the device thinks it is plugged into a host + usb_hw->pwr = USB_USB_PWR_VBUS_DETECT_BITS | USB_USB_PWR_VBUS_DETECT_OVERRIDE_EN_BITS; +#endif + + irq_add_shared_handler(USBCTRL_IRQ, dcd_rp2040_irq, PICO_SHARED_IRQ_HANDLER_HIGHEST_ORDER_PRIORITY); + + // Init control endpoints + tu_memclr(hw_endpoints[0], 2 * sizeof(hw_endpoint_t)); + hw_endpoint_open(0x0, 64, TUSB_XFER_CONTROL, false); + hw_endpoint_open(0x80, 64, TUSB_XFER_CONTROL, false); + + // Init non-control endpoints + reset_non_control_endpoints(); + + // Initializes the USB peripheral for device mode and enables it. + // Don't need to enable the pull up here. Force VBUS + usb_hw->main_ctrl = USB_MAIN_CTRL_CONTROLLER_EN_BITS; + + // Enable individual controller IRQS here. Processor interrupt enable will be used + // for the global interrupt enable... + // Note: Force VBUS detect cause disconnection not detectable + usb_hw->sie_ctrl = USB_SIE_CTRL_EP0_INT_1BUF_BITS; + usb_hw->inte = USB_INTS_BUFF_STATUS_BITS | USB_INTS_BUS_RESET_BITS | USB_INTS_SETUP_REQ_BITS | + USB_INTS_DEV_SUSPEND_BITS | USB_INTS_DEV_RESUME_FROM_HOST_BITS | + (FORCE_VBUS_DETECT ? 0 : USB_INTS_DEV_CONN_DIS_BITS); + + dcd_connect(rhport); + return true; +} + +bool dcd_deinit(uint8_t rhport) { + (void) rhport; + + reset_non_control_endpoints(); + irq_remove_handler(USBCTRL_IRQ, dcd_rp2040_irq); + + // reset usb hardware into initial state + reset_block(RESETS_RESET_USBCTRL_BITS); + unreset_block_wait(RESETS_RESET_USBCTRL_BITS); + + return true; +} + +void dcd_int_enable(__unused uint8_t rhport) { + assert(rhport == 0); + irq_set_enabled(USBCTRL_IRQ, true); +} + +void dcd_int_disable(__unused uint8_t rhport) { + assert(rhport == 0); + irq_set_enabled(USBCTRL_IRQ, false); +} + +void dcd_set_address(uint8_t rhport, uint8_t dev_addr) { + (void)dev_addr; + // Can't set device address in hardware until status xfer has complete + // Send 0len complete response on EP0 IN + dcd_edpt_xfer(rhport, 0x80, NULL, 0, false); +} + +void dcd_remote_wakeup(__unused uint8_t rhport) { + pico_info("dcd_remote_wakeup %d\n", rhport); + assert(rhport == 0); + + // since RESUME interrupt is not triggered if we are the one initiate + // briefly enable SOF to notify usbd when bus is ready + usb_hw_set->inte = USB_INTS_DEV_SOF_BITS; + usb_hw_set->sie_ctrl = USB_SIE_CTRL_RESUME_BITS; +} + +// disconnect by disabling internal pull-up resistor on D+/D- +void dcd_disconnect(__unused uint8_t rhport) { + (void) rhport; + usb_hw_clear->sie_ctrl = USB_SIE_CTRL_PULLUP_EN_BITS; +} + +// connect by enabling internal pull-up resistor on D+/D- +void dcd_connect(__unused uint8_t rhport) { + (void) rhport; + usb_hw_set->sie_ctrl = USB_SIE_CTRL_PULLUP_EN_BITS; +} + +void dcd_sof_enable(uint8_t rhport, bool en) { + (void) rhport; + + _sof_enable = en; + + if (en) { + usb_hw_set->inte = USB_INTS_DEV_SOF_BITS; + } + #if !CFG_TUSB_RP2_ERRATA_E15 + else { + // Don't clear immediately if the SOF workaround is in use. + // The SOF handler will conditionally disable the interrupt. + usb_hw_clear->inte = USB_INTS_DEV_SOF_BITS; + } +#endif +} + +/*------------------------------------------------------------------*/ +/* DCD Endpoint port + *------------------------------------------------------------------*/ +void dcd_edpt0_status_complete(uint8_t rhport, tusb_control_request_t const* request) { + (void) rhport; + + if (request->bmRequestType_bit.recipient == TUSB_REQ_RCPT_DEVICE && + request->bmRequestType_bit.type == TUSB_REQ_TYPE_STANDARD && + request->bRequest == TUSB_REQ_SET_ADDRESS) { + usb_hw->dev_addr_ctrl = (uint8_t) request->wValue; + } +} + +bool dcd_edpt_open(uint8_t rhport, tusb_desc_endpoint_t const* desc_edpt) { + (void) rhport; + const uint8_t xfer_type = desc_edpt->bmAttributes.xfer; + hw_endpoint_open(desc_edpt->bEndpointAddress, tu_edpt_packet_size(desc_edpt), xfer_type, true); + return true; +} + +// New API: Allocate packet buffer used by ISO endpoints +// Some MCU need manual packet buffer allocation, we allocate the largest size to avoid clustering +bool dcd_edpt_iso_alloc(uint8_t rhport, uint8_t ep_addr, uint16_t largest_packet_size) { + (void)rhport; + hw_endpoint_open(ep_addr, largest_packet_size, TUSB_XFER_ISOCHRONOUS, false); + return true; +} + +// New API: Configure and enable an ISO endpoint according to descriptor +bool dcd_edpt_iso_activate(uint8_t rhport, const tusb_desc_endpoint_t *ep_desc) { + (void)rhport; + const uint8_t epnum = tu_edpt_number(ep_desc->bEndpointAddress); + const tusb_dir_t dir = tu_edpt_dir(ep_desc->bEndpointAddress); + struct hw_endpoint *ep = hw_endpoint_get(epnum, dir); + TU_ASSERT(ep->dpram_buf != NULL); // must be inited and allocated previously + + if (ep->state == EPSTATE_ACTIVE) { + hw_endpoint_abort_xfer(ep); // abort any pending transfer + } + ep->max_packet_size = ep_desc->wMaxPacketSize; + + // enable endpoint + io_rw_32 *ep_reg = get_ep_ctrl(epnum, dir); + if (ep_reg != NULL) { + *ep_reg |= EP_CTRL_ENABLE_BITS; + } + return true; +} + +void dcd_edpt_close_all(uint8_t rhport) { + (void) rhport; + // may need to use EP Abort + reset_non_control_endpoints(); +} + +bool dcd_edpt_xfer(uint8_t rhport, uint8_t ep_addr, uint8_t *buffer, uint16_t total_bytes, bool is_isr) { + (void)rhport; + (void)is_isr; + const uint8_t epnum = tu_edpt_number(ep_addr); + const tusb_dir_t dir = tu_edpt_dir(ep_addr); + + hw_endpoint_t *ep = hw_endpoint_get(epnum, dir); + io_rw_32 *ep_reg = get_ep_ctrl(epnum, dir); + io_rw_32 *buf_reg = get_buf_ctrl(epnum, dir); + rp2usb_xfer_start(ep, ep_reg, buf_reg, buffer, NULL, total_bytes); + return true; +} + +#if CFG_TUD_EDPT_DEDICATED_HWFIFO +bool dcd_edpt_xfer_fifo(uint8_t rhport, uint8_t ep_addr, tu_fifo_t *ff, uint16_t total_bytes, bool is_isr) { + (void)rhport; + (void)is_isr; + hw_endpoint_t *ep = hw_endpoint_get(epnum, dir); + io_rw_32 *ep_reg = get_ep_ctrl(epnum, dir); + io_rw_32 *buf_reg = get_buf_ctrl(epnum, dir); + rp2usb_xfer_start(ep, ep_reg, buf_reg, NULL, ff, total_bytes); + return true; +} +#endif + +void dcd_edpt_stall(uint8_t rhport, uint8_t ep_addr) { + (void)rhport; + const uint8_t epnum = tu_edpt_number(ep_addr); + const tusb_dir_t dir = tu_edpt_dir(ep_addr); + hw_endpoint_t *ep = hw_endpoint_get(epnum, dir); + + if (epnum == 0) { + // A stall on EP0 has to be armed so it can be cleared on the next setup packet + usb_hw_set->ep_stall_arm = (dir == TUSB_DIR_IN) ? USB_EP_STALL_ARM_EP0_IN_BITS : USB_EP_STALL_ARM_EP0_OUT_BITS; + } + + // abort first then stall and clear current pending buffer + hw_endpoint_abort_xfer(ep); + io_rw_32 *buf_reg = get_buf_ctrl(epnum, dir); + *buf_reg = USB_BUF_CTRL_STALL; +} + +void dcd_edpt_clear_stall(uint8_t rhport, uint8_t ep_addr) { + (void) rhport; + const uint8_t epnum = tu_edpt_number(ep_addr); + const tusb_dir_t dir = tu_edpt_dir(ep_addr); + + if (epnum != 0) { + struct hw_endpoint* ep = hw_endpoint_get(epnum, dir); + ep->next_pid = 0; // reset data toggle + io_rw_32 *buf_reg = get_buf_ctrl(epnum, dir); + *buf_reg = 0; + } +} + +void __tusb_irq_path_func(dcd_int_handler)(uint8_t rhport) { + (void) rhport; + dcd_rp2040_irq(); +} + +#endif diff --git a/libraries/Adafruit_TinyUSB_Arduino/src/portable/raspberrypi/rp2040/hcd_rp2040.c b/libraries/Adafruit_TinyUSB_Arduino/src/portable/raspberrypi/rp2040/hcd_rp2040.c new file mode 100644 index 0000000..02a4e05 --- /dev/null +++ b/libraries/Adafruit_TinyUSB_Arduino/src/portable/raspberrypi/rp2040/hcd_rp2040.c @@ -0,0 +1,704 @@ + +/* + * The MIT License (MIT) + * + * Copyright (c) 2020 Raspberry Pi (Trading) Ltd. + * Copyright (c) 2021 Ha Thach (tinyusb.org) for Double Buffered + * + * 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. + */ + +#include "tusb_option.h" + +#if CFG_TUH_ENABLED && (CFG_TUSB_MCU == OPT_MCU_RP2040) && !CFG_TUH_RPI_PIO_USB && !CFG_TUH_MAX3421 + + #include "pico.h" + + #if defined(PICO_RP2350) && PICO_RP2350 == 1 + #define HAS_STOP_EPX_ON_NAK + #endif + +// port 0 is native USB port, other is counted as software PIO + #define RHPORT_NATIVE 0 + + //--------------------------------------------------------------------+ + // INCLUDE + //--------------------------------------------------------------------+ + #include "rp2040_usb.h" + #include "osal/osal.h" + + #include "host/hcd.h" + #include "host/usbh.h" + +//--------------------------------------------------------------------+ +// +//--------------------------------------------------------------------+ + +// Host mode uses one shared endpoint register for non-interrupt endpoint +static hw_endpoint_t ep_pool[USB_MAX_ENDPOINTS]; +static hw_endpoint_t *epx = &ep_pool[0]; // current active endpoint + + #ifndef HAS_STOP_EPX_ON_NAK +static volatile bool epx_switch_request = false; + #endif + +enum { + SIE_CTRL_SPEED_DISCONNECT = 0, + SIE_CTRL_SPEED_LOW = 1, + SIE_CTRL_SPEED_FULL = 2, +}; + +enum { + EPX_CTRL_DEFAULT = EP_CTRL_ENABLE_BITS | EP_CTRL_INTERRUPT_PER_BUFFER | offsetof(usb_host_dpram_t, epx_data) +}; + +//--------------------------------------------------------------------+ +// +//--------------------------------------------------------------------+ + +static hw_endpoint_t *edpt_alloc(void) { + for (uint i = 1; i < TU_ARRAY_SIZE(ep_pool); i++) { + hw_endpoint_t *ep = &ep_pool[i]; + if (ep->max_packet_size == 0) { + return ep; + } + } + return NULL; +} + +static hw_endpoint_t *edpt_find(uint8_t daddr, uint8_t ep_addr) { + for (uint32_t i = 0; i < TU_ARRAY_SIZE(ep_pool); i++) { + hw_endpoint_t *ep = &ep_pool[i]; + if ((ep->dev_addr == daddr) && (ep->max_packet_size > 0) && + (ep->ep_addr == ep_addr || (tu_edpt_number(ep_addr) == 0 && tu_edpt_number(ep->ep_addr) == 0))) { + return ep; + } + } + + return NULL; +} + +TU_ATTR_ALWAYS_INLINE static inline io_rw_32 *dpram_int_ep_ctrl(uint8_t int_num) { + return &usbh_dpram->int_ep_ctrl[int_num - 1].ctrl; +} + +TU_ATTR_ALWAYS_INLINE static inline io_rw_32 *dpram_int_ep_buffer_ctrl(uint8_t int_num) { + return &usbh_dpram->int_ep_buffer_ctrl[int_num - 1].ctrl; +} + +//--------------------------------------------------------------------+ +// +//--------------------------------------------------------------------+ + +TU_ATTR_ALWAYS_INLINE static inline uint8_t dev_speed(void) { + return (usb_hw->sie_status & USB_SIE_STATUS_SPEED_BITS) >> USB_SIE_STATUS_SPEED_LSB; +} + +TU_ATTR_ALWAYS_INLINE static inline bool need_pre(uint8_t dev_addr) { + // If this device is different to the speed of the root device + // (i.e. is a low speed device on a full speed hub) then need pre + return hcd_port_speed_get(0) != tuh_speed_get(dev_addr); +} + +//--------------------------------------------------------------------+ +// EPX +//--------------------------------------------------------------------+ +TU_ATTR_ALWAYS_INLINE static inline void sie_stop_xfer(void) { + uint32_t sie_ctrl = (usb_hw->sie_ctrl & SIE_CTRL_BASE_MASK) | USB_SIE_CTRL_STOP_TRANS_BITS; + usb_hw->sie_ctrl = sie_ctrl; + while (usb_hw->sie_ctrl & USB_SIE_CTRL_STOP_TRANS_BITS) {} +} + +static void __tusb_irq_path_func(sie_start_xfer)(bool send_setup, bool is_rx, bool need_pre) { + uint32_t sie_ctrl = usb_hw->sie_ctrl & SIE_CTRL_BASE_MASK; // preserve base bits + if (send_setup) { + sie_ctrl |= USB_SIE_CTRL_SEND_SETUP_BITS; + } else { + sie_ctrl |= (is_rx ? USB_SIE_CTRL_RECEIVE_DATA_BITS : USB_SIE_CTRL_SEND_DATA_BITS); + } + if (need_pre) { + sie_ctrl |= USB_SIE_CTRL_PREAMBLE_EN_BITS; + } + + // START_TRANS bit on SIE_CTRL has the same behavior as the AVAILABLE bit + // described in RP2040 Datasheet, release 2.1, section "4.1.2.5.1. Concurrent access".! + // We write everything except the START_TRANS bit first, then wait some cycles. + usb_hw->sie_ctrl = sie_ctrl; + busy_wait_at_least_cycles(12); + usb_hw->sie_ctrl = sie_ctrl | USB_SIE_CTRL_START_TRANS_BITS; +} + +// prepare epx_ctrl register for new endpoint +TU_ATTR_ALWAYS_INLINE static inline void epx_ctrl_prepare(uint8_t transfer_type) { + usbh_dpram->epx_ctrl = EPX_CTRL_DEFAULT | ((uint32_t)transfer_type << EP_CTRL_BUFFER_TYPE_LSB); +} + +// Save buffer context for EPX preemption (called after STOP_TRANS). +// Undo PID toggle and buffer accounting for buffers NOT completed on the wire. +// A buffer completed on wire means: controller reached STATUS phase (ACK received). +// OUT completed: FULL cleared to 0 in STATUS phase (was 1 when armed) +// IN completed: FULL set to 1 in STATUS phase (was 0 when armed) +// So undo when: AVAIL=1 (never started), or (OUT: FULL=1) or (IN: FULL=0) +static void __tusb_irq_path_func(epx_save_context)(hw_endpoint_t *ep) { + uint32_t buf_ctrl = usbh_dpram->epx_buf_ctrl; + const bool is_out = (tu_edpt_dir(ep->ep_addr) == TUSB_DIR_OUT); + + do { + const uint16_t bc16 = (uint16_t)buf_ctrl; + if (bc16) { + const bool avail = (bc16 & USB_BUF_CTRL_AVAIL); + const bool full = (bc16 & USB_BUF_CTRL_FULL); + if (avail || (is_out ? full : !full)) { + const uint16_t buf_len = bc16 & USB_BUF_CTRL_LEN_MASK; + ep->remaining_len += buf_len; + ep->next_pid ^= 1u; + if (is_out) { + ep->user_buf -= buf_len; + } + } + } + + if (usbh_dpram->epx_ctrl & EP_CTRL_DOUBLE_BUFFERED_BITS) { + buf_ctrl >>= 16; + } else { + buf_ctrl = 0; + } + } while (buf_ctrl > 0); + + usbh_dpram->epx_buf_ctrl = 0; + + ep->state = EPSTATE_PENDING; +} + +// switch epx to new endpoint and start the transfer +static void __tusb_irq_path_func(epx_switch_ep)(hw_endpoint_t *ep) { + const bool is_setup = (ep->state == EPSTATE_PENDING_SETUP); + + epx = ep; // switch pointer + ep->state = EPSTATE_ACTIVE; + + if (is_setup) { + // panic("new setup \n"); + usb_hw->dev_addr_ctrl = ep->dev_addr; + sie_start_xfer(true, false, ep->need_pre); + } else { + const bool is_rx = (tu_edpt_dir(ep->ep_addr) == TUSB_DIR_IN); + io_rw_32 *ep_reg = &usbh_dpram->epx_ctrl; + io_rw_32 *buf_reg = &usbh_dpram->epx_buf_ctrl; + + epx_ctrl_prepare(ep->transfer_type); + rp2usb_buffer_start(ep, ep_reg, buf_reg, is_rx); + + usb_hw->dev_addr_ctrl = (uint32_t)(ep->dev_addr | (tu_edpt_number(ep->ep_addr) << USB_ADDR_ENDP_ENDPOINT_LSB)); + sie_start_xfer(is_setup, is_rx, ep->need_pre); + } +} + +// Round-robin find next pending ep after current epx +static hw_endpoint_t *__tusb_irq_path_func(epx_next_pending)(hw_endpoint_t *cur_ep) { + const uint cur_idx = (uint)(cur_ep - &ep_pool[0]); + for (uint i = cur_idx + 1; i < TU_ARRAY_SIZE(ep_pool); i++) { + if (ep_pool[i].state >= EPSTATE_PENDING) { + return &ep_pool[i]; + } + } + for (uint i = 0; i < cur_idx; i++) { + if (ep_pool[i].state >= EPSTATE_PENDING) { + return &ep_pool[i]; + } + } + return NULL; +} + + +//--------------------------------------------------------------------+ +// Interrupt handlers +//--------------------------------------------------------------------+ +static void __tusb_irq_path_func(xfer_complete_isr)(hw_endpoint_t *ep, xfer_result_t xfer_result, bool is_more) { + // Mark transfer as done before we tell the tinyusb stack + uint32_t xferred_len = ep->xferred_len; + rp2usb_reset_transfer(ep); + hcd_event_xfer_complete(ep->dev_addr, ep->ep_addr, xferred_len, xfer_result, true); + + // Carry more transfer on epx + if (is_more) { + hw_endpoint_t *next_ep = epx_next_pending(epx); + if (next_ep != NULL) { + epx_switch_ep(next_ep); + } + } +} + +static void __tusb_irq_path_func(handle_buf_status_isr)(void) { + pico_trace("buf_status 0x%08lx\n", buf_status); + enum { + BUF_STATUS_EPX = 1u + }; + + // Check EPX first (bit 0). + // Double-buffered: if both buffers completed at once, buf_status re-sets + // immediately after clearing (datasheet Table 406). Process the second buffer too. + while (usb_hw->buf_status & BUF_STATUS_EPX) { + const uint8_t buf_id = (usb_hw->buf_cpu_should_handle & BUF_STATUS_EPX) ? 1 : 0; + usb_hw_clear->buf_status = 1u; // clear + + io_rw_32 *ep_reg = &usbh_dpram->epx_ctrl; + io_rw_32 *buf_reg = &usbh_dpram->epx_buf_ctrl; + #ifndef HAS_STOP_EPX_ON_NAK + // Any packet completion (mid-transfer or final) means data is flowing. + // Clear switch request so the 2-SOF fallback only fires for NAK-retrying endpoints. + epx_switch_request = false; + #endif + if (rp2usb_xfer_continue(epx, ep_reg, buf_reg, buf_id, tu_edpt_dir(epx->ep_addr) == TUSB_DIR_IN)) { + xfer_complete_isr(epx, XFER_RESULT_SUCCESS, true); + } + } + + // Check "interrupt" (asynchronous) endpoints for both IN and OUT + uint32_t buf_status = usb_hw->buf_status & ~(uint32_t)BUF_STATUS_EPX; + while (buf_status) { + // ctz/clz is faster than loop which has only a few bit set in general + const uint8_t idx = (uint8_t)__builtin_ctz(buf_status); + const uint32_t bit = TU_BIT(idx); + usb_hw_clear->buf_status = bit; + buf_status &= ~bit; + + // IN transfer for even i, OUT transfer for odd i + // EPX is bit 0. Bit 1 is not used + // IEP1 IN/OUT is bit 2, 3 + // IEP2 IN/OUT is bit 4, 5 etc + const uint8_t epnum = idx >> 1u; + for (size_t e = 0; e < TU_ARRAY_SIZE(ep_pool); e++) { + hw_endpoint_t *ep = &ep_pool[e]; + if (ep->interrupt_num == epnum) { + io_rw_32 *ep_reg = dpram_int_ep_ctrl(ep->interrupt_num); + io_rw_32 *buf_reg = dpram_int_ep_buffer_ctrl(ep->interrupt_num); + const bool done = rp2usb_xfer_continue(ep, ep_reg, buf_reg, 0, tu_edpt_dir(ep->ep_addr) == TUSB_DIR_IN); + if (done) { + xfer_complete_isr(ep, XFER_RESULT_SUCCESS, false); + } + break; + } + } + } +} + +static void __tusb_irq_path_func(hcd_rp2040_irq)(void) { + const uint32_t status = usb_hw->ints; + + if (status & USB_INTS_HOST_CONN_DIS_BITS) { + uint8_t speed = dev_speed(); + if (speed == SIE_CTRL_SPEED_DISCONNECT) { + hcd_event_device_remove(RHPORT_NATIVE, true); + } else { + if (speed == SIE_CTRL_SPEED_LOW) { + usb_hw->sie_ctrl = SIE_CTRL_BASE | USB_SIE_CTRL_KEEP_ALIVE_EN_BITS; + } else { + usb_hw->sie_ctrl = SIE_CTRL_BASE | USB_SIE_CTRL_SOF_EN_BITS; + } + hcd_event_device_attach(RHPORT_NATIVE, true); + } + usb_hw_clear->sie_status = USB_SIE_STATUS_SPEED_BITS; + } + + if (status & USB_INTS_STALL_BITS) { + usb_hw_clear->sie_status = USB_SIE_STATUS_STALL_REC_BITS; + xfer_complete_isr(epx, XFER_RESULT_STALLED, true); + } + + if (status & USB_INTS_ERROR_RX_TIMEOUT_BITS) { + usb_hw_clear->sie_status = USB_SIE_STATUS_RX_TIMEOUT_BITS; + + const uint32_t sie_ctrl = (usb_hw->sie_ctrl & SIE_CTRL_BASE_MASK) | USB_SIE_CTRL_STOP_TRANS_BITS; + usb_hw->sie_ctrl = sie_ctrl; + // while (usb_hw->sie_ctrl & USB_SIE_CTRL_STOP_TRANS_BITS) {} + + // Even if STOP_TRANS bit is clear, controller maybe in middle of retrying and may re-raise timeout once extra time + // Only handle if epx is active, don't carry more epx transfer since STOP_TRANS is raced and not safe. + if (epx->state == EPSTATE_ACTIVE) { + xfer_complete_isr(epx, XFER_RESULT_FAILED, false); + } + } + + if (status & USB_INTS_TRANS_COMPLETE_BITS) { + // only applies for epx, interrupt endpoint does not seem to raise this + usb_hw_clear->sie_status = USB_SIE_STATUS_TRANS_COMPLETE_BITS; + if (usb_hw->sie_ctrl & USB_SIE_CTRL_SEND_SETUP_BITS) { + uint32_t sie_ctrl = usb_hw->sie_ctrl & SIE_CTRL_BASE_MASK; + usb_hw->sie_ctrl = sie_ctrl; // clear setup bit + epx->xferred_len = 8; + xfer_complete_isr(epx, XFER_RESULT_SUCCESS, true); + } + } + + if (status & USB_INTS_BUFF_STATUS_BITS) { + handle_buf_status_isr(); + } + + // SOF-based round-robin MUST run BEFORE BUFF_STATUS to avoid processing + // buf_status on the wrong EPX after a completion+switch in handle_buf_status_isr. + #ifdef HAS_STOP_EPX_ON_NAK + if (status & USB_INTS_EPX_STOPPED_ON_NAK_BITS) { + usb_hw_clear->nak_poll = USB_NAK_POLL_EPX_STOPPED_ON_NAK_BITS; + hw_endpoint_t *next_ep = epx_next_pending(epx); + if (next_ep != NULL) { + epx_save_context(epx); + epx_switch_ep(next_ep); + } else { + usb_hw_clear->nak_poll = USB_NAK_POLL_STOP_EPX_ON_NAK_BITS; + sie_start_xfer(false, TUSB_DIR_IN == tu_edpt_dir(epx->ep_addr), epx->need_pre); + } + } + #else + // RP2040: on SOF, switch EPX if another endpoint is pending. + // First SOF sets epx_switch_request. If a transfer completes before next SOF, the flag is + // cleared (data is flowing, no need to force-switch). Second SOF with flag still set means + // no data exchanged (endpoint NAK-retrying): STOP_TRANS is safe and we switch. + // This avoids stopping mid-data-transfer which corrupts double-buffered PID tracking. + if (status & USB_INTS_HOST_SOF_BITS) { + (void)usb_hw->sof_rd; // clear SOF by reading SOF_RD + hw_endpoint_t *next_ep = epx_next_pending(epx); + if (next_ep == NULL) { + usb_hw_clear->inte = USB_INTE_HOST_SOF_BITS; + usb_hw->nak_poll = USB_NAK_POLL_RESET; + epx_switch_request = false; + } else if (epx->state == EPSTATE_ACTIVE) { + if (epx_switch_request) { + // Second SOF with no transfer completion: endpoint is NAK-retrying, safe to switch. + epx_switch_request = false; + sie_stop_xfer(); + epx_save_context(epx); + epx_switch_ep(next_ep); + } else { + epx_switch_request = true; + } + } + } + #endif + + if (status & USB_INTS_ERROR_DATA_SEQ_BITS) { + usb_hw_clear->sie_status = USB_SIE_STATUS_DATA_SEQ_ERROR_BITS; + panic("Data Seq Error \n"); + } +} + +void __tusb_irq_path_func(hcd_int_handler)(uint8_t rhport, bool in_isr) { + (void)rhport; + (void)in_isr; + hcd_rp2040_irq(); +} + +//--------------------------------------------------------------------+ +// HCD API +//--------------------------------------------------------------------+ +bool hcd_init(uint8_t rhport, const tusb_rhport_init_t *rh_init) { + (void)rhport; + (void)rh_init; + pico_trace("hcd_init %d\n", rhport); + assert(rhport == 0); + + // Reset any previous state + rp2usb_init(); + + // Force VBUS detect to always present, for now we assume vbus is always provided (without using VBUS En) + usb_hw->pwr = USB_USB_PWR_VBUS_DETECT_BITS | USB_USB_PWR_VBUS_DETECT_OVERRIDE_EN_BITS; + + // Remove shared irq if it was previously added so as not to fill up shared irq slots + irq_remove_handler(USBCTRL_IRQ, hcd_rp2040_irq); + irq_add_shared_handler(USBCTRL_IRQ, hcd_rp2040_irq, PICO_SHARED_IRQ_HANDLER_HIGHEST_ORDER_PRIORITY); + + // clear epx and interrupt eps + memset(&ep_pool, 0, sizeof(ep_pool)); + + // Enable in host mode with SOF / Keep alive on + usb_hw->main_ctrl = USB_MAIN_CTRL_CONTROLLER_EN_BITS | USB_MAIN_CTRL_HOST_NDEVICE_BITS; + usb_hw->sie_ctrl = SIE_CTRL_BASE; + usb_hw->inte = USB_INTE_BUFF_STATUS_BITS | USB_INTE_HOST_CONN_DIS_BITS | USB_INTE_HOST_RESUME_BITS | + USB_INTE_STALL_BITS | USB_INTE_TRANS_COMPLETE_BITS | USB_INTE_ERROR_RX_TIMEOUT_BITS | + USB_INTE_ERROR_DATA_SEQ_BITS; + + #ifdef HAS_STOP_EPX_ON_NAK + usb_hw_set->inte = USB_INTE_EPX_STOPPED_ON_NAK_BITS; + #endif + + return true; +} + +bool hcd_deinit(uint8_t rhport) { + (void)rhport; + irq_remove_handler(USBCTRL_IRQ, hcd_rp2040_irq); + reset_block(RESETS_RESET_USBCTRL_BITS); + unreset_block_wait(RESETS_RESET_USBCTRL_BITS); + return true; +} + +void hcd_port_reset(uint8_t rhport) { + (void)rhport; + // TODO: Nothing to do here yet. Perhaps need to reset some state? +} + +void hcd_port_reset_end(uint8_t rhport) { + (void)rhport; +} + +bool hcd_port_connect_status(uint8_t rhport) { + (void)rhport; + return usb_hw->sie_status & USB_SIE_STATUS_SPEED_BITS; +} + +tusb_speed_t hcd_port_speed_get(uint8_t rhport) { + (void)rhport; + switch (dev_speed()) { + case SIE_CTRL_SPEED_LOW: + return TUSB_SPEED_LOW; + case SIE_CTRL_SPEED_FULL: + return TUSB_SPEED_FULL; + default: + return TUSB_SPEED_INVALID; + } +} + +// Close all opened endpoint belong to this device +void hcd_device_close(uint8_t rhport, uint8_t dev_addr) { + (void)rhport; + + if (dev_addr == 0) { + return; // address 0 is for device enumeration + } + + rp2usb_critical_enter(); + + for (size_t i = 0; i < TU_ARRAY_SIZE(ep_pool); i++) { + hw_endpoint_t *ep = &ep_pool[i]; + if (ep->dev_addr == dev_addr && ep->max_packet_size > 0) { + ep->state = EPSTATE_IDLE; // clear any pending transfer + + if (ep->interrupt_num > 0) { + // disable interrupt endpoint + usb_hw_clear->int_ep_ctrl = TU_BIT(ep->interrupt_num); + usb_hw->int_ep_addr_ctrl[ep->interrupt_num - 1] = 0; + + io_rw_32 *ep_reg = dpram_int_ep_ctrl(ep->interrupt_num); + io_rw_32 *buf_reg = dpram_int_ep_buffer_ctrl(ep->interrupt_num); + *buf_reg = 0; + *ep_reg = 0; + } + + ep->max_packet_size = 0; // mark as unused + } + } + + rp2usb_critical_exit(); +} + +uint32_t hcd_frame_number(uint8_t rhport) { + (void)rhport; + return usb_hw->sof_rd; +} + +void hcd_int_enable(uint8_t rhport) { + (void)rhport; + irq_set_enabled(USBCTRL_IRQ, true); +} + +void hcd_int_disable(uint8_t rhport) { + (void)rhport; + // todo we should check this is disabling from the correct core; note currently this is never called + irq_set_enabled(USBCTRL_IRQ, false); +} + +//--------------------------------------------------------------------+ +// Endpoint API +//--------------------------------------------------------------------+ +bool hcd_edpt_open(uint8_t rhport, uint8_t dev_addr, const tusb_desc_endpoint_t *ep_desc) { + (void)rhport; + pico_trace("hcd_edpt_open dev_addr %d, ep_addr %d\n", dev_addr, ep_desc->bEndpointAddress); + hw_endpoint_t *ep; + if (dev_addr == 0) { + ep = &ep_pool[0]; + } else { + ep = edpt_alloc(); + } + TU_ASSERT(ep); + + const uint8_t ep_addr = ep_desc->bEndpointAddress; + const uint16_t max_packet_size = tu_edpt_packet_size(ep_desc); + + ep->max_packet_size = max_packet_size; + ep->ep_addr = ep_addr; + ep->dev_addr = dev_addr; + ep->transfer_type = ep_desc->bmAttributes.xfer; + ep->need_pre = need_pre(dev_addr); + ep->next_pid = 0u; + + if (ep->transfer_type != TUSB_XFER_INTERRUPT) { + ep->dpram_buf = usbh_dpram->epx_data; + } else { + // from 15 interrupt endpoints pool + uint8_t int_idx; + for (int_idx = 0; int_idx < USB_HOST_INTERRUPT_ENDPOINTS; int_idx++) { + if (!tu_bit_test(usb_hw->int_ep_ctrl, 1 + int_idx)) { + ep->interrupt_num = int_idx + 1; + break; + } + } + assert(int_idx < USB_HOST_INTERRUPT_ENDPOINTS); + assert(ep_desc->bInterval > 0); + + //------------- dpram buf -------------// + // 15x64 last bytes of DPRAM for interrupt endpoint buffers + ep->dpram_buf = (uint8_t *)(USBCTRL_DPRAM_BASE + USB_DPRAM_MAX - (int_idx + 1u) * 64u); + uint32_t ep_ctrl = EP_CTRL_ENABLE_BITS | EP_CTRL_INTERRUPT_PER_BUFFER | + (TUSB_XFER_INTERRUPT << EP_CTRL_BUFFER_TYPE_LSB) | hw_data_offset(ep->dpram_buf) | + ((uint32_t)(ep_desc->bInterval - 1) << EP_CTRL_HOST_INTERRUPT_INTERVAL_LSB); + usbh_dpram->int_ep_ctrl[int_idx].ctrl = ep_ctrl; + + //------------- address control -------------// + const uint8_t epnum = tu_edpt_number(ep_addr); + uint32_t addr_ctrl = (uint32_t)(dev_addr | (epnum << USB_ADDR_ENDP1_ENDPOINT_LSB)); + if (tu_edpt_dir(ep_addr) == TUSB_DIR_OUT) { + addr_ctrl |= USB_ADDR_ENDP1_INTEP_DIR_BITS; + } + if (ep->need_pre) { + addr_ctrl |= USB_ADDR_ENDP1_INTEP_PREAMBLE_BITS; + } + usb_hw->int_ep_addr_ctrl[int_idx] = addr_ctrl; + + // Finally, activate interrupt endpoint + usb_hw_set->int_ep_ctrl = TU_BIT(ep->interrupt_num); + } + + return true; +} + +bool hcd_edpt_close(uint8_t rhport, uint8_t daddr, uint8_t ep_addr) { + (void)rhport; + (void)daddr; + (void)ep_addr; + return false; // TODO not implemented yet +} + +bool hcd_edpt_abort_xfer(uint8_t rhport, uint8_t dev_addr, uint8_t ep_addr) { + (void)rhport; + (void)dev_addr; + (void)ep_addr; + // TODO not implemented yet + return false; +} + +bool hcd_edpt_xfer(uint8_t rhport, uint8_t dev_addr, uint8_t ep_addr, uint8_t *buffer, uint16_t buflen) { + (void)rhport; + + hw_endpoint_t *ep = edpt_find(dev_addr, ep_addr); + TU_ASSERT(ep); + + if (ep->interrupt_num > 0) { + // For interrupt endpoint control and buffer is already configured + // Note: Interrupt is single buffered only + io_rw_32 *ep_reg = dpram_int_ep_ctrl(ep->interrupt_num); + io_rw_32 *buf_reg = dpram_int_ep_buffer_ctrl(ep->interrupt_num); + rp2usb_xfer_start(ep, ep_reg, buf_reg, buffer, NULL, buflen); + } else { + // Control endpoint can change direction 0x00 <-> 0x80 when changing stages + if (ep_addr != ep->ep_addr) { + ep->ep_addr = ep_addr; + ep->next_pid = 1; // data and status stage start with DATA1 + } + + // If EPX is busy with another transfer, mark as pending + rp2usb_critical_enter(); + if (epx->state == EPSTATE_ACTIVE) { + ep->user_buf = buffer; + ep->remaining_len = buflen; + ep->state = EPSTATE_PENDING; + + #ifdef HAS_STOP_EPX_ON_NAK + usb_hw_set->nak_poll = USB_NAK_POLL_STOP_EPX_ON_NAK_BITS; + #else + // Only enable SOF round-robin for non-control endpoints + usb_hw->nak_poll = (300 << USB_NAK_POLL_DELAY_FS_LSB) | (300 << USB_NAK_POLL_DELAY_LS_LSB); + usb_hw_set->inte = USB_INTE_HOST_SOF_BITS; + #endif + } else { + io_rw_32 *ep_reg = &usbh_dpram->epx_ctrl; + io_rw_32 *buf_reg = &usbh_dpram->epx_buf_ctrl; + + epx = ep; + + epx_ctrl_prepare(ep->transfer_type); + rp2usb_xfer_start(ep, ep_reg, buf_reg, buffer, NULL, buflen); // prepare bufctrl + usb_hw->dev_addr_ctrl = (uint32_t)(ep->dev_addr | (tu_edpt_number(ep->ep_addr) << USB_ADDR_ENDP_ENDPOINT_LSB)); + sie_start_xfer(false, tu_edpt_dir(ep->ep_addr) == TUSB_DIR_IN, ep->need_pre); + } + rp2usb_critical_exit(); + } + + return true; +} + +bool hcd_setup_send(uint8_t rhport, uint8_t dev_addr, const uint8_t setup_packet[8]) { + (void)rhport; + + hw_endpoint_t *ep = edpt_find(dev_addr, 0x00); + TU_ASSERT(ep); + + rp2usb_critical_enter(); + + // Copy data into setup packet buffer (usbh only schedules one setup at a time) + for (uint8_t i = 0; i < 8; i++) { + usbh_dpram->setup_packet[i] = setup_packet[i]; + } + + ep->ep_addr = 0; // setup is OUT + ep->remaining_len = 8; + ep->xferred_len = 0; + + // If EPX is busy, mark as pending setup (DPRAM already has the packet) + if (epx->state == EPSTATE_ACTIVE) { + ep->state = EPSTATE_PENDING_SETUP; + #ifdef HAS_STOP_EPX_ON_NAK + usb_hw_set->nak_poll = USB_NAK_POLL_STOP_EPX_ON_NAK_BITS; + #else + usb_hw->nak_poll = (300 << USB_NAK_POLL_DELAY_FS_LSB) | (300 << USB_NAK_POLL_DELAY_LS_LSB); + usb_hw_set->inte = USB_INTE_HOST_SOF_BITS; + #endif + } else { + epx = ep; + ep->state = EPSTATE_ACTIVE; + + usb_hw->dev_addr_ctrl = ep->dev_addr; + sie_start_xfer(true, tu_edpt_dir(ep->ep_addr) == TUSB_DIR_IN, ep->need_pre); + } + + rp2usb_critical_exit(); + return true; +} + +bool hcd_edpt_clear_stall(uint8_t rhport, uint8_t dev_addr, uint8_t ep_addr) { + (void)rhport; + (void)dev_addr; + (void)ep_addr; + + panic("hcd_clear_stall"); + // return true; +} + +#endif diff --git a/libraries/Adafruit_TinyUSB_Arduino/src/portable/raspberrypi/rp2040/rp2040_usb.c b/libraries/Adafruit_TinyUSB_Arduino/src/portable/raspberrypi/rp2040/rp2040_usb.c new file mode 100644 index 0000000..83ac48e --- /dev/null +++ b/libraries/Adafruit_TinyUSB_Arduino/src/portable/raspberrypi/rp2040/rp2040_usb.c @@ -0,0 +1,478 @@ +/* + * The MIT License (MIT) + * + * Copyright (c) 2020 Raspberry Pi (Trading) Ltd. + * Copyright (c) 2021 Ha Thach (tinyusb.org) for Double Buffered + * + * 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. + */ + +#include "tusb_option.h" + +#if CFG_TUSB_MCU == OPT_MCU_RP2040 && (CFG_TUD_ENABLED || CFG_TUH_ENABLED) + + #include + #include "rp2040_usb.h" + + #include "device/dcd.h" + #include "host/hcd.h" + +//--------------------------------------------------------------------+ +// MACRO CONSTANT TYPEDEF PROTOTYPE +//--------------------------------------------------------------------+ + #if CFG_TUSB_RP2_ERRATA_E15 +static bool e15_is_critical_frame_period(void); + #endif + + #if CFG_TUSB_RP2_ERRATA_E2 +static uint8_t rp2040_chipversion = 2; + #endif + +critical_section_t rp2usb_lock; + +//--------------------------------------------------------------------+ +// Implementation +//--------------------------------------------------------------------+ +// Provide own byte by byte memcpy as not all copies are aligned. +// Use volatile to prevent compiler from widening to 16/32-bit accesses +// which cause hard fault on RP2350 when dst/src point to USB DPRAM. +static void unaligned_memcpy(uint8_t *dst, const uint8_t *src, size_t n) { + volatile uint8_t *vdst = dst; + const volatile uint8_t *vsrc = src; + while (n--) { + *vdst++ = *vsrc++; + } +} + + #if CFG_TUD_EDPT_DEDICATED_HWFIFO +void tu_hwfifo_write(volatile void *hwfifo, const uint8_t *src, uint16_t len, const tu_hwfifo_access_t *access_mode) { + (void)access_mode; + unaligned_memcpy((uint8_t *)(uintptr_t)hwfifo, src, len); +} + +void tu_hwfifo_read(const volatile void *hwfifo, uint8_t *dest, uint16_t len, const tu_hwfifo_access_t *access_mode) { + (void)access_mode; + unaligned_memcpy(dest, (const uint8_t *)(uintptr_t)hwfifo, len); +} + #endif + +void rp2usb_init(void) { + // Reset usb controller + reset_block(RESETS_RESET_USBCTRL_BITS); + unreset_block_wait(RESETS_RESET_USBCTRL_BITS); + + #ifdef __GNUC__ + // Clear any previous state just in case + #pragma GCC diagnostic push + #pragma GCC diagnostic ignored "-Warray-bounds" + #if __GNUC__ > 6 + #pragma GCC diagnostic ignored "-Wstringop-overflow" + #endif + #endif + memset(usb_dpram, 0, sizeof(*usb_dpram)); + #ifdef __GNUC__ + #pragma GCC diagnostic pop + #endif + + // Mux the controller to the onboard usb phy + usb_hw->muxing = USB_USB_MUXING_TO_PHY_BITS | USB_USB_MUXING_SOFTCON_BITS; + + #if CFG_TUSB_RP2_ERRATA_E2 + rp2040_chipversion = rp2040_chip_version(); + #endif + + TU_LOG2_INT(sizeof(hw_endpoint_t)); + + critical_section_init(&rp2usb_lock); +} + +void __tusb_irq_path_func(rp2usb_reset_transfer)(hw_endpoint_t *ep) { + ep->state = EPSTATE_IDLE; + ep->remaining_len = 0; + ep->xferred_len = 0; + ep->user_buf = 0; +#if CFG_TUD_EDPT_DEDICATED_HWFIFO + ep->is_xfer_fifo = false; +#endif +} + +void __tusb_irq_path_func(bufctrl_write32)(io_rw_32 *buf_reg, uint32_t value) { + const uint32_t current = *buf_reg; + const uint32_t avail_mask = USB_BUF_CTRL_AVAIL | (USB_BUF_CTRL_AVAIL << 16); + if (current & value & avail_mask) { + panic("buf_ctrl @ 0x%lX already available", (uintptr_t)buf_reg); + } + *buf_reg = value & ~(USB_BUF_CTRL_AVAIL | (USB_BUF_CTRL_AVAIL << 16)); // write other bits first + + // Section 4.1.2.7.1 (rp2040) / 12.7.3.7.1 (rp2350) Concurrent access: after write to buffer control, + // wait for USB controller to see the update before setting AVAILABLE. + // Don't need delay in host mode as host is in charge of when to start the transaction. + if (value & (USB_BUF_CTRL_AVAIL | (USB_BUF_CTRL_AVAIL << 16))) { + if (!rp2usb_is_host_mode()) { + busy_wait_at_least_cycles(12); + } + *buf_reg = value; // then set AVAILABLE bit last + } +} + +void __tusb_irq_path_func(bufctrl_write16)(io_rw_16 *buf_reg16, uint16_t value) { + const uint16_t current = *buf_reg16; + if (current & value & USB_BUF_CTRL_AVAIL) { + panic("buf_ctrl @ 0x%lX already available", (uintptr_t)buf_reg16); + } + *buf_reg16 = value & (uint16_t)~USB_BUF_CTRL_AVAIL; // write other bits first + + // Section 4.1.2.7.1 (rp2040) / 12.7.3.7.1 (rp2350) Concurrent access + if (value & USB_BUF_CTRL_AVAIL) { + if (!rp2usb_is_host_mode()) { + busy_wait_at_least_cycles(12); + } + *buf_reg16 = value; // then set AVAILABLE bit last + } +} + +// prepare buffer, move data if tx, return buffer control +uint16_t __tusb_irq_path_func(bufctrl_prepare16)(hw_endpoint_t *ep, uint8_t *dpram_buf, bool is_rx) { + const uint16_t buflen = tu_min16(ep->remaining_len, ep->max_packet_size); + ep->remaining_len -= buflen; + + uint16_t buf_ctrl = buflen | USB_BUF_CTRL_AVAIL; + if (ep->next_pid) { + buf_ctrl |= USB_BUF_CTRL_DATA1_PID; + } + ep->next_pid ^= 1u; + + if (!is_rx) { + if (buflen) { + // Copy data from user buffer/fifo to hw buffer + #if CFG_TUD_EDPT_DEDICATED_HWFIFO + if (ep->is_xfer_fifo) { + // not in sram, may mess up timing with E15 workaround + tu_hwfifo_write_from_fifo(dpram_buf, ep->user_fifo, buflen, NULL); + } else + #endif + { + unaligned_memcpy(dpram_buf, ep->user_buf, buflen); + ep->user_buf += buflen; + } + } + + buf_ctrl |= USB_BUF_CTRL_FULL; + } + + // Is this the last buffer? Will trigger the trans complete irq but also stop it polling. + // This is used to detect setup packets being sent in host mode + if (ep->remaining_len == 0) { + buf_ctrl |= USB_BUF_CTRL_LAST; + } + + return buf_ctrl; +} + +// Start transaction on hw buffer +void __tusb_irq_path_func(rp2usb_buffer_start)(hw_endpoint_t *ep, io_rw_32 *ep_reg, io_rw_32 *buf_reg, bool is_rx) { + // always compute and start with buffer 0 + uint32_t buf_ctrl = bufctrl_prepare16(ep, ep->dpram_buf, is_rx) | USB_BUF_CTRL_SEL; + + // Note: device EP0 does not have an endpoint control register + if (ep_reg != NULL) { + uint32_t ep_ctrl = *ep_reg; + #if CFG_TUH_ENABLED + const bool force_single = (rp2usb_is_host_mode() && ep->interrupt_num > 0); + #else + const bool force_single = false; + #endif + + if (ep->remaining_len && !force_single) { + // Use buffer 1 (double buffered) if there is still data + buf_ctrl |= (uint32_t)bufctrl_prepare16(ep, ep->dpram_buf + 64, is_rx) << 16; + ep_ctrl |= EP_CTRL_DOUBLE_BUFFERED_BITS; + } else { + // Only buf0 used: clear DOUBLE_BUFFERED so controller doesn't toggle buffer selector + ep_ctrl &= ~(uint32_t)EP_CTRL_DOUBLE_BUFFERED_BITS; + } + *ep_reg = ep_ctrl; + } + + // Finally, write to buffer control which will trigger the transfer the next time the controller polls this endpoint + bufctrl_write32(buf_reg, buf_ctrl); +} + +void rp2usb_xfer_start(hw_endpoint_t *ep, io_rw_32 *ep_reg, io_rw_32 *buf_reg, uint8_t *buffer, tu_fifo_t *ff, + uint16_t total_len) { + (void)ff; + hw_endpoint_lock_update(ep, 1); + + if (ep->state == EPSTATE_ACTIVE) { + TU_LOG(1, "WARN: starting new transfer on already active ep %02X\r\n", ep->ep_addr); + rp2usb_reset_transfer(ep); + } + + // Fill in info now that we're kicking off the hw + ep->remaining_len = total_len; + ep->xferred_len = 0; + ep->state = EPSTATE_ACTIVE; + + #if CFG_TUD_EDPT_DEDICATED_HWFIFO + if (ff != NULL) { + ep->user_fifo = ff; + ep->is_xfer_fifo = true; + } else + #endif + { + ep->user_buf = buffer; + #if CFG_TUD_EDPT_DEDICATED_HWFIFO + ep->is_xfer_fifo = false; + #endif + } + + const bool is_host = rp2usb_is_host_mode(); + const bool is_rx = (is_host == (tu_edpt_dir(ep->ep_addr) == TUSB_DIR_IN)); + + #if CFG_TUD_ENABLED + if (!is_host && ep->future_len > 0) { + // Device only: previous short-packet abort saved data from the other buffer + const uint8_t future_len = ep->future_len; + memcpy(ep->user_buf, ep->dpram_buf + (ep->future_bufid << 6), future_len); + ep->xferred_len += future_len; + ep->remaining_len -= future_len; + ep->user_buf += future_len; + ep->future_len = 0; + ep->future_bufid = 0; + + if (ep->remaining_len == 0) { + const uint16_t xferred_len = ep->xferred_len; + rp2usb_reset_transfer(ep); + dcd_event_xfer_complete(0, ep->ep_addr, xferred_len, XFER_RESULT_SUCCESS, false); + hw_endpoint_lock_update(ep, -1); + return; + } + } + + #if CFG_TUSB_RP2_ERRATA_E15 + if (ep->e15_bulk_in) { + usb_hw_set->inte = USB_INTS_DEV_SOF_BITS; + + // skip transfer if we are in critical frame period + if (e15_is_critical_frame_period()) { + ep->state = EPSTATE_PENDING; + hw_endpoint_lock_update(ep, -1); + return; + } + } + #endif // CFG_TUSB_RP2_ERRATA_E15 + #endif // CFG_TUD_ENABLED + + rp2usb_buffer_start(ep, ep_reg, buf_reg, is_rx); + hw_endpoint_lock_update(ep, -1); +} + +// sync endpoint buffer and return transferred bytes +static uint16_t __tusb_irq_path_func(bufctrl_sync16)(hw_endpoint_t *ep, bool is_rx, uint16_t buf_ctrl, + uint8_t *dpram_buf) { + const uint16_t xferred_bytes = buf_ctrl & USB_BUF_CTRL_LEN_MASK; + + if (!is_rx) { + // We are continuing a transfer here. If we are TX, we have successfully + // sent some data can increase the length we have sent + assert(!(buf_ctrl & USB_BUF_CTRL_FULL)); + } else { + // If we have received some data, so can increase the length + // we have received AFTER we have copied it to the user buffer at the appropriate offset + assert(buf_ctrl & USB_BUF_CTRL_FULL); + #if CFG_TUD_EDPT_DEDICATED_HWFIFO + if (ep->is_xfer_fifo) { + // not in sram, may mess up timing with E15 workaround + tu_hwfifo_read_to_fifo(dpram_buf, ep->user_fifo, xferred_bytes, NULL); + } else + #endif + { + unaligned_memcpy(ep->user_buf, dpram_buf, xferred_bytes); + ep->user_buf += xferred_bytes; + } + } + ep->xferred_len += xferred_bytes; + + // Short packet + if (xferred_bytes < ep->max_packet_size) { + // Reduce total length as this is last packet + ep->remaining_len = 0; + } + + return xferred_bytes; +} + +// Returns true if transfer is complete. +// buf_id: which buffer completed (from BUFF_CPU_SHOULD_HANDLE, only used for double-buffered). +bool __tusb_irq_path_func(rp2usb_xfer_continue)(hw_endpoint_t *ep, io_rw_32 *ep_reg, io_rw_32 *buf_reg, uint8_t buf_id, + bool is_rx) { + hw_endpoint_lock_update(ep, 1); + + if (ep->state == EPSTATE_IDLE) { + // probably land here due to short packet on rx with double buffered + hw_endpoint_lock_update(ep, -1); + return false; + } + + const bool is_host = rp2usb_is_host_mode(); + const bool is_double = (ep_reg != NULL && ((*ep_reg) & EP_CTRL_DOUBLE_BUFFERED_BITS)); + + // Double-buffered: buf_id from BUFF_CPU_SHOULD_HANDLE indicates which buffer completed. + // RP2040-E4 (host only): in single-buffered multi-packet transfers, the controller may write completion status to + // BUF1 half instead of BUF0. The side effect is that controller can execute an extra packet after writing to BUF1 + // since it leaves BUF0 intact, which can be polled before buf_status interrupt is triggered. + uint8_t *dpram_buf = ep->dpram_buf; + if (buf_id) { + #if CFG_TUSB_RP2_ERRATA_E4 + if (!(is_host && !is_double)) // E4 bug: incorrect buf_id, buffer data is still buf0 + #endif + { + dpram_buf += 64; // buf1 offset + } + } + + io_rw_16 *buf_reg16 = (io_rw_16 *)buf_reg; + uint16_t buf_ctrl16 = *(buf_reg16 + buf_id); + + const uint16_t xact_bytes = bufctrl_sync16(ep, is_rx, buf_ctrl16, dpram_buf); + const bool is_last = buf_ctrl16 & USB_BUF_CTRL_LAST; + const bool is_short = xact_bytes < ep->max_packet_size; + const bool is_done = is_short || is_last; + + // Short packet on rx with double buffer: abort the other half (if not last) and reset the buffer control. + // The other buffer may be: (a) still AVAIL, (b) in-progress (controller receiving), or (c) already completed. + // We must abort to safely reclaim it. If it has valid data (FULL), save as future for the next transfer. + // Note: Host mode current does not save next transfer data due to shared epx --> potential issue. However, RP2040-E4 + // causes more or less of the same issue since it write to buf1 and next time it continues to transfer on buf0 (stale) + if (is_short && is_double && is_rx && !is_last) { + const uint32_t abort_bit = TU_BIT(tu_edpt_number(ep->ep_addr) << 1); // abort is device only -> IN endpoint + + if (is_host) { + // host stop current transfer, not safe, can be racing + const uint32_t sie_ctrl = (usb_hw->sie_ctrl & SIE_CTRL_BASE_MASK) | USB_SIE_CTRL_STOP_TRANS_BITS; + usb_hw->sie_ctrl = sie_ctrl; + while (usb_hw->sie_ctrl & USB_SIE_CTRL_STOP_TRANS_BITS) {} + } else { + // device abort current transfer + #if CFG_TUSB_RP2_ERRATA_E2 + if (rp2040_chipversion >= 2) + #endif + { + usb_hw_set->abort = abort_bit; + while ((usb_hw->abort_done & abort_bit) != abort_bit) {} + } + } + + // After abort, check if the other buffer received valid data + io_rw_16 *buf_reg16_other = buf_reg16 + (buf_id ^ 1); + const uint16_t buf_ctrl16_other = *buf_reg16_other; + if (buf_ctrl16_other & USB_BUF_CTRL_FULL) { + // Data already sent into this buffer. Save it for the next transfer. + // buff_status will be clear by the next run + #if CFG_TUD_ENABLED + if (!is_host) { + ep->future_len = (uint8_t)(buf_ctrl16_other & USB_BUF_CTRL_LEN_MASK); + ep->future_bufid = buf_id ^ 1; + } + #endif + } else { + ep->next_pid ^= 1u; // roll back pid if aborted + } + + *buf_reg = 0; // reset buffer control + + if (!is_host) { + #if CFG_TUSB_RP2_ERRATA_E2 + if (rp2040_chipversion >= 2) + #endif + { + usb_hw_clear->abort_done = abort_bit; + usb_hw_clear->abort = abort_bit; + } + } + + hw_endpoint_lock_update(ep, -1); + return true; + } + + if (!is_done && ep->remaining_len > 0) { + #if CFG_TUSB_RP2_ERRATA_E15 + const bool need_e15 = ep->e15_bulk_in; + if (need_e15 && e15_is_critical_frame_period()) { + // mark as pending if matches E15 condition + ep->state = EPSTATE_PENDING; + } else if (need_e15 && ep->state == EPSTATE_PENDING) { + // if already pending, meaning the other buf completes first, don't arm buffer, let SOF handle it + // do nothing + } else + #endif + { + // ping-pong: arm the completed buffer with new data + const uint16_t buf_ctrl16_new = bufctrl_prepare16(ep, dpram_buf, is_rx); + bufctrl_write16(buf_reg16 + buf_id, buf_ctrl16_new); + } + } + + hw_endpoint_lock_update(ep, -1); + return is_done; +} + +//--------------------------------------------------------------------+ +// Errata 15 +//--------------------------------------------------------------------+ + + #if CFG_TUSB_RP2_ERRATA_E15 +// E15 is fixed with RP2350 + +/* Don't mark IN buffers as available during the last 200us of a full-speed + frame. This avoids a situation seen with the USB2.0 hub on a Raspberry + Pi 4 where a late IN token before the next full-speed SOF can cause port + babble and a corrupt ACK packet. The nature of the data corruption has a + chance to cause device lockup. + + Use the next SOF to mark delayed buffers as available. This reduces + available Bulk IN bandwidth by approximately 20%, and requires that the + SOF interrupt is enabled while these transfers are ongoing. + + Inherit the top-level enable from the corresponding Pico-SDK flag. + Applications that will not use the device in a situation where it could + be plugged into a Pi 4 or Pi 400 (for example, when directly connected + to a commodity hub or other host) can turn off the flag in the SDK. +*/ + +volatile uint32_t e15_last_sof = 0; + +// check if it is currently in critical frame period i.e 20% of last usb frame +static bool __tusb_irq_path_func(e15_is_critical_frame_period)(void) { + /* Avoid the last 200us (uframe 6.5-7) of a frame, up to the EOF2 point. + * The device state machine cannot recover from receiving an incorrect PID + * when it is expecting an ACK. */ + uint32_t delta = time_us_32() - e15_last_sof; + if (delta < 800 || delta > 998) { + return false; + } + // TU_LOG(3, "Avoiding sof %lu now %lu last %lu\r\n", (usb_hw->sof_rd + 1) & USB_SOF_RD_BITS, time_us_32(), + // e15_last_sof); + return true; +} + + #endif +#endif diff --git a/libraries/Adafruit_TinyUSB_Arduino/src/portable/raspberrypi/rp2040/rp2040_usb.h b/libraries/Adafruit_TinyUSB_Arduino/src/portable/raspberrypi/rp2040/rp2040_usb.h new file mode 100644 index 0000000..8ebc3e3 --- /dev/null +++ b/libraries/Adafruit_TinyUSB_Arduino/src/portable/raspberrypi/rp2040/rp2040_usb.h @@ -0,0 +1,185 @@ +#ifndef RP2040_COMMON_H_ +#define RP2040_COMMON_H_ + +#include "pico.h" +#include "hardware/structs/usb.h" +#include "hardware/irq.h" +#include "hardware/resets.h" +#include "hardware/timer.h" + +#include "pico/critical_section.h" + +#include "common/tusb_common.h" +#include "osal/osal.h" +#include "common/tusb_fifo.h" + +#if defined(RP2040_USB_HOST_MODE) && defined(RP2040_USB_DEVICE_MODE) + #error TinyUSB device and host mode not supported at the same time +#endif + +#if defined(PICO_RP2040) && PICO_RP2040 == 1 + // RP2040-E2 USB device endpoint abort is not cleared. + #define CFG_TUSB_RP2_ERRATA_E2 1 + + // RP2040-E4: USB host writes to upper half of buffer status in single buffered mode. + #define CFG_TUSB_RP2_ERRATA_E4 1 + + // RP2040-E5: USB device fails to exit RESET state on busy USB bus. + #if defined(PICO_RP2040_USB_DEVICE_ENUMERATION_FIX) && !defined(TUD_OPT_RP2040_USB_DEVICE_ENUMERATION_FIX) + #define TUD_OPT_RP2040_USB_DEVICE_ENUMERATION_FIX PICO_RP2040_USB_DEVICE_ENUMERATION_FIX + #endif + + // RP2040-E15: USB Device controller will hang if certain bus errors occur during an IN transfer. + #ifndef CFG_TUSB_RP2_ERRATA_E15 + #if defined(PICO_RP2040_USB_DEVICE_UFRAME_FIX) + #define CFG_TUSB_RP2_ERRATA_E15 (CFG_TUD_ENABLED && PICO_RP2040_USB_DEVICE_UFRAME_FIX) + #elif defined(TUD_OPT_RP2040_USB_DEVICE_UFRAME_FIX) + #define CFG_TUSB_RP2_ERRATA_E15 (CFG_TUD_ENABLED && TUD_OPT_RP2040_USB_DEVICE_UFRAME_FIX) + #endif + #endif +#endif + +#ifndef CFG_TUSB_RP2_ERRATA_E2 + #define CFG_TUSB_RP2_ERRATA_E2 0 +#endif + +#ifndef CFG_TUSB_RP2_ERRATA_E4 + #define CFG_TUSB_RP2_ERRATA_E4 0 +#endif + +#ifndef TUD_OPT_RP2040_USB_DEVICE_ENUMERATION_FIX + #define TUD_OPT_RP2040_USB_DEVICE_ENUMERATION_FIX 0 +#endif + +#ifndef CFG_TUSB_RP2_ERRATA_E15 + #define CFG_TUSB_RP2_ERRATA_E15 0 +#endif + +#if CFG_TUSB_RP2_ERRATA_E15 + #undef PICO_RP2040_USB_FAST_IRQ + #define PICO_RP2040_USB_FAST_IRQ 1 +#endif + +#ifndef PICO_RP2040_USB_FAST_IRQ + #define PICO_RP2040_USB_FAST_IRQ 0 +#endif + +#if PICO_RP2040_USB_FAST_IRQ + #define __tusb_irq_path_func(x) __no_inline_not_in_flash_func(x) +#else + #define __tusb_irq_path_func(x) x +#endif + +// Flags we set by default in sie_ctrl (we add other bits on top) +enum { + SIE_CTRL_BASE = USB_SIE_CTRL_PULLDOWN_EN_BITS | USB_SIE_CTRL_EP0_INT_1BUF_BITS, + SIE_CTRL_BASE_MASK = USB_SIE_CTRL_PULLDOWN_EN_BITS | USB_SIE_CTRL_EP0_INT_1BUF_BITS | USB_SIE_CTRL_SOF_EN_BITS | + USB_SIE_CTRL_KEEP_ALIVE_EN_BITS +}; + +//--------------------------------------------------------------------+ +// +//--------------------------------------------------------------------+ +#define usb_hw_set ((usb_hw_t *) hw_set_alias_untyped(usb_hw)) +#define usb_hw_clear ((usb_hw_t *) hw_clear_alias_untyped(usb_hw)) + +#define pico_info(...) TU_LOG(2, __VA_ARGS__) +#define pico_trace(...) TU_LOG(3, __VA_ARGS__) + +enum { + EPSTATE_IDLE = 0, + EPSTATE_ACTIVE, + EPSTATE_PENDING, + EPSTATE_PENDING_SETUP +}; + +// Hardware information per endpoint +typedef struct hw_endpoint { + uint8_t ep_addr; + uint8_t next_pid; + uint8_t state; + +#if CFG_TUD_EDPT_DEDICATED_HWFIFO + bool is_xfer_fifo; // transfer using fifo +#endif + +#if CFG_TUD_ENABLED + uint8_t future_bufid; // which buffer holds next data + uint8_t future_len; // next data len +#endif + +#if CFG_TUSB_RP2_ERRATA_E15 + bool e15_bulk_in; // Errata15 device bulk in +#endif + +#if CFG_TUH_ENABLED + uint8_t dev_addr; + uint8_t interrupt_num; // 1-15 for interrupt endpoints + struct TU_ATTR_PACKED { + uint8_t transfer_type : 2; + uint8_t need_pre : 1; // preamble for low-speed device behind full speed hub + }; +#endif + + uint16_t max_packet_size; // max packet size also indicates configured + uint8_t *dpram_buf; // Buffer pointer in usb dpram + + // transfer info + union { + uint8_t *user_buf; // User buffer in main memory + tu_fifo_t *user_fifo; + }; + uint16_t remaining_len; + uint16_t xferred_len; + +} hw_endpoint_t; + +#if CFG_TUSB_RP2_ERRATA_E15 +extern volatile uint32_t e15_last_sof; +#endif + +void rp2usb_init(void); + +// if usb hardware is in host mode +TU_ATTR_ALWAYS_INLINE static inline bool rp2usb_is_host_mode(void) { + return (usb_hw->main_ctrl & USB_MAIN_CTRL_HOST_NDEVICE_BITS) ? true : false; +} + +extern critical_section_t rp2usb_lock; + +TU_ATTR_ALWAYS_INLINE static inline void rp2usb_critical_enter(void) { + critical_section_enter_blocking(&rp2usb_lock); +} +TU_ATTR_ALWAYS_INLINE static inline void rp2usb_critical_exit(void) { + critical_section_exit(&rp2usb_lock); +} + +//--------------------------------------------------------------------+ +// Hardware Endpoint +//--------------------------------------------------------------------+ +void rp2usb_xfer_start(hw_endpoint_t *ep, io_rw_32 *ep_reg, io_rw_32 *buf_reg, uint8_t *buffer, tu_fifo_t *ff, + uint16_t total_len); +bool rp2usb_xfer_continue(hw_endpoint_t *ep, io_rw_32 *ep_reg, io_rw_32 *buf_reg, uint8_t buf_id, bool is_rx); +void rp2usb_buffer_start(hw_endpoint_t *ep, io_rw_32 *ep_reg, io_rw_32 *buf_reg, bool is_rx); +void rp2usb_reset_transfer(hw_endpoint_t *ep); + + +TU_ATTR_ALWAYS_INLINE static inline void hw_endpoint_lock_update(__unused struct hw_endpoint *ep, __unused int delta) { + // todo add critsec as necessary to prevent issues between worker and IRQ... + // note that this is perhaps as simple as disabling IRQs because it would make + // sense to have worker and IRQ on same core, however I think using critsec is about equivalent. +} + +//--------------------------------------------------------------------+ +// Hardware Buffer +//--------------------------------------------------------------------+ +void bufctrl_write32(io_rw_32 *buf_reg, uint32_t value); +void bufctrl_write16(io_rw_16 *buf_reg16, uint16_t value); +uint16_t bufctrl_prepare16(hw_endpoint_t *ep, uint8_t *dpram_buf, bool is_rx); + +TU_ATTR_ALWAYS_INLINE static inline uintptr_t hw_data_offset(uint8_t *buf) { + // Remove usb base from buffer pointer + return (uintptr_t)buf ^ (uintptr_t)usb_dpram; +} + +#endif diff --git a/libraries/Adafruit_TinyUSB_Arduino/src/portable/renesas/rusb2/dcd_rusb2.c b/libraries/Adafruit_TinyUSB_Arduino/src/portable/renesas/rusb2/dcd_rusb2.c new file mode 100644 index 0000000..adbb537 --- /dev/null +++ b/libraries/Adafruit_TinyUSB_Arduino/src/portable/renesas/rusb2/dcd_rusb2.c @@ -0,0 +1,988 @@ +/* + * The MIT License (MIT) + * + * Copyright (c) 2020 Koji Kitayama + * Portions copyrighted (c) 2021 Roland Winistoerfer + * + * 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. + */ + +#include "tusb_option.h" + +#if CFG_TUD_ENABLED && defined(TUP_USBIP_RUSB2) + +#include "device/dcd.h" +#include "rusb2_common.h" + +//--------------------------------------------------------------------+ +// MACRO TYPEDEF CONSTANT ENUM +//--------------------------------------------------------------------+ +enum { + PIPE_COUNT = 10, +}; + +typedef struct { + void *buf; /* the start address of a transfer data buffer */ + uint16_t length; /* the number of bytes in the buffer */ + uint16_t remaining; /* the number of bytes remaining in the buffer */ + + uint8_t ep; /* an assigned endpoint address */ + uint8_t ff; /* `buf` is TU_FUFO or POD */ +} pipe_state_t; + +typedef struct +{ + pipe_state_t pipe[PIPE_COUNT]; + uint8_t ep[2][16]; /* a lookup table for a pipe index from an endpoint address */ + // Track whether sof has been manually enabled + bool sof_enabled; +} dcd_data_t; + +static dcd_data_t _dcd; + +//--------------------------------------------------------------------+ +// INTERNAL OBJECT & FUNCTION DECLARATION +//--------------------------------------------------------------------+ + + +// Transfer conditions specifiable for each pipe for most MCUs +// - Pipe 0: Control transfer with 64-byte single buffer +// - Pipes 1 and 2: Bulk or ISO +// - Pipes 3 to 5: Bulk +// - Pipes 6 to 9: Interrupt +// +// Note: for small mcu such as +// - RA2A1: only pipe 4-7 are available, and no support for ISO +static unsigned find_pipe(unsigned xfer_type) { + #if defined(BSP_MCU_GROUP_RA2A1) + const uint8_t pipe_idx_arr[4][2] = { + { 0, 0 }, // Control + { 0, 0 }, // Isochronous not supported + { 4, 5 }, // Bulk + { 6, 7 }, // Interrupt + }; + #else + const uint8_t pipe_idx_arr[4][2] = { + { 0, 0 }, // Control + { 1, 2 }, // Isochronous + { 1, 5 }, // Bulk + { 6, 9 }, // Interrupt + }; + #endif + + // find backward since only pipe 1, 2 support ISO + const uint8_t idx_first = pipe_idx_arr[xfer_type][0]; + const uint8_t idx_last = pipe_idx_arr[xfer_type][1]; + + for (int i = idx_last; i >= idx_first; i--) { + if (0 == _dcd.pipe[i].ep) { + return (unsigned)i; + } + } + + return 0; +} + +static volatile uint16_t* get_pipectr(rusb2_reg_t *rusb, unsigned num) { + if (num) { + return (volatile uint16_t*)&(rusb->PIPE_CTR[num - 1]); + } else { + return (volatile uint16_t*)&(rusb->DCPCTR); + } +} + +static volatile reg_pipetre_t* get_pipetre(rusb2_reg_t *rusb, unsigned num) { + volatile reg_pipetre_t* tre = NULL; + if ((1 <= num) && (num <= 5)) { + tre = (volatile reg_pipetre_t*)&(rusb->PIPE_TR[num - 1].E); + } + return tre; +} + +static volatile uint16_t* ep_addr_to_pipectr(uint8_t rhport, unsigned ep_addr) { + rusb2_reg_t *rusb = RUSB2_REG(rhport); + const unsigned epn = tu_edpt_number((uint8_t)ep_addr); + + if (epn) { + const unsigned dir = tu_edpt_dir((uint8_t)ep_addr); + const unsigned num = _dcd.ep[dir][epn]; + return get_pipectr(rusb, num); + } else { + return get_pipectr(rusb, 0); + } +} + +static uint16_t edpt0_max_packet_size(rusb2_reg_t* rusb) { + return (uint16_t)rusb->DCPMAXP_b.MXPS; +} + +static uint16_t edpt_max_packet_size(rusb2_reg_t *rusb, unsigned num) { + rusb->PIPESEL = (uint16_t)num; + return rusb->PIPEMAXP; +} + +static inline void pipe_wait_for_ready(rusb2_reg_t * rusb, unsigned num) { + while ( rusb->D0FIFOSEL_b.CURPIPE != num ) {} + while ( !rusb->D0FIFOCTR_b.FRDY ) {} +} + +//--------------------------------------------------------------------+ +// Pipe Transfer +//--------------------------------------------------------------------+ +static bool pipe0_xfer_in(rusb2_reg_t *rusb) { + pipe_state_t *pipe = &_dcd.pipe[0]; + const unsigned rem = pipe->remaining; + + if (!rem) { + pipe->buf = NULL; + return true; + } + + const uint16_t mps = edpt0_max_packet_size(rusb); + const uint16_t len = tu_min16(mps, rem); + void *buf = pipe->buf; + + if (len) { + // uint16_t fifo_sel = RUSB2_CFIFOSEL_ISEL_WRITE | FIFOSEL_BIGEND; + tu_hwfifo_access_t access_mode; + access_mode.param = (uintptr_t)rusb; + // + if (rusb2_is_highspeed_reg(rusb)) { + // fifo_sel |= RUSB2_FIFOSEL_MBW_32BIT; + access_mode.data_stride = 4u; + } else { + // fifo_sel |= RUSB2_FIFOSEL_MBW_16BIT; + access_mode.data_stride = 2u; + } + // rusb->CFIFOSEL = fifo_sel; + // while (0 == (rusb->CFIFOSEL & RUSB2_CFIFOSEL_ISEL_WRITE)) {} + + if (pipe->ff) { + tu_hwfifo_write_from_fifo(&rusb->CFIFO, (tu_fifo_t *)buf, len, &access_mode); + } else { + tu_hwfifo_write(&rusb->CFIFO, buf, len, &access_mode); + pipe->buf = (uint8_t *)buf + len; + } + } + + if (len < mps) { + rusb->CFIFOCTR = RUSB2_CFIFOCTR_BVAL_Msk; + } + + pipe->remaining = rem - len; + return false; +} + +static bool pipe0_xfer_out(rusb2_reg_t *rusb) { + pipe_state_t *pipe = &_dcd.pipe[0]; + const unsigned rem = pipe->remaining; + + const uint16_t mps = edpt0_max_packet_size(rusb); + const uint16_t vld = rusb->CFIFOCTR_b.DTLN; + const uint16_t len = tu_min16(tu_min16(rem, mps), vld); + void *buf = pipe->buf; + + if (len) { + tu_hwfifo_access_t access_mode = {.data_stride = (rusb2_is_highspeed_reg(rusb) ? 4u : 2u), + .param = (uintptr_t)rusb}; + + if (pipe->ff) { + tu_hwfifo_read_to_fifo(&rusb->CFIFO, (tu_fifo_t *)buf, len, &access_mode); + } else { + tu_hwfifo_read(&rusb->CFIFO, buf, len, &access_mode); + pipe->buf = (uint8_t *)buf + len; + } + } + + if (len < mps) { + rusb->CFIFOCTR = RUSB2_CFIFOCTR_BCLR_Msk; + } + + pipe->remaining = rem - len; + if ((len < mps) || (rem == len)) { + pipe->buf = NULL; + return true; + } + + return false; +} + +static bool pipe_xfer_in(rusb2_reg_t* rusb, unsigned num) +{ + pipe_state_t *pipe = &_dcd.pipe[num]; + const unsigned rem = pipe->remaining; + + if (!rem) { + pipe->buf = NULL; + return true; + } + + const uint16_t fifo_sel = num | FIFOSEL_BIGEND; + const bool is_highspeed = rusb2_is_highspeed_reg(rusb); + if (is_highspeed) { + rusb->D0FIFOSEL = fifo_sel | RUSB2_FIFOSEL_MBW_32BIT; + } else { + rusb->D0FIFOSEL = fifo_sel | RUSB2_FIFOSEL_MBW_16BIT; + } + + const uint16_t mps = edpt_max_packet_size(rusb, num); + pipe_wait_for_ready(rusb, num); + uint16_t len = tu_min16(rem, mps); + void *buf = pipe->buf; + + if (len) { + tu_hwfifo_access_t access_mode = {.data_stride = (rusb2_is_highspeed_reg(rusb) ? 4u : 2u), + .param = (uintptr_t)rusb}; + if (pipe->ff) { + tu_hwfifo_write_from_fifo(&rusb->D0FIFO, (tu_fifo_t *)buf, len, &access_mode); + } else { + tu_hwfifo_write(&rusb->D0FIFO, buf, len, &access_mode); + pipe->buf = (uint8_t *)buf + len; + } + } + + if (len < mps) { + rusb->D0FIFOCTR = RUSB2_CFIFOCTR_BVAL_Msk; + } + + rusb->D0FIFOSEL = 0; + while (rusb->D0FIFOSEL_b.CURPIPE) {} /* if CURPIPE bits changes, check written value */ + + pipe->remaining = rem - len; + + return false; +} + +static bool pipe_xfer_out(rusb2_reg_t* rusb, unsigned num) +{ + pipe_state_t *pipe = &_dcd.pipe[num]; + const uint16_t rem = pipe->remaining; + + uint16_t fifo_sel = num | FIFOSEL_BIGEND; + if (rusb2_is_highspeed_reg(rusb)) { + fifo_sel |= RUSB2_FIFOSEL_MBW_32BIT; + } else { + fifo_sel |= RUSB2_FIFOSEL_MBW_16BIT; + } + rusb->D0FIFOSEL = fifo_sel; + + const uint16_t mps = edpt_max_packet_size(rusb, num); + pipe_wait_for_ready(rusb, num); + + const uint16_t vld = (uint16_t)rusb->D0FIFOCTR_b.DTLN; + const uint16_t len = tu_min16(tu_min16(rem, mps), vld); + void *buf = pipe->buf; + + if (len) { + tu_hwfifo_access_t access_mode = {.data_stride = (rusb2_is_highspeed_reg(rusb) ? 4u : 2u), + .param = (uintptr_t)rusb}; + if (pipe->ff) { + tu_hwfifo_read_to_fifo(&rusb->D0FIFO, (tu_fifo_t *)buf, len, &access_mode); + } else { + tu_hwfifo_read(&rusb->D0FIFO, buf, len, &access_mode); + pipe->buf = (uint8_t *)buf + len; + } + } + + if (len < mps) { + rusb->D0FIFOCTR = RUSB2_CFIFOCTR_BCLR_Msk; + } + + rusb->D0FIFOSEL = 0; + while (rusb->D0FIFOSEL_b.CURPIPE) {} /* if CURPIPE bits changes, check written value */ + + pipe->remaining = rem - len; + if ((len < mps) || (rem == len)) { + pipe->buf = NULL; + return NULL != buf; + } + + return false; +} + +static void process_setup_packet(uint8_t rhport) +{ + rusb2_reg_t* rusb = RUSB2_REG(rhport); + if (0 == (rusb->INTSTS0 & RUSB2_INTSTS0_VALID_Msk)) return; + + rusb->CFIFOCTR = RUSB2_CFIFOCTR_BCLR_Msk; + uint16_t setup_packet[4] = { + tu_htole16(rusb->USBREQ), + tu_htole16(rusb->USBVAL), + tu_htole16(rusb->USBINDX), + tu_htole16(rusb->USBLENG) + }; + + rusb->INTSTS0 = ~((uint16_t) RUSB2_INTSTS0_VALID_Msk); + dcd_event_setup_received(rhport, (const uint8_t*)&setup_packet[0], true); +} + +static void process_status_completion(uint8_t rhport) +{ + rusb2_reg_t* rusb = RUSB2_REG(rhport); + uint8_t ep_addr; + /* Check the data stage direction */ + if (rusb->CFIFOSEL & RUSB2_CFIFOSEL_ISEL_WRITE) { + /* IN transfer. */ + ep_addr = tu_edpt_addr(0, TUSB_DIR_IN); + } else { + /* OUT transfer. */ + ep_addr = tu_edpt_addr(0, TUSB_DIR_OUT); + } + + dcd_event_xfer_complete(rhport, ep_addr, 0, XFER_RESULT_SUCCESS, true); +} + +static bool process_pipe0_xfer(rusb2_reg_t *rusb, int buffer_type, uint8_t ep_addr, void *buffer, + uint16_t total_bytes) { + uint16_t fifo_sel = + (rusb2_is_highspeed_reg(rusb) ? RUSB2_FIFOSEL_MBW_32BIT : RUSB2_FIFOSEL_MBW_16BIT) | FIFOSEL_BIGEND; + + /* configure fifo direction and access unit settings */ + if (ep_addr != 0) { + // Control IN + fifo_sel |= RUSB2_CFIFOSEL_ISEL_WRITE; + } + rusb->CFIFOSEL = fifo_sel; + while ((rusb->CFIFOSEL & RUSB2_CFIFOSEL_ISEL_WRITE) != (fifo_sel & RUSB2_CFIFOSEL_ISEL_WRITE)) { + // wait until ISEL_WRITE take effect + } + + pipe_state_t *pipe = &_dcd.pipe[0]; + pipe->ff = buffer_type; + pipe->length = total_bytes; + pipe->remaining = total_bytes; + + if (total_bytes) { + pipe->buf = buffer; + if (ep_addr) { + /* IN */ + TU_ASSERT(rusb->DCPCTR_b.BSTS && (rusb->USBREQ & 0x80)); + pipe0_xfer_in(rusb); + } + rusb->DCPCTR = RUSB2_PIPE_CTR_PID_BUF; + } else { + /* ZLP */ + pipe->buf = NULL; + rusb->DCPCTR = RUSB2_DCPCTR_CCPL_Msk | RUSB2_PIPE_CTR_PID_BUF; + } + + return true; +} + +static bool process_pipe_xfer(rusb2_reg_t* rusb, int buffer_type, uint8_t ep_addr, void* buffer, uint16_t total_bytes) +{ + const unsigned epn = tu_edpt_number(ep_addr); + const unsigned dir = tu_edpt_dir(ep_addr); + const unsigned num = _dcd.ep[dir][epn]; + + TU_ASSERT(num); + + pipe_state_t *pipe = &_dcd.pipe[num]; + pipe->ff = buffer_type; + pipe->buf = buffer; + pipe->length = total_bytes; + pipe->remaining = total_bytes; + + if (dir) { + /* IN */ + if (total_bytes) { + pipe_xfer_in(rusb, num); + } else { + /* ZLP */ + rusb->D0FIFOSEL = num; + pipe_wait_for_ready(rusb, num); + rusb->D0FIFOCTR = RUSB2_CFIFOCTR_BVAL_Msk; + rusb->D0FIFOSEL = 0; + /* if CURPIPE bits changes, check written value */ + while (rusb->D0FIFOSEL_b.CURPIPE) {} + } + } else { + // OUT + volatile reg_pipetre_t *pt = get_pipetre(rusb, num); + + if (pt) { + const uint16_t mps = edpt_max_packet_size(rusb, num); + volatile uint16_t *ctr = get_pipectr(rusb, num); + + if (*ctr & 0x3) *ctr = RUSB2_PIPE_CTR_PID_NAK; + + pt->TRE = TU_BIT(8); + pt->TRN = (total_bytes + mps - 1) / mps; + pt->TRENB = 1; + *ctr = RUSB2_PIPE_CTR_PID_BUF; + } + } + + // TU_LOG2("X %x %d %d\r\n", ep_addr, total_bytes, buffer_type); + return true; +} + +static bool process_edpt_xfer(rusb2_reg_t* rusb, int buffer_type, uint8_t ep_addr, void* buffer, uint16_t total_bytes) +{ + const unsigned epn = tu_edpt_number(ep_addr); + if (0 == epn) { + return process_pipe0_xfer(rusb, buffer_type, ep_addr, buffer, total_bytes); + } else { + return process_pipe_xfer(rusb, buffer_type, ep_addr, buffer, total_bytes); + } +} + +static void process_pipe0_bemp(uint8_t rhport) +{ + rusb2_reg_t* rusb = RUSB2_REG(rhport); + bool completed = pipe0_xfer_in(rusb); + if (completed) { + pipe_state_t *pipe = &_dcd.pipe[0]; + dcd_event_xfer_complete(rhport, tu_edpt_addr(0, TUSB_DIR_IN), + pipe->length, XFER_RESULT_SUCCESS, true); + } +} + +static void process_pipe_brdy(uint8_t rhport, unsigned num) +{ + rusb2_reg_t* rusb = RUSB2_REG(rhport); + pipe_state_t *pipe = &_dcd.pipe[num]; + const unsigned dir = tu_edpt_dir(pipe->ep); + bool completed; + + if (dir) { + /* IN */ + completed = pipe_xfer_in(rusb, num); + } else { + // OUT + if (num) { + completed = pipe_xfer_out(rusb, num); + } else { + completed = pipe0_xfer_out(rusb); + } + } + if (completed) { + dcd_event_xfer_complete(rhport, pipe->ep, + pipe->length - pipe->remaining, + XFER_RESULT_SUCCESS, true); + // TU_LOG1("C %d %d\r\n", num, pipe->length - pipe->remaining); + } +} + +static void process_bus_reset(uint8_t rhport) +{ + rusb2_reg_t* rusb = RUSB2_REG(rhport); + + rusb->BEMPENB = 1; + rusb->BRDYENB = 1; + rusb->CFIFOCTR = RUSB2_CFIFOCTR_BCLR_Msk; + + rusb->D0FIFOSEL = 0; + while (rusb->D0FIFOSEL_b.CURPIPE) {} /* if CURPIPE bits changes, check written value */ + + rusb->D1FIFOSEL = 0; + while (rusb->D1FIFOSEL_b.CURPIPE) {} /* if CURPIPE bits changes, check written value */ + + volatile uint16_t *ctr = (volatile uint16_t*)((uintptr_t) (&rusb->PIPE_CTR[0])); + volatile uint16_t *tre = (volatile uint16_t*)((uintptr_t) (&rusb->PIPE_TR[0].E)); + + for (uint16_t i = 1; i <= 5; ++i) { + rusb->PIPESEL = i; + rusb->PIPECFG = 0; + *ctr = RUSB2_PIPE_CTR_ACLRM_Msk; + *ctr = 0; + ++ctr; + *tre = TU_BIT(8); + tre += 2; + } + + for (uint16_t i = 6; i <= 9; ++i) { + rusb->PIPESEL = i; + rusb->PIPECFG = 0; + *ctr = RUSB2_PIPE_CTR_ACLRM_Msk; + *ctr = 0; + ++ctr; + } + tu_varclr(&_dcd); + + TU_LOG3("Bus reset, RHST = %u\r\n", rusb->DVSTCTR0_b.RHST); + tusb_speed_t speed; + switch(rusb->DVSTCTR0 & RUSB2_DVSTCTR0_RHST_Msk) { + case RUSB2_DVSTCTR0_RHST_LS: + speed = TUSB_SPEED_LOW; + break; + + case RUSB2_DVSTCTR0_RHST_FS: + speed = TUSB_SPEED_FULL; + break; + + case RUSB2_DVSTCTR0_RHST_HS: + speed = TUSB_SPEED_HIGH; + break; + + default: + TU_ASSERT(false, ); + } + + dcd_event_bus_reset(rhport, speed, true); +} + +static void process_set_address(uint8_t rhport) +{ + rusb2_reg_t* rusb = RUSB2_REG(rhport); + const uint16_t addr = (uint16_t)rusb->USBADDR_b.USBADDR; + if (!addr) { + return; + } + + const tusb_control_request_t setup_packet = { +#if defined(__CCRX__) + .bmRequestType = { 0 }, /* Note: CCRX needs the braces over this struct member */ + #else + .bmRequestType = 0, + #endif + .bRequest = TUSB_REQ_SET_ADDRESS, + .wValue = addr, + .wIndex = 0, + .wLength = 0, + }; + + dcd_event_setup_received(rhport, (const uint8_t *) &setup_packet, true); +} + +/*------------------------------------------------------------------*/ +/* Device API + *------------------------------------------------------------------*/ + +#if 0 // previously present in the rx driver before generalization +static uint32_t disable_interrupt(void) +{ + uint32_t pswi; +#if defined(__CCRX__) + pswi = get_psw() & 0x010000; + clrpsw_i(); +#else + pswi = __builtin_rx_mvfc(0) & 0x010000; + __builtin_rx_clrpsw('I'); +#endif + return pswi; +} + +static void enable_interrupt(uint32_t pswi) +{ +#if defined(__CCRX__) + set_psw(get_psw() | pswi); +#else + __builtin_rx_mvtc(0, __builtin_rx_mvfc(0) | pswi); +#endif +} +#endif + +bool dcd_init(uint8_t rhport, const tusb_rhport_init_t* rh_init) { + (void) rh_init; + rusb2_reg_t* rusb = RUSB2_REG(rhport); + rusb2_module_start(rhport, true); + + // We disable SOF for now until needed later on. + // Since TinyUSB doesn't use SOF for now, and this interrupt often (1ms interval) + _dcd.sof_enabled = false; + +#ifdef RUSB2_SUPPORT_HIGHSPEED + if ( rusb2_is_highspeed_rhport(rhport) ) { + rusb->SYSCFG_b.HSE = 1; + + // leave CLKSEL as default (0x11) 24Mhz + + // Power and reset UTMI Phy + uint16_t physet = (rusb->PHYSET | RUSB2_PHYSET_PLLRESET_Msk) & ~RUSB2_PHYSET_DIRPD_Msk; + rusb->PHYSET = physet; + R_BSP_SoftwareDelay((uint32_t) 1, BSP_DELAY_UNITS_MILLISECONDS); + rusb->PHYSET_b.PLLRESET = 0; + + // set UTMI to operating mode and wait for PLL lock confirmation + rusb->LPSTS_b.SUSPENDM = 1; + while (!rusb->PLLSTA_b.PLLLOCK) {} + + rusb->SYSCFG_b.DRPD = 0; + rusb->SYSCFG_b.USBE = 1; + + // Set CPU bus wait time (fine tunne later) + // rusb2->BUSWAIT |= 0x0F00U; + + rusb->PHYSET_b.REPSEL = 1; + } else +#endif + { + rusb->SYSCFG_b.SCKE = 1; + while (!rusb->SYSCFG_b.SCKE) {} + rusb->SYSCFG_b.DRPD = 0; + rusb->SYSCFG_b.DCFM = 0; + rusb->SYSCFG_b.USBE = 1; + + // MCU specific PHY init + rusb2_phy_init(); + + rusb->PHYSLEW = 0x5; + rusb->DPUSR0R_FS_b.FIXPHY0 = 0u; /* USB_BASE Transceiver Output fixed */ + } + + /* Setup default control pipe */ + rusb->DCPMAXP_b.MXPS = 64; + + rusb->INTSTS0 = 0; + rusb->INTENB0 = RUSB2_INTSTS0_VBINT_Msk | RUSB2_INTSTS0_BRDY_Msk | RUSB2_INTSTS0_BEMP_Msk | + RUSB2_INTSTS0_DVST_Msk | RUSB2_INTSTS0_CTRT_Msk | (_dcd.sof_enabled ? RUSB2_INTSTS0_SOFR_Msk : 0) | + RUSB2_INTSTS0_RESM_Msk; + rusb->BEMPENB = 1; + rusb->BRDYENB = 1; + + // If VBUS (detect) pin is not used, application need to call tud_connect() manually after tud_init() + if (rusb->INTSTS0_b.VBSTS) { + dcd_connect(rhport); + } + + return true; +} + +void dcd_int_enable(uint8_t rhport) { + rusb2_int_enable(rhport); +} + +void dcd_int_disable(uint8_t rhport) { + rusb2_int_disable(rhport); +} + +void dcd_set_address(uint8_t rhport, uint8_t dev_addr) { + (void) rhport; + (void) dev_addr; +} + +void dcd_remote_wakeup(uint8_t rhport) +{ + rusb2_reg_t* rusb = RUSB2_REG(rhport); + rusb->DVSTCTR0_b.WKUP = 1; +} + +void dcd_connect(uint8_t rhport) +{ + rusb2_reg_t* rusb = RUSB2_REG(rhport); + + if ( rusb2_is_highspeed_rhport(rhport)) { + rusb->SYSCFG_b.CNEN = 1; + } + rusb->SYSCFG_b.DPRPU = 1; +} + +void dcd_disconnect(uint8_t rhport) +{ + rusb2_reg_t* rusb = RUSB2_REG(rhport); + rusb->SYSCFG_b.DPRPU = 0; +} + +void dcd_sof_enable(uint8_t rhport, bool en) +{ + rusb2_reg_t* rusb = RUSB2_REG(rhport); + _dcd.sof_enabled = en; + rusb->INTENB0_b.SOFE = en ? 1: 0; +} + +//--------------------------------------------------------------------+ +// Endpoint API +//--------------------------------------------------------------------+ +bool dcd_edpt_open(uint8_t rhport, tusb_desc_endpoint_t const * ep_desc) +{ + (void)rhport; + + rusb2_reg_t * rusb = RUSB2_REG(rhport); + const uint8_t ep_addr = ep_desc->bEndpointAddress; + const unsigned epn = tu_edpt_number(ep_addr); + const unsigned dir = tu_edpt_dir(ep_addr); + const unsigned xfer = ep_desc->bmAttributes.xfer; + + const unsigned mps = tu_edpt_packet_size(ep_desc); + + if (xfer == TUSB_XFER_ISOCHRONOUS) { + // Fullspeed ISO is limit to 256 bytes + if ( !rusb2_is_highspeed_rhport(rhport) && mps > 256) { + return false; + } + } + + const unsigned num = find_pipe(xfer); + TU_ASSERT(num); + + _dcd.pipe[num].ep = ep_addr; + _dcd.ep[dir][epn] = num; + + /* setup pipe */ + dcd_int_disable(rhport); + + if ( rusb2_is_highspeed_rhport(rhport) ) { + // FIXME shouldn't be after pipe selection and config, also the BUFNMB should be changed + // depending on the allocation scheme + rusb->PIPEBUF = 0x7C08; + } + + rusb->PIPESEL = num; + rusb->PIPEMAXP = mps; + volatile uint16_t *ctr = get_pipectr(rusb, num); + *ctr = RUSB2_PIPE_CTR_ACLRM_Msk | RUSB2_PIPE_CTR_SQCLR_Msk; + *ctr = 0; + unsigned cfg = (dir << 4) | epn; + + if (xfer == TUSB_XFER_BULK) { + cfg |= (RUSB2_PIPECFG_TYPE_BULK | RUSB2_PIPECFG_SHTNAK_Msk | RUSB2_PIPECFG_DBLB_Msk); + } else if (xfer == TUSB_XFER_INTERRUPT) { + cfg |= RUSB2_PIPECFG_TYPE_INT; + } else { + cfg |= (RUSB2_PIPECFG_TYPE_ISO | RUSB2_PIPECFG_DBLB_Msk); + } + + rusb->PIPECFG = cfg; + rusb->BRDYSTS = 0x3FFu ^ TU_BIT(num); + rusb->BRDYENB |= TU_BIT(num); + + if (dir || (xfer != TUSB_XFER_BULK)) { + *ctr = RUSB2_PIPE_CTR_PID_BUF; + } + + // TU_LOG1("O %d %x %x\r\n", rusb->PIPESEL, rusb->PIPECFG, rusb->PIPEMAXP); + dcd_int_enable(rhport); + + return true; +} + +void dcd_edpt_close_all(uint8_t rhport) +{ + unsigned i = TU_ARRAY_SIZE(_dcd.pipe); + dcd_int_disable(rhport); + while (--i) { /* Close all pipes except 0 */ + const unsigned ep_addr = _dcd.pipe[i].ep; + if (!ep_addr) { + continue; + } + dcd_edpt_close(rhport, (uint8_t)ep_addr); + } + dcd_int_enable(rhport); +} + +void dcd_edpt_close(uint8_t rhport, uint8_t ep_addr) +{ + rusb2_reg_t * rusb = RUSB2_REG(rhport); + const unsigned epn = tu_edpt_number(ep_addr); + const unsigned dir = tu_edpt_dir(ep_addr); + const unsigned num = _dcd.ep[dir][epn]; + + rusb->BRDYENB &= (uint16_t)~TU_BIT(num); + volatile uint16_t *ctr = get_pipectr(rusb, num); + *ctr = 0; + rusb->PIPESEL = (uint16_t)num; + rusb->PIPECFG = 0; + _dcd.pipe[num].ep = 0; + _dcd.ep[dir][epn] = 0; +} + +#if 0 +bool dcd_edpt_iso_alloc(uint8_t rhport, uint8_t ep_addr, uint16_t largest_packet_size) { + (void)rhport; + (void)ep_addr; + (void)largest_packet_size; + return false; +} + +bool dcd_edpt_iso_activate(uint8_t rhport, const tusb_desc_endpoint_t *desc_ep) { + (void)rhport; + (void)desc_ep; + return false; +} +#endif + +bool dcd_edpt_xfer(uint8_t rhport, uint8_t ep_addr, uint8_t * buffer, uint16_t total_bytes, bool is_isr) +{ + (void) is_isr; + rusb2_reg_t* rusb = RUSB2_REG(rhport); + + dcd_int_disable(rhport); + bool r = process_edpt_xfer(rusb, 0, ep_addr, buffer, total_bytes); + dcd_int_enable(rhport); + + return r; +} + +bool dcd_edpt_xfer_fifo(uint8_t rhport, uint8_t ep_addr, tu_fifo_t * ff, uint16_t total_bytes, bool is_isr) +{ + (void) is_isr; + // USB buffers always work in bytes so to avoid unnecessary divisions we demand item_size = 1 + rusb2_reg_t* rusb = RUSB2_REG(rhport); + + dcd_int_disable(rhport); + bool r = process_edpt_xfer(rusb, 1, ep_addr, ff, total_bytes); + dcd_int_enable(rhport); + + return r; +} + +void dcd_edpt_stall(uint8_t rhport, uint8_t ep_addr) +{ + volatile uint16_t *ctr = ep_addr_to_pipectr(rhport, ep_addr); + if (!ctr) { + return; + } + dcd_int_disable(rhport); + const uint32_t pid = *ctr & 0x3; + *ctr = pid | RUSB2_PIPE_CTR_PID_STALL; + *ctr = RUSB2_PIPE_CTR_PID_STALL; + dcd_int_enable(rhport); +} + +void dcd_edpt_clear_stall(uint8_t rhport, uint8_t ep_addr) +{ + rusb2_reg_t * rusb = RUSB2_REG(rhport); + volatile uint16_t *ctr = ep_addr_to_pipectr(rhport, ep_addr); + if (!ctr) { + return; + } + + dcd_int_disable(rhport); + *ctr = RUSB2_PIPE_CTR_SQCLR_Msk; + + if (tu_edpt_dir(ep_addr)) { /* IN */ + *ctr = RUSB2_PIPE_CTR_PID_BUF; + } else { + const unsigned num = _dcd.ep[0][tu_edpt_number(ep_addr)]; + rusb->PIPESEL = (uint16_t)num; + if (rusb->PIPECFG_b.TYPE != 1) { + *ctr = RUSB2_PIPE_CTR_PID_BUF; + } + } + dcd_int_enable(rhport); +} + +//--------------------------------------------------------------------+ +// ISR +//--------------------------------------------------------------------+ + +#if defined(__CCRX__) +TU_ATTR_ALWAYS_INLINE static inline unsigned __builtin_ctz(unsigned int value) { + unsigned int count = 0; + while ((value & 1) == 0) { + value >>= 1; + count++; + } + return count; +} +#endif + +void dcd_int_handler(uint8_t rhport) +{ + rusb2_reg_t* rusb = RUSB2_REG(rhport); + + uint16_t is0 = rusb->INTSTS0; + + /* clear active bits except VALID (don't write 0 to already cleared bits according to the HW manual) */ + rusb->INTSTS0 = ~((RUSB2_INTSTS0_CTRT_Msk | RUSB2_INTSTS0_DVST_Msk | RUSB2_INTSTS0_SOFR_Msk | + RUSB2_INTSTS0_RESM_Msk | RUSB2_INTSTS0_VBINT_Msk) & is0) | RUSB2_INTSTS0_VALID_Msk; + + // VBUS changes + if ( is0 & RUSB2_INTSTS0_VBINT_Msk ) { + if ( rusb->INTSTS0_b.VBSTS ) { + dcd_connect(rhport); + } else { + dcd_disconnect(rhport); + } + } + + // Resumed + if ( is0 & RUSB2_INTSTS0_RESM_Msk ) { + dcd_event_bus_signal(rhport, DCD_EVENT_RESUME, true); + if (!_dcd.sof_enabled) { + rusb->INTENB0_b.SOFE = 0; + } + } + + // SOF received + if ( (is0 & RUSB2_INTSTS0_SOFR_Msk) && rusb->INTENB0_b.SOFE ) { + // USBD will exit suspended mode when SOF event is received + const uint32_t frame = rusb->FRMNUM_b.FRNM; + dcd_event_sof(rhport, frame, true); + if (!_dcd.sof_enabled) { + rusb->INTENB0_b.SOFE = 0; + } + } + + // Device state changes + if ( is0 & RUSB2_INTSTS0_DVST_Msk ) { + switch (is0 & RUSB2_INTSTS0_DVSQ_Msk) { + case RUSB2_INTSTS0_DVSQ_STATE_DEF: + process_bus_reset(rhport); + break; + + case RUSB2_INTSTS0_DVSQ_STATE_ADDR: + process_set_address(rhport); + break; + + case RUSB2_INTSTS0_DVSQ_STATE_SUSP0: + case RUSB2_INTSTS0_DVSQ_STATE_SUSP1: + case RUSB2_INTSTS0_DVSQ_STATE_SUSP2: + case RUSB2_INTSTS0_DVSQ_STATE_SUSP3: + dcd_event_bus_signal(rhport, DCD_EVENT_SUSPEND, true); + if (!_dcd.sof_enabled) { + rusb->INTENB0_b.SOFE = 1; + } + + default: break; + } + } + +// if ( is0 & RUSB2_INTSTS0_NRDY_Msk ) { +// rusb->NRDYSTS = 0; +// } + + // Control transfer stage changes + if ( is0 & RUSB2_INTSTS0_CTRT_Msk ) { + if ( is0 & RUSB2_INTSTS0_CTSQ_CTRL_RDATA ) { + /* A setup packet has been received. */ + process_setup_packet(rhport); + } else if ( 0 == (is0 & RUSB2_INTSTS0_CTSQ_Msk) ) { + /* A ZLP has been sent/received. */ + process_status_completion(rhport); + } + } + + // Buffer empty + if ( is0 & RUSB2_INTSTS0_BEMP_Msk ) { + const uint16_t s = rusb->BEMPSTS; + rusb->BEMPSTS = 0; + if ( s & 1 ) { + process_pipe0_bemp(rhport); + } + } + + // Buffer ready + if ( is0 & RUSB2_INTSTS0_BRDY_Msk ) { + const unsigned m = rusb->BRDYENB; + unsigned s = rusb->BRDYSTS & m; + /* clear active bits (don't write 0 to already cleared bits according to the HW manual) */ + rusb->BRDYSTS = ~s; + while (s) { + const unsigned num = __builtin_ctz(s); + process_pipe_brdy(rhport, num); + s &= ~TU_BIT(num); + } + } +} +#endif diff --git a/libraries/Adafruit_TinyUSB_Arduino/src/portable/renesas/rusb2/hcd_rusb2.c b/libraries/Adafruit_TinyUSB_Arduino/src/portable/renesas/rusb2/hcd_rusb2.c new file mode 100644 index 0000000..4c33150 --- /dev/null +++ b/libraries/Adafruit_TinyUSB_Arduino/src/portable/renesas/rusb2/hcd_rusb2.c @@ -0,0 +1,827 @@ +/* + * The MIT License (MIT) + * + * Copyright (c) 2021 Koji Kitayama + * Portions copyrighted (c) 2021 Roland Winistoerfer + * + * 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. + */ + +#include "tusb_option.h" + +#if CFG_TUH_ENABLED && defined(TUP_USBIP_RUSB2) + +#include "host/hcd.h" +#include "host/usbh.h" +#include "rusb2_common.h" + + #define TU_RUSB2_HCD_DBG 2 + +//--------------------------------------------------------------------+ +// MACRO TYPEDEF CONSTANT ENUM DECLARATION +//--------------------------------------------------------------------+ +enum { + PIPE_COUNT = 10, +}; + +TU_ATTR_PACKED_BEGIN +TU_ATTR_BIT_FIELD_ORDER_BEGIN + +typedef union TU_ATTR_PACKED { + struct { + volatile uint16_t u8: 8; + volatile uint16_t : 0; + }; + volatile uint16_t u16; +} hw_fifo_t; + +typedef struct TU_ATTR_PACKED { + void *buf; /* the start address of a transfer data buffer */ + uint16_t length; /* the number of bytes in the buffer */ + uint16_t remaining; /* the number of bytes remaining in the buffer */ + struct { + uint32_t ep : 8; /* an assigned endpoint address */ + uint32_t dev : 8; /* an assigned device address */ + uint32_t ff : 1; /* `buf` is TU_FUFO or POD */ + uint32_t : 0; + }; +} pipe_state_t; + +TU_ATTR_PACKED_END // End of definition of packed structs (used by the CCRX toolchain) +TU_ATTR_BIT_FIELD_ORDER_END + +typedef struct { + pipe_state_t pipe[PIPE_COUNT]; + uint8_t ep[4][2][15]; /* a lookup table for a pipe index from an endpoint address */ + uint8_t ctl_mps[5]; /* EP0 max packet size for each device */ +} hcd_data_t; + +//--------------------------------------------------------------------+ +// INTERNAL OBJECT & FUNCTION DECLARATION +//--------------------------------------------------------------------+ +static hcd_data_t _hcd; + +// TODO merged with DCD +// Transfer conditions specifiable for each pipe for most MCUs +// - Pipe 0: Control transfer with 64-byte single buffer +// - Pipes 1 and 2: Bulk or ISO +// - Pipes 3 to 5: Bulk +// - Pipes 6 to 9: Interrupt +// +// Note: for small mcu such as +// - RA2A1: only pipe 4-7 are available, and no support for ISO +static unsigned find_pipe(unsigned xfer_type) { + const uint8_t pipe_idx_arr[4][2] = { + { 0, 0 }, // Control + { 1, 2 }, // Isochronous + { 1, 5 }, // Bulk + { 6, 9 }, // Interrupt + }; + + // find backward since only pipe 1, 2 support ISO + const uint8_t idx_first = pipe_idx_arr[xfer_type][0]; + const uint8_t idx_last = pipe_idx_arr[xfer_type][1]; + + for (int i = idx_last; i >= idx_first; i--) { + if (0 == _hcd.pipe[i].ep) return i; + } + + return 0; +} + +static volatile uint16_t* get_pipectr(rusb2_reg_t *rusb, unsigned num) +{ + if (num) { + return (volatile uint16_t*)&(rusb->PIPE_CTR[num - 1]); + } else { + return (volatile uint16_t*)&(rusb->DCPCTR); + } +} + +static volatile reg_pipetre_t* get_pipetre(rusb2_reg_t *rusb, unsigned num) +{ + volatile reg_pipetre_t* tre = NULL; + if ((1 <= num) && (num <= 5)) { + tre = (volatile reg_pipetre_t*)&(rusb->PIPE_TR[num - 1].E); + } + return tre; +} + +static volatile uint16_t* addr_to_pipectr(uint8_t rhport, uint8_t dev_addr, unsigned ep_addr) +{ + rusb2_reg_t* rusb = RUSB2_REG(rhport); + const unsigned epn = tu_edpt_number(ep_addr); + + if (epn) { + const unsigned dir_in = tu_edpt_dir(ep_addr); + const unsigned num = _hcd.ep[dev_addr][dir_in][epn - 1]; + return get_pipectr(rusb, num); + } else { + return get_pipectr(rusb, 0); + } +} + +static uint16_t edpt0_max_packet_size(rusb2_reg_t* rusb) +{ + return rusb->DCPMAXP_b.MXPS; +} + +static uint16_t edpt_max_packet_size(rusb2_reg_t *rusb, unsigned num) +{ + rusb->PIPESEL = num; + return rusb->PIPEMAXP_b.MXPS; +} + +static inline void pipe_wait_for_ready(rusb2_reg_t* rusb, unsigned num) +{ + while (rusb->D0FIFOSEL_b.CURPIPE != num) ; + while (!rusb->D0FIFOCTR_b.FRDY) {} +} + +static bool pipe0_xfer_in(rusb2_reg_t* rusb) +{ + pipe_state_t *pipe = &_hcd.pipe[0]; + const unsigned rem = pipe->remaining; + + const unsigned mps = edpt0_max_packet_size(rusb); + const unsigned vld = rusb->CFIFOCTR_b.DTLN; + const unsigned len = TU_MIN(TU_MIN(rem, mps), vld); + void *buf = pipe->buf; + if (len) { + tu_hwfifo_access_t access_mode = {.data_stride = (rusb2_is_highspeed_reg(rusb) ? 4u : 2u), + .param = (uintptr_t)rusb}; + + rusb->DCPCTR = RUSB2_PIPE_CTR_PID_NAK; + // pipe_read_packet(buf, (volatile void*)&rusb->CFIFO, len); + tu_hwfifo_read(&rusb->CFIFO, buf, len, &access_mode); + pipe->buf = (uint8_t*)buf + len; + } + if (len < mps) { + rusb->CFIFOCTR = RUSB2_CFIFOCTR_BCLR_Msk; + } + pipe->remaining = rem - len; + if ((len < mps) || (rem == len)) { + pipe->buf = NULL; + return true; + } + rusb->DCPCTR = RUSB2_PIPE_CTR_PID_BUF; + return false; +} + +static bool pipe0_xfer_out(rusb2_reg_t* rusb) +{ + pipe_state_t *pipe = &_hcd.pipe[0]; + const unsigned rem = pipe->remaining; + if (!rem) { + pipe->buf = NULL; + return true; + } + const unsigned mps = edpt0_max_packet_size(rusb); + const unsigned len = TU_MIN(mps, rem); + void *buf = pipe->buf; + if (len) { + tu_hwfifo_access_t access_mode = {.data_stride = (rusb2_is_highspeed_reg(rusb) ? 4u : 2u), + .param = (uintptr_t)rusb}; + + // pipe_write_packet(buf, (volatile void*)&rusb->CFIFO, len); + tu_hwfifo_write(&rusb->CFIFO, buf, len, &access_mode); + pipe->buf = (uint8_t*)buf + len; + } + if (len < mps) { + rusb->CFIFOCTR = RUSB2_CFIFOCTR_BVAL_Msk; + } + pipe->remaining = rem - len; + return false; +} + +static bool pipe_xfer_in(rusb2_reg_t* rusb, unsigned num) +{ + pipe_state_t *pipe = &_hcd.pipe[num]; + const unsigned rem = pipe->remaining; + + uint16_t fifo_sel = num | FIFOSEL_BIGEND; + if (rusb2_is_highspeed_reg(rusb)) { + fifo_sel |= RUSB2_FIFOSEL_MBW_32BIT; + } else { + fifo_sel |= RUSB2_FIFOSEL_MBW_16BIT; + } + rusb->D0FIFOSEL = fifo_sel; + + const unsigned mps = edpt_max_packet_size(rusb, num); + pipe_wait_for_ready(rusb, num); + const unsigned vld = rusb->D0FIFOCTR_b.DTLN; + const unsigned len = TU_MIN(TU_MIN(rem, mps), vld); + void *buf = pipe->buf; + if (len) { + // pipe_read_packet(buf, (volatile void*)&rusb->D0FIFO, len); + tu_hwfifo_access_t access_mode = {.data_stride = (rusb2_is_highspeed_reg(rusb) ? 4u : 2u), + .param = (uintptr_t)rusb}; + tu_hwfifo_read(&rusb->D0FIFO, buf, len, &access_mode); + pipe->buf = (uint8_t*)buf + len; + } + if (len < mps) { + rusb->D0FIFOCTR = RUSB2_D0FIFOCTR_BCLR_Msk; + } + rusb->D0FIFOSEL = 0; + while (rusb->D0FIFOSEL_b.CURPIPE) ; /* if CURPIPE bits changes, check written value */ + pipe->remaining = rem - len; + if ((len < mps) || (rem == len)) { + pipe->buf = NULL; + return NULL != buf; + } + return false; +} + +static bool pipe_xfer_out(rusb2_reg_t* rusb, unsigned num) +{ + pipe_state_t *pipe = &_hcd.pipe[num]; + const unsigned rem = pipe->remaining; + + if (!rem) { + pipe->buf = NULL; + return true; + } + + uint16_t fifo_sel = num | FIFOSEL_BIGEND; + if (rusb2_is_highspeed_reg(rusb)) { + fifo_sel |= RUSB2_FIFOSEL_MBW_32BIT; + } else { + fifo_sel |= RUSB2_FIFOSEL_MBW_16BIT; + } + rusb->D0FIFOSEL = fifo_sel; + + const unsigned mps = edpt_max_packet_size(rusb, num); + pipe_wait_for_ready(rusb, num); + const unsigned len = TU_MIN(rem, mps); + void *buf = pipe->buf; + if (len) { + // pipe_write_packet(buf, (volatile void*)&rusb->D0FIFO, len); + tu_hwfifo_access_t access_mode = {.data_stride = (rusb2_is_highspeed_reg(rusb) ? 4u : 2u), + .param = (uintptr_t)rusb}; + tu_hwfifo_write(&rusb->D0FIFO, buf, len, &access_mode); + pipe->buf = (uint8_t*)buf + len; + } + if (len < mps) { + rusb->D0FIFOCTR = RUSB2_D0FIFOCTR_BVAL_Msk; + } + rusb->D0FIFOSEL = 0; + while (rusb->D0FIFOSEL_b.CURPIPE) ; /* if CURPIPE bits changes, check written value */ + pipe->remaining = rem - len; + return false; +} + +static bool process_pipe0_xfer(uint8_t rhport, uint8_t dev_addr, uint8_t ep_addr, void* buffer, uint16_t buflen) +{ + (void)dev_addr; + rusb2_reg_t* rusb = RUSB2_REG(rhport); + const unsigned dir_in = tu_edpt_dir(ep_addr); + + uint16_t fifo_sel = + (rusb2_is_highspeed_reg(rusb) ? RUSB2_FIFOSEL_MBW_32BIT : RUSB2_FIFOSEL_MBW_16BIT) | FIFOSEL_BIGEND; + + /* configure fifo direction and access unit settings */ + if (dir_in == TUSB_DIR_OUT) { + fifo_sel |= RUSB2_CFIFOSEL_ISEL_WRITE; + } + rusb->CFIFOSEL = fifo_sel; + while ((rusb->CFIFOSEL & RUSB2_CFIFOSEL_ISEL_WRITE) != (fifo_sel & RUSB2_CFIFOSEL_ISEL_WRITE)) { + // wait until ISEL_WRITE take effect + } + + pipe_state_t *pipe = &_hcd.pipe[0]; + pipe->ep = ep_addr; + pipe->length = buflen; + pipe->remaining = buflen; + if (buflen) { + pipe->buf = buffer; + if (!dir_in) { /* OUT */ + TU_ASSERT(rusb->DCPCTR_b.BSTS && (rusb->USBREQ & 0x80)); + pipe0_xfer_out(rusb); + } + } else { /* ZLP */ + pipe->buf = NULL; + if (!dir_in) { /* OUT */ + rusb->CFIFOCTR = RUSB2_CFIFOCTR_BVAL_Msk; + } + if (dir_in == rusb->DCPCFG_b.DIR) { + TU_ASSERT(RUSB2_PIPE_CTR_PID_NAK == rusb->DCPCTR_b.PID); + rusb->DCPCTR_b.SQSET = 1; + rusb->DCPCFG_b.DIR = dir_in ^ 1; + } + } + rusb->DCPCTR = RUSB2_PIPE_CTR_PID_BUF; + return true; +} + +static bool process_pipe_xfer(uint8_t rhport, uint8_t dev_addr, uint8_t ep_addr, void *buffer, uint16_t buflen) +{ + rusb2_reg_t* rusb = RUSB2_REG(rhport); + + const unsigned epn = tu_edpt_number(ep_addr); + const unsigned dir_in = tu_edpt_dir(ep_addr); + const unsigned num = _hcd.ep[dev_addr - 1][dir_in][epn - 1]; + + TU_ASSERT(num); + + pipe_state_t *pipe = &_hcd.pipe[num]; + pipe->buf = buffer; + pipe->length = buflen; + pipe->remaining = buflen; + if (!dir_in) { /* OUT */ + if (buflen) { + pipe_xfer_out(rusb, num); + } else { /* ZLP */ + rusb->D0FIFOSEL = num; + pipe_wait_for_ready(rusb, num); + rusb->D0FIFOCTR = RUSB2_D0FIFOCTR_BVAL_Msk; + rusb->D0FIFOSEL = 0; + while (rusb->D0FIFOSEL_b.CURPIPE) {} /* if CURPIPE bits changes, check written value */ + } + } else { + volatile uint16_t *ctr = get_pipectr(rusb, num); + volatile reg_pipetre_t *pt = get_pipetre(rusb, num); + if (pt) { + const unsigned mps = edpt_max_packet_size(rusb, num); + if (*ctr & 0x3) *ctr = RUSB2_PIPE_CTR_PID_NAK; + pt->TRE = TU_BIT(8); + pt->TRN = (buflen + mps - 1) / mps; + pt->TRENB = 1; + } + *ctr = RUSB2_PIPE_CTR_PID_BUF; + } + return true; +} + +static bool process_edpt_xfer(uint8_t rhport, uint8_t dev_addr, uint8_t ep_addr, void* buffer, uint16_t buflen) +{ + const unsigned epn = tu_edpt_number(ep_addr); + if (0 == epn) { + return process_pipe0_xfer(rhport, dev_addr, ep_addr, buffer, buflen); + } else { + return process_pipe_xfer(rhport, dev_addr, ep_addr, buffer, buflen); + } +} + +static void process_pipe0_bemp(uint8_t rhport) +{ + rusb2_reg_t* rusb = RUSB2_REG(rhport); + bool completed = pipe0_xfer_out(rusb); + if (completed) { + pipe_state_t *pipe = &_hcd.pipe[0]; + hcd_event_xfer_complete(pipe->dev, + tu_edpt_addr(0, TUSB_DIR_OUT), + pipe->length - pipe->remaining, + XFER_RESULT_SUCCESS, true); + } +} + +static void process_pipe_nrdy(uint8_t rhport, unsigned num) +{ + rusb2_reg_t* rusb = RUSB2_REG(rhport); + xfer_result_t result; + uint16_t volatile *ctr = get_pipectr(rusb, num); + TU_LOG(TU_RUSB2_HCD_DBG, "NRDY %d %x\r\n", num, *ctr); + switch (*ctr & RUSB2_PIPE_CTR_PID_Msk) { + default: return; + case RUSB2_PIPE_CTR_PID_STALL: result = XFER_RESULT_STALLED; break; + case RUSB2_PIPE_CTR_PID_STALL2: result = XFER_RESULT_STALLED; break; + case RUSB2_PIPE_CTR_PID_NAK: result = XFER_RESULT_FAILED; break; + } + pipe_state_t *pipe = &_hcd.pipe[num]; + hcd_event_xfer_complete(pipe->dev, pipe->ep, + pipe->length - pipe->remaining, + result, true); +} + +static void process_pipe_brdy(uint8_t rhport, unsigned num) +{ + rusb2_reg_t* rusb = RUSB2_REG(rhport); + pipe_state_t *pipe = &_hcd.pipe[num]; + const unsigned dir_in = tu_edpt_dir(pipe->ep); + bool completed; + + if (dir_in) { /* IN */ + if (num) { + completed = pipe_xfer_in(rusb, num); + } else { + completed = pipe0_xfer_in(rusb); + } + } else { + completed = pipe_xfer_out(rusb, num); + } + if (completed) { + hcd_event_xfer_complete(pipe->dev, pipe->ep, + pipe->length - pipe->remaining, + XFER_RESULT_SUCCESS, true); + TU_LOG(TU_RUSB2_HCD_DBG, "C %d %d\r\n", num, pipe->length - pipe->remaining); + } +} + +/*------------------------------------------------------------------*/ +/* Host API + *------------------------------------------------------------------*/ + +#if 0 // previously present in the rx driver before generalization +static uint32_t disable_interrupt(void) +{ + uint32_t pswi; +#if defined(__CCRX__) + pswi = get_psw() & 0x010000; + clrpsw_i(); +#else + pswi = __builtin_rx_mvfc(0) & 0x010000; + __builtin_rx_clrpsw('I'); +#endif + return pswi; +} + +static void enable_interrupt(uint32_t pswi) +{ +#if defined(__CCRX__) + set_psw(get_psw() | pswi); +#else + __builtin_rx_mvtc(0, __builtin_rx_mvfc(0) | pswi); +#endif +} +#endif + +bool hcd_init(uint8_t rhport, const tusb_rhport_init_t* rh_init) { + (void) rh_init; + rusb2_reg_t* rusb = RUSB2_REG(rhport); + rusb2_module_start(rhport, true); + +#ifdef RUSB2_SUPPORT_HIGHSPEED + if (rusb2_is_highspeed_rhport(rhport) ) { + rusb->SYSCFG_b.HSE = 1; + rusb->PHYSET_b.HSEB = 0; + rusb->PHYSET_b.DIRPD = 0; + R_BSP_SoftwareDelay((uint32_t) 1, BSP_DELAY_UNITS_MILLISECONDS); + rusb->PHYSET_b.PLLRESET = 0; + rusb->LPSTS_b.SUSPENDM = 1; + while ( !rusb->PLLSTA_b.PLLLOCK ); + rusb->SYSCFG_b.DRPD = 1; + rusb->SYSCFG_b.DCFM = 1; + rusb->SYSCFG_b.DPRPU = 0; + rusb->SYSCFG_b.CNEN = 1; + rusb->BUSWAIT |= 0x0F00U; + rusb->SOFCFG_b.INTL = 1; + rusb->DVSTCTR0_b.VBUSEN = 1; + rusb->CFIFOSEL_b.MBW = 1; + rusb->D0FIFOSEL_b.MBW = 1; + rusb->D1FIFOSEL_b.MBW = 1; + rusb->INTSTS0 = 0; + for ( volatile int i = 0; i < 30000; ++i ); + rusb->SYSCFG_b.USBE = 1; + } else +#endif + { + rusb->SYSCFG_b.SCKE = 1; + while ( !rusb->SYSCFG_b.SCKE ) {} + rusb->SYSCFG_b.DCFM = 1; // Host function + rusb->SYSCFG_b.DPRPU = 0; // Disable D+ pull up + rusb->SYSCFG_b.DRPD = 1; // Enable D+/D- pull down + + rusb->DVSTCTR0_b.VBUSEN = 1; + for ( volatile int i = 0; i < 30000; ++i ) {} // FIXME do we need to wait here? how long ? + //R_BSP_SoftwareDelay(10, BSP_DELAY_UNITS_MILLISECONDS); + rusb->SYSCFG_b.USBE = 1; + + // MCU specific PHY init + rusb2_phy_init(); + + rusb->PHYSLEW = 0x5; + rusb->DPUSR0R_FS_b.FIXPHY0 = 0u; /* Transceiver Output fixed */ + } + + /* Setup default control pipe */ + rusb->DCPCFG = RUSB2_PIPECFG_SHTNAK_Msk; + rusb->DCPMAXP = 64; + rusb->INTENB0 = RUSB2_INTSTS0_BRDY_Msk | RUSB2_INTSTS0_NRDY_Msk | RUSB2_INTSTS0_BEMP_Msk; + rusb->INTENB1 = RUSB2_INTSTS1_SACK_Msk | RUSB2_INTSTS1_SIGN_Msk | RUSB2_INTSTS1_ATTCH_Msk | RUSB2_INTSTS1_DTCH_Msk; + rusb->BEMPENB = 1; + rusb->NRDYENB = 1; + rusb->BRDYENB = 1; + + return true; +} + +void hcd_int_enable(uint8_t rhport) { + rusb2_int_enable(rhport); +} + +void hcd_int_disable(uint8_t rhport) { + rusb2_int_disable(rhport); +} + +uint32_t hcd_frame_number(uint8_t rhport) { + rusb2_reg_t *rusb = RUSB2_REG(rhport); + return rusb->FRMNUM_b.FRNM; +} + +/*--------------------------------------------------------------------+ + * Port API + *--------------------------------------------------------------------+*/ +bool hcd_port_connect_status(uint8_t rhport) { + rusb2_reg_t* rusb = RUSB2_REG(rhport); + const uint16_t line_state = rusb->SYSSTS0 & RUSB2_SYSSTS0_LNST_Msk; + return line_state == RUSB2_SYSSTS0_LNST_FS_J || line_state == RUSB2_SYSSTS0_LNST_FS_K; +} + +void hcd_port_reset(uint8_t rhport) { + rusb2_reg_t* rusb = RUSB2_REG(rhport); + rusb->DVSTCTR0_b.USBRST = 1; +} + +void hcd_port_reset_end(uint8_t rhport) { + rusb2_reg_t *rusb = RUSB2_REG(rhport); + rusb->DVSTCTR0_b.USBRST = 0; +} + +tusb_speed_t hcd_port_speed_get(uint8_t rhport) { + rusb2_reg_t* rusb = RUSB2_REG(rhport); + switch (rusb->DVSTCTR0_b.RHST) { + case RUSB2_DVSTCTR0_RHST_HS: return TUSB_SPEED_HIGH; + case RUSB2_DVSTCTR0_RHST_FS: return TUSB_SPEED_FULL; + case RUSB2_DVSTCTR0_RHST_LS: return TUSB_SPEED_LOW; + default: + return TUSB_SPEED_INVALID; + } +} + +void hcd_device_close(uint8_t rhport, uint8_t dev_addr) { + rusb2_reg_t* rusb = RUSB2_REG(rhport); + uint16_t volatile *ctr; + + TU_ASSERT(dev_addr < 6,); /* USBa can only handle addresses from 0 to 5. */ + if (!dev_addr) return; + + _hcd.ctl_mps[dev_addr] = 0; + uint8_t *ep = &_hcd.ep[dev_addr - 1][0][0]; + + for (int i = 0; i < 2 * 15; ++i, ++ep) { + unsigned num = *ep; + if (!num || (dev_addr != _hcd.pipe[num].dev)) continue; + + ctr = (uint16_t volatile*)&rusb->PIPE_CTR[num - 1]; + *ctr = 0; + rusb->NRDYENB &= ~TU_BIT(num); + rusb->BRDYENB &= ~TU_BIT(num); + rusb->PIPESEL = num; + rusb->PIPECFG = 0; + rusb->PIPEMAXP = 0; + + _hcd.pipe[num].ep = 0; + _hcd.pipe[num].dev = 0; + *ep = 0; + } +} + +/*--------------------------------------------------------------------+ + * Endpoints API + *--------------------------------------------------------------------+*/ +bool hcd_setup_send(uint8_t rhport, uint8_t dev_addr, uint8_t const setup_packet[8]) +{ + TU_ASSERT(dev_addr < 6); /* USBa can only handle addresses from 0 to 5. */ + + rusb2_reg_t* rusb = RUSB2_REG(rhport); + TU_LOG(TU_RUSB2_HCD_DBG, "S %d %x\r\n", dev_addr, rusb->DCPCTR); + + TU_ASSERT(0 == rusb->DCPCTR_b.SUREQ); + + rusb->DCPCTR = RUSB2_PIPE_CTR_PID_NAK; + + _hcd.pipe[0].buf = NULL; + _hcd.pipe[0].length = 8; + _hcd.pipe[0].remaining = 0; + _hcd.pipe[0].dev = dev_addr; + + while (rusb->DCPCTR_b.PBUSY) ; + rusb->DCPMAXP = (dev_addr << 12) | _hcd.ctl_mps[dev_addr]; + + /* Set direction in advance for DATA stage */ + uint8_t const bmRequesttype = setup_packet[0]; + rusb->DCPCFG_b.DIR = tu_edpt_dir(bmRequesttype) ? 0: 1; + + uint16_t const* p = (uint16_t const*)(uintptr_t)&setup_packet[0]; + rusb->USBREQ = tu_htole16(p[0]); + rusb->USBVAL = p[1]; + rusb->USBINDX = p[2]; + rusb->USBLENG = p[3]; + + rusb->DCPCTR_b.SUREQ = 1; + return true; +} + +bool hcd_edpt_open(uint8_t rhport, uint8_t dev_addr, tusb_desc_endpoint_t const *ep_desc) +{ + TU_ASSERT(dev_addr < 6); /* USBa can only handle addresses from 0 to 5. */ + rusb2_reg_t* rusb = RUSB2_REG(rhport); + + const unsigned ep_addr = ep_desc->bEndpointAddress; + const unsigned epn = tu_edpt_number(ep_addr); + const unsigned mps = tu_edpt_packet_size(ep_desc); + + if (0 == epn) { + rusb->DCPCTR = RUSB2_PIPE_CTR_PID_NAK; + tuh_bus_info_t bus_info; + tuh_bus_info_get(dev_addr, &bus_info); + uint16_t volatile *devadd = (uint16_t volatile *)(uintptr_t) &rusb->DEVADD[0]; + devadd += dev_addr; + while (rusb->DCPCTR_b.PBUSY) {} + rusb->DCPMAXP = (dev_addr << 12) | mps; + *devadd = (TUSB_SPEED_FULL == bus_info.speed) ? RUSB2_DEVADD_USBSPD_FS : RUSB2_DEVADD_USBSPD_LS; + _hcd.ctl_mps[dev_addr] = mps; + return true; + } + + const unsigned dir_in = tu_edpt_dir(ep_addr); + const unsigned xfer = ep_desc->bmAttributes.xfer; + if (xfer == TUSB_XFER_ISOCHRONOUS && mps > 256) { + /* USBa supports up to 256 bytes */ + return false; + } + const unsigned num = find_pipe(xfer); + if (!num) return false; + + _hcd.pipe[num].dev = dev_addr; + _hcd.pipe[num].ep = ep_addr; + _hcd.ep[dev_addr - 1][dir_in][epn - 1] = num; + + /* setup pipe */ + hcd_int_disable(rhport); + + rusb->PIPESEL = num; + rusb->PIPEMAXP = (dev_addr << 12) | mps; + volatile uint16_t *ctr = get_pipectr(rusb, num); + *ctr = RUSB2_PIPE_CTR_ACLRM_Msk | RUSB2_PIPE_CTR_SQCLR_Msk; + *ctr = 0; + + unsigned cfg = ((1 ^ dir_in) << 4) | epn; + if (xfer == TUSB_XFER_BULK) { + cfg |= RUSB2_PIPECFG_TYPE_BULK | RUSB2_PIPECFG_SHTNAK_Msk | RUSB2_PIPECFG_DBLB_Msk; + } else if (xfer == TUSB_XFER_INTERRUPT) { + cfg |= RUSB2_PIPECFG_TYPE_INT; + } else { + cfg |= RUSB2_PIPECFG_TYPE_ISO | RUSB2_PIPECFG_DBLB_Msk; + } + + rusb->PIPECFG = cfg; + rusb->BRDYSTS = 0x3FFu ^ TU_BIT(num); + rusb->NRDYENB |= TU_BIT(num); + rusb->BRDYENB |= TU_BIT(num); + + if (!dir_in) { + *ctr = RUSB2_PIPE_CTR_PID_BUF; + } + + hcd_int_enable(rhport); + + return true; +} + +bool hcd_edpt_close(uint8_t rhport, uint8_t daddr, uint8_t ep_addr) { + (void) rhport; (void) daddr; (void) ep_addr; + return false; // TODO not implemented yet +} + +bool hcd_edpt_xfer(uint8_t rhport, uint8_t dev_addr, uint8_t ep_addr, uint8_t *buffer, uint16_t buflen) +{ + bool r; + hcd_int_disable(rhport); + TU_LOG(TU_RUSB2_HCD_DBG, "X %d %x %u\r\n", dev_addr, ep_addr, buflen); + r = process_edpt_xfer(rhport, dev_addr, ep_addr, buffer, buflen); + hcd_int_enable(rhport); + return r; +} + +bool hcd_edpt_abort_xfer(uint8_t rhport, uint8_t dev_addr, uint8_t ep_addr) { + (void) rhport; + (void) dev_addr; + (void) ep_addr; + // TODO not implemented yet + return false; +} + +bool hcd_edpt_clear_stall(uint8_t rhport, uint8_t dev_addr, uint8_t ep_addr) { + uint16_t volatile *ctr = addr_to_pipectr(rhport, dev_addr, ep_addr); + TU_ASSERT(ctr); + + const uint32_t pid = *ctr & 0x3; + if (pid & 2) { + *ctr = pid & 2; + *ctr = 0; + } + *ctr = RUSB2_PIPE_CTR_SQCLR_Msk; + unsigned const epn = tu_edpt_number(ep_addr); + if (!epn) return true; + + if (!tu_edpt_dir(ep_addr)) { /* OUT */ + *ctr = RUSB2_PIPE_CTR_PID_BUF; + } + return true; +} + +//--------------------------------------------------------------------+ +// ISR +//--------------------------------------------------------------------+ +#if defined(__CCRX__) +TU_ATTR_ALWAYS_INLINE static inline unsigned __builtin_ctz(unsigned int value) { + unsigned int count = 0; + while ((value & 1) == 0) { + value >>= 1; + count++; + } + return count; +} +#endif + +void hcd_int_handler(uint8_t rhport, bool in_isr) { + (void) in_isr; + + rusb2_reg_t* rusb = RUSB2_REG(rhport); + unsigned is0 = rusb->INTSTS0; + unsigned is1 = rusb->INTSTS1; + + /* clear active bits except VALID (don't write 0 to already cleared bits according to the HW manual) */ + rusb->INTSTS1 = ~((RUSB2_INTSTS1_SACK_Msk | RUSB2_INTSTS1_SIGN_Msk | RUSB2_INTSTS1_ATTCH_Msk | RUSB2_INTSTS1_DTCH_Msk) & is1); + rusb->INTSTS0 = ~((RUSB2_INTSTS0_BRDY_Msk | RUSB2_INTSTS0_NRDY_Msk | RUSB2_INTSTS0_BEMP_Msk) & is0); + + TU_LOG3("IS %04x %04x\r\n", is0, is1); + is1 &= rusb->INTENB1; + is0 &= rusb->INTENB0; + + if (is1 & RUSB2_INTSTS1_SACK_Msk) { + /* Set DATA1 in advance for the next transfer. */ + rusb->DCPCTR_b.SQSET = 1; + hcd_event_xfer_complete(rusb->DCPMAXP_b.DEVSEL, tu_edpt_addr(0, TUSB_DIR_OUT), 8, XFER_RESULT_SUCCESS, true); + } + + if (is1 & RUSB2_INTSTS1_SIGN_Msk) { + hcd_event_xfer_complete(rusb->DCPMAXP_b.DEVSEL, tu_edpt_addr(0, TUSB_DIR_OUT), 8, XFER_RESULT_FAILED, true); + } + + if (is1 & RUSB2_INTSTS1_ATTCH_Msk) { + rusb->DVSTCTR0_b.UACT = 1; + rusb->INTENB1 = (rusb->INTENB1 & ~RUSB2_INTSTS1_ATTCH_Msk) | RUSB2_INTSTS1_DTCH_Msk; + hcd_event_device_attach(rhport, true); + } + + if (is1 & RUSB2_INTSTS1_DTCH_Msk) { + rusb->DVSTCTR0_b.UACT = 0; + if (rusb->DCPCTR_b.SUREQ) { + rusb->DCPCTR_b.SUREQCLR = 1; + } + rusb->INTENB1 = (rusb->INTENB1 & ~RUSB2_INTSTS1_DTCH_Msk) | RUSB2_INTSTS1_ATTCH_Msk; + hcd_event_device_remove(rhport, true); + } + + if (is0 & RUSB2_INTSTS0_BEMP_Msk) { + const unsigned s = rusb->BEMPSTS; + rusb->BEMPSTS = 0; + if (s & 1) { + process_pipe0_bemp(rhport); + } + } + + if (is0 & RUSB2_INTSTS0_NRDY_Msk) { + const unsigned m = rusb->NRDYENB; + unsigned s = rusb->NRDYSTS & m; + rusb->NRDYSTS = ~s; + while (s) { + const unsigned num = __builtin_ctz(s); + process_pipe_nrdy(rhport, num); + s &= ~TU_BIT(num); + } + } + if (is0 & RUSB2_INTSTS0_BRDY_Msk) { + const unsigned m = rusb->BRDYENB; + unsigned s = rusb->BRDYSTS & m; + /* clear active bits (don't write 0 to already cleared bits according to the HW manual) */ + rusb->BRDYSTS = ~s; + while (s) { + const unsigned num = __builtin_ctz(s); + process_pipe_brdy(rhport, num); + s &= ~TU_BIT(num); + } + } +} + +#endif diff --git a/libraries/Adafruit_TinyUSB_Arduino/src/portable/renesas/rusb2/rusb2_common.c b/libraries/Adafruit_TinyUSB_Arduino/src/portable/renesas/rusb2/rusb2_common.c new file mode 100644 index 0000000..8addbe4 --- /dev/null +++ b/libraries/Adafruit_TinyUSB_Arduino/src/portable/renesas/rusb2/rusb2_common.c @@ -0,0 +1,116 @@ +/* + * The MIT License (MIT) + * + * Copyright (c) 2023 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. + * + * This file is part of the TinyUSB stack. + */ + +#include "tusb_option.h" + +#if defined(TUP_USBIP_RUSB2) && (CFG_TUH_ENABLED || CFG_TUD_ENABLED) + #include "osal/osal.h" + #include "common/tusb_fifo.h" + + #include "rusb2_common.h" + + #if TU_CHECK_MCU(OPT_MCU_RAXXX) + #include "rusb2_ra.h" + +// USBFS_INT_IRQn and USBHS_USB_INT_RESUME_IRQn are generated by FSP +rusb2_controller_t rusb2_controller[] = { + {.reg_base = R_USB_FS0_BASE, .irqnum = USBFS_INT_IRQn}, + #ifdef RUSB2_SUPPORT_HIGHSPEED + {.reg_base = R_USB_HS0_BASE, .irqnum = USBHS_USB_INT_RESUME_IRQn}, + #endif +}; + +// Application API for setting IRQ number. May throw warnings for missing prototypes. +void tusb_rusb2_set_irqnum(uint8_t rhport, int32_t irqnum); +void tusb_rusb2_set_irqnum(uint8_t rhport, int32_t irqnum) { + rusb2_controller[rhport].irqnum = irqnum; +} + #endif + +//--------------------------------------------------------------------+ +// +//--------------------------------------------------------------------+ +static void hwfifo_set_mbw(rusb2_reg_t *rusb, uintptr_t hwfifo, uint16_t mbw) { + volatile uint16_t *fifo_sel; + if (hwfifo == (uintptr_t)&rusb->CFIFO) { + fifo_sel = &rusb->CFIFOSEL; + } else if (hwfifo == (uintptr_t)&rusb->D0FIFO) { + fifo_sel = &rusb->D0FIFOSEL; + } else if (hwfifo == (uintptr_t)&rusb->D1FIFO) { + fifo_sel = &rusb->D1FIFOSEL; + } else { + return; + } + + *fifo_sel = (*fifo_sel & ~RUSB2_CFIFOSEL_MBW_Msk) | mbw; +} + +// write to hwfifo from buffer with access mode +void tu_hwfifo_write(volatile void *hwfifo, const uint8_t *src, uint16_t len, const tu_hwfifo_access_t *access_mode) { + rusb2_reg_t *rusb = (rusb2_reg_t *)access_mode->param; + const uint8_t *buf8 = (const uint8_t *)src; + + volatile uint16_t *ff16; + volatile uint8_t *ff8; + const bool is_highspeed = rusb2_is_highspeed_reg(rusb); + if (is_highspeed) { + ff16 = (volatile uint16_t *)((uintptr_t)hwfifo + 2); + ff8 = (volatile uint8_t *)((uintptr_t)hwfifo + 3); + } else { + ff16 = (volatile uint16_t *)hwfifo; + ff8 = ((volatile uint8_t *)hwfifo); + } + + // 32-bit access for highspeed + if (is_highspeed) { + volatile uint32_t *ff32 = (volatile uint32_t *)hwfifo; + while (len >= 4) { + *ff32 = tu_unaligned_read32(buf8); + buf8 += 4; + len -= 4; + } + + if (len >= 2) { + // switch to 16-bit access + hwfifo_set_mbw(rusb, (uintptr_t)hwfifo, RUSB2_FIFOSEL_MBW_16BIT); + } + } + + // 16-bit access + while (len >= 2) { + *ff16 = tu_unaligned_read16(buf8); + buf8 += 2; + len -= 2; + } + + // 8-bit access does not need to change MBW + if (len > 0) { + *ff8 = *buf8; + ++buf8; + } +} + +#endif diff --git a/libraries/Adafruit_TinyUSB_Arduino/src/portable/renesas/rusb2/rusb2_common.h b/libraries/Adafruit_TinyUSB_Arduino/src/portable/renesas/rusb2/rusb2_common.h new file mode 100644 index 0000000..6b8c3d7 --- /dev/null +++ b/libraries/Adafruit_TinyUSB_Arduino/src/portable/renesas/rusb2/rusb2_common.h @@ -0,0 +1,58 @@ +/* + * The MIT License (MIT) + * + * Copyright (c) 2025 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. + * + * This file is part of the TinyUSB stack. + */ +#pragma once + +#include "common/tusb_common.h" +#include "rusb2_type.h" + +#if TU_CHECK_MCU(OPT_MCU_RX63X, OPT_MCU_RX65X, OPT_MCU_RX72N) + #include "rusb2_rx.h" +#elif TU_CHECK_MCU(OPT_MCU_RAXXX) + #include "rusb2_ra.h" + + // Hack for D0FIFO definitions on RA Cortex-M23 + #if defined(RENESAS_CORTEX_M23) + #define D0FIFO CFIFO + #define D0FIFOSEL CFIFOSEL + #define D0FIFOSEL_b CFIFOSEL_b + #define D1FIFOSEL CFIFOSEL + #define D1FIFOSEL_b CFIFOSEL_b + #define D0FIFOCTR CFIFOCTR + #define D0FIFOCTR_b CFIFOCTR_b + #endif + +#else + #error "Unsupported MCU" +#endif + + +//--------------------------------------------------------------------+ +// Common +//--------------------------------------------------------------------+ + +enum { + FIFOSEL_BIGEND = (TU_BYTE_ORDER == TU_BIG_ENDIAN ? RUSB2_FIFOSEL_BIGEND : 0) +}; diff --git a/libraries/Adafruit_TinyUSB_Arduino/src/portable/renesas/rusb2/rusb2_ra.h b/libraries/Adafruit_TinyUSB_Arduino/src/portable/renesas/rusb2/rusb2_ra.h new file mode 100644 index 0000000..4774d2e --- /dev/null +++ b/libraries/Adafruit_TinyUSB_Arduino/src/portable/renesas/rusb2/rusb2_ra.h @@ -0,0 +1,109 @@ +/* + * The MIT License (MIT) + * + * Copyright (c) 2022 Rafael Silva (@perigoso) + * + * 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 _RUSB2_RA_H_ +#define _RUSB2_RA_H_ + +#ifdef __cplusplus +extern "C" { +#endif + +#ifdef __GNUC__ +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wstrict-prototypes" +#pragma GCC diagnostic ignored "-Wundef" + +// extra push due to https://github.com/renesas/fsp/pull/278 +#pragma GCC diagnostic push +#endif + +/* renesas fsp api */ +#include "bsp_api.h" + +#ifdef __GNUC__ +#pragma GCC diagnostic pop +#endif + +// IAR does not have __builtin_ctz +#if defined(__ICCARM__) + #define __builtin_ctz(x) __iar_builtin_CLZ(__iar_builtin_RBIT(x)) +#endif + +//--------------------------------------------------------------------+ +// +//--------------------------------------------------------------------+ + +typedef struct { + uint32_t reg_base; + int32_t irqnum; +}rusb2_controller_t; + +#if defined(BSP_MCU_GROUP_RA6M5) || defined(BSP_MCU_GROUP_RA6M3) || (BSP_CFG_MCU_PART_SERIES == 8) + #define RUSB2_SUPPORT_HIGHSPEED + #define RUSB2_CONTROLLER_COUNT 2 + + #define rusb2_is_highspeed_rhport(_p) (_p == 1) + #define rusb2_is_highspeed_reg(_reg) (_reg == RUSB2_REG(1)) +#else + #define RUSB2_CONTROLLER_COUNT 1 + + #define rusb2_is_highspeed_rhport(_p) (false) + #define rusb2_is_highspeed_reg(_reg) (false) +#endif + +extern rusb2_controller_t rusb2_controller[]; +#define RUSB2_REG(_p) ((rusb2_reg_t*) rusb2_controller[_p].reg_base) + +//--------------------------------------------------------------------+ +// RUSB2 API +//--------------------------------------------------------------------+ + +TU_ATTR_ALWAYS_INLINE static inline void rusb2_module_start(uint8_t rhport, bool start) { + uint32_t const mask = 1U << (11+rhport); + if (start) { + R_MSTP->MSTPCRB &= ~mask; + }else { + R_MSTP->MSTPCRB |= mask; + } +} + +TU_ATTR_ALWAYS_INLINE static inline void rusb2_int_enable(uint8_t rhport) { + NVIC_EnableIRQ(rusb2_controller[rhport].irqnum); +} + +TU_ATTR_ALWAYS_INLINE static inline void rusb2_int_disable(uint8_t rhport) { + NVIC_DisableIRQ(rusb2_controller[rhport].irqnum); +} + +// MCU specific PHY init +TU_ATTR_ALWAYS_INLINE static inline void rusb2_phy_init(void) { +} + +#ifdef __cplusplus +} +#endif + +#endif /* _RUSB2_RA_H_ */ diff --git a/libraries/Adafruit_TinyUSB_Arduino/src/portable/renesas/rusb2/rusb2_rx.h b/libraries/Adafruit_TinyUSB_Arduino/src/portable/renesas/rusb2/rusb2_rx.h new file mode 100644 index 0000000..7bf4be4 --- /dev/null +++ b/libraries/Adafruit_TinyUSB_Arduino/src/portable/renesas/rusb2/rusb2_rx.h @@ -0,0 +1,94 @@ +/* + * The MIT License (MIT) + * + * Copyright (c) 2020 Koji Kitayama + * Portions copyrighted (c) 2021 Roland Winistoerfer + * Copyright (c) 2022 Rafael Silva (@perigoso) + * + * 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 _RUSB2_RX_H_ +#define _RUSB2_RX_H_ + +#ifdef __cplusplus +extern "C" { +#endif + +#include "iodefine.h" + +#define RUSB2_REG_BASE (0x000A0000) + +TU_ATTR_ALWAYS_INLINE static inline rusb2_reg_t* RUSB2_REG(uint8_t rhport) { + (void) rhport; + return (rusb2_reg_t *) RUSB2_REG_BASE; +} + + +#define rusb2_is_highspeed_rhport(_p) (false) +#define rusb2_is_highspeed_reg(_reg) (false) + +//--------------------------------------------------------------------+ +// +//--------------------------------------------------------------------+ + + +// Start/Stop MSTP TODO implement later +TU_ATTR_ALWAYS_INLINE static inline void rusb2_module_start(uint8_t rhport, bool start) { + (void) rhport; + (void) start; +} + +TU_ATTR_ALWAYS_INLINE static inline void rusb2_int_enable(uint8_t rhport) +{ + (void) rhport; +#if (CFG_TUSB_MCU == OPT_MCU_RX72N) + IEN(PERIB, INTB185) = 1; +#else + IEN(USB0, USBI0) = 1; +#endif +} + +TU_ATTR_ALWAYS_INLINE static inline void rusb2_int_disable(uint8_t rhport) +{ + (void) rhport; +#if (CFG_TUSB_MCU == OPT_MCU_RX72N) + IEN(PERIB, INTB185) = 0; +#else + IEN(USB0, USBI0) = 0; +#endif +} + +// MCU specific PHY init +TU_ATTR_ALWAYS_INLINE static inline void rusb2_phy_init(void) +{ +#if (CFG_TUSB_MCU == OPT_MCU_RX72N) + IR(PERIB, INTB185) = 0; +#else + IR(USB0, USBI0) = 0; +#endif +} + +#ifdef __cplusplus +} +#endif + +#endif /* _RUSB2_RX_H_ */ diff --git a/libraries/Adafruit_TinyUSB_Arduino/src/portable/renesas/rusb2/rusb2_type.h b/libraries/Adafruit_TinyUSB_Arduino/src/portable/renesas/rusb2/rusb2_type.h new file mode 100644 index 0000000..21f1168 --- /dev/null +++ b/libraries/Adafruit_TinyUSB_Arduino/src/portable/renesas/rusb2/rusb2_type.h @@ -0,0 +1,1784 @@ +/* + * The MIT License (MIT) + * + * Copyright (c) 2022 Rafael Silva (@perigoso) + * + * 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_RUSB2_TYPE_H_ +#define TUSB_RUSB2_TYPE_H_ + +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +// CCRX specific attribute to generate a Code that Accesses Variables in the Declared Size +#ifdef __CCRX__ + #define _ccrx_evenaccess __evenaccess +#else + #define _ccrx_evenaccess +#endif + +//--------------------------------------------------------------------+ +// Register Definitions +//--------------------------------------------------------------------+ +/* Start of definition of packed structs (used by the CCRX toolchain) */ +TU_ATTR_PACKED_BEGIN +TU_ATTR_BIT_FIELD_ORDER_BEGIN + +// TODO same as RUSB2_PIPE_TR_t +typedef struct TU_ATTR_PACKED _ccrx_evenaccess { + union { + struct { + uint16_t : 8; + uint16_t TRCLR: 1; + uint16_t TRENB: 1; + uint16_t : 0; + }; + uint16_t TRE; + }; + uint16_t TRN; +} reg_pipetre_t; + +typedef struct { + union { + volatile uint16_t E; /* (@ 0x00000000) Pipe Transaction Counter Enable Register */ + + struct TU_ATTR_PACKED { + uint16_t : 8; + volatile uint16_t TRCLR : 1; /* [8..8] Transaction Counter Clear */ + volatile uint16_t TRENB : 1; /* [9..9] Transaction Counter Enable */ + uint16_t : 6; + } E_b; + }; + + union { + volatile uint16_t N; /* (@ 0x00000002) Pipe Transaction Counter Register */ + + struct TU_ATTR_PACKED { + volatile uint16_t TRNCNT : 16; /* [15..0] Transaction Counter */ + } N_b; + }; +} RUSB2_PIPE_TR_t; /* Size = 4 (0x4) */ + + +/* RUSB2 Registers Structure */ +typedef struct _ccrx_evenaccess { + union { + volatile uint16_t SYSCFG; /* (@ 0x00000000) System Configuration Control Register */ + + struct TU_ATTR_PACKED { + volatile uint16_t USBE : 1; /* [0..0] USB Operation Enable */ + uint16_t : 2; + volatile uint16_t DMRPU : 1; /* [3..3] D- Line Resistor Control */ + volatile uint16_t DPRPU : 1; /* [4..4] D+ Line Resistor Control */ + volatile uint16_t DRPD : 1; /* [5..5] D+/D- Line Resistor Control */ + volatile uint16_t DCFM : 1; /* [6..6] Controller Function Select */ + volatile uint16_t HSE : 1; // [7..7] High-Speed Operation Enable + volatile uint16_t CNEN : 1; /* [8..8] CNEN Single End Receiver Enable */ + uint16_t : 1; + volatile uint16_t SCKE : 1; /* [10..10] USB Clock Enable */ + uint16_t : 5; + } SYSCFG_b; + }; + + union { + volatile uint16_t BUSWAIT; /* (@ 0x00000002) CPU Bus Wait Register */ + + struct TU_ATTR_PACKED { + volatile uint16_t BWAIT : 4; /* [3..0] CPU Bus Access Wait Specification BWAIT waits (BWAIT+2 access cycles) */ + uint16_t : 12; + } BUSWAIT_b; + }; + + union { + volatile const uint16_t SYSSTS0; /* (@ 0x00000004) System Configuration Status Register 0 */ + + struct TU_ATTR_PACKED { + volatile const uint16_t LNST : 2; /* [1..0] USB Data Line Status Monitor */ + volatile const uint16_t IDMON : 1; /* [2..2] External ID0 Input Pin Monitor */ + uint16_t : 2; + volatile const uint16_t SOFEA : 1; /* [5..5] SOF Active Monitor While Host Controller Function is Selected. */ + volatile const uint16_t HTACT : 1; /* [6..6] USB Host Sequencer Status Monitor */ + uint16_t : 7; + volatile const uint16_t OVCMON : 2; /* [15..14] External USB0_OVRCURA/ USB0_OVRCURB Input Pin Monitor */ + } SYSSTS0_b; + }; + + union { + volatile const uint16_t PLLSTA; /* (@ 0x00000006) PLL Status Register */ + + struct TU_ATTR_PACKED { + volatile const uint16_t PLLLOCK : 1; /* [0..0] PLL Lock Flag */ + uint16_t : 15; + } PLLSTA_b; + }; + + union { + volatile uint16_t DVSTCTR0; /* (@ 0x00000008) Device State Control Register 0 */ + + struct TU_ATTR_PACKED { + volatile const uint16_t RHST : 3; /* [2..0] USB Bus Reset Status */ + uint16_t : 1; + volatile uint16_t UACT : 1; /* [4..4] USB Bus Enable */ + volatile uint16_t RESUME : 1; /* [5..5] Resume Output */ + volatile uint16_t USBRST : 1; /* [6..6] USB Bus Reset Output */ + volatile uint16_t RWUPE : 1; /* [7..7] Wakeup Detection Enable */ + volatile uint16_t WKUP : 1; /* [8..8] Wakeup Output */ + volatile uint16_t VBUSEN : 1; /* [9..9] USB_VBUSEN Output Pin Control */ + volatile uint16_t EXICEN : 1; /* [10..10] USB_EXICEN Output Pin Control */ + volatile uint16_t HNPBTOA : 1; /* [11..11] Host Negotiation Protocol (HNP) */ + uint16_t : 4; + } DVSTCTR0_b; + }; + volatile const uint16_t RESERVED; + + union { + volatile uint16_t TESTMODE; /* (@ 0x0000000C) USB Test Mode Register */ + + struct TU_ATTR_PACKED { + volatile uint16_t UTST : 4; /* [3..0] Test Mode */ + uint16_t : 12; + } TESTMODE_b; + }; + volatile const uint16_t RESERVED1; + volatile const uint32_t RESERVED2; + + union { + volatile uint32_t CFIFO; /* (@ 0x00000014) CFIFO Port Register */ + + struct TU_ATTR_PACKED { + union { + volatile uint16_t CFIFOL; /* (@ 0x00000014) CFIFO Port Register L */ + volatile uint8_t CFIFOLL; /* (@ 0x00000014) CFIFO Port Register LL */ + }; + + union { + volatile uint16_t CFIFOH; /* (@ 0x00000016) CFIFO Port Register H */ + + struct TU_ATTR_PACKED { + volatile const uint8_t RESERVED3; + volatile uint8_t CFIFOHH; /* (@ 0x00000017) CFIFO Port Register HH */ + }; + }; + }; + }; + + union { + volatile uint32_t D0FIFO; /* (@ 0x00000018) D0FIFO Port Register */ + + struct TU_ATTR_PACKED { + union { + volatile uint16_t D0FIFOL; /* (@ 0x00000018) D0FIFO Port Register L */ + volatile uint8_t D0FIFOLL; /* (@ 0x00000018) D0FIFO Port Register LL */ + }; + + union { + volatile uint16_t D0FIFOH; /* (@ 0x0000001A) D0FIFO Port Register H */ + + struct TU_ATTR_PACKED { + volatile const uint8_t RESERVED4; + volatile uint8_t D0FIFOHH; /* (@ 0x0000001B) D0FIFO Port Register HH */ + }; + }; + }; + }; + + union { + volatile uint32_t D1FIFO; /* (@ 0x0000001C) D1FIFO Port Register */ + + struct TU_ATTR_PACKED { + union { + volatile uint16_t D1FIFOL; /* (@ 0x0000001C) D1FIFO Port Register L */ + volatile uint8_t D1FIFOLL; /* (@ 0x0000001C) D1FIFO Port Register LL */ + }; + + union { + volatile uint16_t D1FIFOH; /* (@ 0x0000001E) D1FIFO Port Register H */ + + struct TU_ATTR_PACKED { + volatile const uint8_t RESERVED5; + volatile uint8_t D1FIFOHH; /* (@ 0x0000001F) D1FIFO Port Register HH */ + }; + }; + }; + }; + + union { + volatile uint16_t CFIFOSEL; /* (@ 0x00000020) CFIFO Port Select Register */ + + struct TU_ATTR_PACKED { + volatile uint16_t CURPIPE : 4; /* [3..0] CFIFO Port Access Pipe Specification */ + uint16_t : 1; + volatile uint16_t ISEL : 1; /* [5..5] CFIFO Port Access Direction When DCP is Selected */ + uint16_t : 2; + volatile uint16_t BIGEND : 1; /* [8..8] CFIFO Port Endian Control */ + uint16_t : 1; + volatile uint16_t MBW : 2; /* [11..10] CFIFO Port Access Bit Width */ + uint16_t : 2; + volatile uint16_t REW : 1; /* [14..14] Buffer Pointer Rewind */ + volatile uint16_t RCNT : 1; /* [15..15] Read Count Mode */ + } CFIFOSEL_b; + }; + + union { + volatile uint16_t CFIFOCTR; /* (@ 0x00000022) CFIFO Port Control Register */ + + struct TU_ATTR_PACKED { + volatile const uint16_t DTLN : 12; /* [11..0] Receive Data LengthIndicates the length of the receive data. */ + uint16_t : 1; + volatile const uint16_t FRDY : 1; /* [13..13] FIFO Port Ready */ + volatile uint16_t BCLR : 1; /* [14..14] CPU Buffer ClearNote: Only 0 can be read. */ + volatile uint16_t BVAL : 1; /* [15..15] Buffer Memory Valid Flag */ + } CFIFOCTR_b; + }; + volatile const uint32_t RESERVED6; + + union { + volatile uint16_t D0FIFOSEL; /* (@ 0x00000028) D0FIFO Port Select Register */ + + struct TU_ATTR_PACKED { + volatile uint16_t CURPIPE : 4; /* [3..0] FIFO Port Access Pipe Specification */ + uint16_t : 4; + volatile uint16_t BIGEND : 1; /* [8..8] FIFO Port Endian Control */ + uint16_t : 1; + volatile uint16_t MBW : 2; /* [11..10] FIFO Port Access Bit Width */ + volatile uint16_t DREQE : 1; /* [12..12] DMA/DTC Transfer Request Enable */ + volatile uint16_t DCLRM : 1; /* [13..13] Auto Buffer Memory Clear Mode Accessed after Specified Pipe Data is Read */ + volatile uint16_t REW : 1; /* [14..14] Buffer Pointer RewindNote: Only 0 can be read. */ + volatile uint16_t RCNT : 1; /* [15..15] Read Count Mode */ + } D0FIFOSEL_b; + }; + + union { + volatile uint16_t D0FIFOCTR; /* (@ 0x0000002A) D0FIFO Port Control Register */ + + struct TU_ATTR_PACKED { + volatile const uint16_t DTLN : 12; /* [11..0] Receive Data LengthIndicates the length of the receive data. */ + uint16_t : 1; + volatile const uint16_t FRDY : 1; /* [13..13] FIFO Port Ready */ + volatile uint16_t BCLR : 1; /* [14..14] CPU Buffer ClearNote: Only 0 can be read. */ + volatile uint16_t BVAL : 1; /* [15..15] Buffer Memory Valid Flag */ + } D0FIFOCTR_b; + }; + + union { + volatile uint16_t D1FIFOSEL; /* (@ 0x0000002C) D1FIFO Port Select Register */ + + struct TU_ATTR_PACKED { + volatile uint16_t CURPIPE : 4; /* [3..0] FIFO Port Access Pipe Specification */ + uint16_t : 4; + volatile uint16_t BIGEND : 1; /* [8..8] FIFO Port Endian Control */ + uint16_t : 1; + volatile uint16_t MBW : 2; /* [11..10] FIFO Port Access Bit Width */ + volatile uint16_t DREQE : 1; /* [12..12] DMA/DTC Transfer Request Enable */ + volatile uint16_t DCLRM : 1; /* [13..13] Auto Buffer Memory Clear Mode Accessed after Specified Pipe Data is Read */ + volatile uint16_t REW : 1; /* [14..14] Buffer Pointer Rewind */ + volatile uint16_t RCNT : 1; /* [15..15] Read Count Mode */ + } D1FIFOSEL_b; + }; + + union { + volatile uint16_t D1FIFOCTR; /* (@ 0x0000002E) D1FIFO Port Control Register */ + + struct TU_ATTR_PACKED { + volatile const uint16_t DTLN : 12; /* [11..0] Receive Data LengthIndicates the length of the receive data. */ + uint16_t : 1; + volatile const uint16_t FRDY : 1; /* [13..13] FIFO Port Ready */ + volatile uint16_t BCLR : 1; /* [14..14] CPU Buffer ClearNote: Only 0 can be read. */ + volatile uint16_t BVAL : 1; /* [15..15] Buffer Memory Valid Flag */ + } D1FIFOCTR_b; + }; + + union { + volatile uint16_t INTENB0; /* (@ 0x00000030) Interrupt Enable Register 0 */ + + struct TU_ATTR_PACKED { + uint16_t : 8; + volatile uint16_t BRDYE : 1; /* [8..8] Buffer Ready Interrupt Enable */ + volatile uint16_t NRDYE : 1; /* [9..9] Buffer Not Ready Response Interrupt Enable */ + volatile uint16_t BEMPE : 1; /* [10..10] Buffer Empty Interrupt Enable */ + volatile uint16_t CTRE : 1; /* [11..11] Control Transfer Stage Transition Interrupt Enable */ + volatile uint16_t DVSE : 1; /* [12..12] Device State Transition Interrupt Enable */ + volatile uint16_t SOFE : 1; /* [13..13] Frame Number Update Interrupt Enable */ + volatile uint16_t RSME : 1; /* [14..14] Resume Interrupt Enable */ + volatile uint16_t VBSE : 1; /* [15..15] VBUS Interrupt Enable */ + } INTENB0_b; + }; + + union { + volatile uint16_t INTENB1; /* (@ 0x00000032) Interrupt Enable Register 1 */ + + struct TU_ATTR_PACKED { + volatile uint16_t PDDETINTE0 : 1; /* [0..0] PDDETINT0 Detection Interrupt Enable */ + uint16_t : 3; + volatile uint16_t SACKE : 1; /* [4..4] Setup Transaction Normal Response Interrupt Enable */ + volatile uint16_t SIGNE : 1; /* [5..5] Setup Transaction Error Interrupt Enable */ + volatile uint16_t EOFERRE : 1; /* [6..6] EOF Error Detection Interrupt Enable */ + uint16_t : 1; + volatile uint16_t LPMENDE : 1; /*!< [8..8] LPM Transaction End Interrupt Enable */ + volatile uint16_t L1RSMENDE : 1; /*!< [9..9] L1 Resume End Interrupt Enable */ + uint16_t : 1; + volatile uint16_t ATTCHE : 1; /* [11..11] Connection Detection Interrupt Enable */ + volatile uint16_t DTCHE : 1; /* [12..12] Disconnection Detection Interrupt Enable */ + uint16_t : 1; + volatile uint16_t BCHGE : 1; /* [14..14] USB Bus Change Interrupt Enable */ + volatile uint16_t OVRCRE : 1; /* [15..15] Overcurrent Input Change Interrupt Enable */ + } INTENB1_b; + }; + volatile const uint16_t RESERVED7; + + union { + volatile uint16_t BRDYENB; /* (@ 0x00000036) BRDY Interrupt Enable Register */ + + struct TU_ATTR_PACKED { + volatile uint16_t PIPE0BRDYE : 1; /* [0..0] BRDY Interrupt Enable for PIPE */ + volatile uint16_t PIPE1BRDYE : 1; /* [1..1] BRDY Interrupt Enable for PIPE */ + volatile uint16_t PIPE2BRDYE : 1; /* [2..2] BRDY Interrupt Enable for PIPE */ + volatile uint16_t PIPE3BRDYE : 1; /* [3..3] BRDY Interrupt Enable for PIPE */ + volatile uint16_t PIPE4BRDYE : 1; /* [4..4] BRDY Interrupt Enable for PIPE */ + volatile uint16_t PIPE5BRDYE : 1; /* [5..5] BRDY Interrupt Enable for PIPE */ + volatile uint16_t PIPE6BRDYE : 1; /* [6..6] BRDY Interrupt Enable for PIPE */ + volatile uint16_t PIPE7BRDYE : 1; /* [7..7] BRDY Interrupt Enable for PIPE */ + volatile uint16_t PIPE8BRDYE : 1; /* [8..8] BRDY Interrupt Enable for PIPE */ + volatile uint16_t PIPE9BRDYE : 1; /* [9..9] BRDY Interrupt Enable for PIPE */ + uint16_t : 6; + } BRDYENB_b; + }; + + union { + volatile uint16_t NRDYENB; /* (@ 0x00000038) NRDY Interrupt Enable Register */ + + struct TU_ATTR_PACKED { + volatile uint16_t PIPE0NRDYE : 1; /* [0..0] NRDY Interrupt Enable for PIPE */ + volatile uint16_t PIPE1NRDYE : 1; /* [1..1] NRDY Interrupt Enable for PIPE */ + volatile uint16_t PIPE2NRDYE : 1; /* [2..2] NRDY Interrupt Enable for PIPE */ + volatile uint16_t PIPE3NRDYE : 1; /* [3..3] NRDY Interrupt Enable for PIPE */ + volatile uint16_t PIPE4NRDYE : 1; /* [4..4] NRDY Interrupt Enable for PIPE */ + volatile uint16_t PIPE5NRDYE : 1; /* [5..5] NRDY Interrupt Enable for PIPE */ + volatile uint16_t PIPE6NRDYE : 1; /* [6..6] NRDY Interrupt Enable for PIPE */ + volatile uint16_t PIPE7NRDYE : 1; /* [7..7] NRDY Interrupt Enable for PIPE */ + volatile uint16_t PIPE8NRDYE : 1; /* [8..8] NRDY Interrupt Enable for PIPE */ + volatile uint16_t PIPE9NRDYE : 1; /* [9..9] NRDY Interrupt Enable for PIPE */ + uint16_t : 6; + } NRDYENB_b; + }; + + union { + volatile uint16_t BEMPENB; /* (@ 0x0000003A) BEMP Interrupt Enable Register */ + + struct TU_ATTR_PACKED { + volatile uint16_t PIPE0BEMPE : 1; /* [0..0] BEMP Interrupt Enable for PIPE */ + volatile uint16_t PIPE1BEMPE : 1; /* [1..1] BEMP Interrupt Enable for PIPE */ + volatile uint16_t PIPE2BEMPE : 1; /* [2..2] BEMP Interrupt Enable for PIPE */ + volatile uint16_t PIPE3BEMPE : 1; /* [3..3] BEMP Interrupt Enable for PIPE */ + volatile uint16_t PIPE4BEMPE : 1; /* [4..4] BEMP Interrupt Enable for PIPE */ + volatile uint16_t PIPE5BEMPE : 1; /* [5..5] BEMP Interrupt Enable for PIPE */ + volatile uint16_t PIPE6BEMPE : 1; /* [6..6] BEMP Interrupt Enable for PIPE */ + volatile uint16_t PIPE7BEMPE : 1; /* [7..7] BEMP Interrupt Enable for PIPE */ + volatile uint16_t PIPE8BEMPE : 1; /* [8..8] BEMP Interrupt Enable for PIPE */ + volatile uint16_t PIPE9BEMPE : 1; /* [9..9] BEMP Interrupt Enable for PIPE */ + uint16_t : 6; + } BEMPENB_b; + }; + + union { + volatile uint16_t SOFCFG; /* (@ 0x0000003C) SOF Output Configuration Register */ + + struct TU_ATTR_PACKED { + uint16_t : 4; + volatile const uint16_t EDGESTS : 1; /* [4..4] Edge Interrupt Output Status Monitor */ + volatile uint16_t INTL : 1; /* [5..5] Interrupt Output Sense Select */ + volatile uint16_t BRDYM : 1; /* [6..6] BRDY Interrupt Status Clear Timing */ + uint16_t : 1; + volatile uint16_t TRNENSEL : 1; /* [8..8] Transaction-Enabled Time Select */ + uint16_t : 7; + } SOFCFG_b; + }; + + union { + volatile uint16_t PHYSET; /* (@ 0x0000003E) PHY Setting Register */ + + struct TU_ATTR_PACKED { + volatile uint16_t DIRPD : 1; /* [0..0] Power-Down Control */ + volatile uint16_t PLLRESET : 1; /* [1..1] PLL Reset Control */ + uint16_t : 1; + volatile uint16_t CDPEN : 1; /* [3..3] Charging Downstream Port Enable */ + volatile uint16_t CLKSEL : 2; /* [5..4] Input System Clock Frequency */ + uint16_t : 2; + volatile uint16_t REPSEL : 2; /* [9..8] Terminating Resistance Adjustment Cycle */ + uint16_t : 1; + volatile uint16_t REPSTART : 1; /* [11..11] Forcibly Start Terminating Resistance Adjustment */ + uint16_t : 3; + volatile uint16_t HSEB : 1; /* [15..15] CL-Only Mode */ + } PHYSET_b; + }; + + union { + volatile uint16_t INTSTS0; /* (@ 0x00000040) Interrupt Status Register 0 */ + + struct TU_ATTR_PACKED { + volatile const uint16_t CTSQ : 3; /* [2..0] Control Transfer Stage */ + volatile uint16_t VALID : 1; /* [3..3] USB Request Reception */ + volatile const uint16_t DVSQ : 3; /* [6..4] Device State */ + volatile const uint16_t VBSTS : 1; /* [7..7] VBUS Input Status */ + volatile const uint16_t BRDY : 1; /* [8..8] Buffer Ready Interrupt Status */ + volatile const uint16_t NRDY : 1; /* [9..9] Buffer Not Ready Interrupt Status */ + volatile const uint16_t BEMP : 1; /* [10..10] Buffer Empty Interrupt Status */ + volatile uint16_t CTRT : 1; /* [11..11] Control Transfer Stage Transition Interrupt Status */ + volatile uint16_t DVST : 1; /* [12..12] Device State Transition Interrupt Status */ + volatile uint16_t SOFR : 1; /* [13..13] Frame Number Refresh Interrupt Status */ + volatile uint16_t RESM : 1; /* [14..14] Resume Interrupt Status */ + volatile uint16_t VBINT : 1; /* [15..15] VBUS Interrupt Status */ + } INTSTS0_b; + }; + + union { + volatile uint16_t INTSTS1; /* (@ 0x00000042) Interrupt Status Register 1 */ + + struct TU_ATTR_PACKED { + volatile uint16_t PDDETINT0 : 1; /* [0..0] PDDET0 Detection Interrupt Status */ + uint16_t : 3; + volatile uint16_t SACK : 1; /* [4..4] Setup Transaction Normal Response Interrupt Status */ + volatile uint16_t SIGN : 1; /* [5..5] Setup Transaction Error Interrupt Status */ + volatile uint16_t EOFERR : 1; /* [6..6] EOF Error Detection Interrupt Status */ + uint16_t : 1; + volatile uint16_t LPMEND : 1; /* [8..8] LPM Transaction End Interrupt Status */ + volatile uint16_t L1RSMEND : 1; /* [9..9] L1 Resume End Interrupt Status */ + uint16_t : 1; + volatile uint16_t ATTCH : 1; /* [11..11] ATTCH Interrupt Status */ + volatile uint16_t DTCH : 1; /* [12..12] USB Disconnection Detection Interrupt Status */ + uint16_t : 1; + volatile uint16_t BCHG : 1; /* [14..14] USB Bus Change Interrupt Status */ + volatile uint16_t OVRCR : 1; /* [15..15] Overcurrent Input Change Interrupt Status */ + } INTSTS1_b; + }; + volatile const uint16_t RESERVED8; + + union { + volatile uint16_t BRDYSTS; /* (@ 0x00000046) BRDY Interrupt Status Register */ + + struct TU_ATTR_PACKED { + volatile uint16_t PIPE0BRDY : 1; /* [0..0] BRDY Interrupt Status for PIPE */ + volatile uint16_t PIPE1BRDY : 1; /* [1..1] BRDY Interrupt Status for PIPE */ + volatile uint16_t PIPE2BRDY : 1; /* [2..2] BRDY Interrupt Status for PIPE */ + volatile uint16_t PIPE3BRDY : 1; /* [3..3] BRDY Interrupt Status for PIPE */ + volatile uint16_t PIPE4BRDY : 1; /* [4..4] BRDY Interrupt Status for PIPE */ + volatile uint16_t PIPE5BRDY : 1; /* [5..5] BRDY Interrupt Status for PIPE */ + volatile uint16_t PIPE6BRDY : 1; /* [6..6] BRDY Interrupt Status for PIPE */ + volatile uint16_t PIPE7BRDY : 1; /* [7..7] BRDY Interrupt Status for PIPE */ + volatile uint16_t PIPE8BRDY : 1; /* [8..8] BRDY Interrupt Status for PIPE */ + volatile uint16_t PIPE9BRDY : 1; /* [9..9] BRDY Interrupt Status for PIPE */ + uint16_t : 6; + } BRDYSTS_b; + }; + + union { + volatile uint16_t NRDYSTS; /* (@ 0x00000048) NRDY Interrupt Status Register */ + + struct TU_ATTR_PACKED { + volatile uint16_t PIPE0NRDY : 1; /* [0..0] NRDY Interrupt Status for PIPE */ + volatile uint16_t PIPE1NRDY : 1; /* [1..1] NRDY Interrupt Status for PIPE */ + volatile uint16_t PIPE2NRDY : 1; /* [2..2] NRDY Interrupt Status for PIPE */ + volatile uint16_t PIPE3NRDY : 1; /* [3..3] NRDY Interrupt Status for PIPE */ + volatile uint16_t PIPE4NRDY : 1; /* [4..4] NRDY Interrupt Status for PIPE */ + volatile uint16_t PIPE5NRDY : 1; /* [5..5] NRDY Interrupt Status for PIPE */ + volatile uint16_t PIPE6NRDY : 1; /* [6..6] NRDY Interrupt Status for PIPE */ + volatile uint16_t PIPE7NRDY : 1; /* [7..7] NRDY Interrupt Status for PIPE */ + volatile uint16_t PIPE8NRDY : 1; /* [8..8] NRDY Interrupt Status for PIPE */ + volatile uint16_t PIPE9NRDY : 1; /* [9..9] NRDY Interrupt Status for PIPE */ + uint16_t : 6; + } NRDYSTS_b; + }; + + union { + volatile uint16_t BEMPSTS; /* (@ 0x0000004A) BEMP Interrupt Status Register */ + + struct TU_ATTR_PACKED { + volatile uint16_t PIPE0BEMP : 1; /* [0..0] BEMP Interrupt Status for PIPE */ + volatile uint16_t PIPE1BEMP : 1; /* [1..1] BEMP Interrupt Status for PIPE */ + volatile uint16_t PIPE2BEMP : 1; /* [2..2] BEMP Interrupt Status for PIPE */ + volatile uint16_t PIPE3BEMP : 1; /* [3..3] BEMP Interrupt Status for PIPE */ + volatile uint16_t PIPE4BEMP : 1; /* [4..4] BEMP Interrupt Status for PIPE */ + volatile uint16_t PIPE5BEMP : 1; /* [5..5] BEMP Interrupt Status for PIPE */ + volatile uint16_t PIPE6BEMP : 1; /* [6..6] BEMP Interrupt Status for PIPE */ + volatile uint16_t PIPE7BEMP : 1; /* [7..7] BEMP Interrupt Status for PIPE */ + volatile uint16_t PIPE8BEMP : 1; /* [8..8] BEMP Interrupt Status for PIPE */ + volatile uint16_t PIPE9BEMP : 1; /* [9..9] BEMP Interrupt Status for PIPE */ + uint16_t : 6; + } BEMPSTS_b; + }; + + union { + volatile uint16_t FRMNUM; /* (@ 0x0000004C) Frame Number Register */ + + struct TU_ATTR_PACKED { + volatile const uint16_t FRNM : 11; /* [10..0] Frame NumberLatest frame number */ + uint16_t : 3; + volatile uint16_t CRCE : 1; /* [14..14] Receive Data Error */ + volatile uint16_t OVRN : 1; /* [15..15] Overrun/Underrun Detection Status */ + } FRMNUM_b; + }; + + union { + volatile uint16_t UFRMNUM; /* (@ 0x0000004E) uFrame Number Register */ + + struct TU_ATTR_PACKED { + volatile const uint16_t UFRNM : 3; /* [2..0] MicroframeIndicate the microframe number. */ + uint16_t : 12; + volatile uint16_t DVCHG : 1; /* [15..15] Device State Change */ + } UFRMNUM_b; + }; + + union { + volatile uint16_t USBADDR; /* (@ 0x00000050) USB Address Register */ + + struct TU_ATTR_PACKED { + volatile const uint16_t USBADDR : 7; /* [6..0] USB Address In device controller mode */ + uint16_t : 1; + volatile uint16_t STSRECOV0 : 3; /* [10..8] Status Recovery */ + uint16_t : 5; + } USBADDR_b; + }; + volatile const uint16_t RESERVED9; + + union { + volatile uint16_t USBREQ; /* (@ 0x00000054) USB Request Type Register */ + + struct TU_ATTR_PACKED { + volatile uint16_t BMREQUESTTYPE : 8; /* [7..0] Request TypeThese bits store the USB request bmRequestType value. */ + volatile uint16_t BREQUEST : 8; /* [15..8] RequestThese bits store the USB request bRequest value. */ + } USBREQ_b; + }; + + union { + volatile uint16_t USBVAL; /* (@ 0x00000056) USB Request Value Register */ + + struct TU_ATTR_PACKED { + volatile uint16_t WVALUE : 16; /* [15..0] ValueThese bits store the USB request Value value. */ + } USBVAL_b; + }; + + union { + volatile uint16_t USBINDX; /* (@ 0x00000058) USB Request Index Register */ + + struct TU_ATTR_PACKED { + volatile uint16_t WINDEX : 16; /* [15..0] IndexThese bits store the USB request wIndex value. */ + } USBINDX_b; + }; + + union { + volatile uint16_t USBLENG; /* (@ 0x0000005A) USB Request Length Register */ + + struct TU_ATTR_PACKED { + volatile uint16_t WLENGTH : 16; /* [15..0] LengthThese bits store the USB request wLength value. */ + } USBLENG_b; + }; + + union { + volatile uint16_t DCPCFG; /* (@ 0x0000005C) DCP Configuration Register */ + + struct TU_ATTR_PACKED { + uint16_t : 4; + volatile uint16_t DIR : 1; /* [4..4] Transfer Direction */ + uint16_t : 2; + volatile uint16_t SHTNAK : 1; /* [7..7] Pipe Disabled at End of Transfer */ + volatile uint16_t CNTMD : 1; /* [8..8] Continuous Transfer Mode */ + uint16_t : 7; + } DCPCFG_b; + }; + + union { + volatile uint16_t DCPMAXP; /* (@ 0x0000005E) DCP Maximum Packet Size Register */ + + struct TU_ATTR_PACKED { + volatile uint16_t MXPS : 7; /* [6..0] Maximum Packet Size */ + uint16_t : 5; + volatile uint16_t DEVSEL : 4; /* [15..12] Device Select */ + } DCPMAXP_b; + }; + + union { + volatile uint16_t DCPCTR; /* (@ 0x00000060) DCP Control Register */ + + struct TU_ATTR_PACKED { + volatile uint16_t PID : 2; /* [1..0] Response PID */ + volatile uint16_t CCPL : 1; /* [2..2] Control Transfer End Enable */ + uint16_t : 2; + volatile const uint16_t PBUSY : 1; /* [5..5] Pipe Busy */ + volatile const uint16_t SQMON : 1; /* [6..6] Sequence Toggle Bit Monitor */ + volatile uint16_t SQSET : 1; /* [7..7] Sequence Toggle Bit Set */ + volatile uint16_t SQCLR : 1; /* [8..8] Sequence Toggle Bit Clear */ + uint16_t : 2; + volatile uint16_t SUREQCLR : 1; /* [11..11] SUREQ Bit Clear */ + volatile uint16_t CSSTS : 1; /* [12..12] Split Transaction COMPLETE SPLIT(CSPLIT) Status */ + volatile uint16_t CSCLR : 1; /* [13..13] Split Transaction CSPLIT Status Clear */ + volatile uint16_t SUREQ : 1; /* [14..14] Setup Token Transmission */ + volatile const uint16_t BSTS : 1; /* [15..15] Buffer Status */ + } DCPCTR_b; + }; + volatile const uint16_t RESERVED10; + + union { + volatile uint16_t PIPESEL; /* (@ 0x00000064) Pipe Window Select Register */ + + struct TU_ATTR_PACKED { + volatile uint16_t PIPESEL : 4; /* [3..0] Pipe Window Select */ + uint16_t : 12; + } PIPESEL_b; + }; + volatile const uint16_t RESERVED11; + + union { + volatile uint16_t PIPECFG; /* (@ 0x00000068) Pipe Configuration Register */ + + struct TU_ATTR_PACKED { + volatile uint16_t EPNUM : 4; /* [3..0] Endpoint Number */ + volatile uint16_t DIR : 1; /* [4..4] Transfer Direction */ + uint16_t : 2; + volatile uint16_t SHTNAK : 1; /* [7..7] Pipe Disabled at End of Transfer */ + volatile uint16_t CNTMD : 1; /* [8..8] Continuous Transfer Mode */ + volatile uint16_t DBLB : 1; /* [9..9] Double Buffer Mode */ + volatile uint16_t BFRE : 1; /* [10..10] BRDY Interrupt Operation Specification */ + uint16_t : 3; + volatile uint16_t TYPE : 2; /* [15..14] Transfer Type */ + } PIPECFG_b; + }; + + union { + volatile uint16_t PIPEBUF; /*!< (@ 0x0000006A) Pipe Buffer Register */ + + struct { + volatile uint16_t BUFNMB : 8; // [7..0] Buffer NumberThese bits specify the FIFO buffer number of the selected pipe (04h to 87h) + uint16_t : 2; + volatile uint16_t BUFSIZE : 5; /*!< [14..10] Buffer Size 00h: 64 bytes 01h: 128 bytes : 1Fh: 2 Kbytes */ + uint16_t : 1; + } PIPEBUF_b; + }; + + union { + volatile uint16_t PIPEMAXP; /* (@ 0x0000006C) Pipe Maximum Packet Size Register */ + + struct TU_ATTR_PACKED { + volatile uint16_t MXPS : 11; /* [10..0] Maximum Packet Size */ + uint16_t : 1; + volatile uint16_t DEVSEL : 4; /* [15..12] Device Select */ + } PIPEMAXP_b; + }; + + union { + volatile uint16_t PIPEPERI; /* (@ 0x0000006E) Pipe Cycle Control Register */ + + struct TU_ATTR_PACKED { + volatile uint16_t IITV : 3; /* [2..0] Interval Error Detection Interval */ + uint16_t : 9; + volatile uint16_t IFIS : 1; /* [12..12] Isochronous IN Buffer Flush */ + uint16_t : 3; + } PIPEPERI_b; + }; + + union { + volatile uint16_t PIPE_CTR[9]; /* (@ 0x00000070) Pipe [0..8] Control Register */ + + struct TU_ATTR_PACKED { + volatile uint16_t PID : 2; /* [1..0] Response PID */ + uint16_t : 3; + volatile const uint16_t PBUSY : 1; /* [5..5] Pipe Busy */ + volatile const uint16_t SQMON : 1; /* [6..6] Sequence Toggle Bit Confirmation */ + volatile uint16_t SQSET : 1; /* [7..7] Sequence Toggle Bit Set */ + volatile uint16_t SQCLR : 1; /* [8..8] Sequence Toggle Bit Clear */ + volatile uint16_t ACLRM : 1; /* [9..9] Auto Buffer Clear Mode */ + volatile uint16_t ATREPM : 1; /* [10..10] Auto Response Mode */ + uint16_t : 1; + volatile const uint16_t CSSTS : 1; /* [12..12] CSSTS Status */ + volatile uint16_t CSCLR : 1; /* [13..13] CSPLIT Status Clear */ + volatile const uint16_t INBUFM : 1; /* [14..14] Transmit Buffer Monitor */ + volatile const uint16_t BSTS : 1; /* [15..15] Buffer Status */ + } PIPE_CTR_b[9]; + }; + volatile const uint16_t RESERVED13; + volatile const uint32_t RESERVED14[3]; + volatile RUSB2_PIPE_TR_t PIPE_TR[5]; /* (@ 0x00000090) Pipe Transaction Counter Registers */ + volatile const uint32_t RESERVED15[3]; + + union { + volatile uint16_t USBBCCTRL0; /* (@ 0x000000B0) BC Control Register 0 */ + + struct TU_ATTR_PACKED { + volatile uint16_t RPDME0 : 1; /* [0..0] D- Pin Pull-Down Control */ + volatile uint16_t IDPSRCE0 : 1; /* [1..1] D+ Pin IDPSRC Output Control */ + volatile uint16_t IDMSINKE0 : 1; /* [2..2] D- Pin 0.6 V Input Detection (Comparator and Sink) Control */ + volatile uint16_t VDPSRCE0 : 1; /* [3..3] D+ Pin VDPSRC (0.6 V) Output Control */ + volatile uint16_t IDPSINKE0 : 1; /* [4..4] D+ Pin 0.6 V Input Detection (Comparator and Sink) Control */ + volatile uint16_t VDMSRCE0 : 1; /* [5..5] D- Pin VDMSRC (0.6 V) Output Control */ + uint16_t : 1; + volatile uint16_t BATCHGE0 : 1; /* [7..7] BC (Battery Charger) Function Ch0 General Enable Control */ + volatile const uint16_t CHGDETSTS0 : 1; /* [8..8] D- Pin 0.6 V Input Detection Status */ + volatile const uint16_t PDDETSTS0 : 1; /* [9..9] D+ Pin 0.6 V Input Detection Status */ + uint16_t : 6; + } USBBCCTRL0_b; + }; + volatile const uint16_t RESERVED16; + volatile const uint32_t RESERVED17[4]; + + union { + volatile uint16_t UCKSEL; /* (@ 0x000000C4) USB Clock Selection Register */ + + struct TU_ATTR_PACKED { + volatile uint16_t UCKSELC : 1; /* [0..0] USB Clock Selection */ + uint16_t : 15; + } UCKSEL_b; + }; + volatile const uint16_t RESERVED18; + volatile const uint32_t RESERVED19; + + union { + volatile uint16_t USBMC; /* (@ 0x000000CC) USB Module Control Register */ + + struct TU_ATTR_PACKED { + volatile uint16_t VDDUSBE : 1; /* [0..0] USB Reference Power Supply Circuit On/Off Control */ + uint16_t : 6; + volatile uint16_t VDCEN : 1; /* [7..7] USB Regulator On/Off Control */ + uint16_t : 8; + } USBMC_b; + }; + volatile const uint16_t RESERVED20; + + union { + volatile uint16_t DEVADD[10]; /* (@ 0x000000D0) Device Address Configuration Register */ + + struct TU_ATTR_PACKED { + uint16_t : 6; + volatile uint16_t USBSPD : 2; /* [7..6] Transfer Speed of Communication Target Device */ + volatile uint16_t HUBPORT : 3; /* [10..8] Communication Target Connecting Hub Port */ + volatile uint16_t UPPHUB : 4; /* [14..11] Communication Target Connecting Hub Register */ + uint16_t : 1; + } DEVADD_b[10]; + }; + volatile const uint32_t RESERVED21[3]; + + union { + volatile uint32_t PHYSLEW; /* (@ 0x000000F0) PHY Cross Point Adjustment Register */ + + struct TU_ATTR_PACKED { + volatile uint32_t SLEWR00 : 1; /* [0..0] Receiver Cross Point Adjustment 00 */ + volatile uint32_t SLEWR01 : 1; /* [1..1] Receiver Cross Point Adjustment 01 */ + volatile uint32_t SLEWF00 : 1; /* [2..2] Receiver Cross Point Adjustment 00 */ + volatile uint32_t SLEWF01 : 1; /* [3..3] Receiver Cross Point Adjustment 01 */ + uint32_t : 28; + } PHYSLEW_b; + }; + volatile const uint32_t RESERVED22[3]; + + union { + volatile uint16_t LPCTRL; /* (@ 0x00000100) Low Power Control Register */ + + struct TU_ATTR_PACKED { + uint16_t : 7; + volatile uint16_t HWUPM : 1; /* [7..7] Resume Return Mode Setting */ + uint16_t : 8; + } LPCTRL_b; + }; + + union { + volatile uint16_t LPSTS; /* (@ 0x00000102) Low Power Status Register */ + + struct TU_ATTR_PACKED { + uint16_t : 14; + volatile uint16_t SUSPENDM : 1; /* [14..14] UTMI SuspendM Control */ + uint16_t : 1; + } LPSTS_b; + }; + volatile const uint32_t RESERVED23[15]; + + union { + volatile uint16_t BCCTRL; /* (@ 0x00000140) Battery Charging Control Register */ + + struct TU_ATTR_PACKED { + volatile uint16_t IDPSRCE : 1; /* [0..0] IDPSRC Control */ + volatile uint16_t IDMSINKE : 1; /* [1..1] IDMSINK Control */ + volatile uint16_t VDPSRCE : 1; /* [2..2] VDPSRC Control */ + volatile uint16_t IDPSINKE : 1; /* [3..3] IDPSINK Control */ + volatile uint16_t VDMSRCE : 1; /* [4..4] VDMSRC Control */ + volatile uint16_t DCPMODE : 1; /* [5..5] DCP Mode Control */ + uint16_t : 2; + volatile const uint16_t CHGDETSTS : 1; /* [8..8] CHGDET Status */ + volatile const uint16_t PDDETSTS : 1; /* [9..9] PDDET Status */ + uint16_t : 6; + } BCCTRL_b; + }; + volatile const uint16_t RESERVED24; + + union { + volatile uint16_t PL1CTRL1; /* (@ 0x00000144) Function L1 Control Register 1 */ + + struct TU_ATTR_PACKED { + volatile uint16_t L1RESPEN : 1; /* [0..0] L1 Response Enable */ + volatile uint16_t L1RESPMD : 2; /* [2..1] L1 Response Mode */ + volatile uint16_t L1NEGOMD : 1; /* [3..3] L1 Response Negotiation Control. */ + volatile const uint16_t DVSQ : 4; /* [7..4] DVSQ Extension.DVSQ[3] is Mirror of DVSQ[2:0] in INTSTS0. */ + volatile uint16_t HIRDTHR : 4; /* [11..8] L1 Response Negotiation Threshold Value */ + uint16_t : 2; + volatile uint16_t L1EXTMD : 1; /* [14..14] PHY Control Mode at L1 Return */ + uint16_t : 1; + } PL1CTRL1_b; + }; + + union { + volatile uint16_t PL1CTRL2; /* (@ 0x00000146) Function L1 Control Register 2 */ + + struct TU_ATTR_PACKED { + uint16_t : 8; + volatile uint16_t HIRDMON : 4; /* [11..8] HIRD Value Monitor */ + volatile uint16_t RWEMON : 1; /* [12..12] RWE Value Monitor */ + uint16_t : 3; + } PL1CTRL2_b; + }; + + union { + volatile uint16_t HL1CTRL1; /* (@ 0x00000148) Host L1 Control Register 1 */ + + struct TU_ATTR_PACKED { + volatile uint16_t L1REQ : 1; /* [0..0] L1 Transition Request */ + volatile const uint16_t L1STATUS : 2; /* [2..1] L1 Request Completion Status */ + uint16_t : 13; + } HL1CTRL1_b; + }; + + union { + volatile uint16_t HL1CTRL2; /* (@ 0x0000014A) Host L1 Control Register 2 */ + + struct TU_ATTR_PACKED { + volatile uint16_t L1ADDR : 4; /* [3..0] LPM Token DeviceAddress */ + uint16_t : 4; + volatile uint16_t HIRD : 4; /* [11..8] LPM Token HIRD */ + volatile uint16_t L1RWE : 1; /* [12..12] LPM Token L1 Remote Wake Enable */ + uint16_t : 2; + volatile uint16_t BESL : 1; /* [15..15] BESL & Alternate HIRD */ + } HL1CTRL2_b; + }; + + volatile uint32_t RESERVED25_1; + + union { + volatile uint16_t PHYTRIM1; /*!< (@ 0x00000150) PHY Timing Register 1 */ + + struct { + volatile uint16_t DRISE : 2; /*!< [1..0] FS/LS Rising-Edge Output Waveform Adjustment Function */ + volatile uint16_t DFALL : 2; /*!< [3..2] FS/LS Falling-Edge Output Waveform Adjustment Function */ + uint16_t : 3; + volatile uint16_t PCOMPENB : 1; /*!< [7..7] PVDD Start-up Detection */ + volatile uint16_t HSIUP : 4; /*!< [11..8] HS Output Level Setting */ + volatile uint16_t IMPOFFSET : 3; /*!< [14..12] terminating resistance offset value setting.Offset value for adjusting the terminating resistance. */ + uint16_t : 1; + } PHYTRIM1_b; + }; + + union { + volatile uint16_t PHYTRIM2; /*!< (@ 0x00000152) PHY Timing Register 2 */ + + struct { + volatile uint16_t SQU : 4; /*!< [3..0] Squelch Detection Level */ + uint16_t : 3; + volatile uint16_t HSRXENMO : 1; /*!< [7..7] HS Receive Enable Control Mode */ + volatile uint16_t PDR : 2; /*!< [9..8] HS Output Adjustment Function */ + uint16_t : 2; + volatile uint16_t DIS : 3; /*!< [14..12] Disconnect Detection Level */ + uint16_t : 1; + } PHYTRIM2_b; + }; + volatile uint32_t RESERVED25_2[3]; + + union { + volatile const uint32_t DPUSR0R; /* (@ 0x00000160) Deep Standby USB Transceiver Control/Pin Monitor Register */ + + struct TU_ATTR_PACKED { + uint32_t : 20; + volatile const uint32_t DOVCAHM : 1; /* [20..20] OVRCURA InputIndicates OVRCURA input signal on the HS side of USB port. */ + volatile const uint32_t DOVCBHM : 1; /* [21..21] OVRCURB InputIndicates OVRCURB input signal on the HS side of USB port. */ + uint32_t : 1; + volatile const uint32_t DVBSTSHM : 1; /* [23..23] VBUS InputIndicates VBUS input signal on the HS side of USB port. */ + uint32_t : 8; + } DPUSR0R_b; + }; + + union { + volatile uint32_t DPUSR1R; /* (@ 0x00000164) Deep Standby USB Suspend/Resume Interrupt Register */ + + struct TU_ATTR_PACKED { + uint32_t : 4; + volatile uint32_t DOVCAHE : 1; /* [4..4] OVRCURA Interrupt Enable Clear */ + volatile uint32_t DOVCBHE : 1; /* [5..5] OVRCURB Interrupt Enable Clear */ + uint32_t : 1; + volatile uint32_t DVBSTSHE : 1; /* [7..7] VBUS Interrupt Enable/Clear */ + uint32_t : 12; + volatile const uint32_t DOVCAH : 1; /* [20..20] Indication of Return from OVRCURA Interrupt Source */ + volatile const uint32_t DOVCBH : 1; /* [21..21] Indication of Return from OVRCURB Interrupt Source */ + uint32_t : 1; + volatile const uint32_t DVBSTSH : 1; /* [23..23] Indication of Return from VBUS Interrupt Source */ + uint32_t : 8; + } DPUSR1R_b; + }; + + union { + volatile uint16_t DPUSR2R; /* (@ 0x00000168) Deep Standby USB Suspend/Resume Interrupt Register */ + + struct TU_ATTR_PACKED { + volatile const uint16_t DPINT : 1; /* [0..0] Indication of Return from DP Interrupt Source */ + volatile const uint16_t DMINT : 1; /* [1..1] Indication of Return from DM Interrupt Source */ + uint16_t : 2; + volatile const uint16_t DPVAL : 1; /* [4..4] DP InputIndicates DP input signal on the HS side of USB port. */ + volatile const uint16_t DMVAL : 1; /* [5..5] DM InputIndicates DM input signal on the HS side of USB port. */ + uint16_t : 2; + volatile uint16_t DPINTE : 1; /* [8..8] DP Interrupt Enable Clear */ + volatile uint16_t DMINTE : 1; /* [9..9] DM Interrupt Enable Clear */ + uint16_t : 6; + } DPUSR2R_b; + }; + + union { + volatile uint16_t DPUSRCR; /* (@ 0x0000016A) Deep Standby USB Suspend/Resume Command Register */ + + struct TU_ATTR_PACKED { + volatile uint16_t FIXPHY : 1; /* [0..0] USB Transceiver Control Fix */ + volatile uint16_t FIXPHYPD : 1; /* [1..1] USB Transceiver Control Fix for PLL */ + uint16_t : 14; + } DPUSRCR_b; + }; + volatile const uint32_t RESERVED26[165]; + + union { + volatile uint32_t + DPUSR0R_FS; /* (@ 0x00000400) Deep Software Standby USB Transceiver Control/Pin Monitor Register */ + + struct TU_ATTR_PACKED { + volatile uint32_t SRPC0 : 1; /* [0..0] USB Single End Receiver Control */ + volatile uint32_t RPUE0 : 1; /* [1..1] DP Pull-Up Resistor Control */ + uint32_t : 1; + volatile uint32_t DRPD0 : 1; /* [3..3] D+/D- Pull-Down Resistor Control */ + volatile uint32_t FIXPHY0 : 1; /* [4..4] USB Transceiver Output Fix */ + uint32_t : 11; + volatile const uint32_t DP0 : 1; /* [16..16] USB0 D+ InputIndicates the D+ input signal of the USB. */ + volatile const uint32_t DM0 : 1; /* [17..17] USB D-InputIndicates the D- input signal of the USB. */ + uint32_t : 2; + volatile const uint32_t DOVCA0 : 1; /* [20..20] USB OVRCURA InputIndicates the OVRCURA input signal of the USB. */ + volatile const uint32_t DOVCB0 : 1; /* [21..21] USB OVRCURB InputIndicates the OVRCURB input signal of the USB. */ + uint32_t : 1; + volatile const uint32_t DVBSTS0 : 1; /* [23..23] USB VBUS InputIndicates the VBUS input signal of the USB. */ + uint32_t : 8; + } DPUSR0R_FS_b; + }; + + union { + volatile uint32_t DPUSR1R_FS; /* (@ 0x00000404) Deep Software Standby USB Suspend/Resume Interrupt Register */ + + struct TU_ATTR_PACKED { + volatile uint32_t DPINTE0 : 1; /* [0..0] USB DP Interrupt Enable/Clear */ + volatile uint32_t DMINTE0 : 1; /* [1..1] USB DM Interrupt Enable/Clear */ + uint32_t : 2; + volatile uint32_t DOVRCRAE0 : 1; /* [4..4] USB OVRCURA Interrupt Enable/Clear */ + volatile uint32_t DOVRCRBE0 : 1; /* [5..5] USB OVRCURB Interrupt Enable/Clear */ + uint32_t : 1; + volatile uint32_t DVBSE0 : 1; /* [7..7] USB VBUS Interrupt Enable/Clear */ + uint32_t : 8; + volatile const uint32_t DPINT0 : 1; /* [16..16] USB DP Interrupt Source Recovery */ + volatile const uint32_t DMINT0 : 1; /* [17..17] USB DM Interrupt Source Recovery */ + uint32_t : 2; + volatile const uint32_t DOVRCRA0 : 1; /* [20..20] USB OVRCURA Interrupt Source Recovery */ + volatile const uint32_t DOVRCRB0 : 1; /* [21..21] USB OVRCURB Interrupt Source Recovery */ + uint32_t : 1; + volatile const uint32_t DVBINT0 : 1; /* [23..23] USB VBUS Interrupt Source Recovery */ + uint32_t : 8; + } DPUSR1R_FS_b; + }; +} rusb2_reg_t; /* Size = 1032 (0x408) */ + +TU_ATTR_PACKED_END /* End of definition of packed structs (used by the CCRX toolchain) */ +TU_ATTR_BIT_FIELD_ORDER_END + +/*--------------------------------------------------------------------*/ +/* Register Bit Definitions */ +/*--------------------------------------------------------------------*/ + +// PIPE_TR +// E +#define RUSB2_PIPE_TR_E_TRENB_Pos (9UL) /* TRENB (Bit 9) */ +#define RUSB2_PIPE_TR_E_TRENB_Msk (0x200UL) /* TRENB (Bitfield-Mask: 0x01) */ +#define RUSB2_PIPE_TR_E_TRCLR_Pos (8UL) /* TRCLR (Bit 8) */ +#define RUSB2_PIPE_TR_E_TRCLR_Msk (0x100UL) /* TRCLR (Bitfield-Mask: 0x01) */ + +// N +#define RUSB2_PIPE_TR_N_TRNCNT_Pos (0UL) /* TRNCNT (Bit 0) */ +#define RUSB2_PIPE_TR_N_TRNCNT_Msk (0xffffUL) /* TRNCNT (Bitfield-Mask: 0xffff) */ + +// Core Registers + +// SYSCFG +#define RUSB2_SYSCFG_SCKE_Pos (10UL) /* SCKE (Bit 10) */ +#define RUSB2_SYSCFG_SCKE_Msk (0x400UL) /* SCKE (Bitfield-Mask: 0x01) */ +#define RUSB2_SYSCFG_CNEN_Pos (8UL) /* CNEN (Bit 8) */ +#define RUSB2_SYSCFG_CNEN_Msk (0x100UL) /* CNEN (Bitfield-Mask: 0x01) */ +#define RUSB2_SYSCFG_HSE_Pos (7UL) /*!< HSE (Bit 7) */ +#define RUSB2_SYSCFG_HSE_Msk (0x80UL) /*!< HSE (Bitfield-Mask: 0x01) */ +#define RUSB2_SYSCFG_DCFM_Pos (6UL) /* DCFM (Bit 6) */ +#define RUSB2_SYSCFG_DCFM_Msk (0x40UL) /* DCFM (Bitfield-Mask: 0x01) */ +#define RUSB2_SYSCFG_DRPD_Pos (5UL) /* DRPD (Bit 5) */ +#define RUSB2_SYSCFG_DRPD_Msk (0x20UL) /* DRPD (Bitfield-Mask: 0x01) */ +#define RUSB2_SYSCFG_DPRPU_Pos (4UL) /* DPRPU (Bit 4) */ +#define RUSB2_SYSCFG_DPRPU_Msk (0x10UL) /* DPRPU (Bitfield-Mask: 0x01) */ +#define RUSB2_SYSCFG_DMRPU_Pos (3UL) /* DMRPU (Bit 3) */ +#define RUSB2_SYSCFG_DMRPU_Msk (0x8UL) /* DMRPU (Bitfield-Mask: 0x01) */ +#define RUSB2_SYSCFG_USBE_Pos (0UL) /* USBE (Bit 0) */ +#define RUSB2_SYSCFG_USBE_Msk (0x1UL) /* USBE (Bitfield-Mask: 0x01) */ + +// BUSWAIT +#define RUSB2_BUSWAIT_BWAIT_Pos (0UL) /* BWAIT (Bit 0) */ +#define RUSB2_BUSWAIT_BWAIT_Msk (0xfUL) /* BWAIT (Bitfield-Mask: 0x0f) */ + +// SYSSTS0 +#define RUSB2_SYSSTS0_OVCMON_Pos (14UL) /* OVCMON (Bit 14) */ +#define RUSB2_SYSSTS0_OVCMON_Msk (0xc000UL) /* OVCMON (Bitfield-Mask: 0x03) */ +#define RUSB2_SYSSTS0_HTACT_Pos (6UL) /* HTACT (Bit 6) */ +#define RUSB2_SYSSTS0_HTACT_Msk (0x40UL) /* HTACT (Bitfield-Mask: 0x01) */ +#define RUSB2_SYSSTS0_SOFEA_Pos (5UL) /* SOFEA (Bit 5) */ +#define RUSB2_SYSSTS0_SOFEA_Msk (0x20UL) /* SOFEA (Bitfield-Mask: 0x01) */ +#define RUSB2_SYSSTS0_IDMON_Pos (2UL) /* IDMON (Bit 2) */ +#define RUSB2_SYSSTS0_IDMON_Msk (0x4UL) /* IDMON (Bitfield-Mask: 0x01) */ +#define RUSB2_SYSSTS0_LNST_Pos (0UL) /* LNST (Bit 0) */ +#define RUSB2_SYSSTS0_LNST_Msk (0x3UL) /* LNST (Bitfield-Mask: 0x03) */ + +// PLLSTA +#define RUSB2_PLLSTA_PLLLOCK_Pos (0UL) /* PLLLOCK (Bit 0) */ +#define RUSB2_PLLSTA_PLLLOCK_Msk (0x1UL) /* PLLLOCK (Bitfield-Mask: 0x01) */ + +// DVSTCTR0 +#define RUSB2_DVSTCTR0_HNPBTOA_Pos (11UL) /* HNPBTOA (Bit 11) */ +#define RUSB2_DVSTCTR0_HNPBTOA_Msk (0x800UL) /* HNPBTOA (Bitfield-Mask: 0x01) */ +#define RUSB2_DVSTCTR0_EXICEN_Pos (10UL) /* EXICEN (Bit 10) */ +#define RUSB2_DVSTCTR0_EXICEN_Msk (0x400UL) /* EXICEN (Bitfield-Mask: 0x01) */ +#define RUSB2_DVSTCTR0_VBUSEN_Pos (9UL) /* VBUSEN (Bit 9) */ +#define RUSB2_DVSTCTR0_VBUSEN_Msk (0x200UL) /* VBUSEN (Bitfield-Mask: 0x01) */ +#define RUSB2_DVSTCTR0_WKUP_Pos (8UL) /* WKUP (Bit 8) */ +#define RUSB2_DVSTCTR0_WKUP_Msk (0x100UL) /* WKUP (Bitfield-Mask: 0x01) */ +#define RUSB2_DVSTCTR0_RWUPE_Pos (7UL) /* RWUPE (Bit 7) */ +#define RUSB2_DVSTCTR0_RWUPE_Msk (0x80UL) /* RWUPE (Bitfield-Mask: 0x01) */ +#define RUSB2_DVSTCTR0_USBRST_Pos (6UL) /* USBRST (Bit 6) */ +#define RUSB2_DVSTCTR0_USBRST_Msk (0x40UL) /* USBRST (Bitfield-Mask: 0x01) */ +#define RUSB2_DVSTCTR0_RESUME_Pos (5UL) /* RESUME (Bit 5) */ +#define RUSB2_DVSTCTR0_RESUME_Msk (0x20UL) /* RESUME (Bitfield-Mask: 0x01) */ +#define RUSB2_DVSTCTR0_UACT_Pos (4UL) /* UACT (Bit 4) */ +#define RUSB2_DVSTCTR0_UACT_Msk (0x10UL) /* UACT (Bitfield-Mask: 0x01) */ +#define RUSB2_DVSTCTR0_RHST_Pos (0UL) /* RHST (Bit 0) */ +#define RUSB2_DVSTCTR0_RHST_Msk (0x7UL) /* RHST (Bitfield-Mask: 0x07) */ + +// TESTMODE +#define RUSB2_TESTMODE_UTST_Pos (0UL) /* UTST (Bit 0) */ +#define RUSB2_TESTMODE_UTST_Msk (0xfUL) /* UTST (Bitfield-Mask: 0x0f) */ + +// CFIFOSEL +#define RUSB2_CFIFOSEL_RCNT_Pos (15UL) /* RCNT (Bit 15) */ +#define RUSB2_CFIFOSEL_RCNT_Msk (0x8000UL) /* RCNT (Bitfield-Mask: 0x01) */ +#define RUSB2_CFIFOSEL_REW_Pos (14UL) /* REW (Bit 14) */ +#define RUSB2_CFIFOSEL_REW_Msk (0x4000UL) /* REW (Bitfield-Mask: 0x01) */ +#define RUSB2_CFIFOSEL_MBW_Pos (10UL) /* MBW (Bit 10) */ +#define RUSB2_CFIFOSEL_MBW_Msk (0xc00UL) /* MBW (Bitfield-Mask: 0x03) */ +#define RUSB2_CFIFOSEL_BIGEND_Pos (8UL) /* BIGEND (Bit 8) */ +#define RUSB2_CFIFOSEL_BIGEND_Msk (0x100UL) /* BIGEND (Bitfield-Mask: 0x01) */ +#define RUSB2_CFIFOSEL_ISEL_Pos (5UL) /* ISEL (Bit 5) */ +#define RUSB2_CFIFOSEL_ISEL_Msk (0x20UL) /* ISEL (Bitfield-Mask: 0x01) */ +#define RUSB2_CFIFOSEL_CURPIPE_Pos (0UL) /* CURPIPE (Bit 0) */ +#define RUSB2_CFIFOSEL_CURPIPE_Msk (0xfUL) /* CURPIPE (Bitfield-Mask: 0x0f) */ + +// CFIFOCTR +#define RUSB2_CFIFOCTR_BVAL_Pos (15UL) /* BVAL (Bit 15) */ +#define RUSB2_CFIFOCTR_BVAL_Msk (0x8000UL) /* BVAL (Bitfield-Mask: 0x01) */ +#define RUSB2_CFIFOCTR_BCLR_Pos (14UL) /* BCLR (Bit 14) */ +#define RUSB2_CFIFOCTR_BCLR_Msk (0x4000UL) /* BCLR (Bitfield-Mask: 0x01) */ +#define RUSB2_CFIFOCTR_FRDY_Pos (13UL) /* FRDY (Bit 13) */ +#define RUSB2_CFIFOCTR_FRDY_Msk (0x2000UL) /* FRDY (Bitfield-Mask: 0x01) */ +#define RUSB2_CFIFOCTR_DTLN_Pos (0UL) /* DTLN (Bit 0) */ +#define RUSB2_CFIFOCTR_DTLN_Msk (0xfffUL) /* DTLN (Bitfield-Mask: 0xfff) */ + +// D0FIFOSEL +#define RUSB2_D0FIFOSEL_RCNT_Pos (15UL) /* RCNT (Bit 15) */ +#define RUSB2_D0FIFOSEL_RCNT_Msk (0x8000UL) /* RCNT (Bitfield-Mask: 0x01) */ +#define RUSB2_D0FIFOSEL_REW_Pos (14UL) /* REW (Bit 14) */ +#define RUSB2_D0FIFOSEL_REW_Msk (0x4000UL) /* REW (Bitfield-Mask: 0x01) */ +#define RUSB2_D0FIFOSEL_DCLRM_Pos (13UL) /* DCLRM (Bit 13) */ +#define RUSB2_D0FIFOSEL_DCLRM_Msk (0x2000UL) /* DCLRM (Bitfield-Mask: 0x01) */ +#define RUSB2_D0FIFOSEL_DREQE_Pos (12UL) /* DREQE (Bit 12) */ +#define RUSB2_D0FIFOSEL_DREQE_Msk (0x1000UL) /* DREQE (Bitfield-Mask: 0x01) */ +#define RUSB2_D0FIFOSEL_MBW_Pos (10UL) /* MBW (Bit 10) */ +#define RUSB2_D0FIFOSEL_MBW_Msk (0xc00UL) /* MBW (Bitfield-Mask: 0x03) */ +#define RUSB2_D0FIFOSEL_BIGEND_Pos (8UL) /* BIGEND (Bit 8) */ +#define RUSB2_D0FIFOSEL_BIGEND_Msk (0x100UL) /* BIGEND (Bitfield-Mask: 0x01) */ +#define RUSB2_D0FIFOSEL_CURPIPE_Pos (0UL) /* CURPIPE (Bit 0) */ +#define RUSB2_D0FIFOSEL_CURPIPE_Msk (0xfUL) /* CURPIPE (Bitfield-Mask: 0x0f) */ + +// D0FIFOCTR +#define RUSB2_D0FIFOCTR_BVAL_Pos (15UL) /* BVAL (Bit 15) */ +#define RUSB2_D0FIFOCTR_BVAL_Msk (0x8000UL) /* BVAL (Bitfield-Mask: 0x01) */ +#define RUSB2_D0FIFOCTR_BCLR_Pos (14UL) /* BCLR (Bit 14) */ +#define RUSB2_D0FIFOCTR_BCLR_Msk (0x4000UL) /* BCLR (Bitfield-Mask: 0x01) */ +#define RUSB2_D0FIFOCTR_FRDY_Pos (13UL) /* FRDY (Bit 13) */ +#define RUSB2_D0FIFOCTR_FRDY_Msk (0x2000UL) /* FRDY (Bitfield-Mask: 0x01) */ +#define RUSB2_D0FIFOCTR_DTLN_Pos (0UL) /* DTLN (Bit 0) */ +#define RUSB2_D0FIFOCTR_DTLN_Msk (0xfffUL) /* DTLN (Bitfield-Mask: 0xfff) */ + +// D1FIFOSEL +#define RUSB2_D1FIFOSEL_RCNT_Pos (15UL) /* RCNT (Bit 15) */ +#define RUSB2_D1FIFOSEL_RCNT_Msk (0x8000UL) /* RCNT (Bitfield-Mask: 0x01) */ +#define RUSB2_D1FIFOSEL_REW_Pos (14UL) /* REW (Bit 14) */ +#define RUSB2_D1FIFOSEL_REW_Msk (0x4000UL) /* REW (Bitfield-Mask: 0x01) */ +#define RUSB2_D1FIFOSEL_DCLRM_Pos (13UL) /* DCLRM (Bit 13) */ +#define RUSB2_D1FIFOSEL_DCLRM_Msk (0x2000UL) /* DCLRM (Bitfield-Mask: 0x01) */ +#define RUSB2_D1FIFOSEL_DREQE_Pos (12UL) /* DREQE (Bit 12) */ +#define RUSB2_D1FIFOSEL_DREQE_Msk (0x1000UL) /* DREQE (Bitfield-Mask: 0x01) */ +#define RUSB2_D1FIFOSEL_MBW_Pos (10UL) /* MBW (Bit 10) */ +#define RUSB2_D1FIFOSEL_MBW_Msk (0xc00UL) /* MBW (Bitfield-Mask: 0x03) */ +#define RUSB2_D1FIFOSEL_BIGEND_Pos (8UL) /* BIGEND (Bit 8) */ +#define RUSB2_D1FIFOSEL_BIGEND_Msk (0x100UL) /* BIGEND (Bitfield-Mask: 0x01) */ +#define RUSB2_D1FIFOSEL_CURPIPE_Pos (0UL) /* CURPIPE (Bit 0) */ +#define RUSB2_D1FIFOSEL_CURPIPE_Msk (0xfUL) /* CURPIPE (Bitfield-Mask: 0x0f) */ + +// D1FIFOCTR +#define RUSB2_D1FIFOCTR_BVAL_Pos (15UL) /* BVAL (Bit 15) */ +#define RUSB2_D1FIFOCTR_BVAL_Msk (0x8000UL) /* BVAL (Bitfield-Mask: 0x01) */ +#define RUSB2_D1FIFOCTR_BCLR_Pos (14UL) /* BCLR (Bit 14) */ +#define RUSB2_D1FIFOCTR_BCLR_Msk (0x4000UL) /* BCLR (Bitfield-Mask: 0x01) */ +#define RUSB2_D1FIFOCTR_FRDY_Pos (13UL) /* FRDY (Bit 13) */ +#define RUSB2_D1FIFOCTR_FRDY_Msk (0x2000UL) /* FRDY (Bitfield-Mask: 0x01) */ +#define RUSB2_D1FIFOCTR_DTLN_Pos (0UL) /* DTLN (Bit 0) */ +#define RUSB2_D1FIFOCTR_DTLN_Msk (0xfffUL) /* DTLN (Bitfield-Mask: 0xfff) */ + +// INTENB0 +#define RUSB2_INTENB0_VBSE_Pos (15UL) /* VBSE (Bit 15) */ +#define RUSB2_INTENB0_VBSE_Msk (0x8000UL) /* VBSE (Bitfield-Mask: 0x01) */ +#define RUSB2_INTENB0_RSME_Pos (14UL) /* RSME (Bit 14) */ +#define RUSB2_INTENB0_RSME_Msk (0x4000UL) /* RSME (Bitfield-Mask: 0x01) */ +#define RUSB2_INTENB0_SOFE_Pos (13UL) /* SOFE (Bit 13) */ +#define RUSB2_INTENB0_SOFE_Msk (0x2000UL) /* SOFE (Bitfield-Mask: 0x01) */ +#define RUSB2_INTENB0_DVSE_Pos (12UL) /* DVSE (Bit 12) */ +#define RUSB2_INTENB0_DVSE_Msk (0x1000UL) /* DVSE (Bitfield-Mask: 0x01) */ +#define RUSB2_INTENB0_CTRE_Pos (11UL) /* CTRE (Bit 11) */ +#define RUSB2_INTENB0_CTRE_Msk (0x800UL) /* CTRE (Bitfield-Mask: 0x01) */ +#define RUSB2_INTENB0_BEMPE_Pos (10UL) /* BEMPE (Bit 10) */ +#define RUSB2_INTENB0_BEMPE_Msk (0x400UL) /* BEMPE (Bitfield-Mask: 0x01) */ +#define RUSB2_INTENB0_NRDYE_Pos (9UL) /* NRDYE (Bit 9) */ +#define RUSB2_INTENB0_NRDYE_Msk (0x200UL) /* NRDYE (Bitfield-Mask: 0x01) */ +#define RUSB2_INTENB0_BRDYE_Pos (8UL) /* BRDYE (Bit 8) */ +#define RUSB2_INTENB0_BRDYE_Msk (0x100UL) /* BRDYE (Bitfield-Mask: 0x01) */ + +// INTENB1 +#define RUSB2_INTENB1_OVRCRE_Pos (15UL) /* OVRCRE (Bit 15) */ +#define RUSB2_INTENB1_OVRCRE_Msk (0x8000UL) /* OVRCRE (Bitfield-Mask: 0x01) */ +#define RUSB2_INTENB1_BCHGE_Pos (14UL) /* BCHGE (Bit 14) */ +#define RUSB2_INTENB1_BCHGE_Msk (0x4000UL) /* BCHGE (Bitfield-Mask: 0x01) */ +#define RUSB2_INTENB1_DTCHE_Pos (12UL) /* DTCHE (Bit 12) */ +#define RUSB2_INTENB1_DTCHE_Msk (0x1000UL) /* DTCHE (Bitfield-Mask: 0x01) */ +#define RUSB2_INTENB1_ATTCHE_Pos (11UL) /* ATTCHE (Bit 11) */ +#define RUSB2_INTENB1_ATTCHE_Msk (0x800UL) /* ATTCHE (Bitfield-Mask: 0x01) */ +#define RUSB2_INTENB1_L1RSMENDE_Pos (9UL) /*!< L1RSMENDE (Bit 9) */ +#define RUSB2_INTENB1_L1RSMENDE_Msk (0x200UL) /*!< L1RSMENDE (Bitfield-Mask: 0x01) */ +#define RUSB2_INTENB1_LPMENDE_Pos (8UL) /*!< LPMENDE (Bit 8) */ +#define RUSB2_INTENB1_LPMENDE_Msk (0x100UL) /*!< LPMENDE (Bitfield-Mask: 0x01) */ +#define RUSB2_INTENB1_EOFERRE_Pos (6UL) /* EOFERRE (Bit 6) */ +#define RUSB2_INTENB1_EOFERRE_Msk (0x40UL) /* EOFERRE (Bitfield-Mask: 0x01) */ +#define RUSB2_INTENB1_SIGNE_Pos (5UL) /* SIGNE (Bit 5) */ +#define RUSB2_INTENB1_SIGNE_Msk (0x20UL) /* SIGNE (Bitfield-Mask: 0x01) */ +#define RUSB2_INTENB1_SACKE_Pos (4UL) /* SACKE (Bit 4) */ +#define RUSB2_INTENB1_SACKE_Msk (0x10UL) /* SACKE (Bitfield-Mask: 0x01) */ +#define RUSB2_INTENB1_PDDETINTE0_Pos (0UL) /* PDDETINTE0 (Bit 0) */ +#define RUSB2_INTENB1_PDDETINTE0_Msk (0x1UL) /* PDDETINTE0 (Bitfield-Mask: 0x01) */ + +// BRDYENB +#define RUSB2_BRDYENB_PIPEBRDYE_Pos (0UL) /* PIPEBRDYE (Bit 0) */ +#define RUSB2_BRDYENB_PIPEBRDYE_Msk (0x1UL) /* PIPEBRDYE (Bitfield-Mask: 0x01) */ + +// NRDYENB +#define RUSB2_NRDYENB_PIPENRDYE_Pos (0UL) /* PIPENRDYE (Bit 0) */ +#define RUSB2_NRDYENB_PIPENRDYE_Msk (0x1UL) /* PIPENRDYE (Bitfield-Mask: 0x01) */ + +// BEMPENB +#define RUSB2_BEMPENB_PIPEBEMPE_Pos (0UL) /* PIPEBEMPE (Bit 0) */ +#define RUSB2_BEMPENB_PIPEBEMPE_Msk (0x1UL) /* PIPEBEMPE (Bitfield-Mask: 0x01) */ + +// SOFCFG +#define RUSB2_SOFCFG_TRNENSEL_Pos (8UL) /* TRNENSEL (Bit 8) */ +#define RUSB2_SOFCFG_TRNENSEL_Msk (0x100UL) /* TRNENSEL (Bitfield-Mask: 0x01) */ +#define RUSB2_SOFCFG_BRDYM_Pos (6UL) /* BRDYM (Bit 6) */ +#define RUSB2_SOFCFG_BRDYM_Msk (0x40UL) /* BRDYM (Bitfield-Mask: 0x01) */ +#define RUSB2_SOFCFG_INTL_Pos (5UL) /* INTL (Bit 5) */ +#define RUSB2_SOFCFG_INTL_Msk (0x20UL) /* INTL (Bitfield-Mask: 0x01) */ +#define RUSB2_SOFCFG_EDGESTS_Pos (4UL) /* EDGESTS (Bit 4) */ +#define RUSB2_SOFCFG_EDGESTS_Msk (0x10UL) /* EDGESTS (Bitfield-Mask: 0x01) */ + +// PHYSET +#define RUSB2_PHYSET_HSEB_Pos (15UL) /* HSEB (Bit 15) */ +#define RUSB2_PHYSET_HSEB_Msk (0x8000UL) /* HSEB (Bitfield-Mask: 0x01) */ +#define RUSB2_PHYSET_REPSTART_Pos (11UL) /* REPSTART (Bit 11) */ +#define RUSB2_PHYSET_REPSTART_Msk (0x800UL) /* REPSTART (Bitfield-Mask: 0x01) */ +#define RUSB2_PHYSET_REPSEL_Pos (8UL) /* REPSEL (Bit 8) */ +#define RUSB2_PHYSET_REPSEL_Msk (0x300UL) /* REPSEL (Bitfield-Mask: 0x03) */ +#define RUSB2_PHYSET_CLKSEL_Pos (4UL) /* CLKSEL (Bit 4) */ +#define RUSB2_PHYSET_CLKSEL_Msk (0x30UL) /* CLKSEL (Bitfield-Mask: 0x03) */ +#define RUSB2_PHYSET_CDPEN_Pos (3UL) /* CDPEN (Bit 3) */ +#define RUSB2_PHYSET_CDPEN_Msk (0x8UL) /* CDPEN (Bitfield-Mask: 0x01) */ +#define RUSB2_PHYSET_PLLRESET_Pos (1UL) /* PLLRESET (Bit 1) */ +#define RUSB2_PHYSET_PLLRESET_Msk (0x2UL) /* PLLRESET (Bitfield-Mask: 0x01) */ +#define RUSB2_PHYSET_DIRPD_Pos (0UL) /* DIRPD (Bit 0) */ +#define RUSB2_PHYSET_DIRPD_Msk (0x1UL) /* DIRPD (Bitfield-Mask: 0x01) */ + +// INTSTS0 +#define RUSB2_INTSTS0_VBINT_Pos (15UL) /* VBINT (Bit 15) */ +#define RUSB2_INTSTS0_VBINT_Msk (0x8000UL) /* VBINT (Bitfield-Mask: 0x01) */ +#define RUSB2_INTSTS0_RESM_Pos (14UL) /* RESM (Bit 14) */ +#define RUSB2_INTSTS0_RESM_Msk (0x4000UL) /* RESM (Bitfield-Mask: 0x01) */ +#define RUSB2_INTSTS0_SOFR_Pos (13UL) /* SOFR (Bit 13) */ +#define RUSB2_INTSTS0_SOFR_Msk (0x2000UL) /* SOFR (Bitfield-Mask: 0x01) */ +#define RUSB2_INTSTS0_DVST_Pos (12UL) /* DVST (Bit 12) */ +#define RUSB2_INTSTS0_DVST_Msk (0x1000UL) /* DVST (Bitfield-Mask: 0x01) */ +#define RUSB2_INTSTS0_CTRT_Pos (11UL) /* CTRT (Bit 11) */ +#define RUSB2_INTSTS0_CTRT_Msk (0x800UL) /* CTRT (Bitfield-Mask: 0x01) */ +#define RUSB2_INTSTS0_BEMP_Pos (10UL) /* BEMP (Bit 10) */ +#define RUSB2_INTSTS0_BEMP_Msk (0x400UL) /* BEMP (Bitfield-Mask: 0x01) */ +#define RUSB2_INTSTS0_NRDY_Pos (9UL) /* NRDY (Bit 9) */ +#define RUSB2_INTSTS0_NRDY_Msk (0x200UL) /* NRDY (Bitfield-Mask: 0x01) */ +#define RUSB2_INTSTS0_BRDY_Pos (8UL) /* BRDY (Bit 8) */ +#define RUSB2_INTSTS0_BRDY_Msk (0x100UL) /* BRDY (Bitfield-Mask: 0x01) */ +#define RUSB2_INTSTS0_VBSTS_Pos (7UL) /* VBSTS (Bit 7) */ +#define RUSB2_INTSTS0_VBSTS_Msk (0x80UL) /* VBSTS (Bitfield-Mask: 0x01) */ +#define RUSB2_INTSTS0_DVSQ_Pos (4UL) /* DVSQ (Bit 4) */ +#define RUSB2_INTSTS0_DVSQ_Msk (0x70UL) /* DVSQ (Bitfield-Mask: 0x07) */ +#define RUSB2_INTSTS0_VALID_Pos (3UL) /* VALID (Bit 3) */ +#define RUSB2_INTSTS0_VALID_Msk (0x8UL) /* VALID (Bitfield-Mask: 0x01) */ +#define RUSB2_INTSTS0_CTSQ_Pos (0UL) /* CTSQ (Bit 0) */ +#define RUSB2_INTSTS0_CTSQ_Msk (0x7UL) /* CTSQ (Bitfield-Mask: 0x07) */ + +// INTSTS1 +#define RUSB2_INTSTS1_OVRCR_Pos (15UL) /* OVRCR (Bit 15) */ +#define RUSB2_INTSTS1_OVRCR_Msk (0x8000UL) /* OVRCR (Bitfield-Mask: 0x01) */ +#define RUSB2_INTSTS1_BCHG_Pos (14UL) /* BCHG (Bit 14) */ +#define RUSB2_INTSTS1_BCHG_Msk (0x4000UL) /* BCHG (Bitfield-Mask: 0x01) */ +#define RUSB2_INTSTS1_DTCH_Pos (12UL) /* DTCH (Bit 12) */ +#define RUSB2_INTSTS1_DTCH_Msk (0x1000UL) /* DTCH (Bitfield-Mask: 0x01) */ +#define RUSB2_INTSTS1_ATTCH_Pos (11UL) /* ATTCH (Bit 11) */ +#define RUSB2_INTSTS1_ATTCH_Msk (0x800UL) /* ATTCH (Bitfield-Mask: 0x01) */ +#define RUSB2_INTSTS1_L1RSMEND_Pos (9UL) /* L1RSMEND (Bit 9) */ +#define RUSB2_INTSTS1_L1RSMEND_Msk (0x200UL) /* L1RSMEND (Bitfield-Mask: 0x01) */ +#define RUSB2_INTSTS1_LPMEND_Pos (8UL) /* LPMEND (Bit 8) */ +#define RUSB2_INTSTS1_LPMEND_Msk (0x100UL) /* LPMEND (Bitfield-Mask: 0x01) */ +#define RUSB2_INTSTS1_EOFERR_Pos (6UL) /* EOFERR (Bit 6) */ +#define RUSB2_INTSTS1_EOFERR_Msk (0x40UL) /* EOFERR (Bitfield-Mask: 0x01) */ +#define RUSB2_INTSTS1_SIGN_Pos (5UL) /* SIGN (Bit 5) */ +#define RUSB2_INTSTS1_SIGN_Msk (0x20UL) /* SIGN (Bitfield-Mask: 0x01) */ +#define RUSB2_INTSTS1_SACK_Pos (4UL) /* SACK (Bit 4) */ +#define RUSB2_INTSTS1_SACK_Msk (0x10UL) /* SACK (Bitfield-Mask: 0x01) */ +#define RUSB2_INTSTS1_PDDETINT0_Pos (0UL) /* PDDETINT0 (Bit 0) */ +#define RUSB2_INTSTS1_PDDETINT0_Msk (0x1UL) /* PDDETINT0 (Bitfield-Mask: 0x01) */ + +// BRDYSTS +#define RUSB2_BRDYSTS_PIPEBRDY_Pos (0UL) /* PIPEBRDY (Bit 0) */ +#define RUSB2_BRDYSTS_PIPEBRDY_Msk (0x1UL) /* PIPEBRDY (Bitfield-Mask: 0x01) */ + +// NRDYSTS +#define RUSB2_NRDYSTS_PIPENRDY_Pos (0UL) /* PIPENRDY (Bit 0) */ +#define RUSB2_NRDYSTS_PIPENRDY_Msk (0x1UL) /* PIPENRDY (Bitfield-Mask: 0x01) */ + +// BEMPSTS +#define RUSB2_BEMPSTS_PIPEBEMP_Pos (0UL) /* PIPEBEMP (Bit 0) */ +#define RUSB2_BEMPSTS_PIPEBEMP_Msk (0x1UL) /* PIPEBEMP (Bitfield-Mask: 0x01) */ + +// FRMNUM +#define RUSB2_FRMNUM_OVRN_Pos (15UL) /* OVRN (Bit 15) */ +#define RUSB2_FRMNUM_OVRN_Msk (0x8000UL) /* OVRN (Bitfield-Mask: 0x01) */ +#define RUSB2_FRMNUM_CRCE_Pos (14UL) /* CRCE (Bit 14) */ +#define RUSB2_FRMNUM_CRCE_Msk (0x4000UL) /* CRCE (Bitfield-Mask: 0x01) */ +#define RUSB2_FRMNUM_FRNM_Pos (0UL) /* FRNM (Bit 0) */ +#define RUSB2_FRMNUM_FRNM_Msk (0x7ffUL) /* FRNM (Bitfield-Mask: 0x7ff) */ + +// UFRMNUM +#define RUSB2_UFRMNUM_DVCHG_Pos (15UL) /* DVCHG (Bit 15) */ +#define RUSB2_UFRMNUM_DVCHG_Msk (0x8000UL) /* DVCHG (Bitfield-Mask: 0x01) */ +#define RUSB2_UFRMNUM_UFRNM_Pos (0UL) /* UFRNM (Bit 0) */ +#define RUSB2_UFRMNUM_UFRNM_Msk (0x7UL) /* UFRNM (Bitfield-Mask: 0x07) */ + +// USBADDR +#define RUSB2_USBADDR_STSRECOV0_Pos (8UL) /* STSRECOV0 (Bit 8) */ +#define RUSB2_USBADDR_STSRECOV0_Msk (0x700UL) /* STSRECOV0 (Bitfield-Mask: 0x07) */ +#define RUSB2_USBADDR_USBADDR_Pos (0UL) /* USBADDR (Bit 0) */ +#define RUSB2_USBADDR_USBADDR_Msk (0x7fUL) /* USBADDR (Bitfield-Mask: 0x7f) */ + +// USBREQ +#define RUSB2_USBREQ_BREQUEST_Pos (8UL) /* BREQUEST (Bit 8) */ +#define RUSB2_USBREQ_BREQUEST_Msk (0xff00UL) /* BREQUEST (Bitfield-Mask: 0xff) */ +#define RUSB2_USBREQ_BMREQUESTTYPE_Pos (0UL) /* BMREQUESTTYPE (Bit 0) */ +#define RUSB2_USBREQ_BMREQUESTTYPE_Msk (0xffUL) /* BMREQUESTTYPE (Bitfield-Mask: 0xff) */ + +// USBVAL +#define RUSB2_USBVAL_WVALUE_Pos (0UL) /* WVALUE (Bit 0) */ +#define RUSB2_USBVAL_WVALUE_Msk (0xffffUL) /* WVALUE (Bitfield-Mask: 0xffff) */ + +// USBINDX +#define RUSB2_USBINDX_WINDEX_Pos (0UL) /* WINDEX (Bit 0) */ +#define RUSB2_USBINDX_WINDEX_Msk (0xffffUL) /* WINDEX (Bitfield-Mask: 0xffff) */ + +// USBLENG +#define RUSB2_USBLENG_WLENGTH_Pos (0UL) /* WLENGTH (Bit 0) */ +#define RUSB2_USBLENG_WLENGTH_Msk (0xffffUL) /* WLENGTH (Bitfield-Mask: 0xffff) */ + +// DCPCFG +#define RUSB2_DCPCFG_CNTMD_Pos (8UL) /* CNTMD (Bit 8) */ +#define RUSB2_DCPCFG_CNTMD_Msk (0x100UL) /* CNTMD (Bitfield-Mask: 0x01) */ +#define RUSB2_DCPCFG_SHTNAK_Pos (7UL) /* SHTNAK (Bit 7) */ +#define RUSB2_DCPCFG_SHTNAK_Msk (0x80UL) /* SHTNAK (Bitfield-Mask: 0x01) */ +#define RUSB2_DCPCFG_DIR_Pos (4UL) /* DIR (Bit 4) */ +#define RUSB2_DCPCFG_DIR_Msk (0x10UL) /* DIR (Bitfield-Mask: 0x01) */ + +// DCPMAXP +#define RUSB2_DCPMAXP_DEVSEL_Pos (12UL) /* DEVSEL (Bit 12) */ +#define RUSB2_DCPMAXP_DEVSEL_Msk (0xf000UL) /* DEVSEL (Bitfield-Mask: 0x0f) */ +#define RUSB2_DCPMAXP_MXPS_Pos (0UL) /* MXPS (Bit 0) */ +#define RUSB2_DCPMAXP_MXPS_Msk (0x7fUL) /* MXPS (Bitfield-Mask: 0x7f) */ + +// DCPCTR +#define RUSB2_DCPCTR_BSTS_Pos (15UL) /* BSTS (Bit 15) */ +#define RUSB2_DCPCTR_BSTS_Msk (0x8000UL) /* BSTS (Bitfield-Mask: 0x01) */ +#define RUSB2_DCPCTR_SUREQ_Pos (14UL) /* SUREQ (Bit 14) */ +#define RUSB2_DCPCTR_SUREQ_Msk (0x4000UL) /* SUREQ (Bitfield-Mask: 0x01) */ +#define R_USB_HS0_DCPCTR_CSCLR_Pos (13UL) /*!< CSCLR (Bit 13) */ +#define RUSB2_DCPCTR_CSCLR_Msk (0x2000UL) /*!< CSCLR (Bitfield-Mask: 0x01) */ +#define RUSB2_DCPCTR_CSSTS_Pos (12UL) /*!< CSSTS (Bit 12) */ +#define RUSB2_DCPCTR_CSSTS_Msk (0x1000UL) /*!< CSSTS (Bitfield-Mask: 0x01) */ +#define RUSB2_DCPCTR_SUREQCLR_Pos (11UL) /* SUREQCLR (Bit 11) */ +#define RUSB2_DCPCTR_SUREQCLR_Msk (0x800UL) /* SUREQCLR (Bitfield-Mask: 0x01) */ +#define RUSB2_DCPCTR_SQCLR_Pos (8UL) /* SQCLR (Bit 8) */ +#define RUSB2_DCPCTR_SQCLR_Msk (0x100UL) /* SQCLR (Bitfield-Mask: 0x01) */ +#define RUSB2_DCPCTR_SQSET_Pos (7UL) /* SQSET (Bit 7) */ +#define RUSB2_DCPCTR_SQSET_Msk (0x80UL) /* SQSET (Bitfield-Mask: 0x01) */ +#define RUSB2_DCPCTR_SQMON_Pos (6UL) /* SQMON (Bit 6) */ +#define RUSB2_DCPCTR_SQMON_Msk (0x40UL) /* SQMON (Bitfield-Mask: 0x01) */ +#define RUSB2_DCPCTR_PBUSY_Pos (5UL) /* PBUSY (Bit 5) */ +#define RUSB2_DCPCTR_PBUSY_Msk (0x20UL) /* PBUSY (Bitfield-Mask: 0x01) */ +#define RUSB2_DCPCTR_CCPL_Pos (2UL) /* CCPL (Bit 2) */ +#define RUSB2_DCPCTR_CCPL_Msk (0x4UL) /* CCPL (Bitfield-Mask: 0x01) */ +#define RUSB2_DCPCTR_PID_Pos (0UL) /* PID (Bit 0) */ +#define RUSB2_DCPCTR_PID_Msk (0x3UL) /* PID (Bitfield-Mask: 0x03) */ + +// PIPESEL +#define RUSB2_PIPESEL_PIPESEL_Pos (0UL) /* PIPESEL (Bit 0) */ +#define RUSB2_PIPESEL_PIPESEL_Msk (0xfUL) /* PIPESEL (Bitfield-Mask: 0x0f) */ + +// PIPECFG +#define RUSB2_PIPECFG_TYPE_Pos (14UL) /* TYPE (Bit 14) */ +#define RUSB2_PIPECFG_TYPE_Msk (0xc000UL) /* TYPE (Bitfield-Mask: 0x03) */ +#define RUSB2_PIPECFG_BFRE_Pos (10UL) /* BFRE (Bit 10) */ +#define RUSB2_PIPECFG_BFRE_Msk (0x400UL) /* BFRE (Bitfield-Mask: 0x01) */ +#define RUSB2_PIPECFG_DBLB_Pos (9UL) /* DBLB (Bit 9) */ +#define RUSB2_PIPECFG_DBLB_Msk (0x200UL) /* DBLB (Bitfield-Mask: 0x01) */ +#define RUSB2_PIPECFG_CNTMD_Pos (8UL) /*!< CNTMD (Bit 8) */ +#define RUSB2_PIPECFG_CNTMD_Msk (0x100UL) /*!< CNTMD (Bitfield-Mask: 0x01) */ +#define RUSB2_PIPECFG_SHTNAK_Pos (7UL) /* SHTNAK (Bit 7) */ +#define RUSB2_PIPECFG_SHTNAK_Msk (0x80UL) /* SHTNAK (Bitfield-Mask: 0x01) */ +#define RUSB2_PIPECFG_DIR_Pos (4UL) /* DIR (Bit 4) */ +#define RUSB2_PIPECFG_DIR_Msk (0x10UL) /* DIR (Bitfield-Mask: 0x01) */ +#define RUSB2_PIPECFG_EPNUM_Pos (0UL) /* EPNUM (Bit 0) */ +#define RUSB2_PIPECFG_EPNUM_Msk (0xfUL) /* EPNUM (Bitfield-Mask: 0x0f) */ + +// PIPEBUF +#define RUSB2_PIPEBUF_BUFSIZE_Pos (10UL) /*!< BUFSIZE (Bit 10) */ +#define RUSB2_PIPEBUF_BUFSIZE_Msk (0x7c00UL) /*!< BUFSIZE (Bitfield-Mask: 0x1f) */ +#define RUSB2_PIPEBUF_BUFNMB_Pos (0UL) /*!< BUFNMB (Bit 0) */ +#define RUSB2_PIPEBUF_BUFNMB_Msk (0xffUL) /*!< BUFNMB (Bitfield-Mask: 0xff) */ + +// PIPEMAXP +#define RUSB2_PIPEMAXP_DEVSEL_Pos (12UL) /* DEVSEL (Bit 12) */ +#define RUSB2_PIPEMAXP_DEVSEL_Msk (0xf000UL) /* DEVSEL (Bitfield-Mask: 0x0f) */ +#define RUSB2_PIPEMAXP_MXPS_Pos (0UL) /* MXPS (Bit 0) */ +#define RUSB2_PIPEMAXP_MXPS_Msk (0x1ffUL) /* MXPS (Bitfield-Mask: 0x1ff) */ + +// PIPEPERI +#define RUSB2_PIPEPERI_IFIS_Pos (12UL) /* IFIS (Bit 12) */ +#define RUSB2_PIPEPERI_IFIS_Msk (0x1000UL) /* IFIS (Bitfield-Mask: 0x01) */ +#define RUSB2_PIPEPERI_IITV_Pos (0UL) /* IITV (Bit 0) */ +#define RUSB2_PIPEPERI_IITV_Msk (0x7UL) /* IITV (Bitfield-Mask: 0x07) */ + +// PIPE_CTR +#define RUSB2_PIPE_CTR_BSTS_Pos (15UL) /* BSTS (Bit 15) */ +#define RUSB2_PIPE_CTR_BSTS_Msk (0x8000UL) /* BSTS (Bitfield-Mask: 0x01) */ +#define RUSB2_PIPE_CTR_INBUFM_Pos (14UL) /* INBUFM (Bit 14) */ +#define RUSB2_PIPE_CTR_INBUFM_Msk (0x4000UL) /* INBUFM (Bitfield-Mask: 0x01) */ +#define RUSB2_PIPE_CTR_CSCLR_Pos (13UL) /* CSCLR (Bit 13) */ +#define RUSB2_PIPE_CTR_CSCLR_Msk (0x2000UL) /* CSCLR (Bitfield-Mask: 0x01) */ +#define RUSB2_PIPE_CTR_CSSTS_Pos (12UL) /* CSSTS (Bit 12) */ +#define RUSB2_PIPE_CTR_CSSTS_Msk (0x1000UL) /* CSSTS (Bitfield-Mask: 0x01) */ +#define RUSB2_PIPE_CTR_ATREPM_Pos (10UL) /* ATREPM (Bit 10) */ +#define RUSB2_PIPE_CTR_ATREPM_Msk (0x400UL) /* ATREPM (Bitfield-Mask: 0x01) */ +#define RUSB2_PIPE_CTR_ACLRM_Pos (9UL) /* ACLRM (Bit 9) */ +#define RUSB2_PIPE_CTR_ACLRM_Msk (0x200UL) /* ACLRM (Bitfield-Mask: 0x01) */ +#define RUSB2_PIPE_CTR_SQCLR_Pos (8UL) /* SQCLR (Bit 8) */ +#define RUSB2_PIPE_CTR_SQCLR_Msk (0x100UL) /* SQCLR (Bitfield-Mask: 0x01) */ +#define RUSB2_PIPE_CTR_SQSET_Pos (7UL) /* SQSET (Bit 7) */ +#define RUSB2_PIPE_CTR_SQSET_Msk (0x80UL) /* SQSET (Bitfield-Mask: 0x01) */ +#define RUSB2_PIPE_CTR_SQMON_Pos (6UL) /* SQMON (Bit 6) */ +#define RUSB2_PIPE_CTR_SQMON_Msk (0x40UL) /* SQMON (Bitfield-Mask: 0x01) */ +#define RUSB2_PIPE_CTR_PBUSY_Pos (5UL) /* PBUSY (Bit 5) */ +#define RUSB2_PIPE_CTR_PBUSY_Msk (0x20UL) /* PBUSY (Bitfield-Mask: 0x01) */ +#define RUSB2_PIPE_CTR_PID_Pos (0UL) /* PID (Bit 0) */ +#define RUSB2_PIPE_CTR_PID_Msk (0x3UL) /* PID (Bitfield-Mask: 0x03) */ + +// DEVADD +#define RUSB2_DEVADD_UPPHUB_Pos (11UL) /* UPPHUB (Bit 11) */ +#define RUSB2_DEVADD_UPPHUB_Msk (0x7800UL) /* UPPHUB (Bitfield-Mask: 0x0f) */ +#define RUSB2_DEVADD_HUBPORT_Pos (8UL) /* HUBPORT (Bit 8) */ +#define RUSB2_DEVADD_HUBPORT_Msk (0x700UL) /* HUBPORT (Bitfield-Mask: 0x07) */ +#define RUSB2_DEVADD_USBSPD_Pos (6UL) /* USBSPD (Bit 6) */ +#define RUSB2_DEVADD_USBSPD_Msk (0xc0UL) /* USBSPD (Bitfield-Mask: 0x03) */ + +// USBBCCTRL0 +#define RUSB2_USBBCCTRL0_PDDETSTS0_Pos (9UL) /* PDDETSTS0 (Bit 9) */ +#define RUSB2_USBBCCTRL0_PDDETSTS0_Msk (0x200UL) /* PDDETSTS0 (Bitfield-Mask: 0x01) */ +#define RUSB2_USBBCCTRL0_CHGDETSTS0_Pos (8UL) /* CHGDETSTS0 (Bit 8) */ +#define RUSB2_USBBCCTRL0_CHGDETSTS0_Msk (0x100UL) /* CHGDETSTS0 (Bitfield-Mask: 0x01) */ +#define RUSB2_USBBCCTRL0_BATCHGE0_Pos (7UL) /* BATCHGE0 (Bit 7) */ +#define RUSB2_USBBCCTRL0_BATCHGE0_Msk (0x80UL) /* BATCHGE0 (Bitfield-Mask: 0x01) */ +#define RUSB2_USBBCCTRL0_VDMSRCE0_Pos (5UL) /* VDMSRCE0 (Bit 5) */ +#define RUSB2_USBBCCTRL0_VDMSRCE0_Msk (0x20UL) /* VDMSRCE0 (Bitfield-Mask: 0x01) */ +#define RUSB2_USBBCCTRL0_IDPSINKE0_Pos (4UL) /* IDPSINKE0 (Bit 4) */ +#define RUSB2_USBBCCTRL0_IDPSINKE0_Msk (0x10UL) /* IDPSINKE0 (Bitfield-Mask: 0x01) */ +#define RUSB2_USBBCCTRL0_VDPSRCE0_Pos (3UL) /* VDPSRCE0 (Bit 3) */ +#define RUSB2_USBBCCTRL0_VDPSRCE0_Msk (0x8UL) /* VDPSRCE0 (Bitfield-Mask: 0x01) */ +#define RUSB2_USBBCCTRL0_IDMSINKE0_Pos (2UL) /* IDMSINKE0 (Bit 2) */ +#define RUSB2_USBBCCTRL0_IDMSINKE0_Msk (0x4UL) /* IDMSINKE0 (Bitfield-Mask: 0x01) */ +#define RUSB2_USBBCCTRL0_IDPSRCE0_Pos (1UL) /* IDPSRCE0 (Bit 1) */ +#define RUSB2_USBBCCTRL0_IDPSRCE0_Msk (0x2UL) /* IDPSRCE0 (Bitfield-Mask: 0x01) */ +#define RUSB2_USBBCCTRL0_RPDME0_Pos (0UL) /* RPDME0 (Bit 0) */ +#define RUSB2_USBBCCTRL0_RPDME0_Msk (0x1UL) /* RPDME0 (Bitfield-Mask: 0x01) */ + +// UCKSEL +#define RUSB2_UCKSEL_UCKSELC_Pos (0UL) /* UCKSELC (Bit 0) */ +#define RUSB2_UCKSEL_UCKSELC_Msk (0x1UL) /* UCKSELC (Bitfield-Mask: 0x01) */ + +// USBMC +#define RUSB2_USBMC_VDCEN_Pos (7UL) /* VDCEN (Bit 7) */ +#define RUSB2_USBMC_VDCEN_Msk (0x80UL) /* VDCEN (Bitfield-Mask: 0x01) */ +#define RUSB2_USBMC_VDDUSBE_Pos (0UL) /* VDDUSBE (Bit 0) */ +#define RUSB2_USBMC_VDDUSBE_Msk (0x1UL) /* VDDUSBE (Bitfield-Mask: 0x01) */ + +// PHYSLEW +#define RUSB2_PHYSLEW_SLEWF01_Pos (3UL) /* SLEWF01 (Bit 3) */ +#define RUSB2_PHYSLEW_SLEWF01_Msk (0x8UL) /* SLEWF01 (Bitfield-Mask: 0x01) */ +#define RUSB2_PHYSLEW_SLEWF00_Pos (2UL) /* SLEWF00 (Bit 2) */ +#define RUSB2_PHYSLEW_SLEWF00_Msk (0x4UL) /* SLEWF00 (Bitfield-Mask: 0x01) */ +#define RUSB2_PHYSLEW_SLEWR01_Pos (1UL) /* SLEWR01 (Bit 1) */ +#define RUSB2_PHYSLEW_SLEWR01_Msk (0x2UL) /* SLEWR01 (Bitfield-Mask: 0x01) */ +#define RUSB2_PHYSLEW_SLEWR00_Pos (0UL) /* SLEWR00 (Bit 0) */ +#define RUSB2_PHYSLEW_SLEWR00_Msk (0x1UL) /* SLEWR00 (Bitfield-Mask: 0x01) */ + +// LPCTRL +#define RUSB2_LPCTRL_HWUPM_Pos (7UL) /* HWUPM (Bit 7) */ +#define RUSB2_LPCTRL_HWUPM_Msk (0x80UL) /* HWUPM (Bitfield-Mask: 0x01) */ + +// LPSTS +#define RUSB2_LPSTS_SUSPENDM_Pos (14UL) /* SUSPENDM (Bit 14) */ +#define RUSB2_LPSTS_SUSPENDM_Msk (0x4000UL) /* SUSPENDM (Bitfield-Mask: 0x01) */ + +// BCCTRL +#define RUSB2_BCCTRL_PDDETSTS_Pos (9UL) /* PDDETSTS (Bit 9) */ +#define RUSB2_BCCTRL_PDDETSTS_Msk (0x200UL) /* PDDETSTS (Bitfield-Mask: 0x01) */ +#define RUSB2_BCCTRL_CHGDETSTS_Pos (8UL) /* CHGDETSTS (Bit 8) */ +#define RUSB2_BCCTRL_CHGDETSTS_Msk (0x100UL) /* CHGDETSTS (Bitfield-Mask: 0x01) */ +#define RUSB2_BCCTRL_DCPMODE_Pos (5UL) /* DCPMODE (Bit 5) */ +#define RUSB2_BCCTRL_DCPMODE_Msk (0x20UL) /* DCPMODE (Bitfield-Mask: 0x01) */ +#define RUSB2_BCCTRL_VDMSRCE_Pos (4UL) /* VDMSRCE (Bit 4) */ +#define RUSB2_BCCTRL_VDMSRCE_Msk (0x10UL) /* VDMSRCE (Bitfield-Mask: 0x01) */ +#define RUSB2_BCCTRL_IDPSINKE_Pos (3UL) /* IDPSINKE (Bit 3) */ +#define RUSB2_BCCTRL_IDPSINKE_Msk (0x8UL) /* IDPSINKE (Bitfield-Mask: 0x01) */ +#define RUSB2_BCCTRL_VDPSRCE_Pos (2UL) /* VDPSRCE (Bit 2) */ +#define RUSB2_BCCTRL_VDPSRCE_Msk (0x4UL) /* VDPSRCE (Bitfield-Mask: 0x01) */ +#define RUSB2_BCCTRL_IDMSINKE_Pos (1UL) /* IDMSINKE (Bit 1) */ +#define RUSB2_BCCTRL_IDMSINKE_Msk (0x2UL) /* IDMSINKE (Bitfield-Mask: 0x01) */ +#define RUSB2_BCCTRL_IDPSRCE_Pos (0UL) /* IDPSRCE (Bit 0) */ +#define RUSB2_BCCTRL_IDPSRCE_Msk (0x1UL) /* IDPSRCE (Bitfield-Mask: 0x01) */ + +// PL1CTRL1 +#define RUSB2_PL1CTRL1_L1EXTMD_Pos (14UL) /* L1EXTMD (Bit 14) */ +#define RUSB2_PL1CTRL1_L1EXTMD_Msk (0x4000UL) /* L1EXTMD (Bitfield-Mask: 0x01) */ +#define RUSB2_PL1CTRL1_HIRDTHR_Pos (8UL) /* HIRDTHR (Bit 8) */ +#define RUSB2_PL1CTRL1_HIRDTHR_Msk (0xf00UL) /* HIRDTHR (Bitfield-Mask: 0x0f) */ +#define RUSB2_PL1CTRL1_DVSQ_Pos (4UL) /* DVSQ (Bit 4) */ +#define RUSB2_PL1CTRL1_DVSQ_Msk (0xf0UL) /* DVSQ (Bitfield-Mask: 0x0f) */ +#define RUSB2_PL1CTRL1_L1NEGOMD_Pos (3UL) /* L1NEGOMD (Bit 3) */ +#define RUSB2_PL1CTRL1_L1NEGOMD_Msk (0x8UL) /* L1NEGOMD (Bitfield-Mask: 0x01) */ +#define RUSB2_PL1CTRL1_L1RESPMD_Pos (1UL) /* L1RESPMD (Bit 1) */ +#define RUSB2_PL1CTRL1_L1RESPMD_Msk (0x6UL) /* L1RESPMD (Bitfield-Mask: 0x03) */ +#define RUSB2_PL1CTRL1_L1RESPEN_Pos (0UL) /* L1RESPEN (Bit 0) */ +#define RUSB2_PL1CTRL1_L1RESPEN_Msk (0x1UL) /* L1RESPEN (Bitfield-Mask: 0x01) */ + +// PL1CTRL2 +#define RUSB2_PL1CTRL2_RWEMON_Pos (12UL) /* RWEMON (Bit 12) */ +#define RUSB2_PL1CTRL2_RWEMON_Msk (0x1000UL) /* RWEMON (Bitfield-Mask: 0x01) */ +#define RUSB2_PL1CTRL2_HIRDMON_Pos (8UL) /* HIRDMON (Bit 8) */ +#define RUSB2_PL1CTRL2_HIRDMON_Msk (0xf00UL) /* HIRDMON (Bitfield-Mask: 0x0f) */ + +// HL1CTRL1 +#define RUSB2_HL1CTRL1_L1STATUS_Pos (1UL) /* L1STATUS (Bit 1) */ +#define RUSB2_HL1CTRL1_L1STATUS_Msk (0x6UL) /* L1STATUS (Bitfield-Mask: 0x03) */ +#define RUSB2_HL1CTRL1_L1REQ_Pos (0UL) /* L1REQ (Bit 0) */ +#define RUSB2_HL1CTRL1_L1REQ_Msk (0x1UL) /* L1REQ (Bitfield-Mask: 0x01) */ + +// HL1CTRL2 +#define RUSB2_HL1CTRL2_BESL_Pos (15UL) /* BESL (Bit 15) */ +#define RUSB2_HL1CTRL2_BESL_Msk (0x8000UL) /* BESL (Bitfield-Mask: 0x01) */ +#define RUSB2_HL1CTRL2_L1RWE_Pos (12UL) /* L1RWE (Bit 12) */ +#define RUSB2_HL1CTRL2_L1RWE_Msk (0x1000UL) /* L1RWE (Bitfield-Mask: 0x01) */ +#define RUSB2_HL1CTRL2_HIRD_Pos (8UL) /* HIRD (Bit 8) */ +#define RUSB2_HL1CTRL2_HIRD_Msk (0xf00UL) /* HIRD (Bitfield-Mask: 0x0f) */ +#define RUSB2_HL1CTRL2_L1ADDR_Pos (0UL) /* L1ADDR (Bit 0) */ +#define RUSB2_HL1CTRL2_L1ADDR_Msk (0xfUL) /* L1ADDR (Bitfield-Mask: 0x0f) */ + +// PHYTRIM1 +#define RUSB2_PHYTRIM1_IMPOFFSET_Pos (12UL) /*!< IMPOFFSET (Bit 12) */ +#define RUSB2_PHYTRIM1_IMPOFFSET_Msk (0x7000UL) /*!< IMPOFFSET (Bitfield-Mask: 0x07) */ +#define RUSB2_PHYTRIM1_HSIUP_Pos (8UL) /*!< HSIUP (Bit 8) */ +#define RUSB2_PHYTRIM1_HSIUP_Msk (0xf00UL) /*!< HSIUP (Bitfield-Mask: 0x0f) */ +#define RUSB2_PHYTRIM1_PCOMPENB_Pos (7UL) /*!< PCOMPENB (Bit 7) */ +#define RUSB2_PHYTRIM1_PCOMPENB_Msk (0x80UL) /*!< PCOMPENB (Bitfield-Mask: 0x01) */ +#define RUSB2_PHYTRIM1_DFALL_Pos (2UL) /*!< DFALL (Bit 2) */ +#define RUSB2_PHYTRIM1_DFALL_Msk (0xcUL) /*!< DFALL (Bitfield-Mask: 0x03) */ +#define RUSB2_PHYTRIM1_DRISE_Pos (0UL) /*!< DRISE (Bit 0) */ +#define RUSB2_PHYTRIM1_DRISE_Msk (0x3UL) /*!< DRISE (Bitfield-Mask: 0x03) */ + +// PHYTRIM2 +#define RUSB2_PHYTRIM2_DIS_Pos (12UL) /*!< DIS (Bit 12) */ +#define RUSB2_PHYTRIM2_DIS_Msk (0x7000UL) /*!< DIS (Bitfield-Mask: 0x07) */ +#define RUSB2_PHYTRIM2_PDR_Pos (8UL) /*!< PDR (Bit 8) */ +#define RUSB2_PHYTRIM2_PDR_Msk (0x300UL) /*!< PDR (Bitfield-Mask: 0x03) */ +#define RUSB2_PHYTRIM2_HSRXENMO_Pos (7UL) /*!< HSRXENMO (Bit 7) */ +#define RUSB2_PHYTRIM2_HSRXENMO_Msk (0x80UL) /*!< HSRXENMO (Bitfield-Mask: 0x01) */ +#define RUSB2_PHYTRIM2_SQU_Pos (0UL) /*!< SQU (Bit 0) */ +#define RUSB2_PHYTRIM2_SQU_Msk (0xfUL) /*!< SQU (Bitfield-Mask: 0x0f) */ + +// DPUSR0R +#define RUSB2_DPUSR0R_DVBSTSHM_Pos (23UL) /* DVBSTSHM (Bit 23) */ +#define RUSB2_DPUSR0R_DVBSTSHM_Msk (0x800000UL) /* DVBSTSHM (Bitfield-Mask: 0x01) */ +#define RUSB2_DPUSR0R_DOVCBHM_Pos (21UL) /* DOVCBHM (Bit 21) */ +#define RUSB2_DPUSR0R_DOVCBHM_Msk (0x200000UL) /* DOVCBHM (Bitfield-Mask: 0x01) */ +#define RUSB2_DPUSR0R_DOVCAHM_Pos (20UL) /* DOVCAHM (Bit 20) */ +#define RUSB2_DPUSR0R_DOVCAHM_Msk (0x100000UL) /* DOVCAHM (Bitfield-Mask: 0x01) */ + +// DPUSR1R +#define RUSB2_DPUSR1R_DVBSTSH_Pos (23UL) /* DVBSTSH (Bit 23) */ +#define RUSB2_DPUSR1R_DVBSTSH_Msk (0x800000UL) /* DVBSTSH (Bitfield-Mask: 0x01) */ +#define RUSB2_DPUSR1R_DOVCBH_Pos (21UL) /* DOVCBH (Bit 21) */ +#define RUSB2_DPUSR1R_DOVCBH_Msk (0x200000UL) /* DOVCBH (Bitfield-Mask: 0x01) */ +#define RUSB2_DPUSR1R_DOVCAH_Pos (20UL) /* DOVCAH (Bit 20) */ +#define RUSB2_DPUSR1R_DOVCAH_Msk (0x100000UL) /* DOVCAH (Bitfield-Mask: 0x01) */ +#define RUSB2_DPUSR1R_DVBSTSHE_Pos (7UL) /* DVBSTSHE (Bit 7) */ +#define RUSB2_DPUSR1R_DVBSTSHE_Msk (0x80UL) /* DVBSTSHE (Bitfield-Mask: 0x01) */ +#define RUSB2_DPUSR1R_DOVCBHE_Pos (5UL) /* DOVCBHE (Bit 5) */ +#define RUSB2_DPUSR1R_DOVCBHE_Msk (0x20UL) /* DOVCBHE (Bitfield-Mask: 0x01) */ +#define RUSB2_DPUSR1R_DOVCAHE_Pos (4UL) /* DOVCAHE (Bit 4) */ +#define RUSB2_DPUSR1R_DOVCAHE_Msk (0x10UL) /* DOVCAHE (Bitfield-Mask: 0x01) */ + +// DPUSR2R +#define RUSB2_DPUSR2R_DMINTE_Pos (9UL) /* DMINTE (Bit 9) */ +#define RUSB2_DPUSR2R_DMINTE_Msk (0x200UL) /* DMINTE (Bitfield-Mask: 0x01) */ +#define RUSB2_DPUSR2R_DPINTE_Pos (8UL) /* DPINTE (Bit 8) */ +#define RUSB2_DPUSR2R_DPINTE_Msk (0x100UL) /* DPINTE (Bitfield-Mask: 0x01) */ +#define RUSB2_DPUSR2R_DMVAL_Pos (5UL) /* DMVAL (Bit 5) */ +#define RUSB2_DPUSR2R_DMVAL_Msk (0x20UL) /* DMVAL (Bitfield-Mask: 0x01) */ +#define RUSB2_DPUSR2R_DPVAL_Pos (4UL) /* DPVAL (Bit 4) */ +#define RUSB2_DPUSR2R_DPVAL_Msk (0x10UL) /* DPVAL (Bitfield-Mask: 0x01) */ +#define RUSB2_DPUSR2R_DMINT_Pos (1UL) /* DMINT (Bit 1) */ +#define RUSB2_DPUSR2R_DMINT_Msk (0x2UL) /* DMINT (Bitfield-Mask: 0x01) */ +#define RUSB2_DPUSR2R_DPINT_Pos (0UL) /* DPINT (Bit 0) */ +#define RUSB2_DPUSR2R_DPINT_Msk (0x1UL) /* DPINT (Bitfield-Mask: 0x01) */ + +// DPUSRCR +#define RUSB2_DPUSRCR_FIXPHYPD_Pos (1UL) /* FIXPHYPD (Bit 1) */ +#define RUSB2_DPUSRCR_FIXPHYPD_Msk (0x2UL) /* FIXPHYPD (Bitfield-Mask: 0x01) */ +#define RUSB2_DPUSRCR_FIXPHY_Pos (0UL) /* FIXPHY (Bit 0) */ +#define RUSB2_DPUSRCR_FIXPHY_Msk (0x1UL) /* FIXPHY (Bitfield-Mask: 0x01) */ + +// DPUSR0R_FS +#define RUSB2_DPUSR0R_FS_DVBSTS0_Pos (23UL) /* DVBSTS0 (Bit 23) */ +#define RUSB2_DPUSR0R_FS_DVBSTS0_Msk (0x800000UL) /* DVBSTS0 (Bitfield-Mask: 0x01) */ +#define RUSB2_DPUSR0R_FS_DOVCB0_Pos (21UL) /* DOVCB0 (Bit 21) */ +#define RUSB2_DPUSR0R_FS_DOVCB0_Msk (0x200000UL) /* DOVCB0 (Bitfield-Mask: 0x01) */ +#define RUSB2_DPUSR0R_FS_DOVCA0_Pos (20UL) /* DOVCA0 (Bit 20) */ +#define RUSB2_DPUSR0R_FS_DOVCA0_Msk (0x100000UL) /* DOVCA0 (Bitfield-Mask: 0x01) */ +#define RUSB2_DPUSR0R_FS_DM0_Pos (17UL) /* DM0 (Bit 17) */ +#define RUSB2_DPUSR0R_FS_DM0_Msk (0x20000UL) /* DM0 (Bitfield-Mask: 0x01) */ +#define RUSB2_DPUSR0R_FS_DP0_Pos (16UL) /* DP0 (Bit 16) */ +#define RUSB2_DPUSR0R_FS_DP0_Msk (0x10000UL) /* DP0 (Bitfield-Mask: 0x01) */ +#define RUSB2_DPUSR0R_FS_FIXPHY0_Pos (4UL) /* FIXPHY0 (Bit 4) */ +#define RUSB2_DPUSR0R_FS_FIXPHY0_Msk (0x10UL) /* FIXPHY0 (Bitfield-Mask: 0x01) */ +#define RUSB2_DPUSR0R_FS_DRPD0_Pos (3UL) /* DRPD0 (Bit 3) */ +#define RUSB2_DPUSR0R_FS_DRPD0_Msk (0x8UL) /* DRPD0 (Bitfield-Mask: 0x01) */ +#define RUSB2_DPUSR0R_FS_RPUE0_Pos (1UL) /* RPUE0 (Bit 1) */ +#define RUSB2_DPUSR0R_FS_RPUE0_Msk (0x2UL) /* RPUE0 (Bitfield-Mask: 0x01) */ +#define RUSB2_DPUSR0R_FS_SRPC0_Pos (0UL) /* SRPC0 (Bit 0) */ +#define RUSB2_DPUSR0R_FS_SRPC0_Msk (0x1UL) /* SRPC0 (Bitfield-Mask: 0x01) */ + +// DPUSR1R_FS +#define RUSB2_DPUSR1R_FS_DVBINT0_Pos (23UL) /* DVBINT0 (Bit 23) */ +#define RUSB2_DPUSR1R_FS_DVBINT0_Msk (0x800000UL) /* DVBINT0 (Bitfield-Mask: 0x01) */ +#define RUSB2_DPUSR1R_FS_DOVRCRB0_Pos (21UL) /* DOVRCRB0 (Bit 21) */ +#define RUSB2_DPUSR1R_FS_DOVRCRB0_Msk (0x200000UL) /* DOVRCRB0 (Bitfield-Mask: 0x01) */ +#define RUSB2_DPUSR1R_FS_DOVRCRA0_Pos (20UL) /* DOVRCRA0 (Bit 20) */ +#define RUSB2_DPUSR1R_FS_DOVRCRA0_Msk (0x100000UL) /* DOVRCRA0 (Bitfield-Mask: 0x01) */ +#define RUSB2_DPUSR1R_FS_DMINT0_Pos (17UL) /* DMINT0 (Bit 17) */ +#define RUSB2_DPUSR1R_FS_DMINT0_Msk (0x20000UL) /* DMINT0 (Bitfield-Mask: 0x01) */ +#define RUSB2_DPUSR1R_FS_DPINT0_Pos (16UL) /* DPINT0 (Bit 16) */ +#define RUSB2_DPUSR1R_FS_DPINT0_Msk (0x10000UL) /* DPINT0 (Bitfield-Mask: 0x01) */ +#define RUSB2_DPUSR1R_FS_DVBSE0_Pos (7UL) /* DVBSE0 (Bit 7) */ +#define RUSB2_DPUSR1R_FS_DVBSE0_Msk (0x80UL) /* DVBSE0 (Bitfield-Mask: 0x01) */ +#define RUSB2_DPUSR1R_FS_DOVRCRBE0_Pos (5UL) /* DOVRCRBE0 (Bit 5) */ +#define RUSB2_DPUSR1R_FS_DOVRCRBE0_Msk (0x20UL) /* DOVRCRBE0 (Bitfield-Mask: 0x01) */ +#define RUSB2_DPUSR1R_FS_DOVRCRAE0_Pos (4UL) /* DOVRCRAE0 (Bit 4) */ +#define RUSB2_DPUSR1R_FS_DOVRCRAE0_Msk (0x10UL) /* DOVRCRAE0 (Bitfield-Mask: 0x01) */ +#define RUSB2_DPUSR1R_FS_DMINTE0_Pos (1UL) /* DMINTE0 (Bit 1) */ +#define RUSB2_DPUSR1R_FS_DMINTE0_Msk (0x2UL) /* DMINTE0 (Bitfield-Mask: 0x01) */ +#define RUSB2_DPUSR1R_FS_DPINTE0_Pos (0UL) /* DPINTE0 (Bit 0) */ +#define RUSB2_DPUSR1R_FS_DPINTE0_Msk (0x1UL) /* DPINTE0 (Bitfield-Mask: 0x01) */ + +/*--------------------------------------------------------------------*/ +/* Register Bit Utils */ +/*--------------------------------------------------------------------*/ +#define RUSB2_SYSSTS0_LNST_SE0 (0) +#define RUSB2_SYSSTS0_LNST_FS_J (1u << RUSB2_SYSSTS0_LNST_Pos) /* Full-speed J state */ +#define RUSB2_SYSSTS0_LNST_FS_K (2u << RUSB2_SYSSTS0_LNST_Pos) /* Full-speed K state */ +#define RUSB2_SYSSTS0_LNST_LS_SE1 (3u << RUSB2_SYSSTS0_LNST_Pos) /* Low-speed SE1 state */ + +#define RUSB2_DVSTCTR0_RHST_LS (1U << RUSB2_DVSTCTR0_RHST_Pos) /* Low-speed connection */ +#define RUSB2_DVSTCTR0_RHST_FS (2U << RUSB2_DVSTCTR0_RHST_Pos) /* Full-speed connection */ +#define RUSB2_DVSTCTR0_RHST_HS (3U << RUSB2_DVSTCTR0_RHST_Pos) /* Full-speed connection */ + +#define RUSB2_PIPE_CTR_PID_NAK (0U << RUSB2_PIPE_CTR_PID_Pos) /* NAK response */ +#define RUSB2_PIPE_CTR_PID_BUF (1U << RUSB2_PIPE_CTR_PID_Pos) /* BUF response (depends buffer state) */ +#define RUSB2_PIPE_CTR_PID_STALL (2U << RUSB2_PIPE_CTR_PID_Pos) /* STALL response */ +#define RUSB2_PIPE_CTR_PID_STALL2 (3U << RUSB2_PIPE_CTR_PID_Pos) /* Also STALL response */ + +#define RUSB2_DEVADD_USBSPD_LS (1U << RUSB2_DEVADD_USBSPD_Pos) /* Target Device Low-speed */ +#define RUSB2_DEVADD_USBSPD_FS (2U << RUSB2_DEVADD_USBSPD_Pos) /* Target Device Full-speed */ + +#define RUSB2_CFIFOSEL_ISEL_WRITE (1U << RUSB2_CFIFOSEL_ISEL_Pos) /* FIFO write AKA TX*/ + +#define RUSB2_FIFOSEL_BIGEND (1U << RUSB2_CFIFOSEL_BIGEND_Pos) /* FIFO Big Endian */ +#define RUSB2_FIFOSEL_MBW_8BIT (0U << RUSB2_CFIFOSEL_MBW_Pos) /* 8-bit width */ +#define RUSB2_FIFOSEL_MBW_16BIT (1U << RUSB2_CFIFOSEL_MBW_Pos) /* 16-bit width */ +#define RUSB2_FIFOSEL_MBW_32BIT (2U << RUSB2_CFIFOSEL_MBW_Pos) /* 32-bit width */ + +#define RUSB2_INTSTS0_CTSQ_CTRL_RDATA (1U << RUSB2_INTSTS0_CTSQ_Pos) + +#define RUSB2_INTSTS0_DVSQ_STATE_DEF (1U << RUSB2_INTSTS0_DVSQ_Pos) /* Default state */ +#define RUSB2_INTSTS0_DVSQ_STATE_ADDR (2U << RUSB2_INTSTS0_DVSQ_Pos) /* Address state */ +#define RUSB2_INTSTS0_DVSQ_STATE_SUSP0 (4U << RUSB2_INTSTS0_DVSQ_Pos) /* Suspend state */ +#define RUSB2_INTSTS0_DVSQ_STATE_SUSP1 (5U << RUSB2_INTSTS0_DVSQ_Pos) /* Suspend state */ +#define RUSB2_INTSTS0_DVSQ_STATE_SUSP2 (6U << RUSB2_INTSTS0_DVSQ_Pos) /* Suspend state */ +#define RUSB2_INTSTS0_DVSQ_STATE_SUSP3 (7U << RUSB2_INTSTS0_DVSQ_Pos) /* Suspend state */ + +#define RUSB2_PIPECFG_TYPE_BULK (1U << RUSB2_PIPECFG_TYPE_Pos) +#define RUSB2_PIPECFG_TYPE_INT (2U << RUSB2_PIPECFG_TYPE_Pos) +#define RUSB2_PIPECFG_TYPE_ISO (3U << RUSB2_PIPECFG_TYPE_Pos) + +//--------------------------------------------------------------------+ +// Static Assert +//--------------------------------------------------------------------+ + +TU_VERIFY_STATIC(sizeof(RUSB2_PIPE_TR_t) == 4, "incorrect size"); +TU_VERIFY_STATIC(sizeof(rusb2_reg_t) == 1032, "incorrect size"); + +TU_VERIFY_STATIC(offsetof(rusb2_reg_t, SYSCFG ) == 0x0000, "incorrect offset"); +TU_VERIFY_STATIC(offsetof(rusb2_reg_t, BUSWAIT ) == 0x0002, "incorrect offset"); +TU_VERIFY_STATIC(offsetof(rusb2_reg_t, SYSSTS0 ) == 0x0004, "incorrect offset"); +TU_VERIFY_STATIC(offsetof(rusb2_reg_t, PLLSTA ) == 0x0006, "incorrect offset"); +TU_VERIFY_STATIC(offsetof(rusb2_reg_t, DVSTCTR0 ) == 0x0008, "incorrect offset"); +TU_VERIFY_STATIC(offsetof(rusb2_reg_t, TESTMODE ) == 0x000C, "incorrect offset"); +TU_VERIFY_STATIC(offsetof(rusb2_reg_t, CFIFO ) == 0x0014, "incorrect offset"); +TU_VERIFY_STATIC(offsetof(rusb2_reg_t, D0FIFO ) == 0x0018, "incorrect offset"); +TU_VERIFY_STATIC(offsetof(rusb2_reg_t, D1FIFO ) == 0x001C, "incorrect offset"); +TU_VERIFY_STATIC(offsetof(rusb2_reg_t, CFIFOSEL ) == 0x0020, "incorrect offset"); +TU_VERIFY_STATIC(offsetof(rusb2_reg_t, CFIFOCTR ) == 0x0022, "incorrect offset"); +TU_VERIFY_STATIC(offsetof(rusb2_reg_t, D0FIFOSEL ) == 0x0028, "incorrect offset"); +TU_VERIFY_STATIC(offsetof(rusb2_reg_t, D0FIFOCTR ) == 0x002A, "incorrect offset"); +TU_VERIFY_STATIC(offsetof(rusb2_reg_t, D1FIFOSEL ) == 0x002C, "incorrect offset"); +TU_VERIFY_STATIC(offsetof(rusb2_reg_t, D1FIFOCTR ) == 0x002E, "incorrect offset"); +TU_VERIFY_STATIC(offsetof(rusb2_reg_t, INTENB0 ) == 0x0030, "incorrect offset"); +TU_VERIFY_STATIC(offsetof(rusb2_reg_t, INTENB1 ) == 0x0032, "incorrect offset"); +TU_VERIFY_STATIC(offsetof(rusb2_reg_t, BRDYENB ) == 0x0036, "incorrect offset"); +TU_VERIFY_STATIC(offsetof(rusb2_reg_t, NRDYENB ) == 0x0038, "incorrect offset"); +TU_VERIFY_STATIC(offsetof(rusb2_reg_t, BEMPENB ) == 0x003A, "incorrect offset"); +TU_VERIFY_STATIC(offsetof(rusb2_reg_t, SOFCFG ) == 0x003C, "incorrect offset"); +TU_VERIFY_STATIC(offsetof(rusb2_reg_t, PHYSET ) == 0x003E, "incorrect offset"); +TU_VERIFY_STATIC(offsetof(rusb2_reg_t, INTSTS0 ) == 0x0040, "incorrect offset"); +TU_VERIFY_STATIC(offsetof(rusb2_reg_t, INTSTS1 ) == 0x0042, "incorrect offset"); +TU_VERIFY_STATIC(offsetof(rusb2_reg_t, BRDYSTS ) == 0x0046, "incorrect offset"); +TU_VERIFY_STATIC(offsetof(rusb2_reg_t, NRDYSTS ) == 0x0048, "incorrect offset"); +TU_VERIFY_STATIC(offsetof(rusb2_reg_t, BEMPSTS ) == 0x004A, "incorrect offset"); +TU_VERIFY_STATIC(offsetof(rusb2_reg_t, FRMNUM ) == 0x004C, "incorrect offset"); +TU_VERIFY_STATIC(offsetof(rusb2_reg_t, UFRMNUM ) == 0x004E, "incorrect offset"); +TU_VERIFY_STATIC(offsetof(rusb2_reg_t, USBADDR ) == 0x0050, "incorrect offset"); +TU_VERIFY_STATIC(offsetof(rusb2_reg_t, USBREQ ) == 0x0054, "incorrect offset"); +TU_VERIFY_STATIC(offsetof(rusb2_reg_t, USBVAL ) == 0x0056, "incorrect offset"); +TU_VERIFY_STATIC(offsetof(rusb2_reg_t, USBINDX ) == 0x0058, "incorrect offset"); +TU_VERIFY_STATIC(offsetof(rusb2_reg_t, USBLENG ) == 0x005A, "incorrect offset"); +TU_VERIFY_STATIC(offsetof(rusb2_reg_t, DCPCFG ) == 0x005C, "incorrect offset"); +TU_VERIFY_STATIC(offsetof(rusb2_reg_t, DCPMAXP ) == 0x005E, "incorrect offset"); +TU_VERIFY_STATIC(offsetof(rusb2_reg_t, DCPCTR ) == 0x0060, "incorrect offset"); +TU_VERIFY_STATIC(offsetof(rusb2_reg_t, PIPESEL ) == 0x0064, "incorrect offset"); +TU_VERIFY_STATIC(offsetof(rusb2_reg_t, PIPECFG ) == 0x0068, "incorrect offset"); +TU_VERIFY_STATIC(offsetof(rusb2_reg_t, PIPEBUF ) == 0x006A, "incorrect offset"); +TU_VERIFY_STATIC(offsetof(rusb2_reg_t, PIPEMAXP ) == 0x006C, "incorrect offset"); +TU_VERIFY_STATIC(offsetof(rusb2_reg_t, PIPEPERI ) == 0x006E, "incorrect offset"); +TU_VERIFY_STATIC(offsetof(rusb2_reg_t, PIPE_CTR ) == 0x0070, "incorrect offset"); +TU_VERIFY_STATIC(offsetof(rusb2_reg_t, PIPE_TR ) == 0x0090, "incorrect offset"); +TU_VERIFY_STATIC(offsetof(rusb2_reg_t, USBBCCTRL0 ) == 0x00B0, "incorrect offset"); +TU_VERIFY_STATIC(offsetof(rusb2_reg_t, UCKSEL ) == 0x00C4, "incorrect offset"); +TU_VERIFY_STATIC(offsetof(rusb2_reg_t, USBMC ) == 0x00CC, "incorrect offset"); +TU_VERIFY_STATIC(offsetof(rusb2_reg_t, DEVADD ) == 0x00D0, "incorrect offset"); +TU_VERIFY_STATIC(offsetof(rusb2_reg_t, PHYSLEW ) == 0x00F0, "incorrect offset"); +TU_VERIFY_STATIC(offsetof(rusb2_reg_t, LPCTRL ) == 0x0100, "incorrect offset"); +TU_VERIFY_STATIC(offsetof(rusb2_reg_t, LPSTS ) == 0x0102, "incorrect offset"); +TU_VERIFY_STATIC(offsetof(rusb2_reg_t, BCCTRL ) == 0x0140, "incorrect offset"); +TU_VERIFY_STATIC(offsetof(rusb2_reg_t, PL1CTRL1 ) == 0x0144, "incorrect offset"); +TU_VERIFY_STATIC(offsetof(rusb2_reg_t, PL1CTRL2 ) == 0x0146, "incorrect offset"); +TU_VERIFY_STATIC(offsetof(rusb2_reg_t, HL1CTRL1 ) == 0x0148, "incorrect offset"); +TU_VERIFY_STATIC(offsetof(rusb2_reg_t, HL1CTRL2 ) == 0x014A, "incorrect offset"); +TU_VERIFY_STATIC(offsetof(rusb2_reg_t, PHYTRIM1 ) == 0x0150, "incorrect offset"); +TU_VERIFY_STATIC(offsetof(rusb2_reg_t, PHYTRIM2 ) == 0x0152, "incorrect offset"); +TU_VERIFY_STATIC(offsetof(rusb2_reg_t, DPUSR0R ) == 0x0160, "incorrect offset"); +TU_VERIFY_STATIC(offsetof(rusb2_reg_t, DPUSR1R ) == 0x0164, "incorrect offset"); +TU_VERIFY_STATIC(offsetof(rusb2_reg_t, DPUSR2R ) == 0x0168, "incorrect offset"); +TU_VERIFY_STATIC(offsetof(rusb2_reg_t, DPUSRCR ) == 0x016A, "incorrect offset"); +TU_VERIFY_STATIC(offsetof(rusb2_reg_t, DPUSR0R_FS ) == 0x0400, "incorrect offset"); +TU_VERIFY_STATIC(offsetof(rusb2_reg_t, DPUSR1R_FS ) == 0x0404, "incorrect offset"); + +#ifdef __cplusplus +} +#endif + +#endif /* TUSB_RUSB2_TYPE_H_ */ diff --git a/libraries/Adafruit_TinyUSB_Arduino/src/portable/st/stm32_fsdev/dcd_stm32_fsdev.c b/libraries/Adafruit_TinyUSB_Arduino/src/portable/st/stm32_fsdev/dcd_stm32_fsdev.c new file mode 100644 index 0000000..41da3dd --- /dev/null +++ b/libraries/Adafruit_TinyUSB_Arduino/src/portable/st/stm32_fsdev/dcd_stm32_fsdev.c @@ -0,0 +1,814 @@ +/* + * The MIT License (MIT) + * + * Copyright (c) 2019 Nathan Conrad + * + * Portions: + * Copyright (c) 2019 Ha Thach (tinyusb.org) + * Copyright (c) 2022 Simon Küppers (skuep) + * Copyright (c) 2022 HiFiPhile + * + * 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. + */ + +/********************************************** + * This driver has been tested with the following MCUs: + * - F070, F072, L053, F042F6 + * + * It also should work with minimal changes for any ST MCU with an "USB A"/"PCD"/"HCD" peripheral. This + * covers: + * + * F04x, F072, F078, F070x6/B 1024 byte buffer + * F102, F103 512 byte buffer; no internal D+ pull-up (maybe many more changes?) + * F302xB/C, F303xB/C, F373 512 byte buffer; no internal D+ pull-up + * F302x6/8, F302xD/E2, F303xD/E 1024 byte buffer; no internal D+ pull-up + * C0 2048 byte buffer; 32-bit bus; host mode + * G0 2048 byte buffer; 32-bit bus; host mode + * G4 1024 byte buffer + * H5 2048 byte buffer; 32-bit bus; host mode + * L0x2, L0x3 1024 byte buffer + * L1 512 byte buffer + * L4x2, L4x3 1024 byte buffer + * L5 1024 byte buffer + * U0 1024 byte buffer; 32-bit bus + * U535, U545 2048 byte buffer; 32-bit bus; host mode + * WB35, WB55 1024 byte buffer + * + * To use this driver, you must: + * - If you are using a device with crystal-less USB, set up the clock recovery system (CRS) + * - Remap pins to be D+/D- on devices that they are shared (for example: F042Fx) + * - This is different to the normal "alternate function" GPIO interface, needs to go through SYSCFG->CFGRx register + * - Enable USB clock; Perhaps use __HAL_RCC_USB_CLK_ENABLE(); + * - (Optionally configure GPIO HAL to tell it the USB driver is using the USB pins) + * - call tusb_init(); + * + * Assumptions of the driver: + * - You are not using CAN (it must share the packet buffer) + * - APB clock is >= 10 MHz + * - On some boards, series resistors are required, but not on others. + * - On some boards, D+ pull up resistor (1.5kohm) is required, but not on others. + * - You don't have long-running interrupts; some USB packets must be quickly responded to. + * - You have the ST CMSIS library linked into the project. HAL is not used. + * + * Current driver limitations (i.e., a list of features for you to add): + * - STALL handled, but not tested. + * - Does it work? No clue. + * - All EP BTABLE buffers are created based on max packet size of first EP opened with that address. + * - Packet buffer memory is copied in the interrupt. + * - This is better for performance, but means interrupts are disabled for longer + * - DMA may be the best choice, but it could also be pushed to the USBD task. + * - No double-buffering + * - No DMA + * - Minimal error handling + * - Perhaps error interrupts should be reported to the stack, or cause a device reset? + * - Assumes a single USB peripheral; I think that no hardware has multiple so this is fine. + * - Add a callback for enabling/disabling the D+ PU on devices without an internal PU. + * - F3 models use three separate interrupts. I think we could only use the LP interrupt for + * everything? However, the interrupts are configurable so the DisableInt and EnableInt + * below functions could be adjusting the wrong interrupts (if they had been reconfigured) + * - LPM is not used correctly, or at all? + * + * Notes: + * - The buffer table is allocated as endpoints are opened. The allocation is only + * cleared when the device is reset. This may be bad if the USB device needs + * to be reconfigured. + */ + +#include "tusb_option.h" + +#if CFG_TUD_ENABLED && defined(TUP_USBIP_FSDEV) && !(defined(TUP_USBIP_FSDEV_CH32) && CFG_TUD_WCH_USBIP_FSDEV == 0) + + #include "device/dcd.h" + #include "fsdev_common.h" + +//--------------------------------------------------------------------+ +// MACRO CONSTANT TYPEDEF +//--------------------------------------------------------------------+ + +// One of these for every EP IN & OUT, uses a bit of RAM.... +typedef struct { + uint8_t *buffer; + tu_fifo_t *ff; + uint16_t total_len; + uint16_t queued_len; + uint16_t max_packet_size; + uint8_t ep_idx; // index for USB_EPnR register + bool iso_in_sending; // Workaround for ISO IN EP doesn't have interrupt mask +} xfer_ctl_t; + +// EP allocator +typedef struct { + uint8_t ep_num; + uint8_t ep_type; + bool allocated[2]; +} ep_alloc_t; + +static xfer_ctl_t xfer_status[CFG_TUD_ENDPPOINT_MAX][2]; +static ep_alloc_t ep_alloc_status[FSDEV_EP_COUNT]; +static uint8_t remoteWakeCountdown; // When wake is requested + +//--------------------------------------------------------------------+ +// Prototypes +//--------------------------------------------------------------------+ + +// into the stack. +static void handle_bus_reset(uint8_t rhport); +static void dcd_transmit_packet(xfer_ctl_t *xfer, uint16_t ep_ix); +static bool edpt_xfer(uint8_t rhport, uint8_t ep_num, tusb_dir_t dir); + +// PMA allocation/access +static uint16_t ep_buf_ptr; ///< Points to first free memory location +static uint32_t dcd_pma_alloc(uint16_t len, bool dbuf); +static uint8_t dcd_ep_alloc(uint8_t ep_addr, uint8_t ep_type); + +static void edpt0_open(uint8_t rhport); + +TU_ATTR_ALWAYS_INLINE static inline void edpt0_prepare_setup(void) { + btable_set_rx_bufsize(0, BTABLE_BUF_RX, 8); +} + +//--------------------------------------------------------------------+ +// Inline helper +//--------------------------------------------------------------------+ + +TU_ATTR_ALWAYS_INLINE static inline xfer_ctl_t *xfer_ctl_ptr(uint8_t epnum, uint8_t dir) { + return &xfer_status[epnum][dir]; +} + +//--------------------------------------------------------------------+ +// Controller API +//--------------------------------------------------------------------+ +bool dcd_init(uint8_t rhport, const tusb_rhport_init_t *rh_init) { + (void)rh_init; + + fsdev_core_reset(); + + FSDEV_REG->CNTR = 0; // Enable USB + + #if !defined( CFG_TUSB_FSDEV_32BIT) + // BTABLE register does not exist any more on 32-bit bus devices + FSDEV_REG->BTABLE = FSDEV_BTABLE_BASE; + #endif + + // Enable interrupts for device mode + FSDEV_REG->CNTR |= + U_CNTR_RESETM | U_CNTR_ESOFM | U_CNTR_CTRM | U_CNTR_SUSPM | U_CNTR_WKUPM | U_CNTR_PMAOVRM; + + handle_bus_reset(rhport); + + // Enable pull-up if supported + dcd_connect(rhport); + + return true; +} + +bool dcd_deinit(uint8_t rhport) { + (void)rhport; + + fsdev_deinit(); + + return true; +} + +void dcd_sof_enable(uint8_t rhport, bool en) { + (void)rhport; + + if (en) { + FSDEV_REG->CNTR |= U_CNTR_SOFM; + } else { + FSDEV_REG->CNTR &= ~U_CNTR_SOFM; + } +} + +// Receive Set Address request, mcu port must also include status IN response +void dcd_set_address(uint8_t rhport, uint8_t dev_addr) { + (void)dev_addr; + + // Respond with status + dcd_edpt_xfer(rhport, TUSB_DIR_IN_MASK | 0x00, NULL, 0, false); + + // DCD can only set address after status for this request is complete. + // do it at dcd_edpt0_status_complete() +} + +void dcd_remote_wakeup(uint8_t rhport) { + (void)rhport; + + FSDEV_REG->CNTR |= U_CNTR_RESUME; + remoteWakeCountdown = 4u; // required to be 1 to 15 ms, ESOF should trigger every 1ms. +} + +static void handle_bus_reset(uint8_t rhport) { + FSDEV_REG->DADDR = 0u; // disable USB Function + + for (uint32_t i = 0; i < FSDEV_EP_COUNT; i++) { + // Clear EP allocation status + ep_alloc_status[i].ep_num = 0xFF; + ep_alloc_status[i].ep_type = 0xFF; + ep_alloc_status[i].allocated[0] = false; + ep_alloc_status[i].allocated[1] = false; + } + + // Reset PMA allocation + ep_buf_ptr = FSDEV_BTABLE_BASE + 8 * FSDEV_EP_COUNT; + + edpt0_open(rhport); // open control endpoint (both IN & OUT) + + FSDEV_REG->DADDR = U_DADDR_EF; // Enable USB Function +} + +// Handle CTR interrupt for the TX/IN direction +static void handle_ctr_tx(uint32_t ep_id) { + uint32_t ep_reg = ep_read(ep_id) | U_EP_CTR_TX | U_EP_CTR_RX; + + const uint8_t ep_num = ep_reg & U_EPADDR_FIELD; + xfer_ctl_t *xfer = xfer_ctl_ptr(ep_num, TUSB_DIR_IN); + + if (ep_is_iso(ep_reg)) { + // Ignore spurious interrupts that we don't schedule + // host can send IN token while there is no data to send, since ISO does not have NAK + // this will result to zero length packet --> trigger interrupt (which cannot be masked) + if (!xfer->iso_in_sending) { + return; + } + xfer->iso_in_sending = false; + #if FSDEV_USE_SBUF_ISO == 0 + uint8_t buf_id = (ep_reg & U_EP_DTOG_TX) ? 0 : 1; + #else + uint8_t buf_id = BTABLE_BUF_TX; + #endif + btable_set_count(ep_id, buf_id, 0); + } + + if (xfer->total_len != xfer->queued_len) { + dcd_transmit_packet(xfer, (uint16_t)ep_id); + } else { + dcd_event_xfer_complete(0, ep_num | TUSB_DIR_IN_MASK, xfer->queued_len, XFER_RESULT_SUCCESS, true); + } +} + +static void handle_ctr_setup(uint32_t ep_id) { + uint16_t rx_count = btable_get_count(ep_id, BTABLE_BUF_RX); + uint16_t rx_addr = (uint16_t)btable_get_addr(ep_id, BTABLE_BUF_RX); + uint8_t setup_packet[8] TU_ATTR_ALIGNED(4); + + tu_hwfifo_read(PMA_BUF_AT(rx_addr), setup_packet, rx_count, NULL); + + // Clear CTR RX if another setup packet arrived before this, it will be discarded + ep_write_clear_ctr(ep_id, TUSB_DIR_OUT); + + // Setup packet should always be 8 bytes. If not, we probably missed the packet + if (rx_count == 8) { + dcd_event_setup_received(0, (uint8_t *)setup_packet, true); + // Hardware should reset EP0 RX/TX to NAK and both toggle to 1 + } else { + // Missed setup packet !!! + TU_BREAKPOINT(); + edpt0_prepare_setup(); + } +} + +// Handle CTR interrupt for the RX/OUT direction +static void handle_ctr_rx(uint32_t ep_id) { + uint32_t ep_reg = ep_read(ep_id) | U_EP_CTR_TX | U_EP_CTR_RX; + const uint8_t ep_num = ep_reg & U_EPADDR_FIELD; + const bool is_iso = ep_is_iso(ep_reg); + xfer_ctl_t *xfer = xfer_ctl_ptr(ep_num, TUSB_DIR_OUT); + + uint8_t buf_id; + #if FSDEV_USE_SBUF_ISO == 0 + bool const dbl_buf = is_iso; + #else + bool const dbl_buf = false; + #endif + if (dbl_buf) { + buf_id = (ep_reg & U_EP_DTOG_RX) ? 0 : 1; + } else { + buf_id = BTABLE_BUF_RX; + } + const uint16_t rx_count = btable_get_count(ep_id, buf_id); + uint16_t pma_addr = (uint16_t)btable_get_addr(ep_id, buf_id); + fsdev_pma_buf_t *pma_buf = PMA_BUF_AT(pma_addr); + + if (xfer->ff) { + tu_hwfifo_read_to_fifo(pma_buf, xfer->ff, rx_count, NULL); + } else { + tu_hwfifo_read(pma_buf, xfer->buffer + xfer->queued_len, rx_count, NULL); + } + xfer->queued_len += rx_count; + + if ((rx_count < xfer->max_packet_size) || (xfer->queued_len >= xfer->total_len)) { + // all bytes received or short packet + + // For ch32v203: reset rx bufsize to mps to prevent race condition to cause PMAOVR (occurs with msc write10) + btable_set_rx_bufsize(ep_id, BTABLE_BUF_RX, xfer->max_packet_size); + + dcd_event_xfer_complete(0, ep_num, xfer->queued_len, XFER_RESULT_SUCCESS, true); + + // ch32 seems to unconditionally accept ZLP on EP0 OUT, which can incorrectly use queued_len of previous + // transfer. So reset total_len and queued_len to 0. + xfer->total_len = xfer->queued_len = 0; + } else { + // Set endpoint active again for receiving more data. Note that isochronous endpoints stay active always + if (!is_iso) { + const uint16_t cnt = tu_min16(xfer->total_len - xfer->queued_len, xfer->max_packet_size); + btable_set_rx_bufsize(ep_id, BTABLE_BUF_RX, cnt); + } + ep_reg &= U_EPREG_MASK | EP_STAT_MASK(TUSB_DIR_OUT); // will change RX Status, reserved other toggle bits + ep_change_status(&ep_reg, TUSB_DIR_OUT, EP_STAT_VALID); + ep_write(ep_id, ep_reg, false); + } +} + +void dcd_int_handler(uint8_t rhport) { + uint32_t int_status = FSDEV_REG->ISTR; + + /* Put SOF flag at the beginning of ISR in case to get least amount of jitter if it is used for timing purposes */ + if (int_status & U_ISTR_SOF) { + FSDEV_REG->ISTR = (fsdev_bus_t)~U_ISTR_SOF; + dcd_event_sof(0, FSDEV_REG->FNR & U_FNR_FN, true); + } + + if (int_status & U_ISTR_RESET) { + // USBRST is start of reset. + FSDEV_REG->ISTR = (fsdev_bus_t)~U_ISTR_RESET; + handle_bus_reset(rhport); + dcd_event_bus_reset(0, TUSB_SPEED_FULL, true); + return; // Don't do the rest of the things here; perhaps they've been cleared? + } + + if (int_status & U_ISTR_WKUP) { + FSDEV_REG->CNTR &= ~U_CNTR_LPMODE; + FSDEV_REG->CNTR &= ~U_CNTR_FSUSP; + + FSDEV_REG->ISTR = (fsdev_bus_t)~U_ISTR_WKUP; + dcd_event_bus_signal(0, DCD_EVENT_RESUME, true); + } + + if (int_status & U_ISTR_SUSP) { + /* Suspend is asserted for both suspend and unplug events. without Vbus monitoring, + * these events cannot be differentiated, so we only trigger suspend. */ + + /* Force low-power mode in the macrocell */ + FSDEV_REG->CNTR |= U_CNTR_FSUSP; + FSDEV_REG->CNTR |= U_CNTR_LPMODE; + + /* clear of the ISTR bit must be done after setting of CNTR_FSUSP */ + FSDEV_REG->ISTR = (fsdev_bus_t)~U_ISTR_SUSP; + dcd_event_bus_signal(0, DCD_EVENT_SUSPEND, true); + } + + if (int_status & U_ISTR_ESOF) { + if (remoteWakeCountdown == 1u) { + FSDEV_REG->CNTR &= ~U_CNTR_RESUME; + } + if (remoteWakeCountdown > 0u) { + remoteWakeCountdown--; + } + FSDEV_REG->ISTR = (fsdev_bus_t)~U_ISTR_ESOF; + } + + // loop to handle all pending CTR interrupts + while (FSDEV_REG->ISTR & U_ISTR_CTR) { + // skip DIR bit, and use CTR TX/RX instead, since there is chance we have both TX/RX completed in one interrupt + const uint32_t ep_id = FSDEV_REG->ISTR & U_ISTR_EP_ID; + const uint32_t ep_reg = ep_read(ep_id); + + if (ep_reg & U_EP_CTR_RX) { + #if defined(TUP_USBIP_FSDEV_STM32) && defined(CFG_TUSB_FSDEV_32BIT) + fsdev_btable_workaround_delay(false); + #endif + + if (ep_reg & U_EP_SETUP) { + handle_ctr_setup(ep_id); // CTR will be clear after copied setup packet + } else { + ep_write_clear_ctr(ep_id, TUSB_DIR_OUT); + handle_ctr_rx(ep_id); + } + } + + if (ep_reg & U_EP_CTR_TX) { + ep_write_clear_ctr(ep_id, TUSB_DIR_IN); + handle_ctr_tx(ep_id); + } + } + + if (int_status & U_ISTR_PMAOVR) { + TU_BREAKPOINT(); + FSDEV_REG->ISTR = (fsdev_bus_t)~U_ISTR_PMAOVR; + } +} + +//--------------------------------------------------------------------+ +// Endpoint API +//--------------------------------------------------------------------+ + +// Invoked when a control transfer's status stage is complete. +// May help DCD to prepare for next control transfer, this API is optional. +void dcd_edpt0_status_complete(uint8_t rhport, const tusb_control_request_t *request) { + (void)rhport; + + if (request->bmRequestType_bit.recipient == TUSB_REQ_RCPT_DEVICE && + request->bmRequestType_bit.type == TUSB_REQ_TYPE_STANDARD && request->bRequest == TUSB_REQ_SET_ADDRESS) { + const uint8_t dev_addr = (uint8_t)request->wValue; + FSDEV_REG->DADDR = (U_DADDR_EF | dev_addr); + } + + edpt0_prepare_setup(); +} + +/*** + * Allocate a section of PMA + * In case of double buffering, high 16bit is the address of 2nd buffer + * During failure, TU_ASSERT is used. If this happens, rework/reallocate memory manually. + */ +static uint32_t dcd_pma_alloc(uint16_t len, bool dbuf) { + uint8_t blsize, num_block; + uint16_t aligned_len = pma_align_buffer_size(len, &blsize, &num_block); + (void)blsize; + (void)num_block; + + uint32_t addr = ep_buf_ptr; + ep_buf_ptr = (uint16_t)(ep_buf_ptr + aligned_len); // increment buffer pointer + + if (dbuf) { + addr |= ((uint32_t)ep_buf_ptr) << 16; + ep_buf_ptr = (uint16_t)(ep_buf_ptr + aligned_len); // increment buffer pointer + } + + // Verify packet buffer is not overflowed + TU_ASSERT(ep_buf_ptr <= CFG_TUSB_FSDEV_PMA_SIZE, 0xFFFF); + + return addr; +} + +/*** + * Allocate hardware endpoint + */ +static uint8_t dcd_ep_alloc(uint8_t ep_addr, uint8_t ep_type) { + const uint8_t epnum = tu_edpt_number(ep_addr); + const uint8_t dir = tu_edpt_dir(ep_addr); + + for (uint8_t i = 0; i < FSDEV_EP_COUNT; i++) { + // Check if already allocated + if (ep_alloc_status[i].allocated[dir] && ep_alloc_status[i].ep_type == ep_type && + ep_alloc_status[i].ep_num == epnum) { + return i; + } + + #if FSDEV_USE_SBUF_ISO == 0 + bool const dbl_buf = ep_type == TUSB_XFER_ISOCHRONOUS; + #else + bool const dbl_buf = false; + #endif + + // If EP of current direction is not allocated + // For double-buffered mode both directions needs to be free + if (!ep_alloc_status[i].allocated[dir] && (!dbl_buf || !ep_alloc_status[i].allocated[dir ^ 1])) { + // Check if EP number is the same + if (ep_alloc_status[i].ep_num == 0xFF || ep_alloc_status[i].ep_num == epnum) { + // One EP pair has to be the same type + if (ep_alloc_status[i].ep_type == 0xFF || ep_alloc_status[i].ep_type == ep_type) { + ep_alloc_status[i].ep_num = epnum; + ep_alloc_status[i].ep_type = ep_type; + ep_alloc_status[i].allocated[dir] = true; + + return i; + } + } + } + } + + // Allocation failed + TU_ASSERT(0); +} + +void edpt0_open(uint8_t rhport) { + (void)rhport; + + dcd_ep_alloc(0x0, TUSB_XFER_CONTROL); + dcd_ep_alloc(0x80, TUSB_XFER_CONTROL); + + xfer_status[0][0].max_packet_size = CFG_TUD_ENDPOINT0_SIZE; + xfer_status[0][0].ep_idx = 0; + + xfer_status[0][1].max_packet_size = CFG_TUD_ENDPOINT0_SIZE; + xfer_status[0][1].ep_idx = 0; + + uint16_t pma_addr0 = (uint16_t)dcd_pma_alloc(CFG_TUD_ENDPOINT0_SIZE, false); + uint16_t pma_addr1 = (uint16_t)dcd_pma_alloc(CFG_TUD_ENDPOINT0_SIZE, false); + + btable_set_addr(0, BTABLE_BUF_RX, pma_addr0); + btable_set_addr(0, BTABLE_BUF_TX, pma_addr1); + + uint32_t ep_reg = ep_read(0) & ~U_EPREG_MASK; // only get toggle bits + ep_reg |= U_EP_CONTROL; + ep_change_status(&ep_reg, TUSB_DIR_IN, EP_STAT_NAK); + ep_change_status(&ep_reg, TUSB_DIR_OUT, EP_STAT_NAK); + // no need to explicitly set DTOG bits since we aren't masked DTOG bit + + edpt0_prepare_setup(); // prepare for setup packet + ep_write(0, ep_reg, false); +} + +bool dcd_edpt_open(uint8_t rhport, const tusb_desc_endpoint_t *desc_ep) { + (void)rhport; + const uint8_t ep_addr = desc_ep->bEndpointAddress; + const uint8_t ep_num = tu_edpt_number(ep_addr); + const tusb_dir_t dir = tu_edpt_dir(ep_addr); + const uint16_t packet_size = tu_edpt_packet_size(desc_ep); + const uint8_t ep_idx = dcd_ep_alloc(ep_addr, desc_ep->bmAttributes.xfer); + TU_ASSERT(ep_idx < FSDEV_EP_COUNT); + + uint32_t ep_reg = ep_read(ep_idx) & ~U_EPREG_MASK; + ep_reg |= tu_edpt_number(ep_addr) | U_EP_CTR_TX | U_EP_CTR_RX; + + // Set type + switch (desc_ep->bmAttributes.xfer) { + case TUSB_XFER_BULK: + ep_reg |= U_EP_BULK; + break; + case TUSB_XFER_INTERRUPT: + ep_reg |= U_EP_INTERRUPT; + break; + + default: + // Note: ISO endpoint should use alloc / active functions + TU_ASSERT(false); + } + + /* Create a packet memory buffer area. */ + uint16_t pma_addr = (uint16_t)dcd_pma_alloc(packet_size, false); + btable_set_addr(ep_idx, dir == TUSB_DIR_IN ? BTABLE_BUF_TX : BTABLE_BUF_RX, pma_addr); + + xfer_ctl_t *xfer = xfer_ctl_ptr(ep_num, dir); + xfer->max_packet_size = packet_size; + xfer->ep_idx = ep_idx; + + ep_change_status(&ep_reg, dir, EP_STAT_NAK); + ep_change_dtog(&ep_reg, dir, 0); + + // reserve other direction toggle bits + if (dir == TUSB_DIR_IN) { + ep_reg &= ~(U_EPRX_STAT | U_EP_DTOG_RX); + } else { + ep_reg &= ~(U_EPTX_STAT | U_EP_DTOG_TX); + } + + ep_write(ep_idx, ep_reg, true); + + return true; +} + +void dcd_edpt_close_all(uint8_t rhport) { + dcd_int_disable(rhport); + + for (uint32_t i = 1; i < FSDEV_EP_COUNT; i++) { + // Reset endpoint + ep_write(i, 0, false); + // Clear EP allocation status + ep_alloc_status[i].ep_num = 0xFF; + ep_alloc_status[i].ep_type = 0xFF; + ep_alloc_status[i].allocated[0] = false; + ep_alloc_status[i].allocated[1] = false; + } + + dcd_int_enable(rhport); + + // Reset PMA allocation + ep_buf_ptr = FSDEV_BTABLE_BASE + 8 * FSDEV_EP_COUNT + 2 * CFG_TUD_ENDPOINT0_SIZE; +} + +bool dcd_edpt_iso_alloc(uint8_t rhport, uint8_t ep_addr, uint16_t largest_packet_size) { + (void)rhport; + + const uint8_t ep_num = tu_edpt_number(ep_addr); + const uint8_t dir = tu_edpt_dir(ep_addr); + const uint8_t ep_idx = dcd_ep_alloc(ep_addr, TUSB_XFER_ISOCHRONOUS); + + #if CFG_TUD_FSDEV_DOUBLE_BUFFERED_ISO_EP != 0 + uint32_t pma_addr = dcd_pma_alloc(largest_packet_size, true); + uint16_t pma_addr2 = (uint16_t)(pma_addr >> 16); + #else + uint32_t pma_addr = dcd_pma_alloc(largest_packet_size, false); + uint16_t pma_addr2 = (uint16_t)pma_addr; + #endif + + #if FSDEV_USE_SBUF_ISO == 0 + btable_set_addr(ep_idx, 0, (uint16_t)pma_addr); + btable_set_addr(ep_idx, 1, pma_addr2); + #else + btable_set_addr(ep_idx, dir == TUSB_DIR_IN ? BTABLE_BUF_TX : BTABLE_BUF_RX, (uint16_t)pma_addr); + (void)pma_addr2; + #endif + + xfer_ctl_t *xfer = xfer_ctl_ptr(ep_num, dir); + xfer->ep_idx = ep_idx; + + return true; +} + +bool dcd_edpt_iso_activate(uint8_t rhport, const tusb_desc_endpoint_t *desc_ep) { + (void)rhport; + const uint8_t ep_addr = desc_ep->bEndpointAddress; + const uint8_t ep_num = tu_edpt_number(ep_addr); + const tusb_dir_t dir = tu_edpt_dir(ep_addr); + xfer_ctl_t *xfer = xfer_ctl_ptr(ep_num, dir); + + const uint8_t ep_idx = xfer->ep_idx; + + xfer->max_packet_size = tu_edpt_packet_size(desc_ep); + + uint32_t ep_reg = ep_read(ep_idx) & ~U_EPREG_MASK; + ep_reg |= tu_edpt_number(ep_addr) | U_EP_ISOCHRONOUS | U_EP_CTR_TX | U_EP_CTR_RX; + #if FSDEV_USE_SBUF_ISO != 0 + ep_reg |= U_EP_KIND; + + ep_change_status(&ep_reg, dir, EP_STAT_DISABLED); + ep_change_dtog(&ep_reg, dir, 0); + + if (dir == TUSB_DIR_IN) { + ep_reg &= ~(U_EPRX_STAT | U_EP_DTOG_RX); + } else { + ep_reg &= ~(U_EPTX_STAT | U_EP_DTOG_TX); + } + #else + ep_change_status(&ep_reg, TUSB_DIR_IN, EP_STAT_DISABLED); + ep_change_status(&ep_reg, TUSB_DIR_OUT, EP_STAT_DISABLED); + ep_change_dtog(&ep_reg, dir, 0); + ep_change_dtog(&ep_reg, (tusb_dir_t)(1 - dir), 1); + #endif + + ep_write(ep_idx, ep_reg, true); + + return true; +} + +// Currently, single-buffered, and only 64 bytes at a time (max) +static void dcd_transmit_packet(xfer_ctl_t *xfer, uint16_t ep_ix) { + uint16_t len = tu_min16(xfer->total_len - xfer->queued_len, xfer->max_packet_size); + uint32_t ep_reg = ep_read(ep_ix) | U_EP_CTR_TX | U_EP_CTR_RX; // reserve CTR + + const bool is_iso = ep_is_iso(ep_reg); + + uint8_t buf_id; + #if FSDEV_USE_SBUF_ISO == 0 + bool const dbl_buf = is_iso; + #else + bool const dbl_buf = false; + #endif + if (dbl_buf) { + buf_id = (ep_reg & U_EP_DTOG_TX) ? 1 : 0; + } else { + buf_id = BTABLE_BUF_TX; + } + uint16_t addr_ptr = (uint16_t)btable_get_addr(ep_ix, buf_id); + fsdev_pma_buf_t *pma_buf = PMA_BUF_AT(addr_ptr); + + if (xfer->ff) { + tu_hwfifo_write_from_fifo(pma_buf, xfer->ff, len, NULL); + } else { + tu_hwfifo_write(pma_buf, &(xfer->buffer[xfer->queued_len]), len, NULL); + } + xfer->queued_len += len; + + btable_set_count(ep_ix, buf_id, len); + ep_change_status(&ep_reg, TUSB_DIR_IN, EP_STAT_VALID); + + if (is_iso) { + xfer->iso_in_sending = true; + } + ep_reg &= U_EPREG_MASK | EP_STAT_MASK(TUSB_DIR_IN); // only change TX Status, reserve other toggle bits + ep_write(ep_ix, ep_reg, true); +} + +static bool edpt_xfer(uint8_t rhport, uint8_t ep_num, tusb_dir_t dir) { + (void)rhport; + + xfer_ctl_t *xfer = xfer_ctl_ptr(ep_num, dir); + const uint8_t ep_idx = xfer->ep_idx; + + if (dir == TUSB_DIR_IN) { + dcd_transmit_packet(xfer, ep_idx); + } else { + uint32_t ep_reg = ep_read(ep_idx) | U_EP_CTR_TX | U_EP_CTR_RX; // reserve CTR + ep_reg &= U_EPREG_MASK | EP_STAT_MASK(dir); + + uint16_t cnt = tu_min16(xfer->total_len, xfer->max_packet_size); + + #if FSDEV_USE_SBUF_ISO == 0 + bool const dbl_buf = ep_is_iso(ep_reg); + #else + bool const dbl_buf = false; + #endif + if (dbl_buf) { + btable_set_rx_bufsize(ep_idx, 0, cnt); + btable_set_rx_bufsize(ep_idx, 1, cnt); + } else { + btable_set_rx_bufsize(ep_idx, BTABLE_BUF_RX, cnt); + } + + ep_change_status(&ep_reg, dir, EP_STAT_VALID); + ep_write(ep_idx, ep_reg, true); + } + + return true; +} + +bool dcd_edpt_xfer(uint8_t rhport, uint8_t ep_addr, uint8_t *buffer, uint16_t total_bytes, bool is_isr) { + (void)is_isr; + const uint8_t ep_num = tu_edpt_number(ep_addr); + const tusb_dir_t dir = tu_edpt_dir(ep_addr); + xfer_ctl_t *xfer = xfer_ctl_ptr(ep_num, dir); + + xfer->buffer = buffer; + xfer->ff = NULL; + xfer->total_len = total_bytes; + xfer->queued_len = 0; + + return edpt_xfer(rhport, ep_num, dir); +} + +bool dcd_edpt_xfer_fifo(uint8_t rhport, uint8_t ep_addr, tu_fifo_t *ff, uint16_t total_bytes, bool is_isr) { + (void)is_isr; + const uint8_t ep_num = tu_edpt_number(ep_addr); + const tusb_dir_t dir = tu_edpt_dir(ep_addr); + xfer_ctl_t *xfer = xfer_ctl_ptr(ep_num, dir); + + xfer->buffer = NULL; + xfer->ff = ff; + xfer->total_len = total_bytes; + xfer->queued_len = 0; + + return edpt_xfer(rhport, ep_num, dir); +} + +void dcd_edpt_stall(uint8_t rhport, uint8_t ep_addr) { + (void)rhport; + const uint8_t ep_num = tu_edpt_number(ep_addr); + const tusb_dir_t dir = tu_edpt_dir(ep_addr); + xfer_ctl_t *xfer = xfer_ctl_ptr(ep_num, dir); + const uint8_t ep_idx = xfer->ep_idx; + + uint32_t ep_reg = ep_read(ep_idx) | U_EP_CTR_TX | U_EP_CTR_RX; // reserve CTR bits + ep_reg &= U_EPREG_MASK | EP_STAT_MASK(dir); + ep_change_status(&ep_reg, dir, EP_STAT_STALL); + + ep_write(ep_idx, ep_reg, true); +} + +void dcd_edpt_clear_stall(uint8_t rhport, uint8_t ep_addr) { + (void)rhport; + + const uint8_t ep_num = tu_edpt_number(ep_addr); + const tusb_dir_t dir = tu_edpt_dir(ep_addr); + xfer_ctl_t *xfer = xfer_ctl_ptr(ep_num, dir); + const uint8_t ep_idx = xfer->ep_idx; + + uint32_t ep_reg = ep_read(ep_idx) | U_EP_CTR_TX | U_EP_CTR_RX; // reserve CTR bits + ep_reg &= U_EPREG_MASK | EP_STAT_MASK(dir) | EP_DTOG_MASK(dir); + + if (!ep_is_iso(ep_reg)) { + ep_change_status(&ep_reg, dir, EP_STAT_NAK); + } + ep_change_dtog(&ep_reg, dir, 0); // Reset to DATA0 + ep_write(ep_idx, ep_reg, true); +} + +void dcd_int_enable(uint8_t rhport) { + fsdev_int_enable(rhport); +} + +void dcd_int_disable(uint8_t rhport) { + fsdev_int_disable(rhport); +} + + #if defined(USB_BCDR_DPPU) || defined(SYSCFG_PMC_USB_PU) || defined(EXTEN_USBD_PU_EN) +void dcd_connect(uint8_t rhport) { + fsdev_connect(rhport); +} + +void dcd_disconnect(uint8_t rhport) { + fsdev_disconnect(rhport); +} + #endif + +#endif diff --git a/libraries/Adafruit_TinyUSB_Arduino/src/portable/st/stm32_fsdev/fsdev_at32.h b/libraries/Adafruit_TinyUSB_Arduino/src/portable/st/stm32_fsdev/fsdev_at32.h new file mode 100644 index 0000000..1078843 --- /dev/null +++ b/libraries/Adafruit_TinyUSB_Arduino/src/portable/st/stm32_fsdev/fsdev_at32.h @@ -0,0 +1,116 @@ +/* +* The MIT License (MIT) + * + * Copyright (c) 2024, hathach (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_FSDEV_AT32_H +#define TUSB_FSDEV_AT32_H + +#include "common/tusb_compiler.h" + +#if CFG_TUSB_MCU == OPT_MCU_AT32F403A_407 + #include "at32f403a_407.h" + +#elif CFG_TUSB_MCU == OPT_MCU_AT32F413 + #include "at32f413.h" + +#endif + +#define FSDEV_USE_SBUF_ISO 0 +#define FSDEV_REG_BASE (APB1PERIPH_BASE + 0x00005C00UL) +#define FSDEV_PMA_BASE (APB1PERIPH_BASE + 0x00006000UL) + +#ifndef CFG_TUD_FSDEV_DOUBLE_BUFFERED_ISO_EP + #define CFG_TUD_FSDEV_DOUBLE_BUFFERED_ISO_EP 0 +#endif + +//--------------------------------------------------------------------+ +// +//--------------------------------------------------------------------+ + +#if (CFG_TUSB_MCU == OPT_MCU_AT32F403A_407) || (CFG_TUSB_MCU == OPT_MCU_AT32F413) +static const IRQn_Type fsdev_irq[] = { + USBFS_H_CAN1_TX_IRQn, + USBFS_L_CAN1_RX0_IRQn, + USBFSWakeUp_IRQn +}; +enum { FSDEV_IRQ_NUM = TU_ARRAY_SIZE(fsdev_irq) }; + +#else + #error "Unsupported MCU" +#endif + +TU_ATTR_ALWAYS_INLINE static inline void fsdev_int_enable(uint8_t rhport) { + (void)rhport; + #if (CFG_TUSB_MCU == OPT_MCU_AT32F403A_407) || (CFG_TUSB_MCU == OPT_MCU_AT32F413) + // AT32F403A/407 devices allow to remap the USB interrupt vectors from + // shared USB/CAN IRQs to separate CAN and USB IRQs. + // This dynamically checks if this remap is active to enable the right IRQs. + if (CRM->intmap_bit.usbintmap) { + NVIC_EnableIRQ(USBFS_MAPH_IRQn); + NVIC_EnableIRQ(USBFS_MAPL_IRQn); + NVIC_EnableIRQ(USBFSWakeUp_IRQn); + } else + #endif + { + for(uint8_t i=0; i < FSDEV_IRQ_NUM; i++) { + NVIC_EnableIRQ(fsdev_irq[i]); + } + } +} + +TU_ATTR_ALWAYS_INLINE static inline void fsdev_int_disable(uint8_t rhport) { + (void)rhport; + #if (CFG_TUSB_MCU == OPT_MCU_AT32F403A_407) || (CFG_TUSB_MCU == OPT_MCU_AT32F413) + // AT32F403A/407 devices allow to remap the USB interrupt vectors from + // shared USB/CAN IRQs to separate CAN and USB IRQs. + // This dynamically checks if this remap is active to enable the right IRQs. + if (CRM->intmap_bit.usbintmap) { + NVIC_DisableIRQ(USBFS_MAPH_IRQn); + NVIC_DisableIRQ(USBFS_MAPL_IRQn); + NVIC_DisableIRQ(USBFSWakeUp_IRQn); + } else + #endif + { + for(uint8_t i=0; i < FSDEV_IRQ_NUM; i++) { + NVIC_DisableIRQ(fsdev_irq[i]); + } + } +} + +TU_ATTR_ALWAYS_INLINE static inline void fsdev_disconnect(uint8_t rhport) { + (void) rhport; + /* disable usb phy */ + *(volatile uint32_t*)(FSDEV_REG_BASE + 0x40) |= U_CNTR_PDWN; + /* D+ 1.5k pull-up disable, USB->cfg_bit.puo = TRUE; */ + *(volatile uint32_t *)(FSDEV_REG_BASE+0x60) |= (1u<<1); +} + +TU_ATTR_ALWAYS_INLINE static inline void fsdev_connect(uint8_t rhport) { + (void) rhport; + /* enable usb phy */ + *(volatile uint32_t*)(FSDEV_REG_BASE + 0x40) &= ~U_CNTR_PDWN; + /* Dp 1.5k pull-up enable, USB->cfg_bit.puo = 0; */ + *(volatile uint32_t *)(FSDEV_REG_BASE+0x60) &= ~(1u<<1); +} + +#endif diff --git a/libraries/Adafruit_TinyUSB_Arduino/src/portable/st/stm32_fsdev/fsdev_ch32.h b/libraries/Adafruit_TinyUSB_Arduino/src/portable/st/stm32_fsdev/fsdev_ch32.h new file mode 100644 index 0000000..b92bf3f --- /dev/null +++ b/libraries/Adafruit_TinyUSB_Arduino/src/portable/st/stm32_fsdev/fsdev_ch32.h @@ -0,0 +1,103 @@ +/* +* The MIT License (MIT) + * + * Copyright (c) 2024, hathach (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. + * + */ +/**

© Copyright (c) 2016 STMicroelectronics. + * All rights reserved.

+ * + * This software component is licensed by ST under BSD 3-Clause license, + * the "License"; You may not use this file except in compliance with the + * License. You may obtain a copy of the License at: + * opensource.org/licenses/BSD-3-Clause + */ + +#ifndef TUSB_FSDEV_CH32_H +#define TUSB_FSDEV_CH32_H + +#include "common/tusb_compiler.h" + +// https://github.com/openwch/ch32v307/pull/90 +// https://github.com/openwch/ch32v20x/pull/12 +#ifdef __GNUC__ +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wstrict-prototypes" +#endif + +#if CFG_TUSB_MCU == OPT_MCU_CH32F20X + #include +#elif CFG_TUSB_MCU == OPT_MCU_CH32V20X + #include +#endif + +#ifdef __GNUC__ +#pragma GCC diagnostic pop +#endif + +#define FSDEV_USE_SBUF_ISO 0 +#define FSDEV_REG_BASE (APB1PERIPH_BASE + 0x00005C00UL) +#define FSDEV_PMA_BASE (APB1PERIPH_BASE + 0x00006000UL) + +#ifndef CFG_TUD_FSDEV_DOUBLE_BUFFERED_ISO_EP + #define CFG_TUD_FSDEV_DOUBLE_BUFFERED_ISO_EP 0 +#endif + +//--------------------------------------------------------------------+ +// +//--------------------------------------------------------------------+ + +#if CFG_TUSB_MCU == OPT_MCU_CH32V20X +static const IRQn_Type fsdev_irq[] = { + USB_HP_CAN1_TX_IRQn, + USB_LP_CAN1_RX0_IRQn, + USBWakeUp_IRQn +}; +enum { FSDEV_IRQ_NUM = TU_ARRAY_SIZE(fsdev_irq) }; +#else + #error "Unsupported MCU" +#endif + +TU_ATTR_ALWAYS_INLINE static inline void fsdev_int_enable(uint8_t rhport) { + (void)rhport; + for(uint8_t i=0; i < FSDEV_IRQ_NUM; i++) { + NVIC_EnableIRQ(fsdev_irq[i]); + } +} + +TU_ATTR_ALWAYS_INLINE static inline void fsdev_int_disable(uint8_t rhport) { + (void)rhport; + for(uint8_t i=0; i < FSDEV_IRQ_NUM; i++) { + NVIC_DisableIRQ(fsdev_irq[i]); + } +} + +TU_ATTR_ALWAYS_INLINE static inline void fsdev_disconnect(uint8_t rhport) { + (void) rhport; + EXTEN->EXTEN_CTR &= ~EXTEN_USBD_PU_EN; +} + +TU_ATTR_ALWAYS_INLINE static inline void fsdev_connect(uint8_t rhport) { + (void) rhport; + EXTEN->EXTEN_CTR |= EXTEN_USBD_PU_EN; +} + +#endif diff --git a/libraries/Adafruit_TinyUSB_Arduino/src/portable/st/stm32_fsdev/fsdev_common.c b/libraries/Adafruit_TinyUSB_Arduino/src/portable/st/stm32_fsdev/fsdev_common.c new file mode 100644 index 0000000..7c4572a --- /dev/null +++ b/libraries/Adafruit_TinyUSB_Arduino/src/portable/st/stm32_fsdev/fsdev_common.c @@ -0,0 +1,116 @@ +/* + * The MIT License (MIT) + * + * Copyright (c) 2024 Ha Thach (tinyusb.org) + * Copyright (c) 2025, HiFiPhile (Zixun LI) + * + * 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. + */ + +#include "tusb_option.h" + +#if defined(TUP_USBIP_FSDEV) && (CFG_TUH_ENABLED || CFG_TUD_ENABLED) + +#include "fsdev_common.h" + +//--------------------------------------------------------------------+ +// Global +//--------------------------------------------------------------------+ + +// Reset the USB Core +void fsdev_core_reset(void) { + // Perform USB peripheral reset + FSDEV_REG->CNTR = U_CNTR_FRES | U_CNTR_PDWN; + for (volatile uint32_t i = 0; i < 200; i++) { // should be a few us + asm("NOP"); + } + + FSDEV_REG->CNTR &= ~U_CNTR_PDWN; + + // Wait startup time, for F042 and F070, this is <= 1 us. + for (volatile uint32_t i = 0; i < 200; i++) { // should be a few us + asm("NOP"); + } + + // Clear pending interrupts + FSDEV_REG->ISTR = 0; +} + +// De-initialize the USB Core +void fsdev_deinit(void) { + // Disable all interrupts and force USB reset + FSDEV_REG->CNTR = U_CNTR_FRES; + + // Clear pending interrupts + FSDEV_REG->ISTR = 0; + + // Put USB peripheral in power down mode + FSDEV_REG->CNTR = U_CNTR_FRES | U_CNTR_PDWN; + for (volatile uint32_t i = 0; i < 200; i++) { // should be a few us + asm("NOP"); + } +} + +//--------------------------------------------------------------------+ +// BTable Helper +//--------------------------------------------------------------------+ + +// Aligned buffer size according to hardware +uint16_t pma_align_buffer_size(uint16_t size, uint8_t* blsize, uint8_t* num_block) { + /* The STM32 full speed USB peripheral supports only a limited set of + * buffer sizes given by the RX buffer entry format in the USB_BTABLE. */ + uint16_t block_in_bytes; + if (size > 62) { + block_in_bytes = 32; + *blsize = 1; + *num_block = (uint8_t)tu_div_ceil(size, 32); + } else { + block_in_bytes = 2; + *blsize = 0; + *num_block = (uint8_t)tu_div_ceil(size, 2); + } + + return (*num_block) * block_in_bytes; +} + +// Set RX buffer size +void btable_set_rx_bufsize(uint32_t ep_id, uint8_t buf_id, uint16_t wCount) { + uint8_t blsize, num_block; + (void) pma_align_buffer_size(wCount, &blsize, &num_block); + + /* Encode into register. When BLSIZE==1, we need to subtract 1 block count */ + uint16_t bl_nb = (blsize << 15) | ((num_block - blsize) << 10); + if (bl_nb == 0) { + // zlp but 0 is invalid value, set blsize to 1 (32 bytes) + // Note: lower value can cause PMAOVR on setup with ch32v203 + bl_nb = 1 << 15; + } + +#ifdef CFG_TUSB_FSDEV_32BIT + uint32_t count_addr = FSDEV_BTABLE->ep32[ep_id][buf_id].count_addr; + count_addr = (bl_nb << 16) | (count_addr & 0x0000FFFFu); + FSDEV_BTABLE->ep32[ep_id][buf_id].count_addr = count_addr; +#else + FSDEV_BTABLE->ep16[ep_id][buf_id].count = bl_nb; +#endif +} + +#endif diff --git a/libraries/Adafruit_TinyUSB_Arduino/src/portable/st/stm32_fsdev/fsdev_common.h b/libraries/Adafruit_TinyUSB_Arduino/src/portable/st/stm32_fsdev/fsdev_common.h new file mode 100644 index 0000000..af84b8b --- /dev/null +++ b/libraries/Adafruit_TinyUSB_Arduino/src/portable/st/stm32_fsdev/fsdev_common.h @@ -0,0 +1,456 @@ +/* + * The MIT License (MIT) + * + * Copyright (c) N Conrad + * Copyright (c) 2024, hathach (tinyusb.org) + * Copyright (c) 2025, HiFiPhile (Zixun LI) + * + * 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_FSDEV_COMMON_H +#define TUSB_FSDEV_COMMON_H + +#ifdef __cplusplus +extern "C" { +#endif + +#include "common/tusb_common.h" + +#if CFG_TUD_ENABLED + #include "device/dcd.h" +#endif + +#if CFG_TUH_ENABLED + #include "host/hcd.h" +#endif + +//--------------------------------------------------------------------+ +// FSDEV Register Bit Definitions +// Vendor-independent definitions with U_ prefix to avoid conflicts. +// Based on the common USB FSDEV IP block register layout. +// Lower 16 bits are shared across all variants (STM32, CH32, AT32). +// Upper 16 bits (DRD extensions) only exist on 32-bit DRD MCUs. +//--------------------------------------------------------------------+ + +// EPnR / CHEPnR - Endpoint/Channel Register +// DTOG and STAT bits are toggle-on-write-1. CTR bits are clear-on-write-0. +// +// 15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0 +// CTR_RX DTOG_RX STAT_RX[1:0] SETUP EP_TYPE[1:0] KIND CTR_TX DTOG_TX STAT_TX[1:0] EA[3:0] +// +// DRD 32-bit only (C0, G0, H5, U0, U5): +// 31:27 26 25 24 23 22 21 20 19 18 17 16 +// Rsvd ERR_RX ERR_TX LSEP NAK DEVADDR[6:0] +#define U_EP_CTR_RX 0x8000u +#define U_EP_DTOG_RX 0x4000u +#define U_EPRX_STAT 0x3000u +#define U_EP_SETUP 0x0800u +#define U_EP_T_FIELD 0x0600u +#define U_EP_KIND 0x0100u +#define U_EP_CTR_TX 0x0080u +#define U_EP_DTOG_TX 0x0040u +#define U_EPTX_STAT 0x0030u +#define U_EPADDR_FIELD 0x000Fu + +// DRD 32-bit upper bits +#define U_EP_ERRRX 0x04000000u +#define U_EP_ERRTX 0x02000000u +#define U_EP_LSEP 0x01000000u +#define U_EP_NAK 0x00800000u +#define U_EP_DEVADDR 0x007F0000u +#define U_EP_DEVADDR_Pos 16u + +// Endpoint types (EP_TYPE field values) +#define U_EP_BULK 0x0000u +#define U_EP_CONTROL 0x0200u +#define U_EP_ISOCHRONOUS 0x0400u +#define U_EP_INTERRUPT 0x0600u +#define U_EP_TYPE_MASK (U_EP_T_FIELD) + +// EP register mask components (non-toggle bits preserved during read-modify-write) +// Excludes DTOG_RX, STAT_RX, DTOG_TX, STAT_TX (toggle-on-write-1) +#define U_EPREG_MASK_16 (U_EP_CTR_RX | U_EP_SETUP | U_EP_T_FIELD | U_EP_KIND | U_EP_CTR_TX | U_EPADDR_FIELD) +#define U_EPREG_MASK_32 (U_EP_ERRRX | U_EP_ERRTX | U_EP_LSEP | U_EP_NAK | U_EP_DEVADDR | U_EPREG_MASK_16) + +// EP register mask selection based on bus width +#ifdef CFG_TUSB_FSDEV_32BIT + #define U_EPREG_MASK U_EPREG_MASK_32 +#else + #define U_EPREG_MASK U_EPREG_MASK_16 +#endif + +#define U_EPKIND_MASK ((uint32_t)(~U_EP_KIND) & U_EPREG_MASK) +#define U_EPTX_DTOGMASK (U_EPTX_STAT | U_EPREG_MASK) +#define U_EPRX_DTOGMASK (U_EPRX_STAT | U_EPREG_MASK) + +// Bit positions +#define U_EPTX_STAT_Pos 4u +#define U_EP_DTOG_TX_Pos 6u +#define U_EP_CTR_TX_Pos 7u + +// Data toggle helpers +#define U_EPTX_DTOG1 0x0010u +#define U_EPTX_DTOG2 0x0020u +#define U_EPRX_DTOG1 0x1000u +#define U_EPRX_DTOG2 0x2000u + +// CNTR - Control Register +// 15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0 +// CTRM PMAOVRM ERRM WKUPM SUSPM RESETM SOFM ESOFM Rsvd Rsvd Rsvd RESUME FSUSP LPMODE PDWN FRES +// +// DRD 32-bit only: +// 31 30:16 +// HOST Rsvd +#define U_CNTR_CTRM 0x8000u +#define U_CNTR_PMAOVRM 0x4000u +#define U_CNTR_ERRM 0x2000u +#define U_CNTR_WKUPM 0x1000u +#define U_CNTR_SUSPM 0x0800u +#define U_CNTR_RESETM 0x0400u +#define U_CNTR_SOFM 0x0200u +#define U_CNTR_ESOFM 0x0100u +#define U_CNTR_RESUME 0x0010u +#define U_CNTR_FSUSP 0x0008u +#define U_CNTR_LPMODE 0x0004u +#define U_CNTR_PDWN 0x0002u +#define U_CNTR_FRES 0x0001u + +#define U_CNTR_HOST 0x80000000u // DRD: enable host mode +#define U_CNTR_DCON 0x0400u // DRD host: same bit as RESETM + +// ISTR - Interrupt Status Register +// 15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0 +// CTR PMAOVR ERR WKUP SUSP RESET SOF ESOF Rsvd Rsvd Rsvd DIR EP_ID[3:0] +// +// DRD 32-bit only: +// 31 30 29 28:16 +// Rsvd LS_DCONN DCON_STAT Rsvd +#define U_ISTR_CTR 0x8000u +#define U_ISTR_PMAOVR 0x4000u +#define U_ISTR_ERR 0x2000u +#define U_ISTR_WKUP 0x1000u +#define U_ISTR_SUSP 0x0800u +#define U_ISTR_RESET 0x0400u +#define U_ISTR_SOF 0x0200u +#define U_ISTR_ESOF 0x0100u +#define U_ISTR_DIR 0x0010u +#define U_ISTR_EP_ID 0x000Fu + +#define U_ISTR_LS_DCONN 0x40000000u // DRD: low-speed device connected +#define U_ISTR_DCON_STAT 0x20000000u // DRD: device connection status +#define U_ISTR_DCON 0x0400u // DRD host: same bit as RESET + +// FNR - Frame Number Register (read-only) +// 15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0 +// RXDP RXDM LCK[2:0] FN[10:0] +#define U_FNR_RXDP 0x8000u +#define U_FNR_RXDM 0x4000u +#define U_FNR_FN 0x07FFu + +// DADDR - Device Address Register +// 15:8 7 6 5 4 3 2 1 0 +// Rsvd EF ADD[6:0] +#define U_DADDR_EF 0x80u + +// LPMCSR - LPM Control and Status Register +// Supported: STM32 F0, L0, L4, G0, G4, C0, H5, U0, WB. Not on: F1, F3, AT32, CH32. +// 15:8 7 6 5 4 3 2 1 0 +// Rsvd BESL[3:0] Rsvd REMWAKE Rsvd LPMACK LMPEN +#define U_LPMCSR_LMPEN 0x0001u +#define U_LPMCSR_LPMACK 0x0002u +#define U_LPMCSR_REMWAKE 0x0008u +#define U_LPMCSR_BESL 0x00F0u + +// BCDR - Battery Charging Detector Register +// Supported: STM32 F0, L0, L4, G0, G4, C0, H5, U0, WB. Not on: F1, F3, AT32, CH32. +// 15 14:8 7 6 5 4 3 2 1 0 +// DPPU Rsvd PS2DET SDET PDET DCDET SDEN PDEN DCDEN BCDEN +#define U_BCDR_BCDEN 0x0001u +#define U_BCDR_DCDEN 0x0002u +#define U_BCDR_PDEN 0x0004u +#define U_BCDR_SDEN 0x0008u +#define U_BCDR_DCDET 0x0010u +#define U_BCDR_PDET 0x0020u +#define U_BCDR_SDET 0x0040u +#define U_BCDR_PS2DET 0x0080u +#define U_BCDR_DPPU 0x8000u + +// Channel status (DRD host mode, reuses STAT_TX/STAT_RX bit positions) +#define U_CH_TX_STTX 0x0030u +#define U_CH_TX_ACK_SBUF 0x0000u +#define U_CH_TX_STALL 0x0010u +#define U_CH_TX_NAK 0x0020u + +#define U_CH_RX_STRX 0x3000u +#define U_CH_RX_ACK_SBUF 0x0000u +#define U_CH_RX_STALL 0x1000u +#define U_CH_RX_NAK 0x2000u +#define U_CH_RX_VALID 0x3000u + +//--------------------------------------------------------------------+ +// Registers Typedef +//--------------------------------------------------------------------+ +// hardware limit endpoint +#define FSDEV_EP_COUNT 8 + +// The fsdev_bus_t type can be used for both register and PMA access necessities +#ifdef CFG_TUSB_FSDEV_32BIT +typedef uint32_t fsdev_bus_t; +#else +typedef uint16_t fsdev_bus_t; +#endif + +// volatile 32-bit aligned +#define _va32 volatile TU_ATTR_ALIGNED(4) + +typedef struct { + struct { + _va32 fsdev_bus_t reg; + } ep[FSDEV_EP_COUNT]; + + _va32 uint32_t RESERVED7[8]; // Reserved + _va32 fsdev_bus_t CNTR; // 40: Control register + _va32 fsdev_bus_t ISTR; // 44: Interrupt status register + _va32 fsdev_bus_t FNR; // 48: Frame number register + _va32 fsdev_bus_t DADDR; // 4C: Device address register + _va32 fsdev_bus_t BTABLE; // 50: Buffer Table address register + _va32 fsdev_bus_t LPMCSR; // 54: LPM Control and Status (not on F1, F3, AT32, CH32) + _va32 fsdev_bus_t BCDR; // 58: Battery Charging Detector (not on F1, F3, AT32, CH32) +} fsdev_regs_t; + +TU_VERIFY_STATIC(offsetof(fsdev_regs_t, CNTR) == 0x40, "Wrong offset"); +TU_VERIFY_STATIC(sizeof(fsdev_regs_t) == 0x5C, "Size is not correct"); + +#define FSDEV_REG ((fsdev_regs_t *)FSDEV_REG_BASE) + +//--------------------------------------------------------------------+ +// BTable and PMA Access +//--------------------------------------------------------------------+ + +// If sharing with CAN, one can set this to be non-zero to give CAN space where it wants it +// Both of these MUST be a multiple of 2, and are in byte units. +#ifndef FSDEV_BTABLE_BASE + #define FSDEV_BTABLE_BASE 0U +#endif +TU_VERIFY_STATIC((FSDEV_BTABLE_BASE & 0x7) == 0, "BTABLE base must be aligned to 8 bytes"); + +#define FSDEV_ADDR_DATA_RATIO (CFG_TUSB_FIFO_HWFIFO_ADDR_STRIDE/CFG_TUSB_FIFO_HWFIFO_DATA_STRIDE) + +// Need alignment when access address is 32 bit but data is only 16-bit +#if FSDEV_ADDR_DATA_RATIO == 2 + #define fsdev_addr_data_align TU_ATTR_ALIGNED(4) +#else + #define fsdev_addr_data_align +#endif + +enum { + BTABLE_BUF_TX = 0, + BTABLE_BUF_RX = 1 +}; + +// Buffer Table is located in Packet Memory Area (PMA) and therefore its address access is forced to either +// 16-bit or 32-bit depending on CFG_TUSB_FSDEV_32BIT. +// 0: TX (IN), 1: RX (OUT) +typedef union { + // data is strictly 16-bit access (address could be 32-bit aligned) + struct { + volatile fsdev_addr_data_align uint16_t addr; + volatile fsdev_addr_data_align uint16_t count; + } ep16[FSDEV_EP_COUNT][2]; + + // strictly 32-bit access + struct { + volatile uint32_t count_addr; + } ep32[FSDEV_EP_COUNT][2]; +} fsdev_btable_t; + +TU_VERIFY_STATIC(sizeof(fsdev_btable_t) == FSDEV_EP_COUNT * 8 * FSDEV_ADDR_DATA_RATIO, "size is not correct"); +TU_VERIFY_STATIC(FSDEV_BTABLE_BASE + FSDEV_EP_COUNT * 8 <= CFG_TUSB_FSDEV_PMA_SIZE, "BTABLE does not fit in PMA RAM"); + +#define FSDEV_BTABLE ((volatile fsdev_btable_t *)(FSDEV_PMA_BASE + FSDEV_ADDR_DATA_RATIO * FSDEV_BTABLE_BASE)) + +typedef struct { + volatile fsdev_addr_data_align fsdev_bus_t value; +} fsdev_pma_buf_t; + +#define PMA_BUF_AT(_addr) ((fsdev_pma_buf_t *)(FSDEV_PMA_BASE + FSDEV_ADDR_DATA_RATIO * (_addr))) + +//--------------------------------------------------------------------+ +// Vendor-specific includes +//--------------------------------------------------------------------+ +#if defined(TUP_USBIP_FSDEV_STM32) + #include "fsdev_stm32.h" +#elif defined(TUP_USBIP_FSDEV_CH32) + #include "fsdev_ch32.h" +#elif defined(TUP_USBIP_FSDEV_AT32) + #include "fsdev_at32.h" +#else + #error "Unknown USB IP" +#endif + +//--------------------------------------------------------------------+ +// Endpoint Helper +// - CTR is write 0 to clear +// - DTOG and STAT are write 1 to toggle +//--------------------------------------------------------------------+ +typedef enum { + EP_STAT_DISABLED = 0, + EP_STAT_STALL = 1, + EP_STAT_NAK = 2, + EP_STAT_VALID = 3 +} ep_stat_t; + +#define EP_STAT_MASK(_dir) (3u << (U_EPTX_STAT_Pos + ((_dir) == TUSB_DIR_IN ? 0 : 8))) +#define EP_DTOG_MASK(_dir) (1u << (U_EP_DTOG_TX_Pos + ((_dir) == TUSB_DIR_IN ? 0 : 8))) + +#define CH_STAT_MASK(_dir) (3u << (U_EPTX_STAT_Pos + ((_dir) == TUSB_DIR_IN ? 8 : 0))) +#define CH_DTOG_MASK(_dir) (1u << (U_EP_DTOG_TX_Pos + ((_dir) == TUSB_DIR_IN ? 8 : 0))) + +TU_ATTR_ALWAYS_INLINE static inline uint32_t ep_read(uint32_t ep_id) { + return FSDEV_REG->ep[ep_id].reg; +} + +TU_ATTR_ALWAYS_INLINE static inline void ep_write(uint32_t ep_id, uint32_t value, bool need_exclusive) { + if (need_exclusive) { + fsdev_int_disable(0); + } + + FSDEV_REG->ep[ep_id].reg = (fsdev_bus_t)value; + + if (need_exclusive) { + fsdev_int_enable(0); + } +} + +TU_ATTR_ALWAYS_INLINE static inline void ep_write_clear_ctr(uint32_t ep_id, tusb_dir_t dir) { + uint32_t reg = FSDEV_REG->ep[ep_id].reg; + reg |= U_EP_CTR_TX | U_EP_CTR_RX; + reg &= U_EPREG_MASK; + reg &= ~(1u << (U_EP_CTR_TX_Pos + (dir == TUSB_DIR_IN ? 0u : 8u))); + ep_write(ep_id, reg, false); +} + +TU_ATTR_ALWAYS_INLINE static inline void ep_change_status(uint32_t *reg, tusb_dir_t dir, ep_stat_t state) { + *reg ^= (state << (U_EPTX_STAT_Pos + (dir == TUSB_DIR_IN ? 0 : 8))); +} + +TU_ATTR_ALWAYS_INLINE static inline void ep_change_dtog(uint32_t *reg, tusb_dir_t dir, uint8_t state) { + *reg ^= (state << (U_EP_DTOG_TX_Pos + (dir == TUSB_DIR_IN ? 0 : 8))); +} + +TU_ATTR_ALWAYS_INLINE static inline bool ep_is_iso(uint32_t reg) { + return (reg & U_EP_TYPE_MASK) == U_EP_ISOCHRONOUS; +} + +//--------------------------------------------------------------------+ +// Channel Helper +// - Direction is opposite to endpoint direction +//--------------------------------------------------------------------+ + +TU_ATTR_ALWAYS_INLINE static inline uint32_t ch_read(uint32_t ch_id) { + return ep_read(ch_id); +} + +TU_ATTR_ALWAYS_INLINE static inline void ch_write(uint32_t ch_id, uint32_t value, bool need_exclusive) { + ep_write(ch_id, value, need_exclusive); +} + +TU_ATTR_ALWAYS_INLINE static inline void ch_write_clear_ctr(uint32_t ch_id, tusb_dir_t dir) { + uint32_t reg = FSDEV_REG->ep[ch_id].reg; + reg |= U_EP_CTR_TX | U_EP_CTR_RX; + reg &= U_EPREG_MASK; + reg &= ~(1u << (U_EP_CTR_TX_Pos + (dir == TUSB_DIR_IN ? 8u : 0u))); + ep_write(ch_id, reg, false); +} + +TU_ATTR_ALWAYS_INLINE static inline void ch_change_status(uint32_t *reg, tusb_dir_t dir, ep_stat_t state) { + *reg ^= (state << (U_EPTX_STAT_Pos + (dir == TUSB_DIR_IN ? 8 : 0))); +} + +TU_ATTR_ALWAYS_INLINE static inline void ch_change_dtog(uint32_t *reg, tusb_dir_t dir, uint8_t state) { + *reg ^= (state << (U_EP_DTOG_TX_Pos + (dir == TUSB_DIR_IN ? 8 : 0))); +} + +//--------------------------------------------------------------------+ +// BTable Helper +//--------------------------------------------------------------------+ + +TU_ATTR_ALWAYS_INLINE static inline uint32_t btable_get_addr(uint32_t ep_id, uint8_t buf_id) { +#ifdef CFG_TUSB_FSDEV_32BIT + return FSDEV_BTABLE->ep32[ep_id][buf_id].count_addr & 0x0000FFFFu; +#else + return FSDEV_BTABLE->ep16[ep_id][buf_id].addr; +#endif +} + +TU_ATTR_ALWAYS_INLINE static inline void btable_set_addr(uint32_t ep_id, uint8_t buf_id, uint16_t addr) { +#ifdef CFG_TUSB_FSDEV_32BIT + uint32_t count_addr = FSDEV_BTABLE->ep32[ep_id][buf_id].count_addr; + count_addr = (count_addr & 0xFFFF0000u) | (addr & 0x0000FFFCu); + + FSDEV_BTABLE->ep32[ep_id][buf_id].count_addr = count_addr; +#else + FSDEV_BTABLE->ep16[ep_id][buf_id].addr = addr; +#endif +} + +TU_ATTR_ALWAYS_INLINE static inline uint16_t btable_get_count(uint32_t ep_id, uint8_t buf_id) { + uint16_t count; +#ifdef CFG_TUSB_FSDEV_32BIT + count = (FSDEV_BTABLE->ep32[ep_id][buf_id].count_addr >> 16); +#else + count = FSDEV_BTABLE->ep16[ep_id][buf_id].count; +#endif + return count & 0x3FFU; +} + +TU_ATTR_ALWAYS_INLINE static inline void btable_set_count(uint32_t ep_id, uint8_t buf_id, uint16_t byte_count) { +#ifdef CFG_TUSB_FSDEV_32BIT + uint32_t count_addr = FSDEV_BTABLE->ep32[ep_id][buf_id].count_addr; + count_addr = (count_addr & ~0x03FF0000u) | ((byte_count & 0x3FFu) << 16); + + FSDEV_BTABLE->ep32[ep_id][buf_id].count_addr = count_addr; +#else + uint16_t cnt = FSDEV_BTABLE->ep16[ep_id][buf_id].count; + cnt = (cnt & ~0x3FFU) | (byte_count & 0x3FFU); + + FSDEV_BTABLE->ep16[ep_id][buf_id].count = cnt; +#endif +} + +// Reset the USB Core +void fsdev_core_reset(void); + +// De-initialize the USB Core +void fsdev_deinit(void); + +// Aligned buffer size according to hardware +uint16_t pma_align_buffer_size(uint16_t size, uint8_t *blsize, uint8_t *num_block); + +// Set RX buffer size +void btable_set_rx_bufsize(uint32_t ep_id, uint8_t buf_id, uint16_t wCount); + +#ifdef __cplusplus +} +#endif + +#endif /* TUSB_FSDEV_COMMON_H */ diff --git a/libraries/Adafruit_TinyUSB_Arduino/src/portable/st/stm32_fsdev/fsdev_stm32.h b/libraries/Adafruit_TinyUSB_Arduino/src/portable/st/stm32_fsdev/fsdev_stm32.h new file mode 100644 index 0000000..070aa00 --- /dev/null +++ b/libraries/Adafruit_TinyUSB_Arduino/src/portable/st/stm32_fsdev/fsdev_stm32.h @@ -0,0 +1,326 @@ +/* + * The MIT License (MIT) + * + * Copyright(c) N Conrad + * Copyright (c) 2019 Ha Thach (tinyusb.org) + * + * Redistribution and use in source and binary forms, with or without modification, + * are permitted provided that the following conditions are met: + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * 3. Neither the name of STMicroelectronics nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * This file is part of the TinyUSB stack. + */ + +#ifndef TUSB_FSDEV_STM32_H +#define TUSB_FSDEV_STM32_H + +#if CFG_TUSB_MCU == OPT_MCU_STM32C0 + #include "stm32c0xx.h" + #define FSDEV_HAS_SBUF_ISO 1 + +#elif CFG_TUSB_MCU == OPT_MCU_STM32F0 + #include "stm32f0xx.h" + #define FSDEV_HAS_SBUF_ISO 0 + // F0x2 models are crystal-less + // All have internal D+ pull-up + // 070RB: 2 x 16 bits/word memory LPM Support, BCD Support + // PMA dedicated to USB (no sharing with CAN) + +#elif CFG_TUSB_MCU == OPT_MCU_STM32F1 + #include "stm32f1xx.h" + #define FSDEV_HAS_SBUF_ISO 0 + // NO internal Pull-ups + // *B, and *C: 2 x 16 bits/word + +#elif CFG_TUSB_MCU == OPT_MCU_STM32F3 + #include "stm32f3xx.h" + #define FSDEV_HAS_SBUF_ISO 0 + // NO internal Pull-ups. PMA dedicated to USB (no sharing with CAN) + // xB, and xC: 512 bytes + // x6, x8, xD, and xE: 1024 bytes + LPM Support. When CAN clock is enabled, USB can use the first 768 bytes ONLY. + +#elif CFG_TUSB_MCU == OPT_MCU_STM32G0 + #include "stm32g0xx.h" + #define FSDEV_HAS_SBUF_ISO 1 + +#elif CFG_TUSB_MCU == OPT_MCU_STM32G4 + #include "stm32g4xx.h" + #define FSDEV_HAS_SBUF_ISO 0 + +#elif CFG_TUSB_MCU == OPT_MCU_STM32H5 + #include "stm32h5xx.h" + #define FSDEV_HAS_SBUF_ISO 1 + +#elif CFG_TUSB_MCU == OPT_MCU_STM32L0 + #include "stm32l0xx.h" + #define FSDEV_HAS_SBUF_ISO 0 + +#elif CFG_TUSB_MCU == OPT_MCU_STM32L1 + #include "stm32l1xx.h" + #define FSDEV_HAS_SBUF_ISO 0 + +#elif CFG_TUSB_MCU == OPT_MCU_STM32L4 + #include "stm32l4xx.h" + #define FSDEV_HAS_SBUF_ISO 0 + +#elif CFG_TUSB_MCU == OPT_MCU_STM32L5 + #include "stm32l5xx.h" + #define FSDEV_HAS_SBUF_ISO 0 + + #ifndef USB_PMAADDR + #define USB_PMAADDR (USB_BASE + (USB_PMAADDR_NS - USB_BASE_NS)) + #endif + +#elif CFG_TUSB_MCU == OPT_MCU_STM32U0 + #include "stm32u0xx.h" + #define FSDEV_HAS_SBUF_ISO 1 + +#elif CFG_TUSB_MCU == OPT_MCU_STM32U3 + #include "stm32u3xx.h" + #define FSDEV_HAS_SBUF_ISO 1 + +#elif CFG_TUSB_MCU == OPT_MCU_STM32U5 + #include "stm32u5xx.h" + #define FSDEV_HAS_SBUF_ISO 1 + +#elif CFG_TUSB_MCU == OPT_MCU_STM32WB + #include "stm32wbxx.h" + #define FSDEV_HAS_SBUF_ISO 0 + +#else + #error You are using an untested or unimplemented STM32 variant. Please update the driver. +#endif + +//--------------------------------------------------------------------+ +// Register and PMA Base Address +//--------------------------------------------------------------------+ +#ifndef FSDEV_REG_BASE +#if defined(USB_BASE) + #define FSDEV_REG_BASE USB_BASE +#elif defined(USB_DRD_BASE) + #define FSDEV_REG_BASE USB_DRD_BASE +#elif defined(USB_DRD_FS_BASE) + #define FSDEV_REG_BASE USB_DRD_FS_BASE +#else + #error "FSDEV_REG_BASE not defined" +#endif +#endif + +#ifndef FSDEV_PMA_BASE +#if defined(USB_PMAADDR) + #define FSDEV_PMA_BASE USB_PMAADDR +#elif defined(USB_DRD_PMAADDR) + #define FSDEV_PMA_BASE USB_DRD_PMAADDR +#else + #error "FSDEV_PMA_BASE not defined" +#endif +#endif + +#ifndef FSDEV_HAS_SBUF_ISO + #error "FSDEV_HAS_SBUF_ISO not defined" +#endif + +#ifndef CFG_TUD_FSDEV_DOUBLE_BUFFERED_ISO_EP + // Default configuration for double-buffered isochronous endpoints: + // - Enable double buffering on devices with >1KB Packet Memory Area (PMA) + // to improve isochronous transfer reliability and performance + // - Disable on devices with limited PMA to conserve memory space + #if CFG_TUSB_FSDEV_PMA_SIZE > 1024u + #define CFG_TUD_FSDEV_DOUBLE_BUFFERED_ISO_EP 1 + #else + #define CFG_TUD_FSDEV_DOUBLE_BUFFERED_ISO_EP 0 + #endif +#endif + +#if FSDEV_HAS_SBUF_ISO != 0 && CFG_TUD_FSDEV_DOUBLE_BUFFERED_ISO_EP == 0 + // SBUF_ISO configuration: + // - Some STM32 devices have special hardware support for single-buffered isochronous endpoints + // - When SBUF_ISO bit is available and double buffering is disabled: + // Enable SBUF_ISO to optimize endpoint register usage (one half of endpoint pair register) + #define FSDEV_USE_SBUF_ISO 1 +#else + // When either: + // - Hardware doesn't support SBUF_ISO feature, or + // - Double buffering is enabled for isochronous endpoints + // We must use the entire endpoint pair register + #define FSDEV_USE_SBUF_ISO 0 +#endif + +//--------------------------------------------------------------------+ +// +//--------------------------------------------------------------------+ + +#if TU_CHECK_MCU(OPT_MCU_STM32L1) && !defined(USBWakeUp_IRQn) + #define USBWakeUp_IRQn USB_FS_WKUP_IRQn +#endif + +static const IRQn_Type fsdev_irq[] = { + #if TU_CHECK_MCU(OPT_MCU_STM32F0, OPT_MCU_STM32L0, OPT_MCU_STM32L4, OPT_MCU_STM32U5) + USB_IRQn, + #elif TU_CHECK_MCU(OPT_MCU_STM32L5, OPT_MCU_STM32U3) + USB_FS_IRQn, + #elif TU_CHECK_MCU(OPT_MCU_STM32C0, OPT_MCU_STM32H5, OPT_MCU_STM32U0) + USB_DRD_FS_IRQn, + #elif CFG_TUSB_MCU == OPT_MCU_STM32G0 + #ifdef STM32G0B0xx + USB_IRQn, + #else + USB_UCPD1_2_IRQn, + #endif + #elif CFG_TUSB_MCU == OPT_MCU_STM32F1 + USB_HP_CAN1_TX_IRQn, + USB_LP_CAN1_RX0_IRQn, + USBWakeUp_IRQn, + #elif CFG_TUSB_MCU == OPT_MCU_STM32F3 + USB_HP_CAN_TX_IRQn, + USB_LP_CAN_RX0_IRQn, + USBWakeUp_IRQn, + #elif TU_CHECK_MCU(OPT_MCU_STM32G4, OPT_MCU_STM32L1) + USB_HP_IRQn, + USB_LP_IRQn, + USBWakeUp_IRQn, + #elif CFG_TUSB_MCU == OPT_MCU_STM32WB + USB_HP_IRQn, + USB_LP_IRQn, + #else + #error Unknown arch in USB driver + #endif +}; +enum { FSDEV_IRQ_NUM = TU_ARRAY_SIZE(fsdev_irq) }; + +TU_ATTR_ALWAYS_INLINE static inline void fsdev_int_enable(uint8_t rhport) { + (void)rhport; + + // forces write to RAM before allowing ISR to execute + __DSB(); __ISB(); + + #if CFG_TUSB_MCU == OPT_MCU_STM32F3 && defined(SYSCFG_CFGR1_USB_IT_RMP) + // Some STM32F302/F303 devices allow to remap the USB interrupt vectors from + // shared USB/CAN IRQs to separate CAN and USB IRQs. + // This dynamically checks if this remap is active to enable the right IRQs. + if (SYSCFG->CFGR1 & SYSCFG_CFGR1_USB_IT_RMP) { + NVIC_EnableIRQ(USB_HP_IRQn); + NVIC_EnableIRQ(USB_LP_IRQn); + NVIC_EnableIRQ(USBWakeUp_RMP_IRQn); + } else + #endif + { + for (uint8_t i = 0; i < FSDEV_IRQ_NUM; i++) { + NVIC_EnableIRQ(fsdev_irq[i]); + } + } +} + +TU_ATTR_ALWAYS_INLINE static inline void fsdev_int_disable(uint8_t rhport) { + (void)rhport; + + #if CFG_TUSB_MCU == OPT_MCU_STM32F3 && defined(SYSCFG_CFGR1_USB_IT_RMP) + // Some STM32F302/F303 devices allow to remap the USB interrupt vectors from + // shared USB/CAN IRQs to separate CAN and USB IRQs. + // This dynamically checks if this remap is active to enable the right IRQs. + if (SYSCFG->CFGR1 & SYSCFG_CFGR1_USB_IT_RMP) { + NVIC_DisableIRQ(USB_HP_IRQn); + NVIC_DisableIRQ(USB_LP_IRQn); + NVIC_DisableIRQ(USBWakeUp_RMP_IRQn); + } else + #endif + { + for (uint8_t i = 0; i < FSDEV_IRQ_NUM; i++) { + NVIC_DisableIRQ(fsdev_irq[i]); + } + } + + // CMSIS has a membar after disabling interrupts +} + +//--------------------------------------------------------------------+ +// STM32 FSDEV PMA Buffer Description Table errata workaround +//--------------------------------------------------------------------+ + +#ifdef CFG_TUSB_FSDEV_32BIT +/* Errata: Buffer description table update completes after CTR interrupt triggers + * https://www.st.com/resource/en/errata_sheet/es0561-stm32h503cbebkbrb-device-errata-stmicroelectronics.pdf + * https://www.st.com/resource/en/errata_sheet/es0587-stm32u535xx-and-stm32u545xx-device-errata-stmicroelectronics.pdf + * + * CTR may trigger before final PMA SRAM accesses complete on OUT transfers. + * Insert delay before reading PMA count/data. + * Max CPU frequency in MHz, used to derive conservative FSDEV PMA delay defaults. + */ +#if CFG_TUSB_MCU == OPT_MCU_STM32H5 + #define FSDEV_STM32_CPU_MHZ 250U +#elif CFG_TUSB_MCU == OPT_MCU_STM32U5 + #define FSDEV_STM32_CPU_MHZ 160U +#elif CFG_TUSB_MCU == OPT_MCU_STM32U3 + #define FSDEV_STM32_CPU_MHZ 96U +#elif CFG_TUSB_MCU == OPT_MCU_STM32U0 + #define FSDEV_STM32_CPU_MHZ 56U +#elif CFG_TUSB_MCU == OPT_MCU_STM32G0 + #define FSDEV_STM32_CPU_MHZ 64U +#elif CFG_TUSB_MCU == OPT_MCU_STM32C0 + #define FSDEV_STM32_CPU_MHZ 48U +#endif + +#ifndef CFG_TUSB_FSDEV_BTABLE_FS_DELAY_COUNT + #define CFG_TUSB_FSDEV_BTABLE_FS_DELAY_COUNT (FSDEV_STM32_CPU_MHZ / 4U) +#endif + +#ifndef CFG_TUSB_FSDEV_BTABLE_LS_DELAY_COUNT + #define CFG_TUSB_FSDEV_BTABLE_LS_DELAY_COUNT (FSDEV_STM32_CPU_MHZ * 2U) +#endif + +TU_ATTR_ALWAYS_INLINE static inline void fsdev_btable_workaround_delay(bool low_speed) { + volatile uint32_t cycle_count = low_speed ? CFG_TUSB_FSDEV_BTABLE_LS_DELAY_COUNT : CFG_TUSB_FSDEV_BTABLE_FS_DELAY_COUNT; + while (cycle_count > 0U) { + cycle_count--; + } +} +#endif + +//--------------------------------------------------------------------+ +// Connect / Disconnect +//--------------------------------------------------------------------+ + +#if defined(USB_BCDR_DPPU) + +TU_ATTR_ALWAYS_INLINE static inline void fsdev_disconnect(uint8_t rhport) { + (void)rhport; + FSDEV_REG->BCDR &= ~U_BCDR_DPPU; +} + +TU_ATTR_ALWAYS_INLINE static inline void fsdev_connect(uint8_t rhport) { + (void)rhport; + FSDEV_REG->BCDR |= U_BCDR_DPPU; +} + +#elif defined(SYSCFG_PMC_USB_PU) // works e.g. on STM32L151 + +TU_ATTR_ALWAYS_INLINE static inline void fsdev_disconnect(uint8_t rhport) { + (void)rhport; + SYSCFG->PMC &= ~(SYSCFG_PMC_USB_PU); +} + +TU_ATTR_ALWAYS_INLINE static inline void fsdev_connect(uint8_t rhport) { + (void)rhport; + SYSCFG->PMC |= SYSCFG_PMC_USB_PU; +} +#endif + +#endif /* TUSB_FSDEV_STM32_H */ diff --git a/libraries/Adafruit_TinyUSB_Arduino/src/portable/st/stm32_fsdev/hcd_stm32_fsdev.c b/libraries/Adafruit_TinyUSB_Arduino/src/portable/st/stm32_fsdev/hcd_stm32_fsdev.c new file mode 100644 index 0000000..f920165 --- /dev/null +++ b/libraries/Adafruit_TinyUSB_Arduino/src/portable/st/stm32_fsdev/hcd_stm32_fsdev.c @@ -0,0 +1,835 @@ +/* + * The MIT License (MIT) + * + * Copyright (c) 2025 HiFiPhile (Zixun LI) + * + * 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. + */ + +/********************************************** + * This driver provides USB Host controller support for STM32 MCUs with "USB A"/"PCD"/"HCD" peripheral. + * This covers these MCU families: + * + * C0 2048 byte buffer; 32-bit bus; host mode + * G0 2048 byte buffer; 32-bit bus; host mode + * U3 2048 byte buffer; 32-bit bus; host mode + * H5 2048 byte buffer; 32-bit bus; host mode + * U535, U545 2048 byte buffer; 32-bit bus; host mode + * + */ + +#include "tusb_option.h" + +#if CFG_TUH_ENABLED && defined(TUP_USBIP_FSDEV) && defined(TUP_USBIP_FSDEV_DRD) + +#include "host/hcd.h" +#include "host/usbh.h" +#include "fsdev_common.h" + +//--------------------------------------------------------------------+ +// MACRO CONSTANT TYPEDEF +//--------------------------------------------------------------------+ + +// Debug level for FSDEV +#define FSDEV_DEBUG 3 + +// Max number of endpoints application can open, can be larger than FSDEV_EP_COUNT +#ifndef CFG_TUH_FSDEV_ENDPOINT_MAX + #define CFG_TUH_FSDEV_ENDPOINT_MAX 16u +#endif + +TU_VERIFY_STATIC(CFG_TUH_FSDEV_ENDPOINT_MAX <= 255, "currently only use 8-bit for index"); + +enum { + HCD_XFER_ERROR_MAX = 3, + HCD_XFER_NAK_MAX = 15, + HCD_XFER_NAK_DEFAULT = 3, +}; + +// Host driver struct for each opened endpoint +typedef struct { + uint8_t *buffer; + uint16_t buflen; + uint16_t queued_len; + uint16_t max_packet_size; + uint8_t dev_addr; + uint8_t ep_addr; + uint8_t ep_type; + uint8_t interval; + struct TU_ATTR_PACKED { + uint8_t ls_pre : 1; + uint8_t allocated : 1; + uint8_t next_setup : 1; + uint8_t pid : 1; + }; +} hcd_endpoint_t; + +// Channel direction state +typedef struct { + hcd_endpoint_t* edpt; + struct TU_ATTR_PACKED { + uint8_t allocated : 1; + uint8_t retry : 3; + uint8_t nak : 4; // Max NAK count in current frame + }; +} hcd_channel_dir_t; + +// Additional info for each channel when it is active +typedef struct { + uint8_t dev_addr; + uint8_t ep_num; + uint8_t ep_type; + hcd_channel_dir_t out, in; +} hcd_channel_t; + +static struct { + hcd_channel_t channel[FSDEV_EP_COUNT]; + hcd_endpoint_t edpt[CFG_TUH_FSDEV_ENDPOINT_MAX]; + bool connected; +} _hcd_data; + +static tuh_configure_fsdev_t _tuh_cfg = { + .max_nak = HCD_XFER_NAK_DEFAULT, +}; + +//--------------------------------------------------------------------+ +// Prototypes +//--------------------------------------------------------------------+ + +static uint8_t endpoint_alloc(void); +static uint8_t endpoint_find(uint8_t dev_addr, uint8_t ep_addr); +static uint32_t hcd_pma_alloc(uint8_t channel, tusb_dir_t dir, uint16_t len); +static uint8_t channel_alloc(uint8_t dev_addr, uint8_t ep_addr, uint8_t ep_type); +static bool edpt_xfer_kickoff(uint8_t ep_id); +static bool channel_xfer_start(uint8_t ch_id, tusb_dir_t dir); +static void edpoint_close(uint8_t ep_id); +static void port_status_handler(uint8_t rhport, bool in_isr); +static void ch_handle_ack(uint8_t ch_id, uint32_t ch_reg, tusb_dir_t dir); +static void ch_handle_nak(uint8_t ch_id, uint32_t ch_reg, tusb_dir_t dir); +static void ch_handle_stall(uint8_t ch_id, uint32_t ch_reg, tusb_dir_t dir); +static void ch_handle_error(uint8_t ch_id, uint32_t ch_reg, tusb_dir_t dir); + +//--------------------------------------------------------------------+ +// Inline Functions +//--------------------------------------------------------------------+ + +static inline void endpoint_dealloc(hcd_endpoint_t* edpt) { + edpt->allocated = 0; +} + +static inline void channel_dealloc(hcd_channel_t* ch, tusb_dir_t dir) { + if (dir == TUSB_DIR_OUT) { + ch->out.allocated = 0; + } else { + ch->in.allocated = 0; + } +} + +// Write channel state in specified direction +static inline void channel_write_status(uint8_t ch_id, uint32_t ch_reg, tusb_dir_t dir, ep_stat_t state, bool need_exclusive) { + ch_reg &= U_EPREG_MASK | CH_STAT_MASK(dir); + ch_change_status(&ch_reg, dir, state); + ch_write(ch_id, ch_reg, need_exclusive); +} + +static inline uint16_t channel_get_rx_count(uint8_t ch_id) { + uint32_t ch_reg = ch_read(ch_id); + const bool is_low_speed = (FSDEV_REG->ISTR & U_ISTR_LS_DCONN) || (ch_reg & U_EP_LSEP); + fsdev_btable_workaround_delay(is_low_speed); + + return btable_get_count(ch_id, BTABLE_BUF_RX); +} + +//--------------------------------------------------------------------+ +// Controller API +//--------------------------------------------------------------------+ + +// Optional HCD configuration, called by tuh_configure() +bool hcd_configure(uint8_t rhport, uint32_t cfg_id, const void* cfg_param) { + (void) rhport; + TU_VERIFY(cfg_id == TUH_CFGID_FSDEV && cfg_param != NULL); + + tuh_configure_param_t const* cfg = (tuh_configure_param_t const*) cfg_param; + _tuh_cfg.max_nak = tu_min8(cfg->fsdev.max_nak, HCD_XFER_NAK_MAX); + return true; +} + +// Initialize controller to host mode +bool hcd_init(uint8_t rhport, const tusb_rhport_init_t* rh_init) { + (void) rh_init; + + fsdev_core_reset(); + + FSDEV_REG->CNTR = U_CNTR_HOST; // Enable USB in Host mode + + tu_memclr(&_hcd_data, sizeof(_hcd_data)); + + // Clear pending interrupts + // Normally no interrupts should be pending here since we just reset the core, + // but device mode suspend needs to cleared by WKUP flag + FSDEV_REG->ISTR = 0; + + // Enable interrupts for host mode + FSDEV_REG->CNTR |= U_CNTR_DCON | U_CNTR_CTRM | U_CNTR_SOFM | U_CNTR_ERRM | U_CNTR_PMAOVRM; + + // Initialize port state + _hcd_data.connected = false; + + fsdev_connect(rhport); + + // If DCON_STAT is already set, the controller sometimes misses the initial connection interrupt + if (FSDEV_REG->ISTR & U_ISTR_DCON_STAT) { + tusb_time_delay_ms_api(2); + port_status_handler(rhport, false); + } + + return true; +} + +bool hcd_deinit(uint8_t rhport) { + (void)rhport; + + fsdev_disconnect(rhport); + + fsdev_deinit(); + + return true; +} + +//--------------------------------------------------------------------+ +// Interrupt Helper Functions +//--------------------------------------------------------------------+ + +static inline void sof_handler(void) { + // Reset NAK counters for all active channels + for (uint8_t ch_id = 0; ch_id < FSDEV_EP_COUNT; ch_id++) { + hcd_channel_t* channel = &_hcd_data.channel[ch_id]; + if (channel->out.allocated) { + channel->out.nak = 0; + } + if (channel->in.allocated) { + channel->in.nak = 0; + } + } +} + +static void port_status_handler(uint8_t rhport, bool in_isr) { + uint32_t const fnr_reg = FSDEV_REG->FNR; + uint32_t const istr_reg = FSDEV_REG->ISTR; + // SE0 detected USB Disconnected state + if ((fnr_reg & (U_FNR_RXDP | U_FNR_RXDM)) == 0U) { + _hcd_data.connected = false; + hcd_event_device_remove(rhport, in_isr); + return; + } + + if (!_hcd_data.connected) { + // J-state or K-state detected & LastState=Disconnected + if (((fnr_reg & U_FNR_RXDP) != 0U) || ((istr_reg & U_ISTR_LS_DCONN) != 0U)) { + _hcd_data.connected = true; + hcd_event_device_attach(rhport, in_isr); + } + } else { + // J-state or K-state detected & lastState=Connected: a Missed disconnection is detected + if (((fnr_reg & U_FNR_RXDP) != 0U) || ((istr_reg & U_ISTR_LS_DCONN) != 0U)) { + _hcd_data.connected = false; + hcd_event_device_remove(rhport, in_isr); + } + } +} + +// Handle ACK response +static void ch_handle_ack(uint8_t ch_id, uint32_t ch_reg, tusb_dir_t dir) { + uint8_t const ep_num = ch_reg & U_EPADDR_FIELD; + uint8_t const daddr = (ch_reg & U_EP_DEVADDR) >> U_EP_DEVADDR_Pos; + + uint8_t ep_id = endpoint_find(daddr, ep_num | (dir == TUSB_DIR_IN ? TUSB_DIR_IN_MASK : 0)); + if (ep_id == TUSB_INDEX_INVALID_8) { + return; + } + + hcd_endpoint_t *edpt = &_hcd_data.edpt[ep_id]; + hcd_channel_t *channel = &_hcd_data.channel[ch_id]; + + if (dir == TUSB_DIR_OUT) { + // OUT/TX direction + if (edpt->buflen != edpt->queued_len) { + // More data to send + uint16_t const len = tu_min16(edpt->buflen - edpt->queued_len, edpt->max_packet_size); + uint16_t pma_addr = (uint16_t) btable_get_addr(ch_id, BTABLE_BUF_TX); + tu_hwfifo_write(PMA_BUF_AT(pma_addr), &(edpt->buffer[edpt->queued_len]), len, NULL); + btable_set_count(ch_id, BTABLE_BUF_TX, len); + edpt->queued_len += len; + channel_write_status(ch_id, ch_reg, TUSB_DIR_OUT, EP_STAT_VALID, false); + channel->out.nak = 0; + } else { + // Transfer complete + channel_dealloc(channel, TUSB_DIR_OUT); + edpt->pid = (ch_reg & U_EP_DTOG_TX) ? 1 : 0; + hcd_event_xfer_complete(daddr, ep_num, edpt->queued_len, XFER_RESULT_SUCCESS, true); + } + } else { + // IN/RX direction + uint16_t const rx_count = channel_get_rx_count(ch_id); + uint16_t pma_addr = (uint16_t) btable_get_addr(ch_id, BTABLE_BUF_RX); + tu_hwfifo_read(PMA_BUF_AT(pma_addr), edpt->buffer + edpt->queued_len, rx_count, NULL); + edpt->queued_len += rx_count; + + if ((rx_count < edpt->max_packet_size) || (edpt->queued_len >= edpt->buflen)) { + // Transfer complete (short packet or all bytes received) + channel_dealloc(channel, TUSB_DIR_IN); + edpt->pid = (ch_reg & U_EP_DTOG_RX) ? 1 : 0; + hcd_event_xfer_complete(daddr, ep_num | TUSB_DIR_IN_MASK, edpt->queued_len, XFER_RESULT_SUCCESS, true); + } else { + // More data expected + uint16_t const cnt = tu_min16(edpt->buflen - edpt->queued_len, edpt->max_packet_size); + btable_set_rx_bufsize(ch_id, BTABLE_BUF_RX, cnt); + channel_write_status(ch_id, ch_reg, TUSB_DIR_IN, EP_STAT_VALID, false); + channel->in.nak = 0; + } + } +} + +// Handle NAK response +static void ch_handle_nak(uint8_t ch_id, uint32_t ch_reg, tusb_dir_t dir) { + uint8_t const ep_num = ch_reg & U_EPADDR_FIELD; + uint8_t const daddr = (ch_reg & U_EP_DEVADDR) >> U_EP_DEVADDR_Pos; + + uint8_t ep_id = endpoint_find(daddr, ep_num | (dir == TUSB_DIR_IN ? TUSB_DIR_IN_MASK : 0)); + if (ep_id == TUSB_INDEX_INVALID_8) return; + + hcd_endpoint_t* edpt = &_hcd_data.edpt[ep_id]; + // Retry non-periodic transfer immediately if NAK count not exceeded + // Periodic transfer will be retried by next frame automatically + if (edpt->ep_type == TUSB_XFER_CONTROL || edpt->ep_type == TUSB_XFER_BULK) { + hcd_channel_dir_t* channel_dir = + (dir == TUSB_DIR_OUT) ? &(_hcd_data.channel[ch_id].out) : &(_hcd_data.channel[ch_id].in); + if (channel_dir->nak < HCD_XFER_NAK_MAX) { + channel_dir->nak++; + } + if (channel_dir->nak < _tuh_cfg.max_nak || _tuh_cfg.max_nak == 0) { + channel_write_status(ch_id, ch_reg, dir, EP_STAT_VALID, false); + } + } +} + +// Handle STALL response +static void ch_handle_stall(uint8_t ch_id, uint32_t ch_reg, tusb_dir_t dir) { + uint8_t const ep_num = ch_reg & U_EPADDR_FIELD; + uint8_t const daddr = (ch_reg & U_EP_DEVADDR) >> U_EP_DEVADDR_Pos; + + uint8_t ep_id = endpoint_find(daddr, ep_num | (dir == TUSB_DIR_IN ? TUSB_DIR_IN_MASK : 0)); + if (ep_id == TUSB_INDEX_INVALID_8) return; + + hcd_endpoint_t* edpt = &_hcd_data.edpt[ep_id]; + hcd_channel_t* channel = &_hcd_data.channel[ch_id]; + channel_dealloc(channel, dir); + + channel_write_status(ch_id, ch_reg, dir, EP_STAT_DISABLED, false); + + hcd_event_xfer_complete(daddr, ep_num | (dir == TUSB_DIR_IN ? TUSB_DIR_IN_MASK : 0), + edpt->queued_len, XFER_RESULT_STALLED, true); +} + +// Handle error response +static void ch_handle_error(uint8_t ch_id, uint32_t ch_reg, tusb_dir_t dir) { + uint8_t const ep_num = ch_reg & U_EPADDR_FIELD; + uint8_t const daddr = (ch_reg & U_EP_DEVADDR) >> U_EP_DEVADDR_Pos; + + uint8_t ep_id = endpoint_find(daddr, ep_num | (dir == TUSB_DIR_IN ? TUSB_DIR_IN_MASK : 0)); + if (ep_id == TUSB_INDEX_INVALID_8) return; + + hcd_endpoint_t* edpt = &_hcd_data.edpt[ep_id]; + hcd_channel_t* channel = &_hcd_data.channel[ch_id]; + + ch_reg &= U_EPREG_MASK | CH_STAT_MASK(dir); + ch_reg &= ~(dir == TUSB_DIR_OUT ? U_EP_ERRTX : U_EP_ERRRX); + + hcd_channel_dir_t* channel_dir = + (dir == TUSB_DIR_OUT) ? &(_hcd_data.channel[ch_id].out) : &(_hcd_data.channel[ch_id].in); + if (channel_dir->retry < HCD_XFER_ERROR_MAX) { + // Retry + channel_dir->retry++; + ch_change_status(&ch_reg, dir, EP_STAT_VALID); + } else { + // Failed after retries + channel_dealloc(channel, dir); + ch_change_status(&ch_reg, dir, EP_STAT_DISABLED); + hcd_event_xfer_complete(daddr, ep_num | (dir == TUSB_DIR_IN ? TUSB_DIR_IN_MASK : 0), + edpt->queued_len, XFER_RESULT_FAILED, true); + } + ch_write(ch_id, ch_reg, false); +} + +// Handle CTR interrupt for the TX/OUT direction +static inline void handle_ctr_tx(uint32_t ch_id) { + uint32_t ch_reg = ch_read(ch_id) | U_EP_CTR_TX | U_EP_CTR_RX; + hcd_channel_t* channel = &_hcd_data.channel[ch_id]; + TU_VERIFY(channel->out.allocated == 1,); + + if ((ch_reg & U_EP_ERRTX) == 0U) { + // No error + if ((ch_reg & U_CH_TX_STTX) == U_CH_TX_ACK_SBUF) { + ch_handle_ack(ch_id, ch_reg, TUSB_DIR_OUT); + } else if ((ch_reg & U_CH_TX_STTX) == U_CH_TX_NAK) { + ch_handle_nak(ch_id, ch_reg, TUSB_DIR_OUT); + } else if ((ch_reg & U_CH_TX_STTX) == U_CH_TX_STALL) { + ch_handle_stall(ch_id, ch_reg, TUSB_DIR_OUT); + } + } else { + ch_handle_error(ch_id, ch_reg, TUSB_DIR_OUT); + } +} + +// Handle CTR interrupt for the RX/IN direction +static inline void handle_ctr_rx(uint32_t ch_id) { + uint32_t ch_reg = ch_read(ch_id) | U_EP_CTR_TX | U_EP_CTR_RX; + hcd_channel_t* channel = &_hcd_data.channel[ch_id]; + TU_VERIFY(channel->in.allocated == 1,); + + if ((ch_reg & U_EP_ERRRX) == 0U) { + // No error + if ((ch_reg & U_CH_RX_STRX) == U_CH_RX_ACK_SBUF) { + ch_handle_ack(ch_id, ch_reg, TUSB_DIR_IN); + } else if ((ch_reg & U_CH_RX_STRX) == U_CH_RX_NAK) { + ch_handle_nak(ch_id, ch_reg, TUSB_DIR_IN); + } else if ((ch_reg & U_CH_RX_STRX) == U_CH_RX_STALL){ + ch_handle_stall(ch_id, ch_reg, TUSB_DIR_IN); + } + } else { + ch_handle_error(ch_id, ch_reg, TUSB_DIR_IN); + } +} + +// Interrupt Handler +void hcd_int_handler(uint8_t rhport, bool in_isr) { + uint32_t int_status = FSDEV_REG->ISTR; + + // Start of Frame + if (int_status & U_ISTR_SOF) { + FSDEV_REG->ISTR = (fsdev_bus_t)~U_ISTR_SOF; + sof_handler(); + } + + // Port Change Detected (Connection/Disconnection) + if (int_status & U_ISTR_DCON) { + FSDEV_REG->ISTR = (fsdev_bus_t)~U_ISTR_DCON; + port_status_handler(rhport, in_isr); + } + + // Handle transfer complete (CTR) + while (FSDEV_REG->ISTR & U_ISTR_CTR) { + uint32_t const ch_id = FSDEV_REG->ISTR & U_ISTR_EP_ID; + uint32_t const ch_reg = ch_read(ch_id); + + if (ch_reg & U_EP_CTR_RX) { + ch_write_clear_ctr(ch_id, TUSB_DIR_IN); + handle_ctr_rx(ch_id); + } + + if (ch_reg & U_EP_CTR_TX) { + ch_write_clear_ctr(ch_id, TUSB_DIR_OUT); + handle_ctr_tx(ch_id); + } + } + + if (int_status & U_ISTR_ERR) { + FSDEV_REG->ISTR = (fsdev_bus_t)~U_ISTR_ERR; + // TODO: Handle error + } + + if (int_status & U_ISTR_PMAOVR) { + TU_BREAKPOINT(); + FSDEV_REG->ISTR = (fsdev_bus_t)~U_ISTR_PMAOVR; + } +} + +// Enable USB interrupt +void hcd_int_enable(uint8_t rhport) { + fsdev_int_enable(rhport); +} + +// Disable USB interrupt +void hcd_int_disable(uint8_t rhport) { + fsdev_int_disable(rhport); +} + +// Get frame number (1ms) +uint32_t hcd_frame_number(uint8_t rhport) { + (void) rhport; + return FSDEV_REG->FNR & U_FNR_FN; +} + +//--------------------------------------------------------------------+ +// Port API +//--------------------------------------------------------------------+ + +// Get the current connect status of roothub port +bool hcd_port_connect_status(uint8_t rhport) { + (void) rhport; + return _hcd_data.connected; +} + +// Reset USB bus on the port +void hcd_port_reset(uint8_t rhport) { + (void) rhport; + FSDEV_REG->CNTR |= U_CNTR_FRES; +} + +// Complete bus reset sequence +void hcd_port_reset_end(uint8_t rhport) { + (void) rhport; + FSDEV_REG->CNTR &= ~U_CNTR_FRES; +} + +// Get port link speed +tusb_speed_t hcd_port_speed_get(uint8_t rhport) { + (void) rhport; + if ((FSDEV_REG->ISTR & U_ISTR_LS_DCONN) != 0U) { + return TUSB_SPEED_LOW; + } else { + return TUSB_SPEED_FULL; + } +} + +// HCD closes all opened endpoints belonging to this device +void hcd_device_close(uint8_t rhport, uint8_t dev_addr) { + (void) rhport; + + // Close all endpoints for this device + for(uint32_t i = 0; i < CFG_TUH_FSDEV_ENDPOINT_MAX; i++) { + hcd_endpoint_t* edpt = &_hcd_data.edpt[i]; + if (edpt->allocated == 1 && edpt->dev_addr == dev_addr) { + edpoint_close(i); + } + } + +} + +//--------------------------------------------------------------------+ +// Endpoints API +//--------------------------------------------------------------------+ + +// Open an endpoint +bool hcd_edpt_open(uint8_t rhport, uint8_t dev_addr, tusb_desc_endpoint_t const *ep_desc) { + (void) rhport; + + uint8_t const ep_addr = ep_desc->bEndpointAddress; + uint16_t const packet_size = tu_edpt_packet_size(ep_desc); + uint8_t const ep_type = ep_desc->bmAttributes.xfer; + + uint8_t const ep_id = endpoint_alloc(); + TU_ASSERT(ep_id != TUSB_INDEX_INVALID_8); + + hcd_endpoint_t* edpt = &_hcd_data.edpt[ep_id]; + edpt->dev_addr = dev_addr; + edpt->ep_addr = ep_addr; + edpt->ep_type = ep_type; + edpt->max_packet_size = packet_size; + edpt->interval = ep_desc->bInterval; + edpt->pid = 0; + edpt->ls_pre = (hcd_port_speed_get(rhport) == TUSB_SPEED_FULL && tuh_speed_get(dev_addr) == TUSB_SPEED_LOW) ? 1 : 0; + + return true; +} + +bool hcd_edpt_close(uint8_t rhport, uint8_t dev_addr, uint8_t ep_addr) { + (void) rhport; + + uint8_t const ep_id = endpoint_find(dev_addr, ep_addr); + TU_ASSERT(ep_id != TUSB_INDEX_INVALID_8); + + edpoint_close(ep_id); + + return true; +} + +// Submit a transfer +bool hcd_edpt_xfer(uint8_t rhport, uint8_t dev_addr, uint8_t ep_addr, uint8_t *buffer, uint16_t buflen) { + (void) rhport; + + TU_LOG(FSDEV_DEBUG, "hcd_edpt_xfer addr=%u ep=0x%02X len=%u\r\n", dev_addr, ep_addr, buflen); + + uint8_t const ep_id = endpoint_find(dev_addr, ep_addr); + TU_ASSERT(ep_id != TUSB_INDEX_INVALID_8); + + hcd_endpoint_t *edpt = &_hcd_data.edpt[ep_id]; + + edpt->buffer = buffer; + edpt->buflen = buflen; + edpt->queued_len = 0; + + uint8_t const ep_num = tu_edpt_number(ep_addr); + if (ep_num == 0) { + // update ep_dir since control endpoint can switch direction + edpt->ep_addr = ep_addr; + } + + return edpt_xfer_kickoff(ep_id); +} + +// Abort a queued transfer +bool hcd_edpt_abort_xfer(uint8_t rhport, uint8_t dev_addr, uint8_t ep_addr) { + (void) rhport; + + uint8_t const ep_id = endpoint_find(dev_addr, ep_addr); + TU_ASSERT(ep_id != TUSB_INDEX_INVALID_8); + tusb_dir_t const dir = tu_edpt_dir(ep_addr); + + for (uint8_t i = 0; i < FSDEV_EP_COUNT; i++) { + hcd_channel_t* channel = &_hcd_data.channel[i]; + uint8_t const allocated = (dir == TUSB_DIR_OUT) ? channel->out.allocated : channel->in.allocated; + + if (allocated == 1 && + channel->dev_addr == dev_addr && + channel->ep_num == tu_edpt_number(ep_addr)) { + channel_dealloc(channel, dir); + uint32_t ch_reg = ch_read(i) | U_EP_CTR_TX | U_EP_CTR_RX; + channel_write_status(i, ch_reg, dir, EP_STAT_DISABLED, true); + } + } + + return true; +} + +// Submit a special transfer to send 8-byte Setup Packet +bool hcd_setup_send(uint8_t rhport, uint8_t dev_addr, uint8_t const setup_packet[8]) { + (void) rhport; + + uint8_t const ep_id = endpoint_find(dev_addr, 0); + TU_ASSERT(ep_id != TUSB_INDEX_INVALID_8); + + hcd_endpoint_t *edpt = &_hcd_data.edpt[ep_id]; + edpt->next_setup = true; + edpt->pid = 0; + + return hcd_edpt_xfer(rhport, dev_addr, 0, (uint8_t*)(uintptr_t) setup_packet, 8); +} + +// Clear stall, data toggle is also reset to DATA0 +bool hcd_edpt_clear_stall(uint8_t rhport, uint8_t dev_addr, uint8_t ep_addr) { + (void) rhport; + + uint8_t const ep_id = endpoint_find(dev_addr, ep_addr); + TU_ASSERT(ep_id != TUSB_INDEX_INVALID_8); + + hcd_endpoint_t *edpt = &_hcd_data.edpt[ep_id]; + edpt->pid = 0; + + return true; +} + +//--------------------------------------------------------------------+ +// Helper Functions +//--------------------------------------------------------------------+ + +static uint8_t endpoint_alloc(void) { + for (uint32_t i = 0; i < CFG_TUH_FSDEV_ENDPOINT_MAX; i++) { + hcd_endpoint_t* edpt = &_hcd_data.edpt[i]; + if (edpt->allocated == 0) { + edpt->allocated = 1; + return i; + } + } + return TUSB_INDEX_INVALID_8; +} + +static uint8_t endpoint_find(uint8_t dev_addr, uint8_t ep_addr) { + uint8_t const ep_num = tu_edpt_number(ep_addr); + tusb_dir_t const ep_dir = tu_edpt_dir(ep_addr); + + for (uint32_t i = 0; i < (uint32_t)CFG_TUH_FSDEV_ENDPOINT_MAX; i++) { + hcd_endpoint_t* edpt = &_hcd_data.edpt[i]; + tusb_dir_t const dir = tu_edpt_dir(edpt->ep_addr); + uint8_t const num = tu_edpt_number(edpt->ep_addr); + // Match both ep_num and ep_dir, or match ep_num 0 (control endpoint) + if (edpt->allocated == 1 && edpt->dev_addr == dev_addr && num == ep_num && + (dir == ep_dir || ep_num == 0)) { + return i; + } + } + return TUSB_INDEX_INVALID_8; +} + +// close an opened endpoint +static void edpoint_close(uint8_t ep_id) { + hcd_endpoint_t* edpt = &_hcd_data.edpt[ep_id]; + endpoint_dealloc(edpt); + + // disable active channel belong to this endpoint + for (uint8_t i = 0; i < FSDEV_EP_COUNT; i++) { + hcd_channel_t* channel = &_hcd_data.channel[i]; + uint32_t ch_reg = ch_read(i) | U_EP_CTR_TX | U_EP_CTR_RX; + if (channel->out.allocated == 1 && channel->out.edpt == edpt) { + channel_dealloc(channel, TUSB_DIR_OUT); + channel_write_status(i, ch_reg, TUSB_DIR_OUT, EP_STAT_DISABLED, true); + } + if (channel->in.allocated == 1 && channel->in.edpt == edpt) { + channel_dealloc(channel, TUSB_DIR_IN); + channel_write_status(i, ch_reg, TUSB_DIR_IN, EP_STAT_DISABLED, true); + } + } +} + +// Allocate PMA buffer +static uint32_t hcd_pma_alloc(uint8_t channel, tusb_dir_t dir, uint16_t len) { + (void) len; + // Simple static allocation as we are unlikely to handle ISO endpoints in host mode + // We just give each channel two buffers of max packet size (64 bytes) for IN and OUT + + uint16_t addr = FSDEV_BTABLE_BASE + 8 * FSDEV_EP_COUNT; + addr += channel * TUSB_EPSIZE_BULK_FS * 2 + (dir == TUSB_DIR_IN ? TUSB_EPSIZE_BULK_FS : 0); + + TU_ASSERT(addr <= CFG_TUSB_FSDEV_PMA_SIZE, 0xFFFF); + + return addr; +} + +// Allocate hardware channel +static uint8_t channel_alloc(uint8_t dev_addr, uint8_t ep_addr, uint8_t ep_type) { + uint8_t const ep_num = tu_edpt_number(ep_addr); + tusb_dir_t const dir = tu_edpt_dir(ep_addr); + + // Find channel allocate for same ep_num but other direction + tusb_dir_t const other_dir = (dir == TUSB_DIR_IN) ? TUSB_DIR_OUT : TUSB_DIR_IN; + for (uint8_t i = 0; i < FSDEV_EP_COUNT; i++) { + uint8_t const allocated_dir = (dir == TUSB_DIR_OUT) ? _hcd_data.channel[i].out.allocated : _hcd_data.channel[i].in.allocated; + uint8_t const allocated_other = (other_dir == TUSB_DIR_OUT) ? _hcd_data.channel[i].out.allocated : _hcd_data.channel[i].in.allocated; + if (allocated_dir == 0 && + allocated_other == 1 && + _hcd_data.channel[i].dev_addr == dev_addr && + _hcd_data.channel[i].ep_num == ep_num && + _hcd_data.channel[i].ep_type == ep_type) { + if (dir == TUSB_DIR_OUT) { + _hcd_data.channel[i].out.allocated = 1; + _hcd_data.channel[i].out.retry = 0; + } else { + _hcd_data.channel[i].in.allocated = 1; + _hcd_data.channel[i].in.retry = 0; + } + return i; + } + } + + // Find free channel + for (uint8_t i = 0; i < FSDEV_EP_COUNT; i++) { + if (_hcd_data.channel[i].out.allocated == 0 && _hcd_data.channel[i].in.allocated == 0) { + _hcd_data.channel[i].dev_addr = dev_addr; + _hcd_data.channel[i].ep_num = ep_num; + _hcd_data.channel[i].ep_type = ep_type; + if (dir == TUSB_DIR_OUT) { + _hcd_data.channel[i].out.allocated = 1; + _hcd_data.channel[i].out.retry = 0; + } else { + _hcd_data.channel[i].in.allocated = 1; + _hcd_data.channel[i].in.retry = 0; + } + return i; + } + } + + // Allocation failed + return TUSB_INDEX_INVALID_8; +} + +// kick-off transfer with an endpoint +static bool edpt_xfer_kickoff(uint8_t ep_id) { + hcd_endpoint_t* edpt = &_hcd_data.edpt[ep_id]; + uint8_t ch_id = channel_alloc(edpt->dev_addr, edpt->ep_addr, edpt->ep_type); + TU_ASSERT(ch_id != TUSB_INDEX_INVALID_8); // all channel are in used + + tusb_dir_t const dir = tu_edpt_dir(edpt->ep_addr); + hcd_channel_t* channel = &_hcd_data.channel[ch_id]; + if (dir == TUSB_DIR_OUT) { + channel->out.edpt = edpt; + } else { + channel->in.edpt = edpt; + } + + return channel_xfer_start(ch_id, dir); +} + +static bool channel_xfer_start(uint8_t ch_id, tusb_dir_t dir) { + hcd_channel_t* channel = &_hcd_data.channel[ch_id]; + hcd_endpoint_t* edpt = (dir == TUSB_DIR_OUT) ? channel->out.edpt : channel->in.edpt; + + uint32_t ch_reg = ch_read(ch_id) & ~U_EPREG_MASK; + ch_reg |= tu_edpt_number(edpt->ep_addr) | edpt->dev_addr << U_EP_DEVADDR_Pos | + U_EP_CTR_TX | U_EP_CTR_RX; + + // Set type + switch (edpt->ep_type) { + case TUSB_XFER_BULK: + ch_reg |= U_EP_BULK; + break; + case TUSB_XFER_INTERRUPT: + ch_reg |= U_EP_INTERRUPT; + break; + + case TUSB_XFER_CONTROL: + ch_reg |= U_EP_CONTROL; + break; + + default: + // Note: ISO endpoint is unsupported + TU_ASSERT(false); + } + + /* Create a packet memory buffer area. */ + uint16_t pma_addr = hcd_pma_alloc(ch_id, dir, edpt->max_packet_size); + btable_set_addr(ch_id, dir == TUSB_DIR_OUT ? BTABLE_BUF_TX : BTABLE_BUF_RX, pma_addr); + + if (dir == TUSB_DIR_OUT) { + uint16_t const len = tu_min16(edpt->buflen - edpt->queued_len, edpt->max_packet_size); + tu_hwfifo_write(PMA_BUF_AT(pma_addr), &(edpt->buffer[edpt->queued_len]), len, NULL); + btable_set_count(ch_id, BTABLE_BUF_TX, len); + + edpt->queued_len += len; + } else { + btable_set_rx_bufsize(ch_id, BTABLE_BUF_RX, edpt->max_packet_size); + } + + if (edpt->ls_pre == 1) { + ch_reg |= U_EP_LSEP; + } else { + ch_reg &= ~U_EP_LSEP; + } + + // Setup DATA/STATUS phase start with DATA1 + if (tu_edpt_number(edpt->ep_addr) == 0) { + edpt->pid = 1; + } + + if (edpt->next_setup) { + edpt->next_setup = false; + ch_reg |= U_EP_SETUP; + edpt->pid = 0; + } + + ch_change_status(&ch_reg, dir, EP_STAT_VALID); + ch_change_dtog(&ch_reg, dir, edpt->pid); + ch_reg &= U_EPREG_MASK | CH_STAT_MASK(dir) | CH_DTOG_MASK(dir); + ch_write(ch_id, ch_reg, true); + + return true; +} + +#endif diff --git a/libraries/Adafruit_TinyUSB_Arduino/src/portable/synopsys/dwc2/dcd_dwc2.c b/libraries/Adafruit_TinyUSB_Arduino/src/portable/synopsys/dwc2/dcd_dwc2.c new file mode 100644 index 0000000..30e24a9 --- /dev/null +++ b/libraries/Adafruit_TinyUSB_Arduino/src/portable/synopsys/dwc2/dcd_dwc2.c @@ -0,0 +1,1264 @@ +/* + * The MIT License (MIT) + * + * Copyright (c) 2019 William D. Jones + * 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 + * 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. + */ + +#include "tusb_option.h" + +#if CFG_TUD_ENABLED && defined(TUP_USBIP_DWC2) + +#if !(CFG_TUD_DWC2_SLAVE_ENABLE || CFG_TUD_DWC2_DMA_ENABLE) +#error DWC2 require either CFG_TUD_DWC2_SLAVE_ENABLE or CFG_TUD_DWC2_DMA_ENABLE to be enabled +#endif + +// Debug level for DWC2 +#define DWC2_DEBUG 2 + +#include "device/dcd.h" +#include "device/usbd.h" +#include "device/usbd_pvt.h" +#include "dwc2_common.h" + +//--------------------------------------------------------------------+ +// MACRO TYPEDEF CONSTANT ENUM +//--------------------------------------------------------------------+ +typedef struct { + uint8_t* buffer; + tu_fifo_t* ff; + uint16_t total_len; + uint16_t max_size; + uint8_t interval; + uint8_t iso_retry; // ISO retry counter +} xfer_ctl_t; + +// This variable is modified from ISR context, so it must be protected by critical section +static xfer_ctl_t xfer_status[DWC2_EP_MAX][2]; +#define XFER_CTL_BASE(_ep, _dir) (&xfer_status[_ep][_dir]) + +typedef struct { + // EP0 transfers are limited to 1 packet - larger sizes has to be split + uint16_t ep0_pending[2]; // Index determines direction as tusb_dir_t type + uint16_t dfifo_top; // top free location in DFIFO in words + + // Number of IN endpoints active + uint8_t allocated_epin_count; + + // SOF enabling flag - required for SOF to not get disabled in ISR when SOF was enabled by + bool sof_en; +} dcd_data_t; + +static dcd_data_t _dcd_data; + +CFG_TUD_MEM_SECTION static struct { + TUD_EPBUF_DEF(setup_packet, 8); +} _dcd_usbbuf; + +static tud_configure_dwc2_t _tud_cfg = CFG_TUD_CONFIGURE_DWC2_DEFAULT; + +TU_ATTR_ALWAYS_INLINE static inline uint8_t dwc2_ep_count(const dwc2_regs_t* dwc2) { + #if TU_CHECK_MCU(OPT_MCU_GD32VF103) + (void) dwc2; + return DWC2_EP_MAX; + #else + const dwc2_ghwcfg2_t ghwcfg2 = {.value = dwc2->ghwcfg2}; + return ghwcfg2.num_dev_ep + 1; + #endif +} + +//--------------------------------------------------------------------+ +// +//--------------------------------------------------------------------+ +TU_ATTR_ALWAYS_INLINE static inline bool edpt_is_enabled(dwc2_dep_t* dep) { + return (dep->ctl & EPCTL_EPENA) != 0; +} + + #if CFG_TUD_DWC2_SLAVE_ENABLE +static uint16_t epin_write_tx_fifo(dwc2_regs_t *dwc2, uint8_t epnum); + #endif + + //-------------------------------------------------------------------- + // DMA + //-------------------------------------------------------------------- + #if CFG_TUD_MEM_DCACHE_ENABLE +bool dcd_dcache_clean(const void* addr, uint32_t data_size) { + TU_VERIFY(addr && data_size); + return dwc2_dcache_clean(addr, data_size); +} + +bool dcd_dcache_invalidate(const void* addr, uint32_t data_size) { + TU_VERIFY(addr && data_size); + return dwc2_dcache_invalidate(addr, data_size); +} + +bool dcd_dcache_clean_invalidate(const void* addr, uint32_t data_size) { + TU_VERIFY(addr && data_size); + return dwc2_dcache_clean_invalidate(addr, data_size); +} +#endif + +TU_ATTR_ALWAYS_INLINE static inline bool dma_device_enabled(const dwc2_regs_t* dwc2) { + (void) dwc2; + // Internal DMA only + const dwc2_ghwcfg2_t ghwcfg2 = {.value = dwc2->ghwcfg2}; + return CFG_TUD_DWC2_DMA_ENABLE && ghwcfg2.arch == GHWCFG2_ARCH_INTERNAL_DMA; +} + +static void dma_setup_prepare(uint8_t rhport) { + dwc2_regs_t* dwc2 = DWC2_REG(rhport); + + if (dwc2->gsnpsid >= DWC2_CORE_REV_3_00a) { + if(edpt_is_enabled(&dwc2->epout[0])) { + return; + } + } + + // Receive only 1 packet + dwc2->epout[0].doeptsiz = (1 << DOEPTSIZ_STUPCNT_Pos) | (1 << DOEPTSIZ_PKTCNT_Pos) | (8 << DOEPTSIZ_XFRSIZ_Pos); + dwc2->epout[0].doepdma = (uintptr_t) _dcd_usbbuf.setup_packet; + dwc2->epout[0].doepctl |= DOEPCTL_EPENA | DOEPCTL_USBAEP; +} + +//--------------------------------------------------------------------+ +// Data FIFO +//--------------------------------------------------------------------+ + + +/* Device Data FIFO scheme + The controller has a single SPRAM of otg_dfifo_depth 32-bit words shared between all FIFOs and optional DMA metadata. + otg_dfifo_depth = ghwcfg3.dfifo_depth + EP_LOC_CNT. It is split up into: + + - EPInfo: for storing DMA address registers (DxEPDMAn), only required when DMA is used. + gdfifocfg.EPINFOBASE and gdfifocfg.GDFIFOCfg must be configured before gahbcfg.dmaen is set. + The number of words needed per endpoint direction depends on the DMA mode used at runtime: + - Buffer DMA mode: 1 word per endpoint direction + - Scatter/Gather DMA mode: 4 words per endpoint direction + - TX FIFO: one fifo for each IN endpoint. Size is dynamic depending on packet size, starting from top with EP0 IN. + - Shared RX FIFO: a shared fifo for all OUT endpoints. Typically, can hold up to 2 packets of the largest EP size. + + We allocate TX FIFOs from top to bottom (using a top pointer), this to allow the RX FIFO to grow dynamically, which is + possible since the free space is located between the RX and TX FIFOs. + + --------------- otg_dfifo_depth + | EPInfo | DxEPDMAn (DMA only, sized per runtime DMA mode) + |-------------|-- gdfifocfg.EPINFOBASE (start of EPInfo; FIFO space sized by GDFIFOCFG) + | IN FIFO 0 | control EP + |-------------| + | IN FIFO 1 | + |-------------| + | . . . . | + |-------------| + | IN FIFO n | + |-------------| + | FREE | + |-------------|-- GRXFSIZ (expandable) + | OUT FIFO | + | ( Shared ) | + --------------- 0 + + According to "FIFO RAM allocation" section in RM, FIFO RAM are allocated as follows (each word 32-bits): + - Each EP IN needs at least max packet size + - All EP OUT shared a unique OUT FIFO which uses (for Slave or Buffer DMA, Scatt/Gather DMA use different formula): + - 13 for setup packets + control words (up to 3 setup packets). + - 1 for global NAK (not required/used here). + - Largest-EPsize/4 + 1. (FS: 64 bytes, HS: 512 bytes). Recommended is "2 x (Largest-EPsize/4 + 1)" + - 2 for each used OUT endpoint. + + Therefore, GRXFSIZ = 13 + 1 + 2 x (Largest-EPsize/4 + 1) + 2 x EPOUTnum +*/ + +TU_ATTR_ALWAYS_INLINE static inline uint16_t calc_device_grxfsiz(uint16_t largest_ep_size, uint8_t ep_count) { + return (uint16_t)(13 + 1 + 2 * ((largest_ep_size / 4) + 1) + 2 * ep_count); +} + +static bool dfifo_alloc(uint8_t rhport, uint8_t ep_addr, uint16_t packet_size, bool is_bulk) { + dwc2_regs_t* dwc2 = DWC2_REG(rhport); + const dwc2_controller_t* dwc2_controller = &_dwc2_controller[rhport]; + const uint8_t ep_count = dwc2_controller->ep_count; + const uint8_t epnum = tu_edpt_number(ep_addr); + const uint8_t dir = tu_edpt_dir(ep_addr); + + TU_ASSERT(epnum < ep_count); + + uint16_t fifo_size = (uint16_t)tu_div_ceil(packet_size, 4); + if (dir == TUSB_DIR_OUT) { + // Calculate required size of RX FIFO + const uint16_t new_sz = calc_device_grxfsiz(4 * fifo_size, ep_count); + + // If size_rx needs to be extended check if there is enough free space + if (dwc2->grxfsiz < new_sz) { + TU_ASSERT(new_sz <= _dcd_data.dfifo_top); + dwc2->grxfsiz = new_sz; // Enlarge RX FIFO + } + } else { + // Check IN endpoints concurrently active limit + if(0 != dwc2_controller->ep_in_count) { + TU_ASSERT(_dcd_data.allocated_epin_count < dwc2_controller->ep_in_count); + _dcd_data.allocated_epin_count++; + } + + // Enable double buffering if configured, only effective for non-periodic endpoints + // Since we queue only 1 control transfer at a time, it's only applicable for bulk IN endpoints + if (((_tud_cfg.bm_double_buffered & (1 << epnum)) != 0) && epnum > 0 && is_bulk) { + fifo_size *= 2; + } + + // Check if free space is available + TU_ASSERT(_dcd_data.dfifo_top >= fifo_size + dwc2->grxfsiz); + _dcd_data.dfifo_top -= fifo_size; + // TU_LOG(DWC2_DEBUG, " TX FIFO %u: allocated %u words at offset %u\r\n", epnum, fifo_size, dfifo_top); + + // Both TXFD and TXSA are in unit of 32-bit words. + if (epnum == 0) { + dwc2->dieptxf0 = ((uint32_t) fifo_size << DIEPTXF0_TX0FD_Pos) | _dcd_data.dfifo_top; + } else { + // DIEPTXF starts at FIFO #1. + dwc2->dieptxf[epnum - 1] = ((uint32_t) fifo_size << DIEPTXF_INEPTXFD_Pos) | _dcd_data.dfifo_top; + } + } + + return true; +} + +static void dfifo_device_init(uint8_t rhport) { + const dwc2_controller_t* dwc2_controller = &_dwc2_controller[rhport]; + dwc2_regs_t* dwc2 = DWC2_REG(rhport); + dwc2->grxfsiz = calc_device_grxfsiz(CFG_TUD_ENDPOINT0_SIZE, dwc2_controller->ep_count); + + // Scatter/Gather DMA mode is not yet supported. Buffer DMA only need 1 words per endpoint direction + const bool is_dma = dma_device_enabled(dwc2); + _dcd_data.dfifo_top = dwc2_controller->otg_dfifo_depth; + if (is_dma) { + _dcd_data.dfifo_top -= 2 * dwc2_controller->ep_count; + } + dwc2->gdfifocfg = ((uint32_t) _dcd_data.dfifo_top << GDFIFOCFG_EPINFOBASE_SHIFT) | _dcd_data.dfifo_top; + + // Allocate FIFO for EP0 IN + (void) dfifo_alloc(rhport, 0x80, CFG_TUD_ENDPOINT0_SIZE, false); +} + + +//-------------------------------------------------------------------- +// Endpoint +//-------------------------------------------------------------------- +static void edpt_activate(uint8_t rhport, const tusb_desc_endpoint_t* p_endpoint_desc) { + dwc2_regs_t* dwc2 = DWC2_REG(rhport); + const uint8_t epnum = tu_edpt_number(p_endpoint_desc->bEndpointAddress); + const uint8_t dir = tu_edpt_dir(p_endpoint_desc->bEndpointAddress); + + xfer_ctl_t* xfer = XFER_CTL_BASE(epnum, dir); + xfer->max_size = tu_edpt_packet_size(p_endpoint_desc); + + const dwc2_dsts_t dsts = {.value = dwc2->dsts}; + if (dsts.enum_speed == DCFG_SPEED_HIGH) { + xfer->interval = 1 << (p_endpoint_desc->bInterval - 1); + } else { + xfer->interval = p_endpoint_desc->bInterval; + } + + // Endpoint control + dwc2_depctl_t depctl = {.value = 0}; + depctl.mps = xfer->max_size; + depctl.active = 1; + depctl.type = p_endpoint_desc->bmAttributes.xfer; + if (p_endpoint_desc->bmAttributes.xfer != TUSB_XFER_ISOCHRONOUS) { + depctl.set_data0_iso_even = 1; + } + if (dir == TUSB_DIR_IN) { + depctl.tx_fifo_num = epnum; + } + + dwc2_dep_t* dep = &dwc2->ep[dir == TUSB_DIR_IN ? 0 : 1][epnum]; + dep->ctl = depctl.value; + dwc2->daintmsk |= TU_BIT(epnum + DAINT_SHIFT(dir)); +} + +static void edpt_disable(uint8_t rhport, uint8_t ep_addr, bool stall) { + (void) rhport; + + dwc2_regs_t* dwc2 = DWC2_REG(rhport); + const uint8_t epnum = tu_edpt_number(ep_addr); + const uint8_t dir = tu_edpt_dir(ep_addr); + dwc2_dep_t* dep = &dwc2->ep[dir == TUSB_DIR_IN ? 0 : 1][epnum]; + + const uint32_t stall_mask = (stall ? EPCTL_STALL : 0); + + if (dir == TUSB_DIR_IN) { + if (!edpt_is_enabled(dep)) { + dep->diepctl |= DIEPCTL_SNAK | stall_mask; + } else { + // Stop transmitting packets and NAK IN xfers. + dep->diepctl |= DIEPCTL_SNAK; + while ((dep->diepint & DIEPINT_INEPNE) == 0) {} + + // Disable the endpoint. + dep->diepctl |= DIEPCTL_EPDIS | stall_mask; + while ((dep->diepint & DIEPINT_EPDISD_Msk) == 0) {} + + dep->diepint = DIEPINT_EPDISD; + } + + // Flush the FIFO, and wait until we have confirmed it cleared. + dfifo_flush_tx(dwc2, epnum); + } else { + if (!edpt_is_enabled(dep) || epnum == 0) { + // non-control not-enabled: stall if set + // For EP0 Out, keep it enabled to receive SETUP packets + dep->doepctl |= stall_mask; + } else { + // Asserting GONAK is required to STALL an OUT endpoint. + // Simpler to use polling here, we don't use the "B"OUTNAKEFF interrupt + // anyway, and it can't be cleared by user code. If this while loop never + // finishes, we have bigger problems than just the stack. + dwc2->dctl |= DCTL_SGONAK; + while ((dwc2->gintsts & GINTSTS_BOUTNAKEFF_Msk) == 0) {} + + // Ditto here disable the endpoint. + dep->doepctl |= DOEPCTL_EPDIS | stall_mask; + while ((dep->doepint & DOEPINT_EPDISD_Msk) == 0) {} + + dep->doepint = DOEPINT_EPDISD; + + // Allow other OUT endpoints to keep receiving. + dwc2->dctl |= DCTL_CGONAK; + } + } + + // Clear ActEP + if (!stall && epnum != 0) { + dep->ctl &= ~EPCTL_USBAEP; + } +} + +// Since this function returns void, it is not possible to return a boolean success message +// We must make sure that this function is not called when the EP is disabled +// Must be called from critical section +static void edpt_schedule_packets(uint8_t rhport, const uint8_t epnum, const uint8_t dir) { + dwc2_regs_t* dwc2 = DWC2_REG(rhport); + xfer_ctl_t* const xfer = XFER_CTL_BASE(epnum, dir); + dwc2_dep_t* dep = &dwc2->ep[dir == TUSB_DIR_IN ? 0 : 1][epnum]; + + uint16_t num_packets; + uint16_t total_bytes; + + // EP0 is limited to one packet per xfer + if (epnum == 0) { + total_bytes = tu_min16(_dcd_data.ep0_pending[dir], CFG_TUD_ENDPOINT0_SIZE); + _dcd_data.ep0_pending[dir] -= total_bytes; + num_packets = 1; + } else { + total_bytes = xfer->total_len; + num_packets = (uint16_t)tu_div_ceil(total_bytes, xfer->max_size); + if (num_packets == 0) { + num_packets = 1; // zero length packet still count as 1 + } + } + + // transfer size: A full OUT transfer (multiple packets, possibly) triggers XFRC. + dwc2_ep_tsize_t deptsiz = {.value = 0}; + deptsiz.xfer_size = total_bytes; + deptsiz.packet_count = num_packets; + dep->tsiz = deptsiz.value; + + // control + dwc2_depctl_t depctl = {.value = dep->ctl}; + depctl.clear_nak = 1; + depctl.enable = 1; + if (depctl.type == DEPCTL_EPTYPE_ISOCHRONOUS) { + const dwc2_dsts_t dsts = {.value = dwc2->dsts}; + const uint32_t odd_now = dsts.frame_number & 1u; + if (odd_now != 0) { + depctl.set_data0_iso_even = 1; + } else { + depctl.set_data1_iso_odd = 1; + } + } + + #if CFG_TUD_DWC2_DMA_ENABLE + const bool is_dma = dma_device_enabled(dwc2); + if(is_dma) { + if (dir == TUSB_DIR_IN && total_bytes != 0) { + dcd_dcache_clean(xfer->buffer, total_bytes); + } + dep->diepdma = (uintptr_t) xfer->buffer; + dep->diepctl = depctl.value; // enable endpoint + // Advance buffer pointer for EP0 + if (epnum == 0) { + xfer->buffer += total_bytes; + } + } else + #endif + { + #if CFG_TUD_DWC2_SLAVE_ENABLE + dep->diepctl = depctl.value; // enable endpoint + + if (dir == TUSB_DIR_IN && total_bytes != 0) { + const uint16_t xferred_bytes = epin_write_tx_fifo(dwc2, epnum); + + // Enable TXFE interrupt if there are still data to be sent + // EP0 only sends one packet at a time, so no need to check for EP0 + if ((epnum != 0) && (xfer->total_len - xferred_bytes > 0)) { + dwc2->diepempmsk |= (1u << epnum); + } + } + #endif + } +} + +//-------------------------------------------------------------------- +// Controller API +//-------------------------------------------------------------------- +// optional dcd configuration, called by tud_configure() +bool dcd_configure(uint8_t rhport, uint32_t cfg_id, const void* cfg_param) { + (void) rhport; + TU_VERIFY(cfg_id == TUD_CFGID_DWC2 && cfg_param != NULL); + + const tud_configure_param_t* const cfg = (const tud_configure_param_t*) cfg_param; + _tud_cfg = cfg->dwc2; + return true; +} + +bool dcd_init(uint8_t rhport, const tusb_rhport_init_t* rh_init) { + dwc2_clock_init(rhport, rh_init->role); + + tu_memclr(&_dcd_data, sizeof(_dcd_data)); + + // Core Initialization + dwc2_regs_t* dwc2 = DWC2_REG(rhport); + const bool is_hs_phy = dwc2_core_is_highspeed_phy(dwc2, TUD_OPT_HIGH_SPEED); + const bool is_dma = dma_device_enabled(dwc2); + TU_ASSERT(dwc2_core_init(rhport, is_hs_phy, is_dma)); + + //------------- 7.1 Device Initialization -------------// + // Set device max speed + uint32_t dcfg = dwc2->dcfg & ~DCFG_DSPD_Msk; + if (is_hs_phy) { + // dcfg Highspeed's mask is 0 + + // XCVRDLY: transceiver delay between xcvr_sel and txvalid during device chirp is required + // when using with some PHYs such as USB334x (USB3341, USB3343, USB3346, USB3347) + const dwc2_ghwcfg2_t ghwcfg2 = {.value = dwc2->ghwcfg2}; + if (ghwcfg2.hs_phy_type == GHWCFG2_HSPHY_ULPI) { + dcfg |= DCFG_XCVRDLY; + } + } else { + dcfg |= DCFG_DSPD_FS << DCFG_DSPD_Pos; + } + + dcfg |= DCFG_NZLSOHSK; // send STALL back and discard if host send non-zlp during control status + dwc2->dcfg = dcfg; + + dcd_disconnect(rhport); + + // Force device mode + dwc2->gusbcfg = (dwc2->gusbcfg & ~GUSBCFG_FHMOD) | GUSBCFG_FDMOD; + + // OTG Ctrl + uint32_t gotgctl = dwc2->gotgctl & ~GOTGCTL_AVALOEN; // Clear A-override + if (!_tud_cfg.vbus_sensing) { + gotgctl |= GOTGCTL_BVALOEN | GOTGCTL_BVALOVAL; // force B Valid if not sensing VBus + } + dwc2->gotgctl = gotgctl; + + #ifdef TUP_USBIP_DWC2_STM32 + dwc2_stm32_gccfg_cfg(dwc2, _tud_cfg.vbus_sensing, false); + #endif + + // Enable required interrupts + dwc2->gintmsk |= GINTMSK_OTGINT | GINTMSK_USBRST | GINTMSK_ENUMDNEM | GINTMSK_WUIM; + + uint32_t gahbcfg = dwc2->gahbcfg; + gahbcfg |= GAHBCFG_GINT; // Enable global interrupt + dwc2->gahbcfg = gahbcfg; + + dcd_connect(rhport); + return true; +} + +bool dcd_deinit(uint8_t rhport) { + dcd_disconnect(rhport); + dwc2_core_deinit(rhport); + return true; +} + +void dcd_int_enable(uint8_t rhport) { + dwc2_dcd_int_enable(rhport); +} + +void dcd_int_disable(uint8_t rhport) { + dwc2_dcd_int_disable(rhport); +} + +void dcd_set_address(uint8_t rhport, uint8_t dev_addr) { + dwc2_regs_t* dwc2 = DWC2_REG(rhport); + dwc2->dcfg = (dwc2->dcfg & ~DCFG_DAD_Msk) | (dev_addr << DCFG_DAD_Pos); + + // Response with status after changing device address + dcd_edpt_xfer(rhport, tu_edpt_addr(0, TUSB_DIR_IN), NULL, 0, false); +} + +void dcd_remote_wakeup(uint8_t rhport) { + (void) rhport; + + dwc2_regs_t* dwc2 = DWC2_REG(rhport); + + // set remote wakeup + dwc2->dctl |= DCTL_RWUSIG; + + // enable SOF to detect bus resume + dwc2->gintsts = GINTSTS_SOF; + dwc2->gintmsk |= GINTMSK_SOFM; + + // Per specs: remote wakeup signal bit must be clear within 1-15ms + dwc2_remote_wakeup_delay(); + + dwc2->dctl &= ~DCTL_RWUSIG; +} + +void dcd_connect(uint8_t rhport) { + dwc2_regs_t* dwc2 = DWC2_REG(rhport); + +#if defined(TUP_USBIP_DWC2_ESP32) && !TU_CHECK_MCU(OPT_MCU_ESP32S31) + // S31 is excluded at compile time (no USB_WRAP peripheral). + // On P4, the HS PHY (port 1) must not touch USB_WRAP which belongs to the FS PHY. + if (rhport == 0) { + usb_wrap_otg_conf_reg_t conf = USB_WRAP.otg_conf; + conf.pad_pull_override = 0; + conf.dp_pullup = 0; + conf.dp_pulldown = 0; + conf.dm_pullup = 0; + conf.dm_pulldown = 0; + USB_WRAP.otg_conf = conf; + } +#endif + + dwc2->dctl &= ~DCTL_SDIS; +} + +void dcd_disconnect(uint8_t rhport) { + dwc2_regs_t* dwc2 = DWC2_REG(rhport); + +#if defined(TUP_USBIP_DWC2_ESP32) && !TU_CHECK_MCU(OPT_MCU_ESP32S31) + // S31 is excluded at compile time (no USB_WRAP peripheral). + // On P4, the HS PHY (port 1) must not touch USB_WRAP which belongs to the FS PHY. + if (rhport == 0) { + usb_wrap_otg_conf_reg_t conf = USB_WRAP.otg_conf; + conf.pad_pull_override = 1; + conf.dp_pullup = 0; + conf.dp_pulldown = 1; + conf.dm_pullup = 0; + conf.dm_pulldown = 1; + USB_WRAP.otg_conf = conf; + } +#endif + + dwc2->dctl |= DCTL_SDIS; +} + +// Be advised: audio, video and possibly other iso-ep classes use dcd_sof_enable() to enable/disable its corresponding ISR on purpose! +void dcd_sof_enable(uint8_t rhport, bool en) { + (void) rhport; + dwc2_regs_t* dwc2 = DWC2_REG(rhport); + + _dcd_data.sof_en = en; + + if (en) { + dwc2->gintsts = GINTSTS_SOF; + dwc2->gintmsk |= GINTMSK_SOFM; + } else { + dwc2->gintmsk &= ~GINTMSK_SOFM; + } +} + +/*------------------------------------------------------------------*/ +/* DCD Endpoint port + *------------------------------------------------------------------*/ + +bool dcd_edpt_open(uint8_t rhport, tusb_desc_endpoint_t const* desc_edpt) { + TU_ASSERT(dfifo_alloc(rhport, desc_edpt->bEndpointAddress, tu_edpt_packet_size(desc_edpt), + desc_edpt->bmAttributes.xfer == TUSB_XFER_BULK)); + edpt_activate(rhport, desc_edpt); + return true; +} + +// Close all non-control endpoints, cancel all pending transfers if any. +void dcd_edpt_close_all(uint8_t rhport) { + dwc2_regs_t* dwc2 = DWC2_REG(rhport); + uint8_t const ep_count = _dwc2_controller[rhport].ep_count; + + usbd_spin_lock(false); + + _dcd_data.allocated_epin_count = 0; + + // Disable non-control interrupt + dwc2->daintmsk = (1 << DAINTMSK_OEPM_Pos) | (1 << DAINTMSK_IEPM_Pos); + + for (uint8_t n = 1; n < ep_count; n++) { + for (uint8_t d = 0; d < 2; d++) { + dwc2_dep_t* dep = &dwc2->ep[d][n]; + if (edpt_is_enabled(dep)) { + dep->ctl |= EPCTL_SNAK | EPCTL_EPDIS; + } + xfer_status[n][1-d].max_size = 0; + } + } + + dfifo_flush_tx(dwc2, 0x10); // all tx fifo + dfifo_flush_rx(dwc2); + dfifo_device_init(rhport); // re-init dfifo + + usbd_spin_unlock(false); +} + +bool dcd_edpt_iso_alloc(uint8_t rhport, uint8_t ep_addr, uint16_t largest_packet_size) { + TU_ASSERT(dfifo_alloc(rhport, ep_addr, largest_packet_size, false)); + return true; +} + +bool dcd_edpt_iso_activate(uint8_t rhport, tusb_desc_endpoint_t const * p_endpoint_desc) { + // Disable EP to clear potential incomplete transfers + edpt_disable(rhport, p_endpoint_desc->bEndpointAddress, false); + edpt_activate(rhport, p_endpoint_desc); + return true; +} + +bool dcd_edpt_xfer(uint8_t rhport, uint8_t ep_addr, uint8_t* buffer, uint16_t total_bytes, bool is_isr) { + (void) is_isr; + uint8_t const epnum = tu_edpt_number(ep_addr); + uint8_t const dir = tu_edpt_dir(ep_addr); + xfer_ctl_t* xfer = XFER_CTL_BASE(epnum, dir); + bool ret; + + usbd_spin_lock(is_isr); + + if (xfer->max_size == 0) { + ret = false; // Endpoint is closed + } else { + xfer->buffer = buffer; + xfer->ff = NULL; + xfer->total_len = total_bytes; + xfer->iso_retry = xfer->interval; // Reset ISO retry counter to interval value + + // EP0 can only handle one packet + if (epnum == 0) { + _dcd_data.ep0_pending[dir] = total_bytes; + } + + // Schedule packets to be sent within interrupt + edpt_schedule_packets(rhport, epnum, dir); + ret = true; + } + + usbd_spin_unlock(is_isr); + + return ret; +} + +// The number of bytes has to be given explicitly to allow more flexible control of how many +// bytes should be written and second to keep the return value free to give back a boolean +// success message. If total_bytes is too big, the FIFO will copy only what is available +// into the USB buffer! +bool dcd_edpt_xfer_fifo(uint8_t rhport, uint8_t ep_addr, tu_fifo_t* ff, uint16_t total_bytes, bool is_isr) { + (void) is_isr; + uint8_t const epnum = tu_edpt_number(ep_addr); + uint8_t const dir = tu_edpt_dir(ep_addr); + xfer_ctl_t* xfer = XFER_CTL_BASE(epnum, dir); + bool ret; + + usbd_spin_lock(is_isr); + + if (xfer->max_size == 0) { + ret = false; // Endpoint is closed + } else { + xfer->buffer = NULL; + xfer->ff = ff; + xfer->total_len = total_bytes; + xfer->iso_retry = xfer->interval; // Reset ISO retry counter to interval value + + // Schedule packets to be sent within interrupt + // TODO xfer fifo may only available for slave mode + edpt_schedule_packets(rhport, epnum, dir); + ret = true; + } + + usbd_spin_unlock(is_isr); + + return ret; +} + +void dcd_edpt_stall(uint8_t rhport, uint8_t ep_addr) { + dwc2_regs_t* dwc2 = DWC2_REG(rhport); + edpt_disable(rhport, ep_addr, true); + + // For control endpoint, prepare to receive SETUP packet + if (tu_edpt_number(ep_addr) == 0) { + if (dma_device_enabled(dwc2)) { + dma_setup_prepare(rhport); + } + } +} + +void dcd_edpt_clear_stall(uint8_t rhport, uint8_t ep_addr) { + dwc2_regs_t* dwc2 = DWC2_REG(rhport); + uint8_t const epnum = tu_edpt_number(ep_addr); + uint8_t const dir = tu_edpt_dir(ep_addr); + dwc2_dep_t* dep = &dwc2->ep[dir == TUSB_DIR_IN ? 0 : 1][epnum]; + + // Clear stall and reset data toggle + dep->ctl &= ~EPCTL_STALL;; + dep->ctl |= EPCTL_SD0PID_SEVNFRM; +} + +//-------------------------------------------------------------------- +// Interrupt Handler +//-------------------------------------------------------------------- + +// 7.4.1 Initialization on USB Reset +// Must be called from critical section +static void handle_bus_reset(uint8_t rhport) { + dwc2_regs_t *dwc2 = DWC2_REG(rhport); + const uint8_t ep_count = dwc2_ep_count(dwc2); + + tu_memclr(xfer_status, sizeof(xfer_status)); + + _dcd_data.sof_en = false; + _dcd_data.allocated_epin_count = 0; + + // 1. NAK for all OUT endpoints + for (uint8_t n = 0; n < ep_count; n++) { + dwc2->epout[n].doepctl |= DOEPCTL_SNAK; + } + + // Disable all IN endpoints + for (uint8_t n = 0; n < ep_count; n++) { + dwc2_dep_t* dep = &dwc2->epin[n]; + if (edpt_is_enabled(dep)) { + dep->diepctl |= DIEPCTL_SNAK | DIEPCTL_EPDIS; + } + } + + // 2. Set up interrupt mask for EP0 + dwc2->daintmsk = TU_BIT(DAINTMSK_OEPM_Pos) | TU_BIT(DAINTMSK_IEPM_Pos); + dwc2->doepmsk = DOEPMSK_STUPM | DOEPMSK_XFRCM; + dwc2->diepmsk = DIEPMSK_TOM | DIEPMSK_XFRCM; + + // 4. Set up DFIFO + dfifo_flush_tx(dwc2, 0x10); // all tx fifo + dfifo_flush_rx(dwc2); + dfifo_device_init(rhport); + + // 5. Reset device address + dwc2_dcfg_t dcfg = {.value = dwc2->dcfg}; + dcfg.address = 0; + dwc2->dcfg = dcfg.value; + + // 6. Configure maximum packet size for EP0 + uint8_t mps = 0; + switch (CFG_TUD_ENDPOINT0_SIZE) { + case 8: mps = 3; break; + case 16: mps = 2; break; + case 32: mps = 1; break; + case 64: mps = 0; break; + default: mps = 0; break; + } + + dwc2->epin[0].ctl &= ~DIEPCTL0_MPSIZ_Msk; + dwc2->epout[0].ctl &= ~DOEPCTL0_MPSIZ_Msk; + dwc2->epin[0].ctl |= mps << DIEPCTL0_MPSIZ_Pos; + dwc2->epout[0].ctl |= mps << DOEPCTL0_MPSIZ_Pos; + + xfer_status[0][TUSB_DIR_OUT].max_size = CFG_TUD_ENDPOINT0_SIZE; + xfer_status[0][TUSB_DIR_IN].max_size = CFG_TUD_ENDPOINT0_SIZE; + + if(dma_device_enabled(dwc2)) { + dma_setup_prepare(rhport); + } else { + dwc2->epout[0].doeptsiz |= (3 << DOEPTSIZ_STUPCNT_Pos); + } + + dwc2->gintmsk |= GINTMSK_OTGINT | GINTMSK_OEPINT | GINTMSK_IEPINT | GINTMSK_IISOIXFRM; +} + +static void handle_enum_done(uint8_t rhport) { + dwc2_regs_t *dwc2 = DWC2_REG(rhport); + const dwc2_dsts_t dsts = {.value = dwc2->dsts}; + tusb_speed_t speed; + switch (dsts.enum_speed) { + case DCFG_SPEED_HIGH: + speed = TUSB_SPEED_HIGH; + break; + + case DCFG_SPEED_LOW: + speed = TUSB_SPEED_LOW; + break; + + case DCFG_SPEED_FULL_30_60MHZ: + case DCFG_SPEED_FULL_48MHZ: + default: + speed = TUSB_SPEED_FULL; + break; + } + + // TODO must update GUSBCFG_TRDT according to link speed + dcd_event_bus_reset(rhport, speed, true); +} + +#if 0 +TU_ATTR_ALWAYS_INLINE static inline void print_doepint(uint32_t doepint) { + const char* str[] = { + "XFRC", "DIS", "AHBERR", "SETUP_DONE", + "ORXED", "STATUS_RX", "SETUP_B2B", "RSV7", + "OPERR", "BNA", "RSV10", "ISODROP", + "BBLERR", "NAK", "NYET", "SETUP_RX" + }; + + for(uint32_t i=0; iep[0][epnum]; + xfer_ctl_t *const xfer = XFER_CTL_BASE(epnum, TUSB_DIR_IN); + + dwc2_ep_tsize_t tsiz = {.value = epin->tsiz}; + const uint16_t remain_packets = tsiz.packet_count; + + uint16_t total_bytes_written = 0; + // Process every single packet (only whole packets can be written to fifo) + for (uint16_t i = 0; i < remain_packets; i++) { + tsiz.value = epin->tsiz; + const uint16_t remain_bytes = (uint16_t)tsiz.xfer_size; + const uint16_t xact_bytes = tu_min16(remain_bytes, xfer->max_size); + + // Check if dtxfsts has enough space available + if (xact_bytes > ((epin->dtxfsts & DTXFSTS_INEPTFSAV_Msk) << 2)) { + break; + } + + // Push packet to Tx-FIFO + volatile uint32_t *tx_fifo = dwc2->fifo[epnum]; + if (xfer->ff) { + tu_hwfifo_write_from_fifo(tx_fifo, xfer->ff, xact_bytes, NULL); + total_bytes_written += xact_bytes; + } else { + tu_hwfifo_write(tx_fifo, xfer->buffer, xact_bytes, NULL); + xfer->buffer += xact_bytes; + total_bytes_written += xact_bytes; + } + } + return total_bytes_written; +} + +// Process shared receive FIFO, this interrupt is only used in Slave mode +static void handle_rxflvl_irq(uint8_t rhport) { + dwc2_regs_t* dwc2 = DWC2_REG(rhport); + const volatile uint32_t* rx_fifo = dwc2->fifo[0]; + + // Pop control word off FIFO + const dwc2_grxstsp_t grxstsp = {.value = dwc2->grxstsp}; + const uint8_t epnum = grxstsp.ep_ch_num; + + dwc2_dep_t* epout = &dwc2->epout[epnum]; + + switch (grxstsp.packet_status) { + case GRXSTS_PKTSTS_GLOBAL_OUT_NAK: + // Global OUT NAK: do nothing + break; + + case GRXSTS_PKTSTS_SETUP_RX: { + // Setup packet received + uint32_t* setup = (uint32_t*)(uintptr_t) _dcd_usbbuf.setup_packet; + // We can receive up to three setup packets in succession, but only the last one is valid. + setup[0] = (*rx_fifo); + setup[1] = (*rx_fifo); + break; + } + + case GRXSTS_PKTSTS_SETUP_DONE: + // Setup packet done: + // After popping this out, dwc2 asserts a DOEPINT_SETUP interrupt which is handled by handle_epout_irq() + epout->doeptsiz |= (3 << DOEPTSIZ_STUPCNT_Pos); + break; + + case GRXSTS_PKTSTS_RX_DATA: { + // Out packet received + const uint16_t byte_count = grxstsp.byte_count; + xfer_ctl_t* xfer = XFER_CTL_BASE(epnum, TUSB_DIR_OUT); + + if (byte_count != 0) { + // Read packet off RxFIFO + if (xfer->ff != NULL) { + tu_hwfifo_read_to_fifo(rx_fifo, xfer->ff, byte_count, NULL); + } else { + tu_hwfifo_read(rx_fifo, xfer->buffer, byte_count, NULL); + xfer->buffer += byte_count; + } + } + + // short packet (including ZLP when byte_count == 0), minus remaining bytes (xfer_size) + if (byte_count < xfer->max_size) { + const dwc2_ep_tsize_t tsiz = {.value = epout->tsiz}; + xfer->total_len -= tsiz.xfer_size; + if (epnum == 0) { + _dcd_data.ep0_pending[TUSB_DIR_OUT] = 0; + } + } + break; + } + + case GRXSTS_PKTSTS_RX_COMPLETE: + // Out packet done + // After this entry is popped from the receive FIFO, dwc2 asserts a Transfer Completed interrupt on + // the specified OUT endpoint which will be handled by handle_epout_irq() + break; + + default: break; // nothing to do + } +} + +static void handle_epout_slave(uint8_t rhport, uint8_t epnum, dwc2_doepint_t doepint_bm) { + if (doepint_bm.setup_phase_done) { + // Cleanup previous pending EP0 IN transfer if any + dwc2_dep_t* epin0 = &DWC2_REG(rhport)->epin[0]; + if (edpt_is_enabled(epin0)) { + edpt_disable(rhport, 0x80, false); + } + dcd_event_setup_received(rhport, _dcd_usbbuf.setup_packet, true); + return; + } + + // Normal OUT transfer complete + if (doepint_bm.xfer_complete) { + // only handle data skip if it is setup or status related + // Note: even though (xfer_complete + status_phase_rx) is for buffered DMA only, for STM32L47x (dwc2 v3.00a) they + // can is set when GRXSTS_PKTSTS_SETUP_RX is popped therefore they can bet set before/together with setup_phase_done + if (!doepint_bm.status_phase_rx && !doepint_bm.setup_packet_rx) { + xfer_ctl_t* xfer = XFER_CTL_BASE(epnum, TUSB_DIR_OUT); + if ((epnum == 0) && _dcd_data.ep0_pending[TUSB_DIR_OUT]) { + // EP0 can only handle one packet, Schedule another packet to be received. + edpt_schedule_packets(rhport, epnum, TUSB_DIR_OUT); + } else { + dcd_event_xfer_complete(rhport, epnum, xfer->total_len, XFER_RESULT_SUCCESS, true); + } + } + } +} + +static void handle_epin_slave(uint8_t rhport, uint8_t epnum, dwc2_diepint_t diepint_bm) { + dwc2_regs_t* dwc2 = DWC2_REG(rhport); + dwc2_dep_t* epin = &dwc2->epin[epnum]; + xfer_ctl_t* xfer = XFER_CTL_BASE(epnum, TUSB_DIR_IN); + + if (diepint_bm.xfer_complete) { + if ((epnum == 0) && (0 != _dcd_data.ep0_pending[TUSB_DIR_IN])) { + // EP0 can only handle one packet. Schedule another packet to be transmitted. + edpt_schedule_packets(rhport, epnum, TUSB_DIR_IN); + } else { + dcd_event_xfer_complete(rhport, epnum | TUSB_DIR_IN_MASK, xfer->total_len, XFER_RESULT_SUCCESS, true); + } + } + + // TX FIFO empty bit is read-only. It will only be cleared by hardware when written bytes is more than + // - 64 bytes or + // - Half/Empty of TX FIFO size (configured by GAHBCFG.TXFELVL) + if (diepint_bm.txfifo_empty && tu_bit_test(dwc2->diepempmsk, epnum)) { + epin_write_tx_fifo(dwc2, epnum); + + // Turn off TXFE if all bytes are written. + dwc2_ep_tsize_t tsiz = {.value = epin->tsiz}; + if (tsiz.xfer_size == 0) { + dwc2->diepempmsk &= ~(1u << epnum); + } + } +} +#endif + +#if CFG_TUD_DWC2_DMA_ENABLE +static void handle_epout_dma(uint8_t rhport, uint8_t epnum, dwc2_doepint_t doepint_bm) { + dwc2_regs_t* dwc2 = DWC2_REG(rhport); + + if (doepint_bm.setup_phase_done) { + // Cleanup previous pending EP0 IN transfer if any + dwc2_dep_t* epin0 = &DWC2_REG(rhport)->epin[0]; + if (edpt_is_enabled(epin0)) { + edpt_disable(rhport, 0x80, false); + } + dma_setup_prepare(rhport); + dcd_dcache_invalidate(_dcd_usbbuf.setup_packet, 8); + dcd_event_setup_received(rhport, _dcd_usbbuf.setup_packet, true); + return; + } + + // OUT XFER complete + if (doepint_bm.xfer_complete) { + // only handle data skip if it is setup or status related + // Normal OUT transfer complete + if (!doepint_bm.status_phase_rx && !doepint_bm.setup_packet_rx) { + if ((epnum == 0) && _dcd_data.ep0_pending[TUSB_DIR_OUT]) { + // EP0 can only handle one packet Schedule another packet to be received. + edpt_schedule_packets(rhport, epnum, TUSB_DIR_OUT); + } else { + dwc2_dep_t* epout = &dwc2->epout[epnum]; + xfer_ctl_t* xfer = XFER_CTL_BASE(epnum, TUSB_DIR_OUT); + + // determine actual received bytes + const dwc2_ep_tsize_t tsiz = {.value = epout->tsiz}; + const uint16_t remain = tsiz.xfer_size; + xfer->total_len -= remain; + + // this is ZLP, so prepare EP0 for next setup + // TODO use status phase rx + if(epnum == 0 && xfer->total_len == 0) { + dma_setup_prepare(rhport); + } + + dcd_dcache_invalidate(xfer->buffer, xfer->total_len); + dcd_event_xfer_complete(rhport, epnum, xfer->total_len, XFER_RESULT_SUCCESS, true); + } + } + } +} + +static void handle_epin_dma(uint8_t rhport, uint8_t epnum, dwc2_diepint_t diepint_bm) { + xfer_ctl_t* xfer = XFER_CTL_BASE(epnum, TUSB_DIR_IN); + + if (diepint_bm.xfer_complete) { + if ((epnum == 0) && _dcd_data.ep0_pending[TUSB_DIR_IN]) { + // EP0 can only handle one packet. Schedule another packet to be transmitted. + edpt_schedule_packets(rhport, epnum, TUSB_DIR_IN); + } else { + if(epnum == 0) { + dma_setup_prepare(rhport); + } + dcd_event_xfer_complete(rhport, epnum | TUSB_DIR_IN_MASK, xfer->total_len, XFER_RESULT_SUCCESS, true); + } + } +} +#endif + +static void handle_ep_irq(uint8_t rhport, uint8_t dir) { + dwc2_regs_t* dwc2 = DWC2_REG(rhport); + const bool is_dma = dma_device_enabled(dwc2); + const uint8_t ep_count = dwc2_ep_count(dwc2); + const uint8_t daint_offset = (dir == TUSB_DIR_IN) ? DAINT_IEPINT_Pos : DAINT_OEPINT_Pos; + dwc2_dep_t* ep_base = &dwc2->ep[dir == TUSB_DIR_IN ? 0 : 1][0]; + + // DAINT for a given EP clears when DEPINTx is cleared. + // EPINT will be cleared when DAINT bits are cleared. + for (uint8_t epnum = 0; epnum < ep_count; epnum++) { + if (tu_bit_test(dwc2->daint,daint_offset + epnum)) { + dwc2_dep_t* epout = &ep_base[epnum]; + union { + uint32_t value; + dwc2_diepint_t diepint_bm; + dwc2_doepint_t doepint_bm; + } intr; + intr.value = epout->intr; + + epout->intr = intr.value; // Clear interrupt //-V::2584::{otg_int} + + if (is_dma) { + #if CFG_TUD_DWC2_DMA_ENABLE + if (dir == TUSB_DIR_IN) { + handle_epin_dma(rhport, epnum, intr.diepint_bm); + } else { + handle_epout_dma(rhport, epnum, intr.doepint_bm); + } + #endif + } else { + #if CFG_TUD_DWC2_SLAVE_ENABLE + if (dir == TUSB_DIR_IN) { + handle_epin_slave(rhport, epnum, intr.diepint_bm); + } else { + handle_epout_slave(rhport, epnum, intr.doepint_bm); + } + #endif + } + } + } +} + +static void handle_incomplete_iso_in(uint8_t rhport) { + dwc2_regs_t *dwc2 = DWC2_REG(rhport); + const dwc2_dsts_t dsts = {.value = dwc2->dsts}; + const uint32_t odd_now = dsts.frame_number & 1u; + + // Loop over all IN endpoints + const uint8_t ep_count = dwc2_ep_count(dwc2); + for (uint8_t epnum = 0; epnum < ep_count; epnum++) { + dwc2_dep_t *epin = &dwc2->epin[epnum]; + dwc2_depctl_t depctl = {.value = epin->diepctl}; + // Read DSTS and DIEPCTLn for all isochronous endpoints. If the current EP is enabled and the read value of + // DSTS.SOFFN is the targeted uframe number for this EP, then this EP has an incomplete transfer. + if (depctl.enable && depctl.type == DEPCTL_EPTYPE_ISOCHRONOUS && depctl.dpid_iso_odd == odd_now) { + xfer_ctl_t *xfer = XFER_CTL_BASE(epnum, TUSB_DIR_IN); + if (xfer->iso_retry > 0) { + xfer->iso_retry--; + // Restart ISO transfe: re-write TSIZ and CTL + dwc2_ep_tsize_t deptsiz = {.value = 0}; + deptsiz.xfer_size = xfer->total_len; + deptsiz.packet_count = tu_div_ceil(xfer->total_len, xfer->max_size); + epin->tsiz = deptsiz.value; + + if (odd_now) { + depctl.set_data0_iso_even = 1; + } else { + depctl.set_data1_iso_odd = 1; + } + epin->diepctl = depctl.value; + } else { + // too many retries, give up + edpt_disable(rhport, epnum | TUSB_DIR_IN_MASK, false); + dcd_event_xfer_complete(rhport, epnum | TUSB_DIR_IN_MASK, 0, XFER_RESULT_FAILED, true); + } + } + } +} + +/* Interrupt Hierarchy + DIEPINT DIEPINT + \ / + \ / + DAINT + / \ + / \ + GINTSTS: OEPInt IEPInt | USBReset | EnumDone | USBSusp | WkUpInt | OTGInt | SOF | RXFLVL + + Note: when OTG_MULTI_PROC_INTRPT = 1, Device Each endpoint interrupt deachint/deachmsk/diepeachmsk/doepeachmsk + are combined to generate dedicated interrupt line for each endpoint. + */ +void dcd_int_handler(uint8_t rhport) { + dwc2_regs_t* dwc2 = DWC2_REG(rhport); + const uint32_t gintmask = dwc2->gintmsk; + const uint32_t gintsts = dwc2->gintsts & gintmask; + + if (gintsts & GINTSTS_USBRST) { + // USBRST is start of reset. + dwc2->gintsts = GINTSTS_USBRST; + + usbd_spin_lock(true); + handle_bus_reset(rhport); + usbd_spin_unlock(true); + } + + if (gintsts & GINTSTS_ENUMDNE) { + // ENUMDNE is the end of reset where speed of the link is detected + dwc2->gintsts = GINTSTS_ENUMDNE; + // There may be a pending suspend event, so we clear it first + dwc2->gintsts = GINTSTS_USBSUSP; + dwc2->gintmsk |= GINTMSK_USBSUSPM; + handle_enum_done(rhport); + } + + if (gintsts & GINTSTS_USBSUSP) { + dwc2->gintsts = GINTSTS_USBSUSP; + dwc2->gintmsk &= ~GINTMSK_USBSUSPM; + dcd_event_bus_signal(rhport, DCD_EVENT_SUSPEND, true); + } + + if (gintsts & GINTSTS_WKUINT) { + dwc2->gintsts = GINTSTS_WKUINT; + dwc2->gintmsk |= GINTMSK_USBSUSPM; + dcd_event_bus_signal(rhport, DCD_EVENT_RESUME, true); + } + + // TODO check GINTSTS_DISCINT for disconnect detection + // if(int_status & GINTSTS_DISCINT) + + if (gintsts & GINTSTS_OTGINT) { + // OTG INT bit is read-only + const uint32_t otg_int = dwc2->gotgint; + + if (otg_int & GOTGINT_SEDET) { + dwc2->gintmsk &= ~GINTMSK_OTGINT; + dcd_event_bus_signal(rhport, DCD_EVENT_UNPLUGGED, true); + } + + dwc2->gotgint = otg_int; + } + + if(gintsts & GINTSTS_SOF) { + dwc2->gintsts = GINTSTS_SOF; + dwc2->gintmsk |= GINTMSK_USBSUSPM; + const uint32_t frame = (dwc2->dsts & DSTS_FNSOF) >> DSTS_FNSOF_Pos; + + // Disable SOF interrupt if SOF was not explicitly enabled since SOF was used for remote wakeup detection + if (!_dcd_data.sof_en) { + dwc2->gintmsk &= ~GINTMSK_SOFM; + } + + dcd_event_sof(rhport, frame, true); + } + +#if CFG_TUD_DWC2_SLAVE_ENABLE + // RxFIFO non-empty interrupt handling. + if (gintsts & GINTSTS_RXFLVL) { + // RXFLVL bit is read-only + dwc2->gintmsk &= ~GINTMSK_RXFLVLM; // disable RXFLVL interrupt while reading + + do { + handle_rxflvl_irq(rhport); // read all packets + } while(dwc2->gintsts & GINTSTS_RXFLVL); + + dwc2->gintmsk |= GINTMSK_RXFLVLM; + } +#endif + + // OUT endpoint interrupt handling. + if (gintsts & GINTSTS_OEPINT) { + // OEPINT is read-only, clear using DOEPINTn + handle_ep_irq(rhport, TUSB_DIR_OUT); + } + + // IN endpoint interrupt handling. + if (gintsts & GINTSTS_IEPINT) { + // IEPINT bit read-only, clear using DIEPINTn + handle_ep_irq(rhport, TUSB_DIR_IN); + } + + // Incomplete isochronous IN transfer interrupt handling. + if (gintsts & GINTSTS_IISOIXFR) { + dwc2->gintsts = GINTSTS_IISOIXFR; + handle_incomplete_iso_in(rhport); + } +} + +#if CFG_TUD_TEST_MODE +void dcd_enter_test_mode(uint8_t rhport, tusb_feature_test_mode_t test_selector) { + dwc2_regs_t* dwc2 = DWC2_REG(rhport); + + // Enable the test mode + dwc2->dctl = (dwc2->dctl & ~DCTL_TCTL_Msk) | (((uint8_t) test_selector) << DCTL_TCTL_Pos); +} +#endif + +#endif diff --git a/libraries/Adafruit_TinyUSB_Arduino/src/portable/synopsys/dwc2/dwc2_at32.h b/libraries/Adafruit_TinyUSB_Arduino/src/portable/synopsys/dwc2/dwc2_at32.h new file mode 100644 index 0000000..85fa9b2 --- /dev/null +++ b/libraries/Adafruit_TinyUSB_Arduino/src/portable/synopsys/dwc2/dwc2_at32.h @@ -0,0 +1,139 @@ +/* + * The MIT License (MIT) + * + * Copyright (c) 2021, 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. + * + * This file is part of the TinyUSB stack. + */ + + +#ifndef DWC2_AT32_H_ +#define DWC2_AT32_H_ + +#define DWC2_EP_MAX TUP_DCD_ENDPOINT_MAX + +#if CFG_TUSB_MCU == OPT_MCU_AT32F415 + #include + #define OTG1_DFIFO_DEPTH 320 + #define OTG1_IRQn OTGFS1_IRQn + #define DWC2_OTG1_REG_BASE 0x50000000UL +#elif CFG_TUSB_MCU == OPT_MCU_AT32F435_437 + #include + #define OTG1_DFIFO_DEPTH 320 + #define OTG2_DFIFO_DEPTH 320 + #define OTG1_IRQn OTGFS1_IRQn + #define OTG2_IRQn OTGFS2_IRQn + #define DWC2_OTG1_REG_BASE 0x50000000UL + #define DWC2_OTG2_REG_BASE 0x40040000UL +#elif CFG_TUSB_MCU == OPT_MCU_AT32F423 + #include + #define OTG1_DFIFO_DEPTH 320 + #define OTG1_IRQn OTGFS1_IRQn + #define DWC2_OTG1_REG_BASE 0x50000000UL +#elif CFG_TUSB_MCU == OPT_MCU_AT32F402_405 + #include + #define OTG1_DFIFO_DEPTH 320 + #define OTG2_DFIFO_DEPTH 1024 + #define OTG1_IRQn OTGFS1_IRQn + #define OTG2_IRQn OTGHS_IRQn + #define DWC2_OTG1_REG_BASE 0x50000000UL + #define DWC2_OTG2_REG_BASE 0x40040000UL //OTGHS +#elif CFG_TUSB_MCU == OPT_MCU_AT32F425 + #include + #define OTG1_DFIFO_DEPTH 320 + #define OTG1_IRQn OTGFS1_IRQn + #define DWC2_OTG1_REG_BASE 0x50000000UL +#elif CFG_TUSB_MCU == OPT_MCU_AT32F45X + #include + #define OTG1_DFIFO_DEPTH 320 + #define OTG1_IRQn OTGFS1_IRQn + #define DWC2_OTG1_REG_BASE 0x50000000UL +#endif + +#ifdef __cplusplus +extern "C" { +#endif + +static const dwc2_controller_t _dwc2_controller[] = { + {.reg_base = DWC2_OTG1_REG_BASE, .irqnum = OTG1_IRQn, .ep_count = DWC2_EP_MAX, .otg_dfifo_depth = OTG1_DFIFO_DEPTH}, +#if defined DWC2_OTG2_REG_BASE + {.reg_base = DWC2_OTG2_REG_BASE, .irqnum = OTG2_IRQn, .ep_count = DWC2_EP_MAX, .otg_dfifo_depth = OTG2_DFIFO_DEPTH} +#endif +}; + +// MCU specific to enable dwc2 clock/power before any access to register +TU_ATTR_ALWAYS_INLINE static inline void dwc2_clock_init(uint8_t rhport, tusb_role_t role) { + (void) rhport; + (void) role; +} + +TU_ATTR_ALWAYS_INLINE static inline void dwc2_int_set(uint8_t rhport, tusb_role_t role, bool enabled) { + (void) role; + const IRQn_Type irqn = (IRQn_Type) _dwc2_controller[rhport].irqnum; + if (enabled) { + NVIC_EnableIRQ(irqn); + } else { + NVIC_DisableIRQ(irqn); + } +} + +TU_ATTR_ALWAYS_INLINE static inline void dwc2_dcd_int_enable(uint8_t rhport) { + NVIC_EnableIRQ(_dwc2_controller[rhport].irqnum); +} + +TU_ATTR_ALWAYS_INLINE static inline void dwc2_dcd_int_disable(uint8_t rhport) { + NVIC_DisableIRQ(_dwc2_controller[rhport].irqnum); +} + +TU_ATTR_ALWAYS_INLINE static inline void dwc2_remote_wakeup_delay(void) { + // try to delay for 1 ms + uint32_t count = system_core_clock / 1000; + while (count--) __asm volatile("nop"); +} + +// MCU specific PHY init, called BEFORE core reset +TU_ATTR_ALWAYS_INLINE static inline void dwc2_phy_init(dwc2_regs_t *dwc2, uint8_t hs_phy_type) { + (void) dwc2; + // Enable on-chip HS PHY + if (hs_phy_type == GHWCFG2_HSPHY_UTMI || hs_phy_type == GHWCFG2_HSPHY_UTMI_ULPI) { + } else if (hs_phy_type == GHWCFG2_HSPHY_NOT_SUPPORTED) { + } +} + +// MCU specific PHY deinit, disable PHY power +TU_ATTR_ALWAYS_INLINE static inline void dwc2_phy_deinit(dwc2_regs_t *dwc2, uint8_t hs_phy_type) { + (void) hs_phy_type; + dwc2->stm32_gccfg &= ~(STM32_GCCFG_PWRDWN | STM32_GCCFG_DCDEN | STM32_GCCFG_PDEN); +} + +// MCU specific PHY update, it is called AFTER init() and core reset +TU_ATTR_ALWAYS_INLINE static inline void dwc2_phy_update(dwc2_regs_t *dwc2, uint8_t hs_phy_type) { + (void) dwc2; + (void) hs_phy_type; + + dwc2->stm32_gccfg |= STM32_GCCFG_PWRDWN | STM32_GCCFG_DCDEN | STM32_GCCFG_PDEN; +} + +#ifdef __cplusplus +} +#endif + +#endif /* DWC2_AT32_H_ */ diff --git a/libraries/Adafruit_TinyUSB_Arduino/src/portable/synopsys/dwc2/dwc2_bcm.h b/libraries/Adafruit_TinyUSB_Arduino/src/portable/synopsys/dwc2/dwc2_bcm.h new file mode 100644 index 0000000..91cae82 --- /dev/null +++ b/libraries/Adafruit_TinyUSB_Arduino/src/portable/synopsys/dwc2/dwc2_bcm.h @@ -0,0 +1,102 @@ +/* + * The MIT License (MIT) + * + * Copyright (c) 2021, 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. + * + * This file is part of the TinyUSB stack. + */ + +#ifndef TUSB_DWC2_BCM_H_ +#define TUSB_DWC2_BCM_H_ + +#ifdef __cplusplus + extern "C" { +#endif + +#include "broadcom/defines.h" +#include "broadcom/interrupts.h" +#include "broadcom/caches.h" + +#define DWC2_EP_MAX 8 + +static const dwc2_controller_t _dwc2_controller[] = +{ + { .reg_base = USB_OTG_GLOBAL_BASE, .irqnum = USB_IRQn, .ep_count = DWC2_EP_MAX, .otg_dfifo_depth = 4096 } +}; + +#define dcache_clean(_addr, _size) data_clean(_addr, _size) +#define dcache_invalidate(_addr, _size) data_invalidate(_addr, _size) +#define dcache_clean_invalidate(_addr, _size) data_clean_and_invalidate(_addr, _size) + +// MCU specific to enable dwc2 clock/power before any access to register +TU_ATTR_ALWAYS_INLINE static inline void dwc2_clock_init(uint8_t rhport, tusb_role_t role) { + (void) rhport; + (void) role; +} + +TU_ATTR_ALWAYS_INLINE +static inline void dwc2_dcd_int_enable(uint8_t rhport) +{ + BP_EnableIRQ(_dwc2_controller[rhport].irqnum); +} + +TU_ATTR_ALWAYS_INLINE +static inline void dwc2_dcd_int_disable (uint8_t rhport) +{ + BP_DisableIRQ(_dwc2_controller[rhport].irqnum); +} + +static inline void dwc2_remote_wakeup_delay(void) +{ + // try to delay for 1 ms + // TODO implement later +} + +// MCU specific PHY init, called BEFORE core reset +static inline void dwc2_phy_init(dwc2_regs_t * dwc2, uint8_t hs_phy_type) +{ + (void) dwc2; + (void) hs_phy_type; + + // nothing to do +} + +// MCU specific PHY deinit, disable PHY power +static inline void dwc2_phy_deinit(dwc2_regs_t * dwc2, uint8_t hs_phy_type) { + (void) dwc2; + (void) hs_phy_type; + // nothing to do +} + +// MCU specific PHY update, it is called AFTER init() and core reset +static inline void dwc2_phy_update(dwc2_regs_t * dwc2, uint8_t hs_phy_type) +{ + (void) dwc2; + (void) hs_phy_type; + + // nothing to do +} + +#ifdef __cplusplus + } +#endif + +#endif diff --git a/libraries/Adafruit_TinyUSB_Arduino/src/portable/synopsys/dwc2/dwc2_common.c b/libraries/Adafruit_TinyUSB_Arduino/src/portable/synopsys/dwc2/dwc2_common.c new file mode 100644 index 0000000..33eabae --- /dev/null +++ b/libraries/Adafruit_TinyUSB_Arduino/src/portable/synopsys/dwc2/dwc2_common.c @@ -0,0 +1,277 @@ +/* + * The MIT License (MIT) + * + * Copyright (c) 2024-2025 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. + * + * This file is part of the TinyUSB stack. + */ + +#include "tusb_option.h" + +#define DWC2_COMMON_DEBUG 2 + +#if defined(TUP_USBIP_DWC2) && (CFG_TUH_ENABLED || CFG_TUD_ENABLED) +#include "dwc2_common.h" + +//-------------------------------------------------------------------- +// +//-------------------------------------------------------------------- +static void reset_core(dwc2_regs_t* dwc2) { + // The software must check that bit 31 in this register is set to 1 (AHB Master is Idle) before starting any operation + while (!(dwc2->grstctl & GRSTCTL_AHBIDL)) { + } + + const uint32_t gsnpsid = dwc2->gsnpsid; // preload gsnpsid which is not readable while resetting + dwc2->grstctl |= GRSTCTL_CSRST; // reset core + + if ((gsnpsid & DWC2_CORE_REV_MASK) < (DWC2_CORE_REV_4_20a & DWC2_CORE_REV_MASK)) { + // prior v4.20a: CSRST is self-clearing, and the core clears this bit after all the necessary logic is reset in + // the core, which can take several clocks, depending on the current state of the core. Once this bit has been + // cleared, the software must wait at least 3 PHY clocks before accessing the PHY domain (synchronization delay). + while (dwc2->grstctl & GRSTCTL_CSRST) {} + } else { + // From v4.20a: CSRST bit is write only. The application must clear this bit after checking the bit 29 of this + // register i.e Core Soft Reset Done CSRT_DONE (w1c) + while (!(dwc2->grstctl & GRSTCTL_CSRST_DONE)) {} + dwc2->grstctl = (dwc2->grstctl & ~GRSTCTL_CSRST) | GRSTCTL_CSRST_DONE; + } + + while (!(dwc2->grstctl & GRSTCTL_AHBIDL)) {} // wait for AHB master IDLE +} + +// Dedicated FS PHY is internal with a clock 48Mhz. +static void phy_fs_init(dwc2_regs_t* dwc2) { + TU_LOG(DWC2_COMMON_DEBUG, "Fullspeed PHY init\r\n"); + + uint32_t gusbcfg = dwc2->gusbcfg; + + // Select FS PHY + gusbcfg |= GUSBCFG_PHYSEL; + dwc2->gusbcfg = gusbcfg; + + // MCU specific PHY init before reset + dwc2_phy_init(dwc2, GHWCFG2_HSPHY_NOT_SUPPORTED); + + // Reset core after selecting PHY + reset_core(dwc2); + + // USB turnaround time is critical for certification where long cables and 5-Hubs are used. + // So if you need the AHB to run at less than 30 MHz, and if USB turnaround time is not critical, + // these bits can be programmed to a larger value. Default is 5 + gusbcfg &= ~GUSBCFG_TRDT_Msk; + gusbcfg |= 5u << GUSBCFG_TRDT_Pos; + dwc2->gusbcfg = gusbcfg; + + // MCU specific PHY update post reset + dwc2_phy_update(dwc2, GHWCFG2_HSPHY_NOT_SUPPORTED); +} + +/* dwc2 has 2 highspeed PHYs options + * - UTMI+ is internal highspeed PHY, can be clocked at 30 Mhz (8-bit) or 60 Mhz (16-bit). + * - ULPI is external highspeed PHY, clocked at 60Mhz with 8-bit interface. + * + * In addition, UTMI+/ULPI can be shared to run at fullspeed mode with 48Mhz + */ +static void phy_hs_init(dwc2_regs_t* dwc2) { + uint32_t gusbcfg = dwc2->gusbcfg; + const dwc2_ghwcfg2_t ghwcfg2 = {.value = dwc2->ghwcfg2}; + const dwc2_ghwcfg4_t ghwcfg4 = {.value = dwc2->ghwcfg4}; + + uint8_t phy_width; + if (CFG_TUSB_MCU != OPT_MCU_AT32F402_405 && // at32f402_405 does not support 16-bit + ghwcfg4.phy_data_width) { + phy_width = 16; // 16-bit PHY interface if supported + } else { + phy_width = 8; // 8-bit PHY interface + } + + // De-select FS PHY + gusbcfg &= ~GUSBCFG_PHYSEL; + + if (ghwcfg2.hs_phy_type == GHWCFG2_HSPHY_ULPI) { + TU_LOG(DWC2_COMMON_DEBUG, "Highspeed ULPI PHY init\r\n"); + + // Select ULPI PHY (external) + gusbcfg |= GUSBCFG_ULPI_UTMI_SEL; + + // ULPI is always 8-bit interface + gusbcfg &= ~GUSBCFG_PHYIF16; + + // ULPI select single data rate + gusbcfg &= ~GUSBCFG_DDRSEL; + + // default internal VBUS Indicator and Drive + gusbcfg &= ~(GUSBCFG_ULPIEVBUSD | GUSBCFG_ULPIEVBUSI); + + // Disable FS/LS ULPI + gusbcfg &= ~(GUSBCFG_ULPIFSLS | GUSBCFG_ULPICSM); + } else { + TU_LOG(DWC2_COMMON_DEBUG, "Highspeed UTMI+ PHY init\r\n"); + + // Select UTMI+ PHY (internal) + gusbcfg &= ~GUSBCFG_ULPI_UTMI_SEL; + + // Set 16-bit interface if supported + if (phy_width == 16) { + gusbcfg |= GUSBCFG_PHYIF16; + } else { + gusbcfg &= ~GUSBCFG_PHYIF16; + } + } + + // Apply config + dwc2->gusbcfg = gusbcfg; + + // mcu specific phy init + dwc2_phy_init(dwc2, ghwcfg2.hs_phy_type); + + // Reset core after selecting PHY + reset_core(dwc2); + + // Set turn-around, must after core reset otherwise it will be clear + // - 9 if using 8-bit PHY interface + // - 5 if using 16-bit PHY interface + gusbcfg &= ~GUSBCFG_TRDT_Msk; + gusbcfg |= (phy_width == 16 ? 5u : 9u) << GUSBCFG_TRDT_Pos; + dwc2->gusbcfg = gusbcfg; + + // MCU specific PHY update post reset + dwc2_phy_update(dwc2, ghwcfg2.hs_phy_type); +} + +static bool check_dwc2(dwc2_regs_t* dwc2) { +#if CFG_TUSB_DEBUG >= DWC2_COMMON_DEBUG + // print guid, gsnpsid, ghwcfg1, ghwcfg2, ghwcfg3, ghwcfg4 + // Run 'python dwc2_info.py' and check dwc2_info.md for bit-field value and comparison with other ports + volatile uint32_t const* p = (volatile uint32_t const*) &dwc2->guid; + TU_LOG1("guid, gsnpsid, ghwcfg1, ghwcfg2, ghwcfg3, ghwcfg4\r\n"); + for (size_t i = 0; i < 5; i++) { + TU_LOG1("0x%08" PRIX32 ", ", p[i]); + } + TU_LOG1("0x%08" PRIX32 "\r\n", p[5]); +#endif + + // For some reason: GD32VF103 gsnpsid and all hwcfg register are always zero (skip it) + (void)dwc2; +#if !TU_CHECK_MCU(OPT_MCU_GD32VF103) + enum { GSNPSID_ID_MASK = TU_GENMASK(31, 16) }; + const uint32_t gsnpsid = dwc2->gsnpsid & GSNPSID_ID_MASK; + TU_ASSERT(gsnpsid == DWC2_OTG_ID || gsnpsid == DWC2_FS_IOT_ID || gsnpsid == DWC2_HS_IOT_ID); +#endif + + return true; +} + +//-------------------------------------------------------------------- +// +//-------------------------------------------------------------------- +bool dwc2_core_is_highspeed_phy(dwc2_regs_t* dwc2, bool prefer_hs_phy) { + const dwc2_ghwcfg2_t ghwcfg2 = {.value = dwc2->ghwcfg2}; + const bool has_hs_phy = (ghwcfg2.hs_phy_type != GHWCFG2_HSPHY_NOT_SUPPORTED); + + if (prefer_hs_phy) { + return has_hs_phy; + } else { + const bool has_fs_phy = (ghwcfg2.fs_phy_type != GHWCFG2_FSPHY_NOT_SUPPORTED); + // false if has fs phy, otherwise true since hs phy is the only available phy + return !has_fs_phy && has_hs_phy; + } +} + +bool dwc2_core_init(uint8_t rhport, bool is_hs_phy, bool is_dma) { + dwc2_regs_t* dwc2 = DWC2_REG(rhport); + + // Check Synopsys ID register, failed if controller clock/power is not enabled + TU_ASSERT(check_dwc2(dwc2)); + + // disable global interrupt + dwc2->gahbcfg &= ~GAHBCFG_GINT; + + if (is_hs_phy) { + phy_hs_init(dwc2); + } else { + phy_fs_init(dwc2); + } + + /* Set HS/FS Timeout Calibration to 7 (max available value). + * The number of PHY clocks that the application programs in + * this field is added to the high/full speed interpacket timeout + * duration in the core to account for any additional delays + * introduced by the PHY. This can be required, because the delay + * introduced by the PHY in generating the linestate condition + * can vary from one PHY to another. */ + dwc2->gusbcfg |= (7ul << GUSBCFG_TOCAL_Pos); + + // Enable PHY clock TODO stop/gate clock when suspended mode + dwc2->pcgcctl &= ~(PCGCCTL_STOPPCLK | PCGCCTL_GATEHCLK | PCGCCTL_PWRCLMP | PCGCCTL_RSTPDWNMODULE); + + dfifo_flush_tx(dwc2, 0x10); // all tx fifo + dfifo_flush_rx(dwc2); + + // Clear pending and disable all interrupts + dwc2->gintsts = 0xFFFFFFFFU; + dwc2->gotgint = 0xFFFFFFFFU; + dwc2->gintmsk = 0; + + TU_LOG(DWC2_COMMON_DEBUG, "DMA = %u\r\n", is_dma); + + if (is_dma) { + // DMA seems to be only settable after a core reset, and not possible to switch on-the-fly + dwc2->gahbcfg |= GAHBCFG_DMAEN | GAHBCFG_HBSTLEN_2; + } else { + dwc2->gintmsk |= GINTSTS_RXFLVL; + } + + return true; +} + +void dwc2_core_deinit(uint8_t rhport) { + dwc2_regs_t* dwc2 = DWC2_REG(rhport); + + // Disable global interrupt + dwc2->gahbcfg &= ~GAHBCFG_GINT; + + // Reset core: this also flushes FIFOs and clears all interrupt registers + reset_core(dwc2); + + // Stop PHY clock and gate HCLK for power saving (per databook chapter 14) + dwc2->pcgcctl |= PCGCCTL_STOPPCLK | PCGCCTL_GATEHCLK; + + // MCU-specific PHY deinit (disable PHY power) + const dwc2_ghwcfg2_t ghwcfg2 = {.value = dwc2->ghwcfg2}; + const uint8_t hs_phy_type = (dwc2->gusbcfg & GUSBCFG_PHYSEL) ? GHWCFG2_HSPHY_NOT_SUPPORTED : ghwcfg2.hs_phy_type; + dwc2_phy_deinit(dwc2, hs_phy_type); +} + +// void dwc2_core_handle_common_irq(uint8_t rhport, bool in_isr) { +// (void) in_isr; +// dwc2_regs_t * const dwc2 = DWC2_REG(rhport); +// const uint32_t int_mask = dwc2->gintmsk; +// const uint32_t int_status = dwc2->gintsts & int_mask; +// +// // Device disconnect +// if (int_status & GINTSTS_DISCINT) { +// dwc2->gintsts = GINTSTS_DISCINT; +// } +// +// } + +#endif diff --git a/libraries/Adafruit_TinyUSB_Arduino/src/portable/synopsys/dwc2/dwc2_common.h b/libraries/Adafruit_TinyUSB_Arduino/src/portable/synopsys/dwc2/dwc2_common.h new file mode 100644 index 0000000..5c4798d --- /dev/null +++ b/libraries/Adafruit_TinyUSB_Arduino/src/portable/synopsys/dwc2/dwc2_common.h @@ -0,0 +1,118 @@ +/* +* The MIT License (MIT) + * + * Copyright (c) 2024 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. + * + * This file is part of the TinyUSB stack. + */ + +#ifndef TUSB_DWC2_COMMON_H +#define TUSB_DWC2_COMMON_H + +#include "common/tusb_common.h" +#include "dwc2_type.h" + +#if CFG_TUD_ENABLED +#include "device/dcd.h" +#endif + +#if CFG_TUH_ENABLED +#include "host/hcd.h" +#endif + +/* Following symbols must be defined by port header + - _dwc2_controller[]: array of controllers + - DWC2_EP_MAX: largest EP counts of all controllers + - dwc2_clock_init(): clock init call before + - dwc2_phy_init/dwc2_phy_update: phy init called before and after core reset + - dwc2_phy_deinit(dwc2, hs_phy_type): phy deinit to disable PHY power, only deinit the phy used by core + - dwc2_dcd_int_enable/dwc2_dcd_int_disable + - dwc2_remote_wakeup_delay +*/ + +#if defined(TUP_USBIP_DWC2_STM32) + #include "dwc2_stm32.h" +#elif defined(TUP_USBIP_DWC2_ESP32) + #include "dwc2_esp32.h" +#elif TU_CHECK_MCU(OPT_MCU_GD32VF103) + #include "dwc2_gd32.h" +#elif TU_CHECK_MCU(OPT_MCU_BCM2711, OPT_MCU_BCM2835, OPT_MCU_BCM2837) + #include "dwc2_bcm.h" +#elif TU_CHECK_MCU(OPT_MCU_EFM32GG) + #include "dwc2_efm32.h" +#elif TU_CHECK_MCU(OPT_MCU_XMC4000) + #include "dwc2_xmc.h" +#elif defined(TUP_USBIP_DWC2_AT32) + #include "dwc2_at32.h" +#elif defined(TUP_USBIP_DWC2_NRF) + #include "dwc2_nrf.h" +#else + #error "Unsupported MCUs" +#endif + +enum { + DWC2_CONTROLLER_COUNT = TU_ARRAY_SIZE(_dwc2_controller) +}; + +enum { + OTG_INT_COMMON = 0 // GINTSTS_DISCINT | GINTSTS_CONIDSTSCHNG +}; + +//--------------------------------------------------------------------+ +// Core/Controller +//--------------------------------------------------------------------+ +TU_ATTR_ALWAYS_INLINE static inline dwc2_regs_t* DWC2_REG(uint8_t rhport) { + if (rhport >= DWC2_CONTROLLER_COUNT) { + // user mis-configured, ignore and use first controller + rhport = 0; + } + return (dwc2_regs_t*)_dwc2_controller[rhport].reg_base; +} + +// check if highspeed phy should be used +bool dwc2_core_is_highspeed_phy(dwc2_regs_t* dwc2, bool prefer_hs_phy); +bool dwc2_core_init(uint8_t rhport, bool is_hs_phy, bool is_dma); +void dwc2_core_deinit(uint8_t rhport); +void dwc2_core_handle_common_irq(uint8_t rhport, bool in_isr); + +//--------------------------------------------------------------------+ +// DFIFO +//--------------------------------------------------------------------+ +TU_ATTR_ALWAYS_INLINE static inline void dfifo_flush_tx(dwc2_regs_t* dwc2, uint8_t fnum) { + // flush TX fifo and wait for it cleared + dwc2->grstctl = GRSTCTL_TXFFLSH | (fnum << GRSTCTL_TXFNUM_Pos); + while (0 != (dwc2->grstctl & GRSTCTL_TXFFLSH_Msk)) {} +} + +TU_ATTR_ALWAYS_INLINE static inline void dfifo_flush_rx(dwc2_regs_t* dwc2) { + // flush RX fifo and wait for it cleared + dwc2->grstctl = GRSTCTL_RXFFLSH; + while (0 != (dwc2->grstctl & GRSTCTL_RXFFLSH_Msk)) {} +} + +void dfifo_read_packet(dwc2_regs_t* dwc2, uint8_t* dst, uint16_t len); +void dfifo_write_packet(dwc2_regs_t* dwc2, uint8_t fifo_num, uint8_t const* src, uint16_t len); + +//--------------------------------------------------------------------+ +// DMA +//--------------------------------------------------------------------+ + +#endif diff --git a/libraries/Adafruit_TinyUSB_Arduino/src/portable/synopsys/dwc2/dwc2_efm32.h b/libraries/Adafruit_TinyUSB_Arduino/src/portable/synopsys/dwc2/dwc2_efm32.h new file mode 100644 index 0000000..0633608 --- /dev/null +++ b/libraries/Adafruit_TinyUSB_Arduino/src/portable/synopsys/dwc2/dwc2_efm32.h @@ -0,0 +1,103 @@ +/* + * The MIT License (MIT) + * + * Copyright (c) 2021 Rafael Silva (@perigoso) + * Copyright (c) 2021, 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. + * + * This file is part of the TinyUSB stack. + */ + +#ifndef _DWC2_EFM32_H_ +#define _DWC2_EFM32_H_ + +#ifdef __cplusplus + extern "C" { +#endif + +#include "em_device.h" + +// EFM32 has custom control register before DWC registers +#define DWC2_REG_BASE (USB_BASE + offsetof(USB_TypeDef, GOTGCTL)) +#define DWC2_EP_MAX 7 + +static const dwc2_controller_t _dwc2_controller[] = +{ + { .reg_base = DWC2_REG_BASE, .irqnum = USB_IRQn, .ep_count = DWC2_EP_MAX, .otg_dfifo_depth = 512 } +}; + +// MCU specific to enable dwc2 clock/power before any access to register +TU_ATTR_ALWAYS_INLINE static inline void dwc2_clock_init(uint8_t rhport, tusb_role_t role) { + (void) rhport; + (void) role; +} + +TU_ATTR_ALWAYS_INLINE +static inline void dwc2_dcd_int_enable(uint8_t rhport) +{ + NVIC_EnableIRQ(_dwc2_controller[rhport].irqnum); +} + +TU_ATTR_ALWAYS_INLINE +static inline void dwc2_dcd_int_disable (uint8_t rhport) +{ + NVIC_DisableIRQ(_dwc2_controller[rhport].irqnum); +} + +static inline void dwc2_remote_wakeup_delay(void) +{ + // try to delay for 1 ms +// uint32_t count = SystemCoreClock / 1000; +// while ( count-- ) __NOP(); +} + +// MCU specific PHY init, called BEFORE core reset +static inline void dwc2_phy_init(dwc2_regs_t * dwc2, uint8_t hs_phy_type) +{ + (void) dwc2; + (void) hs_phy_type; + + // Enable PHY + USB->ROUTE = USB_ROUTE_PHYPEN; +} + +// MCU specific PHY deinit, disable PHY power +static inline void dwc2_phy_deinit(dwc2_regs_t * dwc2, uint8_t hs_phy_type) { + (void) dwc2; + (void) hs_phy_type; + // Disable PHY pin + USB->ROUTE = 0; +} + +// MCU specific PHY update, it is called AFTER init() and core reset +static inline void dwc2_phy_update(dwc2_regs_t * dwc2, uint8_t hs_phy_type) +{ + (void) dwc2; + (void) hs_phy_type; + + // EFM32 Manual: turn around must be 5 (reset & default value) + // dwc2->gusbcfg = (dwc2->gusbcfg & ~GUSBCFG_TRDT_Msk) | (5u << GUSBCFG_TRDT_Pos); +} + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/libraries/Adafruit_TinyUSB_Arduino/src/portable/synopsys/dwc2/dwc2_esp32.h b/libraries/Adafruit_TinyUSB_Arduino/src/portable/synopsys/dwc2/dwc2_esp32.h new file mode 100644 index 0000000..436f8dc --- /dev/null +++ b/libraries/Adafruit_TinyUSB_Arduino/src/portable/synopsys/dwc2/dwc2_esp32.h @@ -0,0 +1,200 @@ +/* + * The MIT License (MIT) + * + * Copyright (c) 2021, 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. + * + * This file is part of the TinyUSB stack. + */ + + +#ifndef TUSB_DWC2_ESP32_H_ +#define TUSB_DWC2_ESP32_H_ + +#ifdef __cplusplus + extern "C" { +#endif + +#include "freertos/FreeRTOS.h" +#include "freertos/task.h" + +#include "esp_intr_alloc.h" +#include "soc/periph_defs.h" + +// ESP32-S31 does not have USB_WRAP peripheral (HS-only with UTMI PHY) +#if !TU_CHECK_MCU(OPT_MCU_ESP32S31) +#include "soc/usb_wrap_struct.h" +#endif + +#if TU_CHECK_MCU(OPT_MCU_ESP32S2, OPT_MCU_ESP32S3) +#define DWC2_FS_REG_BASE 0x60080000UL +#define DWC2_EP_MAX 7 + +static const dwc2_controller_t _dwc2_controller[] = { + { .reg_base = DWC2_FS_REG_BASE, .irqnum = ETS_USB_INTR_SOURCE, .ep_count = 7, .ep_in_count = 5, .otg_dfifo_depth = 256 } +}; + +#elif TU_CHECK_MCU(OPT_MCU_ESP32H4) +// H4's USB_WRAP register block uses "wrap_*" field names. Map them to the +// names used by TinyUSB's DWC2 port to keep the source unchanged. +#define otg_conf wrap_otg_conf +#define pad_pull_override wrap_pad_pull_override +#define dp_pullup wrap_dp_pullup +#define dp_pulldown wrap_dp_pulldown +#define dm_pullup wrap_dm_pullup +#define dm_pulldown wrap_dm_pulldown + +#define DWC2_FS_REG_BASE 0x60040000UL +#define DWC2_EP_MAX 7 + +static const dwc2_controller_t _dwc2_controller[] = { + { .reg_base = DWC2_FS_REG_BASE, .irqnum = ETS_USB_OTG11_INTR_SOURCE, .ep_count = 7, .ep_in_count = 5, .otg_dfifo_depth = 256 } +}; + +#elif TU_CHECK_MCU(OPT_MCU_ESP32P4) +#define DWC2_FS_REG_BASE 0x50040000UL +#define DWC2_HS_REG_BASE 0x50000000UL +#define DWC2_EP_MAX 16 + +// On ESP32 for consistency we associate +// - Port0 to OTG_FS, and Port1 to OTG_HS +static const dwc2_controller_t _dwc2_controller[] = { + { .reg_base = DWC2_FS_REG_BASE, .irqnum = ETS_USB_OTG11_CH0_INTR_SOURCE, .ep_count = 7, .ep_in_count = 5, .otg_dfifo_depth = 256 }, + { .reg_base = DWC2_HS_REG_BASE, .irqnum = ETS_USB_OTG_INTR_SOURCE, .ep_count = 16, .ep_in_count = 8, .otg_dfifo_depth = 1024 } +}; + +#elif TU_CHECK_MCU(OPT_MCU_ESP32S31) +#define DWC2_HS_REG_BASE 0x20300000UL +#define DWC2_EP_MAX 16 + +static const dwc2_controller_t _dwc2_controller[] = { + { .reg_base = DWC2_HS_REG_BASE, .irqnum = ETS_USB_OTGHS_INTR_SOURCE, .ep_count = 16, .ep_in_count = 8, .otg_dfifo_depth = 1024 } +}; +#endif + +//--------------------------------------------------------------------+ +// +//--------------------------------------------------------------------+ +static intr_handle_t usb_ih[TU_ARRAY_SIZE(_dwc2_controller)]; + +static void dwc2_int_handler_wrap(void* arg) { + const uint8_t rhport = tu_u16_low((uint16_t)(uintptr_t)arg); + const tusb_role_t role = (tusb_role_t) tu_u16_high((uint16_t)(uintptr_t)arg); +#if CFG_TUD_ENABLED + if (role == TUSB_ROLE_DEVICE) { + dcd_int_handler(rhport); + } +#endif +#if CFG_TUH_ENABLED && !CFG_TUH_MAX3421 + if (role == TUSB_ROLE_HOST) { + hcd_int_handler(rhport, true); + } +#endif +} + +// MCU specific to enable dwc2 clock/power before any access to register +TU_ATTR_ALWAYS_INLINE static inline void dwc2_clock_init(uint8_t rhport, tusb_role_t role) { + (void) rhport; + (void) role; +} + +TU_ATTR_ALWAYS_INLINE static inline void dwc2_int_set(uint8_t rhport, tusb_role_t role, bool enabled) { + if (enabled) { + esp_intr_alloc(_dwc2_controller[rhport].irqnum, ESP_INTR_FLAG_LOWMED, + dwc2_int_handler_wrap, (void*)(uintptr_t)tu_u16(role, rhport), &usb_ih[rhport]); + } else { + esp_intr_free(usb_ih[rhport]); + } +} + +#define dwc2_dcd_int_enable(_rhport) dwc2_int_set(_rhport, TUSB_ROLE_DEVICE, true) +#define dwc2_dcd_int_disable(_rhport) dwc2_int_set(_rhport, TUSB_ROLE_DEVICE, false) + +TU_ATTR_ALWAYS_INLINE static inline void dwc2_remote_wakeup_delay(void) { + vTaskDelay(pdMS_TO_TICKS(1)); +} + +// MCU specific PHY init, called BEFORE core reset +TU_ATTR_ALWAYS_INLINE static inline void dwc2_phy_init(dwc2_regs_t* dwc2, uint8_t hs_phy_type) { + (void)dwc2; + (void)hs_phy_type; + // maybe usb_utmi_hal_init() + +} + +// MCU specific PHY deinit, disable PHY power +TU_ATTR_ALWAYS_INLINE static inline void dwc2_phy_deinit(dwc2_regs_t* dwc2, uint8_t hs_phy_type) { + (void)dwc2; + (void)hs_phy_type; + // PHY managed by ESP-IDF +} + +// MCU specific PHY update, it is called AFTER init() and core reset +TU_ATTR_ALWAYS_INLINE static inline void dwc2_phy_update(dwc2_regs_t* dwc2, uint8_t hs_phy_type) { + (void)dwc2; + (void)hs_phy_type; + // maybe usb_utmi_hal_disable() +} + +//--------------------------------------------------------------------+ +// Data Cache +//--------------------------------------------------------------------+ +#if CFG_TUD_DWC2_DMA_ENABLE || CFG_TUH_DWC2_DMA_ENABLE +#if defined(SOC_CACHE_INTERNAL_MEM_VIA_L1CACHE) && SOC_CACHE_INTERNAL_MEM_VIA_L1CACHE +#include "esp_cache.h" + +#if CFG_TUD_MEM_DCACHE_LINE_SIZE != CONFIG_CACHE_L1_CACHE_LINE_SIZE || \ + CFG_TUH_MEM_DCACHE_LINE_SIZE != CONFIG_CACHE_L1_CACHE_LINE_SIZE +#error "CFG_TUD/TUH_MEM_DCACHE_LINE_SIZE must match CONFIG_CACHE_L1_CACHE_LINE_SIZE" +#endif + +TU_ATTR_ALWAYS_INLINE static inline uint32_t round_up_to_cache_line_size(uint32_t size) { + if (size & (CONFIG_CACHE_L1_CACHE_LINE_SIZE-1)) { + size = (size & ~(CONFIG_CACHE_L1_CACHE_LINE_SIZE-1)) + CONFIG_CACHE_L1_CACHE_LINE_SIZE; + } + return size; +} + +TU_ATTR_ALWAYS_INLINE static inline bool dwc2_dcache_clean(const void* addr, uint32_t data_size) { + const int flag = ESP_CACHE_MSYNC_FLAG_TYPE_DATA | ESP_CACHE_MSYNC_FLAG_DIR_C2M; + data_size = round_up_to_cache_line_size(data_size); + return ESP_OK == esp_cache_msync((void*)addr, data_size, flag); +} + +TU_ATTR_ALWAYS_INLINE static inline bool dwc2_dcache_invalidate(const void* addr, uint32_t data_size) { + const int flag = ESP_CACHE_MSYNC_FLAG_TYPE_DATA | ESP_CACHE_MSYNC_FLAG_DIR_M2C; + data_size = round_up_to_cache_line_size(data_size); + return ESP_OK == esp_cache_msync((void*)addr, data_size, flag); +} + +TU_ATTR_ALWAYS_INLINE static inline bool dwc2_dcache_clean_invalidate(const void* addr, uint32_t data_size) { + const int flag = ESP_CACHE_MSYNC_FLAG_TYPE_DATA | ESP_CACHE_MSYNC_FLAG_DIR_C2M | ESP_CACHE_MSYNC_FLAG_DIR_M2C; + data_size = round_up_to_cache_line_size(data_size); + return ESP_OK == esp_cache_msync((void*)addr, data_size, flag); +} + +#endif +#endif + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/libraries/Adafruit_TinyUSB_Arduino/src/portable/synopsys/dwc2/dwc2_gd32.h b/libraries/Adafruit_TinyUSB_Arduino/src/portable/synopsys/dwc2/dwc2_gd32.h new file mode 100644 index 0000000..28c3fde --- /dev/null +++ b/libraries/Adafruit_TinyUSB_Arduino/src/portable/synopsys/dwc2/dwc2_gd32.h @@ -0,0 +1,114 @@ +/* + * The MIT License (MIT) + * + * Copyright (c) 2021, 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. + * + * This file is part of the TinyUSB stack. + */ + + +#ifndef DWC2_GD32_H_ +#define DWC2_GD32_H_ + +#ifdef __cplusplus + extern "C" { +#endif + +#define DWC2_REG_BASE 0x50000000UL +#define DWC2_EP_MAX 4 + +static const dwc2_controller_t _dwc2_controller[] = +{ + { .reg_base = DWC2_REG_BASE, .irqnum = 86, .ep_count = DWC2_EP_MAX, .otg_dfifo_depth = 320 } +}; + +extern uint32_t SystemCoreClock; + +// The GD32VF103 is a RISC-V MCU, which implements the ECLIC Core-Local +// Interrupt Controller by Nuclei. It is nearly API compatible to the +// NVIC used by ARM MCUs. +#define ECLIC_INTERRUPT_ENABLE_BASE 0xD2001001UL + +TU_ATTR_ALWAYS_INLINE +static inline void __eclic_enable_interrupt (uint32_t irq) { + *(volatile uint8_t*)(ECLIC_INTERRUPT_ENABLE_BASE + (irq * 4)) = 1; +} + +TU_ATTR_ALWAYS_INLINE +static inline void __eclic_disable_interrupt (uint32_t irq){ + *(volatile uint8_t*)(ECLIC_INTERRUPT_ENABLE_BASE + (irq * 4)) = 0; +} + +// MCU specific to enable dwc2 clock/power before any access to register +TU_ATTR_ALWAYS_INLINE static inline void dwc2_clock_init(uint8_t rhport, tusb_role_t role) { + (void) rhport; + (void) role; +} + +TU_ATTR_ALWAYS_INLINE +static inline void dwc2_dcd_int_enable(uint8_t rhport) +{ + __eclic_enable_interrupt(_dwc2_controller[rhport].irqnum); +} + +TU_ATTR_ALWAYS_INLINE +static inline void dwc2_dcd_int_disable (uint8_t rhport) +{ + __eclic_disable_interrupt(_dwc2_controller[rhport].irqnum); +} + +static inline void dwc2_remote_wakeup_delay(void) +{ + // try to delay for 1 ms + uint32_t count = SystemCoreClock / 1000; + while ( count-- ) __asm volatile ("nop"); +} + +// MCU specific PHY init, called BEFORE core reset +static inline void dwc2_phy_init(dwc2_regs_t * dwc2, uint8_t hs_phy_type) +{ + (void) dwc2; + (void) hs_phy_type; + + // nothing to do +} + +// MCU specific PHY deinit, disable PHY power +static inline void dwc2_phy_deinit(dwc2_regs_t * dwc2, uint8_t hs_phy_type) { + (void) dwc2; + (void) hs_phy_type; + // nothing to do +} + +// MCU specific PHY update, it is called AFTER init() and core reset +static inline void dwc2_phy_update(dwc2_regs_t * dwc2, uint8_t hs_phy_type) +{ + (void) dwc2; + (void) hs_phy_type; + + // nothing to do +} + +#ifdef __cplusplus +} +#endif + +#endif /* DWC2_GD32_H_ */ diff --git a/libraries/Adafruit_TinyUSB_Arduino/src/portable/synopsys/dwc2/dwc2_info.md b/libraries/Adafruit_TinyUSB_Arduino/src/portable/synopsys/dwc2/dwc2_info.md new file mode 100644 index 0000000..205684e --- /dev/null +++ b/libraries/Adafruit_TinyUSB_Arduino/src/portable/synopsys/dwc2/dwc2_info.md @@ -0,0 +1,58 @@ +| | AT32 F405 FS | AT32 F405 HS | AT32 F415 | BCM2711 (Pi4) | EFM32GG | ESP32-S2/S3 | ESP32-P4 | nRF54H20 | nRF54LM20 | ST F407/429 HS | ST F207/F407/411/429 FS | ST L476 FS | ST F412/76x FS | ST F76x HS | ST H743/H750 | ST F723/L4P5 FS | ST F723 HS | ST H7RS FS | ST U5A5/H7RS/N6 HS | XMC4500 | GD32VF103 | +|:---------------------------|:---------------|:---------------|:------------|:----------------|:-------------|:--------------|:-------------|:-------------|:-------------|:-----------------|:--------------------------|:-------------|:-----------------|:-------------|:---------------|:------------------|:-------------|:-------------|:---------------------|:-------------|:------------| +| GUID | 0x00002000 | 0x00000000 | 0x00001000 | 0x2708A000 | 0x00000000 | 0x00000000 | 0x00000000 | 0x00000000 | 0x00000000 | 0x00001100 | 0x00001200 | 0x00002000 | 0x00002000 | 0x00002100 | 0x00002300 | 0x00003000 | 0x00003100 | 0x00004000 | 0x00005000 | 0x00AEC000 | 0x00001000 | +| GSNPSID | 0x4F54400A | 0x4F54400A | 0x4F54400A | 0x4F54280A | 0x4F54330A | 0x4F54400A | 0x4F54400A | 0x4F54430A | 0x4F54500B | 0x4F54281A | 0x4F54281A | 0x4F54310A | 0x4F54320A | 0x4F54320A | 0x4F54330A | 0x4F54330A | 0x4F54330A | 0x4F54411A | 0x4F54411A | 0x4F54292A | 0x00000000 | +| - specs version | 4.00a | 4.00a | 4.00a | 2.80a | 3.30a | 4.00a | 4.00a | 4.30a | 5.00b | 2.81a | 2.81a | 3.10a | 3.20a | 3.20a | 3.30a | 3.30a | 3.30a | 4.11a | 4.11a | 2.92a | 0.00W | +| GHWCFG1 | 0x00000000 | 0x00000000 | 0x00000000 | 0x00000000 | 0x00000000 | 0x00000000 | 0x00000000 | 0xAA555000 | 0x00000000 | 0x00000000 | 0x00000000 | 0x00000000 | 0x00000000 | 0x00000000 | 0x00000000 | 0x00000000 | 0x00000000 | 0x00000000 | 0x00000000 | 0x00000000 | 0x00000000 | +| GHWCFG2 | 0x228FDD00 | 0x229FDDD0 | 0x228DCD00 | 0x228DDD50 | 0x228F5910 | 0x224DD930 | 0x215FFFD0 | 0x228BFC72 | 0x22AFFC52 | 0x229ED590 | 0x229DCD20 | 0x229ED520 | 0x229ED520 | 0x229FE190 | 0x229FE190 | 0x229ED520 | 0x229FE1D0 | 0x229ED522 | 0x228FE052 | 0x228F5930 | 0x00000000 | +| - op_mode | HNP SRP | HNP SRP | HNP SRP | HNP SRP | HNP SRP | HNP SRP | HNP SRP | noHNP noSRP | noHNP noSRP | HNP SRP | HNP SRP | HNP SRP | HNP SRP | HNP SRP | HNP SRP | HNP SRP | HNP SRP | noHNP noSRP | noHNP noSRP | HNP SRP | HNP SRP | +| - arch | Slave only | DMA internal | Slave only | DMA internal | DMA internal | DMA internal | DMA internal | DMA internal | DMA internal | DMA internal | Slave only | Slave only | Slave only | DMA internal | DMA internal | Slave only | DMA internal | Slave only | DMA internal | DMA internal | Slave only | +| - single_point | hub | hub | hub | hub | hub | n/a | hub | n/a | hub | hub | n/a | n/a | n/a | hub | hub | n/a | hub | n/a | hub | n/a | hub | +| - hs_phy_type | n/a | UTMI+/ULPI | n/a | UTMI+ | n/a | n/a | UTMI+/ULPI | UTMI+ | UTMI+ | ULPI | n/a | n/a | n/a | ULPI | ULPI | n/a | UTMI+/ULPI | n/a | UTMI+ | n/a | n/a | +| - fs_phy_type | Dedicated | Dedicated | Dedicated | Dedicated | Dedicated | Dedicated | Shared ULPI | n/a | n/a | Dedicated | Dedicated | Dedicated | Dedicated | Dedicated | Dedicated | Dedicated | Dedicated | Dedicated | n/a | Dedicated | n/a | +| - num_dev_ep | 7 | 7 | 3 | 7 | 6 | 6 | 15 | 15 | 15 | 5 | 3 | 5 | 5 | 8 | 8 | 5 | 8 | 5 | 8 | 6 | 0 | +| - num_host_ch | 15 | 15 | 7 | 7 | 13 | 7 | 15 | 15 | 15 | 11 | 7 | 11 | 11 | 15 | 15 | 11 | 15 | 11 | 15 | 13 | 0 | +| - period_channel_support | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 0 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 0 | +| - enable_dynamic_fifo | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 0 | +| - mul_proc_intrpt | 0 | 1 | 0 | 0 | 0 | 0 | 1 | 0 | 0 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 0 | 0 | 0 | +| - reserved21 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 1 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | +| - nptx_q_depth | 8 | 8 | 8 | 8 | 8 | 4 | 4 | 8 | 8 | 8 | 8 | 8 | 8 | 8 | 8 | 8 | 8 | 8 | 8 | 8 | 2 | +| - ptx_q_depth | 8 | 8 | 8 | 8 | 8 | 8 | 4 | 8 | 8 | 8 | 8 | 8 | 8 | 8 | 8 | 8 | 8 | 8 | 8 | 8 | 2 | +| - token_q_depth | 8 | 8 | 8 | 8 | 8 | 8 | 8 | 8 | 8 | 8 | 8 | 8 | 8 | 8 | 8 | 8 | 8 | 8 | 8 | 8 | 0 | +| - otg_enable_ic_usb | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | +| GHWCFG3 | 0x020004E8 | 0x03F006E8 | 0x020004E8 | 0x0FF000E8 | 0x01F204E8 | 0x00C804B5 | 0x03805EB5 | 0x0BEAC0E8 | 0x0BE0C0E8 | 0x03F403E8 | 0x020001E8 | 0x0200D1E8 | 0x0200D1E8 | 0x03EED2E8 | 0x03B8D2E8 | 0x0200D1E8 | 0x03EED2E8 | 0x020081E8 | 0x03B882E8 | 0x027A01E5 | 0x00000000 | +| - xfer_size_width | 8 | 8 | 8 | 8 | 8 | 5 | 5 | 8 | 8 | 8 | 8 | 8 | 8 | 8 | 8 | 8 | 8 | 8 | 8 | 5 | 0 | +| - packet_size_width | 6 | 6 | 6 | 6 | 6 | 3 | 3 | 6 | 6 | 6 | 6 | 6 | 6 | 6 | 6 | 6 | 6 | 6 | 6 | 6 | 0 | +| - otg_enable | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 0 | +| - i2c_enable | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 1 | 1 | 1 | 1 | 0 | 0 | 1 | 0 | 1 | 0 | 1 | 0 | +| - vendor_ctrl_itf | 0 | 1 | 0 | 0 | 0 | 0 | 1 | 0 | 0 | 1 | 0 | 0 | 0 | 1 | 1 | 0 | 1 | 0 | 1 | 0 | 0 | +| - optional_feature_removed | 1 | 1 | 1 | 0 | 1 | 1 | 1 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | +| - synch_reset | 0 | 0 | 0 | 0 | 0 | 0 | 1 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | +| - otg_adp_support | 0 | 0 | 0 | 0 | 0 | 0 | 1 | 0 | 0 | 0 | 0 | 1 | 1 | 1 | 1 | 1 | 1 | 0 | 0 | 0 | 0 | +| - otg_enable_hsic | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | +| - battery_charger_support | 0 | 0 | 0 | 0 | 0 | 0 | 1 | 1 | 1 | 0 | 0 | 1 | 1 | 1 | 1 | 1 | 1 | 0 | 0 | 0 | 0 | +| - lpm_mode | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 1 | 1 | 0 | 0 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 0 | 0 | +| - dfifo_depth | 512 | 1008 | 512 | 4080 | 498 | 200 | 896 | 3050 | 3040 | 1012 | 512 | 512 | 512 | 1006 | 952 | 512 | 1006 | 512 | 952 | 634 | 0 | +| GHWCFG4 | 0x1FF0A020 | 0x1FF0A020 | 0x0000000F | 0x1FF00020 | 0x1BF08030 | 0xD3F0A030 | 0xDFF1A030 | 0x1E10AA60 | 0x3E10AA60 | 0x17F00030 | 0x0FF08030 | 0x17F08030 | 0x17F08030 | 0x23F00030 | 0xE3F00030 | 0x17F08030 | 0x23F00030 | 0x1610B230 | 0xE2103E30 | 0xDBF08030 | 0x00000000 | +| - num_dev_period_in_ep | 0 | 0 | 15 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | +| - partial_powerdown | 0 | 0 | 0 | 0 | 1 | 1 | 1 | 0 | 0 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 0 | +| - ahb_freq_min | 1 | 1 | 0 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 0 | +| - hibernation | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 1 | 1 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | +| - extended_hibernation | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | +| - reserved8 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | +| - enhanced_lpm_support1 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 1 | 1 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 1 | 1 | 0 | 0 | +| - service_interval_flow | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 1 | 0 | 0 | +| - ipg_isoc_support | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 1 | 1 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 1 | 0 | 0 | +| - acg_support | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 1 | 1 | 0 | 0 | +| - enhanced_lpm_support | 1 | 1 | 0 | 0 | 0 | 1 | 1 | 1 | 1 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 1 | 1 | 0 | 0 | +| - phy_data_width | 8/16 bit | 8/16 bit | 8 bit | 8 bit | 8/16 bit | 8/16 bit | 8/16 bit | 8/16 bit | 8/16 bit | 8 bit | 8/16 bit | 8/16 bit | 8/16 bit | 8 bit | 8 bit | 8/16 bit | 8 bit | 8/16 bit | 8 bit | 8/16 bit | 8 bit | +| - ctrl_ep_num | 0 | 0 | 0 | 0 | 0 | 0 | 1 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | +| - iddg_filter | 1 | 1 | 0 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 0 | +| - vbus_valid_filter | 1 | 1 | 0 | 1 | 1 | 1 | 1 | 0 | 0 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 0 | 0 | 1 | 0 | +| - a_valid_filter | 1 | 1 | 0 | 1 | 1 | 1 | 1 | 0 | 0 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 0 | 0 | 1 | 0 | +| - b_valid_filter | 1 | 1 | 0 | 1 | 1 | 1 | 1 | 0 | 0 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 0 | 0 | 1 | 0 | +| - session_end_filter | 1 | 1 | 0 | 1 | 1 | 1 | 1 | 0 | 0 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 0 | 0 | 1 | 0 | +| - dedicated_fifos | 1 | 1 | 0 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 0 | +| - num_dev_in_eps | 7 | 7 | 0 | 7 | 6 | 4 | 7 | 7 | 15 | 5 | 3 | 5 | 5 | 8 | 8 | 5 | 8 | 5 | 8 | 6 | 0 | +| - dma_desc_enable | 0 | 0 | 0 | 0 | 0 | 1 | 1 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 1 | 0 | 0 | 0 | 1 | 1 | 0 | +| - dma_desc_dynamic | 0 | 0 | 0 | 0 | 0 | 1 | 1 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 1 | 0 | 0 | 0 | 1 | 1 | 0 | diff --git a/libraries/Adafruit_TinyUSB_Arduino/src/portable/synopsys/dwc2/dwc2_info.py b/libraries/Adafruit_TinyUSB_Arduino/src/portable/synopsys/dwc2/dwc2_info.py new file mode 100644 index 0000000..8ee04e1 --- /dev/null +++ b/libraries/Adafruit_TinyUSB_Arduino/src/portable/synopsys/dwc2/dwc2_info.py @@ -0,0 +1,216 @@ +#!/usr/bin/env python3 + +import ctypes +import argparse +import pandas as pd + +# hex value for register: guid, gsnpsid, ghwcfg1, ghwcfg2, ghwcfg3, ghwcfg4 +# Note: FS is FullSpeed, HS is HighSpeed +dwc2_reg_list = ['GUID', 'GSNPSID', 'GHWCFG1', 'GHWCFG2', 'GHWCFG3', 'GHWCFG4'] +dwc2_reg_value = { + 'AT32 F405 FS': [0x00002000, 0x4F54400A, 0x00000000, 0x228FDD00, 0x020004E8, 0x1FF0A020], + 'AT32 F405 HS': [0, 0x4F54400A, 0x00000000, 0x229FDDD0, 0x03F006E8, 0x1FF0A020], + 'AT32 F415': [0x00001000, 0x4F54400A, 0x00000000, 0x228DCD00, 0x020004E8, 0x0F], + 'BCM2711 (Pi4)': [0x2708A000, 0x4F54280A, 0, 0x228DDD50, 0xFF000E8, 0x1FF00020], + 'EFM32GG': [0, 0x4F54330A, 0, 0x228F5910, 0x01F204E8, 0x1BF08030], + 'ESP32-S2/S3': [0, 0x4F54400A, 0, 0x224DD930, 0x0C804B5, 0xD3F0A030], + 'ESP32-P4': [0, 0x4F54400A, 0, 0x215FFFD0, 0x03805EB5, 0xDFF1A030], + 'nRF54H20': [0, 0x4F54430A, 0xAA555000, 0x228BFC72, 0x0BEAC0E8, 0x1E10AA60], # base on Preliminary Datasheet v0.7 + 'nRF54LM20': [0, 0x4F54500B, 0x00000000, 0x22AFFC52, 0x0BE0C0E8, 0x3E10AA60], + # ST sort by GUID + 'ST F407/429 HS': [0x1100, 0x4F54281A, 0, 0x229ED590, 0x03F403E8, 0x17F00030], + 'ST F207/F407/411/429 FS': [0x1200, 0x4F54281A, 0, 0x229DCD20, 0x020001E8, 0x0FF08030], + 'ST L476 FS': [0x2000, 0x4F54310A, 0, 0x229ED520, 0x0200D1E8, 0x17F08030], + 'ST F412/76x FS': [0x2000, 0x4F54320A, 0, 0x229ED520, 0x0200D1E8, 0x17F08030], + 'ST F76x HS': [0x2100, 0x4F54320A, 0, 0x229FE190, 0x03EED2E8, 0x23F00030], + 'ST H743/H750': [0x2300, 0x4F54330A, 0, 0x229FE190, 0x03B8D2E8, 0xE3F00030], + 'ST F723/L4P5 FS': [0x3000, 0x4F54330A, 0, 0x229ED520, 0x0200D1E8, 0x17F08030], + 'ST F723 HS': [0x3100, 0x4F54330A, 0, 0x229FE1D0, 0x03EED2E8, 0x23F00030], + 'ST H7RS FS': [0x4000, 0x4F54411A, 0, 0x229ED522, 0x20081E8, 0x1610B230], + 'ST U5A5/H7RS/N6 HS': [0x5000, 0x4F54411A, 0, 0x228FE052, 0x03B882E8, 0xE2103E30], + 'XMC4500': [0xAEC000, 0x4F54292A, 0, 0x228F5930, 0x027A01E5, 0xDBF08030], + 'GD32VF103': [0x1000, 0, 0, 0, 0, 0], +} + +# Combine dwc2_info with dwc2_reg_list +# dwc2_info = { +# 'BCM2711 (Pi4)': { +# 'GUID': 0x2708A000, +# 'GSNPSID': 0x4F54280A, +# 'GHWCFG1': 0, +# 'GHWCFG2': 0x228DDD50, +# 'GHWCFG3': 0xFF000E8, +# 'GHWCFG4': 0x1FF00020 +# }, +dwc2_info = {key: {field: value for field, value in zip(dwc2_reg_list, values)} for key, values in dwc2_reg_value.items()} + + +class GHWCFG2(ctypes.LittleEndianStructure): + _fields_ = [ + ("op_mode", ctypes.c_uint32, 3), + ("arch", ctypes.c_uint32, 2), + ("single_point", ctypes.c_uint32, 1), + ("hs_phy_type", ctypes.c_uint32, 2), + ("fs_phy_type", ctypes.c_uint32, 2), + ("num_dev_ep", ctypes.c_uint32, 4), + ("num_host_ch", ctypes.c_uint32, 4), + ("period_channel_support", ctypes.c_uint32, 1), + ("enable_dynamic_fifo", ctypes.c_uint32, 1), + ("mul_proc_intrpt", ctypes.c_uint32, 1), + ("reserved21", ctypes.c_uint32, 1), + ("nptx_q_depth", ctypes.c_uint32, 2), + ("ptx_q_depth", ctypes.c_uint32, 2), + ("token_q_depth", ctypes.c_uint32, 5), + ("otg_enable_ic_usb", ctypes.c_uint32, 1) + ] + + +class GHWCFG3(ctypes.LittleEndianStructure): + _fields_ = [ + ("xfer_size_width", ctypes.c_uint32, 4), + ("packet_size_width", ctypes.c_uint32, 3), + ("otg_enable", ctypes.c_uint32, 1), + ("i2c_enable", ctypes.c_uint32, 1), + ("vendor_ctrl_itf", ctypes.c_uint32, 1), + ("optional_feature_removed", ctypes.c_uint32, 1), + ("synch_reset", ctypes.c_uint32, 1), + ("otg_adp_support", ctypes.c_uint32, 1), + ("otg_enable_hsic", ctypes.c_uint32, 1), + ("battery_charger_support", ctypes.c_uint32, 1), + ("lpm_mode", ctypes.c_uint32, 1), + ("dfifo_depth", ctypes.c_uint32, 16) + ] + + +class GHWCFG4(ctypes.LittleEndianStructure): + _fields_ = [ + ("num_dev_period_in_ep", ctypes.c_uint32, 4), + ("partial_powerdown", ctypes.c_uint32, 1), + ("ahb_freq_min", ctypes.c_uint32, 1), + ("hibernation", ctypes.c_uint32, 1), + ("extended_hibernation", ctypes.c_uint32, 1), + ("reserved8", ctypes.c_uint32, 1), + ("enhanced_lpm_support1", ctypes.c_uint32, 1), + ("service_interval_flow", ctypes.c_uint32, 1), + ("ipg_isoc_support", ctypes.c_uint32, 1), + ("acg_support", ctypes.c_uint32, 1), + ("enhanced_lpm_support", ctypes.c_uint32, 1), + ("phy_data_width", ctypes.c_uint32, 2), + ("ctrl_ep_num", ctypes.c_uint32, 4), + ("iddg_filter", ctypes.c_uint32, 1), + ("vbus_valid_filter", ctypes.c_uint32, 1), + ("a_valid_filter", ctypes.c_uint32, 1), + ("b_valid_filter", ctypes.c_uint32, 1), + ("session_end_filter", ctypes.c_uint32, 1), + ("dedicated_fifos", ctypes.c_uint32, 1), + ("num_dev_in_eps", ctypes.c_uint32, 4), + ("dma_desc_enable", ctypes.c_uint32, 1), + ("dma_desc_dynamic", ctypes.c_uint32, 1) + ] + +# mapping for specific fields in GHWCFG2 +GHWCFG2_field = { + 'op_mode': { + 0: "HNP SRP", + 1: "SRP", + 2: "noHNP noSRP", + 3: "SRP Device", + 4: "noOTG Device", + 5: "SRP Host", + 6: "noOTG Host" + }, + 'arch': { + 0: "Slave only", + 1: "DMA external", + 2: "DMA internal" + }, + 'single_point': { + 0: "hub", + 1: "n/a" + }, + 'hs_phy_type': { + 0: "n/a", + 1: "UTMI+", + 2: "ULPI", + 3: "UTMI+/ULPI" + }, + 'fs_phy_type': { + 0: "n/a", + 1: "Dedicated", + 2: "Shared UTMI+", + 3: "Shared ULPI" + }, + 'nptx_q_depth': { + 0: "2", + 1: "4", + 2: "8", + }, + 'ptx_q_depth': { + 0: "2", + 1: "4", + 2: "8", + 3: "16" + }, +} + +# mapping for specific fields in GHWCFG4 +GHWCFG4_field = { + 'phy_data_width': { + 0: "8 bit", + 1: "16 bit", + 2: "8/16 bit", + 3: "Reserved" + }, + } + +def main(): + """Render dwc2_info to Markdown table""" + + parser = argparse.ArgumentParser() + args = parser.parse_args() + + # Create an empty list to hold the dictionaries + md_table = [] + + # Iterate over the dwc2_info dictionary and extract fields + for device, reg_values in dwc2_info.items(): + md_item = {"Device": device} + for r_name, r_value in reg_values.items(): + md_item[r_name] = f"0x{r_value:08X}" + + if r_name == 'GSNPSID': + # Get dwc2 specs version + major = ((r_value >> 8) >> 4) & 0x0F + minor = (r_value >> 4) & 0xFF + patch = chr((r_value & 0x0F) + ord('a') - 0xA) + md_item[f' - specs version'] = f"{major:X}.{minor:02X}{patch}" + elif r_name in globals(): + # Get bit-field values which exist as ctypes structures + class_hdl = globals()[r_name] + ghwcfg = class_hdl.from_buffer_copy(r_value.to_bytes(4, byteorder='little')) + for field_name, field_type, _ in class_hdl._fields_: + field_value = getattr(ghwcfg, field_name) + if class_hdl == GHWCFG2 and field_name in GHWCFG2_field: + field_value = GHWCFG2_field[field_name].get(field_value, f"Unknown ({field_value})") + if class_hdl == GHWCFG4 and field_name in GHWCFG4_field: + field_value = GHWCFG4_field[field_name].get(field_value, f"Unknown ({field_value})") + + md_item[f' - {field_name}'] = field_value + + md_table.append(md_item) + + # Create a Pandas DataFrame from the list of dictionaries + df = pd.DataFrame(md_table).set_index('Device') + + # Transpose the DataFrame to switch rows and columns + df = df.T + #print(df) + + # Write the Markdown table to a file + with open('dwc2_info.md', 'w') as md_file: + md_file.write(df.to_markdown()) + md_file.write('\n') + + +if __name__ == '__main__': + main() diff --git a/libraries/Adafruit_TinyUSB_Arduino/src/portable/synopsys/dwc2/dwc2_nrf.h b/libraries/Adafruit_TinyUSB_Arduino/src/portable/synopsys/dwc2/dwc2_nrf.h new file mode 100644 index 0000000..a1bf692 --- /dev/null +++ b/libraries/Adafruit_TinyUSB_Arduino/src/portable/synopsys/dwc2/dwc2_nrf.h @@ -0,0 +1,150 @@ +/* + * The MIT License (MIT) + * + * Copyright (c) 2025 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. + * + * This file is part of the TinyUSB stack. + * + * Modification + * - Gabriel Koppenstein add nRF54LM20 support + */ +#ifndef TUSB_DWC2_NRF_H +#define TUSB_DWC2_NRF_H + +// NRF is device only without OTG support +#include "nrf.h" + +#ifdef __GNUC__ +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wcast-align" +#endif + +#include + +#ifdef __GNUC__ +#pragma GCC diagnostic pop +#endif + +#define DWC2_EP_MAX 16 + +// Use the auto-resolving peripheral pointer (respects TrustZone secure/non-secure mapping) +#if defined(NRF54LM20A_ENGA_XXAA) + #define DWC2_REG_BASE ((uintptr_t)NRF_USBHSCORE) +#else + #define DWC2_REG_BASE ((uintptr_t)NRF_USBHSCORE0) +#endif + +static const dwc2_controller_t _dwc2_controller[] = { + {.reg_base = DWC2_REG_BASE, .irqnum = USBHS_IRQn, .ep_count = 16, .otg_dfifo_depth = 3072}, +}; + +// MCU specific to enable dwc2 clock/power before any access to register +TU_ATTR_ALWAYS_INLINE static inline void dwc2_clock_init(uint8_t rhport, tusb_role_t role) { + (void) rhport; + (void) role; + + #if defined(NRF54LM20A_ENGA_XXAA) + // Start the USB voltage regulator + NRF_VREGUSB->TASKS_START = VREGUSB_TASKS_START_TASKS_START_Trigger; + + // Based on Zephyr usbhs_enable_core() in drivers/usb/udc/udc_dwc2_vendor_quirks.h + // Step 1: Power up core only (PHY not yet) + NRF_USBHS->ENABLE = USBHS_ENABLE_CORE_Msk; + + // Step 2: Override ID=Device (bit 31), and temporarily override VBUSVALID + NRF_USBHS->PHY.OVERRIDEVALUES = (USBHS_PHY_OVERRIDEVALUES_ID_Device << USBHS_PHY_OVERRIDEVALUES_ID_Pos); + NRF_USBHS->PHY.INPUTOVERRIDE = USBHS_PHY_INPUTOVERRIDE_ID_Msk | USBHS_PHY_INPUTOVERRIDE_VBUSVALID_Msk; + + // Step 3: Release PHY power-on reset by enabling PHY + NRF_USBHS->ENABLE = USBHS_ENABLE_PHY_Msk | USBHS_ENABLE_CORE_Msk; + + // Step 4: Wait 45us for PHY clock to start + nrfx_coredep_delay_us(45); + + // Step 5: Release DWC2 reset + NRF_USBHS->TASKS_START = USBHS_TASKS_START_TASKS_START_Trigger; + + // Step 6: Wait for clock to start to avoid hang on too early register read + nrfx_coredep_delay_us(2); + + // Step 7: Clear VBUSVALID override (keep ID=Device override) + // DWC2 is now in Non-Driving opmode; D+ pull-up will activate when DWC2 clears DCTL SftDiscon + NRF_USBHS->PHY.INPUTOVERRIDE = USBHS_PHY_INPUTOVERRIDE_ID_Msk; + + // Barrier: USBHS wrapper (0x5005A000) and USBHSCORE (0x50020000) are separate + // peripheral blocks. Ensure the ENABLE/TASKS_START writes have propagated from + // the Cortex-M33 write buffer to hardware before anyone reads DWC2 core regs. + __DSB(); + #endif +} + +TU_ATTR_ALWAYS_INLINE static inline void dwc2_int_set(uint8_t rhport, tusb_role_t role, bool enabled) { + (void)rhport; + (void)role; + if (enabled) { + NVIC_EnableIRQ(USBHS_IRQn); + } else { + NVIC_DisableIRQ(USBHS_IRQn); + } +} + +#define dwc2_dcd_int_enable(_rhport) dwc2_int_set(_rhport, TUSB_ROLE_DEVICE, true) +#define dwc2_dcd_int_disable(_rhport) dwc2_int_set(_rhport, TUSB_ROLE_DEVICE, false) + +TU_ATTR_ALWAYS_INLINE static inline void dwc2_remote_wakeup_delay(void) { +} + +// MCU specific PHY init, called BEFORE core reset +TU_ATTR_ALWAYS_INLINE static inline void dwc2_phy_init(dwc2_regs_t *dwc2, uint8_t hs_phy_type) { + (void)dwc2; + (void)hs_phy_type; +} + +// MCU specific PHY deinit, disable PHY power +TU_ATTR_ALWAYS_INLINE static inline void dwc2_phy_deinit(dwc2_regs_t *dwc2, uint8_t hs_phy_type) { + (void)dwc2; + (void)hs_phy_type; +} + +// MCU specific PHY update, it is called AFTER init() and core reset +TU_ATTR_ALWAYS_INLINE static inline void dwc2_phy_update(dwc2_regs_t *dwc2, uint8_t hs_phy_type) { + (void)dwc2; + (void)hs_phy_type; +} + +// nRF54 Cortex-M33 has no D-cache, provide no-op stubs for DMA mode +TU_ATTR_ALWAYS_INLINE static inline bool dwc2_dcache_clean(const void *addr, uint32_t data_size) { + (void)addr; + (void)data_size; + return true; +} +TU_ATTR_ALWAYS_INLINE static inline bool dwc2_dcache_invalidate(const void *addr, uint32_t data_size) { + (void)addr; + (void)data_size; + return true; +} +TU_ATTR_ALWAYS_INLINE static inline bool dwc2_dcache_clean_invalidate(const void *addr, uint32_t data_size) { + (void)addr; + (void)data_size; + return true; +} + +#endif diff --git a/libraries/Adafruit_TinyUSB_Arduino/src/portable/synopsys/dwc2/dwc2_stm32.h b/libraries/Adafruit_TinyUSB_Arduino/src/portable/synopsys/dwc2/dwc2_stm32.h new file mode 100644 index 0000000..126632f --- /dev/null +++ b/libraries/Adafruit_TinyUSB_Arduino/src/portable/synopsys/dwc2/dwc2_stm32.h @@ -0,0 +1,471 @@ +/* + * The MIT License (MIT) + * + * Copyright (c) 2021, 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. + * + * This file is part of the TinyUSB stack. + */ + +#ifndef DWC2_STM32_H_ +#define DWC2_STM32_H_ + +#ifdef __cplusplus +extern "C" { +#endif + +// EP_MAX : Max number of bi-directional endpoints including EP0 +// DFIFO_DEPTH_FS/HS : DFIFO depth in 32-bit words (OTG_DFIFO_DEPTH) +#if CFG_TUSB_MCU == OPT_MCU_STM32F1 + #include "stm32f1xx.h" + #define EP_MAX_FS 4 + #define DFIFO_DEPTH_FS 320 + +#elif CFG_TUSB_MCU == OPT_MCU_STM32F2 + #include "stm32f2xx.h" + #define EP_MAX_FS USB_OTG_FS_MAX_IN_ENDPOINTS + #define DFIFO_DEPTH_FS 320 + + #define EP_MAX_HS USB_OTG_HS_MAX_IN_ENDPOINTS + #define DFIFO_DEPTH_HS 1024 + +#elif CFG_TUSB_MCU == OPT_MCU_STM32F4 + #include "stm32f4xx.h" + #define EP_MAX_FS USB_OTG_FS_MAX_IN_ENDPOINTS + #define DFIFO_DEPTH_FS 320 + + #define EP_MAX_HS USB_OTG_HS_MAX_IN_ENDPOINTS + #define DFIFO_DEPTH_HS 1024 + +#elif CFG_TUSB_MCU == OPT_MCU_STM32H7 + #include "stm32h7xx.h" + #define EP_MAX_FS 9 + #define DFIFO_DEPTH_FS 1024 + + #define EP_MAX_HS 9 + #define DFIFO_DEPTH_HS 1024 + + // NOTE: H7 with only 1 USB port: H72x / H73x / H7Ax / H7Bx + // USB_OTG_FS_PERIPH_BASE and OTG_FS_IRQn not defined + #if (! defined USB2_OTG_FS) + #define USB_OTG_FS_PERIPH_BASE USB1_OTG_HS_PERIPH_BASE + #define OTG_FS_IRQn OTG_HS_IRQn + #endif + +#elif CFG_TUSB_MCU == OPT_MCU_STM32H7RS + #include "stm32h7rsxx.h" + #define EP_MAX_FS 6 + #define DFIFO_DEPTH_FS 320 + + #define EP_MAX_HS 9 + #define DFIFO_DEPTH_HS 1024 + +#elif CFG_TUSB_MCU == OPT_MCU_STM32N6 + #include "stm32n6xx.h" + #define EP_MAX_FS 9 + #define DFIFO_DEPTH_FS 1024 + + #define EP_MAX_HS 9 + #define DFIFO_DEPTH_HS 1024 + + #define USB_OTG_FS_PERIPH_BASE USB1_OTG_HS_BASE + #define OTG_FS_IRQn USB1_OTG_HS_IRQn + + #define USB_OTG_HS_PERIPH_BASE USB2_OTG_HS_BASE + #define OTG_HS_IRQn USB2_OTG_HS_IRQn + +#elif CFG_TUSB_MCU == OPT_MCU_STM32F7 + #include "stm32f7xx.h" + #define EP_MAX_FS 6 + #define DFIFO_DEPTH_FS 320 + + #define EP_MAX_HS 9 + #define DFIFO_DEPTH_HS 1024 + +#elif CFG_TUSB_MCU == OPT_MCU_STM32L4 + #include "stm32l4xx.h" + #define EP_MAX_FS 6 + #define DFIFO_DEPTH_FS 320 + +#elif CFG_TUSB_MCU == OPT_MCU_STM32U5 + #include "stm32u5xx.h" + // U59x/5Ax/5Fx/5Gx are highspeed with built-in HS PHY + #ifdef USB_OTG_FS + #define USB_OTG_FS_PERIPH_BASE USB_OTG_FS_BASE + #define EP_MAX_FS 6 + #define DFIFO_DEPTH_FS 320 + #else + #define USB_OTG_HS_PERIPH_BASE USB_OTG_HS_BASE + #define EP_MAX_HS 9 + #define DFIFO_DEPTH_HS 1024 + #endif + +#elif CFG_TUSB_MCU == OPT_MCU_STM32WBA + #if defined(STM32WBA62xx) + #include "stm32wba62xx.h" + #elif defined(STM32WBA64xx) + #include "stm32wba64xx.h" + #elif defined(STM32WBA65xx) + #include "stm32wba65xx.h" + #else + #error "The selected STM32WBA series chip does not support OTG USB HS" + #endif + + #define USB_OTG_HS_PERIPH_BASE USB_OTG_HS_BASE_NS + #define OTG_HS_IRQn USB_OTG_HS_IRQn + #define EP_MAX_HS 9 + #define DFIFO_DEPTH_HS 1024 +#else + #error "Unsupported MCUs" +#endif + +// OTG HS always has higher number of endpoints than FS +#ifdef USB_OTG_HS_PERIPH_BASE + #define DWC2_EP_MAX EP_MAX_HS +#else + #define DWC2_EP_MAX EP_MAX_FS +#endif + +// On STM32 for consistency we associate +// - Port0 to OTG_FS, and Port1 to OTG_HS +static const dwc2_controller_t _dwc2_controller[] = { + #ifdef USB_OTG_FS_PERIPH_BASE + { .reg_base = USB_OTG_FS_PERIPH_BASE, .irqnum = OTG_FS_IRQn, .ep_count = EP_MAX_FS, .otg_dfifo_depth = DFIFO_DEPTH_FS }, + #endif + + #ifdef USB_OTG_HS_PERIPH_BASE + { .reg_base = USB_OTG_HS_PERIPH_BASE, .irqnum = OTG_HS_IRQn, .ep_count = EP_MAX_HS, .otg_dfifo_depth = DFIFO_DEPTH_HS }, + #endif +}; + +//--------------------------------------------------------------------+ +// +//--------------------------------------------------------------------+ + +// SystemCoreClock is already included by family header +// extern uint32_t SystemCoreClock; + +// MCU specific to enable dwc2 clock/power before any access to register +TU_ATTR_ALWAYS_INLINE static inline void dwc2_clock_init(uint8_t rhport, tusb_role_t role) { + (void) rhport; + (void) role; +} + +TU_ATTR_ALWAYS_INLINE static inline void dwc2_int_set(uint8_t rhport, tusb_role_t role, bool enabled) { + (void) role; + const IRQn_Type irqn = (IRQn_Type) _dwc2_controller[rhport].irqnum; + if (enabled) { + NVIC_EnableIRQ(irqn); + } else { + NVIC_DisableIRQ(irqn); + } +} + +#define dwc2_dcd_int_enable(_rhport) dwc2_int_set(_rhport, TUSB_ROLE_DEVICE, true) +#define dwc2_dcd_int_disable(_rhport) dwc2_int_set(_rhport, TUSB_ROLE_DEVICE, false) + + +TU_ATTR_ALWAYS_INLINE static inline void dwc2_remote_wakeup_delay(void) { + // try to delay for 1 ms + uint32_t count = SystemCoreClock / 1000; + while (count--) { + __NOP(); + } +} + +// MCU specific PHY init, called BEFORE core reset +// - dwc2 3.30a (H5) use USB_HS_PHYC +// - dwc2 4.11a (U5) use femtoPHY +// - dwc2 x.xxx (WBA) use USB_OTG_HS +static inline void dwc2_phy_init(dwc2_regs_t* dwc2, uint8_t hs_phy_type) { + if (hs_phy_type == GHWCFG2_HSPHY_NOT_SUPPORTED) { + // Enable on-chip FS PHY + dwc2->stm32_gccfg |= STM32_GCCFG_PWRDWN; + + // https://community.st.com/t5/stm32cubemx-mcus/why-stm32h743-usb-fs-doesn-t-work-if-freertos-tickless-idle/m-p/349480#M18867 + // H7 running on full-speed phy need to disable ULPI clock in sleep mode. + // Otherwise, USB won't work when mcu executing WFI/WFE instruction i.e tick-less RTOS. + // Note: there may be other family that is affected by this, but only H7 and F7 is tested so far + #if defined(USB_OTG_FS_PERIPH_BASE) && defined(RCC_AHB1LPENR_USB2OTGFSULPILPEN) + if ( USB_OTG_FS_PERIPH_BASE == (uint32_t) dwc2 ) { + RCC->AHB1LPENR &= ~RCC_AHB1LPENR_USB2OTGFSULPILPEN; + } + #endif + + #if defined(USB_OTG_HS_PERIPH_BASE) && defined(RCC_AHB1LPENR_USB1OTGHSULPILPEN) + if ( USB_OTG_HS_PERIPH_BASE == (uint32_t) dwc2 ) { + RCC->AHB1LPENR &= ~RCC_AHB1LPENR_USB1OTGHSULPILPEN; + } + #endif + + #if defined(USB_OTG_HS_PERIPH_BASE) && defined(RCC_AHB1LPENR_OTGHSULPILPEN) + if ( USB_OTG_HS_PERIPH_BASE == (uint32_t) dwc2 ) { + RCC->AHB1LPENR &= ~RCC_AHB1LPENR_OTGHSULPILPEN; + } + #endif + + } else { +#if CFG_TUSB_MCU != OPT_MCU_STM32U5 && CFG_TUSB_MCU != OPT_MCU_STM32WBA + // Disable FS PHY, TODO on U5A5 (dwc2 4.11a) 16th bit is 'Host CDP behavior enable' + dwc2->stm32_gccfg &= ~STM32_GCCFG_PWRDWN; +#endif + // Enable on-chip HS PHY + if (hs_phy_type == GHWCFG2_HSPHY_UTMI || hs_phy_type == GHWCFG2_HSPHY_UTMI_ULPI) { + #ifdef USB_HS_PHYC + // Enable UTMI HS PHY + dwc2->stm32_gccfg |= STM32_GCCFG_PHYHSEN; + + // Enable LDO + USB_HS_PHYC->USB_HS_PHYC_LDO |= USB_HS_PHYC_LDO_ENABLE; + + // Wait until LDO ready + while ( 0 == (USB_HS_PHYC->USB_HS_PHYC_LDO & USB_HS_PHYC_LDO_STATUS) ) {} + + uint32_t phyc_pll = 0; + + // TODO Try to get HSE_VALUE from registers instead of depending CFLAGS + switch ( HSE_VALUE ) + { + case 12000000: phyc_pll = USB_HS_PHYC_PLL1_PLLSEL_12MHZ ; break; + case 12500000: phyc_pll = USB_HS_PHYC_PLL1_PLLSEL_12_5MHZ ; break; + case 16000000: phyc_pll = USB_HS_PHYC_PLL1_PLLSEL_16MHZ ; break; + case 24000000: phyc_pll = USB_HS_PHYC_PLL1_PLLSEL_24MHZ ; break; + case 25000000: phyc_pll = USB_HS_PHYC_PLL1_PLLSEL_25MHZ ; break; + case 32000000: phyc_pll = USB_HS_PHYC_PLL1_PLLSEL_Msk ; break; // Value not defined in header + default: + TU_ASSERT(false, ); + } + USB_HS_PHYC->USB_HS_PHYC_PLL = phyc_pll; + + // Control the tuning interface of the High Speed PHY + // Use magic value (USB_HS_PHYC_TUNE_VALUE) from ST driver for F7 + USB_HS_PHYC->USB_HS_PHYC_TUNE |= 0x00000F13U; + + // Enable PLL internal PHY + USB_HS_PHYC->USB_HS_PHYC_PLL |= USB_HS_PHYC_PLL_PLLEN; + + // Wait ~2ms until the PLL is ready (there's no RDY bit to query) + tusb_time_delay_ms_api(2); + #else + + #endif + } + } +} + +// MCU specific PHY deinit, disable PHY power +static inline void dwc2_phy_deinit(dwc2_regs_t* dwc2, uint8_t hs_phy_type) { + if (hs_phy_type == GHWCFG2_HSPHY_NOT_SUPPORTED) { + // Disable on-chip FS PHY + dwc2->stm32_gccfg &= ~STM32_GCCFG_PWRDWN; + } else { + // Disable HS PHY + #ifdef USB_HS_PHYC + dwc2->stm32_gccfg &= ~STM32_GCCFG_PHYHSEN; + // Disable PLL and LDO + USB_HS_PHYC->USB_HS_PHYC_PLL &= ~USB_HS_PHYC_PLL_PLLEN; + USB_HS_PHYC->USB_HS_PHYC_LDO &= ~USB_HS_PHYC_LDO_ENABLE; + #endif + } +} + +// MCU specific PHY update, it is called AFTER init() and core reset +static inline void dwc2_phy_update(dwc2_regs_t* dwc2, uint8_t hs_phy_type) { + // used to set turnaround time for fullspeed, nothing to do in highspeed mode + if (hs_phy_type == GHWCFG2_HSPHY_NOT_SUPPORTED) { + // Turnaround timeout depends on the AHB clock dictated by STM32 Reference Manual + uint32_t turnaround; + + if (SystemCoreClock >= 32000000u) { + turnaround = 0x6u; + } else if (SystemCoreClock >= 27500000u) { + turnaround = 0x7u; + } else if (SystemCoreClock >= 24000000u) { + turnaround = 0x8u; + } else if (SystemCoreClock >= 21800000u) { + turnaround = 0x9u; + } + else if (SystemCoreClock >= 20000000u) { + turnaround = 0xAu; + } + else if (SystemCoreClock >= 18500000u) { + turnaround = 0xBu; + } + else if (SystemCoreClock >= 17200000u) { + turnaround = 0xCu; + } + else if (SystemCoreClock >= 16000000u) { + turnaround = 0xDu; + } + else if (SystemCoreClock >= 15000000u) { + turnaround = 0xEu; + } + else { + turnaround = 0xFu; + } + + dwc2->gusbcfg = (dwc2->gusbcfg & ~GUSBCFG_TRDT_Msk) | (turnaround << GUSBCFG_TRDT_Pos); + } +} + +//------------- GCCFG configuration -------------// +static inline void dwc2_stm32_gccfg_cfg(dwc2_regs_t* dwc2, bool vbus_sensing, bool is_host) { + if (is_host) { + vbus_sensing = false; + } + + uint32_t gccfg = dwc2->stm32_gccfg; + if (dwc2->guid < 0x2000) { + // use VBUSASEN/VBUSBSEN/NOVBUSSENS bits + if (is_host) { + gccfg &= ~(STM32_GCCFG_NOVBUSSENS | STM32_GCCFG_VBUSBSEN | STM32_GCCFG_VBUSASEN); + } else { + if (vbus_sensing) { + gccfg &= ~STM32_GCCFG_NOVBUSSENS; + gccfg |= STM32_GCCFG_VBUSBSEN; + } else { + gccfg |= STM32_GCCFG_NOVBUSSENS; + gccfg &= ~(STM32_GCCFG_VBUSBSEN | STM32_GCCFG_VBUSASEN); + } + } + } else if (dwc2->guid < 0x5000) { + // the later version uses VBDEN with battery charging detection + if (vbus_sensing) { + gccfg |= STM32_GCCFG_VBDEN; + } else { + gccfg &= ~STM32_GCCFG_VBDEN; + } + } else { + // from 0x5000 ST seems to use femtoPHY for UTMI+ HS PHY. Which use VBVALEXTOEN and VBVALOVAL for software override + // external VBUS sensing + // Note: N6 does not support hardware VBUS sensing, so the software override is always active. Therefore, VBDEN and + // VBVALEXTOEN are not available +#if CFG_TUSB_MCU == OPT_MCU_STM32N6 + if (is_host) { + gccfg |= STM32_GCCFG_PULLDOWNEN; + gccfg &= ~(STM32_GCCFG_VBVALOVAL); + } else { + gccfg &= ~STM32_GCCFG_PULLDOWNEN; + gccfg |= STM32_GCCFG_VBVALOVAL; + } +#else + if (is_host) { + gccfg |= STM32_GCCFG_PULLDOWNEN; + gccfg &= ~(STM32_GCCFG_VBDEN | STM32_GCCFG_VBVALEXTOEN | STM32_GCCFG_VBVALOVAL); + } else { + gccfg &= ~STM32_GCCFG_PULLDOWNEN; + if (vbus_sensing) { + gccfg |= STM32_GCCFG_VBDEN; + gccfg &= ~(STM32_GCCFG_VBVALEXTOEN | STM32_GCCFG_VBVALOVAL); + } else { + gccfg &= ~STM32_GCCFG_VBDEN; + gccfg |= STM32_GCCFG_VBVALEXTOEN | STM32_GCCFG_VBVALOVAL; + } + } +#endif + } + + dwc2->stm32_gccfg = gccfg; +} + +//------------- DCache -------------// +#if CFG_TUD_MEM_DCACHE_ENABLE || CFG_TUH_MEM_DCACHE_ENABLE + +typedef struct { + uintptr_t start; + uintptr_t end; +} mem_region_t; + +// Can be used to define additional uncached regions +#ifndef CFG_DWC2_MEM_UNCACHED_REGIONS +#define CFG_DWC2_MEM_UNCACHED_REGIONS +#endif + +static mem_region_t uncached_regions[] = { + // DTCM (although USB DMA can't transfer to/from DTCM) +#if CFG_TUSB_MCU == OPT_MCU_STM32H7 + {.start = 0x20000000, .end = 0x2001FFFF}, +#elif CFG_TUSB_MCU == OPT_MCU_STM32H7RS + // DTCM (although USB DMA can't transfer to/from DTCM) + {.start = 0x20000000, .end = 0x2002FFFF}, +#elif CFG_TUSB_MCU == OPT_MCU_STM32F7 + // DTCM + {.start = 0x20000000, .end = 0x2000FFFF}, +#elif CFG_TUSB_MCU == OPT_MCU_STM32N6 + // DTCM NS + {.start = 0x20000000, .end = 0x2003FFFF}, + // DTCM S + {.start = 0x30000000, .end = 0x3003FFFF}, +#else +#error "Cache maintenance is not supported yet" +#endif + CFG_DWC2_MEM_UNCACHED_REGIONS +}; + +TU_ATTR_ALWAYS_INLINE static inline uint32_t round_up_to_cache_line_size(uint32_t size) { + if (size & (CFG_TUSB_MEM_DCACHE_LINE_SIZE_DEFAULT-1)) { + size = (size & ~(CFG_TUSB_MEM_DCACHE_LINE_SIZE_DEFAULT-1)) + CFG_TUSB_MEM_DCACHE_LINE_SIZE_DEFAULT; + } + return size; +} + +TU_ATTR_ALWAYS_INLINE static inline bool is_cache_mem(uintptr_t addr) { + if (0 == (SCB->CCR & SCB_CCR_DC_Msk)) { + return false; // D-Cache is disabled + } + for (unsigned int i = 0; i < TU_ARRAY_SIZE(uncached_regions); i++) { + if (uncached_regions[i].start <= addr && addr <= uncached_regions[i].end) { return false; } + } + return true; +} + +TU_ATTR_ALWAYS_INLINE static inline bool dwc2_dcache_clean(void const* addr, uint32_t data_size) { + const uintptr_t addr32 = (uintptr_t) addr; + if (is_cache_mem(addr32)) { + data_size = round_up_to_cache_line_size(data_size); + SCB_CleanDCache_by_Addr((uint32_t *) addr32, (int32_t) data_size); + } + return true; +} + +TU_ATTR_ALWAYS_INLINE static inline bool dwc2_dcache_invalidate(void const* addr, uint32_t data_size) { + const uintptr_t addr32 = (uintptr_t) addr; + if (is_cache_mem(addr32)) { + data_size = round_up_to_cache_line_size(data_size); + SCB_InvalidateDCache_by_Addr((void*) addr32, (int32_t) data_size); + } + return true; +} + +TU_ATTR_ALWAYS_INLINE static inline bool dwc2_dcache_clean_invalidate(void const* addr, uint32_t data_size) { + const uintptr_t addr32 = (uintptr_t) addr; + if (is_cache_mem(addr32)) { + data_size = round_up_to_cache_line_size(data_size); + SCB_CleanInvalidateDCache_by_Addr((uint32_t *) addr32, (int32_t) data_size); + } + return true; +} +#endif + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/libraries/Adafruit_TinyUSB_Arduino/src/portable/synopsys/dwc2/dwc2_type.h b/libraries/Adafruit_TinyUSB_Arduino/src/portable/synopsys/dwc2/dwc2_type.h new file mode 100644 index 0000000..7c03a4a --- /dev/null +++ b/libraries/Adafruit_TinyUSB_Arduino/src/portable/synopsys/dwc2/dwc2_type.h @@ -0,0 +1,2317 @@ +/* + * The MIT License (MIT) + * + * Copyright (c) 2024, hathach (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. + * + */ +/**

© Copyright (c) 2019 STMicroelectronics. + * All rights reserved.

+ * + * This software component is licensed by ST under BSD 3-Clause license, + * the "License"; You may not use this file except in compliance with the + * License. You may obtain a copy of the License at: + * opensource.org/licenses/BSD-3-Clause + */ + +#ifndef TUSB_DWC2_TYPES_H_ +#define TUSB_DWC2_TYPES_H_ + +#include "stdint.h" + +#ifdef __cplusplus + extern "C" { +#endif + +// Controller +typedef struct +{ + uintptr_t reg_base; + uint32_t irqnum; + uint8_t ep_count; + uint8_t ep_in_count; + uint16_t otg_dfifo_depth; // total SPRAM in 32-bit words = ghwcfg3.dfifo_depth + EP_LOC_CNT +}dwc2_controller_t; + +// DWC OTG HW Release versions +#define DWC2_CORE_REV_2_71a 0x4f54271a +#define DWC2_CORE_REV_2_72a 0x4f54272a +#define DWC2_CORE_REV_2_80a 0x4f54280a +#define DWC2_CORE_REV_2_90a 0x4f54290a +#define DWC2_CORE_REV_2_91a 0x4f54291a +#define DWC2_CORE_REV_2_92a 0x4f54292a +#define DWC2_CORE_REV_2_94a 0x4f54294a +#define DWC2_CORE_REV_3_00a 0x4f54300a +#define DWC2_CORE_REV_3_10a 0x4f54310a +#define DWC2_CORE_REV_4_00a 0x4f54400a +#define DWC2_CORE_REV_4_11a 0x4f54411a +#define DWC2_CORE_REV_4_20a 0x4f54420a +#define DWC2_CORE_REV_5_00b 0x4F54500b +#define DWC2_FS_IOT_REV_1_00a 0x5531100a +#define DWC2_HS_IOT_REV_1_00a 0x5532100a +#define DWC2_CORE_REV_MASK 0x0000ffff + +// DWC OTG HW Core ID +#define DWC2_OTG_ID 0x4f540000 +#define DWC2_FS_IOT_ID 0x55310000 +#define DWC2_HS_IOT_ID 0x55320000 + +#if 0 +// HS PHY +typedef struct +{ + volatile uint32_t HS_PHYC_PLL; // 000h This register is used to control the PLL of the HS PHY. + volatile uint32_t Reserved04; // 004h Reserved + volatile uint32_t Reserved08; // 008h Reserved + volatile uint32_t HS_PHYC_TUNE; // 00Ch This register is used to control the tuning interface of the High Speed PHY. + volatile uint32_t Reserved10; // 010h Reserved + volatile uint32_t Reserved14; // 014h Reserved + volatile uint32_t HS_PHYC_LDO; // 018h This register is used to control the regulator (LDO). +} HS_PHYC_GlobalTypeDef; +#endif + +enum { + GOTGCTL_OTG_VERSION_1_3 = 0, + GOTGCTL_OTG_VERSION_2_0 = 1, +}; + +enum { + GUSBCFG_PHYSEL_HIGHSPEED = 0, + GUSBCFG_PHYSEL_FULLSPEED = 1, +}; + +enum { + GUSBCFG_PHYHS_UTMI = 0, + GUSBCFG_PHYHS_ULPI = 1, +}; + +enum { + GHWCFG2_OPMODE_HNP_SRP = 0, + GHWCFG2_OPMODE_SRP = 1, + GHWCFG2_OPMODE_NON_HNP_NON_SRP = 2, + GHWCFG2_OPMODE_SRP_DEVICE = 3, + GHWCFFG2_OPMODE_NON_OTG_DEVICE = 4, + GHWCFG2_OPMODE_SRP_HOST = 5, + GHWCFG2_OPMODE_NON_OTG_HOST = 6, +}; +enum { + GHWCFG2_ARCH_SLAVE_ONLY = 0, + GHWCFG2_ARCH_EXTERNAL_DMA = 1, + GHWCFG2_ARCH_INTERNAL_DMA = 2, +}; + +enum { + GHWCFG2_HSPHY_NOT_SUPPORTED = 0, + GHWCFG2_HSPHY_UTMI = 1, // internal PHY (mostly) + GHWCFG2_HSPHY_ULPI = 2, // external PHY (mostly) + GHWCFG2_HSPHY_UTMI_ULPI = 3, // both + +}; + +enum { + GHWCFG2_FSPHY_NOT_SUPPORTED = 0, + GHWCFG2_FSPHY_DEDICATED = 1, // have dedicated FS PHY + GHWCFG2_FSPHY_UTMI = 2, // shared with UTMI+ + GHWCFG2_FSPHY_ULPI = 3, // shared with ULPI +}; + +enum { + GHWCFFG4_PHY_DATA_WIDTH_8 = 0, + GHWCFFG4_PHY_DATA_WIDTH_16 = 1, + GHWCFFG4_PHY_DATA_WIDTH_8_16 = 2, // software selectable +}; + +enum { + HPRT_SPEED_HIGH = 0, + HPRT_SPEED_FULL = 1, + HPRT_SPEED_LOW = 2 +}; + +enum { + GINTSTS_CMODE_DEVICE = 0, + GINTSTS_CMODE_HOST = 1, +}; + +enum { + HCTSIZ_PID_DATA0 = 0, // 00b + HCTSIZ_PID_DATA2 = 1, // 01b + HCTSIZ_PID_DATA1 = 2, // 10b + HCTSIZ_PID_SETUP = 3, // 11b +}; +enum { + HCTSIZ_PID_MDATA = 3, +}; + +enum { + GRXSTS_PKTSTS_GLOBAL_OUT_NAK = 1, + GRXSTS_PKTSTS_RX_DATA = 2, + GRXSTS_PKTSTS_RX_COMPLETE = 3, + GRXSTS_PKTSTS_SETUP_DONE = 4, + GRXSTS_PKTSTS_HOST_DATATOGGLE_ERR = 5, + GRXSTS_PKTSTS_SETUP_RX = 6, + GRXSTS_PKTSTS_HOST_CHANNEL_HALTED = 7 +}; + +// Same as TUSB_XFER_* +enum { + HCCHAR_EPTYPE_CONTROL = 0, + HCCHAR_EPTYPE_ISOCHRONOUS = 1, + HCCHAR_EPTYPE_BULK = 2, + HCCHAR_EPTYPE_INTERRUPT = 3 +}; + +enum { + DCFG_SPEED_HIGH = 0, // Highspeed with 30/60 Mhz + DCFG_SPEED_FULL_30_60MHZ = 1, // Fullspeed with UTMI+/ULPI 30/60 Mhz + DCFG_SPEED_LOW = 2, // Lowspeed with FS PHY at 6 Mhz + DCFG_SPEED_FULL_48MHZ = 3, // Fullspeed with dedicated FS PHY at 48 Mhz +}; + +// Same as TUSB_XFER_* +enum { + DEPCTL_EPTYPE_CONTROL = 0, + DEPCTL_EPTYPE_ISOCHRONOUS = 1, + DEPCTL_EPTYPE_BULK = 2, + DEPCTL_EPTYPE_INTERRUPT = 3 +}; + +//-------------------------------------------------------------------- +// Common Register Bitfield +//-------------------------------------------------------------------- +typedef union { + uint32_t value; + struct TU_ATTR_PACKED { + uint32_t ses_req_scs : 1; // 0 Session request success + uint32_t ses_req : 1; // 1 Session request + uint32_t vbval_ov_en : 1; // 2 VBUS valid override enable + uint32_t vbval_ov_val : 1; // 3 VBUS valid override value + uint32_t aval_ov_en : 1; // 4 A-peripheral session valid override enable + uint32_t aval_ov_al : 1; // 5 A-peripheral session valid override value + uint32_t bval_ov_en : 1; // 6 B-peripheral session valid override enable + uint32_t bval_ov_val : 1; // 7 B-peripheral session valid override value + uint32_t hng_scs : 1; // 8 Host negotiation success + uint32_t hnp_rq : 1; // 9 HNP (host negotiation protocol) request + uint32_t host_set_hnp_en : 1; // 10 Host set HNP enable + uint32_t dev_hnp_en : 1; // 11 Device HNP enabled + uint32_t embedded_host_en : 1; // 12 Embedded host enable + uint32_t rsv13_14 : 2; // 13.14 Reserved + uint32_t dbnc_filter_bypass : 1; // 15 Debounce filter bypass + uint32_t cid_status : 1; // 16 Connector ID status + uint32_t dbnc_done : 1; // 17 Debounce done + uint32_t ases_valid : 1; // 18 A-session valid + uint32_t bses_valid : 1; // 19 B-session valid + uint32_t otg_ver : 1; // 20 OTG version 0: v1.3, 1: v2.0 + uint32_t current_mode : 1; // 21 Current mode of operation. Only from v3.00a + uint32_t mult_val_id_bc : 5; // 22..26 Multi-valued input pin ID battery charger + uint32_t chirp_en : 1; // 27 Chirp detection enable + uint32_t rsv28_30 : 3; // 28.30: Reserved + uint32_t test_mode_corr_eusb2 : 1; // 31 Test mode control for eUSB2 PHY + }; +} dwc2_gotgctl_t; +TU_VERIFY_STATIC(sizeof(dwc2_gotgctl_t) == 4, "incorrect size"); + +typedef union { + uint32_t value; + struct TU_ATTR_PACKED { + uint32_t rsv0_1 : 2; // 0..1 Reserved + uint32_t ses_end_det : 1; // 2 Session end detected + uint32_t rsv3_7 : 5; // 3..7 Reserved + uint32_t srs_status_change : 1; // 8 Session request success status change + uint32_t hns_status_change : 1; // 9 Host negotiation success status change + uint32_t rsv10_16 : 7; // 10..16 Reserved + uint32_t hng_det : 1; // 17 Host negotiation detected + uint32_t adev_timeout_change : 1; // 18 A-device timeout change + uint32_t dbnc_done : 1; // 19 Debounce done + uint32_t mult_val_lp_change : 1; // 20 Multi-valued input pin change + uint32_t rsv21_31 :11; // 21..31 Reserved + }; +} dwc2_gotgint_t; +TU_VERIFY_STATIC(sizeof(dwc2_gotgint_t) == 4, "incorrect size"); + +typedef union { + uint32_t value; + struct TU_ATTR_PACKED { + uint32_t gintmask : 1; // 0 Global interrupt mask + uint32_t hbst_len : 4; // 1..4 Burst length/type + uint32_t dma_en : 1; // 5 DMA enable + uint32_t rsv6 : 1; // 6 Reserved + uint32_t nptxf_empty_lvl : 1; // 7 Non-periodic Tx FIFO empty level + uint32_t ptxf_empty_lvl : 1; // 8 Periodic Tx FIFO empty level + uint32_t rsv9_20 : 12; // 9.20: Reserved + uint32_t remote_mem_support : 1; // 21 Remote memory support + uint32_t notify_all_dma_write : 1; // 22 Notify all DMA writes + uint32_t ahb_single : 1; // 23 AHB single + uint32_t inv_desc_endian : 1; // 24 Inverse descriptor endian + uint32_t rsv25_31 : 7; // 25..31 Reserved + }; +} dwc2_gahbcfg_t; +TU_VERIFY_STATIC(sizeof(dwc2_gahbcfg_t) == 4, "incorrect size"); + +typedef union { + uint32_t value; + struct TU_ATTR_PACKED { + uint32_t timeout_cal : 3; /* 0..2 Timeout calibration. + The USB standard timeout value for high-speed operation is 736 to 816 (inclusive) bit times. The USB standard + timeout value for full- speed operation is 16 to 18 (inclusive) bit times. The application must program this field + based on the speed of enumeration. The number of bit times added per PHY clock are as follows: + - High-speed: PHY clock One 30-MHz = 16 bit times, One 60-MHz = 8 bit times + - Full-speed: PHY clock One 30-MHz = 0.4 bit times, One 60-MHz = 0.2 bit times, One 48-MHz = 0.25 bit times */ + uint32_t phy_if16 : 1; // 3 PHY interface. 0: 8 bits, 1: 16 bits + uint32_t ulpi_utmi_sel : 1; // 4 ULPI/UTMI select. 0: UTMI+, 1: ULPI + uint32_t fs_intf_sel : 1; // 5 Fullspeed serial interface select. 0: 6-pin, 1: 3-pin + uint32_t phy_sel : 1; // 6 HS/FS PHY selection. 0: HS UTMI+ or ULPI, 1: FS serial transceiver + uint32_t ddr_sel : 1; // 7 ULPI DDR select. 0: Single data rate 8-bit, 1: Double data rate 4-bit + uint32_t srp_capable : 1; // 8 SRP-capable + uint32_t hnp_capable : 1; // 9 HNP-capable + uint32_t turnaround_time : 4; // 10..13 Turnaround time. 9: 8-bit UTMI+, 5: 16-bit UTMI+ + uint32_t rsv14 : 1; // 14 Reserved + uint32_t phy_low_power_clk_sel : 1; /* 15 PHY low-power clock select either 480-MHz or 48-MHz (low-power) PHY mode. + In FS/LS modes, the PHY can usually operate on a 48-MHz clock to save power. This bit is valid only for UTMI+ PHYs. + - 0: 480 Mhz internal PLL: the UTMI interface operates at either 60 MHz (8 bit) or 30 MHz (16-bit) + - 1 48 Mhz external clock: the UTMI interface operates at 48 MHz in FS mode and at either 48 or 6 MHz in LS mode */ + uint32_t otg_i2c_sel : 1; // 16 OTG I2C interface select. 0: UTMI-FS, 1: I2C for OTG signals + uint32_t ulpi_fsls : 1; /* 17 ULPI FS/LS select. 0: ULPI, 1: ULPI FS/LS. + valid only when the FS serial transceiver is selected on the ULPI PHY. */ + uint32_t ulpi_auto_resume : 1; // 18 ULPI Auto-resume + uint32_t ulpi_clk_sus_m : 1; // 19 ULPI Clock SuspendM + uint32_t ulpi_ext_vbus_drv : 1; // 20 ULPI External VBUS Drive + uint32_t ulpi_int_vbus_indicator : 1; // 21 ULPI Internal VBUS Indicator + uint32_t term_sel_dl_pulse : 1; // 22 TermSel DLine pulsing + uint32_t indicator_complement : 1; // 23 Indicator complement + uint32_t indicator_pass_through : 1; // 24 Indicator pass through + uint32_t ulpi_if_protect_disable : 1; // 25 ULPI interface protect disable + uint32_t ic_usb_capable : 1; // 26 IC_USB Capable + uint32_t ic_usb_traf_ctl : 1; // 27 IC_USB Traffic Control + uint32_t tx_end_delay : 1; // 28 TX end delay + uint32_t force_host_mode : 1; // 29 Force host mode + uint32_t force_dev_mode : 1; // 30 Force device mode + uint32_t corrupt_tx_pkt : 1; // 31 Corrupt Tx packet. 0: normal, 1: debug + }; +} dwc2_gusbcfg_t; +TU_VERIFY_STATIC(sizeof(dwc2_gusbcfg_t) == 4, "incorrect size"); + +typedef union { + uint32_t value; + struct TU_ATTR_PACKED { + uint32_t core_soft_rst : 1; // 0 Core Soft Reset + uint32_t piufs_soft_rst : 1; // 1 PIU FS Dedicated Controller Soft Reset + uint32_t frame_counter_rst : 1; // 2 Frame Counter Reset (host) + uint32_t intoken_q_flush : 1; // 3 IN Token Queue Flush + uint32_t rx_fifo_flush : 1; // 4 RX FIFO Flush + uint32_t tx_fifo_flush : 1; // 5 TX FIFO Flush + uint32_t tx_fifo_num : 5; // 6..10 TX FIFO Number + uint32_t rsv11_28 :18; // 11..28 Reserved + uint32_t core_soft_rst_done : 1; // 29 Core Soft Reset Done, from v4.20a + uint32_t dma_req : 1; // 30 DMA Request + uint32_t ahb_idle : 1; // 31 AHB Idle + }; +} dwc2_grstctl_t; +TU_VERIFY_STATIC(sizeof(dwc2_grstctl_t) == 4, "incorrect size"); + +typedef union { + uint32_t value; + struct TU_ATTR_PACKED { + uint32_t ep_ch_num : 4; // 0..3 Endpoint/Channel Number + uint32_t byte_count :11; // 4..14 Byte Count + uint32_t dpid : 2; // 15..16 Data PID + uint32_t packet_status : 4; // 17..20 Packet Status + uint32_t frame_number : 4; // 21..24 Frame Number + uint32_t rsv25_31 : 7; // 25..31 Reserved + }; +} dwc2_grxstsp_t; +TU_VERIFY_STATIC(sizeof(dwc2_grxstsp_t) == 4, "incorrect size"); + +typedef union { + uint32_t value; + struct TU_ATTR_PACKED { + uint32_t op_mode : 3; // 0..2 HNP/SRP Host/Device/OTG mode + uint32_t arch : 2; // 3..4 Slave/External/Internal DMA + uint32_t single_point : 1; // 5 0: support hub and split | 1: no hub, no split + uint32_t hs_phy_type : 2; // 6..7 0: not supported | 1: UTMI+ | 2: ULPI | 3: UTMI+ and ULPI + uint32_t fs_phy_type : 2; // 8..9 0: not supported | 1: dedicated | 2: UTMI+ | 3: ULPI + uint32_t num_dev_ep : 4; // 10..13 Number of device endpoints (excluding EP0) + uint32_t num_host_ch : 4; // 14..17 Number of host channel (excluding control) + uint32_t period_channel_support : 1; // 18 Support Periodic OUT Host Channel + uint32_t enable_dynamic_fifo : 1; // 19 Dynamic FIFO Sizing Enabled + uint32_t mul_proc_intrpt : 1; // 20 Multi-Processor Interrupt enabled (OTG_MULTI_PROC_INTRPT) + uint32_t reserved21 : 1; // 21 reserved + uint32_t nptx_q_depth : 2; // 22..23 Non-periodic request queue depth: 0 = 2. 1 = 4, 2 = 8 + uint32_t ptx_q_depth : 2; // 24..25 Host periodic request queue depth: 0 = 2. 1 = 4, 2 = 8 + uint32_t token_q_depth : 5; // 26..30 Device IN token sequence learning queue depth: 0-30 + uint32_t otg_enable_ic_usb : 1; // 31 IC_USB mode specified for mode of operation + }; +} dwc2_ghwcfg2_t; +TU_VERIFY_STATIC(sizeof(dwc2_ghwcfg2_t) == 4, "incorrect size"); + +typedef union { + uint32_t value; + struct TU_ATTR_PACKED { + uint32_t xfer_size_width : 4; // 0..3 Transfer size counter in bits = 11 + n (max 19 bits) + uint32_t packet_size_width : 3; // 4..6 Packet size counter in bits = 4 + n (max 10 bits) + uint32_t otg_enable : 1; // 7 OTG capable + uint32_t i2c_enable : 1; // 8 I2C interface is available + uint32_t vendor_ctrl_itf : 1; // 9 Vendor control interface is available + uint32_t optional_feature_removed : 1; // 10 remove User ID, GPIO, SOF toggle & counter to save gate count + uint32_t synch_reset : 1; // 11 0: async reset | 1: synch reset + uint32_t otg_adp_support : 1; // 12 ADP logic is present along with HSOTG controller + uint32_t otg_enable_hsic : 1; // 13 1: HSIC-capable with shared UTMI PHY interface | 0: non-HSIC + uint32_t battery_charger_support : 1; // s14 upport battery charger + uint32_t lpm_mode : 1; // 15 LPM mode + uint32_t dfifo_depth : 16; // DFIFO depth - EP_LOC_CNT in terms of 32-bit words + }; +} dwc2_ghwcfg3_t; +TU_VERIFY_STATIC(sizeof(dwc2_ghwcfg3_t) == 4, "incorrect size"); + +typedef union { + uint32_t value; + struct TU_ATTR_PACKED { + uint32_t num_dev_period_in_ep : 4; // 0..3 Number of Device Periodic IN Endpoints + uint32_t partial_powerdown : 1; // 4 Partial Power Down Enabled + uint32_t ahb_freq_min : 1; // 5 1: minimum of AHB frequency is less than 60 MHz + uint32_t hibernation : 1; // 6 Hibernation feature is enabled + uint32_t extended_hibernation : 1; // 7 Extended Hibernation feature is enabled + uint32_t reserved8 : 1; // 8 Reserved + uint32_t enhanced_lpm_support1 : 1; // 9 Enhanced LPM Support1 + uint32_t service_interval_flow : 1; // 10 Service Interval flow is supported + uint32_t ipg_isoc_support : 1; // 11 Interpacket GAP ISO OUT worst-case is supported + uint32_t acg_support : 1; // 12 Active clock gating is supported + uint32_t enhanced_lpm_support : 1; // 13 Enhanced LPM Support + uint32_t phy_data_width : 2; // 14..15 0: 8 bits | 1: 16 bits | 2: 8/16 software selectable + uint32_t ctrl_ep_num : 4; // 16..19 Number of Device control endpoints in addition to EP0 + uint32_t iddg_filter : 1; // 20 IDDG Filter Enabled + uint32_t vbus_valid_filter : 1; // 21 VBUS Valid Filter Enabled + uint32_t a_valid_filter : 1; // 22 A Valid Filter Enabled + uint32_t b_valid_filter : 1; // 23 B Valid Filter Enabled + uint32_t session_end_filter : 1; // 24 Session End Filter Enabled + uint32_t dedicated_fifos : 1; // 25 Dedicated tx fifo for device IN Endpoint + uint32_t num_dev_in_eps : 4; // 26..29 Number of Device IN Endpoints including EP0 + uint32_t dma_desc_enabled : 1; // scatter/gather DMA configuration enabled + uint32_t dma_desc_dynamic : 1; // Dynamic scatter/gather DMA + }; +} dwc2_ghwcfg4_t; +TU_VERIFY_STATIC(sizeof(dwc2_ghwcfg4_t) == 4, "incorrect size"); + +typedef union { + uint32_t value; + struct TU_ATTR_PACKED { + uint32_t fifo_available : 16; // 0..15 Number of words available in the Tx FIFO + uint32_t req_queue_available : 8; // 16..23 Number of spaces available in the NPT transmit request queue for both IN and OU + // 24..31 is top entry in the request queue that is currently being processed by the MAC + uint32_t qtop_terminate : 1; // 24 Last entry for selected channel + uint32_t qtop_type : 2; // 25..26 Token (0) In/Out (1) ZLP, (2) Ping/cspit, (3) Channel halt command + uint32_t qtop_ch_num : 4; // 27..30 Channel number + }; +} dwc2_hnptxsts_t; +TU_VERIFY_STATIC(sizeof(dwc2_hnptxsts_t) == 4, "incorrect size"); + +typedef union { + uint32_t value; + struct TU_ATTR_PACKED { + uint32_t fifo_available :16; // 0..15 Number of words available in the Tx FIFO + uint32_t req_queue_available : 7; // 16..22 Number of spaces available in the PTX transmit request queue + uint32_t qtop_terminate : 1; // 23 Last entry for selected channel + uint32_t qtop_last_period : 1; // 24 Last entry for selected channel is a periodic entry + uint32_t qtop_type : 2; // 25..26 Token (0) In/Out (1) ZLP, (2) Ping/cspit, (3) Channel halt command + uint32_t qtop_ch_num : 4; // 27..30 Channel number + uint32_t qtop_odd_frame : 1; // 31 Send in odd frame + }; +} dwc2_hptxsts_t; +TU_VERIFY_STATIC(sizeof(dwc2_hptxsts_t) == 4, "incorrect size"); + +typedef union { + uint32_t value; + struct TU_ATTR_PACKED { + uint32_t conn_status : 1; // 0 Port connect status + uint32_t conn_detected : 1; // 1 Port connect detected + uint32_t enable : 1; // 2 Port enable status + uint32_t enable_change : 1; // 3 Port enable change + uint32_t over_current_active : 1; // 4 Port Over-current active + uint32_t over_current_change : 1; // 5 Port Over-current change + uint32_t resume : 1; // 6 Port resume + uint32_t suspend : 1; // 7 Port suspend + uint32_t reset : 1; // 8 Port reset + uint32_t rsv9 : 1; // 9 Reserved + uint32_t line_status : 2; // 10..11 Line status + uint32_t power : 1; // 12 Port power + uint32_t test_control : 4; // 13..16 Port Test control + uint32_t speed : 2; // 17..18 Port speed + uint32_t rsv19_31 :13; // 19..31 Reserved + }; +} dwc2_hprt_t; +TU_VERIFY_STATIC(sizeof(dwc2_hprt_t) == 4, "incorrect size"); + +typedef union { + uint32_t value; + struct TU_ATTR_PACKED { + uint32_t ep_size : 11; // 0..10 Maximum packet size + uint32_t ep_num : 4; // 11..14 Endpoint number + uint32_t ep_dir : 1; // 15 Endpoint direction + uint32_t rsv16 : 1; // 16 Reserved + uint32_t low_speed_dev : 1; // 17 Low-speed device + uint32_t ep_type : 2; // 18..19 Endpoint type + uint32_t err_multi_count : 2; // 20..21 Error (splitEn = 1) / Multi (SplitEn = 0) count + uint32_t dev_addr : 7; // 22..28 Device address + uint32_t odd_frame : 1; // 29 Odd frame + uint32_t disable : 1; // 30 Channel disable + uint32_t enable : 1; // 31 Channel enable + }; +} dwc2_channel_char_t; +TU_VERIFY_STATIC(sizeof(dwc2_channel_char_t) == 4, "incorrect size"); + +typedef union { + uint32_t value; + struct TU_ATTR_PACKED { + uint32_t hub_port : 7; // 0..6 Hub port number + uint32_t hub_addr : 7; // 7..13 Hub address + uint32_t xact_pos : 2; // 14..15 Transaction position + uint32_t split_compl : 1; // 16 Split completion + uint32_t rsv17_30 : 14; // 17..30 Reserved + uint32_t split_en : 1; // 31 Split enable + }; +} dwc2_channel_split_t; +TU_VERIFY_STATIC(sizeof(dwc2_channel_split_t) == 4, "incorrect size"); + +typedef union { + uint32_t value; + struct TU_ATTR_PACKED { + uint32_t xfer_size : 19; // 0..18 Transfer size in bytes + uint32_t packet_count : 10; // 19..28 Number of packets + uint32_t pid : 2; // 29..30 Packet ID + uint32_t do_ping : 1; // 31 Do PING + }; +} dwc2_channel_tsize_t; +TU_VERIFY_STATIC(sizeof(dwc2_channel_tsize_t) == 4, "incorrect size"); + +typedef union { + uint32_t value; + struct TU_ATTR_PACKED { + uint32_t num : 16; // 0..15 Frame number + uint32_t remainning : 16; // 16..31 Frame remaining + }; +} dwc2_hfnum_t; +TU_VERIFY_STATIC(sizeof(dwc2_hfnum_t) == 4, "incorrect size"); + +// Host Channel +typedef struct { + volatile uint32_t hcchar; // 500 + 20*ch Host Channel Characteristics + volatile uint32_t hcsplt; // 504 + 20*ch Host Channel Split Control + volatile uint32_t hcint; // 508 + 20*ch Host Channel Interrupt + volatile uint32_t hcintmsk; // 50C + 20*ch Host Channel Interrupt Mask + volatile uint32_t hctsiz; // 510 + 20*ch Host Channel Transfer Size + volatile uint32_t hcdma; // 514 + 20*ch Host Channel DMA Address + uint32_t reserved518; // 518 + 20*ch + volatile uint32_t hcdmab; // 51C + 20*ch Host Channel DMA Address +} dwc2_channel_t; + +//-------------------------------------------------------------------- +// Device Register Bitfield +//-------------------------------------------------------------------- +typedef union { + uint32_t value; + struct TU_ATTR_PACKED { + uint32_t speed : 2; // 0..1 Speed + uint32_t nzsts_out_handshake : 1; // 2 Non-zero-length status OUT handshake + uint32_t en_32khz_suspsend : 1; // 3 Enable 32-kHz SUSPEND mode + uint32_t address : 7; // 4..10 Device address + uint32_t period_frame_interval : 2; // 11..12 Periodic frame interval + uint32_t en_out_nak : 1; // 13 Enable Device OUT NAK + uint32_t xcvr_delay : 1; // 14 Transceiver delay + uint32_t erratic_int_mask : 1; // 15 Erratic interrupt mask + uint32_t rsv16 : 1; // 16 Reserved + uint32_t ipg_iso_support : 1; // 17 Interpacket gap ISO support + uint32_t epin_mismatch_count : 5; // 18..22 EP IN mismatch count + uint32_t dma_desc : 1; // 23 Enable scatter/gather DMA descriptor + uint32_t period_schedule_interval : 2; // 24..25 Periodic schedule interval for scatter/gather DMA + uint32_t resume_valid : 6; // 26..31 Resume valid period + }; +} dwc2_dcfg_t; +TU_VERIFY_STATIC(sizeof(dwc2_dcfg_t) == 4, "incorrect size"); + +typedef union { + uint32_t value; + struct TU_ATTR_PACKED { + uint32_t remote_wakeup_signal : 1; // 0 Remote wakeup signal + uint32_t soft_disconnet : 1; // 1 Soft disconnect + uint32_t gnp_in_nak_status : 1; // 2 Global non-periodic NAK IN status + uint32_t gout_nak_status : 1; // 3 Global OUT NAK status + uint32_t test_control : 3; // 4..6 Test control + uint32_t set_gnp_in_nak : 1; // 7 Set global non-periodic IN NAK + uint32_t clear_gnp_in_nak : 1; // 8 Clear global non-periodic IN NAK + uint32_t set_gout_nak : 1; // 9 Set global OUT NAK + uint32_t clear_gout_nak : 1; // 10 Clear global OUT NAK + uint32_t poweron_prog_done : 1; // 11 Power-on programming done + uint32_t rsv12 : 1; // 12 Reserved + uint32_t global_multi_count : 2; // 13..14 Global multi-count + uint32_t ignore_frame_number : 1; // 15 Ignore frame number + uint32_t nak_on_babble : 1; // 16 NAK on babble + uint32_t en_cont_on_bna : 1; // 17 Enable continue on BNA + uint32_t deep_sleep_besl_reject : 1; // 18 Deep sleep BESL reject + uint32_t service_interval : 1; // 19 Service interval for ISO IN endpoint + uint32_t rsv20_31 :12; // 20..31 Reserved + }; +} dwc2_dctl_t; +TU_VERIFY_STATIC(sizeof(dwc2_dctl_t) == 4, "incorrect size"); + +typedef union { + uint32_t value; + struct TU_ATTR_PACKED { + uint32_t suspend_status : 1; // 0 Suspend status + uint32_t enum_speed : 2; // 1..2 Enumerated speed + uint32_t erratic_err : 1; // 3 Erratic error + uint32_t rsv4_7 : 4; // 4..7 Reserved + uint32_t frame_number :14; // 8..21 Frame/MicroFrame number + uint32_t line_status : 2; // 22..23 Line status + uint32_t rsv24_31 : 8; // 24..31 Reserved + }; +} dwc2_dsts_t; +TU_VERIFY_STATIC(sizeof(dwc2_dsts_t) == 4, "incorrect size"); + +typedef union { + uint32_t value; + struct TU_ATTR_PACKED { + uint32_t xfer_complete : 1; // 0 Transfer complete + uint32_t disabled : 1; // 1 Endpoint disabled + uint32_t ahb_err : 1; // 2 AHB error + uint32_t timeout : 1; // 3 Timeout + uint32_t in_rx_txfe : 1; // 4 IN token received when TxFIFO is empty + uint32_t in_rx_ep_mismatch : 1; // 5 IN token received with EP mismatch + uint32_t in_ep_nak_effective : 1; // 6 IN endpoint NAK effective + uint32_t txfifo_empty : 1; // 7 TX FIFO empty + uint32_t txfifo_underrun : 1; // 8 Tx FIFO under run + uint32_t bna : 1; // 9 Buffer not available + uint32_t rsv10 : 1; // 10 Reserved + uint32_t iso_packet_drop : 1; // 11 Isochronous OUT packet drop status + uint32_t babble_err : 1; // 12 Babble error + uint32_t nak : 1; // 13 NAK + uint32_t nyet : 1; // 14 NYET + uint32_t rsv14_31 :17; // 15..31 Reserved + }; +} dwc2_diepint_t; +TU_VERIFY_STATIC(sizeof(dwc2_diepint_t) == 4, "incorrect size"); + +typedef union { + uint32_t value; + struct TU_ATTR_PACKED { + uint32_t mps : 11; // 0..10 Maximum packet size, EP0 only use 2 bits + uint32_t next_ep : 4; // 11..14 Next endpoint number + uint32_t active : 1; // 15 Active + uint32_t dpid_iso_odd : 1; // 16 DATA0/DATA1 for bulk/interrupt, odd frame for isochronous + uint32_t nak_status : 1; // 17 NAK status + uint32_t type : 2; // 18..19 Endpoint type + uint32_t rsv20 : 1; // 20 Reserved + uint32_t stall : 1; // 21 Stall + uint32_t tx_fifo_num : 4; // 22..25 Tx FIFO number (IN) + uint32_t clear_nak : 1; // 26 Clear NAK + uint32_t set_nak : 1; // 27 Set NAK + uint32_t set_data0_iso_even : 1; // 28 Set DATA0 if bulk/interrupt, even frame for isochronous + uint32_t set_data1_iso_odd : 1; // 29 Set DATA1 if bulk/interrupt, odd frame for isochronous + uint32_t disable : 1; // 30 Disable + uint32_t enable : 1; // 31 Enable + }; +} dwc2_depctl_t; +TU_VERIFY_STATIC(sizeof(dwc2_depctl_t) == 4, "incorrect size"); + +typedef union { + uint32_t value; + struct TU_ATTR_PACKED { + uint32_t xfer_complete : 1; // 0 Transfer complete + uint32_t disabled : 1; // 1 Endpoint disabled + uint32_t ahb_err : 1; // 2 AHB error + uint32_t setup_phase_done : 1; // 3 Setup phase done + uint32_t out_rx_ep_disabled : 1; // 4 OUT token received when endpoint disabled + uint32_t status_phase_rx : 1; // 5 Status phase received + uint32_t setup_b2b : 1; // 6 Setup packet back-to-back + uint32_t rsv7 : 1; // 7 Reserved + uint32_t out_packet_err : 1; // 8 OUT packet error + uint32_t bna : 1; // 9 Buffer not available + uint32_t rsv10 : 1; // 10 Reserved + uint32_t iso_packet_drop : 1; // 11 Isochronous OUT packet drop status + uint32_t babble_err : 1; // 12 Babble error + uint32_t nak : 1; // 13 NAK + uint32_t nyet : 1; // 14 NYET + uint32_t setup_packet_rx : 1; // 15 Setup packet received (Buffer DMA Mode only) + uint32_t rsv16_31 :16; // 16..31 Reserved + }; +} dwc2_doepint_t; +TU_VERIFY_STATIC(sizeof(dwc2_doepint_t) == 4, "incorrect size"); + +typedef union { + uint32_t value; + struct TU_ATTR_PACKED { + uint32_t xfer_size : 19; // 0..18 Transfer size in bytes + uint32_t packet_count : 10; // 19..28 Number of packets + uint32_t mc_pid : 2; // 29..30 IN: Multi Count, OUT: PID + }; +} dwc2_ep_tsize_t; +TU_VERIFY_STATIC(sizeof(dwc2_ep_tsize_t) == 4, "incorrect size"); + +// Device IN/OUT Endpoint +typedef struct { + union { + volatile uint32_t diepctl; + volatile uint32_t doepctl; + volatile uint32_t ctl; + }; + uint32_t rsv04; + union { + volatile uint32_t intr; + volatile uint32_t diepint; + volatile uint32_t doepint; + }; + uint32_t rsv0c; + union { + volatile uint32_t dieptsiz; + volatile uint32_t doeptsiz; + volatile uint32_t tsiz; + }; + union { + volatile uint32_t diepdma; + volatile uint32_t doepdma; + }; + volatile uint32_t dtxfsts; + uint32_t rsv1c; +} dwc2_dep_t; + +TU_VERIFY_STATIC(sizeof(dwc2_dep_t) == 0x20, "incorrect size"); + +//-------------------------------------------------------------------- +// CSR Register Map +//-------------------------------------------------------------------- +typedef struct { + //------------- Core Global ------------- + volatile uint32_t gotgctl; // 000 OTG Control and Status + volatile uint32_t gotgint; // 004 OTG Interrupt + volatile uint32_t gahbcfg; // 008 AHB Configuration + volatile uint32_t gusbcfg; // 00c USB Configuration + volatile uint32_t grstctl; // 010 Reset + volatile uint32_t gintsts; // 014 Interrupt + volatile uint32_t gintmsk; // 018 Interrupt Mask + volatile uint32_t grxstsr; // 01c Receive Status Debug Read + volatile uint32_t grxstsp; // 020 Receive Status Read/Pop + volatile uint32_t grxfsiz; // 024 Receive FIFO Size + union { + volatile uint32_t dieptxf0; // 028 EP0 Tx FIFO Size + volatile uint32_t gnptxfsiz; // 028 Non-periodic Transmit FIFO Size + }; + union { + volatile uint32_t hnptxsts; // 02c Non-periodic Transmit FIFO/Queue Status + volatile uint32_t gnptxsts; + }; + volatile uint32_t gi2cctl; // 030 I2C Address + volatile uint32_t gpvndctl; // 034 PHY Vendor Control + union { + volatile uint32_t ggpio; // 038 General Purpose IO + volatile uint32_t stm32_gccfg; // 038 STM32 General Core Configuration + }; + volatile uint32_t guid; // 03C User (Application programmable) ID + volatile uint32_t gsnpsid; // 040 Synopsys ID + Release version + volatile uint32_t ghwcfg1; // 044 User Hardware Configuration1: endpoint dir (2 bit per ep) + volatile uint32_t ghwcfg2; // 048 User Hardware Configuration2 + volatile uint32_t ghwcfg3; // 04C User Hardware Configuration3 + union { + volatile uint32_t ghwcfg4; // 050 User Hardware Configuration4 + volatile dwc2_ghwcfg4_t ghwcfg4_bm; + }; + volatile uint32_t glpmcfg; // 054 Core LPM Configuration + volatile uint32_t gpwrdn; // 058 Power Down + volatile uint32_t gdfifocfg; // 05C DFIFO Software Configuration + volatile uint32_t gadpctl; // 060 ADP Timer, Control and Status + uint32_t reserved64[39]; // 064..0FF + volatile uint32_t hptxfsiz; // 100 Host Periodic Tx FIFO Size + volatile uint32_t dieptxf[15]; // 104..13C Device Periodic Transmit FIFO Size + uint32_t reserved140[176]; // 140..3FF + + //------------ Host ------------- + volatile uint32_t hcfg; // 400 Host Configuration + volatile uint32_t hfir; // 404 Host Frame Interval + volatile uint32_t hfnum; // 408 Host Frame Number / Frame Remaining + uint32_t reserved40c; // 40C + volatile uint32_t hptxsts; // 410 Host Periodic TX FIFO / Queue Status + volatile uint32_t haint; // 414 Host All Channels Interrupt + volatile uint32_t haintmsk; // 418 Host All Channels Interrupt Mask + volatile uint32_t hflbaddr; // 41C Host Frame List Base Address + uint32_t reserved420[8]; // 420..43F + volatile uint32_t hprt; // 440 Host Port Control and Status + uint32_t reserved444[47]; // 444..4FF + + //------------- Host Channel -------- + dwc2_channel_t channel[16]; // 500..6FF Host Channels 0-15 + uint32_t reserved700[64]; // 700..7FF + + //------------- Device ----------- + volatile uint32_t dcfg; // 800 Device Configuration + volatile uint32_t dctl; // 804 Device Control + volatile uint32_t dsts; // 808 Device Status (RO) + uint32_t reserved80c; // 80C + volatile uint32_t diepmsk; // 810 Device IN Endpoint Interrupt Mask + volatile uint32_t doepmsk; // 814 Device OUT Endpoint Interrupt Mask + volatile uint32_t daint; // 818 Device All Endpoints Interrupt + volatile uint32_t daintmsk; // 81C Device All Endpoints Interrupt Mask + volatile uint32_t dtknqr1; // 820 Device IN token sequence learning queue read1 + volatile uint32_t dtknqr2; // 824 Device IN token sequence learning queue read2 + volatile uint32_t dvbusdis; // 828 Device VBUS Discharge Time + volatile uint32_t dvbuspulse; // 82C Device VBUS Pulsing Time + volatile uint32_t dthrctl; // 830 Device threshold Control + volatile uint32_t diepempmsk; // 834 Device IN Endpoint FIFO Empty Interrupt Mask + + // Device Each Endpoint (IN/OUT) Interrupt/Mask for generating dedicated EP interrupt line require + // OTG_MULTI_PROC_INTRPT=1 + volatile uint32_t deachint; // 838 Device Each Endpoint Interrupt + volatile uint32_t deachmsk; // 83C Device Each Endpoint Interrupt mask + volatile uint32_t diepeachmsk[16]; // 840..87C Device Each IN Endpoint mask + volatile uint32_t doepeachmsk[16]; // 880..8BF Device Each OUT Endpoint mask + uint32_t reserved8c0[16]; // 8C0..8FF + + //------------- Device Endpoint ----- + union { + dwc2_dep_t ep[2][16]; // 0: IN, 1 OUT + struct { + dwc2_dep_t epin[16]; // 900..AFF IN Endpoints + dwc2_dep_t epout[16]; // B00..CFF OUT Endpoints + }; + }; + uint32_t reservedd00[64]; // D00..DFF + + //------------- Power Clock --------- + volatile uint32_t pcgcctl; // E00 Power and Clock Gating Characteristic Control + volatile uint32_t pcgcctl1; // E04 Power and Clock Gating Characteristic Control 1 + uint32_t reservede08[126]; // E08..FFF + + //------------- FIFOs ------------- + // Word-accessed only using first pointer since it auto shift + volatile uint32_t fifo[16][0x400]; // 1000..FFFF Endpoint FIFO +} dwc2_regs_t; + +TU_VERIFY_STATIC(offsetof(dwc2_regs_t, hcfg ) == 0x0400, "incorrect size"); +TU_VERIFY_STATIC(offsetof(dwc2_regs_t, channel) == 0x0500, "incorrect size"); +TU_VERIFY_STATIC(offsetof(dwc2_regs_t, dcfg ) == 0x0800, "incorrect size"); +TU_VERIFY_STATIC(offsetof(dwc2_regs_t, epin ) == 0x0900, "incorrect size"); +TU_VERIFY_STATIC(offsetof(dwc2_regs_t, epout ) == 0x0B00, "incorrect size"); +TU_VERIFY_STATIC(offsetof(dwc2_regs_t, pcgcctl) == 0x0E00, "incorrect size"); +TU_VERIFY_STATIC(offsetof(dwc2_regs_t, fifo ) == 0x1000, "incorrect size"); + +//--------------------------------------------------------------------+ +// Register Bit Definitions +//--------------------------------------------------------------------+ + +/******************** Bit definition for GOTGCTL register ********************/ +#define GOTGCTL_SRQSCS_Pos (0U) +#define GOTGCTL_SRQSCS_Msk (0x1UL << GOTGCTL_SRQSCS_Pos) // 0x00000001 +#define GOTGCTL_SRQSCS GOTGCTL_SRQSCS_Msk // Session request success +#define GOTGCTL_SRQ_Pos (1U) +#define GOTGCTL_SRQ_Msk (0x1UL << GOTGCTL_SRQ_Pos) // 0x00000002 +#define GOTGCTL_SRQ GOTGCTL_SRQ_Msk // Session request +#define GOTGCTL_VBVALOEN_Pos (2U) +#define GOTGCTL_VBVALOEN_Msk (0x1UL << GOTGCTL_VBVALOEN_Pos) // 0x00000004 +#define GOTGCTL_VBVALOEN GOTGCTL_VBVALOEN_Msk // VBUS valid override enable +#define GOTGCTL_VBVALOVAL_Pos (3U) +#define GOTGCTL_VBVALOVAL_Msk (0x1UL << GOTGCTL_VBVALOVAL_Pos) // 0x00000008 +#define GOTGCTL_VBVALOVAL GOTGCTL_VBVALOVAL_Msk // VBUS valid override value +#define GOTGCTL_AVALOEN_Pos (4U) +#define GOTGCTL_AVALOEN_Msk (0x1UL << GOTGCTL_AVALOEN_Pos) // 0x00000010 +#define GOTGCTL_AVALOEN GOTGCTL_AVALOEN_Msk // A-peripheral session valid override enable +#define GOTGCTL_AVALOVAL_Pos (5U) +#define GOTGCTL_AVALOVAL_Msk (0x1UL << GOTGCTL_AVALOVAL_Pos) // 0x00000020 +#define GOTGCTL_AVALOVAL GOTGCTL_AVALOVAL_Msk // A-peripheral session valid override value +#define GOTGCTL_BVALOEN_Pos (6U) +#define GOTGCTL_BVALOEN_Msk (0x1UL << GOTGCTL_BVALOEN_Pos) // 0x00000040 +#define GOTGCTL_BVALOEN GOTGCTL_BVALOEN_Msk // B-peripheral session valid override enable +#define GOTGCTL_BVALOVAL_Pos (7U) +#define GOTGCTL_BVALOVAL_Msk (0x1UL << GOTGCTL_BVALOVAL_Pos) // 0x00000080 +#define GOTGCTL_BVALOVAL GOTGCTL_BVALOVAL_Msk // B-peripheral session valid override value +#define GOTGCTL_HNGSCS_Pos (8U) +#define GOTGCTL_HNGSCS_Msk (0x1UL << GOTGCTL_HNGSCS_Pos) // 0x00000100 +#define GOTGCTL_HNGSCS GOTGCTL_HNGSCS_Msk // Host set HNP enable +#define GOTGCTL_HNPRQ_Pos (9U) +#define GOTGCTL_HNPRQ_Msk (0x1UL << GOTGCTL_HNPRQ_Pos) // 0x00000200 +#define GOTGCTL_HNPRQ GOTGCTL_HNPRQ_Msk // HNP request +#define GOTGCTL_HSHNPEN_Pos (10U) +#define GOTGCTL_HSHNPEN_Msk (0x1UL << GOTGCTL_HSHNPEN_Pos) // 0x00000400 +#define GOTGCTL_HSHNPEN GOTGCTL_HSHNPEN_Msk // Host set HNP enable +#define GOTGCTL_DHNPEN_Pos (11U) +#define GOTGCTL_DHNPEN_Msk (0x1UL << GOTGCTL_DHNPEN_Pos) // 0x00000800 +#define GOTGCTL_DHNPEN GOTGCTL_DHNPEN_Msk // Device HNP enabled +#define GOTGCTL_EHEN_Pos (12U) +#define GOTGCTL_EHEN_Msk (0x1UL << GOTGCTL_EHEN_Pos) // 0x00001000 +#define GOTGCTL_EHEN GOTGCTL_EHEN_Msk // Embedded host enable +#define GOTGCTL_CIDSTS_Pos (16U) +#define GOTGCTL_CIDSTS_Msk (0x1UL << GOTGCTL_CIDSTS_Pos) // 0x00010000 +#define GOTGCTL_CIDSTS GOTGCTL_CIDSTS_Msk // Connector ID status +#define GOTGCTL_DBCT_Pos (17U) +#define GOTGCTL_DBCT_Msk (0x1UL << GOTGCTL_DBCT_Pos) // 0x00020000 +#define GOTGCTL_DBCT GOTGCTL_DBCT_Msk // Long/short debounce time +#define GOTGCTL_ASVLD_Pos (18U) +#define GOTGCTL_ASVLD_Msk (0x1UL << GOTGCTL_ASVLD_Pos) // 0x00040000 +#define GOTGCTL_ASVLD GOTGCTL_ASVLD_Msk // A-session valid +#define GOTGCTL_BSESVLD_Pos (19U) +#define GOTGCTL_BSESVLD_Msk (0x1UL << GOTGCTL_BSESVLD_Pos) // 0x00080000 +#define GOTGCTL_BSESVLD GOTGCTL_BSESVLD_Msk // B-session valid +#define GOTGCTL_OTGVER_Pos (20U) +#define GOTGCTL_OTGVER_Msk (0x1UL << GOTGCTL_OTGVER_Pos) // 0x00100000 +#define GOTGCTL_OTGVER GOTGCTL_OTGVER_Msk // OTG version + +/******************** Bit definition for HCFG register ********************/ +#define HCFG_FSLS_PHYCLK_SEL_Pos (0U) +#define HCFG_FSLS_PHYCLK_SEL_Msk (0x3UL << HCFG_FSLS_PHYCLK_SEL_Pos) // 0x00000003 +#define HCFG_FSLS_PHYCLK_SEL HCFG_FSLS_PHYCLK_SEL_Msk // FS/LS PHY clock select +#define HCFG_FSLS_PHYCLK_SEL_30_60MHZ (0x0UL << HCFG_FSLS_PHYCLK_SEL_Pos) // 0x00000000 +#define HCFG_FSLS_PHYCLK_SEL_48MHZ (0x1UL << HCFG_FSLS_PHYCLK_SEL_Pos) // 0x00000001 +#define HCFG_FSLS_PHYCLK_SEL_6MHZ (0x2UL << HCFG_FSLS_PHYCLK_SEL_Pos) // 0x00000002 + +#define HCFG_FSLS_ONLY_Pos (2U) +#define HCFG_FSLS_ONLY_Msk (0x1UL << HCFG_FSLS_ONLY_Pos) // 0x00000004 +#define HCFG_FSLS_ONLY HCFG_FSLS_ONLY_Msk // FS- and LS-only support + +/******************** Bit definition for PCGCR register ********************/ +#define PCGCR_STPPCLK_Pos (0U) +#define PCGCR_STPPCLK_Msk (0x1UL << PCGCR_STPPCLK_Pos) // 0x00000001 +#define PCGCR_STPPCLK PCGCR_STPPCLK_Msk // Stop PHY clock +#define PCGCR_GATEHCLK_Pos (1U) +#define PCGCR_GATEHCLK_Msk (0x1UL << PCGCR_GATEHCLK_Pos) // 0x00000002 +#define PCGCR_GATEHCLK PCGCR_GATEHCLK_Msk // Gate HCLK +#define PCGCR_PHYSUSP_Pos (4U) +#define PCGCR_PHYSUSP_Msk (0x1UL << PCGCR_PHYSUSP_Pos) // 0x00000010 +#define PCGCR_PHYSUSP PCGCR_PHYSUSP_Msk // PHY suspended + +/******************** Bit definition for GOTGINT register ********************/ +#define GOTGINT_SEDET_Pos (2U) +#define GOTGINT_SEDET_Msk (0x1UL << GOTGINT_SEDET_Pos) // 0x00000004 +#define GOTGINT_SEDET GOTGINT_SEDET_Msk // Session end detected +#define GOTGINT_SRSSCHG_Pos (8U) +#define GOTGINT_SRSSCHG_Msk (0x1UL << GOTGINT_SRSSCHG_Pos) // 0x00000100 +#define GOTGINT_SRSSCHG GOTGINT_SRSSCHG_Msk // Session request success status change +#define GOTGINT_HNSSCHG_Pos (9U) +#define GOTGINT_HNSSCHG_Msk (0x1UL << GOTGINT_HNSSCHG_Pos) // 0x00000200 +#define GOTGINT_HNSSCHG GOTGINT_HNSSCHG_Msk // Host negotiation success status change +#define GOTGINT_HNGDET_Pos (17U) +#define GOTGINT_HNGDET_Msk (0x1UL << GOTGINT_HNGDET_Pos) // 0x00020000 +#define GOTGINT_HNGDET GOTGINT_HNGDET_Msk // Host negotiation detected +#define GOTGINT_ADTOCHG_Pos (18U) +#define GOTGINT_ADTOCHG_Msk (0x1UL << GOTGINT_ADTOCHG_Pos) // 0x00040000 +#define GOTGINT_ADTOCHG GOTGINT_ADTOCHG_Msk // A-device timeout change +#define GOTGINT_DBCDNE_Pos (19U) +#define GOTGINT_DBCDNE_Msk (0x1UL << GOTGINT_DBCDNE_Pos) // 0x00080000 +#define GOTGINT_DBCDNE GOTGINT_DBCDNE_Msk // Debounce done +#define GOTGINT_IDCHNG_Pos (20U) +#define GOTGINT_IDCHNG_Msk (0x1UL << GOTGINT_IDCHNG_Pos) // 0x00100000 +#define GOTGINT_IDCHNG GOTGINT_IDCHNG_Msk // Change in ID pin input value + +/******************** Bit definition for DCFG register ********************/ +#define DCFG_DSPD_Pos (0U) +#define DCFG_DSPD_Msk (0x3UL << DCFG_DSPD_Pos) // 0x00000003 +#define DCFG_DSPD_HS 0 // Highspeed +#define DCFG_DSPD_FS_HSPHY 1 // Fullspeed on HS PHY +#define DCFG_DSPD_LS 2 // Lowspeed +#define DCFG_DSPD_FS 3 // Fullspeed on FS PHY + +#define DCFG_NZLSOHSK_Pos (2U) +#define DCFG_NZLSOHSK_Msk (0x1UL << DCFG_NZLSOHSK_Pos) // 0x00000004 +#define DCFG_NZLSOHSK DCFG_NZLSOHSK_Msk // Nonzero-length status OUT handshake + +#define DCFG_DAD_Pos (4U) +#define DCFG_DAD_Msk (0x7FUL << DCFG_DAD_Pos) // 0x000007F0 +#define DCFG_DAD DCFG_DAD_Msk // Device address +#define DCFG_DAD_0 (0x01UL << DCFG_DAD_Pos) // 0x00000010 +#define DCFG_DAD_1 (0x02UL << DCFG_DAD_Pos) // 0x00000020 +#define DCFG_DAD_2 (0x04UL << DCFG_DAD_Pos) // 0x00000040 +#define DCFG_DAD_3 (0x08UL << DCFG_DAD_Pos) // 0x00000080 +#define DCFG_DAD_4 (0x10UL << DCFG_DAD_Pos) // 0x00000100 +#define DCFG_DAD_5 (0x20UL << DCFG_DAD_Pos) // 0x00000200 +#define DCFG_DAD_6 (0x40UL << DCFG_DAD_Pos) // 0x00000400 + +#define DCFG_PFIVL_Pos (11U) +#define DCFG_PFIVL_Msk (0x3UL << DCFG_PFIVL_Pos) // 0x00001800 +#define DCFG_PFIVL DCFG_PFIVL_Msk // Periodic (micro)frame interval +#define DCFG_PFIVL_0 (0x1UL << DCFG_PFIVL_Pos) // 0x00000800 +#define DCFG_PFIVL_1 (0x2UL << DCFG_PFIVL_Pos) // 0x00001000 + +#define DCFG_XCVRDLY_Pos (14U) +#define DCFG_XCVRDLY_Msk (0x1UL << DCFG_XCVRDLY_Pos) // 0x00004000 +#define DCFG_XCVRDLY DCFG_XCVRDLY_Msk // Enables delay between xcvr_sel and txvalid during device chirp + +#define DCFG_PERSCHIVL_Pos (24U) +#define DCFG_PERSCHIVL_Msk (0x3UL << DCFG_PERSCHIVL_Pos) // 0x03000000 +#define DCFG_PERSCHIVL DCFG_PERSCHIVL_Msk // Periodic scheduling interval +#define DCFG_PERSCHIVL_0 (0x1UL << DCFG_PERSCHIVL_Pos) // 0x01000000 +#define DCFG_PERSCHIVL_1 (0x2UL << DCFG_PERSCHIVL_Pos) // 0x02000000 + +/******************** Bit definition for DCTL register ********************/ +#define DCTL_RWUSIG_Pos (0U) +#define DCTL_RWUSIG_Msk (0x1UL << DCTL_RWUSIG_Pos) // 0x00000001 +#define DCTL_RWUSIG DCTL_RWUSIG_Msk // Remote wakeup signaling +#define DCTL_SDIS_Pos (1U) +#define DCTL_SDIS_Msk (0x1UL << DCTL_SDIS_Pos) // 0x00000002 +#define DCTL_SDIS DCTL_SDIS_Msk // Soft disconnect +#define DCTL_GINSTS_Pos (2U) +#define DCTL_GINSTS_Msk (0x1UL << DCTL_GINSTS_Pos) // 0x00000004 +#define DCTL_GINSTS DCTL_GINSTS_Msk // Global IN NAK status +#define DCTL_GONSTS_Pos (3U) +#define DCTL_GONSTS_Msk (0x1UL << DCTL_GONSTS_Pos) // 0x00000008 +#define DCTL_GONSTS DCTL_GONSTS_Msk // Global OUT NAK status + +#define DCTL_TCTL_Pos (4U) +#define DCTL_TCTL_Msk (0x7UL << DCTL_TCTL_Pos) // 0x00000070 +#define DCTL_TCTL DCTL_TCTL_Msk // Test control +#define DCTL_TCTL_0 (0x1UL << DCTL_TCTL_Pos) // 0x00000010 +#define DCTL_TCTL_1 (0x2UL << DCTL_TCTL_Pos) // 0x00000020 +#define DCTL_TCTL_2 (0x4UL << DCTL_TCTL_Pos) // 0x00000040 +#define DCTL_SGINAK_Pos (7U) +#define DCTL_SGINAK_Msk (0x1UL << DCTL_SGINAK_Pos) // 0x00000080 +#define DCTL_SGINAK DCTL_SGINAK_Msk // Set global IN NAK +#define DCTL_CGINAK_Pos (8U) +#define DCTL_CGINAK_Msk (0x1UL << DCTL_CGINAK_Pos) // 0x00000100 +#define DCTL_CGINAK DCTL_CGINAK_Msk // Clear global IN NAK +#define DCTL_SGONAK_Pos (9U) +#define DCTL_SGONAK_Msk (0x1UL << DCTL_SGONAK_Pos) // 0x00000200 +#define DCTL_SGONAK DCTL_SGONAK_Msk // Set global OUT NAK +#define DCTL_CGONAK_Pos (10U) +#define DCTL_CGONAK_Msk (0x1UL << DCTL_CGONAK_Pos) // 0x00000400 +#define DCTL_CGONAK DCTL_CGONAK_Msk // Clear global OUT NAK +#define DCTL_POPRGDNE_Pos (11U) +#define DCTL_POPRGDNE_Msk (0x1UL << DCTL_POPRGDNE_Pos) // 0x00000800 +#define DCTL_POPRGDNE DCTL_POPRGDNE_Msk // Power-on programming done + +/******************** Bit definition for HFIR register ********************/ +#define HFIR_FRIVL_Pos (0U) +#define HFIR_FRIVL_Msk (0xFFFFUL << HFIR_FRIVL_Pos) // 0x0000FFFF +#define HFIR_FRIVL HFIR_FRIVL_Msk // Frame interval +#define HFIR_RELOAD_CTRL_Pos (16U) // available since v2.92a +#define HFIR_RELOAD_CTRL_Msk (0x1UL << HFIR_RELOAD_CTRL_Pos) +#define HFIR_RELOAD_CTRL HFIR_RELOAD_CTRL_Msk + +/******************** Bit definition for HFNUM register ********************/ +#define HFNUM_FRNUM_Pos (0U) +#define HFNUM_FRNUM_Msk (0xFFFFUL << HFNUM_FRNUM_Pos) // 0x0000FFFF +#define HFNUM_FRNUM HFNUM_FRNUM_Msk // Frame number +#define HFNUM_FTREM_Pos (16U) +#define HFNUM_FTREM_Msk (0xFFFFUL << HFNUM_FTREM_Pos) // 0xFFFF0000 +#define HFNUM_FTREM HFNUM_FTREM_Msk // Frame time remaining + +/******************** Bit definition for DSTS register ********************/ +#define DSTS_SUSPSTS_Pos (0U) +#define DSTS_SUSPSTS_Msk (0x1UL << DSTS_SUSPSTS_Pos) // 0x00000001 +#define DSTS_SUSPSTS DSTS_SUSPSTS_Msk // Suspend status +#define DSTS_ENUMSPD_Pos (1U) +#define DSTS_ENUMSPD_Msk (0x3UL << DSTS_ENUMSPD_Pos) // 0x00000006 +#define DSTS_ENUMSPD DSTS_ENUMSPD_Msk // Enumerated speed +#define DSTS_ENUMSPD_HS 0 // Highspeed +#define DSTS_ENUMSPD_FS_HSPHY 1 // Fullspeed on HS PHY +#define DSTS_ENUMSPD_LS 2 // Lowspeed +#define DSTS_ENUMSPD_FS 3 // Fullspeed on FS PHY + + +#define DSTS_EERR_Pos (3U) +#define DSTS_EERR_Msk (0x1UL << DSTS_EERR_Pos) // 0x00000008 +#define DSTS_EERR DSTS_EERR_Msk // Erratic error +#define DSTS_FNSOF_Pos (8U) +#define DSTS_FNSOF_Msk (0x3FFFUL << DSTS_FNSOF_Pos) // 0x003FFF00 +#define DSTS_FNSOF DSTS_FNSOF_Msk // Frame number of the received SOF + +/******************** Bit definition for GAHBCFG register ********************/ +#define GAHBCFG_GINT_Pos (0U) +#define GAHBCFG_GINT_Msk (0x1UL << GAHBCFG_GINT_Pos) // 0x00000001 +#define GAHBCFG_GINT GAHBCFG_GINT_Msk // Global interrupt mask +#define GAHBCFG_HBSTLEN_Pos (1U) +#define GAHBCFG_HBSTLEN_Msk (0xFUL << GAHBCFG_HBSTLEN_Pos) // 0x0000001E +#define GAHBCFG_HBSTLEN GAHBCFG_HBSTLEN_Msk // Burst length/type +#define GAHBCFG_HBSTLEN_0 (0x0UL << GAHBCFG_HBSTLEN_Pos) // Single +#define GAHBCFG_HBSTLEN_1 (0x1UL << GAHBCFG_HBSTLEN_Pos) // INCR +#define GAHBCFG_HBSTLEN_2 (0x3UL << GAHBCFG_HBSTLEN_Pos) // INCR4 +#define GAHBCFG_HBSTLEN_3 (0x5UL << GAHBCFG_HBSTLEN_Pos) // INCR8 +#define GAHBCFG_HBSTLEN_4 (0x7UL << GAHBCFG_HBSTLEN_Pos) // INCR16 +#define GAHBCFG_DMAEN_Pos (5U) +#define GAHBCFG_DMAEN_Msk (0x1UL << GAHBCFG_DMAEN_Pos) // 0x00000020 +#define GAHBCFG_DMAEN GAHBCFG_DMAEN_Msk // DMA enable +#define GAHBCFG_TX_FIFO_EPMTY_LVL_Pos (7U) +#define GAHBCFG_TX_FIFO_EPMTY_LVL_Msk (0x1UL << GAHBCFG_TX_FIFO_EPMTY_LVL_Pos) // 0x00000080 +#define GAHBCFG_TX_FIFO_EPMTY_LVL GAHBCFG_TX_FIFO_EPMTY_LVL_Msk // TxFIFO empty level +#define GAHBCFG_PTX_FIFO_EPMTY_LVL_Pos (8U) +#define GAHBCFG_PTX_FIFO_EPMTY_LVL_Msk (0x1UL << GAHBCFG_PTX_FIFO_EPMTY_LVL_Pos) // 0x00000100 +#define GAHBCFG_PTX_FIFO_EPMTY_LVL GAHBCFG_PTX_FIFO_EPMTY_LVL_Msk // Periodic TxFIFO empty level + +/******************** Bit definition for GUSBCFG register ********************/ +#define GUSBCFG_TOCAL_Pos (0U) +#define GUSBCFG_TOCAL_Msk (0x7UL << GUSBCFG_TOCAL_Pos) // 0x00000007 +#define GUSBCFG_TOCAL GUSBCFG_TOCAL_Msk // HS/FS timeout calibration +#define GUSBCFG_PHYIF16_Pos (3U) +#define GUSBCFG_PHYIF16_Msk (0x1UL << GUSBCFG_PHYIF16_Pos) // 0x00000008 +#define GUSBCFG_PHYIF16 GUSBCFG_PHYIF16_Msk // PHY Interface (PHYIf) +#define GUSBCFG_ULPI_UTMI_SEL_Pos (4U) +#define GUSBCFG_ULPI_UTMI_SEL_Msk (0x1UL << GUSBCFG_ULPI_UTMI_SEL_Pos) // 0x00000010 +#define GUSBCFG_ULPI_UTMI_SEL GUSBCFG_ULPI_UTMI_SEL_Msk // ULPI or UTMI+ Select (ULPI_UTMI_Sel) +#define GUSBCFG_PHYSEL_Pos (6U) +#define GUSBCFG_PHYSEL_Msk (0x1UL << GUSBCFG_PHYSEL_Pos) // 0x00000040 +#define GUSBCFG_PHYSEL GUSBCFG_PHYSEL_Msk // USB 2.0 high-speed ULPI PHY or USB 1.1 full-speed serial transceiver select +#define GUSBCFG_DDRSEL TU_BIT(7) // Single Data Rate (SDR) or Double Data Rate (DDR) or ULPI interface. +#define GUSBCFG_SRPCAP_Pos (8U) +#define GUSBCFG_SRPCAP_Msk (0x1UL << GUSBCFG_SRPCAP_Pos) // 0x00000100 +#define GUSBCFG_SRPCAP GUSBCFG_SRPCAP_Msk // SRP-capable +#define GUSBCFG_HNPCAP_Pos (9U) +#define GUSBCFG_HNPCAP_Msk (0x1UL << GUSBCFG_HNPCAP_Pos) // 0x00000200 +#define GUSBCFG_HNPCAP GUSBCFG_HNPCAP_Msk // HNP-capable +#define GUSBCFG_TRDT_Pos (10U) +#define GUSBCFG_TRDT_Msk (0xFUL << GUSBCFG_TRDT_Pos) // 0x00003C00 +#define GUSBCFG_TRDT GUSBCFG_TRDT_Msk // USB turnaround time +#define GUSBCFG_PHYLPCS_Pos (15U) +#define GUSBCFG_PHYLPCS_Msk (0x1UL << GUSBCFG_PHYLPCS_Pos) // 0x00008000 +#define GUSBCFG_PHYLPCS GUSBCFG_PHYLPCS_Msk // PHY Low-power clock select +#define GUSBCFG_ULPIFSLS_Pos (17U) +#define GUSBCFG_ULPIFSLS_Msk (0x1UL << GUSBCFG_ULPIFSLS_Pos) // 0x00020000 +#define GUSBCFG_ULPIFSLS GUSBCFG_ULPIFSLS_Msk // ULPI FS/LS select +#define GUSBCFG_ULPIAR_Pos (18U) +#define GUSBCFG_ULPIAR_Msk (0x1UL << GUSBCFG_ULPIAR_Pos) // 0x00040000 +#define GUSBCFG_ULPIAR GUSBCFG_ULPIAR_Msk // ULPI Auto-resume +#define GUSBCFG_ULPICSM_Pos (19U) +#define GUSBCFG_ULPICSM_Msk (0x1UL << GUSBCFG_ULPICSM_Pos) // 0x00080000 +#define GUSBCFG_ULPICSM GUSBCFG_ULPICSM_Msk // ULPI Clock SuspendM +#define GUSBCFG_ULPIEVBUSD_Pos (20U) +#define GUSBCFG_ULPIEVBUSD_Msk (0x1UL << GUSBCFG_ULPIEVBUSD_Pos) // 0x00100000 +#define GUSBCFG_ULPIEVBUSD GUSBCFG_ULPIEVBUSD_Msk // ULPI External VBUS Drive +#define GUSBCFG_ULPIEVBUSI_Pos (21U) +#define GUSBCFG_ULPIEVBUSI_Msk (0x1UL << GUSBCFG_ULPIEVBUSI_Pos) // 0x00200000 +#define GUSBCFG_ULPIEVBUSI GUSBCFG_ULPIEVBUSI_Msk // ULPI external VBUS indicator +#define GUSBCFG_TSDPS_Pos (22U) +#define GUSBCFG_TSDPS_Msk (0x1UL << GUSBCFG_TSDPS_Pos) // 0x00400000 +#define GUSBCFG_TSDPS GUSBCFG_TSDPS_Msk // TermSel DLine pulsing selection +#define GUSBCFG_PCCI_Pos (23U) +#define GUSBCFG_PCCI_Msk (0x1UL << GUSBCFG_PCCI_Pos) // 0x00800000 +#define GUSBCFG_PCCI GUSBCFG_PCCI_Msk // Indicator complement +#define GUSBCFG_PTCI_Pos (24U) +#define GUSBCFG_PTCI_Msk (0x1UL << GUSBCFG_PTCI_Pos) // 0x01000000 +#define GUSBCFG_PTCI GUSBCFG_PTCI_Msk // Indicator pass through +#define GUSBCFG_ULPIIPD_Pos (25U) +#define GUSBCFG_ULPIIPD_Msk (0x1UL << GUSBCFG_ULPIIPD_Pos) // 0x02000000 +#define GUSBCFG_ULPIIPD GUSBCFG_ULPIIPD_Msk // ULPI interface protect disable +#define GUSBCFG_FHMOD_Pos (29U) +#define GUSBCFG_FHMOD_Msk (0x1UL << GUSBCFG_FHMOD_Pos) // 0x20000000 +#define GUSBCFG_FHMOD GUSBCFG_FHMOD_Msk // Forced host mode +#define GUSBCFG_FDMOD_Pos (30U) +#define GUSBCFG_FDMOD_Msk (0x1UL << GUSBCFG_FDMOD_Pos) // 0x40000000 +#define GUSBCFG_FDMOD GUSBCFG_FDMOD_Msk // Forced peripheral mode +#define GUSBCFG_CTXPKT_Pos (31U) +#define GUSBCFG_CTXPKT_Msk (0x1UL << GUSBCFG_CTXPKT_Pos) // 0x80000000 +#define GUSBCFG_CTXPKT GUSBCFG_CTXPKT_Msk // Corrupt Tx packet + +/******************** Bit definition for GRSTCTL register ********************/ +#define GRSTCTL_CSRST_Pos (0U) +#define GRSTCTL_CSRST_Msk (0x1UL << GRSTCTL_CSRST_Pos) // 0x00000001 +#define GRSTCTL_CSRST GRSTCTL_CSRST_Msk // Core soft reset +#define GRSTCTL_HSRST_Pos (1U) +#define GRSTCTL_HSRST_Msk (0x1UL << GRSTCTL_HSRST_Pos) // 0x00000002 +#define GRSTCTL_HSRST GRSTCTL_HSRST_Msk // HCLK soft reset +#define GRSTCTL_FCRST_Pos (2U) +#define GRSTCTL_FCRST_Msk (0x1UL << GRSTCTL_FCRST_Pos) // 0x00000004 +#define GRSTCTL_FCRST GRSTCTL_FCRST_Msk // Host frame counter reset +#define GRSTCTL_RXFFLSH_Pos (4U) +#define GRSTCTL_RXFFLSH_Msk (0x1UL << GRSTCTL_RXFFLSH_Pos) // 0x00000010 +#define GRSTCTL_RXFFLSH GRSTCTL_RXFFLSH_Msk // RxFIFO flush +#define GRSTCTL_TXFFLSH_Pos (5U) +#define GRSTCTL_TXFFLSH_Msk (0x1UL << GRSTCTL_TXFFLSH_Pos) // 0x00000020 +#define GRSTCTL_TXFFLSH GRSTCTL_TXFFLSH_Msk // TxFIFO flush +#define GRSTCTL_TXFNUM_Pos (6U) +#define GRSTCTL_TXFNUM_Msk (0x1FUL << GRSTCTL_TXFNUM_Pos) // 0x000007C0 +#define GRSTCTL_TXFNUM GRSTCTL_TXFNUM_Msk // TxFIFO number +#define GRSTCTL_TXFNUM_0 (0x01UL << GRSTCTL_TXFNUM_Pos) // 0x00000040 +#define GRSTCTL_TXFNUM_1 (0x02UL << GRSTCTL_TXFNUM_Pos) // 0x00000080 +#define GRSTCTL_TXFNUM_2 (0x04UL << GRSTCTL_TXFNUM_Pos) // 0x00000100 +#define GRSTCTL_TXFNUM_3 (0x08UL << GRSTCTL_TXFNUM_Pos) // 0x00000200 +#define GRSTCTL_TXFNUM_4 (0x10UL << GRSTCTL_TXFNUM_Pos) // 0x00000400 +#define GRSTCTL_CSRST_DONE_Pos (29) +#define GRSTCTL_CSRST_DONE (1u << GRSTCTL_CSRST_DONE_Pos) // Reset Done, only available from v4.20a +#define GRSTCTL_DMAREQ_Pos (30U) +#define GRSTCTL_DMAREQ_Msk (0x1UL << GRSTCTL_DMAREQ_Pos) // 0x40000000 +#define GRSTCTL_DMAREQ GRSTCTL_DMAREQ_Msk // DMA request signal +#define GRSTCTL_AHBIDL_Pos (31U) +#define GRSTCTL_AHBIDL_Msk (0x1UL << GRSTCTL_AHBIDL_Pos) // 0x80000000 +#define GRSTCTL_AHBIDL GRSTCTL_AHBIDL_Msk // AHB master idle + +/******************** Bit definition for DIEPMSK register ********************/ +#define DIEPMSK_XFRCM_Pos (0U) +#define DIEPMSK_XFRCM_Msk (0x1UL << DIEPMSK_XFRCM_Pos) // 0x00000001 +#define DIEPMSK_XFRCM DIEPMSK_XFRCM_Msk // Transfer completed interrupt mask +#define DIEPMSK_EPDM_Pos (1U) +#define DIEPMSK_EPDM_Msk (0x1UL << DIEPMSK_EPDM_Pos) // 0x00000002 +#define DIEPMSK_EPDM DIEPMSK_EPDM_Msk // Endpoint disabled interrupt mask +#define DIEPMSK_TOM_Pos (3U) +#define DIEPMSK_TOM_Msk (0x1UL << DIEPMSK_TOM_Pos) // 0x00000008 +#define DIEPMSK_TOM DIEPMSK_TOM_Msk // Timeout condition mask (nonisochronous endpoints) +#define DIEPMSK_ITTXFEMSK_Pos (4U) +#define DIEPMSK_ITTXFEMSK_Msk (0x1UL << DIEPMSK_ITTXFEMSK_Pos) // 0x00000010 +#define DIEPMSK_ITTXFEMSK DIEPMSK_ITTXFEMSK_Msk // IN token received when TxFIFO empty mask +#define DIEPMSK_INEPNMM_Pos (5U) +#define DIEPMSK_INEPNMM_Msk (0x1UL << DIEPMSK_INEPNMM_Pos) // 0x00000020 +#define DIEPMSK_INEPNMM DIEPMSK_INEPNMM_Msk // IN token received with EP mismatch mask +#define DIEPMSK_INEPNEM_Pos (6U) +#define DIEPMSK_INEPNEM_Msk (0x1UL << DIEPMSK_INEPNEM_Pos) // 0x00000040 +#define DIEPMSK_INEPNEM DIEPMSK_INEPNEM_Msk // IN endpoint NAK effective mask +#define DIEPMSK_TXFURM_Pos (8U) +#define DIEPMSK_TXFURM_Msk (0x1UL << DIEPMSK_TXFURM_Pos) // 0x00000100 +#define DIEPMSK_TXFURM DIEPMSK_TXFURM_Msk // FIFO underrun mask +#define DIEPMSK_BIM_Pos (9U) +#define DIEPMSK_BIM_Msk (0x1UL << DIEPMSK_BIM_Pos) // 0x00000200 +#define DIEPMSK_BIM DIEPMSK_BIM_Msk // BNA interrupt mask + +/******************** Bit definition for HPTXSTS register ********************/ +#define HPTXSTS_PTXFSAVL_Pos (0U) +#define HPTXSTS_PTXFSAVL_Msk (0xFFFFUL << HPTXSTS_PTXFSAVL_Pos) // 0x0000FFFF +#define HPTXSTS_PTXFSAVL HPTXSTS_PTXFSAVL_Msk // Periodic transmit data FIFO space available +#define HPTXSTS_PTXQSAV_Pos (16U) +#define HPTXSTS_PTXQSAV_Msk (0xFFUL << HPTXSTS_PTXQSAV_Pos) // 0x00FF0000 +#define HPTXSTS_PTXQSAV HPTXSTS_PTXQSAV_Msk // Periodic transmit request queue space available +#define HPTXSTS_PTXQSAV_0 (0x01UL << HPTXSTS_PTXQSAV_Pos) // 0x00010000 +#define HPTXSTS_PTXQSAV_1 (0x02UL << HPTXSTS_PTXQSAV_Pos) // 0x00020000 +#define HPTXSTS_PTXQSAV_2 (0x04UL << HPTXSTS_PTXQSAV_Pos) // 0x00040000 +#define HPTXSTS_PTXQSAV_3 (0x08UL << HPTXSTS_PTXQSAV_Pos) // 0x00080000 +#define HPTXSTS_PTXQSAV_4 (0x10UL << HPTXSTS_PTXQSAV_Pos) // 0x00100000 +#define HPTXSTS_PTXQSAV_5 (0x20UL << HPTXSTS_PTXQSAV_Pos) // 0x00200000 +#define HPTXSTS_PTXQSAV_6 (0x40UL << HPTXSTS_PTXQSAV_Pos) // 0x00400000 +#define HPTXSTS_PTXQSAV_7 (0x80UL << HPTXSTS_PTXQSAV_Pos) // 0x00800000 + +#define HPTXSTS_PTXQTOP_Pos (24U) +#define HPTXSTS_PTXQTOP_Msk (0xFFUL << HPTXSTS_PTXQTOP_Pos) // 0xFF000000 +#define HPTXSTS_PTXQTOP HPTXSTS_PTXQTOP_Msk // Top of the periodic transmit request queue +#define HPTXSTS_PTXQTOP_0 (0x01UL << HPTXSTS_PTXQTOP_Pos) // 0x01000000 +#define HPTXSTS_PTXQTOP_1 (0x02UL << HPTXSTS_PTXQTOP_Pos) // 0x02000000 +#define HPTXSTS_PTXQTOP_2 (0x04UL << HPTXSTS_PTXQTOP_Pos) // 0x04000000 +#define HPTXSTS_PTXQTOP_3 (0x08UL << HPTXSTS_PTXQTOP_Pos) // 0x08000000 +#define HPTXSTS_PTXQTOP_4 (0x10UL << HPTXSTS_PTXQTOP_Pos) // 0x10000000 +#define HPTXSTS_PTXQTOP_5 (0x20UL << HPTXSTS_PTXQTOP_Pos) // 0x20000000 +#define HPTXSTS_PTXQTOP_6 (0x40UL << HPTXSTS_PTXQTOP_Pos) // 0x40000000 +#define HPTXSTS_PTXQTOP_7 (0x80UL << HPTXSTS_PTXQTOP_Pos) // 0x80000000 + +/******************** Bit definition for HAINT register ********************/ +#define HAINT_HAINT_Pos (0U) +#define HAINT_HAINT_Msk (0xFFFFUL << HAINT_HAINT_Pos) // 0x0000FFFF +#define HAINT_HAINT HAINT_HAINT_Msk // Channel interrupts + +/******************** Bit definition for DOEPMSK register ********************/ +#define DOEPMSK_XFRCM_Pos (0U) +#define DOEPMSK_XFRCM_Msk (0x1UL << DOEPMSK_XFRCM_Pos) // 0x00000001 +#define DOEPMSK_XFRCM DOEPMSK_XFRCM_Msk // Transfer completed interrupt mask +#define DOEPMSK_EPDM_Pos (1U) +#define DOEPMSK_EPDM_Msk (0x1UL << DOEPMSK_EPDM_Pos) // 0x00000002 +#define DOEPMSK_EPDM DOEPMSK_EPDM_Msk // Endpoint disabled interrupt mask +#define DOEPMSK_AHBERRM_Pos (2U) +#define DOEPMSK_AHBERRM_Msk (0x1UL << DOEPMSK_AHBERRM_Pos) // 0x00000004 +#define DOEPMSK_AHBERRM DOEPMSK_AHBERRM_Msk // OUT transaction AHB Error interrupt mask +#define DOEPMSK_STUPM_Pos (3U) +#define DOEPMSK_STUPM_Msk (0x1UL << DOEPMSK_STUPM_Pos) // 0x00000008 +#define DOEPMSK_STUPM DOEPMSK_STUPM_Msk // SETUP phase done mask +#define DOEPMSK_OTEPDM_Pos (4U) +#define DOEPMSK_OTEPDM_Msk (0x1UL << DOEPMSK_OTEPDM_Pos) // 0x00000010 +#define DOEPMSK_OTEPDM DOEPMSK_OTEPDM_Msk // OUT token received when endpoint disabled mask +#define DOEPMSK_OTEPSPRM_Pos (5U) +#define DOEPMSK_OTEPSPRM_Msk (0x1UL << DOEPMSK_OTEPSPRM_Pos) // 0x00000020 +#define DOEPMSK_OTEPSPRM DOEPMSK_OTEPSPRM_Msk // Status Phase Received mask +#define DOEPMSK_B2BSTUP_Pos (6U) +#define DOEPMSK_B2BSTUP_Msk (0x1UL << DOEPMSK_B2BSTUP_Pos) // 0x00000040 +#define DOEPMSK_B2BSTUP DOEPMSK_B2BSTUP_Msk // Back-to-back SETUP packets received mask +#define DOEPMSK_OPEM_Pos (8U) +#define DOEPMSK_OPEM_Msk (0x1UL << DOEPMSK_OPEM_Pos) // 0x00000100 +#define DOEPMSK_OPEM DOEPMSK_OPEM_Msk // OUT packet error mask +#define DOEPMSK_BOIM_Pos (9U) +#define DOEPMSK_BOIM_Msk (0x1UL << DOEPMSK_BOIM_Pos) // 0x00000200 +#define DOEPMSK_BOIM DOEPMSK_BOIM_Msk // BNA interrupt mask +#define DOEPMSK_BERRM_Pos (12U) +#define DOEPMSK_BERRM_Msk (0x1UL << DOEPMSK_BERRM_Pos) // 0x00001000 +#define DOEPMSK_BERRM DOEPMSK_BERRM_Msk // Babble error interrupt mask +#define DOEPMSK_NAKM_Pos (13U) +#define DOEPMSK_NAKM_Msk (0x1UL << DOEPMSK_NAKM_Pos) // 0x00002000 +#define DOEPMSK_NAKM DOEPMSK_NAKM_Msk // OUT Packet NAK interrupt mask +#define DOEPMSK_NYETM_Pos (14U) +#define DOEPMSK_NYETM_Msk (0x1UL << DOEPMSK_NYETM_Pos) // 0x00004000 +#define DOEPMSK_NYETM DOEPMSK_NYETM_Msk // NYET interrupt mask + +/******************** Bit definition for GINTSTS register ********************/ +#define GINTSTS_CMOD_Pos (0U) +#define GINTSTS_CMOD_Msk (0x1UL << GINTSTS_CMOD_Pos) // 0x00000001 +#define GINTSTS_CMOD GINTSTS_CMOD_Msk // Current mode of operation +#define GINTSTS_MMIS_Pos (1U) +#define GINTSTS_MMIS_Msk (0x1UL << GINTSTS_MMIS_Pos) // 0x00000002 +#define GINTSTS_MMIS GINTSTS_MMIS_Msk // Mode mismatch interrupt +#define GINTSTS_OTGINT_Pos (2U) +#define GINTSTS_OTGINT_Msk (0x1UL << GINTSTS_OTGINT_Pos) // 0x00000004 +#define GINTSTS_OTGINT GINTSTS_OTGINT_Msk // OTG interrupt +#define GINTSTS_SOF_Pos (3U) +#define GINTSTS_SOF_Msk (0x1UL << GINTSTS_SOF_Pos) // 0x00000008 +#define GINTSTS_SOF GINTSTS_SOF_Msk // Start of frame +#define GINTSTS_RXFLVL_Pos (4U) +#define GINTSTS_RXFLVL_Msk (0x1UL << GINTSTS_RXFLVL_Pos) // 0x00000010 +#define GINTSTS_RXFLVL GINTSTS_RXFLVL_Msk // RxFIFO nonempty +#define GINTSTS_NPTX_FIFO_EMPTY_Pos (5U) +#define GINTSTS_NPTX_FIFO_EMPTY_Msk (0x1UL << GINTSTS_NPTX_FIFO_EMPTY_Pos) // 0x00000020 +#define GINTSTS_NPTX_FIFO_EMPTY GINTSTS_NPTX_FIFO_EMPTY_Msk // Nonperiodic TxFIFO empty +#define GINTSTS_GINAKEFF_Pos (6U) +#define GINTSTS_GINAKEFF_Msk (0x1UL << GINTSTS_GINAKEFF_Pos) // 0x00000040 +#define GINTSTS_GINAKEFF GINTSTS_GINAKEFF_Msk // Global IN nonperiodic NAK effective +#define GINTSTS_BOUTNAKEFF_Pos (7U) +#define GINTSTS_BOUTNAKEFF_Msk (0x1UL << GINTSTS_BOUTNAKEFF_Pos) // 0x00000080 +#define GINTSTS_BOUTNAKEFF GINTSTS_BOUTNAKEFF_Msk // Global OUT NAK effective +#define GINTSTS_ESUSP_Pos (10U) +#define GINTSTS_ESUSP_Msk (0x1UL << GINTSTS_ESUSP_Pos) // 0x00000400 +#define GINTSTS_ESUSP GINTSTS_ESUSP_Msk // Early suspend +#define GINTSTS_USBSUSP_Pos (11U) +#define GINTSTS_USBSUSP_Msk (0x1UL << GINTSTS_USBSUSP_Pos) // 0x00000800 +#define GINTSTS_USBSUSP GINTSTS_USBSUSP_Msk // USB suspend +#define GINTSTS_USBRST_Pos (12U) +#define GINTSTS_USBRST_Msk (0x1UL << GINTSTS_USBRST_Pos) // 0x00001000 +#define GINTSTS_USBRST GINTSTS_USBRST_Msk // USB reset +#define GINTSTS_ENUMDNE_Pos (13U) +#define GINTSTS_ENUMDNE_Msk (0x1UL << GINTSTS_ENUMDNE_Pos) // 0x00002000 +#define GINTSTS_ENUMDNE GINTSTS_ENUMDNE_Msk // Enumeration done +#define GINTSTS_ISOODRP_Pos (14U) +#define GINTSTS_ISOODRP_Msk (0x1UL << GINTSTS_ISOODRP_Pos) // 0x00004000 +#define GINTSTS_ISOODRP GINTSTS_ISOODRP_Msk // Isochronous OUT packet dropped interrupt +#define GINTSTS_EOPF_Pos (15U) +#define GINTSTS_EOPF_Msk (0x1UL << GINTSTS_EOPF_Pos) // 0x00008000 +#define GINTSTS_EOPF GINTSTS_EOPF_Msk // End of periodic frame interrupt +#define GINTSTS_IEPINT_Pos (18U) +#define GINTSTS_IEPINT_Msk (0x1UL << GINTSTS_IEPINT_Pos) // 0x00040000 +#define GINTSTS_IEPINT GINTSTS_IEPINT_Msk // IN endpoint interrupt +#define GINTSTS_OEPINT_Pos (19U) +#define GINTSTS_OEPINT_Msk (0x1UL << GINTSTS_OEPINT_Pos) // 0x00080000 +#define GINTSTS_OEPINT GINTSTS_OEPINT_Msk // OUT endpoint interrupt +#define GINTSTS_IISOIXFR_Pos (20U) +#define GINTSTS_IISOIXFR_Msk (0x1UL << GINTSTS_IISOIXFR_Pos) // 0x00100000 +#define GINTSTS_IISOIXFR GINTSTS_IISOIXFR_Msk // Incomplete isochronous IN transfer +#define GINTSTS_PXFR_INCOMPISOOUT_Pos (21U) +#define GINTSTS_PXFR_INCOMPISOOUT_Msk (0x1UL << GINTSTS_PXFR_INCOMPISOOUT_Pos) // 0x00200000 +#define GINTSTS_PXFR_INCOMPISOOUT GINTSTS_PXFR_INCOMPISOOUT_Msk // Incomplete periodic transfer +#define GINTSTS_DATAFSUSP_Pos (22U) +#define GINTSTS_DATAFSUSP_Msk (0x1UL << GINTSTS_DATAFSUSP_Pos) // 0x00400000 +#define GINTSTS_DATAFSUSP GINTSTS_DATAFSUSP_Msk // Data fetch suspended +#define GINTSTS_RSTDET_Pos (23U) +#define GINTSTS_RSTDET_Msk (0x1UL << GINTSTS_RSTDET_Pos) // 0x00800000 +#define GINTSTS_RSTDET GINTSTS_RSTDET_Msk // Reset detected interrupt +#define GINTSTS_HPRTINT_Pos (24U) +#define GINTSTS_HPRTINT_Msk (0x1UL << GINTSTS_HPRTINT_Pos) // 0x01000000 +#define GINTSTS_HPRTINT GINTSTS_HPRTINT_Msk // Host port interrupt +#define GINTSTS_HCINT_Pos (25U) +#define GINTSTS_HCINT_Msk (0x1UL << GINTSTS_HCINT_Pos) // 0x02000000 +#define GINTSTS_HCINT GINTSTS_HCINT_Msk // Host channels interrupt +#define GINTSTS_PTX_FIFO_EMPTY_Pos (26U) +#define GINTSTS_PTX_FIFO_EMPTY_Msk (0x1UL << GINTSTS_PTX_FIFO_EMPTY_Pos) // 0x04000000 +#define GINTSTS_PTX_FIFO_EMPTY GINTSTS_PTX_FIFO_EMPTY_Msk // Periodic TxFIFO empty +#define GINTSTS_LPMINT_Pos (27U) +#define GINTSTS_LPMINT_Msk (0x1UL << GINTSTS_LPMINT_Pos) // 0x08000000 +#define GINTSTS_LPMINT GINTSTS_LPMINT_Msk // LPM interrupt +#define GINTSTS_CONIDSTSCHNG_Pos (28U) +#define GINTSTS_CONIDSTSCHNG_Msk (0x1UL << GINTSTS_CONIDSTSCHNG_Pos) // 0x10000000 +#define GINTSTS_CONIDSTSCHNG GINTSTS_CONIDSTSCHNG_Msk // Connector ID status change +#define GINTSTS_DISCINT_Pos (29U) +#define GINTSTS_DISCINT_Msk (0x1UL << GINTSTS_DISCINT_Pos) // 0x20000000 +#define GINTSTS_DISCINT GINTSTS_DISCINT_Msk // Disconnect detected interrupt +#define GINTSTS_SRQINT_Pos (30U) +#define GINTSTS_SRQINT_Msk (0x1UL << GINTSTS_SRQINT_Pos) // 0x40000000 +#define GINTSTS_SRQINT GINTSTS_SRQINT_Msk // Session request/new session detected interrupt +#define GINTSTS_WKUINT_Pos (31U) +#define GINTSTS_WKUINT_Msk (0x1UL << GINTSTS_WKUINT_Pos) // 0x80000000 +#define GINTSTS_WKUINT GINTSTS_WKUINT_Msk // Resume/remote wakeup detected interrupt + +/******************** Bit definition for GINTMSK register ********************/ +#define GINTMSK_MMISM_Pos (1U) +#define GINTMSK_MMISM_Msk (0x1UL << GINTMSK_MMISM_Pos) // 0x00000002 +#define GINTMSK_MMISM GINTMSK_MMISM_Msk // Mode mismatch interrupt mask +#define GINTMSK_OTGINT_Pos (2U) +#define GINTMSK_OTGINT_Msk (0x1UL << GINTMSK_OTGINT_Pos) // 0x00000004 +#define GINTMSK_OTGINT GINTMSK_OTGINT_Msk // OTG interrupt mask +#define GINTMSK_SOFM_Pos (3U) +#define GINTMSK_SOFM_Msk (0x1UL << GINTMSK_SOFM_Pos) // 0x00000008 +#define GINTMSK_SOFM GINTMSK_SOFM_Msk // Start of frame mask +#define GINTMSK_RXFLVLM_Pos (4U) +#define GINTMSK_RXFLVLM_Msk (0x1UL << GINTMSK_RXFLVLM_Pos) // 0x00000010 +#define GINTMSK_RXFLVLM GINTMSK_RXFLVLM_Msk // Receive FIFO nonempty mask +#define GINTMSK_NPTXFEM_Pos (5U) +#define GINTMSK_NPTXFEM_Msk (0x1UL << GINTMSK_NPTXFEM_Pos) // 0x00000020 +#define GINTMSK_NPTXFEM GINTMSK_NPTXFEM_Msk // Nonperiodic TxFIFO empty mask +#define GINTMSK_GINAKEFFM_Pos (6U) +#define GINTMSK_GINAKEFFM_Msk (0x1UL << GINTMSK_GINAKEFFM_Pos) // 0x00000040 +#define GINTMSK_GINAKEFFM GINTMSK_GINAKEFFM_Msk // Global nonperiodic IN NAK effective mask +#define GINTMSK_GONAKEFFM_Pos (7U) +#define GINTMSK_GONAKEFFM_Msk (0x1UL << GINTMSK_GONAKEFFM_Pos) // 0x00000080 +#define GINTMSK_GONAKEFFM GINTMSK_GONAKEFFM_Msk // Global OUT NAK effective mask +#define GINTMSK_ESUSPM_Pos (10U) +#define GINTMSK_ESUSPM_Msk (0x1UL << GINTMSK_ESUSPM_Pos) // 0x00000400 +#define GINTMSK_ESUSPM GINTMSK_ESUSPM_Msk // Early suspend mask +#define GINTMSK_USBSUSPM_Pos (11U) +#define GINTMSK_USBSUSPM_Msk (0x1UL << GINTMSK_USBSUSPM_Pos) // 0x00000800 +#define GINTMSK_USBSUSPM GINTMSK_USBSUSPM_Msk // USB suspend mask +#define GINTMSK_USBRST_Pos (12U) +#define GINTMSK_USBRST_Msk (0x1UL << GINTMSK_USBRST_Pos) // 0x00001000 +#define GINTMSK_USBRST GINTMSK_USBRST_Msk // USB reset mask +#define GINTMSK_ENUMDNEM_Pos (13U) +#define GINTMSK_ENUMDNEM_Msk (0x1UL << GINTMSK_ENUMDNEM_Pos) // 0x00002000 +#define GINTMSK_ENUMDNEM GINTMSK_ENUMDNEM_Msk // Enumeration done mask +#define GINTMSK_ISOODRPM_Pos (14U) +#define GINTMSK_ISOODRPM_Msk (0x1UL << GINTMSK_ISOODRPM_Pos) // 0x00004000 +#define GINTMSK_ISOODRPM GINTMSK_ISOODRPM_Msk // Isochronous OUT packet dropped interrupt mask +#define GINTMSK_EOPFM_Pos (15U) +#define GINTMSK_EOPFM_Msk (0x1UL << GINTMSK_EOPFM_Pos) // 0x00008000 +#define GINTMSK_EOPFM GINTMSK_EOPFM_Msk // End of periodic frame interrupt mask +#define GINTMSK_EPMISM_Pos (17U) +#define GINTMSK_EPMISM_Msk (0x1UL << GINTMSK_EPMISM_Pos) // 0x00020000 +#define GINTMSK_EPMISM GINTMSK_EPMISM_Msk // Endpoint mismatch interrupt mask +#define GINTMSK_IEPINT_Pos (18U) +#define GINTMSK_IEPINT_Msk (0x1UL << GINTMSK_IEPINT_Pos) // 0x00040000 +#define GINTMSK_IEPINT GINTMSK_IEPINT_Msk // IN endpoints interrupt mask +#define GINTMSK_OEPINT_Pos (19U) +#define GINTMSK_OEPINT_Msk (0x1UL << GINTMSK_OEPINT_Pos) // 0x00080000 +#define GINTMSK_OEPINT GINTMSK_OEPINT_Msk // OUT endpoints interrupt mask +#define GINTMSK_IISOIXFRM_Pos (20U) +#define GINTMSK_IISOIXFRM_Msk (0x1UL << GINTMSK_IISOIXFRM_Pos) // 0x00100000 +#define GINTMSK_IISOIXFRM GINTMSK_IISOIXFRM_Msk // Incomplete isochronous IN transfer mask +#define GINTMSK_PXFRM_IISOOXFRM_Pos (21U) +#define GINTMSK_PXFRM_IISOOXFRM_Msk (0x1UL << GINTMSK_PXFRM_IISOOXFRM_Pos) // 0x00200000 +#define GINTMSK_PXFRM_IISOOXFRM GINTMSK_PXFRM_IISOOXFRM_Msk // Incomplete periodic transfer mask +#define GINTMSK_FSUSPM_Pos (22U) +#define GINTMSK_FSUSPM_Msk (0x1UL << GINTMSK_FSUSPM_Pos) // 0x00400000 +#define GINTMSK_FSUSPM GINTMSK_FSUSPM_Msk // Data fetch suspended mask +#define GINTMSK_RSTDEM_Pos (23U) +#define GINTMSK_RSTDEM_Msk (0x1UL << GINTMSK_RSTDEM_Pos) // 0x00800000 +#define GINTMSK_RSTDEM GINTMSK_RSTDEM_Msk // Reset detected interrupt mask +#define GINTMSK_PRTIM_Pos (24U) +#define GINTMSK_PRTIM_Msk (0x1UL << GINTMSK_PRTIM_Pos) // 0x01000000 +#define GINTMSK_PRTIM GINTMSK_PRTIM_Msk // Host port interrupt mask +#define GINTMSK_HCIM_Pos (25U) +#define GINTMSK_HCIM_Msk (0x1UL << GINTMSK_HCIM_Pos) // 0x02000000 +#define GINTMSK_HCIM GINTMSK_HCIM_Msk // Host channels interrupt mask +#define GINTMSK_PTXFEM_Pos (26U) +#define GINTMSK_PTXFEM_Msk (0x1UL << GINTMSK_PTXFEM_Pos) // 0x04000000 +#define GINTMSK_PTXFEM GINTMSK_PTXFEM_Msk // Periodic TxFIFO empty mask +#define GINTMSK_LPMINTM_Pos (27U) +#define GINTMSK_LPMINTM_Msk (0x1UL << GINTMSK_LPMINTM_Pos) // 0x08000000 +#define GINTMSK_LPMINTM GINTMSK_LPMINTM_Msk // LPM interrupt Mask +#define GINTMSK_CONIDSTSCHNGM_Pos (28U) +#define GINTMSK_CONIDSTSCHNGM_Msk (0x1UL << GINTMSK_CONIDSTSCHNGM_Pos) // 0x10000000 +#define GINTMSK_CONIDSTSCHNGM GINTMSK_CONIDSTSCHNGM_Msk // Connector ID status change mask +#define GINTMSK_DISCINT_Pos (29U) +#define GINTMSK_DISCINT_Msk (0x1UL << GINTMSK_DISCINT_Pos) // 0x20000000 +#define GINTMSK_DISCINT GINTMSK_DISCINT_Msk // Disconnect detected interrupt mask +#define GINTMSK_SRQIM_Pos (30U) +#define GINTMSK_SRQIM_Msk (0x1UL << GINTMSK_SRQIM_Pos) // 0x40000000 +#define GINTMSK_SRQIM GINTMSK_SRQIM_Msk // Session request/new session detected interrupt mask +#define GINTMSK_WUIM_Pos (31U) +#define GINTMSK_WUIM_Msk (0x1UL << GINTMSK_WUIM_Pos) // 0x80000000 +#define GINTMSK_WUIM GINTMSK_WUIM_Msk // Resume/remote wakeup detected interrupt mask + +/******************** Bit definition for DAINT register ********************/ +#define DAINT_IEPINT_Pos (0U) +#define DAINT_IEPINT_Msk (0xFFFFUL << DAINT_IEPINT_Pos) // 0x0000FFFF +#define DAINT_IEPINT DAINT_IEPINT_Msk // IN endpoint interrupt bits +#define DAINT_OEPINT_Pos (16U) +#define DAINT_OEPINT_Msk (0xFFFFUL << DAINT_OEPINT_Pos) // 0xFFFF0000 +#define DAINT_OEPINT DAINT_OEPINT_Msk // OUT endpoint interrupt bits + +/******************** Bit definition for HAINTMSK register ********************/ +#define HAINTMSK_HAINTM_Pos (0U) +#define HAINTMSK_HAINTM_Msk (0xFFFFUL << HAINTMSK_HAINTM_Pos) // 0x0000FFFF +#define HAINTMSK_HAINTM HAINTMSK_HAINTM_Msk // Channel interrupt mask + +/******************** Bit definition for GRXSTSP register ********************/ +#define GRXSTSP_EPNUM_Pos (0U) +#define GRXSTSP_EPNUM_Msk (0xFUL << GRXSTSP_EPNUM_Pos) // 0x0000000F +#define GRXSTSP_EPNUM GRXSTSP_EPNUM_Msk // IN EP interrupt mask bits +#define GRXSTSP_BCNT_Pos (4U) +#define GRXSTSP_BCNT_Msk (0x7FFUL << GRXSTSP_BCNT_Pos) // 0x00007FF0 +#define GRXSTSP_BCNT GRXSTSP_BCNT_Msk // OUT EP interrupt mask bits +#define GRXSTSP_DPID_Pos (15U) +#define GRXSTSP_DPID_Msk (0x3UL << GRXSTSP_DPID_Pos) // 0x00018000 +#define GRXSTSP_DPID GRXSTSP_DPID_Msk // OUT EP interrupt mask bits +#define GRXSTSP_PKTSTS_Pos (17U) +#define GRXSTSP_PKTSTS_Msk (0xFUL << GRXSTSP_PKTSTS_Pos) // 0x001E0000 +#define GRXSTSP_PKTSTS GRXSTSP_PKTSTS_Msk // OUT EP interrupt mask bits + +/******************** Bit definition for DAINTMSK register ********************/ +#define DAINTMSK_IEPM_Pos (0U) +#define DAINTMSK_IEPM_Msk (0xFFFFUL << DAINTMSK_IEPM_Pos) // 0x0000FFFF +#define DAINTMSK_IEPM DAINTMSK_IEPM_Msk // IN EP interrupt mask bits +#define DAINTMSK_OEPM_Pos (16U) +#define DAINTMSK_OEPM_Msk (0xFFFFUL << DAINTMSK_OEPM_Pos) // 0xFFFF0000 +#define DAINTMSK_OEPM DAINTMSK_OEPM_Msk // OUT EP interrupt mask bits + +#define DAINT_SHIFT(_dir) (((_dir) == TUSB_DIR_IN) ? 0 : 16) + +#if 0 +/******************** Bit definition for OTG register ********************/ +#define CHNUM_Pos (0U) +#define CHNUM_Msk (0xFUL << CHNUM_Pos) // 0x0000000F +#define CHNUM CHNUM_Msk // Channel number +#define CHNUM_0 (0x1UL << CHNUM_Pos) // 0x00000001 +#define CHNUM_1 (0x2UL << CHNUM_Pos) // 0x00000002 +#define CHNUM_2 (0x4UL << CHNUM_Pos) // 0x00000004 +#define CHNUM_3 (0x8UL << CHNUM_Pos) // 0x00000008 +#define BCNT_Pos (4U) +#define BCNT_Msk (0x7FFUL << BCNT_Pos) // 0x00007FF0 +#define BCNT BCNT_Msk // Byte count + +#define DPID_Pos (15U) +#define DPID_Msk (0x3UL << DPID_Pos) // 0x00018000 +#define DPID DPID_Msk // Data PID +#define DPID_0 (0x1UL << DPID_Pos) // 0x00008000 +#define DPID_1 (0x2UL << DPID_Pos) // 0x00010000 + +#define PKTSTS_Pos (17U) +#define PKTSTS_Msk (0xFUL << PKTSTS_Pos) // 0x001E0000 +#define PKTSTS PKTSTS_Msk // Packet status +#define PKTSTS_0 (0x1UL << PKTSTS_Pos) // 0x00020000 +#define PKTSTS_1 (0x2UL << PKTSTS_Pos) // 0x00040000 +#define PKTSTS_2 (0x4UL << PKTSTS_Pos) // 0x00080000 +#define PKTSTS_3 (0x8UL << PKTSTS_Pos) // 0x00100000 + +#define EPNUM_Pos (0U) +#define EPNUM_Msk (0xFUL << EPNUM_Pos) // 0x0000000F +#define EPNUM EPNUM_Msk // Endpoint number +#define EPNUM_0 (0x1UL << EPNUM_Pos) // 0x00000001 +#define EPNUM_1 (0x2UL << EPNUM_Pos) // 0x00000002 +#define EPNUM_2 (0x4UL << EPNUM_Pos) // 0x00000004 +#define EPNUM_3 (0x8UL << EPNUM_Pos) // 0x00000008 + +#define FRMNUM_Pos (21U) +#define FRMNUM_Msk (0xFUL << FRMNUM_Pos) // 0x01E00000 +#define FRMNUM FRMNUM_Msk // Frame number +#define FRMNUM_0 (0x1UL << FRMNUM_Pos) // 0x00200000 +#define FRMNUM_1 (0x2UL << FRMNUM_Pos) // 0x00400000 +#define FRMNUM_2 (0x4UL << FRMNUM_Pos) // 0x00800000 +#define FRMNUM_3 (0x8UL << FRMNUM_Pos) // 0x01000000 +#endif + +/******************** Bit definition for GRXFSIZ register ********************/ +#define GRXFSIZ_RXFD_Pos (0U) +#define GRXFSIZ_RXFD_Msk (0xFFFFUL << GRXFSIZ_RXFD_Pos) // 0x0000FFFF +#define GRXFSIZ_RXFD GRXFSIZ_RXFD_Msk // RxFIFO depth + +/******************** Bit definition for DVBUSDIS register ********************/ +#define DVBUSDIS_VBUSDT_Pos (0U) +#define DVBUSDIS_VBUSDT_Msk (0xFFFFUL << DVBUSDIS_VBUSDT_Pos) // 0x0000FFFF +#define DVBUSDIS_VBUSDT DVBUSDIS_VBUSDT_Msk // Device VBUS discharge time + +/******************** Bit definition for OTG register ********************/ +#define GNPTXFSIZ_NPTXFSA_Pos (0U) +#define GNPTXFSIZ_NPTXFSA_Msk (0xFFFFUL << GNPTXFSIZ_NPTXFSA_Pos) // 0x0000FFFF +#define GNPTXFSIZ_NPTXFSA GNPTXFSIZ_NPTXFSA_Msk // Nonperiodic transmit RAM start address +#define GNPTXFSIZ_NPTXFD_Pos (16U) +#define GNPTXFSIZ_NPTXFD_Msk (0xFFFFUL << GNPTXFSIZ_NPTXFD_Pos) // 0xFFFF0000 +#define GNPTXFSIZ_NPTXFD GNPTXFSIZ_NPTXFD_Msk // Nonperiodic TxFIFO depth +#define DIEPTXF0_TX0FSA_Pos (0U) +#define DIEPTXF0_TX0FSA_Msk (0xFFFFUL << DIEPTXF0_TX0FSA_Pos) // 0x0000FFFF +#define DIEPTXF0_TX0FSA DIEPTXF0_TX0FSA_Msk // Endpoint 0 transmit RAM start address +#define DIEPTXF0_TX0FD_Pos (16U) +#define DIEPTXF0_TX0FD_Msk (0xFFFFUL << DIEPTXF0_TX0FD_Pos) // 0xFFFF0000 +#define DIEPTXF0_TX0FD DIEPTXF0_TX0FD_Msk // Endpoint 0 TxFIFO depth + +/******************** Bit definition for DVBUSPULSE register ********************/ +#define DVBUSPULSE_DVBUSP_Pos (0U) +#define DVBUSPULSE_DVBUSP_Msk (0xFFFUL << DVBUSPULSE_DVBUSP_Pos) // 0x00000FFF +#define DVBUSPULSE_DVBUSP DVBUSPULSE_DVBUSP_Msk // Device VBUS pulsing time + +/******************** Bit definition for GNPTXSTS register ********************/ +#define GNPTXSTS_NPTXFSAV_Pos (0U) +#define GNPTXSTS_NPTXFSAV_Msk (0xFFFFUL << GNPTXSTS_NPTXFSAV_Pos) // 0x0000FFFF +#define GNPTXSTS_NPTXFSAV GNPTXSTS_NPTXFSAV_Msk // Nonperiodic TxFIFO space available + +#define GNPTXSTS_NPTQXSAV_Pos (16U) +#define GNPTXSTS_NPTQXSAV_Msk (0xFFUL << GNPTXSTS_NPTQXSAV_Pos) // 0x00FF0000 +#define GNPTXSTS_NPTQXSAV GNPTXSTS_NPTQXSAV_Msk // Nonperiodic transmit request queue space available +#define GNPTXSTS_NPTQXSAV_0 (0x01UL << GNPTXSTS_NPTQXSAV_Pos) // 0x00010000 +#define GNPTXSTS_NPTQXSAV_1 (0x02UL << GNPTXSTS_NPTQXSAV_Pos) // 0x00020000 +#define GNPTXSTS_NPTQXSAV_2 (0x04UL << GNPTXSTS_NPTQXSAV_Pos) // 0x00040000 +#define GNPTXSTS_NPTQXSAV_3 (0x08UL << GNPTXSTS_NPTQXSAV_Pos) // 0x00080000 +#define GNPTXSTS_NPTQXSAV_4 (0x10UL << GNPTXSTS_NPTQXSAV_Pos) // 0x00100000 +#define GNPTXSTS_NPTQXSAV_5 (0x20UL << GNPTXSTS_NPTQXSAV_Pos) // 0x00200000 +#define GNPTXSTS_NPTQXSAV_6 (0x40UL << GNPTXSTS_NPTQXSAV_Pos) // 0x00400000 +#define GNPTXSTS_NPTQXSAV_7 (0x80UL << GNPTXSTS_NPTQXSAV_Pos) // 0x00800000 + +#define GNPTXSTS_NPTXQTOP_Pos (24U) +#define GNPTXSTS_NPTXQTOP_Msk (0x7FUL << GNPTXSTS_NPTXQTOP_Pos) // 0x7F000000 +#define GNPTXSTS_NPTXQTOP GNPTXSTS_NPTXQTOP_Msk // Top of the nonperiodic transmit request queue +#define GNPTXSTS_NPTXQTOP_0 (0x01UL << GNPTXSTS_NPTXQTOP_Pos) // 0x01000000 +#define GNPTXSTS_NPTXQTOP_1 (0x02UL << GNPTXSTS_NPTXQTOP_Pos) // 0x02000000 +#define GNPTXSTS_NPTXQTOP_2 (0x04UL << GNPTXSTS_NPTXQTOP_Pos) // 0x04000000 +#define GNPTXSTS_NPTXQTOP_3 (0x08UL << GNPTXSTS_NPTXQTOP_Pos) // 0x08000000 +#define GNPTXSTS_NPTXQTOP_4 (0x10UL << GNPTXSTS_NPTXQTOP_Pos) // 0x10000000 +#define GNPTXSTS_NPTXQTOP_5 (0x20UL << GNPTXSTS_NPTXQTOP_Pos) // 0x20000000 +#define GNPTXSTS_NPTXQTOP_6 (0x40UL << GNPTXSTS_NPTXQTOP_Pos) // 0x40000000 + +/******************** Bit definition for DTHRCTL register ********************/ +#define DTHRCTL_NONISOTHREN_Pos (0U) +#define DTHRCTL_NONISOTHREN_Msk (0x1UL << DTHRCTL_NONISOTHREN_Pos) // 0x00000001 +#define DTHRCTL_NONISOTHREN DTHRCTL_NONISOTHREN_Msk // Nonisochronous IN endpoints threshold enable +#define DTHRCTL_ISOTHREN_Pos (1U) +#define DTHRCTL_ISOTHREN_Msk (0x1UL << DTHRCTL_ISOTHREN_Pos) // 0x00000002 +#define DTHRCTL_ISOTHREN DTHRCTL_ISOTHREN_Msk // ISO IN endpoint threshold enable + +#define DTHRCTL_TXTHRLEN_Pos (2U) +#define DTHRCTL_TXTHRLEN_Msk (0x1FFUL << DTHRCTL_TXTHRLEN_Pos) // 0x000007FC +#define DTHRCTL_TXTHRLEN DTHRCTL_TXTHRLEN_Msk // Transmit threshold length +#define DTHRCTL_TXTHRLEN_0 (0x001UL << DTHRCTL_TXTHRLEN_Pos) // 0x00000004 +#define DTHRCTL_TXTHRLEN_1 (0x002UL << DTHRCTL_TXTHRLEN_Pos) // 0x00000008 +#define DTHRCTL_TXTHRLEN_2 (0x004UL << DTHRCTL_TXTHRLEN_Pos) // 0x00000010 +#define DTHRCTL_TXTHRLEN_3 (0x008UL << DTHRCTL_TXTHRLEN_Pos) // 0x00000020 +#define DTHRCTL_TXTHRLEN_4 (0x010UL << DTHRCTL_TXTHRLEN_Pos) // 0x00000040 +#define DTHRCTL_TXTHRLEN_5 (0x020UL << DTHRCTL_TXTHRLEN_Pos) // 0x00000080 +#define DTHRCTL_TXTHRLEN_6 (0x040UL << DTHRCTL_TXTHRLEN_Pos) // 0x00000100 +#define DTHRCTL_TXTHRLEN_7 (0x080UL << DTHRCTL_TXTHRLEN_Pos) // 0x00000200 +#define DTHRCTL_TXTHRLEN_8 (0x100UL << DTHRCTL_TXTHRLEN_Pos) // 0x00000400 +#define DTHRCTL_RXTHREN_Pos (16U) +#define DTHRCTL_RXTHREN_Msk (0x1UL << DTHRCTL_RXTHREN_Pos) // 0x00010000 +#define DTHRCTL_RXTHREN DTHRCTL_RXTHREN_Msk // Receive threshold enable + +#define DTHRCTL_RXTHRLEN_Pos (17U) +#define DTHRCTL_RXTHRLEN_Msk (0x1FFUL << DTHRCTL_RXTHRLEN_Pos) // 0x03FE0000 +#define DTHRCTL_RXTHRLEN DTHRCTL_RXTHRLEN_Msk // Receive threshold length +#define DTHRCTL_RXTHRLEN_0 (0x001UL << DTHRCTL_RXTHRLEN_Pos) // 0x00020000 +#define DTHRCTL_RXTHRLEN_1 (0x002UL << DTHRCTL_RXTHRLEN_Pos) // 0x00040000 +#define DTHRCTL_RXTHRLEN_2 (0x004UL << DTHRCTL_RXTHRLEN_Pos) // 0x00080000 +#define DTHRCTL_RXTHRLEN_3 (0x008UL << DTHRCTL_RXTHRLEN_Pos) // 0x00100000 +#define DTHRCTL_RXTHRLEN_4 (0x010UL << DTHRCTL_RXTHRLEN_Pos) // 0x00200000 +#define DTHRCTL_RXTHRLEN_5 (0x020UL << DTHRCTL_RXTHRLEN_Pos) // 0x00400000 +#define DTHRCTL_RXTHRLEN_6 (0x040UL << DTHRCTL_RXTHRLEN_Pos) // 0x00800000 +#define DTHRCTL_RXTHRLEN_7 (0x080UL << DTHRCTL_RXTHRLEN_Pos) // 0x01000000 +#define DTHRCTL_RXTHRLEN_8 (0x100UL << DTHRCTL_RXTHRLEN_Pos) // 0x02000000 +#define DTHRCTL_ARPEN_Pos (27U) +#define DTHRCTL_ARPEN_Msk (0x1UL << DTHRCTL_ARPEN_Pos) // 0x08000000 +#define DTHRCTL_ARPEN DTHRCTL_ARPEN_Msk // Arbiter parking enable + +/******************** Bit definition for DIEPEMPMSK register ********************/ +#define DIEPEMPMSK_INEPTXFEM_Pos (0U) +#define DIEPEMPMSK_INEPTXFEM_Msk (0xFFFFUL << DIEPEMPMSK_INEPTXFEM_Pos) // 0x0000FFFF +#define DIEPEMPMSK_INEPTXFEM DIEPEMPMSK_INEPTXFEM_Msk // IN EP Tx FIFO empty interrupt mask bits + +/******************** Bit definition for DEACHINT register ********************/ +#define DEACHINT_IEP1INT_Pos (1U) +#define DEACHINT_IEP1INT_Msk (0x1UL << DEACHINT_IEP1INT_Pos) // 0x00000002 +#define DEACHINT_IEP1INT DEACHINT_IEP1INT_Msk // IN endpoint 1interrupt bit +#define DEACHINT_OEP1INT_Pos (17U) +#define DEACHINT_OEP1INT_Msk (0x1UL << DEACHINT_OEP1INT_Pos) // 0x00020000 +#define DEACHINT_OEP1INT DEACHINT_OEP1INT_Msk // OUT endpoint 1 interrupt bit + +/******************** Bit definition for GCCFG register ********************/ +#define STM32_GCCFG_DCDET_Pos (0U) +#define STM32_GCCFG_DCDET_Msk (0x1UL << STM32_GCCFG_DCDET_Pos) // 0x00000001 +#define STM32_GCCFG_DCDET STM32_GCCFG_DCDET_Msk // Data contact detection (DCD) status + +#define STM32_GCCFG_PDET_Pos (1U) +#define STM32_GCCFG_PDET_Msk (0x1UL << STM32_GCCFG_PDET_Pos) // 0x00000002 +#define STM32_GCCFG_PDET STM32_GCCFG_PDET_Msk // Primary detection (PD) status + +#define STM32_GCCFG_SDET_Pos (2U) +#define STM32_GCCFG_SDET_Msk (0x1UL << STM32_GCCFG_SDET_Pos) // 0x00000004 +#define STM32_GCCFG_SDET STM32_GCCFG_SDET_Msk // Secondary detection (SD) status + +#define STM32_GCCFG_PS2DET_Pos (3U) +#define STM32_GCCFG_PS2DET_Msk (0x1UL << STM32_GCCFG_PS2DET_Pos) // 0x00000008 +#define STM32_GCCFG_PS2DET STM32_GCCFG_PS2DET_Msk // DM pull-up detection status + +#define STM32_GCCFG_PWRDWN_Pos (16U) +#define STM32_GCCFG_PWRDWN_Msk (0x1UL << STM32_GCCFG_PWRDWN_Pos) // 0x00010000 +#define STM32_GCCFG_PWRDWN STM32_GCCFG_PWRDWN_Msk // Power down + +#define STM32_GCCFG_BCDEN_Pos (17U) +#define STM32_GCCFG_BCDEN_Msk (0x1UL << STM32_GCCFG_BCDEN_Pos) // 0x00020000 +#define STM32_GCCFG_BCDEN STM32_GCCFG_BCDEN_Msk // Battery charging detector (BCD) enable + +#define STM32_GCCFG_DCDEN_Pos (18U) +#define STM32_GCCFG_DCDEN_Msk (0x1UL << STM32_GCCFG_DCDEN_Pos) // 0x00040000 +#define STM32_GCCFG_DCDEN STM32_GCCFG_DCDEN_Msk // Data contact detection (DCD) mode enable*/ + +#define STM32_GCCFG_PDEN_Pos (19U) +#define STM32_GCCFG_PDEN_Msk (0x1UL << STM32_GCCFG_PDEN_Pos) // 0x00080000 +#define STM32_GCCFG_PDEN STM32_GCCFG_PDEN_Msk // Primary detection (PD) mode enable*/ + +#define STM32_GCCFG_SDEN_Pos (20U) +#define STM32_GCCFG_SDEN_Msk (0x1UL << STM32_GCCFG_SDEN_Pos) // 0x00100000 +#define STM32_GCCFG_SDEN STM32_GCCFG_SDEN_Msk // Secondary detection (SD) mode enable + +#define STM32_GCCFG_VBDEN_Pos (21U) +#define STM32_GCCFG_VBDEN_Msk (0x1UL << STM32_GCCFG_VBDEN_Pos) // 0x00200000 +#define STM32_GCCFG_VBDEN STM32_GCCFG_VBDEN_Msk // VBUS mode enable + +#define STM32_GCCFG_OTGIDEN_Pos (22U) +#define STM32_GCCFG_OTGIDEN_Msk (0x1UL << STM32_GCCFG_OTGIDEN_Pos) // 0x00400000 +#define STM32_GCCFG_OTGIDEN STM32_GCCFG_OTGIDEN_Msk // OTG Id enable + +#define STM32_GCCFG_PHYHSEN_Pos (23U) +#define STM32_GCCFG_PHYHSEN_Msk (0x1UL << STM32_GCCFG_PHYHSEN_Pos) // 0x00800000 +#define STM32_GCCFG_PHYHSEN STM32_GCCFG_PHYHSEN_Msk // HS PHY enable + +// GUID < 0x2000: VBUSASEN, VBUSBSEN, NOVBUSSENS bits +#define STM32_GCCFG_VBUSASEN_Pos (18U) +#define STM32_GCCFG_VBUSASEN_Msk (0x1UL << STM32_GCCFG_VBUSASEN_Pos) // 0x00040000 +#define STM32_GCCFG_VBUSASEN STM32_GCCFG_VBUSASEN_Msk // Enable A-device (host) VBUS sensing + +#define STM32_GCCFG_VBUSBSEN_Pos (19U) +#define STM32_GCCFG_VBUSBSEN_Msk (0x1UL << STM32_GCCFG_VBUSBSEN_Pos) // 0x00080000 +#define STM32_GCCFG_VBUSBSEN STM32_GCCFG_VBUSBSEN_Msk // Enable B-device (peripheral) VBUS sensing + +#define STM32_GCCFG_NOVBUSSENS_Pos (21U) +#define STM32_GCCFG_NOVBUSSENS_Msk (0x1UL << STM32_GCCFG_NOVBUSSENS_Pos) // 0x00200000 +#define STM32_GCCFG_NOVBUSSENS STM32_GCCFG_NOVBUSSENS_Msk // VBUS sensing disable option +// GUID < 0x2000: end + +// TODO: stm32u5a5 SDEN is 22nd bit, conflict with 20th bit above +// #define STM32_GCCFG_SDEN_Pos (22U) +// #define STM32_GCCFG_SDEN_Msk (0x1U << STM32_GCCFG_SDEN_Pos) // 0x00400000 +// #define STM32_GCCFG_SDEN STM32_GCCFG_SDEN_Msk // Secondary detection (PD) mode enable + +// GUID >= 0x5000 use femtoPHY: VBVALOVA, VBVALEXTOEN, PULLDOWNEN +#define STM32_GCCFG_VBVALOVAL_Pos (23U) +#define STM32_GCCFG_VBVALOVAL_Msk (0x1U << STM32_GCCFG_VBVALOVAL_Pos) // 0x00800000 +#define STM32_GCCFG_VBVALOVAL STM32_GCCFG_VBVALOVAL_Msk // Value of VBUSVLDEXT0 femtoPHY input + +#define STM32_GCCFG_VBVALEXTOEN_Pos (24U) +#define STM32_GCCFG_VBVALEXTOEN_Msk (0x1U << STM32_GCCFG_VBVALEXTOEN_Pos) // 0x01000000 +#define STM32_GCCFG_VBVALEXTOEN STM32_GCCFG_VBVALEXTOEN_Msk // Enables of VBUSVLDEXT0 femtoPHY input override + +#define STM32_GCCFG_PULLDOWNEN_Pos (25U) +#define STM32_GCCFG_PULLDOWNEN_Msk (0x1U << STM32_GCCFG_PULLDOWNEN_Pos) // 0x02000000 +#define STM32_GCCFG_PULLDOWNEN STM32_GCCFG_PULLDOWNEN_Msk // Enables of femtoPHY pulldown resistors, used when ID PAD is disabled +// GUID >= 0x5000: end + +/******************** Bit definition for DEACHINTMSK register ********************/ +#define DEACHINTMSK_IEP1INTM_Pos (1U) +#define DEACHINTMSK_IEP1INTM_Msk (0x1UL << DEACHINTMSK_IEP1INTM_Pos) // 0x00000002 +#define DEACHINTMSK_IEP1INTM DEACHINTMSK_IEP1INTM_Msk // IN Endpoint 1 interrupt mask bit +#define DEACHINTMSK_OEP1INTM_Pos (17U) +#define DEACHINTMSK_OEP1INTM_Msk (0x1UL << DEACHINTMSK_OEP1INTM_Pos) // 0x00020000 +#define DEACHINTMSK_OEP1INTM DEACHINTMSK_OEP1INTM_Msk // OUT Endpoint 1 interrupt mask bit + +/******************** Bit definition for CID register ********************/ +#define CID_PRODUCT_ID_Pos (0U) +#define CID_PRODUCT_ID_Msk (0xFFFFFFFFUL << CID_PRODUCT_ID_Pos) // 0xFFFFFFFF +#define CID_PRODUCT_ID CID_PRODUCT_ID_Msk // Product ID field + +/******************** Bit definition for GLPMCFG register ********************/ +#define GLPMCFG_LPMEN_Pos (0U) +#define GLPMCFG_LPMEN_Msk (0x1UL << GLPMCFG_LPMEN_Pos) // 0x00000001 +#define GLPMCFG_LPMEN GLPMCFG_LPMEN_Msk // LPM support enable +#define GLPMCFG_LPMACK_Pos (1U) +#define GLPMCFG_LPMACK_Msk (0x1UL << GLPMCFG_LPMACK_Pos) // 0x00000002 +#define GLPMCFG_LPMACK GLPMCFG_LPMACK_Msk // LPM Token acknowledge enable +#define GLPMCFG_BESL_Pos (2U) +#define GLPMCFG_BESL_Msk (0xFUL << GLPMCFG_BESL_Pos) // 0x0000003C +#define GLPMCFG_BESL GLPMCFG_BESL_Msk // BESL value received with last ACKed LPM Token +#define GLPMCFG_REMWAKE_Pos (6U) +#define GLPMCFG_REMWAKE_Msk (0x1UL << GLPMCFG_REMWAKE_Pos) // 0x00000040 +#define GLPMCFG_REMWAKE GLPMCFG_REMWAKE_Msk // bRemoteWake value received with last ACKed LPM Token +#define GLPMCFG_L1SSEN_Pos (7U) +#define GLPMCFG_L1SSEN_Msk (0x1UL << GLPMCFG_L1SSEN_Pos) // 0x00000080 +#define GLPMCFG_L1SSEN GLPMCFG_L1SSEN_Msk // L1 shallow sleep enable +#define GLPMCFG_BESLTHRS_Pos (8U) +#define GLPMCFG_BESLTHRS_Msk (0xFUL << GLPMCFG_BESLTHRS_Pos) // 0x00000F00 +#define GLPMCFG_BESLTHRS GLPMCFG_BESLTHRS_Msk // BESL threshold +#define GLPMCFG_L1DSEN_Pos (12U) +#define GLPMCFG_L1DSEN_Msk (0x1UL << GLPMCFG_L1DSEN_Pos) // 0x00001000 +#define GLPMCFG_L1DSEN GLPMCFG_L1DSEN_Msk // L1 deep sleep enable +#define GLPMCFG_LPMRSP_Pos (13U) +#define GLPMCFG_LPMRSP_Msk (0x3UL << GLPMCFG_LPMRSP_Pos) // 0x00006000 +#define GLPMCFG_LPMRSP GLPMCFG_LPMRSP_Msk // LPM response +#define GLPMCFG_SLPSTS_Pos (15U) +#define GLPMCFG_SLPSTS_Msk (0x1UL << GLPMCFG_SLPSTS_Pos) // 0x00008000 +#define GLPMCFG_SLPSTS GLPMCFG_SLPSTS_Msk // Port sleep status +#define GLPMCFG_L1RSMOK_Pos (16U) +#define GLPMCFG_L1RSMOK_Msk (0x1UL << GLPMCFG_L1RSMOK_Pos) // 0x00010000 +#define GLPMCFG_L1RSMOK GLPMCFG_L1RSMOK_Msk // Sleep State Resume OK +#define GLPMCFG_LPMCHIDX_Pos (17U) +#define GLPMCFG_LPMCHIDX_Msk (0xFUL << GLPMCFG_LPMCHIDX_Pos) // 0x001E0000 +#define GLPMCFG_LPMCHIDX GLPMCFG_LPMCHIDX_Msk // LPM Channel Index +#define GLPMCFG_LPMRCNT_Pos (21U) +#define GLPMCFG_LPMRCNT_Msk (0x7UL << GLPMCFG_LPMRCNT_Pos) // 0x00E00000 +#define GLPMCFG_LPMRCNT GLPMCFG_LPMRCNT_Msk // LPM retry count +#define GLPMCFG_SNDLPM_Pos (24U) +#define GLPMCFG_SNDLPM_Msk (0x1UL << GLPMCFG_SNDLPM_Pos) // 0x01000000 +#define GLPMCFG_SNDLPM GLPMCFG_SNDLPM_Msk // Send LPM transaction +#define GLPMCFG_LPMRCNTSTS_Pos (25U) +#define GLPMCFG_LPMRCNTSTS_Msk (0x7UL << GLPMCFG_LPMRCNTSTS_Pos) // 0x0E000000 +#define GLPMCFG_LPMRCNTSTS GLPMCFG_LPMRCNTSTS_Msk // LPM retry count status +#define GLPMCFG_ENBESL_Pos (28U) +#define GLPMCFG_ENBESL_Msk (0x1UL << GLPMCFG_ENBESL_Pos) // 0x10000000 +#define GLPMCFG_ENBESL GLPMCFG_ENBESL_Msk // Enable best effort service latency + +// GDFIFOCFG +#define GDFIFOCFG_EPINFOBASE_MASK (0xffff << 16) +#define GDFIFOCFG_EPINFOBASE_SHIFT 16 +#define GDFIFOCFG_GDFIFOCFG_MASK (0xffff << 0) +#define GDFIFOCFG_GDFIFOCFG_SHIFT 0 + +/******************** Bit definition for DIEPEACHMSK1 register ********************/ +#define DIEPEACHMSK1_XFRCM_Pos (0U) +#define DIEPEACHMSK1_XFRCM_Msk (0x1UL << DIEPEACHMSK1_XFRCM_Pos) // 0x00000001 +#define DIEPEACHMSK1_XFRCM DIEPEACHMSK1_XFRCM_Msk // Transfer completed interrupt mask +#define DIEPEACHMSK1_EPDM_Pos (1U) +#define DIEPEACHMSK1_EPDM_Msk (0x1UL << DIEPEACHMSK1_EPDM_Pos) // 0x00000002 +#define DIEPEACHMSK1_EPDM DIEPEACHMSK1_EPDM_Msk // Endpoint disabled interrupt mask +#define DIEPEACHMSK1_TOM_Pos (3U) +#define DIEPEACHMSK1_TOM_Msk (0x1UL << DIEPEACHMSK1_TOM_Pos) // 0x00000008 +#define DIEPEACHMSK1_TOM DIEPEACHMSK1_TOM_Msk // Timeout condition mask (nonisochronous endpoints) +#define DIEPEACHMSK1_ITTXFEMSK_Pos (4U) +#define DIEPEACHMSK1_ITTXFEMSK_Msk (0x1UL << DIEPEACHMSK1_ITTXFEMSK_Pos) // 0x00000010 +#define DIEPEACHMSK1_ITTXFEMSK DIEPEACHMSK1_ITTXFEMSK_Msk // IN token received when TxFIFO empty mask +#define DIEPEACHMSK1_INEPNMM_Pos (5U) +#define DIEPEACHMSK1_INEPNMM_Msk (0x1UL << DIEPEACHMSK1_INEPNMM_Pos) // 0x00000020 +#define DIEPEACHMSK1_INEPNMM DIEPEACHMSK1_INEPNMM_Msk // IN token received with EP mismatch mask +#define DIEPEACHMSK1_INEPNEM_Pos (6U) +#define DIEPEACHMSK1_INEPNEM_Msk (0x1UL << DIEPEACHMSK1_INEPNEM_Pos) // 0x00000040 +#define DIEPEACHMSK1_INEPNEM DIEPEACHMSK1_INEPNEM_Msk // IN endpoint NAK effective mask +#define DIEPEACHMSK1_TXFURM_Pos (8U) +#define DIEPEACHMSK1_TXFURM_Msk (0x1UL << DIEPEACHMSK1_TXFURM_Pos) // 0x00000100 +#define DIEPEACHMSK1_TXFURM DIEPEACHMSK1_TXFURM_Msk // FIFO underrun mask +#define DIEPEACHMSK1_BIM_Pos (9U) +#define DIEPEACHMSK1_BIM_Msk (0x1UL << DIEPEACHMSK1_BIM_Pos) // 0x00000200 +#define DIEPEACHMSK1_BIM DIEPEACHMSK1_BIM_Msk // BNA interrupt mask +#define DIEPEACHMSK1_NAKM_Pos (13U) +#define DIEPEACHMSK1_NAKM_Msk (0x1UL << DIEPEACHMSK1_NAKM_Pos) // 0x00002000 +#define DIEPEACHMSK1_NAKM DIEPEACHMSK1_NAKM_Msk // NAK interrupt mask + +/******************** Bit definition for HPRT register ********************/ +#define HPRT_CONN_STATUS_Pos (0U) +#define HPRT_CONN_STATUS_Msk (0x1UL << HPRT_CONN_STATUS_Pos) // 0x00000001 +#define HPRT_CONN_STATUS HPRT_CONN_STATUS_Msk // Port connect status +#define HPRT_CONN_DETECT_Pos (1U) +#define HPRT_CONN_DETECT_Msk (0x1UL << HPRT_CONN_DETECT_Pos) // 0x00000002 +#define HPRT_CONN_DETECT HPRT_CONN_DETECT_Msk // Port connect detected +#define HPRT_ENABLE_Pos (2U) +#define HPRT_ENABLE_Msk (0x1UL << HPRT_ENABLE_Pos) // 0x00000004 +#define HPRT_ENABLE HPRT_ENABLE_Msk // Port enable +#define HPRT_ENABLE_CHANGE_Pos (3U) +#define HPRT_ENABLE_CHANGE_Msk (0x1UL << HPRT_ENABLE_CHANGE_Pos) // 0x00000008 +#define HPRT_ENABLE_CHANGE HPRT_ENABLE_CHANGE_Msk // Port enable/disable change +#define HPRT_OVER_CURRENT_ACTIVE_Pos (4U) +#define HPRT_OVER_CURRENT_ACTIVE_Msk (0x1UL << HPRT_OVER_CURRENT_ACTIVE_Pos) // 0x00000010 +#define HPRT_OVER_CURRENT_ACTIVE HPRT_OVER_CURRENT_ACTIVE_Msk // Port overcurrent active +#define HPRT_OVER_CURRENT_CHANGE_Pos (5U) +#define HPRT_OVER_CURRENT_CHANGE_Msk (0x1UL << HPRT_OVER_CURRENT_CHANGE_Pos) // 0x00000020 +#define HPRT_OVER_CURRENT_CHANGE HPRT_OVER_CURRENT_CHANGE_Msk // Port overcurrent change +#define HPRT_RESUME_Pos (6U) +#define HPRT_RESUME_Msk (0x1UL << HPRT_RESUME_Pos) // 0x00000040 +#define HPRT_RESUME HPRT_RESUME_Msk // Port resume +#define HPRT_SUSPEND_Pos (7U) +#define HPRT_SUSPEND_Msk (0x1UL << HPRT_SUSPEND_Pos) // 0x00000080 +#define HPRT_SUSPEND HPRT_SUSPEND_Msk // Port suspend +#define HPRT_RESET_Pos (8U) +#define HPRT_RESET_Msk (0x1UL << HPRT_RESET_Pos) // 0x00000100 +#define HPRT_RESET HPRT_RESET_Msk // Port reset +#define HPRT_LINE_STATUS_Pos (10U) +#define HPRT_LINE_STATUS_Msk (0x3UL << HPRT_LINE_STATUS_Pos) // 0x00000C00 +#define HPRT_LINE_STATUS HPRT_LINE_STATUS_Msk // Port line status +#define HPRT_LINE_STATUS_0 (0x1UL << HPRT_LINE_STATUS_Pos) // 0x00000400 +#define HPRT_LINE_STATUS_1 (0x2UL << HPRT_LINE_STATUS_Pos) // 0x00000800 +#define HPRT_POWER_Pos (12U) +#define HPRT_POWER_Msk (0x1UL << HPRT_POWER_Pos) // 0x00001000 +#define HPRT_POWER HPRT_POWER_Msk // Port power +#define HPRT_TEST_CONTROL_Pos (13U) +#define HPRT_TEST_CONTROL_Msk (0xFUL << HPRT_TEST_CONTROL_Pos) // 0x0001E000 +#define HPRT_TEST_CONTROL HPRT_TEST_CONTROL_Msk // Port test control +#define HPRT_TEST_CONTROL_0 (0x1UL << HPRT_TEST_CONTROL_Pos) // 0x00002000 +#define HPRT_TEST_CONTROL_1 (0x2UL << HPRT_TEST_CONTROL_Pos) // 0x00004000 +#define HPRT_TEST_CONTROL_2 (0x4UL << HPRT_TEST_CONTROL_Pos) // 0x00008000 +#define HPRT_TEST_CONTROL_3 (0x8UL << HPRT_TEST_CONTROL_Pos) // 0x00010000 +#define HPRT_SPEED_Pos (17U) +#define HPRT_SPEED_Msk (0x3UL << HPRT_SPEED_Pos) // 0x00060000 +#define HPRT_SPEED HPRT_SPEED_Msk // Port speed +#define HPRT_SPEED_0 (0x1UL << HPRT_SPEED_Pos) // 0x00020000 +#define HPRT_SPEED_1 (0x2UL << HPRT_SPEED_Pos) // 0x00040000 + +/******************** Bit definition for DOEPEACHMSK1 register ********************/ +#define DOEPEACHMSK1_XFRCM_Pos (0U) +#define DOEPEACHMSK1_XFRCM_Msk (0x1UL << DOEPEACHMSK1_XFRCM_Pos) // 0x00000001 +#define DOEPEACHMSK1_XFRCM DOEPEACHMSK1_XFRCM_Msk // Transfer completed interrupt mask +#define DOEPEACHMSK1_EPDM_Pos (1U) +#define DOEPEACHMSK1_EPDM_Msk (0x1UL << DOEPEACHMSK1_EPDM_Pos) // 0x00000002 +#define DOEPEACHMSK1_EPDM DOEPEACHMSK1_EPDM_Msk // Endpoint disabled interrupt mask +#define DOEPEACHMSK1_TOM_Pos (3U) +#define DOEPEACHMSK1_TOM_Msk (0x1UL << DOEPEACHMSK1_TOM_Pos) // 0x00000008 +#define DOEPEACHMSK1_TOM DOEPEACHMSK1_TOM_Msk // Timeout condition mask +#define DOEPEACHMSK1_ITTXFEMSK_Pos (4U) +#define DOEPEACHMSK1_ITTXFEMSK_Msk (0x1UL << DOEPEACHMSK1_ITTXFEMSK_Pos) // 0x00000010 +#define DOEPEACHMSK1_ITTXFEMSK DOEPEACHMSK1_ITTXFEMSK_Msk // IN token received when TxFIFO empty mask +#define DOEPEACHMSK1_INEPNMM_Pos (5U) +#define DOEPEACHMSK1_INEPNMM_Msk (0x1UL << DOEPEACHMSK1_INEPNMM_Pos) // 0x00000020 +#define DOEPEACHMSK1_INEPNMM DOEPEACHMSK1_INEPNMM_Msk // IN token received with EP mismatch mask +#define DOEPEACHMSK1_INEPNEM_Pos (6U) +#define DOEPEACHMSK1_INEPNEM_Msk (0x1UL << DOEPEACHMSK1_INEPNEM_Pos) // 0x00000040 +#define DOEPEACHMSK1_INEPNEM DOEPEACHMSK1_INEPNEM_Msk // IN endpoint NAK effective mask +#define DOEPEACHMSK1_TXFURM_Pos (8U) +#define DOEPEACHMSK1_TXFURM_Msk (0x1UL << DOEPEACHMSK1_TXFURM_Pos) // 0x00000100 +#define DOEPEACHMSK1_TXFURM DOEPEACHMSK1_TXFURM_Msk // OUT packet error mask +#define DOEPEACHMSK1_BIM_Pos (9U) +#define DOEPEACHMSK1_BIM_Msk (0x1UL << DOEPEACHMSK1_BIM_Pos) // 0x00000200 +#define DOEPEACHMSK1_BIM DOEPEACHMSK1_BIM_Msk // BNA interrupt mask +#define DOEPEACHMSK1_BERRM_Pos (12U) +#define DOEPEACHMSK1_BERRM_Msk (0x1UL << DOEPEACHMSK1_BERRM_Pos) // 0x00001000 +#define DOEPEACHMSK1_BERRM DOEPEACHMSK1_BERRM_Msk // Bubble error interrupt mask +#define DOEPEACHMSK1_NAKM_Pos (13U) +#define DOEPEACHMSK1_NAKM_Msk (0x1UL << DOEPEACHMSK1_NAKM_Pos) // 0x00002000 +#define DOEPEACHMSK1_NAKM DOEPEACHMSK1_NAKM_Msk // NAK interrupt mask +#define DOEPEACHMSK1_NYETM_Pos (14U) +#define DOEPEACHMSK1_NYETM_Msk (0x1UL << DOEPEACHMSK1_NYETM_Pos) // 0x00004000 +#define DOEPEACHMSK1_NYETM DOEPEACHMSK1_NYETM_Msk // NYET interrupt mask + +/******************** Bit definition for HPTXFSIZ register ********************/ +#define HPTXFSIZ_PTXSA_Pos (0U) +#define HPTXFSIZ_PTXSA_Msk (0xFFFFUL << HPTXFSIZ_PTXSA_Pos) // 0x0000FFFF +#define HPTXFSIZ_PTXSA HPTXFSIZ_PTXSA_Msk // Host periodic TxFIFO start address +#define HPTXFSIZ_PTXFD_Pos (16U) +#define HPTXFSIZ_PTXFD_Msk (0xFFFFUL << HPTXFSIZ_PTXFD_Pos) // 0xFFFF0000 +#define HPTXFSIZ_PTXFD HPTXFSIZ_PTXFD_Msk // Host periodic TxFIFO depth + +/******************** Bit definition for DIEPCTL register ********************/ +#define DIEPCTL0_MPSIZ_Pos (0U) +#define DIEPCTL0_MPSIZ_Msk (0x3UL << DIEPCTL0_MPSIZ_Pos) // 0x00000003 +#define DIEPCTL0_MPSIZ DIEPCTL0_MPSIZ_Msk // Maximum packet size(endpoint 0) +#define DIEPCTL_MPSIZ_Pos (0U) +#define DIEPCTL_MPSIZ_Msk (0x7FFUL << DIEPCTL_MPSIZ_Pos) // 0x000007FF +#define DIEPCTL_MPSIZ DIEPCTL_MPSIZ_Msk // Maximum packet size +#define DIEPCTL_USBAEP_Pos (15U) +#define DIEPCTL_USBAEP_Msk (0x1UL << DIEPCTL_USBAEP_Pos) // 0x00008000 +#define DIEPCTL_USBAEP DIEPCTL_USBAEP_Msk // USB active endpoint +#define DIEPCTL_EONUM_DPID_Pos (16U) +#define DIEPCTL_EONUM_DPID_Msk (0x1UL << DIEPCTL_EONUM_DPID_Pos) // 0x00010000 +#define DIEPCTL_EONUM_DPID DIEPCTL_EONUM_DPID_Msk // Even/odd frame +#define DIEPCTL_NAKSTS_Pos (17U) +#define DIEPCTL_NAKSTS_Msk (0x1UL << DIEPCTL_NAKSTS_Pos) // 0x00020000 +#define DIEPCTL_NAKSTS DIEPCTL_NAKSTS_Msk // NAK status + +#define DIEPCTL_EPTYP_Pos (18U) +#define DIEPCTL_EPTYP_Msk (0x3UL << DIEPCTL_EPTYP_Pos) // 0x000C0000 +#define DIEPCTL_EPTYP DIEPCTL_EPTYP_Msk // Endpoint type +#define DIEPCTL_EPTYP_0 (0x1UL << DIEPCTL_EPTYP_Pos) // 0x00040000 +#define DIEPCTL_EPTYP_1 (0x2UL << DIEPCTL_EPTYP_Pos) // 0x00080000 +#define DIEPCTL_STALL_Pos (21U) +#define DIEPCTL_STALL_Msk (0x1UL << DIEPCTL_STALL_Pos) // 0x00200000 +#define DIEPCTL_STALL DIEPCTL_STALL_Msk // STALL handshake + +#define DIEPCTL_TXFNUM_Pos (22U) +#define DIEPCTL_TXFNUM_Msk (0xFUL << DIEPCTL_TXFNUM_Pos) // 0x03C00000 +#define DIEPCTL_TXFNUM DIEPCTL_TXFNUM_Msk // TxFIFO number +#define DIEPCTL_TXFNUM_0 (0x1UL << DIEPCTL_TXFNUM_Pos) // 0x00400000 +#define DIEPCTL_TXFNUM_1 (0x2UL << DIEPCTL_TXFNUM_Pos) // 0x00800000 +#define DIEPCTL_TXFNUM_2 (0x4UL << DIEPCTL_TXFNUM_Pos) // 0x01000000 +#define DIEPCTL_TXFNUM_3 (0x8UL << DIEPCTL_TXFNUM_Pos) // 0x02000000 +#define DIEPCTL_CNAK_Pos (26U) +#define DIEPCTL_CNAK_Msk (0x1UL << DIEPCTL_CNAK_Pos) // 0x04000000 +#define DIEPCTL_CNAK DIEPCTL_CNAK_Msk // Clear NAK +#define DIEPCTL_SNAK_Pos (27U) +#define DIEPCTL_SNAK_Msk (0x1UL << DIEPCTL_SNAK_Pos) // 0x08000000 +#define DIEPCTL_SNAK DIEPCTL_SNAK_Msk // Set NAK +#define DIEPCTL_SD0PID_SEVNFRM_Pos (28U) +#define DIEPCTL_SD0PID_SEVNFRM_Msk (0x1UL << DIEPCTL_SD0PID_SEVNFRM_Pos) // 0x10000000 +#define DIEPCTL_SD0PID_SEVNFRM DIEPCTL_SD0PID_SEVNFRM_Msk // Set DATA0 PID +#define DIEPCTL_SODDFRM_Pos (29U) +#define DIEPCTL_SODDFRM_Msk (0x1UL << DIEPCTL_SODDFRM_Pos) // 0x20000000 +#define DIEPCTL_SODDFRM DIEPCTL_SODDFRM_Msk // Set odd frame +#define DIEPCTL_EPDIS_Pos (30U) +#define DIEPCTL_EPDIS_Msk (0x1UL << DIEPCTL_EPDIS_Pos) // 0x40000000 +#define DIEPCTL_EPDIS DIEPCTL_EPDIS_Msk // Endpoint disable +#define DIEPCTL_EPENA_Pos (31U) +#define DIEPCTL_EPENA_Msk (0x1UL << DIEPCTL_EPENA_Pos) // 0x80000000 +#define DIEPCTL_EPENA DIEPCTL_EPENA_Msk // Endpoint enable + +/******************** Bit definition for HCCHAR register ********************/ +#define HCCHAR_MPSIZ_Pos (0U) +#define HCCHAR_MPSIZ_Msk (0x7FFUL << HCCHAR_MPSIZ_Pos) // 0x000007FF +#define HCCHAR_MPSIZ HCCHAR_MPSIZ_Msk // Maximum packet size + +#define HCCHAR_EPNUM_Pos (11U) +#define HCCHAR_EPNUM_Msk (0xFUL << HCCHAR_EPNUM_Pos) // 0x00007800 +#define HCCHAR_EPNUM HCCHAR_EPNUM_Msk // Endpoint number +#define HCCHAR_EPNUM_0 (0x1UL << HCCHAR_EPNUM_Pos) // 0x00000800 +#define HCCHAR_EPNUM_1 (0x2UL << HCCHAR_EPNUM_Pos) // 0x00001000 +#define HCCHAR_EPNUM_2 (0x4UL << HCCHAR_EPNUM_Pos) // 0x00002000 +#define HCCHAR_EPNUM_3 (0x8UL << HCCHAR_EPNUM_Pos) // 0x00004000 +#define HCCHAR_EPDIR_Pos (15U) +#define HCCHAR_EPDIR_Msk (0x1UL << HCCHAR_EPDIR_Pos) // 0x00008000 +#define HCCHAR_EPDIR HCCHAR_EPDIR_Msk // Endpoint direction +#define HCCHAR_LSDEV_Pos (17U) +#define HCCHAR_LSDEV_Msk (0x1UL << HCCHAR_LSDEV_Pos) // 0x00020000 +#define HCCHAR_LSDEV HCCHAR_LSDEV_Msk // Low-speed device + +#define HCCHAR_EPTYP_Pos (18U) +#define HCCHAR_EPTYP_Msk (0x3UL << HCCHAR_EPTYP_Pos) // 0x000C0000 +#define HCCHAR_EPTYP HCCHAR_EPTYP_Msk // Endpoint type +#define HCCHAR_EPTYP_0 (0x1UL << HCCHAR_EPTYP_Pos) // 0x00040000 +#define HCCHAR_EPTYP_1 (0x2UL << HCCHAR_EPTYP_Pos) // 0x00080000 + +#define HCCHAR_MC_Pos (20U) +#define HCCHAR_MC_Msk (0x3UL << HCCHAR_MC_Pos) // 0x00300000 +#define HCCHAR_MC HCCHAR_MC_Msk // Multi Count (MC) / Error Count (EC) +#define HCCHAR_MC_0 (0x1UL << HCCHAR_MC_Pos) // 0x00100000 +#define HCCHAR_MC_1 (0x2UL << HCCHAR_MC_Pos) // 0x00200000 + +#define HCCHAR_DAD_Pos (22U) +#define HCCHAR_DAD_Msk (0x7FUL << HCCHAR_DAD_Pos) // 0x1FC00000 +#define HCCHAR_DAD HCCHAR_DAD_Msk // Device address +#define HCCHAR_DAD_0 (0x01UL << HCCHAR_DAD_Pos) // 0x00400000 +#define HCCHAR_DAD_1 (0x02UL << HCCHAR_DAD_Pos) // 0x00800000 +#define HCCHAR_DAD_2 (0x04UL << HCCHAR_DAD_Pos) // 0x01000000 +#define HCCHAR_DAD_3 (0x08UL << HCCHAR_DAD_Pos) // 0x02000000 +#define HCCHAR_DAD_4 (0x10UL << HCCHAR_DAD_Pos) // 0x04000000 +#define HCCHAR_DAD_5 (0x20UL << HCCHAR_DAD_Pos) // 0x08000000 +#define HCCHAR_DAD_6 (0x40UL << HCCHAR_DAD_Pos) // 0x10000000 +#define HCCHAR_ODDFRM_Pos (29U) +#define HCCHAR_ODDFRM_Msk (0x1UL << HCCHAR_ODDFRM_Pos) // 0x20000000 +#define HCCHAR_ODDFRM HCCHAR_ODDFRM_Msk // Odd frame +#define HCCHAR_CHDIS_Pos (30U) +#define HCCHAR_CHDIS_Msk (0x1UL << HCCHAR_CHDIS_Pos) // 0x40000000 +#define HCCHAR_CHDIS HCCHAR_CHDIS_Msk // Channel disable +#define HCCHAR_CHENA_Pos (31U) +#define HCCHAR_CHENA_Msk (0x1UL << HCCHAR_CHENA_Pos) // 0x80000000 +#define HCCHAR_CHENA HCCHAR_CHENA_Msk // Channel enable + +/******************** Bit definition for HCSPLT register ********************/ + +#define HCSPLT_PRTADDR_Pos (0U) +#define HCSPLT_PRTADDR_Msk (0x7FUL << HCSPLT_PRTADDR_Pos) // 0x0000007F +#define HCSPLT_PRTADDR HCSPLT_PRTADDR_Msk // Port address +#define HCSPLT_PRTADDR_0 (0x01UL << HCSPLT_PRTADDR_Pos) // 0x00000001 +#define HCSPLT_PRTADDR_1 (0x02UL << HCSPLT_PRTADDR_Pos) // 0x00000002 +#define HCSPLT_PRTADDR_2 (0x04UL << HCSPLT_PRTADDR_Pos) // 0x00000004 +#define HCSPLT_PRTADDR_3 (0x08UL << HCSPLT_PRTADDR_Pos) // 0x00000008 +#define HCSPLT_PRTADDR_4 (0x10UL << HCSPLT_PRTADDR_Pos) // 0x00000010 +#define HCSPLT_PRTADDR_5 (0x20UL << HCSPLT_PRTADDR_Pos) // 0x00000020 +#define HCSPLT_PRTADDR_6 (0x40UL << HCSPLT_PRTADDR_Pos) // 0x00000040 + +#define HCSPLT_HUBADDR_Pos (7U) +#define HCSPLT_HUBADDR_Msk (0x7FUL << HCSPLT_HUBADDR_Pos) // 0x00003F80 +#define HCSPLT_HUBADDR HCSPLT_HUBADDR_Msk // Hub address +#define HCSPLT_HUBADDR_0 (0x01UL << HCSPLT_HUBADDR_Pos) // 0x00000080 +#define HCSPLT_HUBADDR_1 (0x02UL << HCSPLT_HUBADDR_Pos) // 0x00000100 +#define HCSPLT_HUBADDR_2 (0x04UL << HCSPLT_HUBADDR_Pos) // 0x00000200 +#define HCSPLT_HUBADDR_3 (0x08UL << HCSPLT_HUBADDR_Pos) // 0x00000400 +#define HCSPLT_HUBADDR_4 (0x10UL << HCSPLT_HUBADDR_Pos) // 0x00000800 +#define HCSPLT_HUBADDR_5 (0x20UL << HCSPLT_HUBADDR_Pos) // 0x00001000 +#define HCSPLT_HUBADDR_6 (0x40UL << HCSPLT_HUBADDR_Pos) // 0x00002000 + +#define HCSPLT_XACTPOS_Pos (14U) +#define HCSPLT_XACTPOS_Msk (0x3UL << HCSPLT_XACTPOS_Pos) // 0x0000C000 +#define HCSPLT_XACTPOS HCSPLT_XACTPOS_Msk // XACTPOS +#define HCSPLT_XACTPOS_0 (0x1UL << HCSPLT_XACTPOS_Pos) // 0x00004000 +#define HCSPLT_XACTPOS_1 (0x2UL << HCSPLT_XACTPOS_Pos) // 0x00008000 +#define HCSPLT_COMPLSPLT_Pos (16U) +#define HCSPLT_COMPLSPLT_Msk (0x1UL << HCSPLT_COMPLSPLT_Pos) // 0x00010000 +#define HCSPLT_COMPLSPLT HCSPLT_COMPLSPLT_Msk // Do complete split +#define HCSPLT_SPLITEN_Pos (31U) +#define HCSPLT_SPLITEN_Msk (0x1UL << HCSPLT_SPLITEN_Pos) // 0x80000000 +#define HCSPLT_SPLITEN HCSPLT_SPLITEN_Msk // Split enable + +/******************** Bit definition for HCINT register ********************/ +#define HCINT_XFER_COMPLETE_Pos (0U) +#define HCINT_XFER_COMPLETE_Msk (0x1UL << HCINT_XFER_COMPLETE_Pos) // 0x00000001 +#define HCINT_XFER_COMPLETE HCINT_XFER_COMPLETE_Msk // Transfer completed +#define HCINT_HALTED_Pos (1U) +#define HCINT_HALTED_Msk (0x1UL << HCINT_HALTED_Pos) // 0x00000002 +#define HCINT_HALTED HCINT_HALTED_Msk // Channel halted +#define HCINT_AHB_ERR_Pos (2U) +#define HCINT_AHB_ERR_Msk (0x1UL << HCINT_AHB_ERR_Pos) // 0x00000004 +#define HCINT_AHB_ERR HCINT_AHB_ERR_Msk // AHB error +#define HCINT_STALL_Pos (3U) +#define HCINT_STALL_Msk (0x1UL << HCINT_STALL_Pos) // 0x00000008 +#define HCINT_STALL HCINT_STALL_Msk // STALL response received interrupt +#define HCINT_NAK_Pos (4U) +#define HCINT_NAK_Msk (0x1UL << HCINT_NAK_Pos) // 0x00000010 +#define HCINT_NAK HCINT_NAK_Msk // NAK response received interrupt +#define HCINT_ACK_Pos (5U) +#define HCINT_ACK_Msk (0x1UL << HCINT_ACK_Pos) // 0x00000020 +#define HCINT_ACK HCINT_ACK_Msk // ACK response received/transmitted interrupt +#define HCINT_NYET_Pos (6U) +#define HCINT_NYET_Msk (0x1UL << HCINT_NYET_Pos) // 0x00000040 +#define HCINT_NYET HCINT_NYET_Msk // Response received interrupt +#define HCINT_XACT_ERR_Pos (7U) +#define HCINT_XACT_ERR_Msk (0x1UL << HCINT_XACT_ERR_Pos) // 0x00000080 +#define HCINT_XACT_ERR HCINT_XACT_ERR_Msk // Transaction error +#define HCINT_BABBLE_ERR_Pos (8U) +#define HCINT_BABBLE_ERR_Msk (0x1UL << HCINT_BABBLE_ERR_Pos) // 0x00000100 +#define HCINT_BABBLE_ERR HCINT_BABBLE_ERR_Msk // Babble error +#define HCINT_FARME_OVERRUN_Pos (9U) +#define HCINT_FARME_OVERRUN_Msk (0x1UL << HCINT_FARME_OVERRUN_Pos) // 0x00000200 +#define HCINT_FARME_OVERRUN HCINT_FARME_OVERRUN_Msk // Frame overrun +#define HCINT_DATATOGGLE_ERR_Pos (10U) +#define HCINT_DATATOGGLE_ERR_Msk (0x1UL << HCINT_DATATOGGLE_ERR_Pos) // 0x00000400 +#define HCINT_DATATOGGLE_ERR HCINT_DATATOGGLE_ERR_Msk // Data toggle error +#define HCINT_BUFFER_NA_Pos (11U) +#define HCINT_BUFFER_NA_Msk (0x1UL << HCINT_BUFFER_NA_Pos) // 0x00000800 +#define HCINT_BUFFER_NA HCINT_BUFFER_NA_Msk // Buffer not available interrupt +#define HCINT_XCS_XACT_ERR_Pos (12U) +#define HCINT_XCS_XACT_ERR_Msk (0x1UL << HCINT_XCS_XACT_ERR_Pos) // 0x00001000 +#define HCINT_XCS_XACT_ERR HCINT_XCS_XACT_ERR_Msk // Excessive transaction error +#define HCINT_DESC_ROLLOVER_Pos (13U) +#define HCINT_DESC_ROLLOVER_Msk (0x1UL << HCINT_DESC_ROLLOVER_Pos) // 0x00002000 +#define HCINT_DESC_ROLLOVER HCINT_DESC_ROLLOVER_Msk // Descriptor rollover + +/******************** Bit definition for DIEPINT register ********************/ +#define DIEPINT_XFRC_Pos (0U) +#define DIEPINT_XFRC_Msk (0x1UL << DIEPINT_XFRC_Pos) // 0x00000001 +#define DIEPINT_XFRC DIEPINT_XFRC_Msk // Transfer completed interrupt +#define DIEPINT_EPDISD_Pos (1U) +#define DIEPINT_EPDISD_Msk (0x1UL << DIEPINT_EPDISD_Pos) // 0x00000002 +#define DIEPINT_EPDISD DIEPINT_EPDISD_Msk // Endpoint disabled interrupt +#define DIEPINT_AHBERR_Pos (2U) +#define DIEPINT_AHBERR_Msk (0x1UL << DIEPINT_AHBERR_Pos) // 0x00000004 +#define DIEPINT_AHBERR DIEPINT_AHBERR_Msk // AHB Error (AHBErr) during an IN transaction +#define DIEPINT_TOC_Pos (3U) +#define DIEPINT_TOC_Msk (0x1UL << DIEPINT_TOC_Pos) // 0x00000008 +#define DIEPINT_TOC DIEPINT_TOC_Msk // Timeout condition +#define DIEPINT_ITTXFE_Pos (4U) +#define DIEPINT_ITTXFE_Msk (0x1UL << DIEPINT_ITTXFE_Pos) // 0x00000010 +#define DIEPINT_ITTXFE DIEPINT_ITTXFE_Msk // IN token received when TxFIFO is empty +#define DIEPINT_INEPNM_Pos (5U) +#define DIEPINT_INEPNM_Msk (0x1UL << DIEPINT_INEPNM_Pos) // 0x00000020 +#define DIEPINT_INEPNM DIEPINT_INEPNM_Msk // IN token received with EP mismatch +#define DIEPINT_INEPNE_Pos (6U) +#define DIEPINT_INEPNE_Msk (0x1UL << DIEPINT_INEPNE_Pos) // 0x00000040 +#define DIEPINT_INEPNE DIEPINT_INEPNE_Msk // IN endpoint NAK effective +#define DIEPINT_TXFE_Pos (7U) +#define DIEPINT_TXFE_Msk (0x1UL << DIEPINT_TXFE_Pos) // 0x00000080 +#define DIEPINT_TXFE DIEPINT_TXFE_Msk // Transmit FIFO empty +#define DIEPINT_TXFIFOUDRN_Pos (8U) +#define DIEPINT_TXFIFOUDRN_Msk (0x1UL << DIEPINT_TXFIFOUDRN_Pos) // 0x00000100 +#define DIEPINT_TXFIFOUDRN DIEPINT_TXFIFOUDRN_Msk // Transmit Fifo Underrun +#define DIEPINT_BNA_Pos (9U) +#define DIEPINT_BNA_Msk (0x1UL << DIEPINT_BNA_Pos) // 0x00000200 +#define DIEPINT_BNA DIEPINT_BNA_Msk // Buffer not available interrupt +#define DIEPINT_PKTDRPSTS_Pos (11U) +#define DIEPINT_PKTDRPSTS_Msk (0x1UL << DIEPINT_PKTDRPSTS_Pos) // 0x00000800 +#define DIEPINT_PKTDRPSTS DIEPINT_PKTDRPSTS_Msk // Packet dropped status +#define DIEPINT_BERR_Pos (12U) +#define DIEPINT_BERR_Msk (0x1UL << DIEPINT_BERR_Pos) // 0x00001000 +#define DIEPINT_BERR DIEPINT_BERR_Msk // Babble error interrupt +#define DIEPINT_NAK_Pos (13U) +#define DIEPINT_NAK_Msk (0x1UL << DIEPINT_NAK_Pos) // 0x00002000 +#define DIEPINT_NAK DIEPINT_NAK_Msk // NAK interrupt + +/******************** Bit definition for DIEPTSIZ register ********************/ + +#define DIEPTSIZ_XFRSIZ_Pos (0U) +#define DIEPTSIZ_XFRSIZ_Msk (0x7FFFFUL << DIEPTSIZ_XFRSIZ_Pos) // 0x0007FFFF +#define DIEPTSIZ_XFRSIZ DIEPTSIZ_XFRSIZ_Msk // Transfer size +#define DIEPTSIZ_PKTCNT_Pos (19U) +#define DIEPTSIZ_PKTCNT_Msk (0x3FFUL << DIEPTSIZ_PKTCNT_Pos) // 0x1FF80000 +#define DIEPTSIZ_PKTCNT DIEPTSIZ_PKTCNT_Msk // Packet count +#define DIEPTSIZ_MULCNT_Pos (29U) +#define DIEPTSIZ_MULCNT_Msk (0x3UL << DIEPTSIZ_MULCNT_Pos) // 0x60000000 +#define DIEPTSIZ_MULCNT DIEPTSIZ_MULCNT_Msk // Packet count + /******************** Bit definition for HCTSIZ register ********************/ +#define HCTSIZ_XFRSIZ_Pos (0U) +#define HCTSIZ_XFRSIZ_Msk (0x7FFFFUL << HCTSIZ_XFRSIZ_Pos) // 0x0007FFFF +#define HCTSIZ_XFRSIZ HCTSIZ_XFRSIZ_Msk // Transfer size +#define HCTSIZ_PKTCNT_Pos (19U) +#define HCTSIZ_PKTCNT_Msk (0x3FFUL << HCTSIZ_PKTCNT_Pos) // 0x1FF80000 +#define HCTSIZ_PKTCNT HCTSIZ_PKTCNT_Msk // Packet count +#define HCTSIZ_DOPING_Pos (31U) +#define HCTSIZ_DOPING_Msk (0x1UL << HCTSIZ_DOPING_Pos) // 0x80000000 +#define HCTSIZ_DOPING HCTSIZ_DOPING_Msk // Do PING +#define HCTSIZ_PID_Pos (29U) +#define HCTSIZ_PID_Msk (0x3UL << HCTSIZ_PID_Pos) // 0x60000000 +#define HCTSIZ_PID HCTSIZ_PID_Msk // Data PID + +/******************** Bit definition for DIEPDMA register ********************/ +#define DIEPDMA_DMAADDR_Pos (0U) +#define DIEPDMA_DMAADDR_Msk (0xFFFFFFFFUL << DIEPDMA_DMAADDR_Pos) // 0xFFFFFFFF +#define DIEPDMA_DMAADDR DIEPDMA_DMAADDR_Msk // DMA address + +/******************** Bit definition for HCDMA register ********************/ +#define HCDMA_DMAADDR_Pos (0U) +#define HCDMA_DMAADDR_Msk (0xFFFFFFFFUL << HCDMA_DMAADDR_Pos) // 0xFFFFFFFF +#define HCDMA_DMAADDR HCDMA_DMAADDR_Msk // DMA address + + /******************** Bit definition for DTXFSTS register ********************/ +#define DTXFSTS_INEPTFSAV_Pos (0U) +#define DTXFSTS_INEPTFSAV_Msk (0xFFFFUL << DTXFSTS_INEPTFSAV_Pos) // 0x0000FFFF +#define DTXFSTS_INEPTFSAV DTXFSTS_INEPTFSAV_Msk // IN endpoint TxFIFO space available + + /******************** Bit definition for DIEPTXF register ********************/ +#define DIEPTXF_INEPTXSA_Pos (0U) +#define DIEPTXF_INEPTXSA_Msk (0xFFFFUL << DIEPTXF_INEPTXSA_Pos) // 0x0000FFFF +#define DIEPTXF_INEPTXSA DIEPTXF_INEPTXSA_Msk // IN endpoint FIFOx transmit RAM start address +#define DIEPTXF_INEPTXFD_Pos (16U) +#define DIEPTXF_INEPTXFD_Msk (0xFFFFUL << DIEPTXF_INEPTXFD_Pos) // 0xFFFF0000 +#define DIEPTXF_INEPTXFD DIEPTXF_INEPTXFD_Msk // IN endpoint TxFIFO depth + + +/******************** Bit definition for Common EPCTL register ********************/ +#define EPCTL_MPSIZ_Pos (0U) +#define EPCTL_MPSIZ_Msk (0x7FFUL << EPCTL_MPSIZ_Pos) // 0x000007FF +#define EPCTL_MPSIZ EPCTL_MPSIZ_Msk // Maximum packet size //Bit 1 +#define EPCTL_USBAEP_Pos (15U) +#define EPCTL_USBAEP_Msk (0x1UL << EPCTL_USBAEP_Pos) // 0x00008000 +#define EPCTL_USBAEP EPCTL_USBAEP_Msk // USB active endpoint +#define EPCTL_NAKSTS_Pos (17U) +#define EPCTL_NAKSTS_Msk (0x1UL << EPCTL_NAKSTS_Pos) // 0x00020000 +#define EPCTL_NAKSTS EPCTL_NAKSTS_Msk // NAK status +#define EPCTL_EPTYP_Pos (18U) +#define EPCTL_EPTYP_Msk (0x3UL << EPCTL_EPTYP_Pos) // 0x000C0000 +#define EPCTL_EPTYP EPCTL_EPTYP_Msk // Endpoint type +#define EPCTL_EPTYP_0 (0x1UL << EPCTL_EPTYP_Pos) // 0x00040000 +#define EPCTL_EPTYP_1 (0x2UL << EPCTL_EPTYP_Pos) // 0x00080000 +#define EPCTL_SNPM EPCTL_SNPM_Msk // Snoop mode +#define EPCTL_STALL_Pos (21U) +#define EPCTL_STALL_Msk (0x1UL << EPCTL_STALL_Pos) // 0x00200000 +#define EPCTL_STALL EPCTL_STALL_Msk // STALL handshake +#define EPCTL_CNAK_Pos (26U) +#define EPCTL_CNAK_Msk (0x1UL << EPCTL_CNAK_Pos) // 0x04000000 +#define EPCTL_CNAK EPCTL_CNAK_Msk // Clear NAK +#define EPCTL_SNAK_Pos (27U) +#define EPCTL_SNAK_Msk (0x1UL << EPCTL_SNAK_Pos) // 0x08000000 +#define EPCTL_SNAK EPCTL_SNAK_Msk // Set NAK +#define EPCTL_SD0PID_SEVNFRM_Pos (28U) +#define EPCTL_SD0PID_SEVNFRM_Msk (0x1UL << EPCTL_SD0PID_SEVNFRM_Pos) // 0x10000000 +#define EPCTL_SD0PID_SEVNFRM EPCTL_SD0PID_SEVNFRM_Msk // Set DATA0 PID +#define EPCTL_SODDFRM_Pos (29U) +#define EPCTL_SODDFRM_Msk (0x1UL << EPCTL_SODDFRM_Pos) // 0x20000000 +#define EPCTL_SODDFRM EPCTL_SODDFRM_Msk // Set odd frame +#define EPCTL_EPDIS_Pos (30U) +#define EPCTL_EPDIS_Msk (0x1UL << EPCTL_EPDIS_Pos) // 0x40000000 +#define EPCTL_EPDIS EPCTL_EPDIS_Msk // Endpoint disable +#define EPCTL_EPENA_Pos (31U) +#define EPCTL_EPENA_Msk (0x1UL << EPCTL_EPENA_Pos) // 0x80000000 +#define EPCTL_EPENA EPCTL_EPENA_Msk // Endpoint enable + +/******************** Bit definition for DOEPCTL register ********************/ +#define DOEPCTL0_MPSIZ_Pos (0U) +#define DOEPCTL0_MPSIZ_Msk (0x3UL << DOEPCTL0_MPSIZ_Pos) // 0x00000003 +#define DOEPCTL0_MPSIZ DOEPCTL0_MPSIZ_Msk // Maximum packet size(endpoint 0) +#define DOEPCTL_MPSIZ_Pos (0U) +#define DOEPCTL_MPSIZ_Msk (0x7FFUL << DOEPCTL_MPSIZ_Pos) // 0x000007FF +#define DOEPCTL_MPSIZ DOEPCTL_MPSIZ_Msk // Maximum packet size //Bit 1 +#define DOEPCTL_USBAEP_Pos (15U) +#define DOEPCTL_USBAEP_Msk (0x1UL << DOEPCTL_USBAEP_Pos) // 0x00008000 +#define DOEPCTL_USBAEP DOEPCTL_USBAEP_Msk // USB active endpoint +#define DOEPCTL_NAKSTS_Pos (17U) +#define DOEPCTL_NAKSTS_Msk (0x1UL << DOEPCTL_NAKSTS_Pos) // 0x00020000 +#define DOEPCTL_NAKSTS DOEPCTL_NAKSTS_Msk // NAK status +#define DOEPCTL_SD0PID_SEVNFRM_Pos (28U) +#define DOEPCTL_SD0PID_SEVNFRM_Msk (0x1UL << DOEPCTL_SD0PID_SEVNFRM_Pos) // 0x10000000 +#define DOEPCTL_SD0PID_SEVNFRM DOEPCTL_SD0PID_SEVNFRM_Msk // Set DATA0 PID +#define DOEPCTL_SODDFRM_Pos (29U) +#define DOEPCTL_SODDFRM_Msk (0x1UL << DOEPCTL_SODDFRM_Pos) // 0x20000000 +#define DOEPCTL_SODDFRM DOEPCTL_SODDFRM_Msk // Set odd frame +#define DOEPCTL_EPTYP_Pos (18U) +#define DOEPCTL_EPTYP_Msk (0x3UL << DOEPCTL_EPTYP_Pos) // 0x000C0000 +#define DOEPCTL_EPTYP DOEPCTL_EPTYP_Msk // Endpoint type +#define DOEPCTL_EPTYP_0 (0x1UL << DOEPCTL_EPTYP_Pos) // 0x00040000 +#define DOEPCTL_EPTYP_1 (0x2UL << DOEPCTL_EPTYP_Pos) // 0x00080000 +#define DOEPCTL_SNPM_Pos (20U) +#define DOEPCTL_SNPM_Msk (0x1UL << DOEPCTL_SNPM_Pos) // 0x00100000 +#define DOEPCTL_SNPM DOEPCTL_SNPM_Msk // Snoop mode +#define DOEPCTL_STALL_Pos (21U) +#define DOEPCTL_STALL_Msk (0x1UL << DOEPCTL_STALL_Pos) // 0x00200000 +#define DOEPCTL_STALL DOEPCTL_STALL_Msk // STALL handshake +#define DOEPCTL_CNAK_Pos (26U) +#define DOEPCTL_CNAK_Msk (0x1UL << DOEPCTL_CNAK_Pos) // 0x04000000 +#define DOEPCTL_CNAK DOEPCTL_CNAK_Msk // Clear NAK +#define DOEPCTL_SNAK_Pos (27U) +#define DOEPCTL_SNAK_Msk (0x1UL << DOEPCTL_SNAK_Pos) // 0x08000000 +#define DOEPCTL_SNAK DOEPCTL_SNAK_Msk // Set NAK +#define DOEPCTL_EPDIS_Pos (30U) +#define DOEPCTL_EPDIS_Msk (0x1UL << DOEPCTL_EPDIS_Pos) // 0x40000000 +#define DOEPCTL_EPDIS DOEPCTL_EPDIS_Msk // Endpoint disable +#define DOEPCTL_EPENA_Pos (31U) +#define DOEPCTL_EPENA_Msk (0x1UL << DOEPCTL_EPENA_Pos) // 0x80000000 +#define DOEPCTL_EPENA DOEPCTL_EPENA_Msk // Endpoint enable + +/******************** Bit definition for DOEPINT register ********************/ +#define DOEPINT_XFRC_Pos (0U) +#define DOEPINT_XFRC_Msk (0x1UL << DOEPINT_XFRC_Pos) // 0x00000001 +#define DOEPINT_XFRC DOEPINT_XFRC_Msk // Transfer completed interrupt +#define DOEPINT_EPDISD_Pos (1U) +#define DOEPINT_EPDISD_Msk (0x1UL << DOEPINT_EPDISD_Pos) // 0x00000002 +#define DOEPINT_EPDISD DOEPINT_EPDISD_Msk // Endpoint disabled interrupt +#define DOEPINT_AHBERR_Pos (2U) +#define DOEPINT_AHBERR_Msk (0x1UL << DOEPINT_AHBERR_Pos) // 0x00000004 +#define DOEPINT_AHBERR DOEPINT_AHBERR_Msk // AHB Error (AHBErr) during an OUT transaction + +#define DOEPINT_SETUP_Pos (3U) +#define DOEPINT_SETUP_Msk (0x1UL << DOEPINT_SETUP_Pos) // 0x00000008 +#define DOEPINT_SETUP DOEPINT_SETUP_Msk // SETUP phase done + +#define DOEPINT_OTEPDIS_Pos (4U) +#define DOEPINT_OTEPDIS_Msk (0x1UL << DOEPINT_OTEPDIS_Pos) // 0x00000010 +#define DOEPINT_OTEPDIS DOEPINT_OTEPDIS_Msk // OUT token received when endpoint disabled + +#define DOEPINT_STSPHSRX_Pos (5U) +#define DOEPINT_STSPHSRX_Msk (0x1UL << DOEPINT_STSPHSRX_Pos) // 0x00000020 +#define DOEPINT_STSPHSRX DOEPINT_STSPHSRX_Msk // Status Phase Received For Control Write + +#define DOEPINT_B2BSTUP_Pos (6U) +#define DOEPINT_B2BSTUP_Msk (0x1UL << DOEPINT_B2BSTUP_Pos) // 0x00000040 +#define DOEPINT_B2BSTUP DOEPINT_B2BSTUP_Msk // Back-to-back SETUP packets received +#define DOEPINT_OUTPKTERR_Pos (8U) +#define DOEPINT_OUTPKTERR_Msk (0x1UL << DOEPINT_OUTPKTERR_Pos) // 0x00000100 +#define DOEPINT_OUTPKTERR DOEPINT_OUTPKTERR_Msk // OUT packet error +#define DOEPINT_NAK_Pos (13U) +#define DOEPINT_NAK_Msk (0x1UL << DOEPINT_NAK_Pos) // 0x00002000 +#define DOEPINT_NAK DOEPINT_NAK_Msk // NAK Packet is transmitted by the device +#define DOEPINT_NYET_Pos (14U) +#define DOEPINT_NYET_Msk (0x1UL << DOEPINT_NYET_Pos) // 0x00004000 +#define DOEPINT_NYET DOEPINT_NYET_Msk // NYET interrupt + +#define DOEPINT_STPKTRX_Pos (15U) +#define DOEPINT_STPKTRX_Msk (0x1UL << DOEPINT_STPKTRX_Pos) // 0x00008000 +#define DOEPINT_STPKTRX DOEPINT_STPKTRX_Msk // Setup Packet Received + +/******************** Bit definition for DOEPTSIZ register ********************/ +#define DOEPTSIZ_XFRSIZ_Pos (0U) +#define DOEPTSIZ_XFRSIZ_Msk (0x7FFFFUL << DOEPTSIZ_XFRSIZ_Pos) // 0x0007FFFF +#define DOEPTSIZ_XFRSIZ DOEPTSIZ_XFRSIZ_Msk // Transfer size +#define DOEPTSIZ_PKTCNT_Pos (19U) +#define DOEPTSIZ_PKTCNT_Msk (0x3FFUL << DOEPTSIZ_PKTCNT_Pos) // 0x1FF80000 +#define DOEPTSIZ_PKTCNT DOEPTSIZ_PKTCNT_Msk // Packet count + +#define DOEPTSIZ_STUPCNT_Pos (29U) +#define DOEPTSIZ_STUPCNT_Msk (0x3UL << DOEPTSIZ_STUPCNT_Pos) // 0x60000000 +#define DOEPTSIZ_STUPCNT DOEPTSIZ_STUPCNT_Msk // SETUP packet count +#define DOEPTSIZ_STUPCNT_0 (0x1UL << DOEPTSIZ_STUPCNT_Pos) // 0x20000000 +#define DOEPTSIZ_STUPCNT_1 (0x2UL << DOEPTSIZ_STUPCNT_Pos) // 0x40000000 + +/******************** Bit definition for PCGCTL register ********************/ +#define PCGCCTL_IF_DEV_MODE TU_BIT(31) +#define PCGCCTL_P2HD_PRT_SPD_MASK (0x3ul << 29) +#define PCGCCTL_P2HD_PRT_SPD_SHIFT 29 +#define PCGCCTL_P2HD_DEV_ENUM_SPD_MASK (0x3ul << 27) +#define PCGCCTL_P2HD_DEV_ENUM_SPD_SHIFT 27 +#define PCGCCTL_MAC_DEV_ADDR_MASK (0x7ful << 20) +#define PCGCCTL_MAC_DEV_ADDR_SHIFT 20 +#define PCGCCTL_MAX_TERMSEL TU_BIT(19) +#define PCGCCTL_MAX_XCVRSELECT_MASK (0x3ul << 17) +#define PCGCCTL_MAX_XCVRSELECT_SHIFT 17 +#define PCGCCTL_PORT_POWER TU_BIT(16) +#define PCGCCTL_PRT_CLK_SEL_MASK (0x3ul << 14) +#define PCGCCTL_PRT_CLK_SEL_SHIFT 14 +#define PCGCCTL_ESS_REG_RESTORED TU_BIT(13) +#define PCGCCTL_EXTND_HIBER_SWITCH TU_BIT(12) +#define PCGCCTL_EXTND_HIBER_PWRCLMP TU_BIT(11) +#define PCGCCTL_ENBL_EXTND_HIBER TU_BIT(10) +#define PCGCCTL_RESTOREMODE TU_BIT(9) +#define PCGCCTL_RESETAFTSUSP TU_BIT(8) +#define PCGCCTL_DEEP_SLEEP TU_BIT(7) +#define PCGCCTL_PHY_IN_SLEEP TU_BIT(6) +#define PCGCCTL_ENBL_SLEEP_GATING TU_BIT(5) +#define PCGCCTL_RSTPDWNMODULE TU_BIT(3) +#define PCGCCTL_PWRCLMP TU_BIT(2) +#define PCGCCTL_GATEHCLK TU_BIT(1) +#define PCGCCTL_STOPPCLK TU_BIT(0) + +#define PCGCTL1_TIMER (0x3ul << 1) +#define PCGCTL1_GATEEN TU_BIT(0) + +#ifdef __cplusplus + } +#endif + +#endif diff --git a/libraries/Adafruit_TinyUSB_Arduino/src/portable/synopsys/dwc2/dwc2_xmc.h b/libraries/Adafruit_TinyUSB_Arduino/src/portable/synopsys/dwc2/dwc2_xmc.h new file mode 100644 index 0000000..85b2a26 --- /dev/null +++ b/libraries/Adafruit_TinyUSB_Arduino/src/portable/synopsys/dwc2/dwc2_xmc.h @@ -0,0 +1,101 @@ +/* + * The MIT License (MIT) + * + * Copyright (c) 2021 Rafael Silva (@perigoso) + * Copyright (c) 2021, 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. + * + * This file is part of the TinyUSB stack. + */ + +#ifndef _DWC2_XMC_H_ +#define _DWC2_XMC_H_ + +#ifdef __cplusplus + extern "C" { +#endif + +#include "xmc_device.h" + +#define DWC2_EP_MAX 7 + +static const dwc2_controller_t _dwc2_controller[] = +{ + // Note: XMC has some custom control registers before DWC registers + { .reg_base = USB0_BASE, .irqnum = USB0_0_IRQn, .ep_count = DWC2_EP_MAX, .otg_dfifo_depth = 512 } +}; + +// MCU specific to enable dwc2 clock/power before any access to register +TU_ATTR_ALWAYS_INLINE static inline void dwc2_clock_init(uint8_t rhport, tusb_role_t role) { + (void) rhport; + (void) role; +} + +TU_ATTR_ALWAYS_INLINE +static inline void dwc2_dcd_int_enable(uint8_t rhport) +{ + NVIC_EnableIRQ(_dwc2_controller[rhport].irqnum); +} + +TU_ATTR_ALWAYS_INLINE +static inline void dwc2_dcd_int_disable (uint8_t rhport) +{ + NVIC_DisableIRQ(_dwc2_controller[rhport].irqnum); +} + +static inline void dwc2_remote_wakeup_delay(void) +{ + // try to delay for 1 ms +// uint32_t count = SystemCoreClock / 1000; +// while ( count-- ) __NOP(); +} + +// MCU specific PHY init, called BEFORE core reset +static inline void dwc2_phy_init(dwc2_regs_t * dwc2, uint8_t hs_phy_type) +{ + (void) dwc2; + (void) hs_phy_type; + + // Enable PHY + //USB->ROUTE = USB_ROUTE_PHYPEN; +} + +// MCU specific PHY deinit, disable PHY power +static inline void dwc2_phy_deinit(dwc2_regs_t * dwc2, uint8_t hs_phy_type) { + (void) dwc2; + (void) hs_phy_type; + // nothing to do +} + +// MCU specific PHY update, it is called AFTER init() and core reset +static inline void dwc2_phy_update(dwc2_regs_t * dwc2, uint8_t hs_phy_type) +{ + (void) dwc2; + (void) hs_phy_type; + + // XMC Manual: turn around must be 5 (reset & default value) + // dwc2->gusbcfg = (dwc2->gusbcfg & ~GUSBCFG_TRDT_Msk) | (5u << GUSBCFG_TRDT_Pos); +} + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/libraries/Adafruit_TinyUSB_Arduino/src/portable/synopsys/dwc2/hcd_dwc2.c b/libraries/Adafruit_TinyUSB_Arduino/src/portable/synopsys/dwc2/hcd_dwc2.c new file mode 100644 index 0000000..6098d6e --- /dev/null +++ b/libraries/Adafruit_TinyUSB_Arduino/src/portable/synopsys/dwc2/hcd_dwc2.c @@ -0,0 +1,1536 @@ +/* + * The MIT License (MIT) + * + * Copyright (c) 2024 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. + * + * This file is part of the TinyUSB stack. + */ + +#include "tusb_option.h" + +#if CFG_TUH_ENABLED && defined(TUP_USBIP_DWC2) && !CFG_TUH_MAX3421 + +#if !(CFG_TUH_DWC2_SLAVE_ENABLE || CFG_TUH_DWC2_DMA_ENABLE) +#error DWC2 require either CFG_TUH_DWC2_SLAVE_ENABLE or CFG_TUH_DWC2_DMA_ENABLE to be enabled +#endif + +#include "host/hcd.h" +#include "host/usbh.h" +#include "dwc2_common.h" + + // Debug level for DWC2 + #define DWC2_DEBUG 2 + + // Max number of endpoints application can open, can be larger than DWC2_CHANNEL_COUNT_MAX + #ifndef CFG_TUH_DWC2_ENDPOINT_MAX + #define CFG_TUH_DWC2_ENDPOINT_MAX 16u + #endif + + #define DWC2_CHANNEL_COUNT_MAX 16u // absolute max channel count +TU_VERIFY_STATIC(CFG_TUH_DWC2_ENDPOINT_MAX <= 255, "currently only use 8-bit for index"); + +enum { + HPRT_W1_MASK = HPRT_CONN_DETECT | HPRT_ENABLE | HPRT_ENABLE_CHANGE | HPRT_OVER_CURRENT_CHANGE | HPRT_SUSPEND +}; + +enum { + HCD_XFER_ERROR_MAX = 3 +}; + +enum { + HCD_XFER_PERIOD_SPLIT_NYET_MAX = 3 +}; + +//-------------------------------------------------------------------- +// +//-------------------------------------------------------------------- + +// Host driver struct for each opened endpoint +typedef struct { + union { + uint32_t hcchar; + dwc2_channel_char_t hcchar_bm; + }; + union { + uint32_t hcsplt; + dwc2_channel_split_t hcsplt_bm; + }; + + struct TU_ATTR_PACKED { + uint32_t uframe_interval : 18; // micro-frame interval + uint32_t speed : 2; + uint32_t next_pid : 2; // PID for next transfer + uint32_t next_do_ping : 1; // Do PING for next transfer if possible (highspeed OUT) + uint32_t closing : 1; // endpoint is closing + // uint32_t : 8; + }; + + uint32_t uframe_countdown; // micro-frame count down to transfer for periodic, only need 18-bit + + uint8_t* buffer; + uint16_t buflen; +} hcd_endpoint_t; + +// Additional info for each channel when it is active +typedef struct { + volatile bool allocated; + uint8_t ep_id; + struct TU_ATTR_PACKED { + uint8_t err_count : 3; + uint8_t period_split_nyet_count : 3; + uint8_t halted_nyet : 1; + uint8_t closing : 1; // closing channel + }; + uint8_t result; + + uint16_t xferred_bytes; // bytes that accumulate transferred though USB bus for the whole hcd_edpt_xfer(), which can + // be composed of multiple channel_xfer_start() (retry with NAK/NYET) + uint16_t fifo_bytes; // bytes written/read from/to FIFO (may not be transferred on USB bus). +} hcd_xfer_t; + +typedef struct { + hcd_xfer_t xfer[DWC2_CHANNEL_COUNT_MAX]; + hcd_endpoint_t edpt[CFG_TUH_DWC2_ENDPOINT_MAX]; +} hcd_data_t; + +static hcd_data_t _hcd_data; +static tuh_configure_dwc2_t _tuh_cfg = {.use_hs_phy = TUH_OPT_HIGH_SPEED}; + +//-------------------------------------------------------------------- +// +//-------------------------------------------------------------------- +TU_ATTR_ALWAYS_INLINE static inline uint8_t dwc2_channel_count(const dwc2_regs_t* dwc2) { + const dwc2_ghwcfg2_t ghwcfg2 = {.value = dwc2->ghwcfg2}; + return tu_min8(ghwcfg2.num_host_ch + 1, DWC2_CHANNEL_COUNT_MAX); +} + +TU_ATTR_ALWAYS_INLINE static inline tusb_speed_t hprt_speed_get(dwc2_regs_t* dwc2) { + tusb_speed_t speed; + const dwc2_hprt_t hprt = {.value = dwc2->hprt}; + switch(hprt.speed) { + case HPRT_SPEED_HIGH: speed = TUSB_SPEED_HIGH; break; + case HPRT_SPEED_FULL: speed = TUSB_SPEED_FULL; break; + case HPRT_SPEED_LOW : speed = TUSB_SPEED_LOW ; break; + default: + speed = TUSB_SPEED_INVALID; + TU_BREAKPOINT(); + break; + } + return speed; +} + +TU_ATTR_ALWAYS_INLINE static inline bool dma_host_enabled(const dwc2_regs_t* dwc2) { + (void) dwc2; + // Internal DMA only + const dwc2_ghwcfg2_t ghwcfg2 = {.value = dwc2->ghwcfg2}; + return CFG_TUH_DWC2_DMA_ENABLE && ghwcfg2.arch == GHWCFG2_ARCH_INTERNAL_DMA; +} + +#if CFG_TUH_MEM_DCACHE_ENABLE +bool hcd_dcache_clean(const void* addr, uint32_t data_size) { + TU_VERIFY(addr && data_size); + return dwc2_dcache_clean(addr, data_size); +} + +bool hcd_dcache_invalidate(const void* addr, uint32_t data_size) { + TU_VERIFY(addr && data_size); + return dwc2_dcache_invalidate(addr, data_size); +} + +bool hcd_dcache_clean_invalidate(const void* addr, uint32_t data_size) { + TU_VERIFY(addr && data_size); + return dwc2_dcache_clean_invalidate(addr, data_size); +} +#endif + +// Allocate a channel for new transfer +TU_ATTR_ALWAYS_INLINE static inline uint8_t channel_alloc(dwc2_regs_t* dwc2) { + const uint8_t max_channel = dwc2_channel_count(dwc2); + for (uint8_t ch_id = 0; ch_id < max_channel; ch_id++) { + hcd_xfer_t* xfer = &_hcd_data.xfer[ch_id]; + if (!xfer->allocated) { + tu_memclr(xfer, sizeof(hcd_xfer_t)); + xfer->allocated = true; + return ch_id; + } + } + return TUSB_INDEX_INVALID_8; +} + +// Check if is periodic (interrupt/isochronous) +TU_ATTR_ALWAYS_INLINE static inline bool channel_is_periodic(uint32_t hcchar) { + const dwc2_channel_char_t hcchar_bm = {.value = hcchar}; + return hcchar_bm.ep_type == HCCHAR_EPTYPE_INTERRUPT || hcchar_bm.ep_type == HCCHAR_EPTYPE_ISOCHRONOUS; +} + +TU_ATTR_ALWAYS_INLINE static inline uint8_t req_queue_avail(const dwc2_regs_t* dwc2, bool is_period) { + if (is_period) { + const dwc2_hptxsts_t hptxsts = {.value = dwc2->hptxsts}; + return hptxsts.req_queue_available; + } else { + const dwc2_hnptxsts_t hnptxsts = {.value = dwc2->hnptxsts}; + return hnptxsts.req_queue_available; + } +} + +TU_ATTR_ALWAYS_INLINE static inline void channel_dealloc(dwc2_regs_t* dwc2, uint8_t ch_id) { + hcd_xfer_t* xfer = &_hcd_data.xfer[ch_id]; + xfer->allocated = false; + dwc2->haintmsk &= ~TU_BIT(ch_id); +} + +TU_ATTR_ALWAYS_INLINE static inline bool channel_disable(const dwc2_regs_t* dwc2, dwc2_channel_t* channel) { + const bool is_period = channel_is_periodic(channel->hcchar); + if (dma_host_enabled(dwc2)) { + // In buffer DMA or external DMA mode: + // - Channel disable must not be programmed for non-split periodic channels. At the end of the next uframe/frame (in + // the worst case), the controller generates a channel halted and disables the channel automatically. + // - For split enabled channels (both non-periodic and periodic), channel disable must not be programmed randomly. + // However, channel disable can be programmed for specific scenarios such as NAK and FrmOvrn. + if (is_period && (channel->hcsplt & HCSPLT_SPLITEN)) { + return true; + } + } else { + while (0 == req_queue_avail(dwc2, is_period)) { + // blocking wait for request queue available + } + } + channel->hcintmsk |= HCINT_HALTED; + channel->hcchar |= HCCHAR_CHDIS | HCCHAR_CHENA; // must set both CHDIS and CHENA + return true; +} + +// attempt to send IN token to receive data +TU_ATTR_ALWAYS_INLINE static inline bool channel_send_in_token(const dwc2_regs_t* dwc2, dwc2_channel_t* channel) { + while (0 == req_queue_avail(dwc2, channel_is_periodic(channel->hcchar))) { + // blocking wait for request queue available + } + channel->hcchar |= HCCHAR_CHENA; + return true; +} + +// Find currently enabled channel. Note: EP0 is bidirectional +TU_ATTR_ALWAYS_INLINE static inline uint8_t channel_find_enabled(dwc2_regs_t* dwc2, uint8_t dev_addr, uint8_t ep_num, uint8_t ep_dir) { + const uint8_t max_channel = dwc2_channel_count(dwc2); + for (uint8_t ch_id = 0; ch_id < max_channel; ch_id++) { + if (_hcd_data.xfer[ch_id].allocated) { + const dwc2_channel_char_t hcchar = {.value = dwc2->channel[ch_id].hcchar}; + if (hcchar.dev_addr == dev_addr && hcchar.ep_num == ep_num && (ep_num == 0 || hcchar.ep_dir == ep_dir)) { + return ch_id; + } + } + } + return TUSB_INDEX_INVALID_8; +} + + +// Allocate a new endpoint +TU_ATTR_ALWAYS_INLINE static inline uint8_t edpt_alloc(void) { + for (uint32_t i = 0; i < CFG_TUH_DWC2_ENDPOINT_MAX; i++) { + hcd_endpoint_t* edpt = &_hcd_data.edpt[i]; + if (edpt->hcchar_bm.enable == 0) { + tu_memclr(edpt, sizeof(hcd_endpoint_t)); + edpt->hcchar_bm.enable = 1; + return i; + } + } + return TUSB_INDEX_INVALID_8; +} + +TU_ATTR_ALWAYS_INLINE static inline void edpt_dealloc(hcd_endpoint_t *edpt) { + edpt->hcchar_bm.enable = 0; +} + +// close an opened endpoint +static void edpt_close(dwc2_regs_t *dwc2, uint8_t ep_id) { + hcd_endpoint_t *edpt = &_hcd_data.edpt[ep_id]; + edpt->closing = 1; // mark endpoint as closing + + // disable active channel belong to this endpoint + for (uint8_t ch_id = 0; ch_id < DWC2_CHANNEL_COUNT_MAX; ch_id++) { + hcd_xfer_t *xfer = &_hcd_data.xfer[ch_id]; + if (xfer->allocated && xfer->ep_id == ep_id) { + dwc2_channel_t *channel = &dwc2->channel[ch_id]; + xfer->closing = 1; + channel_disable(dwc2, channel); + return; // only 1 active channel per endpoint + } + } + + edpt_dealloc(edpt); // no active channel, safe to de-alloc now +} + +// Find an endpoint that is opened previously with hcd_edpt_open() +// Note: EP0 is bidirectional +TU_ATTR_ALWAYS_INLINE static inline uint8_t edpt_find_opened(uint8_t dev_addr, uint8_t ep_num, uint8_t ep_dir) { + for (uint8_t i = 0; i < (uint8_t)CFG_TUH_DWC2_ENDPOINT_MAX; i++) { + const hcd_endpoint_t *edpt = &_hcd_data.edpt[i]; + const dwc2_channel_char_t hcchar_bm = edpt->hcchar_bm; + if (hcchar_bm.enable && hcchar_bm.dev_addr == dev_addr && hcchar_bm.ep_num == ep_num && + (ep_num == 0 || hcchar_bm.ep_dir == ep_dir)) { + return i; + } + } + return TUSB_INDEX_INVALID_8; +} + +TU_ATTR_ALWAYS_INLINE static inline uint16_t cal_packet_count(uint16_t len, uint16_t ep_size) { + if (len == 0) { + return 1; + } else { + return tu_div_ceil(len, ep_size); + } +} + +TU_ATTR_ALWAYS_INLINE static inline uint8_t cal_next_pid(uint8_t pid, uint8_t packet_count) { + if (packet_count & 0x01u) { + return pid ^ 0x02u; // toggle DATA0 and DATA1 + } else { + return pid; + } +} + +//-------------------------------------------------------------------- +// +//-------------------------------------------------------------------- + +/* USB Data FIFO Layout + + The FIFO is split up into + - EPInfo: for storing DMA metadata (check dcd_dwc2.c for more details) + - 1 RX FIFO: for receiving data + - 1 TX FIFO for non-periodic (NPTX) + - 1 TX FIFO for periodic (PTX) + + We allocated TX FIFO from top to bottom (using top pointer), this to allow the RX FIFO to grow dynamically which is + possible since the free space is located between the RX and TX FIFOs. + + ----------------- otg_dfifo_depth + | HCDMAn | (DMA only, sized per runtime DMA mode) + |--------------|-- gdfifocfg.EPINFOBASE (= gdfifocfg.GDFIFOCfg) + | Non-Periodic | + | TX FIFO | + |--------------|--- GNPTXFSIZ.addr (fixed size) + | Periodic | + | TX FIFO | + |--------------|--- HPTXFSIZ.addr (expandable downward) + | FREE | + | | + |--------------|-- GRXFSIZ (expandable upward) + | RX FIFO | + ---------------- 0 +*/ + +/* Programming Guide 2.1.2 FIFO RAM allocation + * RX + * - Largest-EPsize/4 + 2 (status info). recommended x2 if high bandwidth or multiple ISO are used. + * - 2 for transfer complete and channel halted status + * - 1 for each Control/Bulk out endpoint to Handle NAK/NYET (i.e max is number of host channel) + * + * TX non-periodic (NPTX) + * - At least largest-EPsize/4, recommended x2 + * + * TX periodic (PTX) + * - At least largest-EPsize*MulCount/4 (MulCount up to 3 for high-bandwidth ISO/interrupt) +*/ +static void dfifo_host_init(uint8_t rhport, bool is_hs_phy) { + const dwc2_controller_t* dwc2_controller = &_dwc2_controller[rhport]; + dwc2_regs_t* dwc2 = DWC2_REG(rhport); + const dwc2_ghwcfg2_t ghwcfg2 = {.value = dwc2->ghwcfg2}; + + // Scatter/Gather DMA mode is not yet supported. Buffer DMA only need 1 words per channel + const bool is_dma = dma_host_enabled(dwc2); + uint16_t dfifo_top = dwc2_controller->otg_dfifo_depth; + if (is_dma) { + dfifo_top -= ghwcfg2.num_host_ch; + } + + // fixed allocation for now, improve later: + // - ptx_largest is limited to 64 words for FS since most FS core only has 256-320 words total + uint32_t nptx_largest; + uint32_t ptx_largest; + if (is_hs_phy) { + nptx_largest = TUSB_EPSIZE_BULK_HS / 4; + ptx_largest = TUSB_EPSIZE_ISO_HS_MAX / 4; + } else { + nptx_largest = TUSB_EPSIZE_BULK_FS / 4; + ptx_largest = 256 / 4; + } + + uint16_t nptxfsiz = 2 * nptx_largest; + uint16_t rxfsiz = 2 * (ptx_largest + 2) + ghwcfg2.num_host_ch; + TU_ASSERT(dfifo_top >= (nptxfsiz + rxfsiz),); + uint16_t ptxfsiz = dfifo_top - (nptxfsiz + rxfsiz); + + dwc2->gdfifocfg = (dfifo_top << GDFIFOCFG_EPINFOBASE_SHIFT) | dfifo_top; + + dfifo_top -= rxfsiz; + dwc2->grxfsiz = rxfsiz; + + dfifo_top -= nptxfsiz; + dwc2->gnptxfsiz = tu_u32_from_u16(nptxfsiz, dfifo_top); + + dfifo_top -= ptxfsiz; + dwc2->hptxfsiz = tu_u32_from_u16(ptxfsiz, dfifo_top); +} + +//--------------------------------------------------------------------+ +// Controller API +//--------------------------------------------------------------------+ + +// optional hcd configuration, called by tuh_configure() +bool hcd_configure(uint8_t rhport, uint32_t cfg_id, const void* cfg_param) { + (void) rhport; + TU_VERIFY(cfg_id == TUH_CFGID_DWC2 && cfg_param != NULL); + tuh_configure_param_t const* cfg = (tuh_configure_param_t const*) cfg_param; + _tuh_cfg = cfg->dwc2; + return true; +} + +// Initialize controller to host mode +bool hcd_init(uint8_t rhport, const tusb_rhport_init_t* rh_init) { + dwc2_clock_init(rhport, rh_init->role); + + tu_memclr(&_hcd_data, sizeof(_hcd_data)); + + // Core Initialization + dwc2_regs_t* dwc2 = DWC2_REG(rhport); + const bool is_hs_phy = dwc2_core_is_highspeed_phy(dwc2, _tuh_cfg.use_hs_phy); + const bool is_dma = dma_host_enabled(dwc2); + TU_ASSERT(dwc2_core_init(rhport, is_hs_phy, is_dma)); + + //------------- 3.1 Host Initialization -------------// + // Enable HFIR reload + if (dwc2->gsnpsid >= DWC2_CORE_REV_2_92a) { + dwc2->hfir |= HFIR_RELOAD_CTRL; + } + + // force host mode and wait for mode switch + dwc2->gusbcfg = (dwc2->gusbcfg & ~GUSBCFG_FDMOD) | GUSBCFG_FHMOD; + while ((dwc2->gintsts & GINTSTS_CMOD) != GINTSTS_CMODE_HOST) {} + + #ifdef TUP_USBIP_DWC2_STM32 + dwc2_stm32_gccfg_cfg(dwc2, false, true); + #endif + + if (is_hs_phy && (rh_init->speed == TUSB_SPEED_HIGH || rh_init->speed == TUSB_SPEED_AUTO)) { + dwc2->hcfg &= ~HCFG_FSLS_ONLY; // max speed + } else { + dwc2->hcfg |= HCFG_FSLS_ONLY; // disable high speed mode + } + + // configure a fixed-allocated fifo scheme + dfifo_host_init(rhport, is_hs_phy); + + dwc2->hprt = HPRT_W1_MASK; // clear all write-1-clear bits + dwc2->hprt = HPRT_POWER; // turn on VBUS + + // Enable required interrupts + dwc2->gintmsk |= GINTSTS_OTGINT | GINTSTS_HPRTINT | GINTSTS_HCINT | GINTSTS_DISCINT; + + // NPTX can hold at least 2 packet, change interrupt level to half-empty + uint32_t gahbcfg = dwc2->gahbcfg & ~GAHBCFG_TX_FIFO_EPMTY_LVL; + gahbcfg |= GAHBCFG_GINT; // Enable global interrupt + dwc2->gahbcfg = gahbcfg; + + return true; +} + +bool hcd_deinit(uint8_t rhport) { + dwc2_regs_t* dwc2 = DWC2_REG(rhport); + + // Turn off VBUS + dwc2->hprt = HPRT_W1_MASK; // clear w1c bits without side effects + // HPRT_POWER is not set -> VBUS off + + dwc2_core_deinit(rhport); + return true; +} + +// Enable USB interrupt +void hcd_int_enable (uint8_t rhport) { + dwc2_int_set(rhport, TUSB_ROLE_HOST, true); +} + +// Disable USB interrupt +void hcd_int_disable(uint8_t rhport) { + dwc2_int_set(rhport, TUSB_ROLE_HOST, false); +} + +// Get frame number (1ms) +uint32_t hcd_frame_number(uint8_t rhport) { + dwc2_regs_t* dwc2 = DWC2_REG(rhport); + return dwc2->hfnum & HFNUM_FRNUM_Msk; +} + +//--------------------------------------------------------------------+ +// Port API +//--------------------------------------------------------------------+ + +// Get the current connect status of roothub port +bool hcd_port_connect_status(uint8_t rhport) { + dwc2_regs_t* dwc2 = DWC2_REG(rhport); + return dwc2->hprt & HPRT_CONN_STATUS; +} + +// Reset USB bus on the port. Return immediately, bus reset sequence may not be complete. +// Some port would require hcd_port_reset_end() to be invoked after 10ms to complete the reset sequence. +void hcd_port_reset(uint8_t rhport) { + dwc2_regs_t* dwc2 = DWC2_REG(rhport); + uint32_t hprt = dwc2->hprt & ~HPRT_W1_MASK; + hprt |= HPRT_RESET; + dwc2->hprt = hprt; +} + +// Complete bus reset sequence, may be required by some controllers +void hcd_port_reset_end(uint8_t rhport) { + dwc2_regs_t* dwc2 = DWC2_REG(rhport); + uint32_t hprt = dwc2->hprt & ~HPRT_W1_MASK; // skip w1c bits + hprt &= ~HPRT_RESET; + dwc2->hprt = hprt; +} + +// Get port link speed +tusb_speed_t hcd_port_speed_get(uint8_t rhport) { + dwc2_regs_t* dwc2 = DWC2_REG(rhport); + const tusb_speed_t speed = hprt_speed_get(dwc2); + return speed; +} + +// HCD closes all opened endpoints belong to this device +void hcd_device_close(uint8_t rhport, uint8_t dev_addr) { + dwc2_regs_t* dwc2 = DWC2_REG(rhport); + for (uint8_t ep_id = 0; ep_id < CFG_TUH_DWC2_ENDPOINT_MAX; ep_id++) { + const hcd_endpoint_t *edpt = &_hcd_data.edpt[ep_id]; + if (edpt->hcchar_bm.enable && edpt->hcchar_bm.dev_addr == dev_addr) { + edpt_close(dwc2, ep_id); + } + } +} + +//--------------------------------------------------------------------+ +// Endpoints API +//--------------------------------------------------------------------+ + +// Open an endpoint +bool hcd_edpt_open(uint8_t rhport, uint8_t dev_addr, const tusb_desc_endpoint_t* desc_ep) { + dwc2_regs_t* dwc2 = DWC2_REG(rhport); + const tusb_speed_t rh_speed = hprt_speed_get(dwc2); + + tuh_bus_info_t bus_info; + tuh_bus_info_get(dev_addr, &bus_info); + + // find a free endpoint + const uint8_t ep_id = edpt_alloc(); + TU_ASSERT(ep_id < CFG_TUH_DWC2_ENDPOINT_MAX); + hcd_endpoint_t* edpt = &_hcd_data.edpt[ep_id]; + + dwc2_channel_char_t* hcchar_bm = &edpt->hcchar_bm; + hcchar_bm->ep_size = tu_edpt_packet_size(desc_ep); + hcchar_bm->ep_num = tu_edpt_number(desc_ep->bEndpointAddress); + hcchar_bm->ep_dir = tu_edpt_dir(desc_ep->bEndpointAddress); + hcchar_bm->low_speed_dev = (bus_info.speed == TUSB_SPEED_LOW) ? 1 : 0; + hcchar_bm->ep_type = desc_ep->bmAttributes.xfer; // ep_type matches TUSB_XFER_* + hcchar_bm->err_multi_count = 0; + hcchar_bm->dev_addr = dev_addr; + hcchar_bm->odd_frame = 0; + hcchar_bm->disable = 0; + hcchar_bm->enable = 1; + + dwc2_channel_split_t* hcsplt_bm = &edpt->hcsplt_bm; + hcsplt_bm->hub_port = bus_info.hub_port; + hcsplt_bm->hub_addr = bus_info.hub_addr; + hcsplt_bm->xact_pos = 0; + hcsplt_bm->split_compl = 0; + hcsplt_bm->split_en = (rh_speed == TUSB_SPEED_HIGH && bus_info.speed != TUSB_SPEED_HIGH) ? 1 : 0; + + edpt->speed = bus_info.speed; + edpt->next_pid = HCTSIZ_PID_DATA0; + switch (desc_ep->bmAttributes.xfer) { + case TUSB_XFER_ISOCHRONOUS: + edpt->uframe_interval = 1 << (desc_ep->bInterval - 1); + if (bus_info.speed == TUSB_SPEED_FULL) { + edpt->uframe_interval <<= 3; + } + break; + + case TUSB_XFER_INTERRUPT: + if (bus_info.speed == TUSB_SPEED_HIGH) { + edpt->uframe_interval = 1 << (desc_ep->bInterval - 1); + } else { + edpt->uframe_interval = desc_ep->bInterval << 3; + } + break; + + default: + break; + } + + return true; +} + +bool hcd_edpt_close(uint8_t rhport, uint8_t daddr, uint8_t ep_addr) { + dwc2_regs_t *dwc2 = DWC2_REG(rhport); + const uint8_t ep_num = tu_edpt_number(ep_addr); + const uint8_t ep_dir = tu_edpt_dir(ep_addr); + const uint8_t ep_id = edpt_find_opened(daddr, ep_num, ep_dir); + TU_ASSERT(ep_id < CFG_TUH_DWC2_ENDPOINT_MAX); + + edpt_close(dwc2, ep_id); + + return true; +} + +// clean up channel after part of transfer is done but the whole urb is not complete +static void channel_xfer_out_wrapup(dwc2_regs_t* dwc2, uint8_t ch_id) { + hcd_xfer_t* xfer = &_hcd_data.xfer[ch_id]; + const dwc2_channel_t* channel = &dwc2->channel[ch_id]; + hcd_endpoint_t* edpt = &_hcd_data.edpt[xfer->ep_id]; + + const dwc2_channel_tsize_t hctsiz = {.value = channel->hctsiz}; + edpt->next_pid = hctsiz.pid; // save PID + + /* Since hctsiz.xfersize field reflects the number of bytes transferred via the AHB, not the USB) + * For IN: we can use hctsiz.xfersize as remaining bytes. + * For OUT: Must use the hctsiz.pktcnt field to determine how much data has been transferred. This field reflects the + * number of packets that have been transferred via the USB. This is always an integral number of packets if the + * transfer was halted before its normal completion. + */ + const uint16_t remain_packets = hctsiz.packet_count; + const dwc2_channel_char_t hcchar = {.value = channel->hcchar}; + const uint16_t total_packets = cal_packet_count(edpt->buflen, hcchar.ep_size); + const uint16_t actual_bytes = (total_packets - remain_packets) * hcchar.ep_size; + + xfer->fifo_bytes = 0; + xfer->xferred_bytes += actual_bytes; + edpt->buffer += actual_bytes; + edpt->buflen -= actual_bytes; +} + +static bool channel_xfer_start(dwc2_regs_t* dwc2, uint8_t ch_id) { + hcd_xfer_t* xfer = &_hcd_data.xfer[ch_id]; + hcd_endpoint_t* edpt = &_hcd_data.edpt[xfer->ep_id]; + dwc2_channel_char_t* hcchar_bm = &edpt->hcchar_bm; + dwc2_channel_t* channel = &dwc2->channel[ch_id]; + bool const is_period = channel_is_periodic(edpt->hcchar); + + // clear previous state + xfer->fifo_bytes = 0; + + // hchar: restore but don't enable yet + if (is_period) { + hcchar_bm->odd_frame = 1 - (dwc2->hfnum & 1); // transfer on next frame + } + channel->hcchar = (edpt->hcchar & ~HCCHAR_CHENA); + + // hctsiz: zero length packet still count as 1 + const uint16_t packet_count = cal_packet_count(edpt->buflen, hcchar_bm->ep_size); + dwc2_channel_tsize_t hctsiz = {.value = 0}; + hctsiz.pid = edpt->next_pid; // next PID is set in transfer complete interrupt + hctsiz.packet_count = packet_count; + hctsiz.xfer_size = edpt->buflen; + if (edpt->next_do_ping && edpt->speed == TUSB_SPEED_HIGH && + edpt->next_pid != HCTSIZ_PID_SETUP && hcchar_bm->ep_dir == TUSB_DIR_OUT) { + hctsiz.do_ping = 1; + } + channel->hctsiz = hctsiz.value; + edpt->next_do_ping = 0; + + // pre-calculate next PID based on packet count, adjusted in transfer complete interrupt if short packet + if (hcchar_bm->ep_num == 0) { + edpt->next_pid = HCTSIZ_PID_DATA1; // control data and status stage always start with DATA1 + } else { + edpt->next_pid = cal_next_pid(edpt->next_pid, packet_count); + } + + channel->hcsplt = edpt->hcsplt; + channel->hcint = 0xFFFFFFFFU; // clear all channel interrupts + + if (dma_host_enabled(dwc2)) { + channel->hcintmsk = HCINT_HALTED; + dwc2->haintmsk |= TU_BIT(ch_id); + + channel->hcdma = (uint32_t) edpt->buffer; + + if (hcchar_bm->ep_dir == TUSB_DIR_IN) { + channel_send_in_token(dwc2, channel); + } else { + hcd_dcache_clean(edpt->buffer, edpt->buflen); + channel->hcchar |= HCCHAR_CHENA; + } + } else { + uint32_t hcintmsk = HCINT_NAK | HCINT_XACT_ERR | HCINT_STALL | HCINT_XFER_COMPLETE | HCINT_DATATOGGLE_ERR; + if (hcchar_bm->ep_dir == TUSB_DIR_IN) { + hcintmsk |= HCINT_BABBLE_ERR | HCINT_DATATOGGLE_ERR | HCINT_ACK; + } else { + hcintmsk |= HCINT_NYET; + if (edpt->hcsplt_bm.split_en || hctsiz.do_ping) { + hcintmsk |= HCINT_ACK; + } + } + channel->hcintmsk = hcintmsk; + dwc2->haintmsk |= TU_BIT(ch_id); + + // enable channel for slave mode: + // - OUT: it will enable corresponding FIFO channel + // - IN : it will write an IN request to the Non-periodic Request Queue, this will have dwc2 trying to send + // IN Token. If we got NAK, we have to re-enable the channel again in the interrupt. Due to the way usbh stack only + // call hcd_edpt_xfer() once, we will need to manage de-allocate/re-allocate IN channel dynamically. + if (hcchar_bm->ep_dir == TUSB_DIR_IN) { + channel_send_in_token(dwc2, channel); + } else { + channel->hcchar |= HCCHAR_CHENA; + if (edpt->buflen > 0) { + // To prevent conflict with other channel, we will enable periodic/non-periodic FIFO empty interrupt accordingly + // And write packet in the interrupt handler + dwc2->gintmsk |= (is_period ? GINTSTS_PTX_FIFO_EMPTY : GINTSTS_NPTX_FIFO_EMPTY); + } + } + } + + return true; +} + +// kick-off transfer with an endpoint +static bool edpt_xfer_kickoff(dwc2_regs_t* dwc2, uint8_t ep_id) { + uint8_t ch_id = channel_alloc(dwc2); + TU_ASSERT(ch_id < 16); // all channel are in used + hcd_xfer_t* xfer = &_hcd_data.xfer[ch_id]; + xfer->ep_id = ep_id; + xfer->result = XFER_RESULT_INVALID; + + return channel_xfer_start(dwc2, ch_id); +} + +bool hcd_edpt_xfer(uint8_t rhport, uint8_t dev_addr, uint8_t ep_addr, uint8_t * buffer, uint16_t buflen) { + dwc2_regs_t* dwc2 = DWC2_REG(rhport); + const uint8_t ep_num = tu_edpt_number(ep_addr); + const uint8_t ep_dir = tu_edpt_dir(ep_addr); + + uint8_t ep_id = edpt_find_opened(dev_addr, ep_num, ep_dir); + TU_ASSERT(ep_id < CFG_TUH_DWC2_ENDPOINT_MAX); + hcd_endpoint_t *edpt = &_hcd_data.edpt[ep_id]; + TU_VERIFY(edpt->closing == 0); // skip if endpoint is closing + + edpt->buffer = buffer; + edpt->buflen = buflen; + + if (ep_num == 0) { + // update ep_dir since control endpoint can switch direction + edpt->hcchar_bm.ep_dir = ep_dir; + } + + return edpt_xfer_kickoff(dwc2, ep_id); +} + +// Abort a queued transfer. Note: it can only abort transfer that has not been started +// Return true if a queued transfer is aborted, false if there is no transfer to abort +bool hcd_edpt_abort_xfer(uint8_t rhport, uint8_t dev_addr, uint8_t ep_addr) { + dwc2_regs_t* dwc2 = DWC2_REG(rhport); + const uint8_t ep_num = tu_edpt_number(ep_addr); + const uint8_t ep_dir = tu_edpt_dir(ep_addr); + const uint8_t ep_id = edpt_find_opened(dev_addr, ep_num, ep_dir); + TU_VERIFY(ep_id < CFG_TUH_DWC2_ENDPOINT_MAX); + + // hcd_int_disable(rhport); + + // Find enabled channeled and disable it, channel will be de-allocated in the interrupt handler + const uint8_t ch_id = channel_find_enabled(dwc2, dev_addr, ep_num, ep_dir); + if (ch_id < 16) { + dwc2_channel_t* channel = &dwc2->channel[ch_id]; + channel_disable(dwc2, channel); + } + + // hcd_int_enable(rhport); + + return true; +} + +// Submit a special transfer to send 8-byte Setup Packet, when complete hcd_event_xfer_complete() must be invoked +bool hcd_setup_send(uint8_t rhport, uint8_t dev_addr, const uint8_t setup_packet[8]) { + uint8_t ep_id = edpt_find_opened(dev_addr, 0, TUSB_DIR_OUT); + TU_ASSERT(ep_id < CFG_TUH_DWC2_ENDPOINT_MAX); // no opened endpoint + hcd_endpoint_t* edpt = &_hcd_data.edpt[ep_id]; + edpt->next_pid = HCTSIZ_PID_SETUP; + + return hcd_edpt_xfer(rhport, dev_addr, 0, (uint8_t*)(uintptr_t) setup_packet, 8); +} + +// clear stall, data toggle is also reset to DATA0 +bool hcd_edpt_clear_stall(uint8_t rhport, uint8_t dev_addr, uint8_t ep_addr) { + (void) rhport; + const uint8_t ep_num = tu_edpt_number(ep_addr); + const uint8_t ep_dir = tu_edpt_dir(ep_addr); + const uint8_t ep_id = edpt_find_opened(dev_addr, ep_num, ep_dir); + TU_VERIFY(ep_id < CFG_TUH_DWC2_ENDPOINT_MAX); + hcd_endpoint_t* edpt = &_hcd_data.edpt[ep_id]; + + edpt->next_pid = HCTSIZ_PID_DATA0; + + return true; +} + +//-------------------------------------------------------------------- +// HCD Event Handler +//-------------------------------------------------------------------- + +// retry an IN transfer, channel must be halted +static void channel_xfer_in_retry(dwc2_regs_t* dwc2, uint8_t ch_id, uint32_t hcint) { + hcd_xfer_t* xfer = &_hcd_data.xfer[ch_id]; + hcd_endpoint_t* edpt = &_hcd_data.edpt[xfer->ep_id]; + dwc2_channel_t* channel = &dwc2->channel[ch_id]; + dwc2_channel_char_t hcchar = {.value = channel->hcchar}; + + if (channel_is_periodic(hcchar.value)){ + const dwc2_channel_split_t hcsplt = {.value = channel->hcsplt}; + // retry immediately for periodic split NYET if we haven't reach max retry + if (hcsplt.split_en && hcsplt.split_compl && (hcint & HCINT_NYET || xfer->halted_nyet)) { + xfer->period_split_nyet_count++; + xfer->halted_nyet = 0; + if (xfer->period_split_nyet_count < HCD_XFER_PERIOD_SPLIT_NYET_MAX) { + hcchar.odd_frame = 1 - (dwc2->hfnum & 1); // transfer on next frame + channel->hcchar = hcchar.value; + channel_send_in_token(dwc2, channel); + return; + } else { + // too many NYET, de-allocate channel with below code + xfer->period_split_nyet_count = 0; + } + } + + const uint32_t ucount = (hprt_speed_get(dwc2) == TUSB_SPEED_HIGH ? 1 : 8); + if (edpt->uframe_interval == ucount) { + // retry on next frame if bInterval is 1 + hcchar.odd_frame = 1 - (dwc2->hfnum & 1); + channel->hcchar = hcchar.value; + channel_send_in_token(dwc2, channel); + } else { + // otherwise, de-allocate channel, enable SOF set frame counter for later transfer + const dwc2_channel_tsize_t hctsiz = {.value = channel->hctsiz}; + edpt->next_pid = hctsiz.pid; // save PID + edpt->uframe_countdown = edpt->uframe_interval - ucount; + // enable SOF interrupt if not already enabled + if (0 == (dwc2->gintmsk & GINTMSK_SOFM)) { + dwc2->gintsts = GINTSTS_SOF; + dwc2->gintmsk |= GINTMSK_SOFM; + } + // already halted, de-allocate channel (called from DMA isr) + channel_dealloc(dwc2, ch_id); + } + } else { + // for control/bulk: retry immediately + channel_send_in_token(dwc2, channel); + } +} + +#if CFG_TUSB_DEBUG && 0 +TU_ATTR_ALWAYS_INLINE static inline void print_hcint(uint32_t hcint) { + const char* str[] = { + "XFRC", "HALTED", "AHBERR", "STALL", + "NAK", "ACK", "NYET", "XERR", + "BBLERR", "FRMOR", "DTERR", "BNA", + "XCSERR", "DESC_LST" + }; + + for(uint32_t i=0; i<14; i++) { + if (hcint & TU_BIT(i)) { + TU_LOG1("%s ", str[i]); + } + } + TU_LOG1("\r\n"); +} +#endif + +#if CFG_TUH_DWC2_SLAVE_ENABLE +static void handle_rxflvl_irq(uint8_t rhport) { + dwc2_regs_t* dwc2 = DWC2_REG(rhport); + + // Pop control word off FIFO + const dwc2_grxstsp_t grxstsp = {.value= dwc2->grxstsp}; + const uint8_t ch_id = grxstsp.ep_ch_num; + + switch (grxstsp.packet_status) { + case GRXSTS_PKTSTS_RX_DATA: { + // In packet received, pop this entry --> ACK interrupt + const uint16_t byte_count = grxstsp.byte_count; + hcd_xfer_t* xfer = &_hcd_data.xfer[ch_id]; + TU_ASSERT(xfer->ep_id < CFG_TUH_DWC2_ENDPOINT_MAX,); + hcd_endpoint_t* edpt = &_hcd_data.edpt[xfer->ep_id]; + + if (byte_count > 0) { + tu_hwfifo_read(dwc2->fifo[0], edpt->buffer + xfer->xferred_bytes, byte_count, NULL); + xfer->xferred_bytes += byte_count; + xfer->fifo_bytes = byte_count; + } + break; + } + + case GRXSTS_PKTSTS_RX_COMPLETE: + // In transfer complete: After this entry is popped from the rx FIFO, dwc2 asserts a Transfer Completed + // interrupt --> handle_channel_irq() + break; + + case GRXSTS_PKTSTS_HOST_DATATOGGLE_ERR: + // handle in channel interrupt + break; + + case GRXSTS_PKTSTS_HOST_CHANNEL_HALTED: + // triggered when channel.hcchar_bm.disable is set + // TODO handle later + break; + + default: break; // ignore other status + } +} + +// return true if there is still pending data and need more ISR +static bool handle_txfifo_empty(dwc2_regs_t* dwc2, bool is_periodic) { + // Use period txsts for both p/np to get request queue space available (1-bit difference, it is small enough) + const dwc2_hptxsts_t txsts = {.value = (is_periodic ? dwc2->hptxsts : dwc2->hnptxsts)}; + + const uint8_t max_channel = dwc2_channel_count(dwc2); + for (uint8_t ch_id = 0; ch_id < max_channel; ch_id++) { + dwc2_channel_t* channel = &dwc2->channel[ch_id]; + const dwc2_channel_char_t hcchar = {.value = channel->hcchar}; + // skip writing to FIFO if channel is expecting halted. + if (0 == (channel->hcintmsk & HCINT_HALTED) && (hcchar.ep_dir == TUSB_DIR_OUT)) { + hcd_xfer_t *xfer = &_hcd_data.xfer[ch_id]; + TU_ASSERT(xfer->ep_id < CFG_TUH_DWC2_ENDPOINT_MAX); + hcd_endpoint_t* edpt = &_hcd_data.edpt[xfer->ep_id]; + const dwc2_channel_tsize_t hctsiz = {.value = channel->hctsiz}; + const uint16_t remain_packets = hctsiz.packet_count; + for (uint16_t i = 0; i < remain_packets; i++) { + const uint16_t remain_bytes = edpt->buflen - xfer->fifo_bytes; + const uint16_t xact_bytes = tu_min16(remain_bytes, hcchar.ep_size); + + // skip if there is not enough space in FIFO and RequestQueue. + // Packet's last word written to FIFO will trigger a request queue + if ((xact_bytes > (txsts.fifo_available << 2)) || (txsts.req_queue_available == 0)) { + return true; + } + + tu_hwfifo_write(dwc2->fifo[ch_id], edpt->buffer + xfer->fifo_bytes, xact_bytes, NULL); + xfer->fifo_bytes += xact_bytes; + } + } + } + + return false; // no channel has pending data +} + +static bool handle_channel_in_slave(dwc2_regs_t* dwc2, uint8_t ch_id, uint32_t hcint) { + hcd_xfer_t* xfer = &_hcd_data.xfer[ch_id]; + dwc2_channel_t* channel = &dwc2->channel[ch_id]; + hcd_endpoint_t* edpt = &_hcd_data.edpt[xfer->ep_id]; + dwc2_channel_split_t hcsplt = {.value = channel->hcsplt}; + const dwc2_channel_tsize_t hctsiz = {.value = channel->hctsiz}; + bool is_done = false; + + // if (hcsplt.split_en) { + // if (edpt->hcchar_bm.ep_num == 1) { + // TU_LOG1("Frame %u, ch %u: ep %u, hcint 0x%04lX ", dwc2->hfnum_bm.num, ch_id, hcsplt.ep_num, hcint); + // print_hcint(hcint); + // } + + if (hcint & HCINT_XFER_COMPLETE) { + if (edpt->hcchar_bm.ep_num != 0) { + edpt->next_pid = hctsiz.pid; // save pid (already toggled) + } + + const uint16_t remain_packets = hctsiz.packet_count; + if (hcsplt.split_en && remain_packets && xfer->fifo_bytes == edpt->hcchar_bm.ep_size) { + // Split can only complete 1 transaction (up to 1 packet) at a time, schedule more + hcsplt.split_compl = 0; + channel->hcsplt = hcsplt.value; + } else { + xfer->result = XFER_RESULT_SUCCESS; + } + + channel_disable(dwc2, channel); + } else if (hcint & (HCINT_XACT_ERR | HCINT_BABBLE_ERR | HCINT_STALL)) { + if (hcint & HCINT_STALL) { + xfer->result = XFER_RESULT_STALLED; + } else if (hcint & HCINT_BABBLE_ERR) { + xfer->result = XFER_RESULT_FAILED; + } else if (hcint & HCINT_XACT_ERR) { + xfer->err_count++; + channel->hcintmsk |= HCINT_ACK; + } else { + // nothing to do + } + + channel_disable(dwc2, channel); + } else if (hcint & HCINT_NYET) { + // restart complete split + hcsplt.split_compl = 1; + channel->hcsplt = hcsplt.value; + xfer->halted_nyet = 1; + channel_disable(dwc2, channel); + } else if (hcint & HCINT_NAK) { + // NAK received, disable channel to flush all posted request and try again + if (hcsplt.split_en == 1u) { + hcsplt.split_compl = 0; // restart with start-split + channel->hcsplt = hcsplt.value; + } + + channel_disable(dwc2, channel); + } else if (hcint & HCINT_ACK) { + xfer->err_count = 0; + + if (hcsplt.split_en == 1u) { + if (hcsplt.split_compl == 0) { + // start split is ACK --> do complete split + channel->hcintmsk |= HCINT_NYET; + hcsplt.split_compl = 1; + channel->hcsplt = hcsplt.value; + channel_send_in_token(dwc2, channel); + } else { + // do nothing for complete split with DATA, this will trigger XferComplete and handled there + } + } else { + // ACK with data + const uint16_t remain_packets = hctsiz.packet_count; + if (remain_packets > 0) { + // still more packet to receive, also reset to start split + hcsplt.split_compl = 0; + channel->hcsplt = hcsplt.value; + channel_send_in_token(dwc2, channel); + } + } + } else if (hcint & HCINT_HALTED) { + channel->hcintmsk &= ~HCINT_HALTED; + if (xfer->result != XFER_RESULT_INVALID) { + is_done = true; + } else if (xfer->err_count == HCD_XFER_ERROR_MAX) { + xfer->result = XFER_RESULT_FAILED; + is_done = true; + } else if (xfer->closing == 1) { + is_done = true; + } else { + // got here due to NAK or NYET + channel_xfer_in_retry(dwc2, ch_id, hcint); + } + } else if (hcint & HCINT_DATATOGGLE_ERR) { + channel->hcintmsk &= ~HCINT_DATATOGGLE_ERR; + xfer->err_count = 0; + hcsplt.split_compl = 0; // restart with start-split + channel->hcsplt = hcsplt.value; + channel_disable(dwc2, channel); + } else { + // nothing to do + } + return is_done; +} + +static bool handle_channel_out_slave(dwc2_regs_t* dwc2, uint8_t ch_id, uint32_t hcint) { + hcd_xfer_t* xfer = &_hcd_data.xfer[ch_id]; + dwc2_channel_t* channel = &dwc2->channel[ch_id]; + hcd_endpoint_t* edpt = &_hcd_data.edpt[xfer->ep_id]; + dwc2_channel_split_t hcsplt = {.value = channel->hcsplt}; + bool is_done = false; + + if (hcint & HCINT_XFER_COMPLETE) { + is_done = true; + xfer->result = XFER_RESULT_SUCCESS; + channel->hcintmsk &= ~HCINT_ACK; + if (hcint & HCINT_NYET) { + // complete transfer with NYET, do ping next time + edpt->next_do_ping = 1; + } + } else if (hcint & HCINT_STALL) { + xfer->result = XFER_RESULT_STALLED; + channel_disable(dwc2, channel); + } else if (hcint & HCINT_NYET) { + xfer->err_count = 0; + if (hcsplt.split_en == 1u) { + // retry complete split + hcsplt.split_compl = 1; + channel->hcsplt = hcsplt.value; + channel->hcchar |= HCCHAR_CHENA; + } else { + edpt->next_do_ping = 1; + channel_xfer_out_wrapup(dwc2, ch_id); + channel_disable(dwc2, channel); + } + } else if (hcint & (HCINT_NAK | HCINT_XACT_ERR)) { + // clean up transfer so far, disable and start again later + channel_xfer_out_wrapup(dwc2, ch_id); + channel_disable(dwc2, channel); + if (hcint & HCINT_XACT_ERR) { + xfer->err_count++; + channel->hcintmsk |= HCINT_ACK; + } else { + // NAK disable channel to flush all posted request and try again + edpt->next_do_ping = 1; + xfer->err_count = 0; + } + } else if (hcint & HCINT_HALTED) { + channel->hcintmsk &= ~HCINT_HALTED; + if (xfer->result != XFER_RESULT_INVALID) { + is_done = true; + } else if (xfer->err_count == HCD_XFER_ERROR_MAX) { + xfer->result = XFER_RESULT_FAILED; + is_done = true; + } else if (xfer->closing == 1) { + is_done = true; + } else { + // Got here due to NAK or NYET + TU_ASSERT(channel_xfer_start(dwc2, ch_id)); + } + } else if (hcint & HCINT_ACK) { + xfer->err_count = 0; + channel->hcintmsk &= ~HCINT_ACK; + if (hcsplt.split_en == 1u) { + if (hcsplt.split_compl == 0) { + // ACK for start split --> do complete split + hcsplt.split_compl = 1; + channel->hcsplt = hcsplt.value; + channel->hcchar |= HCCHAR_CHENA; + } + } else { + // ACK interrupt is only enabled for Split and PING + // ACK for PING, which mean device is ready to receive data + channel->hctsiz &= ~HCTSIZ_DOPING; // HC already cleared PING bit, but we clear anyway + channel->hcchar |= HCCHAR_CHENA; + } + } else { + // nothing to do + } + + if (is_done) { + xfer->xferred_bytes += xfer->fifo_bytes; + xfer->fifo_bytes = 0; + } + + return is_done; +} +#endif + +#if CFG_TUH_DWC2_DMA_ENABLE +static bool handle_channel_in_dma(dwc2_regs_t* dwc2, uint8_t ch_id, uint32_t hcint) { + hcd_xfer_t* xfer = &_hcd_data.xfer[ch_id]; + dwc2_channel_t* channel = &dwc2->channel[ch_id]; + hcd_endpoint_t* edpt = &_hcd_data.edpt[xfer->ep_id]; + dwc2_channel_char_t hcchar = {.value = channel->hcchar}; + dwc2_channel_split_t hcsplt = {.value = channel->hcsplt}; + const dwc2_channel_tsize_t hctsiz = {.value = channel->hctsiz}; + + bool is_done = false; + + // TU_LOG1("in hcint = %02lX\r\n", hcint); + + if (hcint & HCINT_HALTED) { + if (hcint & (HCINT_XFER_COMPLETE | HCINT_STALL | HCINT_BABBLE_ERR)) { + const uint16_t remain_bytes = (uint16_t) hctsiz.xfer_size; + const uint16_t remain_packets = hctsiz.packet_count; + const uint16_t actual_len = edpt->buflen - remain_bytes; + xfer->xferred_bytes += actual_len; + + is_done = true; + + if (hcint & HCINT_STALL) { + xfer->result = XFER_RESULT_STALLED; + } else if (hcint & HCINT_BABBLE_ERR) { + xfer->result = XFER_RESULT_FAILED; + } else if (hcsplt.split_en && remain_packets && actual_len == hcchar.ep_size) { + // Split can only complete 1 transaction (up to 1 packet) at a time, schedule more + is_done = false; + edpt->buffer += actual_len; + edpt->buflen -= actual_len; + + hcsplt.split_compl = 0; + channel->hcsplt = hcsplt.value; + channel_xfer_in_retry(dwc2, ch_id, hcint); + } else { + xfer->result = XFER_RESULT_SUCCESS; + } + + xfer->err_count = 0; + channel->hcintmsk &= ~HCINT_ACK; + } else if (hcint & HCINT_XACT_ERR) { + xfer->err_count++; + if (xfer->err_count >= HCD_XFER_ERROR_MAX) { + is_done = true; + xfer->result = XFER_RESULT_FAILED; + } else { + channel->hcintmsk |= HCINT_ACK | HCINT_NAK | HCINT_DATATOGGLE_ERR; + hcsplt.split_compl = 0; + channel->hcsplt = hcsplt.value; + channel_xfer_in_retry(dwc2, ch_id, hcint); + } + } else if (hcint & HCINT_NYET) { + // Must handle nyet before nak or ack. Could get a nyet at the same time as either of those on a BULK/CONTROL + // OUT that started with a PING. The nyet takes precedence. + if (hcsplt.split_en) { + // split not yet mean hub has no data, retry complete split + hcsplt.split_compl = 1; + channel->hcsplt = hcsplt.value; + channel_xfer_in_retry(dwc2, ch_id, hcint); + } + } else if (hcint & HCINT_ACK) { + xfer->err_count = 0; + channel->hcintmsk &= ~HCINT_ACK; + if (hcsplt.split_en) { + // start split is ACK --> do complete split + // TODO: for ISO must use xact_pos to plan complete split based on microframe (up to 187.5 bytes/uframe) + hcsplt.split_compl = 1; + channel->hcsplt = hcsplt.value; + if (channel_is_periodic(channel->hcchar)) { + hcchar.odd_frame = 1 - (dwc2->hfnum & 1); // transfer on next frame + channel->hcchar = hcchar.value; + } + channel_send_in_token(dwc2, channel); + } + } else if (hcint & (HCINT_NAK | HCINT_DATATOGGLE_ERR)) { + xfer->err_count = 0; + channel->hcintmsk &= ~(HCINT_NAK | HCINT_DATATOGGLE_ERR); + hcsplt.split_compl = 0; // restart with start-split + channel->hcsplt = hcsplt.value; + channel_xfer_in_retry(dwc2, ch_id, hcint); + } else if (hcint & HCINT_FARME_OVERRUN) { + // retry start-split in next binterval + channel_xfer_in_retry(dwc2, ch_id, hcint); + } + + if (xfer->closing == 1) { + is_done = true; + } + } + + return is_done; +} + +static bool handle_channel_out_dma(dwc2_regs_t* dwc2, uint8_t ch_id, uint32_t hcint) { + hcd_xfer_t* xfer = &_hcd_data.xfer[ch_id]; + dwc2_channel_t* channel = &dwc2->channel[ch_id]; + hcd_endpoint_t* edpt = &_hcd_data.edpt[xfer->ep_id]; + dwc2_channel_split_t hcsplt = {.value = channel->hcsplt}; + + bool is_done = false; + + // TU_LOG1("out hcint = %02lX\r\n", hcint); + + if (hcint & HCINT_HALTED) { + if (hcint & (HCINT_XFER_COMPLETE | HCINT_STALL)) { + is_done = true; + xfer->err_count = 0; + if (hcint & HCINT_XFER_COMPLETE) { + xfer->result = XFER_RESULT_SUCCESS; + xfer->xferred_bytes += edpt->buflen; + } else { + xfer->result = XFER_RESULT_STALLED; + channel_xfer_out_wrapup(dwc2, ch_id); + } + channel->hcintmsk &= ~HCINT_ACK; + } else if (hcint & HCINT_XACT_ERR) { + if (hcint & (HCINT_NAK | HCINT_NYET | HCINT_ACK)) { + xfer->err_count = 0; + // clean up transfer so far and start again + channel_xfer_out_wrapup(dwc2, ch_id); + channel_xfer_start(dwc2, ch_id); + } else { + xfer->err_count++; + if (xfer->err_count >= HCD_XFER_ERROR_MAX) { + xfer->result = XFER_RESULT_FAILED; + is_done = true; + } else { + // clean up transfer so far and start again + channel_xfer_out_wrapup(dwc2, ch_id); + channel_xfer_start(dwc2, ch_id); + } + } + } else if (hcint & HCINT_NYET) { + if (hcsplt.split_en && hcsplt.split_compl) { + // split not yet mean hub has no data, retry complete split + hcsplt.split_compl = 1; + channel->hcsplt = hcsplt.value; + channel->hcchar |= HCCHAR_CHENA; + } + } else if (hcint & HCINT_ACK) { + xfer->err_count = 0; + if (hcsplt.split_en && !hcsplt.split_compl) { + // start split is ACK --> do complete split + hcsplt.split_compl = 1; + channel->hcsplt = hcsplt.value; + channel->hcchar |= HCCHAR_CHENA; + } + } + + if (xfer->closing == 1) { + is_done = true; + } + } else if (hcint & HCINT_ACK) { + xfer->err_count = 0; + channel->hcintmsk &= ~HCINT_ACK; + } + + return is_done; +} +#endif + +static void handle_channel_irq(uint8_t rhport, bool in_isr) { + dwc2_regs_t* dwc2 = DWC2_REG(rhport); + const bool is_dma = dma_host_enabled(dwc2); + const uint8_t max_channel = dwc2_channel_count(dwc2); + + for (uint8_t ch_id = 0; ch_id < max_channel; ch_id++) { + if (tu_bit_test(dwc2->haint, ch_id)) { + dwc2_channel_t* channel = &dwc2->channel[ch_id]; + hcd_xfer_t* xfer = &_hcd_data.xfer[ch_id]; + TU_ASSERT(xfer->ep_id < CFG_TUH_DWC2_ENDPOINT_MAX,); + dwc2_channel_char_t hcchar = {.value = channel->hcchar}; + + const uint32_t hcint = channel->hcint; + channel->hcint = hcint; // clear interrupt + + bool is_done = false; + if (is_dma) { + #if CFG_TUH_DWC2_DMA_ENABLE + if (hcchar.ep_dir == TUSB_DIR_OUT) { + is_done = handle_channel_out_dma(dwc2, ch_id, hcint); + } else { + is_done = handle_channel_in_dma(dwc2, ch_id, hcint); + if (is_done && (channel->hcdma > xfer->xferred_bytes)) { + // hcdma is increased by word --> need to align4 + hcd_dcache_invalidate((void*) tu_align4(channel->hcdma - xfer->xferred_bytes), xfer->xferred_bytes); + } + } + #endif + } else { + #if CFG_TUH_DWC2_SLAVE_ENABLE + if (hcchar.ep_dir == TUSB_DIR_OUT) { + is_done = handle_channel_out_slave(dwc2, ch_id, hcint); + } else { + is_done = handle_channel_in_slave(dwc2, ch_id, hcint); + } + #endif + } + + if (is_done) { + if (xfer->closing == 1) { + hcd_endpoint_t *edpt = &_hcd_data.edpt[xfer->ep_id]; + edpt_dealloc(edpt); + } else { + const uint8_t ep_addr = tu_edpt_addr(hcchar.ep_num, hcchar.ep_dir); + hcd_event_xfer_complete(hcchar.dev_addr, ep_addr, xfer->xferred_bytes, (xfer_result_t)xfer->result, in_isr); + } + channel_dealloc(dwc2, ch_id); + } + } + } +} + +// SOF is enabled for scheduled periodic transfer +static bool handle_sof_irq(uint8_t rhport, bool in_isr) { + (void) in_isr; + dwc2_regs_t* dwc2 = DWC2_REG(rhport); + dwc2->gintsts = GINTSTS_SOF; // Clear the SOF interrupt flag + + bool more_isr = false; + + // If highspeed then SOF is 125us, else 1ms + const uint32_t ucount = (hprt_speed_get(dwc2) == TUSB_SPEED_HIGH ? 1 : 8); + + for(uint8_t ep_id = 0; ep_id < CFG_TUH_DWC2_ENDPOINT_MAX; ep_id++) { + hcd_endpoint_t *edpt = &_hcd_data.edpt[ep_id]; + if (edpt->closing == 0) { + if (edpt->hcchar_bm.enable && channel_is_periodic(edpt->hcchar) && edpt->uframe_countdown > 0) { + edpt->uframe_countdown -= tu_min32(ucount, edpt->uframe_countdown); + if (edpt->uframe_countdown == 0) { + if (!edpt_xfer_kickoff(dwc2, ep_id)) { + edpt->uframe_countdown = ucount; // failed to start, try again next frame + } + } + + more_isr = true; + } + } + } + + return more_isr; +} + +// Config HCFG FS/LS clock and HFIR for SOF interval according to link speed (value is in PHY clock unit) +// Databook Table 2-2: System Clock Speeds +// +-----------+------------------+----------+-----------+-------------------+ +// | PHY | PHY Clock (MHz) | Width | HCFG.Sel | HFIR (clk cycles) | +// +-----------+------------------+----------+-----------+-------------------+ +// | HS UTMI+ | 30 | 16-bit | 30_60 | HS:3749 FS:29999 | +// | HS UTMI+ | 60 | 8-bit | 30_60 | HS:7499 FS:59999 | +// | HS ULPI | 60 | 8-bit | 30_60 | HS:7499 FS:59999 | +// | FS (dead.) | 48 | internal | 48 | FS:47999 | +// | LS via FS | 48 (6 effective) | internal | 6 | LS:47999 | +// +-----------+------------------+----------+-----------+-------------------+ +// HFIR = (interval_us * phy_clock) - 1, where interval is 125us (HS) or 1000us (FS/LS) +static void port0_enable(dwc2_regs_t* dwc2, tusb_speed_t speed) { + uint32_t hcfg = dwc2->hcfg & ~HCFG_FSLS_PHYCLK_SEL; + + const dwc2_gusbcfg_t gusbcfg = {.value = dwc2->gusbcfg}; + uint32_t phy_clock; + + if (gusbcfg.phy_sel == GUSBCFG_PHYSEL_FULLSPEED) { + phy_clock = 48; // dedicated FS is 48Mhz + if (speed == TUSB_SPEED_LOW) { + hcfg |= HCFG_FSLS_PHYCLK_SEL_6MHZ; + } else { + hcfg |= HCFG_FSLS_PHYCLK_SEL_48MHZ; + } + } else { + if (gusbcfg.ulpi_utmi_sel == GUSBCFG_PHYHS_ULPI) { + phy_clock = 60; // ULPI 8-bit is 60Mhz + } else { + // UTMI+ 16-bit is 30Mhz, 8-bit is 60Mhz + phy_clock = gusbcfg.phy_if16 ? 30 : 60; + + // Enable UTMI+ low power mode 48Mhz external clock if not highspeed + if (speed == TUSB_SPEED_HIGH) { + dwc2->gusbcfg &= ~GUSBCFG_PHYLPCS; + } else { + dwc2->gusbcfg |= GUSBCFG_PHYLPCS; + // may need to reset port + } + } + hcfg |= HCFG_FSLS_PHYCLK_SEL_30_60MHZ; + } + + dwc2->hcfg = hcfg; + + uint32_t hfir = dwc2->hfir & ~HFIR_FRIVL_Msk; + if (speed == TUSB_SPEED_HIGH) { + hfir |= 125*phy_clock - 1; // The "- 1" is the correct value. The Synopsys databook was corrected in 3.30a + } else { + hfir |= 1000*phy_clock - 1; + } + + dwc2->hfir = hfir; +} + +/* Handle Host Port interrupt, possible source are: + - Connection Detection + - Enable Change + - Over Current Change +*/ +static void handle_hprt_irq(uint8_t rhport, bool in_isr) { + dwc2_regs_t* dwc2 = DWC2_REG(rhport); + const dwc2_hprt_t hprt_bm = {.value = dwc2->hprt}; + uint32_t hprt = hprt_bm.value & ~HPRT_W1_MASK; + + if (hprt_bm.conn_detected == 1u) { + // Port Connect Detect + hprt |= HPRT_CONN_DETECT; + + if (hprt_bm.conn_status == 1u) { + hcd_event_device_attach(rhport, in_isr); + } + } + + if (hprt_bm.enable_change == 1u) { + // Port enable change + hprt |= HPRT_ENABLE_CHANGE; + + if (hprt_bm.enable == 1u) { + // Port enable + const tusb_speed_t speed = hprt_speed_get(dwc2); + port0_enable(dwc2, speed); + } else { + // TU_ASSERT(false, ); + } + } + + dwc2->hprt = hprt; // clear interrupt +} + +/* Interrupt Hierarchy + HCINTn HPRT + | | + HAINT.CHn | + | | + GINTSTS : HCInt | PrtInt | NPTxFEmp | PTxFEmpp | RXFLVL | SOF +*/ +void hcd_int_handler(uint8_t rhport, bool in_isr) { + dwc2_regs_t* dwc2 = DWC2_REG(rhport); + const uint32_t gintmsk = dwc2->gintmsk; + const uint32_t gintsts = dwc2->gintsts & gintmsk; + + // TU_LOG1_HEX(gintsts); + + if (gintsts & GINTSTS_SOF) { + const bool more_sof = handle_sof_irq(rhport, in_isr); + if (!more_sof) { + dwc2->gintmsk &= ~GINTSTS_SOF; + } + } + + if (gintsts & GINTSTS_HPRTINT) { + // Host port interrupt: source is cleared in HPRT register + // TU_LOG1_HEX(dwc2->hprt); + handle_hprt_irq(rhport, in_isr); + } + + if (gintsts & GINTSTS_HCINT) { + // Host Channel interrupt: source is cleared in HCINT register + // must be handled after TX FIFO empty + handle_channel_irq(rhport, in_isr); + } + + if (gintsts & GINTSTS_DISCINT) { + // Device disconnected + dwc2->gintsts = GINTSTS_DISCINT; + + if (0 == (dwc2->hprt & HPRT_CONN_STATUS)) { + hcd_event_device_remove(rhport, in_isr); + } + } + +#if CFG_TUH_DWC2_SLAVE_ENABLE + // RxFIFO non-empty interrupt handling + if (gintsts & GINTSTS_RXFLVL) { + // RXFLVL bit is read-only + dwc2->gintmsk &= ~GINTSTS_RXFLVL; // disable RXFLVL interrupt while reading + + do { + handle_rxflvl_irq(rhport); // read all packets + } while(dwc2->gintsts & GINTSTS_RXFLVL); + + dwc2->gintmsk |= GINTSTS_RXFLVL; + } + + if (gintsts & GINTSTS_NPTX_FIFO_EMPTY) { + // NPTX FIFO empty interrupt, this is read-only and cleared by hardware when FIFO is written + const bool more_nptxfe = handle_txfifo_empty(dwc2, false); + if (!more_nptxfe) { + // no more pending packet, disable interrupt + dwc2->gintmsk &= ~GINTSTS_NPTX_FIFO_EMPTY; + } + } + + if (gintsts & GINTSTS_PTX_FIFO_EMPTY) { + // PTX FIFO empty interrupt, this is read-only and cleared by hardware when FIFO is written + const bool more_ptxfe = handle_txfifo_empty(dwc2, true); + if (!more_ptxfe) { + // no more pending packet, disable interrupt + dwc2->gintmsk &= ~GINTSTS_PTX_FIFO_EMPTY; + } + } +#endif +} + +#endif diff --git a/libraries/Adafruit_TinyUSB_Arduino/src/portable/wch/ch32_usbfs_reg.h b/libraries/Adafruit_TinyUSB_Arduino/src/portable/wch/ch32_usbfs_reg.h new file mode 100644 index 0000000..68be64f --- /dev/null +++ b/libraries/Adafruit_TinyUSB_Arduino/src/portable/wch/ch32_usbfs_reg.h @@ -0,0 +1,175 @@ +/* + * The MIT License (MIT) + * + * Copyright (c) 2024 Matthew Tran + * Copyright (c) 2024 hathach + * + * 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 USB_CH32_USBFS_REG_H +#define USB_CH32_USBFS_REG_H + +// https://github.com/openwch/ch32v307/pull/90 +// https://github.com/openwch/ch32v20x/pull/12 +#ifdef __GNUC__ +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wstrict-prototypes" +#endif + +#if CFG_TUSB_MCU == OPT_MCU_CH32F20X + #include +#elif CFG_TUSB_MCU == OPT_MCU_CH32V103 + #include + typedef struct + { + __IO uint8_t BASE_CTRL; + __IO uint8_t UDEV_CTRL; + __IO uint8_t INT_EN; + __IO uint8_t DEV_ADDR; + __IO uint8_t Reserve0; + __IO uint8_t MIS_ST; + __IO uint8_t INT_FG; + __IO uint8_t INT_ST; + __IO uint32_t RX_LEN; + __IO uint8_t UEP4_1_MOD; + __IO uint8_t UEP2_3_MOD; + __IO uint8_t UEP5_6_MOD; + __IO uint8_t UEP7_MOD; + __IO uint32_t UEP0_DMA; + __IO uint32_t UEP1_DMA; + __IO uint32_t UEP2_DMA; + __IO uint32_t UEP3_DMA; + __IO uint32_t UEP4_DMA; + __IO uint32_t UEP5_DMA; + __IO uint32_t UEP6_DMA; + __IO uint32_t UEP7_DMA; + __IO uint16_t UEP0_TX_LEN; + __IO uint8_t UEP0_TX_CTRL; + __IO uint8_t UEP0_RX_CTRL; + __IO uint16_t UEP1_TX_LEN; + __IO uint8_t UEP1_TX_CTRL; + __IO uint8_t UEP1_RX_CTRL; + __IO uint16_t UEP2_TX_LEN; + __IO uint8_t UEP2_TX_CTRL; + __IO uint8_t UEP2_RX_CTRL; + __IO uint16_t UEP3_TX_LEN; + __IO uint8_t UEP3_TX_CTRL; + __IO uint8_t UEP3_RX_CTRL; + __IO uint16_t UEP4_TX_LEN; + __IO uint8_t UEP4_TX_CTRL; + __IO uint8_t UEP4_RX_CTRL; + __IO uint16_t UEP5_TX_LEN; + __IO uint8_t UEP5_TX_CTRL; + __IO uint8_t UEP5_RX_CTRL; + __IO uint16_t UEP6_TX_LEN; + __IO uint8_t UEP6_TX_CTRL; + __IO uint8_t UEP6_RX_CTRL; + __IO uint16_t UEP7_TX_LEN; + __IO uint8_t UEP7_TX_CTRL; + __IO uint8_t UEP7_RX_CTRL; + __IO uint32_t Reserve1; + __IO uint32_t OTG_CR; + __IO uint32_t OTG_SR; + } USBOTG_FS_TypeDef; + + #define USBOTG_FS ((USBOTG_FS_TypeDef *) 0x40023400) +#elif CFG_TUSB_MCU == OPT_MCU_CH32V20X + #include +#elif CFG_TUSB_MCU == OPT_MCU_CH32V307 + #include + #define USBHD_IRQn OTG_FS_IRQn +#endif + +#ifdef __GNUC__ +#pragma GCC diagnostic pop +#endif + +// CTRL +#define USBFS_CTRL_DMA_EN (1 << 0) +#define USBFS_CTRL_CLR_ALL (1 << 1) +#define USBFS_CTRL_RESET_SIE (1 << 2) +#define USBFS_CTRL_INT_BUSY (1 << 3) +#define USBFS_CTRL_SYS_CTRL (1 << 4) +#define USBFS_CTRL_DEV_PUEN (1 << 5) +#define USBFS_CTRL_LOW_SPEED (1 << 6) +#define USBFS_CTRL_HOST_MODE (1 << 7) + +// INT_EN +#define USBFS_INT_EN_BUS_RST (1 << 0) +#define USBFS_INT_EN_DETECT (1 << 0) +#define USBFS_INT_EN_TRANSFER (1 << 1) +#define USBFS_INT_EN_SUSPEND (1 << 2) +#define USBFS_INT_EN_HST_SOF (1 << 3) +#define USBFS_INT_EN_FIFO_OV (1 << 4) +#define USBFS_INT_EN_DEV_NAK (1 << 6) +#define USBFS_INT_EN_DEV_SOF (1 << 7) + +// INT_FG +#define USBFS_INT_FG_BUS_RST (1 << 0) +#define USBFS_INT_FG_DETECT (1 << 0) +#define USBFS_INT_FG_TRANSFER (1 << 1) +#define USBFS_INT_FG_SUSPEND (1 << 2) +#define USBFS_INT_FG_HST_SOF (1 << 3) +#define USBFS_INT_FG_FIFO_OV (1 << 4) +#define USBFS_INT_FG_SIE_FREE (1 << 5) +#define USBFS_INT_FG_TOG_OK (1 << 6) +#define USBFS_INT_FG_IS_NAK (1 << 7) + +// INT_ST +#define USBFS_INT_ST_MASK_UIS_ENDP(x) (((x) >> 0) & 0x0F) +#define USBFS_INT_ST_MASK_UIS_TOKEN(x) (((x) >> 4) & 0x03) + +// UDEV_CTRL +#define USBFS_UDEV_CTRL_PORT_EN (1 << 0) +#define USBFS_UDEV_CTRL_GP_BIT (1 << 1) +#define USBFS_UDEV_CTRL_LOW_SPEED (1 << 2) +#define USBFS_UDEV_CTRL_DM_PIN (1 << 4) +#define USBFS_UDEV_CTRL_DP_PIN (1 << 5) +#define USBFS_UDEV_CTRL_PD_DIS (1 << 7) + +// TX_CTRL +#define USBFS_EP_T_RES_MASK (3 << 0) +#define USBFS_EP_T_TOG (1 << 2) +#define USBFS_EP_T_AUTO_TOG (1 << 3) + +#define USBFS_EP_T_RES_ACK (0 << 0) +#define USBFS_EP_T_RES_NYET (1 << 0) +#define USBFS_EP_T_RES_NAK (2 << 0) +#define USBFS_EP_T_RES_STALL (3 << 0) + +// RX_CTRL +#define USBFS_EP_R_RES_MASK (3 << 0) +#define USBFS_EP_R_TOG (1 << 2) +#define USBFS_EP_R_AUTO_TOG (1 << 3) + +#define USBFS_EP_R_RES_ACK (0 << 0) +#define USBFS_EP_R_RES_NYET (1 << 0) +#define USBFS_EP_R_RES_NAK (2 << 0) +#define USBFS_EP_R_RES_STALL (3 << 0) + +// token PID +#define PID_OUT 0 +#define PID_SOF 1 +#define PID_IN 2 +#define PID_SETUP 3 + +#endif // USB_CH32_USBFS_REG_H diff --git a/libraries/Adafruit_TinyUSB_Arduino/src/portable/wch/ch32_usbhs_reg.h b/libraries/Adafruit_TinyUSB_Arduino/src/portable/wch/ch32_usbhs_reg.h new file mode 100644 index 0000000..87300b4 --- /dev/null +++ b/libraries/Adafruit_TinyUSB_Arduino/src/portable/wch/ch32_usbhs_reg.h @@ -0,0 +1,395 @@ +/* + * The MIT License (MIT) + * + * Copyright (c) 2024 Matthew Tran + * Copyright (c) 2024 hathach + * + * 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 USB_CH32_USBHS_REG_H +#define USB_CH32_USBHS_REG_H + +// https://github.com/openwch/ch32v307/pull/90 +// https://github.com/openwch/ch32v20x/pull/12 +#ifdef __GNUC__ +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wstrict-prototypes" +#endif + +#if CFG_TUSB_MCU == OPT_MCU_CH32V307 + #include +#elif CFG_TUSB_MCU == OPT_MCU_CH32F20X + #include +#endif + +#ifdef __GNUC__ +#pragma GCC diagnostic pop +#endif + + +/******************* GLOBAL ******************/ + +// USB CONTROL +#define USBHS_CONTROL_OFFSET 0x00 +#define USBHS_DMA_EN (1 << 0) +#define USBHS_ALL_CLR (1 << 1) +#define USBHS_FORCE_RST (1 << 2) +#define USBHS_INT_BUSY_EN (1 << 3) +#define USBHS_DEV_PU_EN (1 << 4) +#define USBHS_SPEED_MASK (3 << 5) +#define USBHS_FULL_SPEED (0 << 5) +#define USBHS_HIGH_SPEED (1 << 5) +#define USBHS_LOW_SPEED (2 << 5) +#define USBHS_HOST_MODE (1 << 7) + +// USB_INT_EN +#define USBHS_INT_EN_OFFSET 0x02 +#define USBHS_BUS_RST_EN (1 << 0) +#define USBHS_DETECT_EN (1 << 0) +#define USBHS_TRANSFER_EN (1 << 1) +#define USBHS_SUSPEND_EN (1 << 2) +#define USBHS_SOF_ACT_EN (1 << 3) +#define USBHS_FIFO_OV_EN (1 << 4) +#define USBHS_SETUP_ACT_EN (1 << 5) +#define USBHS_ISO_ACT_EN (1 << 6) +#define USBHS_DEV_NAK_EN (1 << 7) + +// USB DEV AD +#define USBHS_DEV_AD_OFFSET 0x03 + +// USB FRAME_NO +#define USBHS_FRAME_NO_OFFSET 0x04 +#define USBHS_FRAME_NO_NUM_MASK (0x7FF) +#define USBHS_FRAME_NO_MICROFRAME_SHIFT (11) +#define USBHS_FRAME_NO_MICROFRAME_MASK (0x7 << USBHS_FRAME_NO_MICROFRAME_SHIFT) + +// USB SUSPEND +#define USBHS_SUSPEND_OFFSET 0x06 +#define USBHS_DEV_REMOTE_WAKEUP (1 << 2) +#define USBHS_LINESTATE_MASK (2 << 4) /* Read Only */ + +// RESERVED0 + +// USB SPEED TYPE +#define USBHS_SPEED_TYPE_OFFSET 0x08 +#define USBHS_SPEED_TYPE_MASK 0x03 +#define USBHS_SPEED_TYPE_FULL 0 +#define USBHS_SPEED_TYPE_HIGH 1 +#define USBHS_SPEED_TYPE_LOW 2 + +// USB_MIS_ST +#define USBHS_MIS_ST_OFFSET 0x09 +#define USBHS_SPLIT_CAN (1 << 0) +#define USBHS_ATTACH (1 << 1) +#define USBHS_SUSPEND (1 << 2) +#define USBHS_BUS_RESET (1 << 3) +#define USBHS_R_FIFO_RDY (1 << 4) +#define USBHS_SIE_FREE (1 << 5) +#define USBHS_SOF_ACT (1 << 6) +#define USBHS_SOF_PRES (1 << 7) + +// INT_FLAG +#define USBHS_INT_FLAG_OFFSET 0x0A +#define USBHS_BUS_RST_FLAG (1 << 0) +#define USBHS_DETECT_FLAG (1 << 0) +#define USBHS_TRANSFER_FLAG (1 << 1) +#define USBHS_SUSPEND_FLAG (1 << 2) +#define USBHS_HST_SOF_FLAG (1 << 3) +#define USBHS_FIFO_OV_FLAG (1 << 4) +#define USBHS_SETUP_FLAG (1 << 5) +#define USBHS_ISO_ACT_FLAG (1 << 6) + +// INT_ST +#define USBHS_INT_ST_OFFSET 0x0B +#define USBHS_DEV_UIS_IS_NAK (1 << 7) +#define USBHS_DEV_UIS_TOG_OK (1 << 6) +#define MASK_UIS_TOKEN (3 << 4) +#define USBHS_TOKEN_PID_OUT (0 << 4) +#define USBHS_TOKEN_PID_SOF (1 << 4) +#define USBHS_TOKEN_PID_IN (2 << 4) +#define USBHS_TOKEN_PID_SETUP (3 << 4) +#define MASK_UIS_ENDP (0x0F) +#define MASK_UIS_H_RES (0x0F) + +#define USBHS_TOGGLE_OK (0x40) +#define USBHS_HOST_RES (0x0f) + +//USB_RX_LEN +#define USBHS_RX_LEN_OFFSET 0x0C +/******************* DEVICE ******************/ + +//UEP_CONFIG +#define USBHS_UEP_CONFIG_OFFSET 0x10 +#define USBHS_EP0_T_EN (1 << 0) +#define USBHS_EP0_R_EN (1 << 16) + +#define USBHS_EP1_T_EN (1 << 1) +#define USBHS_EP1_R_EN (1 << 17) + +#define USBHS_EP2_T_EN (1 << 2) +#define USBHS_EP2_R_EN (1 << 18) + +#define USBHS_EP3_T_EN (1 << 3) +#define USBHS_EP3_R_EN (1 << 19) + +#define USBHS_EP4_T_EN (1 << 4) +#define USBHS_EP4_R_EN (1 << 20) + +#define USBHS_EP5_T_EN (1 << 5) +#define USBHS_EP5_R_EN (1 << 21) + +#define USBHS_EP6_T_EN (1 << 6) +#define USBHS_EP6_R_EN (1 << 22) + +#define USBHS_EP7_T_EN (1 << 7) +#define USBHS_EP7_R_EN (1 << 23) + +#define USBHS_EP8_T_EN (1 << 8) +#define USBHS_EP8_R_EN (1 << 24) + +#define USBHS_EP9_T_EN (1 << 9) +#define USBHS_EP9_R_EN (1 << 25) + +#define USBHS_EP10_T_EN (1 << 10) +#define USBHS_EP10_R_EN (1 << 26) + +#define USBHS_EP11_T_EN (1 << 11) +#define USBHS_EP11_R_EN (1 << 27) + +#define USBHS_EP12_T_EN (1 << 12) +#define USBHS_EP12_R_EN (1 << 28) + +#define USBHS_EP13_T_EN (1 << 13) +#define USBHS_EP13_R_EN (1 << 29) + +#define USBHS_EP14_T_EN (1 << 14) +#define USBHS_EP14_R_EN (1 << 30) + +#define USBHS_EP15_T_EN (1 << 15) +#define USBHS_EP15_R_EN (1 << 31) + +//UEP_TYPE +#define USBHS_UEP_TYPE_OFFSET 0x14 +#define USBHS_EP0_T_TYP (1 << 0) +#define USBHS_EP0_R_TYP (1 << 16) + +#define USBHS_EP1_T_TYP (1 << 1) +#define USBHS_EP1_R_TYP (1 << 17) + +#define USBHS_EP2_T_TYP (1 << 2) +#define USBHS_EP2_R_TYP (1 << 18) + +#define USBHS_EP3_T_TYP (1 << 3) +#define USBHS_EP3_R_TYP (1 << 19) + +#define USBHS_EP4_T_TYP (1 << 4) +#define USBHS_EP4_R_TYP (1 << 20) + +#define USBHS_EP5_T_TYP (1 << 5) +#define USBHS_EP5_R_TYP (1 << 21) + +#define USBHS_EP6_T_TYP (1 << 6) +#define USBHS_EP6_R_TYP (1 << 22) + +#define USBHS_EP7_T_TYP (1 << 7) +#define USBHS_EP7_R_TYP (1 << 23) + +#define USBHS_EP8_T_TYP (1 << 8) +#define USBHS_EP8_R_TYP (1 << 24) + +#define USBHS_EP9_T_TYP (1 << 8) +#define USBHS_EP9_R_TYP (1 << 25) + +#define USBHS_EP10_T_TYP (1 << 10) +#define USBHS_EP10_R_TYP (1 << 26) + +#define USBHS_EP11_T_TYP (1 << 11) +#define USBHS_EP11_R_TYP (1 << 27) + +#define USBHS_EP12_T_TYP (1 << 12) +#define USBHS_EP12_R_TYP (1 << 28) + +#define USBHS_EP13_T_TYP (1 << 13) +#define USBHS_EP13_R_TYP (1 << 29) + +#define USBHS_EP14_T_TYP (1 << 14) +#define USBHS_EP14_R_TYP (1 << 30) + +#define USBHS_EP15_T_TYP (1 << 15) +#define USBHS_EP15_R_TYP (1 << 31) + +/* BUF_MOD UEP1~15 */ +#define USBHS_BUF_MOD_OFFSET 0x18 +#define USBHS_EP0_BUF_MOD (1 << 0) +#define USBHS_EP0_ISO_BUF_MOD (1 << 16) + +#define USBHS_EP1_BUF_MOD (1 << 1) +#define USBHS_EP1_ISO_BUF_MOD (1 << 17) + +#define USBHS_EP2_BUF_MOD (1 << 2) +#define USBHS_EP2_ISO_BUF_MOD (1 << 18) + +#define USBHS_EP3_BUF_MOD (1 << 3) +#define USBHS_EP3_ISO_BUF_MOD (1 << 19) + +#define USBHS_EP4_BUF_MOD (1 << 4) +#define USBHS_EP4_ISO_BUF_MOD (1 << 20) + +#define USBHS_EP5_BUF_MOD (1 << 5) +#define USBHS_EP5_ISO_BUF_MOD (1 << 21) + +#define USBHS_EP6_BUF_MOD (1 << 6) +#define USBHS_EP6_ISO_BUF_MOD (1 << 22) + +#define USBHS_EP7_BUF_MOD (1 << 7) +#define USBHS_EP7_ISO_BUF_MOD (1 << 23) + +#define USBHS_EP8_BUF_MOD (1 << 8) +#define USBHS_EP8_ISO_BUF_MOD (1 << 24) + +#define USBHS_EP9_BUF_MOD (1 << 9) +#define USBHS_EP9_ISO_BUF_MOD (1 << 25) + +#define USBHS_EP10_BUF_MOD (1 << 10) +#define USBHS_EP10_ISO_BUF_MOD (1 << 26) + +#define USBHS_EP11_BUF_MOD (1 << 11) +#define USBHS_EP11_ISO_BUF_MOD (1 << 27) + +#define USBHS_EP12_BUF_MOD (1 << 12) +#define USBHS_EP12_ISO_BUF_MOD (1 << 28) + +#define USBHS_EP13_BUF_MOD (1 << 13) +#define USBHS_EP13_ISO_BUF_MOD (1 << 29) + +#define USBHS_EP14_BUF_MOD (1 << 14) +#define USBHS_EP14_ISO_BUF_MOD (1 << 30) + +#define USBHS_EP15_BUF_MOD (1 << 15) +#define USBHS_EP15_ISO_BUF_MOD (1 << 31) +//USBHS_EPn_T_EN USBHS_EPn_R_EN USBHS_EPn_BUF_MOD Description: Arrange from low to high with UEPn_DMA as the starting address +// 0 0 x The endpoint is disabled and the UEPn_*_DMA buffers are not used. +// 1 0 0 The first address of the receive (OUT) buffer is UEPn_RX_DMA +// 1 0 1 RB_UEPn_RX_TOG[0]=0, use buffer UEPn_RX_DMA RB_UEPn_RX_TOG[0]=1, use buffer UEPn_TX_DMA +// 0 1 0 The first address of the transmit (IN) buffer is UEPn_TX_DMA. +// 0 1 1 RB_UEPn_TX_TOG[0]=0, use buffer UEPn_TX_DMA RB_UEPn_TX_TOG[0]=1, use buffer UEPn_RX_DMA + +/* USB0_DMA */ +#define USBHS_UEP0_DMA_OFFSET(n) (0x1C) // endpoint 0 DMA buffer address + +/* USBX_RX_DMA */ +#define USBHS_UEPx_RX_DMA_OFFSET(n) (0x1C + 4 * (n)) // endpoint x DMA buffer address + +#define USBHS_UEPx_TX_DMA_OFFSET(n) (0x58 + 4 * (n)) // endpoint x DMA buffer address + +#define USBHS_UEPx_MAX_LEN_OFFSET(n) (0x98 + 4 * (n)) // endpoint x DMA buffer address + +#define USBHS_UEPx_T_LEN_OFFSET(n) (0xD8 + 4 * (n)) // endpoint x DMA buffer address +#define USBHS_UEPx_TX_CTRL_OFFSET(n) (0xD8 + 4 * (n) + 2) // endpoint x DMA buffer address +#define USBHS_UEPx_RX_CTRL_OFFSET(n) (0xD8 + 4 * (n) + 3) // endpoint x DMA buffer address + +// UEPn_T_LEN +#define USBHS_EP_T_LEN_MASK (0x7FF) + +//UEPn_TX_CTRL +#define USBHS_EP_T_RES_MASK (3 << 0) +#define USBHS_EP_T_RES_ACK (0 << 0) +#define USBHS_EP_T_RES_NYET (1 << 0) +#define USBHS_EP_T_RES_NAK (2 << 0) +#define USBHS_EP_T_RES_STALL (3 << 0) + +#define USBHS_EP_T_TOG_MASK (3 << 3) +#define USBHS_EP_T_TOG_0 (0 << 3) +#define USBHS_EP_T_TOG_1 (1 << 3) +#define USBHS_EP_T_TOG_2 (2 << 3) +#define USBHS_EP_T_TOG_M (3 << 3) + +#define USBHS_EP_T_AUTOTOG (1 << 5) + +//UEPn_RX_CTRL +#define USBHS_EP_R_RES_MASK (3 << 0) +#define USBHS_EP_R_RES_ACK (0 << 0) +#define USBHS_EP_R_RES_NYET (1 << 0) +#define USBHS_EP_R_RES_NAK (2 << 0) +#define USBHS_EP_R_RES_STALL (3 << 0) + +#define USBHS_EP_R_TOG_MASK (3 << 3) +#define USBHS_EP_R_TOG_0 (0 << 3) +#define USBHS_EP_R_TOG_1 (1 << 3) +#define USBHS_EP_R_TOG_2 (2 << 3) +#define USBHS_EP_R_TOG_M (3 << 3) + +#define USBHS_EP_R_AUTOTOG (1 << 5) + +#define USBHS_TOG_MATCH (1 << 6) + +/******************* HOST ******************/ +// USB HOST_CTRL +#define USBHS_SEND_BUS_RESET (1 << 0) +#define USBHS_SEND_BUS_SUSPEND (1 << 1) +#define USBHS_SEND_BUS_RESUME (1 << 2) +#define USBHS_REMOTE_WAKE (1 << 3) +#define USBHS_PHY_SUSPENDM (1 << 4) +#define USBHS_UH_SOFT_FREE (1 << 6) +#define USBHS_SEND_SOF_EN (1 << 7) + +//UH_CONFIG +#define USBHS_HOST_TX_EN (1 << 3) +#define USBHS_HOST_RX_EN (1 << 18) + +// HOST_EP_TYPE +#define USBHS_ENDP_TX_ISO (1 << 3) +#define USBHS_ENDP_RX_ISO (1 << (16 + 2)) + +// R32_UH_EP_PID +#define USBHS_HOST_MASK_TOKEN (0x0f) +#define USBHS_HOST_MASK_ENDP (0x0f << 4) + +//R8_UH_RX_CTRL +#define USBHS_EP_R_RES_MASK (3 << 0) +#define USBHS_EP_R_RES_ACK (0 << 0) +#define USBHS_EP_R_RES_NYET (1 << 0) +#define USBHS_EP_R_RES_NAK (2 << 0) +#define USBHS_EP_R_RES_STALL (3 << 0) + +#define USBHS_UH_R_RES_NO (1 << 2) +#define USBHS_UH_R_TOG_1 (1 << 3) +#define USBHS_UH_R_TOG_2 (2 << 3) +#define USBHS_UH_R_TOG_3 (3 << 3) +#define USBHS_UH_R_TOG_AUTO (1 << 5) +#define USBHS_UH_R_DATA_NO (1 << 6) +//R8_UH_TX_CTRL +#define USBHS_UH_T_RES_MASK (3 << 0) +#define USBHS_UH_T_RES_ACK (0 << 0) +#define USBHS_UH_T_RES_NYET (1 << 0) +#define USBHS_UH_T_RES_NAK (2 << 0) +#define USBHS_UH_T_RES_STALL (3 << 0) + +#define USBHS_UH_T_RES_NO (1 << 2) +#define USBHS_UH_T_TOG_1 (1 << 3) +#define USBHS_UH_T_TOG_2 (2 << 3) +#define USBHS_UH_T_TOG_3 (3 << 3) +#define USBHS_UH_T_TOG_AUTO (1 << 5) +#define USBHS_UH_T_DATA_NO (1 << 6) + + +#endif diff --git a/libraries/Adafruit_TinyUSB_Arduino/src/portable/wch/dcd_ch32_usbfs.c b/libraries/Adafruit_TinyUSB_Arduino/src/portable/wch/dcd_ch32_usbfs.c new file mode 100644 index 0000000..5cd25e3 --- /dev/null +++ b/libraries/Adafruit_TinyUSB_Arduino/src/portable/wch/dcd_ch32_usbfs.c @@ -0,0 +1,355 @@ +/* + * The MIT License (MIT) + * + * Copyright (c) 2024 Matthew Tran + * Copyright (c) 2024 hathach + * + * 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. + */ + +#include "tusb_option.h" + +#if CFG_TUD_ENABLED && defined(TUP_USBIP_WCH_USBFS) && CFG_TUD_WCH_USBIP_USBFS + +#include "device/dcd.h" +#include "ch32_usbfs_reg.h" + +/* private defines */ +#define EP_MAX (8) + +#define EP_DMA(ep) ((&USBOTG_FS->UEP0_DMA)[ep]) +#define EP_TX_LEN(ep) ((&USBOTG_FS->UEP0_TX_LEN)[2 * ep]) +#define EP_TX_CTRL(ep) ((&USBOTG_FS->UEP0_TX_CTRL)[4 * ep]) +#define EP_RX_CTRL(ep) ((&USBOTG_FS->UEP0_RX_CTRL)[4 * ep]) + +/* private data */ +struct usb_xfer { + bool valid; + uint8_t* buffer; + size_t len; + size_t processed_len; + size_t max_size; +}; + +static struct { + bool ep0_tog; + bool isochronous[EP_MAX]; + struct usb_xfer xfer[EP_MAX][2]; + TU_ATTR_ALIGNED(4) uint8_t buffer[EP_MAX][2][64]; + TU_ATTR_ALIGNED(4) struct { + // OUT transfers >64 bytes will overwrite queued IN data! + uint8_t out[64]; + uint8_t in[1023]; + uint8_t pad; + } ep3_buffer; +} data; + +/* private helpers */ +static void update_in(uint8_t rhport, uint8_t ep, bool force) { + struct usb_xfer* xfer = &data.xfer[ep][TUSB_DIR_IN]; + if (xfer->valid) { + if (force || xfer->len) { + size_t len = TU_MIN(xfer->max_size, xfer->len); + if (ep == 0) { + memcpy(data.buffer[ep][TUSB_DIR_OUT], xfer->buffer, len); // ep0 uses same chunk + } else if (ep == 3) { + memcpy(data.ep3_buffer.in, xfer->buffer, len); + } else { + memcpy(data.buffer[ep][TUSB_DIR_IN], xfer->buffer, len); + } + xfer->buffer += len; + xfer->len -= len; + xfer->processed_len += len; + + EP_TX_LEN(ep) = len; + if (ep == 0) { + EP_TX_CTRL(0) = USBFS_EP_T_RES_ACK | (data.ep0_tog ? USBFS_EP_T_TOG : 0); + data.ep0_tog = !data.ep0_tog; + } else if (data.isochronous[ep]) { + EP_TX_CTRL(ep) = (EP_TX_CTRL(ep) & ~(USBFS_EP_T_RES_MASK)) | USBFS_EP_T_RES_NYET; + } else { + EP_TX_CTRL(ep) = (EP_TX_CTRL(ep) & ~(USBFS_EP_T_RES_MASK)) | USBFS_EP_T_RES_ACK; + } + } else { + xfer->valid = false; + EP_TX_CTRL(ep) = (EP_TX_CTRL(ep) & ~(USBFS_EP_T_RES_MASK)) | USBFS_EP_T_RES_NAK; + dcd_event_xfer_complete( + rhport, ep | TUSB_DIR_IN_MASK, xfer->processed_len, + XFER_RESULT_SUCCESS, true); + } + } +} + +static void update_out(uint8_t rhport, uint8_t ep, size_t rx_len) { + struct usb_xfer* xfer = &data.xfer[ep][TUSB_DIR_OUT]; + if (xfer->valid) { + size_t len = TU_MIN(xfer->max_size, TU_MIN(xfer->len, rx_len)); + if (ep == 3) { + memcpy(xfer->buffer, data.ep3_buffer.out, len); + } else { + memcpy(xfer->buffer, data.buffer[ep][TUSB_DIR_OUT], len); + } + xfer->buffer += len; + xfer->len -= len; + xfer->processed_len += len; + + if (xfer->len == 0 || len < xfer->max_size) { + xfer->valid = false; + dcd_event_xfer_complete(rhport, ep, xfer->processed_len, XFER_RESULT_SUCCESS, true); + } + + if (ep == 0) { + EP_RX_CTRL(0) = USBFS_EP_R_RES_ACK; + } + } +} + +/* public functions */ +bool dcd_init(uint8_t rhport, const tusb_rhport_init_t* rh_init) { + (void) rh_init; + // init registers + USBOTG_FS->BASE_CTRL = USBFS_CTRL_SYS_CTRL | USBFS_CTRL_INT_BUSY | USBFS_CTRL_DMA_EN; + USBOTG_FS->UDEV_CTRL = USBFS_UDEV_CTRL_PD_DIS | USBFS_UDEV_CTRL_PORT_EN; + USBOTG_FS->DEV_ADDR = 0x00; + + USBOTG_FS->INT_FG = 0xFF; + USBOTG_FS->INT_EN = USBFS_INT_EN_BUS_RST | USBFS_INT_EN_TRANSFER | USBFS_INT_EN_SUSPEND; + + // setup endpoint 0 + EP_DMA(0) = (uint32_t) &data.buffer[0][0]; + EP_TX_LEN(0) = 0; + EP_TX_CTRL(0) = USBFS_EP_T_RES_NAK; + EP_RX_CTRL(0) = USBFS_EP_R_RES_ACK; + + // enable other endpoints but NAK everything + USBOTG_FS->UEP4_1_MOD = 0xCC; + USBOTG_FS->UEP2_3_MOD = 0xCC; + USBOTG_FS->UEP5_6_MOD = 0xCC; + USBOTG_FS->UEP7_MOD = 0x0C; + + for (uint8_t ep = 1; ep < EP_MAX; ep++) { + EP_DMA(ep) = (uint32_t) &data.buffer[ep][0]; + EP_TX_LEN(ep) = 0; + EP_TX_CTRL(ep) = USBFS_EP_T_AUTO_TOG | USBFS_EP_T_RES_NAK; + EP_RX_CTRL(ep) = USBFS_EP_R_AUTO_TOG | USBFS_EP_R_RES_NAK; + } + EP_DMA(3) = (uint32_t) &data.ep3_buffer.out[0]; + + dcd_connect(rhport); + + return true; +} + +void dcd_int_handler(uint8_t rhport) { + (void) rhport; + uint8_t status = USBOTG_FS->INT_FG; + if (status & USBFS_INT_FG_TRANSFER) { + uint8_t ep = USBFS_INT_ST_MASK_UIS_ENDP(USBOTG_FS->INT_ST); + uint8_t token = USBFS_INT_ST_MASK_UIS_TOKEN(USBOTG_FS->INT_ST); + + switch (token) { + case PID_OUT: { + uint16_t rx_len = USBOTG_FS->RX_LEN; + update_out(rhport, ep, rx_len); + break; + } + + case PID_IN: + update_in(rhport, ep, false); + break; + + case PID_SETUP: + // setup clears stall + EP_TX_CTRL(0) = USBFS_EP_T_RES_NAK; + EP_RX_CTRL(0) = USBFS_EP_R_RES_ACK; + + data.ep0_tog = true; + dcd_event_setup_received(rhport, &data.buffer[0][TUSB_DIR_OUT][0], true); + break; + } + + USBOTG_FS->INT_FG = USBFS_INT_FG_TRANSFER; + } else if (status & USBFS_INT_FG_BUS_RST) { + data.ep0_tog = true; + data.xfer[0][TUSB_DIR_OUT].max_size = 64; + data.xfer[0][TUSB_DIR_IN].max_size = 64; + + //dcd_event_bus_reset(rhport, (USBOTG_FS->BASE_CTRL & USBFS_CTRL_LOW_SPEED) ? TUSB_SPEED_LOW : TUSB_SPEED_FULL, true); + dcd_event_bus_reset(rhport, (USBOTG_FS->UDEV_CTRL & USBFS_UDEV_CTRL_LOW_SPEED) ? TUSB_SPEED_LOW : TUSB_SPEED_FULL, true); + + USBOTG_FS->DEV_ADDR = 0x00; + EP_RX_CTRL(0) = USBFS_EP_R_RES_ACK; + + USBOTG_FS->INT_FG = USBFS_INT_FG_BUS_RST; + } else if (status & USBFS_INT_FG_SUSPEND) { + dcd_event_t event = {.rhport = rhport, .event_id = DCD_EVENT_SUSPEND}; + dcd_event_handler(&event, true); + USBOTG_FS->INT_FG = USBFS_INT_FG_SUSPEND; + } +} + +void dcd_int_enable(uint8_t rhport) { + (void) rhport; + NVIC_EnableIRQ(USBHD_IRQn); +} + +void dcd_int_disable(uint8_t rhport) { + (void) rhport; + NVIC_DisableIRQ(USBHD_IRQn); +} + +void dcd_set_address(uint8_t rhport, uint8_t dev_addr) { + (void) dev_addr; + dcd_edpt_xfer(rhport, 0x80, NULL, 0, false); // zlp status response +} + +void dcd_remote_wakeup(uint8_t rhport) { + (void) rhport; + // TODO optional +} + +void dcd_connect(uint8_t rhport) { + (void) rhport; + USBOTG_FS->BASE_CTRL |= USBFS_CTRL_DEV_PUEN; +} + +void dcd_disconnect(uint8_t rhport) { + (void) rhport; + USBOTG_FS->BASE_CTRL &= ~USBFS_CTRL_DEV_PUEN; +} + +void dcd_sof_enable(uint8_t rhport, bool en) { + (void) rhport; + (void) en; + + // TODO implement later +} + +void dcd_edpt0_status_complete(uint8_t rhport, tusb_control_request_t const* request) { + (void) rhport; + if (request->bmRequestType_bit.recipient == TUSB_REQ_RCPT_DEVICE && + request->bmRequestType_bit.type == TUSB_REQ_TYPE_STANDARD && + request->bRequest == TUSB_REQ_SET_ADDRESS) { + USBOTG_FS->DEV_ADDR = (uint8_t) request->wValue; + } + EP_TX_CTRL(0) = USBFS_EP_T_RES_NAK; + EP_RX_CTRL(0) = USBFS_EP_R_RES_ACK; +} + +bool dcd_edpt_open(uint8_t rhport, tusb_desc_endpoint_t const* desc_ep) { + (void) rhport; + uint8_t ep = tu_edpt_number(desc_ep->bEndpointAddress); + uint8_t dir = tu_edpt_dir(desc_ep->bEndpointAddress); + TU_ASSERT(ep < EP_MAX); + + data.isochronous[ep] = desc_ep->bmAttributes.xfer == TUSB_XFER_ISOCHRONOUS; + data.xfer[ep][dir].max_size = tu_edpt_packet_size(desc_ep); + + if (ep != 0) { + if (dir == TUSB_DIR_OUT) { + if (data.isochronous[ep]) { + EP_RX_CTRL(ep) = USBFS_EP_R_AUTO_TOG | USBFS_EP_R_RES_NYET; + } else { + EP_RX_CTRL(ep) = USBFS_EP_R_AUTO_TOG | USBFS_EP_R_RES_ACK; + } + } else { + EP_TX_LEN(ep) = 0; + EP_TX_CTRL(ep) = USBFS_EP_T_AUTO_TOG | USBFS_EP_T_RES_NAK; + } + } + return true; +} + +void dcd_edpt_close_all(uint8_t rhport) { + (void) rhport; + // TODO optional +} + +bool dcd_edpt_iso_alloc(uint8_t rhport, uint8_t ep_addr, uint16_t largest_packet_size) { + (void) rhport; + (void) ep_addr; + (void)largest_packet_size; + return false; +} + +bool dcd_edpt_iso_activate(uint8_t rhport, const tusb_desc_endpoint_t *desc_ep) { + (void)rhport; + (void)desc_ep; + return false; +} + +bool dcd_edpt_xfer(uint8_t rhport, uint8_t ep_addr, uint8_t * buffer, uint16_t total_bytes, bool is_isr) { + (void) is_isr; + (void) rhport; + uint8_t ep = tu_edpt_number(ep_addr); + uint8_t dir = tu_edpt_dir(ep_addr); + + struct usb_xfer* xfer = &data.xfer[ep][dir]; + dcd_int_disable(rhport); + xfer->valid = true; + xfer->buffer = buffer; + xfer->len = total_bytes; + xfer->processed_len = 0; + dcd_int_enable(rhport); + + if (dir == TUSB_DIR_IN) { + update_in(rhport, ep, true); + } + return true; +} + +void dcd_edpt_stall(uint8_t rhport, uint8_t ep_addr) { + (void) rhport; + uint8_t ep = tu_edpt_number(ep_addr); + uint8_t dir = tu_edpt_dir(ep_addr); + if (ep == 0) { + if (dir == TUSB_DIR_OUT) { + EP_RX_CTRL(0) = USBFS_EP_R_RES_STALL; + } else { + EP_TX_LEN(0) = 0; + EP_TX_CTRL(0) = USBFS_EP_T_RES_STALL; + } + } else { + if (dir == TUSB_DIR_OUT) { + EP_RX_CTRL(ep) = (EP_RX_CTRL(ep) & ~USBFS_EP_R_RES_MASK) | USBFS_EP_R_RES_STALL; + } else { + EP_TX_CTRL(ep) = (EP_TX_CTRL(ep) & ~USBFS_EP_T_RES_MASK) | USBFS_EP_T_RES_STALL; + } + } +} + +void dcd_edpt_clear_stall(uint8_t rhport, uint8_t ep_addr) { + (void) rhport; + uint8_t ep = tu_edpt_number(ep_addr); + uint8_t dir = tu_edpt_dir(ep_addr); + if (ep == 0) { + if (dir == TUSB_DIR_OUT) { + EP_RX_CTRL(0) = USBFS_EP_R_RES_ACK; + } + } else { + if (dir == TUSB_DIR_OUT) { + EP_RX_CTRL(ep) = (EP_RX_CTRL(ep) & ~(USBFS_EP_R_RES_MASK | USBFS_EP_R_TOG)) | USBFS_EP_R_RES_ACK; + } else { + EP_TX_CTRL(ep) = (EP_TX_CTRL(ep) & ~(USBFS_EP_T_RES_MASK | USBFS_EP_T_TOG)) | USBFS_EP_T_RES_NAK; + } + } +} +#endif diff --git a/libraries/Adafruit_TinyUSB_Arduino/src/portable/wch/dcd_ch32_usbhs.c b/libraries/Adafruit_TinyUSB_Arduino/src/portable/wch/dcd_ch32_usbhs.c new file mode 100644 index 0000000..11734de --- /dev/null +++ b/libraries/Adafruit_TinyUSB_Arduino/src/portable/wch/dcd_ch32_usbhs.c @@ -0,0 +1,439 @@ +/* + * The MIT License (MIT) + * + * Copyright (c) 2022 Greg Davill + * Copyright (c) 2023 Denis Krasutski + * + * 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. + */ + +#include "tusb_option.h" + +#if CFG_TUD_ENABLED && defined(TUP_USBIP_WCH_USBHS) && defined(CFG_TUD_WCH_USBIP_USBHS) && \ + (CFG_TUD_WCH_USBIP_USBHS == 1) + #include "ch32_usbhs_reg.h" + + #include "device/dcd.h" + + // Max number of bi-directional endpoints including EP0 + #define EP_MAX 16 + +typedef struct { + uint8_t* buffer; + uint16_t total_len; + uint16_t queued_len; + uint16_t max_size; + bool is_last_packet; + bool is_iso; +} xfer_ctl_t; + +typedef enum { + EP_RESPONSE_ACK, + EP_RESPONSE_NAK, +} ep_response_list_t; + +#define XFER_CTL_BASE(_ep, _dir) &xfer_status[_ep][_dir] +static xfer_ctl_t xfer_status[EP_MAX][2]; + +#define EP_TX_LEN(ep) *(volatile uint16_t *)((volatile uint16_t *)&(USBHSD->UEP0_TX_LEN) + (ep) * 2) +#define EP_TX_CTRL(ep) *(volatile uint8_t *)((volatile uint8_t *)&(USBHSD->UEP0_TX_CTRL) + (ep) * 4) +#define EP_RX_CTRL(ep) *(volatile uint8_t *)((volatile uint8_t *)&(USBHSD->UEP0_RX_CTRL) + (ep) * 4) +#define EP_RX_MAX_LEN(ep) *(volatile uint16_t *)((volatile uint16_t *)&(USBHSD->UEP0_MAX_LEN) + (ep) * 2) + +#define EP_TX_DMA_ADDR(ep) *(volatile uint32_t *)((volatile uint32_t *)&(USBHSD->UEP1_TX_DMA) + (ep - 1)) +#define EP_RX_DMA_ADDR(ep) *(volatile uint32_t *)((volatile uint32_t *)&(USBHSD->UEP1_RX_DMA) + (ep - 1)) + +/* Endpoint Buffer */ +TU_ATTR_ALIGNED(4) static uint8_t ep0_buffer[CFG_TUD_ENDPOINT0_SIZE]; + +static void ep_set_response_and_toggle(uint8_t ep_num, tusb_dir_t ep_dir, ep_response_list_t response_type) { + if (ep_dir == TUSB_DIR_IN) { + uint8_t response = (response_type == EP_RESPONSE_ACK) ? USBHS_EP_T_RES_ACK : USBHS_EP_T_RES_NAK; + if (ep_num == 0) { + if (response_type == EP_RESPONSE_ACK) { + if (EP_TX_LEN(ep_num) == 0) { + EP_TX_CTRL(ep_num) |= USBHS_EP_T_TOG_1; + } else { + EP_TX_CTRL(ep_num) ^= USBHS_EP_T_TOG_1; + } + } + } + if (xfer_status[ep_num][TUSB_DIR_IN].is_iso == true) { + EP_TX_CTRL(ep_num) = USBHS_EP_T_AUTOTOG; + } else { + EP_TX_CTRL(ep_num) = (EP_TX_CTRL(ep_num) & ~(USBHS_EP_T_RES_MASK)) | response; + } + } else { + uint8_t response = (response_type == EP_RESPONSE_ACK) ? USBHS_EP_R_RES_ACK : USBHS_EP_R_RES_NAK; + if (ep_num == 0) { + if (response_type == EP_RESPONSE_ACK) { + if (xfer_status[ep_num][TUSB_DIR_OUT].queued_len == 0) { + EP_RX_CTRL(ep_num) |= USBHS_EP_R_TOG_1; + } + } else { + EP_RX_CTRL(ep_num) ^= USBHS_EP_R_TOG_1; + } + } + EP_RX_CTRL(ep_num) = (EP_RX_CTRL(ep_num) & ~(USBHS_EP_R_RES_MASK)) | response; + } +} + +static void xfer_data_packet(uint8_t ep_num, tusb_dir_t ep_dir, xfer_ctl_t* xfer) { + if (ep_dir == TUSB_DIR_IN) { + uint16_t remaining = xfer->total_len - xfer->queued_len; + uint16_t next_tx_size = TU_MIN(remaining, xfer->max_size); + + if (ep_num == 0) { + memcpy(ep0_buffer, &xfer->buffer[xfer->queued_len], next_tx_size); + } else { + EP_TX_DMA_ADDR(ep_num) = (uint32_t) &xfer->buffer[xfer->queued_len]; + } + + EP_TX_LEN(ep_num) = next_tx_size; + xfer->queued_len += next_tx_size; + if (xfer->queued_len == xfer->total_len) { + xfer->is_last_packet = true; + } + if (xfer->is_iso == true) { + /* Enable EP to generate ISA_ACT interrupt */ + USBHSD->ENDP_CONFIG |= (USBHS_EP0_T_EN << ep_num); + } + } else { /* TUSB_DIR_OUT */ + uint16_t left_to_receive = xfer->total_len - xfer->queued_len; + uint16_t max_possible_rx_size = TU_MIN(xfer->max_size, left_to_receive); + + if (max_possible_rx_size == left_to_receive) { + xfer->is_last_packet = true; + } + + if (ep_num > 0) { + EP_RX_DMA_ADDR(ep_num) = (uint32_t) &xfer->buffer[xfer->queued_len]; + EP_RX_MAX_LEN(ep_num) = max_possible_rx_size; + } + } + ep_set_response_and_toggle(ep_num, ep_dir, USBHS_EP_R_RES_ACK); +} + +bool dcd_init(uint8_t rhport, const tusb_rhport_init_t* rh_init) { + (void) rhport; + (void) rh_init; + + memset(&xfer_status, 0, sizeof(xfer_status)); + + USBHSD->HOST_CTRL = 0x00; + USBHSD->HOST_CTRL = USBHS_PHY_SUSPENDM; + + USBHSD->CONTROL = 0; + +#if TUD_OPT_HIGH_SPEED + USBHSD->CONTROL = USBHS_DMA_EN | USBHS_INT_BUSY_EN | USBHS_HIGH_SPEED; +#else + #error OPT_MODE_FULL_SPEED not currently supported on CH32 + USBHSD->CONTROL = USBHS_DMA_EN | USBHS_INT_BUSY_EN | USBHS_FULL_SPEED; +#endif + + USBHSD->INT_EN = 0; + USBHSD->INT_EN = USBHS_SETUP_ACT_EN | USBHS_TRANSFER_EN | USBHS_BUS_RST_EN | USBHS_SUSPEND_EN | USBHS_ISO_ACT_EN; + + USBHSD->ENDP_CONFIG = USBHS_EP0_T_EN | USBHS_EP0_R_EN; + USBHSD->ENDP_TYPE = 0x00; + USBHSD->BUF_MODE = 0x00; + + for (int ep = 0; ep < EP_MAX; ep++) { + EP_TX_LEN(ep) = 0; + EP_TX_CTRL(ep) = USBHS_EP_T_AUTOTOG | USBHS_EP_T_RES_NAK; + EP_RX_CTRL(ep) = USBHS_EP_R_AUTOTOG | USBHS_EP_R_RES_NAK; + + EP_RX_MAX_LEN(ep) = 0; + } + + USBHSD->UEP0_DMA = (uint32_t) ep0_buffer; + USBHSD->UEP0_MAX_LEN = CFG_TUD_ENDPOINT0_SIZE; + xfer_status[0][TUSB_DIR_OUT].max_size = CFG_TUD_ENDPOINT0_SIZE; + xfer_status[0][TUSB_DIR_IN].max_size = CFG_TUD_ENDPOINT0_SIZE; + + USBHSD->DEV_AD = 0; + USBHSD->CONTROL |= USBHS_DEV_PU_EN; + + return true; +} + +void dcd_int_enable(uint8_t rhport) { + (void) rhport; + NVIC_EnableIRQ(USBHS_IRQn); +} + +void dcd_int_disable(uint8_t rhport) { + (void) rhport; + NVIC_DisableIRQ(USBHS_IRQn); +} + +void dcd_edpt_close_all(uint8_t rhport) { + (void) rhport; + + for (size_t ep = 1; ep < EP_MAX; ep++) { + EP_TX_LEN(ep) = 0; + EP_TX_CTRL(ep) = USBHS_EP_T_AUTOTOG | USBHS_EP_T_RES_NAK; + EP_RX_CTRL(ep) = USBHS_EP_R_AUTOTOG | USBHS_EP_R_RES_NAK; + + EP_RX_MAX_LEN(ep) = 0; + } + + USBHSD->ENDP_CONFIG = USBHS_EP0_T_EN | USBHS_EP0_R_EN; +} + +void dcd_set_address(uint8_t rhport, uint8_t dev_addr) { + (void) dev_addr; + + // Response with zlp status + dcd_edpt_xfer(rhport, 0x80, NULL, 0, false); +} + +void dcd_remote_wakeup(uint8_t rhport) { + (void) rhport; +} + +void dcd_sof_enable(uint8_t rhport, bool en) { + (void) rhport; + if (en) { + USBHSD->INT_EN |= USBHS_SOF_ACT_EN; + } else { + USBHSD->INT_EN &= ~(USBHS_SOF_ACT_EN); + } +} + +void dcd_edpt0_status_complete(uint8_t rhport, tusb_control_request_t const* request) { + (void) rhport; + + if (request->bmRequestType_bit.recipient == TUSB_REQ_RCPT_DEVICE && + request->bmRequestType_bit.type == TUSB_REQ_TYPE_STANDARD && + request->bRequest == TUSB_REQ_SET_ADDRESS) { + USBHSD->DEV_AD = (uint8_t) request->wValue; + } + + EP_TX_CTRL(0) = USBHS_EP_T_RES_NAK | USBHS_EP_T_TOG_0; + EP_RX_CTRL(0) = USBHS_EP_R_RES_NAK | USBHS_EP_R_TOG_0; +} + +bool dcd_edpt_open(uint8_t rhport, tusb_desc_endpoint_t const* desc_edpt) { + (void) rhport; + + uint8_t const ep_num = tu_edpt_number(desc_edpt->bEndpointAddress); + tusb_dir_t const dir = tu_edpt_dir(desc_edpt->bEndpointAddress); + + TU_ASSERT(ep_num < EP_MAX); + + if (ep_num == 0) { + return true; + } + + xfer_ctl_t* xfer = XFER_CTL_BASE(ep_num, dir); + xfer->max_size = tu_edpt_packet_size(desc_edpt); + + xfer->is_iso = (desc_edpt->bmAttributes.xfer == TUSB_XFER_ISOCHRONOUS); + if (dir == TUSB_DIR_OUT) { + USBHSD->ENDP_CONFIG |= (USBHS_EP0_R_EN << ep_num); + EP_RX_CTRL(ep_num) = USBHS_EP_R_AUTOTOG | USBHS_EP_R_RES_NAK; + if (xfer->is_iso == true) { + USBHSD->ENDP_TYPE |= (USBHS_EP0_R_TYP << ep_num); + } + EP_RX_MAX_LEN(ep_num) = xfer->max_size; + } else { + if (xfer->is_iso == true) { + USBHSD->ENDP_TYPE |= (USBHS_EP0_T_TYP << ep_num); + } else { + /* Enable all types except Isochronous to avoid ISO_ACT interrupt generation */ + USBHSD->ENDP_CONFIG |= (USBHS_EP0_T_EN << ep_num); + } + EP_TX_LEN(ep_num) = 0; + EP_TX_CTRL(ep_num) = USBHS_EP_T_AUTOTOG | USBHS_EP_T_RES_NAK | USBHS_EP_T_TOG_0; + } + + return true; +} + +void dcd_edpt_close(uint8_t rhport, uint8_t ep_addr) { + (void) rhport; + + uint8_t const ep_num = tu_edpt_number(ep_addr); + tusb_dir_t const dir = tu_edpt_dir(ep_addr); + + if (dir == TUSB_DIR_OUT) { + EP_RX_CTRL(ep_num) = USBHS_EP_R_AUTOTOG | USBHS_EP_R_RES_NAK; + EP_RX_MAX_LEN(ep_num) = 0; + USBHSD->ENDP_TYPE &= ~(USBHS_EP0_R_TYP << ep_num); + USBHSD->ENDP_CONFIG &= ~(USBHS_EP0_R_EN << ep_num); + } else { // TUSB_DIR_IN + EP_TX_CTRL(ep_num) = USBHS_EP_T_AUTOTOG | USBHS_EP_T_RES_NAK | USBHS_EP_T_TOG_0; + EP_TX_LEN(ep_num) = 0; + USBHSD->ENDP_TYPE &= ~(USBHS_EP0_T_TYP << ep_num); + USBHSD->ENDP_CONFIG &= ~(USBHS_EP0_T_EN << ep_num); + } +} + + #if 0 +bool dcd_edpt_iso_alloc(uint8_t rhport, uint8_t ep_addr, uint16_t largest_packet_size) { + (void) rhport; + (void) ep_addr; + (void) largest_packet_size; + return false; +} + +bool dcd_edpt_iso_activate(uint8_t rhport, tusb_desc_endpoint_t const * desc_ep) { + (void) rhport; + (void) desc_ep; + return false; +} + #endif + +void dcd_edpt_stall(uint8_t rhport, uint8_t ep_addr) { + (void) rhport; + + uint8_t const ep_num = tu_edpt_number(ep_addr); + tusb_dir_t const dir = tu_edpt_dir(ep_addr); + + if (dir == TUSB_DIR_OUT) { + EP_RX_CTRL(ep_num) = USBHS_EP_R_RES_STALL; + } else { + EP_TX_LEN(0) = 0; + EP_TX_CTRL(ep_num) = USBHS_EP_T_RES_STALL; + } +} + +void dcd_edpt_clear_stall(uint8_t rhport, uint8_t ep_addr) { + (void) rhport; + + uint8_t const ep_num = tu_edpt_number(ep_addr); + tusb_dir_t const dir = tu_edpt_dir(ep_addr); + + if (dir == TUSB_DIR_OUT) { + EP_RX_CTRL(ep_num) = USBHS_EP_R_AUTOTOG | USBHS_EP_R_RES_NAK; + } else { + EP_TX_CTRL(ep_num) = USBHS_EP_T_AUTOTOG | USBHS_EP_R_RES_NAK; + } +} + +bool dcd_edpt_xfer(uint8_t rhport, uint8_t ep_addr, uint8_t * buffer, uint16_t total_bytes, bool is_isr) { + (void) is_isr; + (void) rhport; + uint8_t const ep_num = tu_edpt_number(ep_addr); + tusb_dir_t const dir = tu_edpt_dir(ep_addr); + + xfer_ctl_t* xfer = XFER_CTL_BASE(ep_num, dir); + xfer->buffer = buffer; + xfer->total_len = total_bytes; + xfer->queued_len = 0; + xfer->is_last_packet = false; + + xfer_data_packet(ep_num, dir, xfer); + + return true; +} + +void dcd_int_handler(uint8_t rhport) { + (void) rhport; + + uint8_t int_flag = USBHSD->INT_FG; + uint8_t int_status = USBHSD->INT_ST; + + if (int_flag & (USBHS_ISO_ACT_FLAG | USBHS_TRANSFER_FLAG)) { + uint8_t const token = int_status & MASK_UIS_TOKEN; + + if (token == USBHS_TOKEN_PID_SOF) { + uint32_t frame_count = USBHSD->FRAME_NO & USBHS_FRAME_NO_NUM_MASK; + dcd_event_sof(rhport, frame_count, true); + }else { + uint8_t const ep_num = int_status & MASK_UIS_ENDP; + tusb_dir_t const ep_dir = (token == USBHS_TOKEN_PID_IN) ? TUSB_DIR_IN : TUSB_DIR_OUT; + uint8_t const ep_addr = tu_edpt_addr(ep_num, ep_dir); + xfer_ctl_t* xfer = XFER_CTL_BASE(ep_num, ep_dir); + + if (token == USBHS_TOKEN_PID_OUT) { + uint16_t rx_len = USBHSD->RX_LEN; + + if (ep_num == 0) { + memcpy(&xfer->buffer[xfer->queued_len], ep0_buffer, rx_len); + } + + xfer->queued_len += rx_len; + if (rx_len < xfer->max_size) { + xfer->is_last_packet = true; + } + } else if (token == USBHS_TOKEN_PID_IN) { + if (xfer->is_iso && xfer->is_last_packet) { + /* Disable EP to avoid ISO_ACT interrupt generation */ + USBHSD->ENDP_CONFIG &= ~(USBHS_EP0_T_EN << ep_num); + } else { + // Do nothing, no need to update xfer->is_last_packet, it is already updated in xfer_data_packet + } + } + + if (xfer->is_last_packet == true) { + ep_set_response_and_toggle(ep_num, ep_dir, EP_RESPONSE_NAK); + dcd_event_xfer_complete(0, ep_addr, xfer->queued_len, XFER_RESULT_SUCCESS, true); + } else { + /* prepare next part of packet to xref */ + xfer_data_packet(ep_num, ep_dir, xfer); + } + } + + USBHSD->INT_FG = (int_flag & (USBHS_ISO_ACT_FLAG | USBHS_TRANSFER_FLAG)); /* Clear flag */ + } else if (int_flag & USBHS_SETUP_FLAG) { + ep_set_response_and_toggle(0, TUSB_DIR_IN, EP_RESPONSE_NAK); + ep_set_response_and_toggle(0, TUSB_DIR_OUT, EP_RESPONSE_NAK); + dcd_event_setup_received(0, ep0_buffer, true); + + USBHSD->INT_FG = USBHS_SETUP_FLAG; /* Clear flag */ + } else if (int_flag & USBHS_BUS_RST_FLAG) { + // TODO CH32 does not detect actual speed at this time (should be known at end of reset) + // This interrupt probably triggered at start of bus reset +// tusb_speed_t actual_speed; +// switch(USBHSD->SPEED_TYPE & USBHS_SPEED_TYPE_MASK){ +// case USBHS_SPEED_TYPE_HIGH: +// actual_speed = TUSB_SPEED_HIGH; +// break; +// case USBHS_SPEED_TYPE_FULL: +// actual_speed = TUSB_SPEED_FULL; +// break; +// case USBHS_SPEED_TYPE_LOW: +// actual_speed = TUSB_SPEED_LOW; +// break; +// default: +// TU_ASSERT(0,); +// break; +// } +// dcd_event_bus_reset(0, actual_speed, true); + + dcd_event_bus_reset(0, TUSB_SPEED_HIGH, true); + + USBHSD->DEV_AD = 0; + EP_RX_CTRL(0) = USBHS_EP_R_RES_ACK | USBHS_EP_R_TOG_0; + EP_TX_CTRL(0) = USBHS_EP_T_RES_NAK | USBHS_EP_T_TOG_0; + + USBHSD->INT_FG = USBHS_BUS_RST_FLAG; /* Clear flag */ + } else if (int_flag & USBHS_SUSPEND_FLAG) { + dcd_event_t event = {.rhport = rhport, .event_id = DCD_EVENT_SUSPEND}; + dcd_event_handler(&event, true); + + USBHSD->INT_FG = USBHS_SUSPEND_FLAG; /* Clear flag */ + } +} +#endif diff --git a/libraries/Adafruit_TinyUSB_Arduino/src/portable/wch/hcd_ch32_usbfs.c b/libraries/Adafruit_TinyUSB_Arduino/src/portable/wch/hcd_ch32_usbfs.c new file mode 100644 index 0000000..7bbf122 --- /dev/null +++ b/libraries/Adafruit_TinyUSB_Arduino/src/portable/wch/hcd_ch32_usbfs.c @@ -0,0 +1,679 @@ +/* + * The MIT License (MIT) + * + * Copyright (c) 2024 Mitsumine Suzu (verylowfreq) + * + * 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. + */ + +#include "tusb_option.h" + +#if CFG_TUH_ENABLED && defined(TUP_USBIP_WCH_USBFS) && defined(CFG_TUH_WCH_USBIP_USBFS) && CFG_TUH_WCH_USBIP_USBFS + +#include + +#include "host/hcd.h" +#include "host/usbh.h" +#include "host/usbh_pvt.h" + +#include "bsp/board_api.h" + +#ifdef __GNUC__ +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wstrict-prototypes" +#endif + +#include "ch32v20x.h" + +#ifdef __GNUC__ +#pragma GCC diagnostic pop +#endif + +#include "ch32v20x_usb.h" + +#define USBFS_RX_BUF_LEN 64 +#define USBFS_TX_BUF_LEN 64 +TU_ATTR_ALIGNED(4) static uint8_t USBFS_RX_Buf[USBFS_RX_BUF_LEN]; +TU_ATTR_ALIGNED(4) static uint8_t USBFS_TX_Buf[USBFS_TX_BUF_LEN]; + +#define USB_XFER_TIMEOUT_MILLIS 100 +// #define USB_INTERRUPT_XFER_TIMEOUT_MILLIS 1 + +#define PANIC(...) \ + do { \ + printf("%s() L%d: ", __func__, __LINE__); \ + printf("\r\n[PANIC] " __VA_ARGS__); \ + while (true) {} \ + } while (false) + +#define LOG_CH32_USBFSH(...) TU_LOG3(__VA_ARGS__) + +// Busywait for delay microseconds/nanoseconds +TU_ATTR_ALWAYS_INLINE static inline void loopdelay(uint32_t count) { + volatile uint32_t c = count / 3; + if (c == 0) { return; } + // while (c-- != 0); + asm volatile( + "1: \n" // loop label + " addi %0, %0, -1 \n" // c-- + " bne %0, zero, 1b \n" // if (c != 0) goto loop + : "+r"(c) // c is input/output operand + ); +} + +// Endpoint status +typedef struct usb_edpt { + // Is this a valid struct + bool configured; + + uint8_t dev_addr; + uint8_t ep_addr; + uint8_t max_packet_size; + + uint8_t xfer_type; + + // Data toggle (0 or not 0) for DATA0/1 + uint8_t data_toggle; + + bool is_nak_pending; + uint16_t buflen; + uint8_t* buf; +} usb_edpt_t; + +static usb_edpt_t usb_edpt_list[CFG_TUH_DEVICE_MAX * 6] = {}; + +typedef struct usb_current_xfer_st { + bool is_busy; + uint8_t dev_addr; + uint8_t ep_addr; + // Xfer started time in millis for timeout + uint32_t start_ms; + uint8_t *buffer; + uint16_t bufferlen; + uint16_t xferred_len; + bool nak_pending; +} usb_current_xfer_t; + +static volatile usb_current_xfer_t usb_current_xfer_info = {}; + +static usb_edpt_t *get_edpt_record(uint8_t dev_addr, uint8_t ep_addr) { + for (size_t i = 0; i < TU_ARRAY_SIZE(usb_edpt_list); i++) { + usb_edpt_t *cur = &usb_edpt_list[i]; + if (cur->configured && cur->dev_addr == dev_addr && cur->ep_addr == ep_addr) { + return cur; + } + } + return NULL; +} + +static usb_edpt_t *get_empty_record_slot(void) { + for (size_t i = 0; i < TU_ARRAY_SIZE(usb_edpt_list); i++) { + if (!usb_edpt_list[i].configured) { + return &usb_edpt_list[i]; + } + } + return NULL; +} + +static usb_edpt_t *add_edpt_record(uint8_t dev_addr, uint8_t ep_addr, uint16_t max_packet_size, uint8_t xfer_type) { + usb_edpt_t *slot = get_empty_record_slot(); + TU_ASSERT(slot != NULL, NULL); + + slot->dev_addr = dev_addr; + slot->ep_addr = ep_addr; + slot->max_packet_size = max_packet_size; + slot->xfer_type = xfer_type; + slot->data_toggle = 0; + slot->is_nak_pending = false; + slot->buflen = 0; + slot->buf = NULL; + + slot->configured = true; + + return slot; +} + +static usb_edpt_t *get_or_add_edpt_record(uint8_t dev_addr, uint8_t ep_addr, uint16_t max_packet_size, uint8_t xfer_type) { + usb_edpt_t *ret = get_edpt_record(dev_addr, ep_addr); + if (ret != NULL) { + return ret; + } else { + return add_edpt_record(dev_addr, ep_addr, max_packet_size, xfer_type); + } +} + +static void remove_edpt_record_for_device(uint8_t dev_addr) { + for (size_t i = 0; i < TU_ARRAY_SIZE(usb_edpt_list); i++) { + if (usb_edpt_list[i].configured && usb_edpt_list[i].dev_addr == dev_addr) { + usb_edpt_list[i].configured = false; + } + } +} + +// static void dump_edpt_record_list() { +// for (size_t i = 0; i < TU_ARRAY_SIZE(usb_edpt_list); i++) { +// usb_edpt_t* cur = &usb_edpt_list[i]; +// if (cur->configured) { +// printf("[%2d] Device 0x%02x Endpoint 0x%02x\r\n", i, cur->dev_addr, cur->ep_addr); +// } else { +// printf("[%2d] not configured\r\n", i); +// } +// } +// } + +static bool interrupt_enabled = false; + +/** Enable or disable USBFS Host function */ +static void hardware_init_host(bool enabled) { + // Reset USBOTG module + USBOTG_H_FS->BASE_CTRL = USBFS_UC_RESET_SIE | USBFS_UC_CLR_ALL; + + tusb_time_delay_ms_api(1); + USBOTG_H_FS->BASE_CTRL = 0; + + if (!enabled) { + // Disable all feature + USBOTG_H_FS->BASE_CTRL = 0; + } else { + // Enable USB Host features + // NVIC_DisableIRQ(USBFS_IRQn); + hcd_int_disable(0); + USBOTG_H_FS->BASE_CTRL = USBFS_UC_HOST_MODE | USBFS_UC_INT_BUSY | USBFS_UC_DMA_EN; + USBOTG_H_FS->HOST_EP_MOD = USBFS_UH_EP_TX_EN | USBFS_UH_EP_RX_EN; + USBOTG_H_FS->HOST_RX_DMA = (uint32_t) USBFS_RX_Buf; + USBOTG_H_FS->HOST_TX_DMA = (uint32_t) USBFS_TX_Buf; + // USBOTG_H_FS->INT_EN = USBFS_UIE_TRANSFER | USBFS_UIE_DETECT; + USBOTG_H_FS->INT_EN = USBFS_UIE_DETECT; + } +} + +static bool hardware_start_xfer(uint8_t pid, uint8_t ep_addr, uint8_t data_toggle) { + LOG_CH32_USBFSH("hardware_start_xfer(pid=%s(0x%02x), ep_addr=0x%02x, toggle=%d)\r\n", + pid == USB_PID_IN ? "IN" : pid == USB_PID_OUT ? "OUT" + : pid == USB_PID_SETUP ? "SETUP" + : "(other)", + pid, ep_addr, data_toggle); + + //WORKAROUND: For LowSpeed device, insert small delay + bool is_lowspeed_device = tuh_speed_get(usb_current_xfer_info.dev_addr) == TUSB_SPEED_LOW; + if (is_lowspeed_device) { + //NOTE: worked -> SystemCoreClock / 1000000 * 50, 25 + // NOT worked -> 20 and less (at 144MHz internal clock) + loopdelay(SystemCoreClock / 1000000 * 40); + } + + uint8_t pid_edpt = (pid << 4) | (tu_edpt_number(ep_addr) & 0x0f); + USBOTG_H_FS->HOST_TX_CTRL = (data_toggle != 0) ? USBFS_UH_T_TOG : 0; + USBOTG_H_FS->HOST_RX_CTRL = (data_toggle != 0) ? USBFS_UH_R_TOG : 0; + USBOTG_H_FS->HOST_EP_PID = pid_edpt; + USBOTG_H_FS->INT_EN |= USBFS_UIE_TRANSFER; + USBOTG_H_FS->INT_FG = USBFS_UIF_TRANSFER; + return true; +} + + +/** Set device address to communicate */ +static void hardware_update_device_address(uint8_t dev_addr) { + // Keep the bit of GP_BIT. Other 7bits are actual device address. + USBOTG_H_FS->DEV_ADDR = (USBOTG_H_FS->DEV_ADDR & USBFS_UDA_GP_BIT) | (dev_addr & USBFS_USB_ADDR_MASK); +} + +/** Set port speed */ +static void hardware_update_port_speed(tusb_speed_t speed) { + LOG_CH32_USBFSH("hardware_update_port_speed(%s)\r\n", speed == TUSB_SPEED_FULL ? "Full" : speed == TUSB_SPEED_LOW ? "Low" + : "(invalid)"); + switch (speed) { + case TUSB_SPEED_LOW: + USBOTG_H_FS->BASE_CTRL |= USBFS_UC_LOW_SPEED; + USBOTG_H_FS->HOST_CTRL |= USBFS_UH_LOW_SPEED; + USBOTG_H_FS->HOST_SETUP |= USBFS_UH_PRE_PID_EN; + return; + case TUSB_SPEED_FULL: + USBOTG_H_FS->BASE_CTRL &= ~USBFS_UC_LOW_SPEED; + USBOTG_H_FS->HOST_CTRL &= ~USBFS_UH_LOW_SPEED; + USBOTG_H_FS->HOST_SETUP &= ~USBFS_UH_PRE_PID_EN; + return; + default: + PANIC("hardware_update_port_speed(%d)\r\n", speed); + } +} + +static void hardware_set_port_address_speed(uint8_t dev_addr) { + hardware_update_device_address(dev_addr); + tusb_speed_t rhport_speed = hcd_port_speed_get(0); + tusb_speed_t dev_speed = tuh_speed_get(dev_addr); + hardware_update_port_speed(dev_speed); + if (rhport_speed == TUSB_SPEED_FULL && dev_speed == TUSB_SPEED_LOW) { + USBOTG_H_FS->HOST_CTRL &= ~USBFS_UH_LOW_SPEED; + } +} + +static bool hardware_device_attached(void) { + return USBOTG_H_FS->MIS_ST & USBFS_UMS_DEV_ATTACH; +} + +//--------------------------------------------------------------------+ +// HCD API +//--------------------------------------------------------------------+ +bool hcd_init(uint8_t rhport, const tusb_rhport_init_t *rh_init) { + (void) rhport; + (void) rh_init; + hardware_init_host(true); + + return true; +} + +bool hcd_deinit(uint8_t rhport) { + (void) rhport; + hardware_init_host(false); + + return true; +} + +static bool int_state_for_portreset = false; + +void hcd_port_reset(uint8_t rhport) { + (void) rhport; + LOG_CH32_USBFSH("hcd_port_reset()\r\n"); + int_state_for_portreset = interrupt_enabled; + // NVIC_DisableIRQ(USBFS_IRQn); + hcd_int_disable(rhport); + hardware_update_device_address(0x00); + + // USBOTG_H_FS->HOST_SETUP = 0x00; + + USBOTG_H_FS->HOST_CTRL |= USBFS_UH_BUS_RESET; + + return; +} + +void hcd_port_reset_end(uint8_t rhport) { + (void) rhport; + LOG_CH32_USBFSH("hcd_port_reset_end()\r\n"); + + USBOTG_H_FS->HOST_CTRL &= ~USBFS_UH_BUS_RESET; + tusb_time_delay_ms_api(2); + + if ((USBOTG_H_FS->HOST_CTRL & USBFS_UH_PORT_EN) == 0) { + if (hcd_port_speed_get(0) == TUSB_SPEED_LOW) { + hardware_update_port_speed(TUSB_SPEED_LOW); + } + } + + USBOTG_H_FS->HOST_CTRL |= USBFS_UH_PORT_EN; + USBOTG_H_FS->HOST_SETUP |= USBFS_UH_SOF_EN; + + // Suppress the attached event + USBOTG_H_FS->INT_FG |= USBFS_UIF_DETECT; + + if (int_state_for_portreset) { + hcd_int_enable(rhport); + } +} + +bool hcd_port_connect_status(uint8_t rhport) { + (void) rhport; + + return hardware_device_attached(); +} + +tusb_speed_t hcd_port_speed_get(uint8_t rhport) { + (void) rhport; + if (USBOTG_H_FS->MIS_ST & USBFS_UMS_DM_LEVEL) { + return TUSB_SPEED_LOW; + } else { + return TUSB_SPEED_FULL; + } +} + +// Close all opened endpoint belong to this device +void hcd_device_close(uint8_t rhport, uint8_t dev_addr) { + (void) rhport; + LOG_CH32_USBFSH("hcd_device_close(%d, 0x%02x)\r\n", rhport, dev_addr); + remove_edpt_record_for_device(dev_addr); +} + +uint32_t hcd_frame_number(uint8_t rhport) { + (void) rhport; + + return tusb_time_millis_api(); +} + +void hcd_int_enable(uint8_t rhport) { + (void) rhport; + NVIC_EnableIRQ(USBFS_IRQn); + interrupt_enabled = true; +} + +void hcd_int_disable(uint8_t rhport) { + (void) rhport; + NVIC_DisableIRQ(USBFS_IRQn); + interrupt_enabled = false; +} + + +static void xfer_retry(void* _params) { + LOG_CH32_USBFSH("xfer_retry()\r\n"); + usb_edpt_t* edpt_info = (usb_edpt_t*)_params; + if (usb_current_xfer_info.nak_pending) { + usb_current_xfer_info.nak_pending = false; + edpt_info->is_nak_pending = false; + + uint8_t dev_addr = edpt_info->dev_addr; + uint8_t ep_addr = edpt_info->ep_addr; + uint16_t buflen = edpt_info->buflen; + uint8_t* buf = edpt_info->buf; + + // Check connectivity + usb_edpt_t* edpt_info_current = get_edpt_record(dev_addr, ep_addr); + if (edpt_info_current) { + hcd_edpt_xfer(0, dev_addr, ep_addr, buf, buflen); + } + } +} + + +void hcd_int_handler(uint8_t rhport, bool in_isr) { + (void) rhport; + (void) in_isr; + + if (USBOTG_H_FS->INT_FG & USBFS_UIF_DETECT) { + // Clear the flag + USBOTG_H_FS->INT_FG = USBFS_UIF_DETECT; + // Read the detection state + bool attached = hardware_device_attached(); + LOG_CH32_USBFSH("hcd_int_handler() attached = %d\r\n", attached ? 1 : 0); + if (attached) { + hcd_event_device_attach(rhport, true); + } else { + hcd_event_device_remove(rhport, true); + } + return; + } + + if (USBOTG_H_FS->INT_FG & USBFS_UIF_TRANSFER) { + // Disable transfer interrupt + USBOTG_H_FS->INT_EN &= ~USBFS_UIE_TRANSFER; + // Clear the flag + // USBOTG_H_FS->INT_FG = USBFS_UIF_TRANSFER; + // Copy PID and Endpoint + uint8_t pid_edpt = USBOTG_H_FS->HOST_EP_PID; + uint8_t status = USBOTG_H_FS->INT_ST; + uint8_t dev_addr = USBOTG_H_FS->DEV_ADDR & USBFS_USB_ADDR_MASK; + // Clear register to stop transfer + // USBOTG_H_FS->HOST_EP_PID = 0x00; + + LOG_CH32_USBFSH("hcd_int_handler() pid_edpt=0x%02x\r\n", pid_edpt); + + uint8_t request_pid = pid_edpt >> 4; + uint8_t response_pid = status & USBFS_UIS_H_RES_MASK; + uint8_t ep_addr = pid_edpt & 0x0f; + if (request_pid == USB_PID_IN) { + ep_addr |= 0x80; + } + + usb_edpt_t *edpt_info = get_edpt_record(dev_addr, ep_addr); + if (edpt_info == NULL) { + PANIC("\r\nget_edpt_record(0x%02x, 0x%02x) returned NULL in USBHD_IRQHandler\r\n", dev_addr, ep_addr); + } + + if (status & USBFS_UIS_TOG_OK) { + edpt_info->data_toggle ^= 0x01; + + switch (request_pid) { + case USB_PID_SETUP: + case USB_PID_OUT: { + uint16_t tx_len = USBOTG_H_FS->HOST_TX_LEN; + usb_current_xfer_info.bufferlen -= tx_len; + usb_current_xfer_info.xferred_len += tx_len; + if (usb_current_xfer_info.bufferlen == 0) { + LOG_CH32_USBFSH("USB_PID_%s completed %d bytes\r\n", request_pid == USB_PID_OUT ? "OUT" : "SETUP", usb_current_xfer_info.xferred_len); + usb_current_xfer_info.is_busy = false; + hcd_event_xfer_complete(dev_addr, ep_addr, usb_current_xfer_info.xferred_len, XFER_RESULT_SUCCESS, in_isr); + return; + } else { + LOG_CH32_USBFSH("USB_PID_OUT continue...\r\n"); + usb_current_xfer_info.buffer += tx_len; + uint16_t copylen = TU_MIN(edpt_info->max_packet_size, usb_current_xfer_info.bufferlen); + memcpy(USBFS_TX_Buf, usb_current_xfer_info.buffer, copylen); + hardware_start_xfer(USB_PID_OUT, ep_addr, edpt_info->data_toggle); + return; + } + } + case USB_PID_IN: { + uint16_t received_len = USBOTG_H_FS->RX_LEN; + usb_current_xfer_info.xferred_len += received_len; + uint16_t xferred_len = usb_current_xfer_info.xferred_len; + LOG_CH32_USBFSH("Read %d bytes\r\n", received_len); + // if (received_len > 0 && (usb_current_xfer_info.buffer == NULL || usb_current_xfer_info.bufferlen == 0)) { + // PANIC("Data received but buffer not set\r\n"); + // } + memcpy(usb_current_xfer_info.buffer, USBFS_RX_Buf, received_len); + usb_current_xfer_info.buffer += received_len; + if ((received_len < edpt_info->max_packet_size) || (xferred_len == usb_current_xfer_info.bufferlen)) { + // USB device sent all data. + LOG_CH32_USBFSH("USB_PID_IN completed\r\n"); + usb_current_xfer_info.is_busy = false; + hcd_event_xfer_complete(dev_addr, ep_addr, xferred_len, XFER_RESULT_SUCCESS, in_isr); + return; + } else { + // USB device may send more data. + LOG_CH32_USBFSH("Read more data\r\n"); + hardware_start_xfer(USB_PID_IN, ep_addr, edpt_info->data_toggle); + return; + } + } + default: { + LOG_CH32_USBFSH("hcd_int_handler() L%d: unexpected response PID: 0x%02x\r\n", __LINE__, response_pid); + usb_current_xfer_info.is_busy = false; + hcd_event_xfer_complete(dev_addr, ep_addr, 0, XFER_RESULT_FAILED, in_isr); + return; + } + } + } else { + if (response_pid == USB_PID_STALL) { + LOG_CH32_USBFSH("STALL response\r\n"); + hcd_edpt_clear_stall(0, dev_addr, ep_addr); + edpt_info->data_toggle = 0; + hardware_start_xfer(request_pid, ep_addr, 0); + return; + } else if (response_pid == USB_PID_NAK) { + LOG_CH32_USBFSH("NAK reposense\r\n"); + uint32_t elapsed_time = tusb_time_millis_api() - usb_current_xfer_info.start_ms; + (void)elapsed_time; + if (edpt_info->xfer_type == TUSB_XFER_INTERRUPT) { + usb_current_xfer_info.is_busy = false; + hcd_event_xfer_complete(dev_addr, ep_addr, 0, XFER_RESULT_SUCCESS, in_isr); + + } else { + usb_current_xfer_info.is_busy = false; + usb_current_xfer_info.nak_pending = true; + + + edpt_info->is_nak_pending = true; + edpt_info->buflen = usb_current_xfer_info.bufferlen; + edpt_info->buf = usb_current_xfer_info.buffer; + + hcd_event_t event = { + .rhport = rhport, + .dev_addr = dev_addr, + .event_id = USBH_EVENT_FUNC_CALL, + .func_call = { + .func = xfer_retry, + .param = edpt_info + } + }; + hcd_event_handler(&event, in_isr); + } + return; + } else if (response_pid == USB_PID_DATA0 || response_pid == USB_PID_DATA1) { + LOG_CH32_USBFSH("Data toggle mismatched and DATA0/1 (not STALL). RX_LEN=%d\r\n", USBOTG_H_FS->RX_LEN); + usb_current_xfer_info.is_busy = false; + hcd_event_xfer_complete(dev_addr, ep_addr, 0, XFER_RESULT_FAILED, in_isr); + return; + } else { + LOG_CH32_USBFSH("hcd_int_handler() L%d: unexpected response PID: 0x%02x\r\n", __LINE__, response_pid); + usb_current_xfer_info.is_busy = false; + hcd_event_xfer_complete(dev_addr, ep_addr, 0, XFER_RESULT_FAILED, in_isr); + return; + } + } + } +} + +//--------------------------------------------------------------------+ +// Endpoint API +//--------------------------------------------------------------------+ + +bool hcd_edpt_open(uint8_t rhport, uint8_t dev_addr, tusb_desc_endpoint_t const *ep_desc) { + (void) rhport; + uint8_t ep_addr = ep_desc->bEndpointAddress; + uint8_t ep_num = tu_edpt_number(ep_addr); + uint16_t max_packet_size = ep_desc->wMaxPacketSize; + uint8_t xfer_type = ep_desc->bmAttributes.xfer; + LOG_CH32_USBFSH("hcd_edpt_open(rhport=%d, dev_addr=0x%02x, %p) EndpointAdderss=0x%02x,maxPacketSize=%d,xfer_type=%d\r\n", rhport, dev_addr, ep_desc, ep_addr, max_packet_size, xfer_type); + + while (usb_current_xfer_info.is_busy) { } + + if (ep_num == 0x00) { + TU_ASSERT(get_or_add_edpt_record(dev_addr, 0x00, max_packet_size, xfer_type) != NULL, false); + TU_ASSERT(get_or_add_edpt_record(dev_addr, 0x80, max_packet_size, xfer_type) != NULL, false); + } else { + TU_ASSERT(get_or_add_edpt_record(dev_addr, ep_addr, max_packet_size, xfer_type) != NULL, false); + } + + USBOTG_H_FS->HOST_CTRL |= USBFS_UH_PORT_EN; + USBOTG_H_FS->HOST_SETUP |= USBFS_UH_SOF_EN; + + hardware_set_port_address_speed(dev_addr); + + return true; +} + +bool hcd_edpt_xfer(uint8_t rhport, uint8_t dev_addr, uint8_t ep_addr, uint8_t *buffer, uint16_t buflen) { + (void) rhport; + + LOG_CH32_USBFSH("hcd_edpt_xfer(%d, 0x%02x, 0x%02x, ...)\r\n", rhport, dev_addr, ep_addr); + + while (usb_current_xfer_info.is_busy) {} + usb_current_xfer_info.is_busy = true; + + usb_edpt_t *edpt_info = get_edpt_record(dev_addr, ep_addr); + TU_ASSERT(edpt_info != NULL); + + hardware_set_port_address_speed(dev_addr); + + usb_current_xfer_info.dev_addr = dev_addr; + usb_current_xfer_info.ep_addr = ep_addr; + usb_current_xfer_info.buffer = buffer; + usb_current_xfer_info.bufferlen = buflen; + usb_current_xfer_info.start_ms = tusb_time_millis_api(); + usb_current_xfer_info.xferred_len = 0; + usb_current_xfer_info.nak_pending = false; + + if (tu_edpt_dir(ep_addr) == TUSB_DIR_IN) { + LOG_CH32_USBFSH("hcd_edpt_xfer(): READ, dev_addr=0x%02x, ep_addr=0x%02x, len=%d\r\n", dev_addr, ep_addr, buflen); + return hardware_start_xfer(USB_PID_IN, ep_addr, edpt_info->data_toggle); + } else { + LOG_CH32_USBFSH("hcd_edpt_xfer(): WRITE, dev_addr=0x%02x, ep_addr=0x%02x, len=%d\r\n", dev_addr, ep_addr, buflen); + uint16_t copylen = TU_MIN(edpt_info->max_packet_size, buflen); + USBOTG_H_FS->HOST_TX_LEN = copylen; + memcpy(USBFS_TX_Buf, buffer, copylen); + return hardware_start_xfer(USB_PID_OUT, ep_addr, edpt_info->data_toggle); + } +} + +bool hcd_edpt_abort_xfer(uint8_t rhport, uint8_t dev_addr, uint8_t ep_addr) { + (void) rhport; + (void) dev_addr; + (void) ep_addr; + + return false; +} + +bool hcd_setup_send(uint8_t rhport, uint8_t dev_addr, uint8_t const setup_packet[8]) { + (void) rhport; + + while (usb_current_xfer_info.is_busy) {} + + usb_current_xfer_info.is_busy = true; + + LOG_CH32_USBFSH("hcd_setup_send(rhport=%d, dev_addr=0x%02x, %p)\r\n", rhport, dev_addr, setup_packet); + + hardware_set_port_address_speed(dev_addr); + + usb_edpt_t *edpt_info_tx = get_edpt_record(dev_addr, 0x00); + usb_edpt_t *edpt_info_rx = get_edpt_record(dev_addr, 0x80); + TU_ASSERT(edpt_info_tx != NULL, false); + TU_ASSERT(edpt_info_rx != NULL, false); + + // Initialize data toggle (SETUP always starts with DATA0) + // Data toggle for OUT is toggled in hcd_int_handler() + edpt_info_tx->data_toggle = 0; + // Data toggle for IN must be set 0x01 manually. + edpt_info_rx->data_toggle = 0x01; + const uint16_t setup_packet_datalen = 8; + memcpy(USBFS_TX_Buf, setup_packet, setup_packet_datalen); + USBOTG_H_FS->HOST_TX_LEN = setup_packet_datalen; + uint8_t ep_addr = (setup_packet[0] & 0x80) ? 0x80 : 0x00; + usb_current_xfer_info.dev_addr = dev_addr; + usb_current_xfer_info.ep_addr = ep_addr; + usb_current_xfer_info.start_ms = tusb_time_millis_api(); + usb_current_xfer_info.buffer = USBFS_TX_Buf; + usb_current_xfer_info.bufferlen = setup_packet_datalen; + usb_current_xfer_info.xferred_len = 0; + usb_current_xfer_info.nak_pending = false; + + hardware_start_xfer(USB_PID_SETUP, 0, 0); + + return true; +} + +bool hcd_edpt_clear_stall(uint8_t rhport, uint8_t dev_addr, uint8_t ep_addr) { + (void) rhport; + (void) dev_addr; + LOG_CH32_USBFSH("hcd_edpt_clear_stall(rhport=%d, dev_addr=0x%02x, ep_addr=0x%02x)\r\n", rhport, dev_addr, ep_addr); + uint8_t edpt_num = tu_edpt_number(ep_addr); + uint8_t setup_request_clear_stall[8] = { + 0x02, 0x01, 0x00, 0x00, edpt_num, 0x00, 0x00, 0x00 + }; + memcpy(USBFS_TX_Buf, setup_request_clear_stall, 8); + USBOTG_H_FS->HOST_TX_LEN = 8; + + bool prev_int_state = interrupt_enabled; + hcd_int_disable(0); + + USBOTG_H_FS->HOST_EP_PID = (USB_PID_SETUP << 4) | 0x00; + USBOTG_H_FS->INT_FG |= USBFS_UIF_TRANSFER; + while ((USBOTG_H_FS->INT_FG & USBFS_UIF_TRANSFER) == 0) {} + USBOTG_H_FS->HOST_EP_PID = 0; + uint8_t response_pid = USBOTG_H_FS->INT_ST & USBFS_UIS_H_RES_MASK; + (void) response_pid; + LOG_CH32_USBFSH("hcd_edpt_clear_stall() response pid=0x%02x\r\n", response_pid); + + if (prev_int_state) { + hcd_int_enable(0); + } + + return true; +} + +#endif diff --git a/libraries/Adafruit_TinyUSB_Arduino/src/tusb.c b/libraries/Adafruit_TinyUSB_Arduino/src/tusb.c new file mode 100644 index 0000000..5d656fb --- /dev/null +++ b/libraries/Adafruit_TinyUSB_Arduino/src/tusb.c @@ -0,0 +1,570 @@ +/* + * 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. + * + * This file is part of the TinyUSB stack. + */ + +#include "tusb_option.h" + +#if CFG_TUH_ENABLED || CFG_TUD_ENABLED + +#include "tusb.h" +#include "common/tusb_private.h" + +#if CFG_TUD_ENABLED +#include "device/usbd_pvt.h" +#endif + +#if CFG_TUH_ENABLED +#include "host/usbh_pvt.h" +#endif + +// Suppress IAR warning +// Warning[Pe111]: statement is unreachable +#if defined(__ICCARM__) +#pragma diag_suppress = Pe111 +#endif + +tusb_role_t _tusb_rhport_role[TUP_USBIP_CONTROLLER_NUM] = { TUSB_ROLE_INVALID }; + +//-------------------------------------------------------------------- +// Weak/Default API, can be overwritten by Application +//-------------------------------------------------------------------- + +#if CFG_TUSB_OS != OPT_OS_NONE +TU_ATTR_WEAK uint32_t tusb_time_millis_api(void) { + return osal_time_millis(); +} + +TU_ATTR_WEAK void tusb_time_delay_ms_api(uint32_t ms) { + osal_task_delay(ms); +} + +#else +// tusb_time_millis_api() must be implemented by user application. + +TU_ATTR_WEAK void tusb_time_delay_ms_api(uint32_t ms) { + // delay using millis() + const uint32_t time_ms = tusb_time_millis_api(); + while ((tusb_time_millis_api() - time_ms) < ms) {} +} +#endif + +TU_ATTR_WEAK void *tusb_app_virt_to_phys(void *virt_addr) { + return virt_addr; +} + +TU_ATTR_WEAK void* tusb_app_phys_to_virt(void *phys_addr) { + return phys_addr; +} + +//--------------------------------------------------------------------+ +// Public API +//--------------------------------------------------------------------+ +bool tusb_rhport_init(uint8_t rhport, const tusb_rhport_init_t* rh_init) { + // backward compatible called with tusb_init(void) + #if defined(TUD_OPT_RHPORT) || defined(TUH_OPT_RHPORT) + if (rh_init == NULL) { + #if CFG_TUD_ENABLED && defined(TUD_OPT_RHPORT) + // init device stack CFG_TUSB_RHPORTx_MODE must be defined + const tusb_rhport_init_t dev_init = { + .role = TUSB_ROLE_DEVICE, + .speed = TUD_OPT_HIGH_SPEED ? TUSB_SPEED_HIGH : TUSB_SPEED_FULL + }; + TU_ASSERT ( tud_rhport_init(TUD_OPT_RHPORT, &dev_init) ); + _tusb_rhport_role[TUD_OPT_RHPORT] = TUSB_ROLE_DEVICE; + #endif + + #if CFG_TUH_ENABLED && defined(TUH_OPT_RHPORT) + // init host stack CFG_TUSB_RHPORTx_MODE must be defined + const tusb_rhport_init_t host_init = { + .role = TUSB_ROLE_HOST, + .speed = TUH_OPT_HIGH_SPEED ? TUSB_SPEED_HIGH : TUSB_SPEED_FULL + }; + TU_ASSERT( tuh_rhport_init(TUH_OPT_RHPORT, &host_init) ); + _tusb_rhport_role[TUH_OPT_RHPORT] = TUSB_ROLE_HOST; + #endif + + return true; + } + #endif + + // new API with explicit rhport and role + TU_ASSERT(rhport < TUP_USBIP_CONTROLLER_NUM && rh_init->role != TUSB_ROLE_INVALID); + _tusb_rhport_role[rhport] = rh_init->role; + + #if CFG_TUD_ENABLED + if (rh_init->role == TUSB_ROLE_DEVICE) { + TU_ASSERT(tud_rhport_init(rhport, rh_init)); + } + #endif + + #if CFG_TUH_ENABLED + if (rh_init->role == TUSB_ROLE_HOST) { + TU_ASSERT(tuh_rhport_init(rhport, rh_init)); + } + #endif + + return true; +} + +bool tusb_inited(void) { + bool ret = false; + + #if CFG_TUD_ENABLED + if (tud_inited()) { + ret = true; + } + #endif + + #if CFG_TUH_ENABLED + if (tuh_inited()) { + ret = true; + } + #endif + + return ret; +} + +void tusb_int_handler(uint8_t rhport, bool in_isr) { + TU_VERIFY(rhport < TUP_USBIP_CONTROLLER_NUM,); + + #if CFG_TUD_ENABLED + if (_tusb_rhport_role[rhport] == TUSB_ROLE_DEVICE) { + (void) in_isr; + dcd_int_handler(rhport); + } + #endif + + #if CFG_TUH_ENABLED + if (_tusb_rhport_role[rhport] == TUSB_ROLE_HOST) { + hcd_int_handler(rhport, in_isr); + } + #endif +} + +bool tusb_deinit(uint8_t rhport) { + TU_VERIFY(rhport < TUP_USBIP_CONTROLLER_NUM); + bool ret = false; + + #if CFG_TUD_ENABLED + if (_tusb_rhport_role[rhport] == TUSB_ROLE_DEVICE) { + TU_ASSERT(tud_deinit(rhport)); + _tusb_rhport_role[rhport] = TUSB_ROLE_INVALID; + ret = true; + } + #endif + + #if CFG_TUH_ENABLED + if (_tusb_rhport_role[rhport] == TUSB_ROLE_HOST) { + TU_ASSERT(tuh_deinit(rhport)); + _tusb_rhport_role[rhport] = TUSB_ROLE_INVALID; + ret = true; + } + #endif + + return ret; +} + +//--------------------------------------------------------------------+ +// Descriptor helper +//--------------------------------------------------------------------+ + +uint8_t const* tu_desc_find(uint8_t const* desc, uint8_t const* end, uint8_t byte1) { + while (desc + 1 < end) { + if (desc[1] == byte1) { + return desc; + } + desc += desc[DESC_OFFSET_LEN]; + } + return NULL; +} + +uint8_t const* tu_desc_find2(uint8_t const* desc, uint8_t const* end, uint8_t byte1, uint8_t byte2) { + while (desc + 2 < end) { + if (desc[1] == byte1 && desc[2] == byte2) { + return desc; + } + desc += desc[DESC_OFFSET_LEN]; + } + return NULL; +} + +uint8_t const* tu_desc_find3(uint8_t const* desc, uint8_t const* end, uint8_t byte1, uint8_t byte2, uint8_t byte3) { + while (desc + 3 < end) { + if (desc[1] == byte1 && desc[2] == byte2 && desc[3] == byte3) { + return desc; + } + desc += desc[DESC_OFFSET_LEN]; + } + return NULL; +} + +//--------------------------------------------------------------------+ +// Endpoint Helper for both Host and Device stack +//--------------------------------------------------------------------+ + +bool tu_edpt_claim(volatile uint8_t* ep_state, osal_mutex_t mutex) { + (void) mutex; + + // pre-check to help reducing mutex lock + TU_VERIFY((*ep_state & (TU_EDPT_STATE_BUSY | TU_EDPT_STATE_CLAIMED)) == 0); + (void) osal_mutex_lock(mutex, OSAL_TIMEOUT_WAIT_FOREVER); + + // can only claim the endpoint if it is not busy and not claimed yet. + bool const available = (*ep_state & (TU_EDPT_STATE_BUSY | TU_EDPT_STATE_CLAIMED)) == 0; + if (available) { + *ep_state |= TU_EDPT_STATE_CLAIMED; + } + + (void) osal_mutex_unlock(mutex); + return available; +} + +bool tu_edpt_release(volatile uint8_t* ep_state, osal_mutex_t mutex) { + (void) mutex; + (void) osal_mutex_lock(mutex, OSAL_TIMEOUT_WAIT_FOREVER); + + // can only release the endpoint if it is claimed and not busy + bool const ret = (*ep_state & (TU_EDPT_STATE_CLAIMED | TU_EDPT_STATE_BUSY)) == TU_EDPT_STATE_CLAIMED; + if (ret) { + *ep_state &= (uint8_t) ~TU_EDPT_STATE_CLAIMED; + } + + (void) osal_mutex_unlock(mutex); + return ret; +} + +#if CFG_TUSB_DEBUG +bool tu_edpt_validate(const tusb_desc_endpoint_t *desc_ep, tusb_speed_t speed) { + const uint16_t max_packet_size = tu_edpt_packet_size(desc_ep); + TU_LOG2(" Open EP %02X with Size = %u\r\n", desc_ep->bEndpointAddress, max_packet_size); + TU_ASSERT(max_packet_size > 0); + + switch (desc_ep->bmAttributes.xfer) { + case TUSB_XFER_ISOCHRONOUS: { + const uint16_t spec_size = (speed == TUSB_SPEED_HIGH ? 1024 : 1023); + TU_ASSERT(max_packet_size <= spec_size); + break; + } + + case TUSB_XFER_BULK: + if (speed == TUSB_SPEED_HIGH) { + // Bulk highspeed must be EXACTLY 512 + TU_ASSERT(max_packet_size == 512); + } else { + // Bulk fullspeed can only be 8, 16, 32, 64 + TU_ASSERT(max_packet_size == 8 || max_packet_size == 16 || max_packet_size == 32 || max_packet_size == 64); + } + break; + + case TUSB_XFER_INTERRUPT: { + const uint16_t spec_size = (speed == TUSB_SPEED_HIGH ? 1024 : 64); + TU_ASSERT(max_packet_size <= spec_size); + break; + } + + default: + return false; + } + + return true; +} +#endif + +bool tu_bind_driver_to_ep_itf(uint8_t driver_id, uint8_t ep2drv[][2], uint8_t itf2drv[], uint8_t itf_max, + const uint8_t *p_desc, uint16_t desc_len) { + const uint8_t *desc_end = p_desc + desc_len; + while (tu_desc_in_bounds(p_desc, desc_end)) { + const uint8_t desc_type = tu_desc_type(p_desc); + + if (desc_type == TUSB_DESC_ENDPOINT) { + const uint8_t ep_addr = ((const tusb_desc_endpoint_t *)p_desc)->bEndpointAddress; + const uint8_t ep_num = tu_edpt_number(ep_addr); + const uint8_t ep_dir = tu_edpt_dir(ep_addr); + ep2drv[ep_num][ep_dir] = driver_id; + } else if (desc_type == TUSB_DESC_INTERFACE) { + const tusb_desc_interface_t *desc_itf = (const tusb_desc_interface_t *)p_desc; + if (desc_itf->bAlternateSetting == 0) { + TU_ASSERT(desc_itf->bInterfaceNumber < itf_max); + itf2drv[desc_itf->bInterfaceNumber] = driver_id; + } + } + + p_desc = tu_desc_next(p_desc); + } + return true; +} + +//--------------------------------------------------------------------+ +// Endpoint Stream Helper for both Host and Device stack +//--------------------------------------------------------------------+ + +bool tu_edpt_stream_init(tu_edpt_stream_t *s, bool is_host, bool is_tx, bool overwritable, void *ff_buf, + uint16_t ff_bufsize, uint8_t *ep_buf) { + (void) is_tx; + + if (ff_buf == NULL || ff_bufsize == 0) { + return false; + } + + s->is_host = is_host; + tu_fifo_config(&s->ff, ff_buf, ff_bufsize, overwritable); + + #if OSAL_MUTEX_REQUIRED + if (ff_buf != NULL && ff_bufsize > 0) { + osal_mutex_t new_mutex = osal_mutex_create(&s->ff_mutexdef); + tu_fifo_config_mutex(&s->ff, is_tx ? new_mutex : NULL, is_tx ? NULL : new_mutex); + } + #endif + + s->ep_buf = ep_buf; + + return true; +} + +static bool stream_claim(tu_edpt_stream_t *s) { + TU_VERIFY(s->ep_addr != 0); // must be opened + if (s->is_host) { + #if CFG_TUH_ENABLED + return usbh_edpt_claim(s->hwid, s->ep_addr); + #endif + } else { + #if CFG_TUD_ENABLED + return usbd_edpt_claim(s->hwid, s->ep_addr); + #endif + } + return false; +} + +static bool stream_xfer(tu_edpt_stream_t *s, uint16_t count) { + if (s->is_host) { + #if CFG_TUH_ENABLED + return usbh_edpt_xfer(s->hwid, s->ep_addr, count ? s->ep_buf : NULL, count); + #endif + } else { + #if CFG_TUD_ENABLED + if (s->ep_buf == NULL) { + return usbd_edpt_xfer_fifo(s->hwid, s->ep_addr, &s->ff, count, false); + } else { + return usbd_edpt_xfer(s->hwid, s->ep_addr, count ? s->ep_buf : NULL, count, false); + } + #endif + } + return false; +} + +static bool stream_release(tu_edpt_stream_t *s) { + if (s->is_host) { + #if CFG_TUH_ENABLED + return usbh_edpt_release(s->hwid, s->ep_addr); + #endif + } else { + #if CFG_TUD_ENABLED + return usbd_edpt_release(s->hwid, s->ep_addr); + #endif + } + return false; +} + +//--------------------------------------------------------------------+ +// Stream Write +//--------------------------------------------------------------------+ +bool tu_edpt_stream_write_zlp_if_needed(tu_edpt_stream_t *s, uint32_t last_xferred_bytes) { + // ZLP condition: no pending data, last transferred bytes is multiple of packet size + TU_VERIFY(tu_fifo_empty(&s->ff) && last_xferred_bytes > 0 && (0 == (last_xferred_bytes & (s->mps - 1)))); + TU_VERIFY(stream_claim(s)); + TU_ASSERT(stream_xfer(s, 0)); + return true; +} + +uint32_t tu_edpt_stream_write_xfer(tu_edpt_stream_t *s) { + const uint16_t ff_count = tu_fifo_count(&s->ff); + TU_VERIFY(ff_count > 0, 0); // skip if no data + TU_VERIFY(stream_claim(s), 0); + + // Pull data from FIFO -> EP buf + uint16_t count; + if (s->ep_buf == NULL) { + count = tu_fifo_count(&s->ff); // re-get count since fifo can be changed + } else { + count = tu_fifo_read_n(&s->ff, s->ep_buf, s->xfer_len); + } + + if (count > 0) { + TU_ASSERT(stream_xfer(s, count), 0); + return count; + } else { + // Release endpoint since we don't make any transfer + // Note: data is dropped if terminal is not connected + stream_release(s); + return 0; + } +} + +uint32_t tu_edpt_stream_write(tu_edpt_stream_t *s, const void *buffer, uint32_t bufsize) { + TU_VERIFY(bufsize > 0); + const uint16_t ret = tu_fifo_write_n(&s->ff, buffer, (uint16_t) bufsize); + + // flush if fifo has more than packet size or + // in rare case: fifo depth is configured too small (which never reach packet size) + if ((tu_fifo_count(&s->ff) >= s->mps) || (tu_fifo_depth(&s->ff) < s->mps)) { + tu_edpt_stream_write_xfer(s); + } + return ret; +} + +uint32_t tu_edpt_stream_write_available(tu_edpt_stream_t *s) { + return (uint32_t)tu_fifo_remaining(&s->ff); +} + +//--------------------------------------------------------------------+ +// Stream Read +//--------------------------------------------------------------------+ +uint32_t tu_edpt_stream_read_xfer(tu_edpt_stream_t *s) { + uint16_t available = tu_fifo_remaining(&s->ff); + + // Prepare for incoming data but only allow what we can store in the ring buffer. + // TODO Actually we can still carry out the transfer, keeping count of received bytes + // and slowly move it to the FIFO when read(). + // This pre-check reduces endpoint claiming + TU_VERIFY(available >= s->mps); + TU_VERIFY(stream_claim(s), 0); + available = tu_fifo_remaining(&s->ff); // re-get available since fifo can be changed + + if (available >= s->mps) { + // multiple of packet size limit by ep bufsize + uint16_t count = (uint16_t) (available & ~(s->mps - 1)); + count = tu_min16(count, s->xfer_len); + TU_ASSERT(stream_xfer(s, count), 0); + return count; + } else { + // Release endpoint since we don't make any transfer + stream_release(s); + return 0; + } +} + +uint32_t tu_edpt_stream_read(tu_edpt_stream_t *s, void *buffer, uint32_t bufsize) { + const uint32_t num_read = tu_fifo_read_n(&s->ff, buffer, (uint16_t)bufsize); + tu_edpt_stream_read_xfer(s); + return num_read; +} + +//--------------------------------------------------------------------+ +// Debug +//--------------------------------------------------------------------+ + +#if CFG_TUSB_DEBUG +#include + +#if CFG_TUSB_DEBUG >= CFG_TUH_LOG_LEVEL || CFG_TUSB_DEBUG >= CFG_TUD_LOG_LEVEL +char const* const tu_str_speed[] = {"Full", "Low", "High"}; +char const* const tu_str_std_request[] = { + "Get Status", + "Clear Feature", + "Reserved", + "Set Feature", + "Reserved", + "Set Address", + "Get Descriptor", + "Set Descriptor", + "Get Configuration", + "Set Configuration", + "Get Interface", + "Set Interface", + "Synch Frame" +}; + +char const* const tu_str_xfer_result[] = { + "OK", "FAILED", "STALLED", "TIMEOUT" +}; +#endif + +static void dump_str_line(uint8_t const* buf, uint16_t count) { + tu_printf(" |"); + // each line is 16 bytes + for (uint16_t i = 0; i < count; i++) { + int ch = buf[i]; + tu_printf("%c", isprint(ch) ? ch : '.'); + } + tu_printf("|\r\n"); +} + +/* Print out memory contents + * - buf : buffer + * - count : number of item + * - indent: prefix spaces on every line + */ +void tu_print_mem(void const* buf, uint32_t count, uint8_t indent) { + uint8_t const size = 1; // fixed 1 byte for now + if (!buf || !count) { + tu_printf("NULL\r\n"); + return; + } + + uint8_t const* buf8 = (uint8_t const*) buf; + char format[] = "%00X"; + format[2] += (uint8_t) (2 * size); // 1 byte = 2 hex digits + const uint8_t item_per_line = 16 / size; + + for (unsigned int i = 0; i < count; i++) { + unsigned int value = 0; + + if (i % item_per_line == 0) { + // Print Ascii + if (i != 0) { + dump_str_line(buf8 - 16, 16); + } + for (uint8_t s = 0; s < indent; s++) { + tu_printf(" "); + } + // print offset or absolute address + tu_printf("%04X: ", 16 * i / item_per_line); + } + + tu_memcpy_s(&value, sizeof(value), buf8, size); + buf8 += size; + + tu_printf(" "); + tu_printf(format, value); + } + + // fill up last row to 16 for printing ascii + const uint32_t remain = count % 16; + uint8_t nback = (uint8_t) (remain ? remain : 16); + if (remain > 0) { + for (uint32_t i = 0; i < 16 - remain; i++) { + tu_printf(" "); + for (int j = 0; j < 2 * size; j++) { + tu_printf(" "); + } + } + } + + dump_str_line(buf8 - nback, nback); +} + +#endif + +#endif // host or device enabled diff --git a/libraries/Adafruit_TinyUSB_Arduino/src/tusb.h b/libraries/Adafruit_TinyUSB_Arduino/src/tusb.h new file mode 100644 index 0000000..c80c843 --- /dev/null +++ b/libraries/Adafruit_TinyUSB_Arduino/src/tusb.h @@ -0,0 +1,185 @@ +/* + * 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. + * + * This file is part of the TinyUSB stack. + */ + +#ifndef TUSB_H_ +#define TUSB_H_ + +#ifdef __cplusplus + extern "C" { +#endif + +//--------------------------------------------------------------------+ +// INCLUDE +//--------------------------------------------------------------------+ +#include "common/tusb_common.h" +#include "osal/osal.h" +#include "common/tusb_fifo.h" + +//------------- TypeC -------------// +#if CFG_TUC_ENABLED + #include "typec/usbc.h" +#endif + +//------------- HOST -------------// +#if CFG_TUH_ENABLED + #include "host/usbh.h" + + #if CFG_TUH_HID + #include "class/hid/hid_host.h" + #endif + + #if CFG_TUH_MSC + #include "class/msc/msc_host.h" + #endif + + #if CFG_TUH_CDC + #include "class/cdc/cdc_host.h" + #endif + + #if CFG_TUH_MIDI + #include "class/midi/midi_host.h" + #endif + + #if CFG_TUH_VENDOR + #include "class/vendor/vendor_host.h" + #endif +#else + #ifndef tuh_int_handler + #define tuh_int_handler(...) + #endif +#endif + +//------------- DEVICE -------------// +#if CFG_TUD_ENABLED + #include "device/usbd.h" + + #if CFG_TUD_HID + #include "class/hid/hid_device.h" + #endif + + #if CFG_TUD_CDC + #include "class/cdc/cdc_device.h" + #endif + + #if CFG_TUD_MSC + #include "class/msc/msc_device.h" + #endif + + #if CFG_TUD_PRINTER + #include "class/printer/printer_device.h" + #endif + + #if CFG_TUD_MTP + #include "class/mtp/mtp_device.h" + #endif + + #if CFG_TUD_AUDIO + #include "class/audio/audio_device.h" + #endif + + #if CFG_TUD_VIDEO + #include "class/video/video_device.h" + #endif + + #if CFG_TUD_MIDI + #include "class/midi/midi_device.h" + #endif + + #if CFG_TUD_VENDOR + #include "class/vendor/vendor_device.h" + #endif + + #if CFG_TUD_USBTMC + #include "class/usbtmc/usbtmc_device.h" + #endif + + #if CFG_TUD_DFU_RUNTIME + #include "class/dfu/dfu_rt_device.h" + #endif + + #if CFG_TUD_DFU + #include "class/dfu/dfu_device.h" + #endif + + #if CFG_TUD_ECM_RNDIS || CFG_TUD_NCM + #include "class/net/net_device.h" + #endif + + #if CFG_TUD_BTH + #include "class/bth/bth_device.h" + #endif +#else + #ifndef tud_int_handler + #define tud_int_handler(...) + #endif +#endif + + +//--------------------------------------------------------------------+ +// Application API +//--------------------------------------------------------------------+ +#if CFG_TUH_ENABLED || CFG_TUD_ENABLED + +// Internal helper for backward compatible with tusb_init(void) +bool tusb_rhport_init(uint8_t rhport, const tusb_rhport_init_t* rh_init); + +// Initialize roothub port with device/host role +// Note: when using with RTOS, this should be called after scheduler/kernel is started. +// Since USB IRQ handler does use RTOS queue API. +// Note2: defined as macro for backward compatible with tusb_init(void), can be changed to function in the future. +#if defined(TUD_OPT_RHPORT) || defined(TUH_OPT_RHPORT) + #define _tusb_init_arg0() tusb_rhport_init(0, NULL) +#else + #define _tusb_init_arg0() TU_VERIFY_STATIC(false, "CFG_TUSB_RHPORT0_MODE/CFG_TUSB_RHPORT1_MODE must be defined") +#endif + +#define _tusb_init_arg1(_rhport) _tusb_init_arg0() +#define _tusb_init_arg2(_rhport, _rh_init) tusb_rhport_init(_rhport, _rh_init) +#define tusb_init(...) TU_FUNC_OPTIONAL_ARG(_tusb_init, __VA_ARGS__) + +// Check if stack is initialized +bool tusb_inited(void); + +// Called to handle usb interrupt/event. tusb_init(rhport, role) must be called before +void tusb_int_handler(uint8_t rhport, bool in_isr); + +// Deinit usb stack on roothub port +bool tusb_deinit(uint8_t rhport); + +#else + +#define tusb_init(...) (false) +#define tusb_int_handler(...) do {}while(0) +#define tusb_inited() (false) +#define tusb_deinit(...) (false) + +#endif + +#ifdef __cplusplus + } +#endif + +#endif /* TUSB_H_ */ diff --git a/libraries/Adafruit_TinyUSB_Arduino/src/tusb_config.h b/libraries/Adafruit_TinyUSB_Arduino/src/tusb_config.h new file mode 100644 index 0000000..aefbd6a --- /dev/null +++ b/libraries/Adafruit_TinyUSB_Arduino/src/tusb_config.h @@ -0,0 +1,63 @@ +/* + * The MIT License (MIT) + * + * Copyright (c) 2018, hathach for Adafruit + * + * 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_ARDUINO_H_ +#define TUSB_CONFIG_ARDUINO_H_ + +#ifdef __cplusplus + extern "C" { +#endif + +#if defined(ARDUINO_ARCH_SAMD) + #include "arduino/ports/samd/tusb_config_samd.h" + +#elif defined(ARDUINO_NRF52_ADAFRUIT) + #include "arduino/ports/nrf/tusb_config_nrf.h" + +#elif defined(ARDUINO_ARCH_RP2040) + #include "arduino/ports/rp2040/tusb_config_rp2040.h" + +#elif defined(ARDUINO_ARCH_ESP32) + // do nothing since we force include "arduino/ports/esp32/tusb_config_esp32.h" in tusb_option.h + +#elif defined(ARDUINO_ARCH_CH32) || defined(CH32V20x) || defined(CH32V30x) + #include "arduino/ports/ch32/tusb_config_ch32.h" + +#elif defined(ARDUINO_ARCH_STM32) || defined(ARDUINO_ARCH_ARDUINO_CORE_STM32) + #include "arduino/ports/stm32/tusb_config_stm32.h" + +#else + #error TinyUSB Arduino Library does not support your core yet +#endif + +// Debug TinyUSB with Serial1 +#if CFG_TUSB_DEBUG +#define CFG_TUSB_DEBUG_PRINTF log_printf +#endif + +#ifdef __cplusplus + } +#endif + +#endif diff --git a/libraries/Adafruit_TinyUSB_Arduino/src/tusb_option.h b/libraries/Adafruit_TinyUSB_Arduino/src/tusb_option.h new file mode 100644 index 0000000..5c37602 --- /dev/null +++ b/libraries/Adafruit_TinyUSB_Arduino/src/tusb_option.h @@ -0,0 +1,855 @@ +/* + * 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. + * + * This file is part of the TinyUSB stack. + */ + +#pragma once + +#include "common/tusb_compiler.h" + +// Version is release as major.minor.revision eg 1.0.0 +#define TUSB_VERSION_MAJOR 0 +#define TUSB_VERSION_MINOR 20 +#define TUSB_VERSION_REVISION 1 + +#define TUSB_VERSION_NUMBER (TUSB_VERSION_MAJOR * 10000 + TUSB_VERSION_MINOR * 100 + TUSB_VERSION_REVISION) +#define TUSB_VERSION_STRING TU_XSTRING(TUSB_VERSION_MAJOR) "." TU_XSTRING(TUSB_VERSION_MINOR) "." TU_XSTRING(TUSB_VERSION_REVISION) + +//--------------------------------------------------------------------+ +// Supported MCUs +// CFG_TUSB_MCU must be defined to one of following value +//--------------------------------------------------------------------+ + +#define OPT_MCU_NONE 0 + +// LPC +#define OPT_MCU_LPC11UXX 1 ///< NXP LPC11Uxx +#define OPT_MCU_LPC13XX 2 ///< NXP LPC13xx +#define OPT_MCU_LPC15XX 3 ///< NXP LPC15xx +#define OPT_MCU_LPC175X_6X 4 ///< NXP LPC175x, LPC176x +#define OPT_MCU_LPC177X_8X 5 ///< NXP LPC177x, LPC178x +#define OPT_MCU_LPC18XX 6 ///< NXP LPC18xx +#define OPT_MCU_LPC40XX 7 ///< NXP LPC40xx +#define OPT_MCU_LPC43XX 8 ///< NXP LPC43xx +#define OPT_MCU_LPC51 9 ///< NXP LPC51 +#define OPT_MCU_LPC51UXX OPT_MCU_LPC51 ///< NXP LPC51 +#define OPT_MCU_LPC54 10 ///< NXP LPC54 +#define OPT_MCU_LPC55 11 ///< NXP LPC55 +// legacy naming +#define OPT_MCU_LPC54XXX OPT_MCU_LPC54 +#define OPT_MCU_LPC55XX OPT_MCU_LPC55 + +// NRF +#define OPT_MCU_NRF5X 100 ///< Nordic nRF 52,53 series +#define OPT_MCU_NRF54 101 ///< Nordic nRF54 series + +// SAM +#define OPT_MCU_SAMD21 200 ///< MicroChip SAMD21 +#define OPT_MCU_SAMD51 201 ///< MicroChip SAMD51 +#define OPT_MCU_SAMG 202 ///< MicroChip SAMDG series +#define OPT_MCU_SAME5X 203 ///< MicroChip SAM E5x +#define OPT_MCU_SAMD11 204 ///< MicroChip SAMD11 +#define OPT_MCU_SAML2X 205 ///< MicroChip SAML2x +#define OPT_MCU_SAML21 OPT_MCU_SAML2X ///< SAML21 backward compatibility +#define OPT_MCU_SAML22 OPT_MCU_SAML2X ///< SAML22 backward compatibility +#define OPT_MCU_SAMX7X 207 ///< MicroChip SAME70, S70, V70, V71 family + +// STM32 +#define OPT_MCU_STM32F0 300 ///< ST F0 +#define OPT_MCU_STM32F1 301 ///< ST F1 +#define OPT_MCU_STM32F2 302 ///< ST F2 +#define OPT_MCU_STM32F3 303 ///< ST F3 +#define OPT_MCU_STM32F4 304 ///< ST F4 +#define OPT_MCU_STM32F7 305 ///< ST F7 +#define OPT_MCU_STM32H7 306 ///< ST H7 +#define OPT_MCU_STM32L1 308 ///< ST L1 +#define OPT_MCU_STM32L0 307 ///< ST L0 +#define OPT_MCU_STM32L4 309 ///< ST L4 +#define OPT_MCU_STM32G0 310 ///< ST G0 +#define OPT_MCU_STM32G4 311 ///< ST G4 +#define OPT_MCU_STM32WB 312 ///< ST WB +#define OPT_MCU_STM32U5 313 ///< ST U5 +#define OPT_MCU_STM32L5 314 ///< ST L5 +#define OPT_MCU_STM32H5 315 ///< ST H5 +#define OPT_MCU_STM32U0 316 ///< ST U0 +#define OPT_MCU_STM32H7RS 317 ///< ST F7RS +#define OPT_MCU_STM32C0 318 ///< ST C0 +#define OPT_MCU_STM32N6 319 ///< ST N6 +#define OPT_MCU_STM32WBA 320 ///< ST WBA +#define OPT_MCU_STM32U3 321 ///< ST U3 + +// Sony +#define OPT_MCU_CXD56 400 ///< SONY CXD56 + +// TI +#define OPT_MCU_MSP430x5xx 500 ///< TI MSP430x5xx +#define OPT_MCU_MSP432E4 510 ///< TI MSP432E4xx +#define OPT_MCU_TM4C123 511 ///< TI Tiva-C 123x +#define OPT_MCU_TM4C129 512 ///< TI Tiva-C 129x + +// ValentyUSB eptri +#define OPT_MCU_VALENTYUSB_EPTRI 600 ///< Fomu eptri config + +// NXP iMX RT +#define OPT_MCU_MIMXRT1XXX 700 ///< NXP iMX RT1xxx Series +#define OPT_MCU_MIMXRT10XX OPT_MCU_MIMXRT1XXX ///< RT10xx +#define OPT_MCU_MIMXRT11XX OPT_MCU_MIMXRT1XXX ///< RT11xx + +// Nuvoton +#define OPT_MCU_NUC121 800 +#define OPT_MCU_NUC126 801 +#define OPT_MCU_NUC120 802 +#define OPT_MCU_NUC505 803 + +// Espressif +#define OPT_MCU_ESP32S2 900 ///< Espressif ESP32-S2 +#define OPT_MCU_ESP32S3 901 ///< Espressif ESP32-S3 +#define OPT_MCU_ESP32 902 ///< Espressif ESP32 (for host max3421e) +#define OPT_MCU_ESP32C3 903 ///< Espressif ESP32-C3 +#define OPT_MCU_ESP32C6 904 ///< Espressif ESP32-C6 +#define OPT_MCU_ESP32C2 905 ///< Espressif ESP32-C2 +#define OPT_MCU_ESP32H2 906 ///< Espressif ESP32-H2 +#define OPT_MCU_ESP32P4 907 ///< Espressif ESP32-P4 +#define OPT_MCU_ESP32C5 908 ///< Espressif ESP32-C5 +#define OPT_MCU_ESP32C61 909 ///< Espressif ESP32-C61 +#define OPT_MCU_ESP32H4 910 ///< Espressif ESP32-H4 +#define OPT_MCU_ESP32S31 911 ///< Espressif ESP32-S31 + +// Dialog +#define OPT_MCU_DA1469X 1000 ///< Dialog Semiconductor DA1469x + +// Raspberry Pi +#define OPT_MCU_RP2040 1100 ///< Raspberry Pi RP2040 + +// NXP Kinetis +#define OPT_MCU_KINETIS_KL 1200 ///< NXP KL series +#define OPT_MCU_KINETIS_K32L 1201 ///< NXP K32L series +#define OPT_MCU_KINETIS_K32 1201 ///< Alias to K32L +#define OPT_MCU_KINETIS_K 1202 ///< NXP K series + +#define OPT_MCU_MKL25ZXX 1200 ///< Alias to KL (obsolete) +#define OPT_MCU_K32L2BXX 1201 ///< Alias to K32 (obsolete) + +// Silabs +#define OPT_MCU_EFM32GG 1300 ///< Silabs EFM32GG + +// Renesas RX +#define OPT_MCU_RX63X 1400 ///< Renesas RX63N/631 +#define OPT_MCU_RX65X 1401 ///< Renesas RX65N/RX651 +#define OPT_MCU_RX72N 1402 ///< Renesas RX72N +#define OPT_MCU_RAXXX 1403 ///< Renesas RA generic + +// Mind Motion +#define OPT_MCU_MM32F327X 1500 ///< Mind Motion MM32F327 + +// GigaDevice +#define OPT_MCU_GD32VF103 1600 ///< GigaDevice GD32VF103 + +// Broadcom +#define OPT_MCU_BCM2711 1700 ///< Broadcom BCM2711 +#define OPT_MCU_BCM2835 1701 ///< Broadcom BCM2835 +#define OPT_MCU_BCM2837 1702 ///< Broadcom BCM2837 + +// Infineon +#define OPT_MCU_XMC4000 1800 ///< Infineon XMC4000 + +// PIC +#define OPT_MCU_PIC32MZ 1900 ///< MicroChip PIC32MZ family +#define OPT_MCU_PIC32MM 1901 ///< MicroChip PIC32MM family +#define OPT_MCU_PIC32MX 1902 ///< MicroChip PIC32MX family +#define OPT_MCU_PIC32MK 1903 ///< MicroChip PIC32MK family +#define OPT_MCU_PIC24 1910 ///< MicroChip PIC24 family +#define OPT_MCU_DSPIC33 1911 ///< MicroChip DSPIC33 family + +// BridgeTek +#define OPT_MCU_FT90X 2000 ///< BridgeTek FT90x +#define OPT_MCU_FT93X 2001 ///< BridgeTek FT93x + +// Allwinner +#define OPT_MCU_F1C100S 2100 ///< Allwinner F1C100s family + +// WCH +#define OPT_MCU_CH32V307 2200 ///< WCH CH32V307 +#define OPT_MCU_CH32F20X 2210 ///< WCH CH32F20x +#define OPT_MCU_CH32V20X 2220 ///< WCH CH32V20X +#define OPT_MCU_CH32V103 2230 ///< WCH CH32V103 + +// NXP LPC MCX +#define OPT_MCU_MCXN9 2300 ///< NXP MCX N9 Series +#define OPT_MCU_MCXA15 2301 ///< NXP MCX A15 Series +#define OPT_MCU_RW61X 2302 ///< NXP RW61x Series + +// Analog Devices +#define OPT_MCU_MAX32690 2400 ///< ADI MAX32690 +#define OPT_MCU_MAX32665 2401 ///< ADI MAX32666/5 +#define OPT_MCU_MAX32666 2401 ///< ADI MAX32666/5 +#define OPT_MCU_MAX32650 2402 ///< ADI MAX32650/1/2 +#define OPT_MCU_MAX78002 2403 ///< ADI MAX78002 + +// ArteryTek +#define OPT_MCU_AT32F403A_407 2500 ///< ArteryTek AT32F403A_AT32F407 +#define OPT_MCU_AT32F415 2501 ///< ArteryTek AT32F415 +#define OPT_MCU_AT32F435_437 2502 ///< ArteryTek AT32F435_AT32F437 +#define OPT_MCU_AT32F423 2503 ///< ArteryTek AT32F423 +#define OPT_MCU_AT32F402_405 2504 ///< ArteryTek AT32F402_405 +#define OPT_MCU_AT32F425 2505 ///< ArteryTek AT32F425 +#define OPT_MCU_AT32F413 2506 ///< ArteryTek AT32F413 +#define OPT_MCU_AT32F45X 2507 ///< ArteryTek AT32F45x + +// HPMicro +#define OPT_MCU_HPM 2600 ///< HPMicro + +// Check if configured MCU is one of listed +// Apply TU_MCU_IS_EQUAL with || as separator to list of input +#define TU_MCU_IS_EQUAL(_m) (CFG_TUSB_MCU == (_m)) +#define TU_CHECK_MCU(...) (TU_ARGS_APPLY(TU_MCU_IS_EQUAL, ||, __VA_ARGS__)) + +//--------------------------------------------------------------------+ +// Supported OS +//--------------------------------------------------------------------+ + +#define OPT_OS_NONE 1 ///< No RTOS +#define OPT_OS_FREERTOS 2 ///< FreeRTOS +#define OPT_OS_MYNEWT 3 ///< Mynewt OS +#define OPT_OS_CUSTOM 4 ///< Custom OS is implemented by application +#define OPT_OS_PICO 5 ///< Raspberry Pi Pico SDK +#define OPT_OS_RTTHREAD 6 ///< RT-Thread +#define OPT_OS_RTX4 7 ///< Keil RTX 4 +#define OPT_OS_ZEPHYR 8 ///< Zephyr +#define OPT_OS_THREADX 9 ///< ThreadX + +//--------------------------------------------------------------------+ +// Mode and Speed +//--------------------------------------------------------------------+ + +// Low byte is operational mode +#define OPT_MODE_NONE 0x0000 ///< Disabled +#define OPT_MODE_DEVICE 0x0001 ///< Device Mode +#define OPT_MODE_HOST 0x0002 ///< Host Mode + +// High byte is max operational speed (corresponding to tusb_speed_t) +#define OPT_MODE_DEFAULT_SPEED 0x0000u ///< Default (max) speed supported by MCU +#define OPT_MODE_LOW_SPEED 0x0100u ///< Low Speed +#define OPT_MODE_FULL_SPEED 0x0200u ///< Full Speed +#define OPT_MODE_HIGH_SPEED 0x0400u ///< High Speed +#define OPT_MODE_SPEED_MASK 0xff00u + +//--------------------------------------------------------------------+ +// Include tusb_config.h +//--------------------------------------------------------------------+ + +// Allow to use command line to change the config name/location +#ifdef CFG_TUSB_CONFIG_FILE + #include CFG_TUSB_CONFIG_FILE +#elif defined(ARDUINO_ARCH_ESP32) + // ESP32 out-of-sync + #include "arduino/ports/esp32/tusb_config_esp32.h" +#else + #include "tusb_config.h" +#endif + +#include "common/tusb_mcu.h" + +//--------------------------------------------------------------------+ +// USBIP +//--------------------------------------------------------------------+ + +//------------- ChipIdea -------------// +// Enable CI_HS VBUS Charge. Set this to 1 if the USB_VBUS pin is not connected to 5V VBUS (note: 3.3V is +// insufficient). +#ifndef CFG_TUD_CI_HS_VBUS_CHARGE + #ifndef CFG_TUD_CI_HS_VBUS_CHARGE_DEFAULT + #define CFG_TUD_CI_HS_VBUS_CHARGE_DEFAULT 0 + #endif + #define CFG_TUD_CI_HS_VBUS_CHARGE CFG_TUD_CI_HS_VBUS_CHARGE_DEFAULT +#endif + +// CI_HS support FIFO transfer if endpoint buffer is 4k aligned and size is multiple of 4k, also DCACHE is disabled +#ifndef CFG_TUD_CI_HS_EPBUF_4K_ALIGNED + #define CFG_TUD_CI_HS_EPBUF_4K_ALIGNED 0 +#endif + +#if CFG_TUD_CI_HS_EPBUF_4K_ALIGNED && !CFG_TUD_MEM_DCACHE_ENABLE + #define CFG_TUD_EDPT_DEDICATED_HWFIFO 1 +#endif + +//------------- DWC2 -------------// +// DMA mode for device +#ifndef CFG_TUD_DWC2_DMA_ENABLE + #ifndef CFG_TUD_DWC2_DMA_ENABLE_DEFAULT + #define CFG_TUD_DWC2_DMA_ENABLE_DEFAULT 0 + #endif + + #define CFG_TUD_DWC2_DMA_ENABLE CFG_TUD_DWC2_DMA_ENABLE_DEFAULT +#endif + +// Slave mode for device +#ifndef CFG_TUD_DWC2_SLAVE_ENABLE + #ifndef CFG_TUD_DWC2_SLAVE_ENABLE_DEFAULT + #define CFG_TUD_DWC2_SLAVE_ENABLE_DEFAULT !CFG_TUD_DWC2_DMA_ENABLE // disabled if DMA is enabled + #endif + + #define CFG_TUD_DWC2_SLAVE_ENABLE CFG_TUD_DWC2_SLAVE_ENABLE_DEFAULT +#endif + +// DMA mode for host +#ifndef CFG_TUH_DWC2_DMA_ENABLE + #ifndef CFG_TUH_DWC2_DMA_ENABLE_DEFAULT + #define CFG_TUH_DWC2_DMA_ENABLE_DEFAULT 0 + #endif + + #define CFG_TUH_DWC2_DMA_ENABLE CFG_TUH_DWC2_DMA_ENABLE_DEFAULT +#endif + +// Slave mode for host +#ifndef CFG_TUH_DWC2_SLAVE_ENABLE + #ifndef CFG_TUH_DWC2_SLAVE_ENABLE_DEFAULT + #define CFG_TUH_DWC2_SLAVE_ENABLE_DEFAULT !CFG_TUH_DWC2_DMA_ENABLE // disabled if DMA is enabled + #endif + + #define CFG_TUH_DWC2_SLAVE_ENABLE CFG_TUH_DWC2_SLAVE_ENABLE_DEFAULT +#endif + +#if defined(TUP_USBIP_DWC2) + #define CFG_TUD_EDPT_DEDICATED_HWFIFO CFG_TUD_DWC2_SLAVE_ENABLE + #define CFG_TUH_EDPT_DEDICATED_HWFIFO CFG_TUH_DWC2_SLAVE_ENABLE + + #define CFG_TUSB_FIFO_HWFIFO_DATA_STRIDE 4 // 32bit access + #define CFG_TUSB_FIFO_HWFIFO_ADDR_STRIDE 0 // fixed hwfifo address +#endif + +//------------ FSDEV --------------// +#if defined(TUP_USBIP_FSDEV) + #define CFG_TUD_EDPT_DEDICATED_HWFIFO 1 + + #if CFG_TUSB_FSDEV_PMA_SIZE == 2048 || TU_CHECK_MCU(OPT_MCU_STM32U0) + // 32-bit access scheme + #define CFG_TUSB_FIFO_HWFIFO_DATA_STRIDE 4 // 32-bit data + #define CFG_TUSB_FIFO_HWFIFO_ADDR_STRIDE 4 // 32-bit address increase + #define CFG_TUSB_FSDEV_32BIT + #elif CFG_TUSB_FSDEV_PMA_SIZE == 1024 + // 2 x 16-bit access scheme + #define CFG_TUSB_FIFO_HWFIFO_DATA_STRIDE 2 // 16-bit data + #define CFG_TUSB_FIFO_HWFIFO_ADDR_STRIDE 2 // 16-bit address increase + #elif CFG_TUSB_FSDEV_PMA_SIZE == 512 + // 1 x 16-bit access scheme + #define CFG_TUSB_FIFO_HWFIFO_DATA_STRIDE 2 // 16-bit data + #define CFG_TUSB_FIFO_HWFIFO_ADDR_STRIDE 4 // 32-bit address increase + #endif +#endif + +//------------ MAX3421 -------------// +// Enable MAX3421 USB host controller +#ifndef CFG_TUH_MAX3421 + #define CFG_TUH_MAX3421 0 +#endif + +//------------ MUSB --------------// +#if defined(TUP_USBIP_MUSB) + #define CFG_TUD_EDPT_DEDICATED_HWFIFO 1 + #define CFG_TUSB_FIFO_HWFIFO_DATA_STRIDE 4 // 32 bit data + #define CFG_TUSB_FIFO_HWFIFO_DATA_ODD_16BIT_ACCESS // allow odd 16bit access + #define CFG_TUSB_FIFO_HWFIFO_DATA_ODD_8BIT_ACCESS // allow odd 8bit access + #define CFG_TUSB_FIFO_HWFIFO_ADDR_STRIDE 0 // fixed hwfifo +#endif + +//------------- Raspberry Pi -------------// +// Enable PIO-USB software host controller +#ifndef CFG_TUH_RPI_PIO_USB + #define CFG_TUH_RPI_PIO_USB 0 +#endif + +#ifndef CFG_TUD_RPI_PIO_USB + #define CFG_TUD_RPI_PIO_USB 0 +#endif + +#if (CFG_TUSB_MCU == OPT_MCU_RP2040) && !CFG_TUD_RPI_PIO_USB + #define CFG_TUD_EDPT_DEDICATED_HWFIFO 0 + #define CFG_TUSB_FIFO_HWFIFO_DATA_STRIDE 1 + #define CFG_TUSB_FIFO_HWFIFO_ADDR_STRIDE 1 + #define CFG_TUSB_FIFO_HWFIFO_CUSTOM_WRITE + #define CFG_TUSB_FIFO_HWFIFO_CUSTOM_READ +#endif + +//------------ RUSB2 --------------// +#if defined(TUP_USBIP_RUSB2) + #define CFG_TUD_EDPT_DEDICATED_HWFIFO 1 + #define CFG_TUSB_FIFO_HWFIFO_DATA_STRIDE (2 | (TUD_OPT_HIGH_SPEED ? 4 : 0)) // 16 bit and 32 bit if highspeed + #define CFG_TUSB_FIFO_HWFIFO_ADDR_STRIDE 0 + #define CFG_TUSB_FIFO_HWFIFO_CUSTOM_WRITE // custom write since rusb2 can change access width 32 -> 16 and can write + // odd byte with byte access +#endif + +//------- Microchip SAMX7X -------// +// DMA mode for device +#ifndef CFG_TUD_SAMX7X_DMA_ENABLE + #ifndef CFG_TUD_SAMX7X_DMA_ENABLE_DEFAULT + #define CFG_TUD_SAMX7X_DMA_ENABLE_DEFAULT 0 + #endif + + #define CFG_TUD_SAMX7X_DMA_ENABLE CFG_TUD_SAMX7X_DMA_ENABLE_DEFAULT +#endif + +#if (CFG_TUSB_MCU == OPT_MCU_SAMX7X) + #define CFG_TUSB_FIFO_HWFIFO_DATA_STRIDE 4 + #define CFG_TUSB_FIFO_HWFIFO_ADDR_STRIDE 4 + #define CFG_TUSB_FIFO_HWFIFO_DATA_ODD_16BIT_ACCESS + #define CFG_TUSB_FIFO_HWFIFO_DATA_ODD_8BIT_ACCESS + #define CFG_TUD_EDPT_DEDICATED_HWFIFO 1 +#endif + +//-------------------------------------------------------------------- +// RootHub Mode detection +//-------------------------------------------------------------------- + +//------------- Root hub as Device -------------// + +#if defined(CFG_TUSB_RHPORT0_MODE) && ((CFG_TUSB_RHPORT0_MODE) & OPT_MODE_DEVICE) + #define TUD_RHPORT_MODE (CFG_TUSB_RHPORT0_MODE) + #define TUD_OPT_RHPORT 0 +#elif defined(CFG_TUSB_RHPORT1_MODE) && ((CFG_TUSB_RHPORT1_MODE) & OPT_MODE_DEVICE) + #define TUD_RHPORT_MODE (CFG_TUSB_RHPORT1_MODE) + #define TUD_OPT_RHPORT 1 +#else + #define TUD_RHPORT_MODE OPT_MODE_NONE +#endif + +#ifndef CFG_TUD_ENABLED + // fallback to use CFG_TUSB_RHPORTx_MODE + #define CFG_TUD_ENABLED (TUD_RHPORT_MODE & OPT_MODE_DEVICE) +#endif + +#ifndef CFG_TUD_MAX_SPEED + // fallback to use CFG_TUSB_RHPORTx_MODE + #define CFG_TUD_MAX_SPEED (TUD_RHPORT_MODE & OPT_MODE_SPEED_MASK) +#endif + +// For backward compatible +#define TUSB_OPT_DEVICE_ENABLED CFG_TUD_ENABLED + +// highspeed support indicator +#define TUD_OPT_HIGH_SPEED (CFG_TUD_MAX_SPEED ? (CFG_TUD_MAX_SPEED & OPT_MODE_HIGH_SPEED) : TUP_RHPORT_HIGHSPEED) + +//------------- Root hub as Host -------------// + +#if defined(CFG_TUSB_RHPORT0_MODE) && ((CFG_TUSB_RHPORT0_MODE) & OPT_MODE_HOST) + #define TUH_RHPORT_MODE (CFG_TUSB_RHPORT0_MODE) + #define TUH_OPT_RHPORT 0 +#elif defined(CFG_TUSB_RHPORT1_MODE) && ((CFG_TUSB_RHPORT1_MODE) & OPT_MODE_HOST) + #define TUH_RHPORT_MODE (CFG_TUSB_RHPORT1_MODE) + #define TUH_OPT_RHPORT 1 +#else + #define TUH_RHPORT_MODE OPT_MODE_NONE +#endif + +#ifndef CFG_TUH_ENABLED + // fallback to use CFG_TUSB_RHPORTx_MODE + #define CFG_TUH_ENABLED (TUH_RHPORT_MODE & OPT_MODE_HOST) +#endif + +#ifndef CFG_TUH_MAX_SPEED + // fallback to use CFG_TUSB_RHPORTx_MODE + #define CFG_TUH_MAX_SPEED (TUH_RHPORT_MODE & OPT_MODE_SPEED_MASK) +#endif + +// For backward compatible +#define TUSB_OPT_HOST_ENABLED CFG_TUH_ENABLED + +// highspeed support indicator +#define TUH_OPT_HIGH_SPEED (CFG_TUH_MAX_SPEED ? (CFG_TUH_MAX_SPEED & OPT_MODE_HIGH_SPEED) : TUP_RHPORT_HIGHSPEED) + + +//--------------------------------------------------------------------+ +// TODO move later +//--------------------------------------------------------------------+ + +// TUP_MCU_STRICT_ALIGN will overwrite TUP_ARCH_STRICT_ALIGN. +// In case TUP_MCU_STRICT_ALIGN = 1 and TUP_ARCH_STRICT_ALIGN =0, we will not reply on compiler +// to generate unaligned access code. +// LPC_IP3511 Highspeed cannot access unaligned memory on USB_RAM +#if TUD_OPT_HIGH_SPEED && TU_CHECK_MCU(OPT_MCU_LPC54XXX, OPT_MCU_LPC55XX) + #define TUP_MCU_STRICT_ALIGN 1 +#else + #define TUP_MCU_STRICT_ALIGN 0 +#endif + +//--------------------------------------------------------------------+ +// Common Options (Default) +//--------------------------------------------------------------------+ + +// Debug enable to print out error message +#ifndef CFG_TUSB_DEBUG + #define CFG_TUSB_DEBUG 0 +#endif + +// Level where CFG_TUSB_DEBUG must be at least for USBH is logged +#ifndef CFG_TUH_LOG_LEVEL + #define CFG_TUH_LOG_LEVEL 2 +#endif + +// Level where CFG_TUSB_DEBUG must be at least for USBD is logged +#ifndef CFG_TUD_LOG_LEVEL + #define CFG_TUD_LOG_LEVEL 2 +#endif + +// Memory section for placing buffer used for usb transferring. If MEM_SECTION is different for +// host and device use: CFG_TUD_MEM_SECTION, CFG_TUH_MEM_SECTION instead +#ifndef CFG_TUSB_MEM_SECTION + #define CFG_TUSB_MEM_SECTION +#endif + +// Alignment requirement of buffer used for usb transferring. if MEM_ALIGN is different for +// host and device controller use: CFG_TUD_MEM_ALIGN, CFG_TUH_MEM_ALIGN instead +#ifndef CFG_TUSB_MEM_ALIGN + #define CFG_TUSB_MEM_ALIGN TU_ATTR_ALIGNED(4) +#endif + +#ifndef CFG_TUSB_MEM_DCACHE_LINE_SIZE + #ifndef CFG_TUSB_MEM_DCACHE_LINE_SIZE_DEFAULT + #define CFG_TUSB_MEM_DCACHE_LINE_SIZE_DEFAULT 1 + #endif + + #define CFG_TUSB_MEM_DCACHE_LINE_SIZE CFG_TUSB_MEM_DCACHE_LINE_SIZE_DEFAULT +#endif + +// OS selection +#ifndef CFG_TUSB_OS + #define CFG_TUSB_OS OPT_OS_NONE +#endif + +#ifndef CFG_TUSB_OS_INC_PATH + #ifndef CFG_TUSB_OS_INC_PATH_DEFAULT + #define CFG_TUSB_OS_INC_PATH_DEFAULT + #endif + + #define CFG_TUSB_OS_INC_PATH CFG_TUSB_OS_INC_PATH_DEFAULT +#endif + +//-------------------------------------------------------------------- +// Device Options (Default) +//-------------------------------------------------------------------- + +// Attribute to place data in accessible RAM for device controller (default: CFG_TUSB_MEM_SECTION) +#ifndef CFG_TUD_MEM_SECTION + #define CFG_TUD_MEM_SECTION CFG_TUSB_MEM_SECTION +#endif + +// Attribute to align memory for device controller (default: CFG_TUSB_MEM_ALIGN) +#ifndef CFG_TUD_MEM_ALIGN + #define CFG_TUD_MEM_ALIGN CFG_TUSB_MEM_ALIGN +#endif + +#ifndef CFG_TUD_MEM_DCACHE_ENABLE + #ifndef CFG_TUD_MEM_DCACHE_ENABLE_DEFAULT + #define CFG_TUD_MEM_DCACHE_ENABLE_DEFAULT 0 + #endif + + #define CFG_TUD_MEM_DCACHE_ENABLE CFG_TUD_MEM_DCACHE_ENABLE_DEFAULT +#endif + +#ifndef CFG_TUD_MEM_DCACHE_LINE_SIZE + #define CFG_TUD_MEM_DCACHE_LINE_SIZE CFG_TUSB_MEM_DCACHE_LINE_SIZE +#endif + +#ifndef CFG_TUD_ENDPOINT0_SIZE + #define CFG_TUD_ENDPOINT0_SIZE 64 +#endif + +#ifndef CFG_TUD_ENDPOINT0_BUFSIZE + #define CFG_TUD_ENDPOINT0_BUFSIZE CFG_TUD_ENDPOINT0_SIZE +#endif + +#ifndef CFG_TUD_INTERFACE_MAX + #define CFG_TUD_INTERFACE_MAX 16 +#endif + +// max events processed in one tud_task_ext() call, 0 for unlimited +#ifndef CFG_TUD_TASK_EVENTS_PER_RUN + #define CFG_TUD_TASK_EVENTS_PER_RUN 16 +#endif + +// default to max hardware endpoint, but can be smaller to save RAM +#ifndef CFG_TUD_ENDPPOINT_MAX + #define CFG_TUD_ENDPPOINT_MAX TUP_DCD_ENDPOINT_MAX +#endif + +#if CFG_TUD_ENDPPOINT_MAX > TUP_DCD_ENDPOINT_MAX + #error "CFG_TUD_ENDPPOINT_MAX must be less than or equal to TUP_DCD_ENDPOINT_MAX" +#endif + +// USB 2.0 7.1.20: compliance test mode support +#ifndef CFG_TUD_TEST_MODE + #define CFG_TUD_TEST_MODE 0 +#endif + +#ifndef CFG_TUD_VBUS_DETECT_HW_DEFAULT + #define CFG_TUD_VBUS_DETECT_HW_DEFAULT 0 +#endif + +// Enable VBUS Detect hardware, usually via functional GPIO +#ifndef CFG_TUD_VBUS_DETECT_HW + #define CFG_TUD_VBUS_DETECT_HW CFG_TUD_VBUS_DETECT_HW_DEFAULT +#endif + +//------------- Device Class Driver -------------// +#ifndef CFG_TUD_BTH + #define CFG_TUD_BTH 0 +#endif + +#if CFG_TUD_BTH && !defined(CFG_TUD_BTH_ISO_ALT_COUNT) +#error CFG_TUD_BTH_ISO_ALT_COUNT must be defined to tell Bluetooth driver the number of ISO endpoints to use +#endif + +#ifndef CFG_TUD_CDC + #define CFG_TUD_CDC 0 +#endif + +#ifndef CFG_TUD_MSC + #define CFG_TUD_MSC 0 +#endif + +#ifndef CFG_TUD_MTP + #define CFG_TUD_MTP 0 +#endif + +#ifndef CFG_TUD_HID + #define CFG_TUD_HID 0 +#endif + +#ifndef CFG_TUD_AUDIO + #define CFG_TUD_AUDIO 0 +#endif + +#ifndef CFG_TUD_VIDEO + #define CFG_TUD_VIDEO 0 +#endif + +#ifndef CFG_TUD_MIDI + #define CFG_TUD_MIDI 0 +#endif + +#ifndef CFG_TUD_VENDOR + #define CFG_TUD_VENDOR 0 +#endif + +#ifndef CFG_TUD_USBTMC + #define CFG_TUD_USBTMC 0 +#endif + +#ifndef CFG_TUD_DFU_RUNTIME + #define CFG_TUD_DFU_RUNTIME 0 +#endif + +#ifndef CFG_TUD_DFU + #define CFG_TUD_DFU 0 +#endif + +#ifndef CFG_TUD_ECM_RNDIS + #ifdef CFG_TUD_NET + #warning "CFG_TUD_NET is renamed to CFG_TUD_ECM_RNDIS" + #define CFG_TUD_ECM_RNDIS CFG_TUD_NET + #else + #define CFG_TUD_ECM_RNDIS 0 + #endif +#endif + +#ifndef CFG_TUD_NCM + #define CFG_TUD_NCM 0 +#endif + +#ifndef CFG_TUD_PRINTER + #define CFG_TUD_PRINTER 0 +#endif + +#ifndef CFG_TUD_EDPT_DEDICATED_HWFIFO + #define CFG_TUD_EDPT_DEDICATED_HWFIFO 0 +#endif + +//-------------------------------------------------------------------- +// Host Options (Default) +//-------------------------------------------------------------------- +#if CFG_TUH_ENABLED + #ifndef CFG_TUH_DEVICE_MAX + #define CFG_TUH_DEVICE_MAX 1 + #endif + + #ifndef CFG_TUH_ENUMERATION_BUFSIZE + #define CFG_TUH_ENUMERATION_BUFSIZE 256 + #endif +#endif // CFG_TUH_ENABLED + +// Attribute to place data in accessible RAM for host controller (default: CFG_TUSB_MEM_SECTION) +#ifndef CFG_TUH_MEM_SECTION + #define CFG_TUH_MEM_SECTION CFG_TUSB_MEM_SECTION +#endif + +// Attribute to align memory for host controller +#ifndef CFG_TUH_MEM_ALIGN + #define CFG_TUH_MEM_ALIGN CFG_TUSB_MEM_ALIGN +#endif + +#ifndef CFG_TUH_MEM_DCACHE_ENABLE + #ifndef CFG_TUH_MEM_DCACHE_ENABLE_DEFAULT + #define CFG_TUH_MEM_DCACHE_ENABLE_DEFAULT 0 + #endif + + #define CFG_TUH_MEM_DCACHE_ENABLE CFG_TUH_MEM_DCACHE_ENABLE_DEFAULT +#endif + +#ifndef CFG_TUH_MEM_DCACHE_LINE_SIZE + #define CFG_TUH_MEM_DCACHE_LINE_SIZE CFG_TUSB_MEM_DCACHE_LINE_SIZE +#endif + +// max events processed in one tuh_task_ext() call, 0 for unlimited +#ifndef CFG_TUH_TASK_EVENTS_PER_RUN + #define CFG_TUH_TASK_EVENTS_PER_RUN 16 +#endif + +//------------- CLASS -------------// + +#ifndef CFG_TUH_HUB + #define CFG_TUH_HUB 0 +#endif + +#ifndef CFG_TUH_CDC + #define CFG_TUH_CDC 0 +#endif + +// FTDI is not part of CDC class, only to re-use CDC driver API +#ifndef CFG_TUH_CDC_FTDI + #define CFG_TUH_CDC_FTDI 0 +#endif + +// List of product IDs that can use the FTDI CDC driver. 0x0403 is FTDI's VID +#ifndef CFG_TUH_CDC_FTDI_VID_PID_LIST + #define CFG_TUH_CDC_FTDI_VID_PID_LIST \ + {0x0403, 0x6001}, /* Similar device to SIO above */ \ + {0x0403, 0x6006}, /* FTDI's alternate PID for above */ \ + {0x0403, 0x6010}, /* Dual channel device */ \ + {0x0403, 0x6011}, /* Quad channel hi-speed device */ \ + {0x0403, 0x6014}, /* Single channel hi-speed device */ \ + {0x0403, 0x6015}, /* FT-X series (FT201X, FT230X, FT231X, etc) */ \ + {0x0403, 0x6040}, /* Dual channel hi-speed device with PD */ \ + {0x0403, 0x6041}, /* Quad channel hi-speed device with PD */ \ + {0x0403, 0x6042}, /* Dual channel hi-speed device with PD */ \ + {0x0403, 0x6043}, /* Quad channel hi-speed device with PD */ \ + {0x0403, 0x6044}, /* Dual channel hi-speed device with PD */ \ + {0x0403, 0x6045}, /* Dual channel hi-speed device with PD */ \ + {0x0403, 0x6048}, /* Quad channel automotive grade hi-speed device */ \ + {0x0403, 0x8372}, /* Product Id SIO application of 8U100AX */ \ + {0x0403, 0xFBFA}, /* Product ID for FT232RL */ \ + {0x0403, 0xCD18}, /* ??? */ +#endif + +// CP210X is not part of CDC class, only to re-use CDC driver API +#ifndef CFG_TUH_CDC_CP210X + #define CFG_TUH_CDC_CP210X 0 +#endif + +// List of product IDs that can use the CP210X CDC driver. 0x10C4 is Silicon Labs' VID +#ifndef CFG_TUH_CDC_CP210X_VID_PID_LIST + #define CFG_TUH_CDC_CP210X_VID_PID_LIST \ + { 0x10C4, 0xEA60 }, /* Silicon Labs factory default */ \ + { 0x10C4, 0xEA61 }, /* Silicon Labs factory default */ \ + { 0x10C4, 0xEA70 } /* Silicon Labs Dual Port factory default */ +#endif + +#ifndef CFG_TUH_CDC_CH34X + // CH34X is not part of CDC class, only to re-use CDC driver API + #define CFG_TUH_CDC_CH34X 0 +#endif + +// List of product IDs that can use the CH34X CDC driver +#ifndef CFG_TUH_CDC_CH34X_VID_PID_LIST + #define CFG_TUH_CDC_CH34X_VID_PID_LIST \ + { 0x1a86, 0x5523 }, /* ch341 chip */ \ + { 0x1a86, 0x7522 }, /* ch340k chip */ \ + { 0x1a86, 0x7523 }, /* ch340 chip */ \ + { 0x1a86, 0xe523 }, /* ch330 chip */ \ + { 0x4348, 0x5523 }, /* ch340 custom chip */ \ + { 0x2184, 0x0057 }, /* overtaken from Linux Kernel driver /drivers/usb/serial/ch341.c */ \ + { 0x9986, 0x7523 } /* overtaken from Linux Kernel driver /drivers/usb/serial/ch341.c */ +#endif + +#ifndef CFG_TUH_CDC_PL2303 + // PL2303 is not part of CDC class, only to re-use CDC driver API + #define CFG_TUH_CDC_PL2303 0 +#endif + +#ifndef CFG_TUH_CDC_PL2303_VID_PID_LIST + // List of product IDs that can use the PL2303 CDC driver + #define CFG_TUH_CDC_PL2303_VID_PID_LIST \ + { 0x067b, 0x2303 }, /* initial 2303 */ \ + { 0x067b, 0x2304 }, /* TB */ \ + { 0x067b, 0x23a3 }, /* GC */ \ + { 0x067b, 0x23b3 }, /* GB */ \ + { 0x067b, 0x23c3 }, /* GT */ \ + { 0x067b, 0x23d3 }, /* GL */ \ + { 0x067b, 0x23e3 }, /* GE */ \ + { 0x067b, 0x23f3 } /* GS */ +#endif + +#ifndef CFG_TUH_HID + #define CFG_TUH_HID 0 +#endif + +#ifndef CFG_TUH_MIDI + #define CFG_TUH_MIDI 0 +#endif + +#ifndef CFG_TUH_MSC + #define CFG_TUH_MSC 0 +#endif + +#ifndef CFG_TUH_VENDOR + #define CFG_TUH_VENDOR 0 +#endif + +#ifndef CFG_TUH_API_EDPT_XFER + #define CFG_TUH_API_EDPT_XFER 0 +#endif + +#ifndef CFG_TUH_EDPT_DEDICATED_HWFIFO + #define CFG_TUH_EDPT_DEDICATED_HWFIFO 0 +#endif + +//--------------------------------------------------------------------+ +// TypeC Options (Default) +//--------------------------------------------------------------------+ + +#ifndef CFG_TUC_ENABLED +#define CFG_TUC_ENABLED 0 + +#define tuc_int_handler(_p) +#endif + +//------------------------------------------------------------------ +// Configuration Validation +//------------------------------------------------------------------ +#if CFG_TUD_ENDPOINT0_SIZE > 64 + #error Control Endpoint Max Packet Size cannot be larger than 64 +#endif + +// To avoid GCC compiler warnings when -pedantic option is used (strict ISO C) +typedef int make_iso_compilers_happy; From fd1b46bc90413753c44eb76aa2300dd483b68a55 Mon Sep 17 00:00:00 2001 From: CValdesS Date: Sat, 10 Oct 2026 23:04:22 +0200 Subject: [PATCH 07/10] libraries/Adafruit_TinyUSB_Arduino: add the nRF54LM20 USBHS port The nRF54LM20's USBHS block is a Synopsys DWC2 core with a high-speed PHY, which TinyUSB drives through its dwc2 port (OPT_MCU_NRF54). - ports/nrf54: tusb_config (device-only CDC at high speed, 512-byte bulk endpoints) and the Arduino port. The USB task waits for VBUS (VREGUSB; bit 2 at +0x400 for the state at start-up, as in Zephyr's quirk for the nRF54L, VBUSDETECTED for a cable plugged in later) and starts PCLK24M (CLOCK XO24MSTART) before the stack, as the DWC2 registers are not accessible earlier. tinyusb_nrf54_stage and two timestamps record the bring-up for reading over SWD. - tusb_config.h / Adafruit_TinyUSB_API.h: select the port for ARDUINO_ARCH_NRF54. - dwc2_nrf.h: also match NRF54LM20A_XXAA (upstream only checks the engineering-sample NRF54LM20A_ENGA_XXAA, so the PHY bring-up compiled to nothing), and take nrfx_coredep.h from either soc/ or lib/ (nrfx 4). PCLK24M keeps the HFXO running, and the SoftDevice, which starts the HFXO itself (sd_softdevice_enable() included) and waits for XOTUNED, never gets one from a crystal that is already running and tuned: it spun in its clock code and the watchdog reset the chip. So the SoftDevice has to hold the HFXO before PCLK24M starts: - Bluefruit: the LF clock configuration and sd_softdevice_enable() move into bluefruit_softdevice_enable(), which only acts the first time; begin() calls it. The TinyUSB port calls it from TinyUSB_Port_InitDevice(), before setup(), when Bluefruit is linked (weak reference), and requests the HFXO through the SoftDevice (sd_clock_hfclk_request) before starting PCLK24M. USB then attaches once and never has to leave the bus to hand the crystal over. - Weak usb_softdevice_pre_enable() / usb_softdevice_post_enable() hooks around sd_softdevice_enable() cover a SoftDevice enabled after USB started: the port detaches, releases PCLK24M across the enable and attaches again. SD_EVT_IRQn is still only enabled by begin(); until then the flash driver drains its own completions (meshtastic #1), so this relies on that. Tested on the nRF54LM20-DK with Meshtastic: the SoftDevice up at 426 ms and PCLK24M 1 ms later, the CDC port at 0.7 s with no drop and the whole boot log; 3 boots to 60 s with no watchdog reset; 18 boot-time flash writes through the SoftDevice, 0 timeouts; the Meshtastic CLI and web client over USB, BLE alongside. --- .../src/arduino/Adafruit_TinyUSB_API.h | 1 + .../ports/nrf54/Adafruit_TinyUSB_nrf54.cpp | 199 ++++++++++++++++++ .../arduino/ports/nrf54/tusb_config_nrf54.h | 107 ++++++++++ .../src/portable/synopsys/dwc2/dwc2_nrf.h | 9 +- .../src/tusb_config.h | 3 + libraries/Bluefruit54Lib/src/bluefruit.cpp | 34 ++- 6 files changed, 347 insertions(+), 6 deletions(-) create mode 100644 libraries/Adafruit_TinyUSB_Arduino/src/arduino/ports/nrf54/Adafruit_TinyUSB_nrf54.cpp create mode 100644 libraries/Adafruit_TinyUSB_Arduino/src/arduino/ports/nrf54/tusb_config_nrf54.h diff --git a/libraries/Adafruit_TinyUSB_Arduino/src/arduino/Adafruit_TinyUSB_API.h b/libraries/Adafruit_TinyUSB_Arduino/src/arduino/Adafruit_TinyUSB_API.h index 77826a6..aa50c04 100644 --- a/libraries/Adafruit_TinyUSB_Arduino/src/arduino/Adafruit_TinyUSB_API.h +++ b/libraries/Adafruit_TinyUSB_Arduino/src/arduino/Adafruit_TinyUSB_API.h @@ -33,6 +33,7 @@ #define TINYUSB_API_VERSION 30000 #if defined(ARDUINO_ARCH_SAMD) || defined(ARDUINO_NRF52_ADAFRUIT) || \ + defined(ARDUINO_ARCH_NRF54) || \ defined(ARDUINO_ARCH_ESP32) || defined(ARDUINO_ARCH_CH32) || \ (defined(ARDUINO_ARCH_RP2040) && !defined(ARDUINO_ARCH_MBED)) #define TINYUSB_HAS_BUITLTIN_CORE_SUPPORT diff --git a/libraries/Adafruit_TinyUSB_Arduino/src/arduino/ports/nrf54/Adafruit_TinyUSB_nrf54.cpp b/libraries/Adafruit_TinyUSB_Arduino/src/arduino/ports/nrf54/Adafruit_TinyUSB_nrf54.cpp new file mode 100644 index 0000000..cadb621 --- /dev/null +++ b/libraries/Adafruit_TinyUSB_Arduino/src/arduino/ports/nrf54/Adafruit_TinyUSB_nrf54.cpp @@ -0,0 +1,199 @@ +/* + * The MIT License (MIT) + * + * Copyright (c) 2019 Ha Thach for Adafruit Industries + * + * 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_option.h" + +#if defined(ARDUINO_ARCH_NRF54) && CFG_TUD_ENABLED + +#include "nrf.h" +#include "nrf_soc.h" + +#include "Arduino.h" +#include "arduino/Adafruit_USBD_Device.h" + +//--------------------------------------------------------------------+ +// MACRO TYPEDEF CONSTANT ENUM DECLARATION +//--------------------------------------------------------------------+ + +#define USBD_STACK_SZ (256) + +// The core runs its peripheral interrupts at 3 alongside the SoftDevice +#define USBHS_IRQ_PRIORITY 3 + +// Bring-up record, readable over SWD. Stage: 1 waiting for VBUS, 4 starting +// PCLK24M, 5 starting the stack, 6 running. The two times are millis() when PCLK24M was requested and +// when the SoftDevice came up (0 = not yet). +extern "C" volatile uint32_t tinyusb_nrf54_stage; +extern "C" volatile uint32_t tinyusb_nrf54_pclk24m_ms; +extern "C" volatile uint32_t tinyusb_nrf54_softdevice_ms; +volatile uint32_t tinyusb_nrf54_stage = 0; +volatile uint32_t tinyusb_nrf54_pclk24m_ms = 0; +volatile uint32_t tinyusb_nrf54_softdevice_ms = 0; + +static volatile bool softdevice_enabled = false; +static volatile bool xo24m_requested = false; + +//--------------------------------------------------------------------+ +// Forward USB interrupt events to TinyUSB IRQ Handler +//--------------------------------------------------------------------+ +extern "C" void USBHS_IRQHandler(void) { tud_int_handler(0); } + +//--------------------------------------------------------------------+ +// Porting API +//--------------------------------------------------------------------+ + +// VREGUSB has no documented status register. Like Zephyr's dwc2 quirk for +// the nRF54L, take bit 2 at offset 0x400 as "VBUS present" for the state at +// start-up; VBUSDETECTED covers a cable plugged in later. +static bool vbus_present(void) { + uint32_t const status = + *(volatile uint32_t const *)((uintptr_t)NRF_VREGUSB + 0x400); + return (status & (1UL << 2)) != 0; +} + +// PCLK24M keeps the HFXO running, and the SoftDevice, which starts the HFXO +// itself (in sd_softdevice_enable() too) and waits for XOTUNED, never sees one +// from a crystal that is already running and tuned (datasheet, PCLK24M and +// HFXO): it spins there and the watchdog resets the chip. So once the +// SoftDevice is up it holds the HFXO first, and PCLK24M is released across +// sd_softdevice_enable() (see the hooks below). +static void start_pclk24m(void) { + if (softdevice_enabled) { + uint32_t running = 0; + sd_clock_hfclk_request(); + while (sd_clock_hfclk_is_running(&running) == NRF_SUCCESS && !running) { + } + } + + tinyusb_nrf54_pclk24m_ms = millis(); + NRF_CLOCK->EVENTS_XO24MSTARTED = 0; + NRF_CLOCK->TASKS_XO24MSTART = 1; + while (!NRF_CLOCK->EVENTS_XO24MSTARTED) { + } +} + +// USB Device Driver task +// This top level thread process all usb events and invoke callbacks +static void usb_device_task(void *param) { + (void)param; + + NVIC_SetPriority(USBHS_IRQn, USBHS_IRQ_PRIORITY); + + // The DWC2 registers can only be accessed once VBUS is valid and PCLK24M + // runs, so the stack starts only after both + tinyusb_nrf54_stage = 1; + NRF_VREGUSB->EVENTS_VBUSDETECTED = 0; + NRF_VREGUSB->TASKS_START = 1; + while (!vbus_present() && !NRF_VREGUSB->EVENTS_VBUSDETECTED) { + vTaskDelay(pdMS_TO_TICKS(100)); + } + NRF_VREGUSB->EVENTS_VBUSDETECTED = 0; + + // Start right away, before setup(), so a host can follow the boot log. When + // the SoftDevice comes up later, the hooks below hand the HFXO over to it, + // which drops USB off the bus once. + tinyusb_nrf54_stage = 4; + xo24m_requested = true; + start_pclk24m(); + + tinyusb_nrf54_stage = 5; + const tusb_rhport_init_t rh_init = { + .role = TUSB_ROLE_DEVICE, + .speed = TUD_OPT_HIGH_SPEED ? TUSB_SPEED_HIGH : TUSB_SPEED_FULL, + }; + tusb_init(0, &rh_init); + + tinyusb_nrf54_stage = 6; + + // RTOS forever loop + while (1) { + tud_task(); + TinyUSB_Device_FlushCDC(); + } +} + +// Called by Bluefruit right before sd_softdevice_enable(). If USB already runs, +// let the HFXO stop so the SoftDevice can start and tune it; USB leaves the bus +// until the post-enable hook brings PCLK24M back. Detach first: with the clock +// simply cut, the host keeps a port that has gone silent instead of seeing the +// device leave, and a terminal never reconnects. +extern "C" void usb_softdevice_pre_enable(void) { + if (!xo24m_requested) { + return; + } + + tud_disconnect(); + vTaskDelay(pdMS_TO_TICKS(20)); + NVIC_DisableIRQ(USBHS_IRQn); + NRF_CLOCK->TASKS_XO24MSTOP = 1; + for (uint32_t i = 0; + (NRF_CLOCK->XO.STAT & CLOCK_XO_STAT_STATE_Msk) && i < 100000; i++) { + } +} + +// Called by Bluefruit right after sd_softdevice_enable() +extern "C" void usb_softdevice_post_enable(void) { + softdevice_enabled = true; + tinyusb_nrf54_softdevice_ms = millis(); + + if (xo24m_requested) { + start_pclk24m(); + NVIC_EnableIRQ(USBHS_IRQn); + tud_connect(); + } +} + +// Bluefruit (Bluefruit54Lib), when linked +extern "C" bool bluefruit_softdevice_enable(void) __attribute__((weak)); + +void TinyUSB_Port_InitDevice(uint8_t rhport) { + (void)rhport; + + // Called before setup(). Enabling the SoftDevice here lets it hold the HFXO + // before PCLK24M starts, so USB attaches once and never has to drop off the + // bus to hand the crystal over when the application starts BLE. + if (bluefruit_softdevice_enable) { + bluefruit_softdevice_enable(); + } + + // Create a task for tinyusb device stack + xTaskCreate(usb_device_task, "usbd", USBD_STACK_SZ, NULL, TASK_PRIO_HIGH, + NULL); +} + +void TinyUSB_Port_EnterDFU(void) { + // Reset to Bootloader + enterSerialDfu(); +} + +uint8_t TinyUSB_Port_GetSerialNumber(uint8_t serial_id[16]) { + uint32_t *serial_32 = (uint32_t *)serial_id; + + serial_32[0] = __builtin_bswap32(NRF_FICR->INFO.DEVICEID[1]); + serial_32[1] = __builtin_bswap32(NRF_FICR->INFO.DEVICEID[0]); + + return 8; +} + +#endif // ARDUINO_ARCH_NRF54 && CFG_TUD_ENABLED diff --git a/libraries/Adafruit_TinyUSB_Arduino/src/arduino/ports/nrf54/tusb_config_nrf54.h b/libraries/Adafruit_TinyUSB_Arduino/src/arduino/ports/nrf54/tusb_config_nrf54.h new file mode 100644 index 0000000..515a4ba --- /dev/null +++ b/libraries/Adafruit_TinyUSB_Arduino/src/arduino/ports/nrf54/tusb_config_nrf54.h @@ -0,0 +1,107 @@ +/* + * The MIT License (MIT) + * + * Copyright (c) 2019 Ha Thach for Adafruit Industries + * + * 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_NRF54_H_ +#define _TUSB_CONFIG_NRF54_H_ + +#ifdef __cplusplus +extern "C" { +#endif + +// nRF54LM20: the USBHS block is a Synopsys DWC2 core with a high-speed PHY +// (OPT_MCU_NRF54 selects the dwc2 port, see dwc2_nrf.h). Device only. + +#ifndef pdTICKS_TO_MS +#define pdTICKS_TO_MS(xTimeInTicks) \ + ((TickType_t)(((uint64_t)(xTimeInTicks) * (uint64_t)1000U) / \ + (uint64_t)configTICK_RATE_HZ)) +#endif + +//-------------------------------------------------------------------- +// COMMON CONFIGURATION +//-------------------------------------------------------------------- +#define CFG_TUSB_MCU OPT_MCU_NRF54 +#define CFG_TUSB_OS OPT_OS_FREERTOS + +#ifndef CFG_TUSB_DEBUG +#define CFG_TUSB_DEBUG 0 +#endif + +#define CFG_TUSB_MEM_SECTION +#define CFG_TUSB_MEM_ALIGN __attribute__((aligned(4))) + +#ifdef USE_TINYUSB +#define CFG_TUD_ENABLED 1 +#else +#define CFG_TUD_ENABLED 0 +#endif +#define CFG_TUH_ENABLED 0 + +#define CFG_TUD_MAX_SPEED OPT_MODE_HIGH_SPEED + +// USB identity. Nothing is registered for this board: keep the library's +// default VID/PID (an Adafruit VID that host tools already list as a serial +// device) and only name the product. +#ifndef USB_MANUFACTURER +#define USB_MANUFACTURER "Nordic Semiconductor" +#endif +#ifndef USB_PRODUCT +#define USB_PRODUCT "nRF54LM20 DK" +#endif + +//-------------------------------------------------------------------- +// DEVICE CONFIGURATION +//-------------------------------------------------------------------- +#if CFG_TUD_ENABLED + +#define CFG_TUD_ENDPOINT0_SIZE 64 + +#ifndef CFG_TUD_CDC +#define CFG_TUD_CDC 1 +#endif +#ifndef CFG_TUD_MSC +#define CFG_TUD_MSC 0 +#endif +#ifndef CFG_TUD_HID +#define CFG_TUD_HID 0 +#endif +#ifndef CFG_TUD_MIDI +#define CFG_TUD_MIDI 0 +#endif +#ifndef CFG_TUD_VENDOR +#define CFG_TUD_VENDOR 0 +#endif + +// Bulk endpoints are 512 bytes at high speed +#define CFG_TUD_CDC_EP_BUFSIZE 512 +#define CFG_TUD_CDC_RX_BUFSIZE 512 +#define CFG_TUD_CDC_TX_BUFSIZE 1024 + +#endif // CFG_TUD_ENABLED + +#ifdef __cplusplus +} +#endif + +#endif // _TUSB_CONFIG_NRF54_H_ diff --git a/libraries/Adafruit_TinyUSB_Arduino/src/portable/synopsys/dwc2/dwc2_nrf.h b/libraries/Adafruit_TinyUSB_Arduino/src/portable/synopsys/dwc2/dwc2_nrf.h index a1bf692..041eaee 100644 --- a/libraries/Adafruit_TinyUSB_Arduino/src/portable/synopsys/dwc2/dwc2_nrf.h +++ b/libraries/Adafruit_TinyUSB_Arduino/src/portable/synopsys/dwc2/dwc2_nrf.h @@ -37,7 +37,12 @@ #pragma GCC diagnostic ignored "-Wcast-align" #endif +// nrfx 4 moved nrfx_coredep.h from soc/ to lib/ +#if __has_include() #include +#else +#include +#endif #ifdef __GNUC__ #pragma GCC diagnostic pop @@ -46,7 +51,7 @@ #define DWC2_EP_MAX 16 // Use the auto-resolving peripheral pointer (respects TrustZone secure/non-secure mapping) -#if defined(NRF54LM20A_ENGA_XXAA) +#if defined(NRF54LM20A_ENGA_XXAA) || defined(NRF54LM20A_XXAA) #define DWC2_REG_BASE ((uintptr_t)NRF_USBHSCORE) #else #define DWC2_REG_BASE ((uintptr_t)NRF_USBHSCORE0) @@ -61,7 +66,7 @@ TU_ATTR_ALWAYS_INLINE static inline void dwc2_clock_init(uint8_t rhport, tusb_ro (void) rhport; (void) role; - #if defined(NRF54LM20A_ENGA_XXAA) + #if defined(NRF54LM20A_ENGA_XXAA) || defined(NRF54LM20A_XXAA) // Start the USB voltage regulator NRF_VREGUSB->TASKS_START = VREGUSB_TASKS_START_TASKS_START_Trigger; diff --git a/libraries/Adafruit_TinyUSB_Arduino/src/tusb_config.h b/libraries/Adafruit_TinyUSB_Arduino/src/tusb_config.h index aefbd6a..f559bea 100644 --- a/libraries/Adafruit_TinyUSB_Arduino/src/tusb_config.h +++ b/libraries/Adafruit_TinyUSB_Arduino/src/tusb_config.h @@ -35,6 +35,9 @@ #elif defined(ARDUINO_NRF52_ADAFRUIT) #include "arduino/ports/nrf/tusb_config_nrf.h" +#elif defined(ARDUINO_ARCH_NRF54) + #include "arduino/ports/nrf54/tusb_config_nrf54.h" + #elif defined(ARDUINO_ARCH_RP2040) #include "arduino/ports/rp2040/tusb_config_rp2040.h" diff --git a/libraries/Bluefruit54Lib/src/bluefruit.cpp b/libraries/Bluefruit54Lib/src/bluefruit.cpp index 6408faa..0f20957 100644 --- a/libraries/Bluefruit54Lib/src/bluefruit.cpp +++ b/libraries/Bluefruit54Lib/src/bluefruit.cpp @@ -42,6 +42,15 @@ uint32_t sd_app_ram_start_required = 0; #include "utility/bonding.h" +// Defined by the TinyUSB port on the nRF54LM20, which has to hand the HFXO over to the SoftDevice +extern "C" __attribute__((weak)) void usb_softdevice_pre_enable(void) +{ +} + +extern "C" __attribute__((weak)) void usb_softdevice_post_enable(void) +{ +} + #ifndef CFG_BLE_TX_POWER_LEVEL #define CFG_BLE_TX_POWER_LEVEL 0 #endif @@ -257,10 +266,12 @@ void AdafruitBluefruit::configCentralBandwidth(uint8_t bw) } } -bool AdafruitBluefruit::begin(uint8_t prph_count, uint8_t central_count) +// Enables the SoftDevice, once. Shared by begin() and the TinyUSB port on the nRF54LM20, which enables it +// before setup() so that it already holds the HFXO when USB starts PCLK24M (see Adafruit_TinyUSB_nrf54.cpp) +extern "C" bool bluefruit_softdevice_enable(void) { - _prph_count = prph_count; - _central_count = central_count; + static bool enabled = false; + if ( enabled ) return true; // Configure Clock #if defined( USE_LFXO ) @@ -289,12 +300,27 @@ bool AdafruitBluefruit::begin(uint8_t prph_count, uint8_t central_count) #error Clock Source is not configured, define USE_LFXO or USE_LFRC according to your board in variant.h #endif - // Enable SoftDevice + // Enable SoftDevice. The USB port (TinyUSB, nRF54LM20) releases PCLK24M across it + usb_softdevice_pre_enable(); extern uint32_t __softdevice_start__; sd_isr_forwarding_enable((uint32_t) &__softdevice_start__); uint32_t sd_err = sd_softdevice_enable(&clock_cfg, nrf_error_cb); if ( sd_err != NRF_SUCCESS ) sd_isr_forwarding_disable(); VERIFY_STATUS( sd_err, false ); + enabled = true; + + // The SoftDevice now owns the HFXO; the USB port (TinyUSB, nRF54LM20) has to request it here + usb_softdevice_post_enable(); + + return true; +} + +bool AdafruitBluefruit::begin(uint8_t prph_count, uint8_t central_count) +{ + _prph_count = prph_count; + _central_count = central_count; + + VERIFY( bluefruit_softdevice_enable(), false ); // sd_ble_enable() fails with INVALID_STATE until the RNG is seeded VERIFY( seed_softdevice_rng(), false ); From cbe2d17a63e1160814cb3ec093a86459585c3ee0 Mon Sep 17 00:00:00 2001 From: CValdesS Date: Sat, 10 Oct 2026 17:44:10 +0200 Subject: [PATCH 08/10] cores: start TinyUSB on the nRF54LM20 when USE_TINYUSB is set - startup_nrf54lm20a.S: give IRQ 90 (USBHS) a weak USBHS_IRQHandler; it sat in the Default_Handler block, so the port's handler was never reached (and was dropped by the linker). - main.cpp: TinyUSB_Device_Init(0) before setup(), as on nRF52. - Arduino.h / Uart.h: with USE_TINYUSB, Serial is the TinyUSB CDC port instead of an alias of the console UART. --- cores/nRF5/Arduino.h | 5 +++++ cores/nRF5/Uart.h | 9 ++++++--- cores/nRF5/main.cpp | 4 ++++ cores/nRF5/startup_nrf54lm20a.S | 17 +++++++++++++++-- 4 files changed, 30 insertions(+), 5 deletions(-) diff --git a/cores/nRF5/Arduino.h b/cores/nRF5/Arduino.h index 12efa81..0cc52a1 100644 --- a/cores/nRF5/Arduino.h +++ b/cores/nRF5/Arduino.h @@ -60,6 +60,11 @@ void resumeLoop(void); #include "Uart.h" #endif +#ifdef USE_TINYUSB +// Needed for declaring Serial +#include "Adafruit_USBD_CDC.h" +#endif + #include "delay.h" #include "binary.h" #include "common_inc.h" diff --git a/cores/nRF5/Uart.h b/cores/nRF5/Uart.h index 7567184..f069f7c 100644 --- a/cores/nRF5/Uart.h +++ b/cores/nRF5/Uart.h @@ -108,7 +108,10 @@ extern Uart Serial1; extern Uart Serial2; #endif -// On nRF52 BSPs the plain `Serial` symbol is the USB CDC port; nRF54L has -// no USB peripheral, so map `Serial` onto the console UART so existing -// Arduino sketches that print to `Serial` keep working. +// On nRF52 BSPs the plain `Serial` symbol is the USB CDC port. Without USB +// (nRF54L05/L10/L15, or TinyUSB not selected) map `Serial` onto the console +// UART so existing Arduino sketches that print to `Serial` keep working; with +// USE_TINYUSB (nRF54LM20 USBHS) the TinyUSB library declares it as the CDC port. +#ifndef USE_TINYUSB #define Serial SERIAL_PORT_CONSOLE +#endif diff --git a/cores/nRF5/main.cpp b/cores/nRF5/main.cpp index 781d123..f519732 100644 --- a/cores/nRF5/main.cpp +++ b/cores/nRF5/main.cpp @@ -52,6 +52,10 @@ static void loop_task(void* arg) { (void) arg; +#ifdef USE_TINYUSB + TinyUSB_Device_Init(0); +#endif + #if CFG_DEBUG // If Serial is not begin(), call it to avoid hard fault if(!Serial) Serial.begin(115200); diff --git a/cores/nRF5/startup_nrf54lm20a.S b/cores/nRF5/startup_nrf54lm20a.S index d034b1c..e0e4af5 100644 --- a/cores/nRF5/startup_nrf54lm20a.S +++ b/cores/nRF5/startup_nrf54lm20a.S @@ -89,8 +89,14 @@ g_pfnVectors: .word Default_Handler /* IRQ 77: SERIAL00 (UARTE00/SPIM00/TWIM00) - Serial1 */ .word SERIAL00_IRQHandler - /* IRQ 78..132 */ - .rept 55 + /* IRQ 78..89 */ + .rept 12 + .word Default_Handler + .endr + /* IRQ 90: USBHS - the TinyUSB port defines the handler when USB is used. */ + .word USBHS_IRQHandler + /* IRQ 91..132 */ + .rept 42 .word Default_Handler .endr /* IRQ 133: TIMER10 - forwarded to the SoftDevice (nordic/sd_isr.S). */ @@ -417,6 +423,13 @@ QDEC21_IRQHandler: /* Serial2 is only instantiated when a variant defines PIN_SERIAL2_*; * Wire only claims SERIAL22 in target mode. Both slots still have to * resolve when the variant leaves them unused. */ + /* USBHS: only claimed by the TinyUSB port (USE_TINYUSB). */ + .weak USBHS_IRQHandler + .type USBHS_IRQHandler, %function +USBHS_IRQHandler: + b . + .size USBHS_IRQHandler, .-USBHS_IRQHandler + .weak SERIAL20_IRQHandler .type SERIAL20_IRQHandler, %function SERIAL20_IRQHandler: From 63cdd0024f54bbcf6e7efc00d3e324a2914e6488 Mon Sep 17 00:00:00 2001 From: CValdesS Date: Sat, 10 Oct 2026 20:20:08 +0200 Subject: [PATCH 09/10] linker: add nrf54lm20a_s145_v10_bl64k.ld for a 64 KB bootloader nRF54_Bootloader's USB DFU (nRF54LM20-DK) does not fit in 32 KB, so that bootloader takes 64 KB (linker/nrf54lm20a_bl64k.ld) and starts the application at 0x10000. This script is nrf54lm20a_s145_v10.ld with FLASH moved to match; InternalFS, the settings page and the SoftDevice stay where they were. A board selects it with build.arduino.ldscript. --- .../nRF5/linker/nrf54lm20a_s145_v10_bl64k.ld | 54 +++++++++++++++++++ 1 file changed, 54 insertions(+) create mode 100644 cores/nRF5/linker/nrf54lm20a_s145_v10_bl64k.ld diff --git a/cores/nRF5/linker/nrf54lm20a_s145_v10_bl64k.ld b/cores/nRF5/linker/nrf54lm20a_s145_v10_bl64k.ld new file mode 100644 index 0000000..299f641 --- /dev/null +++ b/cores/nRF5/linker/nrf54lm20a_s145_v10_bl64k.ld @@ -0,0 +1,54 @@ +/* nRF54LM20A application with the s145 v10.0.1 SoftDevice, behind a 64 KB + * bootloader (USB DFU, as on the nRF54LM20-DK). + * + * Same as nrf54lm20a_s145_v10.ld except for where the application starts: + * the USB stack does not fit in a 32 KB bootloader. Keep the rest of the two + * in step. + * + * The layout is a contract with linker/nrf54lm20a_bl64k.ld in nRF54_Bootloader + * (CODE_REGION_1_START=0x10000 in the board's board.cmake) and has to change + * together with it. s145 has no MBR, so the bootloader owns RRAM 0x0 and the + * reset vector: + * + * 0x000000 - 0x010000 bootloader (63 KB code + 1 KB config) + * 0x010000 - 0x1C9000 application: this image, own vector table + * 0x1C9000 - 0x1D1000 InternalFS / LittleFS (32 KB, __flash_arduino_size) + * 0x1D1000 - 0x1D2000 bootloader settings page + * 0x1D2000 - 0x1DA800 unused + * 0x1DA800 - 0x1FCC00 s145 v10.0.1; RRAM is 0x1FD000 (2036 KB) + */ +SEARCH_DIR(.) +GROUP(-lgcc -lc -lnosys) + +MEMORY +{ + FLASH (rx) : ORIGIN = 0x00010000, LENGTH = 0x1B9000 + + /* 0x20000000 - 0x20004800 belongs to the SoftDevice (s145 v10.0.1 needs + * 0x1480 minimum, the rest covers sd_ble_enable()), the application runs + * from 0x20004800 to 0x2003FF80, and the last 128 bytes of the first RAM + * bank are the bootloader handover area below. + * + * The LM20A has a second 256 KB bank at 0x20040000, which this script does + * not use. The bootloader only uses the first bank and keeps its handover + * area at the top of it, so application RAM spanning both would load and + * zero straight over that area. The second bank's top is also not usable: + * measured on a XIAO nRF54LM20A over SWD, 0x2007FEE0 and above bus-faults + * for both the debugger and the CPU, and a __StackTop at a nominal + * 0x20080000 locks the core up on the first push in SystemInit. */ + RAM (rwx) : ORIGIN = 0x20004800, LENGTH = 0x3B780 + + /* Bootloader handover area, which the application must never load or zero: + * 60 bytes of DFU peer data at 0x2003FF80 followed by its 16-bit CRC + * (BLEDfu.cpp writes them, dfu_ble_svc.c in the bootloader reads them), and + * directly above, at 0x2003FFF8, the bootloader's double-reset word. */ + NOINIT (rwx) : ORIGIN = 0x2003FF80, LENGTH = 0x78 +} + +/* InternalFS size; see __flash_arduino_size in nrf54_common.ld. */ +__flash_arduino_size = 0x8000; + +/* Base of the SoftDevice image; see __softdevice_start__ in nrf54_common.ld. */ +__softdevice_start__ = 0x001DA800; + +INCLUDE "nrf54_common.ld" From 756624688b68ca781f39ee727e05bfbeaad1ab1e Mon Sep 17 00:00:00 2001 From: CValdesS Date: Sat, 10 Oct 2026 21:06:15 +0200 Subject: [PATCH 10/10] cores: define enterUf2Dfu() (GPREGRET 0x57) wiring.h declared it but nothing defined it. 0x57 is DFU_MAGIC_UF2_RESET, as in Adafruit's nRF52 bootloader: nRF54_Bootloader with USB (nRF54LM20) then shows its UF2 drive; a bootloader without USB does serial DFU over its UART. --- cores/nRF5/wiring.c | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/cores/nRF5/wiring.c b/cores/nRF5/wiring.c index c7fb808..36ae0aa 100644 --- a/cores/nRF5/wiring.c +++ b/cores/nRF5/wiring.c @@ -25,6 +25,7 @@ nrf_nvic_state_t nrf_nvic_state; #define DFU_MAGIC_SERIAL_ONLY_RESET 0x4e +#define DFU_MAGIC_UF2_RESET 0x57 #define DFU_MAGIC_OTA_RESET 0xA8 // Must match temp register in bootloader @@ -106,6 +107,13 @@ void enterOTADfu(void) reset_mcu(DFU_MAGIC_OTA_RESET); } +// Bootloader with USB (nRF54LM20): CDC and the UF2 drive. Without USB the bootloader falls back +// to serial DFU over its UART. +void enterUf2Dfu(void) +{ + reset_mcu(DFU_MAGIC_UF2_RESET); +} + void waitForEvent(void) { #if 0