ccgx: Wait for hardware to settle when coming out of reset

This commit is contained in:
Richard Hughes 2020-03-24 16:28:59 +00:00
parent 3990a71401
commit fd7f13f905
2 changed files with 404 additions and 0 deletions

View File

@ -122,6 +122,78 @@ typedef enum {
CY_I2C_DATA_CONFIG_NAK = 1 << 1, /* only for read */ CY_I2C_DATA_CONFIG_NAK = 1 << 1, /* only for read */
} CyI2CDataConfigBits; } 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 { typedef enum {
CY_PD_REG_DEVICE_MODE_ADDR, CY_PD_REG_DEVICE_MODE_ADDR,
CY_PD_BOOT_MODE_REASON, CY_PD_BOOT_MODE_REASON,
@ -166,3 +238,107 @@ typedef enum {
#define PD_I2CM_USB_EP_BULK_IN 0x83 #define PD_I2CM_USB_EP_BULK_IN 0x83
#define PD_I2CM_USB_EP_INTR_IN 0x84 #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;

View File

@ -33,6 +33,9 @@ struct _FuCcgxHpiDevice
G_DEFINE_TYPE (FuCcgxHpiDevice, fu_ccgx_hpi_device, FU_TYPE_USB_DEVICE) 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 static void
fu_ccgx_hpi_device_to_string (FuDevice *device, guint idt, GString *str) 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; 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 static gboolean
fu_ccgx_hpi_device_attach (FuDevice *device, GError **error) 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); FuCcgxHpiDevice *self = FU_CCGX_HPI_DEVICE (device);
CyI2CConfig i2c_config = { 0x0 }; CyI2CConfig i2c_config = { 0x0 };
guint32 hpi_event = 0;
guint8 mode = 0; guint8 mode = 0;
g_autofree gchar *instance_id = NULL; g_autofree gchar *instance_id = NULL;
g_autoptr(GError) error_local = NULL;
/* set the new config */ /* set the new config */
if (!fu_ccgx_hpi_device_get_i2c_config (self, &i2c_config, error)) { 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); 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 */ /* success */
return TRUE; return TRUE;
} }