All of lore.kernel.org
 help / color / mirror / Atom feed
From: Allen_Lin <allencl_lin@hotmail.com>
To: dmitry.torokhov@gmail.com, robh+dt@kernel.org,
	krzysztof.kozlowski+dt@linaro.org, conor+dt@kernel.org,
	jikos@kernel.org, benjamin.tissoires@redhat.com,
	linux-input@vger.kernel.org, devicetree@vger.kernel.org,
	linux-kernel@vger.kernel.org
Cc: Allen_Lin <allencl_lin@hotmail.com>
Subject: [PATCH v3 2/2] Input: Add Himax HX83102J touchscreen driver
Date: Wed, 27 Dec 2023 13:35:09 +0800	[thread overview]
Message-ID: <SEZPR06MB560897A94EB645FC8FFD4FCE9E9FA@SEZPR06MB5608.apcprd06.prod.outlook.com> (raw)
In-Reply-To: <20231227053509.894642-1-allencl_lin@hotmail.com>

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 --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
-- 
2.34.1


      parent reply	other threads:[~2023-12-27  5:35 UTC|newest]

Thread overview: 8+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
     [not found] <20231227053509.894642-1-allencl_lin@hotmail.com>
2023-12-27  5:35 ` [PATCH v3 1/2] dt-bindings: input: Add Himax HX83102J touchscreen Allen_Lin
2023-12-28  8:30   ` kernel test robot
2023-12-28 10:36   ` Krzysztof Kozlowski
2023-12-29  9:08     ` Allen Lin
     [not found]     ` <TY0PR06MB561188EBD127F8ECF4A7052B9E9DA@TY0PR06MB5611.apcprd06.prod.outlook.com>
2023-12-30 14:19       ` Krzysztof Kozlowski
2024-01-02  2:36         ` Allen Lin
2024-01-04  1:14         ` Allen Lin
2023-12-27  5:35 ` Allen_Lin [this message]

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=SEZPR06MB560897A94EB645FC8FFD4FCE9E9FA@SEZPR06MB5608.apcprd06.prod.outlook.com \
    --to=allencl_lin@hotmail.com \
    --cc=benjamin.tissoires@redhat.com \
    --cc=conor+dt@kernel.org \
    --cc=devicetree@vger.kernel.org \
    --cc=dmitry.torokhov@gmail.com \
    --cc=jikos@kernel.org \
    --cc=krzysztof.kozlowski+dt@linaro.org \
    --cc=linux-input@vger.kernel.org \
    --cc=linux-kernel@vger.kernel.org \
    --cc=robh+dt@kernel.org \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.