From 049fdb2bc080b85cb51a58faeab2b6642ac4f883 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Kope=C4=87?= Date: Thu, 13 Feb 2025 13:21:06 +0100 Subject: [PATCH] Add Always on USB MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add option to partially power platform while off (VccPRIM and VDD5), in order to keep the USB-A and USB-C ports powered. This allows using the laptop as a power bank while it is off. Refactor the power sequencing code to make it easier to partially power the platform instead of only going full on or full off. Power draw measured at USB-C plug is 0.161W, so we still get weeks of standby. Tested only on V540TU so far. Both USB-C ports work, and the right USB-A port works in BC 1.2 SDP mode. Left USB-A port does not work, but that's expected. Signed-off-by: Michał Kopeć --- .../system76/common/include/board/options.h | 1 + .../system76/common/include/board/power.h | 8 +- src/board/system76/common/options.c | 5 +- src/board/system76/common/power.c | 147 +++++++++++++----- src/board/system76/common/usbpd/tps65987.c | 6 +- 5 files changed, 119 insertions(+), 48 deletions(-) diff --git a/src/board/system76/common/include/board/options.h b/src/board/system76/common/include/board/options.h index fb16514f6..540af1de1 100644 --- a/src/board/system76/common/include/board/options.h +++ b/src/board/system76/common/include/board/options.h @@ -22,6 +22,7 @@ enum { OPT_BAT_THRESHOLD_START, OPT_BAT_THRESHOLD_STOP, OPT_ALLOW_BAT_BOOST, + OPT_ALWAYS_ON_USB, NUM_OPTIONS }; diff --git a/src/board/system76/common/include/board/power.h b/src/board/system76/common/include/board/power.h index 0381dc010..91180b7d3 100644 --- a/src/board/system76/common/include/board/power.h +++ b/src/board/system76/common/include/board/power.h @@ -6,10 +6,16 @@ #include enum PowerState { + // Full On POWER_STATE_S0 = 0x0, + // Suspend to RAM POWER_STATE_S3 = 0x3, + // Soft off POWER_STATE_S5 = 0x5, - POWER_STATE_OFF = 0xf, + // Only USB is powered (Always On USB) + POWER_STATE_G3_AOU = 0xfe, + // Platform is off + POWER_STATE_G3 = 0xff, }; extern enum PowerState power_state; diff --git a/src/board/system76/common/options.c b/src/board/system76/common/options.c index 8fe62299d..d67c93cc2 100644 --- a/src/board/system76/common/options.c +++ b/src/board/system76/common/options.c @@ -12,9 +12,10 @@ uint8_t DEFAULT_OPTIONS[NUM_OPTIONS] = { [OPT_POWER_ON_AC] = 0, [OPT_KBLED_LEVEL_I] = 0, [OPT_KBLED_COLOR_I] = 0, - [OPT_BAT_THRESHOLD_START] = 95, - [OPT_BAT_THRESHOLD_STOP] = 98, + [OPT_BAT_THRESHOLD_START] = 1, + [OPT_BAT_THRESHOLD_STOP] = 10, [OPT_ALLOW_BAT_BOOST] = 0, + [OPT_ALWAYS_ON_USB] = 1, }; // clang-format on diff --git a/src/board/system76/common/power.c b/src/board/system76/common/power.c index 401b5b014..12f23104b 100644 --- a/src/board/system76/common/power.c +++ b/src/board/system76/common/power.c @@ -132,16 +132,21 @@ extern uint8_t main_cycle; // RSMRST# de-assertion to SUSPWRDNACK valid #define tPLT01 delay_ms(200) -enum PowerState power_state = POWER_STATE_OFF; +enum PowerState power_state = POWER_STATE_G3; #if USE_S0IX uint8_t pep_hook = PEP_DISPLAY_FLAG; #endif enum PowerState calculate_power_state(void) { + if (!gpio_get(&USB_CHARGE_EN)) { + // VDD5, VccPRIM not powered + return POWER_STATE_G3; + } + if (!gpio_get(&EC_RSMRST_N)) { // S5 plane not powered - return POWER_STATE_OFF; + return POWER_STATE_G3_AOU; } #if CONFIG_BUS_ESPI @@ -184,8 +189,11 @@ void update_power_state(void) { #if LEVEL >= LEVEL_DEBUG switch (power_state) { - case POWER_STATE_OFF: - DEBUG("POWER_STATE_OFF\n"); + case POWER_STATE_G3: + DEBUG("POWER_STATE_G3\n"); + break; + case POWER_STATE_G3_AOU: + DEBUG("POWER_STATE_G3_AOU\n"); break; case POWER_STATE_S5: DEBUG("POWER_STATE_S5\n"); @@ -201,6 +209,13 @@ void update_power_state(void) { } } +static bool is_standby_power_needed(void) { + if (options_get(OPT_ALWAYS_ON_USB)) + return true; + + return false; +} + void power_init(void) { // See Figure 12-19 in Whiskey Lake Platform Design Guide // | VCCRTC | RTCRST# | VccPRIM | @@ -213,17 +228,7 @@ void power_init(void) { update_power_state(); } -void power_on(void) { - // Configure WLAN GPIOs before powering on - wireless_power(true); - - DEBUG("%02X: power_on\n", main_cycle); - - // See Figure 12-19 in Whiskey Lake Platform Design Guide - // TODO - signal timing graph - // See Figure 12-25 in Whiskey Lake Platform Design Guide - // TODO - rail timing graph - +static void power_sequence_g3_g3aou(void) { #if HAVE_VA_EC_EN // Enable VCCPRIM_* planes - must be enabled prior to USB power in order to // avoid leakage @@ -233,8 +238,14 @@ void power_on(void) { GPIO_SET_DEBUG(PD_EN, true); #endif tPCH06; - // Enable VDD5 + GPIO_SET_DEBUG(USB_CHARGE_EN, true); +} + +static void power_sequence_g3aou_s5(void) { + // Configure WLAN GPIOs before powering on + wireless_power(true); + GPIO_SET_DEBUG(DD_ON, true); #if HAVE_SUS_PWR_ACK @@ -261,7 +272,9 @@ void power_on(void) { // Wait for SUSPWRDNACK validity tPLT01; +} +static void power_sequence_s5_s0(void) { GPIO_SET_DEBUG(PWR_BTN_N, false); delay_ms(32); // PWRBTN# must assert for at least 16 ms, we do twice that GPIO_SET_DEBUG(PWR_BTN_N, true); @@ -294,9 +307,7 @@ void power_on(void) { } } -void power_off(void) { - DEBUG("%02X: power_off\n", main_cycle); - +static void power_sequence_sx_g3aou(void) { #if HAVE_PCH_PWROK_EC // De-assert SYS_PWROK GPIO_SET_DEBUG(PCH_PWROK_EC, false); @@ -315,8 +326,17 @@ void power_off(void) { // De-assert RSMRST# GPIO_SET_DEBUG(EC_RSMRST_N, false); - // Disable VDD5 + // Configure WLAN GPIOs after powering off + wireless_power(false); + GPIO_SET_DEBUG(DD_ON, false); + + update_power_state(); +} + +static void power_sequence_g3aou_g3(void) { + // Disable VDD5 + GPIO_SET_DEBUG(USB_CHARGE_EN, false); tPCH12; #if HAVE_PD_EN @@ -332,13 +352,63 @@ void power_off(void) { GPIO_SET_DEBUG(PCH_DPWROK_EC, false); #endif // HAVE_PCH_DPWROK_EC tPCH14; +} - // Configure WLAN GPIOs after powering off - wireless_power(false); +static void power_sequence(enum PowerState target) { + update_power_state(); + + if (target == power_state) { + return; + } + + DEBUG("Power state: %x -> %x\n", power_state, target); + + if (target < power_state) { + if (power_state == POWER_STATE_G3) + power_sequence_g3_g3aou(); + + if (target == POWER_STATE_G3_AOU) + return; + + if (power_state > POWER_STATE_S5) + power_sequence_g3aou_s5(); + + if (target == POWER_STATE_S5) + return; + + if (power_state > POWER_STATE_S0) + power_sequence_s5_s0(); + } + + if (target > power_state) { + if (target == POWER_STATE_S3 || target == POWER_STATE_S5) + WARN("Warning: S3 or S5 entry from S0 must be initiated by AP!\n"); + + if (power_state < POWER_STATE_G3_AOU) + power_sequence_sx_g3aou(); + + if (target == POWER_STATE_G3_AOU) + return; + + if (power_state < POWER_STATE_G3) + power_sequence_g3aou_g3(); + } update_power_state(); } +void power_on(void) { + DEBUG("%02X: power_on\n", main_cycle); + + power_sequence(POWER_STATE_S0); +} + +void power_off(void) { + DEBUG("%02X: power_off\n", main_cycle); + + power_sequence(is_standby_power_needed() ? POWER_STATE_G3_AOU : POWER_STATE_G3); +} + void power_apply_limit(bool ac) { uint32_t supply_watts = 0; uint32_t watts = 0; @@ -399,11 +469,11 @@ void power_cpu_reset(void) { fan_reset(); // Reset KBC and touchpad states kbled_reset(); - // Set power limits - power_apply_limit(!gpio_get(&ACIN_N)); kbc_clear_lock(); ps2_reset(&PS2_1); ps2_reset(&PS2_TOUCHPAD); + // Set power limits + power_apply_limit(!gpio_get(&ACIN_N)); } static bool power_button_disabled(void) { @@ -418,6 +488,9 @@ void power_event(void) { static uint32_t ac_unplug_time = 0; bool ac_new = gpio_get(&ACIN_N); + if (power_state == POWER_STATE_G3 && is_standby_power_needed()) + power_sequence(POWER_STATE_G3_AOU); + if (ac_new != ac_last) { // Configure smart charger DEBUG("Power adapter "); @@ -429,20 +502,8 @@ void power_event(void) { } else { DEBUG("plugged in\n"); battery_charger_configure(); - if (options_get(OPT_POWER_ON_AC) == 1) { - switch (power_state) { - case POWER_STATE_OFF: - power_on(); - break; - case POWER_STATE_S5: - GPIO_SET_DEBUG(PWR_BTN_N, false); - delay_ms(32); // PWRBTN# must assert for at least 16 ms, we do twice that - GPIO_SET_DEBUG(PWR_BTN_N, true); - break; - default: - break; - } - } + if (options_get(OPT_POWER_ON_AC) == 1) + power_on(); } power_apply_limit(!ac_new); battery_debug(); @@ -495,8 +556,7 @@ void power_event(void) { DEBUG("%02X: Power switch press\n", main_cycle); // Enable S5 power if necessary, before sending PWR_BTN - update_power_state(); - if (power_state == POWER_STATE_OFF) { + if (power_state >= POWER_STATE_G3_AOU) { if (config_should_reset()) config_reset(); power_on(); @@ -608,7 +668,6 @@ void power_event(void) { { // Disable S5 power plane if not needed if (power_state == POWER_STATE_S5) { - // Stay in S5 on AC if (gpio_get(&ACIN_N)) { power_off(); } @@ -628,7 +687,7 @@ void power_event(void) { if (!wake_new && wake_last) { update_power_state(); DEBUG("%02X: LAN_WAKEUP# asserted\n", main_cycle); - if (power_state == POWER_STATE_OFF) { + if (power_state == POWER_STATE_G3) { power_on(); } } @@ -676,6 +735,10 @@ void power_event(void) { // AC plugged in, orange light gpio_set(&LED_PWR, false); gpio_set(&LED_ACIN, true); + } else if (power_state == POWER_STATE_G3_AOU) { + // G3, Always on USB, lights off + gpio_set(&LED_PWR, false); + gpio_set(&LED_ACIN, false); } else { // CPU off and AC adapter unplugged, flashing orange light gpio_set(&LED_PWR, false); diff --git a/src/board/system76/common/usbpd/tps65987.c b/src/board/system76/common/usbpd/tps65987.c index b6a20a05f..87a4a2465 100644 --- a/src/board/system76/common/usbpd/tps65987.c +++ b/src/board/system76/common/usbpd/tps65987.c @@ -272,12 +272,12 @@ void usbpd_event(void) { } #endif - static enum PowerState last_power_state = POWER_STATE_OFF; + static enum PowerState last_power_state = POWER_STATE_G3; update_power_state(); if (power_state != last_power_state) { update = true; - if (last_power_state == POWER_STATE_OFF) { + if (last_power_state == POWER_STATE_G3) { // VIN_3V3 now available, allow PD to use it instead of Vbus usbpd_dbfg(); #ifdef USBPD_DUAL_PORT @@ -332,7 +332,7 @@ void usbpd_event(void) { #if HAVE_PD_IRQ /* For now, all we do is clear all events */ - if (power_state != POWER_STATE_OFF && !gpio_get(&PD_IRQ)) + if (power_state != POWER_STATE_G3 && !gpio_get(&PD_IRQ)) usbpd_clear_event(); #endif }