@@ -488,8 +495,312 @@ static ssize_t ili210x_calibrate(struct device *dev,
}
static DEVICE_ATTR(calibrate, S_IWUSR, NULL, ili210x_calibrate);
+static int ili251x_firmware_to_buffer(struct device *dev,
+ const char *fwname, u8 **buf,
+ u16 *ac_end, u16 *df_end)
+{
+ const struct firmware *fw = NULL;
+ const struct ihex_binrec *rec;
+ u32 fw_addr, fw_last_addr = 0;
+ u16 fw_len;
+ u8 *fw_buf;
+ int ret;
+
+ ret = request_ihex_firmware(&fw, fwname, dev);
+ if (ret) {
+ dev_err(dev, "Failed to request firmware %s, ret=%d\n", fwname, ret);
+ return ret;
+ }
+
+ /*
+ * The firmware ihex blob can never be bigger than 64 kiB, so make this
+ * simple -- allocate a 64 kiB buffer, iterate over the ihex blob records
+ * once, copy them all into this buffer at the right locations, and then
+ * do all operations on this linear buffer.
+ */
+ fw_buf = kcalloc(1, SZ_64K, GFP_KERNEL);
+ if (!fw_buf) {
+ ret = -ENOMEM;
+ goto err_alloc;
+ }
+
+ rec = (const struct ihex_binrec *)fw->data;
+ while (rec) {
+ fw_addr = be32_to_cpu(rec->addr);
+ fw_len = be16_to_cpu(rec->len);
+
+ if (fw_addr + fw_len > SZ_64K) {
+ ret = -EFBIG;
+ goto err_big;
+ }
+
+ /* Find the last address before DF start address, that is AC end */
+ if (fw_addr == 0xf000)
+ *ac_end = fw_last_addr;
+ fw_last_addr = fw_addr + fw_len;
+
+ memcpy(fw_buf + fw_addr, rec->data, fw_len);
+ rec = ihex_next_binrec(rec);
+ }
+
+ /* DF end address is the last address in the firmware blob */
+ *df_end = fw_addr + fw_len;
+ *buf = fw_buf;
+ release_firmware(fw);
+ return 0;
+err_big:
+ kfree(fw_buf);
+err_alloc:
+ release_firmware(fw);
+ return ret;
+}
+
+/* Switch mode between Application and BootLoader */
+static int ili251x_switch_ic_mode(struct i2c_client *client, u8 cmd_mode)
+{
+ struct ili210x *priv = i2c_get_clientdata(client);
+ u8 cmd_wren[3] = { REG_WRITE_ENABLE, 0x5a, 0xa5 };
+ u8 md[2];
+ int ret;
+
+ ret = priv->chip->read_reg(client, REG_GET_MODE, md, sizeof(md));
+ if (ret)
+ return ret;
+ /* Mode already set */
+ if ((cmd_mode == REG_SET_MODE_AP && md[0] == REG_GET_MODE_AP) ||
+ (cmd_mode == REG_SET_MODE_BL && md[0] == REG_GET_MODE_BL))
+ return 0;
+
+ /* Unlock writes */
+ ret = i2c_master_send(client, cmd_wren, sizeof(cmd_wren));
+ if (ret != sizeof(cmd_wren))
+ return -EINVAL;
+
+ mdelay(20);
+
+ /* Select mode (BootLoader or Application) */
+ ret = i2c_master_send(client, &cmd_mode, 1);
+ if (ret != 1)
+ return -EINVAL;
+
+ mdelay(200); /* Reboot into bootloader takes a lot of time ... */
+
+ /* Read back mode */
+ ret = priv->chip->read_reg(client, REG_GET_MODE, md, sizeof(md));
+ if (ret)
+ return ret;
+ /* Check if mode is correct now. */
+ if ((cmd_mode == REG_SET_MODE_AP && md[0] == REG_GET_MODE_AP) ||
+ (cmd_mode == REG_SET_MODE_BL && md[0] == REG_GET_MODE_BL))
+ return 0;
+
+ return -EINVAL;
+}
+
+static int ili251x_firmware_busy(struct i2c_client *client)
+{
+ struct ili210x *priv = i2c_get_clientdata(client);
+ int ret, i = 0;
+ u8 data;
+
+ do {
+ /* The read_reg already contains suitable delay */
+ ret = priv->chip->read_reg(client, 0x80, &data, 1);
+ if (ret)
+ return ret;
+ if (i++ == 100000)
+ return -ETIMEDOUT;
+ } while (data != 0x50);
+
+ return 0;
+}
+
+static int ili251x_firmware_write_to_ic(struct device *dev, u8 *fwbuf,
+ u16 start, u16 end, u8 dataflash)
+{
+ struct i2c_client *client = to_i2c_client(dev);
+ struct ili210x *priv = i2c_get_clientdata(client);
+ u8 cmd_crc = REG_READ_DATA_CRC;
+ u8 crcrb[4] = { 0 };
+ u8 fw_data[33];
+ u16 fw_addr;
+ int ret;
+
+ /*
+ * The DF (dataflash) needs 2 bytes offset for unknown reasons,
+ * the AC (application) has 2 bytes CRC16-CCITT at the end.
+ */
+ u16 crc = crc_ccitt(0, fwbuf + start + (dataflash ? 2 : 0),
+ end - start - 2);
+
+ /* Unlock write to either AC (application) or DF (dataflash) area */
+ u8 cmd_wr[10] = {
+ REG_WRITE_ENABLE, 0x5a, 0xa5, dataflash,
+ (end >> 16) & 0xff, (end >> 8) & 0xff, end & 0xff,
+ (crc >> 16) & 0xff, (crc >> 8) & 0xff, crc & 0xff
+ };
+
+ ret = i2c_master_send(client, cmd_wr, sizeof(cmd_wr));
+ if (ret != sizeof(cmd_wr))
+ return -EINVAL;
+
+ ret = ili251x_firmware_busy(client);
+ if (ret)
+ return ret;
+
+ for (fw_addr = start; fw_addr < end; fw_addr += 32) {
+ fw_data[0] = REG_WRITE_DATA;
+ memcpy(&(fw_data[1]), fwbuf + fw_addr, 32);
+ ret = i2c_master_send(client, fw_data, 33);
+ if (ret != sizeof(fw_data))
+ return ret;
+ ret = ili251x_firmware_busy(client);
+ if (ret)
+ return ret;
+ }
+
+ ret = i2c_master_send(client, &cmd_crc, 1);
+ if (ret != 1)
+ return -EINVAL;
+
+ ret = ili251x_firmware_busy(client);
+ if (ret)
+ return ret;
+
+ ret = priv->chip->read_reg(client, REG_READ_DATA_CRC,
+ &crcrb, sizeof(crcrb));
+ if (ret)
+ return ret;
+
+ /* Check CRC readback */
+ if ((crcrb[0] != (crc & 0xff)) || crcrb[1] != ((crc >> 8) & 0xff))
+ return -EINVAL;
+
+ return 0;
+}
+
+static int ili251x_firmware_reset(struct i2c_client *client)
+{
+ u8 cmd_reset[2] = { 0xf2, 0x01 };
+ int ret;
+
+ ret = i2c_master_send(client, cmd_reset, sizeof(cmd_reset));
+ if (ret != sizeof(cmd_reset))
+ return -EINVAL;
+
+ return ili251x_firmware_busy(client);
+}
+
+static void ili251x_hardware_reset(struct device *dev)
+{
+ struct i2c_client *client = to_i2c_client(dev);
+ struct ili210x *priv = i2c_get_clientdata(client);
+
+ /* Reset the controller */
+ gpiod_set_value_cansleep(priv->reset_gpio, 1);
+ usleep_range(10000, 15000);
+ gpiod_set_value_cansleep(priv->reset_gpio, 0);
+ msleep(300);
+}
+
+static ssize_t ili210x_firmware_update_store(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf, size_t count)
+{
+ struct i2c_client *client = to_i2c_client(dev);
+ struct ili210x *priv = i2c_get_clientdata(client);
+ char fwname[NAME_MAX];
+ u16 ac_end, df_end;
+ u8 *fwbuf;
+ int ret;
+ int i;
+
+ if (count >= NAME_MAX) {
+ dev_err(dev, "File name too long\n");
+ return -EINVAL;
+ }
+
+ memcpy(fwname, buf, count);
+ if (fwname[count - 1] == '\n')
+ fwname[count - 1] = '\0';
+ else
+ fwname[count] = '\0';
+
+ ret = ili251x_firmware_to_buffer(dev, fwname, &fwbuf, &ac_end, &df_end);
+ if (ret)
+ return ret;
+
+ mutex_lock(&priv->lock);
+
+ dev_info(dev, "Firmware update started, firmware=%s\n", fwname);
+
+ ili251x_hardware_reset(dev);
+
+ ret = ili251x_firmware_reset(client);
+ if (ret)
+ goto exit;
+
+ /* This may not succeed on first try, so re-try a few times. */
+ for (i = 0; i < 5; i++) {
+ ret = ili251x_switch_ic_mode(client, REG_SET_MODE_BL);
+ if (!ret)
+ break;
+ }
+
+ if (ret)
+ goto exit;
+
+ dev_info(dev, "IC is now in BootLoader mode\n");
+
+ msleep(200); /* The bootloader seems to need some time too. */
+
+ ret = ili251x_firmware_write_to_ic(dev, fwbuf, 0xf000, df_end, 1);
+ if (ret) {
+ dev_err(dev, "DF firmware update failed, ret=%d\n", ret);
+ goto exit;
+ }
+
+ dev_info(dev, "DataFlash firmware written\n");
+
+ ret = ili251x_firmware_write_to_ic(dev, fwbuf, 0x2000, ac_end, 0);
+ if (ret) {
+ dev_err(dev, "AC firmware update failed, ret=%d\n", ret);
+ goto exit;
+ }
+
+ dev_info(dev, "Application firmware written\n");
+
+ /* This may not succeed on first try, so re-try a few times. */
+ for (i = 0; i < 5; i++) {
+ ret = ili251x_switch_ic_mode(client, REG_SET_MODE_AP);
+ if (!ret)
+ break;
+ }
+
+ if (ret)
+ goto exit;
+
+ dev_info(dev, "IC is now in Application mode\n");
+
+ ret = ili251x_firmware_update_resolution(dev);
+ if (ret)
+ goto exit;
+
+ ret = count;
+
+exit:
+ ili251x_hardware_reset(dev);
+ dev_info(dev, "Firmware update ended, ret=%i\n", ret);
+ mutex_unlock(&priv->lock);
+ kfree(fwbuf);
+ return ret;
+}
+
+static DEVICE_ATTR(firmware_update, 0200, NULL, ili210x_firmware_update_store);
+
static struct attribute *ili210x_attributes[] = {
&dev_attr_calibrate.attr,
+ &dev_attr_firmware_update.attr,
&dev_attr_firmware_version.attr,
&dev_attr_kernel_version.attr,
&dev_attr_protocol_version.attr,