From: George Chan <gchan9527@xxxxxxxxx> This is a driver for the Novatek in-cell touch controller and supports various chips from the NT36xxx family, currently including below as listed: - NT36525 - NT36672A - NT36675 - NT36676F - NT36772 - NT36870 Functionality like wake gestures and firmware flashing is not included. Firmware loading at probe is supported. This driver is lightly based on the downstream implementation [1]. [1] https://github.com/Rasenkai/caf-tsoft-Novatek-nt36xxx And rewrite as standard i2c driver [2][3] [2] https://lore.kernel.org/all/20201026173045.165236-4-kholk11@xxxxxxxxx/T/ [3] https://lore.kernel.org/lkml/20230808-topic-nt36xxx-v10-1-dd135dfa0b5e@xxxxxxxxxx/T/ Then a new rewrite further happened for spi driver [4] [4] https://github.com/99degree/linux/tree/nt36xxx This driver referenced to goodix berlin driver spi ops that had been in mainline kernel. Also, implementation included panel-follower ops, pm_runtime ops as well as firmware download ops that is added as spi driver. Signed-off-by: AngeloGioacchino Del Regno <kholk11@xxxxxxxxx> Signed-off-by: George Chan <gchan9527@xxxxxxxxx> --- drivers/input/touchscreen/Kconfig | 13 + drivers/input/touchscreen/Makefile | 2 + drivers/input/touchscreen/nt36xxx.h | 142 +++ drivers/input/touchscreen/nt36xxx_core.c | 1422 ++++++++++++++++++++++++++++++ drivers/input/touchscreen/nt36xxx_spi.c | 256 ++++++ 5 files changed, 1835 insertions(+) diff --git a/drivers/input/touchscreen/Kconfig b/drivers/input/touchscreen/Kconfig index 1ac26fc2e3..654877da4c 100644 --- a/drivers/input/touchscreen/Kconfig +++ b/drivers/input/touchscreen/Kconfig @@ -663,6 +663,19 @@ config TOUCHSCREEN_IMAGIS To compile this driver as a module, choose M here: the module will be called imagis. +config TOUCHSCREEN_NT36XXX_SPI + tristate "Novatek NT36XXX In-Cell SPI touchscreen controller" + depends on SPI_MASTER + select REGMAP + help + Say Y here if you have a Novatek NT36xxx series In-Cell + touchscreen connected to your system over SPI. + + If unsure, say N. + + To compile this driver as a module, choose M here: the + module will be called nt36xxx_ts_spi. + config TOUCHSCREEN_IMX6UL_TSC tristate "Freescale i.MX6UL touchscreen controller" depends on ((OF && GPIOLIB) || COMPILE_TEST) && HAS_IOMEM diff --git a/drivers/input/touchscreen/Makefile b/drivers/input/touchscreen/Makefile index 82bc837ca0..6358a636a5 100644 --- a/drivers/input/touchscreen/Makefile +++ b/drivers/input/touchscreen/Makefile @@ -7,6 +7,7 @@ wm97xx-ts-y := wm97xx-core.o goodix_ts-y := goodix.o goodix_fwupload.o +nt36xxx_spi_ts-y := nt36xxx_spi.o nt36xxx_core.o obj-$(CONFIG_TOUCHSCREEN_88PM860X) += 88pm860x-ts.o obj-$(CONFIG_TOUCHSCREEN_AD7877) += ad7877.o @@ -67,6 +68,7 @@ obj-$(CONFIG_TOUCHSCREEN_MSG2638) += msg2638.o obj-$(CONFIG_TOUCHSCREEN_MTOUCH) += mtouch.o obj-$(CONFIG_TOUCHSCREEN_MK712) += mk712.o obj-$(CONFIG_TOUCHSCREEN_NOVATEK_NVT_TS) += novatek-nvt-ts.o +obj-$(CONFIG_TOUCHSCREEN_NT36XXX_SPI) += nt36xxx_spi_ts.o obj-$(CONFIG_TOUCHSCREEN_HP600) += hp680_ts_input.o obj-$(CONFIG_TOUCHSCREEN_HP7XX) += jornada720_ts.o obj-$(CONFIG_TOUCHSCREEN_IPAQ_MICRO) += ipaq-micro-ts.o diff --git a/drivers/input/touchscreen/nt36xxx.h b/drivers/input/touchscreen/nt36xxx.h new file mode 100644 index 0000000000..7bf1f72290 --- /dev/null +++ b/drivers/input/touchscreen/nt36xxx.h @@ -0,0 +1,142 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * Copyright (C) 2010 - 2017 Novatek, Inc. + * Copyright (C) 2020 AngeloGioacchino Del Regno <kholk11@xxxxxxxxx> + * Copyright (C) 2023-2024 George Chan <gchan9527@xxxxxxxxx> + */ + +#ifndef NT36XXX_H +#define NT36XXX_H + +#define NT36XXX_INPUT_DEVICE_NAME "Novatek NT36XXX Touch Sensor" +#define MAX_SPI_FREQ_HZ 5000000 + +/* FW Param address */ +#define NT36XXX_FW_ADDR 0x01 + +#define NT36XXX_TRANSFER_LEN (63*1024) + +/* due to extra framework layer, the transfer trunk is as small as + * 128 otherwize dma error happened, all routed to spi_sync() +*/ + +/* Number of bytes for chip identification */ +#define NT36XXX_ID_LEN_MAX 6 + +/* Touch info */ +#define TOUCH_DEFAULT_MAX_WIDTH 1080 +#define TOUCH_DEFAULT_MAX_HEIGHT 2246 +#define TOUCH_MAX_FINGER_NUM 10 +#define TOUCH_MAX_PRESSURE 1000 + +/* Point data length */ +#define POINT_DATA_LEN 65 + +/* Misc */ +#define NT36XXX_NUM_SUPPLIES 4 +#define NT36XXX_MAX_RETRIES 5 +#define NT36XXX_MAX_FW_RST_RETRY 50 + +enum nt36xxx_chips { + NT36525_IC = 0x1, + NT36672A_IC, + NT36676F_IC, + NT36772_IC, + NT36675_IC, + NT36870_IC, + NTMAX_IC, +}; + +enum nt36xxx_cmds { + NT36XXX_CMD_ENTER_SLEEP = 0x11, + NT36XXX_CMD_BOOTLOADER_RESET = 0x69, +}; + +enum nt36xxx_events { + NT36XXX_EVT_REPORT = 0x00, + NT36XXX_EVT_CRC = 0x35, + NT36XXX_EVT_HOST_CMD = 0x50, + NT36XXX_EVT_HS_OR_SUBCMD = 0x51, /* Handshake or subcommand byte */ + NT36XXX_EVT_RESET_COMPLETE = 0x60, + NT36XXX_EVT_FWINFO = 0x78, + NT36XXX_EVT_READ_PID = 0x80, + NT36XXX_EVT_PROJECTID = 0x9a, /* Excess 0x80 write bit, messed trouble, ignored */ +}; + +enum nt36xxx_fw_state { + NT36XXX_STATE_INIT = 0xa0, /* IC Reset */ + NT36XXX_STATE_REK = 0xa1, /* ReK baseline */ + NT36XXX_STATE_REK_FINISH = 0xa2, /* Baseline is ready */ + NT36XXX_STATE_NORMAL_RUN = 0xa3, /* Firmware is running */ + NT36XXX_STATE_MAX = 0xaf +}; + +struct nt36xxx_ts; + +struct nvt_fw_parse_data { + uint8_t partition; + uint8_t ilm_dlm_num; +}; + +struct nvt_ts_bin_map { + char name[12]; + uint32_t bin_addr; + uint32_t sram_addr; + uint32_t size; + uint32_t crc; + uint32_t loaded; +}; + +struct nvt_ts_hw_info { + uint8_t carrier_system; + uint8_t hw_crc; +}; + +struct nt36xxx_abs_object { + u16 x; + u16 y; + u16 z; + u8 tm; +}; + +struct nt36xxx_fw_info { + u8 fw_ver; + u8 x_num; + u8 y_num; + u8 max_buttons; + u16 abs_x_max; + u16 abs_y_max; + u16 nvt_pid; +}; + +struct nt36xxx_chip_data { + const u32 *mmap; + const struct regmap_config *config; + + const char* fw_name; + unsigned int max_x; + unsigned int max_y; + unsigned int abs_x_max; + unsigned int abs_y_max; + unsigned int max_button; + const struct input_id *id; +}; + +struct nt36xxx_trim_table { + u8 id[NT36XXX_ID_LEN_MAX]; + u8 mask[NT36XXX_ID_LEN_MAX]; + enum nt36xxx_chips mapid; + uint8_t carrier_system; + uint8_t hw_crc; +}; + +int nt36xxx_probe(struct device *dev, int irq, const struct input_id *id, + struct regmap *regmap); + +extern const struct dev_pm_ops nt36xxx_pm_ops; +extern const u32 nt36675_memory_maps[]; +extern const u32 nt36672a_memory_maps[]; +extern const u32 nt36772_memory_maps[]; +extern const u32 nt36676f_memory_maps[]; +extern const u32 nt36525_memory_maps[]; +#endif diff --git a/drivers/input/touchscreen/nt36xxx_core.c b/drivers/input/touchscreen/nt36xxx_core.c new file mode 100644 index 0000000000..a27abe566b --- /dev/null +++ b/drivers/input/touchscreen/nt36xxx_core.c @@ -0,0 +1,1422 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Driver for Novatek NT36xxx series touchscreens + * + * Copyright (C) 2010 - 2018 Novatek, Inc. + * Copyright (C) 2020 XiaoMi, Inc. + * Copyright (C) 2020 AngeloGioacchino Del Regno <kholk11@xxxxxxxxx> + * Copyright (C) 2023-2024 George Chan <gchan9527@xxxxxxxxx> + * + * Based on nt36xxx.c i2c driver from AngeloGioacchino Del Regno + */ + +#include <linux/delay.h> +#include <linux/device.h> +#include <linux/devm-helpers.h> +#include <linux/firmware.h> +#include <linux/gpio/consumer.h> +#include <linux/input/mt.h> +#include <linux/input/touchscreen.h> +#include <linux/interrupt.h> +#include <linux/irqnr.h> +#include <linux/of.h> +#include <linux/of_device.h> +#include <linux/pm_runtime.h> +#include <linux/printk.h> +#include <linux/regmap.h> +#include <linux/regulator/consumer.h> +#include <linux/unaligned.h> +#include <drm/drm_panel.h> + +#include "nt36xxx.h" + +/* Main mmap to spi addr */ +enum { + MMAP_BASELINE_ADDR, + MMAP_BASELINE_BTN_ADDR, + MMAP_BLD_CRC_EN_ADDR, + MMAP_BLD_DES_ADDR, + MMAP_BLD_ILM_DLM_CRC_ADDR, + MMAP_BLD_LENGTH_ADDR, + MMAP_BOOT_RDY_ADDR, + MMAP_DIFF_BTN_PIPE0_ADDR, + MMAP_DIFF_BTN_PIPE1_ADDR, + MMAP_DIFF_PIPE0_ADDR, + MMAP_DIFF_PIPE1_ADDR, + MMAP_DLM_DES_ADDR, + MMAP_DLM_LENGTH_ADDR, + MMAP_DMA_CRC_EN_ADDR, + MMAP_DMA_CRC_FLAG_ADDR, + MMAP_ENG_RST_ADDR, + MMAP_EVENT_BUF_ADDR, + MMAP_G_DLM_CHECKSUM_ADDR, + MMAP_G_ILM_CHECKSUM_ADDR, + MMAP_ILM_DES_ADDR, + MMAP_ILM_LENGTH_ADDR, + MMAP_POR_CD_ADDR, + MMAP_RAW_BTN_PIPE0_ADDR, + MMAP_RAW_BTN_PIPE1_ADDR, + MMAP_RAW_PIPE0_ADDR, + MMAP_RAW_PIPE1_ADDR, + MMAP_READ_FLASH_CHECKSUM_ADDR, + MMAP_RW_FLASH_DATA_ADDR, + MMAP_R_DLM_CHECKSUM_ADDR, + MMAP_R_ILM_CHECKSUM_ADDR, + MMAP_SPI_RD_FAST_ADDR, + MMAP_SWRST_N8_ADDR, + + /* below are magic numbers in source code */ + MMAP_MAGIC_NUMBER_0X1F64E_ADDR, + + /* this addr is not specific to */ + MMAP_TOP_ADDR, + MMAP_MAX_ADDR = MMAP_TOP_ADDR, +} nt36xxx_ts_mem_map; + +static struct drm_panel_follower_funcs nt36xxx_panel_follower_funcs; + +struct nt36xxx_ts { + struct regmap *regmap; + + struct input_dev *input; + struct regulator_bulk_data *supplies; + struct gpio_desc *reset_gpio; + struct gpio_desc *irq_gpio; + int irq; + struct device *dev; + + struct mutex lock; + +#define NT36XXX_STATUS_SUSPEND BIT(0) +#define NT36XXX_STATUS_DOWNLOAD_COMPLETE BIT(1) +#define NT36XXX_STATUS_DOWNLOAD_RECOVER BIT(2) +#define NT36XXX_STATUS_PREPARE_FIRMWARE BIT(3) +#define NT36XXX_STATUS_NEED_FIRMWARE BIT(4) + + unsigned int status; + + struct touchscreen_properties prop; + struct nt36xxx_fw_info fw_info; + struct nt36xxx_abs_object abs_obj; + + struct drm_panel_follower panel_follower; + + struct delayed_work work; + + /* this is a duplicate with nt36xxx_chip_data and since the address might + * change in boot/init/download stages so make it a copy of initial map and + * update accordingly + */ + u32 *mmap; + u32 mmap_data[MMAP_MAX_ADDR]; + + struct nvt_fw_parse_data fw_data; + struct nvt_ts_bin_map *bin_map; + + uint8_t hw_crc; + + const char * fw_name; + struct firmware fw_entry; /* containing request fw data */ + const struct nt36xxx_chip_data *data; +}; + +static const struct nt36xxx_trim_table trim_id_table[] = { + /* TODO: port and test all related module */ + { + .id = { 0x0A, 0xFF, 0xFF, 0x72, 0x66, 0x03 }, + .mask = { 1, 0, 0, 1, 1, 1 }, + .mapid = NT36672A_IC, + }, + { + .id = { 0x55, 0x00, 0xFF, 0x00, 0x00, 0x00 }, + .mask = { 1, 1, 0, 1, 1, 1 }, + .mapid = NT36772_IC, + }, + { + .id = { 0x55, 0x72, 0xFF, 0x00, 0x00, 0x00 }, + .mask = { 1, 1, 0, 1, 1, 1 }, + .mapid = NT36772_IC, + }, + { + .id = { 0xAA, 0x00, 0xFF, 0x00, 0x00, 0x00 }, + .mask = { 1, 1, 0, 1, 1, 1 }, + .mapid = NT36772_IC, + }, + { + .id = { 0xAA, 0x72, 0xFF, 0x00, 0x00, 0x00 }, + .mask = { 1, 1, 0, 1, 1, 1 }, + .mapid = NT36772_IC, + }, + { + .id = { 0xFF, 0xFF, 0xFF, 0x72, 0x67, 0x03 }, + .mask = { 0, 0, 0, 1, 1, 1 }, + .mapid = NT36772_IC, + }, + { + .id = { 0xFF, 0xFF, 0xFF, 0x70, 0x66, 0x03 }, + .mask = { 0, 0, 0, 1, 1, 1 }, + .mapid = NT36772_IC, + }, + { + .id = { 0xFF, 0xFF, 0xFF, 0x70, 0x67, 0x03 }, + .mask = { 0, 0, 0, 1, 1, 1 }, + .mapid = NT36772_IC, + }, + { + .id = { 0xFF, 0xFF, 0xFF, 0x72, 0x66, 0x03 }, + .mask = { 0, 0, 0, 1, 1, 1 }, + .mapid = NT36772_IC, + }, + { + .id = { 0xFF, 0xFF, 0xFF, 0x25, 0x65, 0x03 }, + .mask = { 0, 0, 0, 1, 1, 1 }, + .mapid = NT36772_IC, + }, + { + .id = { 0xFF, 0xFF, 0xFF, 0x70, 0x68, 0x03 }, + .mask = { 0, 0, 0, 1, 1, 1 }, + .mapid = NT36772_IC, + }, + { + .id = { 0xFF, 0xFF, 0xFF, 0x76, 0x66, 0x03 }, + .mask = { 0, 0, 0, 1, 1, 1 }, + .mapid = NT36676F_IC, + }, + { + .id = { 0xFF, 0xFF, 0xFF, 0x75, 0x66, 0x03}, + .mask = { 0, 0, 0, 1, 1, 1 }, + .mapid = NT36675_IC, + .hw_crc = 2, + }, + { }, +}; + +const u32 nt36675_memory_maps[] = { + [MMAP_EVENT_BUF_ADDR] = 0x22D00, + [MMAP_RAW_PIPE0_ADDR] = 0x24000, + [MMAP_RAW_PIPE1_ADDR] = 0x24000, + [MMAP_BASELINE_ADDR] = 0x21B90, + [MMAP_DIFF_PIPE0_ADDR] = 0x20C60, + [MMAP_DIFF_PIPE1_ADDR] = 0x24C60, + [MMAP_READ_FLASH_CHECKSUM_ADDR] = 0x24000, + [MMAP_RW_FLASH_DATA_ADDR] = 0x24002, + [MMAP_BOOT_RDY_ADDR] = 0x3F10D, + [MMAP_BLD_LENGTH_ADDR] = 0x3F138, + [MMAP_ILM_LENGTH_ADDR] = 0x3F118, + [MMAP_DLM_LENGTH_ADDR] = 0x3F130, + [MMAP_BLD_DES_ADDR] = 0x3F114, + [MMAP_ILM_DES_ADDR] = 0x3F128, + [MMAP_DLM_DES_ADDR] = 0x3F12C, + [MMAP_G_ILM_CHECKSUM_ADDR] = 0x3F100, + [MMAP_G_DLM_CHECKSUM_ADDR] = 0x3F104, + [MMAP_R_ILM_CHECKSUM_ADDR] = 0x3F120, + [MMAP_R_DLM_CHECKSUM_ADDR] = 0x3F124, + [MMAP_BLD_CRC_EN_ADDR] = 0x3F30E, + [MMAP_DMA_CRC_EN_ADDR] = 0x3F136, + [MMAP_BLD_ILM_DLM_CRC_ADDR] = 0x3F133, + [MMAP_DMA_CRC_FLAG_ADDR] = 0x3F134, + + /* below are specified by dts, so it might change by project-based */ + [MMAP_SPI_RD_FAST_ADDR] = 0x03F310, + [MMAP_SWRST_N8_ADDR] = 0x03F0FE, + + [MMAP_ENG_RST_ADDR] = 0x7FFF80, + [MMAP_MAGIC_NUMBER_0X1F64E_ADDR] = 0x1F64E, + + [MMAP_TOP_ADDR] = 0xffffff, +}; + +const u32 nt36672a_memory_maps[] = { + [MMAP_EVENT_BUF_ADDR] = 0x21C00, + [MMAP_RAW_PIPE0_ADDR] = 0x20000, + [MMAP_RAW_PIPE1_ADDR] = 0x23000, + [MMAP_BASELINE_ADDR] = 0x20BFC, + [MMAP_BASELINE_BTN_ADDR] = 0x23BFC, + [MMAP_DIFF_PIPE0_ADDR] = 0x206DC, + [MMAP_DIFF_PIPE1_ADDR] = 0x236DC, + [MMAP_RAW_BTN_PIPE0_ADDR] = 0x20510, + [MMAP_RAW_BTN_PIPE1_ADDR] = 0x23510, + [MMAP_DIFF_BTN_PIPE0_ADDR] = 0x20BF0, + [MMAP_DIFF_BTN_PIPE1_ADDR] = 0x23BF0, + [MMAP_READ_FLASH_CHECKSUM_ADDR] = 0x24000, + [MMAP_RW_FLASH_DATA_ADDR] = 0x24002, + /* Phase 2 Host Download */ + [MMAP_BOOT_RDY_ADDR] = 0x3F10D, + /* BLD CRC */ + [MMAP_BLD_LENGTH_ADDR] = 0x3F10E, //0x3F10E ~ 0x3F10F (2 bytes) + [MMAP_ILM_LENGTH_ADDR] = 0x3F118, //0x3F118 ~ 0x3F119 (2 bytes) + [MMAP_DLM_LENGTH_ADDR] = 0x3F130, //0x3F130 ~ 0x3F131 (2 bytes) + [MMAP_BLD_DES_ADDR] = 0x3F114, //0x3F114 ~ 0x3F116 (3 bytes) + [MMAP_ILM_DES_ADDR] = 0x3F128, //0x3F128 ~ 0x3F12A (3 bytes) + [MMAP_DLM_DES_ADDR] = 0x3F12C, //0x3F12C ~ 0x3F12E (3 bytes) + [MMAP_G_ILM_CHECKSUM_ADDR] = 0x3F100, //0x3F100 ~ 0x3F103 (4 bytes) + [MMAP_G_DLM_CHECKSUM_ADDR] = 0x3F104, //0x3F104 ~ 0x3F107 (4 bytes) + [MMAP_R_ILM_CHECKSUM_ADDR] = 0x3F120, //0x3F120 ~ 0x3F123 (4 bytes) + [MMAP_R_DLM_CHECKSUM_ADDR] = 0x3F124, //0x3F124 ~ 0x3F127 (4 bytes) + [MMAP_BLD_CRC_EN_ADDR] = 0x3F30E, + [MMAP_DMA_CRC_EN_ADDR] = 0x3F132, + [MMAP_BLD_ILM_DLM_CRC_ADDR] = 0x3F133, + [MMAP_DMA_CRC_FLAG_ADDR] = 0x3F134, + + /* below are specified by dts, so it might change by project-based */ + [MMAP_SPI_RD_FAST_ADDR] = 0x03F310, + [MMAP_SWRST_N8_ADDR] = 0x03F0FE, + + [MMAP_ENG_RST_ADDR] = 0x7FFF80, + [MMAP_MAGIC_NUMBER_0X1F64E_ADDR] = 0x1F64E, + + [MMAP_TOP_ADDR] = 0xffffff, +}; + +const u32 nt36676f_memory_maps[] = { + [MMAP_EVENT_BUF_ADDR] = 0x11A00, + [MMAP_RAW_PIPE0_ADDR] = 0x10000, + [MMAP_RAW_PIPE1_ADDR] = 0x12000, + [MMAP_BASELINE_ADDR] = 0x10B08, + [MMAP_BASELINE_BTN_ADDR] = 0x12B08, + [MMAP_DIFF_PIPE0_ADDR] = 0x1064C, + [MMAP_DIFF_PIPE1_ADDR] = 0x1264C, + [MMAP_RAW_BTN_PIPE0_ADDR] = 0x10634, + [MMAP_RAW_BTN_PIPE1_ADDR] = 0x12634, + [MMAP_DIFF_BTN_PIPE0_ADDR] = 0x10AFC, + [MMAP_DIFF_BTN_PIPE1_ADDR] = 0x12AFC, + [MMAP_READ_FLASH_CHECKSUM_ADDR] = 0x14000, + [MMAP_RW_FLASH_DATA_ADDR] = 0x14002, + + /* below are specified by dts, so it might change by project-based */ + [MMAP_SPI_RD_FAST_ADDR] = 0x03F310, + [MMAP_SWRST_N8_ADDR] = 0x03F0FE, + + [MMAP_ENG_RST_ADDR] = 0x7FFF80, + [MMAP_MAGIC_NUMBER_0X1F64E_ADDR] = 0x1F64E, + + [MMAP_TOP_ADDR] = 0xffffff, +}; + +const u32 nt36772_memory_maps[] = { + [MMAP_EVENT_BUF_ADDR] = 0x11E00, + [MMAP_RAW_PIPE0_ADDR] = 0x10000, + [MMAP_RAW_PIPE1_ADDR] = 0x12000, + [MMAP_BASELINE_ADDR] = 0x10E70, + [MMAP_BASELINE_BTN_ADDR] = 0x12E70, + [MMAP_DIFF_PIPE0_ADDR] = 0x10830, + [MMAP_DIFF_PIPE1_ADDR] = 0x12830, + [MMAP_RAW_BTN_PIPE0_ADDR] = 0x10E60, + [MMAP_RAW_BTN_PIPE1_ADDR] = 0x12E60, + [MMAP_DIFF_BTN_PIPE0_ADDR] = 0x10E68, + [MMAP_DIFF_BTN_PIPE1_ADDR] = 0x12E68, + [MMAP_READ_FLASH_CHECKSUM_ADDR] = 0x14000, + [MMAP_RW_FLASH_DATA_ADDR] = 0x14002, + /* Phase 2 Host Download */ + [MMAP_BOOT_RDY_ADDR] = 0x1F141, + [MMAP_POR_CD_ADDR] = 0x1F61C, + /* BLD CRC */ + [MMAP_R_ILM_CHECKSUM_ADDR] = 0x1BF00, + + /* below are specified by dts, so it might change by project-based */ + [MMAP_SPI_RD_FAST_ADDR] = 0x03F310, + [MMAP_SWRST_N8_ADDR] = 0x03F0FE, + + [MMAP_ENG_RST_ADDR] = 0x7FFF80, + [MMAP_MAGIC_NUMBER_0X1F64E_ADDR] = 0x1F64E, + + [MMAP_TOP_ADDR] = 0xffffff, +}; + +const u32 nt36525_memory_maps[] = { + [MMAP_EVENT_BUF_ADDR] = 0x11A00, + [MMAP_RAW_PIPE0_ADDR] = 0x10000, + [MMAP_RAW_PIPE1_ADDR] = 0x12000, + [MMAP_BASELINE_ADDR] = 0x10B08, + [MMAP_BASELINE_BTN_ADDR] = 0x12B08, + [MMAP_DIFF_PIPE0_ADDR] = 0x1064C, + [MMAP_DIFF_PIPE1_ADDR] = 0x1264C, + [MMAP_RAW_BTN_PIPE0_ADDR] = 0x10634, + [MMAP_RAW_BTN_PIPE1_ADDR] = 0x12634, + [MMAP_DIFF_BTN_PIPE0_ADDR] = 0x10AFC, + [MMAP_DIFF_BTN_PIPE1_ADDR] = 0x12AFC, + [MMAP_READ_FLASH_CHECKSUM_ADDR] = 0x14000, + [MMAP_RW_FLASH_DATA_ADDR] = 0x14002, + + /* Phase 2 Host Download */ + [MMAP_BOOT_RDY_ADDR] = 0x1F141, + [MMAP_POR_CD_ADDR] = 0x1F61C, + /* BLD CRC */ + [MMAP_R_ILM_CHECKSUM_ADDR] = 0x1BF00, + + /* below are specified by dts, so it might change by project-based */ + [MMAP_SPI_RD_FAST_ADDR] = 0x03F310, + [MMAP_SWRST_N8_ADDR] = 0x03F0FE, + + [MMAP_ENG_RST_ADDR] = 0x7FFF80, + [MMAP_MAGIC_NUMBER_0X1F64E_ADDR] = 0x1F64E, + + [MMAP_TOP_ADDR] = 0xffffff, +}; + +void __maybe_unused _debug_irq(struct nt36xxx_ts *ts, int line) { + struct irq_desc *desc; + desc = irq_data_to_desc( irq_get_irq_data(ts->irq)); + dev_info(ts->dev, "%d irq_desc depth=%d", line, desc->depth ); +} + +#define debug_irq(a) _debug_irq(a, __LINE__) + +static int nt36xxx_eng_reset_idle(struct nt36xxx_ts *ts) +{ + int ret; + + if(!ts) { + dev_err(ts->dev, "%s %s empty", __func__, "nt36xxx_ts"); + return -EINVAL; + } + + if(!ts->mmap) { + dev_err(ts->dev, "%s %s empty", __func__, "ts->mmap"); + return -EINVAL; + } + + if(ts->mmap[MMAP_ENG_RST_ADDR] == 0) { + dev_err(ts->dev, "%s %s empty", __func__, "MMAP_ENG_RST_ADDR"); + return -EINVAL; + } + + /* HACK to output something without read */ + ret = regmap_write(ts->regmap, ts->mmap[MMAP_ENG_RST_ADDR], + 0x5a); + if (ret) { + dev_err(ts->dev, "%s regmap write error\n", __func__); + return ret; + } + + /* Wait until the MCU resets the fw state */ + usleep_range(15000, 16000); + + /* seemed not long enough */ + msleep(30); + return ret; +} + +/* + * nt36xxx_bootloader_reset - Reset MCU to bootloader + * @ts: Main driver structure + * + * Return: Always zero for success, negative number for error + */ +static int nt36xxx_bootloader_reset(struct nt36xxx_ts *ts) +{ + int ret = 0; + + //in spi version, need to set page to SWRST_N8_ADDR + if (ts->mmap[MMAP_SWRST_N8_ADDR]) { + ret = regmap_write(ts->regmap, ts->mmap[MMAP_SWRST_N8_ADDR], + NT36XXX_CMD_BOOTLOADER_RESET); + if (ret) + return ret; + } else { + pr_info("plz make sure MMAP_SWRST_N8_ADDR is set!\n"); + return -EINVAL; + } + + /* MCU has to reboot from bootloader: this is the typical boot time */ + msleep(35); + + if (ts->mmap[MMAP_SPI_RD_FAST_ADDR]) { + ret = regmap_write(ts->regmap, ts->mmap[MMAP_SPI_RD_FAST_ADDR], 0); + if (ret) + return ret; + } + + return ret; +} + +/** + * nt36xxx_check_reset_state - Check the boot state during reset + * @ts: Main driver structure + * @fw_state: Enumeration containing firmware states + * + * Return: Always zero for success, negative number for error + */ +static int nt36xxx_check_reset_state(struct nt36xxx_ts *ts, + enum nt36xxx_fw_state fw_state) +{ + u8 buf[8] = { 0 }; + int ret = 0, retry = NT36XXX_MAX_FW_RST_RETRY; + + do { + ret = regmap_raw_read(ts->regmap, ts->mmap[MMAP_EVENT_BUF_ADDR] + | NT36XXX_EVT_RESET_COMPLETE, buf, 6); + if (likely(ret == 0) && + (buf[1] >= fw_state) && + (buf[1] <= NT36XXX_STATE_MAX)) { + ret = 0; + break; + } + usleep_range(10000, 11000); + } while (--retry); + + if (!retry) { + dev_err(ts->dev, "Firmware reset failed.\n"); + ret = -EBUSY; + } + + return ret; +} + +/** + * nt36xxx_report - Report touch events + * @ts: Main driver structure + * + * Return: Always zero for success, negative number for error + */ +static void nt36xxx_report(struct nt36xxx_ts *ts) +{ + struct nt36xxx_abs_object *obj = &ts->abs_obj; + struct input_dev *input = ts->input; + u8 input_id = 0; + u8 point[POINT_DATA_LEN + 1] = { 0 }; + unsigned int ppos = 0; + int i, ret, finger_cnt = 0; + uint8_t press_id[TOUCH_MAX_FINGER_NUM] = {0}; + + ret = regmap_raw_read(ts->regmap, ts->mmap[MMAP_EVENT_BUF_ADDR], + point, sizeof(point)); + if (ret < 0) { + dev_err(ts->dev, + "Cannot read touch point data: %d\n", ret); + goto xfer_error; + } + + /* wdt recovery and esd check */ + for (i = 0; i < 7; i++) { + if ((point[i] != 0xFD) && (point[i] != 0xFE) && (point[i] != 0x77)) { + break; + } + + mutex_lock(&ts->lock); + ts->status |= NT36XXX_STATUS_DOWNLOAD_RECOVER; + mutex_unlock(&ts->lock); + goto xfer_error; + } + + for (i = 0; i < TOUCH_MAX_FINGER_NUM; i++) { + ppos = 6 * i + 1; + input_id = point[ppos + 0] >> 3; + + if ((input_id == 0) || (input_id > TOUCH_MAX_FINGER_NUM)) { + continue; + } + + if (((point[ppos] & 0x07) == 0x01) || + ((point[ppos] & 0x07) == 0x02)) { + obj->x = (point[ppos + 1] << 4) + + (point[ppos + 3] >> 4); + obj->y = (point[ppos + 2] << 4) + + (point[ppos + 3] & 0xf); + + if ((obj->x > ts->prop.max_x) || + (obj->y > ts->prop.max_y)) + continue; + + obj->tm = point[ppos + 4]; + if (obj->tm == 0) + obj->tm = 1; + + obj->z = point[ppos + 5]; + if (i < 2) { + obj->z += point[i + 63] << 8; + if (obj->z > TOUCH_MAX_PRESSURE) + obj->z = TOUCH_MAX_PRESSURE; + } + + if (obj->z == 0) + obj->z = 1; + + press_id[input_id - 1] = 1; + + input_mt_slot(input, input_id - 1); + input_mt_report_slot_state(input, + MT_TOOL_FINGER, true); + touchscreen_report_pos(input, &ts->prop, + obj->x, + obj->y, true); + + input_report_abs(input, ABS_MT_TOUCH_MAJOR, obj->tm); + input_report_abs(input, ABS_MT_PRESSURE, obj->z); + + finger_cnt++; + } + } + + input_mt_sync_frame(input); + + input_sync(input); + +xfer_error: + return; +} + +static irqreturn_t nt36xxx_irq_handler(int irq, void *dev_id) +{ + struct nt36xxx_ts *ts = dev_id; + + if (!ts->mmap) + goto exit; + + disable_irq_nosync(ts->irq); + + nt36xxx_report(ts); + + enable_irq(ts->irq); + +exit: + if (ts->status & NT36XXX_STATUS_DOWNLOAD_RECOVER) { + mutex_lock(&ts->lock); + ts->status &= ~NT36XXX_STATUS_DOWNLOAD_RECOVER; + mutex_unlock(&ts->lock); + /* TODO: other builtin eeprom model might have another reset + * approach other than download, might add here afterward */ + if (ts->fw_name) + schedule_delayed_work(&ts->work, 40000); + } + + return IRQ_HANDLED; +} + + +/** + * nt36xxx_chip_version_init - Detect Novatek NT36xxx family IC + * @ts: Main driver structure + * + * This function reads the ChipID from the IC and sets the right + * memory map for the detected chip. + * + * Return: Always zero for success, negative number for error + */ +static int nt36xxx_chip_version_init(struct nt36xxx_ts *ts) +{ + u8 buf[32] = { 0 }; + int retry = NT36XXX_MAX_RETRIES; + int sz = sizeof(trim_id_table) / sizeof(struct nt36xxx_trim_table); + int i, list, mapid, ret; + + ret = nt36xxx_bootloader_reset(ts); + if (ret) { + dev_err(ts->dev, "Can't reset the nvt IC\n"); + return ret; + } + + do { + ret = regmap_raw_read(ts->regmap, ts->mmap[MMAP_MAGIC_NUMBER_0X1F64E_ADDR], buf, 7); + + if (ret) + continue; + + dev_dbg(ts->dev, "%s buf[0]=0x%02X, buf[1]=0x%02X, buf[2]=0x%02X, buf[3]=0x%02X, buf[4]=0x%02X, buf[5]=0x%02X, buf[6]=0x%02X sz=%d\n", + __func__, buf[0], buf[1], buf[2], buf[3], buf[4], buf[5], buf[6], sz); + + /* Compare read chip id with trim list */ + for (list = 0; list < sz; list++) { + + /* Compare each not masked byte */ + for (i = 0; i < NT36XXX_ID_LEN_MAX; i++) { + if (trim_id_table[list].mask[i] && + buf[i + 1] != trim_id_table[list].id[i]) + break; + } + + /* found and match with mask */ + if (i == NT36XXX_ID_LEN_MAX) { + mapid = trim_id_table[list].mapid; + ret = 0; + ts->hw_crc = trim_id_table[list].hw_crc; + + if (mapid == 0) { + dev_info(ts->dev, "NVT touch IC hw not found i=%d list=%d\n", i, list); + ret = -ENOENT; + goto exit; + } + + WARN_ON(ts->hw_crc < 1); + + dev_dbg(ts->dev, "hw crc support=%d\n", ts->hw_crc); + + dev_info(ts->dev, "This is NVT touch IC, %06x, mapid %d", *(int*)&buf[4], mapid); + return 0; + } + + ret = -ENOENT; + } + + usleep_range(10000, 11000); + } while (--retry); + +exit: + return ret; +} + +/* + * this function is nearly direct copy from vendor source +*/ +static int32_t nvt_bin_header_parser(struct device *dev, int hw_crc, const u8 *fwdata, size_t fwsize, struct nvt_ts_bin_map **bin_map_ptr, uint8_t *partition_ptr, uint8_t ilm_dlm_num) +{ + uint8_t list = 0; + uint32_t pos = 0x00; + uint32_t end = 0x00; + uint8_t info_sec_num = 0; + uint8_t ovly_sec_num = 0; + uint8_t ovly_info = 0; + uint8_t partition; + struct nvt_ts_bin_map *bin_map; + + /* Find the header size */ + end = fwdata[0] + (fwdata[1] << 8) + (fwdata[2] << 16) + (fwdata[3] << 24); + pos = 0x30; /* info section start at 0x30 offset */ + while (pos < end) { + info_sec_num ++; + pos += 0x10; /* each header info is 16 bytes */ + } + + /* + * Find the DLM OVLY section + * [0:3] Overlay Section Number + * [4] Overlay Info + */ + ovly_info = (fwdata[0x28] & 0x10) >> 4; + ovly_sec_num = (ovly_info) ? (fwdata[0x28] & 0x0F) : 0; + + /* + * calculate all partition number + * ilm_dlm_num (ILM & DLM) + ovly_sec_num + info_sec_num + */ + *partition_ptr = partition = ilm_dlm_num + ovly_sec_num + info_sec_num; + dev_dbg(dev, "ovly_info = %d, ilm_dlm_num = %d, ovly_sec_num = %d, info_sec_num = %d, partition = %d\n", + ovly_info, ilm_dlm_num, ovly_sec_num, info_sec_num, partition); + + /* allocated memory for header info */ + *bin_map_ptr = bin_map = (struct nvt_ts_bin_map *)kzalloc((partition + 1) * sizeof(struct nvt_ts_bin_map), GFP_KERNEL); + if(bin_map == NULL) { + dev_err(dev, "kzalloc for bin_map failed!\n"); + return -ENOMEM; + } + + for (list = 0; list < partition; list++) { + /* + * [1] parsing ILM & DLM header info + * bin_addr : sram_addr : size (12-bytes) + * crc located at 0x18 & 0x1C + */ + if (list < ilm_dlm_num) { + memcpy(&bin_map[list].bin_addr, &(fwdata[0 + list*12]), 4); + memcpy(&bin_map[list].sram_addr, &(fwdata[4 + list*12]), 4); + memcpy(&bin_map[list].size, &(fwdata[8 + list*12]), 4); + memcpy(&bin_map[list].crc, &(fwdata[0x18 + list*4]), 4); + + if (!hw_crc) { + dev_err(dev, "%s %d sw-crc not support", __func__, __LINE__); + return -EINVAL; + } + + if (list == 0) + sprintf(bin_map[list].name, "ILM"); + else if (list == 1) + sprintf(bin_map[list].name, "DLM"); + } + + /* + * [2] parsing others header info + * sram_addr : size : bin_addr : crc (16-bytes) + */ + if ((list >= ilm_dlm_num) && (list < (ilm_dlm_num + info_sec_num))) { + + /* others partition located at 0x30 offset */ + pos = 0x30 + (0x10 * (list - ilm_dlm_num)); + + memcpy(&bin_map[list].sram_addr, &(fwdata[pos]), 4); + memcpy(&bin_map[list].size, &(fwdata[pos+4]), 4); + memcpy(&bin_map[list].bin_addr, &(fwdata[pos+8]), 4); + memcpy(&bin_map[list].crc, &(fwdata[pos+12]), 4); + + if (!hw_crc) { + dev_info(dev, "ok, hw_crc not presents!"); + return -EINVAL; + } + + /* detect header end to protect parser function */ + if ((bin_map[list].bin_addr == 0) && (bin_map[list].size != 0)) { + sprintf(bin_map[list].name, "Header"); + } else { + sprintf(bin_map[list].name, "Info-%d", (list - ilm_dlm_num)); + } + } + + /* + * [3] parsing overlay section header info + * sram_addr : size : bin_addr : crc (16-bytes) + */ + if (list >= (ilm_dlm_num + info_sec_num)) { + /* overlay info located at DLM (list = 1) start addr */ + pos = bin_map[1].bin_addr + (0x10 * (list- ilm_dlm_num - info_sec_num)); + + memcpy(&bin_map[list].sram_addr, &(fwdata[pos]), 4); + memcpy(&bin_map[list].size, &(fwdata[pos+4]), 4); + memcpy(&bin_map[list].bin_addr, &(fwdata[pos+8]), 4); + memcpy(&bin_map[list].crc, &(fwdata[pos+12]), 4); + + if (!hw_crc) { + dev_err(dev, "%s %d sw_crc not support", __func__, __LINE__); + return -EINVAL; + } + + sprintf(bin_map[list].name, "Overlay-%d", (list- ilm_dlm_num - info_sec_num)); + } + + /* BIN size error detect */ + if ((bin_map[list].bin_addr + bin_map[list].size) > fwsize) { + dev_err(dev, "access range (0x%08X to 0x%08X) is larger than bin size!\n", + bin_map[list].bin_addr, bin_map[list].bin_addr + bin_map[list].size); + return -EINVAL; + } + + dev_dbg(dev, "[%d][%s] SRAM (0x%08X), SIZE (0x%08X), BIN (0x%08X), CRC (0x%08X)\n", + list, bin_map[list].name, + bin_map[list].sram_addr, bin_map[list].size, bin_map[list].bin_addr, bin_map[list].crc); + } + + return 0; +} + +static int32_t nt36xxx_download_firmware_hw_crc(struct nt36xxx_ts *ts) { + uint32_t list = 0; + uint32_t bin_addr, sram_addr, size; + struct nvt_ts_bin_map *bin_map = ts->bin_map; + + nt36xxx_bootloader_reset(ts); + + for (list = 0; list < ts->fw_data.partition; list++) { + int j; + + /* initialize variable */ + sram_addr = bin_map[list].sram_addr; + size = bin_map[list].size; + bin_addr = bin_map[list].bin_addr; + + /* ignore reserved partition (Reserved Partition size is zero) */ + if (!size) { + dev_dbg(ts->dev, "found empty part %d. skipping ", list); + continue; + } else { + size = size + 1; + dev_dbg(ts->dev, "found useful part %d. size 0x%x ", list, size); + } + + bin_map[list].loaded = 1; + + if (size / NT36XXX_TRANSFER_LEN) + dev_dbg(ts->dev, "%s %d paged write [%s] 0x%x, window 0x%x, residue 0x%x", + __func__, __LINE__, bin_map[list].name, size, + NT36XXX_TRANSFER_LEN, size % NT36XXX_TRANSFER_LEN); + + for (j = 0; j < size; j += NT36XXX_TRANSFER_LEN) { + int window_size = ((size - j) / NT36XXX_TRANSFER_LEN) ? NT36XXX_TRANSFER_LEN : + ((size - j) % NT36XXX_TRANSFER_LEN); + + regmap_bulk_write(ts->regmap, sram_addr + j, &ts->fw_entry.data[bin_addr + j], + window_size); + } + + } + + return 0; +} + +static void nt36xxx_release_memory(void *data); +static int _nt36xxx_boot_prepare_firmware(struct nt36xxx_ts *ts) { + int i, ret; + size_t fw_need_write_size = 0; + const struct firmware *fw_entry; + void *data; + + WARN_ON(ts->hw_crc != 2); + + /* add one more guard */ + if (ts->status & NT36XXX_STATUS_PREPARE_FIRMWARE) + return 0; + + /* supposed we need to load once and use many time */ + if (ts->fw_entry.data) + return 0; + + ret = request_firmware(&fw_entry, ts->fw_name, ts->dev); + if (ret) { + dev_err(ts->dev, "request fw fail name=%s\n", ts->fw_name); + return -ENOMEM; + } + + /* + * must allocate in DMA buffer otherwise fail spi tx DMA + * so we need to manage our own fw struct + * pm_resume need to re-upload fw for NT36675 IC + * + */ + ts->fw_entry.data = data = kmemdup(fw_entry->data, fw_entry->size, GFP_KERNEL | GFP_DMA); + + release_firmware(fw_entry); + if (!ts->fw_entry.data) { + dev_err(ts->dev, "memdup fw_data fail\n"); + return -ENOMEM; + } + ts->fw_entry.size = fw_entry->size; + + WARN_ON(ts->fw_entry.data[0] != fw_entry->data[0]); + + for (i = (ts->fw_entry.size / 4096); i > 0; i--) { + if (strncmp(&ts->fw_entry.data[i * 4096 - 3], "NVT", 3) == 0) { + fw_need_write_size = i * 4096; + break; + } + + if (strncmp(&ts->fw_entry.data[i * 4096 - 3], "MOD", 3) == 0) { + fw_need_write_size = i * 4096; + break; + } + } + + if (fw_need_write_size == 0) { + dev_err(ts->dev, "fw parsing error\n"); + kfree (data); + if (ts->bin_map) { + kfree(ts->bin_map); + ts->bin_map = NULL; + } + return -EIO; + } + + if (*(ts->fw_entry.data + (fw_need_write_size - 4096)) + *(ts->fw_entry.data + + ((fw_need_write_size - 4096) + 1)) != 0xFF) { + dev_err(ts->dev, "bin file FW_VER + FW_VER_BAR should be 0xFF!"); + dev_err(ts->dev, "FW_VER=0x%02X, FW_VER_BAR=0x%02X\n", + *(ts->fw_entry.data+(fw_need_write_size - 4096)), + *(ts->fw_entry.data+(fw_need_write_size - 4096 + 1))); + + kfree (data); + if (ts->bin_map) { + kfree(ts->bin_map); + ts->bin_map = NULL; + } + return -EIO; + } + + ts->fw_data.ilm_dlm_num = 2; + + ret = nvt_bin_header_parser(ts->dev, ts->hw_crc, ts->fw_entry.data, ts->fw_entry.size, + &ts->bin_map, &ts->fw_data.partition, ts->fw_data.ilm_dlm_num); + if (ret) { + kfree (data); + if(ret != -ENOMEM){ + if (ts->bin_map) { + kfree(ts->bin_map); + ts->bin_map = NULL; + } + } + + /* really dont let the tasklet re-enter since no needed for broken fw data */ + ts->status |= NT36XXX_STATUS_DOWNLOAD_COMPLETE; + dev_err(ts->dev, "Parsing fw error, stop re-loading fw now on, ret=0x%x!", ret); + return ret; + } + + ts->status |= NT36XXX_STATUS_PREPARE_FIRMWARE; + + ret = devm_add_action_or_reset(ts->dev, nt36xxx_release_memory, ts); + if (ret) + return ret; + + return 0; +} + +static int _nt36xxx_boot_download_firmware(struct nt36xxx_ts *ts) { + int i, ret, retry = 0; + u8 val[8 * 4] = {0}; + + if (!(ts->status & NT36XXX_STATUS_PREPARE_FIRMWARE)) + return -EIO; + + if (ts->hw_crc) { + ret = nt36xxx_download_firmware_hw_crc(ts); + if (ret) { + dev_err(ts->dev, "nt36xxx_download_firmware_hw_crc fail!"); + return ret; + } + + } else { + dev_err(ts->dev, "non-hw_crc model is not support yet!"); + return -EIO; + } + + /* set ilm & dlm reg bank */ + for (i = 0; i < ts->fw_data.partition; i++) { + if (0 == strncmp(ts->bin_map[i].name, "ILM", 3)) { + regmap_raw_write(ts->regmap, ts->mmap[MMAP_ILM_DES_ADDR], &ts->bin_map[i].sram_addr, 3); + regmap_raw_write(ts->regmap, ts->mmap[MMAP_ILM_LENGTH_ADDR], &ts->bin_map[i].size, 3); + + /* crc > 1 then len = 4, crc = 1 then len = 3 */ + regmap_raw_write(ts->regmap, ts->mmap[MMAP_G_ILM_CHECKSUM_ADDR], &ts->bin_map[i].crc, + sizeof(ts->bin_map[i].crc)); + } + if (0 == strncmp(ts->bin_map[i].name, "DLM", 3)) { + regmap_raw_write(ts->regmap, ts->mmap[MMAP_DLM_DES_ADDR], &ts->bin_map[i].sram_addr, 3); + regmap_raw_write(ts->regmap, ts->mmap[MMAP_DLM_LENGTH_ADDR], &ts->bin_map[i].size, 3); + + /* crc > 1 then len = 4, crc = 1 then len = 3 */ + regmap_raw_write(ts->regmap, ts->mmap[MMAP_G_DLM_CHECKSUM_ADDR], &ts->bin_map[i].crc, + sizeof(ts->bin_map[i].crc)); + } + } + + /* nvt_bld_crc_enable() */ + /* crc enable */ + regmap_raw_read(ts->regmap, ts->mmap[MMAP_BLD_CRC_EN_ADDR], val, 1); + + val[0] |= 1 << 7; + regmap_raw_write(ts->regmap, ts->mmap[MMAP_BLD_CRC_EN_ADDR], val, 1); + + /* enable fw crc */ + val[0] = 0; + regmap_raw_write(ts->regmap, ts->mmap[MMAP_EVENT_BUF_ADDR] | NT36XXX_EVT_RESET_COMPLETE, val, 1); + + val[0] = 0xae; + regmap_raw_write(ts->regmap, ts->mmap[MMAP_EVENT_BUF_ADDR] | NT36XXX_EVT_HOST_CMD, val, 1); + + /* nvt_boot_ready() */ + /* Set Boot Ready Bit */ + val[0] = 0x1; + regmap_raw_write(ts->regmap, ts->mmap[MMAP_BOOT_RDY_ADDR], val, 1); + + /* old logic 5ms, retention to 10ms */ + usleep_range(10000, 11000); + + /* nvt_check_fw_reset_state() */ + ret = nt36xxx_check_reset_state(ts, NT36XXX_STATE_INIT); + if (ret) + return ret; + +check_fw: + /* nvt_get_fw_info() */ + ret = regmap_raw_read(ts->regmap, ts->mmap[MMAP_EVENT_BUF_ADDR] | NT36XXX_EVT_FWINFO, val, 16); + if (ret) + return ret; + + dev_dbg(ts->dev, "Get default fw_ver=%d, max_x=%d, max_y=%d, by default max_x=%d max_y=%d\n", + val[2], ts->prop.max_x, ts->prop.max_y, ts->data->max_x, ts->data->max_y); + + if (val[0] != 0xff && retry < 5) { + dev_err(ts->dev, "FW info is broken! fw_ver=0x%02X, ~fw_ver=0x%02X\n", val[1], val[2]); + retry++; + goto check_fw; + } + + dev_info(ts->dev, "Touch IC fw loaded ok"); + + ts->status |= NT36XXX_STATUS_DOWNLOAD_COMPLETE; + + return 0; +} + +static void nt36xxx_download_firmware(struct work_struct *work) { + struct nt36xxx_ts *ts = container_of(work, struct nt36xxx_ts, work.work); + int ret; + + cancel_delayed_work(&ts->work); + + mutex_lock(&ts->lock); + _nt36xxx_boot_prepare_firmware(ts); + mutex_unlock(&ts->lock); + + if (!(ts->status & NT36XXX_STATUS_PREPARE_FIRMWARE)) + goto exit; + + /* so the pm resume might have code to enable regulators. */ + ret = pm_runtime_resume_and_get(ts->dev); + if (ret) { + dev_err(ts->dev, "%s resume fail 0x%x", __func__, ret); + goto exit; + } + + disable_irq_nosync(ts->irq); + + mutex_lock(&ts->lock); + + ret = nt36xxx_eng_reset_idle(ts); + if (ret) { + dev_err(ts->dev, "Failed to check chip version\n"); + goto unlock; + } + + /* Set memory maps for the specific chip version */ + ret = nt36xxx_chip_version_init(ts); + if (ret) { + dev_err(ts->dev, "Failed to check chip version\n"); + goto unlock; + } + + dev_dbg(ts->dev, "ts->status=0x%x", ts->status); + + _nt36xxx_boot_download_firmware(ts); +unlock: + mutex_unlock(&ts->lock); + enable_irq(ts->irq); + + pm_runtime_put(ts->dev); +exit: + if (!(ts->status & NT36XXX_STATUS_DOWNLOAD_COMPLETE)) { + schedule_delayed_work(&ts->work, 4000); + } +} + +static void nt36xxx_release_memory(void *data) +{ + struct nt36xxx_ts *ts = data; + kfree(ts->bin_map); + kfree(ts->fw_entry.data); +} + +static void nt36xxx_disable_regulators(void *data) +{ + struct nt36xxx_ts *ts = data; + + regulator_bulk_disable(NT36XXX_NUM_SUPPLIES, ts->supplies); +} + +static int nt36xxx_input_dev_config(struct nt36xxx_ts *ts, const struct input_id *id) +{ + struct device *dev = ts->dev; + int ret; + + ts->input = devm_input_allocate_device(dev); + if (!ts->input) + return -ENOMEM; + + input_set_drvdata(ts->input, ts); + + ts->input->phys = devm_kasprintf(dev, GFP_KERNEL, + "%s/input0", dev_name(dev)); + if (!ts->input->phys) + return -ENOMEM; + + ts->input->name = "nt36xxx_spi_0"; + ts->input->dev.parent = dev; + ts->input->id = *id; + + input_set_abs_params(ts->input, ABS_MT_PRESSURE, 0, + TOUCH_MAX_PRESSURE, 0, 0); + input_set_abs_params(ts->input, ABS_MT_TOUCH_MAJOR, 0, 255, 0, 0); + + input_set_abs_params(ts->input, ABS_MT_POSITION_X, 0, + ts->data->abs_x_max - 1, 0, 0); + input_set_abs_params(ts->input, ABS_MT_POSITION_Y, 0, + ts->data->abs_y_max - 1, 0, 0); + + touchscreen_parse_properties(ts->input, true, &ts->prop); + + WARN_ON(ts->prop.max_x < 1); + + ret = input_mt_init_slots(ts->input, TOUCH_MAX_FINGER_NUM, + INPUT_MT_DIRECT | INPUT_MT_DROP_UNUSED); + if (ret) { + dev_err(dev, "Cannot init MT slots (%d)\n", ret); + return ret; + } + + ret = input_register_device(ts->input); + if (ret) { + dev_err(dev, "Failed to register input device: %d\n", + ret); + return ret; + } + + return 0; +} + +static int nt36xxx_of_compatible(struct device *dev) +{ + struct device_node *np = dev->of_node; + + if (!of_device_is_compatible(np, "novatek,NVT-default-spi")) { + const char *path = "/chosen"; + struct device_node *dt_node; + const char *bootargs; + + dt_node = of_find_node_by_path(path); + if (!dt_node) { + dev_err(dev, "Failed to find device-tree node: %s\n", path); + return -ENODEV; + } + + if (!of_property_read_string(dt_node, "bootargs", &bootargs)) + if (!strstr(bootargs, "tianma") && !strstr(bootargs, "nt36")) + return -ENODEV; + + dev_info(dev, "Try to probe novatek/tianma panel as specified in chosen/bootargs."); + } + return 0; +} + +int nt36xxx_probe(struct device *dev, int irq, const struct input_id *id, + struct regmap *regmap) +{ + const struct nt36xxx_chip_data *chip_data; + const char *signed_fwname = NULL; + int ret; + + struct nt36xxx_ts *ts = devm_kzalloc(dev, sizeof(struct nt36xxx_ts), GFP_KERNEL); + if (!ts) + return -ENOMEM; + + dev_set_drvdata(dev, ts); + + chip_data = of_device_get_match_data(dev); + if(!chip_data) + return -EINVAL; + + ts->dev = dev; + ts->regmap = regmap; + ts->irq = irq; + + ts->data = chip_data; + memcpy(ts->mmap_data, chip_data->mmap, sizeof(ts->mmap_data)); + ts->mmap = ts->mmap_data; + + ts->supplies = devm_kcalloc(dev, NT36XXX_NUM_SUPPLIES, + sizeof(*ts->supplies), GFP_KERNEL); + if (!ts->supplies) + return -ENOMEM; + + ts->reset_gpio = devm_gpiod_get_optional(dev, "reset", GPIOD_OUT_LOW); + if (IS_ERR(ts->reset_gpio)) + return PTR_ERR(ts->reset_gpio); + + gpiod_set_consumer_name(ts->reset_gpio, "nt36xxx_reset"); + + ts->irq_gpio = devm_gpiod_get_optional(dev, "irq", GPIOD_IN); + if (IS_ERR(ts->irq_gpio)) + return PTR_ERR(ts->irq_gpio); + + if (irq <= 0) { + ts->irq = gpiod_to_irq(ts->irq_gpio); + if (ts->irq <=0) { + dev_err(dev, "either need irq or irq-gpio specified in devicetree node!\n"); + return -EINVAL; + } + + dev_info(ts->dev, "irq %d", ts->irq); + } + + gpiod_set_consumer_name(ts->irq_gpio, "nt36xxx_irq"); + + if (drm_is_panel_follower(dev)) + goto skip_regulators; + + /* These supplies are optional, also shared with LCD panel */ + ts->supplies[0].supply = "vdd"; + ts->supplies[1].supply = "vio"; + ts->supplies[2].supply = "vio2"; + ts->supplies[3].supply = "vio3"; + ret = devm_regulator_bulk_get(dev, + NT36XXX_NUM_SUPPLIES, + ts->supplies); + if (ret) + return dev_err_probe(dev, ret, + "Cannot get supplies: %d\n", ret); + + ret = regulator_bulk_enable(NT36XXX_NUM_SUPPLIES, ts->supplies); + if (ret) + return ret; + + usleep_range(10000, 11000); + + ret = devm_add_action_or_reset(dev, nt36xxx_disable_regulators, ts); + if (ret) + return ret; + +skip_regulators: + mutex_init(&ts->lock); + + ret = nt36xxx_eng_reset_idle(ts); + if (ret) { + dev_err(dev, "Failed to check chip version\n"); + return ret; + } + + /* Set memory maps for the specific chip version */ + ret = nt36xxx_chip_version_init(ts); + if (ret) { + dev_err(dev, "Failed to check chip version\n"); + return ret; + } + + ret = nt36xxx_of_compatible(dev); + if (ret) { + return ret; + } + + /* copy the const mmap into drvdata */ + memcpy(ts->mmap_data, ts->data->mmap, sizeof(ts->mmap_data)); + ts->mmap = ts->mmap_data; + + ret = nt36xxx_input_dev_config(ts, ts->data->id); + if (ret) { + dev_err(dev, "failed set input device: %d\n", ret); + return ret; + } + + ret = devm_request_threaded_irq(dev, ts->irq, NULL, nt36xxx_irq_handler, + IRQ_TYPE_EDGE_RISING | IRQF_ONESHOT, dev_name(dev), ts); + if (ret) { + dev_err(dev, "request irq failed: %d\n", ret); + return ret; + } + + /* init with default name */ + ts->fw_name = ts->data->fw_name; + /* support overriding fw name */ + of_property_read_string_index(ts->dev->of_node, "firmware-name", 0, &signed_fwname); + if (signed_fwname) + ts->fw_name = signed_fwname; + + if (drm_is_panel_follower(dev)) { + ts->panel_follower.funcs = &nt36xxx_panel_follower_funcs; + devm_drm_panel_add_follower(dev, &ts->panel_follower); + } + + pm_runtime_enable(dev); + + /* have to make sure this is first time schedule work, if devm_drm_panel_add_follower + * called into internal resume with schedule_delay_work, then block it over there */ + if (ts->fw_name) { + ts->status |= NT36XXX_STATUS_NEED_FIRMWARE; + + /* make the driver sleep while waiting tasklet fw download */ + pm_runtime_suspend(dev); + + devm_delayed_work_autocancel(dev, &ts->work, nt36xxx_download_firmware); + schedule_delayed_work(&ts->work, 0); + } + + dev_info(dev, "probe ok!"); + return 0; +} + +EXPORT_SYMBOL_GPL(nt36xxx_probe); + +static int __maybe_unused nt36xxx_internal_pm_suspend(struct device *dev) +{ + struct nt36xxx_ts *ts = dev_get_drvdata(dev); + int ret = 0; + + mutex_lock(&ts->lock); + ts->status |= NT36XXX_STATUS_SUSPEND; + mutex_unlock(&ts->lock); + + cancel_delayed_work_sync(&ts->work); + + /* adding the mutex is to protect concurrent with download_task */ + mutex_lock(&ts->lock); + if (ts->mmap[MMAP_EVENT_BUF_ADDR]) { + ret = regmap_write(ts->regmap, ts->mmap[MMAP_EVENT_BUF_ADDR], NT36XXX_CMD_ENTER_SLEEP); + } + + if (ret) + dev_err(ts->dev, "Cannot enter suspend!\n"); + mutex_unlock(&ts->lock); + + return 0; +} + +static int __maybe_unused nt36xxx_pm_suspend(struct device *dev) +{ + struct nt36xxx_ts *ts = dev_get_drvdata(dev); + int ret=0; + + if (drm_is_panel_follower(dev)) + return 0; + + disable_irq_nosync(ts->irq); + + regulator_bulk_disable(NT36XXX_NUM_SUPPLIES, ts->supplies); + + ret = nt36xxx_internal_pm_suspend(dev); + return ret; +} + +static int __maybe_unused nt36xxx_internal_pm_resume(struct device *dev) +{ + struct nt36xxx_ts *ts = dev_get_drvdata(dev); + + mutex_lock(&ts->lock); + if(ts->status & (NT36XXX_STATUS_SUSPEND | NT36XXX_STATUS_DOWNLOAD_COMPLETE)) + ts->status &= ~(NT36XXX_STATUS_SUSPEND | NT36XXX_STATUS_DOWNLOAD_COMPLETE); + mutex_unlock(&ts->lock); + + if (ts->status & NT36XXX_STATUS_NEED_FIRMWARE) + schedule_delayed_work(&ts->work, 0); + + return 0; +} + +static int __maybe_unused nt36xxx_pm_resume(struct device *dev) +{ + struct nt36xxx_ts *ts = dev_get_drvdata(dev); + int ret=0; + + if (drm_is_panel_follower(dev)) + return 0; + + enable_irq(ts->irq); + + ret = regulator_bulk_enable(NT36XXX_NUM_SUPPLIES, ts->supplies); + + ret = nt36xxx_internal_pm_resume(dev); + return ret; +} + +EXPORT_GPL_SIMPLE_DEV_PM_OPS(nt36xxx_pm_ops, + nt36xxx_pm_suspend, + nt36xxx_pm_resume); + +static int panel_prepared(struct drm_panel_follower *follower) +{ + struct nt36xxx_ts *ts = container_of(follower, struct nt36xxx_ts, panel_follower); + + if (ts->status & NT36XXX_STATUS_SUSPEND) + enable_irq(ts->irq); + + /* supposed to clear the flag here, but leave to internal_pm_resume + * for greater purpose, then clear flag as: + * ts->status &= ~NT36XXX_STATUS_SUSPEND; + */ + return nt36xxx_internal_pm_resume(ts->dev); +} + +static int panel_unpreparing(struct drm_panel_follower *follower) +{ + struct nt36xxx_ts *ts = container_of(follower, struct nt36xxx_ts, panel_follower); + + mutex_lock(&ts->lock); + ts->status |= NT36XXX_STATUS_SUSPEND; + mutex_unlock(&ts->lock); + + disable_irq_nosync(ts->irq); + + return nt36xxx_internal_pm_suspend(ts->dev); +} + +static struct drm_panel_follower_funcs nt36xxx_panel_follower_funcs = { + .panel_prepared = panel_prepared, + .panel_unpreparing = panel_unpreparing, +}; + +MODULE_LICENSE("GPL"); +MODULE_DESCRIPTION("NT36XXX Touchscreen driver"); +MODULE_AUTHOR("AngeloGioacchino Del Regno <kholk11@xxxxxxxxx>"); +MODULE_AUTHOR("George Chan <gchan9527@xxxxxxxxx>"); diff --git a/drivers/input/touchscreen/nt36xxx_spi.c b/drivers/input/touchscreen/nt36xxx_spi.c new file mode 100644 index 0000000000..21d2a4c79c --- /dev/null +++ b/drivers/input/touchscreen/nt36xxx_spi.c @@ -0,0 +1,256 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * NT36XXX SPI Touchscreen Driver + * + * Copyright (C) 2020 - 2021 Goodix, Inc. + * Copyright (C) 2023 Linaro Ltd. + * Copyright (C) 2023-2024 George Chan <gchan9527@xxxxxxxxx> + * + * Based on goodix_ts_berlin driver. + */ +#include <linux/input.h> +#include <linux/input/touchscreen.h> +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/of.h> +#include <linux/of_device.h> +#include <linux/regmap.h> +#include <linux/spi/spi.h> +#include <linux/unaligned.h> + +#include "nt36xxx.h" + +#define SPI_READ_PREFIX_LEN 1 +#define SPI_WRITE_PREFIX_LEN 1 + +#define DEBUG 0 + +/* + * there are two kinds of spi read/write: + * (a)spi_read()/spi_write()/spi_write_then_read(), + * (b)and the spi_sync itself. + * + * we have to choose one and stick together, cross-use otherwise caused problem. + * the addressing mode is | 0xff 0xXX 0xYY | 0xZ1 ... data1...| 0xZ2 ...data2... | ... + * 0xXX is bit[23..16] + * 0xYY is bit[15..7] + * above describe a 'page select' ops + * 0xZ1 is bit[7..0], addr for read ops + * 0xZ2 is bit[7..0] | 0x80, addr for write ops + * there is no restriction on the read write order. +*/ +static int nt36xxx_spi_write(void *dev, const void *data, + size_t len) +{ + struct spi_device *spi = to_spi_device((struct device *)dev); + int32_t ret; + + void *data1 = kmemdup(data, len, GFP_KERNEL|GFP_DMA); + if (!data1) + return -ENOMEM; + + u8 addr[4] = { 0xff, *(u32 *)data >> 15, *(u32 *)data >> 7, (*(u32 *)data & 0x7f) | 0x80}; + memcpy(data1, addr, 4); + + dev_dbg(dev, "%s len=0x%lx", __func__, len); + + spi_write(spi, data1, 3); + ret = spi_write(spi, data1 + 3, len - 3); + if (ret) + dev_err(dev, "transfer err %d\n ", ret); + else if (DEBUG) { + + print_hex_dump(KERN_INFO, __func__, DUMP_PREFIX_OFFSET, + 16, 1, data, 3, true); + + print_hex_dump(KERN_INFO, __func__, DUMP_PREFIX_OFFSET, + 16, 1, data + 3, (len - 3) > 0x20 ? 0x20 : len - 3 , true); + } + + kfree(data1); + return ret; +} + +static int nt36xxx_spi_read(void *dev, const void *reg_buf, + size_t reg_size, void *val_buf, + size_t val_size) +{ + struct spi_device *spi = to_spi_device(dev); + int ret; + u8 addr[4] = { 0xff, *(u32 *)reg_buf >> 15, *(u32 *)reg_buf >> 7, *(u32 *)reg_buf & 0x7f }; + + ret = spi_write(spi, addr, 3); + if (ret) { + dev_err(dev, "transfer0 err %s %d ret=%d", __func__, __LINE__, ret); + return ret; + } + + ret = spi_write_then_read(spi, &addr[3] , 1, val_buf, val_size); + if (ret) { + dev_err(dev, "transfer1 err %s %d ret=%d", __func__, __LINE__, ret); + return ret; + } + + if (DEBUG) { + print_hex_dump(KERN_INFO, __func__, DUMP_PREFIX_OFFSET, + 16, 1, addr, 3, true); + + print_hex_dump(KERN_INFO, __func__, DUMP_PREFIX_OFFSET, + 16, 1, addr, (val_size) > 0x20 ? 0x20 : val_size % 0x20 , true); + + print_hex_dump(KERN_INFO, __func__, DUMP_PREFIX_OFFSET, + 16, 1, val_buf, (val_size > 0x20) ? 0x20 : val_size % 0x20 , true); + } + + return ret; +} + +const struct regmap_config nt36xxx_regmap_config_32bit = { + .name = "nt36xxx_hw", + .reg_bits = 32, + .val_bits = 8, + .read = nt36xxx_spi_read, + .write = nt36xxx_spi_write, + + .max_raw_read = NT36XXX_TRANSFER_LEN + 8, + .max_raw_write = NT36XXX_TRANSFER_LEN + 8, + + .zero_flag_mask = true, /* this is needed to make sure addr is not write_masked */ + .cache_type = REGCACHE_NONE, +}; + +static const struct input_id nt36xxx_spi_input_id = { + .bustype = BUS_SPI, +}; + +static int nt36xxx_spi_probe(struct spi_device *spi) +{ + struct regmap_config *regmap_config; + struct regmap *regmap; + size_t max_size; + int ret = 0; + + dev_dbg(&spi->dev, "%s %d", __func__, __LINE__); + + regmap_config = devm_kmemdup(&spi->dev, &nt36xxx_regmap_config_32bit, + sizeof(*regmap_config), GFP_KERNEL); + if (!regmap_config) { + dev_err(&spi->dev, "memdup regmap_config fail\n"); + return -ENOMEM; + } + + spi->mode = SPI_MODE_0; + spi->bits_per_word = 8; + ret = spi_setup(spi); + if (ret) { + dev_err(&spi->dev, "SPI setup error %d\n", ret); + return ret; + } + + /* don't exceed max specified SPI CLK frequency */ + if (spi->max_speed_hz > MAX_SPI_FREQ_HZ) { + dev_err(&spi->dev, "SPI CLK %d Hz?\n", spi->max_speed_hz); + return -EINVAL; + } + + max_size = spi_max_transfer_size(spi); + regmap_config->max_raw_read = max_size - SPI_READ_PREFIX_LEN; + regmap_config->max_raw_write = max_size - SPI_WRITE_PREFIX_LEN; + + regmap = devm_regmap_init(&spi->dev, NULL, spi, regmap_config); + if (IS_ERR(regmap)) + return PTR_ERR(regmap); + + return nt36xxx_probe(&spi->dev, spi->irq, + &nt36xxx_spi_input_id, regmap); +} + +const struct nt36xxx_chip_data default_config = { + .config = &nt36xxx_regmap_config_32bit, + .mmap = nt36676f_memory_maps, + .max_x = 1080, + .max_y = 2400, + .abs_x_max = 1080, + .abs_y_max = 2400, + .id = &nt36xxx_spi_input_id, +}; + +const struct nt36xxx_chip_data miatoll_tianma_nt36675 = { + .config = &nt36xxx_regmap_config_32bit, + .mmap = nt36675_memory_maps, + .fw_name = "novatek_ts_tianma_fw.bin", + .max_x = 1080, + .max_y = 2400, + .abs_x_max = 1080, + .abs_y_max = 2400, + .id = &nt36xxx_spi_input_id, +}; + +const struct nt36xxx_chip_data generic_nt36676f = { + .config = &nt36xxx_regmap_config_32bit, + .mmap = nt36676f_memory_maps, + .max_x = 1080, + .max_y = 2400, + .abs_x_max = 1080, + .abs_y_max = 2400, + .id = &nt36xxx_spi_input_id, +}; + +const struct nt36xxx_chip_data generic_nt36772 = { + .config = &nt36xxx_regmap_config_32bit, + .mmap = nt36772_memory_maps, + .max_x = 1080, + .max_y = 2400, + .abs_x_max = 1080, + .abs_y_max = 2400, + .id = &nt36xxx_spi_input_id, +}; + +const struct nt36xxx_chip_data generic_nt36525 = { + .config = &nt36xxx_regmap_config_32bit, + .mmap = nt36525_memory_maps, + .max_x = 1080, + .max_y = 2400, + .abs_x_max = 1080, + .abs_y_max = 2400, + .id = &nt36xxx_spi_input_id, +}; + +static const struct spi_device_id nt36xxx_spi_ids[] = { + { "nt36675-spi", 0 }, + { }, +}; +MODULE_DEVICE_TABLE(spi, nt36xxx_spi_ids); + +static const struct of_device_id nt36xxx_spi_of_match[] = { + { .compatible = "novatek,nt36675-spi", .data = &miatoll_tianma_nt36675, }, + { .compatible = "novatek,nt36672a-spi", .data = &miatoll_tianma_nt36675, }, + { .compatible = "novatek,nt36676f-spi", .data = &generic_nt36676f, }, + { .compatible = "novatek,nt36772-spi", .data = &generic_nt36772, }, + { .compatible = "novatek,nt36525-spi", .data = &generic_nt36525, }, + /* + * this is served for two special purpose. + * (1) detect/display model only, and bail out in the end + * (2) checking device varients, mixed use of novatek and focaltech spi ic + * TODO: might add auto select mmap for unknown nvt device. + */ + { .compatible = "novatek,NVT-default-spi", .data = &default_config, }, + { } +}; +MODULE_DEVICE_TABLE(of, nt36xxx_spi_of_match); + +static struct spi_driver nt36xxx_spi_driver = { + .driver = { + .name = "nt36675-spi", + .of_match_table = nt36xxx_spi_of_match, + .pm = pm_sleep_ptr(&nt36xxx_pm_ops), + }, + .probe = nt36xxx_spi_probe, + .id_table = nt36xxx_spi_ids, +}; +module_spi_driver(nt36xxx_spi_driver); + +MODULE_LICENSE("GPL"); +MODULE_DESCRIPTION("NT36XXX SPI Touchscreen driver"); +MODULE_AUTHOR("Neil Armstrong <neil.armstrong@xxxxxxxxxx>"); +MODULE_AUTHOR("George Chan <gchan9527@xxxxxxxxx>"); -- 2.43.0