Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions src/board/system76/common/include/board/usbpd.h
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,11 @@
#ifndef _BOARD_USBPD_H
#define _BOARD_USBPD_H

#include <stdint.h>

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
41 changes: 41 additions & 0 deletions src/board/system76/common/smfi.c
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
#include <board/dgpu.h>
#include <board/fan.h>
#include <board/wireless.h>
#include <board/usbpd.h>
#include <common/debug.h>

#if CONFIG_SECURITY
Expand Down Expand Up @@ -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]);
Expand Down Expand Up @@ -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();
Expand Down
5 changes: 5 additions & 0 deletions src/board/system76/common/usbpd/none.c
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
76 changes: 76 additions & 0 deletions src/board/system76/common/usbpd/tps65987.c
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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;
}
2 changes: 2 additions & 0 deletions src/common/include/common/command.h
Original file line number Diff line number Diff line change
Expand Up @@ -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
};

Expand Down
Loading