fwupd/plugins/vli/fu-vli-pd-device.c
Richard Hughes 1981c63d58 Remove FuFirmwareImage and just use FuFirmware instead
This allows us to 'nest' firmware formats, and removes a ton of duplication.

The aim here is to deprecate FuFirmwareImage -- it's almost always acting
as a 'child' FuFirmware instance, and even copies most of the vfuncs to allow
custom types. If I'm struggling to work out what should be a FuFirmware and
what should be a FuFirmwareImage then a plugin author has no hope.

For simple payloads we were adding bytes into an image and then the image into
a firmware. This gets really messy when most plugins are treating the FuFirmware
*as* the binary firmware file.

The GBytes saved in the FuFirmware would be considered the payload with the
aim of not using FuFirmwareImage in the single-image case.
2021-03-09 21:14:12 +00:00

713 lines
22 KiB
C

/*
* Copyright (C) 2015 VIA Corporation
* Copyright (C) 2019 Richard Hughes <richard@hughsie.com>
*
* SPDX-License-Identifier: LGPL-2.1+
*/
#include "config.h"
#include "fu-common.h"
#include "fu-firmware.h"
#include "fu-vli-pd-device.h"
#include "fu-vli-pd-firmware.h"
#include "fu-vli-pd-parade-device.h"
struct _FuVliPdDevice
{
FuVliDevice parent_instance;
};
G_DEFINE_TYPE (FuVliPdDevice, fu_vli_pd_device, FU_TYPE_VLI_DEVICE)
static gboolean
fu_vli_pd_device_read_regs (FuVliPdDevice *self, guint16 addr,
guint8 *buf, gsize bufsz, GError **error)
{
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,
0xe0,
((addr & 0xff) << 8) | 0x01,
addr >> 8,
buf, bufsz, NULL,
1000, NULL, error)) {
g_prefix_error (error, "failed to write register @0x%x: ", addr);
return FALSE;
}
if (g_getenv ("FWUPD_VLI_USBHUB_VERBOSE") != NULL) {
g_autofree gchar *title = g_strdup_printf ("ReadRegs@0x%x", addr);
fu_common_dump_raw (G_LOG_DOMAIN, title, buf, bufsz);
}
return TRUE;
}
static gboolean
fu_vli_pd_device_read_reg (FuVliPdDevice *self, guint16 addr, guint8 *value, GError **error)
{
return fu_vli_pd_device_read_regs (self, addr, value, 0x1, error);
}
static gboolean
fu_vli_pd_device_write_reg (FuVliPdDevice *self, guint16 addr, guint8 value, GError **error)
{
if (g_getenv ("FWUPD_VLI_USBHUB_VERBOSE") != NULL) {
g_autofree gchar *title = g_strdup_printf ("WriteReg@0x%x", addr);
fu_common_dump_raw (G_LOG_DOMAIN, title, &value, sizeof(value));
}
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,
0xe0,
((addr & 0xff) << 8) | 0x02,
addr >> 8,
&value, sizeof(value), NULL,
1000, NULL, error)) {
g_prefix_error (error, "failed to write register @0x%x: ", addr);
return FALSE;
}
return TRUE;
}
static gboolean
fu_vli_pd_device_spi_read_status (FuVliDevice *self, guint8 *status, GError **error)
{
guint8 spi_cmd = 0x0;
if (!fu_vli_device_get_spi_cmd (self, FU_VLI_DEVICE_SPI_REQ_READ_STATUS,
&spi_cmd, error))
return FALSE;
return 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,
0xc5, spi_cmd, 0x0000,
status, 0x1, NULL,
FU_VLI_DEVICE_TIMEOUT,
NULL, error);
}
static gboolean
fu_vli_pd_device_spi_read_data (FuVliDevice *self, guint32 addr, guint8 *buf, gsize bufsz, GError **error)
{
guint8 spi_cmd = 0x0;
guint16 value;
guint16 index;
if (!fu_vli_device_get_spi_cmd (self, FU_VLI_DEVICE_SPI_REQ_READ_DATA,
&spi_cmd, error))
return FALSE;
value = ((addr << 8) & 0xff00) | spi_cmd;
index = addr >> 8;
return 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,
0xc4, value, index,
buf, bufsz, NULL,
FU_VLI_DEVICE_TIMEOUT,
NULL, error);
}
static gboolean
fu_vli_pd_device_spi_write_status (FuVliDevice *self, guint8 status, GError **error)
{
guint8 spi_cmd = 0x0;
guint16 value;
if (!fu_vli_device_get_spi_cmd (self, FU_VLI_DEVICE_SPI_REQ_WRITE_STATUS,
&spi_cmd, error))
return FALSE;
value = ((guint16) status << 8) | spi_cmd;
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,
0xd8, value, 0x0,
NULL, 0, NULL,
FU_VLI_DEVICE_TIMEOUT,
NULL, error)) {
return FALSE;
}
/* Fix_For_GD_&_EN_SPI_Flash */
g_usleep (100 * 1000);
return TRUE;
}
static gboolean
fu_vli_pd_device_spi_write_enable (FuVliDevice *self, GError **error)
{
guint8 spi_cmd = 0x0;
if (!fu_vli_device_get_spi_cmd (self, FU_VLI_DEVICE_SPI_REQ_WRITE_EN,
&spi_cmd, error))
return FALSE;
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,
0xd4, spi_cmd, 0x0000,
NULL, 0x0, NULL,
FU_VLI_DEVICE_TIMEOUT,
NULL, error)) {
g_prefix_error (error, "failed to write enable SPI: ");
return FALSE;
}
return TRUE;
}
static gboolean
fu_vli_pd_device_spi_chip_erase (FuVliDevice *self, GError **error)
{
guint8 spi_cmd = 0x0;
if (!fu_vli_device_get_spi_cmd (self, FU_VLI_DEVICE_SPI_REQ_CHIP_ERASE,
&spi_cmd, error))
return FALSE;
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,
0xd1, spi_cmd, 0x0000,
NULL, 0x0, NULL,
FU_VLI_DEVICE_TIMEOUT,
NULL, error)) {
return FALSE;
}
return TRUE;
}
static gboolean
fu_vli_pd_device_spi_sector_erase (FuVliDevice *self, guint32 addr, GError **error)
{
guint8 spi_cmd = 0x0;
guint16 value;
guint16 index;
if (!fu_vli_device_get_spi_cmd (self, FU_VLI_DEVICE_SPI_REQ_SECTOR_ERASE,
&spi_cmd, error))
return FALSE;
value = ((addr << 8) & 0xff00) | spi_cmd;
index = addr >> 8;
return 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,
0xd2, value, index,
NULL, 0x0, NULL,
FU_VLI_DEVICE_TIMEOUT,
NULL, error);
}
static gboolean
fu_vli_pd_device_spi_write_data (FuVliDevice *self,
guint32 addr,
const guint8 *buf,
gsize bufsz,
GError **error)
{
guint8 spi_cmd = 0x0;
guint16 value;
guint16 index;
if (!fu_vli_device_get_spi_cmd (self, FU_VLI_DEVICE_SPI_REQ_PAGE_PROG,
&spi_cmd, error))
return FALSE;
value = ((addr << 8) & 0xff00) | spi_cmd;
index = addr >> 8;
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,
0xdc, value, index,
(guint8 *) buf, bufsz, NULL,
FU_VLI_DEVICE_TIMEOUT,
NULL, error)) {
return FALSE;
}
return TRUE;
}
static gboolean
fu_vli_pd_device_parade_setup (FuVliPdDevice *self, GError **error)
{
g_autoptr(FuDevice) dev = NULL;
g_autoptr(GError) error_local = NULL;
/* add child */
dev = fu_vli_pd_parade_device_new (FU_VLI_DEVICE (self));
if (!fu_device_probe (dev, &error_local)) {
if (g_error_matches (error_local,
FWUPD_ERROR,
FWUPD_ERROR_NOT_FOUND)) {
g_debug ("%s", error_local->message);
} else {
g_warning ("cannot create I²C parade device: %s",
error_local->message);
}
return TRUE;
}
if (!fu_device_setup (dev, error)) {
g_prefix_error (error, "failed to set up parade device: ");
return FALSE;
}
fu_device_add_child (FU_DEVICE (self), dev);
return TRUE;
}
static gboolean
fu_vli_pd_device_setup (FuVliDevice *device, GError **error)
{
FuVliPdDevice *self = FU_VLI_PD_DEVICE (device);
guint32 version_raw;
guint8 verbuf[4] = { 0x0 };
guint8 tmp = 0;
g_autofree gchar *version_str = NULL;
/* get version */
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,
0xe2, 0x0001, 0x0000,
verbuf, sizeof(verbuf), NULL,
1000, NULL, error)) {
g_prefix_error (error, "failed to get version: ");
return FALSE;
}
if (!fu_common_read_uint32_safe (verbuf, sizeof(verbuf), 0x0,
&version_raw, G_BIG_ENDIAN, error))
return FALSE;
fu_device_set_version_raw (FU_DEVICE (self), version_raw);
version_str = fu_common_version_from_uint32 (version_raw, FWUPD_VERSION_FORMAT_QUAD);
fu_device_set_version (FU_DEVICE (self), version_str);
/* get device kind if not already in ROM mode */
if (fu_vli_device_get_kind (device) == FU_VLI_DEVICE_KIND_UNKNOWN) {
if (!fu_vli_pd_device_read_reg (self, 0x0018, &tmp, error))
return FALSE;
switch (tmp & 0xF0) {
case 0x00:
fu_vli_device_set_kind (device, FU_VLI_DEVICE_KIND_VL100);
break;
case 0x10:
/* this is also the code for VL101, but VL102 is more likely */
fu_vli_device_set_kind (device, FU_VLI_DEVICE_KIND_VL102);
break;
case 0x80:
fu_vli_device_set_kind (device, FU_VLI_DEVICE_KIND_VL103);
break;
case 0x90:
fu_vli_device_set_kind (device, FU_VLI_DEVICE_KIND_VL104);
break;
default:
g_set_error (error,
FWUPD_ERROR,
FWUPD_ERROR_INVALID_FILE,
"unable to map 0x0018=0x%02X to device kind",
tmp);
return FALSE;
}
}
/* get bootloader mode */
if (!fu_vli_pd_device_read_reg (self, 0x00F7, &tmp, error))
return FALSE;
if ((tmp & 0x80) == 0x00)
fu_device_add_flag (FU_DEVICE (self), FWUPD_DEVICE_FLAG_IS_BOOTLOADER);
else
fu_device_remove_flag (FU_DEVICE (self), FWUPD_DEVICE_FLAG_IS_BOOTLOADER);
/* detect any I²C child, e.g. parade device */
if (fu_device_has_custom_flag (FU_DEVICE (self), "has-i2c-ps186")) {
if (!fu_vli_pd_device_parade_setup (self, error))
return FALSE;
}
/* success */
return TRUE;
}
static FuFirmware *
fu_vli_pd_device_prepare_firmware (FuDevice *device,
GBytes *fw,
FwupdInstallFlags flags,
GError **error)
{
FuVliPdDevice *self = FU_VLI_PD_DEVICE (device);
FuVliDeviceKind device_kind;
g_autoptr(FuFirmware) firmware = fu_vli_pd_firmware_new ();
/* check size */
if (g_bytes_get_size (fw) > fu_device_get_firmware_size_max (device)) {
g_set_error (error,
FWUPD_ERROR,
FWUPD_ERROR_INVALID_FILE,
"firmware too large, got 0x%x, expected <= 0x%x",
(guint) g_bytes_get_size (fw),
(guint) fu_device_get_firmware_size_max (device));
return NULL;
}
/* check is compatible with firmware */
if (!fu_firmware_parse (firmware, fw, flags, error))
return NULL;
device_kind = fu_vli_pd_firmware_get_kind (FU_VLI_PD_FIRMWARE (firmware));
if (fu_vli_device_get_kind (FU_VLI_DEVICE (self)) != device_kind) {
g_set_error (error,
FWUPD_ERROR,
FWUPD_ERROR_INVALID_FILE,
"firmware incompatible, got %s, expected %s",
fu_vli_common_device_kind_to_string (device_kind),
fu_vli_common_device_kind_to_string (fu_vli_device_get_kind (FU_VLI_DEVICE (self))));
return NULL;
}
/* we could check this against flags */
g_debug ("parsed version: %s", fu_firmware_get_version (firmware));
return g_steal_pointer (&firmware);
}
static GBytes *
fu_vli_pd_device_dump_firmware (FuDevice *device, GError **error)
{
FuVliPdDevice *self = FU_VLI_PD_DEVICE (device);
g_autoptr(FuDeviceLocker) locker = NULL;
/* require detach -> attach */
locker = fu_device_locker_new_full (device,
(FuDeviceLockerFunc) fu_device_detach,
(FuDeviceLockerFunc) fu_device_attach,
error);
if (locker == NULL)
return NULL;
fu_device_set_status (FU_DEVICE (self), FWUPD_STATUS_DEVICE_READ);
return fu_vli_device_spi_read (FU_VLI_DEVICE (self), 0x0,
fu_device_get_firmware_size_max (device),
error);
}
static gboolean
fu_vli_pd_device_write_gpios (FuVliPdDevice *self, GError **error)
{
/* disable UART-Rx mode */
if (!fu_vli_pd_device_write_reg (self, 0x0015, 0x7F, error))
return FALSE;
/* disable 'Watch Mode', chip is not in debug mode */
if (!fu_vli_pd_device_write_reg (self, 0x0019, 0x00, error))
return FALSE;
/* GPIO3 output enable, switch/CMOS/Boost control pin */
if (!fu_vli_pd_device_write_reg (self, 0x001C, 0x02, error))
return FALSE;
return TRUE;
}
static gboolean
fu_vli_pd_device_write_dual_firmware (FuVliPdDevice *self, GBytes *fw, GError **error)
{
const guint8 *buf = NULL;
const guint8 *sbuf = NULL;
gsize bufsz = 0;
gsize sbufsz = 0;
guint16 crc_actual;
guint16 crc_file = 0x0;
guint32 sec_addr = 0x28000;
g_autoptr(GBytes) spi_fw = NULL;
/* check spi fw1 crc16 */
fu_device_set_status (FU_DEVICE (self), FWUPD_STATUS_DEVICE_VERIFY);
spi_fw = fu_vli_device_spi_read (FU_VLI_DEVICE (self),
fu_vli_device_get_offset (FU_VLI_DEVICE (self)),
fu_device_get_firmware_size_max (FU_DEVICE (self)),
error);
if (spi_fw == NULL)
return FALSE;
sbuf = g_bytes_get_data (spi_fw, &sbufsz);
if (sbufsz != 0x8000)
sec_addr = 0x30000;
if (!fu_common_read_uint16_safe (sbuf, sbufsz, sbufsz - 2, &crc_file,
G_LITTLE_ENDIAN, error)) {
g_prefix_error (error, "failed to read file CRC: ");
return FALSE;
}
crc_actual = fu_common_crc16 (sbuf, sbufsz - 2);
fu_device_set_status (FU_DEVICE (self), FWUPD_STATUS_DEVICE_WRITE);
/* update fw2 first if fw1 correct */
buf = g_bytes_get_data (fw, &bufsz);
if (crc_actual == crc_file) {
if (!fu_vli_device_spi_write (FU_VLI_DEVICE (self),
sec_addr,
buf, bufsz, error))
return FALSE;
if (!fu_vli_device_spi_write (FU_VLI_DEVICE (self),
fu_vli_device_get_offset (FU_VLI_DEVICE (self)),
buf, bufsz, error))
return FALSE;
/* else update fw1 first */
} else {
if (!fu_vli_device_spi_write (FU_VLI_DEVICE (self),
fu_vli_device_get_offset (FU_VLI_DEVICE (self)),
buf, bufsz, error))
return FALSE;
if (!fu_vli_device_spi_write (FU_VLI_DEVICE (self),
sec_addr,
buf, bufsz, error))
return FALSE;
}
/* success */
return TRUE;
}
static gboolean
fu_vli_pd_device_write_firmware (FuDevice *device,
FuFirmware *firmware,
FwupdInstallFlags flags,
GError **error)
{
FuVliPdDevice *self = FU_VLI_PD_DEVICE (device);
gsize bufsz = 0;
guint8 tmp = 0;
const guint8 *buf = NULL;
g_autoptr(GBytes) fw = NULL;
/* binary blob */
fw = fu_firmware_get_bytes (firmware, error);
if (fw == NULL)
return FALSE;
/* write GPIOs in new mode */
if (!fu_vli_pd_device_write_gpios (self, error))
return FALSE;
/* disable write protect in GPIO_3 */
if (!fu_vli_pd_device_read_reg (self, 0x0003, &tmp, error))
return FALSE;
if (!fu_vli_pd_device_write_reg (self, 0x0003, tmp | 0x44, error))
return FALSE;
/* dual image on VL103 */
if (fu_vli_device_get_kind (FU_VLI_DEVICE (device)) == FU_VLI_DEVICE_KIND_VL103 &&
fu_device_has_flag (device, FWUPD_DEVICE_FLAG_DUAL_IMAGE))
return fu_vli_pd_device_write_dual_firmware (self, fw, error);
/* erase */
fu_device_set_status (FU_DEVICE (self), FWUPD_STATUS_DEVICE_ERASE);
if (!fu_vli_device_spi_erase_all (FU_VLI_DEVICE (self), error))
return FALSE;
/* write in chunks */
fu_device_set_status (FU_DEVICE (self), FWUPD_STATUS_DEVICE_WRITE);
buf = g_bytes_get_data (fw, &bufsz);
if (!fu_vli_device_spi_write (FU_VLI_DEVICE (self),
fu_vli_device_get_offset (FU_VLI_DEVICE (self)),
buf, bufsz, error))
return FALSE;
/* success */
return TRUE;
}
static gboolean
fu_vli_pd_device_detach (FuDevice *device, GError **error)
{
FuVliPdDevice *self = FU_VLI_PD_DEVICE (device);
g_autoptr(GError) error_local = NULL;
/* sanity check */
if (fu_device_has_flag (device, FWUPD_DEVICE_FLAG_IS_BOOTLOADER)) {
g_debug ("already in bootloader mode, skipping");
return TRUE;
}
/* write GPIOs */
if (!fu_vli_pd_device_write_gpios (self, error))
return FALSE;
/* VL103 set ROM sig does not work, so use alternate function */
if (fu_vli_device_get_kind (FU_VLI_DEVICE (device)) == FU_VLI_DEVICE_KIND_VL103) {
fu_device_set_status (device, FWUPD_STATUS_DEVICE_RESTART);
if (!g_usb_device_control_transfer (fu_usb_device_get_dev (FU_USB_DEVICE (device)),
G_USB_DEVICE_DIRECTION_HOST_TO_DEVICE,
G_USB_DEVICE_REQUEST_TYPE_VENDOR,
G_USB_DEVICE_RECIPIENT_DEVICE,
0xc0, 0x0000, 0x0000,
NULL, 0x0, NULL,
FU_VLI_DEVICE_TIMEOUT,
NULL, &error_local)) {
if (g_error_matches (error_local,
G_USB_DEVICE_ERROR,
G_USB_DEVICE_ERROR_FAILED)) {
g_debug ("ignoring %s", error_local->message);
} else {
g_propagate_prefixed_error (error,
g_steal_pointer (&error_local),
"failed to restart device: ");
return FALSE;
}
}
fu_device_add_flag (device, FWUPD_DEVICE_FLAG_WAIT_FOR_REPLUG);
return TRUE;
}
/* patch APP5 FW bug (2AF2 -> 2AE2) on VL100-App5 and VL102 */
if (fu_vli_device_get_kind (FU_VLI_DEVICE (device)) == FU_VLI_DEVICE_KIND_VL100 ||
fu_vli_device_get_kind (FU_VLI_DEVICE (device)) == FU_VLI_DEVICE_KIND_VL102) {
guint8 tmp = 0;
if (!fu_vli_pd_device_read_reg (self, 0x0018, &tmp, error))
return FALSE;
if (tmp != 0x80) {
if (!fu_vli_pd_device_write_reg (self, 0x2AE2, 0x1E, error))
return FALSE;
if (!fu_vli_pd_device_write_reg (self, 0x2AE3, 0xC3, error))
return FALSE;
if (!fu_vli_pd_device_write_reg (self, 0x2AE4, 0x5A, error))
return FALSE;
if (!fu_vli_pd_device_write_reg (self, 0x2AE5, 0x87, error))
return FALSE;
}
}
/* set ROM sig */
if (!g_usb_device_control_transfer (fu_usb_device_get_dev (FU_USB_DEVICE (device)),
G_USB_DEVICE_DIRECTION_HOST_TO_DEVICE,
G_USB_DEVICE_REQUEST_TYPE_VENDOR,
G_USB_DEVICE_RECIPIENT_DEVICE,
0xa0,
0x0000, 0x0000,
NULL, 0x0, NULL,
FU_VLI_DEVICE_TIMEOUT,
NULL, error))
return FALSE;
/* reset from SPI_Code into ROM_Code */
fu_device_set_status (device, FWUPD_STATUS_DEVICE_RESTART);
if (!g_usb_device_control_transfer (fu_usb_device_get_dev (FU_USB_DEVICE (device)),
G_USB_DEVICE_DIRECTION_HOST_TO_DEVICE,
G_USB_DEVICE_REQUEST_TYPE_VENDOR,
G_USB_DEVICE_RECIPIENT_DEVICE,
0xb0, 0x0000, 0x0000,
NULL, 0x0, NULL,
FU_VLI_DEVICE_TIMEOUT,
NULL, &error_local)) {
if (g_error_matches (error_local,
G_USB_DEVICE_ERROR,
G_USB_DEVICE_ERROR_FAILED)) {
g_debug ("ignoring %s", error_local->message);
} else {
g_propagate_prefixed_error (error,
g_steal_pointer (&error_local),
"failed to restart device: ");
return FALSE;
}
}
fu_device_add_flag (device, FWUPD_DEVICE_FLAG_WAIT_FOR_REPLUG);
return TRUE;
}
static gboolean
fu_vli_pd_device_attach (FuDevice *device, GError **error)
{
FuVliPdDevice *self = FU_VLI_PD_DEVICE (device);
g_autoptr(GError) error_local = NULL;
/* Work around a silicon bug: Once the CC-resistor is removed, the
* CC-host thinks the device is un-plugged and turn off VBUS (power).
* When VL103 is powered-off, VL103 puts a resistor at CC-pin.
* The CC-host will think the device is re-plugged and provides VBUS
* again. Then, VL103 will be powered on and runs new FW. */
if (fu_vli_device_get_kind (FU_VLI_DEVICE (device)) == FU_VLI_DEVICE_KIND_VL103) {
if (!fu_vli_pd_device_write_reg (self, 0x1201, 0xf6, error))
return FALSE;
if (!fu_vli_pd_device_write_reg (self, 0x1001, 0xf6, error))
return FALSE;
fu_device_set_status (device, FWUPD_STATUS_DEVICE_RESTART);
fu_device_add_flag (device, FWUPD_DEVICE_FLAG_WAIT_FOR_REPLUG);
return TRUE;
}
/* sanity check */
if (!fu_device_has_flag (device, FWUPD_DEVICE_FLAG_IS_BOOTLOADER)) {
g_debug ("already in runtime mode, skipping");
return TRUE;
}
/* chip reset command works only for non-VL103 */
if (!g_usb_device_control_transfer (fu_usb_device_get_dev (FU_USB_DEVICE (device)),
G_USB_DEVICE_DIRECTION_HOST_TO_DEVICE,
G_USB_DEVICE_REQUEST_TYPE_VENDOR,
G_USB_DEVICE_RECIPIENT_DEVICE,
0xb0,
0x0000, 0x0000,
NULL, 0x0, NULL,
FU_VLI_DEVICE_TIMEOUT,
NULL, &error_local)) {
if (g_error_matches (error_local,
G_USB_DEVICE_ERROR,
G_USB_DEVICE_ERROR_NO_DEVICE) ||
g_error_matches (error_local,
G_USB_DEVICE_ERROR,
G_USB_DEVICE_ERROR_TIMED_OUT) ||
g_error_matches (error_local,
G_USB_DEVICE_ERROR,
G_USB_DEVICE_ERROR_FAILED)) {
g_debug ("ignoring %s", error_local->message);
} else {
g_propagate_prefixed_error (error,
g_steal_pointer (&error_local),
"failed to restart device: ");
return FALSE;
}
}
/* replug */
fu_device_set_status (device, FWUPD_STATUS_DEVICE_RESTART);
fu_device_add_flag (device, FWUPD_DEVICE_FLAG_WAIT_FOR_REPLUG);
return TRUE;
}
static void
fu_vli_pd_device_kind_changed_cb (FuVliDevice *device, GParamSpec *pspec, gpointer user_data)
{
if (fu_vli_device_get_kind (device) == FU_VLI_DEVICE_KIND_VL103) {
/* wait for USB-C timeout */
fu_device_set_remove_delay (FU_DEVICE (device), 10000);
}
}
static void
fu_vli_pd_device_init (FuVliPdDevice *self)
{
fu_device_add_icon (FU_DEVICE (self), "audio-card");
fu_device_add_protocol (FU_DEVICE (self), "com.vli.pd");
fu_device_set_summary (FU_DEVICE (self), "USB PD");
fu_device_add_flag (FU_DEVICE (self), FWUPD_DEVICE_FLAG_UPDATABLE);
fu_device_add_flag (FU_DEVICE (self), FWUPD_DEVICE_FLAG_CAN_VERIFY_IMAGE);
fu_device_set_remove_delay (FU_DEVICE (self), FU_DEVICE_REMOVE_DELAY_RE_ENUMERATE);
fu_device_set_version_format (FU_DEVICE (self), FWUPD_VERSION_FORMAT_QUAD);
fu_vli_device_set_spi_auto_detect (FU_VLI_DEVICE (self), FALSE);
/* connect up attach or detach vfuncs when kind is known */
g_signal_connect (self, "notify::kind",
G_CALLBACK (fu_vli_pd_device_kind_changed_cb), NULL);
}
static void
fu_vli_pd_device_class_init (FuVliPdDeviceClass *klass)
{
FuDeviceClass *klass_device = FU_DEVICE_CLASS (klass);
FuVliDeviceClass *klass_vli_device = FU_VLI_DEVICE_CLASS (klass);
klass_device->dump_firmware = fu_vli_pd_device_dump_firmware;
klass_device->write_firmware = fu_vli_pd_device_write_firmware;
klass_device->prepare_firmware = fu_vli_pd_device_prepare_firmware;
klass_device->attach = fu_vli_pd_device_attach;
klass_device->detach = fu_vli_pd_device_detach;
klass_vli_device->setup = fu_vli_pd_device_setup;
klass_vli_device->spi_chip_erase = fu_vli_pd_device_spi_chip_erase;
klass_vli_device->spi_sector_erase = fu_vli_pd_device_spi_sector_erase;
klass_vli_device->spi_read_data = fu_vli_pd_device_spi_read_data;
klass_vli_device->spi_read_status = fu_vli_pd_device_spi_read_status;
klass_vli_device->spi_write_data = fu_vli_pd_device_spi_write_data;
klass_vli_device->spi_write_enable = fu_vli_pd_device_spi_write_enable;
klass_vli_device->spi_write_status = fu_vli_pd_device_spi_write_status;
}