fwupd/plugins/ccgx/fu-ccgx-hpi-device.c
Richard Hughes be12af78f0 ccgx: Add extra instance IDs to match specific firmware
The VID:PID of the device in HPI mode is shared between multiple vendors, and
so we need to use both the silicon ID and the application ID to match specific
firmware updates.
2020-03-31 08:59:27 +01:00

1500 lines
43 KiB
C

/*
* Copyright (C) 2020 Cypress Semiconductor Corporation.
* Copyright (C) 2020 Richard Hughes <richard@hughsie.com>
*
* 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;
gboolean enter_alt_mode;
};
G_DEFINE_TYPE (FuCcgxHpiDevice, fu_ccgx_hpi_device, FU_TYPE_USB_DEVICE)
#define HPI_CMD_FLASH_READ_WRITE_DELAY_US 30000
#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_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);
fu_common_string_append_kx (str, idt, "FlashRowSize", self->flash_row_size);
fu_common_string_append_kx (str, idt, "FlashSize", self->flash_size);
}
typedef struct {
guint8 mode;
guint8 *i2c_status;
} FuCcgxHpiDeviceRetryHelper;
static gboolean
fu_ccgx_hpi_device_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_get_i2c_status_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_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) | helper->mode,
0x0,
helper->i2c_status,
CY_I2C_GET_STATUS_LEN,
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;
}
return TRUE;
}
static gboolean
fu_ccgx_hpi_device_get_i2c_status (FuCcgxHpiDevice *self,
guint8 mode,
guint8 *i2c_status, /* out */
GError **error)
{
FuCcgxHpiDeviceRetryHelper helper = {
.mode = mode,
.i2c_status = i2c_status,
};
return fu_device_retry (FU_DEVICE (self),
fu_ccgx_hpi_device_get_i2c_status_cb,
HPI_CMD_RESET_RETRY_CNT,
&helper, error);
}
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;
}
/* write */
if (buf[0] & 0x80) {
g_set_error (error,
FWUPD_ERROR,
FWUPD_ERROR_INTERNAL,
"i2c status error in i2c write [0x%x] event: %s",
(guint8) buf[0], error_local->message);
/* read */
} else {
g_set_error (error,
FWUPD_ERROR,
FWUPD_ERROR_INTERNAL,
"i2c status error in i2c read [0x%x] event: %s",
(guint8) buf[0], error_local->message);
}
return FALSE;
}
return TRUE;
}
static gboolean
fu_ccgx_hpi_device_i2c_read (FuCcgxHpiDevice *self,
guint8 *buf, gsize bufsz,
CyI2CDataConfigBits cfg_bits,
GError **error)
{
guint8 i2c_status = 0x0;
guint8 slave_address = 0;
if (!fu_ccgx_hpi_device_get_i2c_status (self, CY_I2C_MODE_READ, &i2c_status, error)) {
g_prefix_error (error, "i2c read error: ");
return FALSE;
}
if (i2c_status & CY_I2C_ERROR_BIT) {
g_set_error (error,
FWUPD_ERROR,
FWUPD_ERROR_INTERNAL,
"i2c status error in i2c read: 0x%x",
(guint8) i2c_status);
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 i2c_status = 0x0;
guint8 slave_address;
g_autoptr(GError) error_local = NULL;
if (!fu_ccgx_hpi_device_get_i2c_status (self,
CY_I2C_MODE_WRITE,
&i2c_status,
error)) {
g_prefix_error (error, "i2c get status error: ");
return FALSE;
}
if (i2c_status & CY_I2C_ERROR_BIT) {
g_set_error (error,
FWUPD_ERROR,
FWUPD_ERROR_INTERNAL,
"i2c status error in i2c write: 0x%x",
(guint8) i2c_status);
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 i2c_status = 0x0;
guint8 slave_address = 0;
g_autoptr(GError) error_local = NULL;
if (!fu_ccgx_hpi_device_get_i2c_status (self,
CY_I2C_MODE_WRITE,
&i2c_status,
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_local)) {
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 (FuCcgxHpiDevice *self,
guint16 addr,
guint8 *buf,
guint16 bufsz,
GError **error)
{
g_autofree guint8 *bufhw = g_malloc0 (self->hpi_addrsz);
for (guint32 i = 0; i < self->hpi_addrsz; i++)
bufhw[i] = (guint8) (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, buf, bufsz,
CY_I2C_DATA_CONFIG_STOP | CY_I2C_DATA_CONFIG_NAK,
error)) {
g_prefix_error (error, "read error: ");
return FALSE;
}
g_usleep (HPI_CMD_FLASH_READ_WRITE_DELAY_US);
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);
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;
}
g_usleep (HPI_CMD_FLASH_READ_WRITE_DELAY_US);
return TRUE;
}
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_FLASH_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 fw resp code error: 0x%x",
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 mode resp code error: 0x%x", 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 resp code error: 0x%x", 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,
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 resp code error: 0x%x", 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 resp code error: 0x%x", 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_attach (FuDevice *device, GError **error)
{
FuCcgxHpiDevice *self = FU_CCGX_HPI_DEVICE (device);
/* jump to Alt FW */
if (self->fw_image_type == FW_IMAGE_TYPE_DUAL_ASYMMETRIC &&
self->fw_mode == FW_MODE_FW2 &&
self->enter_alt_mode) {
guint8 buf[] = {
CY_PD_JUMP_TO_ALT_FW_CMD_SIG,
};
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;
}
/* just reset device */
} else {
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;
}
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);
}
/* 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 */
self->enter_alt_mode = TRUE;
return fu_device_attach (device, error);
}
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 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_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)) {
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);
/* add extra instance ID */
instance_id = g_strdup_printf ("USB\\VID_%04X&PID_%04X&MODE_%s",
fu_usb_device_get_vid (FU_USB_DEVICE (device)),
fu_usb_device_get_pid (FU_USB_DEVICE (device)),
fu_ccgx_fw_mode_to_string (self->fw_mode));
fu_device_add_instance_id (device, instance_id);
/* 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 version_raw = 0;
guint32 versions[FW_MODE_LAST] = { 0x0 };
guint8 bufver[HPI_DEVICE_VERSION_SIZE_HPIV2] = { 0x0 };
g_autofree gchar *version = NULL;
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;
self->fw_app_type = versions[FW_MODE_FW1] & 0xffff;
/* fw2 */
if (!fu_common_read_uint32_safe (bufver, sizeof(bufver),
0x14, &versions[FW_MODE_FW2],
G_LITTLE_ENDIAN, error))
return FALSE;
/* asymmetric these seem swapped, but we can only update the
* "other" image whilst running in the current image */
if (self->fw_image_type == FW_IMAGE_TYPE_DUAL_SYMMETRIC) {
version_raw = versions[self->fw_mode];
} else if (self->fw_image_type == FW_IMAGE_TYPE_DUAL_ASYMMETRIC) {
version_raw = versions[fu_ccgx_fw_mode_get_alternate (self->fw_mode)];
}
/* set device */
version = fu_ccgx_version_to_string (version_raw);
fu_device_set_version_raw (device, version_raw);
fu_device_set_version (device, version);
}
/* add extra instance IDs */
if (self->silicon_id != 0x0) {
g_autofree gchar *instance_id1 = NULL;
instance_id1 = g_strdup_printf ("USB\\VID_%04X&PID_%04X&SID_%04X",
fu_usb_device_get_vid (FU_USB_DEVICE (device)),
fu_usb_device_get_pid (FU_USB_DEVICE (device)),
self->silicon_id);
fu_device_add_instance_id (device, instance_id1);
}
if (self->silicon_id != 0x0 && self->fw_app_type != 0x0) {
g_autofree gchar *instance_id2 = NULL;
g_autofree gchar *instance_id3 = NULL;
instance_id2 = g_strdup_printf ("USB\\VID_%04X&PID_%04X&SID_%04X&APP_%04X",
fu_usb_device_get_vid (FU_USB_DEVICE (device)),
fu_usb_device_get_pid (FU_USB_DEVICE (device)),
self->silicon_id,
self->fw_app_type);
fu_device_add_instance_id (device, instance_id2);
instance_id3 = g_strdup_printf ("USB\\VID_%04X&PID_%04X&SID_%04X&APP_%04X&MODE_%s",
fu_usb_device_get_vid (FU_USB_DEVICE (device)),
fu_usb_device_get_pid (FU_USB_DEVICE (device)),
self->silicon_id,
self->fw_app_type,
fu_ccgx_fw_mode_to_string (self->fw_mode));
fu_device_add_instance_id (device, instance_id3);
}
/* 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);
/* we can recover the I²C link using reset */
fu_device_retry_add_recovery (FU_DEVICE (self),
G_USB_DEVICE_ERROR,
G_USB_DEVICE_ERROR_IO,
fu_ccgx_hpi_device_reset_cb);
fu_device_retry_add_recovery (FU_DEVICE (self),
G_USB_DEVICE_ERROR,
G_USB_DEVICE_ERROR_TIMED_OUT,
fu_ccgx_hpi_device_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->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;
}