@@ -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
@@ -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
new file mode 100644
@@ -0,0 +1,142 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/*
+ * Copyright (C) 2010 - 2017 Novatek, Inc.
+ * Copyright (C) 2020 AngeloGioacchino Del Regno <kholk11@gmail.com>
+ * Copyright (C) 2023-2024 George Chan <gchan9527@gmail.com>
+ */
+
+#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
new file mode 100644
@@ -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@gmail.com>
+ * Copyright (C) 2023-2024 George Chan <gchan9527@gmail.com>
+ *
+ * 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@gmail.com>");
+MODULE_AUTHOR("George Chan <gchan9527@gmail.com>");
new file mode 100644
@@ -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@gmail.com>
+ *
+ * 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@linaro.org>");
+MODULE_AUTHOR("George Chan <gchan9527@gmail.com>");