Add support for reflashing V5 bootloader firmwares into 
RMI4 devices.

Signed-off-by: Christopher Heiny <[email protected]>
Signed-off-by: Vincent Huang <[email protected]>
Cc: Dmitry Torokhov <[email protected]>
Cc: Benjamin Tissoires <[email protected]>
Cc: Linux Walleij <[email protected]>
Cc: David Herrmann <[email protected]>
Cc: Jiri Kosina <[email protected]>

---

 drivers/input/rmi4/Kconfig         |   9 +
 drivers/input/rmi4/Makefile        |   1 +
 drivers/input/rmi4/rmi_bus.c       |   3 +
 drivers/input/rmi4/rmi_driver.h    |  11 +
 drivers/input/rmi4/rmi_fw_update.c | 961 +++++++++++++++++++++++++++++++++++++
 5 files changed, 985 insertions(+)

diff --git a/drivers/input/rmi4/Kconfig b/drivers/input/rmi4/Kconfig
index d0c7b6e..9b88b6a 100644
--- a/drivers/input/rmi4/Kconfig
+++ b/drivers/input/rmi4/Kconfig
@@ -25,6 +25,15 @@ config RMI4_DEBUG
 
          If unsure, say N.
 
+config RMI4_FWLIB
+       bool "RMI4 Firmware Update"
+       depends on RMI4_CORE
+       help
+         Say Y here to enable in-kernel firmware update capability.
+
+         Add support to the RMI4 core to enable reflashing of device
+         firmware.
+
 config RMI4_I2C
        tristate "RMI4 I2C Support"
        depends on RMI4_CORE && I2C
diff --git a/drivers/input/rmi4/Makefile b/drivers/input/rmi4/Makefile
index 5c6bad5..570ea80 100644
--- a/drivers/input/rmi4/Makefile
+++ b/drivers/input/rmi4/Makefile
@@ -1,5 +1,6 @@
 obj-$(CONFIG_RMI4_CORE) += rmi_core.o
 rmi_core-y := rmi_bus.o rmi_driver.o rmi_f01.o
+rmi_core-$(CONFIG_RMI4_FWLIB) += rmi_fw_update.o
 
 # Function drivers
 obj-$(CONFIG_RMI4_F11) += rmi_f11.o
diff --git a/drivers/input/rmi4/rmi_bus.c b/drivers/input/rmi4/rmi_bus.c
index 6e0454a..3c93d08 100644
--- a/drivers/input/rmi4/rmi_bus.c
+++ b/drivers/input/rmi4/rmi_bus.c
@@ -117,6 +117,8 @@ int rmi_register_transport_device(struct rmi_transport_dev 
*xport)
        if (error)
                goto err_put_device;
 
+       rmi_reflash_init(rmi_dev);
+
        dev_dbg(xport->dev, "%s: Registered %s as %s.\n", __func__,
                pdata->sensor_name, dev_name(&rmi_dev->dev));
 
@@ -139,6 +141,7 @@ void rmi_unregister_transport_device(struct 
rmi_transport_dev *xport)
        struct rmi_device *rmi_dev = xport->rmi_dev;
 
        device_del(&rmi_dev->dev);
+       rmi_reflash_cleanup(rmi_dev);
        rmi_physical_teardown_debugfs(rmi_dev);
        put_device(&rmi_dev->dev);
 }
diff --git a/drivers/input/rmi4/rmi_driver.h b/drivers/input/rmi4/rmi_driver.h
index 0cc9089..c49b123 100644
--- a/drivers/input/rmi4/rmi_driver.h
+++ b/drivers/input/rmi4/rmi_driver.h
@@ -29,6 +29,8 @@
 
 #define RMI_PDT_PROPS_HAS_BSR 0x02
 
+struct rmi_reflash_data;
+
 struct rmi_driver_data {
        struct list_head function_list;
 
@@ -81,6 +83,8 @@ struct rmi_driver_data {
        u8 reg_debug_size;
 #endif
 
+       struct rmi_reflash_data *reflash_data;
+
        void *data;
 };
 
@@ -114,5 +118,12 @@ int check_bootloader_mode(struct rmi_device *rmi_dev,
 void rmi_free_function_list(struct rmi_device *rmi_dev);
 int rmi_driver_detect_functions(struct rmi_device *rmi_dev);
 
+#ifdef CONFIG_RMI4_FWLIB
+void rmi_reflash_init(struct rmi_device *rmi_dev);
+void rmi_reflash_cleanup(struct rmi_device *rmi_dev);
+#else
+#define rmi_reflash_init(rmi_dev)
+#define rmi_reflash_cleanup(rmi_dev)
+#endif
 
 #endif
diff --git a/drivers/input/rmi4/rmi_fw_update.c 
b/drivers/input/rmi4/rmi_fw_update.c
new file mode 100644
index 0000000..7118401
--- /dev/null
+++ b/drivers/input/rmi4/rmi_fw_update.c
@@ -0,0 +1,961 @@
+/*
+ * Copyright (c) 2012-2014 Synaptics Incorporated
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 as published by
+ * the Free Software Foundation.
+ */
+
+#include <linux/delay.h>
+#include <linux/device.h>
+#include <linux/firmware.h>
+#include <linux/ihex.h>
+#include <linux/kernel.h>
+#include <linux/rmi.h>
+#include <linux/slab.h>
+#include <linux/time.h>
+#include "rmi_driver.h"
+#include "rmi_f01.h"
+
+/* F34 Query register defs. */
+
+#define RMI_F34_QUERY_SIZE             7
+#define RMI_F34_HAS_NEW_REG_MAP                (1 << 0)
+#define RMI_F34_IS_UNLOCKED            (1 << 1)
+#define RMI_F34_HAS_CONFIG_ID          (1 << 2)
+#define RMI_F34_BLOCK_SIZE_OFFSET      1
+#define RMI_F34_FW_BLOCKS_OFFSET       3
+#define RMI_F34_CONFIG_BLOCKS_OFFSET   5
+
+struct rmi_f34_queries {
+       bool    new_reg_map;
+       bool    unlocked;
+       bool    has_config_id;
+       u16     block_size;
+       u16     fw_block_count;
+       u16     config_block_count;
+};
+
+/* F34 Data register defs. */
+
+#define RMI_F34_BLOCK_DATA_OFFSET      2
+
+#define RMI_F34_COMMAND_MASK           0x0F
+#define RMI_F34_STATUS_MASK            0x07
+#define RMI_F34_STATUS_SHIFT           4
+#define RMI_F34_ENABLED_MASK           0x80
+
+#define RMI_F34_WRITE_FW_BLOCK        0x02
+#define RMI_F34_ERASE_ALL             0x03
+#define RMI_F34_WRITE_CONFIG_BLOCK    0x06
+#define RMI_F34_ENABLE_FLASH_PROG     0x0f
+
+struct rmi_f34_control_status {
+       u8 command;
+       u8 status;
+       bool program_enabled;
+};
+
+/* Timeouts for various F34 operations. */
+#define RMI_F34_ENABLE_WAIT_MS 300
+#define RMI_F34_ERASE_WAIT_MS (5 * 1000)
+#define RMI_F34_IDLE_WAIT_MS 500
+
+#define IS_IDLE(ctl_ptr) ((!ctl_ptr->status) && (!ctl_ptr->command))
+
+
+/* Image file defs. */
+#define RMI_IMG_CHECKSUM_OFFSET                        0
+#define RMI_IMG_IO_OFFSET                      0x06
+#define RMI_IMG_BOOTLOADER_VERSION_OFFSET      0x07
+#define RMI_IMG_IMAGE_SIZE_OFFSET              0x08
+#define RMI_IMG_CONFIG_SIZE_OFFSET             0x0C
+#define RMI_IMG_PACKAGE_ID_OFFSET              0x1A
+#define RMI_IMG_FW_BUILD_ID_OFFSET             0x50
+
+#define RMI_IMG_PRODUCT_INFO_LENGTH            2
+
+#define RMI_IMG_PRODUCT_ID_OFFSET              0x10
+#define RMI_IMG_PRODUCT_INFO_OFFSET            0x1E
+
+#define RMI_F34_FW_IMAGE_OFFSET 0x100
+
+/* Image file V5, Option 0 */
+struct rmi_image_header {
+       u32 checksum;
+       unsigned int image_size;
+       unsigned int config_size;
+       u8 options;
+       u8 io;
+       u32 fw_build_id;
+       u32 package_id;
+       u8 bootloader_version;
+       u8 product_id[RMI_PRODUCT_ID_LENGTH + 1];
+       u8 product_info[RMI_IMG_PRODUCT_INFO_LENGTH];
+};
+
+static u32 rmi_extract_u32(const u8 *ptr)
+{
+       return (u32)ptr[0] +
+               (u32)ptr[1] * 0x100 +
+               (u32)ptr[2] * 0x10000 +
+               (u32)ptr[3] * 0x1000000;
+}
+
+#define RMI_NAME_BUFFER_SIZE 64
+
+struct rmi_reflash_data {
+       struct rmi_device *rmi_dev;
+       bool force;
+       ulong busy;
+       char name_buf[RMI_NAME_BUFFER_SIZE];
+       char *img_name;
+       struct pdt_entry f01_pdt;
+       struct f01_basic_properties f01_props;
+       u8 device_status;
+       struct pdt_entry f34_pdt;
+       u8 bootloader_id[2];
+       struct rmi_f34_queries f34_queries;
+       u16 f34_status_address;
+       struct rmi_f34_control_status f34_controls;
+       const u8 *firmware_data;
+       const u8 *config_data;
+       struct work_struct reflash_work;
+};
+
+static void rmi_extract_header(const u8 *data, int pos,
+                              struct rmi_image_header *header)
+{
+       header->checksum =
+                       rmi_extract_u32(&data[pos + RMI_IMG_CHECKSUM_OFFSET]);
+       header->io = data[pos + RMI_IMG_IO_OFFSET];
+       header->bootloader_version =
+                       data[pos + RMI_IMG_BOOTLOADER_VERSION_OFFSET];
+       header->image_size =
+                       rmi_extract_u32(&data[pos + RMI_IMG_IMAGE_SIZE_OFFSET]);
+       header->config_size =
+                      rmi_extract_u32(&data[pos + RMI_IMG_CONFIG_SIZE_OFFSET]);
+       if (header->io == 1) {
+               header->fw_build_id =
+                      rmi_extract_u32(&data[pos + RMI_IMG_FW_BUILD_ID_OFFSET]);
+               header->package_id =
+                       rmi_extract_u32(&data[pos + RMI_IMG_PACKAGE_ID_OFFSET]);
+       }
+       memcpy(header->product_id, &data[pos + RMI_IMG_PRODUCT_ID_OFFSET],
+              RMI_PRODUCT_ID_LENGTH);
+       header->product_id[RMI_PRODUCT_ID_LENGTH] = 0;
+       memcpy(header->product_info, &data[pos + RMI_IMG_PRODUCT_INFO_OFFSET],
+              RMI_IMG_PRODUCT_INFO_LENGTH);
+}
+
+static int rmi_find_functions(struct rmi_device *rmi_dev,
+                             void *ctx, const struct pdt_entry *pdt)
+{
+       struct rmi_reflash_data *data = ctx;
+
+       if (pdt->page_start > 0)
+               return RMI_SCAN_DONE;
+
+       if (pdt->function_number == 0x01)
+               memcpy(&data->f01_pdt, pdt, sizeof(struct pdt_entry));
+       else if (pdt->function_number == 0x34)
+               memcpy(&data->f34_pdt, pdt, sizeof(struct pdt_entry));
+
+       return RMI_SCAN_CONTINUE;
+}
+
+static int rmi_find_f01_and_f34(struct rmi_reflash_data *data)
+{
+       struct rmi_device *rmi_dev = data->rmi_dev;
+       int retval;
+
+       data->f01_pdt.function_number = data->f34_pdt.function_number = 0;
+       retval = rmi_scan_pdt(rmi_dev, data, rmi_find_functions);
+       if (retval < 0)
+               return retval;
+
+       if (!data->f01_pdt.function_number) {
+               dev_err(&rmi_dev->dev, "Failed to find F01 for reflash.\n");
+               return -ENODEV;
+       }
+
+       if (!data->f34_pdt.function_number) {
+               dev_err(&rmi_dev->dev, "Failed to find F34 for reflash.\n");
+               return -ENODEV;
+       }
+
+       return 0;
+}
+
+static int rmi_read_f34_controls(struct rmi_reflash_data *data)
+{
+       int retval;
+       u8 buf;
+
+       retval = rmi_read(data->rmi_dev, data->f34_status_address, &buf);
+       if (retval)
+               return retval;
+
+       data->f34_controls.command = buf & RMI_F34_COMMAND_MASK;
+       data->f34_controls.status = (buf >> RMI_F34_STATUS_SHIFT)
+                                               & RMI_F34_STATUS_MASK;
+       data->f34_controls.program_enabled = !!(buf & RMI_F34_ENABLED_MASK);
+
+       return 0;
+}
+
+static int rmi_read_f01_status(struct rmi_reflash_data *data)
+{
+       int retval;
+
+       retval = rmi_read(data->rmi_dev, data->f01_pdt.data_base_addr,
+                         &data->device_status);
+       if (retval)
+               return retval;
+
+       return 0;
+}
+
+#define RMI_MIN_SLEEP_TIME_US 50
+#define RMI_MAX_SLEEP_TIME_US 100
+
+/*
+ * Wait until the status is idle and we're ready to continue.
+ *
+ * In order to indicate that the previous command has succeeded, the
+ * bootloader is supposed to signal idle state by:
+ *
+ *  + atomically do the following by writing 0x80 to F34_FLASH_Data3
+ *     - set status to 0,
+ *     - set command to 0, and
+ *     - leave progam_enabled as 1
+ *  + assert attn
+ *
+ * and then we're supposed to be able to see that we're in IDLE state.
+ * But some (most?) bootloaders do this
+ *
+ *  + clear F34_FLASH_Data3
+ *  + assert attn
+ *  + set the program_enabled bit
+ *
+ * and a significant number of those don't even bother to assert
+ * ATTN, so you've got to poll them (which is what we're doing
+ * here). Regardless of whether you're polling or using ATTN,
+ * there's a race condition between clearing Data3 and setting
+ * program_enabled. So when we lose that race, we emit this warning
+ * (using dev_WARN_ONCE to avoid filling the log with complaints)
+ * and retry a few times. If a correct idle state is reached during
+ * the retries, then we just continue with the process. If it's not
+ * reached (that is, if Data3 contains anything but 0x80 after the
+ * timeout), then something has gone horribly wrong, and we abort the
+ * reflash process.
+ *
+ */
+static int rmi_wait_for_idle(struct rmi_reflash_data *data, int timeout_ms)
+{
+       int timeout_count = ((timeout_ms * 1000) / RMI_MAX_SLEEP_TIME_US) + 1;
+       int count = 0;
+       struct rmi_f34_control_status *controls = &data->f34_controls;
+       int retval;
+
+       do {
+               if (count || timeout_count == 1)
+                       usleep_range(RMI_MIN_SLEEP_TIME_US,
+                                    RMI_MAX_SLEEP_TIME_US);
+               retval = rmi_read_f34_controls(data);
+               count++;
+               if (retval)
+                       continue;
+               else if (IS_IDLE(controls)) {
+                       if (dev_WARN_ONCE(&data->rmi_dev->dev,
+                                         !data->f34_controls.program_enabled,
+                                         "Reflash is idle but program_enabled 
bit isn't set.\n"
+                                         ))
+                               /*
+                                * This works around a bug in certain device
+                                * firmwares, where the idle state is reached,
+                                * but the program_enabled bit is not yet set.
+                                */
+                               continue;
+                       return 0;
+               }
+       } while (count < timeout_count);
+
+       dev_err(&data->rmi_dev->dev,
+               "ERROR: Timeout waiting for idle status.\n");
+       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 rmi_read_f34_queries(struct rmi_reflash_data *data)
+{
+       int retval;
+       u8 id_str[3];
+       u8 buf[RMI_F34_QUERY_SIZE];
+
+       retval = rmi_read_block(data->rmi_dev, data->f34_pdt.query_base_addr,
+                               data->bootloader_id, 2);
+       if (retval) {
+               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,
+                               buf, RMI_F34_QUERY_SIZE);
+       if (retval) {
+               dev_err(&data->rmi_dev->dev,
+                       "Failed to read F34 queries (code %d).\n", retval);
+               return retval;
+       }
+
+       data->f34_queries.new_reg_map = buf[0] & RMI_F34_HAS_NEW_REG_MAP;
+       data->f34_queries.unlocked = buf[0] & RMI_F34_IS_UNLOCKED;
+       data->f34_queries.has_config_id = buf[0] & RMI_F34_HAS_CONFIG_ID;
+       data->f34_queries.block_size =
+               le16_to_cpu(*((u16 *)(buf + RMI_F34_BLOCK_SIZE_OFFSET)));
+       data->f34_queries.fw_block_count =
+               le16_to_cpu(*((u16 *)(buf + RMI_F34_FW_BLOCKS_OFFSET)));
+       data->f34_queries.config_block_count =
+               le16_to_cpu(*((u16 *)(buf + RMI_F34_CONFIG_BLOCKS_OFFSET)));
+
+       id_str[0] = data->bootloader_id[0];
+       id_str[1] = data->bootloader_id[1];
+       id_str[2] = 0;
+
+       dev_dbg(&data->rmi_dev->dev, "F34 bootloader id: %s (%#04x %#04x)\n",
+               id_str, data->bootloader_id[0], data->bootloader_id[1]);
+       dev_dbg(&data->rmi_dev->dev, "F34 has config id: %d\n",
+               data->f34_queries.has_config_id);
+       dev_dbg(&data->rmi_dev->dev, "F34 unlocked:      %d\n",
+               data->f34_queries.unlocked);
+       dev_dbg(&data->rmi_dev->dev, "F34 new reg map:   %d\n",
+               data->f34_queries.new_reg_map);
+       dev_dbg(&data->rmi_dev->dev, "F34 block size:    %d\n",
+               data->f34_queries.block_size);
+       dev_dbg(&data->rmi_dev->dev, "F34 fw blocks:     %d\n",
+               data->f34_queries.fw_block_count);
+       dev_dbg(&data->rmi_dev->dev, "F34 config blocks: %d\n",
+               data->f34_queries.config_block_count);
+
+       data->f34_status_address = data->f34_pdt.data_base_addr +
+               RMI_F34_BLOCK_DATA_OFFSET + data->f34_queries.block_size;
+
+       return 0;
+}
+
+static int rmi_write_bootloader_id(struct rmi_reflash_data *data)
+{
+       int retval;
+       struct rmi_device *rmi_dev = data->rmi_dev;
+
+       retval = rmi_write_block(rmi_dev,
+                     data->f34_pdt.data_base_addr + RMI_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 rmi_write_f34_command(struct rmi_reflash_data *data, u8 command)
+{
+       int retval;
+       struct rmi_device *rmi_dev = data->rmi_dev;
+
+       retval = rmi_write(rmi_dev, data->f34_status_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 rmi_enter_flash_programming(struct rmi_reflash_data *data)
+{
+       int retval;
+       struct rmi_device *rmi_dev = data->rmi_dev;
+       u8 f01_control_0;
+
+       retval = rmi_write_bootloader_id(data);
+       if (retval < 0)
+               return retval;
+
+       dev_dbg(&rmi_dev->dev, "Enabling flash programming.\n");
+       retval = rmi_write_f34_command(data, RMI_F34_ENABLE_FLASH_PROG);
+       if (retval < 0)
+               return retval;
+
+       retval = rmi_wait_for_idle(data, RMI_F34_ENABLE_WAIT_MS);
+       if (retval) {
+               dev_err(&rmi_dev->dev, "Did not reach idle state after %d ms. 
Code: %d.\n",
+                       RMI_F34_ENABLE_WAIT_MS, retval);
+               return retval;
+       }
+       if (!data->f34_controls.program_enabled) {
+               dev_err(&rmi_dev->dev, "Reached idle, but programming not 
enabled.\n");
+               return -EINVAL;
+       }
+       dev_dbg(&rmi_dev->dev, "HOORAY! Programming is enabled!\n");
+
+       retval = rmi_find_f01_and_f34(data);
+       if (retval) {
+               dev_err(&rmi_dev->dev, "Failed to rescan pdt.  Code: %d.\n",
+                       retval);
+               return retval;
+       }
+
+       retval = rmi_read_f01_status(data);
+       if (retval) {
+               dev_err(&rmi_dev->dev, "Failed to read F01 status after 
enabling reflash. Code: %d.\n",
+                       retval);
+               return retval;
+       }
+       if (!RMI_F01_STATUS_BOOTLOADER(data->device_status)) {
+               dev_err(&rmi_dev->dev, "Device reports as not in flash 
programming mode.\n");
+               return -EINVAL;
+       }
+
+       retval = rmi_read_f34_queries(data);
+       if (retval) {
+               dev_err(&rmi_dev->dev, "F34 queries failed, code = %d.\n",
+                       retval);
+               return retval;
+       }
+
+       retval = rmi_read(rmi_dev, data->f01_pdt.control_base_addr,
+                         &f01_control_0);
+       if (retval) {
+               dev_err(&rmi_dev->dev, "F01_CTRL_0 read failed, code = %d.\n",
+                       retval);
+               return retval;
+       }
+       f01_control_0 |= RMI_F01_CRTL0_NOSLEEP_BIT;
+       f01_control_0 = (f01_control_0 & ~RMI_F01_CTRL0_SLEEP_MODE_MASK)
+                       | RMI_SLEEP_MODE_NORMAL;
+
+       retval = rmi_write(rmi_dev, data->f01_pdt.control_base_addr,
+                          f01_control_0);
+       if (retval < 0) {
+               dev_err(&rmi_dev->dev, "F01_CTRL_0 write failed, code = %d.\n",
+                       retval);
+               return retval;
+       }
+
+       return 0;
+}
+
+static void rmi_reset_device(struct rmi_reflash_data *data)
+{
+       int retval;
+       const struct rmi_device_platform_data *pdata =
+                               rmi_get_platform_data(data->rmi_dev);
+
+       dev_dbg(&data->rmi_dev->dev, "Resetting...\n");
+       retval = rmi_write(data->rmi_dev, data->f01_pdt.command_base_addr,
+                          RMI_F01_CMD_DEVICE_RESET);
+       if (retval < 0)
+               dev_warn(&data->rmi_dev->dev,
+                        "WARNING - post-flash reset failed, code: %d.\n",
+                        retval);
+       msleep(pdata->reset_delay_ms ?: RMI_F01_DEFAULT_RESET_DELAY_MS);
+       dev_dbg(&data->rmi_dev->dev, "Reset completed.\n");
+}
+
+/*
+ * Send data to the device one block at a time.
+ */
+static int rmi_write_blocks(struct rmi_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 + RMI_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 = rmi_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 = rmi_wait_for_idle(data, RMI_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 rmi_write_firmware(struct rmi_reflash_data *data)
+{
+       return rmi_write_blocks(data, (u8 *) data->firmware_data,
+               data->f34_queries.fw_block_count, RMI_F34_WRITE_FW_BLOCK);
+}
+
+static int rmi_write_configuration(struct rmi_reflash_data *data)
+{
+       return rmi_write_blocks(data, (u8 *) data->config_data,
+               data->f34_queries.config_block_count,
+               RMI_F34_WRITE_CONFIG_BLOCK);
+}
+
+static void rmi_reflash_firmware(struct rmi_reflash_data *data)
+{
+       struct timespec start;
+       struct timespec end;
+       s64 duration_ns;
+       int retval = 0;
+
+       retval = rmi_enter_flash_programming(data);
+       if (retval) {
+               dev_err(&data->rmi_dev->dev, "Failed to enter flash programming 
(code: %d).\n",
+                       retval);
+               return;
+       }
+
+       retval = rmi_write_bootloader_id(data);
+       if (retval) {
+               dev_err(&data->rmi_dev->dev, "Failed to enter write bootloader 
ID (code: %d).\n",
+                       retval);
+               return;
+       }
+
+       dev_dbg(&data->rmi_dev->dev, "Erasing FW...\n");
+       getnstimeofday(&start);
+       retval = rmi_write_f34_command(data, RMI_F34_ERASE_ALL);
+       if (retval) {
+               dev_err(&data->rmi_dev->dev, "Erase failed (code: %d).\n",
+                       retval);
+               return;
+       }
+
+       retval = rmi_wait_for_idle(data, RMI_F34_ERASE_WAIT_MS);
+       if (retval) {
+               dev_err(&data->rmi_dev->dev,
+                       "Failed to reach idle state. Code: %d.\n", retval);
+               return;
+       }
+       getnstimeofday(&end);
+       duration_ns = timespec_to_ns(&end) - timespec_to_ns(&start);
+       dev_dbg(&data->rmi_dev->dev,
+                "Erase complete, time: %lld ns.\n", duration_ns);
+
+       if (data->firmware_data) {
+               dev_dbg(&data->rmi_dev->dev, "Writing firmware...\n");
+               getnstimeofday(&start);
+               retval = rmi_write_firmware(data);
+               if (retval) {
+                       dev_err(&data->rmi_dev->dev,
+                               "Failed to write FW (code: %d).\n", retval);
+                       return;
+               }
+               getnstimeofday(&end);
+               duration_ns = timespec_to_ns(&end) - timespec_to_ns(&start);
+               dev_dbg(&data->rmi_dev->dev,
+                        "Done writing FW, time: %lld ns.\n", duration_ns);
+       }
+
+       if (data->config_data) {
+               dev_dbg(&data->rmi_dev->dev, "Writing configuration...\n");
+               getnstimeofday(&start);
+               retval = rmi_write_configuration(data);
+               if (retval) {
+                       dev_err(&data->rmi_dev->dev,
+                               "Failed to write config (code: %d).\n", retval);
+                       return;
+               }
+               getnstimeofday(&end);
+               duration_ns = timespec_to_ns(&end) - timespec_to_ns(&start);
+               dev_dbg(&data->rmi_dev->dev,
+                        "Done writing config, time: %lld ns.\n", duration_ns);
+       }
+}
+
+static bool rmi_go_nogo(struct rmi_reflash_data *data,
+                       struct rmi_image_header *header)
+{
+       if (data->force) {
+               dev_dbg(&data->rmi_dev->dev, "Reflash force flag in effect.\n");
+               return true;
+       }
+
+       if (header->io == 1) {
+               if (header->fw_build_id > data->f01_props.build_id) {
+                       dev_dbg(&data->rmi_dev->dev, "Image file has newer 
Packrat.\n");
+                       return true;
+               } else {
+                       dev_dbg(&data->rmi_dev->dev, "Image file has lower 
Packrat ID than device.\n");
+               }
+       }
+
+       return false;
+}
+
+static const char rmi_fw_name_format[] = "%s.img";
+
+static void rmi_fw_update(struct rmi_device *rmi_dev)
+{
+       struct timespec start;
+       struct timespec end;
+       s64 duration_ns;
+       char *firmware_name;
+       const struct firmware *fw_entry = NULL;
+       int retval;
+       struct rmi_image_header *header = NULL;
+       u8 pdt_props;
+       struct rmi_driver_data *drv_data = dev_get_drvdata(&rmi_dev->dev);
+       struct rmi_reflash_data *data = drv_data->reflash_data;
+       const struct rmi_device_platform_data *pdata =
+                               rmi_get_platform_data(rmi_dev);
+
+       dev_dbg(&rmi_dev->dev, "%s called.\n", __func__);
+       dev_dbg(&rmi_dev->dev, "force: %d\n", data->force);
+       dev_dbg(&rmi_dev->dev, "img_name: %s\n", data->img_name);
+       dev_dbg(&rmi_dev->dev, "firmware_name: %s\n", pdata->firmware_name);
+
+       getnstimeofday(&start);
+
+       firmware_name = kcalloc(RMI_NAME_BUFFER_SIZE, sizeof(char), GFP_KERNEL);
+       if (!firmware_name) {
+               dev_err(&rmi_dev->dev, "Failed to allocate firmware_name.\n");
+               goto done;
+       }
+       header = kzalloc(sizeof(struct rmi_image_header), GFP_KERNEL);
+       if (!header) {
+               dev_err(&rmi_dev->dev, "Failed to allocate header.\n");
+               goto done;
+       }
+
+       retval = rmi_read(rmi_dev, PDT_PROPERTIES_LOCATION, &pdt_props);
+       if (retval) {
+               dev_warn(&rmi_dev->dev,
+                        "Failed to read PDT props at %#06x (code %d). Assuming 
0x00.\n",
+                        PDT_PROPERTIES_LOCATION, retval);
+       }
+       if (pdt_props & RMI_PDT_PROPS_HAS_BSR) {
+               dev_warn(&rmi_dev->dev,
+                        "Firmware update for LTS not currently supported.\n");
+               goto done;
+       }
+
+       retval = rmi_f01_read_properties(rmi_dev, data->f01_pdt.query_base_addr,
+                                        &data->f01_props);
+       if (retval) {
+               dev_err(&rmi_dev->dev, "F01 queries failed, code = %d.\n",
+                       retval);
+               goto done;
+       }
+       retval = rmi_read_f34_queries(data);
+       if (retval) {
+               dev_err(&rmi_dev->dev, "F34 queries failed, code = %d.\n",
+                       retval);
+               goto done;
+       }
+       if (data->img_name && strlen(data->img_name))
+               snprintf(firmware_name, RMI_NAME_BUFFER_SIZE,
+                        rmi_fw_name_format, data->img_name);
+       else if (pdata->firmware_name && strlen(pdata->firmware_name))
+               snprintf(firmware_name, RMI_NAME_BUFFER_SIZE,
+                        rmi_fw_name_format, pdata->firmware_name);
+       else {
+               if (!strlen(data->f01_props.product_id)) {
+                       dev_err(&rmi_dev->dev, "Product ID is missing or empty 
- will not reflash.\n");
+                       goto done;
+               }
+               snprintf(firmware_name, RMI_NAME_BUFFER_SIZE,
+                        rmi_fw_name_format, data->f01_props.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);
+               goto done;
+       }
+
+       dev_dbg(&rmi_dev->dev, "Got firmware %s, size: %d.\n", firmware_name,
+               fw_entry->size);
+       rmi_extract_header(fw_entry->data, 0, header);
+       dev_dbg(&rmi_dev->dev, "Img checksum:           %#08X\n",
+               header->checksum);
+       dev_dbg(&rmi_dev->dev, "Img io:                 %#04X\n",
+               header->io);
+       dev_dbg(&rmi_dev->dev, "Img image size:         %d\n",
+               header->image_size);
+       dev_dbg(&rmi_dev->dev, "Img config size:        %d\n",
+               header->config_size);
+       dev_dbg(&rmi_dev->dev, "Img bootloader version: %d\n",
+               header->bootloader_version);
+       dev_dbg(&rmi_dev->dev, "Img product id:         %s\n",
+               header->product_id);
+       dev_dbg(&rmi_dev->dev, "Img product info:       %#04x %#04x\n",
+               header->product_info[0], header->product_info[1]);
+       if (header->io == 1) {
+               dev_dbg(&rmi_dev->dev, "Img Packrat:            %d\n",
+                       header->fw_build_id);
+               dev_dbg(&rmi_dev->dev, "Img package:            %d\n",
+                       header->package_id);
+       }
+
+       if (header->image_size)
+               data->firmware_data = fw_entry->data + RMI_F34_FW_IMAGE_OFFSET;
+       if (header->config_size)
+               data->config_data = fw_entry->data + RMI_F34_FW_IMAGE_OFFSET +
+                       header->image_size;
+
+       if (rmi_go_nogo(data, header)) {
+               dev_dbg(&rmi_dev->dev, "Go/NoGo said go.\n");
+               rmi_free_function_list(rmi_dev);
+               rmi_reflash_firmware(data);
+               rmi_reset_device(data);
+               rmi_driver_detect_functions(rmi_dev);
+       } else
+               dev_dbg(&rmi_dev->dev, "Go/NoGo said don't reflash.\n");
+
+       if (fw_entry)
+               release_firmware(fw_entry);
+
+
+done:
+       getnstimeofday(&end);
+       duration_ns = timespec_to_ns(&end) - timespec_to_ns(&start);
+       dev_dbg(&rmi_dev->dev, "Time to reflash: %lld ns.\n", duration_ns);
+
+       kfree(firmware_name);
+       kfree(header);
+       return;
+}
+
+/*
+ * We also reflash the device if (a) in kernel reflashing is
+ * enabled, and (b) the reflash module decides it requires reflashing.
+ *
+ * We have to do this before actually building the PDT because the reflash
+ * might cause various registers to move around.
+ */
+static int rmi_device_reflash(struct rmi_device *rmi_dev)
+{
+       struct device *dev = &rmi_dev->dev;
+       struct rmi_driver_data *drv_data = dev_get_drvdata(dev);
+       struct rmi_reflash_data *data = drv_data->reflash_data;
+       int retval;
+
+       retval = rmi_find_f01_and_f34(data);
+       if (retval < 0)
+               return retval;
+
+       rmi_fw_update(rmi_dev);
+
+       clear_bit(0, &data->busy);
+
+       return 0;
+}
+
+static ssize_t rmi_reflash_img_name_show(struct device *dev,
+                                        struct device_attribute *attr,
+                                        char *buf)
+{
+       struct rmi_device *rmi_dev = to_rmi_device(dev);
+       struct rmi_driver_data *drv_data = dev_get_drvdata(&rmi_dev->dev);
+       struct rmi_reflash_data *data = drv_data->reflash_data;
+
+       return snprintf(buf, PAGE_SIZE, "%s\n", data->img_name);
+}
+
+static ssize_t rmi_reflash_img_name_store(struct device *dev,
+                                   struct device_attribute *attr,
+                                   const char *buf, size_t count)
+{
+       struct rmi_device *rmi_dev = to_rmi_device(dev);
+       struct rmi_driver_data *drv_data = dev_get_drvdata(&rmi_dev->dev);
+       struct rmi_reflash_data *data = drv_data->reflash_data;
+
+       if (test_and_set_bit(0, &data->busy))
+               return -EBUSY;
+
+       if (!count) {
+               data->img_name = NULL;
+       } else {
+               strlcpy(data->name_buf, buf, count);
+               data->img_name = strstrip(data->name_buf);
+       }
+
+       clear_bit(0, &data->busy);
+       return count;
+}
+
+static ssize_t rmi_reflash_force_show(struct device *dev,
+                                     struct device_attribute *attr, char *buf)
+{
+       struct rmi_device *rmi_dev = to_rmi_device(dev);
+       struct rmi_driver_data *drv_data = dev_get_drvdata(&rmi_dev->dev);
+       struct rmi_reflash_data *data = drv_data->reflash_data;
+
+       return snprintf(buf, PAGE_SIZE, "%u\n", data->force);
+}
+
+static ssize_t rmi_reflash_force_store(struct device *dev,
+                                      struct device_attribute *attr,
+                                      const char *buf, size_t count)
+{
+       struct rmi_device *rmi_dev = to_rmi_device(dev);
+       struct rmi_driver_data *drv_data = dev_get_drvdata(&rmi_dev->dev);
+       struct rmi_reflash_data *data = drv_data->reflash_data;
+       int retval;
+       unsigned long val;
+
+       if (test_and_set_bit(0, &data->busy))
+               return -EBUSY;
+
+       retval = kstrtoul(buf, 10, &val);
+       if (retval)
+               count = retval;
+       else
+               data->force = !!val;
+
+       clear_bit(0, &data->busy);
+
+       return count;
+}
+
+static ssize_t rmi_reflash_show(struct device *dev,
+                               struct device_attribute *attr, char *buf)
+{
+       struct rmi_device *rmi_dev = to_rmi_device(dev);
+       struct rmi_driver_data *drv_data = dev_get_drvdata(&rmi_dev->dev);
+       struct rmi_reflash_data *data = drv_data->reflash_data;
+
+       return snprintf(buf, PAGE_SIZE, "%u\n", test_bit(0, &data->busy));
+}
+
+static ssize_t rmi_reflash_store(struct device *dev,
+                                struct device_attribute *attr,
+                                const char *buf, size_t count)
+{
+       int retval;
+       unsigned long val;
+       struct rmi_device *rmi_dev = to_rmi_device(dev);
+       struct rmi_driver_data *drv_data = dev_get_drvdata(&rmi_dev->dev);
+       struct rmi_reflash_data *data = drv_data->reflash_data;
+
+       retval = kstrtoul(buf, 10, &val);
+       if (retval)
+               return retval;
+
+       if (test_and_set_bit(0, &data->busy))
+               return -EBUSY;
+
+       if (val)
+               /*
+                * TODO: Here we start a work thread to go do the reflash, but
+                * maybe we can just use request_firmware_timeout().
+                */
+               schedule_work(&data->reflash_work);
+       else
+               clear_bit(0, &data->busy);
+
+       return count;
+}
+
+static void rmi_reflash_work(struct work_struct *work)
+{
+       struct rmi_reflash_data *data =
+               container_of(work, struct rmi_reflash_data, reflash_work);
+       struct rmi_device *rmi_dev = data->rmi_dev;
+       int error;
+
+       dev_dbg(&rmi_dev->dev, "%s runs.\n", __func__);
+       error = rmi_device_reflash(rmi_dev);
+       if (error < 0)
+               dev_err(&rmi_dev->dev, "Reflash attempt failed with code: %d.",
+                       error);
+       clear_bit(0, &data->busy);
+}
+
+static DEVICE_ATTR(reflash_force,
+                       (S_IRUGO | S_IWUGO),
+                       rmi_reflash_force_show, rmi_reflash_force_store);
+static DEVICE_ATTR(reflash_name,
+                       (S_IRUGO | S_IWUGO),
+                       rmi_reflash_img_name_show, rmi_reflash_img_name_store);
+static DEVICE_ATTR(reflash,
+                       (S_IRUGO | S_IWUGO),
+                       rmi_reflash_show, rmi_reflash_store);
+
+static struct attribute *rmi_reflash_attrs[] = {
+       &dev_attr_reflash_force.attr,
+       &dev_attr_reflash_name.attr,
+       &dev_attr_reflash.attr,
+       NULL
+};
+
+static const struct attribute_group rmi_reflash_attributes = {
+       .attrs = rmi_reflash_attrs,
+};
+
+void rmi_reflash_init(struct rmi_device *rmi_dev)
+{
+       int error;
+       struct rmi_driver_data *drv_data = dev_get_drvdata(&rmi_dev->dev);
+       struct rmi_reflash_data *data;
+
+       dev_dbg(&rmi_dev->dev, "%s called.\n", __func__);
+
+       data = devm_kzalloc(&rmi_dev->dev, sizeof(struct rmi_reflash_data),
+                           GFP_KERNEL);
+
+       error = sysfs_create_group(&rmi_dev->dev.kobj, &rmi_reflash_attributes);
+       if (error) {
+               dev_warn(&rmi_dev->dev, "Failed to create reflash sysfs 
attributes.\n");
+               return;
+       }
+
+       INIT_WORK(&data->reflash_work, rmi_reflash_work);
+       data->rmi_dev = rmi_dev;
+       drv_data->reflash_data = data;
+}
+
+void rmi_reflash_cleanup(struct rmi_device *rmi_dev)
+{
+       struct rmi_driver_data *drv_data = dev_get_drvdata(&rmi_dev->dev);
+       struct rmi_reflash_data *data = drv_data->reflash_data;
+
+       sysfs_remove_group(&rmi_dev->dev.kobj, &rmi_reflash_attributes);
+       devm_kfree(&rmi_dev->dev, data);
+}
--
To unsubscribe from this list: send the line "unsubscribe linux-input" in
the body of a message to [email protected]
More majordomo info at  http://vger.kernel.org/majordomo-info.html

Reply via email to