mirror of
https://git.proxmox.com/git/fwupd
synced 2025-05-16 17:43:55 +00:00
852 lines
24 KiB
C
852 lines
24 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-ccgx-common.h"
|
|
#include "fu-ccgx-hpi-common.h"
|
|
#include "fu-ccgx-hpi-device.h"
|
|
#include "fu-ccgx-cyacd-firmware.h"
|
|
#include "fu-ccgx-cyacd-firmware-image.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;
|
|
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_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)
|
|
{
|
|
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_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);
|
|
}
|
|
|
|
static gboolean
|
|
fu_ccgx_hpi_device_get_i2c_status (FuCcgxHpiDevice *self,
|
|
guint8 mode,
|
|
guint8 *i2c_status, /* out */
|
|
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_STATUS_CMD,
|
|
(((guint16) self->scb_index) << CY_SCB_INDEX_POS) | mode,
|
|
0x0,
|
|
(guint8 *) &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_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;
|
|
}
|
|
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;
|
|
}
|
|
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_reg_read (FuCcgxHpiDevice *self,
|
|
guint16 addr,
|
|
guint8 *buf,
|
|
guint16 bufsz,
|
|
GError **error)
|
|
{
|
|
g_autofree guint8 *bufhw = g_malloc0 (self->hpi_addrsz + 1);
|
|
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;
|
|
}
|
|
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)
|
|
{
|
|
g_set_error_literal (error,
|
|
FWUPD_ERROR,
|
|
FWUPD_ERROR_NOT_SUPPORTED,
|
|
"not supported");
|
|
return FALSE;
|
|
}
|
|
|
|
static FuFirmware *
|
|
fu_ccgx_hpi_device_prepare_firmware (FuDevice *device,
|
|
GBytes *fw,
|
|
FwupdInstallFlags flags,
|
|
GError **error)
|
|
{
|
|
FuCcgxHpiDevice *self = FU_CCGX_HPI_DEVICE (device);
|
|
g_autoptr(FuFirmware) firmware = fu_ccgx_cyacd_firmware_new ();
|
|
g_autoptr(GPtrArray) images = NULL;
|
|
|
|
/* parse all images */
|
|
if (!fu_firmware_parse (firmware, fw, flags, error))
|
|
return NULL;
|
|
|
|
/* check the silicon ID of all images */
|
|
images = fu_firmware_get_images (firmware);
|
|
for (guint i = 0; i < images->len; i++) {
|
|
FuFirmwareImage *img = g_ptr_array_index (images, i);
|
|
guint16 fw_app_type = fu_ccgx_cyacd_firmware_image_get_app_type (FU_CCGX_CYACD_FIRMWARE_IMAGE (img));
|
|
if (fu_firmware_image_get_addr (img) != self->silicon_id) {
|
|
g_set_error (error,
|
|
FWUPD_ERROR,
|
|
FWUPD_ERROR_NOT_SUPPORTED,
|
|
"silicon id mismatch on image %u, "
|
|
"expected 0x%x, got 0x%x",
|
|
i, self->silicon_id,
|
|
(guint) fu_firmware_image_get_addr (img));
|
|
return NULL;
|
|
}
|
|
if (fw_app_type != self->fw_app_type) {
|
|
g_set_error (error,
|
|
FWUPD_ERROR,
|
|
FWUPD_ERROR_NOT_SUPPORTED,
|
|
"app type mismatch on image %u, "
|
|
"expected 0x%x, got 0x%x",
|
|
i, self->fw_app_type, fw_app_type);
|
|
return NULL;
|
|
}
|
|
}
|
|
|
|
return g_steal_pointer (&firmware);
|
|
}
|
|
|
|
static gboolean
|
|
fu_ccgx_hpi_write_firmware (FuDevice *device,
|
|
FuFirmware *firmware,
|
|
FwupdInstallFlags flags,
|
|
GError **error)
|
|
{
|
|
g_set_error_literal (error,
|
|
FWUPD_ERROR,
|
|
FWUPD_ERROR_NOT_SUPPORTED,
|
|
"not supported");
|
|
return FALSE;
|
|
}
|
|
|
|
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) {
|
|
g_set_error (error,
|
|
FWUPD_ERROR,
|
|
FWUPD_ERROR_NOT_SUPPORTED,
|
|
"Invalid row size for Instance ID: %s",
|
|
instance_id);
|
|
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 ver_fw1 = 0;
|
|
guint32 ver_fw2 = 0;
|
|
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;
|
|
}
|
|
if (!fu_common_read_uint32_safe (bufver, sizeof(bufver),
|
|
0x0c, &ver_fw1,
|
|
G_LITTLE_ENDIAN, error))
|
|
return FALSE;
|
|
self->fw_app_type = ver_fw1 & 0xffff;
|
|
if (!fu_common_read_uint32_safe (bufver, sizeof(bufver),
|
|
0x14, &ver_fw2,
|
|
G_LITTLE_ENDIAN, error))
|
|
return FALSE;
|
|
/* these seem swapped, but we can only update the "other" image
|
|
* whilst running in the current image */
|
|
if (self->fw_mode == FW_MODE_FW2) {
|
|
g_autofree gchar *version = fu_ccgx_version_to_string (ver_fw1);
|
|
fu_device_set_version_raw (device, ver_fw1);
|
|
fu_device_set_version (device, version);
|
|
} else if (self->fw_mode == FW_MODE_FW1) {
|
|
g_autofree gchar *version = fu_ccgx_version_to_string (ver_fw2);
|
|
fu_device_set_version_raw (device, ver_fw2);
|
|
fu_device_set_version (device, version);
|
|
}
|
|
}
|
|
|
|
/* 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);
|
|
}
|
|
|
|
/* success */
|
|
return TRUE;
|
|
}
|
|
|
|
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;
|
|
}
|
|
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_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);
|
|
|
|
/* 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;
|
|
}
|