From patchwork Thu Aug 19 20:17:58 2021 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Marek Vasut X-Patchwork-Id: 12447933 Return-Path: X-Spam-Checker-Version: SpamAssassin 3.4.0 (2014-02-07) on aws-us-west-2-korg-lkml-1.web.codeaurora.org X-Spam-Level: X-Spam-Status: No, score=-18.8 required=3.0 tests=BAYES_00,DKIM_SIGNED, DKIM_VALID,DKIM_VALID_AU,HEADER_FROM_DIFFERENT_DOMAINS,INCLUDES_CR_TRAILER, INCLUDES_PATCH,MAILING_LIST_MULTI,SPF_HELO_NONE,SPF_PASS,USER_AGENT_GIT autolearn=ham autolearn_force=no version=3.4.0 Received: from mail.kernel.org (mail.kernel.org [198.145.29.99]) by smtp.lore.kernel.org (Postfix) with ESMTP id 0C86FC4338F for ; Thu, 19 Aug 2021 20:18:27 +0000 (UTC) Received: from vger.kernel.org (vger.kernel.org [23.128.96.18]) by mail.kernel.org (Postfix) with ESMTP id D516F610F9 for ; Thu, 19 Aug 2021 20:18:26 +0000 (UTC) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S234626AbhHSUTC (ORCPT ); Thu, 19 Aug 2021 16:19:02 -0400 Received: from phobos.denx.de ([85.214.62.61]:43890 "EHLO phobos.denx.de" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S229853AbhHSUTC (ORCPT ); Thu, 19 Aug 2021 16:19:02 -0400 Received: from tr.lan (ip-89-176-112-137.net.upcbroadband.cz [89.176.112.137]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits)) (No client certificate requested) (Authenticated sender: marex@denx.de) by phobos.denx.de (Postfix) with ESMTPSA id 26AD38023C; Thu, 19 Aug 2021 22:18:24 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=denx.de; s=phobos-20191101; t=1629404304; bh=oIVhj386a6c2MMOqqy8i4/TR5xjoGdMqefgrqvPYzBA=; h=From:To:Cc:Subject:Date:From; b=X6wW7ACjuMSsMMLeS8559sLvegXDWGXkfq31enm0g77cMj+Dd27LYZ/hWbb5JEinH 1YsXqfZLuCRSmDi47I1eSMF6jAinepMhSvhOLWVUu3Bn5kuhozAVWeE6y33eVKOK1Q x+1xH6RC9WuO/FIX0sEkbp5Ix3Xhu0oVkCozdGuXGz/Taaqn49rKjm6X5cafw0p2p2 xhm3Zl6QYVaf5jNEjBDq8UYv9hon2xDXG4o25qcbYYaXMDKY0G4ftlc29D6RMWTfa1 LcZnP8kltG/FYl52vy3+dqEFnNh+9fBh7BrBB4Udl5m1GULrH+lvLoImLZR3mAViY/ o+3gTwzXo0uEw== From: Marek Vasut To: linux-input@vger.kernel.org Cc: ch@denx.de, Marek Vasut , Dmitry Torokhov , Joe Hung , Luca Hsu Subject: [PATCH 1/2] Input: ili210x - export ili251x version details via sysfs Date: Thu, 19 Aug 2021 22:17:58 +0200 Message-Id: <20210819201759.2084481-1-marex@denx.de> X-Mailer: git-send-email 2.30.2 MIME-Version: 1.0 X-Virus-Scanned: clamav-milter 0.103.2 at phobos.denx.de X-Virus-Status: Clean Precedence: bulk List-ID: X-Mailing-List: linux-input@vger.kernel.org The ili251x firmware protocol permits readout of firmware version, protocol version, mcu version and current mode (application, boot loader, forced update). These information are useful when updating the firmware on the il251x, e.g. to avoid updating the same firmware into the device multiple times. The locking is now necessary to avoid races between interrupt handler and the sysfs readouts. Note that the protocol differs considerably between the ili2xxx devices, this patch therefore implements this functionality only for ili251x that I can test. Signed-off-by: Marek Vasut Cc: Dmitry Torokhov Cc: Joe Hung Cc: Luca Hsu --- NOTE: Regarding checkpatch warnings, Consider renaming function(s) 'ili251x_firmware_version_show' to 'firmware_version_show', I considered it and decided against it, because grepping for ili251x in debug symbols gives far more accurate results than grepping for firmware_version. --- drivers/input/touchscreen/ili210x.c | 130 +++++++++++++++++++++++++++- 1 file changed, 127 insertions(+), 3 deletions(-) diff --git a/drivers/input/touchscreen/ili210x.c b/drivers/input/touchscreen/ili210x.c index 30576a5f2f045..894f74dba5abf 100644 --- a/drivers/input/touchscreen/ili210x.c +++ b/drivers/input/touchscreen/ili210x.c @@ -22,6 +22,12 @@ /* Touchscreen commands */ #define REG_TOUCHDATA 0x10 #define REG_PANEL_INFO 0x20 +#define REG_FIRMWARE_VERSION 0x40 +#define REG_PROTOCOL_VERSION 0x42 +#define REG_KERNEL_VERSION 0x61 +#define REG_IC_MODE 0xc0 +#define REG_IC_MODE_AP 0x5a +#define REG_IC_MODE_BL 0x55 #define REG_CALIBRATE 0xcc struct ili2xxx_chip { @@ -43,6 +49,7 @@ struct ili210x { struct input_dev *input; struct gpio_desc *reset_gpio; struct touchscreen_properties prop; + struct mutex lock; const struct ili2xxx_chip *chip; bool stop; }; @@ -307,7 +314,9 @@ static irqreturn_t ili210x_irq(int irq, void *irq_data) int error; do { + mutex_lock(&priv->lock); error = chip->get_touch_data(client, touchdata); + mutex_unlock(&priv->lock); if (error) { dev_err(&client->dev, "Unable to get touch data: %d\n", error); @@ -323,6 +332,108 @@ static irqreturn_t ili210x_irq(int irq, void *irq_data) return IRQ_HANDLED; } +static ssize_t ili251x_firmware_version_show(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct i2c_client *client = to_i2c_client(dev); + struct ili210x *priv = i2c_get_clientdata(client); + u8 fw[8]; + int ret; + + /* Get firmware version */ + mutex_lock(&priv->lock); + ret = priv->chip->read_reg(client, REG_FIRMWARE_VERSION, + &fw, sizeof(fw)); + mutex_unlock(&priv->lock); + + if (ret) + return ret; + + if (!ret) { + ret = scnprintf(buf, PAGE_SIZE, + "%02x%02x.%02x%02x.%02x%02x.%02x%02x\n", + fw[0], fw[1], fw[2], fw[3], + fw[4], fw[5], fw[6], fw[7]); + } + + return ret; +} +static DEVICE_ATTR(firmware_version, 0444, ili251x_firmware_version_show, NULL); + +static ssize_t ili251x_kernel_version_show(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct i2c_client *client = to_i2c_client(dev); + struct ili210x *priv = i2c_get_clientdata(client); + u8 kv[5]; + int ret; + + /* Get kernel version */ + mutex_lock(&priv->lock); + ret = priv->chip->read_reg(client, REG_KERNEL_VERSION, + &kv, sizeof(kv)); + mutex_unlock(&priv->lock); + + if (ret) + return ret; + + return scnprintf(buf, PAGE_SIZE, "%02x.%02x.%02x.%02x.%02x\n", + kv[0], kv[1], kv[2], kv[3], kv[4]); +} +static DEVICE_ATTR(kernel_version, 0444, ili251x_kernel_version_show, NULL); + +static ssize_t ili251x_protocol_version_show(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct i2c_client *client = to_i2c_client(dev); + struct ili210x *priv = i2c_get_clientdata(client); + u8 pv[2]; + int ret; + + /* Get protocol version */ + mutex_lock(&priv->lock); + ret = priv->chip->read_reg(client, REG_PROTOCOL_VERSION, + &pv, sizeof(pv)); + mutex_unlock(&priv->lock); + + if (ret) + return ret; + + return scnprintf(buf, PAGE_SIZE, "%02x.%02x\n", pv[0], pv[1]); +} +static DEVICE_ATTR(protocol_version, 0444, ili251x_protocol_version_show, NULL); + +static ssize_t ili251x_mode_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct i2c_client *client = to_i2c_client(dev); + struct ili210x *priv = i2c_get_clientdata(client); + char *mode = "AP"; + u8 md[2]; + int ret; + + /* Get mode */ + mutex_lock(&priv->lock); + ret = priv->chip->read_reg(client, REG_IC_MODE, &md, sizeof(md)); + mutex_unlock(&priv->lock); + + if (ret) + return ret; + + if (md[0] == REG_IC_MODE_AP) /* Application Mode */ + mode = "AP"; + else if (md[0] == REG_IC_MODE_BL) /* BootLoader Mode */ + mode = "BL"; + else /* Unknown Mode */ + mode = "??"; + + return scnprintf(buf, PAGE_SIZE, "%02x.%02x:%s\n", md[0], md[1], mode); +} +static DEVICE_ATTR(mode, 0444, ili251x_mode_show, NULL); + static ssize_t ili210x_calibrate(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) @@ -351,22 +462,34 @@ static DEVICE_ATTR(calibrate, S_IWUSR, NULL, ili210x_calibrate); static struct attribute *ili210x_attributes[] = { &dev_attr_calibrate.attr, + &dev_attr_firmware_version.attr, + &dev_attr_kernel_version.attr, + &dev_attr_protocol_version.attr, + &dev_attr_mode.attr, NULL, }; -static umode_t ili210x_calibrate_visible(struct kobject *kobj, +static umode_t ili210x_attributes_visible(struct kobject *kobj, struct attribute *attr, int index) { struct device *dev = kobj_to_dev(kobj); struct i2c_client *client = to_i2c_client(dev); struct ili210x *priv = i2c_get_clientdata(client); - return priv->chip->has_calibrate_reg ? attr->mode : 0; + /* Calibrate is present on all ILI2xxx which have calibrate register */ + if (attr == &dev_attr_calibrate.attr) + return priv->chip->has_calibrate_reg ? attr->mode : 0; + + /* Firmware/Kernel/Protocol/BootMode is implememted only for ILI251x */ + if (priv->chip != &ili251x_chip) + return 0; + + return attr->mode; } static const struct attribute_group ili210x_attr_group = { .attrs = ili210x_attributes, - .is_visible = ili210x_calibrate_visible, + .is_visible = ili210x_attributes_visible, }; static void ili210x_power_down(void *data) @@ -437,6 +560,7 @@ static int ili210x_i2c_probe(struct i2c_client *client, priv->input = input; priv->reset_gpio = reset_gpio; priv->chip = chip; + mutex_init(&priv->lock); i2c_set_clientdata(client, priv); /* Setup input device */ From patchwork Thu Aug 19 20:17:59 2021 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Marek Vasut X-Patchwork-Id: 12447935 Return-Path: X-Spam-Checker-Version: SpamAssassin 3.4.0 (2014-02-07) on aws-us-west-2-korg-lkml-1.web.codeaurora.org X-Spam-Level: X-Spam-Status: No, score=-18.8 required=3.0 tests=BAYES_00,DKIM_SIGNED, DKIM_VALID,DKIM_VALID_AU,HEADER_FROM_DIFFERENT_DOMAINS,INCLUDES_CR_TRAILER, INCLUDES_PATCH,MAILING_LIST_MULTI,SPF_HELO_NONE,SPF_PASS,USER_AGENT_GIT autolearn=ham autolearn_force=no version=3.4.0 Received: from mail.kernel.org (mail.kernel.org [198.145.29.99]) by smtp.lore.kernel.org (Postfix) with ESMTP id 8EC3EC4320A for ; Thu, 19 Aug 2021 20:18:27 +0000 (UTC) Received: from vger.kernel.org (vger.kernel.org [23.128.96.18]) by mail.kernel.org (Postfix) with ESMTP id 75B4560FDC for ; Thu, 19 Aug 2021 20:18:27 +0000 (UTC) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S229853AbhHSUTD (ORCPT ); Thu, 19 Aug 2021 16:19:03 -0400 Received: from phobos.denx.de ([85.214.62.61]:43916 "EHLO phobos.denx.de" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S231683AbhHSUTC (ORCPT ); Thu, 19 Aug 2021 16:19:02 -0400 Received: from tr.lan (ip-89-176-112-137.net.upcbroadband.cz [89.176.112.137]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits)) (No client certificate requested) (Authenticated sender: marex@denx.de) by phobos.denx.de (Postfix) with ESMTPSA id AADFB80F47; Thu, 19 Aug 2021 22:18:24 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=denx.de; s=phobos-20191101; t=1629404305; bh=NEqC85ilOkEZPLWwsARDpxYbWvqOBDg/qEveQG7twjo=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=lSZWFXGK9H0K5T2fbA9zak/RG6W8MO0I7zvDEMiGbq97gz6cERlFoJNdRAL/QsH3k vhCijihaLuSaE31JQ5FB1bKqJ/czVJGdvD2ZP2qkZFw9OQmnwZZKgAGjYVJyqo6/TV oACvlS6T7/8igsQHFGb7MODzjHNH+n+iSxCVVJOKbNvH3W6oiSf2T3hBCqbES81Vd6 qFYDeXF0q+IiNb7gEPrX7aDGgPV4c4p1WZE22kmE9K1A7EI1ejfr6aas7iiUL98lfA fAkBJVfEXS+XLOWliiOahG4vLqofYhV9DnDg/PYMhCZJhW0focvXbQHS78FF2pAnpI 3qE2dZDtzlpNw== From: Marek Vasut To: linux-input@vger.kernel.org Cc: ch@denx.de, Marek Vasut , Dmitry Torokhov , Joe Hung , Luca Hsu Subject: [PATCH 2/2] Input: ili210x - add ili251x firmware update support Date: Thu, 19 Aug 2021 22:17:59 +0200 Message-Id: <20210819201759.2084481-2-marex@denx.de> X-Mailer: git-send-email 2.30.2 In-Reply-To: <20210819201759.2084481-1-marex@denx.de> References: <20210819201759.2084481-1-marex@denx.de> MIME-Version: 1.0 X-Virus-Scanned: clamav-milter 0.103.2 at phobos.denx.de X-Virus-Status: Clean Precedence: bulk List-ID: X-Mailing-List: linux-input@vger.kernel.org The ili251x firmware can be updated, this is used when switching between different modes of operation of the touch surface, e.g. glove operation. This patch implements the firmware update mechanism triggered by a write of firmware name into an sysfs attribute. The firmware itself is distributed as an intel hex file with non-standard types. The first two lines are of type 0xad, which indicates the start of DataFlash payload, that is always at address 0xf000 on the ili251x, so it can be dropped, and 0xac which indicates the position of firmware info in the Application payload, that is always at address 0x2020 on the ili251x and we do not care. The rest of the firmware is data of type 0x00, and we care about that. To convert the firmware hex file into something usable by the kernel, remove the first two lines and then use ihex2fw: $ tail -n +3 input.hex > temp.hex $ ./tools/firmware/ihex2fw temp.hex firmware/touch.bin To trigger the firmware update, place touch.bin or whichever file name you pick into /lib/firmware/, write that zero terminated file name into firmware_update sysfs attribute, wait about 30-40 seconds. The firmware update is slow. Then verify firmware_version and mode sysfs attributes to verify whether the firmware got updated and the controller switched back to application (AP) mode by reading out firmware_version attribute in sysfs. Note that the content of firmware_version, e.g. 0600.0005.abcd.aa04 can be matched to the content of the firmware hex file. The first four bytes, 0x06 0x00 0x00 0x05 can be found at ^:102030 00 05000006, the next four bytes 0xab 0xcd 0xaa 0x04 at ^:10F000 00 nnnnnnnn ABCDAA04. Note that the protocol differs considerably between the ili2xxx devices, this patch therefore implements this functionality only for ili251x that I can test. Signed-off-by: Marek Vasut Cc: Dmitry Torokhov Cc: Joe Hung Cc: Luca Hsu --- drivers/input/touchscreen/Kconfig | 1 + drivers/input/touchscreen/ili210x.c | 291 ++++++++++++++++++++++++++++ 2 files changed, 292 insertions(+) diff --git a/drivers/input/touchscreen/Kconfig b/drivers/input/touchscreen/Kconfig index ad454cd2855a0..4d34043cdf010 100644 --- a/drivers/input/touchscreen/Kconfig +++ b/drivers/input/touchscreen/Kconfig @@ -425,6 +425,7 @@ config TOUCHSCREEN_HYCON_HY46XX config TOUCHSCREEN_ILI210X tristate "Ilitek ILI210X based touchscreen" depends on I2C + select CRC_CCITT help Say Y here if you have a ILI210X based touchscreen controller. This driver supports models ILI2102, diff --git a/drivers/input/touchscreen/ili210x.c b/drivers/input/touchscreen/ili210x.c index 894f74dba5abf..f754356a98b29 100644 --- a/drivers/input/touchscreen/ili210x.c +++ b/drivers/input/touchscreen/ili210x.c @@ -1,7 +1,9 @@ // SPDX-License-Identifier: GPL-2.0-only +#include #include #include #include +#include #include #include #include @@ -28,6 +30,10 @@ #define REG_IC_MODE 0xc0 #define REG_IC_MODE_AP 0x5a #define REG_IC_MODE_BL 0x55 +#define REG_SET_MODE_AP 0xc1 +#define REG_SET_MODE_BL 0xc2 +#define REG_WRITE_DATA 0xc3 +#define REG_WRITE_ENABLE 0xc4 #define REG_CALIBRATE 0xcc struct ili2xxx_chip { @@ -460,8 +466,293 @@ 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; +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_IC_MODE, md, sizeof(md)); + if (ret) + return ret; + /* Mode already set */ + if ((cmd_mode == REG_SET_MODE_AP && md[0] == REG_IC_MODE_AP) || + (cmd_mode == REG_SET_MODE_BL && md[0] == REG_IC_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_IC_MODE, md, sizeof(md)); + if (ret) + return ret; + /* Check if mode is correct now. */ + if ((cmd_mode == REG_SET_MODE_AP && md[0] == REG_IC_MODE_AP) || + (cmd_mode == REG_SET_MODE_BL && md[0] == REG_IC_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 crcrb[4] = { 0 }; + u8 cmd_mode = 0xc7; + 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_mode, 1); + if (ret != 1) + return -EINVAL; + + ret = ili251x_firmware_busy(client); + if (ret) + return ret; + + ret = priv->chip->read_reg(client, 0xc7, &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); + u16 ac_end, df_end; + u8 *fwbuf; + int ret; + int i; + + ret = ili251x_firmware_to_buffer(dev, buf, &fwbuf, &ac_end, &df_end); + if (ret) + return ret; + + mutex_lock(&priv->lock); + + dev_info(dev, "Firmware update started, firmware=%s\n", buf); + + 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 = 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,