diff --git a/src/board/system76/common/include/board/usbpd.h b/src/board/system76/common/include/board/usbpd.h index aa47745..0be34f1 100644 --- a/src/board/system76/common/include/board/usbpd.h +++ b/src/board/system76/common/include/board/usbpd.h @@ -5,5 +5,7 @@ void usbpd_init(void); void usbpd_event(void); +void usbpd_disable_charging(void); +void usbpd_enable_charging(void); #endif // _BOARD_USBPD_H diff --git a/src/board/system76/common/main.c b/src/board/system76/common/main.c index 7e82933..2564251 100644 --- a/src/board/system76/common/main.c +++ b/src/board/system76/common/main.c @@ -104,6 +104,9 @@ void main(void) { for (main_cycle = 0;; main_cycle++) { switch (main_cycle % 3U) { case 0: + // Handle USB-C events immediately before power states + usbpd_event(); + // Handle power states power_event(); break; diff --git a/src/board/system76/common/power.c b/src/board/system76/common/power.c index 2a68d4a..0ea1032 100644 --- a/src/board/system76/common/power.c +++ b/src/board/system76/common/power.c @@ -244,6 +244,9 @@ void power_on(void) { // Wait for SUSPWRDNACK validity tPLT01; + // Ensure USB-PD is disabled before turning on CPU + usbpd_disable_charging(); + 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); @@ -374,9 +377,6 @@ void power_event(void) { //TODO: if this returns false, retry? power_peci_limit(false); - // Check for USB-PD charger limit - usbpd_event(); - // Configure smart charger DEBUG("Power adapter "); if (ac_new) { diff --git a/src/board/system76/common/usbpd/none.c b/src/board/system76/common/usbpd/none.c index 90a4993..dc8787e 100644 --- a/src/board/system76/common/usbpd/none.c +++ b/src/board/system76/common/usbpd/none.c @@ -5,3 +5,7 @@ void usbpd_init(void) {} void usbpd_event(void) {} + +void usbpd_disable_charging(void) {} + +void usbpd_enable_charging(void) {} diff --git a/src/board/system76/common/usbpd/tps65987.c b/src/board/system76/common/usbpd/tps65987.c index aa3df13..ad83a82 100644 --- a/src/board/system76/common/usbpd/tps65987.c +++ b/src/board/system76/common/usbpd/tps65987.c @@ -4,6 +4,8 @@ // I2C register reference: https://www.ti.com/lit/ug/slvubh2b/slvubh2b.pdf #include +#include +#include #include #include #include @@ -16,9 +18,9 @@ void usbpd_init(void) { i2c_reset(&I2C_USBPD, true); } -int16_t usbpd_current_limit(void) { +static int16_t usbpd_current_limit(void) { uint8_t value[7] = { 0 }; - int16_t res = i2c_get(&I2C_USBPD, USBPD_ADDRESS, REG_ACTIVE_CONTRACT_PDO, value, 7); + int16_t res = i2c_get(&I2C_USBPD, USBPD_ADDRESS, REG_ACTIVE_CONTRACT_PDO, value, sizeof(value)); if (res == 7) { if (value[0] == 6) { uint32_t pdo = @@ -26,40 +28,39 @@ int16_t usbpd_current_limit(void) { (((uint32_t)value[2]) << 8) | (((uint32_t)value[3]) << 16) | (((uint32_t)value[4]) << 24); - TRACE("USBPD PDO %08lX\n", pdo); + DEBUG("USBPD PDO %08lX ", pdo); uint8_t kind = (uint8_t)((pdo >> 30) & 0b11); if (kind == 0b00) { - TRACE(" FIX"); + DEBUG("FIX "); uint32_t current_ma = (pdo & 0x3FF) * 10; - TRACE(" %ld.%03ld A", current_ma / 1000, current_ma % 1000); + DEBUG("%ld.%03ld A ", current_ma / 1000, current_ma % 1000); uint32_t voltage_mv = ((pdo >> 10) & 0x3FF) * 50; - TRACE(" %ld.%03ld V", voltage_mv / 1000, voltage_mv % 1000); + DEBUG("%ld.%03ld V\n", voltage_mv / 1000, voltage_mv % 1000); return (int16_t)current_ma; } else if (kind == 0b01) { - TRACE(" BAT"); + DEBUG("BAT "); uint32_t power_mw = (pdo & 0x3FF) * 250; - TRACE(" %ld.%03ld W", power_mw / 1000, power_mw % 1000); + DEBUG("%ld.%03ld W ", power_mw / 1000, power_mw % 1000); uint32_t min_voltage_mv = ((pdo >> 10) & 0x3FF) * 50; - TRACE(" %ld.%03ld Vmin", min_voltage_mv / 1000, min_voltage_mv % 1000); + DEBUG("%ld.%03ld Vmin ", min_voltage_mv / 1000, min_voltage_mv % 1000); uint32_t max_voltage_mv = ((pdo >> 20) & 0x3FF) * 50; - TRACE(" %ld.%03ld Vax", max_voltage_mv / 1000, max_voltage_mv % 1000); + DEBUG("%ld.%03ld Vax\n", max_voltage_mv / 1000, max_voltage_mv % 1000); //TODO return -0x5000; } else if (kind == 0b10) { - TRACE(" VAR"); + DEBUG("VAR "); uint32_t current_ma = (pdo & 0x3FF) * 10; - TRACE(" %ld.%03ld A", current_ma / 1000, current_ma % 1000); + DEBUG("%ld.%03ld A ", current_ma / 1000, current_ma % 1000); uint32_t min_voltage_mv = ((pdo >> 10) & 0x3FF) * 50; - TRACE(" %ld.%03ld Vmin", min_voltage_mv / 1000, min_voltage_mv % 1000); + DEBUG("%ld.%03ld Vmin ", min_voltage_mv / 1000, min_voltage_mv % 1000); uint32_t max_voltage_mv = ((pdo >> 20) & 0x3FF) * 50; - TRACE(" %ld.%03ld Vax", max_voltage_mv / 1000, max_voltage_mv % 1000); + DEBUG("%ld.%03ld Vax\n", max_voltage_mv / 1000, max_voltage_mv % 1000); return (int16_t)current_ma; } else { - TRACE(" AUG"); + DEBUG("AUG\n"); //TODO return -0x4000; } - TRACE("\n"); } else { return -(0x3000 | (int16_t)value[0]); } @@ -70,11 +71,11 @@ int16_t usbpd_current_limit(void) { } } -void usbpd_event(void) { - /* Dump all registers for debugging - for(uint8_t reg = 0x00; reg < 0x80; reg+=1) { +static void usbpd_dump(void) { + /* Dump all registers for debugging */ + for(uint8_t reg = 0x00; reg < 0x40; reg+=1) { uint8_t value[65] = { 0 }; - int16_t res = i2c_get(&I2C_DGPU, USBPD_ADDRESS, reg, value, 65); + int16_t res = i2c_get(&I2C_USBPD, USBPD_ADDRESS, reg, value, sizeof(value)); if (res < 0) { DEBUG("USBPD %02X ERROR %04X\n", reg, res); } else { @@ -85,24 +86,171 @@ void usbpd_event(void) { DEBUG("\n"); } } - */ +} - DEBUG("USBPD LIMIT "); - int16_t res = usbpd_current_limit(); - if (res < 0) { - DEBUG("ERR %04X\n", -res); - //TODO: determine if USB-PD is actually plugged (SINK_CTRL_EC) - battery_charger_input_current = CHARGER_INPUT_CURRENT; - } else { - if (res < CHARGER_INPUT_CURRENT) { - // Use USB-PD charger current if it provides less than AC adapter - battery_charger_input_current = (uint16_t)res; - } else { - // Use AC adapter current if USB-PD charger supports more - battery_charger_input_current = CHARGER_INPUT_CURRENT; +void usbpd_event(void) { + bool update = false; + + static bool last_ac_in = false; + bool ac_in = !gpio_get(&ACIN_N); + if (ac_in != last_ac_in) { + last_ac_in = ac_in; + update = true; + + DEBUG("AC_IN %d\n", ac_in); + } + + static bool last_jack_in = false; + bool jack_in = !gpio_get(&JACK_IN_N); + if (jack_in != last_jack_in) { + last_jack_in = jack_in; + update = true; + + DEBUG("JACK_IN %d\n", jack_in); + } + + static bool last_sink_ctrl = false; + bool sink_ctrl = gpio_get(&SINK_CTRL); + if (sink_ctrl != last_sink_ctrl) { + last_sink_ctrl = sink_ctrl; + update = true; + + DEBUG("SINK_CTRL %d\n", sink_ctrl); + } + + static enum PowerState last_power_state = POWER_STATE_OFF; + update_power_state(); + if (power_state != last_power_state) { + last_power_state = power_state; + update = true; + } + + if (update) { + // Default to disabling input current + uint16_t next_input_current = 0; + + if (ac_in) { + if (jack_in) { + // Use default input current + next_input_current = CHARGER_INPUT_CURRENT; + } else if (sink_ctrl) { + int16_t res = usbpd_current_limit(); + if (res < 0) { + DEBUG("ERR %04X\n", -res); + } else if (res < CHARGER_INPUT_CURRENT) { + // Use USB-PD charger current if it provides less than AC adapter + next_input_current = (uint16_t)res; + } else { + // Use default input current if USB-PD charger provides more + next_input_current = CHARGER_INPUT_CURRENT; + } + } + } + + if (sink_ctrl) { + if (jack_in || power_state == POWER_STATE_S0) { + usbpd_disable_charging(); + } else { + usbpd_enable_charging(); + } + } + + if (next_input_current != battery_charger_input_current) { + battery_charger_input_current = next_input_current; + DEBUG("CHARGER LIMIT %d mA\n", battery_charger_input_current); + //TODO: update smart charger } - DEBUG("%d mA\n", battery_charger_input_current); } //TODO: dynamically set charger current (is this needed?) } + +static int16_t usbpd_aneg(void) { + int16_t res; + + uint8_t cmd[5] = { 4, 'A', 'N', 'e', 'g' }; + res = i2c_set(&I2C_USBPD, USBPD_ADDRESS, 0x08, cmd, sizeof(cmd)); + if (res < 0) { + return res; + } + + //TODO: wait on command completion + + return 0; +} + +void usbpd_disable_charging(void) { + int16_t res; + + DEBUG("USBPD DISABLE CHARGING "); + + // Read current value + uint8_t value[2] = { 0 }; + res = i2c_get(&I2C_USBPD, USBPD_ADDRESS, 0x33, value, sizeof(value)); + if (res < 0) { + DEBUG("ERR %04X\n", -res); + return; + } + + // Charging already disabled + if (value[1] == 1) { + DEBUG("NOP\n"); + return; + } + + // Enable only the first TX sink PDO (5V) + value[0] = 1; + value[1] = 1; + res = i2c_set(&I2C_USBPD, USBPD_ADDRESS, 0x33, value, sizeof(value)); + if (res < 0) { + DEBUG("ERR %04X\n", -res); + return; + } + + // Auto negotiate sink update + res = usbpd_aneg(); + if (res < 0) { + DEBUG("ERR %04X\n", -res); + return; + } + + DEBUG("OK\n"); +} + +void usbpd_enable_charging(void) { + int16_t res; + + DEBUG("USBPD ENABLE CHARGING "); + + // Read current value + uint8_t value[2] = { 0 }; + res = i2c_get(&I2C_USBPD, USBPD_ADDRESS, 0x33, value, sizeof(value)); + if (res < 0) { + DEBUG("ERR %04X\n", -res); + return; + } + + // Charging already enabled + if (value[1] == 2) { + DEBUG("NOP\n"); + return; + } + + // Enable the first two TX sink PDO (5V and 20V) + value[0] = 1; + value[1] = 2; + res = i2c_set(&I2C_USBPD, USBPD_ADDRESS, 0x33, value, sizeof(value)); + if (res < 0) { + DEBUG("ERR %04X\n", -res); + return; + } + + // Auto negotiate sink update + res = usbpd_aneg(); + if (res < 0) { + DEBUG("ERR %04X\n", -res); + return; + } + + DEBUG("OK\n"); +} diff --git a/src/board/system76/oryp11/gpio.c b/src/board/system76/oryp11/gpio.c index c355ae7..f66e608 100644 --- a/src/board/system76/oryp11/gpio.c +++ b/src/board/system76/oryp11/gpio.c @@ -15,6 +15,7 @@ struct Gpio __code DGPU_PWR_EN = GPIO(H, 4); struct Gpio __code EC_EN = GPIO(B, 6); // renamed to SUSBC_EC# struct Gpio __code EC_RSMRST_N = GPIO(E, 5); struct Gpio __code GC6_FB_EN = GPIO(J, 3); +struct Gpio __code JACK_IN_N = GPIO(G, 1); struct Gpio __code LAN_WAKEUP_N = GPIO(B, 2); struct Gpio __code LED_ACIN = GPIO(H, 2); struct Gpio __code LED_BAT_CHG = GPIO(H, 5); @@ -27,6 +28,7 @@ struct Gpio __code PCH_PWROK_EC = GPIO(C, 6); // renamed to SYS_PWROK_EC struct Gpio __code PD_EN = GPIO(F, 6); // renamed to PD_POWER_EN struct Gpio __code PWR_BTN_N = GPIO(D, 5); struct Gpio __code PWR_SW_N = GPIO(B, 3); +struct Gpio __code SINK_CTRL = GPIO(H, 1); struct Gpio __code SLP_SUS_N = GPIO(H, 7); struct Gpio __code VA_EC_EN = GPIO(J, 4); struct Gpio __code WLAN_PWR_EN = GPIO(D, 3); diff --git a/src/board/system76/oryp11/include/board/gpio.h b/src/board/system76/oryp11/include/board/gpio.h index bb7f318..34b2567 100644 --- a/src/board/system76/oryp11/include/board/gpio.h +++ b/src/board/system76/oryp11/include/board/gpio.h @@ -20,6 +20,7 @@ extern struct Gpio __code DGPU_PWR_EN; extern struct Gpio __code EC_EN; extern struct Gpio __code EC_RSMRST_N; extern struct Gpio __code GC6_FB_EN; +extern struct Gpio __code JACK_IN_N; extern struct Gpio __code LAN_WAKEUP_N; extern struct Gpio __code LED_ACIN; #define HAVE_LED_AIRPLANE_N 0 @@ -35,6 +36,7 @@ extern struct Gpio __code PD_EN; #define HAVE_PM_PWROK 0 extern struct Gpio __code PWR_BTN_N; extern struct Gpio __code PWR_SW_N; +extern struct Gpio __code SINK_CTRL; extern struct Gpio __code SLP_SUS_N; #define HAVE_SUS_PWR_ACK 0 extern struct Gpio __code VA_EC_EN;