[PATCH 12/12] HID: spi-hid: add quirkis to support mode switch for Ilitek touch

Jingyuan Liang posted 12 patches 1 month, 1 week ago
There is a newer version of this series
[PATCH 12/12] HID: spi-hid: add quirkis to support mode switch for Ilitek touch
Posted by Jingyuan Liang 1 month, 1 week ago
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@chromium.org>
---
 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