/* * Copyright (C) 2020 Cypress Semiconductor Corporation. * Copyright (C) 2020 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include "fu-chunk.h" #include "fu-ccgx-common.h" #include "fu-ccgx-hpi-common.h" #include "fu-ccgx-hpi-device.h" #include "fu-ccgx-firmware.h" #include "fu-ccgx-firmware.h" struct _FuCcgxHpiDevice { FuUsbDevice parent_instance; guint8 inf_num; /* USB interface number */ guint8 scb_index; guint16 silicon_id; guint16 fw_app_type; guint8 hpi_addrsz; /* hpiv1: 1 byte, hpiv2: 2 byte */ guint8 num_ports; /* max number of ports */ FWMode fw_mode; FWImageType fw_image_type; guint8 slave_address; guint8 ep_bulk_in; guint8 ep_bulk_out; guint8 ep_intr_in; guint32 flash_row_size; guint32 flash_size; }; G_DEFINE_TYPE (FuCcgxHpiDevice, fu_ccgx_hpi_device, FU_TYPE_USB_DEVICE) #define HPI_CMD_REG_READ_WRITE_DELAY_US 10000 #define HPI_CMD_ENTER_FLASH_MODE_DELAY_US 20000 #define HPI_CMD_SETUP_EVENT_WAIT_TIME_MS 200 #define HPI_CMD_SETUP_EVENT_CLEAR_TIME_MS 150 #define HPI_CMD_COMMAND_RESPONSE_TIME_MS 500 #define HPI_CMD_COMMAND_CLEAR_EVENT_TIME_MS 30 #define HPI_CMD_RESET_COMPLETE_DELAY_US 150000 #define HPI_CMD_RETRY_DELAY 30 /* ms */ #define HPI_CMD_RESET_RETRY_CNT 3 static void fu_ccgx_hpi_device_to_string (FuDevice *device, guint idt, GString *str) { FuCcgxHpiDevice *self = FU_CCGX_HPI_DEVICE (device); fu_common_string_append_kx (str, idt, "InfNum", self->inf_num); fu_common_string_append_kx (str, idt, "ScbIndex", self->scb_index); fu_common_string_append_kx (str, idt, "SiliconId", self->silicon_id); fu_common_string_append_kx (str, idt, "FwAppType", self->fw_app_type); fu_common_string_append_kx (str, idt, "HpiAddrsz", self->hpi_addrsz); fu_common_string_append_kx (str, idt, "NumPorts", self->num_ports); fu_common_string_append_kv (str, idt, "FWMode", fu_ccgx_fw_mode_to_string (self->fw_mode)); fu_common_string_append_kv (str, idt, "FwImageType", fu_ccgx_fw_image_type_to_string (self->fw_image_type)); fu_common_string_append_kx (str, idt, "EpBulkIn", self->ep_bulk_in); fu_common_string_append_kx (str, idt, "EpBulkOut", self->ep_bulk_out); fu_common_string_append_kx (str, idt, "EpIntrIn", self->ep_intr_in); if (self->flash_row_size > 0) fu_common_string_append_kx (str, idt, "FlashRowSize", self->flash_row_size); if (self->flash_size > 0) fu_common_string_append_kx (str, idt, "FlashSize", self->flash_size); } typedef struct { guint8 mode; guint16 addr; guint8 *buf; gsize bufsz; } FuCcgxHpiDeviceRetryHelper; static gboolean fu_ccgx_hpi_device_i2c_reset_cb (FuDevice *device, gpointer user_data, GError **error) { FuCcgxHpiDevice *self = FU_CCGX_HPI_DEVICE (device); FuCcgxHpiDeviceRetryHelper *helper = (FuCcgxHpiDeviceRetryHelper *) user_data; g_autoptr(GError) error_local = NULL; if (!g_usb_device_control_transfer (fu_usb_device_get_dev (FU_USB_DEVICE (self)), G_USB_DEVICE_DIRECTION_HOST_TO_DEVICE, G_USB_DEVICE_REQUEST_TYPE_VENDOR, G_USB_DEVICE_RECIPIENT_DEVICE, CY_I2C_RESET_CMD, (self->scb_index << CY_SCB_INDEX_POS) | helper->mode, 0x0, NULL, 0x0, NULL, FU_CCGX_HPI_WAIT_TIMEOUT, NULL, &error_local)) { g_set_error (error, FWUPD_ERROR, FWUPD_ERROR_INTERNAL, "failed to reset i2c: %s", error_local->message); return FALSE; } return TRUE; } static gboolean fu_ccgx_hpi_device_check_i2c_status (FuCcgxHpiDevice *self, guint8 mode, GError **error) { guint8 buf[CY_I2C_GET_STATUS_LEN] = { 0x0 }; g_autoptr(GError) error_local = NULL; if (!g_usb_device_control_transfer (fu_usb_device_get_dev (FU_USB_DEVICE (self)), G_USB_DEVICE_DIRECTION_DEVICE_TO_HOST, G_USB_DEVICE_REQUEST_TYPE_VENDOR, G_USB_DEVICE_RECIPIENT_DEVICE, CY_I2C_GET_STATUS_CMD, (((guint16) self->scb_index) << CY_SCB_INDEX_POS) | mode, 0x0, buf, sizeof(buf), NULL, FU_CCGX_HPI_WAIT_TIMEOUT, NULL, &error_local)) { g_set_error (error, FWUPD_ERROR, FWUPD_ERROR_INTERNAL, "failed to get i2c status: %s", error_local->message); return FALSE; } if (buf[0] & CY_I2C_ERROR_BIT) { if (buf[0] & 0x80) { g_set_error (error, FWUPD_ERROR, FWUPD_ERROR_WRITE, "i2c status write error: 0x%x", buf[0]); return FALSE; } g_set_error (error, FWUPD_ERROR, FWUPD_ERROR_READ, "i2c status read error: 0x%x", buf[0]); return FALSE; } return TRUE; } static gboolean fu_ccgx_hpi_device_get_i2c_config (FuCcgxHpiDevice *self, CyI2CConfig *i2c_config, GError **error) { g_autoptr(GError) error_local = NULL; if (!g_usb_device_control_transfer (fu_usb_device_get_dev (FU_USB_DEVICE (self)), G_USB_DEVICE_DIRECTION_DEVICE_TO_HOST, G_USB_DEVICE_REQUEST_TYPE_VENDOR, G_USB_DEVICE_RECIPIENT_DEVICE, CY_I2C_GET_CONFIG_CMD, ((guint16) self->scb_index) << CY_SCB_INDEX_POS, 0x0, (guint8 *) i2c_config, sizeof(*i2c_config), NULL, FU_CCGX_HPI_WAIT_TIMEOUT, NULL, &error_local)) { g_set_error (error, FWUPD_ERROR, FWUPD_ERROR_INTERNAL, "i2c get config error: control xfer: %s", error_local->message); return FALSE; } return TRUE; } static gboolean fu_ccgx_hpi_device_set_i2c_config (FuCcgxHpiDevice *self, CyI2CConfig *i2c_config, GError **error) { g_autoptr(GError) error_local = NULL; if (!g_usb_device_control_transfer (fu_usb_device_get_dev (FU_USB_DEVICE (self)), G_USB_DEVICE_DIRECTION_HOST_TO_DEVICE, G_USB_DEVICE_REQUEST_TYPE_VENDOR, G_USB_DEVICE_RECIPIENT_DEVICE, CY_I2C_SET_CONFIG_CMD, ((guint16) self->scb_index) << CY_SCB_INDEX_POS, 0x0, (guint8 *) i2c_config, sizeof(*i2c_config), NULL, FU_CCGX_HPI_WAIT_TIMEOUT, NULL, &error_local)) { g_set_error (error, FWUPD_ERROR, FWUPD_ERROR_INTERNAL, "i2c set config error: control xfer: %s", error_local->message); return FALSE; } return TRUE; } static gboolean fu_ccgx_hpi_device_wait_for_notify (FuCcgxHpiDevice *self, guint16 *bytes_pending, GError **error) { guint8 buf[CY_I2C_EVENT_NOTIFICATION_LEN] = { 0x0 }; g_autoptr(GError) error_local = NULL; if (!g_usb_device_interrupt_transfer (fu_usb_device_get_dev (FU_USB_DEVICE (self)), self->ep_intr_in, buf, sizeof(buf), NULL, FU_CCGX_HPI_WAIT_TIMEOUT, NULL, &error_local)) { g_set_error (error, FWUPD_ERROR, FWUPD_ERROR_INTERNAL, "failed to get i2c event: %s", error_local->message); return FALSE; } /* @bytes_pending available on failure */ if (buf[0] & CY_I2C_ERROR_BIT) { if (bytes_pending != NULL) { if (!fu_common_read_uint16_safe (buf, sizeof(buf), 0x01, bytes_pending, G_LITTLE_ENDIAN, error)) return FALSE; } if (buf[0] & 0x80) { g_set_error (error, FWUPD_ERROR, FWUPD_ERROR_WRITE, "i2c status write error: 0x%x", buf[0]); return FALSE; } g_set_error (error, FWUPD_ERROR, FWUPD_ERROR_READ, "i2c status read error: 0x%x", buf[0]); return FALSE; } return TRUE; } static gboolean fu_ccgx_hpi_device_i2c_read (FuCcgxHpiDevice *self, guint8 *buf, gsize bufsz, CyI2CDataConfigBits cfg_bits, GError **error) { guint8 slave_address = 0; if (!fu_ccgx_hpi_device_check_i2c_status (self, CY_I2C_MODE_READ, error)) { g_prefix_error (error, "i2c read error: "); return FALSE; } slave_address = (self->slave_address & 0x7F) | (self->scb_index << 7); if (!g_usb_device_control_transfer (fu_usb_device_get_dev (FU_USB_DEVICE (self)), G_USB_DEVICE_DIRECTION_HOST_TO_DEVICE, G_USB_DEVICE_REQUEST_TYPE_VENDOR, G_USB_DEVICE_RECIPIENT_DEVICE, CY_I2C_READ_CMD, (((guint16) slave_address) << 8) | cfg_bits, bufsz, NULL, 0x0, NULL, FU_CCGX_HPI_WAIT_TIMEOUT, NULL, error)) { g_prefix_error (error, "i2c read error: control xfer: "); return FALSE; } if (!g_usb_device_bulk_transfer (fu_usb_device_get_dev (FU_USB_DEVICE (self)), self->ep_bulk_in, buf, bufsz, NULL, FU_CCGX_HPI_WAIT_TIMEOUT, NULL, error)) { g_prefix_error (error, "i2c read error: bulk xfer: "); return FALSE; } /* 10 msec delay */ g_usleep (I2C_READ_WRITE_DELAY_US); if (!fu_ccgx_hpi_device_wait_for_notify (self, NULL, error)) { g_prefix_error (error, "i2c read error: "); return FALSE; } return TRUE; } static gboolean fu_ccgx_hpi_device_i2c_write (FuCcgxHpiDevice *self, guint8 *buf, gsize bufsz, CyI2CDataConfigBits cfg_bits, GError **error) { guint8 slave_address; g_autoptr(GError) error_local = NULL; if (!fu_ccgx_hpi_device_check_i2c_status (self, CY_I2C_MODE_WRITE, error)) { g_prefix_error (error, "i2c get status error: "); return FALSE; } slave_address = (self->slave_address & 0x7F) | (self->scb_index << 7); if (!g_usb_device_control_transfer (fu_usb_device_get_dev (FU_USB_DEVICE (self)), G_USB_DEVICE_DIRECTION_HOST_TO_DEVICE, G_USB_DEVICE_REQUEST_TYPE_VENDOR, G_USB_DEVICE_RECIPIENT_DEVICE, CY_I2C_WRITE_CMD, ((guint16) slave_address << 8) | (cfg_bits & CY_I2C_DATA_CONFIG_STOP), bufsz, /* idx */ NULL, 0x0, NULL, FU_CCGX_HPI_WAIT_TIMEOUT, NULL, error)) { g_prefix_error (error, "i2c write error: control xfer: "); return FALSE; } if (!g_usb_device_bulk_transfer (fu_usb_device_get_dev (FU_USB_DEVICE (self)), self->ep_bulk_out, buf, bufsz, NULL, FU_CCGX_HPI_WAIT_TIMEOUT, NULL, error)) { g_prefix_error (error, "i2c write error: bulk xfer: "); return FALSE; } /* 10 msec delay */ g_usleep (I2C_READ_WRITE_DELAY_US); if (!fu_ccgx_hpi_device_wait_for_notify (self, NULL, error)) { g_prefix_error (error, "i2c wait for notification error: "); return FALSE; } return TRUE; } static gboolean fu_ccgx_hpi_device_i2c_write_no_resp (FuCcgxHpiDevice *self, guint8 *buf, gsize bufsz, CyI2CDataConfigBits cfg_bits, GError **error) { guint8 slave_address = 0; g_autoptr(GError) error_local = NULL; if (!fu_ccgx_hpi_device_check_i2c_status (self, CY_I2C_MODE_WRITE, error)) { g_prefix_error (error, "i2c write error: "); return FALSE; } slave_address = (self->slave_address & 0x7F) | (self->scb_index << 7); if (!g_usb_device_control_transfer (fu_usb_device_get_dev (FU_USB_DEVICE (self)), G_USB_DEVICE_DIRECTION_HOST_TO_DEVICE, G_USB_DEVICE_REQUEST_TYPE_VENDOR, G_USB_DEVICE_RECIPIENT_DEVICE, CY_I2C_WRITE_CMD, ((guint16) slave_address << 8) | (cfg_bits & CY_I2C_DATA_CONFIG_STOP), bufsz, NULL, 0x0, NULL, FU_CCGX_HPI_WAIT_TIMEOUT, NULL, error)) { g_prefix_error (error, "i2c write error: control xfer: "); return FALSE; } /* device will reboot after this, so txfer will fail */ if (!g_usb_device_bulk_transfer (fu_usb_device_get_dev (FU_USB_DEVICE (self)), self->ep_bulk_out, buf, bufsz, NULL, FU_CCGX_HPI_WAIT_TIMEOUT, NULL, &error_local)) { g_debug ("ignoring i2c write error: bulk xfer: %s", error_local->message); } return TRUE; } static gboolean fu_ccgx_hpi_device_reg_read_cb (FuDevice *device, gpointer user_data, GError **error) { FuCcgxHpiDeviceRetryHelper *helper = (FuCcgxHpiDeviceRetryHelper *) user_data; FuCcgxHpiDevice *self = FU_CCGX_HPI_DEVICE (device); g_autofree guint8 *bufhw = g_malloc0 (self->hpi_addrsz); for (guint32 i = 0; i < self->hpi_addrsz; i++) bufhw[i] = (guint8) (helper->addr >> (8 * i)); if (!fu_ccgx_hpi_device_i2c_write (self, bufhw, self->hpi_addrsz, CY_I2C_DATA_CONFIG_NAK, error)) { g_prefix_error (error, "write error: "); return FALSE; } if (!fu_ccgx_hpi_device_i2c_read (self, helper->buf, helper->bufsz, CY_I2C_DATA_CONFIG_STOP | CY_I2C_DATA_CONFIG_NAK, error)) { g_prefix_error (error, "read error: "); return FALSE; } g_usleep (HPI_CMD_REG_READ_WRITE_DELAY_US); return TRUE; } static gboolean fu_ccgx_hpi_device_reg_read (FuCcgxHpiDevice *self, guint16 addr, guint8 *buf, gsize bufsz, GError **error) { FuCcgxHpiDeviceRetryHelper helper = { .addr = addr, .mode = CY_I2C_MODE_READ, .buf = buf, .bufsz = bufsz, }; return fu_device_retry (FU_DEVICE (self), fu_ccgx_hpi_device_reg_read_cb, HPI_CMD_RESET_RETRY_CNT, &helper, error); } static gboolean fu_ccgx_hpi_device_reg_write_cb (FuDevice *device, gpointer user_data, GError **error) { FuCcgxHpiDeviceRetryHelper *helper = (FuCcgxHpiDeviceRetryHelper *) user_data; FuCcgxHpiDevice *self = FU_CCGX_HPI_DEVICE (device); g_autofree guint8 *bufhw = g_malloc0 (helper->bufsz + self->hpi_addrsz); for (guint32 i = 0; i < self->hpi_addrsz; i++) bufhw[i] = (guint8) (helper->addr >> (8*i)); memcpy (&bufhw[self->hpi_addrsz], helper->buf, helper->bufsz); if (!fu_ccgx_hpi_device_i2c_write (self, bufhw, helper->bufsz + self->hpi_addrsz, CY_I2C_DATA_CONFIG_STOP | CY_I2C_DATA_CONFIG_NAK, error)) { g_prefix_error (error, "reg write error: "); return FALSE; } g_usleep (HPI_CMD_REG_READ_WRITE_DELAY_US); return TRUE; } static gboolean fu_ccgx_hpi_device_reg_write (FuCcgxHpiDevice *self, guint16 addr, const guint8 *buf, gsize bufsz, GError **error) { FuCcgxHpiDeviceRetryHelper helper = { .addr = addr, .mode = CY_I2C_MODE_WRITE, .buf = (guint8 *) buf, .bufsz = bufsz, }; return fu_device_retry (FU_DEVICE (self), fu_ccgx_hpi_device_reg_write_cb, HPI_CMD_RESET_RETRY_CNT, &helper, error); } static gboolean fu_ccgx_hpi_device_reg_write_no_resp (FuCcgxHpiDevice *self, guint16 addr, guint8 *buf, guint16 bufsz, GError **error) { g_autofree guint8 *bufhw = g_malloc0 (bufsz + self->hpi_addrsz); 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_no_resp (self, bufhw, bufsz + self->hpi_addrsz, CY_I2C_DATA_CONFIG_STOP | CY_I2C_DATA_CONFIG_NAK, error)) { g_prefix_error (error, "reg write no-resp error: "); return FALSE; } g_usleep (HPI_CMD_REG_READ_WRITE_DELAY_US); 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_clear_all_events (FuCcgxHpiDevice *self, guint32 io_timeout, GError **error) { HPIEvent event_array[HPI_REG_SECTION_ALL + 1] = { 0x0 }; if (io_timeout == 0) { return fu_ccgx_hpi_device_app_read_intr_reg (self, HPI_REG_SECTION_ALL, event_array, NULL, error); } for (guint8 i = 0; i < self->num_ports; i++) { g_autoptr(GError) error_local = NULL; if (!fu_ccgx_hpi_device_wait_for_event (self, i, event_array, io_timeout, &error_local)) { if (!g_error_matches (error_local, G_IO_ERROR, G_IO_ERROR_TIMED_OUT)) { g_propagate_prefixed_error (error, g_steal_pointer (&error_local), "failed to clear events: "); return FALSE; } } } return TRUE; } static gboolean fu_ccgx_hpi_validate_fw (FuCcgxHpiDevice *self, guint8 fw_index, GError **error) { CyPDResp hpi_event = 0; if (!fu_ccgx_hpi_device_clear_all_events (self, HPI_CMD_COMMAND_CLEAR_EVENT_TIME_MS, error)) return FALSE; if (!fu_ccgx_hpi_device_reg_write (self, CY_PD_REG_VALIDATE_FW_ADDR, &fw_index, sizeof(fw_index), error)) { g_prefix_error (error, "validate fw error: "); return FALSE; } if (!fu_ccgx_hpi_device_get_event (self, HPI_REG_SECTION_DEV, &hpi_event, HPI_CMD_COMMAND_RESPONSE_TIME_MS, error)) { g_prefix_error (error, "validate fw resp error: "); return FALSE; } if (hpi_event != CY_PD_RESP_SUCCESS) { g_set_error (error, FWUPD_ERROR, FWUPD_ERROR_INTERNAL, "validate failed: %s [0x%x]", fu_ccgx_pd_resp_to_string (hpi_event), hpi_event); return FALSE; } return TRUE; } static gboolean fu_ccgx_hpi_enter_flash_mode (FuCcgxHpiDevice *self, GError **error) { CyPDResp hpi_event = 0; guint8 buf[] = { CY_PD_ENTER_FLASHING_MODE_CMD_SIG }; if (!fu_ccgx_hpi_device_clear_all_events (self, HPI_CMD_COMMAND_CLEAR_EVENT_TIME_MS, error)) return FALSE; if (!fu_ccgx_hpi_device_reg_write (self, CY_PD_REG_ENTER_FLASH_MODE_ADDR, buf, sizeof(buf), error)) { g_prefix_error (error, "enter flash mode error: "); return FALSE; } if (!fu_ccgx_hpi_device_get_event (self, HPI_REG_SECTION_DEV, &hpi_event, HPI_CMD_COMMAND_RESPONSE_TIME_MS, error)) { g_prefix_error (error, "enter flash mode resp error: "); return FALSE; } if (hpi_event != CY_PD_RESP_SUCCESS) { g_set_error (error, FWUPD_ERROR, FWUPD_ERROR_INTERNAL, "enter flash failed: %s [0x%x]", fu_ccgx_pd_resp_to_string (hpi_event), hpi_event); return FALSE; } /* wait 10 msec */ g_usleep (HPI_CMD_ENTER_FLASH_MODE_DELAY_US); return TRUE; } static gboolean fu_ccgx_hpi_leave_flash_mode (FuCcgxHpiDevice *self, GError **error) { CyPDResp hpi_event = 0; guint8 buf = { 0x0 }; if (!fu_ccgx_hpi_device_clear_all_events (self, HPI_CMD_COMMAND_CLEAR_EVENT_TIME_MS, error)) return FALSE; if (!fu_ccgx_hpi_device_reg_write (self, CY_PD_REG_ENTER_FLASH_MODE_ADDR, &buf, sizeof(buf), error)) { g_prefix_error (error, "leave flash mode error: "); return FALSE; } if (!fu_ccgx_hpi_device_get_event (self, HPI_REG_SECTION_DEV, &hpi_event, HPI_CMD_COMMAND_RESPONSE_TIME_MS, error)) { g_prefix_error (error, "leave flash mode resp error: "); return FALSE; } if (hpi_event != CY_PD_RESP_SUCCESS) { g_set_error (error, FWUPD_ERROR, FWUPD_ERROR_INTERNAL, "leave flash mode failed: %s [0x%x]", fu_ccgx_pd_resp_to_string (hpi_event), hpi_event); return FALSE; } /* wait 10 msec */ g_usleep (HPI_CMD_ENTER_FLASH_MODE_DELAY_US); return TRUE; } static gboolean fu_ccgx_hpi_write_flash (FuCcgxHpiDevice *self, guint16 addr, const guint8 *buf, guint16 bufsz, GError **error) { CyPDResp hpi_event = 0; guint16 addr_tmp = 0; guint8 bufhw[] = { CY_PD_FLASH_READ_WRITE_CMD_SIG, CY_PD_REG_FLASH_ROW_WRITE_CMD, addr & 0xFF, addr >> 8, }; if (!fu_ccgx_hpi_device_clear_all_events (self, HPI_CMD_COMMAND_CLEAR_EVENT_TIME_MS, error)) return FALSE; /* write data to memory */ addr_tmp = self->hpi_addrsz > 1 ? HPI_DEV_REG_FLASH_MEM : CY_PD_REG_BOOTDATA_MEMORY_ADDR; if (!fu_ccgx_hpi_device_reg_write (self, addr_tmp, buf, bufsz, error)) { g_prefix_error (error, "write buf to memory error"); return FALSE; } if (!fu_ccgx_hpi_device_reg_write (self, CY_PD_REG_FLASH_READ_WRITE_ADDR, bufhw, sizeof(bufhw), error)) { g_prefix_error (error, "write flash error: "); return FALSE; } /* wait until flash is written */ if (!fu_ccgx_hpi_device_get_event (self, HPI_REG_SECTION_DEV, &hpi_event, HPI_CMD_COMMAND_RESPONSE_TIME_MS, error)) { g_prefix_error (error, "write flash resp error"); return FALSE; } if (hpi_event != CY_PD_RESP_SUCCESS) { g_set_error (error, FWUPD_ERROR, FWUPD_ERROR_INTERNAL, "write flash failed: %s [0x%x]", fu_ccgx_pd_resp_to_string (hpi_event), hpi_event); return FALSE; } return TRUE; } static gboolean fu_ccgx_hpi_read_flash (FuCcgxHpiDevice *self, guint16 addr, guint8 *buf, guint16 bufsz, GError **error) { CyPDResp hpi_event = 0; guint16 addr_tmp; guint8 bufhw[] = { CY_PD_FLASH_READ_WRITE_CMD_SIG, CY_PD_REG_FLASH_ROW_READ_CMD, addr & 0xFF, addr >> 8, }; /* set address */ if (!fu_ccgx_hpi_device_clear_all_events (self, HPI_CMD_COMMAND_CLEAR_EVENT_TIME_MS, error)) return FALSE; if (!fu_ccgx_hpi_device_reg_write (self, CY_PD_REG_FLASH_READ_WRITE_ADDR, bufhw, sizeof(bufhw), error)) { g_prefix_error (error, "read flash error: "); return FALSE; } /* wait until flash is read */ if (!fu_ccgx_hpi_device_get_event (self, HPI_REG_SECTION_DEV, &hpi_event, HPI_CMD_COMMAND_RESPONSE_TIME_MS, error)) { g_prefix_error (error, "read flash resp error: "); return FALSE; } if (hpi_event != CY_PD_RESP_FLASH_DATA_AVAILABLE) { g_set_error (error, FWUPD_ERROR, FWUPD_ERROR_INTERNAL, "read flash failed: %s [0x%x]", fu_ccgx_pd_resp_to_string (hpi_event), hpi_event); return FALSE; } addr_tmp = self->hpi_addrsz > 1 ? HPI_DEV_REG_FLASH_MEM : CY_PD_REG_BOOTDATA_MEMORY_ADDR; if (!fu_ccgx_hpi_device_reg_read (self, addr_tmp, buf, bufsz, error)) { g_prefix_error (error, "read data from memory error"); return FALSE; } return TRUE; } static gboolean fu_ccgx_hpi_device_detach (FuDevice *device, GError **error) { FuCcgxHpiDevice *self = FU_CCGX_HPI_DEVICE (device); guint8 buf[] = { CY_PD_JUMP_TO_ALT_FW_CMD_SIG, }; /* not required */ if (fu_device_has_flag (device, FWUPD_DEVICE_FLAG_IS_BOOTLOADER) || self->fw_image_type == FW_IMAGE_TYPE_DUAL_SYMMETRIC) return TRUE; /* jump to Alt FW */ if (!fu_ccgx_hpi_device_clear_all_events (self, HPI_CMD_COMMAND_CLEAR_EVENT_TIME_MS, error)) return FALSE; fu_device_set_status (device, FWUPD_STATUS_DEVICE_RESTART); if (!fu_ccgx_hpi_device_reg_write (self, CY_PD_JUMP_TO_BOOT_REG_ADDR, buf, sizeof(buf), error)) { g_prefix_error (error, "jump to alt mode error: "); return FALSE; } /* sym not required */ fu_device_add_flag (device, FWUPD_DEVICE_FLAG_WAIT_FOR_REPLUG); /* success */ return TRUE; } static gboolean fu_ccgx_hpi_device_attach (FuDevice *device, GError **error) { FuCcgxHpiDevice *self = FU_CCGX_HPI_DEVICE (device); guint8 buf[] = { CY_PD_DEVICE_RESET_CMD_SIG, CY_PD_REG_RESET_DEVICE_CMD, }; if (!fu_ccgx_hpi_device_clear_all_events (self, HPI_CMD_COMMAND_CLEAR_EVENT_TIME_MS, error)) return FALSE; fu_device_set_status (device, FWUPD_STATUS_DEVICE_RESTART); if (!fu_ccgx_hpi_device_reg_write_no_resp (self, CY_PD_REG_RESET_ADDR, buf, sizeof(buf), error)) { g_prefix_error (error, "reset device error: "); return FALSE; } fu_device_add_flag (device, FWUPD_DEVICE_FLAG_WAIT_FOR_REPLUG); return TRUE; } static FuFirmware * fu_ccgx_hpi_device_prepare_firmware (FuDevice *device, GBytes *fw, FwupdInstallFlags flags, GError **error) { FuCcgxHpiDevice *self = FU_CCGX_HPI_DEVICE (device); FWMode fw_mode; guint16 fw_app_type; guint16 fw_silicon_id; g_autoptr(FuFirmware) firmware = fu_ccgx_firmware_new (); /* parse all images */ if (!fu_firmware_parse (firmware, fw, flags, error)) return NULL; /* check the silicon ID */ fw_silicon_id = fu_ccgx_firmware_get_silicon_id (FU_CCGX_FIRMWARE (firmware)); if (fw_silicon_id != self->silicon_id) { g_set_error (error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "silicon id mismatch, expected 0x%x, got 0x%x", self->silicon_id, fw_silicon_id); return NULL; } if ((flags & FWUPD_INSTALL_FLAG_FORCE) == 0) { fw_app_type = fu_ccgx_firmware_get_app_type (FU_CCGX_FIRMWARE (firmware)); if (fw_app_type != self->fw_app_type) { g_set_error (error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "app type mismatch, expected 0x%x, got 0x%x", self->fw_app_type, fw_app_type); return NULL; } } fw_mode = fu_ccgx_firmware_get_fw_mode (FU_CCGX_FIRMWARE (firmware)); if (fw_mode != fu_ccgx_fw_mode_get_alternate (self->fw_mode)) { g_set_error (error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "FWMode mismatch, expected %s, got %s", fu_ccgx_fw_mode_to_string (fu_ccgx_fw_mode_get_alternate (self->fw_mode)), fu_ccgx_fw_mode_to_string (fw_mode)); return NULL; } return g_steal_pointer (&firmware); } static gboolean fu_ccgx_hpi_get_metadata_offset (FuCcgxHpiDevice *self, FWMode fw_mode, guint32 *addr, guint32 *offset, GError **error) { guint32 addr_max; /* sanity check */ if (self->flash_row_size == 0x0) { g_set_error_literal (error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "unset support row size"); return FALSE; } /* get the row offset for the flash size */ addr_max = self->flash_size / self->flash_row_size; if (self->flash_row_size == 128) { *offset = HPI_META_DATA_OFFSET_ROW_128; } else if (self->flash_row_size == 256) { *offset = HPI_META_DATA_OFFSET_ROW_256; } else { g_set_error (error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "unsupported support row size: 0x%x", self->flash_row_size); return FALSE; } /* get the row offset in the flash */ switch (fw_mode) { case FW_MODE_FW1: *addr = addr_max - 1; break; case FW_MODE_FW2: *addr = addr_max - 2; break; default: g_set_error_literal (error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "boot recovery not supported"); return FALSE; } return TRUE; } /* this will only work after fu_ccgx_hpi_enter_flash_mode() has been used */ static gboolean fu_ccgx_hpi_load_metadata (FuCcgxHpiDevice *self, FWMode fw_mode, CCGxMetaData *metadata, GError **error) { guint32 addr = 0x0; guint32 md_offset = 0x0; g_autofree guint8 *buf = NULL; /* read flash at correct address */ if (!fu_ccgx_hpi_get_metadata_offset (self, fw_mode, &addr, &md_offset, error)) return FALSE; buf = g_malloc0 (self->flash_row_size); if (!fu_ccgx_hpi_read_flash (self, addr, buf, self->flash_row_size, error)) { g_prefix_error (error, "fw metadata read error: "); return FALSE; } return fu_memcpy_safe ((guint8 *) metadata, sizeof(*metadata), 0x0, buf, self->flash_row_size, md_offset, sizeof(metadata), error); } /* this will only work after fu_ccgx_hpi_enter_flash_mode() has been used */ static gboolean fu_ccgx_hpi_save_metadata (FuCcgxHpiDevice *self, FWMode fw_mode, CCGxMetaData *metadata, GError **error) { guint32 addr = 0x0; guint32 md_offset = 0x0; g_autofree guint8 *buf = NULL; /* read entire row of flash at correct address */ if (!fu_ccgx_hpi_get_metadata_offset (self, fw_mode, &addr, &md_offset, error)) return FALSE; buf = g_malloc0 (self->flash_row_size); if (!fu_ccgx_hpi_read_flash (self, addr, buf, self->flash_row_size, error)) { g_prefix_error (error, "fw metadata read existing error: "); return FALSE; } if (!fu_memcpy_safe (buf, self->flash_row_size, md_offset, (guint8 *) metadata, sizeof(*metadata), 0x0, sizeof(metadata), error)) return FALSE; if (!fu_ccgx_hpi_write_flash (self, addr, buf, self->flash_row_size, error)) { g_prefix_error (error, "fw metadata write error: "); return FALSE; } return TRUE; } static gboolean fu_ccgx_hpi_write_firmware (FuDevice *device, FuFirmware *firmware, FwupdInstallFlags flags, GError **error) { FuCcgxHpiDevice *self = FU_CCGX_HPI_DEVICE (device); CCGxMetaData metadata = { 0x0 }; GPtrArray *records = fu_ccgx_firmware_get_records (FU_CCGX_FIRMWARE (firmware)); FWMode fw_mode_alt = fu_ccgx_fw_mode_get_alternate (self->fw_mode); g_autoptr(FuDeviceLocker) locker = NULL; /* enter flash mode */ locker = fu_device_locker_new_full (self, (FuDeviceLockerFunc) fu_ccgx_hpi_enter_flash_mode, (FuDeviceLockerFunc) fu_ccgx_hpi_leave_flash_mode, error); if (locker == NULL) return FALSE; /* invalidate metadata for alternate image */ fu_device_set_status (device, FWUPD_STATUS_DEVICE_READ); if (!fu_ccgx_hpi_load_metadata (self, fw_mode_alt, &metadata, error)) return FALSE; fu_device_set_status (device, FWUPD_STATUS_DEVICE_ERASE); metadata.metadata_valid = 0x00; if (!fu_ccgx_hpi_save_metadata (self, fw_mode_alt, &metadata, error)) return FALSE; /* write new image */ fu_device_set_status (device, FWUPD_STATUS_DEVICE_WRITE); for (guint i = 0; i < records->len; i++) { FuCcgxFirmwareRecord *rcd = g_ptr_array_index (records, i); /* write chunk */ if (!fu_ccgx_hpi_write_flash (self, rcd->row_number, g_bytes_get_data (rcd->data, NULL), g_bytes_get_size (rcd->data), error)) { g_prefix_error (error, "fw write error @0x%x: ", rcd->row_number); return FALSE; } /* update progress */ fu_device_set_progress_full (device, (gsize) i, (gsize) records->len - 1); } /* validate fw */ fu_device_set_status (device, FWUPD_STATUS_DEVICE_VERIFY); if (!fu_ccgx_hpi_validate_fw (self, fw_mode_alt, error)) { g_prefix_error (error, "fw validate error: "); return FALSE; } /* this is a good time to leave the flash mode *before* rebooting */ if (!fu_device_locker_close (locker, error)) return FALSE; /* success */ return TRUE; } static gboolean fu_ccgx_hpi_device_ensure_silicon_id (FuCcgxHpiDevice *self, GError **error) { guint8 buf[2] = { 0x0 }; g_autofree gchar *instance_id = NULL; if (!fu_ccgx_hpi_device_reg_read (self, CY_PD_SILICON_ID, buf, sizeof(buf), error)) { g_prefix_error (error, "get silicon id error: "); return FALSE; } if (!fu_common_read_uint16_safe (buf, sizeof(buf), 0x0, &self->silicon_id, G_LITTLE_ENDIAN, error)) return FALSE; /* add quirks */ instance_id = g_strdup_printf ("CCGX\\SID_%X", self->silicon_id); fu_device_add_instance_id_full (FU_DEVICE (self), instance_id, FU_DEVICE_INSTANCE_FLAG_ONLY_QUIRKS); /* sanity check */ if (self->flash_row_size == 0x0 || self->flash_size == 0x0 || self->flash_size % self->flash_row_size != 0) { g_set_error (error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "invalid row size for Instance ID %s: 0x%x/0x%x", instance_id, self->flash_row_size, self->flash_size); return FALSE; } /* success */ return TRUE; } static void fu_ccgx_hpi_device_set_version_raw (FuCcgxHpiDevice *self, guint32 version_raw) { g_autofree gchar *version = fu_ccgx_version_to_string (version_raw); fu_device_set_version (FU_DEVICE (self), version); fu_device_set_version_raw (FU_DEVICE (self), version_raw); } static void fu_ccgx_hpi_device_setup_with_fw_mode (FuCcgxHpiDevice *self) { fu_device_set_logical_id (FU_DEVICE (self), fu_ccgx_fw_mode_to_string (self->fw_mode)); } static void fu_ccgx_hpi_device_setup_with_app_type (FuCcgxHpiDevice *self) { if (self->silicon_id != 0x0 && self->fw_app_type != 0x0) { g_autofree gchar *instance_id1 = NULL; g_autofree gchar *instance_id2 = NULL; /* we get fw_image_type from the quirk */ instance_id1 = g_strdup_printf ("USB\\VID_%04X&PID_%04X&SID_%04X&APP_%04X", fu_usb_device_get_vid (FU_USB_DEVICE (self)), fu_usb_device_get_pid (FU_USB_DEVICE (self)), self->silicon_id, self->fw_app_type); fu_device_add_instance_id (FU_DEVICE (self), instance_id1); instance_id2 = g_strdup_printf ("USB\\VID_%04X&PID_%04X&SID_%04X&APP_%04X&MODE_%s", fu_usb_device_get_vid (FU_USB_DEVICE (self)), fu_usb_device_get_pid (FU_USB_DEVICE (self)), self->silicon_id, self->fw_app_type, fu_ccgx_fw_mode_to_string (self->fw_mode)); fu_device_add_instance_id (FU_DEVICE (self), instance_id2); } } static gboolean 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_autoptr(GError) error_local = NULL; /* set the new config */ if (!fu_ccgx_hpi_device_get_i2c_config (self, &i2c_config, error)) { g_prefix_error (error, "get config error: "); return FALSE; } i2c_config.frequency = FU_CCGX_HPI_FREQ; i2c_config.is_master = TRUE; i2c_config.is_msb_first = TRUE; if (!fu_ccgx_hpi_device_set_i2c_config (self, &i2c_config, error)) { g_prefix_error (error, "set config error: "); return FALSE; } if (!fu_ccgx_hpi_device_reg_read (self, CY_PD_REG_DEVICE_MODE_ADDR, &mode, 1, error)) { g_prefix_error (error, "get device mode error: "); return FALSE; } self->hpi_addrsz = mode & 0x80 ? 2 : 1; self->num_ports = (mode >> 2) & 0x03 ? 2 : 1; self->fw_mode = (FWMode) (mode & 0x03); fu_ccgx_hpi_device_setup_with_fw_mode (self); /* get silicon ID */ if (!fu_ccgx_hpi_device_ensure_silicon_id (self, error)) return FALSE; /* get correct version if not in boot mode */ if (self->fw_mode != FW_MODE_BOOT) { guint16 bufsz; guint32 versions[FW_MODE_LAST] = { 0x0 }; guint8 bufver[HPI_DEVICE_VERSION_SIZE_HPIV2] = { 0x0 }; bufsz = self->hpi_addrsz == 1 ? HPI_DEVICE_VERSION_SIZE_HPIV1 : HPI_DEVICE_VERSION_SIZE_HPIV2; if (!fu_ccgx_hpi_device_reg_read (self, CY_PD_GET_VERSION, bufver, bufsz, error)) { g_prefix_error (error, "get version error: "); return FALSE; } /* fw1 */ if (!fu_common_read_uint32_safe (bufver, sizeof(bufver), 0x0c, &versions[FW_MODE_FW1], G_LITTLE_ENDIAN, error)) return FALSE; /* fw2 */ if (!fu_common_read_uint32_safe (bufver, sizeof(bufver), 0x14, &versions[FW_MODE_FW2], G_LITTLE_ENDIAN, error)) return FALSE; /* add GUIDs that are specific to the firmware app type */ self->fw_app_type = versions[self->fw_mode] & 0xffff; fu_ccgx_hpi_device_setup_with_app_type (self); /* if running in bootloader force an upgrade to any version */ if (fu_device_has_flag (device, FWUPD_DEVICE_FLAG_IS_BOOTLOADER)) { fu_ccgx_hpi_device_set_version_raw (self, 0x0); } else { fu_ccgx_hpi_device_set_version_raw (self, versions[self->fw_mode]); } } /* not supported in boot mode */ if (self->fw_mode == FW_MODE_BOOT) { fu_device_remove_flag (FU_DEVICE (self), FWUPD_DEVICE_FLAG_UPDATABLE); } else { 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); } /* start with no events in the queue */ return fu_ccgx_hpi_device_clear_all_events (self, HPI_CMD_SETUP_EVENT_CLEAR_TIME_MS, error); } static gboolean fu_ccgx_hpi_device_set_quirk_kv (FuDevice *device, const gchar *key, const gchar *value, GError **error) { FuCcgxHpiDevice *self = FU_CCGX_HPI_DEVICE (device); if (g_strcmp0 (key, "SiliconId") == 0) { guint64 tmp = fu_common_strtoull (value); if (tmp < G_MAXUINT16) { self->silicon_id = tmp; return TRUE; } g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA, "invalid SiliconId"); return FALSE; } if (g_strcmp0 (key, "FlashRowSize") == 0) { guint64 tmp = fu_common_strtoull (value); if (tmp < G_MAXUINT32) { self->flash_row_size = tmp; return TRUE; } g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA, "invalid FlashRowSize"); return FALSE; } if (g_strcmp0 (key, "FlashSize") == 0) { guint64 tmp = fu_common_strtoull (value); if (tmp < G_MAXUINT32) { self->flash_size = tmp; return TRUE; } g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA, "invalid FlashSize"); return FALSE; } if (g_strcmp0 (key, "ImageKind") == 0) { self->fw_image_type = fu_ccgx_fw_image_type_from_string (value); if (self->fw_image_type != FW_IMAGE_TYPE_UNKNOWN) return TRUE; g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA, "invalid ImageKind"); return FALSE; } g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED, "no supported"); return FALSE; } static gboolean fu_ccgx_hpi_device_open (FuUsbDevice *device, GError **error) { FuCcgxHpiDevice *self = FU_CCGX_HPI_DEVICE (device); g_autoptr(GError) error_local = NULL; if (!g_usb_device_claim_interface (fu_usb_device_get_dev (device), self->inf_num, G_USB_DEVICE_CLAIM_INTERFACE_BIND_KERNEL_DRIVER, &error_local)) { g_set_error (error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "cannot claim interface: %s", error_local->message); return FALSE; } return TRUE; } static gboolean fu_ccgx_hpi_device_close (FuUsbDevice *device, GError **error) { FuCcgxHpiDevice *self = FU_CCGX_HPI_DEVICE (device); g_autoptr(GError) error_local = NULL; if (!g_usb_device_release_interface (fu_usb_device_get_dev (device), self->inf_num, G_USB_DEVICE_CLAIM_INTERFACE_BIND_KERNEL_DRIVER, &error_local)) { g_set_error (error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "cannot release interface: %s", error_local->message); return FALSE; } return TRUE; } static void fu_ccgx_hpi_device_init (FuCcgxHpiDevice *self) { self->inf_num = 0x0; self->hpi_addrsz = 1; self->num_ports = 1; self->slave_address = PD_I2C_SLAVE_ADDRESS; self->ep_bulk_out = PD_I2C_USB_EP_BULK_OUT; self->ep_bulk_in = PD_I2C_USB_EP_BULK_IN; self->ep_intr_in = PD_I2C_USB_EP_INTR_IN; fu_device_set_protocol (FU_DEVICE (self), "com.cypress.ccgx"); fu_device_set_install_duration (FU_DEVICE (self), 60); fu_device_set_version_format (FU_DEVICE (self), FWUPD_VERSION_FORMAT_TRIPLET); fu_device_set_remove_delay (FU_DEVICE (self), 120000); /* over a minute to flash offline */ fu_device_add_flag (FU_DEVICE (self), FWUPD_DEVICE_FLAG_REQUIRE_AC); fu_device_add_flag (FU_DEVICE (self), FWUPD_DEVICE_FLAG_DUAL_IMAGE); fu_device_add_flag (FU_DEVICE (self), FWUPD_DEVICE_FLAG_CAN_VERIFY_IMAGE); fu_device_add_flag (FU_DEVICE (self), FWUPD_DEVICE_FLAG_SELF_RECOVERY); fu_device_retry_set_delay (FU_DEVICE (self), HPI_CMD_RETRY_DELAY); /* we can recover the I²C link using reset */ fu_device_retry_add_recovery (FU_DEVICE (self), FWUPD_ERROR, FWUPD_ERROR_READ, fu_ccgx_hpi_device_i2c_reset_cb); fu_device_retry_add_recovery (FU_DEVICE (self), FWUPD_ERROR, FWUPD_ERROR_WRITE, fu_ccgx_hpi_device_i2c_reset_cb); /* this might not be true for future hardware */ if (self->inf_num > 0) self->scb_index = 1; } static void fu_ccgx_hpi_device_class_init (FuCcgxHpiDeviceClass *klass) { FuDeviceClass *klass_device = FU_DEVICE_CLASS (klass); FuUsbDeviceClass *klass_usb_device = FU_USB_DEVICE_CLASS (klass); klass_device->to_string = fu_ccgx_hpi_device_to_string; klass_device->write_firmware = fu_ccgx_hpi_write_firmware; klass_device->prepare_firmware = fu_ccgx_hpi_device_prepare_firmware; klass_device->detach = fu_ccgx_hpi_device_detach; klass_device->attach = fu_ccgx_hpi_device_attach; klass_device->setup = fu_ccgx_hpi_device_setup; klass_device->set_quirk_kv = fu_ccgx_hpi_device_set_quirk_kv; klass_usb_device->open = fu_ccgx_hpi_device_open; klass_usb_device->close = fu_ccgx_hpi_device_close; }