From fd7f13f9050a1d26ec0723ca63e4c8440e97ed8f Mon Sep 17 00:00:00 2001 From: Richard Hughes Date: Tue, 24 Mar 2020 16:28:59 +0000 Subject: [PATCH] ccgx: Wait for hardware to settle when coming out of reset --- plugins/ccgx/fu-ccgx-hpi-common.h | 176 +++++++++++++++++++++++ plugins/ccgx/fu-ccgx-hpi-device.c | 228 ++++++++++++++++++++++++++++++ 2 files changed, 404 insertions(+) diff --git a/plugins/ccgx/fu-ccgx-hpi-common.h b/plugins/ccgx/fu-ccgx-hpi-common.h index c53155401..95bd41543 100644 --- a/plugins/ccgx/fu-ccgx-hpi-common.h +++ b/plugins/ccgx/fu-ccgx-hpi-common.h @@ -122,6 +122,78 @@ typedef enum { CY_I2C_DATA_CONFIG_NAK = 1 << 1, /* only for read */ } CyI2CDataConfigBits; +typedef enum { + HPI_DEV_REG_DEVICE_MODE = 0, + HPI_DEV_REG_BOOT_MODE_REASON, + HPI_DEV_REG_SI_ID, + HPI_DEV_REG_SI_ID_LSB, + HPI_DEV_REG_BL_LAST_ROW, + HPI_DEV_REG_BL_LAST_ROW_LSB, + HPI_DEV_REG_INTR_ADDR, + HPI_DEV_REG_JUMP_TO_BOOT, + HPI_DEV_REG_RESET_ADDR, + HPI_DEV_REG_RESET_CMD, + HPI_DEV_REG_ENTER_FLASH_MODE, + HPI_DEV_REG_VALIDATE_FW_ADDR, + HPI_DEV_REG_FLASH_READ_WRITE, + HPI_DEV_REG_FLASH_READ_WRITE_CMD, + HPI_DEV_REG_FLASH_ROW, + HPI_DEV_REG_FLASH_ROW_LSB, + HPI_DEV_REG_ALL_VERSION, + HPI_DEV_REG_ALL_VERSION_BYTE_1, + HPI_DEV_REG_ALL_VERSION_BYTE_2, + HPI_DEV_REG_ALL_VERSION_BYTE_3, + HPI_DEV_REG_ALL_VERSION_BYTE_4, + HPI_DEV_REG_ALL_VERSION_BYTE_5, + HPI_DEV_REG_ALL_VERSION_BYTE_6, + HPI_DEV_REG_ALL_VERSION_BYTE_7, + HPI_DEV_REG_ALL_VERSION_BYTE_8, + HPI_DEV_REG_ALL_VERSION_BYTE_9, + HPI_DEV_REG_ALL_VERSION_BYTE_10, + HPI_DEV_REG_ALL_VERSION_BYTE_11, + HPI_DEV_REG_ALL_VERSION_BYTE_12, + HPI_DEV_REG_ALL_VERSION_BYTE_13, + HPI_DEV_REG_ALL_VERSION_BYTE_14, + HPI_DEV_REG_ALL_VERSION_BYTE_15, + HPI_DEV_REG_FW_2_VERSION, + HPI_DEV_REG_FW_2_VERSION_BYTE_1, + HPI_DEV_REG_FW_2_VERSION_BYTE_2, + HPI_DEV_REG_FW_2_VERSION_BYTE_3, + HPI_DEV_REG_FW_2_VERSION_BYTE_4, + HPI_DEV_REG_FW_2_VERSION_BYTE_5, + HPI_DEV_REG_FW_2_VERSION_BYTE_6, + HPI_DEV_REG_FW_2_VERSION_BYTE_7, + HPI_DEV_REG_FW_BIN_LOC, + HPI_DEV_REG_FW_1_BIN_LOC_LSB, + HPI_DEV_REG_FW_2_BIN_LOC_MSB, + HPI_DEV_REG_FW_2_BIN_LOC_LSB, + HPI_DEV_REG_PORT_ENABLE, + HPI_DEV_SPACE_REG_LEN, + HPI_DEV_REG_RESPONSE = 0x007E, + HPI_DEV_REG_FLASH_MEM = 0x0200 +} HPIDevReg; + +typedef enum { + HPI_REG_SECTION_DEV = 0, /* device information */ + HPI_REG_SECTION_PORT_0, /* USB-PD Port 0 related */ + HPI_REG_SECTION_PORT_1, /* USB-PD Port 1 related */ + HPI_REG_SECTION_ALL /* select all registers */ +} HPIRegSection; + +typedef struct __attribute__((packed)) { + guint16 event_code; + guint16 event_length; + guint8 event_data[128]; +} HPIEvent; + +typedef enum { + HPI_REG_PART_REG = 0, /* register region */ + HPI_REG_PART_DATA = 1, /* data memory */ + HPI_REG_PART_FLASH = 2, /* flash memory */ + HPI_REG_PART_PDDATA_READ = 4, /* read data memory */ + HPI_REG_PART_PDDATA_WRITE = 8, /* write data memory */ +} HPIRegPart; + typedef enum { CY_PD_REG_DEVICE_MODE_ADDR, CY_PD_BOOT_MODE_REASON, @@ -166,3 +238,107 @@ typedef enum { #define PD_I2CM_USB_EP_BULK_IN 0x83 #define PD_I2CM_USB_EP_INTR_IN 0x84 +typedef enum { + /* responses */ + CY_PD_RESP_NO_RESPONSE, + CY_PD_RESP_SUCCESS = 0x02, + CY_PD_RESP_FLASH_DATA_AVAILABLE, + CY_PD_RESP_INVALID_COMMAND = 0x05, + CY_PD_RESP_COLLISION_DETECTED, + CY_PD_RESP_FLASH_UPDATE_FAILED, + CY_PD_RESP_INVALID_FW, + CY_PD_RESP_INVALID_ARGUMENTS, + CY_PD_RESP_NOT_SUPPORTED, + CY_PD_RESP_TRANSACTION_FAILED = 0x0C, + CY_PD_RESP_PD_COMMAND_FAILED, + CY_PD_RESP_UNDEFINED, + CY_PD_RESP_RA_DETECT = 0x10, + CY_PD_RESP_RA_REMOVED, + + /* device specific events */ + CY_PD_RESP_RESET_COMPLETE = 0x80, + CY_PD_RESP_MESSAGE_QUEUE_OVERFLOW, + + /* type-c specific events */ + CY_PD_RESP_OVER_CURRENT_DETECTED, + CY_PD_RESP_OVER_VOLTAGE_DETECTED, + CY_PD_RESP_TYPC_C_CONNECTED, + CY_PD_RESP_TYPE_C_DISCONNECTED, + + /* pd specific events and asynchronous messages */ + CY_PD_RESP_PD_CONTRACT_ESTABLISHED, + CY_PD_RESP_DR_SWAP, + CY_PD_RESP_PR_SWAP, + CY_PD_RESP_VCON_SWAP, + CY_PD_RESP_PS_RDY, + CY_PD_RESP_GOTOMIN, + CY_PD_RESP_ACCEPT_MESSAGE, + CY_PD_RESP_REJECT_MESSAGE, + CY_PD_RESP_WAIT_MESSAGE, + CY_PD_RESP_HARD_RESET, + CY_PD_RESP_VDM_RECEIVED, + CY_PD_RESP_SRC_CAP_RCVD, + CY_PD_RESP_SINK_CAP_RCVD, + CY_PD_RESP_DP_ALTERNATE_MODE, + CY_PD_RESP_DP_DEVICE_CONNECTED, + CY_PD_RESP_DP_DEVICE_NOT_CONNECTED, + CY_PD_RESP_DP_SID_NOT_FOUND, + CY_PD_RESP_MULTIPLE_SVID_DISCOVERED, + CY_PD_RESP_DP_FUNCTION_NOT_SUPPORTED, + CY_PD_RESP_DP_PORT_CONFIG_NOT_SUPPORTED, + CY_PD_HARD_RESET_SENT, + CY_PD_SOFT_RESET_SENT, + CY_PD_CABLE_RESET_SENT, + CY_PD_SOURCE_DISBALED_STATE_ENTERED, + CY_PD_SENDER_RESPONSE_TIMER_TIMEOUT, + CY_PD_NO_VDM_RESPONSE_RECEIVED +} CyPDResp; + +typedef enum { + HPI_RESPONSE_NO_RESPONSE, + HPI_RESPONSE_SUCCESS = 0x02, + HPI_RESPONSE_FLASH_DATA_AVAILABLE, + HPI_RESPONSE_INVALID_COMMAND = 0x05, + HPI_RESPONSE_FLASH_UPDATE_FAILED = 0x07, + HPI_RESPONSE_INVALID_FW, + HPI_RESPONSE_INVALID_ARGUMENT, + HPI_RESPONSE_NOT_SUPPORTED, + HPI_RESPONSE_PD_TRANSACTION_FAILED = 0x0C, + HPI_RESPONSE_PD_COMMAND_FAILED, + HPI_RESPONSE_UNDEFINED_ERROR = 0x0F, + HPI_EVENT_RESET_COMPLETE = 0x80, + HPI_EVENT_MSG_OVERFLOW, + HPI_EVENT_OC_DETECT, + HPI_EVENT_OV_DETECT, + HPI_EVENT_CONNECT_DETECT, + HPI_EVENT_DISCONNECT_DETECT, + HPI_EVENT_NEGOTIATION_COMPLETE, + HPI_EVENT_SWAP_COMPLETE, + HPI_EVENT_PS_RDY_RECEIVED = 0x8A, + HPI_EVENT_GOTO_MIN_RECEIVED, + HPI_EVENT_ACCEPT_RECEIVED, + HPI_EVENT_REJECT_RECEIVED, + HPI_EVENT_WAIT_RECEIVED, + HPI_EVENT_HARD_RESET_RECEIVED, + HPI_EVENT_VDM_RECEIVED = 0x90, + HPI_EVENT_SOURCE_CAP_RECEIVED, + HPI_EVENT_SINK_CAP_RECEIVED, + HPI_EVENT_DP_MODE_ENTERED, + HPI_EVENT_DP_STATUS_UPDATE, + HPI_EVENT_DP_SID_NOT_FOUND = 0x96, + HPI_EVENT_DP_MANY_SID_FOUND, + HPI_EVENT_DP_NO_CABLE_SUPPORT, + HPI_EVENT_DP_NO_UFP_SUPPORT, + HPI_EVENT_HARD_RESET_SENT, + HPI_EVENT_SOFT_RESET_SENT, + HPI_EVENT_CABLE_RESET_SENT, + HPI_EVENT_SOURCE_DISABLED, + HPI_EVENT_SENDER_TIMEOUT, + HPI_EVENT_VDM_NO_RESPONSE, + HPI_EVENT_UNEXPECTED_VOLTAGE, + HPI_EVENT_ERROR_RECOVERY, + HPI_EVENT_EMCA_DETECT = 0xA6, + HPI_EVENT_RP_CHANGE_DETECT = 0xAA, + HPI_EVENT_TB_ENTERED = 0xB0, + HPI_EVENT_TB_EXITED +} HPIResp; diff --git a/plugins/ccgx/fu-ccgx-hpi-device.c b/plugins/ccgx/fu-ccgx-hpi-device.c index 079b50358..bc6e5a38a 100644 --- a/plugins/ccgx/fu-ccgx-hpi-device.c +++ b/plugins/ccgx/fu-ccgx-hpi-device.c @@ -33,6 +33,9 @@ struct _FuCcgxHpiDevice G_DEFINE_TYPE (FuCcgxHpiDevice, fu_ccgx_hpi_device, FU_TYPE_USB_DEVICE) +#define HPI_CMD_SETUP_EVENT_WAIT_TIME_MS 200 +#define HPI_CMD_RESET_COMPLETE_DELAY_US 150000 + static void fu_ccgx_hpi_device_to_string (FuDevice *device, guint idt, GString *str) { @@ -305,6 +308,212 @@ fu_ccgx_hpi_device_reg_read (FuCcgxHpiDevice *self, return TRUE; } +static gboolean +fu_ccgx_hpi_device_reg_write (FuCcgxHpiDevice *self, + guint16 addr, + guint8 *buf, + guint16 bufsz, + GError **error) +{ + g_autofree guint8 *bufhw = g_malloc0 (bufsz + self->hpi_addrsz + 1); + for (guint32 i = 0; i < self->hpi_addrsz; i++) + bufhw[i] = (guint8) (addr >> (8*i)); + memcpy (&bufhw[self->hpi_addrsz], buf, bufsz); + if (!fu_ccgx_hpi_device_i2c_write (self, bufhw, bufsz + self->hpi_addrsz, + CY_I2C_DATA_CONFIG_STOP | CY_I2C_DATA_CONFIG_NAK, + error)) { + g_prefix_error (error, "reg write error: "); + return FALSE; + } + return TRUE; +} + +static gboolean +fu_ccgx_hpi_device_clear_intr (FuCcgxHpiDevice *self, + HPIRegSection section, + GError **error) +{ + guint8 intr = 0; + for (guint8 i = 0; i <= self->num_ports; i++) { + if (i == section || section == HPI_REG_SECTION_ALL) + intr |= 1 << i; + } + if (!fu_ccgx_hpi_device_reg_write (self, HPI_DEV_REG_INTR_ADDR, + &intr, sizeof(intr), + error)) { + g_prefix_error (error, "failed to clear interrupt: "); + return FALSE; + } + return TRUE; +} + +static guint16 +fu_ccgx_hpi_device_reg_addr_gen (guint8 section, guint8 part, guint8 addr) +{ + return (((guint16) section) << 12) | (((guint16) part) << 8) | addr; +} + +static gboolean +fu_ccgx_hpi_device_read_event_reg (FuCcgxHpiDevice *self, + HPIRegSection section, + HPIEvent *event, + GError **error) +{ + + if (section != HPI_REG_SECTION_DEV) { + guint16 reg_addr; + guint8 buf[4] = { 0x0 }; + + /* first read the response register */ + reg_addr = fu_ccgx_hpi_device_reg_addr_gen (section, + HPI_REG_PART_PDDATA_READ, + 0); + if (!fu_ccgx_hpi_device_reg_read (self, + reg_addr, + buf, sizeof(buf), + error)) { + g_prefix_error (error, "read response reg error:"); + return FALSE; + } + + /* byte 1 is reserved and should read as zero */ + buf[1] = 0; + memcpy ((guint8 *) event, buf, sizeof(buf)); + if (event->event_length != 0) { + reg_addr = fu_ccgx_hpi_device_reg_addr_gen (section, + HPI_REG_PART_PDDATA_READ, + sizeof(buf)); + if (!fu_ccgx_hpi_device_reg_read (self, + reg_addr, + event->event_data, + event->event_length, + error)) { + g_prefix_error (error, "read event data error:"); + return FALSE; + } + } + } else { + guint8 buf[2] = { 0x0 }; + if (!fu_ccgx_hpi_device_reg_read (self, + CY_PD_REG_RESPONSE_ADDR, + buf, sizeof(buf), + error)) { + g_prefix_error (error, "read response reg error:"); + return FALSE; + } + event->event_code = buf[0]; + event->event_length = buf[1]; + if (event->event_length != 0) { + /* read the data memory */ + if (!fu_ccgx_hpi_device_reg_read (self, + CY_PD_REG_BOOTDATA_MEMORY_ADDR, + event->event_data, + event->event_length, + error)) { + g_prefix_error (error, "read event data error:"); + return FALSE; + } + } + } + + /* success */ + return fu_ccgx_hpi_device_clear_intr (self, section, error); +} + +static gboolean +fu_ccgx_hpi_device_app_read_intr_reg (FuCcgxHpiDevice *self, + HPIRegSection section, + HPIEvent *event_array, + guint8 *event_count, + GError **error) +{ + guint16 reg_addr; + guint8 event_count_tmp = 0; + guint8 intr_reg = 0; + + reg_addr = fu_ccgx_hpi_device_reg_addr_gen (HPI_REG_SECTION_DEV, + HPI_REG_PART_REG, + HPI_DEV_REG_INTR_ADDR); + if (!fu_ccgx_hpi_device_reg_read (self, + reg_addr, + &intr_reg, + sizeof(intr_reg), + error)) { + g_prefix_error (error, "read intr reg error: "); + return FALSE; + } + + /* device section will not come here */ + for (guint8 i = 0; i <= self->num_ports; i++) { + /* check if this section is needed */ + if (section == i || section == HPI_REG_SECTION_ALL) { + /* check whether this section has any event/response */ + if ((1 << i) & intr_reg) { + if (!fu_ccgx_hpi_device_read_event_reg (self, + section, + &event_array[i], + error)) { + g_prefix_error (error, "read event error: "); + return FALSE; + } + event_count_tmp++; + } + } + } + if (event_count != NULL) + *event_count = event_count_tmp; + return TRUE; +} + +static gboolean +fu_ccgx_hpi_device_wait_for_event (FuCcgxHpiDevice *self, + HPIRegSection section, + HPIEvent *event_array, + guint32 timeout_ms, + GError **error) +{ + guint8 event_count = 0; + g_autoptr(GTimer) start_time = g_timer_new (); + do { + if (!fu_ccgx_hpi_device_app_read_intr_reg (self, + section, + event_array, + &event_count, + error)) + return FALSE; + if (event_count > 0) + return TRUE; + } while (g_timer_elapsed (start_time, NULL) * 1000.f <= timeout_ms); + + /* timed out */ + g_set_error (error, + G_IO_ERROR, + G_IO_ERROR_TIMED_OUT, + "failed to wait for event in %ums", + timeout_ms); + return FALSE; +} + +static gboolean +fu_ccgx_hpi_device_get_event (FuCcgxHpiDevice *self, + HPIRegSection reg_section, + CyPDResp *event, + guint32 io_timeout, + GError **error ) +{ + HPIEvent event_array[HPI_REG_SECTION_ALL + 1] = { 0x0 }; + if (!fu_ccgx_hpi_device_wait_for_event (self, + reg_section, + event_array, + io_timeout, + error)) { + g_prefix_error (error, "failed to get event: "); + return FALSE; + } + *event = event_array[reg_section].event_code; + return TRUE; +} + static gboolean fu_ccgx_hpi_device_attach (FuDevice *device, GError **error) { @@ -412,8 +621,10 @@ fu_ccgx_hpi_device_setup (FuDevice *device, GError **error) { FuCcgxHpiDevice *self = FU_CCGX_HPI_DEVICE (device); CyI2CConfig i2c_config = { 0x0 }; + guint32 hpi_event = 0; guint8 mode = 0; g_autofree gchar *instance_id = NULL; + g_autoptr(GError) error_local = NULL; /* set the new config */ if (!fu_ccgx_hpi_device_get_i2c_config (self, &i2c_config, error)) { @@ -492,6 +703,23 @@ fu_ccgx_hpi_device_setup (FuDevice *device, GError **error) fu_device_add_flag (FU_DEVICE (self), FWUPD_DEVICE_FLAG_UPDATABLE); } + /* if we are coming back from reset, wait for hardware to settle */ + if (!fu_ccgx_hpi_device_get_event (self, + HPI_REG_SECTION_DEV, + &hpi_event, + HPI_CMD_SETUP_EVENT_WAIT_TIME_MS, + &error_local)) { + if (!g_error_matches (error_local, + G_IO_ERROR, + G_IO_ERROR_TIMED_OUT)) { + g_propagate_error (error, g_steal_pointer (&error_local)); + return FALSE; + } + } else { + if (hpi_event == CY_PD_RESP_RESET_COMPLETE) + g_usleep (HPI_CMD_RESET_COMPLETE_DELAY_US); + } + /* success */ return TRUE; }