diff mbox series

[v3,2/2] Input: Add Himax HX83102J touchscreen driver

Message ID SEZPR06MB560897A94EB645FC8FFD4FCE9E9FA@SEZPR06MB5608.apcprd06.prod.outlook.com
State New
Headers show
Series None | expand

Commit Message

Allen_Lin Dec. 27, 2023, 5:35 a.m. UTC
Add a new driver for Himax touchscreen series touchscreen controllers.
This driver supports Himax IC using the SPI interface to
acquire HID packets.

Signed-off-by: Allen_Lin <allencl_lin@hotmail.com>
---
 MAINTAINERS                    |    1 +
 drivers/hid/Kconfig            |    8 +
 drivers/hid/Makefile           |    2 +
 drivers/hid/hid-himax-83102j.c | 1096 ++++++++++++++++++++++++++++++++
 drivers/hid/hid-himax-83102j.h |  202 ++++++
 5 files changed, 1309 insertions(+)
 create mode 100644 drivers/hid/hid-himax-83102j.c
 create mode 100644 drivers/hid/hid-himax-83102j.h
diff mbox series

Patch

diff --git a/MAINTAINERS b/MAINTAINERS
index 6a92e40d126d..e9553edf0bf0 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -9507,6 +9507,7 @@  M:	Allen Lin <allencl_lin@hotmail.com>
 L:	linux-input@vger.kernel.org
 S:	Maintained
 F:	Documentation/devicetree/bindings/input/himax,hx83102j.yaml
+F:	drivers/hid/hid-himax-83102j.*
 
 HIMAX HX83112B TOUCHSCREEN SUPPORT
 M:	Job Noorman <job@noorman.info>
diff --git a/drivers/hid/Kconfig b/drivers/hid/Kconfig
index 4ce74af79657..e9bb95c288ec 100644
--- a/drivers/hid/Kconfig
+++ b/drivers/hid/Kconfig
@@ -1325,6 +1325,14 @@  config HID_KUNIT_TEST
 
 	  If in doubt, say "N".
 
+config HID_HIMAX_HX83102J
+	tristate "Himax hx83102j touchpanel CHIPSET"
+	depends on HID
+	help
+	  Say Y here if you have a Himax CHIPSET touchscreen.
+	  HIMAX controllers are multi touch controllers which can
+	  report 10 touches at a time.
+	  If unsure, say N.
 endmenu
 
 source "drivers/hid/bpf/Kconfig"
diff --git a/drivers/hid/Makefile b/drivers/hid/Makefile
index 8a06d0f840bc..3474a9d8c43d 100644
--- a/drivers/hid/Makefile
+++ b/drivers/hid/Makefile
@@ -169,3 +169,5 @@  obj-$(INTEL_ISH_FIRMWARE_DOWNLOADER)	+= intel-ish-hid/
 obj-$(CONFIG_AMD_SFH_HID)       += amd-sfh-hid/
 
 obj-$(CONFIG_SURFACE_HID_CORE)  += surface-hid/
+
+obj-$(CONFIG_HID_HIMAX_HX83102J)	+= hid-himax-83102j.o
diff --git a/drivers/hid/hid-himax-83102j.c b/drivers/hid/hid-himax-83102j.c
new file mode 100644
index 000000000000..414c5fb99885
--- /dev/null
+++ b/drivers/hid/hid-himax-83102j.c
@@ -0,0 +1,1096 @@ 
+// SPDX-License-Identifier: GPL-2.0
+/*  Himax hx83102j Driver Code for Common IC to simulate HID
+ *
+ *  Copyright (C) 2023 Himax Corporation.
+ */
+
+#include "hid-himax-83102j.h"
+
+static void hx83102j_pin_reset(struct himax_ts_data *ts);
+static void himax_ts_work(struct himax_ts_data *ts);
+static int himax_resume(struct device *dev);
+static int himax_suspend(struct device *dev);
+static int himax_chip_init(struct himax_ts_data *ts);
+static bool hx83102j_sense_off(struct himax_ts_data *ts, bool check_en);
+
+static int himax_spi_read(struct himax_ts_data *ts, u8 *cmd,
+			  u8 cmd_len, u8 *buf, u32 len)
+{
+	struct spi_message m;
+	int result = 0;
+	int retry;
+	int error;
+	struct spi_transfer	t = {
+		.len = cmd_len + len,
+	};
+
+	t.tx_buf = ts->xfer_data;
+	t.rx_buf = ts->xfer_data;
+
+	spi_message_init(&m);
+	spi_message_add_tail(&t, &m);
+
+	for (retry = 0; retry < HIMAX_BUS_RETRY_TIMES; retry++) {
+		error = spi_sync(ts->spi, &m);
+		if (!unlikely(error))
+			break;
+	}
+
+	if (retry == HIMAX_BUS_RETRY_TIMES) {
+		dev_err(ts->dev, "SPI read error retry over %d", HIMAX_BUS_RETRY_TIMES);
+		result = -EIO;
+		goto err_retry_over;
+	} else {
+		memcpy(buf, ts->xfer_data + cmd_len, len);
+	}
+
+err_retry_over:
+	return result;
+}
+
+static int himax_spi_write(struct himax_ts_data *ts, u8 *buf,
+			   u32 length)
+{
+	int status;
+	struct spi_message	m;
+	struct spi_transfer	t = {
+			.tx_buf		= buf,
+			.len		= length,
+	};
+
+	spi_message_init(&m);
+	spi_message_add_tail(&t, &m);
+
+	status = spi_sync(ts->spi, &m);
+
+	if (status == 0) {
+		status = m.status;
+		if (status == 0)
+			status = m.actual_length;
+	}
+
+	return status;
+}
+
+static int himax_bus_read(struct himax_ts_data *ts, u8 cmd,
+		   u8 *buf, u32 len)
+{
+	int result = -1;
+	u8 hw_addr = 0x00;
+
+	if (len > HIMAX_BUS_R_DLEN) {
+		dev_err(ts->dev, "len[%d] is over %d", len, HIMAX_BUS_R_DLEN);
+		return result;
+	}
+
+	mutex_lock(&ts->rw_lock);
+
+	hw_addr = 0xF3;
+
+	memset(ts->xfer_data, 0, HIMAX_BUS_R_HLEN + len);
+	ts->xfer_data[0] = hw_addr;
+	ts->xfer_data[1] = cmd;
+	ts->xfer_data[2] = 0x00;
+	result = himax_spi_read(ts, ts->xfer_data, HIMAX_BUS_R_HLEN, buf, len);
+
+	mutex_unlock(&ts->rw_lock);
+
+	return result;
+}
+
+static int himax_bus_write(struct himax_ts_data *ts, u8 cmd,
+		    u8 *addr, u8 *data, u32 len)
+{
+	int result = -1;
+	u8 offset = 0;
+	u32 tmp_len = len;
+	u8 hw_addr = 0x00;
+
+	if (len > HIMAX_BUS_W_DLEN) {
+		dev_err(ts->dev, "len[%d] is over %d", len, HIMAX_BUS_W_DLEN);
+		return -EFAULT;
+	}
+
+	mutex_lock(&ts->rw_lock);
+
+	hw_addr = 0xF2;
+
+	ts->xfer_data[0] = hw_addr;
+	ts->xfer_data[1] = cmd;
+	offset = HIMAX_BUS_W_HLEN;
+
+	if (addr) {
+		memcpy(ts->xfer_data + offset, addr, 4);
+		offset += 4;
+		tmp_len -= 4;
+	}
+
+	if (data)
+		memcpy(ts->xfer_data + offset, data, tmp_len);
+
+	result = himax_spi_write(ts, ts->xfer_data, len + HIMAX_BUS_W_HLEN);
+
+	mutex_unlock(&ts->rw_lock);
+
+	return (result == len + HIMAX_BUS_W_HLEN) ? 0 : -EIO;
+}
+
+static void himax_int_enable(struct himax_ts_data *ts, int enable)
+{
+	unsigned long irqflags = 0;
+	int irqnum = ts->himax_irq;
+
+	spin_lock_irqsave(&ts->irq_lock, irqflags);
+	if (enable == 1 && atomic_read(&ts->irq_state) == 0) {
+		atomic_set(&ts->irq_state, 1);
+		enable_irq(irqnum);
+		ts->irq_enabled = 1;
+	} else if (enable == 0 && atomic_read(&ts->irq_state) == 1) {
+		atomic_set(&ts->irq_state, 0);
+		disable_irq_nosync(irqnum);
+		ts->irq_enabled = 0;
+	}
+
+	spin_unlock_irqrestore(&ts->irq_lock, irqflags);
+}
+
+static void himax_ts_isr_func(struct himax_ts_data *ts)
+{
+	himax_ts_work(ts);
+}
+
+static irqreturn_t himax_ts_thread(int irq, void *ptr)
+{
+	himax_ts_isr_func((struct himax_ts_data *)ptr);
+
+	return IRQ_HANDLED;
+}
+
+static int himax_int_register_trigger(struct himax_ts_data *ts)
+{
+	int ret = 0;
+
+	if (ts->ic_data->HX_INT_IS_EDGE) {
+		ret = devm_request_threaded_irq(ts->dev, ts->himax_irq, NULL,
+					  himax_ts_thread, IRQF_TRIGGER_FALLING | IRQF_ONESHOT,
+					  ts->dev->driver->name, ts);
+	} else {
+		ret = devm_request_threaded_irq(ts->dev, ts->himax_irq, NULL,
+					  himax_ts_thread, IRQF_TRIGGER_LOW | IRQF_ONESHOT,
+					  ts->dev->driver->name, ts);
+	}
+
+	return ret;
+}
+
+static int himax_ts_register_interrupt(struct himax_ts_data *ts)
+{
+	int ret = 0;
+
+	ts->irq_enabled = 0;
+
+	if (ts->himax_irq) {
+
+		ret = himax_int_register_trigger(ts);
+
+		if (ret == 0) {
+			ts->irq_enabled = 1;
+			atomic_set(&ts->irq_state, 1);
+		} else {
+
+			dev_err(ts->dev, "request_irq failed");
+		}
+	}
+
+	return ret;
+}
+
+static void himax_mcu_burst_enable(struct himax_ts_data *ts,
+				   u8 auto_add_4_byte)
+{
+	u8 tmp_data[HIMAX_REG_DATA_LEN];
+	int ret;
+
+	tmp_data[0] = HIMAX_IC_CMD_CONTI;
+
+	ret = himax_bus_write(ts, HIMAX_IC_ADR_CONTI, NULL, tmp_data, 1);
+	if (ret < 0) {
+		dev_err(ts->dev, "bus access fail!");
+		return;
+	}
+
+	tmp_data[0] = (HIMAX_IC_CMD_INCR4 | auto_add_4_byte);
+
+	ret = himax_bus_write(ts, HIMAX_IC_ADR_INCR4, NULL, tmp_data, 1);
+	if (ret < 0) {
+		dev_err(ts->dev, "bus access fail!");
+		return;
+	}
+}
+static int himax_mcu_register_read(struct himax_ts_data *ts, u32 addr,
+				   u8 *buf, u32 len)
+{
+	int ret = 0;
+	union {
+		u8 byte[4];
+		u32 word;
+	} target_addr = { .word = cpu_to_le32(addr) };
+	u8 direction_switch = HIMAX_IC_CMD_AHB_ACCESS_DIRECTION_READ;
+
+	mutex_lock(&ts->reg_lock);
+
+	if (addr == HIMAX_FLASH_ADDR_SPI200_DATA)
+		himax_mcu_burst_enable(ts, 0);
+	else if (len > HIMAX_REG_DATA_LEN)
+		himax_mcu_burst_enable(ts, 1);
+	else
+		himax_mcu_burst_enable(ts, 0);
+
+	ret = himax_bus_write(ts, HIMAX_IC_ADR_AHB_ADDR_BYTE_0, target_addr.byte, NULL, 4);
+	if (ret < 0) {
+		dev_err(ts->dev, "bus access fail!");
+		goto read_end;
+	}
+
+	ret = himax_bus_write(ts, HIMAX_IC_ADR_AHB_ACCESS_DIRECTION, NULL,
+			      &direction_switch, 1);
+	if (ret < 0) {
+		dev_err(ts->dev, "bus access fail!");
+		goto read_end;
+	}
+
+	ret = himax_bus_read(ts, HIMAX_IC_ADR_AHB_RDATA_BYTE_0, buf, len);
+	if (ret < 0) {
+		dev_err(ts->dev, "bus access fail!");
+		goto read_end;
+	}
+
+read_end:
+	mutex_unlock(&ts->reg_lock);
+
+	return ret;
+}
+
+static int himax_mcu_register_write(struct himax_ts_data *ts, u32 addr,
+				    u8 *val, u32 len)
+{
+	int ret = 0;
+	const u32 max_trans_sz = 4 * 1024;
+	int i = 0;
+	union {
+		u8 byte[4];
+		u16 half[2];
+		u32 word;
+	} target_addr;
+	u32 temp_len = 0;
+
+	mutex_lock(&ts->reg_lock);
+	if (addr == HIMAX_FLASH_ADDR_SPI200_DATA)
+		himax_mcu_burst_enable(ts, 0);
+	else if (len > HIMAX_REG_DATA_LEN)
+		himax_mcu_burst_enable(ts, 1);
+	else
+		himax_mcu_burst_enable(ts, 0);
+
+	if (len > max_trans_sz) {
+		for (i = 0; i < len; i += max_trans_sz) {
+			if ((len - i) > max_trans_sz)
+				temp_len = max_trans_sz;
+			else
+				temp_len = len % max_trans_sz;
+
+			target_addr.word = cpu_to_le32(addr + i);
+			ret = himax_bus_write(ts, HIMAX_IC_ADR_AHB_ADDR_BYTE_0,
+					      target_addr.byte, val + i, temp_len + HIMAX_REG_ADDR_LEN);
+			if (ret < 0) {
+				dev_err(ts->dev, "xfer fail!");
+				goto write_end;
+			}
+		}
+	} else {
+		target_addr.word = cpu_to_le32(addr);
+		ret = himax_bus_write(ts, HIMAX_IC_ADR_AHB_ADDR_BYTE_0, target_addr.byte, val,
+				      len + HIMAX_REG_ADDR_LEN);
+		if (ret < 0) {
+			dev_err(ts->dev, "xfer fail!");
+			goto write_end;
+		}
+	}
+write_end:
+	mutex_unlock(&ts->reg_lock);
+
+	return ret;
+}
+
+
+static void himax_ap_notify_fw_sus(struct himax_ts_data *ts, int suspend)
+{
+	int retry = 0;
+	int read_sts = 0;
+	union {
+		u8 byte[4];
+		u16 half[2];
+		u32 word;
+	} rdata, data;
+
+	if (suspend)
+		data.word = HIMAX_FW_DATA_AP_NOTIFY_FW_SUS_EN;
+	else
+		data.word = HIMAX_FW_DATA_AP_NOTIFY_FW_SUS_DIS;
+
+	data.word = cpu_to_le32(data.word);
+	do {
+		himax_mcu_register_write(ts, HIMAX_FW_ADDR_AP_NOTIFY_FW_SUS, data.byte,
+			4);
+		usleep_range(1000, 1001);
+		read_sts = himax_mcu_register_read(ts, HIMAX_FW_ADDR_AP_NOTIFY_FW_SUS, rdata.byte,
+			4);
+	} while ((retry++ < 10) && (read_sts != 0) &&
+		(rdata.word != data.word));
+}
+
+static void himax_resume_proc(struct himax_ts_data *ts, bool suspended)
+{
+		himax_ap_notify_fw_sus(ts, 0);
+}
+
+static void himax_mcu_ic_reset(struct himax_ts_data *ts, u8 loadconfig,
+			       u8 int_off)
+{
+
+	if (ts->gpiod_rst) {
+		if (int_off)
+			himax_int_enable(ts, 0);
+
+		hx83102j_pin_reset(ts);
+
+		if (int_off)
+			himax_int_enable(ts, 1);
+	}
+}
+static void himax_mcu_touch_information(struct himax_ts_data *ts)
+{
+	if (ts->ic_data->HX_RX_NUM == 0xFFFFFFFF)
+		ts->ic_data->HX_RX_NUM = 48;
+
+	if (ts->ic_data->HX_TX_NUM == 0xFFFFFFFF)
+		ts->ic_data->HX_TX_NUM = 32;
+
+	if (ts->ic_data->HX_BT_NUM == 0xFFFFFFFF)
+		ts->ic_data->HX_BT_NUM = 0;
+
+	if (ts->ic_data->HX_MAX_PT == 0xFFFFFFFF)
+		ts->ic_data->HX_MAX_PT = 10;
+
+	if (ts->ic_data->HX_INT_IS_EDGE == 0xFF)
+		ts->ic_data->HX_INT_IS_EDGE = false;
+
+	if (ts->ic_data->HX_STYLUS_FUNC == 0xFF)
+		ts->ic_data->HX_STYLUS_FUNC = 1;
+
+	if (ts->ic_data->HX_STYLUS_ID_V2 == 0xFF)
+		ts->ic_data->HX_STYLUS_ID_V2 = 0;
+
+	if (ts->ic_data->HX_STYLUS_RATIO == 0xFF)
+		ts->ic_data->HX_STYLUS_RATIO = 1;
+
+}
+static bool hx83102j_chip_detect(struct himax_ts_data *ts)
+{
+	union {
+		u8 byte[4];
+		u16 half[2];
+		u32 word;
+	} data;
+	bool ret_data = false;
+	int ret = 0;
+	int i = 0;
+	bool check_flash;
+
+	hx83102j_pin_reset(ts);
+	ret = himax_bus_read(ts, 0x13, data.byte, 1);
+	if (ret < 0) {
+		dev_err(ts->dev, "bus access fail!");
+		return false;
+	}
+
+	check_flash = false;
+
+	if (hx83102j_sense_off(ts, check_flash) == false) {
+		ret_data = false;
+		dev_err(ts->dev, "hx83102_sense_off Fail!");
+		return ret_data;
+	}
+
+	for (i = 0; i < 5; i++) {
+		ret = himax_mcu_register_read(ts, HIMAX_HX83102J_ICID_ADDR, data.byte, 4);
+		if (ret != 0) {
+			ret_data = false;
+			dev_err(ts->dev, "read ic id Fail");
+			return ret_data;
+		}
+
+		if ((data.word & 0xFFFFFF00) == HIMAX_HX83102J_ICID_DATA) {
+			strscpy(ts->chip_name,
+				HIMAX_HX83102J_ID, 30);
+			ts->ic_data->icid = data.word;
+			ret_data = true;
+			break;
+		}
+		dev_err(ts->dev, "Read driver IC ID = %X,%X,%X",
+		  data.byte[3], data.byte[2], data.byte[1]);
+		ret_data = false;
+		dev_err(ts->dev, "Read driver ID register Fail!");
+		dev_err(ts->dev, "Could NOT find Himax Chipset");
+		dev_err(ts->dev, "Please check:\n1.VCCD,VCCA,VSP,VSN");
+		dev_err(ts->dev, "2. LCM_RST,TP_RST");
+		dev_err(ts->dev, "3. Power On Sequence");
+	}
+
+	return ret_data;
+}
+static bool hx83102j_sense_off(struct himax_ts_data *ts, bool check_en)
+{
+	u32 cnt = 0;
+	union {
+		u8 byte[4];
+		u16 half[2];
+		u32 word;
+	} data;
+	int ret = 0;
+
+	do {
+		data.word = cpu_to_le32(HIMAX_FW_DATA_FW_STOP);
+		if (cnt == 0 ||
+		    (data.byte[0] != 0xA5 &&
+		    data.byte[0] != 0x00 &&
+		    data.byte[0] != 0x87))
+			himax_mcu_register_write(ts, HIMAX_FW_ADDR_CTRL_FW,
+				data.byte, 4);
+		usleep_range(10000, 10001);
+
+		himax_mcu_register_read(ts, HIMAX_IC_ADR_CS_CENTRAL_STATE,
+			data.byte, 4);
+
+		if (data.byte[0] != 0x05)
+			break;
+
+		himax_mcu_register_read(ts, HIMAX_FW_ADDR_CTRL_FW,
+			data.byte, 4);
+	} while (data.byte[0] != 0x87 && ++cnt < 35 && check_en);
+
+	cnt = 0;
+
+	do {
+		/**
+		 * set Enter safe mode : 0x31 ==> 0x9527
+		 */
+		data.half[0] = cpu_to_le16(HIMAX_HX83102J_SAFE_MODE_PASSWORD);
+		ret = himax_bus_write(ts, 0x31, NULL, data.byte, 2);
+		if (ret < 0) {
+			dev_err(ts->dev, "bus access fail!");
+			return false;
+		}
+
+		/**
+		 *Check enter_save_mode
+		 */
+		himax_mcu_register_read(ts, HIMAX_IC_ADR_CS_CENTRAL_STATE, data.byte, 4);
+
+		if (data.byte[0] == 0x0C) {
+			/**
+			 *Reset TCON
+			 */
+			data.word = 0;
+			himax_mcu_register_write(ts, HIMAX_HX83102J_IC_ADR_TCON_RST, data.byte, 4);
+			usleep_range(1000, 1001);
+			return true;
+		}
+		usleep_range(5000, 5001);
+		hx83102j_pin_reset(ts);
+	} while (cnt++ < 5);
+
+	return false;
+}
+
+static bool hx83102j_read_event_stack(struct himax_ts_data *ts,
+				      u8 *buf, u32 length)
+{
+	int ret = 0;
+
+	ret = himax_bus_read(ts, HIMAX_FW_ADDR_EVENT_ADDR, buf, length);
+
+	return (ret == 0) ? true : false;
+}
+
+static void hx83102j_pin_reset(struct himax_ts_data *ts)
+{
+	if (ts->gpiod_rst) {
+		gpiod_set_value(ts->gpiod_rst, 1);
+		usleep_range(100 * 100, 101 * 100);
+		gpiod_set_value(ts->gpiod_rst, 0);
+		usleep_range(200 * 100, 201 * 100);
+	}
+}
+
+static int himax_touch_get(struct himax_ts_data *ts, u8 *buf, int ts_path)
+{
+	u32 read_size = 0;
+	int ts_status = 0;
+
+	switch (ts_path) {
+	case HIMAX_REPORT_COORD:
+		read_size = ts->touch_all_size;
+		break;
+	default:
+		break;
+	}
+
+	if (read_size == 0) {
+		dev_err(ts->dev, "Read size fault!");
+		ts_status = HIMAX_TS_GET_DATA_FAIL;
+	} else {
+		if (!hx83102j_read_event_stack(ts, buf, read_size)) {
+			dev_err(ts->dev, "can't read data from chip!");
+			ts_status = HIMAX_TS_GET_DATA_FAIL;
+		}
+	}
+
+	return ts_status;
+}
+
+static int himax_hid_parse(struct hid_device *hid)
+{
+	struct himax_ts_data *ts = NULL;
+	int ret;
+
+	if (!hid) {
+		dev_err(ts->dev, "hid is NULL");
+		return -EINVAL;
+	}
+
+	ts = hid->driver_data;
+	if (!ts) {
+		dev_err(ts->dev, "hid->driver_data is NULL");
+		return -EINVAL;
+	}
+
+	ret = hid_parse_report(hid, ts->hid_rd_data.rd_data,
+			       ts->hid_rd_data.rd_length);
+	if (ret) {
+		dev_err(ts->dev, "failed parse report");
+		return	ret;
+	}
+	return 0;
+}
+
+static int himax_hid_start(struct hid_device *hid)
+{
+	return 0;
+}
+
+static void himax_hid_stop(struct hid_device *hid)
+{
+}
+
+static int himax_hid_open(struct hid_device *hid)
+{
+	return 0;
+}
+
+static void himax_hid_close(struct hid_device *hid)
+{
+}
+
+static int himax_hid_get_raw_report(const struct hid_device *hid, unsigned char reportnum,
+				 __u8 *buf, size_t len, unsigned char report_type)
+{
+	struct himax_ts_data *ts = NULL;
+	int ret = 0;
+
+	ts = hid->driver_data;
+	if (!ts) {
+		dev_err(ts->dev, "hid->driver_data is NULL");
+		return -EINVAL;
+	}
+
+
+	switch (reportnum) {
+	case ID_CONTACT_COUNT:
+		if (!ts->ic_data) {
+			dev_err(ts->dev, "ts->ic_data is NULL");
+			return -EINVAL;
+		}
+		buf[1] = ts->ic_data->HX_MAX_PT;
+		ret = len;
+		break;
+	default:
+		ret = -EINVAL;
+	};
+	return ret;
+}
+
+static int himax_raw_request(struct hid_device *hid, unsigned char reportnum,
+			  __u8 *buf, size_t len, unsigned char rtype, int reqtype)
+{
+	switch (reqtype) {
+	case HID_REQ_GET_REPORT:
+		return himax_hid_get_raw_report(hid, reportnum, buf, len, rtype);
+	default:
+		return -EIO;
+	}
+
+	return -EINVAL;
+}
+
+static struct hid_ll_driver himax_hid_ll_driver = {
+	.parse = himax_hid_parse,
+	.start = himax_hid_start,
+	.stop = himax_hid_stop,
+	.open = himax_hid_open,
+	.close = himax_hid_close,
+	.raw_request = himax_raw_request
+};
+
+static int himax_hid_report(const struct himax_ts_data *ts, u8 *data, s32 len)
+{
+	int ret = 0;
+
+	if (ts->hid)
+		ret = hid_input_report(ts->hid, HID_INPUT_REPORT, data, len, 1);
+
+	return ret;
+}
+static int himax_hid_probe(struct himax_ts_data *ts)
+{
+	int ret;
+	struct hid_device *hid = NULL;
+
+	if (!ts) {
+		dev_err(ts->dev, "ts is NULL");
+		return -EINVAL;
+	}
+	hid = ts->hid;
+	if (hid) {
+		hid_destroy_device(hid);
+		hid = NULL;
+	}
+
+	hid = hid_allocate_device();
+	if (IS_ERR(hid)) {
+		ret = PTR_ERR(hid);
+		return ret;
+	}
+
+	hid->driver_data = ts;
+	hid->ll_driver = &himax_hid_ll_driver;
+	hid->bus = BUS_SPI;
+	hid->dev.parent = &ts->spi->dev;
+
+	hid->version = ts->hid_desc.bcd_version;
+	hid->vendor = ts->hid_desc.vendor_id;
+	hid->product = ts->hid_desc.product_id;
+	snprintf(hid->name, sizeof(hid->name), "%s %04X:%04X", "hid-hxtp",
+		 hid->vendor, hid->product);
+
+	ret = hid_add_device(hid);
+	if (ret) {
+		dev_err(ts->dev, "failed add hid device");
+		goto err_hid_data;
+	}
+	ts->hid = hid;
+	mutex_unlock(&ts->hid_ioctl_lock);
+	return 0;
+
+err_hid_data:
+	hid_destroy_device(hid);
+	return ret;
+}
+
+static void himax_hid_remove(struct himax_ts_data *ts)
+{
+	mutex_lock(&ts->hid_ioctl_lock);
+	if (ts && ts->hid)
+		hid_destroy_device(ts->hid);
+	else
+		goto out;
+	ts->hid = NULL;
+out:
+	mutex_unlock(&ts->hid_ioctl_lock);
+}
+
+
+static int himax_ts_operation(struct himax_ts_data *ts,
+			      int ts_path)
+{
+	int ts_status = HIMAX_TS_NORMAL_END;
+	int ret = 0;
+	u32 offset = 0;
+
+	memset(ts->xfer_buff,
+	       0x00,
+		ts->touch_all_size * sizeof(u8));
+	ts_status = himax_touch_get(ts, ts->xfer_buff, ts_path);
+	if (ts_status == HIMAX_TS_GET_DATA_FAIL)
+		goto end_function;
+	if (ts->hid_probe) {
+		offset += ts->hid_desc.max_input_length;
+		if (ts->ic_data->HX_STYLUS_FUNC) {
+			ret += himax_hid_report(ts,
+				ts->xfer_buff + offset + HIMAX_HID_REPORT_HDR_SZ,
+				ts->hid_desc.max_input_length - HIMAX_HID_REPORT_HDR_SZ);
+			offset += ts->hid_desc.max_input_length;
+		}
+	}
+
+	if (ret != 0)
+		ts_status = HIMAX_TS_GET_DATA_FAIL;
+
+end_function:
+	return ts_status;
+}
+static void himax_ts_work(struct himax_ts_data *ts)
+{
+	int ts_status = HIMAX_TS_NORMAL_END;
+	int ts_path = 0;
+
+
+	ts_path = HIMAX_REPORT_COORD;
+	ts_status = himax_ts_operation(ts, ts_path);
+	if (ts_status == HIMAX_TS_GET_DATA_FAIL)
+		himax_mcu_ic_reset(ts, false, true);
+
+}
+
+static int himax_hid_rd_init(struct himax_ts_data *ts)
+{
+	int ret = 0;
+	u32 rd_sz = 0;
+
+	rd_sz = ts->hid_desc.report_desc_length;
+	if (ts->flash_ver_info.addr_hid_rd_desc != 0) {
+		if (ts->hid_rd_data.rd_data &&
+		    rd_sz != ts->hid_rd_data.rd_length) {
+			kfree(ts->hid_rd_data.rd_data);
+			ts->hid_rd_data.rd_data = NULL;
+		}
+
+		if (!ts->hid_rd_data.rd_data)
+			ts->hid_rd_data.rd_data = kzalloc(rd_sz, GFP_KERNEL);
+
+		if (ts->hid_rd_data.rd_data) {
+		} else {
+			dev_err(ts->dev, "hid rd data alloc fail");
+			ret = -ENOMEM;
+		}
+	}
+
+	return ret;
+}
+
+static void himax_hid_register(struct himax_ts_data *ts)
+{
+	if (ts->hid_probe) {
+		hid_destroy_device(ts->hid);
+		ts->hid = NULL;
+		ts->hid_probe = false;
+	}
+
+	if (himax_hid_probe(ts) != 0) {
+		dev_err(ts->dev, "hid probe fail");
+		ts->hid_probe = false;
+	} else {
+		ts->hid_probe = true;
+	}
+}
+
+static int himax_hid_report_data_init(struct himax_ts_data *ts)
+{
+	int ret = 0;
+
+	ts->touch_info_size = ts->hid_desc.max_input_length;
+	if (ts->ic_data->HX_STYLUS_FUNC)
+		ts->touch_info_size += ts->hid_desc.max_input_length;
+
+	ts->touch_all_size = ts->touch_info_size;
+	return ret;
+}
+
+static void himax_hid_update(struct work_struct *work)
+{
+	struct himax_ts_data *ts = container_of(work, struct himax_ts_data,
+			work_hid_update.work);
+
+	himax_int_enable(ts, false);
+	if (himax_hid_rd_init(ts) == 0) {
+		himax_hid_register(ts);
+		if (ts->hid_probe)
+			himax_hid_report_data_init(ts);
+	}
+	himax_int_enable(ts, true);
+}
+
+static int himax_chip_suspend(struct himax_ts_data *ts)
+{
+	int ret = 0;
+
+	ts->suspended = true;
+	himax_int_enable(ts, false);
+	if (ts->gpiod_rst)
+		gpiod_set_value(ts->gpiod_rst, 1);
+	himax_hid_remove(ts);
+	return ret;
+}
+
+static int himax_chip_resume(struct himax_ts_data *ts)
+{
+	int ret = 0;
+
+	ts->suspended = false;
+	if (ts->gpiod_rst)
+		gpiod_set_value(ts->gpiod_rst, 0);
+	himax_resume_proc(ts, ts->suspended);
+		himax_hid_probe(ts);
+		himax_int_enable(ts, true);
+	return ret;
+}
+
+static int himax_suspend(struct device *dev)
+{
+	struct himax_ts_data *ts = dev_get_drvdata(dev);
+
+	if (!ts->initialized) {
+		dev_err(ts->dev, "init not ready, skip!");
+		return -ECANCELED;
+	}
+	himax_chip_suspend(ts);
+	return 0;
+}
+
+static void himax_shutdown(struct spi_device *spi)
+{
+	struct himax_ts_data *ts = spi_get_drvdata(spi);
+
+	if (!ts->initialized) {
+		dev_err(ts->dev, "init not ready, skip!");
+		return;
+	}
+
+	himax_int_enable(ts, false);
+	himax_hid_remove(ts);
+}
+
+static int himax_resume(struct device *dev)
+{
+	int ret = 0;
+	struct himax_ts_data *ts = dev_get_drvdata(dev);
+
+	if (!ts->initialized) {
+		if (himax_chip_init(ts))
+			return -ECANCELED;
+	}
+	ret = himax_chip_resume(ts);
+	if (ret < 0)
+		dev_err(ts->dev, "resume failed!");
+	return ret;
+}
+static const struct dev_pm_ops himax_hid_pm = {
+	.suspend = himax_suspend,
+	.resume = himax_resume,
+	.restore = himax_resume,
+};
+
+#if defined(CONFIG_OF)
+static const struct of_device_id himax_table[] = {
+	{ .compatible = "himax,hx83102j" },
+	{},
+};
+MODULE_DEVICE_TABLE(of, himax_table);
+#endif
+
+static int himax_chip_init(struct himax_ts_data *ts)
+{
+	himax_mcu_touch_information(ts);
+	spin_lock_init(&ts->irq_lock);
+	if (himax_ts_register_interrupt(ts)) {
+		dev_err(ts->dev, "register interrupt failed");
+		return -EIO;
+	}
+	himax_int_enable(ts, false);
+	INIT_DELAYED_WORK(&ts->work_hid_update, himax_hid_update);
+	ts->suspended = false;
+	ts->initialized = true;
+	return 0;
+
+}
+static bool himax_platform_init(struct himax_ts_data *ts,
+				struct himax_platform_data *local_pdata)
+{
+	struct himax_platform_data *pdata;
+
+	ts->xfer_buff = devm_kzalloc(ts->dev, HIMAX_FULL_STACK_SIZE, GFP_KERNEL);
+	if (!ts->xfer_buff)
+		return false;
+
+	pdata = devm_kzalloc(ts->dev, sizeof(struct himax_platform_data), GFP_KERNEL);
+	if (!pdata)
+		return false;
+
+
+	ts->ic_data = devm_kzalloc(ts->dev, sizeof(struct himax_ic_data), GFP_KERNEL);
+	if (!ts->ic_data)
+		return false;
+
+	memset(ts->ic_data, 0xFF, sizeof(struct himax_ic_data));
+	memcpy(pdata, local_pdata, sizeof(struct himax_platform_data));
+	ts->pdata = pdata;
+	pdata->ts = ts;
+	ts->gpiod_rst = pdata->gpiod_rst;
+	if (pdata->gpiod_rst)
+		gpiod_set_value(pdata->gpiod_rst, 1);
+	if (pdata->gpiod_rst)
+		gpiod_set_value(pdata->gpiod_rst, 0);
+
+	return true;
+}
+
+static int himax_spi_drv_probe(struct spi_device *spi)
+{
+	struct himax_ts_data *ts = NULL;
+	int ret = 0;
+	bool bret = false;
+	static struct himax_platform_data pdata = {0};
+
+	ts = devm_kzalloc(&spi->dev, sizeof(struct himax_ts_data), GFP_KERNEL);
+	if (!ts)
+		return -ENOMEM;
+
+	ts->dev = &spi->dev;
+	if (spi->master->flags & SPI_MASTER_HALF_DUPLEX) {
+		dev_err(ts->dev, "Full duplex not supported by host");
+		return -EIO;
+	}
+	pdata.ts = ts;
+	ts->dev = &spi->dev;
+	if (!spi->irq) {
+		dev_dbg(ts->dev, "no IRQ?\n");
+		return -EINVAL;
+	}
+	ts->himax_irq = spi->irq;
+	pdata.gpiod_rst = devm_gpiod_get(ts->dev, "reset", GPIOD_OUT_HIGH);
+	if (IS_ERR(pdata.gpiod_rst)) {
+		dev_err(ts->dev, "gpio-rst value is not valid");
+		return -EIO;
+	}
+
+
+	ts->xfer_data = devm_kzalloc(ts->dev, HIMAX_BUS_RW_MAX_LEN, GFP_KERNEL);
+	if (!ts->xfer_data)
+		return -ENOMEM;
+
+	spi->bits_per_word = 8;
+	spi->mode = SPI_MODE_3;
+	spi->chip_select = 0;
+
+	ts->spi = spi;
+	mutex_init(&ts->rw_lock);
+	mutex_init(&ts->reg_lock);
+	mutex_init(&ts->hid_ioctl_lock);
+	dev_set_drvdata(&spi->dev, ts);
+	spi_set_drvdata(spi, ts);
+
+	ts->probe_finish = false;
+	ts->initialized = false;
+	ts->ic_boot_done = false;
+	bret = himax_platform_init(ts, &pdata);
+	if (!bret) {
+		dev_err(ts->dev, "platform init failed");
+		return -ENODEV;
+	}
+
+	bret = hx83102j_chip_detect(ts);
+	if (!bret) {
+		dev_err(ts->dev, "IC detect failed");
+		return -ENODEV;
+	}
+
+	ret = himax_chip_init(ts);
+	if (ret < 0)
+		return ret;
+	ts->probe_finish = true;
+	return ret;
+
+}
+
+
+static void himax_spi_drv_remove(struct spi_device *spi)
+{
+	struct himax_ts_data *ts = spi_get_drvdata(spi);
+
+	if (ts->probe_finish) {
+		if (ts->ic_boot_done) {
+			himax_int_enable(ts, false);
+
+			if (ts->hid_probe) {
+				himax_hid_remove(ts);
+				ts->hid_probe = false;
+			}
+
+			kfree(ts->hid_rd_data.rd_data);
+			ts->hid_rd_data.rd_data = NULL;
+
+			ts->ic_boot_done = false;
+		}
+	}
+	spi_set_drvdata(spi, NULL);
+
+}
+static struct spi_driver himax_hid_over_spi_driver = {
+	.driver = {
+		.name =		"hx83102j",
+		.owner =	THIS_MODULE,
+		.pm	= &himax_hid_pm,
+#if defined(CONFIG_OF)
+		.of_match_table = of_match_ptr(himax_table),
+#endif
+	},
+	.probe =	himax_spi_drv_probe,
+	.remove =	himax_spi_drv_remove,
+	.shutdown =	himax_shutdown,
+};
+static void himax_spi_drv_exit(void)
+{
+	spi_unregister_driver(&himax_hid_over_spi_driver);
+}
+
+static int himax_spi_drv_init(void)
+{
+	int ret;
+
+	ret = spi_register_driver(&himax_hid_over_spi_driver);
+	return ret;
+}
+
+static int __init himax_ic_init(void)
+{
+	int ret = 0;
+
+	ret = himax_spi_drv_init();
+	return ret;
+}
+
+static void __exit himax_ic_exit(void)
+{
+	himax_spi_drv_exit();
+}
+
+#if !defined(CONFIG_HID_HIMAX)
+module_init(himax_ic_init);
+#else
+late_initcall(himax_ic_init);
+#endif
+module_exit(himax_ic_exit);
+
+MODULE_DESCRIPTION("Himax SPI driver for HID simulator for " HIMAX_HX83102J_ID);
+MODULE_AUTHOR("Himax, Inc.");
+MODULE_LICENSE("GPL");
diff --git a/drivers/hid/hid-himax-83102j.h b/drivers/hid/hid-himax-83102j.h
new file mode 100644
index 000000000000..61e9d006f9be
--- /dev/null
+++ b/drivers/hid/hid-himax-83102j.h
@@ -0,0 +1,202 @@ 
+/* SPDX-License-Identifier: GPL-2.0 */
+
+#ifndef __HX_IC_83102J_H__
+#define __HX_IC_83102J_H__
+
+#include <linux/slab.h>
+#include <linux/module.h>
+#include <linux/kernel.h>
+#include <linux/delay.h>
+#include <linux/regulator/consumer.h>
+#include <linux/interrupt.h>
+#include <linux/irq.h>
+#include <linux/acpi.h>
+#include <linux/spi/spi.h>
+#include <linux/hid.h>
+#include <linux/sizes.h>
+#include <linux/fs.h>
+#include <linux/gpio.h>
+#include <linux/types.h>
+#include <linux/of.h>
+#include <linux/of_gpio.h>
+#include <linux/proc_fs.h>
+#include <linux/version.h>
+#include <linux/firmware.h>
+#include <linux/stddef.h>
+#include <linux/power_supply.h>
+
+#define HIMAX_BUS_RETRY_TIMES 3
+// SPI bus read/write max length
+#define HIMAX_BUS_RW_MAX_LEN 0x20006
+// SPI bus read header length
+#define HIMAX_BUS_R_HLEN 3
+// SPI bus read data length, must be multiple of 4 and smaller than BUS_RW_MAX_LEN - BUS_R_HLEN
+#define HIMAX_BUS_R_DLEN ((HIMAX_BUS_RW_MAX_LEN - HIMAX_BUS_R_HLEN) - ((HIMAX_BUS_RW_MAX_LEN - HIMAX_BUS_R_HLEN) % 4))
+// SPI bus write header length
+#define HIMAX_BUS_W_HLEN 2
+// SPI bus write data length, must be multiple of 4 and smaller than BUS_RW_MAX_LEN - BUS_W_HLEN
+#define HIMAX_BUS_W_DLEN ((HIMAX_BUS_RW_MAX_LEN - HIMAX_BUS_W_HLEN) - ((HIMAX_BUS_RW_MAX_LEN - HIMAX_BUS_W_HLEN) % 4))
+
+enum HID_ID_FUNCT {
+	ID_CONTACT_COUNT = 0x03,
+};
+
+enum HID_FW_UPDATE_STATUS_CODE {
+	FWUP_ERROR_NO_ERROR = 0x77,
+	FWUP_ERROR_NO_MAIN = 0xC2,
+	FWUP_ERROR_BL_COMPLETE = 0xB1,
+	FWUP_ERROR_BL = 0xB2,
+	FWUP_ERROR_FLASH_PROGRAMMING = 0xB5,
+};
+
+
+// Register setting
+#define HIMAX_REG_DATA_LEN			4
+#define HIMAX_REG_ADDR_LEN			4
+#define HIMAX_MAX_TRANS_SZ			128
+#define HIMAX_MAX_RETRY_TIMES			5
+
+#define HIMAX_HX83102J_STACK_SIZE			128
+#define HIMAX_HX83102J_IC_ADR_TCON_RST     0x80020004
+#define HIMAX_HX83102J_SAFE_MODE_PASSWORD			0x9527
+#define HIMAX_HX83102J_ICID_ADDR					0x900000D0
+#define HIMAX_HX83102J_ICID_DATA					0x83102900
+#define HIMAX_HX83102J_MAX_RX_NUM			48
+#define HIMAX_HX83102J_MAX_TX_NUM			32
+
+#define HIMAX_IC_ADR_AHB_ADDR_BYTE_0           0x00
+#define HIMAX_IC_ADR_AHB_RDATA_BYTE_0          0x08
+#define HIMAX_IC_ADR_AHB_ACCESS_DIRECTION      0x0c
+#define HIMAX_IC_ADR_CONTI                     0x13
+#define HIMAX_IC_ADR_INCR4                     0x0D
+#define HIMAX_IC_CMD_AHB_ACCESS_DIRECTION_READ 0x00
+#define HIMAX_IC_CMD_CONTI                     0x31
+#define HIMAX_IC_CMD_INCR4                     0x10
+#define HIMAX_IC_ADR_CS_CENTRAL_STATE          0x900000A8
+
+#define HIMAX_FW_ADDR_CTRL_FW                     0x9000005c
+#define HIMAX_FW_USB_DETECT_ADDR                  0x10007F38
+#define HIMAX_FW_DATA_SAFE_MODE_RELEASE_PW_RESET  0x00000000
+#define HIMAX_FW_DATA_FW_STOP                     0x000000A5
+#define HIMAX_FW_ADDR_AP_NOTIFY_FW_SUS            0x10007FD0
+#define HIMAX_FW_DATA_AP_NOTIFY_FW_SUS_EN         0xA55AA55A
+#define HIMAX_FW_DATA_AP_NOTIFY_FW_SUS_DIS        0x00000000
+#define HIMAX_FW_ADDR_EVENT_ADDR                  0x30
+#define HIMAX_FW_FUNC_HANDSHAKING_PWD             0xA55AA55A
+
+#define HIMAX_FLASH_ADDR_CTRL_BASE           0x80000000
+#define HIMAX_FLASH_ADDR_SPI200_DATA         (HIMAX_FLASH_ADDR_CTRL_BASE + 0x2c)
+
+#define HIMAX_HID_REPORT_HDR_SZ (2)
+#define HIMAX_HX83102J_ID		"HX83102J"
+
+
+struct flash_version_info {
+	u32 addr_hid_rd_desc;
+};
+
+struct himax_hid_rd_data_t {
+	u8 *rd_data;
+	u32 rd_length;
+};
+union himax_dword_data_t {
+	u32 dword;
+	u8 byte[4];
+};
+
+enum hid_reg_action {
+	REG_READ = 0,
+	REG_WRITE = 1
+};
+
+enum hid_reg_types {
+	REG_TYPE_EXT_AHB,
+	REG_TYPE_EXT_SRAM,
+	REG_TYPE_EXT_TYPE = 0xFFFFFFFF
+};
+struct himax_hid_req_cfg_t {
+	u32 data_type;
+	u32 input_RD_de;
+};
+
+#define HIMAX_FULL_STACK_SIZE \
+	(HIMAX_HX83102J_STACK_SIZE +\
+	(2 + HIMAX_HX83102J_MAX_RX_NUM * HIMAX_HX83102J_MAX_TX_NUM + HIMAX_HX83102J_MAX_TX_NUM + HIMAX_HX83102J_MAX_RX_NUM)\
+	* 2)
+
+struct himax_ic_data {
+	u32 HX_RX_NUM;
+	u32 HX_TX_NUM;
+	u32 HX_BT_NUM;
+	u32 HX_MAX_PT;
+	u8 HX_INT_IS_EDGE;
+	u8 HX_STYLUS_FUNC;
+	u8 HX_STYLUS_ID_V2;
+	u8 HX_STYLUS_RATIO;
+	u32 icid;
+};
+
+enum HX_TS_PATH {
+	HIMAX_REPORT_COORD = 1,
+};
+
+enum HX_TS_STATUS {
+	HIMAX_TS_GET_DATA_FAIL = -4,
+	HIMAX_TS_NORMAL_END = 0,
+};
+
+struct himax_hid_desc_t {
+	u16 desc_length;
+	u16 bcd_version;
+	u16 report_desc_length;
+	u16 max_input_length;
+	u16 max_output_length;
+	u16 max_fragment_length;
+	u16 vendor_id;
+	u16 product_id;
+	u16 version_id;
+	u16 flags;
+	u32 reserved;
+} __packed;
+
+struct himax_ts_data {
+	bool initialized;
+	bool probe_finish;
+	bool suspended;
+	char chip_name[30];
+	bool ic_boot_done;
+	u8 *xfer_data;
+	struct himax_ic_data *ic_data;
+	int touch_all_size;
+	int touch_info_size;
+	struct flash_version_info flash_ver_info;
+	u8 irq_enabled;
+	struct gpio_desc *gpiod_rst;
+	s32 (*power)(s32 on);
+	struct device *dev;
+	struct himax_platform_data *pdata;
+	/* mutex lock for reg access */
+	struct mutex reg_lock;
+	/* mutex lock for read/write action */
+	struct mutex rw_lock;
+	/* mutex lock for hid ioctl action */
+	struct mutex hid_ioctl_lock;
+	atomic_t irq_state;
+	/* spin lock for irq */
+	spinlock_t irq_lock;
+	struct spi_device	*spi;
+	s32 himax_irq;
+	u8 *xfer_buff;
+	struct hid_device *hid;
+	struct himax_hid_desc_t hid_desc;
+	struct himax_hid_rd_data_t hid_rd_data;
+	bool hid_probe;
+	struct delayed_work work_hid_update;
+};
+
+struct himax_platform_data {
+	struct himax_ts_data *ts;
+	struct gpio_desc *gpiod_rst;
+};
+
+#endif