mirror of
https://git.proxmox.com/git/fwupd
synced 2025-06-04 16:05:11 +00:00

Asking the user for the UID mapping isn't working very well, as it requires lots of manual handholding. It also doesn't work very well when the device vendor does not actually have a PCI ID or if the vendor has split into two entities. Just use the OUI address as an additional VendorID and match any of the device IDs against any of the metadata-supplied values.
617 lines
19 KiB
C
617 lines
19 KiB
C
/*
|
|
* Copyright (C) 2020 Richard Hughes <richard@hughsie.com>
|
|
*
|
|
* SPDX-License-Identifier: LGPL-2.1+
|
|
*/
|
|
|
|
#include <linux/i2c-dev.h>
|
|
|
|
#include "config.h"
|
|
|
|
#include "fu-elantp-common.h"
|
|
#include "fu-elantp-firmware.h"
|
|
#include "fu-elantp-i2c-device.h"
|
|
#include "fu-chunk.h"
|
|
|
|
struct _FuElantpI2cDevice {
|
|
FuUdevDevice parent_instance;
|
|
guint16 i2c_addr;
|
|
guint16 ic_page_count;
|
|
guint16 iap_type;
|
|
guint16 iap_ctrl;
|
|
guint16 iap_password;
|
|
guint16 module_id;
|
|
guint16 fw_page_size;
|
|
guint8 pattern;
|
|
};
|
|
|
|
G_DEFINE_TYPE (FuElantpI2cDevice, fu_elantp_i2c_device, FU_TYPE_UDEV_DEVICE)
|
|
|
|
static void
|
|
fu_elantp_i2c_device_to_string (FuDevice *device, guint idt, GString *str)
|
|
{
|
|
FuElantpI2cDevice *self = FU_ELANTP_I2C_DEVICE (device);
|
|
fu_common_string_append_kx (str, idt, "I2cAddr", self->i2c_addr);
|
|
fu_common_string_append_kx (str, idt, "ModuleId", self->module_id);
|
|
fu_common_string_append_kx (str, idt, "Pattern", self->pattern);
|
|
fu_common_string_append_kx (str, idt, "FwPageSize", self->fw_page_size);
|
|
fu_common_string_append_kx (str, idt, "IcPageCount", self->ic_page_count);
|
|
fu_common_string_append_kx (str, idt, "IapType", self->iap_type);
|
|
fu_common_string_append_kx (str, idt, "IapCtrl", self->iap_ctrl);
|
|
}
|
|
|
|
static gboolean
|
|
fu_elantp_i2c_device_probe (FuUdevDevice *device, GError **error)
|
|
{
|
|
/* check is valid */
|
|
if (g_strcmp0 (fu_udev_device_get_subsystem (device), "i2c-dev") != 0) {
|
|
g_set_error (error,
|
|
FWUPD_ERROR,
|
|
FWUPD_ERROR_NOT_SUPPORTED,
|
|
"is not correct subsystem=%s, expected i2c-dev",
|
|
fu_udev_device_get_subsystem (device));
|
|
return FALSE;
|
|
}
|
|
if (fu_udev_device_get_device_file (device) == NULL) {
|
|
g_set_error_literal (error,
|
|
FWUPD_ERROR,
|
|
FWUPD_ERROR_NOT_SUPPORTED,
|
|
"no device file");
|
|
return FALSE;
|
|
}
|
|
|
|
/* set the physical ID */
|
|
return fu_udev_device_set_physical_id (device, "i2c", error);
|
|
}
|
|
|
|
static gboolean
|
|
fu_elantp_i2c_device_send_cmd (FuElantpI2cDevice *self,
|
|
guint8 *tx, gssize txsz,
|
|
guint8 *rx, gssize rxsz,
|
|
GError **error)
|
|
{
|
|
if (g_getenv ("FWUPD_ELANTP_VERBOSE") != NULL)
|
|
fu_common_dump_raw (G_LOG_DOMAIN, "Write", tx, txsz);
|
|
if (!fu_udev_device_pwrite_full (FU_UDEV_DEVICE (self), 0, tx, txsz, error))
|
|
return FALSE;
|
|
if (rxsz == 0)
|
|
return TRUE;
|
|
if (!fu_udev_device_pread_full (FU_UDEV_DEVICE (self), 0, rx, rxsz, error))
|
|
return FALSE;
|
|
if (g_getenv ("FWUPD_ELANTP_VERBOSE") != NULL)
|
|
fu_common_dump_raw (G_LOG_DOMAIN, "Read", rx, rxsz);
|
|
return TRUE;
|
|
}
|
|
|
|
static gboolean
|
|
fu_elantp_i2c_device_write_cmd (FuElantpI2cDevice *self, guint16 reg, guint16 cmd, GError **error)
|
|
{
|
|
guint8 buf[4];
|
|
fu_common_write_uint16 (buf + 0x0, reg, G_LITTLE_ENDIAN);
|
|
fu_common_write_uint16 (buf + 0x2, cmd, G_LITTLE_ENDIAN);
|
|
return fu_elantp_i2c_device_send_cmd (self, buf, sizeof(buf), NULL, 0, error);
|
|
}
|
|
|
|
static gboolean
|
|
fu_elantp_i2c_device_read_cmd (FuElantpI2cDevice *self, guint16 reg,
|
|
guint8 *rx, gsize rxsz, GError **error)
|
|
{
|
|
guint8 buf[2];
|
|
fu_common_write_uint16 (buf + 0x0, reg, G_LITTLE_ENDIAN);
|
|
return fu_elantp_i2c_device_send_cmd (self, buf, sizeof(buf),
|
|
rx, rxsz, error);
|
|
}
|
|
|
|
static gboolean
|
|
fu_elantp_i2c_device_ensure_iap_ctrl (FuElantpI2cDevice *self, GError **error)
|
|
{
|
|
guint8 buf[2] = { 0x0 };
|
|
if (!fu_elantp_i2c_device_read_cmd (self, ETP_CMD_I2C_IAP_CTRL, buf, sizeof(buf), error)) {
|
|
g_prefix_error (error, "failed to read IAPControl: ");
|
|
return FALSE;
|
|
}
|
|
self->iap_ctrl = fu_common_read_uint16 (buf, G_LITTLE_ENDIAN);
|
|
|
|
/* in bootloader mode? */
|
|
if ((self->iap_ctrl & ETP_I2C_MAIN_MODE_ON) == 0)
|
|
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);
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
static gboolean
|
|
fu_elantp_i2c_device_setup (FuDevice *device, GError **error)
|
|
{
|
|
FuElantpI2cDevice *self = FU_ELANTP_I2C_DEVICE (device);
|
|
guint16 fwver;
|
|
guint16 iap_ver;
|
|
guint16 tmp;
|
|
guint32 pid;
|
|
guint32 vid;
|
|
guint8 buf[30] = { 0x0 };
|
|
guint8 ic_type;
|
|
g_autofree gchar *instance_id1 = NULL;
|
|
g_autofree gchar *instance_id2 = NULL;
|
|
g_autofree gchar *instance_id_ic_type = NULL;
|
|
g_autofree gchar *version_bl = NULL;
|
|
g_autofree gchar *version = NULL;
|
|
|
|
/* read the I2C descriptor */
|
|
if (!fu_elantp_i2c_device_read_cmd (self, ETP_CMD_GET_HID_DESCRIPTOR,
|
|
buf, sizeof(buf), error)) {
|
|
g_prefix_error (error, "failed to get HID descriptor: ");
|
|
return FALSE;
|
|
}
|
|
vid = fu_common_read_uint16 (buf + 20, G_LITTLE_ENDIAN);
|
|
pid = fu_common_read_uint16 (buf + 22, G_LITTLE_ENDIAN);
|
|
|
|
/* set the vendor ID */
|
|
if (vid != 0x0000) {
|
|
g_autofree gchar *vendor_id = NULL;
|
|
vendor_id = g_strdup_printf ("HIDRAW:0x%04X", vid);
|
|
fu_device_add_vendor_id (device, vendor_id);
|
|
}
|
|
|
|
/* add GUIDs in order of priority */
|
|
if (vid != 0x0 && pid != 0x0) {
|
|
g_autofree gchar *devid = NULL;
|
|
devid = g_strdup_printf ("HIDRAW\\VID_%04X&PID_%04X",
|
|
vid, pid);
|
|
fu_device_add_instance_id (device, devid);
|
|
}
|
|
|
|
/* get pattern */
|
|
if (!fu_elantp_i2c_device_read_cmd (self,
|
|
ETP_CMD_I2C_GET_HID_ID,
|
|
buf, sizeof(buf), error)) {
|
|
g_prefix_error (error, "failed to read I2C ID: ");
|
|
return FALSE;
|
|
}
|
|
tmp = fu_common_read_uint16 (buf, G_LITTLE_ENDIAN);
|
|
self->pattern = tmp != 0xffff ? (tmp & 0xff00) >> 8 : 0;
|
|
|
|
/* get current firmware version */
|
|
if (!fu_elantp_i2c_device_read_cmd (self,
|
|
ETP_CMD_I2C_FW_VERSION,
|
|
buf, sizeof(buf), error)) {
|
|
g_prefix_error (error, "failed to read fw version: ");
|
|
return FALSE;
|
|
}
|
|
fwver = fu_common_read_uint16 (buf, G_LITTLE_ENDIAN);
|
|
if (fwver == 0xFFFF || fwver == ETP_CMD_I2C_FW_VERSION)
|
|
fwver = 0;
|
|
version = fu_common_version_from_uint16 (fwver, FWUPD_VERSION_FORMAT_HEX);
|
|
fu_device_set_version (device, version);
|
|
|
|
/* get IAP firmware version */
|
|
if (!fu_elantp_i2c_device_read_cmd (self,
|
|
self->pattern == 0 ? ETP_CMD_I2C_IAP_VERSION : ETP_CMD_I2C_IAP_VERSION_2,
|
|
buf, sizeof(buf), error)) {
|
|
g_prefix_error (error, "failed to read bootloader version: ");
|
|
return FALSE;
|
|
}
|
|
if (self->pattern >= 1) {
|
|
iap_ver = buf[1];
|
|
} else {
|
|
iap_ver = fu_common_read_uint16 (buf, G_LITTLE_ENDIAN);
|
|
}
|
|
version_bl = fu_common_version_from_uint16 (iap_ver, FWUPD_VERSION_FORMAT_HEX);
|
|
fu_device_set_version_bootloader (device, version_bl);
|
|
|
|
/* get module ID */
|
|
if (!fu_elantp_i2c_device_read_cmd (self,
|
|
ETP_CMD_GET_MODULE_ID,
|
|
buf, sizeof(buf), error)) {
|
|
g_prefix_error (error, "failed to read module ID: ");
|
|
return FALSE;
|
|
}
|
|
self->module_id = fu_common_read_uint16 (buf, G_LITTLE_ENDIAN);
|
|
|
|
/* define the extra instance IDs */
|
|
instance_id1 = g_strdup_printf ("HIDRAW\\VEN_%04X&DEV_%04X&MOD_%04X",
|
|
vid, pid, self->module_id);
|
|
fu_device_add_instance_id (device, instance_id1);
|
|
|
|
/* get OSM version */
|
|
if (!fu_elantp_i2c_device_read_cmd (self, ETP_CMD_I2C_OSM_VERSION, buf, sizeof(buf), error)) {
|
|
g_prefix_error (error, "failed to read OSM version: ");
|
|
return FALSE;
|
|
}
|
|
tmp = fu_common_read_uint16 (buf, G_LITTLE_ENDIAN);
|
|
if (tmp == ETP_CMD_I2C_OSM_VERSION || tmp == 0xFFFF) {
|
|
if (!fu_elantp_i2c_device_read_cmd (self, ETP_CMD_I2C_IAP_ICBODY, buf, sizeof(buf), error)) {
|
|
g_prefix_error (error, "failed to read IC body: ");
|
|
return FALSE;
|
|
}
|
|
ic_type = fu_common_read_uint16 (buf, G_LITTLE_ENDIAN) & 0xFF;
|
|
} else {
|
|
ic_type = (tmp >> 8) & 0xFF;
|
|
}
|
|
instance_id_ic_type = g_strdup_printf ("ELANTP\\ICTYPE_%02X", ic_type);
|
|
fu_device_add_instance_id (device, instance_id_ic_type);
|
|
|
|
/* define the extra instance IDs (ic_type + module_id) */
|
|
instance_id2 = g_strdup_printf ("ELANTP\\ICTYPE_%02X&MOD_%04X",
|
|
ic_type, self->module_id);
|
|
fu_device_add_instance_id (device, instance_id2);
|
|
|
|
/* no quirk entry */
|
|
if (self->ic_page_count == 0x0) {
|
|
g_set_error (error,
|
|
FWUPD_ERROR,
|
|
FWUPD_ERROR_NOT_SUPPORTED,
|
|
"no page count for ELANTP\\ICTYPE_%02X",
|
|
ic_type);
|
|
return FALSE;
|
|
}
|
|
fu_device_set_firmware_size (device, (guint64) self->ic_page_count * (guint64) 64);
|
|
|
|
/* is in bootloader mode */
|
|
if (!fu_elantp_i2c_device_ensure_iap_ctrl (self, error))
|
|
return FALSE;
|
|
|
|
/* success */
|
|
return TRUE;
|
|
}
|
|
|
|
static gboolean
|
|
fu_elantp_i2c_device_open (FuUdevDevice *device, GError **error)
|
|
{
|
|
FuElantpI2cDevice *self = FU_ELANTP_I2C_DEVICE (device);
|
|
gint addr = self->i2c_addr;
|
|
guint8 tx_buf[] = { 0x02, 0x01 };
|
|
|
|
/* set target address */
|
|
if (!fu_udev_device_ioctl (device, I2C_SLAVE,
|
|
GINT_TO_POINTER (addr), NULL, NULL)) {
|
|
if (!fu_udev_device_ioctl (device, I2C_SLAVE_FORCE,
|
|
GINT_TO_POINTER (addr), NULL, error)) {
|
|
g_prefix_error (error,
|
|
"failed to set target address to 0x%x: ",
|
|
self->i2c_addr);
|
|
return FALSE;
|
|
}
|
|
}
|
|
|
|
/* read i2c device */
|
|
return fu_udev_device_pwrite_full (device, 0x0, tx_buf, sizeof(tx_buf), error);
|
|
}
|
|
|
|
static FuFirmware *
|
|
fu_elantp_i2c_device_prepare_firmware (FuDevice *device,
|
|
GBytes *fw,
|
|
FwupdInstallFlags flags,
|
|
GError **error)
|
|
{
|
|
FuElantpI2cDevice *self = FU_ELANTP_I2C_DEVICE (device);
|
|
guint16 module_id;
|
|
g_autoptr(FuFirmware) firmware = fu_elantp_firmware_new ();
|
|
|
|
/* check is compatible with hardware */
|
|
if (!fu_firmware_parse (firmware, fw, flags, error))
|
|
return NULL;
|
|
module_id = fu_elantp_firmware_get_module_id (FU_ELANTP_FIRMWARE (firmware));
|
|
if (self->module_id != module_id) {
|
|
g_set_error (error,
|
|
FWUPD_ERROR,
|
|
FWUPD_ERROR_INVALID_FILE,
|
|
"firmware incompatible, got 0x%04x, expected 0x%04x",
|
|
module_id, self->module_id);
|
|
return NULL;
|
|
}
|
|
|
|
/* success */
|
|
return g_steal_pointer (&firmware);
|
|
}
|
|
|
|
static gboolean
|
|
fu_elantp_i2c_device_write_firmware (FuDevice *device,
|
|
FuFirmware *firmware,
|
|
FwupdInstallFlags flags,
|
|
GError **error)
|
|
{
|
|
FuElantpI2cDevice *self = FU_ELANTP_I2C_DEVICE (device);
|
|
FuElantpFirmware *firmware_elantp = FU_ELANTP_FIRMWARE (firmware);
|
|
gsize bufsz = 0;
|
|
guint16 checksum = 0;
|
|
guint16 checksum_device = 0;
|
|
guint16 iap_addr;
|
|
const guint8 *buf;
|
|
guint8 csum_buf[2] = { 0x0 };
|
|
g_autoptr(GBytes) fw = NULL;
|
|
g_autoptr(GPtrArray) chunks = NULL;
|
|
|
|
/* simple image */
|
|
fw = fu_firmware_get_image_default_bytes (firmware, error);
|
|
if (fw == NULL)
|
|
return FALSE;
|
|
|
|
/* write each block */
|
|
fu_device_set_status (device, FWUPD_STATUS_DEVICE_WRITE);
|
|
buf = g_bytes_get_data (fw, &bufsz);
|
|
iap_addr = fu_elantp_firmware_get_iap_addr (firmware_elantp);
|
|
chunks = fu_chunk_array_new (buf + iap_addr, bufsz - iap_addr, 0x0, 0x0, self->fw_page_size);
|
|
for (guint i = 0; i < chunks->len; i++) {
|
|
FuChunk *chk = g_ptr_array_index (chunks, i);
|
|
guint16 csum_tmp = fu_elantp_calc_checksum (chk->data, chk->data_sz);
|
|
gsize blksz = self->fw_page_size + 4;
|
|
g_autofree guint8 *blk = g_malloc0 (blksz);
|
|
|
|
/* write block */
|
|
blk[0] = ETP_I2C_IAP_REG_L;
|
|
blk[1] = ETP_I2C_IAP_REG_H;
|
|
if (!fu_memcpy_safe (blk, blksz, 0x2, /* dst */
|
|
chk->data, chk->data_sz, 0x0, /* src */
|
|
chk->data_sz, error))
|
|
return FALSE;
|
|
|
|
fu_common_write_uint16 (blk + chk->data_sz + 2, csum_tmp, G_LITTLE_ENDIAN);
|
|
|
|
if (!fu_elantp_i2c_device_send_cmd (self, blk, blksz, NULL, 0, error))
|
|
return FALSE;
|
|
g_usleep (self->fw_page_size == 512 ?
|
|
ELANTP_DELAY_WRITE_BLOCK_512 * 1000 :
|
|
ELANTP_DELAY_WRITE_BLOCK * 1000);
|
|
|
|
if (!fu_elantp_i2c_device_ensure_iap_ctrl (self, error))
|
|
return FALSE;
|
|
if (self->iap_ctrl & (ETP_FW_IAP_PAGE_ERR | ETP_FW_IAP_INTF_ERR)) {
|
|
g_set_error (error,
|
|
FWUPD_ERROR,
|
|
FWUPD_ERROR_WRITE,
|
|
"bootloader reports failed write: 0x%x",
|
|
self->iap_ctrl);
|
|
return FALSE;
|
|
}
|
|
|
|
/* update progress */
|
|
checksum += csum_tmp;
|
|
fu_device_set_progress_full (device, (gsize) i, (gsize) chunks->len);
|
|
}
|
|
|
|
/* verify the written checksum */
|
|
fu_device_set_status (device, FWUPD_STATUS_DEVICE_VERIFY);
|
|
if (!fu_elantp_i2c_device_read_cmd (self, ETP_CMD_I2C_IAP_CHECKSUM,
|
|
csum_buf, sizeof(csum_buf), error))
|
|
return FALSE;
|
|
checksum_device = fu_common_read_uint16 (csum_buf, G_LITTLE_ENDIAN);
|
|
if (checksum != checksum_device) {
|
|
g_set_error (error,
|
|
FWUPD_ERROR,
|
|
FWUPD_ERROR_WRITE,
|
|
"checksum failed 0x%04x != 0x%04x",
|
|
checksum, checksum_device);
|
|
return FALSE;
|
|
}
|
|
|
|
/* wait for a reset */
|
|
fu_device_set_progress (device, 0);
|
|
fu_device_set_status (device, FWUPD_STATUS_DEVICE_RESTART);
|
|
g_usleep (ELANTP_DELAY_COMPLETE * 1000);
|
|
return TRUE;
|
|
}
|
|
|
|
static gboolean
|
|
fu_elantp_i2c_device_detach (FuDevice *device, GError **error)
|
|
{
|
|
guint16 iap_ver;
|
|
guint16 ic_type;
|
|
guint8 buf[2] = { 0x0 };
|
|
guint16 tmp;
|
|
FuElantpI2cDevice *self = FU_ELANTP_I2C_DEVICE (device);
|
|
|
|
/* sanity check */
|
|
if (fu_device_has_flag (device, FWUPD_DEVICE_FLAG_IS_BOOTLOADER)) {
|
|
g_debug ("in bootloader mode, reset IC");
|
|
fu_device_set_status (device, FWUPD_STATUS_DEVICE_RESTART);
|
|
if (!fu_elantp_i2c_device_write_cmd (self,
|
|
ETP_CMD_I2C_IAP_RESET,
|
|
ETP_I2C_IAP_RESET,
|
|
error))
|
|
return FALSE;
|
|
g_usleep (ELANTP_DELAY_RESET * 1000);
|
|
}
|
|
/* get OSM version */
|
|
if (!fu_elantp_i2c_device_read_cmd (self, ETP_CMD_I2C_OSM_VERSION, buf, sizeof(buf), error)) {
|
|
g_prefix_error (error, "failed to read OSM version: ");
|
|
return FALSE;
|
|
}
|
|
tmp = fu_common_read_uint16 (buf, G_LITTLE_ENDIAN);
|
|
if (tmp == ETP_CMD_I2C_OSM_VERSION || tmp == 0xFFFF) {
|
|
if (!fu_elantp_i2c_device_read_cmd (self, ETP_CMD_I2C_IAP_ICBODY, buf, sizeof(buf), error)) {
|
|
g_prefix_error (error, "failed to read IC body: ");
|
|
return FALSE;
|
|
}
|
|
ic_type = fu_common_read_uint16 (buf, G_LITTLE_ENDIAN) & 0xFF;
|
|
} else {
|
|
ic_type = (tmp >> 8) & 0xFF;
|
|
}
|
|
|
|
/* get IAP firmware version */
|
|
if (!fu_elantp_i2c_device_read_cmd (self,
|
|
self->pattern == 0 ? ETP_CMD_I2C_IAP_VERSION : ETP_CMD_I2C_IAP_VERSION_2,
|
|
buf, sizeof(buf), error)) {
|
|
g_prefix_error (error, "failed to read bootloader version: ");
|
|
return FALSE;
|
|
}
|
|
if (self->pattern >= 1) {
|
|
iap_ver = buf[1];
|
|
} else {
|
|
iap_ver = fu_common_read_uint16 (buf, G_LITTLE_ENDIAN);
|
|
}
|
|
|
|
/* set the page size */
|
|
self->fw_page_size = 64;
|
|
if (ic_type >= 0x10) {
|
|
if (iap_ver >= 1) {
|
|
if (iap_ver >= 2 && (ic_type == 0x14 || ic_type==0x15)) {
|
|
self->fw_page_size = 512;
|
|
} else {
|
|
self->fw_page_size = 128;
|
|
}
|
|
/* set the IAP type, presumably some kind of ABI */
|
|
if (!fu_elantp_i2c_device_write_cmd (self,
|
|
ETP_CMD_I2C_IAP_TYPE,
|
|
self->fw_page_size / 2,
|
|
error))
|
|
return FALSE;
|
|
if (!fu_elantp_i2c_device_read_cmd (self, ETP_CMD_I2C_IAP_TYPE ,
|
|
buf, sizeof(buf), error)) {
|
|
g_prefix_error (error, "failed to read IAP type: ");
|
|
return FALSE;
|
|
}
|
|
self->iap_type = fu_common_read_uint16 (buf, G_LITTLE_ENDIAN);
|
|
if (self->iap_type != self->fw_page_size / 2) {
|
|
g_set_error_literal (error,
|
|
FWUPD_ERROR,
|
|
FWUPD_ERROR_NOT_SUPPORTED,
|
|
"failed to set IAP type");
|
|
return FALSE;
|
|
}
|
|
}
|
|
}
|
|
if (!fu_elantp_i2c_device_write_cmd (self,
|
|
ETP_CMD_I2C_IAP,
|
|
self->iap_password,
|
|
error))
|
|
return FALSE;
|
|
g_usleep (ELANTP_DELAY_UNLOCK * 1000);
|
|
if (!fu_elantp_i2c_device_ensure_iap_ctrl (self, error))
|
|
return FALSE;
|
|
if ((self->iap_ctrl & ETP_FW_IAP_CHECK_PW) == 0) {
|
|
g_set_error_literal (error,
|
|
FWUPD_ERROR,
|
|
FWUPD_ERROR_WRITE,
|
|
"unexpected bootloader password");
|
|
return FALSE;
|
|
}
|
|
|
|
/* success */
|
|
return TRUE;
|
|
}
|
|
|
|
static gboolean
|
|
fu_elantp_i2c_device_attach (FuDevice *device, GError **error)
|
|
{
|
|
FuElantpI2cDevice *self = FU_ELANTP_I2C_DEVICE (device);
|
|
|
|
/* sanity check */
|
|
if (!fu_device_has_flag (device, FWUPD_DEVICE_FLAG_IS_BOOTLOADER)) {
|
|
g_debug ("already in runtime mode, skipping");
|
|
return TRUE;
|
|
}
|
|
|
|
/* reset back to runtime */
|
|
fu_device_set_status (device, FWUPD_STATUS_DEVICE_RESTART);
|
|
if (!fu_elantp_i2c_device_write_cmd (self, ETP_CMD_I2C_IAP_RESET, ETP_I2C_IAP_RESET, error))
|
|
return FALSE;
|
|
g_usleep (ELANTP_DELAY_RESET * 1000);
|
|
if (!fu_elantp_i2c_device_write_cmd (self, ETP_CMD_I2C_IAP_RESET, ETP_I2C_ENABLE_REPORT, error)) {
|
|
g_prefix_error (error, "cannot enable TP report: ");
|
|
return FALSE;
|
|
}
|
|
if (!fu_elantp_i2c_device_write_cmd (self, 0x0306, 0x003, error)) {
|
|
g_prefix_error (error, "cannot switch to TP PTP mode: ");
|
|
return FALSE;
|
|
}
|
|
if (!fu_elantp_i2c_device_ensure_iap_ctrl (self, error))
|
|
return FALSE;
|
|
|
|
/* success */
|
|
return TRUE;
|
|
}
|
|
|
|
static gboolean
|
|
fu_elantp_i2c_device_set_quirk_kv (FuDevice *device,
|
|
const gchar *key,
|
|
const gchar *value,
|
|
GError **error)
|
|
{
|
|
FuElantpI2cDevice *self = FU_ELANTP_I2C_DEVICE (device);
|
|
if (g_strcmp0 (key, "ElantpIcPageCount") == 0) {
|
|
guint64 tmp = fu_common_strtoull (value);
|
|
if (tmp > 0xffff) {
|
|
g_set_error_literal (error,
|
|
G_IO_ERROR,
|
|
G_IO_ERROR_NOT_SUPPORTED,
|
|
"ElantpIcPageCount only supports "
|
|
"values <= 0xffff");
|
|
return FALSE;
|
|
}
|
|
self->ic_page_count = (guint16) tmp;
|
|
return TRUE;
|
|
}
|
|
if (g_strcmp0 (key, "ElantpIapPassword") == 0) {
|
|
guint64 tmp = fu_common_strtoull (value);
|
|
if (tmp > 0xffff) {
|
|
g_set_error_literal (error,
|
|
G_IO_ERROR,
|
|
G_IO_ERROR_NOT_SUPPORTED,
|
|
"ElantpIapPassword only supports "
|
|
"values <= 0xffff");
|
|
return FALSE;
|
|
}
|
|
self->iap_password = (guint16) tmp;
|
|
return TRUE;
|
|
}
|
|
if (g_strcmp0 (key, "ElantpI2cTargetAddress") == 0) {
|
|
guint64 tmp = fu_common_strtoull (value);
|
|
if (tmp > 0xffff) {
|
|
g_set_error_literal (error,
|
|
G_IO_ERROR,
|
|
G_IO_ERROR_NOT_SUPPORTED,
|
|
"ElantpI2cTargetAddress only supports "
|
|
"values <= 0xffff");
|
|
return FALSE;
|
|
}
|
|
self->i2c_addr = (guint16) tmp;
|
|
return TRUE;
|
|
}
|
|
g_set_error_literal (error,
|
|
G_IO_ERROR,
|
|
G_IO_ERROR_NOT_SUPPORTED,
|
|
"quirk key not supported");
|
|
return FALSE;
|
|
}
|
|
|
|
static void
|
|
fu_elantp_i2c_device_init (FuElantpI2cDevice *self)
|
|
{
|
|
fu_device_add_flag (FU_DEVICE (self), FWUPD_DEVICE_FLAG_INTERNAL);
|
|
fu_device_add_flag (FU_DEVICE (self), FWUPD_DEVICE_FLAG_UPDATABLE);
|
|
fu_device_set_summary (FU_DEVICE (self), "Elan Touchpad (I²C Recovery)");
|
|
fu_device_add_icon (FU_DEVICE (self), "input-touchpad");
|
|
fu_device_set_protocol (FU_DEVICE (self), "tw.com.emc.elantp");
|
|
fu_device_set_version_format (FU_DEVICE (self), FWUPD_VERSION_FORMAT_HEX);
|
|
fu_udev_device_set_flags (FU_UDEV_DEVICE (self),
|
|
FU_UDEV_DEVICE_FLAG_OPEN_READ |
|
|
FU_UDEV_DEVICE_FLAG_OPEN_WRITE);
|
|
}
|
|
|
|
static void
|
|
fu_elantp_i2c_device_finalize (GObject *object)
|
|
{
|
|
G_OBJECT_CLASS (fu_elantp_i2c_device_parent_class)->finalize (object);
|
|
}
|
|
|
|
static void
|
|
fu_elantp_i2c_device_class_init (FuElantpI2cDeviceClass *klass)
|
|
{
|
|
GObjectClass *object_class = G_OBJECT_CLASS (klass);
|
|
FuDeviceClass *klass_device = FU_DEVICE_CLASS (klass);
|
|
FuUdevDeviceClass *klass_udev_device = FU_UDEV_DEVICE_CLASS (klass);
|
|
object_class->finalize = fu_elantp_i2c_device_finalize;
|
|
klass_device->to_string = fu_elantp_i2c_device_to_string;
|
|
klass_device->attach = fu_elantp_i2c_device_attach;
|
|
klass_device->detach = fu_elantp_i2c_device_detach;
|
|
klass_device->set_quirk_kv = fu_elantp_i2c_device_set_quirk_kv;
|
|
klass_device->setup = fu_elantp_i2c_device_setup;
|
|
klass_device->reload = fu_elantp_i2c_device_setup;
|
|
klass_device->write_firmware = fu_elantp_i2c_device_write_firmware;
|
|
klass_device->prepare_firmware = fu_elantp_i2c_device_prepare_firmware;
|
|
klass_udev_device->probe = fu_elantp_i2c_device_probe;
|
|
klass_udev_device->open = fu_elantp_i2c_device_open;
|
|
}
|