Merge branches 'for-4.0/upstream-fixes', 'for-4.1/genius', 'for-4.1/huion-uclogic...
authorJiri Kosina <jkosina@suse.cz>
Mon, 13 Apr 2015 21:41:15 +0000 (23:41 +0200)
committerJiri Kosina <jkosina@suse.cz>
Mon, 13 Apr 2015 21:41:15 +0000 (23:41 +0200)
30 files changed:
Documentation/ABI/testing/sysfs-driver-hid
Documentation/ABI/testing/sysfs-driver-hid-logitech-lg4ff
drivers/hid/Kconfig
drivers/hid/hid-core.c
drivers/hid/hid-debug.c
drivers/hid/hid-ids.h
drivers/hid/hid-input.c
drivers/hid/hid-kye.c
drivers/hid/hid-lg.c
drivers/hid/hid-lg.h
drivers/hid/hid-lg4ff.c
drivers/hid/hid-lg4ff.h [new file with mode: 0644]
drivers/hid/hid-logitech-hidpp.c
drivers/hid/hid-multitouch.c
drivers/hid/hid-rmi.c
drivers/hid/hid-sony.c
drivers/hid/hid-steelseries.c
drivers/hid/hid-tivo.c
drivers/hid/i2c-hid/i2c-hid.c
drivers/hid/usbhid/hid-pidff.c
drivers/hid/usbhid/hid-quirks.c
drivers/hid/wacom.h
drivers/hid/wacom_sys.c
drivers/hid/wacom_wac.c
drivers/hid/wacom_wac.h
drivers/input/input-mt.c
include/linux/hid.h
include/uapi/linux/input.h
samples/hidraw/Makefile
samples/hidraw/hid-example.c

index b6490e1..48942ca 100644 (file)
@@ -8,3 +8,13 @@ Description:   When read, this file returns the device's raw binary HID
                report descriptor.
                This file cannot be written.
 Users:         HIDAPI library (http://www.signal11.us/oss/hidapi)
+
+What:          For USB devices : /sys/bus/usb/devices/<busnum>-<devnum>:<config num>.<interface num>/<hid-bus>:<vendor-id>:<product-id>.<num>/country
+               For BT devices  : /sys/class/bluetooth/hci<addr>/<hid-bus>:<vendor-id>:<product-id>.<num>/country
+               Symlink         : /sys/class/hidraw/hidraw<num>/device/country
+Date:          February 2015
+KernelVersion: 3.19
+Contact:       Olivier Gay <ogay@logitech.com>
+Description:   When read, this file returns the hex integer value in ASCII
+               of the device's HID country code (e.g. 21 for US).
+               This file cannot be written.
index 167d903..b3f6a2a 100644 (file)
@@ -5,3 +5,48 @@ Contact:       Michal Malý <madcatxster@gmail.com>
 Description:   Display minimum, maximum and current range of the steering
                wheel. Writing a value within min and max boundaries sets the
                range of the wheel.
+
+What:          /sys/bus/hid/drivers/logitech/<dev>/alternate_modes
+Date:          Feb 2015
+KernelVersion: 4.1
+Contact:       Michal Malý <madcatxster@gmail.com>
+Description:   Displays a set of alternate modes supported by a wheel. Each
+               mode is listed as follows:
+                 Tag: Mode Name
+               Currently active mode is marked with an asterisk. List also
+               contains an abstract item "native" which always denotes the
+               native mode of the wheel. Echoing the mode tag switches the
+               wheel into the corresponding mode. Depending on the exact model
+               of the wheel not all listed modes might always be selectable.
+               If a wheel cannot be switched into the desired mode, -EINVAL
+               is returned accompanied with an explanatory message in the
+               kernel log.
+               This entry is not created for devices that have only one mode.
+
+               Currently supported mode switches:
+               Driving Force Pro:
+                 DF-EX --> DFP
+
+               G25:
+                 DF-EX --> DFP --> G25
+
+               G27:
+                 DF-EX <*> DFP <-> G25 <-> G27
+                 DF-EX <*--------> G25 <-> G27
+                 DF-EX <*----------------> G27
+
+               DFGT:
+                 DF-EX <*> DFP <-> DFGT
+                 DF-EX <*--------> DFGT
+
+               * hid_logitech module must be loaded with lg4ff_no_autoswitch=1
+                 parameter set in order for the switch to DF-EX mode to work.
+
+What:          /sys/bus/hid/drivers/logitech/<dev>/real_id
+Date:          Feb 2015
+KernelVersion: 4.1
+Contact:       Michal Malý <madcatxster@gmail.com>
+Description:   Displays the real model of the wheel regardless of any
+               alternate mode the wheel might be switched to.
+               It is a read-only value.
+               This entry is not created for devices that have only one mode.
index 60c34cc..1590106 100644 (file)
@@ -92,7 +92,7 @@ menu "Special HID drivers"
        depends on HID
 
 config HID_A4TECH
-       tristate "A4 tech mice" if EXPERT
+       tristate "A4 tech mice"
        depends on HID
        default !EXPERT
        ---help---
@@ -113,7 +113,7 @@ config HID_ACRUX_FF
        game controllers.
 
 config HID_APPLE
-       tristate "Apple {i,Power,Mac}Books" if EXPERT
+       tristate "Apple {i,Power,Mac}Books"
        depends on HID
        default !EXPERT
        ---help---
@@ -141,7 +141,7 @@ config HID_AUREAL
        Support for Aureal Cy se W-01RN Remote Controller and other Aureal derived remotes.
 
 config HID_BELKIN
-       tristate "Belkin Flip KVM and Wireless keyboard" if EXPERT
+       tristate "Belkin Flip KVM and Wireless keyboard"
        depends on HID
        default !EXPERT
        ---help---
@@ -158,14 +158,14 @@ config HID_BETOP_FF
         - BETOP 2185 PC & BFM MODE
 
 config HID_CHERRY
-       tristate "Cherry Cymotion keyboard" if EXPERT
+       tristate "Cherry Cymotion keyboard"
        depends on HID
        default !EXPERT
        ---help---
        Support for Cherry Cymotion keyboard.
 
 config HID_CHICONY
-       tristate "Chicony Tactical pad" if EXPERT
+       tristate "Chicony Tactical pad"
        depends on HID
        default !EXPERT
        ---help---
@@ -196,7 +196,7 @@ config HID_CP2112
        customizable USB descriptor fields are exposed as sysfs attributes.
 
 config HID_CYPRESS
-       tristate "Cypress mouse and barcode readers" if EXPERT
+       tristate "Cypress mouse and barcode readers"
        depends on HID
        default !EXPERT
        ---help---
@@ -245,7 +245,7 @@ config HID_ELO
        different devices than those handled by CONFIG_TOUCHSCREEN_USB_ELO.
 
 config HID_EZKEY
-       tristate "Ezkey BTC 8193 keyboard" if EXPERT
+       tristate "Ezkey BTC 8193 keyboard"
        depends on HID
        default !EXPERT
        ---help---
@@ -338,7 +338,7 @@ config HID_TWINHAN
        Support for Twinhan IR remote control.
 
 config HID_KENSINGTON
-       tristate "Kensington Slimblade Trackball" if EXPERT
+       tristate "Kensington Slimblade Trackball"
        depends on HID
        default !EXPERT
        ---help---
@@ -366,7 +366,7 @@ config HID_LENOVO
        - ThinkPad Compact USB Keyboard with TrackPoint (supports Fn keys)
 
 config HID_LOGITECH
-       tristate "Logitech devices" if EXPERT
+       tristate "Logitech devices"
        depends on HID
        default !EXPERT
        ---help---
@@ -455,14 +455,14 @@ config HID_MAGICMOUSE
        Apple Wireless "Magic" Mouse and the Apple Wireless "Magic" Trackpad.
 
 config HID_MICROSOFT
-       tristate "Microsoft non-fully HID-compliant devices" if EXPERT
+       tristate "Microsoft non-fully HID-compliant devices"
        depends on HID
        default !EXPERT
        ---help---
        Support for Microsoft devices that are not fully compliant with HID standard.
 
 config HID_MONTEREY
-       tristate "Monterey Genius KB29E keyboard" if EXPERT
+       tristate "Monterey Genius KB29E keyboard"
        depends on HID
        default !EXPERT
        ---help---
@@ -632,7 +632,6 @@ config HID_PICOLCD_CIR
 
 config HID_PLANTRONICS
        tristate "Plantronics USB HID Driver"
-       default !EXPERT
        depends on HID
        ---help---
        Provides HID support for Plantronics telephony devices.
index 7c669c3..722a925 100644 (file)
@@ -1562,12 +1562,26 @@ read_report_descriptor(struct file *filp, struct kobject *kobj,
        return count;
 }
 
+static ssize_t
+show_country(struct device *dev, struct device_attribute *attr,
+               char *buf)
+{
+       struct hid_device *hdev = container_of(dev, struct hid_device, dev);
+
+       return sprintf(buf, "%02x\n", hdev->country & 0xff);
+}
+
 static struct bin_attribute dev_bin_attr_report_desc = {
        .attr = { .name = "report_descriptor", .mode = 0444 },
        .read = read_report_descriptor,
        .size = HID_MAX_DESCRIPTOR_SIZE,
 };
 
+static struct device_attribute dev_attr_country = {
+       .attr = { .name = "country", .mode = 0444 },
+       .show = show_country,
+};
+
 int hid_connect(struct hid_device *hdev, unsigned int connect_mask)
 {
        static const char *types[] = { "Device", "Pointer", "Mouse", "Device",
@@ -1646,6 +1660,11 @@ int hid_connect(struct hid_device *hdev, unsigned int connect_mask)
                bus = "<UNKNOWN>";
        }
 
+       ret = device_create_file(&hdev->dev, &dev_attr_country);
+       if (ret)
+               hid_warn(hdev,
+                        "can't create sysfs country code attribute err: %d\n", ret);
+
        ret = device_create_bin_file(&hdev->dev, &dev_bin_attr_report_desc);
        if (ret)
                hid_warn(hdev,
@@ -1661,6 +1680,7 @@ EXPORT_SYMBOL_GPL(hid_connect);
 
 void hid_disconnect(struct hid_device *hdev)
 {
+       device_remove_file(&hdev->dev, &dev_attr_country);
        device_remove_bin_file(&hdev->dev, &dev_bin_attr_report_desc);
        if (hdev->claimed & HID_CLAIMED_INPUT)
                hidinput_disconnect(hdev);
@@ -1824,6 +1844,7 @@ static const struct hid_device_id hid_have_special_driver[] = {
        { HID_USB_DEVICE(USB_VENDOR_ID_KYE, USB_DEVICE_ID_KYE_MOUSEPEN_I608X) },
        { HID_USB_DEVICE(USB_VENDOR_ID_KYE, USB_DEVICE_ID_KYE_MOUSEPEN_I608X_2) },
        { HID_USB_DEVICE(USB_VENDOR_ID_KYE, USB_DEVICE_ID_KYE_EASYPEN_M610X) },
+       { HID_USB_DEVICE(USB_VENDOR_ID_KYE, USB_DEVICE_ID_KYE_PENSKETCH_M912) },
        { 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)
@@ -1959,6 +1980,7 @@ static const struct hid_device_id hid_have_special_driver[] = {
        { HID_USB_DEVICE(USB_VENDOR_ID_THRUSTMASTER, 0xb65a) },
        { HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_TIVO, USB_DEVICE_ID_TIVO_SLIDE_BT) },
        { HID_USB_DEVICE(USB_VENDOR_ID_TIVO, USB_DEVICE_ID_TIVO_SLIDE) },
+       { HID_USB_DEVICE(USB_VENDOR_ID_TIVO, USB_DEVICE_ID_TIVO_SLIDE_PRO) },
        { HID_USB_DEVICE(USB_VENDOR_ID_TOPSEED, USB_DEVICE_ID_TOPSEED_CYBERLINK) },
        { HID_USB_DEVICE(USB_VENDOR_ID_TOPSEED2, USB_DEVICE_ID_TOPSEED2_RF_COMBO) },
        { HID_USB_DEVICE(USB_VENDOR_ID_TWINHAN, USB_DEVICE_ID_TWINHAN_IR_REMOTE) },
index 8bf61d2..c785095 100644 (file)
@@ -165,6 +165,7 @@ static const struct hid_usage_entry hid_usage_table[] = {
     {0, 0x53, "DeviceIndex"},
     {0, 0x54, "ContactCount"},
     {0, 0x55, "ContactMaximumNumber"},
+    {0, 0x59, "ButtonType"},
     {0, 0x5A, "SecondaryBarrelSwitch"},
     {0, 0x5B, "TransducerSerialNumber"},
   { 15, 0, "PhysicalInterfaceDevice" },
@@ -1127,7 +1128,8 @@ static ssize_t hid_debug_events_read(struct file *file, char __user *buffer,
 
                                if (!list->hdev || !list->hdev->debug) {
                                        ret = -EIO;
-                                       break;
+                                       set_current_state(TASK_RUNNING);
+                                       goto out;
                                }
 
                                /* allow O_NONBLOCK from other threads */
index 204312b..41f167e 100644 (file)
 #define USB_DEVICE_ID_UGCI_FLYING      0x0020
 #define USB_DEVICE_ID_UGCI_FIGHTING    0x0030
 
+#define USB_VENDOR_ID_HP               0x03f0
+#define USB_PRODUCT_ID_HP_LOGITECH_OEM_USB_OPTICAL_MOUSE_0A4A  0x0a4a
+#define USB_PRODUCT_ID_HP_LOGITECH_OEM_USB_OPTICAL_MOUSE_0B4A  0x0b4a
+#define USB_PRODUCT_ID_HP_PIXART_OEM_USB_OPTICAL_MOUSE         0x134a
+
 #define USB_VENDOR_ID_HUION            0x256c
 #define USB_DEVICE_ID_HUION_TABLET     0x006e
 
 #define USB_DEVICE_ID_KYE_MOUSEPEN_I608X       0x5011
 #define USB_DEVICE_ID_KYE_MOUSEPEN_I608X_2     0x501a
 #define USB_DEVICE_ID_KYE_EASYPEN_M610X        0x5013
+#define USB_DEVICE_ID_KYE_PENSKETCH_M912       0x5015
 
 #define USB_VENDOR_ID_LABTEC           0x1020
 #define USB_DEVICE_ID_LABTEC_WIRELESS_KEYBOARD 0x0006
 #define USB_VENDOR_ID_LOGITECH         0x046d
 #define USB_DEVICE_ID_LOGITECH_AUDIOHUB 0x0a0e
 #define USB_DEVICE_ID_LOGITECH_T651    0xb00c
+#define USB_DEVICE_ID_LOGITECH_C077    0xc007
 #define USB_DEVICE_ID_LOGITECH_RECEIVER        0xc101
 #define USB_DEVICE_ID_LOGITECH_HARMONY_FIRST  0xc110
 #define USB_DEVICE_ID_LOGITECH_HARMONY_LAST 0xc14f
 #define USB_DEVICE_ID_LOGITECH_HARMONY_PS3 0x0306
+#define USB_DEVICE_ID_LOGITECH_MOUSE_C01A      0xc01a
+#define USB_DEVICE_ID_LOGITECH_MOUSE_C05A      0xc05a
+#define USB_DEVICE_ID_LOGITECH_MOUSE_C06A      0xc06a
 #define USB_DEVICE_ID_LOGITECH_RUMBLEPAD_CORD  0xc20a
 #define USB_DEVICE_ID_LOGITECH_RUMBLEPAD       0xc211
 #define USB_DEVICE_ID_LOGITECH_EXTREME_3D      0xc215
 #define USB_VENDOR_ID_TIVO             0x150a
 #define USB_DEVICE_ID_TIVO_SLIDE_BT    0x1200
 #define USB_DEVICE_ID_TIVO_SLIDE       0x1201
+#define USB_DEVICE_ID_TIVO_SLIDE_PRO   0x1203
 
 #define USB_VENDOR_ID_TOPSEED          0x0766
 #define USB_DEVICE_ID_TOPSEED_CYBERLINK        0x0204
 #define USB_DEVICE_ID_ZYTRONIC_ZXY100  0x0005
 
 #define USB_VENDOR_ID_PRIMAX   0x0461
+#define USB_DEVICE_ID_PRIMAX_MOUSE_4D22        0x4d22
 #define USB_DEVICE_ID_PRIMAX_KEYBOARD  0x4e05
 
 
index 052869d..19603ef 100644 (file)
@@ -711,6 +711,29 @@ static void hidinput_configure_usage(struct hid_input *hidinput, struct hid_fiel
                }
                break;
 
+       case HID_UP_TELEPHONY:
+               switch (usage->hid & HID_USAGE) {
+               case 0x2f: map_key_clear(KEY_MICMUTE);          break;
+               case 0xb0: map_key_clear(KEY_NUMERIC_0);        break;
+               case 0xb1: map_key_clear(KEY_NUMERIC_1);        break;
+               case 0xb2: map_key_clear(KEY_NUMERIC_2);        break;
+               case 0xb3: map_key_clear(KEY_NUMERIC_3);        break;
+               case 0xb4: map_key_clear(KEY_NUMERIC_4);        break;
+               case 0xb5: map_key_clear(KEY_NUMERIC_5);        break;
+               case 0xb6: map_key_clear(KEY_NUMERIC_6);        break;
+               case 0xb7: map_key_clear(KEY_NUMERIC_7);        break;
+               case 0xb8: map_key_clear(KEY_NUMERIC_8);        break;
+               case 0xb9: map_key_clear(KEY_NUMERIC_9);        break;
+               case 0xba: map_key_clear(KEY_NUMERIC_STAR);     break;
+               case 0xbb: map_key_clear(KEY_NUMERIC_POUND);    break;
+               case 0xbc: map_key_clear(KEY_NUMERIC_A);        break;
+               case 0xbd: map_key_clear(KEY_NUMERIC_B);        break;
+               case 0xbe: map_key_clear(KEY_NUMERIC_C);        break;
+               case 0xbf: map_key_clear(KEY_NUMERIC_D);        break;
+               default: goto ignore;
+               }
+               break;
+
        case HID_UP_CONSUMER:   /* USB HUT v1.12, pages 75-84 */
                switch (usage->hid & HID_USAGE) {
                case 0x000: goto ignore;
index 158fcf5..32e6d8d 100644 (file)
@@ -268,6 +268,137 @@ static __u8 easypen_m610x_rdesc_fixed[] = {
        0xC0                          /*  End Collection                  */
 };
 
+
+/* Original PenSketch M912 report descriptor size */
+#define PENSKETCH_M912_RDESC_ORIG_SIZE 482
+
+/* Fixed PenSketch M912 report descriptor */
+static __u8 pensketch_m912_rdesc_fixed[] = {
+       0x05, 0x01,                   /*  Usage Page (Desktop),           */
+       0x08,                         /*  Usage (00h),                    */
+       0xA1, 0x01,                   /*  Collection (Application),       */
+       0x85, 0x05,                   /*    Report ID (5),                */
+       0x06, 0x00, 0xFF,             /*    Usage Page (FF00h),           */
+       0x09, 0x01,                   /*    Usage (01h),                  */
+       0x15, 0x81,                   /*    Logical Minimum (-127),       */
+       0x25, 0x7F,                   /*    Logical Maximum (127),        */
+       0x75, 0x08,                   /*    Report Size (8),              */
+       0x95, 0x07,                   /*    Report Count (7),             */
+       0xB1, 0x02,                   /*    Feature (Variable),           */
+       0xC0,                         /*  End Collection,                 */
+       0x05, 0x0D,                   /*  Usage Page (Digitizer),         */
+       0x09, 0x02,                   /*  Usage (Pen),                    */
+       0xA1, 0x01,                   /*  Collection (Application),       */
+       0x85, 0x10,                   /*    Report ID (16),               */
+       0x09, 0x20,                   /*    Usage (Stylus),               */
+       0xA0,                         /*    Collection (Physical),        */
+       0x09, 0x42,                   /*      Usage (Tip Switch),         */
+       0x09, 0x44,                   /*      Usage (Barrel Switch),      */
+       0x09, 0x46,                   /*      Usage (Tablet Pick),        */
+       0x14,                         /*      Logical Minimum (0),        */
+       0x25, 0x01,                   /*      Logical Maximum (1),        */
+       0x75, 0x01,                   /*      Report Size (1),            */
+       0x95, 0x03,                   /*      Report Count (3),           */
+       0x81, 0x02,                   /*      Input (Variable),           */
+       0x95, 0x04,                   /*      Report Count (4),           */
+       0x81, 0x03,                   /*      Input (Constant, Variable), */
+       0x09, 0x32,                   /*      Usage (In Range),           */
+       0x95, 0x01,                   /*      Report Count (1),           */
+       0x81, 0x02,                   /*      Input (Variable),           */
+       0x75, 0x10,                   /*      Report Size (16),           */
+       0x95, 0x01,                   /*      Report Count (1),           */
+       0xA4,                         /*      Push,                       */
+       0x05, 0x01,                   /*      Usage Page (Desktop),       */
+       0x55, 0xFD,                   /*      Unit Exponent (-3),         */
+       0x65, 0x13,                   /*      Unit (Inch),                */
+       0x14,                         /*      Logical Minimum (0),        */
+       0x34,                         /*      Physical Minimum (0),       */
+       0x09, 0x30,                   /*      Usage (X),                  */
+       0x27, 0x00, 0xF0, 0x00, 0x00, /*      Logical Maximum (61440),    */
+       0x46, 0xE0, 0x2E,             /*      Physical Maximum (12000),   */
+       0x81, 0x02,                   /*      Input (Variable),           */
+       0x09, 0x31,                   /*      Usage (Y),                  */
+       0x27, 0x00, 0xB4, 0x00, 0x00, /*      Logical Maximum (46080),    */
+       0x46, 0x28, 0x23,             /*      Physical Maximum (9000),    */
+       0x81, 0x02,                   /*      Input (Variable),           */
+       0xB4,                         /*      Pop,                        */
+       0x09, 0x30,                   /*      Usage (Tip Pressure),       */
+       0x14,                         /*      Logical Minimum (0),        */
+       0x26, 0xFF, 0x07,             /*      Logical Maximum (2047),     */
+       0x81, 0x02,                   /*      Input (Variable),           */
+       0xC0,                         /*    End Collection,               */
+       0xC0,                         /*  End Collection,                 */
+       0x05, 0x0D,                   /*  Usage Page (Digitizer),         */
+       0x09, 0x21,                   /*  Usage (Puck),                   */
+       0xA1, 0x01,                   /*  Collection (Application),       */
+       0x85, 0x11,                   /*    Report ID (17),               */
+       0x09, 0x21,                   /*    Usage (Puck),                 */
+       0xA0,                         /*    Collection (Physical),        */
+       0x05, 0x09,                   /*      Usage Page (Button),        */
+       0x75, 0x01,                   /*      Report Size (1),            */
+       0x19, 0x01,                   /*      Usage Minimum (01h),        */
+       0x29, 0x03,                   /*      Usage Maximum (03h),        */
+       0x14,                         /*      Logical Minimum (0),        */
+       0x25, 0x01,                   /*      Logical Maximum (1),        */
+       0x95, 0x03,                   /*      Report Count (3),           */
+       0x81, 0x02,                   /*      Input (Variable),           */
+       0x95, 0x04,                   /*      Report Count (4),           */
+       0x81, 0x01,                   /*      Input (Constant),           */
+       0x95, 0x01,                   /*      Report Count (1),           */
+       0x0B, 0x32, 0x00, 0x0D, 0x00, /*      Usage (Digitizer In Range), */
+       0x14,                         /*      Logical Minimum (0),        */
+       0x25, 0x01,                   /*      Logical Maximum (1),        */
+       0x81, 0x02,                   /*      Input (Variable),           */
+       0xA4,                         /*      Push,                       */
+       0x05, 0x01,                   /*      Usage Page (Desktop),       */
+       0x75, 0x10,                   /*      Report Size (16),           */
+       0x95, 0x01,                   /*      Report Count (1),           */
+       0x55, 0xFD,                   /*      Unit Exponent (-3),         */
+       0x65, 0x13,                   /*      Unit (Inch),                */
+       0x14,                         /*      Logical Minimum (0),        */
+       0x34,                         /*      Physical Minimum (0),       */
+       0x09, 0x30,                   /*      Usage (X),                  */
+       0x27, 0x00, 0xF0, 0x00, 0x00, /*      Logical Maximum (61440),    */
+       0x46, 0xE0, 0x2E,             /*      Physical Maximum (12000),   */
+       0x81, 0x02,                   /*      Input (Variable),           */
+       0x09, 0x31,                   /*      Usage (Y),                  */
+       0x27, 0x00, 0xB4, 0x00, 0x00, /*      Logical Maximum (46080),    */
+       0x46, 0x28, 0x23,             /*      Physical Maximum (9000),    */
+       0x81, 0x02,                   /*      Input (Variable),           */
+       0x09, 0x38,                   /*      Usage (Wheel),              */
+       0x75, 0x08,                   /*      Report Size (8),            */
+       0x95, 0x01,                   /*      Report Count (1),           */
+       0x15, 0xFF,                   /*      Logical Minimum (-1),       */
+       0x25, 0x01,                   /*      Logical Maximum (1),        */
+       0x34,                         /*      Physical Minimum (0),       */
+       0x44,                         /*      Physical Maximum (0),       */
+       0x81, 0x06,                   /*      Input (Variable, Relative), */
+       0xB4,                         /*      Pop,                        */
+       0xC0,                         /*    End Collection,               */
+       0xC0,                         /*  End Collection,                 */
+       0x05, 0x0C,                   /*  Usage Page (Consumer),          */
+       0x09, 0x01,                   /*  Usage (Consumer Control),       */
+       0xA1, 0x01,                   /*  Collection (Application),       */
+       0x85, 0x12,                   /*    Report ID (18),               */
+       0x14,                         /*    Logical Minimum (0),          */
+       0x25, 0x01,                   /*    Logical Maximum (1),          */
+       0x75, 0x01,                   /*    Report Size (1),              */
+       0x95, 0x08,                   /*    Report Count (8),             */
+       0x05, 0x0C,                   /*    Usage Page (Consumer),        */
+       0x0A, 0x6A, 0x02,             /*    Usage (AC Delete),            */
+       0x0A, 0x1A, 0x02,             /*    Usage (AC Undo),              */
+       0x0A, 0x01, 0x02,             /*    Usage (AC New),               */
+       0x0A, 0x2F, 0x02,             /*    Usage (AC Zoom),              */
+       0x0A, 0x25, 0x02,             /*    Usage (AC Forward),           */
+       0x0A, 0x24, 0x02,             /*    Usage (AC Back),              */
+       0x0A, 0x2D, 0x02,             /*    Usage (AC Zoom In),           */
+       0x0A, 0x2E, 0x02,             /*    Usage (AC Zoom Out),          */
+       0x81, 0x02,                   /*    Input (Variable),             */
+       0x95, 0x30,                   /*    Report Count (48),            */
+       0x81, 0x03,                   /*    Input (Constant, Variable),   */
+       0xC0                          /*  End Collection                  */
+};
+
 static __u8 *kye_consumer_control_fixup(struct hid_device *hdev, __u8 *rdesc,
                unsigned int *rsize, int offset, const char *device_name) {
        /*
@@ -335,6 +466,12 @@ static __u8 *kye_report_fixup(struct hid_device *hdev, __u8 *rdesc,
                        *rsize = sizeof(easypen_m610x_rdesc_fixed);
                }
                break;
+       case USB_DEVICE_ID_KYE_PENSKETCH_M912:
+               if (*rsize == PENSKETCH_M912_RDESC_ORIG_SIZE) {
+                       rdesc = pensketch_m912_rdesc_fixed;
+                       *rsize = sizeof(pensketch_m912_rdesc_fixed);
+               }
+               break;
        case USB_DEVICE_ID_GENIUS_GILA_GAMING_MOUSE:
                rdesc = kye_consumer_control_fixup(hdev, rdesc, rsize, 104,
                                        "Genius Gila Gaming Mouse");
@@ -418,6 +555,7 @@ static int kye_probe(struct hid_device *hdev, const struct hid_device_id *id)
        case USB_DEVICE_ID_KYE_MOUSEPEN_I608X:
        case USB_DEVICE_ID_KYE_MOUSEPEN_I608X_2:
        case USB_DEVICE_ID_KYE_EASYPEN_M610X:
+       case USB_DEVICE_ID_KYE_PENSKETCH_M912:
                ret = kye_tablet_enable(hdev);
                if (ret) {
                        hid_err(hdev, "tablet enabling failed\n");
@@ -457,6 +595,8 @@ static const struct hid_device_id kye_devices[] = {
                                USB_DEVICE_ID_GENIUS_GX_IMPERATOR) },
        { HID_USB_DEVICE(USB_VENDOR_ID_KYE,
                                USB_DEVICE_ID_GENIUS_MANTICORE) },
+       { HID_USB_DEVICE(USB_VENDOR_ID_KYE,
+                               USB_DEVICE_ID_KYE_PENSKETCH_M912) },
        { }
 };
 MODULE_DEVICE_TABLE(hid, kye_devices);
index f91ff14..b86c18e 100644 (file)
@@ -27,6 +27,7 @@
 #include "usbhid/usbhid.h"
 #include "hid-ids.h"
 #include "hid-lg.h"
+#include "hid-lg4ff.h"
 
 #define LG_RDESC               0x001
 #define LG_BAD_RELATIVE_KEYS   0x002
@@ -818,4 +819,10 @@ static struct hid_driver lg_driver = {
 };
 module_hid_driver(lg_driver);
 
+#ifdef CONFIG_LOGIWHEELS_FF
+int lg4ff_no_autoswitch = 0;
+module_param_named(lg4ff_no_autoswitch, lg4ff_no_autoswitch, int, S_IRUGO);
+MODULE_PARM_DESC(lg4ff_no_autoswitch, "Do not switch multimode wheels to their native mode automatically");
+#endif
+
 MODULE_LICENSE("GPL");
index 142ce3f..10dd8f0 100644 (file)
@@ -24,16 +24,4 @@ int lg3ff_init(struct hid_device *hdev);
 static inline int lg3ff_init(struct hid_device *hdev) { return -1; }
 #endif
 
-#ifdef CONFIG_LOGIWHEELS_FF
-int lg4ff_adjust_input_event(struct hid_device *hid, struct hid_field *field,
-                            struct hid_usage *usage, __s32 value, struct lg_drv_data *drv_data);
-int lg4ff_init(struct hid_device *hdev);
-int lg4ff_deinit(struct hid_device *hdev);
-#else
-static inline int lg4ff_adjust_input_event(struct hid_device *hid, struct hid_field *field,
-                                          struct hid_usage *usage, __s32 value, struct lg_drv_data *drv_data) { return 0; }
-static inline int lg4ff_init(struct hid_device *hdev) { return -1; }
-static inline int lg4ff_deinit(struct hid_device *hdev) { return -1; }
-#endif
-
 #endif
index db0dd9b..1232210 100644 (file)
 
 #include "usbhid/usbhid.h"
 #include "hid-lg.h"
+#include "hid-lg4ff.h"
 #include "hid-ids.h"
 
-#define DFGT_REV_MAJ 0x13
-#define DFGT_REV_MIN 0x22
-#define DFGT2_REV_MIN 0x26
-#define DFP_REV_MAJ 0x11
-#define DFP_REV_MIN 0x06
-#define FFEX_REV_MAJ 0x21
-#define FFEX_REV_MIN 0x00
-#define G25_REV_MAJ 0x12
-#define G25_REV_MIN 0x22
-#define G27_REV_MAJ 0x12
-#define G27_REV_MIN 0x38
-#define G27_2_REV_MIN 0x39
-
 #define to_hid_device(pdev) container_of(pdev, struct hid_device, dev)
 
+#define LG4FF_MMODE_IS_MULTIMODE 0
+#define LG4FF_MMODE_SWITCHED 1
+#define LG4FF_MMODE_NOT_MULTIMODE 2
+
+#define LG4FF_MODE_NATIVE_IDX 0
+#define LG4FF_MODE_DFEX_IDX 1
+#define LG4FF_MODE_DFP_IDX 2
+#define LG4FF_MODE_G25_IDX 3
+#define LG4FF_MODE_DFGT_IDX 4
+#define LG4FF_MODE_G27_IDX 5
+#define LG4FF_MODE_MAX_IDX 6
+
+#define LG4FF_MODE_NATIVE BIT(LG4FF_MODE_NATIVE_IDX)
+#define LG4FF_MODE_DFEX BIT(LG4FF_MODE_DFEX_IDX)
+#define LG4FF_MODE_DFP BIT(LG4FF_MODE_DFP_IDX)
+#define LG4FF_MODE_G25 BIT(LG4FF_MODE_G25_IDX)
+#define LG4FF_MODE_DFGT BIT(LG4FF_MODE_DFGT_IDX)
+#define LG4FF_MODE_G27 BIT(LG4FF_MODE_G27_IDX)
+
+#define LG4FF_DFEX_TAG "DF-EX"
+#define LG4FF_DFEX_NAME "Driving Force / Formula EX"
+#define LG4FF_DFP_TAG "DFP"
+#define LG4FF_DFP_NAME "Driving Force Pro"
+#define LG4FF_G25_TAG "G25"
+#define LG4FF_G25_NAME "G25 Racing Wheel"
+#define LG4FF_G27_TAG "G27"
+#define LG4FF_G27_NAME "G27 Racing Wheel"
+#define LG4FF_DFGT_TAG "DFGT"
+#define LG4FF_DFGT_NAME "Driving Force GT"
+
+#define LG4FF_FFEX_REV_MAJ 0x21
+#define LG4FF_FFEX_REV_MIN 0x00
+
 static void hid_lg4ff_set_range_dfp(struct hid_device *hid, u16 range);
 static void hid_lg4ff_set_range_g25(struct hid_device *hid, u16 range);
 
@@ -59,6 +80,10 @@ struct lg4ff_device_entry {
        __u8  led_state;
        struct led_classdev *led[5];
 #endif
+       u32 alternate_modes;
+       const char *real_tag;
+       const char *real_name;
+       u16 real_product_id;
        struct list_head list;
        void (*set_range)(struct hid_device *hid, u16 range);
 };
@@ -77,6 +102,35 @@ struct lg4ff_wheel {
        void (*set_range)(struct hid_device *hid, u16 range);
 };
 
+struct lg4ff_compat_mode_switch {
+       const __u8 cmd_count;   /* Number of commands to send */
+       const __u8 cmd[];
+};
+
+struct lg4ff_wheel_ident_info {
+       const u16 mask;
+       const u16 result;
+       const u16 real_product_id;
+};
+
+struct lg4ff_wheel_ident_checklist {
+       const u32 count;
+       const struct lg4ff_wheel_ident_info *models[];
+};
+
+struct lg4ff_multimode_wheel {
+       const u16 product_id;
+       const u32 alternate_modes;
+       const char *real_tag;
+       const char *real_name;
+};
+
+struct lg4ff_alternate_mode {
+       const u16 product_id;
+       const char *tag;
+       const char *name;
+};
+
 static const struct lg4ff_wheel lg4ff_devices[] = {
        {USB_DEVICE_ID_LOGITECH_WHEEL,       lg4ff_wheel_effects, 40, 270, NULL},
        {USB_DEVICE_ID_LOGITECH_MOMO_WHEEL,  lg4ff_wheel_effects, 40, 270, NULL},
@@ -88,46 +142,106 @@ static const struct lg4ff_wheel lg4ff_devices[] = {
        {USB_DEVICE_ID_LOGITECH_WII_WHEEL,   lg4ff_wheel_effects, 40, 270, NULL}
 };
 
-struct lg4ff_native_cmd {
-       const __u8 cmd_num;     /* Number of commands to send */
-       const __u8 cmd[];
+static const struct lg4ff_multimode_wheel lg4ff_multimode_wheels[] = {
+       {USB_DEVICE_ID_LOGITECH_DFP_WHEEL,
+        LG4FF_MODE_NATIVE | LG4FF_MODE_DFP | LG4FF_MODE_DFEX,
+        LG4FF_DFP_TAG, LG4FF_DFP_NAME},
+       {USB_DEVICE_ID_LOGITECH_G25_WHEEL,
+        LG4FF_MODE_NATIVE | LG4FF_MODE_G25 | LG4FF_MODE_DFP | LG4FF_MODE_DFEX,
+        LG4FF_G25_TAG, LG4FF_G25_NAME},
+       {USB_DEVICE_ID_LOGITECH_DFGT_WHEEL,
+        LG4FF_MODE_NATIVE | LG4FF_MODE_DFGT | LG4FF_MODE_DFP | LG4FF_MODE_DFEX,
+        LG4FF_DFGT_TAG, LG4FF_DFGT_NAME},
+       {USB_DEVICE_ID_LOGITECH_G27_WHEEL,
+        LG4FF_MODE_NATIVE | LG4FF_MODE_G27 | LG4FF_MODE_G25 | LG4FF_MODE_DFP | LG4FF_MODE_DFEX,
+        LG4FF_G27_TAG, LG4FF_G27_NAME},
 };
 
-struct lg4ff_usb_revision {
-       const __u16 rev_maj;
-       const __u16 rev_min;
-       const struct lg4ff_native_cmd *command;
+static const struct lg4ff_alternate_mode lg4ff_alternate_modes[] = {
+       [LG4FF_MODE_NATIVE_IDX] = {0, "native", ""},
+       [LG4FF_MODE_DFEX_IDX] = {USB_DEVICE_ID_LOGITECH_WHEEL, LG4FF_DFEX_TAG, LG4FF_DFEX_NAME},
+       [LG4FF_MODE_DFP_IDX] = {USB_DEVICE_ID_LOGITECH_DFP_WHEEL, LG4FF_DFP_TAG, LG4FF_DFP_NAME},
+       [LG4FF_MODE_G25_IDX] = {USB_DEVICE_ID_LOGITECH_G25_WHEEL, LG4FF_G25_TAG, LG4FF_G25_NAME},
+       [LG4FF_MODE_DFGT_IDX] = {USB_DEVICE_ID_LOGITECH_DFGT_WHEEL, LG4FF_DFGT_TAG, LG4FF_DFGT_NAME},
+       [LG4FF_MODE_G27_IDX] = {USB_DEVICE_ID_LOGITECH_G27_WHEEL, LG4FF_G27_TAG, LG4FF_G27_NAME}
 };
 
-static const struct lg4ff_native_cmd native_dfp = {
-       1,
-       {0xf8, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00}
+/* Multimode wheel identificators */
+static const struct lg4ff_wheel_ident_info lg4ff_dfp_ident_info = {
+       0xf000,
+       0x1000,
+       USB_DEVICE_ID_LOGITECH_DFP_WHEEL
+};
+
+static const struct lg4ff_wheel_ident_info lg4ff_g25_ident_info = {
+       0xff00,
+       0x1200,
+       USB_DEVICE_ID_LOGITECH_G25_WHEEL
+};
+
+static const struct lg4ff_wheel_ident_info lg4ff_g27_ident_info = {
+       0xfff0,
+       0x1230,
+       USB_DEVICE_ID_LOGITECH_G27_WHEEL
 };
 
-static const struct lg4ff_native_cmd native_dfgt = {
+static const struct lg4ff_wheel_ident_info lg4ff_dfgt_ident_info = {
+       0xff00,
+       0x1300,
+       USB_DEVICE_ID_LOGITECH_DFGT_WHEEL
+};
+
+/* Multimode wheel identification checklists */
+static const struct lg4ff_wheel_ident_checklist lg4ff_main_checklist = {
+       4,
+       {&lg4ff_dfgt_ident_info,
+        &lg4ff_g27_ident_info,
+        &lg4ff_g25_ident_info,
+        &lg4ff_dfp_ident_info}
+};
+
+/* Compatibility mode switching commands */
+/* EXT_CMD9 - Understood by G27 and DFGT */
+static const struct lg4ff_compat_mode_switch lg4ff_mode_switch_ext09_dfex = {
        2,
-       {0xf8, 0x0a, 0x00, 0x00, 0x00, 0x00, 0x00,      /* 1st command */
-        0xf8, 0x09, 0x03, 0x01, 0x00, 0x00, 0x00}      /* 2nd command */
+       {0xf8, 0x0a, 0x00, 0x00, 0x00, 0x00, 0x00,      /* Revert mode upon USB reset */
+        0xf8, 0x09, 0x00, 0x01, 0x00, 0x00, 0x00}      /* Switch mode to DF-EX with detach */
 };
 
-static const struct lg4ff_native_cmd native_g25 = {
-       1,
-       {0xf8, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00}
+static const struct lg4ff_compat_mode_switch lg4ff_mode_switch_ext09_dfp = {
+       2,
+       {0xf8, 0x0a, 0x00, 0x00, 0x00, 0x00, 0x00,      /* Revert mode upon USB reset */
+        0xf8, 0x09, 0x01, 0x01, 0x00, 0x00, 0x00}      /* Switch mode to DFP with detach */
+};
+
+static const struct lg4ff_compat_mode_switch lg4ff_mode_switch_ext09_g25 = {
+       2,
+       {0xf8, 0x0a, 0x00, 0x00, 0x00, 0x00, 0x00,      /* Revert mode upon USB reset */
+        0xf8, 0x09, 0x02, 0x01, 0x00, 0x00, 0x00}      /* Switch mode to G25 with detach */
+};
+
+static const struct lg4ff_compat_mode_switch lg4ff_mode_switch_ext09_dfgt = {
+       2,
+       {0xf8, 0x0a, 0x00, 0x00, 0x00, 0x00, 0x00,      /* Revert mode upon USB reset */
+        0xf8, 0x09, 0x03, 0x01, 0x00, 0x00, 0x00}      /* Switch mode to DFGT with detach */
 };
 
-static const struct lg4ff_native_cmd native_g27 = {
+static const struct lg4ff_compat_mode_switch lg4ff_mode_switch_ext09_g27 = {
        2,
-       {0xf8, 0x0a, 0x00, 0x00, 0x00, 0x00, 0x00,      /* 1st command */
-        0xf8, 0x09, 0x04, 0x01, 0x00, 0x00, 0x00}      /* 2nd command */
+       {0xf8, 0x0a, 0x00, 0x00, 0x00, 0x00, 0x00,      /* Revert mode upon USB reset */
+        0xf8, 0x09, 0x04, 0x01, 0x00, 0x00, 0x00}      /* Switch mode to G27 with detach */
 };
 
-static const struct lg4ff_usb_revision lg4ff_revs[] = {
-       {DFGT_REV_MAJ, DFGT_REV_MIN, &native_dfgt},     /* Driving Force GT */
-       {DFGT_REV_MAJ, DFGT2_REV_MIN, &native_dfgt},    /* Driving Force GT v2 */
-       {DFP_REV_MAJ,  DFP_REV_MIN,  &native_dfp},      /* Driving Force Pro */
-       {G25_REV_MAJ,  G25_REV_MIN,  &native_g25},      /* G25 */
-       {G27_REV_MAJ,  G27_REV_MIN,  &native_g27},      /* G27 */
-       {G27_REV_MAJ,  G27_2_REV_MIN,  &native_g27},    /* G27 v2 */
+/* EXT_CMD1 - Understood by DFP, G25, G27 and DFGT */
+static const struct lg4ff_compat_mode_switch lg4ff_mode_switch_ext01_dfp = {
+       1,
+       {0xf8, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00}
+};
+
+/* EXT_CMD16 - Understood by G25 and G27 */
+static const struct lg4ff_compat_mode_switch lg4ff_mode_switch_ext16_g25 = {
+       1,
+       {0xf8, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00}
 };
 
 /* Recalculates X axis value accordingly to currently selected range */
@@ -396,20 +510,216 @@ static void hid_lg4ff_set_range_dfp(struct hid_device *hid, __u16 range)
        hid_hw_request(hid, report, HID_REQ_SET_REPORT);
 }
 
-static void hid_lg4ff_switch_native(struct hid_device *hid, const struct lg4ff_native_cmd *cmd)
+static const struct lg4ff_compat_mode_switch *lg4ff_get_mode_switch_command(const u16 real_product_id, const u16 target_product_id)
+{
+       switch (real_product_id) {
+       case USB_DEVICE_ID_LOGITECH_DFP_WHEEL:
+               switch (target_product_id) {
+               case USB_DEVICE_ID_LOGITECH_DFP_WHEEL:
+                       return &lg4ff_mode_switch_ext01_dfp;
+               /* DFP can only be switched to its native mode */
+               default:
+                       return NULL;
+               }
+               break;
+       case USB_DEVICE_ID_LOGITECH_G25_WHEEL:
+               switch (target_product_id) {
+               case USB_DEVICE_ID_LOGITECH_DFP_WHEEL:
+                       return &lg4ff_mode_switch_ext01_dfp;
+               case USB_DEVICE_ID_LOGITECH_G25_WHEEL:
+                       return &lg4ff_mode_switch_ext16_g25;
+               /* G25 can only be switched to DFP mode or its native mode */
+               default:
+                       return NULL;
+               }
+               break;
+       case USB_DEVICE_ID_LOGITECH_G27_WHEEL:
+               switch (target_product_id) {
+               case USB_DEVICE_ID_LOGITECH_WHEEL:
+                       return &lg4ff_mode_switch_ext09_dfex;
+               case USB_DEVICE_ID_LOGITECH_DFP_WHEEL:
+                       return &lg4ff_mode_switch_ext09_dfp;
+               case USB_DEVICE_ID_LOGITECH_G25_WHEEL:
+                       return &lg4ff_mode_switch_ext09_g25;
+               case USB_DEVICE_ID_LOGITECH_G27_WHEEL:
+                       return &lg4ff_mode_switch_ext09_g27;
+               /* G27 can only be switched to DF-EX, DFP, G25 or its native mode */
+               default:
+                       return NULL;
+               }
+               break;
+       case USB_DEVICE_ID_LOGITECH_DFGT_WHEEL:
+               switch (target_product_id) {
+               case USB_DEVICE_ID_LOGITECH_WHEEL:
+                       return &lg4ff_mode_switch_ext09_dfex;
+               case USB_DEVICE_ID_LOGITECH_DFP_WHEEL:
+                       return &lg4ff_mode_switch_ext09_dfp;
+               case USB_DEVICE_ID_LOGITECH_DFGT_WHEEL:
+                       return &lg4ff_mode_switch_ext09_dfgt;
+               /* DFGT can only be switched to DF-EX, DFP or its native mode */
+               default:
+                       return NULL;
+               }
+               break;
+       /* No other wheels have multiple modes */
+       default:
+               return NULL;
+       }
+}
+
+static int lg4ff_switch_compatibility_mode(struct hid_device *hid, const struct lg4ff_compat_mode_switch *s)
 {
        struct list_head *report_list = &hid->report_enum[HID_OUTPUT_REPORT].report_list;
        struct hid_report *report = list_entry(report_list->next, struct hid_report, list);
-       __u8 i, j;
+       __s32 *value = report->field[0]->value;
+       u8 i;
 
-       j = 0;
-       while (j < 7*cmd->cmd_num) {
-               for (i = 0; i < 7; i++)
-                       report->field[0]->value[i] = cmd->cmd[j++];
+       for (i = 0; i < s->cmd_count; i++) {
+               u8 j;
+
+               for (j = 0; j < 7; j++)
+                       value[j] = s->cmd[j + (7*i)];
 
                hid_hw_request(hid, report, HID_REQ_SET_REPORT);
        }
+       hid_hw_wait(hid);
+       return 0;
+}
+
+static ssize_t lg4ff_alternate_modes_show(struct device *dev, struct device_attribute *attr, char *buf)
+{
+       struct hid_device *hid = to_hid_device(dev);
+       struct lg4ff_device_entry *entry;
+       struct lg_drv_data *drv_data;
+       ssize_t count = 0;
+       int i;
+
+       drv_data = hid_get_drvdata(hid);
+       if (!drv_data) {
+               hid_err(hid, "Private driver data not found!\n");
+               return 0;
+       }
+
+       entry = drv_data->device_props;
+       if (!entry) {
+               hid_err(hid, "Device properties not found!\n");
+               return 0;
+       }
+
+       if (!entry->real_name) {
+               hid_err(hid, "NULL pointer to string\n");
+               return 0;
+       }
+
+       for (i = 0; i < LG4FF_MODE_MAX_IDX; i++) {
+               if (entry->alternate_modes & BIT(i)) {
+                       /* Print tag and full name */
+                       count += scnprintf(buf + count, PAGE_SIZE - count, "%s: %s",
+                                          lg4ff_alternate_modes[i].tag,
+                                          !lg4ff_alternate_modes[i].product_id ? entry->real_name : lg4ff_alternate_modes[i].name);
+                       if (count >= PAGE_SIZE - 1)
+                               return count;
+
+                       /* Mark the currently active mode with an asterisk */
+                       if (lg4ff_alternate_modes[i].product_id == entry->product_id ||
+                           (lg4ff_alternate_modes[i].product_id == 0 && entry->product_id == entry->real_product_id))
+                               count += scnprintf(buf + count, PAGE_SIZE - count, " *\n");
+                       else
+                               count += scnprintf(buf + count, PAGE_SIZE - count, "\n");
+
+                       if (count >= PAGE_SIZE - 1)
+                               return count;
+               }
+       }
+
+       return count;
+}
+
+static ssize_t lg4ff_alternate_modes_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t count)
+{
+       struct hid_device *hid = to_hid_device(dev);
+       struct lg4ff_device_entry *entry;
+       struct lg_drv_data *drv_data;
+       const struct lg4ff_compat_mode_switch *s;
+       u16 target_product_id = 0;
+       int i, ret;
+       char *lbuf;
+
+       drv_data = hid_get_drvdata(hid);
+       if (!drv_data) {
+               hid_err(hid, "Private driver data not found!\n");
+               return -EINVAL;
+       }
+
+       entry = drv_data->device_props;
+       if (!entry) {
+               hid_err(hid, "Device properties not found!\n");
+               return -EINVAL;
+       }
+
+       /* Allow \n at the end of the input parameter */
+       lbuf = kasprintf(GFP_KERNEL, "%s", buf);
+       if (!lbuf)
+               return -ENOMEM;
+
+       i = strlen(lbuf);
+       if (lbuf[i-1] == '\n') {
+               if (i == 1) {
+                       kfree(lbuf);
+                       return -EINVAL;
+               }
+               lbuf[i-1] = '\0';
+       }
+
+       for (i = 0; i < LG4FF_MODE_MAX_IDX; i++) {
+               const u16 mode_product_id = lg4ff_alternate_modes[i].product_id;
+               const char *tag = lg4ff_alternate_modes[i].tag;
+
+               if (entry->alternate_modes & BIT(i)) {
+                       if (!strcmp(tag, lbuf)) {
+                               if (!mode_product_id)
+                                       target_product_id = entry->real_product_id;
+                               else
+                                       target_product_id = mode_product_id;
+                               break;
+                       }
+               }
+       }
+
+       if (i == LG4FF_MODE_MAX_IDX) {
+               hid_info(hid, "Requested mode \"%s\" is not supported by the device\n", lbuf);
+               kfree(lbuf);
+               return -EINVAL;
+       }
+       kfree(lbuf); /* Not needed anymore */
+
+       if (target_product_id == entry->product_id) /* Nothing to do */
+               return count;
+
+       /* Automatic switching has to be disabled for the switch to DF-EX mode to work correctly */
+       if (target_product_id == USB_DEVICE_ID_LOGITECH_WHEEL && !lg4ff_no_autoswitch) {
+               hid_info(hid, "\"%s\" cannot be switched to \"DF-EX\" mode. Load the \"hid_logitech\" module with \"lg4ff_no_autoswitch=1\" parameter set and try again\n",
+                        entry->real_name);
+               return -EINVAL;
+       }
+
+       /* Take care of hardware limitations */
+       if ((entry->real_product_id == USB_DEVICE_ID_LOGITECH_DFP_WHEEL || entry->real_product_id == USB_DEVICE_ID_LOGITECH_G25_WHEEL) &&
+           entry->product_id > target_product_id) {
+               hid_info(hid, "\"%s\" cannot be switched back into \"%s\" mode\n", entry->real_name, lg4ff_alternate_modes[i].name);
+               return -EINVAL;
+       }
+
+       s = lg4ff_get_mode_switch_command(entry->real_product_id, target_product_id);
+       if (!s) {
+               hid_err(hid, "Invalid target product ID %X\n", target_product_id);
+               return -EINVAL;
+       }
+
+       ret = lg4ff_switch_compatibility_mode(hid, s);
+       return (ret == 0 ? count : ret);
 }
+static DEVICE_ATTR(alternate_modes, S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH, lg4ff_alternate_modes_show, lg4ff_alternate_modes_store);
 
 /* Read current range and display it in terminal */
 static ssize_t range_show(struct device *dev, struct device_attribute *attr,
@@ -472,6 +782,41 @@ static ssize_t range_store(struct device *dev, struct device_attribute *attr,
 }
 static DEVICE_ATTR_RW(range);
 
+static ssize_t lg4ff_real_id_show(struct device *dev, struct device_attribute *attr, char *buf)
+{
+       struct hid_device *hid = to_hid_device(dev);
+       struct lg4ff_device_entry *entry;
+       struct lg_drv_data *drv_data;
+       size_t count;
+
+       drv_data = hid_get_drvdata(hid);
+       if (!drv_data) {
+               hid_err(hid, "Private driver data not found!\n");
+               return 0;
+       }
+
+       entry = drv_data->device_props;
+       if (!entry) {
+               hid_err(hid, "Device properties not found!\n");
+               return 0;
+       }
+
+       if (!entry->real_tag || !entry->real_name) {
+               hid_err(hid, "NULL pointer to string\n");
+               return 0;
+       }
+
+       count = scnprintf(buf, PAGE_SIZE, "%s: %s\n", entry->real_tag, entry->real_name);
+       return count;
+}
+
+static ssize_t lg4ff_real_id_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t count)
+{
+       /* Real ID is a read-only value */
+       return -EPERM;
+}
+static DEVICE_ATTR(real_id, S_IRUGO, lg4ff_real_id_show, lg4ff_real_id_store);
+
 #ifdef CONFIG_LEDS_CLASS
 static void lg4ff_set_leds(struct hid_device *hid, __u8 leds)
 {
@@ -555,20 +900,119 @@ static enum led_brightness lg4ff_led_get_brightness(struct led_classdev *led_cde
 }
 #endif
 
+static u16 lg4ff_identify_multimode_wheel(struct hid_device *hid, const u16 reported_product_id, const u16 bcdDevice)
+{
+       const struct lg4ff_wheel_ident_checklist *checklist;
+       int i, from_idx, to_idx;
+
+       switch (reported_product_id) {
+       case USB_DEVICE_ID_LOGITECH_WHEEL:
+       case USB_DEVICE_ID_LOGITECH_DFP_WHEEL:
+               checklist = &lg4ff_main_checklist;
+               from_idx = 0;
+               to_idx = checklist->count - 1;
+               break;
+       case USB_DEVICE_ID_LOGITECH_G25_WHEEL:
+               checklist = &lg4ff_main_checklist;
+               from_idx = 0;
+               to_idx = checklist->count - 2; /* End identity check at G25 */
+               break;
+       case USB_DEVICE_ID_LOGITECH_G27_WHEEL:
+               checklist = &lg4ff_main_checklist;
+               from_idx = 1; /* Start identity check at G27 */
+               to_idx = checklist->count - 3; /* End identity check at G27 */
+               break;
+       case USB_DEVICE_ID_LOGITECH_DFGT_WHEEL:
+               checklist = &lg4ff_main_checklist;
+               from_idx = 0;
+               to_idx = checklist->count - 4; /* End identity check at DFGT */
+               break;
+       default:
+               return 0;
+       }
+
+       for (i = from_idx; i <= to_idx; i++) {
+               const u16 mask = checklist->models[i]->mask;
+               const u16 result = checklist->models[i]->result;
+               const u16 real_product_id = checklist->models[i]->real_product_id;
+
+               if ((bcdDevice & mask) == result) {
+                       dbg_hid("Found wheel with real PID %X whose reported PID is %X\n", real_product_id, reported_product_id);
+                       return real_product_id;
+               }
+       }
+
+       /* No match found. This is either Driving Force or an unknown
+        * wheel model, do not touch it */
+       dbg_hid("Wheel with bcdDevice %X was not recognized as multimode wheel, leaving in its current mode\n", bcdDevice);
+       return 0;
+}
+
+static int lg4ff_handle_multimode_wheel(struct hid_device *hid, u16 *real_product_id, const u16 bcdDevice)
+{
+       const u16 reported_product_id = hid->product;
+       int ret;
+
+       *real_product_id = lg4ff_identify_multimode_wheel(hid, reported_product_id, bcdDevice);
+       /* Probed wheel is not a multimode wheel */
+       if (!*real_product_id) {
+               *real_product_id = reported_product_id;
+               dbg_hid("Wheel is not a multimode wheel\n");
+               return LG4FF_MMODE_NOT_MULTIMODE;
+       }
+
+       /* Switch from "Driving Force" mode to native mode automatically.
+        * Otherwise keep the wheel in its current mode */
+       if (reported_product_id == USB_DEVICE_ID_LOGITECH_WHEEL &&
+           reported_product_id != *real_product_id &&
+           !lg4ff_no_autoswitch) {
+               const struct lg4ff_compat_mode_switch *s = lg4ff_get_mode_switch_command(*real_product_id, *real_product_id);
+
+               if (!s) {
+                       hid_err(hid, "Invalid product id %X\n", *real_product_id);
+                       return LG4FF_MMODE_NOT_MULTIMODE;
+               }
+
+               ret = lg4ff_switch_compatibility_mode(hid, s);
+               if (ret) {
+                       /* Wheel could not have been switched to native mode,
+                        * leave it in "Driving Force" mode and continue */
+                       hid_err(hid, "Unable to switch wheel mode, errno %d\n", ret);
+                       return LG4FF_MMODE_IS_MULTIMODE;
+               }
+               return LG4FF_MMODE_SWITCHED;
+       }
+
+       return LG4FF_MMODE_IS_MULTIMODE;
+}
+
+
 int lg4ff_init(struct hid_device *hid)
 {
        struct hid_input *hidinput = list_entry(hid->inputs.next, struct hid_input, list);
        struct input_dev *dev = hidinput->input;
+       const struct usb_device_descriptor *udesc = &(hid_to_usb_dev(hid)->descriptor);
+       const u16 bcdDevice = le16_to_cpu(udesc->bcdDevice);
        struct lg4ff_device_entry *entry;
        struct lg_drv_data *drv_data;
-       struct usb_device_descriptor *udesc;
        int error, i, j;
-       __u16 bcdDevice, rev_maj, rev_min;
+       int mmode_ret, mmode_idx = -1;
+       u16 real_product_id;
 
        /* Check that the report looks ok */
        if (!hid_validate_values(hid, HID_OUTPUT_REPORT, 0, 0, 7))
                return -1;
 
+       /* Check if a multimode wheel has been connected and
+        * handle it appropriately */
+       mmode_ret = lg4ff_handle_multimode_wheel(hid, &real_product_id, bcdDevice);
+
+       /* Wheel has been told to switch to native mode. There is no point in going on
+        * with the initialization as the wheel will do a USB reset when it switches mode
+        */
+       if (mmode_ret == LG4FF_MMODE_SWITCHED)
+               return 0;
+
        /* Check what wheel has been connected */
        for (i = 0; i < ARRAY_SIZE(lg4ff_devices); i++) {
                if (hid->product == lg4ff_devices[i].product_id) {
@@ -583,25 +1027,15 @@ int lg4ff_init(struct hid_device *hid)
                return -1;
        }
 
-       /* Attempt to switch wheel to native mode when applicable */
-       udesc = &(hid_to_usb_dev(hid)->descriptor);
-       if (!udesc) {
-               hid_err(hid, "NULL USB device descriptor\n");
-               return -1;
-       }
-       bcdDevice = le16_to_cpu(udesc->bcdDevice);
-       rev_maj = bcdDevice >> 8;
-       rev_min = bcdDevice & 0xff;
-
-       if (lg4ff_devices[i].product_id == USB_DEVICE_ID_LOGITECH_WHEEL) {
-               dbg_hid("Generic wheel detected, can it do native?\n");
-               dbg_hid("USB revision: %2x.%02x\n", rev_maj, rev_min);
+       if (mmode_ret == LG4FF_MMODE_IS_MULTIMODE) {
+               for (mmode_idx = 0; mmode_idx < ARRAY_SIZE(lg4ff_multimode_wheels); mmode_idx++) {
+                       if (real_product_id == lg4ff_multimode_wheels[mmode_idx].product_id)
+                               break;
+               }
 
-               for (j = 0; j < ARRAY_SIZE(lg4ff_revs); j++) {
-                       if (lg4ff_revs[j].rev_maj == rev_maj && lg4ff_revs[j].rev_min == rev_min) {
-                               hid_lg4ff_switch_native(hid, lg4ff_revs[j].command);
-                               hid_info(hid, "Switched to native mode\n");
-                       }
+               if (mmode_idx == ARRAY_SIZE(lg4ff_multimode_wheels)) {
+                       hid_err(hid, "Device product ID %X is not listed as a multimode wheel", real_product_id);
+                       return -1;
                }
        }
 
@@ -630,14 +1064,23 @@ int lg4ff_init(struct hid_device *hid)
        drv_data->device_props = entry;
 
        entry->product_id = lg4ff_devices[i].product_id;
+       entry->real_product_id = real_product_id;
        entry->min_range = lg4ff_devices[i].min_range;
        entry->max_range = lg4ff_devices[i].max_range;
        entry->set_range = lg4ff_devices[i].set_range;
+       if (mmode_ret == LG4FF_MMODE_IS_MULTIMODE) {
+               BUG_ON(mmode_idx == -1);
+               entry->alternate_modes = lg4ff_multimode_wheels[mmode_idx].alternate_modes;
+               entry->real_tag = lg4ff_multimode_wheels[mmode_idx].real_tag;
+               entry->real_name = lg4ff_multimode_wheels[mmode_idx].real_name;
+       }
 
        /* Check if autocentering is available and
         * set the centering force to zero by default */
        if (test_bit(FF_AUTOCENTER, dev->ffbit)) {
-               if (rev_maj == FFEX_REV_MAJ && rev_min == FFEX_REV_MIN) /* Formula Force EX expects different autocentering command */
+               /* Formula Force EX expects different autocentering command */
+               if ((bcdDevice >> 8) == LG4FF_FFEX_REV_MAJ &&
+                   (bcdDevice & 0xff) == LG4FF_FFEX_REV_MIN)
                        dev->ff->set_autocenter = hid_lg4ff_set_autocenter_ffex;
                else
                        dev->ff->set_autocenter = hid_lg4ff_set_autocenter_default;
@@ -649,6 +1092,14 @@ int lg4ff_init(struct hid_device *hid)
        error = device_create_file(&hid->dev, &dev_attr_range);
        if (error)
                return error;
+       if (mmode_ret == LG4FF_MMODE_IS_MULTIMODE) {
+               error = device_create_file(&hid->dev, &dev_attr_real_id);
+               if (error)
+                       return error;
+               error = device_create_file(&hid->dev, &dev_attr_alternate_modes);
+               if (error)
+                       return error;
+       }
        dbg_hid("sysfs interface created\n");
 
        /* Set the maximum range to start with */
@@ -711,24 +1162,26 @@ out:
        return 0;
 }
 
-
-
 int lg4ff_deinit(struct hid_device *hid)
 {
        struct lg4ff_device_entry *entry;
        struct lg_drv_data *drv_data;
 
-       device_remove_file(&hid->dev, &dev_attr_range);
-
        drv_data = hid_get_drvdata(hid);
        if (!drv_data) {
                hid_err(hid, "Error while deinitializing device, no private driver data.\n");
                return -1;
        }
        entry = drv_data->device_props;
-       if (!entry) {
-               hid_err(hid, "Error while deinitializing device, no device properties data.\n");
-               return -1;
+       if (!entry)
+               goto out; /* Nothing more to do */
+
+       device_remove_file(&hid->dev, &dev_attr_range);
+
+       /* Multimode devices will have at least the "MODE_NATIVE" bit set */
+       if (entry->alternate_modes) {
+               device_remove_file(&hid->dev, &dev_attr_real_id);
+               device_remove_file(&hid->dev, &dev_attr_alternate_modes);
        }
 
 #ifdef CONFIG_LEDS_CLASS
@@ -752,6 +1205,7 @@ int lg4ff_deinit(struct hid_device *hid)
        /* Deallocate memory */
        kfree(entry);
 
+out:
        dbg_hid("Device successfully unregistered\n");
        return 0;
 }
diff --git a/drivers/hid/hid-lg4ff.h b/drivers/hid/hid-lg4ff.h
new file mode 100644 (file)
index 0000000..5b6a508
--- /dev/null
@@ -0,0 +1,18 @@
+#ifndef __HID_LG4FF_H
+#define __HID_LG4FF_H
+
+#ifdef CONFIG_LOGIWHEELS_FF
+extern int lg4ff_no_autoswitch; /* From hid-lg.c */
+
+int lg4ff_adjust_input_event(struct hid_device *hid, struct hid_field *field,
+                            struct hid_usage *usage, __s32 value, struct lg_drv_data *drv_data);
+int lg4ff_init(struct hid_device *hdev);
+int lg4ff_deinit(struct hid_device *hdev);
+#else
+static inline int lg4ff_adjust_input_event(struct hid_device *hid, struct hid_field *field,
+                                          struct hid_usage *usage, __s32 value, struct lg_drv_data *drv_data) { return 0; }
+static inline int lg4ff_init(struct hid_device *hdev) { return -1; }
+static inline int lg4ff_deinit(struct hid_device *hdev) { return -1; }
+#endif
+
+#endif
index e77658c..b3cf6fd 100644 (file)
@@ -28,6 +28,11 @@ MODULE_LICENSE("GPL");
 MODULE_AUTHOR("Benjamin Tissoires <benjamin.tissoires@gmail.com>");
 MODULE_AUTHOR("Nestor Lopez Casado <nlopezcasad@logitech.com>");
 
+static bool disable_raw_mode;
+module_param(disable_raw_mode, bool, 0644);
+MODULE_PARM_DESC(disable_raw_mode,
+       "Disable Raw mode reporting for touchpads and keep firmware gestures.");
+
 #define REPORT_ID_HIDPP_SHORT                  0x10
 #define REPORT_ID_HIDPP_LONG                   0x11
 
@@ -1188,6 +1193,11 @@ static int hidpp_probe(struct hid_device *hdev, const struct hid_device_id *id)
 
        hidpp->quirks = id->driver_data;
 
+       if (disable_raw_mode) {
+               hidpp->quirks &= ~HIDPP_QUIRK_CLASS_WTP;
+               hidpp->quirks &= ~HIDPP_QUIRK_DELAYED_INIT;
+       }
+
        if (hidpp->quirks & HIDPP_QUIRK_CLASS_WTP) {
                ret = wtp_allocate(hdev, id);
                if (ret)
@@ -1210,6 +1220,7 @@ static int hidpp_probe(struct hid_device *hdev, const struct hid_device_id *id)
        connected = hidpp_is_connected(hidpp);
        if (id->group != HID_GROUP_LOGITECH_DJ_DEVICE) {
                if (!connected) {
+                       ret = -ENODEV;
                        hid_err(hdev, "Device not connected");
                        hid_device_io_stop(hdev);
                        goto hid_parse_fail;
index f65e78b..6a9b05b 100644 (file)
@@ -42,7 +42,6 @@
 #include <linux/hid.h>
 #include <linux/module.h>
 #include <linux/slab.h>
-#include <linux/usb.h>
 #include <linux/input/mt.h>
 #include <linux/string.h>
 
@@ -72,6 +71,8 @@ MODULE_LICENSE("GPL");
 #define MT_INPUTMODE_TOUCHSCREEN       0x02
 #define MT_INPUTMODE_TOUCHPAD          0x03
 
+#define MT_BUTTONTYPE_CLICKPAD         0
+
 struct mt_slot {
        __s32 x, y, cx, cy, p, w, h;
        __s32 contactid;        /* the device ContactID assigned to this slot */
@@ -116,6 +117,8 @@ struct mt_device {
        __u8 touches_by_report; /* how many touches are present in one report:
                                * 1 means we should use a serial protocol
                                * > 1 means hybrid (multitouch) protocol */
+       __u8 buttons_count;     /* number of physical buttons per touchpad */
+       bool is_buttonpad;      /* is this device a button pad? */
        bool serial_maybe;      /* need to check for serial protocol */
        bool curvalid;          /* is the current contact valid? */
        unsigned mt_flags;      /* flags to pass to input-mt */
@@ -333,6 +336,16 @@ static void mt_feature_mapping(struct hid_device *hdev,
                        /* check if the maxcontacts is given by the class */
                        td->maxcontacts = td->mtclass.maxcontacts;
 
+               break;
+       case HID_DG_BUTTONTYPE:
+               if (usage->usage_index >= field->report_count) {
+                       dev_err(&hdev->dev, "HID_DG_BUTTONTYPE out of range\n");
+                       break;
+               }
+
+               if (field->value[usage->usage_index] == MT_BUTTONTYPE_CLICKPAD)
+                       td->is_buttonpad = true;
+
                break;
        }
 }
@@ -379,6 +392,10 @@ static int mt_touch_input_mapping(struct hid_device *hdev, struct hid_input *hi,
                td->inputmode_value = MT_INPUTMODE_TOUCHPAD;
        }
 
+       /* count the buttons on touchpads */
+       if ((usage->hid & HID_USAGE_PAGE) == HID_UP_BUTTON)
+               td->buttons_count++;
+
        if (usage->usage_index)
                prev_usage = &field->usage[usage->usage_index - 1];
 
@@ -728,6 +745,13 @@ static void mt_touch_input_configured(struct hid_device *hdev,
        if (cls->quirks & MT_QUIRK_NOT_SEEN_MEANS_UP)
                td->mt_flags |= INPUT_MT_DROP_UNUSED;
 
+       /* check for clickpads */
+       if ((td->mt_flags & INPUT_MT_POINTER) && (td->buttons_count == 1))
+               td->is_buttonpad = true;
+
+       if (td->is_buttonpad)
+               __set_bit(INPUT_PROP_BUTTONPAD, input->propbit);
+
        input_mt_init_slots(input, td->maxcontacts, td->mt_flags);
 
        td->mt_flags = 0;
index 49d4fe4..368ffdf 100644 (file)
@@ -104,6 +104,7 @@ struct rmi_data {
 
        unsigned long flags;
 
+       struct rmi_function f01;
        struct rmi_function f11;
        struct rmi_function f30;
 
@@ -124,6 +125,7 @@ struct rmi_data {
        struct hid_device *hdev;
 
        unsigned long device_flags;
+       unsigned long firmware_id;
 };
 
 #define RMI_PAGE(addr) (((addr) >> 8) & 0xff)
@@ -272,6 +274,46 @@ static inline int rmi_read(struct hid_device *hdev, u16 addr, void *buf)
        return rmi_read_block(hdev, addr, buf, 1);
 }
 
+static int rmi_write_block(struct hid_device *hdev, u16 addr, void *buf,
+               const int len)
+{
+       struct rmi_data *data = hid_get_drvdata(hdev);
+       int ret;
+
+       mutex_lock(&data->page_mutex);
+
+       if (RMI_PAGE(addr) != data->page) {
+               ret = rmi_set_page(hdev, RMI_PAGE(addr));
+               if (ret < 0)
+                       goto exit;
+       }
+
+       data->writeReport[0] = RMI_WRITE_REPORT_ID;
+       data->writeReport[1] = len;
+       data->writeReport[2] = addr & 0xFF;
+       data->writeReport[3] = (addr >> 8) & 0xFF;
+       memcpy(&data->writeReport[4], buf, len);
+
+       ret = rmi_write_report(hdev, data->writeReport,
+                                       data->output_report_size);
+       if (ret < 0) {
+               dev_err(&hdev->dev,
+                       "failed to write request output report (%d)\n",
+                       ret);
+               goto exit;
+       }
+       ret = 0;
+
+exit:
+       mutex_unlock(&data->page_mutex);
+       return ret;
+}
+
+static inline int rmi_write(struct hid_device *hdev, u16 addr, void *buf)
+{
+       return rmi_write_block(hdev, addr, buf, 1);
+}
+
 static void rmi_f11_process_touch(struct rmi_data *hdata, int slot,
                u8 finger_state, u8 *touch_data)
 {
@@ -532,6 +574,9 @@ static void rmi_register_function(struct rmi_data *data,
        u16 page_base = page << 8;
 
        switch (pdt_entry->function_number) {
+       case 0x01:
+               f = &data->f01;
+               break;
        case 0x11:
                f = &data->f11;
                break;
@@ -604,6 +649,92 @@ error_exit:
        return retval;
 }
 
+#define RMI_DEVICE_F01_BASIC_QUERY_LEN 11
+
+static int rmi_populate_f01(struct hid_device *hdev)
+{
+       struct rmi_data *data = hid_get_drvdata(hdev);
+       u8 basic_queries[RMI_DEVICE_F01_BASIC_QUERY_LEN];
+       u8 info[3];
+       int ret;
+       bool has_query42;
+       bool has_lts;
+       bool has_sensor_id;
+       bool has_ds4_queries = false;
+       bool has_build_id_query = false;
+       bool has_package_id_query = false;
+       u16 query_offset = data->f01.query_base_addr;
+       u16 prod_info_addr;
+       u8 ds4_query_len;
+
+       ret = rmi_read_block(hdev, query_offset, basic_queries,
+                               RMI_DEVICE_F01_BASIC_QUERY_LEN);
+       if (ret) {
+               hid_err(hdev, "Can not read basic queries from Function 0x1.\n");
+               return ret;
+       }
+
+       has_lts = !!(basic_queries[0] & BIT(2));
+       has_sensor_id = !!(basic_queries[1] & BIT(3));
+       has_query42 = !!(basic_queries[1] & BIT(7));
+
+       query_offset += 11;
+       prod_info_addr = query_offset + 6;
+       query_offset += 10;
+
+       if (has_lts)
+               query_offset += 20;
+
+       if (has_sensor_id)
+               query_offset++;
+
+       if (has_query42) {
+               ret = rmi_read(hdev, query_offset, info);
+               if (ret) {
+                       hid_err(hdev, "Can not read query42.\n");
+                       return ret;
+               }
+               has_ds4_queries = !!(info[0] & BIT(0));
+               query_offset++;
+       }
+
+       if (has_ds4_queries) {
+               ret = rmi_read(hdev, query_offset, &ds4_query_len);
+               if (ret) {
+                       hid_err(hdev, "Can not read DS4 Query length.\n");
+                       return ret;
+               }
+               query_offset++;
+
+               if (ds4_query_len > 0) {
+                       ret = rmi_read(hdev, query_offset, info);
+                       if (ret) {
+                               hid_err(hdev, "Can not read DS4 query.\n");
+                               return ret;
+                       }
+
+                       has_package_id_query = !!(info[0] & BIT(0));
+                       has_build_id_query = !!(info[0] & BIT(1));
+               }
+       }
+
+       if (has_package_id_query)
+               prod_info_addr++;
+
+       if (has_build_id_query) {
+               ret = rmi_read_block(hdev, prod_info_addr, info, 3);
+               if (ret) {
+                       hid_err(hdev, "Can not read product info.\n");
+                       return ret;
+               }
+
+               data->firmware_id = info[1] << 8 | info[0];
+               data->firmware_id += info[2] * 65536;
+       }
+
+       return 0;
+}
+
 static int rmi_populate_f11(struct hid_device *hdev)
 {
        struct rmi_data *data = hid_get_drvdata(hdev);
@@ -620,6 +751,8 @@ static int rmi_populate_f11(struct hid_device *hdev)
        bool has_gestures;
        bool has_rel;
        bool has_data40 = false;
+       bool has_dribble = false;
+       bool has_palm_detect = false;
        unsigned x_size, y_size;
        u16 query_offset;
 
@@ -661,6 +794,14 @@ static int rmi_populate_f11(struct hid_device *hdev)
        has_rel = !!(buf[0] & BIT(3));
        has_gestures = !!(buf[0] & BIT(5));
 
+       ret = rmi_read(hdev, data->f11.query_base_addr + 5, buf);
+       if (ret) {
+               hid_err(hdev, "can not get absolute data sources: %d.\n", ret);
+               return ret;
+       }
+
+       has_dribble = !!(buf[0] & BIT(4));
+
        /*
         * At least 4 queries are guaranteed to be present in F11
         * +1 for query 5 which is present since absolute events are
@@ -680,6 +821,7 @@ static int rmi_populate_f11(struct hid_device *hdev)
                                ret);
                        return ret;
                }
+               has_palm_detect = !!(buf[0] & BIT(0));
                has_query10 = !!(buf[0] & BIT(2));
 
                query_offset += 2; /* query 7 and 8 are present */
@@ -766,17 +908,38 @@ static int rmi_populate_f11(struct hid_device *hdev)
         * retrieve the ctrl registers
         * the ctrl register has a size of 20 but a fw bug split it into 16 + 4,
         * and there is no way to know if the first 20 bytes are here or not.
-        * We use only the first 10 bytes, so get only them.
+        * We use only the first 12 bytes, so get only them.
         */
-       ret = rmi_read_block(hdev, data->f11.control_base_addr, buf, 10);
+       ret = rmi_read_block(hdev, data->f11.control_base_addr, buf, 12);
        if (ret) {
-               hid_err(hdev, "can not read ctrl block of size 10: %d.\n", ret);
+               hid_err(hdev, "can not read ctrl block of size 11: %d.\n", ret);
                return ret;
        }
 
        data->max_x = buf[6] | (buf[7] << 8);
        data->max_y = buf[8] | (buf[9] << 8);
 
+       if (has_dribble) {
+               buf[0] = buf[0] & ~BIT(6);
+               ret = rmi_write(hdev, data->f11.control_base_addr, buf);
+               if (ret) {
+                       hid_err(hdev, "can not write to control reg 0: %d.\n",
+                               ret);
+                       return ret;
+               }
+       }
+
+       if (has_palm_detect) {
+               buf[11] = buf[11] & ~BIT(0);
+               ret = rmi_write(hdev, data->f11.control_base_addr + 11,
+                               &buf[11]);
+               if (ret) {
+                       hid_err(hdev, "can not write to control reg 11: %d.\n",
+                               ret);
+                       return ret;
+               }
+       }
+
        return 0;
 }
 
@@ -858,6 +1021,12 @@ static int rmi_populate(struct hid_device *hdev)
                return ret;
        }
 
+       ret = rmi_populate_f01(hdev);
+       if (ret) {
+               hid_err(hdev, "Error while initializing F01 (%d).\n", ret);
+               return ret;
+       }
+
        ret = rmi_populate_f11(hdev);
        if (ret) {
                hid_err(hdev, "Error while initializing F11 (%d).\n", ret);
@@ -907,6 +1076,8 @@ static void rmi_input_configured(struct hid_device *hdev, struct hid_input *hi)
        if (ret)
                goto exit;
 
+       hid_info(hdev, "firmware id: %ld\n", data->firmware_id);
+
        __set_bit(EV_ABS, input->evbit);
        input_set_abs_params(input, ABS_MT_POSITION_X, 1, data->max_x, 0, 0);
        input_set_abs_params(input, ABS_MT_POSITION_Y, 1, data->max_y, 0, 0);
index 1896c01..27f67e7 100644 (file)
@@ -802,7 +802,8 @@ union sixaxis_output_report_01 {
 #define DS4_REPORT_0x05_SIZE 32
 #define DS4_REPORT_0x11_SIZE 78
 #define DS4_REPORT_0x81_SIZE 7
-#define SIXAXIS_REPORT_0xF2_SIZE 18
+#define SIXAXIS_REPORT_0xF2_SIZE 17
+#define SIXAXIS_REPORT_0xF5_SIZE 8
 
 static DEFINE_SPINLOCK(sony_dev_list_lock);
 static LIST_HEAD(sony_device_list);
@@ -1130,18 +1131,38 @@ static void sony_input_configured(struct hid_device *hdev,
  */
 static int sixaxis_set_operational_usb(struct hid_device *hdev)
 {
+       const int buf_size =
+               max(SIXAXIS_REPORT_0xF2_SIZE, SIXAXIS_REPORT_0xF5_SIZE);
+       __u8 *buf;
        int ret;
-       char *buf = kmalloc(18, GFP_KERNEL);
 
+       buf = kmalloc(buf_size, GFP_KERNEL);
        if (!buf)
                return -ENOMEM;
 
-       ret = hid_hw_raw_request(hdev, 0xf2, buf, 17, HID_FEATURE_REPORT,
-                                HID_REQ_GET_REPORT);
+       ret = hid_hw_raw_request(hdev, 0xf2, buf, SIXAXIS_REPORT_0xF2_SIZE,
+                                HID_FEATURE_REPORT, HID_REQ_GET_REPORT);
+       if (ret < 0) {
+               hid_err(hdev, "can't set operational mode: step 1\n");
+               goto out;
+       }
+
+       /*
+        * Some compatible controllers like the Speedlink Strike FX and
+        * Gasia need another query plus an USB interrupt to get operational.
+        */
+       ret = hid_hw_raw_request(hdev, 0xf5, buf, SIXAXIS_REPORT_0xF5_SIZE,
+                                HID_FEATURE_REPORT, HID_REQ_GET_REPORT);
+       if (ret < 0) {
+               hid_err(hdev, "can't set operational mode: step 2\n");
+               goto out;
+       }
 
+       ret = hid_hw_output_report(hdev, buf, 1);
        if (ret < 0)
-               hid_err(hdev, "can't set operational mode\n");
+               hid_err(hdev, "can't set operational mode: step 3\n");
 
+out:
        kfree(buf);
 
        return ret;
index 29f328f..3edd4ac 100644 (file)
@@ -12,7 +12,6 @@
  */
 
 #include <linux/device.h>
-#include <linux/usb.h>
 #include <linux/hid.h>
 #include <linux/module.h>
 
index d790d8d..d986969 100644 (file)
@@ -64,6 +64,7 @@ static const struct hid_device_id tivo_devices[] = {
        /* TiVo Slide Bluetooth remote, pairs with a Broadcom dongle */
        { HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_TIVO, USB_DEVICE_ID_TIVO_SLIDE_BT) },
        { HID_USB_DEVICE(USB_VENDOR_ID_TIVO, USB_DEVICE_ID_TIVO_SLIDE) },
+       { HID_USB_DEVICE(USB_VENDOR_ID_TIVO, USB_DEVICE_ID_TIVO_SLIDE_PRO) },
        { }
 };
 MODULE_DEVICE_TABLE(hid, tivo_devices);
index 36053f3..ab4dd95 100644 (file)
@@ -37,6 +37,7 @@
 #include <linux/mutex.h>
 #include <linux/acpi.h>
 #include <linux/of.h>
+#include <linux/gpio/consumer.h>
 
 #include <linux/i2c/i2c-hid.h>
 
@@ -144,6 +145,8 @@ struct i2c_hid {
        unsigned long           flags;          /* device flags */
 
        wait_queue_head_t       wait;           /* For waiting the interrupt */
+       struct gpio_desc        *desc;
+       int                     irq;
 
        struct i2c_hid_platform_data pdata;
 };
@@ -785,16 +788,16 @@ static int i2c_hid_init_irq(struct i2c_client *client)
        struct i2c_hid *ihid = i2c_get_clientdata(client);
        int ret;
 
-       dev_dbg(&client->dev, "Requesting IRQ: %d\n", client->irq);
+       dev_dbg(&client->dev, "Requesting IRQ: %d\n", ihid->irq);
 
-       ret = request_threaded_irq(client->irq, NULL, i2c_hid_irq,
+       ret = request_threaded_irq(ihid->irq, NULL, i2c_hid_irq,
                        IRQF_TRIGGER_LOW | IRQF_ONESHOT,
                        client->name, ihid);
        if (ret < 0) {
                dev_warn(&client->dev,
                        "Could not register for %s interrupt, irq = %d,"
                        " ret = %d\n",
-                       client->name, client->irq, ret);
+                       client->name, ihid->irq, ret);
 
                return ret;
        }
@@ -841,6 +844,14 @@ static int i2c_hid_fetch_hid_descriptor(struct i2c_hid *ihid)
 }
 
 #ifdef CONFIG_ACPI
+
+/* Default GPIO mapping */
+static const struct acpi_gpio_params i2c_hid_irq_gpio = { 0, 0, true };
+static const struct acpi_gpio_mapping i2c_hid_acpi_gpios[] = {
+       { "gpios", &i2c_hid_irq_gpio, 1 },
+       { },
+};
+
 static int i2c_hid_acpi_pdata(struct i2c_client *client,
                struct i2c_hid_platform_data *pdata)
 {
@@ -866,7 +877,7 @@ static int i2c_hid_acpi_pdata(struct i2c_client *client,
        pdata->hid_descriptor_address = obj->integer.value;
        ACPI_FREE(obj);
 
-       return 0;
+       return acpi_dev_add_driver_gpios(adev, i2c_hid_acpi_gpios);
 }
 
 static const struct acpi_device_id i2c_hid_acpi_match[] = {
@@ -930,12 +941,6 @@ static int i2c_hid_probe(struct i2c_client *client,
 
        dbg_hid("HID probe called for i2c 0x%02x\n", client->addr);
 
-       if (!client->irq) {
-               dev_err(&client->dev,
-                       "HID over i2c has not been provided an Int IRQ\n");
-               return -EINVAL;
-       }
-
        ihid = kzalloc(sizeof(struct i2c_hid), GFP_KERNEL);
        if (!ihid)
                return -ENOMEM;
@@ -955,6 +960,23 @@ static int i2c_hid_probe(struct i2c_client *client,
                ihid->pdata = *platform_data;
        }
 
+       if (client->irq > 0) {
+               ihid->irq = client->irq;
+       } else if (ACPI_COMPANION(&client->dev)) {
+               ihid->desc = gpiod_get(&client->dev, NULL, GPIOD_IN);
+               if (IS_ERR(ihid->desc)) {
+                       dev_err(&client->dev, "Failed to get GPIO interrupt\n");
+                       return PTR_ERR(ihid->desc);
+               }
+
+               ihid->irq = gpiod_to_irq(ihid->desc);
+               if (ihid->irq < 0) {
+                       gpiod_put(ihid->desc);
+                       dev_err(&client->dev, "Failed to convert GPIO to IRQ\n");
+                       return ihid->irq;
+               }
+       }
+
        i2c_set_clientdata(client, ihid);
 
        ihid->client = client;
@@ -1017,13 +1039,16 @@ err_mem_free:
        hid_destroy_device(hid);
 
 err_irq:
-       free_irq(client->irq, ihid);
+       free_irq(ihid->irq, ihid);
 
 err_pm:
        pm_runtime_put_noidle(&client->dev);
        pm_runtime_disable(&client->dev);
 
 err:
+       if (ihid->desc)
+               gpiod_put(ihid->desc);
+
        i2c_hid_free_buffers(ihid);
        kfree(ihid);
        return ret;
@@ -1042,13 +1067,18 @@ static int i2c_hid_remove(struct i2c_client *client)
        hid = ihid->hid;
        hid_destroy_device(hid);
 
-       free_irq(client->irq, ihid);
+       free_irq(ihid->irq, ihid);
 
        if (ihid->bufsize)
                i2c_hid_free_buffers(ihid);
 
+       if (ihid->desc)
+               gpiod_put(ihid->desc);
+
        kfree(ihid);
 
+       acpi_dev_remove_driver_gpios(ACPI_COMPANION(&client->dev));
+
        return 0;
 }
 
@@ -1060,9 +1090,9 @@ static int i2c_hid_suspend(struct device *dev)
        struct hid_device *hid = ihid->hid;
        int ret = 0;
 
-       disable_irq(client->irq);
+       disable_irq(ihid->irq);
        if (device_may_wakeup(&client->dev))
-               enable_irq_wake(client->irq);
+               enable_irq_wake(ihid->irq);
 
        if (hid->driver && hid->driver->suspend)
                ret = hid->driver->suspend(hid, PMSG_SUSPEND);
@@ -1080,13 +1110,13 @@ static int i2c_hid_resume(struct device *dev)
        struct i2c_hid *ihid = i2c_get_clientdata(client);
        struct hid_device *hid = ihid->hid;
 
-       enable_irq(client->irq);
+       enable_irq(ihid->irq);
        ret = i2c_hid_hwreset(client);
        if (ret)
                return ret;
 
        if (device_may_wakeup(&client->dev))
-               disable_irq_wake(client->irq);
+               disable_irq_wake(ihid->irq);
 
        if (hid->driver && hid->driver->reset_resume) {
                ret = hid->driver->reset_resume(hid);
@@ -1101,17 +1131,19 @@ static int i2c_hid_resume(struct device *dev)
 static int i2c_hid_runtime_suspend(struct device *dev)
 {
        struct i2c_client *client = to_i2c_client(dev);
+       struct i2c_hid *ihid = i2c_get_clientdata(client);
 
        i2c_hid_set_power(client, I2C_HID_PWR_SLEEP);
-       disable_irq(client->irq);
+       disable_irq(ihid->irq);
        return 0;
 }
 
 static int i2c_hid_runtime_resume(struct device *dev)
 {
        struct i2c_client *client = to_i2c_client(dev);
+       struct i2c_hid *ihid = i2c_get_clientdata(client);
 
-       enable_irq(client->irq);
+       enable_irq(ihid->irq);
        i2c_hid_set_power(client, I2C_HID_PWR_ON);
        return 0;
 }
index 0b531c6..08174d3 100644 (file)
@@ -568,6 +568,12 @@ static int pidff_upload_effect(struct input_dev *dev, struct ff_effect *effect,
        int type_id;
        int error;
 
+       pidff->block_load[PID_EFFECT_BLOCK_INDEX].value[0] = 0;
+       if (old) {
+               pidff->block_load[PID_EFFECT_BLOCK_INDEX].value[0] =
+                       pidff->pid_id[effect->id];
+       }
+
        switch (effect->type) {
        case FF_CONSTANT:
                if (!old) {
index 6c9eab4..a775143 100644 (file)
@@ -78,6 +78,13 @@ static const struct hid_blacklist {
        { USB_VENDOR_ID_ELO, USB_DEVICE_ID_ELO_TS2700, HID_QUIRK_NOGET },
        { USB_VENDOR_ID_FORMOSA, USB_DEVICE_ID_FORMOSA_IR_RECEIVER, HID_QUIRK_NO_INIT_REPORTS },
        { USB_VENDOR_ID_FREESCALE, USB_DEVICE_ID_FREESCALE_MX28, HID_QUIRK_NOGET },
+       { USB_VENDOR_ID_HP, USB_PRODUCT_ID_HP_LOGITECH_OEM_USB_OPTICAL_MOUSE_0A4A, HID_QUIRK_ALWAYS_POLL },
+       { USB_VENDOR_ID_HP, USB_PRODUCT_ID_HP_LOGITECH_OEM_USB_OPTICAL_MOUSE_0B4A, HID_QUIRK_ALWAYS_POLL },
+       { USB_VENDOR_ID_HP, USB_PRODUCT_ID_HP_PIXART_OEM_USB_OPTICAL_MOUSE, HID_QUIRK_ALWAYS_POLL },
+       { USB_VENDOR_ID_LOGITECH, USB_DEVICE_ID_LOGITECH_C077, HID_QUIRK_ALWAYS_POLL },
+       { USB_VENDOR_ID_LOGITECH, USB_DEVICE_ID_LOGITECH_MOUSE_C01A, HID_QUIRK_ALWAYS_POLL },
+       { USB_VENDOR_ID_LOGITECH, USB_DEVICE_ID_LOGITECH_MOUSE_C05A, HID_QUIRK_ALWAYS_POLL },
+       { USB_VENDOR_ID_LOGITECH, USB_DEVICE_ID_LOGITECH_MOUSE_C06A, HID_QUIRK_ALWAYS_POLL },
        { USB_VENDOR_ID_MGE, USB_DEVICE_ID_MGE_UPS, HID_QUIRK_NOGET },
        { USB_VENDOR_ID_MICROSOFT, USB_DEVICE_ID_MS_TYPE_COVER_3, HID_QUIRK_NO_INIT_REPORTS },
        { USB_VENDOR_ID_MICROSOFT, USB_DEVICE_ID_MS_TYPE_COVER_3_JP, HID_QUIRK_NO_INIT_REPORTS },
@@ -91,6 +98,7 @@ static const struct hid_blacklist {
        { USB_VENDOR_ID_PIXART, USB_DEVICE_ID_PIXART_OPTICAL_TOUCH_SCREEN, HID_QUIRK_NO_INIT_REPORTS },
        { USB_VENDOR_ID_PIXART, USB_DEVICE_ID_PIXART_OPTICAL_TOUCH_SCREEN1, HID_QUIRK_NO_INIT_REPORTS },
        { USB_VENDOR_ID_PIXART, USB_DEVICE_ID_PIXART_OPTICAL_TOUCH_SCREEN2, HID_QUIRK_NO_INIT_REPORTS },
+       { USB_VENDOR_ID_PRIMAX, USB_DEVICE_ID_PRIMAX_MOUSE_4D22, HID_QUIRK_ALWAYS_POLL },
        { USB_VENDOR_ID_PRODIGE, USB_DEVICE_ID_PRODIGE_CORDLESS, HID_QUIRK_NOGET },
        { USB_VENDOR_ID_QUANTA, USB_DEVICE_ID_QUANTA_OPTICAL_TOUCH_3001, HID_QUIRK_NOGET },
        { USB_VENDOR_ID_QUANTA, USB_DEVICE_ID_QUANTA_OPTICAL_TOUCH_3008, HID_QUIRK_NOGET },
@@ -123,6 +131,7 @@ static const struct hid_blacklist {
        { USB_VENDOR_ID_KYE, USB_DEVICE_ID_KYE_MOUSEPEN_I608X, HID_QUIRK_MULTI_INPUT },
        { USB_VENDOR_ID_KYE, USB_DEVICE_ID_KYE_MOUSEPEN_I608X_2, HID_QUIRK_MULTI_INPUT },
        { USB_VENDOR_ID_KYE, USB_DEVICE_ID_KYE_EASYPEN_M610X, HID_QUIRK_MULTI_INPUT },
+       { USB_VENDOR_ID_KYE, USB_DEVICE_ID_KYE_PENSKETCH_M912, HID_QUIRK_MULTI_INPUT },
        { USB_VENDOR_ID_NTRIG, USB_DEVICE_ID_NTRIG_DUOSENSE, HID_QUIRK_NO_INIT_REPORTS },
        { USB_VENDOR_ID_SEMICO, USB_DEVICE_ID_SEMICO_USB_KEYKOARD, HID_QUIRK_NO_INIT_REPORTS },
        { USB_VENDOR_ID_SYNAPTICS, USB_DEVICE_ID_SYNAPTICS_LTS1, HID_QUIRK_NO_INIT_REPORTS },
index 7db4328..ad7318d 100644 (file)
@@ -129,13 +129,6 @@ static inline void wacom_schedule_work(struct wacom_wac *wacom_wac)
        schedule_work(&wacom->work);
 }
 
-static inline void wacom_notify_battery(struct wacom_wac *wacom_wac)
-{
-       struct wacom *wacom = container_of(wacom_wac, struct wacom, wacom_wac);
-
-       power_supply_changed(&wacom->battery);
-}
-
 extern const struct hid_device_id wacom_ids[];
 
 void wacom_wac_irq(struct wacom_wac *wacom_wac, size_t len);
@@ -149,4 +142,5 @@ void wacom_wac_usage_mapping(struct hid_device *hdev,
 int wacom_wac_event(struct hid_device *hdev, struct hid_field *field,
                struct hid_usage *usage, __s32 value);
 void wacom_wac_report(struct hid_device *hdev, struct hid_report *report);
+void wacom_battery_work(struct work_struct *work);
 #endif
index f0568a7..1b00d8d 100644 (file)
@@ -406,6 +406,9 @@ static int wacom_query_tablet_data(struct hid_device *hdev,
                else if (features->type == WACOM_27QHDT) {
                        return wacom_set_device_mode(hdev, 131, 3, 2);
                }
+               else if (features->type == BAMBOO_PAD) {
+                       return wacom_set_device_mode(hdev, 2, 2, 2);
+               }
        } else if (features->device_type == BTN_TOOL_PEN) {
                if (features->type <= BAMBOO_PT && features->type != WIRELESS) {
                        return wacom_set_device_mode(hdev, 2, 2, 2);
@@ -524,6 +527,11 @@ static int wacom_add_shared_data(struct hid_device *hdev)
 
        wacom_wac->shared = &data->shared;
 
+       if (wacom_wac->features.device_type == BTN_TOOL_FINGER)
+               wacom_wac->shared->touch = hdev;
+       else if (wacom_wac->features.device_type == BTN_TOOL_PEN)
+               wacom_wac->shared->pen = hdev;
+
 out:
        mutex_unlock(&wacom_udev_list_lock);
        return retval;
@@ -541,14 +549,22 @@ static void wacom_release_shared_data(struct kref *kref)
        kfree(data);
 }
 
-static void wacom_remove_shared_data(struct wacom_wac *wacom)
+static void wacom_remove_shared_data(struct wacom *wacom)
 {
        struct wacom_hdev_data *data;
+       struct wacom_wac *wacom_wac = &wacom->wacom_wac;
+
+       if (wacom_wac->shared) {
+               data = container_of(wacom_wac->shared, struct wacom_hdev_data,
+                                   shared);
+
+               if (wacom_wac->shared->touch == wacom->hdev)
+                       wacom_wac->shared->touch = NULL;
+               else if (wacom_wac->shared->pen == wacom->hdev)
+                       wacom_wac->shared->pen = NULL;
 
-       if (wacom->shared) {
-               data = container_of(wacom->shared, struct wacom_hdev_data, shared);
                kref_put(&data->kref, wacom_release_shared_data);
-               wacom->shared = NULL;
+               wacom_wac->shared = NULL;
        }
 }
 
@@ -929,6 +945,7 @@ static void wacom_destroy_leds(struct wacom *wacom)
 }
 
 static enum power_supply_property wacom_battery_props[] = {
+       POWER_SUPPLY_PROP_PRESENT,
        POWER_SUPPLY_PROP_STATUS,
        POWER_SUPPLY_PROP_SCOPE,
        POWER_SUPPLY_PROP_CAPACITY
@@ -948,6 +965,9 @@ static int wacom_battery_get_property(struct power_supply *psy,
        int ret = 0;
 
        switch (psp) {
+               case POWER_SUPPLY_PROP_PRESENT:
+                       val->intval = wacom->wacom_wac.bat_connected;
+                       break;
                case POWER_SUPPLY_PROP_SCOPE:
                        val->intval = POWER_SUPPLY_SCOPE_DEVICE;
                        break;
@@ -961,6 +981,8 @@ static int wacom_battery_get_property(struct power_supply *psy,
                        else if (wacom->wacom_wac.battery_capacity == 100 &&
                                    wacom->wacom_wac.ps_connected)
                                val->intval = POWER_SUPPLY_STATUS_FULL;
+                       else if (wacom->wacom_wac.ps_connected)
+                               val->intval = POWER_SUPPLY_STATUS_NOT_CHARGING;
                        else
                                val->intval = POWER_SUPPLY_STATUS_DISCHARGING;
                        break;
@@ -1041,8 +1063,7 @@ static int wacom_initialize_battery(struct wacom *wacom)
 
 static void wacom_destroy_battery(struct wacom *wacom)
 {
-       if ((wacom->wacom_wac.features.quirks & WACOM_QUIRK_BATTERY) &&
-            wacom->battery.dev) {
+       if (wacom->battery.dev) {
                power_supply_unregister(&wacom->battery);
                wacom->battery.dev = NULL;
                power_supply_unregister(&wacom->ac);
@@ -1313,6 +1334,20 @@ fail:
        return;
 }
 
+void wacom_battery_work(struct work_struct *work)
+{
+       struct wacom *wacom = container_of(work, struct wacom, work);
+
+       if ((wacom->wacom_wac.features.quirks & WACOM_QUIRK_BATTERY) &&
+            !wacom->battery.dev) {
+               wacom_initialize_battery(wacom);
+       }
+       else if (!(wacom->wacom_wac.features.quirks & WACOM_QUIRK_BATTERY) &&
+                wacom->battery.dev) {
+               wacom_destroy_battery(wacom);
+       }
+}
+
 /*
  * Not all devices report physical dimensions from HID.
  * Compute the default from hardcoded logical dimension
@@ -1373,6 +1408,9 @@ static int wacom_probe(struct hid_device *hdev,
 
        hdev->quirks |= HID_QUIRK_NO_INIT_REPORTS;
 
+       /* hid-core sets this quirk for the boot interface */
+       hdev->quirks &= ~HID_QUIRK_NOGET;
+
        wacom = kzalloc(sizeof(struct wacom), GFP_KERNEL);
        if (!wacom)
                return -ENOMEM;
@@ -1412,6 +1450,21 @@ static int wacom_probe(struct hid_device *hdev,
                        goto fail_allocate_inputs;
        }
 
+       /*
+        * Bamboo Pad has a generic hid handling for the Pen, and we switch it
+        * into debug mode for the touch part.
+        * We ignore the other interfaces.
+        */
+       if (features->type == BAMBOO_PAD) {
+               if (features->pktlen == WACOM_PKGLEN_PENABLED) {
+                       features->type = HID_GENERIC;
+               } else if ((features->pktlen != WACOM_PKGLEN_BPAD_TOUCH) &&
+                          (features->pktlen != WACOM_PKGLEN_BPAD_TOUCH_USB)) {
+                       error = -ENODEV;
+                       goto fail_shared_data;
+               }
+       }
+
        /* set the default size in case we do not get them from hid */
        wacom_set_default_phy(features);
 
@@ -1446,6 +1499,12 @@ static int wacom_probe(struct hid_device *hdev,
                features->y_max = 4096;
        }
 
+       /*
+        * Same thing for Bamboo PAD
+        */
+       if (features->type == BAMBOO_PAD)
+               features->device_type = BTN_TOOL_FINGER;
+
        if (hdev->bus == BUS_BLUETOOTH)
                features->quirks |= WACOM_QUIRK_BATTERY;
 
@@ -1462,19 +1521,17 @@ static int wacom_probe(struct hid_device *hdev,
        snprintf(wacom_wac->pad_name, sizeof(wacom_wac->pad_name),
                "%s Pad", features->name);
 
-       if (features->quirks & WACOM_QUIRK_MULTI_INPUT) {
-               /* Append the device type to the name */
-               if (features->device_type != BTN_TOOL_FINGER)
-                       strlcat(wacom_wac->name, " Pen", WACOM_NAME_MAX);
-               else if (features->touch_max)
-                       strlcat(wacom_wac->name, " Finger", WACOM_NAME_MAX);
-               else
-                       strlcat(wacom_wac->name, " Pad", WACOM_NAME_MAX);
+       /* Append the device type to the name */
+       if (features->device_type != BTN_TOOL_FINGER)
+               strlcat(wacom_wac->name, " Pen", WACOM_NAME_MAX);
+       else if (features->touch_max)
+               strlcat(wacom_wac->name, " Finger", WACOM_NAME_MAX);
+       else
+               strlcat(wacom_wac->name, " Pad", WACOM_NAME_MAX);
 
-               error = wacom_add_shared_data(hdev);
-               if (error)
-                       goto fail_shared_data;
-       }
+       error = wacom_add_shared_data(hdev);
+       if (error)
+               goto fail_shared_data;
 
        if (!(features->quirks & WACOM_QUIRK_MONITOR) &&
             (features->quirks & WACOM_QUIRK_BATTERY)) {
@@ -1527,7 +1584,7 @@ fail_register_inputs:
        wacom_clean_inputs(wacom);
        wacom_destroy_battery(wacom);
 fail_battery:
-       wacom_remove_shared_data(wacom_wac);
+       wacom_remove_shared_data(wacom);
 fail_shared_data:
        wacom_clean_inputs(wacom);
 fail_allocate_inputs:
@@ -1550,7 +1607,7 @@ static void wacom_remove(struct hid_device *hdev)
        if (hdev->bus == BUS_BLUETOOTH)
                device_remove_file(&hdev->dev, &dev_attr_speed);
        wacom_destroy_battery(wacom);
-       wacom_remove_shared_data(&wacom->wacom_wac);
+       wacom_remove_shared_data(wacom);
 
        hid_set_drvdata(hdev, NULL);
        kfree(wacom);
index 046351c..69c7df7 100644 (file)
@@ -45,6 +45,27 @@ static unsigned short batcap_gr[8] = { 1, 15, 25, 35, 50, 70, 100, 100 };
  */
 static unsigned short batcap_i4[8] = { 1, 15, 30, 45, 60, 70, 85, 100 };
 
+static void wacom_notify_battery(struct wacom_wac *wacom_wac,
+       int bat_capacity, bool bat_charging, bool bat_connected,
+       bool ps_connected)
+{
+       struct wacom *wacom = container_of(wacom_wac, struct wacom, wacom_wac);
+       bool changed = wacom_wac->battery_capacity != bat_capacity  ||
+                      wacom_wac->bat_charging     != bat_charging  ||
+                      wacom_wac->bat_connected    != bat_connected ||
+                      wacom_wac->ps_connected     != ps_connected;
+
+       if (changed) {
+               wacom_wac->battery_capacity = bat_capacity;
+               wacom_wac->bat_charging = bat_charging;
+               wacom_wac->bat_connected = bat_connected;
+               wacom_wac->ps_connected = ps_connected;
+
+               if (wacom->battery.dev)
+                       power_supply_changed(&wacom->battery);
+       }
+}
+
 static int wacom_penpartner_irq(struct wacom_wac *wacom)
 {
        unsigned char *data = wacom->data;
@@ -419,17 +440,26 @@ static int wacom_graphire_irq(struct wacom_wac *wacom)
                rw = (data[7] >> 2 & 0x07);
                battery_capacity = batcap_gr[rw];
                ps_connected = rw == 7;
-               if ((wacom->battery_capacity != battery_capacity) ||
-                   (wacom->ps_connected != ps_connected)) {
-                       wacom->battery_capacity = battery_capacity;
-                       wacom->ps_connected = ps_connected;
-                       wacom_notify_battery(wacom);
-               }
+               wacom_notify_battery(wacom, battery_capacity, ps_connected,
+                                    1, ps_connected);
        }
 exit:
        return retval;
 }
 
+static void wacom_intuos_schedule_prox_event(struct wacom_wac *wacom_wac)
+{
+       struct wacom *wacom = container_of(wacom_wac, struct wacom, wacom_wac);
+       struct hid_report *r;
+       struct hid_report_enum *re;
+
+       re = &(wacom->hdev->report_enum[HID_FEATURE_REPORT]);
+       r = re->report_id_hash[WACOM_REPORT_INTUOSREAD];
+       if (r) {
+               hid_hw_request(wacom->hdev, r, HID_REQ_GET_REPORT);
+       }
+}
+
 static int wacom_intuos_inout(struct wacom_wac *wacom)
 {
        struct wacom_features *features = &wacom->features;
@@ -551,8 +581,9 @@ static int wacom_intuos_inout(struct wacom_wac *wacom)
           (features->type == CINTIQ && !(data[1] & 0x40)))
                return 1;
 
-       if (features->quirks & WACOM_QUIRK_MULTI_INPUT)
-               wacom->shared->stylus_in_proximity = true;
+       wacom->shared->stylus_in_proximity = true;
+       if (wacom->shared->touch_down)
+               return 1;
 
        /* in Range while exiting */
        if (((data[1] & 0xfe) == 0x20) && wacom->reporting_data) {
@@ -564,8 +595,7 @@ static int wacom_intuos_inout(struct wacom_wac *wacom)
 
        /* Exit report */
        if ((data[1] & 0xfe) == 0x80) {
-               if (features->quirks & WACOM_QUIRK_MULTI_INPUT)
-                       wacom->shared->stylus_in_proximity = false;
+               wacom->shared->stylus_in_proximity = false;
                wacom->reporting_data = false;
 
                /* don't report exit if we don't know the ID */
@@ -606,8 +636,11 @@ static int wacom_intuos_inout(struct wacom_wac *wacom)
        }
 
        /* don't report other events if we don't know the ID */
-       if (!wacom->id[idx])
+       if (!wacom->id[idx]) {
+               /* but reschedule a read of the current tool */
+               wacom_intuos_schedule_prox_event(wacom);
                return 1;
+       }
 
        return 0;
 }
@@ -1019,15 +1052,9 @@ static int wacom_intuos_bt_irq(struct wacom_wac *wacom, size_t len)
                bat_charging = (power_raw & 0x08) ? 1 : 0;
                ps_connected = (power_raw & 0x10) ? 1 : 0;
                battery_capacity = batcap_i4[power_raw & 0x07];
-               if ((wacom->battery_capacity != battery_capacity) ||
-                   (wacom->bat_charging != bat_charging) ||
-                   (wacom->ps_connected != ps_connected)) {
-                       wacom->battery_capacity = battery_capacity;
-                       wacom->bat_charging = bat_charging;
-                       wacom->ps_connected = ps_connected;
-                       wacom_notify_battery(wacom);
-               }
-
+               wacom_notify_battery(wacom, battery_capacity, bat_charging,
+                                    battery_capacity || bat_charging,
+                                    ps_connected);
                break;
        default:
                dev_dbg(wacom->input->dev.parent,
@@ -1038,12 +1065,34 @@ static int wacom_intuos_bt_irq(struct wacom_wac *wacom, size_t len)
        return 0;
 }
 
+static int wacom_wac_finger_count_touches(struct wacom_wac *wacom)
+{
+       struct input_dev *input = wacom->input;
+       unsigned touch_max = wacom->features.touch_max;
+       int count = 0;
+       int i;
+
+       /* non-HID_GENERIC single touch input doesn't call this routine */
+       if ((touch_max == 1) && (wacom->features.type == HID_GENERIC))
+               return wacom->hid_data.tipswitch &&
+                      !wacom->shared->stylus_in_proximity;
+
+       for (i = 0; i < input->mt->num_slots; i++) {
+               struct input_mt_slot *ps = &input->mt->slots[i];
+               int id = input_mt_get_value(ps, ABS_MT_TRACKING_ID);
+               if (id >= 0)
+                       count++;
+       }
+
+       return count;
+}
+
 static int wacom_24hdt_irq(struct wacom_wac *wacom)
 {
        struct input_dev *input = wacom->input;
        unsigned char *data = wacom->data;
        int i;
-       int current_num_contacts = 0;
+       int current_num_contacts = data[61];
        int contacts_to_send = 0;
        int num_contacts_left = 4; /* maximum contacts per packet */
        int byte_per_packet = WACOM_BYTES_PER_24HDT_PACKET;
@@ -1054,8 +1103,6 @@ static int wacom_24hdt_irq(struct wacom_wac *wacom)
                num_contacts_left = 10;
                byte_per_packet = WACOM_BYTES_PER_QHDTHID_PACKET;
                y_offset = 0;
-       } else {
-               current_num_contacts = data[61];
        }
 
        /*
@@ -1098,13 +1145,13 @@ static int wacom_24hdt_irq(struct wacom_wac *wacom)
                        }
                }
        }
-       input_mt_report_pointer_emulation(input, true);
+       input_mt_sync_frame(input);
 
        wacom->num_contacts_left -= contacts_to_send;
-       if (wacom->num_contacts_left <= 0)
+       if (wacom->num_contacts_left <= 0) {
                wacom->num_contacts_left = 0;
-
-       wacom->shared->touch_down = (wacom->num_contacts_left > 0);
+               wacom->shared->touch_down = wacom_wac_finger_count_touches(wacom);
+       }
        return 1;
 }
 
@@ -1149,13 +1196,13 @@ static int wacom_mt_touch(struct wacom_wac *wacom)
                        input_report_abs(input, ABS_MT_POSITION_Y, y);
                }
        }
-       input_mt_report_pointer_emulation(input, true);
+       input_mt_sync_frame(input);
 
        wacom->num_contacts_left -= contacts_to_send;
-       if (wacom->num_contacts_left < 0)
+       if (wacom->num_contacts_left <= 0) {
                wacom->num_contacts_left = 0;
-
-       wacom->shared->touch_down = (wacom->num_contacts_left > 0);
+               wacom->shared->touch_down = wacom_wac_finger_count_touches(wacom);
+       }
        return 1;
 }
 
@@ -1163,7 +1210,6 @@ static int wacom_tpc_mt_touch(struct wacom_wac *wacom)
 {
        struct input_dev *input = wacom->input;
        unsigned char *data = wacom->data;
-       int contact_with_no_pen_down_count = 0;
        int i;
 
        for (i = 0; i < 2; i++) {
@@ -1178,13 +1224,12 @@ static int wacom_tpc_mt_touch(struct wacom_wac *wacom)
 
                        input_report_abs(input, ABS_MT_POSITION_X, x);
                        input_report_abs(input, ABS_MT_POSITION_Y, y);
-                       contact_with_no_pen_down_count++;
                }
        }
-       input_mt_report_pointer_emulation(input, true);
+       input_mt_sync_frame(input);
 
        /* keep touch state for pen event */
-       wacom->shared->touch_down = (contact_with_no_pen_down_count > 0);
+       wacom->shared->touch_down = wacom_wac_finger_count_touches(wacom);
 
        return 1;
 }
@@ -1193,29 +1238,25 @@ static int wacom_tpc_single_touch(struct wacom_wac *wacom, size_t len)
 {
        unsigned char *data = wacom->data;
        struct input_dev *input = wacom->input;
-       bool prox;
+       bool prox = !wacom->shared->stylus_in_proximity;
        int x = 0, y = 0;
 
        if (wacom->features.touch_max > 1 || len > WACOM_PKGLEN_TPC2FG)
                return 0;
 
-       if (!wacom->shared->stylus_in_proximity) {
-               if (len == WACOM_PKGLEN_TPC1FG) {
-                       prox = data[0] & 0x01;
-                       x = get_unaligned_le16(&data[1]);
-                       y = get_unaligned_le16(&data[3]);
-               } else if (len == WACOM_PKGLEN_TPC1FG_B) {
-                       prox = data[2] & 0x01;
-                       x = get_unaligned_le16(&data[3]);
-                       y = get_unaligned_le16(&data[5]);
-               } else {
-                       prox = data[1] & 0x01;
-                       x = le16_to_cpup((__le16 *)&data[2]);
-                       y = le16_to_cpup((__le16 *)&data[4]);
-               }
-       } else
-               /* force touch out when pen is in prox */
-               prox = 0;
+       if (len == WACOM_PKGLEN_TPC1FG) {
+               prox = prox && (data[0] & 0x01);
+               x = get_unaligned_le16(&data[1]);
+               y = get_unaligned_le16(&data[3]);
+       } else if (len == WACOM_PKGLEN_TPC1FG_B) {
+               prox = prox && (data[2] & 0x01);
+               x = get_unaligned_le16(&data[3]);
+               y = get_unaligned_le16(&data[5]);
+       } else {
+               prox = prox && (data[1] & 0x01);
+               x = le16_to_cpup((__le16 *)&data[2]);
+               y = le16_to_cpup((__le16 *)&data[4]);
+       }
 
        if (prox) {
                input_report_abs(input, ABS_X, x);
@@ -1516,29 +1557,6 @@ static int wacom_wac_finger_event(struct hid_device *hdev,
        return 0;
 }
 
-static int wacom_wac_finger_count_touches(struct hid_device *hdev)
-{
-       struct wacom *wacom = hid_get_drvdata(hdev);
-       struct wacom_wac *wacom_wac = &wacom->wacom_wac;
-       struct input_dev *input = wacom_wac->input;
-       unsigned touch_max = wacom_wac->features.touch_max;
-       int count = 0;
-       int i;
-
-       if (touch_max == 1)
-               return wacom_wac->hid_data.tipswitch &&
-                      !wacom_wac->shared->stylus_in_proximity;
-
-       for (i = 0; i < input->mt->num_slots; i++) {
-               struct input_mt_slot *ps = &input->mt->slots[i];
-               int id = input_mt_get_value(ps, ABS_MT_TRACKING_ID);
-               if (id >= 0)
-                       count++;
-       }
-
-       return count;
-}
-
 static void wacom_wac_finger_report(struct hid_device *hdev,
                struct hid_report *report)
 {
@@ -1553,7 +1571,7 @@ static void wacom_wac_finger_report(struct hid_device *hdev,
        input_sync(input);
 
        /* keep touch state for pen event */
-       wacom_wac->shared->touch_down = wacom_wac_finger_count_touches(hdev);
+       wacom_wac->shared->touch_down = wacom_wac_finger_count_touches(wacom_wac);
 }
 
 void wacom_wac_usage_mapping(struct hid_device *hdev,
@@ -1643,12 +1661,13 @@ static int wacom_bpt_touch(struct wacom_wac *wacom)
                }
        }
 
-       input_mt_report_pointer_emulation(input, true);
+       input_mt_sync_frame(input);
 
        input_report_key(pad_input, BTN_LEFT, (data[1] & 0x08) != 0);
        input_report_key(pad_input, BTN_FORWARD, (data[1] & 0x04) != 0);
        input_report_key(pad_input, BTN_BACK, (data[1] & 0x02) != 0);
        input_report_key(pad_input, BTN_RIGHT, (data[1] & 0x01) != 0);
+       wacom->shared->touch_down = wacom_wac_finger_count_touches(wacom);
 
        return 1;
 }
@@ -1733,7 +1752,8 @@ static int wacom_bpt3_touch(struct wacom_wac *wacom)
                        wacom_bpt3_button_msg(wacom, data + offset);
 
        }
-       input_mt_report_pointer_emulation(input, true);
+       input_mt_sync_frame(input);
+       wacom->shared->touch_down = wacom_wac_finger_count_touches(wacom);
 
        return 1;
 }
@@ -1745,20 +1765,9 @@ static int wacom_bpt_pen(struct wacom_wac *wacom)
        unsigned char *data = wacom->data;
        int prox = 0, x = 0, y = 0, p = 0, d = 0, pen = 0, btn1 = 0, btn2 = 0;
 
-       if (data[0] != WACOM_REPORT_PENABLED && data[0] != WACOM_REPORT_USB)
+       if (data[0] != WACOM_REPORT_PENABLED)
            return 0;
 
-       if (data[0] == WACOM_REPORT_USB) {
-               if (features->type == INTUOSHT &&
-                   wacom->shared->touch_input &&
-                   features->touch_max) {
-                       input_report_switch(wacom->shared->touch_input,
-                                           SW_MUTE_DEVICE, data[8] & 0x40);
-                       input_sync(wacom->shared->touch_input);
-               }
-               return 0;
-       }
-
        prox = (data[1] & 0x20) == 0x20;
 
        /*
@@ -1771,17 +1780,21 @@ static int wacom_bpt_pen(struct wacom_wac *wacom)
         *
         * Hardware does report zero in most out-of-prox cases but not all.
         */
-       if (prox) {
-               if (!wacom->shared->stylus_in_proximity) {
-                       if (data[1] & 0x08) {
-                               wacom->tool[0] = BTN_TOOL_RUBBER;
-                               wacom->id[0] = ERASER_DEVICE_ID;
-                       } else {
-                               wacom->tool[0] = BTN_TOOL_PEN;
-                               wacom->id[0] = STYLUS_DEVICE_ID;
-                       }
-                       wacom->shared->stylus_in_proximity = true;
+       if (!wacom->shared->stylus_in_proximity) {
+               if (data[1] & 0x08) {
+                       wacom->tool[0] = BTN_TOOL_RUBBER;
+                       wacom->id[0] = ERASER_DEVICE_ID;
+               } else {
+                       wacom->tool[0] = BTN_TOOL_PEN;
+                       wacom->id[0] = STYLUS_DEVICE_ID;
                }
+       }
+
+       wacom->shared->stylus_in_proximity = prox;
+       if (wacom->shared->touch_down)
+               return 0;
+
+       if (prox) {
                x = le16_to_cpup((__le16 *)&data[2]);
                y = le16_to_cpup((__le16 *)&data[4]);
                p = le16_to_cpup((__le16 *)&data[6]);
@@ -1797,6 +1810,8 @@ static int wacom_bpt_pen(struct wacom_wac *wacom)
                pen = data[1] & 0x01;
                btn1 = data[1] & 0x02;
                btn2 = data[1] & 0x04;
+       } else {
+               wacom->id[0] = 0;
        }
 
        input_report_key(input, BTN_TOUCH, pen);
@@ -1808,11 +1823,6 @@ static int wacom_bpt_pen(struct wacom_wac *wacom)
        input_report_abs(input, ABS_PRESSURE, p);
        input_report_abs(input, ABS_DISTANCE, d);
 
-       if (!prox) {
-               wacom->id[0] = 0;
-               wacom->shared->stylus_in_proximity = false;
-       }
-
        input_report_key(input, wacom->tool[0], prox); /* PEN or RUBBER */
        input_report_abs(input, ABS_MISC, wacom->id[0]); /* TOOL ID */
 
@@ -1831,6 +1841,91 @@ static int wacom_bpt_irq(struct wacom_wac *wacom, size_t len)
        return 0;
 }
 
+static void wacom_bamboo_pad_pen_event(struct wacom_wac *wacom,
+               unsigned char *data)
+{
+       unsigned char prefix;
+
+       /*
+        * We need to reroute the event from the debug interface to the
+        * pen interface.
+        * We need to add the report ID to the actual pen report, so we
+        * temporary overwrite the first byte to prevent having to kzalloc/kfree
+        * and memcpy the report.
+        */
+       prefix = data[0];
+       data[0] = WACOM_REPORT_BPAD_PEN;
+
+       /*
+        * actually reroute the event.
+        * No need to check if wacom->shared->pen is valid, hid_input_report()
+        * will check for us.
+        */
+       hid_input_report(wacom->shared->pen, HID_INPUT_REPORT, data,
+                        WACOM_PKGLEN_PENABLED, 1);
+
+       data[0] = prefix;
+}
+
+static int wacom_bamboo_pad_touch_event(struct wacom_wac *wacom,
+               unsigned char *data)
+{
+       struct input_dev *input = wacom->input;
+       unsigned char *finger_data, prefix;
+       unsigned id;
+       int x, y;
+       bool valid;
+
+       prefix = data[0];
+
+       for (id = 0; id < wacom->features.touch_max; id++) {
+               valid = !!(prefix & BIT(id)) &&
+                       !wacom->shared->stylus_in_proximity;
+
+               input_mt_slot(input, id);
+               input_mt_report_slot_state(input, MT_TOOL_FINGER, valid);
+
+               if (!valid)
+                       continue;
+
+               finger_data = data + 1 + id * 3;
+               x = finger_data[0] | ((finger_data[1] & 0x0f) << 8);
+               y = (finger_data[2] << 4) | (finger_data[1] >> 4);
+
+               input_report_abs(input, ABS_MT_POSITION_X, x);
+               input_report_abs(input, ABS_MT_POSITION_Y, y);
+       }
+
+       input_mt_sync_frame(input);
+
+       input_report_key(input, BTN_LEFT, prefix & 0x40);
+       input_report_key(input, BTN_RIGHT, prefix & 0x80);
+
+       /* keep touch state for pen event */
+       wacom->shared->touch_down = !!prefix &&
+                                   !wacom->shared->stylus_in_proximity;
+
+       return 1;
+}
+
+static int wacom_bamboo_pad_irq(struct wacom_wac *wacom, size_t len)
+{
+       unsigned char *data = wacom->data;
+
+       if (!((len == WACOM_PKGLEN_BPAD_TOUCH) ||
+             (len == WACOM_PKGLEN_BPAD_TOUCH_USB)) ||
+           (data[0] != WACOM_REPORT_BPAD_TOUCH))
+               return 0;
+
+       if (data[1] & 0x01)
+               wacom_bamboo_pad_pen_event(wacom, &data[1]);
+
+       if (data[1] & 0x02)
+               return wacom_bamboo_pad_touch_event(wacom, &data[9]);
+
+       return 0;
+}
+
 static int wacom_wireless_irq(struct wacom_wac *wacom, size_t len)
 {
        unsigned char *data = wacom->data;
@@ -1841,7 +1936,7 @@ static int wacom_wireless_irq(struct wacom_wac *wacom, size_t len)
 
        connected = data[1] & 0x01;
        if (connected) {
-               int pid, battery, ps_connected;
+               int pid, battery, charging;
 
                if ((wacom->shared->type == INTUOSHT) &&
                    wacom->shared->touch_input &&
@@ -1853,33 +1948,66 @@ static int wacom_wireless_irq(struct wacom_wac *wacom, size_t len)
 
                pid = get_unaligned_be16(&data[6]);
                battery = (data[5] & 0x3f) * 100 / 31;
-               ps_connected = !!(data[5] & 0x80);
+               charging = !!(data[5] & 0x80);
                if (wacom->pid != pid) {
                        wacom->pid = pid;
                        wacom_schedule_work(wacom);
                }
 
-               if (wacom->shared->type &&
-                   (battery != wacom->battery_capacity ||
-                    ps_connected != wacom->ps_connected)) {
-                       wacom->battery_capacity = battery;
-                       wacom->ps_connected = ps_connected;
-                       wacom->bat_charging = ps_connected &&
-                                               wacom->battery_capacity < 100;
-                       wacom_notify_battery(wacom);
-               }
+               if (wacom->shared->type)
+                       wacom_notify_battery(wacom, battery, charging, 1, 0);
+
        } else if (wacom->pid != 0) {
                /* disconnected while previously connected */
                wacom->pid = 0;
                wacom_schedule_work(wacom);
-               wacom->battery_capacity = 0;
-               wacom->bat_charging = 0;
-               wacom->ps_connected = 0;
+               wacom_notify_battery(wacom, 0, 0, 0, 0);
        }
 
        return 0;
 }
 
+static int wacom_status_irq(struct wacom_wac *wacom_wac, size_t len)
+{
+       struct wacom *wacom = container_of(wacom_wac, struct wacom, wacom_wac);
+       struct wacom_features *features = &wacom_wac->features;
+       unsigned char *data = wacom_wac->data;
+
+       if (data[0] != WACOM_REPORT_USB)
+               return 0;
+
+       if (features->type == INTUOSHT &&
+           wacom_wac->shared->touch_input &&
+           features->touch_max) {
+               input_report_switch(wacom_wac->shared->touch_input,
+                                   SW_MUTE_DEVICE, data[8] & 0x40);
+               input_sync(wacom_wac->shared->touch_input);
+       }
+
+       if (data[9] & 0x02) { /* wireless module is attached */
+               int battery = (data[8] & 0x3f) * 100 / 31;
+               bool charging = !!(data[8] & 0x80);
+
+               wacom_notify_battery(wacom_wac, battery, charging,
+                                    battery || charging, 1);
+
+               if (!wacom->battery.dev &&
+                   !(features->quirks & WACOM_QUIRK_BATTERY)) {
+                       features->quirks |= WACOM_QUIRK_BATTERY;
+                       INIT_WORK(&wacom->work, wacom_battery_work);
+                       wacom_schedule_work(wacom_wac);
+               }
+       }
+       else if ((features->quirks & WACOM_QUIRK_BATTERY) &&
+                wacom->battery.dev) {
+               features->quirks &= ~WACOM_QUIRK_BATTERY;
+               INIT_WORK(&wacom->work, wacom_battery_work);
+               wacom_schedule_work(wacom_wac);
+               wacom_notify_battery(wacom_wac, 0, 0, 0, 0);
+       }
+       return 0;
+}
+
 void wacom_wac_irq(struct wacom_wac *wacom_wac, size_t len)
 {
        bool sync;
@@ -1949,6 +2077,8 @@ void wacom_wac_irq(struct wacom_wac *wacom_wac, size_t len)
        case INTUOSPL:
                if (len == WACOM_PKGLEN_BBTOUCH3)
                        sync = wacom_bpt3_touch(wacom_wac);
+               else if (wacom_wac->data[0] == WACOM_REPORT_USB)
+                       sync = wacom_status_irq(wacom_wac, len);
                else
                        sync = wacom_intuos_irq(wacom_wac);
                break;
@@ -1964,7 +2094,14 @@ void wacom_wac_irq(struct wacom_wac *wacom_wac, size_t len)
 
        case BAMBOO_PT:
        case INTUOSHT:
-               sync = wacom_bpt_irq(wacom_wac, len);
+               if (wacom_wac->data[0] == WACOM_REPORT_USB)
+                       sync = wacom_status_irq(wacom_wac, len);
+               else
+                       sync = wacom_bpt_irq(wacom_wac, len);
+               break;
+
+       case BAMBOO_PAD:
+               sync = wacom_bamboo_pad_irq(wacom_wac, len);
                break;
 
        case WIRELESS:
@@ -2036,12 +2173,6 @@ void wacom_setup_device_quirks(struct wacom_features *features)
                features->y_max = 1023;
        }
 
-       /* these device have multiple inputs */
-       if (features->type >= WIRELESS ||
-           (features->type >= INTUOS5S && features->type <= INTUOSHT) ||
-           (features->oVid && features->oPid))
-               features->quirks |= WACOM_QUIRK_MULTI_INPUT;
-
        /* quirk for bamboo touch with 2 low res touches */
        if (features->type == BAMBOO_PT &&
            features->pktlen == WACOM_PKGLEN_BBTOUCH) {
@@ -2305,6 +2436,13 @@ int wacom_setup_pentouch_input_capabilities(struct input_dev *input_dev,
                                              0, 0);
                }
                break;
+       case BAMBOO_PAD:
+               __clear_bit(ABS_MISC, input_dev->absbit);
+               input_mt_init_slots(input_dev, features->touch_max,
+                                   INPUT_MT_POINTER);
+               __set_bit(BTN_LEFT, input_dev->keybit);
+               __set_bit(BTN_RIGHT, input_dev->keybit);
+               break;
        }
        return 0;
 }
@@ -2754,6 +2892,15 @@ static const struct wacom_features wacom_features_0x304 =
        { "Wacom Cintiq 13HD", 59152, 33448, 1023, 63,
          WACOM_13HD, WACOM_INTUOS3_RES, WACOM_INTUOS3_RES,
          WACOM_CINTIQ_OFFSET, WACOM_CINTIQ_OFFSET };
+static const struct wacom_features wacom_features_0x333 =
+       { "Wacom Cintiq 13HD touch", 59152, 33448, 2047, 63,
+         WACOM_13HD, WACOM_INTUOS3_RES, WACOM_INTUOS3_RES,
+         WACOM_CINTIQ_OFFSET, WACOM_CINTIQ_OFFSET,
+         .oVid = USB_VENDOR_ID_WACOM, .oPid = 0x335 };
+static const struct wacom_features wacom_features_0x335 =
+       { "Wacom Cintiq 13HD touch", .type = WACOM_24HDT, /* Touch */
+         .oVid = USB_VENDOR_ID_WACOM, .oPid = 0x333, .touch_max = 10,
+         .check_for_hid_type = true, .hid_type = HID_TYPE_USBNONE };
 static const struct wacom_features wacom_features_0xC7 =
        { "Wacom DTU1931", 37832, 30305, 511, 0,
          PL, WACOM_INTUOS_RES, WACOM_INTUOS_RES };
@@ -2958,6 +3105,12 @@ static const struct wacom_features wacom_features_0x30C =
        { "Wacom ISDv5 30C", .type = WACOM_24HDT, /* Touch */
          .oVid = USB_VENDOR_ID_WACOM, .oPid = 0x30A, .touch_max = 10,
          .check_for_hid_type = true, .hid_type = HID_TYPE_USBNONE };
+static const struct wacom_features wacom_features_0x318 =
+       { "Wacom USB Bamboo PAD", 4095, 4095, /* Touch */
+         .type = BAMBOO_PAD, 35, 48, .touch_max = 4 };
+static const struct wacom_features wacom_features_0x319 =
+       { "Wacom Wireless Bamboo PAD", 4095, 4095, /* Touch */
+         .type = BAMBOO_PAD, 35, 48, .touch_max = 4 };
 static const struct wacom_features wacom_features_0x323 =
        { "Wacom Intuos P M", 21600, 13500, 1023, 31,
          INTUOSHT, WACOM_INTUOS_RES, WACOM_INTUOS_RES,
@@ -2974,6 +3127,10 @@ static const struct wacom_features wacom_features_HID_ANY_ID =
        HID_DEVICE(BUS_BLUETOOTH, HID_GROUP_WACOM, USB_VENDOR_ID_WACOM, prod),\
        .driver_data = (kernel_ulong_t)&wacom_features_##prod
 
+#define I2C_DEVICE_WACOM(prod)                                         \
+       HID_DEVICE(BUS_I2C, HID_GROUP_WACOM, USB_VENDOR_ID_WACOM, prod),\
+       .driver_data = (kernel_ulong_t)&wacom_features_##prod
+
 #define USB_DEVICE_LENOVO(prod)                                        \
        HID_USB_DEVICE(USB_VENDOR_ID_LENOVO, prod),                     \
        .driver_data = (kernel_ulong_t)&wacom_features_##prod
@@ -3106,11 +3263,15 @@ const struct hid_device_id wacom_ids[] = {
        { USB_DEVICE_WACOM(0x314) },
        { USB_DEVICE_WACOM(0x315) },
        { USB_DEVICE_WACOM(0x317) },
+       { USB_DEVICE_WACOM(0x318) },
+       { USB_DEVICE_WACOM(0x319) },
        { USB_DEVICE_WACOM(0x323) },
        { USB_DEVICE_WACOM(0x32A) },
        { USB_DEVICE_WACOM(0x32B) },
        { USB_DEVICE_WACOM(0x32C) },
        { USB_DEVICE_WACOM(0x32F) },
+       { USB_DEVICE_WACOM(0x333) },
+       { USB_DEVICE_WACOM(0x335) },
        { USB_DEVICE_WACOM(0x4001) },
        { USB_DEVICE_WACOM(0x4004) },
        { USB_DEVICE_WACOM(0x5000) },
@@ -3118,6 +3279,7 @@ const struct hid_device_id wacom_ids[] = {
        { USB_DEVICE_LENOVO(0x6004) },
 
        { USB_DEVICE_WACOM(HID_ANY_ID) },
+       { I2C_DEVICE_WACOM(HID_ANY_ID) },
        { }
 };
 MODULE_DEVICE_TABLE(hid, wacom_ids);
index 021ee1c..4700ac9 100644 (file)
@@ -33,6 +33,8 @@
 #define WACOM_PKGLEN_MTTPC     40
 #define WACOM_PKGLEN_DTUS      68
 #define WACOM_PKGLEN_PENABLED   8
+#define WACOM_PKGLEN_BPAD_TOUCH        32
+#define WACOM_PKGLEN_BPAD_TOUCH_USB    64
 
 /* wacom data size per MT contact */
 #define WACOM_BYTES_PER_MT_PACKET      11
 #define WACOM_REPORT_24HDT             1
 #define WACOM_REPORT_WL                        128
 #define WACOM_REPORT_USB               192
+#define WACOM_REPORT_BPAD_PEN          3
+#define WACOM_REPORT_BPAD_TOUCH                16
 
 /* device quirks */
-#define WACOM_QUIRK_MULTI_INPUT                0x0001
-#define WACOM_QUIRK_BBTOUCH_LOWRES     0x0002
-#define WACOM_QUIRK_NO_INPUT           0x0004
-#define WACOM_QUIRK_MONITOR            0x0008
-#define WACOM_QUIRK_BATTERY            0x0010
+#define WACOM_QUIRK_BBTOUCH_LOWRES     0x0001
+#define WACOM_QUIRK_NO_INPUT           0x0002
+#define WACOM_QUIRK_MONITOR            0x0004
+#define WACOM_QUIRK_BATTERY            0x0008
 
 #define WACOM_PEN_FIELD(f)     (((f)->logical == HID_DG_STYLUS) || \
                                 ((f)->physical == HID_DG_STYLUS) || \
@@ -122,6 +125,7 @@ enum {
        BAMBOO_PT,
        WACOM_24HDT,
        WACOM_27QHDT,
+       BAMBOO_PAD,
        TABLETPC,   /* add new TPC below */
        TABLETPCE,
        TABLETPC2FG,
@@ -169,6 +173,8 @@ struct wacom_shared {
        unsigned touch_max;
        int type;
        struct input_dev *touch_input;
+       struct hid_device *pen;
+       struct hid_device *touch;
 };
 
 struct hid_data {
@@ -205,6 +211,7 @@ struct wacom_wac {
        int battery_capacity;
        int num_contacts_left;
        int bat_charging;
+       int bat_connected;
        int ps_connected;
        u8 bt_features;
        u8 bt_high_speed;
index cb150a1..34feb3e 100644 (file)
@@ -88,10 +88,13 @@ int input_mt_init_slots(struct input_dev *dev, unsigned int num_slots,
                        goto err_mem;
        }
 
-       /* Mark slots as 'unused' */
+       /* Mark slots as 'inactive' */
        for (i = 0; i < num_slots; i++)
                input_mt_set_value(&mt->slots[i], ABS_MT_TRACKING_ID, -1);
 
+       /* Mark slots as 'unused' */
+       mt->frame = 1;
+
        dev->mt = mt;
        return 0;
 err_mem:
@@ -439,6 +442,8 @@ EXPORT_SYMBOL(input_mt_assign_slots);
  * set the key on the first unused slot and return.
  *
  * If no available slot can be found, -1 is returned.
+ * Note that for this function to work properly, input_mt_sync_frame() has
+ * to be called at each frame.
  */
 int input_mt_get_slot_by_key(struct input_dev *dev, int key)
 {
@@ -453,7 +458,7 @@ int input_mt_get_slot_by_key(struct input_dev *dev, int key)
                        return s - mt->slots;
 
        for (s = mt->slots; s != mt->slots + mt->num_slots; s++)
-               if (!input_mt_is_active(s)) {
+               if (!input_mt_is_active(s) && !input_mt_is_used(mt, s)) {
                        s->key = key;
                        return s - mt->slots;
                }
index efc7787..0167e0e 100644 (file)
@@ -159,6 +159,7 @@ struct hid_item {
 #define HID_UP_LED             0x00080000
 #define HID_UP_BUTTON          0x00090000
 #define HID_UP_ORDINAL         0x000a0000
+#define HID_UP_TELEPHONY       0x000b0000
 #define HID_UP_CONSUMER                0x000c0000
 #define HID_UP_DIGITIZER       0x000d0000
 #define HID_UP_PID             0x000f0000
@@ -269,6 +270,7 @@ struct hid_item {
 #define HID_DG_DEVICEINDEX     0x000d0053
 #define HID_DG_CONTACTCOUNT    0x000d0054
 #define HID_DG_CONTACTMAX      0x000d0055
+#define HID_DG_BUTTONTYPE      0x000d0059
 #define HID_DG_BARRELSWITCH2   0x000d005a
 #define HID_DG_TOOLSERIALNUMBER        0x000d005b
 
index b0a8130..2b628c3 100644 (file)
@@ -702,6 +702,10 @@ struct input_keymap_entry {
 #define KEY_NUMERIC_9          0x209
 #define KEY_NUMERIC_STAR       0x20a
 #define KEY_NUMERIC_POUND      0x20b
+#define KEY_NUMERIC_A          0x20c   /* Phone key A - HUT Telephony 0xb9 */
+#define KEY_NUMERIC_B          0x20d
+#define KEY_NUMERIC_C          0x20e
+#define KEY_NUMERIC_D          0x20f
 
 #define KEY_CAMERA_FOCUS       0x210
 #define KEY_WPS_BUTTON         0x211   /* WiFi Protected Setup key */
index 382eeae..a9ab961 100644 (file)
@@ -8,3 +8,5 @@ hostprogs-y := hid-example
 always := $(hostprogs-y)
 
 HOSTCFLAGS_hid-example.o += -I$(objtree)/usr/include
+
+all: hid-example
index 512a7e5..92e6c15 100644 (file)
@@ -46,10 +46,14 @@ int main(int argc, char **argv)
        char buf[256];
        struct hidraw_report_descriptor rpt_desc;
        struct hidraw_devinfo info;
+       char *device = "/dev/hidraw0";
+
+       if (argc > 1)
+               device = argv[1];
 
        /* Open the Device with non-blocking reads. In real life,
           don't use a hard coded path; use libudev instead. */
-       fd = open("/dev/hidraw0", O_RDWR|O_NONBLOCK);
+       fd = open(device, O_RDWR|O_NONBLOCK);
 
        if (fd < 0) {
                perror("Unable to open device");