|
1 | 1 | // SPDX-License-Identifier: GPL-2.0-only
|
| 2 | +#include <linux/crc-ccitt.h> |
2 | 3 | #include <linux/delay.h>
|
3 | 4 | #include <linux/gpio/consumer.h>
|
4 | 5 | #include <linux/i2c.h>
|
| 6 | +#include <linux/ihex.h> |
5 | 7 | #include <linux/input.h>
|
6 | 8 | #include <linux/input/mt.h>
|
7 | 9 | #include <linux/input/touchscreen.h>
|
|
25 | 27 | #define REG_FIRMWARE_VERSION 0x40
|
26 | 28 | #define REG_PROTOCOL_VERSION 0x42
|
27 | 29 | #define REG_KERNEL_VERSION 0x61
|
| 30 | +#define REG_IC_BUSY 0x80 |
| 31 | +#define REG_IC_BUSY_NOT_BUSY 0x50 |
28 | 32 | #define REG_GET_MODE 0xc0
|
29 | 33 | #define REG_GET_MODE_AP 0x5a
|
30 | 34 | #define REG_GET_MODE_BL 0x55
|
| 35 | +#define REG_SET_MODE_AP 0xc1 |
| 36 | +#define REG_SET_MODE_BL 0xc2 |
| 37 | +#define REG_WRITE_DATA 0xc3 |
| 38 | +#define REG_WRITE_ENABLE 0xc4 |
| 39 | +#define REG_READ_DATA_CRC 0xc7 |
31 | 40 | #define REG_CALIBRATE 0xcc
|
32 | 41 |
|
| 42 | +#define ILI251X_FW_FILENAME "ilitek/ili251x.bin" |
| 43 | + |
33 | 44 | struct ili2xxx_chip {
|
34 | 45 | int (*read_reg)(struct i2c_client *client, u8 reg,
|
35 | 46 | void *buf, size_t len);
|
@@ -546,8 +557,305 @@ static ssize_t ili210x_calibrate(struct device *dev,
|
546 | 557 | }
|
547 | 558 | static DEVICE_ATTR(calibrate, S_IWUSR, NULL, ili210x_calibrate);
|
548 | 559 |
|
| 560 | +static int ili251x_firmware_to_buffer(const struct firmware *fw, |
| 561 | + u8 **buf, u16 *ac_end, u16 *df_end) |
| 562 | +{ |
| 563 | + const struct ihex_binrec *rec; |
| 564 | + u32 fw_addr, fw_last_addr = 0; |
| 565 | + u16 fw_len; |
| 566 | + u8 *fw_buf; |
| 567 | + int error; |
| 568 | + |
| 569 | + /* |
| 570 | + * The firmware ihex blob can never be bigger than 64 kiB, so make this |
| 571 | + * simple -- allocate a 64 kiB buffer, iterate over the ihex blob records |
| 572 | + * once, copy them all into this buffer at the right locations, and then |
| 573 | + * do all operations on this linear buffer. |
| 574 | + */ |
| 575 | + fw_buf = kzalloc(SZ_64K, GFP_KERNEL); |
| 576 | + if (!fw_buf) |
| 577 | + return -ENOMEM; |
| 578 | + |
| 579 | + rec = (const struct ihex_binrec *)fw->data; |
| 580 | + while (rec) { |
| 581 | + fw_addr = be32_to_cpu(rec->addr); |
| 582 | + fw_len = be16_to_cpu(rec->len); |
| 583 | + |
| 584 | + /* The last 32 Byte firmware block can be 0xffe0 */ |
| 585 | + if (fw_addr + fw_len > SZ_64K || fw_addr > SZ_64K - 32) { |
| 586 | + error = -EFBIG; |
| 587 | + goto err_big; |
| 588 | + } |
| 589 | + |
| 590 | + /* Find the last address before DF start address, that is AC end */ |
| 591 | + if (fw_addr == 0xf000) |
| 592 | + *ac_end = fw_last_addr; |
| 593 | + fw_last_addr = fw_addr + fw_len; |
| 594 | + |
| 595 | + memcpy(fw_buf + fw_addr, rec->data, fw_len); |
| 596 | + rec = ihex_next_binrec(rec); |
| 597 | + } |
| 598 | + |
| 599 | + /* DF end address is the last address in the firmware blob */ |
| 600 | + *df_end = fw_addr + fw_len; |
| 601 | + *buf = fw_buf; |
| 602 | + return 0; |
| 603 | + |
| 604 | +err_big: |
| 605 | + kfree(fw_buf); |
| 606 | + return error; |
| 607 | +} |
| 608 | + |
| 609 | +/* Switch mode between Application and BootLoader */ |
| 610 | +static int ili251x_switch_ic_mode(struct i2c_client *client, u8 cmd_mode) |
| 611 | +{ |
| 612 | + struct ili210x *priv = i2c_get_clientdata(client); |
| 613 | + u8 cmd_wren[3] = { REG_WRITE_ENABLE, 0x5a, 0xa5 }; |
| 614 | + u8 md[2]; |
| 615 | + int error; |
| 616 | + |
| 617 | + error = priv->chip->read_reg(client, REG_GET_MODE, md, sizeof(md)); |
| 618 | + if (error) |
| 619 | + return error; |
| 620 | + /* Mode already set */ |
| 621 | + if ((cmd_mode == REG_SET_MODE_AP && md[0] == REG_GET_MODE_AP) || |
| 622 | + (cmd_mode == REG_SET_MODE_BL && md[0] == REG_GET_MODE_BL)) |
| 623 | + return 0; |
| 624 | + |
| 625 | + /* Unlock writes */ |
| 626 | + error = i2c_master_send(client, cmd_wren, sizeof(cmd_wren)); |
| 627 | + if (error != sizeof(cmd_wren)) |
| 628 | + return -EINVAL; |
| 629 | + |
| 630 | + mdelay(20); |
| 631 | + |
| 632 | + /* Select mode (BootLoader or Application) */ |
| 633 | + error = i2c_master_send(client, &cmd_mode, 1); |
| 634 | + if (error != 1) |
| 635 | + return -EINVAL; |
| 636 | + |
| 637 | + mdelay(200); /* Reboot into bootloader takes a lot of time ... */ |
| 638 | + |
| 639 | + /* Read back mode */ |
| 640 | + error = priv->chip->read_reg(client, REG_GET_MODE, md, sizeof(md)); |
| 641 | + if (error) |
| 642 | + return error; |
| 643 | + /* Check if mode is correct now. */ |
| 644 | + if ((cmd_mode == REG_SET_MODE_AP && md[0] == REG_GET_MODE_AP) || |
| 645 | + (cmd_mode == REG_SET_MODE_BL && md[0] == REG_GET_MODE_BL)) |
| 646 | + return 0; |
| 647 | + |
| 648 | + return -EINVAL; |
| 649 | +} |
| 650 | + |
| 651 | +static int ili251x_firmware_busy(struct i2c_client *client) |
| 652 | +{ |
| 653 | + struct ili210x *priv = i2c_get_clientdata(client); |
| 654 | + int error, i = 0; |
| 655 | + u8 data; |
| 656 | + |
| 657 | + do { |
| 658 | + /* The read_reg already contains suitable delay */ |
| 659 | + error = priv->chip->read_reg(client, REG_IC_BUSY, &data, 1); |
| 660 | + if (error) |
| 661 | + return error; |
| 662 | + if (i++ == 100000) |
| 663 | + return -ETIMEDOUT; |
| 664 | + } while (data != REG_IC_BUSY_NOT_BUSY); |
| 665 | + |
| 666 | + return 0; |
| 667 | +} |
| 668 | + |
| 669 | +static int ili251x_firmware_write_to_ic(struct device *dev, u8 *fwbuf, |
| 670 | + u16 start, u16 end, u8 dataflash) |
| 671 | +{ |
| 672 | + struct i2c_client *client = to_i2c_client(dev); |
| 673 | + struct ili210x *priv = i2c_get_clientdata(client); |
| 674 | + u8 cmd_crc = REG_READ_DATA_CRC; |
| 675 | + u8 crcrb[4] = { 0 }; |
| 676 | + u8 fw_data[33]; |
| 677 | + u16 fw_addr; |
| 678 | + int error; |
| 679 | + |
| 680 | + /* |
| 681 | + * The DF (dataflash) needs 2 bytes offset for unknown reasons, |
| 682 | + * the AC (application) has 2 bytes CRC16-CCITT at the end. |
| 683 | + */ |
| 684 | + u16 crc = crc_ccitt(0, fwbuf + start + (dataflash ? 2 : 0), |
| 685 | + end - start - 2); |
| 686 | + |
| 687 | + /* Unlock write to either AC (application) or DF (dataflash) area */ |
| 688 | + u8 cmd_wr[10] = { |
| 689 | + REG_WRITE_ENABLE, 0x5a, 0xa5, dataflash, |
| 690 | + (end >> 16) & 0xff, (end >> 8) & 0xff, end & 0xff, |
| 691 | + (crc >> 16) & 0xff, (crc >> 8) & 0xff, crc & 0xff |
| 692 | + }; |
| 693 | + |
| 694 | + error = i2c_master_send(client, cmd_wr, sizeof(cmd_wr)); |
| 695 | + if (error != sizeof(cmd_wr)) |
| 696 | + return -EINVAL; |
| 697 | + |
| 698 | + error = ili251x_firmware_busy(client); |
| 699 | + if (error) |
| 700 | + return error; |
| 701 | + |
| 702 | + for (fw_addr = start; fw_addr < end; fw_addr += 32) { |
| 703 | + fw_data[0] = REG_WRITE_DATA; |
| 704 | + memcpy(&(fw_data[1]), fwbuf + fw_addr, 32); |
| 705 | + error = i2c_master_send(client, fw_data, 33); |
| 706 | + if (error != sizeof(fw_data)) |
| 707 | + return error; |
| 708 | + error = ili251x_firmware_busy(client); |
| 709 | + if (error) |
| 710 | + return error; |
| 711 | + } |
| 712 | + |
| 713 | + error = i2c_master_send(client, &cmd_crc, 1); |
| 714 | + if (error != 1) |
| 715 | + return -EINVAL; |
| 716 | + |
| 717 | + error = ili251x_firmware_busy(client); |
| 718 | + if (error) |
| 719 | + return error; |
| 720 | + |
| 721 | + error = priv->chip->read_reg(client, REG_READ_DATA_CRC, |
| 722 | + &crcrb, sizeof(crcrb)); |
| 723 | + if (error) |
| 724 | + return error; |
| 725 | + |
| 726 | + /* Check CRC readback */ |
| 727 | + if ((crcrb[0] != (crc & 0xff)) || crcrb[1] != ((crc >> 8) & 0xff)) |
| 728 | + return -EINVAL; |
| 729 | + |
| 730 | + return 0; |
| 731 | +} |
| 732 | + |
| 733 | +static int ili251x_firmware_reset(struct i2c_client *client) |
| 734 | +{ |
| 735 | + u8 cmd_reset[2] = { 0xf2, 0x01 }; |
| 736 | + int error; |
| 737 | + |
| 738 | + error = i2c_master_send(client, cmd_reset, sizeof(cmd_reset)); |
| 739 | + if (error != sizeof(cmd_reset)) |
| 740 | + return -EINVAL; |
| 741 | + |
| 742 | + return ili251x_firmware_busy(client); |
| 743 | +} |
| 744 | + |
| 745 | +static void ili251x_hardware_reset(struct device *dev) |
| 746 | +{ |
| 747 | + struct i2c_client *client = to_i2c_client(dev); |
| 748 | + struct ili210x *priv = i2c_get_clientdata(client); |
| 749 | + |
| 750 | + /* Reset the controller */ |
| 751 | + gpiod_set_value_cansleep(priv->reset_gpio, 1); |
| 752 | + usleep_range(10000, 15000); |
| 753 | + gpiod_set_value_cansleep(priv->reset_gpio, 0); |
| 754 | + msleep(300); |
| 755 | +} |
| 756 | + |
| 757 | +static ssize_t ili210x_firmware_update_store(struct device *dev, |
| 758 | + struct device_attribute *attr, |
| 759 | + const char *buf, size_t count) |
| 760 | +{ |
| 761 | + struct i2c_client *client = to_i2c_client(dev); |
| 762 | + const char *fwname = ILI251X_FW_FILENAME; |
| 763 | + const struct firmware *fw; |
| 764 | + u16 ac_end, df_end; |
| 765 | + u8 *fwbuf; |
| 766 | + int error; |
| 767 | + int i; |
| 768 | + |
| 769 | + error = request_ihex_firmware(&fw, fwname, dev); |
| 770 | + if (error) { |
| 771 | + dev_err(dev, "Failed to request firmware %s, error=%d\n", |
| 772 | + fwname, error); |
| 773 | + return error; |
| 774 | + } |
| 775 | + |
| 776 | + error = ili251x_firmware_to_buffer(fw, &fwbuf, &ac_end, &df_end); |
| 777 | + release_firmware(fw); |
| 778 | + if (error) |
| 779 | + return error; |
| 780 | + |
| 781 | + /* |
| 782 | + * Disable touchscreen IRQ, so that we would not get spurious touch |
| 783 | + * interrupt during firmware update, and so that the IRQ handler won't |
| 784 | + * trigger and interfere with the firmware update. There is no bit in |
| 785 | + * the touch controller to disable the IRQs during update, so we have |
| 786 | + * to do it this way here. |
| 787 | + */ |
| 788 | + disable_irq(client->irq); |
| 789 | + |
| 790 | + dev_dbg(dev, "Firmware update started, firmware=%s\n", fwname); |
| 791 | + |
| 792 | + ili251x_hardware_reset(dev); |
| 793 | + |
| 794 | + error = ili251x_firmware_reset(client); |
| 795 | + if (error) |
| 796 | + goto exit; |
| 797 | + |
| 798 | + /* This may not succeed on first try, so re-try a few times. */ |
| 799 | + for (i = 0; i < 5; i++) { |
| 800 | + error = ili251x_switch_ic_mode(client, REG_SET_MODE_BL); |
| 801 | + if (!error) |
| 802 | + break; |
| 803 | + } |
| 804 | + |
| 805 | + if (error) |
| 806 | + goto exit; |
| 807 | + |
| 808 | + dev_dbg(dev, "IC is now in BootLoader mode\n"); |
| 809 | + |
| 810 | + msleep(200); /* The bootloader seems to need some time too. */ |
| 811 | + |
| 812 | + error = ili251x_firmware_write_to_ic(dev, fwbuf, 0xf000, df_end, 1); |
| 813 | + if (error) { |
| 814 | + dev_err(dev, "DF firmware update failed, error=%d\n", error); |
| 815 | + goto exit; |
| 816 | + } |
| 817 | + |
| 818 | + dev_dbg(dev, "DataFlash firmware written\n"); |
| 819 | + |
| 820 | + error = ili251x_firmware_write_to_ic(dev, fwbuf, 0x2000, ac_end, 0); |
| 821 | + if (error) { |
| 822 | + dev_err(dev, "AC firmware update failed, error=%d\n", error); |
| 823 | + goto exit; |
| 824 | + } |
| 825 | + |
| 826 | + dev_dbg(dev, "Application firmware written\n"); |
| 827 | + |
| 828 | + /* This may not succeed on first try, so re-try a few times. */ |
| 829 | + for (i = 0; i < 5; i++) { |
| 830 | + error = ili251x_switch_ic_mode(client, REG_SET_MODE_AP); |
| 831 | + if (!error) |
| 832 | + break; |
| 833 | + } |
| 834 | + |
| 835 | + if (error) |
| 836 | + goto exit; |
| 837 | + |
| 838 | + dev_dbg(dev, "IC is now in Application mode\n"); |
| 839 | + |
| 840 | + error = ili251x_firmware_update_cached_state(dev); |
| 841 | + if (error) |
| 842 | + goto exit; |
| 843 | + |
| 844 | + error = count; |
| 845 | + |
| 846 | +exit: |
| 847 | + ili251x_hardware_reset(dev); |
| 848 | + dev_dbg(dev, "Firmware update ended, error=%i\n", error); |
| 849 | + enable_irq(client->irq); |
| 850 | + kfree(fwbuf); |
| 851 | + return error; |
| 852 | +} |
| 853 | + |
| 854 | +static DEVICE_ATTR(firmware_update, 0200, NULL, ili210x_firmware_update_store); |
| 855 | + |
549 | 856 | static struct attribute *ili210x_attributes[] = {
|
550 | 857 | &dev_attr_calibrate.attr,
|
| 858 | + &dev_attr_firmware_update.attr, |
551 | 859 | &dev_attr_firmware_version.attr,
|
552 | 860 | &dev_attr_kernel_version.attr,
|
553 | 861 | &dev_attr_protocol_version.attr,
|
|
0 commit comments