From ed1c70837531184a632c9cb1fe0649bf661ea139 Mon Sep 17 00:00:00 2001 From: Michael Pobega Date: Wed, 15 Jul 2026 08:53:51 -0400 Subject: [PATCH] feat(tg5040): keep device awake while connected as a USB gadget Add an opt-in "Keep awake over USB" setting that prevents screen-off and deep sleep while the device is enumerated as a USB gadget by a host, as distinct from merely charging. Detection reads /sys/class/udc/*/state and treats "configured" as connected (PLAT_isUSBConnected), so it reflects an active data connection rather than power presence. This matters when the battery is full: charging already blocks sleep, but a full battery on USB would otherwise let the device sleep and drop the connection. The state is polled on the existing battery thread into a new pwr.is_usb_connected atomic and, gated by CFG_getKeepAwakeWhenUSB, is factored into both PWR_preventAutosleep() (no screen-off) and the PWR_waitForWake() deep-sleep deferral, mirroring how is_charging is handled. Setting defaults to off and is exposed in Settings under the tg5040-only block. tg5050 and desktop get PLAT_isUSBConnected stubs returning 0. Disclosure: LLMs were used to develop this code. --- workspace/all/common/api.c | 12 ++++++++-- workspace/all/common/api.h | 2 ++ workspace/all/common/config.c | 23 +++++++++++++++++++ workspace/all/common/config.h | 5 ++++ workspace/all/settings/settings.cpp | 7 ++++++ workspace/desktop/platform/platform.c | 5 ++++ workspace/tg5040/platform/platform.c | 33 +++++++++++++++++++++++++++ workspace/tg5050/platform/platform.c | 6 +++++ 8 files changed, 91 insertions(+), 2 deletions(-) diff --git a/workspace/all/common/api.c b/workspace/all/common/api.c index f70c2bc31..aa5a299bb 100644 --- a/workspace/all/common/api.c +++ b/workspace/all/common/api.c @@ -184,6 +184,7 @@ static struct PWR_Context pthread_t battery_pt; SDL_atomic_t is_charging; + SDL_atomic_t is_usb_connected; SDL_atomic_t charge; SDL_atomic_t is_online; @@ -3967,6 +3968,7 @@ static void PWR_updateBatteryStatus(void) PLAT_getBatteryStatusFine(&is_charging, &charge); SDL_AtomicSet(&pwr.is_charging, is_charging); SDL_AtomicSet(&pwr.charge, charge); + SDL_AtomicSet(&pwr.is_usb_connected, PLAT_isUSBConnected()); // this is technically redundant, but PWR_update() might not always be called to conserve battery and cycles LEDS_applyRules(); @@ -4315,7 +4317,8 @@ static void PWR_waitForWake(void) SDL_Delay(200); if (SDL_GetTicks() - sleep_ticks >= sleepDelay) { // increased to two minutes - if (SDL_AtomicGet(&pwr.is_charging)) + if (SDL_AtomicGet(&pwr.is_charging) || + (CFG_getKeepAwakeWhenUSB() && SDL_AtomicGet(&pwr.is_usb_connected))) { sleep_ticks += 60000; // check again in a minute continue; @@ -4396,7 +4399,8 @@ void PWR_enableAutosleep(void) } int PWR_preventAutosleep(void) { - return SDL_AtomicGet(&pwr.is_charging) || !pwr.can_autosleep || GetHDMI(); + return SDL_AtomicGet(&pwr.is_charging) || !pwr.can_autosleep || GetHDMI() || + (CFG_getKeepAwakeWhenUSB() && SDL_AtomicGet(&pwr.is_usb_connected)); } // updated by PWR_updateBatteryStatus() @@ -4404,6 +4408,10 @@ int PWR_isCharging(void) { return SDL_AtomicGet(&pwr.is_charging); } +int PWR_isUSBConnected(void) +{ + return SDL_AtomicGet(&pwr.is_usb_connected); +} int PWR_getBattery(void) { // 10-100 in 10-20% fragments return SDL_AtomicGet(&pwr.charge); diff --git a/workspace/all/common/api.h b/workspace/all/common/api.h index 5ebb91f33..828af8271 100644 --- a/workspace/all/common/api.h +++ b/workspace/all/common/api.h @@ -581,6 +581,7 @@ void PWR_enableAutosleep(void); int PWR_preventAutosleep(void); int PWR_isCharging(void); +int PWR_isUSBConnected(void); int PWR_getBattery(void); int PWR_isOnline(void); @@ -732,6 +733,7 @@ int PLAT_supportsOverscan(void); #define PWR_LOW_CHARGE 10 void PLAT_getBatteryStatus(int* is_charging, int* charge); // 0,1 and 0,10,20,40,60,80,100 void PLAT_getBatteryStatusFine(int* is_charging, int* charge); // 0,1 and 0-100 +int PLAT_isUSBConnected(void); // 1 if the device is configured as a USB gadget on a host, else 0 void PLAT_enableBacklight(int enable); int PLAT_supportsDeepSleep(void); int PLAT_deepSleep(void); diff --git a/workspace/all/common/config.c b/workspace/all/common/config.c index cc735eec7..0b7e6e7df 100644 --- a/workspace/all/common/config.c +++ b/workspace/all/common/config.c @@ -68,6 +68,7 @@ void CFG_defaults(NextUISettings *cfg) .screenTimeoutSecs = CFG_DEFAULT_SCREENTIMEOUTSECS, .suspendTimeoutSecs = CFG_DEFAULT_SUSPENDTIMEOUTSECS, .powerOffProtection = CFG_DEFAULT_POWEROFFPROTECTION, + .keepAwakeWhenUSB = CFG_DEFAULT_KEEPAWAKEWHENUSB, .haptics = CFG_DEFAULT_HAPTICS, .romsUseFolderBackground = CFG_DEFAULT_ROMSUSEFOLDERBACKGROUND, @@ -302,6 +303,11 @@ void CFG_init(FontLoad_callback_t cb, ColorSet_callback_t ccb) CFG_setPowerOffProtection((bool)temp_value); continue; } + if (sscanf(line, "keepAwakeWhenUSB=%i", &temp_value) == 1) + { + CFG_setKeepAwakeWhenUSB((bool)temp_value); + continue; + } if (sscanf(line, "switcherscale=%i", &temp_value) == 1) { CFG_setGameSwitcherScaling(temp_value); @@ -703,6 +709,17 @@ void CFG_setPowerOffProtection(bool enable) CFG_sync(); } +bool CFG_getKeepAwakeWhenUSB(void) +{ + return settings.keepAwakeWhenUSB; +} + +void CFG_setKeepAwakeWhenUSB(bool enable) +{ + settings.keepAwakeWhenUSB = enable; + CFG_sync(); +} + bool CFG_getShowClock(void) { return settings.showClock; @@ -1355,6 +1372,10 @@ void CFG_get(const char *key, char *value) { sprintf(value, "%i", CFG_getPowerOffProtection()); } + else if (strcmp(key, "keepAwakeWhenUSB") == 0) + { + sprintf(value, "%i", CFG_getKeepAwakeWhenUSB()); + } else if (strcmp(key, "switcherscale") == 0) { sprintf(value, "%i", CFG_getGameSwitcherScaling()); @@ -1552,6 +1573,7 @@ void CFG_sync(void) fprintf(file, "screentimeout=%i\n", settings.screenTimeoutSecs); fprintf(file, "suspendTimeout=%i\n", settings.suspendTimeoutSecs); fprintf(file, "powerOffProtection=%i\n", settings.powerOffProtection); + fprintf(file, "keepAwakeWhenUSB=%i\n", settings.keepAwakeWhenUSB); fprintf(file, "switcherscale=%i\n", settings.gameSwitcherScaling); fprintf(file, "haptics=%i\n", settings.haptics); fprintf(file, "romfolderbg=%i\n", settings.romsUseFolderBackground); @@ -1622,6 +1644,7 @@ void CFG_print(void) printf("\t\"screentimeout\": %i,\n", settings.screenTimeoutSecs); printf("\t\"suspendTimeout\": %i,\n", settings.suspendTimeoutSecs); printf("\t\"powerOffProtection\": %i,\n", settings.powerOffProtection); + printf("\t\"keepAwakeWhenUSB\": %i,\n", settings.keepAwakeWhenUSB); printf("\t\"switcherscale\": %i,\n", settings.gameSwitcherScaling); printf("\t\"haptics\": %i,\n", settings.haptics); printf("\t\"romfolderbg\": %i,\n", settings.romsUseFolderBackground); diff --git a/workspace/all/common/config.h b/workspace/all/common/config.h index 3d8b7e80c..fc2aeb0b3 100644 --- a/workspace/all/common/config.h +++ b/workspace/all/common/config.h @@ -139,6 +139,7 @@ typedef struct uint32_t screenTimeoutSecs; uint32_t suspendTimeoutSecs; bool powerOffProtection; + bool keepAwakeWhenUSB; // Emulator int saveFormat; @@ -215,6 +216,7 @@ typedef struct #define CFG_DEFAULT_SCREENTIMEOUTSECS 60 #define CFG_DEFAULT_SUSPENDTIMEOUTSECS 30 #define CFG_DEFAULT_POWEROFFPROTECTION true +#define CFG_DEFAULT_KEEPAWAKEWHENUSB false #define CFG_DEFAULT_HAPTICS false #define CFG_DEFAULT_ROMSUSEFOLDERBACKGROUND true #define CFG_DEFAULT_SAVEFORMAT SAVE_FORMAT_SAV @@ -310,6 +312,9 @@ void CFG_setSuspendTimeoutSecs(uint32_t secs); // Enable/disable PMIC power-off protection mode. bool CFG_getPowerOffProtection(void); void CFG_setPowerOffProtection(bool enable); +// Keep the device awake while it is connected to a host as a USB device. +bool CFG_getKeepAwakeWhenUSB(void); +void CFG_setKeepAwakeWhenUSB(bool enable); // Show/hide clock in the status pill. bool CFG_getShowClock(void); void CFG_setShowClock(bool show); diff --git a/workspace/all/settings/settings.cpp b/workspace/all/settings/settings.cpp index 3e8185482..0fe461046 100644 --- a/workspace/all/settings/settings.cpp +++ b/workspace/all/settings/settings.cpp @@ -633,6 +633,13 @@ int main(int argc, char *argv[]) [](const std::any &value) { CFG_setPowerOffProtection(std::any_cast(value)); }, []() { CFG_setPowerOffProtection(CFG_DEFAULT_POWEROFFPROTECTION); }} ); + + systemItems.push_back( + new MenuItem{ListItemType::Generic, "Keep awake over USB", "Prevent screen-off and sleep while connected to a\ncomputer as a USB device (not just charging).", {false, true}, on_off, + []() -> std::any { return CFG_getKeepAwakeWhenUSB(); }, + [](const std::any &value) { CFG_setKeepAwakeWhenUSB(std::any_cast(value)); }, + []() { CFG_setKeepAwakeWhenUSB(CFG_DEFAULT_KEEPAWAKEWHENUSB); }} + ); } if(deviceInfo.hasActiveCooling()) diff --git a/workspace/desktop/platform/platform.c b/workspace/desktop/platform/platform.c index ab9fe52c0..48091515e 100644 --- a/workspace/desktop/platform/platform.c +++ b/workspace/desktop/platform/platform.c @@ -48,6 +48,11 @@ void PLAT_getBatteryStatusFine(int* is_charging, int* charge) *charge = 100; } +int PLAT_isUSBConnected(void) +{ + return 0; +} + void PLAT_enableBacklight(int enable) { // buh } diff --git a/workspace/tg5040/platform/platform.c b/workspace/tg5040/platform/platform.c index afa0e3777..0ef532ff5 100644 --- a/workspace/tg5040/platform/platform.c +++ b/workspace/tg5040/platform/platform.c @@ -189,6 +189,39 @@ void PLAT_getBatteryStatusFine(int *is_charging, int *charge) } } +int PLAT_isUSBConnected(void) +{ + // The UDC (USB Device Controller) reports "configured" once a host has + // enumerated us as a USB gadget. This is independent of merely being + // plugged into a charger, so it lets us tell "connected to a computer" + // apart from "connected to power". + DIR *dir = opendir("/sys/class/udc"); + if (!dir) + return 0; + + int connected = 0; + struct dirent *entry; + while ((entry = readdir(dir)) != NULL) + { + if (entry->d_name[0] == '.') + continue; + + char path[512]; + snprintf(path, sizeof(path), "/sys/class/udc/%s/state", entry->d_name); + + char state[32] = {0}; + getFile(path, state, sizeof(state)); + if (strncmp(state, "configured", 10) == 0) + { + connected = 1; + break; + } + } + + closedir(dir); + return connected; +} + void PLAT_enableBacklight(int enable) { if (enable) { if (is_brick || is_brickpro) SetRawBrightness(8); diff --git a/workspace/tg5050/platform/platform.c b/workspace/tg5050/platform/platform.c index c20ebdc7c..4cd03c15e 100644 --- a/workspace/tg5050/platform/platform.c +++ b/workspace/tg5050/platform/platform.c @@ -214,6 +214,12 @@ void PLAT_getBatteryStatusFine(int *is_charging, int *charge) } } +int PLAT_isUSBConnected(void) +{ + // Not implemented for this platform yet. + return 0; +} + void PLAT_enableBacklight(int enable) { if (enable) { SetBrightness(GetBrightness());