diff mbox

[RFC,6/17] input: RMI4 firmware update

Message ID 1345241877-16200-7-git-send-email-cheiny@synaptics.com (mailing list archive)
State New, archived
Headers show

Commit Message

Christopher Heiny Aug. 17, 2012, 10:17 p.m. UTC
Signed-off-by: Christopher Heiny <cheiny@synaptics.com>

Cc: Dmitry Torokhov <dmitry.torokhov@gmail.com>
Cc: Linus Walleij <linus.walleij@stericsson.com>
Cc: Naveen Kumar Gaddipati <naveen.gaddipati@stericsson.com>
Cc: Joeri de Gram <j.de.gram@gmail.com>

Acked-by: Jean Delvare <khali@linux-fr.org>

---

 drivers/input/rmi4/rmi_fw_update.c |  724 ++++++++++++++++++++++++++++++++++++
 1 files changed, 724 insertions(+), 0 deletions(-)

--
To unsubscribe from this list: send the line "unsubscribe linux-input" in
the body of a message to majordomo@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html

Comments

Linus Walleij Aug. 27, 2012, 9:01 p.m. UTC | #1
On Fri, Aug 17, 2012 at 3:17 PM, Christopher Heiny <cheiny@synaptics.com> wrote:

No commit message. Describe exactly what this feature is for and how
it works and how it differs from standard kernel firmware loading.
(Where the kernel loads firmware into devices at boot time.)
i.e. mention that all devices have their own flash memory etc.

> +#define DEBUG

No, use previously describe Kconfig approach.

> +#define RIM_HACK 1

Que ce que c'est?

Should this also be a Kconfig, or mayble always enabled simply,
and detected at runtime if need be?

(...)
> +#define HAS_BSR_MASK 0x20

"Has*" sounds like something boolean, I guess this bit tells if you
have BSR, so say:

#include <linux/bitops.h>

#define HAS_BSR BIT(5)

> +#define PRODUCT_ID_OFFSET 0x10
> +#define PRODUCT_ID_SIZE 10
> +#define PRODUCT_INFO_OFFSET 0x1E
> +#define PRODUCT_INFO_SIZE 2

It's really nice that you have this info in the product.

> +/** Image file V5, Option 0

Pls actually add some kerneldoc here :-)
This is malformed as it looks now, anything following /** will be parsed.

> + */
> +struct image_header {
> +       u32 checksum;
> +       unsigned int image_size;

Should this be size_t?

> +       unsigned int config_size;

size_t?

> +       unsigned char options;

u8? Some kind of enum? (If it's a bitfield use u8.)

> +       unsigned char bootloader_version;

u8?

> +       u8 product_id[RMI_PRODUCT_ID_LENGTH + 1];
> +       unsigned char product_info[PRODUCT_INFO_SIZE];

u8[]?

> +};
> +
> +static u32 extract_u32(const u8 *ptr)
> +{
> +       return (u32)ptr[0] +
> +               (u32)ptr[1] * 0x100 +
> +               (u32)ptr[2] * 0x10000 +
> +               (u32)ptr[3] * 0x1000000;

This looks very dangerous from an endianness point of view.
If you run this driver in a big endian system, what happens?

> +}
> +
> +struct reflash_data {

This would be nice to kerneldoc.

> +       struct rmi_device *rmi_dev;
> +       struct pdt_entry *f01_pdt;
> +       union f01_basic_queries f01_queries;
> +       union f01_device_control_0 f01_controls;
> +       char product_id[RMI_PRODUCT_ID_LENGTH+1];
> +       struct pdt_entry *f34_pdt;
> +       u8 bootloader_id[2];
> +       union f34_query_regs f34_queries;
> +       union f34_control_status f34_controls;
> +       const u8 *firmware_data;
> +       const u8 *config_data;
> +};
> +
> +/* If this parameter is true, we will update the firmware regardless of
> + * the versioning info.
> + */
> +static bool force = 1;

= true;

> +module_param(force, bool, S_IRUGO | S_IWUSR);
> +MODULE_PARM_DESC(param, "Force reflash of RMI4 devices");

Looks reasonable...

> +
> +/* If this parameter is not NULL, we'll use that name for the firmware image,
> + * instead of getting it from the F01 queries.
> + */
> +static char *img_name;
> +module_param(img_name, charp, S_IRUGO | S_IWUSR);
> +MODULE_PARM_DESC(param, "Name of the RMI4 firmware image");

Is there a default name?

(...)
> +#define MIN_SLEEP_TIME_US 50
> +#define MAX_SLEEP_TIME_US 100
> +
> +/* Wait until the status is idle and we're ready to continue */
> +static int wait_for_idle(struct reflash_data *data, int timeout_ms)
> +{
> +       int timeout_count = ((timeout_ms * 1000) / MAX_SLEEP_TIME_US) + 1;

Use DIV_ROUND_UP() from <linux/kernel.h>:

= DIV_ROUND_UP((timeout_ms * 1000), MAX_SLEEP_TIME_US);

> +       int count = 0;

Consider just int i;

(...)
> +static int read_f34_queries(struct reflash_data *data)
> +{
> +       int retval;
> +       u8 id_str[3];

If it's a string it should be char id_str[3]; right?

(...)
> +#ifdef DEBUG
> +       dev_info(&data->rmi_dev->dev, "Got F34 data->f34_queries.\n");

This is equivalent to using dev_dbg() so use that.

> +       dev_info(&data->rmi_dev->dev, "F34 bootloader id: %s (%#04x %#04x)\n",
> +                id_str, data->bootloader_id[0], data->bootloader_id[1]);
> +       dev_info(&data->rmi_dev->dev, "F34 has config id: %d\n",
> +                data->f34_queries.has_config_id);
> +       dev_info(&data->rmi_dev->dev, "F34 unlocked:      %d\n",
> +                data->f34_queries.unlocked);
> +       dev_info(&data->rmi_dev->dev, "F34 regMap:        %d\n",
> +                data->f34_queries.reg_map);
> +       dev_info(&data->rmi_dev->dev, "F34 block size:    %d\n",
> +                data->f34_queries.block_size);
> +       dev_info(&data->rmi_dev->dev, "F34 fw blocks:     %d\n",
> +                data->f34_queries.fw_block_count);
> +       dev_info(&data->rmi_dev->dev, "F34 config blocks: %d\n",
> +                data->f34_queries.config_block_count);

Dito.

(...)
> +static int enter_flash_programming(struct reflash_data *data)
> +{
> +       int retval;
> +       union f01_device_status device_status;
> +       struct rmi_device *rmi_dev = data->rmi_dev;
> +
> +       retval = write_bootloader_id(data);
> +       if (retval < 0)
> +               return retval;
> +
> +       dev_info(&rmi_dev->dev, "Enabling flash programming.\n");
> +       retval = write_f34_command(data, F34_ENABLE_FLASH_PROG);
> +       if (retval < 0)
> +               return retval;
> +
> +#if    RIM_HACK
> +       data->f01_controls.nosleep = true;
> +       retval = write_f01_controls(data);
> +       if (retval < 0)
> +               return retval;
> +#endif

So just default-enable this since it's defined to 1? BTW what is an RIM?

(...)
> +       dev_info(&rmi_dev->dev, "HOORAY! Programming is enabled!\n");

I always enjoy it when the kernel is happy ;-)

(...)
> +static int write_firmware(struct reflash_data *data)
> +{
> +       return write_blocks(data, (u8 *) data->firmware_data,
> +               data->f34_queries.fw_block_count, F34_WRITE_FW_BLOCK);
> +}
> +
> +static int write_configuration(struct reflash_data *data)
> +{
> +       return write_blocks(data, (u8 *) data->config_data,
> +               data->f34_queries.config_block_count, F34_WRITE_CONFIG_BLOCK);
> +}

Now there are these one-function call functions again, are you sure you
can't just inline these?

> +static void reflash_firmware(struct reflash_data *data)
> +{
> +#ifdef DEBUG
> +       struct timespec start;
> +       struct timespec end;
> +       s64 duration_ns;
> +#endif

This actually seems to be a valid case for #ifdef:ing!

> +       int retval = 0;
> +
> +       retval = enter_flash_programming(data);
> +       if (retval)
> +               return;
> +
> +       retval = write_bootloader_id(data);
> +       if (retval)
> +               return;
> +
> +#ifdef DEBUG
> +       dev_info(&data->rmi_dev->dev, "Erasing FW...\n");

dev_dbg() includes the DEBUG flag.

> +       getnstimeofday(&start);

But this is a

> +#endif
> +       retval = write_f34_command(data, F34_ERASE_ALL);
> +       if (retval)
> +               return;
> +
> +       retval = wait_for_idle(data, F34_ERASE_WAIT_MS);
> +       if (retval) {
> +               dev_err(&data->rmi_dev->dev,
> +                       "Failed to reach idle state. Code: %d.\n", retval);
> +               return;
> +       }
> +#ifdef DEBUG
> +       getnstimeofday(&end);
> +       duration_ns = timespec_to_ns(&end) - timespec_to_ns(&start);
> +       dev_info(&data->rmi_dev->dev,
> +                "Erase complete, time: %lld ns.\n", duration_ns);

dev_dbg()

> +#endif
> +
> +       if (data->firmware_data) {
> +#ifdef DEBUG
> +               dev_info(&data->rmi_dev->dev, "Writing firmware...\n");
> +               getnstimeofday(&start);
> +#endif
> +               retval = write_firmware(data);
> +               if (retval)
> +                       return;
> +#ifdef DEBUG
> +               getnstimeofday(&end);
> +               duration_ns = timespec_to_ns(&end) - timespec_to_ns(&start);
> +               dev_info(&data->rmi_dev->dev,
> +                        "Done writing FW, time: %lld ns.\n", duration_ns);
> +#endif
> +       }
> +
> +       if (data->config_data) {
> +#ifdef DEBUG
> +               dev_info(&data->rmi_dev->dev, "Writing configuration...\n");
> +               getnstimeofday(&start);
> +#endif
> +               retval = write_configuration(data);
> +               if (retval)
> +                       return;
> +#ifdef DEBUG
> +               getnstimeofday(&end);
> +               duration_ns = timespec_to_ns(&end) - timespec_to_ns(&start);
> +               dev_info(&data->rmi_dev->dev,
> +                        "Done writing config, time: %lld ns.\n", duration_ns);
> +#endif
> +       }
> +}

Hard to avoid #ifdef:ing here, but maybe you could use some stub functions.

(...)
> +void rmi4_fw_update(struct rmi_device *rmi_dev,
> +               struct pdt_entry *f01_pdt, struct pdt_entry *f34_pdt)
> +{
> +#ifdef DEBUG
> +       struct timespec start;
> +       struct timespec end;
> +       s64 duration_ns;
> +#endif
> +       char firmware_name[RMI_PRODUCT_ID_LENGTH + 12];
> +       const struct firmware *fw_entry = NULL;
> +       int retval;
> +       struct image_header header;
> +       union pdt_properties pdt_props;
> +       struct reflash_data data = {
> +               .rmi_dev = rmi_dev,
> +               .f01_pdt = f01_pdt,
> +               .f34_pdt = f34_pdt,
> +       };
> +       struct rmi_device_platform_data *pdata = to_rmi_platform_data(rmi_dev);
> +
> +       dev_info(&rmi_dev->dev, "%s called.\n", __func__);
> +#ifdef DEBUG
> +       getnstimeofday(&start);
> +#endif
> +
> +       retval = rmi_read(rmi_dev, PDT_PROPERTIES_LOCATION, pdt_props.regs);
> +       if (retval < 0) {
> +               dev_warn(&rmi_dev->dev,
> +                        "Failed to read PDT props at %#06x (code %d). Assuming 0x00.\n",
> +                        PDT_PROPERTIES_LOCATION, retval);
> +       }
> +       if (pdt_props.has_bsr) {
> +               dev_warn(&rmi_dev->dev,
> +                        "Firmware update for LTS not currently supported.\n");
> +               return;
> +       }
> +
> +       retval = read_f01_queries(&data);
> +       if (retval) {
> +               dev_err(&rmi_dev->dev, "F01 queries failed, code = %d.\n",
> +                       retval);
> +               return;
> +       }
> +       retval = read_f34_queries(&data);
> +       if (retval) {
> +               dev_err(&rmi_dev->dev, "F34 queries failed, code = %d.\n",
> +                       retval);
> +               return;
> +       }
> +       if (pdata->firmware_name && strlen(pdata->firmware_name))
> +               snprintf(firmware_name, sizeof(firmware_name), "rmi4/%s.img",
> +                       pdata->firmware_name);

Aha this works out nicely with the firmware name from pdata...

> +       else
> +               snprintf(firmware_name, sizeof(firmware_name), "rmi4/%s.img",
> +                       (img_name && strlen(img_name))
> +                               ? img_name : data.product_id);

And in the other case as module parameter right?

> +       dev_info(&rmi_dev->dev, "Requesting %s.\n", firmware_name);
> +       retval = request_firmware(&fw_entry, firmware_name, &rmi_dev->dev);
> +       if (retval != 0) {
> +               dev_err(&rmi_dev->dev, "Firmware %s not available, code = %d\n",
> +                       firmware_name, retval);
> +               return;
> +       }
> +
> +#ifdef DEBUG

I don't see why this would be #ifdef DEBUG, it seems to be very useful
information
to always have in the dmesg.

> +       dev_info(&rmi_dev->dev, "Got firmware, size: %d.\n", fw_entry->size);

dev_dbg() if you insist to have this as debug only.

> +       extract_header(fw_entry->data, 0, &header);
> +       dev_info(&rmi_dev->dev, "Img checksum:           %#08X\n",
> +                header.checksum);
> +       dev_info(&rmi_dev->dev, "Img image size:         %d\n",
> +                header.image_size);
> +       dev_info(&rmi_dev->dev, "Img config size:        %d\n",
> +                header.config_size);
> +       dev_info(&rmi_dev->dev, "Img bootloader version: %d\n",
> +                header.bootloader_version);
> +       dev_info(&rmi_dev->dev, "Img product id:         %s\n",
> +                header.product_id);
> +       dev_info(&rmi_dev->dev, "Img product info:       %#04x %#04x\n",
> +                header.product_info[0], header.product_info[1]);
> +#endif

Yours,
Linus Walleij
--
To unsubscribe from this list: send the line "unsubscribe linux-input" in
the body of a message to majordomo@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html
diff mbox

Patch

diff --git a/drivers/input/rmi4/rmi_fw_update.c b/drivers/input/rmi4/rmi_fw_update.c
new file mode 100644
index 0000000..7f6c315
--- /dev/null
+++ b/drivers/input/rmi4/rmi_fw_update.c
@@ -0,0 +1,724 @@ 
+/*
+ * Copyright (c) 2012 Synaptics Incorporated
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+#define DEBUG
+
+#define RIM_HACK 1
+
+#include <linux/delay.h>
+#include <linux/firmware.h>
+#include <linux/ihex.h>
+#include <linux/kernel.h>
+#include<linux/moduleparam.h>
+#include <linux/rmi.h>
+#include <linux/time.h>
+#include "rmi_driver.h"
+#include "rmi_f01.h"
+#include "rmi_f34.h"
+
+#define HAS_BSR_MASK 0x20
+
+#define CHECKSUM_OFFSET 0
+#define BOOTLOADER_VERSION_OFFSET 0x07
+#define IMAGE_SIZE_OFFSET 0x08
+#define CONFIG_SIZE_OFFSET 0x0C
+#define PRODUCT_ID_OFFSET 0x10
+#define PRODUCT_ID_SIZE 10
+#define PRODUCT_INFO_OFFSET 0x1E
+#define PRODUCT_INFO_SIZE 2
+
+#define F01_RESET_MASK 0x01
+
+#define ENABLE_WAIT_US (300 * 1000)
+
+/** Image file V5, Option 0
+ */
+struct image_header {
+	u32 checksum;
+	unsigned int image_size;
+	unsigned int config_size;
+	unsigned char options;
+	unsigned char bootloader_version;
+	u8 product_id[RMI_PRODUCT_ID_LENGTH + 1];
+	unsigned char product_info[PRODUCT_INFO_SIZE];
+};
+
+static u32 extract_u32(const u8 *ptr)
+{
+	return (u32)ptr[0] +
+		(u32)ptr[1] * 0x100 +
+		(u32)ptr[2] * 0x10000 +
+		(u32)ptr[3] * 0x1000000;
+}
+
+struct reflash_data {
+	struct rmi_device *rmi_dev;
+	struct pdt_entry *f01_pdt;
+	union f01_basic_queries f01_queries;
+	union f01_device_control_0 f01_controls;
+	char product_id[RMI_PRODUCT_ID_LENGTH+1];
+	struct pdt_entry *f34_pdt;
+	u8 bootloader_id[2];
+	union f34_query_regs f34_queries;
+	union f34_control_status f34_controls;
+	const u8 *firmware_data;
+	const u8 *config_data;
+};
+
+/* If this parameter is true, we will update the firmware regardless of
+ * the versioning info.
+ */
+static bool force = 1;
+module_param(force, bool, S_IRUGO | S_IWUSR);
+MODULE_PARM_DESC(param, "Force reflash of RMI4 devices");
+
+/* If this parameter is not NULL, we'll use that name for the firmware image,
+ * instead of getting it from the F01 queries.
+ */
+static char *img_name;
+module_param(img_name, charp, S_IRUGO | S_IWUSR);
+MODULE_PARM_DESC(param, "Name of the RMI4 firmware image");
+
+#define RMI4_IMAGE_FILE_REV1_OFFSET 30
+#define RMI4_IMAGE_FILE_REV2_OFFSET 31
+#define IMAGE_FILE_CHECKSUM_SIZE 4
+#define FIRMWARE_IMAGE_AREA_OFFSET 0x100
+
+static void extract_header(const u8 *data, int pos, struct image_header *header)
+{
+	header->checksum = extract_u32(&data[pos + CHECKSUM_OFFSET]);
+	header->bootloader_version = data[pos + BOOTLOADER_VERSION_OFFSET];
+	header->image_size = extract_u32(&data[pos + IMAGE_SIZE_OFFSET]);
+	header->config_size = extract_u32(&data[pos + CONFIG_SIZE_OFFSET]);
+	memcpy(header->product_id, &data[pos + PRODUCT_ID_OFFSET],
+	       RMI_PRODUCT_ID_LENGTH);
+	header->product_id[PRODUCT_ID_SIZE] = 0;
+	memcpy(header->product_info, &data[pos + PRODUCT_INFO_OFFSET],
+	       RMI_PRODUCT_ID_LENGTH);
+}
+
+static int rescan_pdt(struct reflash_data *data)
+{
+	int retval;
+	bool f01_found;
+	bool f34_found;
+	struct pdt_entry pdt_entry;
+	int i;
+	struct rmi_device *rmi_dev = data->rmi_dev;
+	struct pdt_entry *f34_pdt = data->f34_pdt;
+	struct pdt_entry *f01_pdt = data->f01_pdt;
+
+	/* Per spec, once we're in reflash we only need to look at the first
+	 * PDT page for potentially changed F01 and F34 information.
+	 */
+	for (i = PDT_START_SCAN_LOCATION; i >= PDT_END_SCAN_LOCATION;
+			i -= sizeof(pdt_entry)) {
+		retval = rmi_read_block(rmi_dev, i, (u8 *)&pdt_entry,
+					sizeof(pdt_entry));
+		if (retval != sizeof(pdt_entry)) {
+			dev_err(&rmi_dev->dev,
+				"Read PDT entry at %#06x failed: %d.\n",
+				i, retval);
+			return retval;
+		}
+
+		if (RMI4_END_OF_PDT(pdt_entry.function_number))
+			break;
+
+		if (pdt_entry.function_number == 0x01) {
+			memcpy(f01_pdt, &pdt_entry, sizeof(pdt_entry));
+			f01_found = true;
+		} else if (pdt_entry.function_number == 0x34) {
+			memcpy(f34_pdt, &pdt_entry, sizeof(pdt_entry));
+			f34_found = true;
+		}
+	}
+
+	if (!f01_found) {
+		dev_err(&rmi_dev->dev, "Failed to find F01 PDT entry.\n");
+		retval = -ENODEV;
+	} else if (!f34_found) {
+		dev_err(&rmi_dev->dev, "Failed to find F34 PDT entry.\n");
+		retval = -ENODEV;
+	} else
+		retval = 0;
+
+	return retval;
+}
+
+static int read_f34_controls(struct reflash_data *data)
+{
+	int retval;
+
+	retval = rmi_read(data->rmi_dev, data->f34_controls.address,
+			  data->f34_controls.regs);
+	if (retval < 0)
+		return retval;
+
+	return 0;
+}
+
+static int read_f01_status(struct reflash_data *data,
+			   union f01_device_status *device_status)
+{
+	int retval;
+
+	retval = rmi_read(data->rmi_dev, data->f01_pdt->data_base_addr,
+			  device_status->regs);
+	if (retval < 0)
+		return retval;
+
+	return 0;
+}
+
+static int read_f01_controls(struct reflash_data *data)
+{
+	int retval;
+
+	retval = rmi_read(data->rmi_dev, data->f01_pdt->control_base_addr,
+			  data->f01_controls.regs);
+	if (retval < 0)
+		return retval;
+	return 0;
+}
+
+static int write_f01_controls(struct reflash_data *data)
+{
+	int retval;
+
+	retval = rmi_write(data->rmi_dev, data->f01_pdt->control_base_addr,
+			  data->f01_controls.regs[0]);
+	if (retval < 0)
+		return retval;
+	return 0;
+}
+
+#define MIN_SLEEP_TIME_US 50
+#define MAX_SLEEP_TIME_US 100
+
+/* Wait until the status is idle and we're ready to continue */
+static int wait_for_idle(struct reflash_data *data, int timeout_ms)
+{
+	int timeout_count = ((timeout_ms * 1000) / MAX_SLEEP_TIME_US) + 1;
+	int count = 0;
+	union f34_control_status *controls = &data->f34_controls;
+	int retval;
+
+	do {
+		if (count || timeout_count == 1)
+			usleep_range(MIN_SLEEP_TIME_US, MAX_SLEEP_TIME_US);
+		retval = read_f34_controls(data);
+		count++;
+		if (retval < 0)
+			continue;
+		else if (IS_IDLE(controls)) {
+			if (!data->f34_controls.program_enabled) {
+				/** This works around a bug in certain device
+				 * firmwares, where the idle state is reached,
+				 * but the program_enabled bit is not yet set.
+				 */
+				dev_warn(&data->rmi_dev->dev, "Yikes!  We're not enabled!\n");
+				msleep(1000);
+				read_f34_controls(data);
+			}
+			return 0;
+		}
+	} while (count < timeout_count);
+
+	dev_err(&data->rmi_dev->dev,
+		"ERROR: Timeout waiting for idle status, last status: %#04x.\n",
+		controls->regs[0]);
+	dev_err(&data->rmi_dev->dev, "Command: %#04x\n", controls->command);
+	dev_err(&data->rmi_dev->dev, "Status:  %#04x\n", controls->status);
+	dev_err(&data->rmi_dev->dev, "Enabled: %d\n",
+			controls->program_enabled);
+	dev_err(&data->rmi_dev->dev, "Idle:    %d\n", IS_IDLE(controls));
+	return -ETIMEDOUT;
+}
+
+
+static int read_f01_queries(struct reflash_data *data)
+{
+	int retval;
+	u16 addr = data->f01_pdt->query_base_addr;
+
+	retval = rmi_read_block(data->rmi_dev, addr, data->f01_queries.regs,
+				ARRAY_SIZE(data->f01_queries.regs));
+	if (retval < 0) {
+		dev_err(&data->rmi_dev->dev,
+			"Failed to read F34 queries (code %d).\n", retval);
+		return retval;
+	}
+	addr += ARRAY_SIZE(data->f01_queries.regs);
+
+	retval = rmi_read_block(data->rmi_dev, addr, data->product_id,
+				RMI_PRODUCT_ID_LENGTH);
+	if (retval < 0) {
+		dev_err(&data->rmi_dev->dev,
+			"Failed to read product ID (code %d).\n", retval);
+		return retval;
+	}
+	data->product_id[RMI_PRODUCT_ID_LENGTH] = 0;
+	dev_info(&data->rmi_dev->dev, "F01 Product id:   %s\n",
+			data->product_id);
+	dev_info(&data->rmi_dev->dev, "F01 product info: %#04x %#04x\n",
+			data->f01_queries.productinfo_1,
+			data->f01_queries.productinfo_2);
+
+	return 0;
+}
+
+static int read_f34_queries(struct reflash_data *data)
+{
+	int retval;
+	u8 id_str[3];
+
+	retval = rmi_read_block(data->rmi_dev, data->f34_pdt->query_base_addr,
+				data->bootloader_id, 2);
+	if (retval < 0) {
+		dev_err(&data->rmi_dev->dev,
+			"Failed to read F34 bootloader_id (code %d).\n",
+			retval);
+		return retval;
+	}
+	retval = rmi_read_block(data->rmi_dev, data->f34_pdt->query_base_addr+2,
+			data->f34_queries.regs,
+			ARRAY_SIZE(data->f34_queries.regs));
+	if (retval < 0) {
+		dev_err(&data->rmi_dev->dev,
+			"Failed to read F34 queries (code %d).\n", retval);
+		return retval;
+	}
+	data->f34_queries.block_size =
+			le16_to_cpu(data->f34_queries.block_size);
+	data->f34_queries.fw_block_count =
+			le16_to_cpu(data->f34_queries.fw_block_count);
+	data->f34_queries.config_block_count =
+			le16_to_cpu(data->f34_queries.config_block_count);
+	id_str[0] = data->bootloader_id[0];
+	id_str[1] = data->bootloader_id[1];
+	id_str[2] = 0;
+#ifdef DEBUG
+	dev_info(&data->rmi_dev->dev, "Got F34 data->f34_queries.\n");
+	dev_info(&data->rmi_dev->dev, "F34 bootloader id: %s (%#04x %#04x)\n",
+		 id_str, data->bootloader_id[0], data->bootloader_id[1]);
+	dev_info(&data->rmi_dev->dev, "F34 has config id: %d\n",
+		 data->f34_queries.has_config_id);
+	dev_info(&data->rmi_dev->dev, "F34 unlocked:      %d\n",
+		 data->f34_queries.unlocked);
+	dev_info(&data->rmi_dev->dev, "F34 regMap:        %d\n",
+		 data->f34_queries.reg_map);
+	dev_info(&data->rmi_dev->dev, "F34 block size:    %d\n",
+		 data->f34_queries.block_size);
+	dev_info(&data->rmi_dev->dev, "F34 fw blocks:     %d\n",
+		 data->f34_queries.fw_block_count);
+	dev_info(&data->rmi_dev->dev, "F34 config blocks: %d\n",
+		 data->f34_queries.config_block_count);
+#endif
+
+	data->f34_controls.address = data->f34_pdt->data_base_addr +
+			F34_BLOCK_DATA_OFFSET + data->f34_queries.block_size;
+
+	return 0;
+}
+
+static int write_bootloader_id(struct reflash_data *data)
+{
+	int retval;
+	struct rmi_device *rmi_dev = data->rmi_dev;
+	struct pdt_entry *f34_pdt = data->f34_pdt;
+
+	retval = rmi_write_block(rmi_dev,
+			f34_pdt->data_base_addr + F34_BLOCK_DATA_OFFSET,
+			data->bootloader_id, ARRAY_SIZE(data->bootloader_id));
+	if (retval < 0) {
+		dev_err(&rmi_dev->dev,
+			"Failed to write bootloader ID. Code: %d.\n", retval);
+		return retval;
+	}
+
+	return 0;
+}
+
+static int write_f34_command(struct reflash_data *data, u8 command)
+{
+	int retval;
+	struct rmi_device *rmi_dev = data->rmi_dev;
+
+	retval = rmi_write(rmi_dev, data->f34_controls.address, command);
+	if (retval < 0) {
+		dev_err(&rmi_dev->dev,
+			"Failed to write F34 command %#04x. Code: %d.\n",
+			command, retval);
+		return retval;
+	}
+
+	return 0;
+}
+
+static int enter_flash_programming(struct reflash_data *data)
+{
+	int retval;
+	union f01_device_status device_status;
+	struct rmi_device *rmi_dev = data->rmi_dev;
+
+	retval = write_bootloader_id(data);
+	if (retval < 0)
+		return retval;
+
+	dev_info(&rmi_dev->dev, "Enabling flash programming.\n");
+	retval = write_f34_command(data, F34_ENABLE_FLASH_PROG);
+	if (retval < 0)
+		return retval;
+
+#if	RIM_HACK
+	data->f01_controls.nosleep = true;
+	retval = write_f01_controls(data);
+	if (retval < 0)
+		return retval;
+#endif
+
+	retval = wait_for_idle(data, F34_ENABLE_WAIT_MS);
+	if (retval) {
+		dev_err(&rmi_dev->dev, "Did not reach idle state after %d ms. Code: %d.\n",
+			F34_ENABLE_WAIT_MS, retval);
+		return retval;
+	}
+	if (!data->f34_controls.program_enabled) {
+		dev_err(&rmi_dev->dev, "Reached idle, but programming not enabled (current status register: %#04x).\n",
+					data->f34_controls.regs[0]);
+		return -EINVAL;
+	}
+	dev_info(&rmi_dev->dev, "HOORAY! Programming is enabled!\n");
+
+	retval = rescan_pdt(data);
+	if (retval) {
+		dev_err(&rmi_dev->dev, "Failed to rescan pdt.  Code: %d.\n",
+			retval);
+		return retval;
+	}
+
+	retval = read_f01_status(data, &device_status);
+	if (retval) {
+		dev_err(&rmi_dev->dev, "Failed to read F01 status after enabling reflash. Code: %d.\n",
+			retval);
+		return retval;
+	}
+	if (!(device_status.flash_prog)) {
+		dev_err(&rmi_dev->dev, "Device reports as not in flash programming mode.\n");
+		return -EINVAL;
+	}
+
+	retval = read_f34_queries(data);
+	if (retval) {
+		dev_err(&rmi_dev->dev, "F34 queries failed, code = %d.\n",
+			retval);
+		return retval;
+	}
+
+	retval = read_f01_controls(data);
+	if (retval) {
+		dev_err(&rmi_dev->dev, "F01 controls read failed, code = %d.\n",
+			retval);
+		return retval;
+	}
+	data->f01_controls.nosleep = true;
+	data->f01_controls.sleep_mode = RMI_SLEEP_MODE_NORMAL;
+
+	retval = write_f01_controls(data);
+	if (retval) {
+		dev_err(&rmi_dev->dev, "F01 controls write failed, code = %d.\n",
+			retval);
+		return retval;
+	}
+
+	return retval;
+}
+
+static void reset_device(struct reflash_data *data)
+{
+	int retval;
+	struct rmi_device_platform_data *pdata =
+		to_rmi_platform_data(data->rmi_dev);
+
+	dev_info(&data->rmi_dev->dev, "Resetting...\n");
+	retval = rmi_write(data->rmi_dev, data->f01_pdt->command_base_addr,
+			   F01_RESET_MASK);
+	if (retval < 0)
+		dev_warn(&data->rmi_dev->dev,
+			 "WARNING - post-flash reset failed, code: %d.\n",
+			 retval);
+	msleep(pdata->reset_delay_ms);
+	dev_info(&data->rmi_dev->dev, "Reset completed.\n");
+}
+
+/*
+ * Send data to the device one block at a time.
+ */
+static int write_blocks(struct reflash_data *data, u8 *block_ptr,
+			u16 block_count, u8 cmd)
+{
+	int block_num;
+	u8 zeros[] = {0, 0};
+	int retval;
+	u16 addr = data->f34_pdt->data_base_addr + F34_BLOCK_DATA_OFFSET;
+
+	retval = rmi_write_block(data->rmi_dev, data->f34_pdt->data_base_addr,
+				 zeros, ARRAY_SIZE(zeros));
+	if (retval < 0) {
+		dev_err(&data->rmi_dev->dev, "Failed to write initial zeros. Code=%d.\n",
+			retval);
+		return retval;
+	}
+
+	for (block_num = 0; block_num < block_count; ++block_num) {
+		retval = rmi_write_block(data->rmi_dev, addr, block_ptr,
+					 data->f34_queries.block_size);
+		if (retval < 0) {
+			dev_err(&data->rmi_dev->dev, "Failed to write block %d. Code=%d.\n",
+				block_num, retval);
+			return retval;
+		}
+
+		retval = write_f34_command(data, cmd);
+		if (retval) {
+			dev_err(&data->rmi_dev->dev, "Failed to write command for block %d. Code=%d.\n",
+				block_num, retval);
+			return retval;
+		}
+
+
+		retval = wait_for_idle(data, F34_IDLE_WAIT_MS);
+		if (retval) {
+			dev_err(&data->rmi_dev->dev, "Failed to go idle after writing block %d. Code=%d.\n",
+				block_num, retval);
+			return retval;
+		}
+
+		block_ptr += data->f34_queries.block_size;
+	}
+
+	return 0;
+}
+
+static int write_firmware(struct reflash_data *data)
+{
+	return write_blocks(data, (u8 *) data->firmware_data,
+		data->f34_queries.fw_block_count, F34_WRITE_FW_BLOCK);
+}
+
+static int write_configuration(struct reflash_data *data)
+{
+	return write_blocks(data, (u8 *) data->config_data,
+		data->f34_queries.config_block_count, F34_WRITE_CONFIG_BLOCK);
+}
+
+static void reflash_firmware(struct reflash_data *data)
+{
+#ifdef DEBUG
+	struct timespec start;
+	struct timespec end;
+	s64 duration_ns;
+#endif
+	int retval = 0;
+
+	retval = enter_flash_programming(data);
+	if (retval)
+		return;
+
+	retval = write_bootloader_id(data);
+	if (retval)
+		return;
+
+#ifdef	DEBUG
+	dev_info(&data->rmi_dev->dev, "Erasing FW...\n");
+	getnstimeofday(&start);
+#endif
+	retval = write_f34_command(data, F34_ERASE_ALL);
+	if (retval)
+		return;
+
+	retval = wait_for_idle(data, F34_ERASE_WAIT_MS);
+	if (retval) {
+		dev_err(&data->rmi_dev->dev,
+			"Failed to reach idle state. Code: %d.\n", retval);
+		return;
+	}
+#ifdef	DEBUG
+	getnstimeofday(&end);
+	duration_ns = timespec_to_ns(&end) - timespec_to_ns(&start);
+	dev_info(&data->rmi_dev->dev,
+		 "Erase complete, time: %lld ns.\n", duration_ns);
+#endif
+
+	if (data->firmware_data) {
+#ifdef	DEBUG
+		dev_info(&data->rmi_dev->dev, "Writing firmware...\n");
+		getnstimeofday(&start);
+#endif
+		retval = write_firmware(data);
+		if (retval)
+			return;
+#ifdef	DEBUG
+		getnstimeofday(&end);
+		duration_ns = timespec_to_ns(&end) - timespec_to_ns(&start);
+		dev_info(&data->rmi_dev->dev,
+			 "Done writing FW, time: %lld ns.\n", duration_ns);
+#endif
+	}
+
+	if (data->config_data) {
+#ifdef	DEBUG
+		dev_info(&data->rmi_dev->dev, "Writing configuration...\n");
+		getnstimeofday(&start);
+#endif
+		retval = write_configuration(data);
+		if (retval)
+			return;
+#ifdef	DEBUG
+		getnstimeofday(&end);
+		duration_ns = timespec_to_ns(&end) - timespec_to_ns(&start);
+		dev_info(&data->rmi_dev->dev,
+			 "Done writing config, time: %lld ns.\n", duration_ns);
+#endif
+	}
+}
+
+/* Returns false if the firmware should not be reflashed.
+ */
+static bool go_nogo(struct reflash_data *data, struct image_header *header)
+{
+	union f01_device_status device_status;
+	int retval;
+
+	if (data->f01_queries.productinfo_1 < header->product_info[0] ||
+		data->f01_queries.productinfo_2 < header->product_info[1]) {
+		dev_info(&data->rmi_dev->dev,
+			 "FW product ID is older than image product ID.\n");
+		return true;
+	}
+
+	retval = read_f01_status(data, &device_status);
+	if (retval)
+		dev_err(&data->rmi_dev->dev,
+			"Failed to read F01 status. Code: %d.\n", retval);
+
+	return device_status.flash_prog || force;
+}
+
+void rmi4_fw_update(struct rmi_device *rmi_dev,
+		struct pdt_entry *f01_pdt, struct pdt_entry *f34_pdt)
+{
+#ifdef DEBUG
+	struct timespec start;
+	struct timespec end;
+	s64 duration_ns;
+#endif
+	char firmware_name[RMI_PRODUCT_ID_LENGTH + 12];
+	const struct firmware *fw_entry = NULL;
+	int retval;
+	struct image_header header;
+	union pdt_properties pdt_props;
+	struct reflash_data data = {
+		.rmi_dev = rmi_dev,
+		.f01_pdt = f01_pdt,
+		.f34_pdt = f34_pdt,
+	};
+	struct rmi_device_platform_data *pdata = to_rmi_platform_data(rmi_dev);
+
+	dev_info(&rmi_dev->dev, "%s called.\n", __func__);
+#ifdef	DEBUG
+	getnstimeofday(&start);
+#endif
+
+	retval = rmi_read(rmi_dev, PDT_PROPERTIES_LOCATION, pdt_props.regs);
+	if (retval < 0) {
+		dev_warn(&rmi_dev->dev,
+			 "Failed to read PDT props at %#06x (code %d). Assuming 0x00.\n",
+			 PDT_PROPERTIES_LOCATION, retval);
+	}
+	if (pdt_props.has_bsr) {
+		dev_warn(&rmi_dev->dev,
+			 "Firmware update for LTS not currently supported.\n");
+		return;
+	}
+
+	retval = read_f01_queries(&data);
+	if (retval) {
+		dev_err(&rmi_dev->dev, "F01 queries failed, code = %d.\n",
+			retval);
+		return;
+	}
+	retval = read_f34_queries(&data);
+	if (retval) {
+		dev_err(&rmi_dev->dev, "F34 queries failed, code = %d.\n",
+			retval);
+		return;
+	}
+	if (pdata->firmware_name && strlen(pdata->firmware_name))
+		snprintf(firmware_name, sizeof(firmware_name), "rmi4/%s.img",
+			pdata->firmware_name);
+	else
+		snprintf(firmware_name, sizeof(firmware_name), "rmi4/%s.img",
+			(img_name && strlen(img_name))
+				? img_name : data.product_id);
+	dev_info(&rmi_dev->dev, "Requesting %s.\n", firmware_name);
+	retval = request_firmware(&fw_entry, firmware_name, &rmi_dev->dev);
+	if (retval != 0) {
+		dev_err(&rmi_dev->dev, "Firmware %s not available, code = %d\n",
+			firmware_name, retval);
+		return;
+	}
+
+#ifdef	DEBUG
+	dev_info(&rmi_dev->dev, "Got firmware, size: %d.\n", fw_entry->size);
+	extract_header(fw_entry->data, 0, &header);
+	dev_info(&rmi_dev->dev, "Img checksum:           %#08X\n",
+		 header.checksum);
+	dev_info(&rmi_dev->dev, "Img image size:         %d\n",
+		 header.image_size);
+	dev_info(&rmi_dev->dev, "Img config size:        %d\n",
+		 header.config_size);
+	dev_info(&rmi_dev->dev, "Img bootloader version: %d\n",
+		 header.bootloader_version);
+	dev_info(&rmi_dev->dev, "Img product id:         %s\n",
+		 header.product_id);
+	dev_info(&rmi_dev->dev, "Img product info:       %#04x %#04x\n",
+		 header.product_info[0], header.product_info[1]);
+#endif
+
+	if (header.image_size)
+		data.firmware_data = fw_entry->data + F34_FW_IMAGE_OFFSET;
+	if (header.config_size)
+		data.config_data = fw_entry->data + F34_FW_IMAGE_OFFSET +
+			header.image_size;
+
+	if (go_nogo(&data, &header)) {
+		reflash_firmware(&data);
+		reset_device(&data);
+	} else
+		dev_info(&rmi_dev->dev, "Go/NoGo said don't reflash.\n");
+
+	if (fw_entry)
+		release_firmware(fw_entry);
+#ifdef	DEBUG
+	getnstimeofday(&end);
+	duration_ns = timespec_to_ns(&end) - timespec_to_ns(&start);
+	dev_info(&rmi_dev->dev, "Time to reflash: %lld ns.\n", duration_ns);
+#endif
+}