/*
 * Copyright (C) 2012 Andrew Duggan
 * Copyright (C) 2012 Synaptics Inc.
 * Copyright (C) 2019 Richard Hughes <richard@hughsie.com>
 *
 * SPDX-License-Identifier: LGPL-2.1+
 */

#include "config.h"

#include <fwupdplugin.h>

#include "fu-synaptics-rmi-v7-device.h"

#define RMI_F34_ERASE_WAIT_MS 10000 /* ms */

typedef enum {
	RMI_FLASH_CMD_IDLE = 0x00,
	RMI_FLASH_CMD_ENTER_BL,
	RMI_FLASH_CMD_READ,
	RMI_FLASH_CMD_WRITE,
	RMI_FLASH_CMD_ERASE,
	RMI_FLASH_CMD_ERASE_AP,
	RMI_FLASH_CMD_SENSOR_ID,
} RmiFlashCommand;

typedef enum {
	RMI_PARTITION_ID_NONE = 0x00,
	RMI_PARTITION_ID_BOOTLOADER = 0x01,
	RMI_PARTITION_ID_DEVICE_CONFIG,
	RMI_PARTITION_ID_FLASH_CONFIG,
	RMI_PARTITION_ID_MANUFACTURING_BLOCK,
	RMI_PARTITION_ID_GUEST_SERIALIZATION,
	RMI_PARTITION_ID_GLOBAL_PARAMETERS,
	RMI_PARTITION_ID_CORE_CODE,
	RMI_PARTITION_ID_CORE_CONFIG,
	RMI_PARTITION_ID_GUEST_CODE,
	RMI_PARTITION_ID_DISPLAY_CONFIG,
	RMI_PARTITION_ID_EXTERNAL_TOUCH_AFE_CONFIG,
	RMI_PARTITION_ID_UTILITY_PARAMETER,
} RmiPartitionId;

static const gchar *
rmi_firmware_partition_id_to_string(RmiPartitionId partition_id)
{
	if (partition_id == RMI_PARTITION_ID_NONE)
		return "none";
	if (partition_id == RMI_PARTITION_ID_BOOTLOADER)
		return "bootloader";
	if (partition_id == RMI_PARTITION_ID_DEVICE_CONFIG)
		return "device-config";
	if (partition_id == RMI_PARTITION_ID_FLASH_CONFIG)
		return "flash-config";
	if (partition_id == RMI_PARTITION_ID_MANUFACTURING_BLOCK)
		return "manufacturing-block";
	if (partition_id == RMI_PARTITION_ID_GUEST_SERIALIZATION)
		return "guest-serialization";
	if (partition_id == RMI_PARTITION_ID_GLOBAL_PARAMETERS)
		return "global-parameters";
	if (partition_id == RMI_PARTITION_ID_CORE_CODE)
		return "core-code";
	if (partition_id == RMI_PARTITION_ID_CORE_CONFIG)
		return "core-config";
	if (partition_id == RMI_PARTITION_ID_GUEST_CODE)
		return "guest-code";
	if (partition_id == RMI_PARTITION_ID_DISPLAY_CONFIG)
		return "display-config";
	if (partition_id == RMI_PARTITION_ID_EXTERNAL_TOUCH_AFE_CONFIG)
		return "external-touch-afe-config";
	if (partition_id == RMI_PARTITION_ID_UTILITY_PARAMETER)
		return "utility-parameter";
	return NULL;
}

gboolean
fu_synaptics_rmi_v7_device_detach(FuDevice *device, FuProgress *progress, GError **error)
{
	FuSynapticsRmiDevice *self = FU_SYNAPTICS_RMI_DEVICE(device);
	g_autoptr(GByteArray) enable_req = g_byte_array_new();
	FuSynapticsRmiFlash *flash = fu_synaptics_rmi_device_get_flash(self);
	FuSynapticsRmiFunction *f34;

	/* f34 */
	f34 = fu_synaptics_rmi_device_get_function(self, 0x34, error);
	if (f34 == NULL)
		return FALSE;

	/* disable interrupts */
	if (!fu_synaptics_rmi_device_disable_irqs(self, error))
		return FALSE;

	/* enter BL */
	fu_byte_array_append_uint8(enable_req, RMI_PARTITION_ID_BOOTLOADER);
	fu_byte_array_append_uint32(enable_req, 0x0, G_LITTLE_ENDIAN);
	fu_byte_array_append_uint8(enable_req, RMI_FLASH_CMD_ENTER_BL);
	fu_byte_array_append_uint8(enable_req, flash->bootloader_id[0]);
	fu_byte_array_append_uint8(enable_req, flash->bootloader_id[1]);
	if (!fu_synaptics_rmi_device_write(self,
					   f34->data_base + 1,
					   enable_req,
					   FU_SYNAPTICS_RMI_DEVICE_FLAG_NONE,
					   error)) {
		g_prefix_error(error, "failed to enable programming: ");
		return FALSE;
	}

	/* wait for idle */
	if (!fu_synaptics_rmi_device_wait_for_idle(self,
						   RMI_F34_ENABLE_WAIT_MS,
						   RMI_DEVICE_WAIT_FOR_IDLE_FLAG_NONE,
						   error))
		return FALSE;
	if (!fu_synaptics_rmi_device_poll_wait(self, error))
		return FALSE;
	g_usleep(1000 * RMI_F34_ENABLE_WAIT_MS);
	return TRUE;
}

static gboolean
fu_synaptics_rmi_v7_device_erase_all(FuSynapticsRmiDevice *self, GError **error)
{
	FuSynapticsRmiFunction *f34;
	FuSynapticsRmiFlash *flash = fu_synaptics_rmi_device_get_flash(self);
	g_autoptr(GByteArray) erase_cmd = g_byte_array_new();

	/* f34 */
	f34 = fu_synaptics_rmi_device_get_function(self, 0x34, error);
	if (f34 == NULL)
		return FALSE;

	fu_byte_array_append_uint8(erase_cmd, RMI_PARTITION_ID_CORE_CODE);
	fu_byte_array_append_uint32(erase_cmd, 0x0, G_LITTLE_ENDIAN);
	if (flash->bootloader_id[1] == 8) {
		/* For bootloader v8 */
		fu_byte_array_append_uint8(erase_cmd, RMI_FLASH_CMD_ERASE_AP);
	} else {
		/* For bootloader v7 */
		fu_byte_array_append_uint8(erase_cmd, RMI_FLASH_CMD_ERASE);
	}
	fu_byte_array_append_uint8(erase_cmd, flash->bootloader_id[0]);
	fu_byte_array_append_uint8(erase_cmd, flash->bootloader_id[1]);
	/* for BL8 device, we need hold 1 seconds after querying F34 status to
	 * avoid not get attention by following giving erase command */
	if (flash->bootloader_id[1] == 8)
		g_usleep(1000 * 1000);
	if (!fu_synaptics_rmi_device_write(self,
					   f34->data_base + 1,
					   erase_cmd,
					   FU_SYNAPTICS_RMI_DEVICE_FLAG_NONE,
					   error)) {
		g_prefix_error(error, "failed to unlock erasing: ");
		return FALSE;
	}
	g_usleep(1000 * 100);
	if (flash->bootloader_id[1] == 8) {
		/* wait for ATTN */
		if (!fu_synaptics_rmi_device_wait_for_idle(self,
							   RMI_F34_ERASE_WAIT_MS,
							   RMI_DEVICE_WAIT_FOR_IDLE_FLAG_NONE,
							   error)) {
			g_prefix_error(error, "failed to wait for idle: ");
			return FALSE;
		}
	}
	if (!fu_synaptics_rmi_device_poll_wait(self, error)) {
		g_prefix_error(error, "failed to get flash success: ");
		return FALSE;
	}

	/* for BL7, we need erase config partition */
	if (flash->bootloader_id[1] == 7) {
		g_autoptr(GByteArray) erase_config_cmd = g_byte_array_new();

		fu_byte_array_append_uint8(erase_config_cmd, RMI_PARTITION_ID_CORE_CONFIG);
		fu_byte_array_append_uint32(erase_config_cmd, 0x0, G_LITTLE_ENDIAN);
		fu_byte_array_append_uint8(erase_config_cmd, RMI_FLASH_CMD_ERASE);

		g_usleep(1000 * 100);
		if (!fu_synaptics_rmi_device_write(self,
						   f34->data_base + 1,
						   erase_config_cmd,
						   FU_SYNAPTICS_RMI_DEVICE_FLAG_NONE,
						   error)) {
			g_prefix_error(error, "failed to erase core config: ");
			return FALSE;
		}

		/* wait for ATTN */
		g_usleep(1000 * 100);
		if (!fu_synaptics_rmi_device_wait_for_idle(
			self,
			RMI_F34_ERASE_WAIT_MS,
			RMI_DEVICE_WAIT_FOR_IDLE_FLAG_REFRESH_F34,
			error)) {
			g_prefix_error(error, "failed to wait for idle: ");
			return FALSE;
		}
		if (!fu_synaptics_rmi_device_poll_wait(self, error)) {
			g_prefix_error(error, "failed to get flash success: ");
			return FALSE;
		}
	}
	return TRUE;
}

static gboolean
fu_synaptics_rmi_v7_device_write_blocks(FuSynapticsRmiDevice *self,
					guint32 address,
					const guint8 *data,
					guint32 datasz,
					GError **error)
{
	FuSynapticsRmiFlash *flash = fu_synaptics_rmi_device_get_flash(self);
	g_autoptr(GPtrArray) chunks = NULL;

	/* write FW blocks */
	chunks = fu_chunk_array_new(data,
				    datasz,
				    0x00, /* start addr */
				    0x00, /* page_sz */
				    flash->block_size);
	for (guint i = 0; i < chunks->len; i++) {
		FuChunk *chk = g_ptr_array_index(chunks, i);
		g_autoptr(GByteArray) req = g_byte_array_new();
		g_byte_array_append(req, fu_chunk_get_data(chk), fu_chunk_get_data_sz(chk));
		if (!fu_synaptics_rmi_device_write(self,
						   address,
						   req,
						   FU_SYNAPTICS_RMI_DEVICE_FLAG_NONE,
						   error)) {
			g_prefix_error(error,
				       "failed to write block @0x%x:%x: ",
				       address,
				       fu_chunk_get_address(chk));
			return FALSE;
		}
	}

	/* wait for idle */
	if (!fu_synaptics_rmi_device_wait_for_idle(self,
						   RMI_F34_IDLE_WAIT_MS,
						   RMI_DEVICE_WAIT_FOR_IDLE_FLAG_NONE,
						   error)) {
		g_prefix_error(error, "failed to wait for idle @0x%x: ", address);
		return FALSE;
	}

	/* success */
	return TRUE;
}

static gboolean
fu_synaptics_rmi_v7_device_write_partition(FuSynapticsRmiDevice *self,
					   RmiPartitionId partition_id,
					   GBytes *bytes,
					   FuProgress *progress,
					   GError **error)
{
	FuSynapticsRmiFunction *f34;
	FuSynapticsRmiFlash *flash = fu_synaptics_rmi_device_get_flash(self);
	g_autoptr(GByteArray) req_offset = g_byte_array_new();
	g_autoptr(GByteArray) req_partition_id = g_byte_array_new();
	g_autoptr(GPtrArray) chunks = NULL;

	/* f34 */
	f34 = fu_synaptics_rmi_device_get_function(self, 0x34, error);
	if (f34 == NULL)
		return FALSE;

	/* write partition id */
	g_debug("writing partition %s…", rmi_firmware_partition_id_to_string(partition_id));
	fu_byte_array_append_uint8(req_partition_id, partition_id);
	if (!fu_synaptics_rmi_device_write(self,
					   f34->data_base + 0x1,
					   req_partition_id,
					   FU_SYNAPTICS_RMI_DEVICE_FLAG_NONE,
					   error)) {
		g_prefix_error(error, "failed to write flash partition: ");
		return FALSE;
	}
	fu_byte_array_append_uint16(req_offset, 0x0, G_LITTLE_ENDIAN);
	if (!fu_synaptics_rmi_device_write(self,
					   f34->data_base + 0x2,
					   req_offset,
					   FU_SYNAPTICS_RMI_DEVICE_FLAG_NONE,
					   error)) {
		g_prefix_error(error, "failed to write offset: ");
		return FALSE;
	}

	/* write partition */
	chunks =
	    fu_chunk_array_new_from_bytes(bytes,
					  0x00, /* start addr */
					  0x00, /* page_sz */
					  (gsize)flash->payload_length * (gsize)flash->block_size);
	for (guint i = 0; i < chunks->len; i++) {
		FuChunk *chk = g_ptr_array_index(chunks, i);
		g_autoptr(GByteArray) req_trans_sz = g_byte_array_new();
		g_autoptr(GByteArray) req_cmd = g_byte_array_new();
		fu_byte_array_append_uint16(req_trans_sz,
					    fu_chunk_get_data_sz(chk) / flash->block_size,
					    G_LITTLE_ENDIAN);
		if (!fu_synaptics_rmi_device_write(self,
						   f34->data_base + 0x3,
						   req_trans_sz,
						   FU_SYNAPTICS_RMI_DEVICE_FLAG_NONE,
						   error)) {
			g_prefix_error(error, "failed to write transfer length: ");
			return FALSE;
		}
		fu_byte_array_append_uint8(req_cmd, RMI_FLASH_CMD_WRITE);
		if (!fu_synaptics_rmi_device_write(self,
						   f34->data_base + 0x4,
						   req_cmd,
						   FU_SYNAPTICS_RMI_DEVICE_FLAG_NONE,
						   error)) {
			g_prefix_error(error, "failed to flash command: ");
			return FALSE;
		}
		if (!fu_synaptics_rmi_v7_device_write_blocks(self,
							     f34->data_base + 0x5,
							     fu_chunk_get_data(chk),
							     fu_chunk_get_data_sz(chk),
							     error))
			return FALSE;
		fu_progress_set_percentage_full(progress, (gsize)i + 1, (gsize)chunks->len);
	}
	return TRUE;
}

gboolean
fu_synaptics_rmi_v7_device_write_firmware(FuDevice *device,
					  FuFirmware *firmware,
					  FuProgress *progress,
					  FwupdInstallFlags flags,
					  GError **error)
{
	FuSynapticsRmiDevice *self = FU_SYNAPTICS_RMI_DEVICE(device);
	FuSynapticsRmiFlash *flash = fu_synaptics_rmi_device_get_flash(self);
	FuSynapticsRmiFunction *f34;
	g_autoptr(GBytes) bytes_bin = NULL;
	g_autoptr(GBytes) bytes_cfg = NULL;
	g_autoptr(GBytes) bytes_flashcfg = NULL;

	/* progress */
	fu_progress_set_id(progress, G_STRLOC);
	fu_progress_add_flag(progress, FU_PROGRESS_FLAG_GUESSED);
	fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_BUSY, 1);
	fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_ERASE, 10);
	fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_WRITE, 20);
	fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_WRITE, 20);
	fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_WRITE, 20);

	/* we should be in bootloader mode now, but check anyway */
	if (!fu_device_has_flag(device, FWUPD_DEVICE_FLAG_IS_BOOTLOADER)) {
		g_set_error_literal(error,
				    FWUPD_ERROR,
				    FWUPD_ERROR_NOT_SUPPORTED,
				    "not bootloader, perhaps need detach?!");
		return FALSE;
	}

	/* f34 */
	f34 = fu_synaptics_rmi_device_get_function(self, 0x34, error);
	if (f34 == NULL)
		return FALSE;

	/* get both images */
	bytes_bin = fu_firmware_get_image_by_id_bytes(firmware, "ui", error);
	if (bytes_bin == NULL)
		return FALSE;
	bytes_cfg = fu_firmware_get_image_by_id_bytes(firmware, "config", error);
	if (bytes_cfg == NULL)
		return FALSE;
	if (flash->bootloader_id[1] == 8) {
		bytes_flashcfg = fu_firmware_get_image_by_id_bytes(firmware, "flash-config", error);
		if (bytes_flashcfg == NULL)
			return FALSE;
	}

	/* disable powersaving */
	if (!fu_synaptics_rmi_device_disable_sleep(self, error))
		return FALSE;
	fu_progress_step_done(progress);

	/* erase all */
	if (!fu_synaptics_rmi_v7_device_erase_all(self, error)) {
		g_prefix_error(error, "failed to erase all: ");
		return FALSE;
	}
	fu_progress_step_done(progress);

	/* write flash config for v8 */
	if (bytes_flashcfg != NULL) {
		if (!fu_synaptics_rmi_v7_device_write_partition(self,
								RMI_PARTITION_ID_FLASH_CONFIG,
								bytes_flashcfg,
								fu_progress_get_child(progress),
								error))
			return FALSE;
	}
	fu_progress_step_done(progress);

	/* write core code */
	if (!fu_synaptics_rmi_v7_device_write_partition(self,
							RMI_PARTITION_ID_CORE_CODE,
							bytes_bin,
							fu_progress_get_child(progress),
							error))
		return FALSE;
	fu_progress_step_done(progress);

	/* write core config */
	if (!fu_synaptics_rmi_v7_device_write_partition(self,
							RMI_PARTITION_ID_CORE_CONFIG,
							bytes_cfg,
							fu_progress_get_child(progress),
							error))
		return FALSE;
	fu_progress_step_done(progress);

	/* success */
	return TRUE;
}

typedef struct __attribute__((packed)) {
	guint16 partition_id;
	guint16 partition_len;
	guint16 partition_addr;
	guint16 partition_prop;
} RmiPartitionTbl;

G_STATIC_ASSERT(sizeof(RmiPartitionTbl) == 8);

static gboolean
fu_synaptics_rmi_device_read_flash_config_v7(FuSynapticsRmiDevice *self, GError **error)
{
	FuSynapticsRmiFlash *flash = fu_synaptics_rmi_device_get_flash(self);
	FuSynapticsRmiFunction *f34;
	g_autoptr(GByteArray) req_addr_zero = g_byte_array_new();
	g_autoptr(GByteArray) req_cmd = g_byte_array_new();
	g_autoptr(GByteArray) req_partition_id = g_byte_array_new();
	g_autoptr(GByteArray) req_transfer_length = g_byte_array_new();
	g_autoptr(GByteArray) res = NULL;

	/* f34 */
	f34 = fu_synaptics_rmi_device_get_function(self, 0x34, error);
	if (f34 == NULL)
		return FALSE;

	/* set partition id for bootloader 7 */
	fu_byte_array_append_uint8(req_partition_id, RMI_PARTITION_ID_FLASH_CONFIG);
	if (!fu_synaptics_rmi_device_write(self,
					   f34->data_base + 0x1,
					   req_partition_id,
					   FU_SYNAPTICS_RMI_DEVICE_FLAG_NONE,
					   error)) {
		g_prefix_error(error, "failed to write flash partition id: ");
		return FALSE;
	}
	fu_byte_array_append_uint16(req_addr_zero, 0x0, G_LITTLE_ENDIAN);
	if (!fu_synaptics_rmi_device_write(self,
					   f34->data_base + 0x2,
					   req_addr_zero,
					   FU_SYNAPTICS_RMI_DEVICE_FLAG_NONE,
					   error)) {
		g_prefix_error(error, "failed to write flash config address: ");
		return FALSE;
	}

	/* set transfer length */
	fu_byte_array_append_uint16(req_transfer_length, flash->config_length, G_LITTLE_ENDIAN);
	if (!fu_synaptics_rmi_device_write(self,
					   f34->data_base + 0x3,
					   req_transfer_length,
					   FU_SYNAPTICS_RMI_DEVICE_FLAG_NONE,
					   error)) {
		g_prefix_error(error, "failed to set transfer length: ");
		return FALSE;
	}

	/* set command to read */
	fu_byte_array_append_uint8(req_cmd, RMI_FLASH_CMD_READ);
	if (!fu_synaptics_rmi_device_write(self,
					   f34->data_base + 0x4,
					   req_cmd,
					   FU_SYNAPTICS_RMI_DEVICE_FLAG_NONE,
					   error)) {
		g_prefix_error(error, "failed to write command to read: ");
		return FALSE;
	}
	if (!fu_synaptics_rmi_device_poll_wait(self, error)) {
		g_prefix_error(error, "failed to wait: ");
		return FALSE;
	}

	/* read back entire buffer in blocks */
	res =
	    fu_synaptics_rmi_device_read(self,
					 f34->data_base + 0x5,
					 (guint32)flash->block_size * (guint32)flash->config_length,
					 error);
	if (res == NULL) {
		g_prefix_error(error, "failed to read: ");
		return FALSE;
	}

	/* debugging */
	if (g_getenv("FWUPD_SYNAPTICS_RMI_VERBOSE") != NULL) {
		fu_common_dump_full(G_LOG_DOMAIN,
				    "FlashConfig",
				    res->data,
				    res->len,
				    80,
				    FU_DUMP_FLAGS_NONE);
	}

	/* parse the config length */
	for (guint i = 0x2; i < res->len; i += sizeof(RmiPartitionTbl)) {
		RmiPartitionTbl tbl;
		if (!fu_memcpy_safe((guint8 *)&tbl,
				    sizeof(tbl),
				    0x0, /* dst */
				    res->data,
				    res->len,
				    i, /* src */
				    sizeof(tbl),
				    error))
			return FALSE;
		g_debug("found partition %s (0x%02x)",
			rmi_firmware_partition_id_to_string(tbl.partition_id),
			tbl.partition_id);
		if (tbl.partition_id == RMI_PARTITION_ID_CORE_CONFIG) {
			flash->block_count_cfg = tbl.partition_len;
			continue;
		}
		if (tbl.partition_id == RMI_PARTITION_ID_CORE_CODE) {
			flash->block_count_fw = tbl.partition_len;
			continue;
		}
		if (tbl.partition_id == RMI_PARTITION_ID_NONE)
			break;
	}

	/* success */
	return TRUE;
}

gboolean
fu_synaptics_rmi_v7_device_setup(FuSynapticsRmiDevice *self, GError **error)
{
	FuSynapticsRmiFlash *flash = fu_synaptics_rmi_device_get_flash(self);
	FuSynapticsRmiFunction *f34;
	guint8 offset;
	g_autoptr(GByteArray) f34_data0 = NULL;
	g_autoptr(GByteArray) f34_dataX = NULL;

	/* f34 */
	f34 = fu_synaptics_rmi_device_get_function(self, 0x34, error);
	if (f34 == NULL)
		return FALSE;

	f34_data0 = fu_synaptics_rmi_device_read(self, f34->query_base, 1, error);
	if (f34_data0 == NULL) {
		g_prefix_error(error, "failed to read bootloader ID: ");
		return FALSE;
	}
	offset = (f34_data0->data[0] & 0b00000111) + 1;
	f34_dataX = fu_synaptics_rmi_device_read(self, f34->query_base + offset, 21, error);
	if (f34_dataX == NULL)
		return FALSE;
	if (!fu_common_read_uint8_safe(f34_dataX->data,
				       f34_dataX->len,
				       0x0,
				       &flash->bootloader_id[0],
				       error))
		return FALSE;
	if (!fu_common_read_uint8_safe(f34_dataX->data,
				       f34_dataX->len,
				       0x1,
				       &flash->bootloader_id[1],
				       error))
		return FALSE;
	if (!fu_common_read_uint16_safe(f34_dataX->data,
					f34_dataX->len,
					0x07,
					&flash->block_size,
					G_LITTLE_ENDIAN,
					error))
		return FALSE;
	if (!fu_common_read_uint16_safe(f34_dataX->data,
					f34_dataX->len,
					0x0d,
					&flash->config_length,
					G_LITTLE_ENDIAN,
					error))
		return FALSE;
	if (!fu_common_read_uint16_safe(f34_dataX->data,
					f34_dataX->len,
					0x0f,
					&flash->payload_length,
					G_LITTLE_ENDIAN,
					error))
		return FALSE;
	if (!fu_common_read_uint32_safe(f34_dataX->data,
					f34_dataX->len,
					0x02,
					&flash->build_id,
					G_LITTLE_ENDIAN,
					error))
		return FALSE;

	/* sanity check */
	if ((guint32)flash->block_size * (guint32)flash->config_length > G_MAXUINT16) {
		g_set_error(error,
			    FWUPD_ERROR,
			    FWUPD_ERROR_NOT_SUPPORTED,
			    "block size 0x%x or config length 0x%x invalid",
			    flash->block_size,
			    flash->config_length);
		return FALSE;
	}

	/* read flash config */
	return fu_synaptics_rmi_device_read_flash_config_v7(self, error);
}

gboolean
fu_synaptics_rmi_v7_device_query_status(FuSynapticsRmiDevice *self, GError **error)
{
	FuSynapticsRmiFunction *f34;
	guint8 status;
	g_autoptr(GByteArray) f34_data = NULL;

	/* f34 */
	f34 = fu_synaptics_rmi_device_get_function(self, 0x34, error);
	if (f34 == NULL)
		return FALSE;
	f34_data = fu_synaptics_rmi_device_read(self, f34->data_base, 0x1, error);
	if (f34_data == NULL) {
		g_prefix_error(error, "failed to read the f01 data base: ");
		return FALSE;
	}
	status = f34_data->data[0];
	if (status & 0x80) {
		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);
	}
	if (status == 0x01) {
		g_set_error_literal(error,
				    FWUPD_ERROR,
				    FWUPD_ERROR_NOT_SUPPORTED,
				    "operation only supported in bootloader mode");
		return FALSE;
	}
	if (status == 0x02) {
		g_set_error_literal(error,
				    FWUPD_ERROR,
				    FWUPD_ERROR_NOT_SUPPORTED,
				    "partition ID is not supported by the bootloader");
		return FALSE;
	}
	if (status == 0x03) {
		g_set_error_literal(error,
				    FWUPD_ERROR,
				    FWUPD_ERROR_NOT_SUPPORTED,
				    "partition supported, but command not supported");
		return FALSE;
	}
	if (status == 0x04) {
		g_set_error_literal(error,
				    FWUPD_ERROR,
				    FWUPD_ERROR_INVALID_FILE,
				    "invalid block offset");
		return FALSE;
	}
	if (status == 0x05) {
		g_set_error_literal(error,
				    FWUPD_ERROR,
				    FWUPD_ERROR_INVALID_FILE,
				    "invalid transfer");
		return FALSE;
	}
	if (status == 0x06) {
		g_set_error_literal(error,
				    FWUPD_ERROR,
				    FWUPD_ERROR_NOT_SUPPORTED,
				    "partition has not been erased");
		return FALSE;
	}
	if (status == 0x07) {
		g_set_error_literal(error,
				    FWUPD_ERROR,
				    FWUPD_ERROR_SIGNATURE_INVALID,
				    "flash programming key incorrect");
		return FALSE;
	}
	if (status == 0x08) {
		g_set_error_literal(error,
				    FWUPD_ERROR,
				    FWUPD_ERROR_INTERNAL,
				    "bad partition table");
		return FALSE;
	}
	if (status == 0x09) {
		g_set_error_literal(error,
				    FWUPD_ERROR,
				    FWUPD_ERROR_INVALID_FILE,
				    "transfer checksum failed");
		return FALSE;
	}
	if (status == 0x1f) {
		g_set_error_literal(error,
				    FWUPD_ERROR,
				    FWUPD_ERROR_INVALID_FILE,
				    "flash hardware failure");
		return FALSE;
	}
	return TRUE;
}
