HID: i2c-hid: add a simple quirk to fix device defects
authorHungNien Chen <hn.chen@weidahitech.com>
Thu, 10 Nov 2016 03:47:13 +0000 (11:47 +0800)
committerJiri Kosina <jkosina@suse.cz>
Thu, 10 Nov 2016 09:23:31 +0000 (10:23 +0100)
Certain devices produced by Weida Tech need to have a wakeup command sent to
them before powering on. The call itself will come back with error, but the
device can be powered on afterwards.

[jkosina@suse.cz: rewrite changelog]
[jkosina@suse.cz: remove unused device ID addition]
Signed-off-by: HungNien Chen <hn.chen@weidahitech.com>
Reviewed-by: Benjamin Tissoires <benjamin.tissoires@redhat.com>
Signed-off-by: Jiri Kosina <jkosina@suse.cz>
drivers/hid/hid-ids.h
drivers/hid/i2c-hid/i2c-hid.c

index cd59c79eebdd2bd896c1ff19d687f25686d7fb35..c36ec6d01b8b980fd59a8729598cc78abf7ed576 100644 (file)
 #define USB_DEVICE_ID_WALTOP_MEDIA_TABLET_14_1_INCH    0x0500
 #define USB_DEVICE_ID_WALTOP_SIRIUS_BATTERY_FREE_TABLET        0x0502
 
+#define        USB_VENDOR_ID_WEIDA             0x2575
+#define        USB_DEVICE_ID_WEIDA_8752        0xC300
+#define        USB_DEVICE_ID_WEIDA_8755        0xC301
+
 #define USB_VENDOR_ID_WISEGROUP                0x0925
 #define USB_DEVICE_ID_SMARTJOY_PLUS    0x0005
 #define USB_DEVICE_ID_SUPER_JOY_BOX_3  0x8888
index fe6b4e0eab4a64f52f6d2568fe8fac4ab14af5c9..ce518795cb30e7846e5b7fdf15eb71334c152d78 100644 (file)
 
 #include <linux/i2c/i2c-hid.h>
 
+#include "../hid-ids.h"
+
+/* quirks to control the device */
+#define I2C_HID_QUIRK_SET_PWR_WAKEUP_DEV       BIT(0)
+
 /* flags */
 #define I2C_HID_STARTED                0
 #define I2C_HID_RESET_PENDING  1
@@ -142,6 +147,7 @@ struct i2c_hid {
        char                    *argsbuf;       /* Command arguments buffer */
 
        unsigned long           flags;          /* device flags */
+       unsigned long           quirks;         /* Various quirks */
 
        wait_queue_head_t       wait;           /* For waiting the interrupt */
 
@@ -151,6 +157,39 @@ struct i2c_hid {
        struct mutex            reset_lock;
 };
 
+static const struct i2c_hid_quirks {
+       __u16 idVendor;
+       __u16 idProduct;
+       __u32 quirks;
+} i2c_hid_quirks[] = {
+       { USB_VENDOR_ID_WEIDA, USB_DEVICE_ID_WEIDA_8752,
+               I2C_HID_QUIRK_SET_PWR_WAKEUP_DEV },
+       { USB_VENDOR_ID_WEIDA, USB_DEVICE_ID_WEIDA_8755,
+               I2C_HID_QUIRK_SET_PWR_WAKEUP_DEV },
+       { 0, 0 }
+};
+
+/*
+ * i2c_hid_lookup_quirk: return any quirks associated with a I2C HID device
+ * @idVendor: the 16-bit vendor ID
+ * @idProduct: the 16-bit product ID
+ *
+ * Returns: a u32 quirks value.
+ */
+static u32 i2c_hid_lookup_quirk(const u16 idVendor, const u16 idProduct)
+{
+       u32 quirks = 0;
+       int n;
+
+       for (n = 0; i2c_hid_quirks[n].idVendor; n++)
+               if (i2c_hid_quirks[n].idVendor == idVendor &&
+                   (i2c_hid_quirks[n].idProduct == (__u16)HID_ANY_ID ||
+                    i2c_hid_quirks[n].idProduct == idProduct))
+                       quirks = i2c_hid_quirks[n].quirks;
+
+       return quirks;
+}
+
 static int __i2c_hid_command(struct i2c_client *client,
                const struct i2c_hid_cmd *command, u8 reportID,
                u8 reportType, u8 *args, int args_len,
@@ -343,11 +382,27 @@ static int i2c_hid_set_power(struct i2c_client *client, int power_state)
 
        i2c_hid_dbg(ihid, "%s\n", __func__);
 
+       /*
+        * Some devices require to send a command to wakeup before power on.
+        * The call will get a return value (EREMOTEIO) but device will be
+        * triggered and activated. After that, it goes like a normal device.
+        */
+       if (power_state == I2C_HID_PWR_ON &&
+           ihid->quirks & I2C_HID_QUIRK_SET_PWR_WAKEUP_DEV) {
+               ret = i2c_hid_command(client, &hid_set_power_cmd, NULL, 0);
+
+               /* Device was already activated */
+               if (!ret)
+                       goto set_pwr_exit;
+       }
+
        ret = __i2c_hid_command(client, &hid_set_power_cmd, power_state,
                0, NULL, 0, NULL, 0);
+
        if (ret)
                dev_err(&client->dev, "failed to change power setting.\n");
 
+set_pwr_exit:
        return ret;
 }
 
@@ -1032,6 +1087,8 @@ static int i2c_hid_probe(struct i2c_client *client,
                 client->name, hid->vendor, hid->product);
        strlcpy(hid->phys, dev_name(&client->dev), sizeof(hid->phys));
 
+       ihid->quirks = i2c_hid_lookup_quirk(hid->vendor, hid->product);
+
        ret = hid_add_device(hid);
        if (ret) {
                if (ret != -ENODEV)