diff mbox series

[v3,15/17] HID: add HID device reset callback

Message ID 20220513093927.1632262-16-acz@semihalf.com (mailing list archive)
State New, archived
Headers show
Series *** Implement simple haptic HID support *** | expand

Commit Message

Angela Czubak May 13, 2022, 9:39 a.m. UTC
HID-over-I2C devices might reset on their own. Any device configuration
applied before the reset might be brought back to defaults so we need to
reconfigure to make sure the driver state is consistent.

Add a reset callback to the hid driver structure.
Issue it if the driver implements it and the device reset gets observed.

Signed-off-by: Angela Czubak <acz@semihalf.com>
---
 drivers/hid/i2c-hid/i2c-hid-core.c | 21 +++++++++++++++++++++
 include/linux/hid.h                |  2 ++
 2 files changed, 23 insertions(+)
diff mbox series

Patch

diff --git a/drivers/hid/i2c-hid/i2c-hid-core.c b/drivers/hid/i2c-hid/i2c-hid-core.c
index c078f09a2318..37f134a3d9cb 100644
--- a/drivers/hid/i2c-hid/i2c-hid-core.c
+++ b/drivers/hid/i2c-hid/i2c-hid-core.c
@@ -116,6 +116,7 @@  struct i2c_hid {
 	struct mutex		reset_lock;
 
 	struct i2chid_ops	*ops;
+	struct work_struct	reset_work;
 };
 
 static const struct i2c_hid_quirks {
@@ -504,6 +505,19 @@  static int i2c_hid_hwreset(struct i2c_hid *ihid)
 	return ret;
 }
 
+static void i2c_hid_reset_worker(struct work_struct *work)
+{
+	struct i2c_hid *ihid = container_of(work, struct i2c_hid, reset_work);
+	struct hid_device *hid = ihid->hid;
+
+	down(&hid->driver_input_lock);
+
+	if (hid->driver && hid->driver->reset)
+		hid->driver->reset(hid);
+
+	up(&hid->driver_input_lock);
+}
+
 static void i2c_hid_get_input(struct i2c_hid *ihid)
 {
 	u16 size = le16_to_cpu(ihid->hdesc.wMaxInputLength);
@@ -529,6 +543,8 @@  static void i2c_hid_get_input(struct i2c_hid *ihid)
 		/* host or device initiated RESET completed */
 		if (test_and_clear_bit(I2C_HID_RESET_PENDING, &ihid->flags))
 			wake_up(&ihid->wait);
+		else
+			schedule_work(&ihid->reset_work);
 		return;
 	}
 
@@ -821,6 +837,10 @@  static int i2c_hid_start(struct hid_device *hid)
 
 static void i2c_hid_stop(struct hid_device *hid)
 {
+	struct i2c_client *client = hid->driver_data;
+	struct i2c_hid *ihid = i2c_get_clientdata(client);
+
+	cancel_work_sync(&ihid->reset_work);
 	hid->claimed = 0;
 }
 
@@ -988,6 +1008,7 @@  int i2c_hid_core_probe(struct i2c_client *client, struct i2chid_ops *ops,
 	ihid->wHIDDescRegister = cpu_to_le16(hid_descriptor_address);
 
 	init_waitqueue_head(&ihid->wait);
+	INIT_WORK(&ihid->reset_work, i2c_hid_reset_worker);
 	mutex_init(&ihid->reset_lock);
 
 	/* we need to allocate the command buffer without knowing the maximum
diff --git a/include/linux/hid.h b/include/linux/hid.h
index 3f5899c62821..9db9b7133f1a 100644
--- a/include/linux/hid.h
+++ b/include/linux/hid.h
@@ -784,6 +784,7 @@  struct hid_usage_id {
  * @suspend: invoked on suspend (NULL means nop)
  * @resume: invoked on resume if device was not reset (NULL means nop)
  * @reset_resume: invoked on resume if device was reset (NULL means nop)
+ * @reset: invoked if device was reset (NULL means nop)
  *
  * probe should return -errno on error, or 0 on success. During probe,
  * input will not be passed to raw_event unless hid_device_io_start is
@@ -840,6 +841,7 @@  struct hid_driver {
 	int (*resume)(struct hid_device *hdev);
 	int (*reset_resume)(struct hid_device *hdev);
 #endif
+	int (*reset)(struct hid_device *hdev);
 /* private: */
 	struct device_driver driver;
 };