From 4971c7865e90fc25dcc8797339e41168d519b2a7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Kope=C4=87?= Date: Tue, 21 Nov 2023 17:39:48 +0100 Subject: [PATCH] tps65987.c: ensure PD controller is in good state when turning on / off MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Michał Kopeć --- .../system76/common/include/board/usbpd.h | 15 +- src/board/system76/common/power.c | 6 +- src/board/system76/common/usbpd/tps65987.c | 133 ++++++++++++++---- 3 files changed, 127 insertions(+), 27 deletions(-) diff --git a/src/board/system76/common/include/board/usbpd.h b/src/board/system76/common/include/board/usbpd.h index 503e0b22c..5ba4c9f92 100644 --- a/src/board/system76/common/include/board/usbpd.h +++ b/src/board/system76/common/include/board/usbpd.h @@ -3,10 +3,23 @@ #ifndef _BOARD_USBPD_H #define _BOARD_USBPD_H +enum { + // Fully functional, normal operation + USBPD_MODE_APP = 0, + // PD controller is running BIST + USBPD_MODE_BIST, + // PD controller booted in dead battery mode + USBPD_MODE_BOOT, + // Simulated port disconnect by previously issued DISC command + USBPD_MODE_DISC, + // Other values indicate limited functionality + USBPD_MODE_UNKNOWN, +}; + void usbpd_init(void); void usbpd_event(void); void usbpd_disable_charging(void); void usbpd_enable_charging(void); -int16_t usbpd_disc(uint8_t timeout); +void usbpd_set_mode(int16_t mode); #endif // _BOARD_USBPD_H diff --git a/src/board/system76/common/power.c b/src/board/system76/common/power.c index 7373acdb1..b55598fee 100644 --- a/src/board/system76/common/power.c +++ b/src/board/system76/common/power.c @@ -293,8 +293,10 @@ void power_off(void) { // Commit settings to flash on shutdown options_save_config(); - // Trigger USB-PD disconnect, will be reconnected after TI reset - usbpd_disc(1); + // Cycle the PD controller and dock connection off and on to work around + // dock issues + usbpd_set_mode(USBPD_MODE_DISC); + usbpd_set_mode(USBPD_MODE_APP); #if HAVE_PCH_PWROK_EC // De-assert SYS_PWROK diff --git a/src/board/system76/common/usbpd/tps65987.c b/src/board/system76/common/usbpd/tps65987.c index bab9dec1d..8e7caa4fc 100644 --- a/src/board/system76/common/usbpd/tps65987.c +++ b/src/board/system76/common/usbpd/tps65987.c @@ -3,6 +3,7 @@ // USB-PD driver for TPS65987 and the mostly compatible TPS65993 and TPS65994. // I2C register reference: https://www.ti.com/lit/ug/slvubh2b/slvubh2b.pdf +#include #include #include #include @@ -12,12 +13,11 @@ #define USBPD_ADDRESS 0x20 +#define REG_MODE 0x03 +#define REG_CMD1 0x08 +#define REG_DATA1 0x09 #define REG_ACTIVE_CONTRACT_PDO 0x34 -void usbpd_init(void) { - i2c_reset(&I2C_USBPD, true); -} - enum { // PDO is empty USBPD_ERR_PDO_ZERO = 0x1000, @@ -94,6 +94,101 @@ static void usbpd_dump(void) { } } +#define FOURCC(a, b, c, d) (((((d) << 8) | (c)) << 8 | (b)) << 8 | (a)) + +// Check the operational state of the PD controller +static int16_t usbpd_get_mode(void) { + int16_t res; + uint32_t mode; + uint8_t reg[5] = { 0 }; + + DEBUG("USBPD controller mode: "); + + res = i2c_get(&I2C_USBPD, USBPD_ADDRESS, REG_MODE, reg, sizeof(reg)); + if (res < 0 || reg[0] < 4) { + DEBUG("UNKNOWN (I2C error %d)\n", res); + return USBPD_MODE_UNKNOWN; + } + + mode = FOURCC(reg[1], reg[2], reg[3], reg[4]); + + switch (mode) { + case FOURCC('A', 'P', 'P', ' '): + DEBUG("APP\n"); + return USBPD_MODE_APP; + case FOURCC('B', 'I', 'S', 'T'): + DEBUG("BIST\n"); + return USBPD_MODE_BIST; + case FOURCC('B', 'O', 'O', 'T'): + DEBUG("BOOT\n"); + return USBPD_MODE_BOOT; + case FOURCC('D', 'I', 'S', 'C'): + DEBUG("DISC\n"); + return USBPD_MODE_DISC; + } + + DEBUG("UNKNOWN\n"); + return USBPD_MODE_UNKNOWN; +} + +// Disconnect port for [timeout] seconds +// If timeout is 0, port will remain disconnected indefinitely +static int16_t usbpd_disc(uint8_t timeout) { + int16_t res; + uint8_t cmd[5] = { 4, 'D', 'I', 'S', 'C' }; + uint8_t data[2] = { 1, 0 }; + + data[1] = timeout; + + do { + res = i2c_set(&I2C_USBPD, USBPD_ADDRESS, REG_DATA1, data, sizeof(data)); + } while (res < 0); + + do { + res = i2c_set(&I2C_USBPD, USBPD_ADDRESS, REG_CMD1, cmd, sizeof(cmd)); + } while (res < 0); + + // TI says this never fails and completes immediately + return 0; +} + +// Return to normal operation +// Reboots the PD controller and exits any modal tasks +static int16_t usbpd_gaid(void) { + int16_t res; + + uint8_t cmd[5] = { 4, 'G', 'a', 'i', 'd' }; + + do { + res = i2c_set(&I2C_USBPD, USBPD_ADDRESS, REG_CMD1, cmd, sizeof(cmd)); + } while (res < 0); + + i2c_reset(&I2C_USBPD, true); + + uint32_t deadline = time_get() + 1000; // 1 second + while (usbpd_get_mode() != USBPD_MODE_APP && time_get() < deadline) {}; + + if (time_get() > deadline) { + DEBUG("USBPD reboot timed out\n"); + return -1; + } + + return 0; +} + +void usbpd_set_mode(int16_t mode) { + switch (mode) { + case USBPD_MODE_APP: + usbpd_gaid(); + break; + case USBPD_MODE_DISC: + usbpd_disc(0); + break; + default: + return; + } +} + void usbpd_event(void) { bool update = false; int16_t res; @@ -165,26 +260,7 @@ 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; -} - -int16_t usbpd_disc(uint8_t timeout) { - int16_t res; - - uint8_t cmd[5] = { 4, 'D', 'I', 'S', 'C' }; - uint8_t data[2] = { 1, 0 }; - - data[1] = timeout; - - res = i2c_set(&I2C_USBPD, USBPD_ADDRESS, 0x09, data, sizeof(data)); - res = i2c_set(&I2C_USBPD, USBPD_ADDRESS, 0x08, cmd, sizeof(cmd)); + res = i2c_set(&I2C_USBPD, USBPD_ADDRESS, REG_CMD1, cmd, sizeof(cmd)); if (res < 0) { return res; } @@ -269,3 +345,12 @@ void usbpd_enable_charging(void) { DEBUG("OK\n"); } + +void usbpd_init(void) { + i2c_reset(&I2C_USBPD, true); + + int16_t mode = usbpd_get_mode(); + + if (mode == USBPD_MODE_DISC || mode == USBPD_MODE_UNKNOWN) + usbpd_set_mode(USBPD_MODE_APP); +}