diff --git a/src/board/system76/common/include/board/usbpd.h b/src/board/system76/common/include/board/usbpd.h index 3283c07f1..ea8d1e631 100644 --- a/src/board/system76/common/include/board/usbpd.h +++ b/src/board/system76/common/include/board/usbpd.h @@ -3,8 +3,11 @@ #ifndef _BOARD_USBPD_H #define _BOARD_USBPD_H +#include + void usbpd_init(void); void usbpd_reset(void); void usbpd_event(void); +int8_t usbpd_ucsi(uint8_t *control, uint8_t *out_data, uint8_t *out_len); #endif // _BOARD_USBPD_H diff --git a/src/board/system76/common/smfi.c b/src/board/system76/common/smfi.c index 987e9c498..59a103598 100644 --- a/src/board/system76/common/smfi.c +++ b/src/board/system76/common/smfi.c @@ -25,6 +25,7 @@ #include #include #include +#include #include #if CONFIG_SECURITY @@ -257,6 +258,43 @@ static enum Result cmd_fan_curve_set(void) __reentrant { return RES_OK; } +// UCSI-ACPI command: +// Input [0..7] : UCSI CONTROL (command, DataLength, CommandSpecific[6]) +// Output [0] : response length N +// Output [1..N] : raw DataX response bytes (command-specific) +// Byte 1 is the task return code. +// coreboot maps the response to UCSI CCI + MESSAGE_IN for the OS. +static enum Result cmd_ucsi(void) { + uint8_t control[8]; + uint8_t out_data[16]; + uint8_t out_len; + uint8_t i; + int8_t res; + + for (i = 0; i < 8; i++) + control[i] = smfi_cmd[SMFI_CMD_DATA + i]; + + DEBUG("UCSI cmd=%02X%02X data=%02X%02X%02X%02X%02X%02X\n", + control[0], control[1], + control[2], control[3], control[4], control[5], control[6], control[7]); + + out_len = 0; + res = usbpd_ucsi(control, out_data, &out_len); + + DEBUG("UCSI res=%d out_len=%d\n", res, out_len); + if (out_len > 0) + DEBUG("UCSI out[0]=%02X [1]=%02X\n", out_data[0], out_data[1]); + + if (res < 0) + return RES_ERR; + + smfi_cmd[SMFI_CMD_DATA] = out_len; + for (i = 0; i < out_len; i++) + smfi_cmd[SMFI_CMD_DATA + 1 + i] = out_data[i]; + + return RES_OK; +} + static enum Result cmd_camera_enablement_set(void) { camera_switch_enabled = smfi_cmd[SMFI_CMD_DATA]; gpio_set(&CCD_EN, smfi_cmd[SMFI_CMD_DATA]); @@ -428,6 +466,9 @@ void smfi_event(void) { case CMD_OPTION_SET: smfi_cmd[SMFI_CMD_RES] = cmd_option_set(); break; + case CMD_UCSI: + smfi_cmd[SMFI_CMD_RES] = cmd_ucsi(); + break; #if CONFIG_SECURITY case CMD_SECURITY_GET: smfi_cmd[SMFI_CMD_RES] = cmd_security_get(); diff --git a/src/board/system76/common/usbpd/none.c b/src/board/system76/common/usbpd/none.c index 5e09a9f60..d661bcd60 100644 --- a/src/board/system76/common/usbpd/none.c +++ b/src/board/system76/common/usbpd/none.c @@ -7,3 +7,8 @@ void usbpd_init(void) {} void usbpd_event(void) {} void usbpd_disable_charging(void) {} + +int8_t usbpd_ucsi(uint8_t *control, uint8_t *out_data, uint8_t *out_len) { + (void)control; (void)out_data; (void)out_len; + return -1; +} diff --git a/src/board/system76/common/usbpd/tps65987.c b/src/board/system76/common/usbpd/tps65987.c index e74a2ace0..102fe9000 100644 --- a/src/board/system76/common/usbpd/tps65987.c +++ b/src/board/system76/common/usbpd/tps65987.c @@ -24,6 +24,7 @@ #define REG_GLOBAL_CONFIG 0x27 #define REG_ACTIVE_CONTRACT_PDO 0x34 + #ifndef HAVE_PD_IRQ #define HAVE_PD_IRQ 0 #endif @@ -378,3 +379,78 @@ void usbpd_event(void) { void usbpd_init(void) { i2c_reset(&I2C_USBPD, true); } + +// Proxy a UCSI command from the OPM (BIOS/OS via SMFI) to the TPS65987 PPM. +// +// Uses the TPS65987 'UCSI' 4CC command interface (One-PD Controller Host Interface): +// - UCSI CONTROL bytes → REG_DATA1 (DataX, reg 0x09) +// - 'UCSI' 4CC written to REG_CMD1; poll until cleared (controller done) +// - Raw DataX response returned in out_data[] +// +// control[8]: UCSI CONTROL bytes (command, DataLength, CommandSpecific[6]) +// out_data[16]: output buffer for TPS65987 DataX response (command-specific) +// out_len: actual bytes written to out_data +// +// Routing: ConnectorNumber from control[2] bits[6:0]: 1→PORT_A, 2→PORT_B. +// +// Returns 0 on success, -1 on I2C error, -2 on timeout. +int8_t usbpd_ucsi(uint8_t *control, uint8_t *out_data, uint8_t *out_len) { + int16_t res; + uint8_t i; + uint16_t timeout; + uint8_t buf[17]; + uint8_t addr; + + // Route to correct port based on ConnectorNumber (control[2] bits 6:0) + addr = ((control[2] & 0x7F) > 1) ? PORT_B_ADDRESS : PORT_A_ADDRESS; + + // Write UCSI CONTROL to DataX (REG_DATA1) + // TPS65987 register format: buf[0] = data length, buf[1..8] = UCSI CONTROL + buf[0] = 8; + for (i = 0; i < 8; i++) + buf[i + 1] = control[i]; + res = i2c_set(&I2C_USBPD, addr, REG_DATA1, buf, 9); + if (res < 0) + return -1; + + // Issue 'UCSI' 4CC command + { + uint8_t cmd[5] = { 4, 'U', 'C', 'S', 'I' }; + res = i2c_set(&I2C_USBPD, addr, REG_CMD1, cmd, sizeof(cmd)); + if (res < 0) + return -1; + } + + // Poll REG_CMD1 until cleared (TPS65987 zeroes it when command completes) + for (timeout = 200; timeout > 0; timeout--) { + uint8_t cmd[5] = { 0, 0, 0, 0, 0 }; + res = i2c_get(&I2C_USBPD, addr, REG_CMD1, cmd, sizeof(cmd)); + if (res < 0) { + DEBUG("UCSI poll I2C err %d\n", res); + return -1; + } + if (!cmd[1] && !cmd[2] && !cmd[3] && !cmd[4]) + break; + } + if (timeout == 0) { + DEBUG("UCSI poll timeout\n"); + return -2; + } + + // Read response from DataX (REG_DATA1) + for (i = 0; i < 17; i++) + buf[i] = 0; + res = i2c_get(&I2C_USBPD, addr, REG_DATA1, buf, sizeof(buf)); + if (res < 0) { + DEBUG("UCSI read I2C err %d\n", res); + return -1; + } + + DEBUG("UCSI DataX len=%d task_rc=%02X\n", buf[0], buf[1]); + + *out_len = (buf[0] < 16) ? buf[0] : 16; + for (i = 0; i < *out_len; i++) + out_data[i] = buf[i + 1]; + + return 0; +} diff --git a/src/common/include/common/command.h b/src/common/include/common/command.h index 1359aad03..99d290104 100644 --- a/src/common/include/common/command.h +++ b/src/common/include/common/command.h @@ -60,6 +60,8 @@ enum Command { CMD_OPTION_GET = 25, // Set a persistent option by index CMD_OPTION_SET = 26, + // Send a UCSI command to USB-PD controller and return CCI + MESSAGE_IN + CMD_UCSI = 27, //TODO };