Merge branches 'for-3.17/upstream', 'for-3.17/cp2112', 'for-3.17/huion', 'for-3.17...
authorJiri Kosina <jkosina@suse.cz>
Wed, 6 Aug 2014 09:09:53 +0000 (11:09 +0200)
committerJiri Kosina <jkosina@suse.cz>
Wed, 6 Aug 2014 09:09:53 +0000 (11:09 +0200)
18 files changed:
Documentation/ABI/testing/sysfs-driver-hid-lenovo [new file with mode: 0644]
Documentation/ABI/testing/sysfs-driver-hid-lenovo-tpkbd [deleted file]
drivers/hid/Kconfig
drivers/hid/Makefile
drivers/hid/hid-core.c
drivers/hid/hid-cp2112.c
drivers/hid/hid-huion.c
drivers/hid/hid-ids.h
drivers/hid/hid-lenovo-tpkbd.c [deleted file]
drivers/hid/hid-lenovo.c [new file with mode: 0644]
drivers/hid/hid-picolcd_debugfs.c
drivers/hid/hid-rmi.c
drivers/hid/hid-roccat-lua.c
drivers/hid/hid-sony.c
drivers/hid/i2c-hid/i2c-hid.c
drivers/hid/usbhid/hid-core.c
drivers/hid/usbhid/hid-quirks.c
include/linux/hid.h

diff --git a/Documentation/ABI/testing/sysfs-driver-hid-lenovo b/Documentation/ABI/testing/sysfs-driver-hid-lenovo
new file mode 100644 (file)
index 0000000..53a0725
--- /dev/null
@@ -0,0 +1,50 @@
+What:          /sys/bus/usb/devices/<busnum>-<devnum>:<config num>.<interface num>/<hid-bus>:<vendor-id>:<product-id>.<num>/press_to_select
+Date:          July 2011
+Contact:       linux-input@vger.kernel.org
+Description:   This controls if mouse clicks should be generated if the trackpoint is quickly pressed. How fast this press has to be
+               is being controlled by press_speed.
+               Values are 0 or 1.
+               Applies to Thinkpad USB Keyboard with TrackPoint.
+
+What:          /sys/bus/usb/devices/<busnum>-<devnum>:<config num>.<interface num>/<hid-bus>:<vendor-id>:<product-id>.<num>/dragging
+Date:          July 2011
+Contact:       linux-input@vger.kernel.org
+Description:   If this setting is enabled, it is possible to do dragging by pressing the trackpoint. This requires press_to_select to be enabled.
+               Values are 0 or 1.
+               Applies to Thinkpad USB Keyboard with TrackPoint.
+
+What:          /sys/bus/usb/devices/<busnum>-<devnum>:<config num>.<interface num>/<hid-bus>:<vendor-id>:<product-id>.<num>/release_to_select
+Date:          July 2011
+Contact:       linux-input@vger.kernel.org
+Description:   For details regarding this setting please refer to http://www.pc.ibm.com/ww/healthycomputing/trkpntb.html
+               Values are 0 or 1.
+               Applies to Thinkpad USB Keyboard with TrackPoint.
+
+What:          /sys/bus/usb/devices/<busnum>-<devnum>:<config num>.<interface num>/<hid-bus>:<vendor-id>:<product-id>.<num>/select_right
+Date:          July 2011
+Contact:       linux-input@vger.kernel.org
+Description:   This setting controls if the mouse click events generated by pressing the trackpoint (if press_to_select is enabled) generate
+               a left or right mouse button click.
+               Values are 0 or 1.
+               Applies to Thinkpad USB Keyboard with TrackPoint.
+
+What:          /sys/bus/usb/devices/<busnum>-<devnum>:<config num>.<interface num>/<hid-bus>:<vendor-id>:<product-id>.<num>/sensitivity
+Date:          July 2011
+Contact:       linux-input@vger.kernel.org
+Description:   This file contains the trackpoint sensitivity.
+               Values are decimal integers from 1 (lowest sensitivity) to 255 (highest sensitivity).
+               Applies to Thinkpad USB Keyboard with TrackPoint.
+
+What:          /sys/bus/usb/devices/<busnum>-<devnum>:<config num>.<interface num>/<hid-bus>:<vendor-id>:<product-id>.<num>/press_speed
+Date:          July 2011
+Contact:       linux-input@vger.kernel.org
+Description:   This setting controls how fast the trackpoint needs to be pressed to generate a mouse click if press_to_select is enabled.
+               Values are decimal integers from 1 (slowest) to 255 (fastest).
+               Applies to Thinkpad USB Keyboard with TrackPoint.
+
+What:          /sys/bus/usb/devices/<busnum>-<devnum>:<config num>.<interface num>/<hid-bus>:<vendor-id>:<product-id>.<num>/fn_lock
+Date:          July 2014
+Contact:       linux-input@vger.kernel.org
+Description:   This setting controls whether Fn Lock is enabled on the keyboard (i.e. if F1 is Mute or F1)
+               Values are 0 or 1
+               Applies to ThinkPad Compact (USB|Bluetooth) Keyboard with TrackPoint.
diff --git a/Documentation/ABI/testing/sysfs-driver-hid-lenovo-tpkbd b/Documentation/ABI/testing/sysfs-driver-hid-lenovo-tpkbd
deleted file mode 100644 (file)
index 57b92cb..0000000
+++ /dev/null
@@ -1,38 +0,0 @@
-What:          /sys/bus/usb/devices/<busnum>-<devnum>:<config num>.<interface num>/<hid-bus>:<vendor-id>:<product-id>.<num>/press_to_select
-Date:          July 2011
-Contact:       linux-input@vger.kernel.org
-Description:   This controls if mouse clicks should be generated if the trackpoint is quickly pressed. How fast this press has to be
-               is being controlled by press_speed.
-               Values are 0 or 1.
-
-What:          /sys/bus/usb/devices/<busnum>-<devnum>:<config num>.<interface num>/<hid-bus>:<vendor-id>:<product-id>.<num>/dragging
-Date:          July 2011
-Contact:       linux-input@vger.kernel.org
-Description:   If this setting is enabled, it is possible to do dragging by pressing the trackpoint. This requires press_to_select to be enabled.
-               Values are 0 or 1.
-
-What:          /sys/bus/usb/devices/<busnum>-<devnum>:<config num>.<interface num>/<hid-bus>:<vendor-id>:<product-id>.<num>/release_to_select
-Date:          July 2011
-Contact:       linux-input@vger.kernel.org
-Description:   For details regarding this setting please refer to http://www.pc.ibm.com/ww/healthycomputing/trkpntb.html
-               Values are 0 or 1.
-
-What:          /sys/bus/usb/devices/<busnum>-<devnum>:<config num>.<interface num>/<hid-bus>:<vendor-id>:<product-id>.<num>/select_right
-Date:          July 2011
-Contact:       linux-input@vger.kernel.org
-Description:   This setting controls if the mouse click events generated by pressing the trackpoint (if press_to_select is enabled) generate
-               a left or right mouse button click.
-               Values are 0 or 1.
-
-What:          /sys/bus/usb/devices/<busnum>-<devnum>:<config num>.<interface num>/<hid-bus>:<vendor-id>:<product-id>.<num>/sensitivity
-Date:          July 2011
-Contact:       linux-input@vger.kernel.org
-Description:   This file contains the trackpoint sensitivity.
-               Values are decimal integers from 1 (lowest sensitivity) to 255 (highest sensitivity).
-
-What:          /sys/bus/usb/devices/<busnum>-<devnum>:<config num>.<interface num>/<hid-bus>:<vendor-id>:<product-id>.<num>/press_speed
-Date:          July 2011
-Contact:       linux-input@vger.kernel.org
-Description:   This setting controls how fast the trackpoint needs to be pressed to generate a mouse click if press_to_select is enabled.
-               Values are decimal integers from 1 (slowest) to 255 (fastest).
-
index 5e79c6a..e02cf59 100644 (file)
@@ -331,18 +331,20 @@ config HID_LCPOWER
        ---help---
        Support for LC-Power RC1000MCE RF remote control.
 
-config HID_LENOVO_TPKBD
-       tristate "Lenovo ThinkPad USB Keyboard with TrackPoint"
+config HID_LENOVO
+       tristate "Lenovo / Thinkpad devices"
        depends on HID
        select NEW_LEDS
        select LEDS_CLASS
        ---help---
-       Support for the Lenovo ThinkPad USB Keyboard with TrackPoint.
+       Support for Lenovo devices that are not fully compliant with HID standard.
 
-       Say Y here if you have a Lenovo ThinkPad USB Keyboard with TrackPoint
-       and would like to use device-specific features like changing the
-       sensitivity of the trackpoint, using the microphone mute button or
-       controlling the mute and microphone mute LEDs.
+       Say Y if you want support for the non-compliant features of the Lenovo
+       Thinkpad standalone keyboards, e.g:
+       - ThinkPad USB Keyboard with TrackPoint (supports extra LEDs and trackpoint
+         configuration)
+       - ThinkPad Compact Bluetooth Keyboard with TrackPoint (supports Fn keys)
+       - ThinkPad Compact USB Keyboard with TrackPoint (supports Fn keys)
 
 config HID_LOGITECH
        tristate "Logitech devices" if EXPERT
@@ -785,7 +787,7 @@ config HID_XINMO
        depends on HID
        ---help---
        Support for Xin-Mo devices that are not fully compliant with the HID
-       standard. Currently only supports the Xin-Mo Dual Arcade. Say Y here
+       standard. Currently only supports the Xin-Mo Dual Arcade. Say Y here
        if you have a Xin-Mo Dual Arcade controller.
 
 config HID_ZEROPLUS
index a6fa6ba..5e96be3 100644 (file)
@@ -59,7 +59,7 @@ obj-$(CONFIG_HID_KENSINGTON)  += hid-kensington.o
 obj-$(CONFIG_HID_KEYTOUCH)     += hid-keytouch.o
 obj-$(CONFIG_HID_KYE)          += hid-kye.o
 obj-$(CONFIG_HID_LCPOWER)       += hid-lcpower.o
-obj-$(CONFIG_HID_LENOVO_TPKBD) += hid-lenovo-tpkbd.o
+obj-$(CONFIG_HID_LENOVO)       += hid-lenovo.o
 obj-$(CONFIG_HID_LOGITECH)     += hid-logitech.o
 obj-$(CONFIG_HID_LOGITECH_DJ)  += hid-logitech-dj.o
 obj-$(CONFIG_HID_MAGICMOUSE)    += hid-magicmouse.o
index 8ed66fd..6c813c6 100644 (file)
@@ -783,7 +783,9 @@ static int hid_scan_report(struct hid_device *hid)
        * Vendor specific handlings
        */
        if ((hid->vendor == USB_VENDOR_ID_SYNAPTICS) &&
-           (hid->group == HID_GROUP_GENERIC))
+           (hid->group == HID_GROUP_GENERIC) &&
+           /* only bind to the mouse interface of composite USB devices */
+           (hid->bus != BUS_USB || hid->type == HID_TYPE_USBMOUSE))
                /* hid-rmi should take care of them, not hid-generic */
                hid->group = HID_GROUP_RMI;
 
@@ -1782,7 +1784,7 @@ static const struct hid_device_id hid_have_special_driver[] = {
        { HID_USB_DEVICE(USB_VENDOR_ID_HOLTEK_ALT, USB_DEVICE_ID_HOLTEK_ALT_MOUSE_A070) },
        { HID_USB_DEVICE(USB_VENDOR_ID_HOLTEK_ALT, USB_DEVICE_ID_HOLTEK_ALT_MOUSE_A072) },
        { HID_USB_DEVICE(USB_VENDOR_ID_HOLTEK_ALT, USB_DEVICE_ID_HOLTEK_ALT_MOUSE_A081) },
-       { HID_USB_DEVICE(USB_VENDOR_ID_HUION, USB_DEVICE_ID_HUION_580) },
+       { HID_USB_DEVICE(USB_VENDOR_ID_HUION, USB_DEVICE_ID_HUION_TABLET) },
        { HID_USB_DEVICE(USB_VENDOR_ID_JESS2, USB_DEVICE_ID_JESS2_COLOR_RUMBLE_PAD) },
        { HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_ION, USB_DEVICE_ID_ICADE) },
        { HID_USB_DEVICE(USB_VENDOR_ID_KENSINGTON, USB_DEVICE_ID_KS_SLIMBLADE) },
@@ -1796,8 +1798,10 @@ static const struct hid_device_id hid_have_special_driver[] = {
        { HID_USB_DEVICE(USB_VENDOR_ID_KYE, USB_DEVICE_ID_KYE_EASYPEN_M610X) },
        { HID_USB_DEVICE(USB_VENDOR_ID_LABTEC, USB_DEVICE_ID_LABTEC_WIRELESS_KEYBOARD) },
        { HID_USB_DEVICE(USB_VENDOR_ID_LCPOWER, USB_DEVICE_ID_LCPOWER_LC1000 ) },
-#if IS_ENABLED(CONFIG_HID_LENOVO_TPKBD)
+#if IS_ENABLED(CONFIG_HID_LENOVO)
        { HID_USB_DEVICE(USB_VENDOR_ID_LENOVO, USB_DEVICE_ID_LENOVO_TPKBD) },
+       { HID_USB_DEVICE(USB_VENDOR_ID_LENOVO, USB_DEVICE_ID_LENOVO_CUSBKBD) },
+       { HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_LENOVO, USB_DEVICE_ID_LENOVO_CBTKBD) },
 #endif
        { HID_USB_DEVICE(USB_VENDOR_ID_LOGITECH, USB_DEVICE_ID_MX3000_RECEIVER) },
        { HID_USB_DEVICE(USB_VENDOR_ID_LOGITECH, USB_DEVICE_ID_S510_RECEIVER) },
@@ -2266,6 +2270,7 @@ static const struct hid_device_id hid_ignore_list[] = {
        { HID_USB_DEVICE(USB_VENDOR_ID_IMATION, USB_DEVICE_ID_DISC_STAKKA) },
        { HID_USB_DEVICE(USB_VENDOR_ID_JABRA, USB_DEVICE_ID_JABRA_SPEAK_410) },
        { HID_USB_DEVICE(USB_VENDOR_ID_JABRA, USB_DEVICE_ID_JABRA_SPEAK_510) },
+       { HID_USB_DEVICE(USB_VENDOR_ID_JABRA, USB_DEVICE_ID_JABRA_GN9350E) },
        { HID_USB_DEVICE(USB_VENDOR_ID_KBGEAR, USB_DEVICE_ID_KBGEAR_JAMSTUDIO) },
        { HID_USB_DEVICE(USB_VENDOR_ID_KWORLD, USB_DEVICE_ID_KWORLD_RADIO_FM700) },
        { HID_USB_DEVICE(USB_VENDOR_ID_KYE, USB_DEVICE_ID_KYE_GPEN_560) },
index 56be85a..a822db5 100644 (file)
@@ -240,8 +240,6 @@ static int cp2112_gpio_direction_output(struct gpio_chip *chip,
        u8 buf[5];
        int ret;
 
-       cp2112_gpio_set(chip, offset, value);
-
        ret = hid_hw_raw_request(hdev, CP2112_GPIO_CONFIG, buf,
                                       sizeof(buf), HID_FEATURE_REPORT,
                                       HID_REQ_GET_REPORT);
@@ -260,6 +258,12 @@ static int cp2112_gpio_direction_output(struct gpio_chip *chip,
                return ret;
        }
 
+       /*
+        * Set gpio value when output direction is already set,
+        * as specified in AN495, Rev. 0.2, cpt. 4.4
+        */
+       cp2112_gpio_set(chip, offset, value);
+
        return 0;
 }
 
@@ -425,6 +429,105 @@ static int cp2112_write_req(void *buf, u8 slave_address, u8 command, u8 *data,
        return data_length + 4;
 }
 
+static int cp2112_i2c_write_req(void *buf, u8 slave_address, u8 *data,
+                               u8 data_length)
+{
+       struct cp2112_write_req_report *report = buf;
+
+       if (data_length > sizeof(report->data))
+               return -EINVAL;
+
+       report->report = CP2112_DATA_WRITE_REQUEST;
+       report->slave_address = slave_address << 1;
+       report->length = data_length;
+       memcpy(report->data, data, data_length);
+       return data_length + 3;
+}
+
+static int cp2112_i2c_xfer(struct i2c_adapter *adap, struct i2c_msg *msgs,
+                          int num)
+{
+       struct cp2112_device *dev = (struct cp2112_device *)adap->algo_data;
+       struct hid_device *hdev = dev->hdev;
+       u8 buf[64];
+       ssize_t count;
+       unsigned int retries;
+       int ret;
+
+       hid_dbg(hdev, "I2C %d messages\n", num);
+
+       if (num != 1) {
+               hid_err(hdev,
+                       "Multi-message I2C transactions not supported\n");
+               return -EOPNOTSUPP;
+       }
+
+       if (msgs->flags & I2C_M_RD)
+               count = cp2112_read_req(buf, msgs->addr, msgs->len);
+       else
+               count = cp2112_i2c_write_req(buf, msgs->addr, msgs->buf,
+                                            msgs->len);
+
+       if (count < 0)
+               return count;
+
+       ret = hid_hw_power(hdev, PM_HINT_FULLON);
+       if (ret < 0) {
+               hid_err(hdev, "power management error: %d\n", ret);
+               return ret;
+       }
+
+       ret = cp2112_hid_output(hdev, buf, count, HID_OUTPUT_REPORT);
+       if (ret < 0) {
+               hid_warn(hdev, "Error starting transaction: %d\n", ret);
+               goto power_normal;
+       }
+
+       for (retries = 0; retries < XFER_STATUS_RETRIES; ++retries) {
+               ret = cp2112_xfer_status(dev);
+               if (-EBUSY == ret)
+                       continue;
+               if (ret < 0)
+                       goto power_normal;
+               break;
+       }
+
+       if (XFER_STATUS_RETRIES <= retries) {
+               hid_warn(hdev, "Transfer timed out, cancelling.\n");
+               buf[0] = CP2112_CANCEL_TRANSFER;
+               buf[1] = 0x01;
+
+               ret = cp2112_hid_output(hdev, buf, 2, HID_OUTPUT_REPORT);
+               if (ret < 0)
+                       hid_warn(hdev, "Error cancelling transaction: %d\n",
+                                ret);
+
+               ret = -ETIMEDOUT;
+               goto power_normal;
+       }
+
+       if (!(msgs->flags & I2C_M_RD))
+               goto finish;
+
+       ret = cp2112_read(dev, msgs->buf, msgs->len);
+       if (ret < 0)
+               goto power_normal;
+       if (ret != msgs->len) {
+               hid_warn(hdev, "short read: %d < %d\n", ret, msgs->len);
+               ret = -EIO;
+               goto power_normal;
+       }
+
+finish:
+       /* return the number of transferred messages */
+       ret = 1;
+
+power_normal:
+       hid_hw_power(hdev, PM_HINT_NORMAL);
+       hid_dbg(hdev, "I2C transfer finished: %d\n", ret);
+       return ret;
+}
+
 static int cp2112_xfer(struct i2c_adapter *adap, u16 addr,
                       unsigned short flags, char read_write, u8 command,
                       int size, union i2c_smbus_data *data)
@@ -591,7 +694,8 @@ power_normal:
 
 static u32 cp2112_functionality(struct i2c_adapter *adap)
 {
-       return I2C_FUNC_SMBUS_BYTE |
+       return I2C_FUNC_I2C |
+               I2C_FUNC_SMBUS_BYTE |
                I2C_FUNC_SMBUS_BYTE_DATA |
                I2C_FUNC_SMBUS_WORD_DATA |
                I2C_FUNC_SMBUS_BLOCK_DATA |
@@ -601,6 +705,7 @@ static u32 cp2112_functionality(struct i2c_adapter *adap)
 }
 
 static const struct i2c_algorithm smbus_algorithm = {
+       .master_xfer    = cp2112_i2c_xfer,
        .smbus_xfer     = cp2112_xfer,
        .functionality  = cp2112_functionality,
 };
index cbf4da4..60f44cd 100644 (file)
@@ -2,6 +2,7 @@
  *  HID driver for Huion devices not fully compliant with HID standard
  *
  *  Copyright (c) 2013 Martin Rusko
+ *  Copyright (c) 2014 Nikolai Kondrashov
  */
 
 /*
 #include <linux/hid.h>
 #include <linux/module.h>
 #include <linux/usb.h>
+#include <asm/unaligned.h>
 #include "usbhid/usbhid.h"
 
 #include "hid-ids.h"
 
-/* Original Huion 580 report descriptor size */
-#define HUION_580_RDESC_ORIG_SIZE      177
-
-/* Fixed Huion 580 report descriptor */
-static __u8 huion_580_rdesc_fixed[] = {
-       0x05, 0x0D,         /*  Usage Page (Digitizer),             */
-       0x09, 0x02,         /*  Usage (Pen),                        */
-       0xA1, 0x01,         /*  Collection (Application),           */
-       0x85, 0x07,         /*      Report ID (7),                  */
-       0x09, 0x20,         /*      Usage (Stylus),                 */
-       0xA0,               /*      Collection (Physical),          */
-       0x14,               /*          Logical Minimum (0),        */
-       0x25, 0x01,         /*          Logical Maximum (1),        */
-       0x75, 0x01,         /*          Report Size (1),            */
-       0x09, 0x42,         /*          Usage (Tip Switch),         */
-       0x09, 0x44,         /*          Usage (Barrel Switch),      */
-       0x09, 0x46,         /*          Usage (Tablet Pick),        */
-       0x95, 0x03,         /*          Report Count (3),           */
-       0x81, 0x02,         /*          Input (Variable),           */
-       0x95, 0x03,         /*          Report Count (3),           */
-       0x81, 0x03,         /*          Input (Constant, Variable), */
-       0x09, 0x32,         /*          Usage (In Range),           */
-       0x95, 0x01,         /*          Report Count (1),           */
-       0x81, 0x02,         /*          Input (Variable),           */
-       0x95, 0x01,         /*          Report Count (1),           */
-       0x81, 0x03,         /*          Input (Constant, Variable), */
-       0x75, 0x10,         /*          Report Size (16),           */
-       0x95, 0x01,         /*          Report Count (1),           */
-       0xA4,               /*          Push,                       */
-       0x05, 0x01,         /*          Usage Page (Desktop),       */
-       0x65, 0x13,         /*          Unit (Inch),                */
-       0x55, 0xFD,         /*          Unit Exponent (-3),         */
-       0x34,               /*          Physical Minimum (0),       */
-       0x09, 0x30,         /*          Usage (X),                  */
-       0x46, 0x40, 0x1F,   /*          Physical Maximum (8000),    */
-       0x26, 0x00, 0x7D,   /*          Logical Maximum (32000),    */
-       0x81, 0x02,         /*          Input (Variable),           */
-       0x09, 0x31,         /*          Usage (Y),                  */
-       0x46, 0x88, 0x13,   /*          Physical Maximum (5000),    */
-       0x26, 0x20, 0x4E,   /*          Logical Maximum (20000),    */
-       0x81, 0x02,         /*          Input (Variable),           */
-       0xB4,               /*          Pop,                        */
-       0x09, 0x30,         /*          Usage (Tip Pressure),       */
-       0x26, 0xFF, 0x07,   /*          Logical Maximum (2047),     */
-       0x81, 0x02,         /*          Input (Variable),           */
-       0xC0,               /*      End Collection,                 */
-       0xC0                /*  End Collection                      */
+/* Report descriptor template placeholder head */
+#define HUION_PH_HEAD  0xFE, 0xED, 0x1D
+
+/* Report descriptor template placeholder IDs */
+enum huion_ph_id {
+       HUION_PH_ID_X_LM,
+       HUION_PH_ID_X_PM,
+       HUION_PH_ID_Y_LM,
+       HUION_PH_ID_Y_PM,
+       HUION_PH_ID_PRESSURE_LM,
+       HUION_PH_ID_NUM
+};
+
+/* Report descriptor template placeholder */
+#define HUION_PH(_ID) HUION_PH_HEAD, HUION_PH_ID_##_ID
+
+/* Fixed report descriptor template */
+static const __u8 huion_tablet_rdesc_template[] = {
+       0x05, 0x0D,             /*  Usage Page (Digitizer),                 */
+       0x09, 0x02,             /*  Usage (Pen),                            */
+       0xA1, 0x01,             /*  Collection (Application),               */
+       0x85, 0x07,             /*      Report ID (7),                      */
+       0x09, 0x20,             /*      Usage (Stylus),                     */
+       0xA0,                   /*      Collection (Physical),              */
+       0x14,                   /*          Logical Minimum (0),            */
+       0x25, 0x01,             /*          Logical Maximum (1),            */
+       0x75, 0x01,             /*          Report Size (1),                */
+       0x09, 0x42,             /*          Usage (Tip Switch),             */
+       0x09, 0x44,             /*          Usage (Barrel Switch),          */
+       0x09, 0x46,             /*          Usage (Tablet Pick),            */
+       0x95, 0x03,             /*          Report Count (3),               */
+       0x81, 0x02,             /*          Input (Variable),               */
+       0x95, 0x03,             /*          Report Count (3),               */
+       0x81, 0x03,             /*          Input (Constant, Variable),     */
+       0x09, 0x32,             /*          Usage (In Range),               */
+       0x95, 0x01,             /*          Report Count (1),               */
+       0x81, 0x02,             /*          Input (Variable),               */
+       0x95, 0x01,             /*          Report Count (1),               */
+       0x81, 0x03,             /*          Input (Constant, Variable),     */
+       0x75, 0x10,             /*          Report Size (16),               */
+       0x95, 0x01,             /*          Report Count (1),               */
+       0xA4,                   /*          Push,                           */
+       0x05, 0x01,             /*          Usage Page (Desktop),           */
+       0x65, 0x13,             /*          Unit (Inch),                    */
+       0x55, 0xFD,             /*          Unit Exponent (-3),             */
+       0x34,                   /*          Physical Minimum (0),           */
+       0x09, 0x30,             /*          Usage (X),                      */
+       0x27, HUION_PH(X_LM),   /*          Logical Maximum (PLACEHOLDER),  */
+       0x47, HUION_PH(X_PM),   /*          Physical Maximum (PLACEHOLDER), */
+       0x81, 0x02,             /*          Input (Variable),               */
+       0x09, 0x31,             /*          Usage (Y),                      */
+       0x27, HUION_PH(Y_LM),   /*          Logical Maximum (PLACEHOLDER),  */
+       0x47, HUION_PH(Y_PM),   /*          Physical Maximum (PLACEHOLDER), */
+       0x81, 0x02,             /*          Input (Variable),               */
+       0xB4,                   /*          Pop,                            */
+       0x09, 0x30,             /*          Usage (Tip Pressure),           */
+       0x27,
+       HUION_PH(PRESSURE_LM),  /*          Logical Maximum (PLACEHOLDER),  */
+       0x81, 0x02,             /*          Input (Variable),               */
+       0xC0,                   /*      End Collection,                     */
+       0xC0                    /*  End Collection                          */
+};
+
+/* Driver data */
+struct huion_drvdata {
+       __u8 *rdesc;
+       unsigned int rsize;
 };
 
 static __u8 *huion_report_fixup(struct hid_device *hdev, __u8 *rdesc,
                unsigned int *rsize)
 {
+       struct huion_drvdata *drvdata = hid_get_drvdata(hdev);
        switch (hdev->product) {
-       case USB_DEVICE_ID_HUION_580:
-               if (*rsize == HUION_580_RDESC_ORIG_SIZE) {
-                       rdesc = huion_580_rdesc_fixed;
-                       *rsize = sizeof(huion_580_rdesc_fixed);
+       case USB_DEVICE_ID_HUION_TABLET:
+               if (drvdata->rdesc != NULL) {
+                       rdesc = drvdata->rdesc;
+                       *rsize = drvdata->rsize;
                }
                break;
        }
@@ -83,82 +106,144 @@ static __u8 *huion_report_fixup(struct hid_device *hdev, __u8 *rdesc,
 }
 
 /**
- * Enable fully-functional tablet mode by reading special string
- * descriptor.
+ * Enable fully-functional tablet mode and determine device parameters.
  *
  * @hdev:      HID device
- *
- * The specific string descriptor and data were discovered by sniffing
- * the Windows driver traffic.
  */
 static int huion_tablet_enable(struct hid_device *hdev)
 {
        int rc;
-       char buf[22];
+       struct usb_device *usb_dev = hid_to_usb_dev(hdev);
+       struct huion_drvdata *drvdata = hid_get_drvdata(hdev);
+       __le16 buf[6];
 
-       rc = usb_string(hid_to_usb_dev(hdev), 0x64, buf, sizeof(buf));
-       if (rc < 0)
-               return rc;
+       /*
+        * Read string descriptor containing tablet parameters. The specific
+        * string descriptor and data were discovered by sniffing the Windows
+        * driver traffic.
+        * NOTE: This enables fully-functional tablet mode.
+        */
+       rc = usb_control_msg(usb_dev, usb_rcvctrlpipe(usb_dev, 0),
+                               USB_REQ_GET_DESCRIPTOR, USB_DIR_IN,
+                               (USB_DT_STRING << 8) + 0x64,
+                               0x0409, buf, sizeof(buf),
+                               USB_CTRL_GET_TIMEOUT);
+       if (rc == -EPIPE)
+               hid_warn(hdev, "device parameters not found\n");
+       else if (rc < 0)
+               hid_warn(hdev, "failed to get device parameters: %d\n", rc);
+       else if (rc != sizeof(buf))
+               hid_warn(hdev, "invalid device parameters\n");
+       else {
+               s32 params[HUION_PH_ID_NUM];
+               s32 resolution;
+               __u8 *p;
+               s32 v;
+
+               /* Extract device parameters */
+               params[HUION_PH_ID_X_LM] = le16_to_cpu(buf[1]);
+               params[HUION_PH_ID_Y_LM] = le16_to_cpu(buf[2]);
+               params[HUION_PH_ID_PRESSURE_LM] = le16_to_cpu(buf[4]);
+               resolution = le16_to_cpu(buf[5]);
+               if (resolution == 0) {
+                       params[HUION_PH_ID_X_PM] = 0;
+                       params[HUION_PH_ID_Y_PM] = 0;
+               } else {
+                       params[HUION_PH_ID_X_PM] = params[HUION_PH_ID_X_LM] *
+                                                       1000 / resolution;
+                       params[HUION_PH_ID_Y_PM] = params[HUION_PH_ID_Y_LM] *
+                                                       1000 / resolution;
+               }
+
+               /* Allocate fixed report descriptor */
+               drvdata->rdesc = devm_kmalloc(&hdev->dev,
+                                       sizeof(huion_tablet_rdesc_template),
+                                       GFP_KERNEL);
+               if (drvdata->rdesc == NULL) {
+                       hid_err(hdev, "failed to allocate fixed rdesc\n");
+                       return -ENOMEM;
+               }
+               drvdata->rsize = sizeof(huion_tablet_rdesc_template);
+
+               /* Format fixed report descriptor */
+               memcpy(drvdata->rdesc, huion_tablet_rdesc_template,
+                       drvdata->rsize);
+               for (p = drvdata->rdesc;
+                    p <= drvdata->rdesc + drvdata->rsize - 4;) {
+                       if (p[0] == 0xFE && p[1] == 0xED && p[2] == 0x1D &&
+                           p[3] < sizeof(params)) {
+                               v = params[p[3]];
+                               put_unaligned(cpu_to_le32(v), (s32 *)p);
+                               p += 4;
+                       } else {
+                               p++;
+                       }
+               }
+       }
 
        return 0;
 }
 
 static int huion_probe(struct hid_device *hdev, const struct hid_device_id *id)
 {
-       int ret;
+       int rc;
        struct usb_interface *intf = to_usb_interface(hdev->dev.parent);
+       struct huion_drvdata *drvdata;
+
+       /* Allocate and assign driver data */
+       drvdata = devm_kzalloc(&hdev->dev, sizeof(*drvdata), GFP_KERNEL);
+       if (drvdata == NULL) {
+               hid_err(hdev, "failed to allocate driver data\n");
+               return -ENOMEM;
+       }
+       hid_set_drvdata(hdev, drvdata);
 
-       /* Ignore interfaces 1 (mouse) and 2 (keyboard) for Huion 580 tablet,
-        * as they are not used
-        */
        switch (id->product) {
-       case USB_DEVICE_ID_HUION_580:
-               if (intf->cur_altsetting->desc.bInterfaceNumber != 0x00)
-                       return -ENODEV;
+       case USB_DEVICE_ID_HUION_TABLET:
+               /* If this is the pen interface */
+               if (intf->cur_altsetting->desc.bInterfaceNumber == 0) {
+                       rc = huion_tablet_enable(hdev);
+                       if (rc) {
+                               hid_err(hdev, "tablet enabling failed\n");
+                               return rc;
+                       }
+               }
                break;
        }
 
-       ret = hid_parse(hdev);
-       if (ret) {
+       rc = hid_parse(hdev);
+       if (rc) {
                hid_err(hdev, "parse failed\n");
-               goto err;
+               return rc;
        }
 
-       ret = hid_hw_start(hdev, HID_CONNECT_DEFAULT);
-       if (ret) {
+       rc = hid_hw_start(hdev, HID_CONNECT_DEFAULT);
+       if (rc) {
                hid_err(hdev, "hw start failed\n");
-               goto err;
-       }
-
-       switch (id->product) {
-       case USB_DEVICE_ID_HUION_580:
-               ret = huion_tablet_enable(hdev);
-               if (ret) {
-                       hid_err(hdev, "tablet enabling failed\n");
-                       goto enabling_err;
-               }
-               break;
+               return rc;
        }
 
        return 0;
-enabling_err:
-       hid_hw_stop(hdev);
-err:
-       return ret;
 }
 
 static int huion_raw_event(struct hid_device *hdev, struct hid_report *report,
                        u8 *data, int size)
 {
-       /* If this is a pen input report then invert the in-range bit */
-       if (report->type == HID_INPUT_REPORT && report->id == 0x07 && size >= 2)
+       struct usb_interface *intf = to_usb_interface(hdev->dev.parent);
+
+       /* If this is a pen input report */
+       if (intf->cur_altsetting->desc.bInterfaceNumber == 0 &&
+           report->type == HID_INPUT_REPORT &&
+           report->id == 0x07 && size >= 2)
+               /* Invert the in-range bit */
                data[1] ^= 0x40;
 
        return 0;
 }
 
 static const struct hid_device_id huion_devices[] = {
-       { HID_USB_DEVICE(USB_VENDOR_ID_HUION, USB_DEVICE_ID_HUION_580) },
+       { HID_USB_DEVICE(USB_VENDOR_ID_HUION, USB_DEVICE_ID_HUION_TABLET) },
+       { HID_USB_DEVICE(USB_VENDOR_ID_UCLOGIC, USB_DEVICE_ID_HUION_TABLET) },
        { }
 };
 MODULE_DEVICE_TABLE(hid, huion_devices);
index 48b66bb..d53bdda 100644 (file)
 #define USB_DEVICE_ID_UGCI_FIGHTING    0x0030
 
 #define USB_VENDOR_ID_HUION            0x256c
-#define USB_DEVICE_ID_HUION_580                0x006e
+#define USB_DEVICE_ID_HUION_TABLET     0x006e
 
 #define USB_VENDOR_ID_IDEACOM          0x1cb6
 #define USB_DEVICE_ID_IDEACOM_IDC6650  0x6650
 #define USB_DEVICE_ID_HOLTEK_ALT_MOUSE_A070    0xa070
 #define USB_DEVICE_ID_HOLTEK_ALT_MOUSE_A072    0xa072
 #define USB_DEVICE_ID_HOLTEK_ALT_MOUSE_A081    0xa081
+#define USB_DEVICE_ID_HOLTEK_ALT_KEYBOARD_A096 0xa096
 
 #define USB_VENDOR_ID_IMATION          0x0718
 #define USB_DEVICE_ID_DISC_STAKKA      0xd000
 #define USB_VENDOR_ID_JABRA            0x0b0e
 #define USB_DEVICE_ID_JABRA_SPEAK_410  0x0412
 #define USB_DEVICE_ID_JABRA_SPEAK_510  0x0420
+#define USB_DEVICE_ID_JABRA_GN9350E    0x9350
 
 #define USB_VENDOR_ID_JESS             0x0c45
 #define USB_DEVICE_ID_JESS_YUREX       0x1010
 
 #define USB_VENDOR_ID_LENOVO           0x17ef
 #define USB_DEVICE_ID_LENOVO_TPKBD     0x6009
+#define USB_DEVICE_ID_LENOVO_CUSBKBD   0x6047
+#define USB_DEVICE_ID_LENOVO_CBTKBD    0x6048
 
 #define USB_VENDOR_ID_LG               0x1fd2
 #define USB_DEVICE_ID_LG_MULTITOUCH    0x0064
diff --git a/drivers/hid/hid-lenovo-tpkbd.c b/drivers/hid/hid-lenovo-tpkbd.c
deleted file mode 100644 (file)
index 2d25b6c..0000000
+++ /dev/null
@@ -1,462 +0,0 @@
-/*
- *  HID driver for Lenovo ThinkPad USB Keyboard with TrackPoint
- *
- *  Copyright (c) 2012 Bernhard Seibold
- */
-
-/*
- * This program is free software; you can redistribute it and/or modify it
- * under the terms of the GNU General Public License as published by the Free
- * Software Foundation; either version 2 of the License, or (at your option)
- * any later version.
- */
-
-#include <linux/module.h>
-#include <linux/sysfs.h>
-#include <linux/device.h>
-#include <linux/hid.h>
-#include <linux/input.h>
-#include <linux/leds.h>
-
-#include "hid-ids.h"
-
-/* This is only used for the trackpoint part of the driver, hence _tp */
-struct tpkbd_data_pointer {
-       int led_state;
-       struct led_classdev led_mute;
-       struct led_classdev led_micmute;
-       int press_to_select;
-       int dragging;
-       int release_to_select;
-       int select_right;
-       int sensitivity;
-       int press_speed;
-};
-
-#define map_key_clear(c) hid_map_usage_clear(hi, usage, bit, max, EV_KEY, (c))
-
-static int tpkbd_input_mapping(struct hid_device *hdev,
-               struct hid_input *hi, struct hid_field *field,
-               struct hid_usage *usage, unsigned long **bit, int *max)
-{
-       if (usage->hid == (HID_UP_BUTTON | 0x0010)) {
-               /* mark the device as pointer */
-               hid_set_drvdata(hdev, (void *)1);
-               map_key_clear(KEY_MICMUTE);
-               return 1;
-       }
-       return 0;
-}
-
-#undef map_key_clear
-
-static int tpkbd_features_set(struct hid_device *hdev)
-{
-       struct hid_report *report;
-       struct tpkbd_data_pointer *data_pointer = hid_get_drvdata(hdev);
-
-       report = hdev->report_enum[HID_FEATURE_REPORT].report_id_hash[4];
-
-       report->field[0]->value[0]  = data_pointer->press_to_select   ? 0x01 : 0x02;
-       report->field[0]->value[0] |= data_pointer->dragging          ? 0x04 : 0x08;
-       report->field[0]->value[0] |= data_pointer->release_to_select ? 0x10 : 0x20;
-       report->field[0]->value[0] |= data_pointer->select_right      ? 0x80 : 0x40;
-       report->field[1]->value[0] = 0x03; // unknown setting, imitate windows driver
-       report->field[2]->value[0] = data_pointer->sensitivity;
-       report->field[3]->value[0] = data_pointer->press_speed;
-
-       hid_hw_request(hdev, report, HID_REQ_SET_REPORT);
-       return 0;
-}
-
-static ssize_t pointer_press_to_select_show(struct device *dev,
-               struct device_attribute *attr,
-               char *buf)
-{
-       struct hid_device *hdev = container_of(dev, struct hid_device, dev);
-       struct tpkbd_data_pointer *data_pointer = hid_get_drvdata(hdev);
-
-       return snprintf(buf, PAGE_SIZE, "%u\n", data_pointer->press_to_select);
-}
-
-static ssize_t pointer_press_to_select_store(struct device *dev,
-               struct device_attribute *attr,
-               const char *buf,
-               size_t count)
-{
-       struct hid_device *hdev = container_of(dev, struct hid_device, dev);
-       struct tpkbd_data_pointer *data_pointer = hid_get_drvdata(hdev);
-       int value;
-
-       if (kstrtoint(buf, 10, &value))
-               return -EINVAL;
-       if (value < 0 || value > 1)
-               return -EINVAL;
-
-       data_pointer->press_to_select = value;
-       tpkbd_features_set(hdev);
-
-       return count;
-}
-
-static ssize_t pointer_dragging_show(struct device *dev,
-               struct device_attribute *attr,
-               char *buf)
-{
-       struct hid_device *hdev = container_of(dev, struct hid_device, dev);
-       struct tpkbd_data_pointer *data_pointer = hid_get_drvdata(hdev);
-
-       return snprintf(buf, PAGE_SIZE, "%u\n", data_pointer->dragging);
-}
-
-static ssize_t pointer_dragging_store(struct device *dev,
-               struct device_attribute *attr,
-               const char *buf,
-               size_t count)
-{
-       struct hid_device *hdev = container_of(dev, struct hid_device, dev);
-       struct tpkbd_data_pointer *data_pointer = hid_get_drvdata(hdev);
-       int value;
-
-       if (kstrtoint(buf, 10, &value))
-               return -EINVAL;
-       if (value < 0 || value > 1)
-               return -EINVAL;
-
-       data_pointer->dragging = value;
-       tpkbd_features_set(hdev);
-
-       return count;
-}
-
-static ssize_t pointer_release_to_select_show(struct device *dev,
-               struct device_attribute *attr,
-               char *buf)
-{
-       struct hid_device *hdev = container_of(dev, struct hid_device, dev);
-       struct tpkbd_data_pointer *data_pointer = hid_get_drvdata(hdev);
-
-       return snprintf(buf, PAGE_SIZE, "%u\n", data_pointer->release_to_select);
-}
-
-static ssize_t pointer_release_to_select_store(struct device *dev,
-               struct device_attribute *attr,
-               const char *buf,
-               size_t count)
-{
-       struct hid_device *hdev = container_of(dev, struct hid_device, dev);
-       struct tpkbd_data_pointer *data_pointer = hid_get_drvdata(hdev);
-       int value;
-
-       if (kstrtoint(buf, 10, &value))
-               return -EINVAL;
-       if (value < 0 || value > 1)
-               return -EINVAL;
-
-       data_pointer->release_to_select = value;
-       tpkbd_features_set(hdev);
-
-       return count;
-}
-
-static ssize_t pointer_select_right_show(struct device *dev,
-               struct device_attribute *attr,
-               char *buf)
-{
-       struct hid_device *hdev = container_of(dev, struct hid_device, dev);
-       struct tpkbd_data_pointer *data_pointer = hid_get_drvdata(hdev);
-
-       return snprintf(buf, PAGE_SIZE, "%u\n", data_pointer->select_right);
-}
-
-static ssize_t pointer_select_right_store(struct device *dev,
-               struct device_attribute *attr,
-               const char *buf,
-               size_t count)
-{
-       struct hid_device *hdev = container_of(dev, struct hid_device, dev);
-       struct tpkbd_data_pointer *data_pointer = hid_get_drvdata(hdev);
-       int value;
-
-       if (kstrtoint(buf, 10, &value))
-               return -EINVAL;
-       if (value < 0 || value > 1)
-               return -EINVAL;
-
-       data_pointer->select_right = value;
-       tpkbd_features_set(hdev);
-
-       return count;
-}
-
-static ssize_t pointer_sensitivity_show(struct device *dev,
-               struct device_attribute *attr,
-               char *buf)
-{
-       struct hid_device *hdev = container_of(dev, struct hid_device, dev);
-       struct tpkbd_data_pointer *data_pointer = hid_get_drvdata(hdev);
-
-       return snprintf(buf, PAGE_SIZE, "%u\n",
-               data_pointer->sensitivity);
-}
-
-static ssize_t pointer_sensitivity_store(struct device *dev,
-               struct device_attribute *attr,
-               const char *buf,
-               size_t count)
-{
-       struct hid_device *hdev = container_of(dev, struct hid_device, dev);
-       struct tpkbd_data_pointer *data_pointer = hid_get_drvdata(hdev);
-       int value;
-
-       if (kstrtoint(buf, 10, &value) || value < 1 || value > 255)
-               return -EINVAL;
-
-       data_pointer->sensitivity = value;
-       tpkbd_features_set(hdev);
-
-       return count;
-}
-
-static ssize_t pointer_press_speed_show(struct device *dev,
-               struct device_attribute *attr,
-               char *buf)
-{
-       struct hid_device *hdev = container_of(dev, struct hid_device, dev);
-       struct tpkbd_data_pointer *data_pointer = hid_get_drvdata(hdev);
-
-       return snprintf(buf, PAGE_SIZE, "%u\n",
-               data_pointer->press_speed);
-}
-
-static ssize_t pointer_press_speed_store(struct device *dev,
-               struct device_attribute *attr,
-               const char *buf,
-               size_t count)
-{
-       struct hid_device *hdev = container_of(dev, struct hid_device, dev);
-       struct tpkbd_data_pointer *data_pointer = hid_get_drvdata(hdev);
-       int value;
-
-       if (kstrtoint(buf, 10, &value) || value < 1 || value > 255)
-               return -EINVAL;
-
-       data_pointer->press_speed = value;
-       tpkbd_features_set(hdev);
-
-       return count;
-}
-
-static struct device_attribute dev_attr_pointer_press_to_select =
-       __ATTR(press_to_select, S_IWUSR | S_IRUGO,
-                       pointer_press_to_select_show,
-                       pointer_press_to_select_store);
-
-static struct device_attribute dev_attr_pointer_dragging =
-       __ATTR(dragging, S_IWUSR | S_IRUGO,
-                       pointer_dragging_show,
-                       pointer_dragging_store);
-
-static struct device_attribute dev_attr_pointer_release_to_select =
-       __ATTR(release_to_select, S_IWUSR | S_IRUGO,
-                       pointer_release_to_select_show,
-                       pointer_release_to_select_store);
-
-static struct device_attribute dev_attr_pointer_select_right =
-       __ATTR(select_right, S_IWUSR | S_IRUGO,
-                       pointer_select_right_show,
-                       pointer_select_right_store);
-
-static struct device_attribute dev_attr_pointer_sensitivity =
-       __ATTR(sensitivity, S_IWUSR | S_IRUGO,
-                       pointer_sensitivity_show,
-                       pointer_sensitivity_store);
-
-static struct device_attribute dev_attr_pointer_press_speed =
-       __ATTR(press_speed, S_IWUSR | S_IRUGO,
-                       pointer_press_speed_show,
-                       pointer_press_speed_store);
-
-static struct attribute *tpkbd_attributes_pointer[] = {
-       &dev_attr_pointer_press_to_select.attr,
-       &dev_attr_pointer_dragging.attr,
-       &dev_attr_pointer_release_to_select.attr,
-       &dev_attr_pointer_select_right.attr,
-       &dev_attr_pointer_sensitivity.attr,
-       &dev_attr_pointer_press_speed.attr,
-       NULL
-};
-
-static const struct attribute_group tpkbd_attr_group_pointer = {
-       .attrs = tpkbd_attributes_pointer,
-};
-
-static enum led_brightness tpkbd_led_brightness_get(
-                       struct led_classdev *led_cdev)
-{
-       struct device *dev = led_cdev->dev->parent;
-       struct hid_device *hdev = container_of(dev, struct hid_device, dev);
-       struct tpkbd_data_pointer *data_pointer = hid_get_drvdata(hdev);
-       int led_nr = 0;
-
-       if (led_cdev == &data_pointer->led_micmute)
-               led_nr = 1;
-
-       return data_pointer->led_state & (1 << led_nr)
-                               ? LED_FULL
-                               : LED_OFF;
-}
-
-static void tpkbd_led_brightness_set(struct led_classdev *led_cdev,
-                       enum led_brightness value)
-{
-       struct device *dev = led_cdev->dev->parent;
-       struct hid_device *hdev = container_of(dev, struct hid_device, dev);
-       struct tpkbd_data_pointer *data_pointer = hid_get_drvdata(hdev);
-       struct hid_report *report;
-       int led_nr = 0;
-
-       if (led_cdev == &data_pointer->led_micmute)
-               led_nr = 1;
-
-       if (value == LED_OFF)
-               data_pointer->led_state &= ~(1 << led_nr);
-       else
-               data_pointer->led_state |= 1 << led_nr;
-
-       report = hdev->report_enum[HID_OUTPUT_REPORT].report_id_hash[3];
-       report->field[0]->value[0] = (data_pointer->led_state >> 0) & 1;
-       report->field[0]->value[1] = (data_pointer->led_state >> 1) & 1;
-       hid_hw_request(hdev, report, HID_REQ_SET_REPORT);
-}
-
-static int tpkbd_probe_tp(struct hid_device *hdev)
-{
-       struct device *dev = &hdev->dev;
-       struct tpkbd_data_pointer *data_pointer;
-       size_t name_sz = strlen(dev_name(dev)) + 16;
-       char *name_mute, *name_micmute;
-       int i;
-
-       /* Validate required reports. */
-       for (i = 0; i < 4; i++) {
-               if (!hid_validate_values(hdev, HID_FEATURE_REPORT, 4, i, 1))
-                       return -ENODEV;
-       }
-       if (!hid_validate_values(hdev, HID_OUTPUT_REPORT, 3, 0, 2))
-               return -ENODEV;
-
-       if (sysfs_create_group(&hdev->dev.kobj,
-                               &tpkbd_attr_group_pointer)) {
-               hid_warn(hdev, "Could not create sysfs group\n");
-       }
-
-       data_pointer = devm_kzalloc(&hdev->dev,
-                                   sizeof(struct tpkbd_data_pointer),
-                                   GFP_KERNEL);
-       if (data_pointer == NULL) {
-               hid_err(hdev, "Could not allocate memory for driver data\n");
-               return -ENOMEM;
-       }
-
-       // set same default values as windows driver
-       data_pointer->sensitivity = 0xa0;
-       data_pointer->press_speed = 0x38;
-
-       name_mute = devm_kzalloc(&hdev->dev, name_sz, GFP_KERNEL);
-       name_micmute = devm_kzalloc(&hdev->dev, name_sz, GFP_KERNEL);
-       if (name_mute == NULL || name_micmute == NULL) {
-               hid_err(hdev, "Could not allocate memory for led data\n");
-               return -ENOMEM;
-       }
-       snprintf(name_mute, name_sz, "%s:amber:mute", dev_name(dev));
-       snprintf(name_micmute, name_sz, "%s:amber:micmute", dev_name(dev));
-
-       hid_set_drvdata(hdev, data_pointer);
-
-       data_pointer->led_mute.name = name_mute;
-       data_pointer->led_mute.brightness_get = tpkbd_led_brightness_get;
-       data_pointer->led_mute.brightness_set = tpkbd_led_brightness_set;
-       data_pointer->led_mute.dev = dev;
-       led_classdev_register(dev, &data_pointer->led_mute);
-
-       data_pointer->led_micmute.name = name_micmute;
-       data_pointer->led_micmute.brightness_get = tpkbd_led_brightness_get;
-       data_pointer->led_micmute.brightness_set = tpkbd_led_brightness_set;
-       data_pointer->led_micmute.dev = dev;
-       led_classdev_register(dev, &data_pointer->led_micmute);
-
-       tpkbd_features_set(hdev);
-
-       return 0;
-}
-
-static int tpkbd_probe(struct hid_device *hdev,
-               const struct hid_device_id *id)
-{
-       int ret;
-
-       ret = hid_parse(hdev);
-       if (ret) {
-               hid_err(hdev, "hid_parse failed\n");
-               goto err;
-       }
-
-       ret = hid_hw_start(hdev, HID_CONNECT_DEFAULT);
-       if (ret) {
-               hid_err(hdev, "hid_hw_start failed\n");
-               goto err;
-       }
-
-       if (hid_get_drvdata(hdev)) {
-               hid_set_drvdata(hdev, NULL);
-               ret = tpkbd_probe_tp(hdev);
-               if (ret)
-                       goto err_hid;
-       }
-
-       return 0;
-err_hid:
-       hid_hw_stop(hdev);
-err:
-       return ret;
-}
-
-static void tpkbd_remove_tp(struct hid_device *hdev)
-{
-       struct tpkbd_data_pointer *data_pointer = hid_get_drvdata(hdev);
-
-       sysfs_remove_group(&hdev->dev.kobj,
-                       &tpkbd_attr_group_pointer);
-
-       led_classdev_unregister(&data_pointer->led_micmute);
-       led_classdev_unregister(&data_pointer->led_mute);
-
-       hid_set_drvdata(hdev, NULL);
-}
-
-static void tpkbd_remove(struct hid_device *hdev)
-{
-       if (hid_get_drvdata(hdev))
-               tpkbd_remove_tp(hdev);
-
-       hid_hw_stop(hdev);
-}
-
-static const struct hid_device_id tpkbd_devices[] = {
-       { HID_USB_DEVICE(USB_VENDOR_ID_LENOVO, USB_DEVICE_ID_LENOVO_TPKBD) },
-       { }
-};
-
-MODULE_DEVICE_TABLE(hid, tpkbd_devices);
-
-static struct hid_driver tpkbd_driver = {
-       .name = "lenovo_tpkbd",
-       .id_table = tpkbd_devices,
-       .input_mapping = tpkbd_input_mapping,
-       .probe = tpkbd_probe,
-       .remove = tpkbd_remove,
-};
-module_hid_driver(tpkbd_driver);
-
-MODULE_LICENSE("GPL");
diff --git a/drivers/hid/hid-lenovo.c b/drivers/hid/hid-lenovo.c
new file mode 100644 (file)
index 0000000..bf227f7
--- /dev/null
@@ -0,0 +1,708 @@
+/*
+ *  HID driver for Lenovo:
+ *  - ThinkPad USB Keyboard with TrackPoint (tpkbd)
+ *  - ThinkPad Compact Bluetooth Keyboard with TrackPoint (cptkbd)
+ *  - ThinkPad Compact USB Keyboard with TrackPoint (cptkbd)
+ *
+ *  Copyright (c) 2012 Bernhard Seibold
+ *  Copyright (c) 2014 Jamie Lentin <jm@lentin.co.uk>
+ */
+
+/*
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
+ * any later version.
+ */
+
+#include <linux/module.h>
+#include <linux/sysfs.h>
+#include <linux/device.h>
+#include <linux/hid.h>
+#include <linux/input.h>
+#include <linux/leds.h>
+
+#include "hid-ids.h"
+
+struct lenovo_drvdata_tpkbd {
+       int led_state;
+       struct led_classdev led_mute;
+       struct led_classdev led_micmute;
+       int press_to_select;
+       int dragging;
+       int release_to_select;
+       int select_right;
+       int sensitivity;
+       int press_speed;
+};
+
+struct lenovo_drvdata_cptkbd {
+       bool fn_lock;
+};
+
+#define map_key_clear(c) hid_map_usage_clear(hi, usage, bit, max, EV_KEY, (c))
+
+static int lenovo_input_mapping_tpkbd(struct hid_device *hdev,
+               struct hid_input *hi, struct hid_field *field,
+               struct hid_usage *usage, unsigned long **bit, int *max)
+{
+       if (usage->hid == (HID_UP_BUTTON | 0x0010)) {
+               /* This sub-device contains trackpoint, mark it */
+               hid_set_drvdata(hdev, (void *)1);
+               map_key_clear(KEY_MICMUTE);
+               return 1;
+       }
+       return 0;
+}
+
+static int lenovo_input_mapping_cptkbd(struct hid_device *hdev,
+               struct hid_input *hi, struct hid_field *field,
+               struct hid_usage *usage, unsigned long **bit, int *max)
+{
+       /* HID_UP_LNVENDOR = USB, HID_UP_MSVENDOR = BT */
+       if ((usage->hid & HID_USAGE_PAGE) == HID_UP_MSVENDOR ||
+           (usage->hid & HID_USAGE_PAGE) == HID_UP_LNVENDOR) {
+               set_bit(EV_REP, hi->input->evbit);
+               switch (usage->hid & HID_USAGE) {
+               case 0x00f1: /* Fn-F4: Mic mute */
+                       map_key_clear(KEY_MICMUTE);
+                       return 1;
+               case 0x00f2: /* Fn-F5: Brightness down */
+                       map_key_clear(KEY_BRIGHTNESSDOWN);
+                       return 1;
+               case 0x00f3: /* Fn-F6: Brightness up */
+                       map_key_clear(KEY_BRIGHTNESSUP);
+                       return 1;
+               case 0x00f4: /* Fn-F7: External display (projector) */
+                       map_key_clear(KEY_SWITCHVIDEOMODE);
+                       return 1;
+               case 0x00f5: /* Fn-F8: Wireless */
+                       map_key_clear(KEY_WLAN);
+                       return 1;
+               case 0x00f6: /* Fn-F9: Control panel */
+                       map_key_clear(KEY_CONFIG);
+                       return 1;
+               case 0x00f8: /* Fn-F11: View open applications (3 boxes) */
+                       map_key_clear(KEY_SCALE);
+                       return 1;
+               case 0x00fa: /* Fn-Esc: Fn-lock toggle */
+                       map_key_clear(KEY_FN_ESC);
+                       return 1;
+               case 0x00fb: /* Fn-F12: Open My computer (6 boxes) USB-only */
+                       /* NB: This mapping is invented in raw_event below */
+                       map_key_clear(KEY_FILE);
+                       return 1;
+               }
+       }
+
+       return 0;
+}
+
+static int lenovo_input_mapping(struct hid_device *hdev,
+               struct hid_input *hi, struct hid_field *field,
+               struct hid_usage *usage, unsigned long **bit, int *max)
+{
+       switch (hdev->product) {
+       case USB_DEVICE_ID_LENOVO_TPKBD:
+               return lenovo_input_mapping_tpkbd(hdev, hi, field,
+                                                       usage, bit, max);
+       case USB_DEVICE_ID_LENOVO_CUSBKBD:
+       case USB_DEVICE_ID_LENOVO_CBTKBD:
+               return lenovo_input_mapping_cptkbd(hdev, hi, field,
+                                                       usage, bit, max);
+       default:
+               return 0;
+       }
+}
+
+#undef map_key_clear
+
+/* Send a config command to the keyboard */
+static int lenovo_send_cmd_cptkbd(struct hid_device *hdev,
+                       unsigned char byte2, unsigned char byte3)
+{
+       int ret;
+       unsigned char buf[] = {0x18, byte2, byte3};
+
+       switch (hdev->product) {
+       case USB_DEVICE_ID_LENOVO_CUSBKBD:
+               ret = hid_hw_raw_request(hdev, 0x13, buf, sizeof(buf),
+                                       HID_FEATURE_REPORT, HID_REQ_SET_REPORT);
+               break;
+       case USB_DEVICE_ID_LENOVO_CBTKBD:
+               ret = hid_hw_output_report(hdev, buf, sizeof(buf));
+               break;
+       default:
+               ret = -EINVAL;
+               break;
+       }
+
+       return ret < 0 ? ret : 0; /* BT returns 0, USB returns sizeof(buf) */
+}
+
+static void lenovo_features_set_cptkbd(struct hid_device *hdev)
+{
+       int ret;
+       struct lenovo_drvdata_cptkbd *cptkbd_data = hid_get_drvdata(hdev);
+
+       ret = lenovo_send_cmd_cptkbd(hdev, 0x05, cptkbd_data->fn_lock);
+       if (ret)
+               hid_err(hdev, "Fn-lock setting failed: %d\n", ret);
+}
+
+static ssize_t attr_fn_lock_show_cptkbd(struct device *dev,
+               struct device_attribute *attr,
+               char *buf)
+{
+       struct hid_device *hdev = container_of(dev, struct hid_device, dev);
+       struct lenovo_drvdata_cptkbd *cptkbd_data = hid_get_drvdata(hdev);
+
+       return snprintf(buf, PAGE_SIZE, "%u\n", cptkbd_data->fn_lock);
+}
+
+static ssize_t attr_fn_lock_store_cptkbd(struct device *dev,
+               struct device_attribute *attr,
+               const char *buf,
+               size_t count)
+{
+       struct hid_device *hdev = container_of(dev, struct hid_device, dev);
+       struct lenovo_drvdata_cptkbd *cptkbd_data = hid_get_drvdata(hdev);
+       int value;
+
+       if (kstrtoint(buf, 10, &value))
+               return -EINVAL;
+       if (value < 0 || value > 1)
+               return -EINVAL;
+
+       cptkbd_data->fn_lock = !!value;
+       lenovo_features_set_cptkbd(hdev);
+
+       return count;
+}
+
+static struct device_attribute dev_attr_fn_lock_cptkbd =
+       __ATTR(fn_lock, S_IWUSR | S_IRUGO,
+                       attr_fn_lock_show_cptkbd,
+                       attr_fn_lock_store_cptkbd);
+
+static struct attribute *lenovo_attributes_cptkbd[] = {
+       &dev_attr_fn_lock_cptkbd.attr,
+       NULL
+};
+
+static const struct attribute_group lenovo_attr_group_cptkbd = {
+       .attrs = lenovo_attributes_cptkbd,
+};
+
+static int lenovo_raw_event(struct hid_device *hdev,
+                       struct hid_report *report, u8 *data, int size)
+{
+       /*
+        * Compact USB keyboard's Fn-F12 report holds down many other keys, and
+        * its own key is outside the usage page range. Remove extra
+        * keypresses and remap to inside usage page.
+        */
+       if (unlikely(hdev->product == USB_DEVICE_ID_LENOVO_CUSBKBD
+                       && size == 3
+                       && data[0] == 0x15
+                       && data[1] == 0x94
+                       && data[2] == 0x01)) {
+               data[1] = 0x0;
+               data[2] = 0x4;
+       }
+
+       return 0;
+}
+
+static int lenovo_features_set_tpkbd(struct hid_device *hdev)
+{
+       struct hid_report *report;
+       struct lenovo_drvdata_tpkbd *data_pointer = hid_get_drvdata(hdev);
+
+       report = hdev->report_enum[HID_FEATURE_REPORT].report_id_hash[4];
+
+       report->field[0]->value[0]  = data_pointer->press_to_select   ? 0x01 : 0x02;
+       report->field[0]->value[0] |= data_pointer->dragging          ? 0x04 : 0x08;
+       report->field[0]->value[0] |= data_pointer->release_to_select ? 0x10 : 0x20;
+       report->field[0]->value[0] |= data_pointer->select_right      ? 0x80 : 0x40;
+       report->field[1]->value[0] = 0x03; // unknown setting, imitate windows driver
+       report->field[2]->value[0] = data_pointer->sensitivity;
+       report->field[3]->value[0] = data_pointer->press_speed;
+
+       hid_hw_request(hdev, report, HID_REQ_SET_REPORT);
+       return 0;
+}
+
+static ssize_t attr_press_to_select_show_tpkbd(struct device *dev,
+               struct device_attribute *attr,
+               char *buf)
+{
+       struct hid_device *hdev = container_of(dev, struct hid_device, dev);
+       struct lenovo_drvdata_tpkbd *data_pointer = hid_get_drvdata(hdev);
+
+       return snprintf(buf, PAGE_SIZE, "%u\n", data_pointer->press_to_select);
+}
+
+static ssize_t attr_press_to_select_store_tpkbd(struct device *dev,
+               struct device_attribute *attr,
+               const char *buf,
+               size_t count)
+{
+       struct hid_device *hdev = container_of(dev, struct hid_device, dev);
+       struct lenovo_drvdata_tpkbd *data_pointer = hid_get_drvdata(hdev);
+       int value;
+
+       if (kstrtoint(buf, 10, &value))
+               return -EINVAL;
+       if (value < 0 || value > 1)
+               return -EINVAL;
+
+       data_pointer->press_to_select = value;
+       lenovo_features_set_tpkbd(hdev);
+
+       return count;
+}
+
+static ssize_t attr_dragging_show_tpkbd(struct device *dev,
+               struct device_attribute *attr,
+               char *buf)
+{
+       struct hid_device *hdev = container_of(dev, struct hid_device, dev);
+       struct lenovo_drvdata_tpkbd *data_pointer = hid_get_drvdata(hdev);
+
+       return snprintf(buf, PAGE_SIZE, "%u\n", data_pointer->dragging);
+}
+
+static ssize_t attr_dragging_store_tpkbd(struct device *dev,
+               struct device_attribute *attr,
+               const char *buf,
+               size_t count)
+{
+       struct hid_device *hdev = container_of(dev, struct hid_device, dev);
+       struct lenovo_drvdata_tpkbd *data_pointer = hid_get_drvdata(hdev);
+       int value;
+
+       if (kstrtoint(buf, 10, &value))
+               return -EINVAL;
+       if (value < 0 || value > 1)
+               return -EINVAL;
+
+       data_pointer->dragging = value;
+       lenovo_features_set_tpkbd(hdev);
+
+       return count;
+}
+
+static ssize_t attr_release_to_select_show_tpkbd(struct device *dev,
+               struct device_attribute *attr,
+               char *buf)
+{
+       struct hid_device *hdev = container_of(dev, struct hid_device, dev);
+       struct lenovo_drvdata_tpkbd *data_pointer = hid_get_drvdata(hdev);
+
+       return snprintf(buf, PAGE_SIZE, "%u\n", data_pointer->release_to_select);
+}
+
+static ssize_t attr_release_to_select_store_tpkbd(struct device *dev,
+               struct device_attribute *attr,
+               const char *buf,
+               size_t count)
+{
+       struct hid_device *hdev = container_of(dev, struct hid_device, dev);
+       struct lenovo_drvdata_tpkbd *data_pointer = hid_get_drvdata(hdev);
+       int value;
+
+       if (kstrtoint(buf, 10, &value))
+               return -EINVAL;
+       if (value < 0 || value > 1)
+               return -EINVAL;
+
+       data_pointer->release_to_select = value;
+       lenovo_features_set_tpkbd(hdev);
+
+       return count;
+}
+
+static ssize_t attr_select_right_show_tpkbd(struct device *dev,
+               struct device_attribute *attr,
+               char *buf)
+{
+       struct hid_device *hdev = container_of(dev, struct hid_device, dev);
+       struct lenovo_drvdata_tpkbd *data_pointer = hid_get_drvdata(hdev);
+
+       return snprintf(buf, PAGE_SIZE, "%u\n", data_pointer->select_right);
+}
+
+static ssize_t attr_select_right_store_tpkbd(struct device *dev,
+               struct device_attribute *attr,
+               const char *buf,
+               size_t count)
+{
+       struct hid_device *hdev = container_of(dev, struct hid_device, dev);
+       struct lenovo_drvdata_tpkbd *data_pointer = hid_get_drvdata(hdev);
+       int value;
+
+       if (kstrtoint(buf, 10, &value))
+               return -EINVAL;
+       if (value < 0 || value > 1)
+               return -EINVAL;
+
+       data_pointer->select_right = value;
+       lenovo_features_set_tpkbd(hdev);
+
+       return count;
+}
+
+static ssize_t attr_sensitivity_show_tpkbd(struct device *dev,
+               struct device_attribute *attr,
+               char *buf)
+{
+       struct hid_device *hdev = container_of(dev, struct hid_device, dev);
+       struct lenovo_drvdata_tpkbd *data_pointer = hid_get_drvdata(hdev);
+
+       return snprintf(buf, PAGE_SIZE, "%u\n",
+               data_pointer->sensitivity);
+}
+
+static ssize_t attr_sensitivity_store_tpkbd(struct device *dev,
+               struct device_attribute *attr,
+               const char *buf,
+               size_t count)
+{
+       struct hid_device *hdev = container_of(dev, struct hid_device, dev);
+       struct lenovo_drvdata_tpkbd *data_pointer = hid_get_drvdata(hdev);
+       int value;
+
+       if (kstrtoint(buf, 10, &value) || value < 1 || value > 255)
+               return -EINVAL;
+
+       data_pointer->sensitivity = value;
+       lenovo_features_set_tpkbd(hdev);
+
+       return count;
+}
+
+static ssize_t attr_press_speed_show_tpkbd(struct device *dev,
+               struct device_attribute *attr,
+               char *buf)
+{
+       struct hid_device *hdev = container_of(dev, struct hid_device, dev);
+       struct lenovo_drvdata_tpkbd *data_pointer = hid_get_drvdata(hdev);
+
+       return snprintf(buf, PAGE_SIZE, "%u\n",
+               data_pointer->press_speed);
+}
+
+static ssize_t attr_press_speed_store_tpkbd(struct device *dev,
+               struct device_attribute *attr,
+               const char *buf,
+               size_t count)
+{
+       struct hid_device *hdev = container_of(dev, struct hid_device, dev);
+       struct lenovo_drvdata_tpkbd *data_pointer = hid_get_drvdata(hdev);
+       int value;
+
+       if (kstrtoint(buf, 10, &value) || value < 1 || value > 255)
+               return -EINVAL;
+
+       data_pointer->press_speed = value;
+       lenovo_features_set_tpkbd(hdev);
+
+       return count;
+}
+
+static struct device_attribute dev_attr_press_to_select_tpkbd =
+       __ATTR(press_to_select, S_IWUSR | S_IRUGO,
+                       attr_press_to_select_show_tpkbd,
+                       attr_press_to_select_store_tpkbd);
+
+static struct device_attribute dev_attr_dragging_tpkbd =
+       __ATTR(dragging, S_IWUSR | S_IRUGO,
+                       attr_dragging_show_tpkbd,
+                       attr_dragging_store_tpkbd);
+
+static struct device_attribute dev_attr_release_to_select_tpkbd =
+       __ATTR(release_to_select, S_IWUSR | S_IRUGO,
+                       attr_release_to_select_show_tpkbd,
+                       attr_release_to_select_store_tpkbd);
+
+static struct device_attribute dev_attr_select_right_tpkbd =
+       __ATTR(select_right, S_IWUSR | S_IRUGO,
+                       attr_select_right_show_tpkbd,
+                       attr_select_right_store_tpkbd);
+
+static struct device_attribute dev_attr_sensitivity_tpkbd =
+       __ATTR(sensitivity, S_IWUSR | S_IRUGO,
+                       attr_sensitivity_show_tpkbd,
+                       attr_sensitivity_store_tpkbd);
+
+static struct device_attribute dev_attr_press_speed_tpkbd =
+       __ATTR(press_speed, S_IWUSR | S_IRUGO,
+                       attr_press_speed_show_tpkbd,
+                       attr_press_speed_store_tpkbd);
+
+static struct attribute *lenovo_attributes_tpkbd[] = {
+       &dev_attr_press_to_select_tpkbd.attr,
+       &dev_attr_dragging_tpkbd.attr,
+       &dev_attr_release_to_select_tpkbd.attr,
+       &dev_attr_select_right_tpkbd.attr,
+       &dev_attr_sensitivity_tpkbd.attr,
+       &dev_attr_press_speed_tpkbd.attr,
+       NULL
+};
+
+static const struct attribute_group lenovo_attr_group_tpkbd = {
+       .attrs = lenovo_attributes_tpkbd,
+};
+
+static enum led_brightness lenovo_led_brightness_get_tpkbd(
+                       struct led_classdev *led_cdev)
+{
+       struct device *dev = led_cdev->dev->parent;
+       struct hid_device *hdev = container_of(dev, struct hid_device, dev);
+       struct lenovo_drvdata_tpkbd *data_pointer = hid_get_drvdata(hdev);
+       int led_nr = 0;
+
+       if (led_cdev == &data_pointer->led_micmute)
+               led_nr = 1;
+
+       return data_pointer->led_state & (1 << led_nr)
+                               ? LED_FULL
+                               : LED_OFF;
+}
+
+static void lenovo_led_brightness_set_tpkbd(struct led_classdev *led_cdev,
+                       enum led_brightness value)
+{
+       struct device *dev = led_cdev->dev->parent;
+       struct hid_device *hdev = container_of(dev, struct hid_device, dev);
+       struct lenovo_drvdata_tpkbd *data_pointer = hid_get_drvdata(hdev);
+       struct hid_report *report;
+       int led_nr = 0;
+
+       if (led_cdev == &data_pointer->led_micmute)
+               led_nr = 1;
+
+       if (value == LED_OFF)
+               data_pointer->led_state &= ~(1 << led_nr);
+       else
+               data_pointer->led_state |= 1 << led_nr;
+
+       report = hdev->report_enum[HID_OUTPUT_REPORT].report_id_hash[3];
+       report->field[0]->value[0] = (data_pointer->led_state >> 0) & 1;
+       report->field[0]->value[1] = (data_pointer->led_state >> 1) & 1;
+       hid_hw_request(hdev, report, HID_REQ_SET_REPORT);
+}
+
+static int lenovo_probe_tpkbd(struct hid_device *hdev)
+{
+       struct device *dev = &hdev->dev;
+       struct lenovo_drvdata_tpkbd *data_pointer;
+       size_t name_sz = strlen(dev_name(dev)) + 16;
+       char *name_mute, *name_micmute;
+       int i;
+       int ret;
+
+       /*
+        * Only register extra settings against subdevice where input_mapping
+        * set drvdata to 1, i.e. the trackpoint.
+        */
+       if (!hid_get_drvdata(hdev))
+               return 0;
+
+       hid_set_drvdata(hdev, NULL);
+
+       /* Validate required reports. */
+       for (i = 0; i < 4; i++) {
+               if (!hid_validate_values(hdev, HID_FEATURE_REPORT, 4, i, 1))
+                       return -ENODEV;
+       }
+       if (!hid_validate_values(hdev, HID_OUTPUT_REPORT, 3, 0, 2))
+               return -ENODEV;
+
+       ret = sysfs_create_group(&hdev->dev.kobj, &lenovo_attr_group_tpkbd);
+       if (ret)
+               hid_warn(hdev, "Could not create sysfs group: %d\n", ret);
+
+       data_pointer = devm_kzalloc(&hdev->dev,
+                                   sizeof(struct lenovo_drvdata_tpkbd),
+                                   GFP_KERNEL);
+       if (data_pointer == NULL) {
+               hid_err(hdev, "Could not allocate memory for driver data\n");
+               return -ENOMEM;
+       }
+
+       // set same default values as windows driver
+       data_pointer->sensitivity = 0xa0;
+       data_pointer->press_speed = 0x38;
+
+       name_mute = devm_kzalloc(&hdev->dev, name_sz, GFP_KERNEL);
+       name_micmute = devm_kzalloc(&hdev->dev, name_sz, GFP_KERNEL);
+       if (name_mute == NULL || name_micmute == NULL) {
+               hid_err(hdev, "Could not allocate memory for led data\n");
+               return -ENOMEM;
+       }
+       snprintf(name_mute, name_sz, "%s:amber:mute", dev_name(dev));
+       snprintf(name_micmute, name_sz, "%s:amber:micmute", dev_name(dev));
+
+       hid_set_drvdata(hdev, data_pointer);
+
+       data_pointer->led_mute.name = name_mute;
+       data_pointer->led_mute.brightness_get = lenovo_led_brightness_get_tpkbd;
+       data_pointer->led_mute.brightness_set = lenovo_led_brightness_set_tpkbd;
+       data_pointer->led_mute.dev = dev;
+       led_classdev_register(dev, &data_pointer->led_mute);
+
+       data_pointer->led_micmute.name = name_micmute;
+       data_pointer->led_micmute.brightness_get =
+               lenovo_led_brightness_get_tpkbd;
+       data_pointer->led_micmute.brightness_set =
+               lenovo_led_brightness_set_tpkbd;
+       data_pointer->led_micmute.dev = dev;
+       led_classdev_register(dev, &data_pointer->led_micmute);
+
+       lenovo_features_set_tpkbd(hdev);
+
+       return 0;
+}
+
+static int lenovo_probe_cptkbd(struct hid_device *hdev)
+{
+       int ret;
+       struct lenovo_drvdata_cptkbd *cptkbd_data;
+
+       /* All the custom action happens on the USBMOUSE device for USB */
+       if (hdev->product == USB_DEVICE_ID_LENOVO_CUSBKBD
+                       && hdev->type != HID_TYPE_USBMOUSE) {
+               hid_dbg(hdev, "Ignoring keyboard half of device\n");
+               return 0;
+       }
+
+       cptkbd_data = devm_kzalloc(&hdev->dev,
+                                       sizeof(*cptkbd_data),
+                                       GFP_KERNEL);
+       if (cptkbd_data == NULL) {
+               hid_err(hdev, "can't alloc keyboard descriptor\n");
+               return -ENOMEM;
+       }
+       hid_set_drvdata(hdev, cptkbd_data);
+
+       /*
+        * Tell the keyboard a driver understands it, and turn F7, F9, F11 into
+        * regular keys
+        */
+       ret = lenovo_send_cmd_cptkbd(hdev, 0x01, 0x03);
+       if (ret)
+               hid_warn(hdev, "Failed to switch F7/9/11 mode: %d\n", ret);
+
+       /* Turn Fn-Lock on by default */
+       cptkbd_data->fn_lock = true;
+       lenovo_features_set_cptkbd(hdev);
+
+       ret = sysfs_create_group(&hdev->dev.kobj, &lenovo_attr_group_cptkbd);
+       if (ret)
+               hid_warn(hdev, "Could not create sysfs group: %d\n", ret);
+
+       return 0;
+}
+
+static int lenovo_probe(struct hid_device *hdev,
+               const struct hid_device_id *id)
+{
+       int ret;
+
+       ret = hid_parse(hdev);
+       if (ret) {
+               hid_err(hdev, "hid_parse failed\n");
+               goto err;
+       }
+
+       ret = hid_hw_start(hdev, HID_CONNECT_DEFAULT);
+       if (ret) {
+               hid_err(hdev, "hid_hw_start failed\n");
+               goto err;
+       }
+
+       switch (hdev->product) {
+       case USB_DEVICE_ID_LENOVO_TPKBD:
+               ret = lenovo_probe_tpkbd(hdev);
+               break;
+       case USB_DEVICE_ID_LENOVO_CUSBKBD:
+       case USB_DEVICE_ID_LENOVO_CBTKBD:
+               ret = lenovo_probe_cptkbd(hdev);
+               break;
+       default:
+               ret = 0;
+               break;
+       }
+       if (ret)
+               goto err_hid;
+
+       return 0;
+err_hid:
+       hid_hw_stop(hdev);
+err:
+       return ret;
+}
+
+static void lenovo_remove_tpkbd(struct hid_device *hdev)
+{
+       struct lenovo_drvdata_tpkbd *data_pointer = hid_get_drvdata(hdev);
+
+       /*
+        * Only the trackpoint half of the keyboard has drvdata and stuff that
+        * needs unregistering.
+        */
+       if (data_pointer == NULL)
+               return;
+
+       sysfs_remove_group(&hdev->dev.kobj,
+                       &lenovo_attr_group_tpkbd);
+
+       led_classdev_unregister(&data_pointer->led_micmute);
+       led_classdev_unregister(&data_pointer->led_mute);
+
+       hid_set_drvdata(hdev, NULL);
+}
+
+static void lenovo_remove_cptkbd(struct hid_device *hdev)
+{
+       sysfs_remove_group(&hdev->dev.kobj,
+                       &lenovo_attr_group_cptkbd);
+}
+
+static void lenovo_remove(struct hid_device *hdev)
+{
+       switch (hdev->product) {
+       case USB_DEVICE_ID_LENOVO_TPKBD:
+               lenovo_remove_tpkbd(hdev);
+               break;
+       case USB_DEVICE_ID_LENOVO_CUSBKBD:
+       case USB_DEVICE_ID_LENOVO_CBTKBD:
+               lenovo_remove_cptkbd(hdev);
+               break;
+       }
+
+       hid_hw_stop(hdev);
+}
+
+static const struct hid_device_id lenovo_devices[] = {
+       { HID_USB_DEVICE(USB_VENDOR_ID_LENOVO, USB_DEVICE_ID_LENOVO_TPKBD) },
+       { HID_USB_DEVICE(USB_VENDOR_ID_LENOVO, USB_DEVICE_ID_LENOVO_CUSBKBD) },
+       { HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_LENOVO, USB_DEVICE_ID_LENOVO_CBTKBD) },
+       { }
+};
+
+MODULE_DEVICE_TABLE(hid, lenovo_devices);
+
+static struct hid_driver lenovo_driver = {
+       .name = "lenovo",
+       .id_table = lenovo_devices,
+       .input_mapping = lenovo_input_mapping,
+       .probe = lenovo_probe,
+       .remove = lenovo_remove,
+       .raw_event = lenovo_raw_event,
+};
+module_hid_driver(lenovo_driver);
+
+MODULE_LICENSE("GPL");
index 024cdf3..3c13af6 100644 (file)
@@ -883,16 +883,13 @@ void picolcd_exit_devfs(struct picolcd_data *data)
 
        dent = data->debug_reset;
        data->debug_reset = NULL;
-       if (dent)
-               debugfs_remove(dent);
+       debugfs_remove(dent);
        dent = data->debug_eeprom;
        data->debug_eeprom = NULL;
-       if (dent)
-               debugfs_remove(dent);
+       debugfs_remove(dent);
        dent = data->debug_flash;
        data->debug_flash = NULL;
-       if (dent)
-               debugfs_remove(dent);
+       debugfs_remove(dent);
        mutex_destroy(&data->mutex_flash);
 }
 
index 578bbe6..0dc2514 100644 (file)
@@ -377,7 +377,7 @@ static int rmi_input_event(struct hid_device *hdev, u8 *data, int size)
        irq_mask |= hdata->f30.irq_mask;
 
        if (data[1] & ~irq_mask)
-               hid_warn(hdev, "unknown intr source:%02lx %s:%d\n",
+               hid_dbg(hdev, "unknown intr source:%02lx %s:%d\n",
                        data[1] & ~irq_mask, __FILE__, __LINE__);
 
        if (hdata->f11.interrupt_base < hdata->f30.interrupt_base) {
@@ -400,7 +400,7 @@ static int rmi_read_data_event(struct hid_device *hdev, u8 *data, int size)
        struct rmi_data *hdata = hid_get_drvdata(hdev);
 
        if (!test_bit(RMI_READ_REQUEST_PENDING, &hdata->flags)) {
-               hid_err(hdev, "no read request pending\n");
+               hid_dbg(hdev, "no read request pending\n");
                return 0;
        }
 
@@ -549,10 +549,12 @@ static int rmi_populate_f11(struct hid_device *hdev)
        u8 buf[20];
        int ret;
        bool has_query9;
-       bool has_query10;
+       bool has_query10 = false;
        bool has_query11;
        bool has_query12;
        bool has_physical_props;
+       bool has_gestures;
+       bool has_rel;
        unsigned x_size, y_size;
        u16 query12_offset;
 
@@ -589,19 +591,32 @@ static int rmi_populate_f11(struct hid_device *hdev)
                return -ENODEV;
        }
 
-       /* query 8 to find out if query 10 exists */
-       ret = rmi_read(hdev, data->f11.query_base_addr + 8, buf);
-       if (ret) {
-               hid_err(hdev, "can not read gesture information: %d.\n", ret);
-               return ret;
+       has_rel = !!(buf[0] & BIT(3));
+       has_gestures = !!(buf[0] & BIT(5));
+
+       if (has_gestures) {
+               /* query 8 to find out if query 10 exists */
+               ret = rmi_read(hdev, data->f11.query_base_addr + 8, buf);
+               if (ret) {
+                       hid_err(hdev, "can not read gesture information: %d.\n",
+                               ret);
+                       return ret;
+               }
+               has_query10 = !!(buf[0] & BIT(2));
        }
-       has_query10 = !!(buf[0] & BIT(2));
 
        /*
-        * At least 8 queries are guaranteed to be present in F11
-        * +1 for query12.
+        * At least 4 queries are guaranteed to be present in F11
+        * +1 for query 5 which is present since absolute events are
+        * reported and +1 for query 12.
         */
-       query12_offset = 9;
+       query12_offset = 6;
+
+       if (has_rel)
+               ++query12_offset; /* query 6 is present */
+
+       if (has_gestures)
+               query12_offset += 2; /* query 7 and 8 are present */
 
        if (has_query9)
                ++query12_offset;
@@ -833,6 +848,8 @@ static int rmi_probe(struct hid_device *hdev, const struct hid_device_id *id)
        struct rmi_data *data = NULL;
        int ret;
        size_t alloc_size;
+       struct hid_report *input_report;
+       struct hid_report *output_report;
 
        data = devm_kzalloc(&hdev->dev, sizeof(struct rmi_data), GFP_KERNEL);
        if (!data)
@@ -851,12 +868,26 @@ static int rmi_probe(struct hid_device *hdev, const struct hid_device_id *id)
                return ret;
        }
 
-       data->input_report_size = (hdev->report_enum[HID_INPUT_REPORT]
-               .report_id_hash[RMI_ATTN_REPORT_ID]->size >> 3)
-               + 1 /* report id */;
-       data->output_report_size = (hdev->report_enum[HID_OUTPUT_REPORT]
-               .report_id_hash[RMI_WRITE_REPORT_ID]->size >> 3)
-               + 1 /* report id */;
+       input_report = hdev->report_enum[HID_INPUT_REPORT]
+                       .report_id_hash[RMI_ATTN_REPORT_ID];
+       if (!input_report) {
+               hid_err(hdev, "device does not have expected input report\n");
+               ret = -ENODEV;
+               return ret;
+       }
+
+       data->input_report_size = (input_report->size >> 3) + 1 /* report id */;
+
+       output_report = hdev->report_enum[HID_OUTPUT_REPORT]
+                       .report_id_hash[RMI_WRITE_REPORT_ID];
+       if (!output_report) {
+               hid_err(hdev, "device does not have expected output report\n");
+               ret = -ENODEV;
+               return ret;
+       }
+
+       data->output_report_size = (output_report->size >> 3)
+                                       + 1 /* report id */;
 
        alloc_size = data->output_report_size + data->input_report_size;
 
index 6adc0fa..65e2e76 100644 (file)
@@ -61,7 +61,7 @@ static ssize_t lua_sysfs_write(struct file *fp, struct kobject *kobj,
                return -EINVAL;
 
        mutex_lock(&lua->lua_lock);
-       retval = roccat_common2_send(usb_dev, command, (void *)buf, real_size);
+       retval = roccat_common2_send(usb_dev, command, buf, real_size);
        mutex_unlock(&lua->lua_lock);
 
        return retval ? retval : real_size;
index 2259eaa..c372368 100644 (file)
 
 #define MAX_LEDS 4
 
-static const u8 sixaxis_rdesc_fixup[] = {
-       0x95, 0x13, 0x09, 0x01, 0x81, 0x02, 0x95, 0x0C,
-       0x81, 0x01, 0x75, 0x10, 0x95, 0x04, 0x26, 0xFF,
-       0x03, 0x46, 0xFF, 0x03, 0x09, 0x01, 0x81, 0x02
-};
-
-static const u8 sixaxis_rdesc_fixup2[] = {
-       0x05, 0x01, 0x09, 0x04, 0xa1, 0x01, 0xa1, 0x02,
-       0x85, 0x01, 0x75, 0x08, 0x95, 0x01, 0x15, 0x00,
-       0x26, 0xff, 0x00, 0x81, 0x03, 0x75, 0x01, 0x95,
-       0x13, 0x15, 0x00, 0x25, 0x01, 0x35, 0x00, 0x45,
-       0x01, 0x05, 0x09, 0x19, 0x01, 0x29, 0x13, 0x81,
-       0x02, 0x75, 0x01, 0x95, 0x0d, 0x06, 0x00, 0xff,
-       0x81, 0x03, 0x15, 0x00, 0x26, 0xff, 0x00, 0x05,
-       0x01, 0x09, 0x01, 0xa1, 0x00, 0x75, 0x08, 0x95,
-       0x04, 0x35, 0x00, 0x46, 0xff, 0x00, 0x09, 0x30,
-       0x09, 0x31, 0x09, 0x32, 0x09, 0x35, 0x81, 0x02,
-       0xc0, 0x05, 0x01, 0x95, 0x13, 0x09, 0x01, 0x81,
-       0x02, 0x95, 0x0c, 0x81, 0x01, 0x75, 0x10, 0x95,
-       0x04, 0x26, 0xff, 0x03, 0x46, 0xff, 0x03, 0x09,
-       0x01, 0x81, 0x02, 0xc0, 0xa1, 0x02, 0x85, 0x02,
-       0x75, 0x08, 0x95, 0x30, 0x09, 0x01, 0xb1, 0x02,
-       0xc0, 0xa1, 0x02, 0x85, 0xee, 0x75, 0x08, 0x95,
-       0x30, 0x09, 0x01, 0xb1, 0x02, 0xc0, 0xa1, 0x02,
-       0x85, 0xef, 0x75, 0x08, 0x95, 0x30, 0x09, 0x01,
-       0xb1, 0x02, 0xc0, 0xc0,
+static __u8 sixaxis_rdesc[] = {
+       0x05, 0x01,         /*  Usage Page (Desktop),               */
+       0x09, 0x04,         /*  Usage (Joystik),                    */
+       0xA1, 0x01,         /*  Collection (Application),           */
+       0xA1, 0x02,         /*      Collection (Logical),           */
+       0x85, 0x01,         /*          Report ID (1),              */
+       0x75, 0x08,         /*          Report Size (8),            */
+       0x95, 0x01,         /*          Report Count (1),           */
+       0x15, 0x00,         /*          Logical Minimum (0),        */
+       0x26, 0xFF, 0x00,   /*          Logical Maximum (255),      */
+       0x81, 0x03,         /*          Input (Constant, Variable), */
+       0x75, 0x01,         /*          Report Size (1),            */
+       0x95, 0x13,         /*          Report Count (19),          */
+       0x15, 0x00,         /*          Logical Minimum (0),        */
+       0x25, 0x01,         /*          Logical Maximum (1),        */
+       0x35, 0x00,         /*          Physical Minimum (0),       */
+       0x45, 0x01,         /*          Physical Maximum (1),       */
+       0x05, 0x09,         /*          Usage Page (Button),        */
+       0x19, 0x01,         /*          Usage Minimum (01h),        */
+       0x29, 0x13,         /*          Usage Maximum (13h),        */
+       0x81, 0x02,         /*          Input (Variable),           */
+       0x75, 0x01,         /*          Report Size (1),            */
+       0x95, 0x0D,         /*          Report Count (13),          */
+       0x06, 0x00, 0xFF,   /*          Usage Page (FF00h),         */
+       0x81, 0x03,         /*          Input (Constant, Variable), */
+       0x15, 0x00,         /*          Logical Minimum (0),        */
+       0x26, 0xFF, 0x00,   /*          Logical Maximum (255),      */
+       0x05, 0x01,         /*          Usage Page (Desktop),       */
+       0x09, 0x01,         /*          Usage (Pointer),            */
+       0xA1, 0x00,         /*          Collection (Physical),      */
+       0x75, 0x08,         /*              Report Size (8),        */
+       0x95, 0x04,         /*              Report Count (4),       */
+       0x35, 0x00,         /*              Physical Minimum (0),   */
+       0x46, 0xFF, 0x00,   /*              Physical Maximum (255), */
+       0x09, 0x30,         /*              Usage (X),              */
+       0x09, 0x31,         /*              Usage (Y),              */
+       0x09, 0x32,         /*              Usage (Z),              */
+       0x09, 0x35,         /*              Usage (Rz),             */
+       0x81, 0x02,         /*              Input (Variable),       */
+       0xC0,               /*          End Collection,             */
+       0x05, 0x01,         /*          Usage Page (Desktop),       */
+       0x95, 0x13,         /*          Report Count (19),          */
+       0x09, 0x01,         /*          Usage (Pointer),            */
+       0x81, 0x02,         /*          Input (Variable),           */
+       0x95, 0x0C,         /*          Report Count (12),          */
+       0x81, 0x01,         /*          Input (Constant),           */
+       0x75, 0x10,         /*          Report Size (16),           */
+       0x95, 0x04,         /*          Report Count (4),           */
+       0x26, 0xFF, 0x03,   /*          Logical Maximum (1023),     */
+       0x46, 0xFF, 0x03,   /*          Physical Maximum (1023),    */
+       0x09, 0x01,         /*          Usage (Pointer),            */
+       0x81, 0x02,         /*          Input (Variable),           */
+       0xC0,               /*      End Collection,                 */
+       0xA1, 0x02,         /*      Collection (Logical),           */
+       0x85, 0x02,         /*          Report ID (2),              */
+       0x75, 0x08,         /*          Report Size (8),            */
+       0x95, 0x30,         /*          Report Count (48),          */
+       0x09, 0x01,         /*          Usage (Pointer),            */
+       0xB1, 0x02,         /*          Feature (Variable),         */
+       0xC0,               /*      End Collection,                 */
+       0xA1, 0x02,         /*      Collection (Logical),           */
+       0x85, 0xEE,         /*          Report ID (238),            */
+       0x75, 0x08,         /*          Report Size (8),            */
+       0x95, 0x30,         /*          Report Count (48),          */
+       0x09, 0x01,         /*          Usage (Pointer),            */
+       0xB1, 0x02,         /*          Feature (Variable),         */
+       0xC0,               /*      End Collection,                 */
+       0xA1, 0x02,         /*      Collection (Logical),           */
+       0x85, 0xEF,         /*          Report ID (239),            */
+       0x75, 0x08,         /*          Report Size (8),            */
+       0x95, 0x30,         /*          Report Count (48),          */
+       0x09, 0x01,         /*          Usage (Pointer),            */
+       0xB1, 0x02,         /*          Feature (Variable),         */
+       0xC0,               /*      End Collection,                 */
+       0xC0                /*  End Collection                      */
 };
 
 /*
@@ -778,6 +827,13 @@ struct sony_sc {
        __u8 led_count;
 };
 
+static __u8 *sixaxis_fixup(struct hid_device *hdev, __u8 *rdesc,
+                            unsigned int *rsize)
+{
+       *rsize = sizeof(sixaxis_rdesc);
+       return sixaxis_rdesc;
+}
+
 static __u8 *ps3remote_fixup(struct hid_device *hdev, __u8 *rdesc,
                             unsigned int *rsize)
 {
@@ -819,8 +875,6 @@ static int ps3remote_mapping(struct hid_device *hdev, struct hid_input *hi,
        return 1;
 }
 
-
-/* Sony Vaio VGX has wrongly mouse pointer declared as constant */
 static __u8 *sony_report_fixup(struct hid_device *hdev, __u8 *rdesc,
                unsigned int *rsize)
 {
@@ -857,20 +911,8 @@ static __u8 *sony_report_fixup(struct hid_device *hdev, __u8 *rdesc,
                *rsize = sizeof(dualshock4_bt_rdesc);
        }
 
-       /* The HID descriptor exposed over BT has a trailing zero byte */
-       if ((((sc->quirks & SIXAXIS_CONTROLLER_USB) && *rsize == 148) ||
-                       ((sc->quirks & SIXAXIS_CONTROLLER_BT) && *rsize == 149)) &&
-                       rdesc[83] == 0x75) {
-               hid_info(hdev, "Fixing up Sony Sixaxis report descriptor\n");
-               memcpy((void *)&rdesc[83], (void *)&sixaxis_rdesc_fixup,
-                       sizeof(sixaxis_rdesc_fixup));
-       } else if (sc->quirks & SIXAXIS_CONTROLLER_USB &&
-                  *rsize > sizeof(sixaxis_rdesc_fixup2)) {
-               hid_info(hdev, "Sony Sixaxis clone detected. Using original report descriptor (size: %d clone; %d new)\n",
-                        *rsize, (int)sizeof(sixaxis_rdesc_fixup2));
-               *rsize = sizeof(sixaxis_rdesc_fixup2);
-               memcpy(rdesc, &sixaxis_rdesc_fixup2, *rsize);
-       }
+       if (sc->quirks & SIXAXIS_CONTROLLER)
+               return sixaxis_fixup(hdev, rdesc, rsize);
 
        if (sc->quirks & PS3REMOTE)
                return ps3remote_fixup(hdev, rdesc, rsize);
@@ -1307,7 +1349,7 @@ static int sony_leds_init(struct sony_sc *sc)
        static const char * const ds4_name_str[] = { "red", "green", "blue",
                                                  "global" };
        __u8 initial_values[MAX_LEDS] = { 0 };
-       __u8 max_brightness[MAX_LEDS] = { 1 };
+       __u8 max_brightness[MAX_LEDS] = { [0 ... (MAX_LEDS - 1)] = 1 };
        __u8 use_hw_blink[MAX_LEDS] = { 0 };
 
        BUG_ON(!(sc->quirks & SONY_LED_SUPPORT));
@@ -1830,9 +1872,7 @@ static int sony_probe(struct hid_device *hdev, const struct hid_device_id *id)
 
        if (sc->quirks & VAIO_RDESC_CONSTANT)
                connect_mask |= HID_CONNECT_HIDDEV_FORCE;
-       else if (sc->quirks & SIXAXIS_CONTROLLER_USB)
-               connect_mask |= HID_CONNECT_HIDDEV_FORCE;
-       else if (sc->quirks & SIXAXIS_CONTROLLER_BT)
+       else if (sc->quirks & SIXAXIS_CONTROLLER)
                connect_mask |= HID_CONNECT_HIDDEV_FORCE;
 
        ret = hid_hw_start(hdev, connect_mask);
index 21aafc8..747d544 100644 (file)
@@ -1054,21 +1054,29 @@ static int i2c_hid_remove(struct i2c_client *client)
 static int i2c_hid_suspend(struct device *dev)
 {
        struct i2c_client *client = to_i2c_client(dev);
+       struct i2c_hid *ihid = i2c_get_clientdata(client);
+       struct hid_device *hid = ihid->hid;
+       int ret = 0;
 
        disable_irq(client->irq);
        if (device_may_wakeup(&client->dev))
                enable_irq_wake(client->irq);
 
+       if (hid->driver && hid->driver->suspend)
+               ret = hid->driver->suspend(hid, PMSG_SUSPEND);
+
        /* Save some power */
        i2c_hid_set_power(client, I2C_HID_PWR_SLEEP);
 
-       return 0;
+       return ret;
 }
 
 static int i2c_hid_resume(struct device *dev)
 {
        int ret;
        struct i2c_client *client = to_i2c_client(dev);
+       struct i2c_hid *ihid = i2c_get_clientdata(client);
+       struct hid_device *hid = ihid->hid;
 
        enable_irq(client->irq);
        ret = i2c_hid_hwreset(client);
@@ -1078,6 +1086,11 @@ static int i2c_hid_resume(struct device *dev)
        if (device_may_wakeup(&client->dev))
                disable_irq_wake(client->irq);
 
+       if (hid->driver && hid->driver->reset_resume) {
+               ret = hid->driver->reset_resume(hid);
+               return ret;
+       }
+
        return 0;
 }
 #endif
index 7b88f4c..79cf503 100644 (file)
@@ -58,7 +58,7 @@ module_param_named(ignoreled, ignoreled, uint, 0644);
 MODULE_PARM_DESC(ignoreled, "Autosuspend with active leds");
 
 /* Quirks specified at module load time */
-static char *quirks_param[MAX_USBHID_BOOT_QUIRKS] = { [ 0 ... (MAX_USBHID_BOOT_QUIRKS - 1) ] = NULL };
+static char *quirks_param[MAX_USBHID_BOOT_QUIRKS];
 module_param_array_named(quirks, quirks_param, charp, NULL, 0444);
 MODULE_PARM_DESC(quirks, "Add/modify USB HID quirks by specifying "
                " quirks=vendorID:productID:quirks"
@@ -536,7 +536,8 @@ static void __usbhid_submit_report(struct hid_device *hid, struct hid_report *re
        int head;
        struct usbhid_device *usbhid = hid->driver_data;
 
-       if ((hid->quirks & HID_QUIRK_NOGET) && dir == USB_DIR_IN)
+       if (((hid->quirks & HID_QUIRK_NOGET) && dir == USB_DIR_IN) ||
+               test_bit(HID_DISCONNECTED, &usbhid->iofl))
                return;
 
        if (usbhid->urbout && dir == USB_DIR_OUT && report->type == HID_OUTPUT_REPORT) {
@@ -1366,6 +1367,9 @@ static void usbhid_disconnect(struct usb_interface *intf)
                return;
 
        usbhid = hid->driver_data;
+       spin_lock_irq(&usbhid->lock);   /* Sync with error and led handlers */
+       set_bit(HID_DISCONNECTED, &usbhid->iofl);
+       spin_unlock_irq(&usbhid->lock);
        hid_destroy_device(hid);
        kfree(usbhid);
 }
index 31e6727..0dd5681 100644 (file)
@@ -124,6 +124,7 @@ static const struct hid_blacklist {
        { USB_VENDOR_ID_SYNAPTICS, USB_DEVICE_ID_SYNAPTICS_HD, HID_QUIRK_NO_INIT_REPORTS },
        { USB_VENDOR_ID_SYNAPTICS, USB_DEVICE_ID_SYNAPTICS_QUAD_HD, HID_QUIRK_NO_INIT_REPORTS },
        { USB_VENDOR_ID_SYNAPTICS, USB_DEVICE_ID_SYNAPTICS_TP_V103, HID_QUIRK_NO_INIT_REPORTS },
+       { USB_VENDOR_ID_HOLTEK_ALT, USB_DEVICE_ID_HOLTEK_ALT_KEYBOARD_A096, HID_QUIRK_NO_INIT_INPUT_REPORTS },
 
        { 0, 0 }
 };
index 77632cf..fca74f1 100644 (file)
@@ -167,6 +167,7 @@ struct hid_item {
 #define HID_UP_MSVENDOR                0xff000000
 #define HID_UP_CUSTOM          0x00ff0000
 #define HID_UP_LOGIVENDOR      0xffbc0000
+#define HID_UP_LNVENDOR                0xffa00000
 #define HID_UP_SENSOR          0x00200000
 
 #define HID_USAGE              0x0000ffff