[PATCH 12/12] HID: spi-hid: add quirkis to support mode switch for Ilitek touch
From: Jingyuan Liang
Date: Tue Mar 03 2026 - 01:14:39 EST
Add quirks to support mode switch among Ilitek normal, debug and test mode
and allow delay before send output reports.
Add a shared variable to configure response timeout value for Ilitek
touch controllers.
Signed-off-by: Jingyuan Liang <jingyliang@xxxxxxxxxxxx>
---
drivers/hid/spi-hid/spi-hid-core.c | 84 +++++++++++++++++++++++++++++++++++++-
drivers/hid/spi-hid/spi-hid-core.h | 4 ++
drivers/hid/spi-hid/spi-hid.h | 6 +++
3 files changed, 93 insertions(+), 1 deletion(-)
diff --git a/drivers/hid/spi-hid/spi-hid-core.c b/drivers/hid/spi-hid/spi-hid-core.c
index 893a0d4642d2..736e51f10cfc 100644
--- a/drivers/hid/spi-hid/spi-hid-core.c
+++ b/drivers/hid/spi-hid/spi-hid-core.c
@@ -22,6 +22,7 @@
#include <linux/completion.h>
#include <linux/crc32.h>
+#include <linux/delay.h>
#include <linux/device.h>
#include <linux/dma-mapping.h>
#include <linux/err.h>
@@ -45,9 +46,14 @@
#include <linux/wait.h>
#include <linux/workqueue.h>
+#include "../hid-ids.h"
#include "spi-hid.h"
#include "spi-hid-core.h"
+/* quirks to control the device */
+#define SPI_HID_QUIRK_MODE_SWITCH BIT(0)
+#define SPI_HID_QUIRK_READ_DELAY BIT(1)
+
/* Protocol constants */
#define SPI_HID_READ_APPROVAL_CONSTANT 0xff
#define SPI_HID_INPUT_HEADER_SYNC_BYTE 0x5a
@@ -86,6 +92,16 @@
#define SPI_HID_CREATE_DEVICE 4
#define SPI_HID_ERROR 5
+static const struct spi_hid_quirks {
+ __u16 idVendor;
+ __u16 idProduct;
+ __u32 quirks;
+} spi_hid_quirks[] = {
+ { USB_VENDOR_ID_ILITEK, HID_ANY_ID,
+ SPI_HID_QUIRK_MODE_SWITCH | SPI_HID_QUIRK_READ_DELAY },
+ { 0, 0 }
+};
+
/* Processed data from input report header */
struct spi_hid_input_header {
u8 version;
@@ -112,6 +128,27 @@ struct spi_hid_output_report {
static struct hid_ll_driver spi_hid_ll_driver;
+/**
+ * spi_hid_lookup_quirk: return any quirks associated with a SPI HID device
+ * @idVendor: the 16-bit vendor ID
+ * @idProduct: the 16-bit product ID
+ *
+ * Returns: a u32 quirks value.
+ */
+static u32 spi_hid_lookup_quirk(const u16 idVendor, const u16 idProduct)
+{
+ u32 quirks = 0;
+ int n;
+
+ for (n = 0; spi_hid_quirks[n].idVendor; n++)
+ if (spi_hid_quirks[n].idVendor == idVendor &&
+ (spi_hid_quirks[n].idProduct == (__u16)HID_ANY_ID ||
+ spi_hid_quirks[n].idProduct == idProduct))
+ quirks = spi_hid_quirks[n].quirks;
+
+ return quirks;
+}
+
static void spi_hid_populate_read_approvals(const struct spi_hid_conf *conf,
u8 *header_buf, u8 *body_buf)
{
@@ -382,6 +419,9 @@ static int spi_hid_send_output_report(struct spi_hid *shid,
u8 padding;
int error;
+ if (shid->quirks & SPI_HID_QUIRK_READ_DELAY)
+ usleep_range(2000, 2100);
+
guard(mutex)(&shid->output_lock);
if (report->content_length > shid->desc.max_output_length) {
dev_err(dev, "Output report too big, content_length 0x%x.",
@@ -406,18 +446,38 @@ static int spi_hid_send_output_report(struct spi_hid *shid,
return error;
}
+static const u32 spi_hid_get_timeout(struct spi_hid *shid)
+{
+ struct device *dev = &shid->spi->dev;
+ u32 timeout;
+
+ timeout = READ_ONCE(shid->ops->response_timeout_ms);
+
+ if (timeout < SPI_HID_RESP_TIMEOUT || timeout > 10000) {
+ dev_dbg(dev, "Response timeout is out of range, using default %d",
+ SPI_HID_RESP_TIMEOUT);
+ timeout = SPI_HID_RESP_TIMEOUT;
+ }
+
+ return timeout;
+}
+
static int spi_hid_sync_request(struct spi_hid *shid,
struct spi_hid_output_report *report)
{
struct device *dev = &shid->spi->dev;
+ u32 timeout = SPI_HID_RESP_TIMEOUT;
int error;
error = spi_hid_send_output_report(shid, report);
if (error)
return error;
+ if (shid->quirks & SPI_HID_QUIRK_MODE_SWITCH)
+ timeout = spi_hid_get_timeout(shid);
+
error = wait_for_completion_interruptible_timeout(&shid->output_done,
- msecs_to_jiffies(SPI_HID_RESP_TIMEOUT));
+ msecs_to_jiffies(timeout));
if (error == 0) {
dev_err(dev, "Response timed out.");
return -ETIMEDOUT;
@@ -561,6 +621,8 @@ static int spi_hid_create_device(struct spi_hid *shid)
hid->vendor = shid->desc.vendor_id;
hid->product = shid->desc.product_id;
+ shid->quirks = spi_hid_lookup_quirk(hid->vendor, hid->product);
+
snprintf(hid->name, sizeof(hid->name), "spi %04X:%04X",
hid->vendor, hid->product);
strscpy(hid->phys, dev_name(&shid->spi->dev), sizeof(hid->phys));
@@ -836,6 +898,24 @@ static irqreturn_t spi_hid_dev_irq(int irq, void *_shid)
goto out;
}
+ if (shid->quirks & SPI_HID_QUIRK_MODE_SWITCH) {
+ /*
+ * Update reset_pending on mode transitions inferred from
+ * response timeout (entering/exiting a mode).
+ */
+ u32 timeout = spi_hid_get_timeout(shid);
+ bool mode_enabled = timeout > SPI_HID_RESP_TIMEOUT;
+
+ if (mode_enabled != shid->prev_mode_enabled) {
+ if (mode_enabled)
+ set_bit(SPI_HID_RESET_PENDING, &shid->flags);
+ else
+ clear_bit(SPI_HID_RESET_PENDING, &shid->flags);
+ }
+
+ shid->prev_mode_enabled = mode_enabled;
+ }
+
if (shid->input_message.status < 0) {
dev_warn(dev, "Error reading header: %d.",
shid->input_message.status);
@@ -1190,6 +1270,8 @@ static int spi_hid_dev_init(struct spi_hid *shid)
struct device *dev = &spi->dev;
int error;
+ shid->ops->custom_init(shid->ops);
+
shid->ops->assert_reset(shid->ops);
shid->ops->sleep_minimal_reset_delay(shid->ops);
diff --git a/drivers/hid/spi-hid/spi-hid-core.h b/drivers/hid/spi-hid/spi-hid-core.h
index 88e9020d37aa..8441dbad95d4 100644
--- a/drivers/hid/spi-hid/spi-hid-core.h
+++ b/drivers/hid/spi-hid/spi-hid-core.h
@@ -62,6 +62,10 @@ struct spi_hid {
u16 response_length;
u16 bufsize;
+ bool prev_mode_enabled; /* Previous device mode tracked for SPI_HID_QUIRK_MODE_SWITCH. */
+
+ unsigned long quirks; /* Various quirks. */
+
enum hidspi_power_state power_state;
u8 reset_attempts; /* The number of reset attempts. */
diff --git a/drivers/hid/spi-hid/spi-hid.h b/drivers/hid/spi-hid/spi-hid.h
index 5651c7fb706a..3c0369bdb4ab 100644
--- a/drivers/hid/spi-hid/spi-hid.h
+++ b/drivers/hid/spi-hid/spi-hid.h
@@ -25,6 +25,9 @@ struct spi_hid_conf {
* @power_down: do sequencing to power down the device
* @assert_reset: do sequencing to assert the reset line
* @deassert_reset: do sequencing to deassert the reset line
+ * @sleep_minimal_reset_delay: minimal sleep delay during reset
+ * @custom_init: customized device init
+ * @response_timeout_ms: output report response timeout in ms
*/
struct spihid_ops {
int (*power_up)(struct spihid_ops *ops);
@@ -32,6 +35,9 @@ struct spihid_ops {
int (*assert_reset)(struct spihid_ops *ops);
int (*deassert_reset)(struct spihid_ops *ops);
void (*sleep_minimal_reset_delay)(struct spihid_ops *ops);
+ int (*custom_init)(struct spihid_ops *ops);
+
+ u32 response_timeout_ms;
};
int spi_hid_core_probe(struct spi_device *spi, struct spihid_ops *ops,
--
2.53.0.473.g4a7958ca14-goog