Skip to content

Commit

Permalink
Add Always on USB
Browse files Browse the repository at this point in the history
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ć <michal.kopec@3mdeb.com>
  • Loading branch information
mkopec committed Feb 19, 2025
1 parent 6866284 commit 049fdb2
Show file tree
Hide file tree
Showing 5 changed files with 119 additions and 48 deletions.
1 change: 1 addition & 0 deletions src/board/system76/common/include/board/options.h
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ enum {
OPT_BAT_THRESHOLD_START,
OPT_BAT_THRESHOLD_STOP,
OPT_ALLOW_BAT_BOOST,
OPT_ALWAYS_ON_USB,
NUM_OPTIONS
};

Expand Down
8 changes: 7 additions & 1 deletion src/board/system76/common/include/board/power.h
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,16 @@
#include <stdbool.h>

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;
Expand Down
5 changes: 3 additions & 2 deletions src/board/system76/common/options.c
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
147 changes: 105 additions & 42 deletions src/board/system76/common/power.c
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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");
Expand All @@ -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 |
Expand All @@ -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
Expand All @@ -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
Expand All @@ -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);
Expand Down Expand Up @@ -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);
Expand All @@ -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
Expand All @@ -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;
Expand Down Expand Up @@ -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) {
Expand All @@ -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 ");
Expand All @@ -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();
Expand Down Expand Up @@ -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();
Expand Down Expand Up @@ -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();
}
Expand All @@ -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();
}
}
Expand Down Expand Up @@ -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);
Expand Down
6 changes: 3 additions & 3 deletions src/board/system76/common/usbpd/tps65987.c
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
}
Expand Down

0 comments on commit 049fdb2

Please sign in to comment.