From 8eca325ae2e49279a59901f27ef81e86a4ea1983 Mon Sep 17 00:00:00 2001 From: Richard Hughes Date: Sat, 22 Oct 2022 17:20:46 +0100 Subject: [PATCH] elantp: Add ELAN hapticpad support Co-authored-by: jingle.wu --- plugins/elantp/fu-elantp-common.h | 20 + plugins/elantp/fu-elantp-haptic-firmware.c | 131 +++ plugins/elantp/fu-elantp-haptic-firmware.h | 22 + plugins/elantp/fu-elantp-hid-device.c | 43 +- plugins/elantp/fu-elantp-hid-haptic-device.c | 1068 ++++++++++++++++++ plugins/elantp/fu-elantp-hid-haptic-device.h | 21 + plugins/elantp/fu-elantp-i2c-device.c | 1 + plugins/elantp/meson.build | 2 + 8 files changed, 1307 insertions(+), 1 deletion(-) create mode 100644 plugins/elantp/fu-elantp-haptic-firmware.c create mode 100644 plugins/elantp/fu-elantp-haptic-firmware.h create mode 100644 plugins/elantp/fu-elantp-hid-haptic-device.c create mode 100644 plugins/elantp/fu-elantp-hid-haptic-device.h diff --git a/plugins/elantp/fu-elantp-common.h b/plugins/elantp/fu-elantp-common.h index 7f63baa8e..59f2f73e6 100644 --- a/plugins/elantp/fu-elantp-common.h +++ b/plugins/elantp/fu-elantp-common.h @@ -24,6 +24,25 @@ #define ETP_CMD_I2C_GET_HID_ID 0x0100 #define ETP_CMD_I2C_IAP_TYPE 0x0304 +#define ETP_CMD_I2C_FLIM_TYPE_ENABLE 0x0104 +#define ETP_CMD_I2C_SET_EEPROM_CTRL 0x0321 +#define ETP_CMD_I2C_GET_EEPROM_FW_VERSION 0x0710 +#define ETP_CMD_I2C_GET_EEPROM_IAP_VERSION 0x0711 +#define ETP_CMD_I2C_SET_EEPROM_ENTER_IAP 0x0607 +#define ETP_CMD_I2C_SET_EEPROM_LEAVE_IAP 0x0606 +#define ETP_CMD_I2C_SET_EEPROM_DATATYPE 0x0702 +#define ETP_CMD_I2C_CALC_EEPROM_CHECKSUM 0x060F +#define ETP_CMD_I2C_READ_EEPROM_CHECKSUM 0x070A +#define ETP_CMD_I2C_HAPTIC_RESTART 0x0601 +#define ETP_CMD_I2C_EEPROM_SETTING 0x0322 +#define ETP_CMD_I2C_EEPROM_LONG_TRANS_ENABLE 0x4607 +#define ETP_CMD_I2C_EEPROM_SETTING_INITIAL 0x0000 +#define ETP_CMD_I2C_EEPROM_WRITE_INFOMATION 0x4600 +#define ETP_CMD_I2C_EEPROM_WRITE_CHECKSUM 0x048B + +#define ETP_FW_FLIM_TYPE_ENABLE_BIT 0x1 +#define ETP_FW_EEPROM_ENABLE_BIT 0x2 + #define ETP_I2C_IAP_TYPE_REG 0x0040 #define ETP_I2C_ENABLE_REPORT 0x0800 @@ -41,6 +60,7 @@ #define ELANTP_DELAY_COMPLETE 1200 /* ms */ #define ELANTP_DELAY_RESET 30 /* ms */ +#define ELANTP_EEPROM_READ_DELAY 100 /* ms */ #define ELANTP_DELAY_UNLOCK 100 /* ms */ #define ELANTP_DELAY_WRITE_BLOCK 35 /* ms */ #define ELANTP_DELAY_WRITE_BLOCK_512 50 /* ms */ diff --git a/plugins/elantp/fu-elantp-haptic-firmware.c b/plugins/elantp/fu-elantp-haptic-firmware.c new file mode 100644 index 000000000..e260375b3 --- /dev/null +++ b/plugins/elantp/fu-elantp-haptic-firmware.c @@ -0,0 +1,131 @@ +/* + * Copyright (C) 2022 Jingle Wu + * + * SPDX-License-Identifier: LGPL-2.1+ + */ + +#include "config.h" + +#include + +#include "fu-elantp-common.h" +#include "fu-elantp-haptic-firmware.h" + +struct _FuElantpHapticFirmware { + FuFirmwareClass parent_instance; + guint16 driver_ic; +}; + +G_DEFINE_TYPE(FuElantpHapticFirmware, fu_elantp_haptic_firmware, FU_TYPE_FIRMWARE) + +const guint8 elantp_haptic_signature_ictype02[] = {0xFF, 0x40, 0xA2, 0x5B}; + +guint16 +fu_elantp_haptic_firmware_get_driver_ic(FuElantpHapticFirmware *self) +{ + g_return_val_if_fail(FU_IS_ELANTP_HAPTIC_FIRMWARE(self), 0); + return self->driver_ic; +} + +static void +fu_elantp_haptic_firmware_export(FuFirmware *firmware, + FuFirmwareExportFlags flags, + XbBuilderNode *bn) +{ + FuElantpHapticFirmware *self = FU_ELANTP_HAPTIC_FIRMWARE(firmware); + fu_xmlb_builder_insert_kx(bn, "driver_ic", self->driver_ic); +} + +static gboolean +fu_elantp_haptic_firmware_check_magic(FuFirmware *firmware, + GBytes *fw, + gsize offset, + GError **error) +{ + gsize bufsz = g_bytes_get_size(fw); + const guint8 *buf = g_bytes_get_data(fw, NULL); + + for (gsize i = 0; i < sizeof(elantp_haptic_signature_ictype02); i++) { + guint8 tmp = 0x0; + if (!fu_memread_uint8_safe(buf, bufsz, i + offset, &tmp, error)) + return FALSE; + if (tmp != elantp_haptic_signature_ictype02[i]) { + g_set_error(error, + FWUPD_ERROR, + FWUPD_ERROR_INVALID_FILE, + "signature[%u] invalid: got 0x%2x, expected 0x%02x", + (guint)i, + tmp, + elantp_haptic_signature_ictype02[i]); + return FALSE; + } + } + return TRUE; +} + +static gboolean +fu_elantp_haptic_firmware_parse(FuFirmware *firmware, + GBytes *fw, + gsize offset, + FwupdInstallFlags flags, + GError **error) +{ + FuElantpHapticFirmware *self = FU_ELANTP_HAPTIC_FIRMWARE(firmware); + gsize bufsz = 0; + guint8 v_s = 0; + guint8 v_d = 0; + guint8 v_m = 0; + guint8 v_y = 0; + guint8 tmp = 0; + g_autofree gchar *version_str = NULL; + const guint8 *buf = g_bytes_get_data(fw, &bufsz); + + g_return_val_if_fail(fw != NULL, FALSE); + + if (!fu_memread_uint8_safe(buf, bufsz, offset + 0x4, &tmp, error)) + return FALSE; + v_m = tmp & 0xF; + v_s = (tmp & 0xF0) >> 4; + if (!fu_memread_uint8_safe(buf, bufsz, offset + 0x5, &v_d, error)) + return FALSE; + if (!fu_memread_uint8_safe(buf, bufsz, offset + 0x6, &v_y, error)) + return FALSE; + if (v_y == 0xFF || v_d == 0xFF || v_m == 0xF) { + g_set_error(error, + FWUPD_ERROR, + FWUPD_ERROR_INVALID_FILE, + "bad firmware version %02d%02d%02d%02d", + v_y, + v_m, + v_d, + v_s); + return FALSE; + } + + version_str = g_strdup_printf("%02d%02d%02d%02d", v_y, v_m, v_d, v_s); + fu_firmware_set_version(FU_FIRMWARE(self), version_str); + + /* success */ + self->driver_ic = 0x2; + return TRUE; +} + +static void +fu_elantp_haptic_firmware_init(FuElantpHapticFirmware *self) +{ +} + +static void +fu_elantp_haptic_firmware_class_init(FuElantpHapticFirmwareClass *klass) +{ + FuFirmwareClass *klass_firmware = FU_FIRMWARE_CLASS(klass); + klass_firmware->check_magic = fu_elantp_haptic_firmware_check_magic; + klass_firmware->parse = fu_elantp_haptic_firmware_parse; + klass_firmware->export = fu_elantp_haptic_firmware_export; +} + +FuFirmware * +fu_elantp_haptic_firmware_new(void) +{ + return FU_FIRMWARE(g_object_new(FU_TYPE_ELANTP_HAPTIC_FIRMWARE, NULL)); +} diff --git a/plugins/elantp/fu-elantp-haptic-firmware.h b/plugins/elantp/fu-elantp-haptic-firmware.h new file mode 100644 index 000000000..4cf49d55c --- /dev/null +++ b/plugins/elantp/fu-elantp-haptic-firmware.h @@ -0,0 +1,22 @@ +/* + * Copyright (C) 2022 Jingle Wu + * + * SPDX-License-Identifier: LGPL-2.1+ + */ + +#pragma once + +#include + +#define FU_TYPE_ELANTP_HAPTIC_FIRMWARE (fu_elantp_haptic_firmware_get_type()) +G_DECLARE_FINAL_TYPE(FuElantpHapticFirmware, + fu_elantp_haptic_firmware, + FU, + ELANTP_HAPTIC_FIRMWARE, + FuFirmware) + +FuFirmware * +fu_elantp_haptic_firmware_new(void); + +guint16 +fu_elantp_haptic_firmware_get_driver_ic(FuElantpHapticFirmware *self); diff --git a/plugins/elantp/fu-elantp-hid-device.c b/plugins/elantp/fu-elantp-hid-device.c index 55630e011..7f5783b8f 100644 --- a/plugins/elantp/fu-elantp-hid-device.c +++ b/plugins/elantp/fu-elantp-hid-device.c @@ -14,6 +14,7 @@ #include "fu-elantp-common.h" #include "fu-elantp-firmware.h" #include "fu-elantp-hid-device.h" +#include "fu-elantp-hid-haptic-device.h" struct _FuElantpHidDevice { FuUdevDevice parent_instance; @@ -159,6 +160,38 @@ fu_elantp_hid_device_ensure_iap_ctrl(FuElantpHidDevice *self, GError **error) return TRUE; } +static gboolean +fu_elantp_hid_device_read_hatpic_enable(FuElantpHidDevice *self, GError **error) +{ + guint8 buf[2] = {0x0}; + guint16 value; + if (!fu_elantp_hid_device_read_cmd(self, + ETP_CMD_I2C_FLIM_TYPE_ENABLE, + buf, + sizeof(buf), + error)) { + g_prefix_error(error, "failed to read haptic enable cmd: "); + return FALSE; + } + value = fu_memread_uint16(buf, G_LITTLE_ENDIAN); + if (value == 0xFFFF || value == ETP_CMD_I2C_FLIM_TYPE_ENABLE) { + g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "not hapticpad"); + return FALSE; + } + + if ((buf[0] & ETP_FW_FLIM_TYPE_ENABLE_BIT) == 0 || + (buf[0] & ETP_FW_EEPROM_ENABLE_BIT) == 0) { + g_set_error(error, + FWUPD_ERROR, + FWUPD_ERROR_NOT_SUPPORTED, + "the haptic eeprom not supported"); + return FALSE; + } + + /* success */ + return TRUE; +} + static gboolean fu_elantp_hid_device_setup(FuDevice *device, GError **error) { @@ -171,6 +204,7 @@ fu_elantp_hid_device_setup(FuDevice *device, GError **error) guint8 ic_type; g_autofree gchar *version_bl = NULL; g_autofree gchar *version = NULL; + g_autoptr(GError) error_local = NULL; /* get pattern */ if (!fu_elantp_hid_device_read_cmd(self, ETP_CMD_I2C_GET_HID_ID, buf, sizeof(buf), error)) { @@ -178,7 +212,7 @@ fu_elantp_hid_device_setup(FuDevice *device, GError **error) return FALSE; } tmp = fu_memread_uint16(buf, G_LITTLE_ENDIAN); - self->pattern = tmp != 0xffff ? (tmp & 0xff00) >> 8 : 0; + self->pattern = tmp != 0xFFFF ? (tmp & 0xFF00) >> 8 : 0; /* get current firmware version */ if (!fu_elantp_hid_device_read_cmd(self, ETP_CMD_I2C_FW_VERSION, buf, sizeof(buf), error)) { @@ -271,6 +305,12 @@ fu_elantp_hid_device_setup(FuDevice *device, GError **error) if (!fu_elantp_hid_device_ensure_iap_ctrl(self, error)) return FALSE; + if (!fu_elantp_hid_device_read_hatpic_enable(self, &error_local)) { + g_debug("no haptic device detected: %s", error_local->message); + } else { + g_autoptr(FuElantpHidHapticDevice) cfg = fu_elantp_haptic_device_new(device); + fu_device_add_child(FU_DEVICE(device), FU_DEVICE(cfg)); + } /* success */ return TRUE; } @@ -606,6 +646,7 @@ fu_elantp_hid_device_init(FuElantpHidDevice *self) fu_device_set_summary(FU_DEVICE(self), "Touchpad"); fu_device_add_icon(FU_DEVICE(self), "input-touchpad"); fu_device_add_protocol(FU_DEVICE(self), "tw.com.emc.elantp"); + fu_device_set_vendor(FU_DEVICE(self), "ELAN Microelectronics"); fu_device_set_version_format(FU_DEVICE(self), FWUPD_VERSION_FORMAT_HEX); fu_device_set_priority(FU_DEVICE(self), 1); /* better than i2c */ fu_udev_device_set_flags(FU_UDEV_DEVICE(self), diff --git a/plugins/elantp/fu-elantp-hid-haptic-device.c b/plugins/elantp/fu-elantp-hid-haptic-device.c new file mode 100644 index 000000000..ae929821b --- /dev/null +++ b/plugins/elantp/fu-elantp-hid-haptic-device.c @@ -0,0 +1,1068 @@ +/* + * Copyright (C) 2022 Jingle Wu + * + * SPDX-License-Identifier: LGPL-2.1+ + */ + +#include "config.h" + +#include + +#include +#include + +#include "fu-elantp-common.h" +#include "fu-elantp-haptic-firmware.h" +#include "fu-elantp-hid-haptic-device.h" + +struct _FuElantpHidHapticDevice { + FuUdevDevice parent_instance; + guint16 ic_page_count; + guint16 iap_type; + guint16 tp_iap_ctrl; + guint16 iap_ctrl; + guint16 iap_password; + guint16 module_id; + guint16 fw_page_size; + guint8 pattern; + gint16 driver_ic; + guint8 iap_ver; +}; + +G_DEFINE_TYPE(FuElantpHidHapticDevice, fu_elantp_hid_haptic_device, FU_TYPE_UDEV_DEVICE) + +#define FU_ELANTP_DEVICE_IOCTL_TIMEOUT 5000 /* ms */ + +static FuElantpHidDevice * +fu_elantp_haptic_device_get_parent(FuDevice *self, GError **error) +{ + FuDevice *parent = fu_device_get_parent(FU_DEVICE(self)); + if (parent == NULL) { + g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_INTERNAL, "no parent set"); + return NULL; + } + return FU_ELANTP_HID_DEVICE(FU_UDEV_DEVICE(parent)); +} + +static gboolean +fu_elantp_hid_haptic_device_detach(FuDevice *device, FuProgress *progress, GError **error); + +static void +fu_elantp_hid_haptic_device_to_string(FuDevice *device, guint idt, GString *str) +{ + FuElantpHidHapticDevice *self = FU_ELANTP_HID_HAPTIC_DEVICE(device); + fu_string_append_kx(str, idt, "ModuleId", self->module_id); + fu_string_append_kx(str, idt, "Pattern", self->pattern); + fu_string_append_kx(str, idt, "FwPageSize", self->fw_page_size); + fu_string_append_kx(str, idt, "IcPageCount", self->ic_page_count); + fu_string_append_kx(str, idt, "IapType", self->iap_type); + fu_string_append_kx(str, idt, "TpIapCtrl", self->tp_iap_ctrl); + fu_string_append_kx(str, idt, "IapCtrl", self->iap_ctrl); + fu_string_append_kx(str, idt, "DriverIC", self->driver_ic); + fu_string_append_kx(str, idt, "IAPVersion", self->iap_ver); +} + +static gboolean +fu_elantp_hid_haptic_device_probe(FuDevice *device, GError **error) +{ + /* set the physical ID */ + return fu_udev_device_set_physical_id(FU_UDEV_DEVICE(device), "hid", error); +} + +static gboolean +fu_elantp_hid_haptic_device_send_cmd(FuDevice *self, + const guint8 *tx, + gsize txsz, + guint8 *rx, + gsize rxsz, + GError **error) +{ + g_autofree guint8 *buf = NULL; + gsize bufsz = rxsz + 3; + + if (g_getenv("FWUPD_ELANTP_VERBOSE") != NULL) + fu_dump_raw(G_LOG_DOMAIN, "SetReport", tx, txsz); + + if (!fu_udev_device_ioctl(FU_UDEV_DEVICE(self), + HIDIOCSFEATURE(txsz), + (guint8 *)tx, + NULL, + FU_ELANTP_DEVICE_IOCTL_TIMEOUT, + error)) + return FALSE; + + if (rxsz == 0) + return TRUE; + + /* GetFeature */ + buf = g_malloc0(bufsz); + buf[0] = tx[0]; /* report number */ + if (!fu_udev_device_ioctl(FU_UDEV_DEVICE(self), + HIDIOCGFEATURE(bufsz), + buf, + NULL, + FU_ELANTP_DEVICE_IOCTL_TIMEOUT, + error)) + return FALSE; + + if (g_getenv("FWUPD_ELANTP_VERBOSE") != NULL) + fu_dump_raw(G_LOG_DOMAIN, "GetReport", buf, bufsz); + + /* success */ + return fu_memcpy_safe(rx, + rxsz, + 0x0, /* dst */ + buf, + bufsz, + 0x3, /* src */ + rxsz, + error); +} + +static gboolean +fu_elantp_hid_haptic_device_read_cmd(FuDevice *self, + guint16 reg, + guint8 *buf, + gsize bufz, + GError **error) +{ + guint8 tmp[5] = {0x0D, 0x05, 0x03}; + fu_memwrite_uint16(tmp + 0x3, reg, G_LITTLE_ENDIAN); + return fu_elantp_hid_haptic_device_send_cmd(self, tmp, sizeof(tmp), buf, bufz, error); +} + +static gint +fu_elantp_hid_haptic_device_write_cmd(FuDevice *self, guint16 reg, guint16 cmd, GError **error) +{ + guint8 buf[5] = {0x0D}; + fu_memwrite_uint16(buf + 0x1, reg, G_LITTLE_ENDIAN); + fu_memwrite_uint16(buf + 0x3, cmd, G_LITTLE_ENDIAN); + return fu_elantp_hid_haptic_device_send_cmd(self, buf, sizeof(buf), NULL, 0, error); +} + +static gboolean +fu_elantp_hid_haptic_device_ensure_iap_ctrl(FuDevice *parent, + FuElantpHidHapticDevice *self, + GError **error) +{ + guint8 buf[2] = {0x0}; + if (!fu_elantp_hid_haptic_device_read_cmd(parent, + ETP_CMD_I2C_IAP_CTRL, + buf, + sizeof(buf), + error)) { + g_prefix_error(error, "failed to read IAPControl: "); + return FALSE; + } + self->tp_iap_ctrl = fu_memread_uint16(buf, G_LITTLE_ENDIAN); + + /* in bootloader mode? */ + if ((self->tp_iap_ctrl & ETP_I2C_MAIN_MODE_ON) == 0) + fu_device_add_flag(FU_DEVICE(parent), FWUPD_DEVICE_FLAG_IS_BOOTLOADER); + else + fu_device_remove_flag(FU_DEVICE(parent), FWUPD_DEVICE_FLAG_IS_BOOTLOADER); + + return TRUE; +} + +static gboolean +fu_elantp_hid_haptic_device_ensure_eeprom_iap_ctrl(FuDevice *parent, + FuElantpHidHapticDevice *self, + GError **error) +{ + guint8 buf[2] = {0x0}; + if (!fu_elantp_hid_haptic_device_read_cmd(parent, + ETP_CMD_I2C_SET_EEPROM_CTRL, + buf, + sizeof(buf), + error)) { + g_prefix_error(error, "failed to read IAPControl: "); + return FALSE; + } + self->iap_ctrl = fu_memread_uint16(buf, G_LITTLE_ENDIAN); + + if ((self->iap_ctrl & 0x800) != 0x800) { + g_set_error(error, G_IO_ERROR, G_IO_ERROR_PARTIAL_INPUT, "bit11 fail"); + return FALSE; + } + if ((self->iap_ctrl & 0x1000) == 0x1000) { + g_set_error(error, G_IO_ERROR, G_IO_ERROR_BUSY, "bit12 fail, resend"); + return FALSE; + } + + return TRUE; +} + +static gboolean +fu_elantp_hid_haptic_device_get_hatpic_driver_ic(FuDevice *parent, + FuElantpHidHapticDevice *self, + GError **error) +{ + guint8 buf[2] = {0x0}; + guint16 value; + if (!fu_elantp_hid_haptic_device_read_cmd(parent, + ETP_CMD_I2C_FLIM_TYPE_ENABLE, + buf, + sizeof(buf), + error)) { + g_prefix_error(error, "failed to read haptic enable cmd: "); + return FALSE; + } + value = fu_memread_uint16(buf, G_LITTLE_ENDIAN); + if (value == 0xFFFF || value == ETP_CMD_I2C_FLIM_TYPE_ENABLE) { + g_set_error_literal(error, + G_IO_ERROR, + G_IO_ERROR_NOT_SUPPORTED, + "failed to read haptic enable cmd"); + return FALSE; + } + + if ((buf[0] & ETP_FW_FLIM_TYPE_ENABLE_BIT) == 0 || + (buf[0] & ETP_FW_EEPROM_ENABLE_BIT) == 0) { + g_set_error_literal(error, + G_IO_ERROR, + G_IO_ERROR_NOT_SUPPORTED, + "eeprom enable bit unset"); + return FALSE; + } + + /* success */ + self->driver_ic = (buf[0] >> 4) & 0xF; + return TRUE; +} + +static gboolean +fu_elantp_hid_haptic_device_get_version(FuDevice *parent, + FuElantpHidHapticDevice *self, + GError **error) +{ + guint16 v_s = 0; + guint16 v_d = 0; + guint16 v_m = 0; + guint16 v_y = 0; + guint8 buf[2] = {0x0}; + + if (!fu_elantp_hid_haptic_device_write_cmd(parent, + ETP_CMD_I2C_SET_EEPROM_CTRL, + ETP_CMD_I2C_GET_EEPROM_FW_VERSION, + error)) { + g_prefix_error(error, "failed to write haptic version cmd: "); + return FALSE; + } + g_usleep(ELANTP_DELAY_RESET * 1000); + + if (!fu_elantp_hid_haptic_device_read_cmd(parent, 0x0321, buf, sizeof(buf), error)) { + g_prefix_error(error, "failed to read haptic version cmd: "); + return FALSE; + } + v_d = buf[0]; + v_m = buf[1] & 0xF; + v_s = (buf[1] & 0xF0) >> 4; + + if (!fu_elantp_hid_haptic_device_write_cmd(parent, + ETP_CMD_I2C_SET_EEPROM_CTRL, + ETP_CMD_I2C_GET_EEPROM_IAP_VERSION, + error)) { + g_prefix_error(error, "failed to write haptic iap version cmd: "); + return FALSE; + } + g_usleep(ELANTP_DELAY_RESET * 1000); + + if (!fu_elantp_hid_haptic_device_read_cmd(parent, 0x0321, buf, sizeof(buf), error)) { + g_prefix_error(error, "failed to read haptic iap version cmd: "); + return FALSE; + } + v_y = buf[0]; + self->iap_ver = buf[1]; + + if (v_y == 0xFF && v_d == 0xFF && v_m == 0xF) { + fu_device_set_version(FU_DEVICE(self), "0"); + } else { + g_autofree gchar *str = g_strdup_printf("%02d%02d%02d%02d", v_y, v_m, v_d, v_s); + fu_device_set_version(FU_DEVICE(self), str); + } + + return TRUE; +} + +typedef struct { + guint16 checksum; + guint16 iap_password; +} FuElantpHaptictpWaitFlashEEPROMChecksumHelper; + +static gboolean +fu_elantp_hid_haptic_device_write_checksum_cb(FuDevice *parent, gpointer user_data, GError **error) +{ + guint8 buf[2] = {0x0}; + guint16 value; + FuElantpHaptictpWaitFlashEEPROMChecksumHelper *helper = user_data; + + if (!fu_elantp_hid_haptic_device_write_cmd(parent, + ETP_CMD_I2C_EEPROM_SETTING, + ETP_CMD_I2C_EEPROM_WRITE_INFOMATION, + error)) { + g_prefix_error(error, "failed to write haptic info: "); + return FALSE; + } + if (!fu_elantp_hid_haptic_device_read_cmd(parent, + ETP_CMD_I2C_EEPROM_SETTING, + buf, + sizeof(buf), + error)) { + g_prefix_error(error, "failed to read haptic info: "); + return FALSE; + } + value = fu_memread_uint16(buf, G_LITTLE_ENDIAN); + + if ((value & 0xFFFF) != ETP_CMD_I2C_EEPROM_WRITE_INFOMATION) { + g_set_error(error, + G_IO_ERROR, + G_IO_ERROR_FAILED, + "failed to set haptic info (0x%04x): ", + value); + return FALSE; + } + if (!fu_elantp_hid_haptic_device_write_cmd(parent, + ETP_CMD_I2C_IAP, + helper->iap_password, + error)) { + g_prefix_error(error, "failed to write iap password: "); + return FALSE; + } + if (!fu_elantp_hid_haptic_device_write_cmd(parent, + ETP_CMD_I2C_EEPROM_WRITE_CHECKSUM, + helper->checksum, + error)) { + g_prefix_error(error, "failed to write eeprom checksum: "); + return FALSE; + } + if (!fu_elantp_hid_haptic_device_write_cmd(parent, + ETP_CMD_I2C_EEPROM_SETTING, + ETP_CMD_I2C_EEPROM_SETTING_INITIAL, + error)) { + g_prefix_error(error, "failed to set haptic initial setting: "); + return FALSE; + } + if (!fu_elantp_hid_haptic_device_read_cmd(parent, + ETP_CMD_I2C_EEPROM_WRITE_CHECKSUM, + buf, + sizeof(buf), + error)) { + g_prefix_error(error, "failed to read haptic checksum: "); + return FALSE; + } + value = fu_memread_uint16(buf, G_LITTLE_ENDIAN); + if ((value & 0xFFFF) != helper->checksum) { + g_set_error(error, + G_IO_ERROR, + G_IO_ERROR_INVALID_DATA, + "eeprom checksum failed 0x%04x != 0x%04x : ", + value, + helper->checksum); + return FALSE; + } + + /* success */ + return TRUE; +} + +static gboolean +fu_elantp_hid_haptic_device_wait_calc_checksum_cb(FuDevice *parent, + gpointer user_data, + GError **error) +{ + guint16 ctrl; + guint8 buf[2] = {0x0}; + + if (!fu_elantp_hid_haptic_device_write_cmd(parent, + ETP_CMD_I2C_SET_EEPROM_CTRL, + ETP_CMD_I2C_SET_EEPROM_DATATYPE, + error)) { + g_prefix_error(error, "failed to write eeprom datatype: "); + return FALSE; + } + if (!fu_elantp_hid_haptic_device_read_cmd(parent, + ETP_CMD_I2C_SET_EEPROM_CTRL, + buf, + sizeof(buf), + error)) { + g_prefix_error(error, "failed to read calc haptic cmd: "); + return FALSE; + } + ctrl = fu_memread_uint16(buf, G_LITTLE_ENDIAN); + if ((ctrl & 0x20) == 0x20) { + g_set_error(error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA, "ctrl failed 0x%04x", ctrl); + return FALSE; + } + + return TRUE; +} + +static gboolean +fu_elantp_hid_haptic_device_get_checksum(FuDevice *parent, guint16 *checksum, GError **error) +{ + guint8 buf[2] = {0x0}; + g_autoptr(GError) error_local = NULL; + + if (!fu_elantp_hid_haptic_device_write_cmd(parent, + ETP_CMD_I2C_SET_EEPROM_CTRL, + ETP_CMD_I2C_CALC_EEPROM_CHECKSUM, + error)) + return FALSE; + if (!fu_device_retry_full(parent, + fu_elantp_hid_haptic_device_wait_calc_checksum_cb, + 100, + ELANTP_EEPROM_READ_DELAY, + NULL, + &error_local)) { + g_set_error(error, + FWUPD_ERROR, + FWUPD_ERROR_READ, + "failed to wait calc eeprom checksum (%s)", + error_local->message); + return FALSE; + } + if (!fu_elantp_hid_haptic_device_write_cmd(parent, + ETP_CMD_I2C_SET_EEPROM_CTRL, + ETP_CMD_I2C_READ_EEPROM_CHECKSUM, + error)) + return FALSE; + if (!fu_elantp_hid_haptic_device_read_cmd(parent, + ETP_CMD_I2C_SET_EEPROM_CTRL, + buf, + sizeof(buf), + error)) { + g_prefix_error(error, "failed to read haptic checksum cmd: "); + return FALSE; + } + *checksum = fu_memread_uint16(buf, G_LITTLE_ENDIAN); + + return TRUE; +} + +static gboolean +fu_elantp_hid_haptic_device_setup(FuDevice *device, GError **error) +{ + FuElantpHidDevice *parent; + FuElantpHidHapticDevice *self = FU_ELANTP_HID_HAPTIC_DEVICE(device); + FuUdevDevice *udev_parent; + guint8 ic_type; + guint16 tmp; + guint8 buf[2] = {0x0}; + g_autofree gchar *version_bl = NULL; + + parent = fu_elantp_haptic_device_get_parent(device, error); + if (parent == NULL) + return FALSE; + + if (!fu_elantp_hid_haptic_device_get_hatpic_driver_ic(FU_DEVICE(parent), self, error)) { + g_prefix_error(error, "this module is not support haptic EEPROM."); + return FALSE; + } + + /* get pattern */ + if (!fu_elantp_hid_haptic_device_read_cmd(FU_DEVICE(parent), + ETP_CMD_I2C_GET_HID_ID, + buf, + sizeof(buf), + error)) { + g_prefix_error(error, "failed to read HID ID: "); + return FALSE; + } + tmp = fu_memread_uint16(buf, G_LITTLE_ENDIAN); + self->pattern = tmp != 0xFFFF ? (tmp & 0xFF00) >> 8 : 0; + + if (!fu_elantp_hid_haptic_device_get_version(FU_DEVICE(parent), self, error)) + return FALSE; + + version_bl = fu_version_from_uint16(self->iap_ver, FWUPD_VERSION_FORMAT_HEX); + fu_device_set_version_bootloader(device, version_bl); + + /* get module ID */ + if (!fu_elantp_hid_haptic_device_read_cmd(FU_DEVICE(parent), + ETP_CMD_GET_MODULE_ID, + buf, + sizeof(buf), + error)) { + g_prefix_error(error, "failed to read module ID: "); + return FALSE; + } + self->module_id = fu_memread_uint16(buf, G_LITTLE_ENDIAN); + + /* define the extra instance IDs */ + udev_parent = FU_UDEV_DEVICE(parent); + fu_device_add_instance_u16(device, "VEN", fu_udev_device_get_vendor(udev_parent)); + fu_device_add_instance_u16(device, "DEV", fu_udev_device_get_model(udev_parent)); + fu_device_add_instance_u16(device, "DRIVERIC", self->driver_ic); + fu_device_add_instance_u16(device, "MOD", self->module_id); + if (!fu_device_build_instance_id(device, + error, + "HIDRAW", + "VEN", + "DEV", + "DRIVERIC", + "MOD", + NULL)) + return FALSE; + + /* get OSM version */ + if (!fu_elantp_hid_haptic_device_read_cmd(FU_DEVICE(parent), + ETP_CMD_I2C_OSM_VERSION, + buf, + sizeof(buf), + error)) { + g_prefix_error(error, "failed to read OSM version: "); + return FALSE; + } + tmp = fu_memread_uint16(buf, G_LITTLE_ENDIAN); + if (tmp == ETP_CMD_I2C_OSM_VERSION || tmp == 0xFFFF) { + if (!fu_elantp_hid_haptic_device_read_cmd(FU_DEVICE(parent), + ETP_CMD_I2C_IAP_ICBODY, + buf, + sizeof(buf), + error)) { + g_prefix_error(error, "failed to read IC body: "); + return FALSE; + } + ic_type = fu_memread_uint16(buf, G_LITTLE_ENDIAN) & 0xFF; + } else + ic_type = (tmp >> 8) & 0xFF; + + /* define the extra instance IDs (ic_type + module_id + driver) */ + fu_device_add_instance_u8(device, "ICTYPE", ic_type); + fu_device_build_instance_id(device, NULL, "ELANTP", "ICTYPE", NULL); + fu_device_build_instance_id(device, NULL, "ELANTP", "ICTYPE", "", NULL); + fu_device_build_instance_id(device, NULL, "ELANTP", "ICTYPE", "DRIVERIC", "MOD", NULL); + fu_device_add_instance_str(device, "DRIVER", "HID"); + fu_device_build_instance_id(device, + NULL, + "ELANTP", + "ICTYPE", + "DRIVERIC", + "MOD", + "DRIVER", + NULL); + + /* 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, 32768); + + /* find out if in bootloader mode */ + if (!fu_elantp_hid_haptic_device_ensure_iap_ctrl(FU_DEVICE(parent), self, error)) + return FALSE; + + /* success */ + return TRUE; +} + +static FuFirmware * +fu_elantp_hid_haptic_device_prepare_firmware(FuDevice *device, + GBytes *fw, + FwupdInstallFlags flags, + GError **error) +{ + FuElantpHidHapticDevice *self = FU_ELANTP_HID_HAPTIC_DEVICE(device); + guint16 driver_ic; + g_autoptr(FuFirmware) firmware = fu_elantp_haptic_firmware_new(); + + /* check is compatible with hardware */ + if (!fu_firmware_parse(firmware, fw, flags, error)) + return NULL; + driver_ic = fu_elantp_haptic_firmware_get_driver_ic(FU_ELANTP_HAPTIC_FIRMWARE(firmware)); + if (driver_ic != self->driver_ic) { + g_set_error(error, + G_IO_ERROR, + G_IO_ERROR_NOT_SUPPORTED, + "driver IC 0x%x != 0x%x", + (guint)driver_ic, + (guint)self->driver_ic); + return NULL; + } + + /* success */ + return g_steal_pointer(&firmware); +} + +static gboolean +fu_elantp_hid_haptic_device_write_firmware(FuDevice *device, + FuFirmware *firmware, + FuProgress *progress, + FwupdInstallFlags flags, + GError **error) +{ + FuElantpHidDevice *parent; + FuElantpHidHapticDevice *self = FU_ELANTP_HID_HAPTIC_DEVICE(device); + gsize bufsz = 0; + guint16 checksum = 0; + guint16 checksum_device = 0; + guint16 eeprom_fw_page_size = 32; + guint16 retry_cnt = 0; + guint8 first_page[32] = {0x0}; + const gchar *fw_ver; + const gchar *fw_ver_device; + const guint8 *buf; + g_autoptr(GBytes) fw = NULL; + g_autoptr(GPtrArray) chunks = NULL; + g_autoptr(GError) error_local = NULL; + FuElantpHaptictpWaitFlashEEPROMChecksumHelper helper; + + /* progress */ + fu_progress_set_id(progress, G_STRLOC); + fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_BUSY, 5, "detach"); + fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_WRITE, 80, NULL); + fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_VERIFY, 10, NULL); + fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_RESTART, 5, NULL); + + /* simple image */ + fw = fu_firmware_get_bytes(firmware, error); + if (fw == NULL) + return FALSE; + + parent = fu_elantp_haptic_device_get_parent(device, error); + if (parent == NULL) + return FALSE; + + /* detach */ + if (!fu_elantp_hid_haptic_device_detach(device, fu_progress_get_child(progress), error)) + return FALSE; + fu_progress_step_done(progress); + + /* write each block */ + buf = g_bytes_get_data(fw, &bufsz); + chunks = fu_chunk_array_new(buf, bufsz, 0x0, 0x0, eeprom_fw_page_size); + for (guint i = 0; i <= chunks->len; i++) { + guint16 index = i * eeprom_fw_page_size; + FuChunk *chk; + guint16 csum_tmp; + gsize blksz = self->fw_page_size + 3; + g_autofree guint8 *blk = g_malloc0(blksz); + g_autoptr(GError) error_iapctrl = NULL; + + if (i == chunks->len) + chk = g_ptr_array_index(chunks, 0); + else + chk = g_ptr_array_index(chunks, i); + + /* write block */ + blk[0] = 0x0B; /* report ID */ + blk[1] = eeprom_fw_page_size + 5; + blk[2] = 0xA2; + fu_memwrite_uint16(blk + 0x3, index, G_BIG_ENDIAN); + + if (i == 0) { + memset(&first_page[0], 0xFF, sizeof(first_page)); + csum_tmp = fu_sum16(first_page, eeprom_fw_page_size); + if (!fu_memcpy_safe(blk, + blksz, + 0x5, /* dst */ + first_page, + eeprom_fw_page_size, + 0x0, /* src */ + eeprom_fw_page_size, + error)) + return FALSE; + + fu_memwrite_uint16(blk + eeprom_fw_page_size + 5, csum_tmp, G_BIG_ENDIAN); + csum_tmp = 0; + } else { + csum_tmp = fu_sum16(fu_chunk_get_data(chk), fu_chunk_get_data_sz(chk)); + if (!fu_memcpy_safe(blk, + blksz, + 0x5, /* dst */ + fu_chunk_get_data(chk), + fu_chunk_get_data_sz(chk), + 0x0, /* src */ + fu_chunk_get_data_sz(chk), + error)) + return FALSE; + + fu_memwrite_uint16(blk + fu_chunk_get_data_sz(chk) + 5, + csum_tmp, + G_BIG_ENDIAN); + } + + if (!fu_elantp_hid_haptic_device_send_cmd(FU_DEVICE(parent), + 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_hid_haptic_device_write_cmd(FU_DEVICE(parent), + ETP_CMD_I2C_SET_EEPROM_CTRL, + ETP_CMD_I2C_SET_EEPROM_DATATYPE, + error)) + return FALSE; + + if (!fu_elantp_hid_haptic_device_ensure_eeprom_iap_ctrl(FU_DEVICE(parent), + self, + &error_iapctrl)) { + if (g_error_matches(error_iapctrl, G_IO_ERROR, G_IO_ERROR_BUSY)) { + i -= 1; + retry_cnt++; + if (retry_cnt >= 3) { + g_set_error(error, + FWUPD_ERROR, + FWUPD_ERROR_WRITE, + "bootloader reports failed write: 0x%x (%s)", + self->iap_ctrl, + error_iapctrl->message); + return FALSE; + } + break; + } + g_set_error(error, + FWUPD_ERROR, + FWUPD_ERROR_WRITE, + "bootloader reports failed write: 0x%x (%s)", + self->iap_ctrl, + error_iapctrl->message); + return FALSE; + } + retry_cnt = 0; + + /* update progress */ + checksum += csum_tmp; + fu_progress_set_percentage_full(fu_progress_get_child(progress), + (gsize)i + 1, + (gsize)chunks->len + 1); + } + fu_progress_step_done(progress); + + if (!fu_elantp_hid_haptic_device_write_cmd(FU_DEVICE(parent), + ETP_CMD_I2C_EEPROM_SETTING, + ETP_CMD_I2C_EEPROM_SETTING_INITIAL, + error)) { + g_prefix_error(error, "cannot disable EEPROM Long Transmission mode: "); + return FALSE; + } + if (!fu_elantp_hid_haptic_device_write_cmd(FU_DEVICE(parent), + ETP_CMD_I2C_SET_EEPROM_CTRL, + ETP_CMD_I2C_SET_EEPROM_LEAVE_IAP, + error)) { + g_prefix_error(error, "cannot leave EEPROM IAP: "); + return FALSE; + } + g_usleep(ELANTP_DELAY_RESET * 1000); + if (!fu_elantp_hid_haptic_device_get_checksum(FU_DEVICE(parent), &checksum_device, error)) { + g_prefix_error(error, "read device checksum fail: "); + return FALSE; + } + if (checksum != checksum_device) { + g_set_error(error, + FWUPD_ERROR, + FWUPD_ERROR_WRITE, + "checksum failed 0x%04x != 0x%04x", + checksum, + checksum_device); + return FALSE; + } + + helper.checksum = checksum_device; + helper.iap_password = self->iap_password; + if (!fu_device_retry_full(FU_DEVICE(parent), + fu_elantp_hid_haptic_device_write_checksum_cb, + 3, + ELANTP_DELAY_WRITE_BLOCK, + &helper, + &error_local)) { + g_prefix_error(error, "write device checksum fail (%s): ", error_local->message); + return FALSE; + } + + if (!fu_elantp_hid_haptic_device_get_version(FU_DEVICE(parent), self, error)) + return FALSE; + fw_ver_device = fu_device_get_version(device); + fw_ver = fu_firmware_get_version(firmware); + + if (g_strcmp0(fw_ver_device, fw_ver) != 0) { + g_set_error(error, + FWUPD_ERROR, + FWUPD_ERROR_WRITE, + "firmware version failed %s != %s", + fw_ver, + fw_ver_device); + return FALSE; + } + fu_progress_step_done(progress); + + if (!fu_elantp_hid_haptic_device_write_cmd(FU_DEVICE(parent), + ETP_CMD_I2C_SET_EEPROM_CTRL, + ETP_CMD_I2C_HAPTIC_RESTART, + error)) { + g_prefix_error(error, "cannot restart haptic DriverIC: "); + return FALSE; + } + fu_progress_step_done(progress); + + return TRUE; +} + +static gboolean +fu_elantp_hid_haptic_device_detach(FuDevice *device, FuProgress *progress, GError **error) +{ + FuElantpHidDevice *parent; + FuElantpHidHapticDevice *self = FU_ELANTP_HID_HAPTIC_DEVICE(device); + guint16 tp_iap_ver; + guint16 tp_ic_type; + guint8 buf[2] = {0x0}; + guint16 ctrl; + guint16 tmp; + + /* haptic EEPROM IAP process runs in the TP main code */ + if (fu_device_has_flag(device, FWUPD_DEVICE_FLAG_IS_BOOTLOADER)) { + g_set_error(error, + G_IO_ERROR, + G_IO_ERROR_NOT_SUPPORTED, + "in touchpad bootloader mode"); + return FALSE; + } + + if (self->driver_ic != 0x2 || self->iap_ver != 0x1) { + g_set_error(error, + G_IO_ERROR, + G_IO_ERROR_NOT_SUPPORTED, + "no support for EEPROM IAP 0x%x 0x%x: ", + (guint)self->driver_ic, + (guint)self->iap_ver); + return FALSE; + } + parent = fu_elantp_haptic_device_get_parent(device, error); + if (parent == NULL) + return FALSE; + + /* get OSM version */ + if (!fu_elantp_hid_haptic_device_read_cmd(FU_DEVICE(parent), + ETP_CMD_I2C_OSM_VERSION, + buf, + sizeof(buf), + error)) { + g_prefix_error(error, "failed to read OSM version: "); + return FALSE; + } + tmp = fu_memread_uint16(buf, G_LITTLE_ENDIAN); + if (tmp == ETP_CMD_I2C_OSM_VERSION || tmp == 0xFFFF) { + if (!fu_elantp_hid_haptic_device_read_cmd(FU_DEVICE(parent), + ETP_CMD_I2C_IAP_ICBODY, + buf, + sizeof(buf), + error)) { + g_prefix_error(error, "failed to read IC body: "); + return FALSE; + } + tp_ic_type = fu_memread_uint16(buf, G_LITTLE_ENDIAN) & 0xFF; + } else + tp_ic_type = (tmp >> 8) & 0xFF; + + /* get IAP firmware version */ + if (!fu_elantp_hid_haptic_device_read_cmd(FU_DEVICE(parent), + 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) + tp_iap_ver = buf[1]; + else + tp_iap_ver = fu_memread_uint16(buf, G_LITTLE_ENDIAN); + + /* set the page size */ + self->fw_page_size = 64; + if (tp_ic_type >= 0x10) { + if (tp_iap_ver >= 1) { + /* set the IAP type, presumably some kind of ABI */ + if (tp_iap_ver >= 2 && (tp_ic_type == 0x14 || tp_ic_type == 0x15)) { + self->fw_page_size = 512; + } else { + self->fw_page_size = 128; + } + + if (!fu_elantp_hid_haptic_device_write_cmd(FU_DEVICE(parent), + ETP_CMD_I2C_IAP_TYPE, + self->fw_page_size / 2, + error)) + return FALSE; + if (!fu_elantp_hid_haptic_device_read_cmd(FU_DEVICE(parent), + ETP_CMD_I2C_IAP_TYPE, + buf, + sizeof(buf), + error)) { + g_prefix_error(error, "failed to read IAP type: "); + return FALSE; + } + self->iap_type = fu_memread_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_hid_haptic_device_write_cmd(FU_DEVICE(parent), + ETP_CMD_I2C_EEPROM_SETTING, + ETP_CMD_I2C_EEPROM_LONG_TRANS_ENABLE, + error)) { + g_prefix_error(error, "cannot enable EEPROM Long Transmission mode: "); + return FALSE; + } + + if (!fu_elantp_hid_haptic_device_write_cmd(FU_DEVICE(parent), + ETP_CMD_I2C_SET_EEPROM_CTRL, + ETP_CMD_I2C_SET_EEPROM_ENTER_IAP, + error)) { + g_prefix_error(error, "cannot enter EEPROM IAP: "); + return FALSE; + } + + if (!fu_elantp_hid_haptic_device_read_cmd(FU_DEVICE(parent), + ETP_CMD_I2C_SET_EEPROM_CTRL, + buf, + sizeof(buf), + error)) + return FALSE; + ctrl = fu_memread_uint16(buf, G_LITTLE_ENDIAN); + if ((ctrl & 0x800) == 0x800) { + g_set_error(error, + FWUPD_ERROR, + FWUPD_ERROR_READ, + "unexpected EEPROM bootloader control %x", + ctrl); + return FALSE; + } + + /* success */ + return TRUE; +} + +static gboolean +fu_elantp_hid_haptic_device_attach(FuDevice *device, FuProgress *progress, GError **error) +{ + FuElantpHidDevice *parent; + FuElantpHidHapticDevice *self = FU_ELANTP_HID_HAPTIC_DEVICE(device); + + parent = fu_elantp_haptic_device_get_parent(device, error); + if (parent == NULL) + return FALSE; + + /* reset back to runtime */ + if (!fu_elantp_hid_haptic_device_write_cmd(FU_DEVICE(parent), + ETP_CMD_I2C_IAP_RESET, + ETP_I2C_IAP_RESET, + error)) { + g_prefix_error(error, "cannot reset TP: "); + return FALSE; + } + g_usleep(ELANTP_DELAY_RESET * 1000); + if (!fu_elantp_hid_haptic_device_write_cmd(FU_DEVICE(parent), + ETP_CMD_I2C_IAP_RESET, + ETP_I2C_ENABLE_REPORT, + error)) { + g_prefix_error(error, "cannot enable TP report: "); + return FALSE; + } + if (!fu_elantp_hid_haptic_device_write_cmd(FU_DEVICE(parent), 0x0306, 0x003, error)) { + g_prefix_error(error, "cannot switch to TP PTP mode: "); + return FALSE; + } + if (!fu_elantp_hid_haptic_device_ensure_iap_ctrl(FU_DEVICE(parent), self, error)) + return FALSE; + + /* sanity check */ + if (fu_device_has_flag(device, FWUPD_DEVICE_FLAG_IS_BOOTLOADER)) { + g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_READ, "in bootloader mode"); + return FALSE; + } + + /* success */ + return TRUE; +} + +static gboolean +fu_elantp_hid_haptic_device_set_quirk_kv(FuDevice *device, + const gchar *key, + const gchar *value, + GError **error) +{ + FuElantpHidHapticDevice *self = FU_ELANTP_HID_HAPTIC_DEVICE(device); + guint64 tmp = 0; + + if (g_strcmp0(key, "ElantpIcPageCount") == 0) { + if (!fu_strtoull(value, &tmp, 0, G_MAXUINT16, error)) + return FALSE; + self->ic_page_count = (guint16)tmp; + return TRUE; + } + if (g_strcmp0(key, "ElantpIapPassword") == 0) { + if (!fu_strtoull(value, &tmp, 0, G_MAXUINT16, error)) + return FALSE; + self->iap_password = (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_hid_haptic_device_set_progress(FuDevice *self, FuProgress *progress) +{ + fu_progress_set_id(progress, G_STRLOC); + fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_RESTART, 5, "detach"); + fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_WRITE, 85, "write"); + fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_RESTART, 2, "attach"); + fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_BUSY, 8, "reload"); +} + +static void +fu_elantp_hid_haptic_device_init(FuElantpHidHapticDevice *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_add_internal_flag(FU_DEVICE(self), FU_DEVICE_INTERNAL_FLAG_USE_PARENT_FOR_OPEN); + fu_device_add_icon(FU_DEVICE(self), "input-touchpad"); + fu_device_add_protocol(FU_DEVICE(self), "tw.com.emc.elantp.haptic"); + fu_device_set_name(FU_DEVICE(self), "HapticPad EEPROM"); + fu_device_set_logical_id(FU_DEVICE(self), "eeprom"); + fu_device_set_version_format(FU_DEVICE(self), FWUPD_VERSION_FORMAT_PLAIN); + fu_device_set_priority(FU_DEVICE(self), 1); /* better than i2c */ +} + +static void +fu_elantp_hid_haptic_device_class_init(FuElantpHidHapticDeviceClass *klass) +{ + FuDeviceClass *klass_device = FU_DEVICE_CLASS(klass); + klass_device->to_string = fu_elantp_hid_haptic_device_to_string; + klass_device->attach = fu_elantp_hid_haptic_device_attach; + klass_device->set_quirk_kv = fu_elantp_hid_haptic_device_set_quirk_kv; + klass_device->setup = fu_elantp_hid_haptic_device_setup; + klass_device->reload = fu_elantp_hid_haptic_device_setup; + klass_device->write_firmware = fu_elantp_hid_haptic_device_write_firmware; + klass_device->prepare_firmware = fu_elantp_hid_haptic_device_prepare_firmware; + klass_device->probe = fu_elantp_hid_haptic_device_probe; + klass_device->set_progress = fu_elantp_hid_haptic_device_set_progress; +} + +FuElantpHidHapticDevice * +fu_elantp_haptic_device_new(FuDevice *device) +{ + FuElantpHidHapticDevice *self; + self = g_object_new(FU_TYPE_ELANTP_HID_HAPTIC_DEVICE, NULL); + return FU_ELANTP_HID_HAPTIC_DEVICE(self); +} diff --git a/plugins/elantp/fu-elantp-hid-haptic-device.h b/plugins/elantp/fu-elantp-hid-haptic-device.h new file mode 100644 index 000000000..974905874 --- /dev/null +++ b/plugins/elantp/fu-elantp-hid-haptic-device.h @@ -0,0 +1,21 @@ +/* + * Copyright (C) 2022 Jingle Wu + * + * SPDX-License-Identifier: LGPL-2.1+ + */ + +#pragma once + +#include + +#include "fu-elantp-hid-device.h" + +#define FU_TYPE_ELANTP_HID_HAPTIC_DEVICE (fu_elantp_hid_haptic_device_get_type()) +G_DECLARE_FINAL_TYPE(FuElantpHidHapticDevice, + fu_elantp_hid_haptic_device, + FU, + ELANTP_HID_HAPTIC_DEVICE, + FuUdevDevice) + +FuElantpHidHapticDevice * +fu_elantp_haptic_device_new(FuDevice *device); diff --git a/plugins/elantp/fu-elantp-i2c-device.c b/plugins/elantp/fu-elantp-i2c-device.c index 04015cbd4..add8d328c 100644 --- a/plugins/elantp/fu-elantp-i2c-device.c +++ b/plugins/elantp/fu-elantp-i2c-device.c @@ -807,6 +807,7 @@ fu_elantp_i2c_device_init(FuElantpI2cDevice *self) fu_device_add_icon(FU_DEVICE(self), "input-touchpad"); fu_device_add_protocol(FU_DEVICE(self), "tw.com.emc.elantp"); fu_device_set_version_format(FU_DEVICE(self), FWUPD_VERSION_FORMAT_HEX); + fu_device_set_vendor(FU_DEVICE(self), "ELAN Microelectronics"); fu_udev_device_set_flags(FU_UDEV_DEVICE(self), FU_UDEV_DEVICE_FLAG_OPEN_READ | FU_UDEV_DEVICE_FLAG_OPEN_WRITE); fu_device_register_private_flag(FU_DEVICE(self), diff --git a/plugins/elantp/meson.build b/plugins/elantp/meson.build index 446f0247b..4fde3648c 100644 --- a/plugins/elantp/meson.build +++ b/plugins/elantp/meson.build @@ -6,8 +6,10 @@ plugin_builtin_elantp = static_library('fu_plugin_elantp', sources: [ 'fu-elantp-plugin.c', 'fu-elantp-firmware.c', # fuzzing + 'fu-elantp-haptic-firmware.c', 'fu-elantp-hid-device.c', 'fu-elantp-i2c-device.c', + 'fu-elantp-hid-haptic-device.c', ], include_directories: plugin_incdirs, c_args: [