Merge branch 'for-linus' of git://git.kernel.org/pub/scm/linux/kernel/git/hid/hid
authorLinus Torvalds <torvalds@linux-foundation.org>
Fri, 30 Apr 2021 19:53:02 +0000 (12:53 -0700)
committerLinus Torvalds <torvalds@linux-foundation.org>
Fri, 30 Apr 2021 19:53:02 +0000 (12:53 -0700)
Pull HID updates from Jiri Kosina:

 - Surface Aggregator Module support from Maximilian Luz

 - Apple Magic Mouse 2 support from John Chen

 - Support for newer Quad/BT 2.0 Logitech receivers in HID proxy mode
   from Hans de Goede

 - Thinkpad X1 Tablet keyboard support from Hans de Goede

 - Support for FTDI FT260 I2C host adapter from Michael Zaidman

 - other various small device-specific quirks, fixes and cleanups

* 'for-linus' of git://git.kernel.org/pub/scm/linux/kernel/git/hid/hid: (46 commits)
  HID: wacom: Setup pen input capabilities to the targeted tools
  HID: hid-sensor-hub: Move 'hsdev' description to correct struct definition
  HID: hid-sensor-hub: Remove unused struct member 'quirks'
  HID: wacom_sys: Demote kernel-doc abuse
  HID: hid-sensor-custom: Remove unused variable 'ret'
  HID: hid-uclogic-params: Ensure function names are present and correct in kernel-doc headers
  HID: hid-uclogic-rdesc: Kernel-doc is for functions and structs
  HID: hid-logitech-hidpp: Fix conformant kernel-doc header and demote abuses
  HID: hid-picolcd_core: Remove unused variable 'ret'
  HID: hid-kye: Fix incorrect function name for kye_tablet_enable()
  HID: hid-core: Fix incorrect function name in header
  HID: hid-alps: Correct struct misnaming
  HID: usbhid: hid-pidff: Demote a couple kernel-doc abuses
  HID: usbhid: Repair a formatting issue in a struct description
  HID: hid-thrustmaster: Demote a bunch of kernel-doc abuses
  HID: input: map battery capacity (00850065)
  HID: magicmouse: fix reconnection of Magic Mouse 2
  HID: magicmouse: fix 3 button emulation of Mouse 2
  HID: magicmouse: add Apple Magic Mouse 2 support
  HID: lenovo: Add support for Thinkpad X1 Tablet Thin keyboard
  ...

38 files changed:
MAINTAINERS
drivers/hid/Kconfig
drivers/hid/Makefile
drivers/hid/hid-alps.c
drivers/hid/hid-core.c
drivers/hid/hid-debug.c
drivers/hid/hid-elan.c
drivers/hid/hid-ft260.c [new file with mode: 0644]
drivers/hid/hid-ids.h
drivers/hid/hid-input.c
drivers/hid/hid-kye.c
drivers/hid/hid-lenovo.c
drivers/hid/hid-lg.c
drivers/hid/hid-logitech-dj.c
drivers/hid/hid-logitech-hidpp.c
drivers/hid/hid-magicmouse.c
drivers/hid/hid-picolcd_core.c
drivers/hid/hid-plantronics.c
drivers/hid/hid-quirks.c
drivers/hid/hid-sensor-custom.c
drivers/hid/hid-sensor-hub.c
drivers/hid/hid-thrustmaster.c [new file with mode: 0644]
drivers/hid/hid-uclogic-params.c
drivers/hid/hid-uclogic-rdesc.c
drivers/hid/i2c-hid/i2c-hid-acpi.c
drivers/hid/surface-hid/Kconfig [new file with mode: 0644]
drivers/hid/surface-hid/Makefile [new file with mode: 0644]
drivers/hid/surface-hid/surface_hid.c [new file with mode: 0644]
drivers/hid/surface-hid/surface_hid_core.c [new file with mode: 0644]
drivers/hid/surface-hid/surface_hid_core.h [new file with mode: 0644]
drivers/hid/surface-hid/surface_kbd.c [new file with mode: 0644]
drivers/hid/usbhid/hid-pidff.c
drivers/hid/usbhid/hiddev.c
drivers/hid/usbhid/usbkbd.c
drivers/hid/wacom_sys.c
drivers/hid/wacom_wac.c
drivers/hid/wacom_wac.h
include/linux/hid.h

index 9a8e2c2..30aa825 100644 (file)
@@ -7432,6 +7432,13 @@ F:       fs/verity/
 F:     include/linux/fsverity.h
 F:     include/uapi/linux/fsverity.h
 
+FT260 FTDI USB-HID TO I2C BRIDGE DRIVER
+M:     Michael Zaidman <michael.zaidman@gmail.com>
+L:     linux-i2c@vger.kernel.org
+L:     linux-input@vger.kernel.org
+S:     Maintained
+F:     drivers/hid/hid-ft260.c
+
 FUJITSU LAPTOP EXTRAS
 M:     Jonathan Woithe <jwoithe@just42.net>
 L:     platform-driver-x86@vger.kernel.org
@@ -12079,6 +12086,13 @@ S:     Maintained
 T:     git git://git.kernel.org/pub/scm/linux/kernel/git/pdx86/platform-drivers-x86.git
 F:     drivers/platform/surface/
 
+MICROSOFT SURFACE HID TRANSPORT DRIVER
+M:     Maximilian Luz <luzmaximilian@gmail.com>
+L:     linux-input@vger.kernel.org
+L:     platform-driver-x86@vger.kernel.org
+S:     Maintained
+F:     drivers/hid/surface-hid/
+
 MICROSOFT SURFACE HOT-PLUG DRIVER
 M:     Maximilian Luz <luzmaximilian@gmail.com>
 L:     platform-driver-x86@vger.kernel.org
index 786b71e..4bf263c 100644 (file)
@@ -351,6 +351,17 @@ config HID_EZKEY
        help
        Support for Ezkey BTC 8193 keyboard.
 
+config HID_FT260
+       tristate "FTDI FT260 USB HID to I2C host support"
+       depends on USB_HID && HIDRAW && I2C
+       help
+         Provides I2C host adapter functionality over USB-HID through FT260
+         device. The customizable USB descriptor fields are exposed as sysfs
+         attributes.
+
+         To compile this driver as a module, choose M here: the module
+         will be called hid-ft260.
+
 config HID_GEMBIRD
        tristate "Gembird Joypad"
        depends on HID
@@ -1042,10 +1053,11 @@ config HID_THINGM
 
 config HID_THRUSTMASTER
        tristate "ThrustMaster devices support"
-       depends on HID
+       depends on USB_HID
        help
-         Say Y here if you have a THRUSTMASTER FireStore Dual Power 2 or
-         a THRUSTMASTER Ferrari GT Rumble Wheel.
+         Say Y here if you have a THRUSTMASTER FireStore Dual Power 2,
+         a THRUSTMASTER Ferrari GT Rumble Wheel or Thrustmaster FFB
+         Wheel (T150RS, T300RS, T300 Ferrari Alcantara Edition, T500RS).
 
 config THRUSTMASTER_FF
        bool "ThrustMaster devices force feedback support"
@@ -1206,4 +1218,6 @@ source "drivers/hid/intel-ish-hid/Kconfig"
 
 source "drivers/hid/amd-sfh-hid/Kconfig"
 
+source "drivers/hid/surface-hid/Kconfig"
+
 endmenu
index c4f6d5c..193431e 100644 (file)
@@ -46,6 +46,7 @@ obj-$(CONFIG_HID_ELAN)                += hid-elan.o
 obj-$(CONFIG_HID_ELECOM)       += hid-elecom.o
 obj-$(CONFIG_HID_ELO)          += hid-elo.o
 obj-$(CONFIG_HID_EZKEY)                += hid-ezkey.o
+obj-$(CONFIG_HID_FT260)                += hid-ft260.o
 obj-$(CONFIG_HID_GEMBIRD)      += hid-gembird.o
 obj-$(CONFIG_HID_GFRM)         += hid-gfrm.o
 obj-$(CONFIG_HID_GLORIOUS)  += hid-glorious.o
@@ -112,7 +113,8 @@ obj-$(CONFIG_HID_STEAM)             += hid-steam.o
 obj-$(CONFIG_HID_STEELSERIES)  += hid-steelseries.o
 obj-$(CONFIG_HID_SUNPLUS)      += hid-sunplus.o
 obj-$(CONFIG_HID_GREENASIA)    += hid-gaff.o
-obj-$(CONFIG_HID_THRUSTMASTER) += hid-tmff.o
+obj-$(CONFIG_HID_THRUSTMASTER) += hid-tmff.o hid-thrustmaster.o
+obj-$(CONFIG_HID_TMINIT)       += hid-tminit.o
 obj-$(CONFIG_HID_TIVO)         += hid-tivo.o
 obj-$(CONFIG_HID_TOPSEED)      += hid-topseed.o
 obj-$(CONFIG_HID_TWINHAN)      += hid-twinhan.o
@@ -145,3 +147,5 @@ obj-$(CONFIG_INTEL_ISH_HID) += intel-ish-hid/
 obj-$(INTEL_ISH_FIRMWARE_DOWNLOADER)   += intel-ish-hid/
 
 obj-$(CONFIG_AMD_SFH_HID)       += amd-sfh-hid/
+
+obj-$(CONFIG_SURFACE_HID_CORE)  += surface-hid/
index 6b66593..2b986d0 100644 (file)
@@ -74,7 +74,7 @@ enum dev_num {
        UNKNOWN,
 };
 /**
- * struct u1_data
+ * struct alps_dev
  *
  * @input: pointer to the kernel input device
  * @input2: pointer to the kernel input2 device
index 097cb1e..0ae9f6d 100644 (file)
@@ -2129,7 +2129,7 @@ struct hid_dynid {
 };
 
 /**
- * store_new_id - add a new HID device ID to this driver and re-probe devices
+ * new_id_store - add a new HID device ID to this driver and re-probe devices
  * @drv: target device driver
  * @buf: buffer for scanning device ID data
  * @count: input size
index d7eaf91..59f8d71 100644 (file)
@@ -417,6 +417,7 @@ static const struct hid_usage_entry hid_usage_table[] = {
     { 0x85, 0x44, "Charging" },
     { 0x85, 0x45, "Discharging" },
     { 0x85, 0x4b, "NeedReplacement" },
+    { 0x85, 0x65, "AbsoluteStateOfCharge" },
     { 0x85, 0x66, "RemainingCapacity" },
     { 0x85, 0x68, "RunTimeToEmpty" },
     { 0x85, 0x6a, "AverageTimeToFull" },
index dae1937..0210498 100644 (file)
@@ -410,15 +410,6 @@ static int elan_start_multitouch(struct hid_device *hdev)
        return 0;
 }
 
-static enum led_brightness elan_mute_led_get_brigtness(struct led_classdev *led_cdev)
-{
-       struct device *dev = led_cdev->dev->parent;
-       struct hid_device *hdev = to_hid_device(dev);
-       struct elan_drvdata *drvdata = hid_get_drvdata(hdev);
-
-       return drvdata->mute_led_state;
-}
-
 static int elan_mute_led_set_brigtness(struct led_classdev *led_cdev,
                                       enum led_brightness value)
 {
@@ -445,8 +436,9 @@ static int elan_mute_led_set_brigtness(struct led_classdev *led_cdev,
        kfree(dmabuf);
 
        if (ret != ELAN_LED_REPORT_SIZE) {
-               hid_err(hdev, "Failed to set mute led brightness: %d\n", ret);
-               return ret;
+               if (ret != -ENODEV)
+                       hid_err(hdev, "Failed to set mute led brightness: %d\n", ret);
+               return ret < 0 ? ret : -EIO;
        }
 
        drvdata->mute_led_state = led_state;
@@ -459,9 +451,10 @@ static int elan_init_mute_led(struct hid_device *hdev)
        struct led_classdev *mute_led = &drvdata->mute_led;
 
        mute_led->name = "elan:red:mute";
-       mute_led->brightness_get = elan_mute_led_get_brigtness;
+       mute_led->default_trigger = "audio-mute";
        mute_led->brightness_set_blocking = elan_mute_led_set_brigtness;
        mute_led->max_brightness = LED_ON;
+       mute_led->flags = LED_HW_PLUGGABLE;
        mute_led->dev = &hdev->dev;
 
        return devm_led_classdev_register(&hdev->dev, mute_led);
diff --git a/drivers/hid/hid-ft260.c b/drivers/hid/hid-ft260.c
new file mode 100644 (file)
index 0000000..a575160
--- /dev/null
@@ -0,0 +1,1054 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * hid-ft260.c - FTDI FT260 USB HID to I2C host bridge
+ *
+ * Copyright (c) 2021, Michael Zaidman <michaelz@xsightlabs.com>
+ *
+ * Data Sheet:
+ *   https://www.ftdichip.com/Support/Documents/DataSheets/ICs/DS_FT260.pdf
+ */
+
+#include "hid-ids.h"
+#include <linux/hidraw.h>
+#include <linux/i2c.h>
+#include <linux/module.h>
+#include <linux/usb.h>
+
+#ifdef DEBUG
+static int ft260_debug = 1;
+#else
+static int ft260_debug;
+#endif
+module_param_named(debug, ft260_debug, int, 0600);
+MODULE_PARM_DESC(debug, "Toggle FT260 debugging messages");
+
+#define ft260_dbg(format, arg...)                                        \
+       do {                                                              \
+               if (ft260_debug)                                          \
+                       pr_info("%s: " format, __func__, ##arg);          \
+       } while (0)
+
+#define FT260_REPORT_MAX_LENGTH (64)
+#define FT260_I2C_DATA_REPORT_ID(len) (FT260_I2C_REPORT_MIN + (len - 1) / 4)
+/*
+ * The input report format assigns 62 bytes for the data payload, but ft260
+ * returns 60 and 2 in two separate transactions. To minimize transfer time
+ * in reading chunks mode, set the maximum read payload length to 60 bytes.
+ */
+#define FT260_RD_DATA_MAX (60)
+#define FT260_WR_DATA_MAX (60)
+
+/*
+ * Device interface configuration.
+ * The FT260 has 2 interfaces that are controlled by DCNF0 and DCNF1 pins.
+ * First implementes USB HID to I2C bridge function and
+ * second - USB HID to UART bridge function.
+ */
+enum {
+       FT260_MODE_ALL                  = 0x00,
+       FT260_MODE_I2C                  = 0x01,
+       FT260_MODE_UART                 = 0x02,
+       FT260_MODE_BOTH                 = 0x03,
+};
+
+/* Control pipe */
+enum {
+       FT260_GET_RQST_TYPE             = 0xA1,
+       FT260_GET_REPORT                = 0x01,
+       FT260_SET_RQST_TYPE             = 0x21,
+       FT260_SET_REPORT                = 0x09,
+       FT260_FEATURE                   = 0x03,
+};
+
+/* Report IDs / Feature In */
+enum {
+       FT260_CHIP_VERSION              = 0xA0,
+       FT260_SYSTEM_SETTINGS           = 0xA1,
+       FT260_I2C_STATUS                = 0xC0,
+       FT260_I2C_READ_REQ              = 0xC2,
+       FT260_I2C_REPORT_MIN            = 0xD0,
+       FT260_I2C_REPORT_MAX            = 0xDE,
+       FT260_GPIO                      = 0xB0,
+       FT260_UART_INTERRUPT_STATUS     = 0xB1,
+       FT260_UART_STATUS               = 0xE0,
+       FT260_UART_RI_DCD_STATUS        = 0xE1,
+       FT260_UART_REPORT               = 0xF0,
+};
+
+/* Feature Out */
+enum {
+       FT260_SET_CLOCK                 = 0x01,
+       FT260_SET_I2C_MODE              = 0x02,
+       FT260_SET_UART_MODE             = 0x03,
+       FT260_ENABLE_INTERRUPT          = 0x05,
+       FT260_SELECT_GPIO2_FUNC         = 0x06,
+       FT260_ENABLE_UART_DCD_RI        = 0x07,
+       FT260_SELECT_GPIOA_FUNC         = 0x08,
+       FT260_SELECT_GPIOG_FUNC         = 0x09,
+       FT260_SET_INTERRUPT_TRIGGER     = 0x0A,
+       FT260_SET_SUSPEND_OUT_POLAR     = 0x0B,
+       FT260_ENABLE_UART_RI_WAKEUP     = 0x0C,
+       FT260_SET_UART_RI_WAKEUP_CFG    = 0x0D,
+       FT260_SET_I2C_RESET             = 0x20,
+       FT260_SET_I2C_CLOCK_SPEED       = 0x22,
+       FT260_SET_UART_RESET            = 0x40,
+       FT260_SET_UART_CONFIG           = 0x41,
+       FT260_SET_UART_BAUD_RATE        = 0x42,
+       FT260_SET_UART_DATA_BIT         = 0x43,
+       FT260_SET_UART_PARITY           = 0x44,
+       FT260_SET_UART_STOP_BIT         = 0x45,
+       FT260_SET_UART_BREAKING         = 0x46,
+       FT260_SET_UART_XON_XOFF         = 0x49,
+};
+
+/* Response codes in I2C status report */
+enum {
+       FT260_I2C_STATUS_SUCCESS        = 0x00,
+       FT260_I2C_STATUS_CTRL_BUSY      = 0x01,
+       FT260_I2C_STATUS_ERROR          = 0x02,
+       FT260_I2C_STATUS_ADDR_NO_ACK    = 0x04,
+       FT260_I2C_STATUS_DATA_NO_ACK    = 0x08,
+       FT260_I2C_STATUS_ARBITR_LOST    = 0x10,
+       FT260_I2C_STATUS_CTRL_IDLE      = 0x20,
+       FT260_I2C_STATUS_BUS_BUSY       = 0x40,
+};
+
+/* I2C Conditions flags */
+enum {
+       FT260_FLAG_NONE                 = 0x00,
+       FT260_FLAG_START                = 0x02,
+       FT260_FLAG_START_REPEATED       = 0x03,
+       FT260_FLAG_STOP                 = 0x04,
+       FT260_FLAG_START_STOP           = 0x06,
+       FT260_FLAG_START_STOP_REPEATED  = 0x07,
+};
+
+#define FT260_SET_REQUEST_VALUE(report_id) ((FT260_FEATURE << 8) | report_id)
+
+/* Feature In reports */
+
+struct ft260_get_chip_version_report {
+       u8 report;              /* FT260_CHIP_VERSION */
+       u8 chip_code[4];        /* FTDI chip identification code */
+       u8 reserved[8];
+} __packed;
+
+struct ft260_get_system_status_report {
+       u8 report;              /* FT260_SYSTEM_SETTINGS */
+       u8 chip_mode;           /* DCNF0 and DCNF1 status, bits 0-1 */
+       u8 clock_ctl;           /* 0 - 12MHz, 1 - 24MHz, 2 - 48MHz */
+       u8 suspend_status;      /* 0 - not suspended, 1 - suspended */
+       u8 pwren_status;        /* 0 - FT260 is not ready, 1 - ready */
+       u8 i2c_enable;          /* 0 - disabled, 1 - enabled */
+       u8 uart_mode;           /* 0 - OFF; 1 - RTS_CTS, 2 - DTR_DSR, */
+                               /* 3 - XON_XOFF, 4 - No flow control */
+       u8 hid_over_i2c_en;     /* 0 - disabled, 1 - enabled */
+       u8 gpio2_function;      /* 0 - GPIO,  1 - SUSPOUT, */
+                               /* 2 - PWREN, 4 - TX_LED */
+       u8 gpioA_function;      /* 0 - GPIO, 3 - TX_ACTIVE, 4 - TX_LED */
+       u8 gpioG_function;      /* 0 - GPIO, 2 - PWREN, */
+                               /* 5 - RX_LED, 6 - BCD_DET */
+       u8 suspend_out_pol;     /* 0 - active-high, 1 - active-low */
+       u8 enable_wakeup_int;   /* 0 - disabled, 1 - enabled */
+       u8 intr_cond;           /* Interrupt trigger conditions */
+       u8 power_saving_en;     /* 0 - disabled, 1 - enabled */
+       u8 reserved[10];
+} __packed;
+
+struct ft260_get_i2c_status_report {
+       u8 report;              /* FT260_I2C_STATUS */
+       u8 bus_status;          /* I2C bus status */
+       __le16 clock;           /* I2C bus clock in range 60-3400 KHz */
+       u8 reserved;
+} __packed;
+
+/* Feature Out reports */
+
+struct ft260_set_system_clock_report {
+       u8 report;              /* FT260_SYSTEM_SETTINGS */
+       u8 request;             /* FT260_SET_CLOCK */
+       u8 clock_ctl;           /* 0 - 12MHz, 1 - 24MHz, 2 - 48MHz */
+} __packed;
+
+struct ft260_set_i2c_mode_report {
+       u8 report;              /* FT260_SYSTEM_SETTINGS */
+       u8 request;             /* FT260_SET_I2C_MODE */
+       u8 i2c_enable;          /* 0 - disabled, 1 - enabled */
+} __packed;
+
+struct ft260_set_uart_mode_report {
+       u8 report;              /* FT260_SYSTEM_SETTINGS */
+       u8 request;             /* FT260_SET_UART_MODE */
+       u8 uart_mode;           /* 0 - OFF; 1 - RTS_CTS, 2 - DTR_DSR, */
+                               /* 3 - XON_XOFF, 4 - No flow control */
+} __packed;
+
+struct ft260_set_i2c_reset_report {
+       u8 report;              /* FT260_SYSTEM_SETTINGS */
+       u8 request;             /* FT260_SET_I2C_RESET */
+} __packed;
+
+struct ft260_set_i2c_speed_report {
+       u8 report;              /* FT260_SYSTEM_SETTINGS */
+       u8 request;             /* FT260_SET_I2C_CLOCK_SPEED */
+       __le16 clock;           /* I2C bus clock in range 60-3400 KHz */
+} __packed;
+
+/* Data transfer reports */
+
+struct ft260_i2c_write_request_report {
+       u8 report;              /* FT260_I2C_REPORT */
+       u8 address;             /* 7-bit I2C address */
+       u8 flag;                /* I2C transaction condition */
+       u8 length;              /* data payload length */
+       u8 data[60];            /* data payload */
+} __packed;
+
+struct ft260_i2c_read_request_report {
+       u8 report;              /* FT260_I2C_READ_REQ */
+       u8 address;             /* 7-bit I2C address */
+       u8 flag;                /* I2C transaction condition */
+       __le16 length;          /* data payload length */
+} __packed;
+
+struct ft260_i2c_input_report {
+       u8 report;              /* FT260_I2C_REPORT */
+       u8 length;              /* data payload length */
+       u8 data[2];             /* data payload */
+} __packed;
+
+static const struct hid_device_id ft260_devices[] = {
+       { HID_USB_DEVICE(USB_VENDOR_ID_FUTURE_TECHNOLOGY,
+                        USB_DEVICE_ID_FT260) },
+       { /* END OF LIST */ }
+};
+MODULE_DEVICE_TABLE(hid, ft260_devices);
+
+struct ft260_device {
+       struct i2c_adapter adap;
+       struct hid_device *hdev;
+       struct completion wait;
+       struct mutex lock;
+       u8 write_buf[FT260_REPORT_MAX_LENGTH];
+       u8 *read_buf;
+       u16 read_idx;
+       u16 read_len;
+       u16 clock;
+};
+
+static int ft260_hid_feature_report_get(struct hid_device *hdev,
+                                       unsigned char report_id, u8 *data,
+                                       size_t len)
+{
+       u8 *buf;
+       int ret;
+
+       buf = kmalloc(len, GFP_KERNEL);
+       if (!buf)
+               return -ENOMEM;
+
+       ret = hid_hw_raw_request(hdev, report_id, buf, len, HID_FEATURE_REPORT,
+                                HID_REQ_GET_REPORT);
+       memcpy(data, buf, len);
+       kfree(buf);
+       return ret;
+}
+
+static int ft260_hid_feature_report_set(struct hid_device *hdev, u8 *data,
+                                       size_t len)
+{
+       u8 *buf;
+       int ret;
+
+       buf = kmemdup(data, len, GFP_KERNEL);
+       if (!buf)
+               return -ENOMEM;
+
+       buf[0] = FT260_SYSTEM_SETTINGS;
+
+       ret = hid_hw_raw_request(hdev, buf[0], buf, len, HID_FEATURE_REPORT,
+                                HID_REQ_SET_REPORT);
+
+       kfree(buf);
+       return ret;
+}
+
+static int ft260_i2c_reset(struct hid_device *hdev)
+{
+       struct ft260_set_i2c_reset_report report;
+       int ret;
+
+       report.request = FT260_SET_I2C_RESET;
+
+       ret = ft260_hid_feature_report_set(hdev, (u8 *)&report, sizeof(report));
+       if (ret < 0) {
+               hid_err(hdev, "failed to reset I2C controller: %d\n", ret);
+               return ret;
+       }
+
+       ft260_dbg("done\n");
+       return ret;
+}
+
+static int ft260_xfer_status(struct ft260_device *dev)
+{
+       struct hid_device *hdev = dev->hdev;
+       struct ft260_get_i2c_status_report report;
+       int ret;
+
+       ret = ft260_hid_feature_report_get(hdev, FT260_I2C_STATUS,
+                                          (u8 *)&report, sizeof(report));
+       if (ret < 0) {
+               hid_err(hdev, "failed to retrieve status: %d\n", ret);
+               return ret;
+       }
+
+       dev->clock = le16_to_cpu(report.clock);
+       ft260_dbg("bus_status %#02x, clock %u\n", report.bus_status,
+                 dev->clock);
+
+       if (report.bus_status & FT260_I2C_STATUS_CTRL_BUSY)
+               return -EAGAIN;
+
+       if (report.bus_status & FT260_I2C_STATUS_BUS_BUSY)
+               return -EBUSY;
+
+       if (report.bus_status & FT260_I2C_STATUS_ERROR)
+               return -EIO;
+
+       ret = -EIO;
+
+       if (report.bus_status & FT260_I2C_STATUS_ADDR_NO_ACK)
+               ft260_dbg("unacknowledged address\n");
+
+       if (report.bus_status & FT260_I2C_STATUS_DATA_NO_ACK)
+               ft260_dbg("unacknowledged data\n");
+
+       if (report.bus_status & FT260_I2C_STATUS_ARBITR_LOST)
+               ft260_dbg("arbitration loss\n");
+
+       if (report.bus_status & FT260_I2C_STATUS_CTRL_IDLE)
+               ret = 0;
+
+       return ret;
+}
+
+static int ft260_hid_output_report(struct hid_device *hdev, u8 *data,
+                                  size_t len)
+{
+       u8 *buf;
+       int ret;
+
+       buf = kmemdup(data, len, GFP_KERNEL);
+       if (!buf)
+               return -ENOMEM;
+
+       ret = hid_hw_output_report(hdev, buf, len);
+
+       kfree(buf);
+       return ret;
+}
+
+static int ft260_hid_output_report_check_status(struct ft260_device *dev,
+                                               u8 *data, int len)
+{
+       int ret, usec, try = 3;
+       struct hid_device *hdev = dev->hdev;
+
+       ret = ft260_hid_output_report(hdev, data, len);
+       if (ret < 0) {
+               hid_err(hdev, "%s: failed to start transfer, ret %d\n",
+                       __func__, ret);
+               ft260_i2c_reset(hdev);
+               return ret;
+       }
+
+       /* transfer time = 1 / clock(KHz) * 10 bits * bytes */
+       usec = 10000 / dev->clock * len;
+       usleep_range(usec, usec + 100);
+       ft260_dbg("wait %d usec, len %d\n", usec, len);
+       do {
+               ret = ft260_xfer_status(dev);
+               if (ret != -EAGAIN)
+                       break;
+       } while (--try);
+
+       if (ret == 0 || ret == -EBUSY)
+               return 0;
+
+       ft260_i2c_reset(hdev);
+       return -EIO;
+}
+
+static int ft260_i2c_write(struct ft260_device *dev, u8 addr, u8 *data,
+                          int data_len, u8 flag)
+{
+       int len, ret, idx = 0;
+       struct hid_device *hdev = dev->hdev;
+       struct ft260_i2c_write_request_report *rep =
+               (struct ft260_i2c_write_request_report *)dev->write_buf;
+
+       do {
+               if (data_len <= FT260_WR_DATA_MAX)
+                       len = data_len;
+               else
+                       len = FT260_WR_DATA_MAX;
+
+               rep->report = FT260_I2C_DATA_REPORT_ID(len);
+               rep->address = addr;
+               rep->length = len;
+               rep->flag = flag;
+
+               memcpy(rep->data, &data[idx], len);
+
+               ft260_dbg("rep %#02x addr %#02x off %d len %d d[0] %#02x\n",
+                         rep->report, addr, idx, len, data[0]);
+
+               ret = ft260_hid_output_report_check_status(dev, (u8 *)rep,
+                                                          len + 4);
+               if (ret < 0) {
+                       hid_err(hdev, "%s: failed to start transfer, ret %d\n",
+                               __func__, ret);
+                       return ret;
+               }
+
+               data_len -= len;
+               idx += len;
+
+       } while (data_len > 0);
+
+       return 0;
+}
+
+static int ft260_smbus_write(struct ft260_device *dev, u8 addr, u8 cmd,
+                            u8 *data, u8 data_len, u8 flag)
+{
+       int ret = 0;
+       int len = 4;
+
+       struct ft260_i2c_write_request_report *rep =
+               (struct ft260_i2c_write_request_report *)dev->write_buf;
+
+       rep->address = addr;
+       rep->data[0] = cmd;
+       rep->length = data_len + 1;
+       rep->flag = flag;
+       len += rep->length;
+
+       rep->report = FT260_I2C_DATA_REPORT_ID(len);
+
+       if (data_len > 0)
+               memcpy(&rep->data[1], data, data_len);
+
+       ft260_dbg("rep %#02x addr %#02x cmd %#02x datlen %d replen %d\n",
+                 rep->report, addr, cmd, rep->length, len);
+
+       ret = ft260_hid_output_report_check_status(dev, (u8 *)rep, len);
+
+       return ret;
+}
+
+static int ft260_i2c_read(struct ft260_device *dev, u8 addr, u8 *data,
+                         u16 len, u8 flag)
+{
+       struct ft260_i2c_read_request_report rep;
+       struct hid_device *hdev = dev->hdev;
+       int timeout;
+       int ret;
+
+       if (len > FT260_RD_DATA_MAX) {
+               hid_err(hdev, "%s: unsupported rd len: %d\n", __func__, len);
+               return -EINVAL;
+       }
+
+       dev->read_idx = 0;
+       dev->read_buf = data;
+       dev->read_len = len;
+
+       rep.report = FT260_I2C_READ_REQ;
+       rep.length = cpu_to_le16(len);
+       rep.address = addr;
+       rep.flag = flag;
+
+       ft260_dbg("rep %#02x addr %#02x len %d\n", rep.report, rep.address,
+                 rep.length);
+
+       reinit_completion(&dev->wait);
+
+       ret = ft260_hid_output_report(hdev, (u8 *)&rep, sizeof(rep));
+       if (ret < 0) {
+               hid_err(hdev, "%s: failed to start transaction, ret %d\n",
+                       __func__, ret);
+               return ret;
+       }
+
+       timeout = msecs_to_jiffies(5000);
+       if (!wait_for_completion_timeout(&dev->wait, timeout)) {
+               ft260_i2c_reset(hdev);
+               return -ETIMEDOUT;
+       }
+
+       ret = ft260_xfer_status(dev);
+       if (ret == 0)
+               return 0;
+
+       ft260_i2c_reset(hdev);
+       return -EIO;
+}
+
+/*
+ * A random read operation is implemented as a dummy write operation, followed
+ * by a current address read operation. The dummy write operation is used to
+ * load the target byte address into the current byte address counter, from
+ * which the subsequent current address read operation then reads.
+ */
+static int ft260_i2c_write_read(struct ft260_device *dev, struct i2c_msg *msgs)
+{
+       int len, ret;
+       u16 left_len = msgs[1].len;
+       u8 *read_buf = msgs[1].buf;
+       u8 addr = msgs[0].addr;
+       u16 read_off = 0;
+       struct hid_device *hdev = dev->hdev;
+
+       if (msgs[0].len > 2) {
+               hid_err(hdev, "%s: unsupported wr len: %d\n", __func__,
+                       msgs[0].len);
+               return -EOPNOTSUPP;
+       }
+
+       memcpy(&read_off, msgs[0].buf, msgs[0].len);
+
+       do {
+               if (left_len <= FT260_RD_DATA_MAX)
+                       len = left_len;
+               else
+                       len = FT260_RD_DATA_MAX;
+
+               ft260_dbg("read_off %#x left_len %d len %d\n", read_off,
+                         left_len, len);
+
+               ret = ft260_i2c_write(dev, addr, (u8 *)&read_off, msgs[0].len,
+                                     FT260_FLAG_START);
+               if (ret < 0)
+                       return ret;
+
+               ret = ft260_i2c_read(dev, addr, read_buf, len,
+                                    FT260_FLAG_START_STOP);
+               if (ret < 0)
+                       return ret;
+
+               left_len -= len;
+               read_buf += len;
+               read_off += len;
+
+       } while (left_len > 0);
+
+       return 0;
+}
+
+static int ft260_i2c_xfer(struct i2c_adapter *adapter, struct i2c_msg *msgs,
+                         int num)
+{
+       int ret;
+       struct ft260_device *dev = i2c_get_adapdata(adapter);
+       struct hid_device *hdev = dev->hdev;
+
+       mutex_lock(&dev->lock);
+
+       ret = hid_hw_power(hdev, PM_HINT_FULLON);
+       if (ret < 0) {
+               hid_err(hdev, "failed to enter FULLON power mode: %d\n", ret);
+               mutex_unlock(&dev->lock);
+               return ret;
+       }
+
+       if (num == 1) {
+               if (msgs->flags & I2C_M_RD)
+                       ret = ft260_i2c_read(dev, msgs->addr, msgs->buf,
+                                            msgs->len, FT260_FLAG_START_STOP);
+               else
+                       ret = ft260_i2c_write(dev, msgs->addr, msgs->buf,
+                                             msgs->len, FT260_FLAG_START_STOP);
+               if (ret < 0)
+                       goto i2c_exit;
+
+       } else {
+               /* Combined write then read message */
+               ret = ft260_i2c_write_read(dev, msgs);
+               if (ret < 0)
+                       goto i2c_exit;
+       }
+
+       ret = num;
+i2c_exit:
+       hid_hw_power(hdev, PM_HINT_NORMAL);
+       mutex_unlock(&dev->lock);
+       return ret;
+}
+
+static int ft260_smbus_xfer(struct i2c_adapter *adapter, u16 addr, u16 flags,
+                           char read_write, u8 cmd, int size,
+                           union i2c_smbus_data *data)
+{
+       int ret;
+       struct ft260_device *dev = i2c_get_adapdata(adapter);
+       struct hid_device *hdev = dev->hdev;
+
+       ft260_dbg("smbus size %d\n", size);
+
+       mutex_lock(&dev->lock);
+
+       ret = hid_hw_power(hdev, PM_HINT_FULLON);
+       if (ret < 0) {
+               hid_err(hdev, "power management error: %d\n", ret);
+               mutex_unlock(&dev->lock);
+               return ret;
+       }
+
+       switch (size) {
+       case I2C_SMBUS_QUICK:
+               if (read_write == I2C_SMBUS_READ)
+                       ret = ft260_i2c_read(dev, addr, &data->byte, 0,
+                                            FT260_FLAG_START_STOP);
+               else
+                       ret = ft260_smbus_write(dev, addr, cmd, NULL, 0,
+                                               FT260_FLAG_START_STOP);
+               break;
+       case I2C_SMBUS_BYTE:
+               if (read_write == I2C_SMBUS_READ)
+                       ret = ft260_i2c_read(dev, addr, &data->byte, 1,
+                                            FT260_FLAG_START_STOP);
+               else
+                       ret = ft260_smbus_write(dev, addr, cmd, NULL, 0,
+                                               FT260_FLAG_START_STOP);
+               break;
+       case I2C_SMBUS_BYTE_DATA:
+               if (read_write == I2C_SMBUS_READ) {
+                       ret = ft260_smbus_write(dev, addr, cmd, NULL, 0,
+                                               FT260_FLAG_START);
+                       if (ret)
+                               goto smbus_exit;
+
+                       ret = ft260_i2c_read(dev, addr, &data->byte, 1,
+                                            FT260_FLAG_START_STOP_REPEATED);
+               } else {
+                       ret = ft260_smbus_write(dev, addr, cmd, &data->byte, 1,
+                                               FT260_FLAG_START_STOP);
+               }
+               break;
+       case I2C_SMBUS_WORD_DATA:
+               if (read_write == I2C_SMBUS_READ) {
+                       ret = ft260_smbus_write(dev, addr, cmd, NULL, 0,
+                                               FT260_FLAG_START);
+                       if (ret)
+                               goto smbus_exit;
+
+                       ret = ft260_i2c_read(dev, addr, (u8 *)&data->word, 2,
+                                            FT260_FLAG_START_STOP_REPEATED);
+               } else {
+                       ret = ft260_smbus_write(dev, addr, cmd,
+                                               (u8 *)&data->word, 2,
+                                               FT260_FLAG_START_STOP);
+               }
+               break;
+       case I2C_SMBUS_BLOCK_DATA:
+               if (read_write == I2C_SMBUS_READ) {
+                       ret = ft260_smbus_write(dev, addr, cmd, NULL, 0,
+                                               FT260_FLAG_START);
+                       if (ret)
+                               goto smbus_exit;
+
+                       ret = ft260_i2c_read(dev, addr, data->block,
+                                            data->block[0] + 1,
+                                            FT260_FLAG_START_STOP_REPEATED);
+               } else {
+                       ret = ft260_smbus_write(dev, addr, cmd, data->block,
+                                               data->block[0] + 1,
+                                               FT260_FLAG_START_STOP);
+               }
+               break;
+       case I2C_SMBUS_I2C_BLOCK_DATA:
+               if (read_write == I2C_SMBUS_READ) {
+                       ret = ft260_smbus_write(dev, addr, cmd, NULL, 0,
+                                               FT260_FLAG_START);
+                       if (ret)
+                               goto smbus_exit;
+
+                       ret = ft260_i2c_read(dev, addr, data->block + 1,
+                                            data->block[0],
+                                            FT260_FLAG_START_STOP_REPEATED);
+               } else {
+                       ret = ft260_smbus_write(dev, addr, cmd, data->block + 1,
+                                               data->block[0],
+                                               FT260_FLAG_START_STOP);
+               }
+               break;
+       default:
+               hid_err(hdev, "unsupported smbus transaction size %d\n", size);
+               ret = -EOPNOTSUPP;
+       }
+
+smbus_exit:
+       hid_hw_power(hdev, PM_HINT_NORMAL);
+       mutex_unlock(&dev->lock);
+       return ret;
+}
+
+static u32 ft260_functionality(struct i2c_adapter *adap)
+{
+       return I2C_FUNC_I2C | I2C_FUNC_SMBUS_BYTE | I2C_FUNC_SMBUS_QUICK |
+              I2C_FUNC_SMBUS_BYTE_DATA | I2C_FUNC_SMBUS_WORD_DATA |
+              I2C_FUNC_SMBUS_BLOCK_DATA | I2C_FUNC_SMBUS_I2C_BLOCK;
+}
+
+static const struct i2c_adapter_quirks ft260_i2c_quirks = {
+       .flags = I2C_AQ_COMB_WRITE_THEN_READ,
+       .max_comb_1st_msg_len = 2,
+};
+
+static const struct i2c_algorithm ft260_i2c_algo = {
+       .master_xfer = ft260_i2c_xfer,
+       .smbus_xfer = ft260_smbus_xfer,
+       .functionality = ft260_functionality,
+};
+
+static int ft260_get_system_config(struct hid_device *hdev,
+                                  struct ft260_get_system_status_report *cfg)
+{
+       int ret;
+       int len = sizeof(struct ft260_get_system_status_report);
+
+       ret = ft260_hid_feature_report_get(hdev, FT260_SYSTEM_SETTINGS,
+                                          (u8 *)cfg, len);
+       if (ret != len) {
+               hid_err(hdev, "failed to retrieve system status\n");
+               if (ret >= 0)
+                       return -EIO;
+       }
+       return 0;
+}
+
+static int ft260_is_interface_enabled(struct hid_device *hdev)
+{
+       struct ft260_get_system_status_report cfg;
+       struct usb_interface *usbif = to_usb_interface(hdev->dev.parent);
+       int interface = usbif->cur_altsetting->desc.bInterfaceNumber;
+       int ret;
+
+       ret = ft260_get_system_config(hdev, &cfg);
+       if (ret)
+               return ret;
+
+       ft260_dbg("interface:  0x%02x\n", interface);
+       ft260_dbg("chip mode:  0x%02x\n", cfg.chip_mode);
+       ft260_dbg("clock_ctl:  0x%02x\n", cfg.clock_ctl);
+       ft260_dbg("i2c_enable: 0x%02x\n", cfg.i2c_enable);
+       ft260_dbg("uart_mode:  0x%02x\n", cfg.uart_mode);
+
+       switch (cfg.chip_mode) {
+       case FT260_MODE_ALL:
+       case FT260_MODE_BOTH:
+               if (interface == 1) {
+                       hid_info(hdev, "uart interface is not supported\n");
+                       return 0;
+               }
+               ret = 1;
+               break;
+       case FT260_MODE_UART:
+               if (interface == 0) {
+                       hid_info(hdev, "uart is unsupported on interface 0\n");
+                       ret = 0;
+               }
+               break;
+       case FT260_MODE_I2C:
+               if (interface == 1) {
+                       hid_info(hdev, "i2c is unsupported on interface 1\n");
+                       ret = 0;
+               }
+               break;
+       }
+       return ret;
+}
+
+static int ft260_byte_show(struct hid_device *hdev, int id, u8 *cfg, int len,
+                          u8 *field, u8 *buf)
+{
+       int ret;
+
+       ret = ft260_hid_feature_report_get(hdev, id, cfg, len);
+       if (ret != len && ret >= 0)
+               return -EIO;
+
+       return scnprintf(buf, PAGE_SIZE, "%hi\n", *field);
+}
+
+static int ft260_word_show(struct hid_device *hdev, int id, u8 *cfg, int len,
+                          u16 *field, u8 *buf)
+{
+       int ret;
+
+       ret = ft260_hid_feature_report_get(hdev, id, cfg, len);
+       if (ret != len && ret >= 0)
+               return -EIO;
+
+       return scnprintf(buf, PAGE_SIZE, "%hi\n", le16_to_cpu(*field));
+}
+
+#define FT260_ATTR_SHOW(name, reptype, id, type, func)                        \
+       static ssize_t name##_show(struct device *kdev,                        \
+                                  struct device_attribute *attr, char *buf)   \
+       {                                                                      \
+               struct reptype rep;                                            \
+               struct hid_device *hdev = to_hid_device(kdev);                 \
+               type *field = &rep.name;                                       \
+               int len = sizeof(rep);                                         \
+                                                                              \
+               return func(hdev, id, (u8 *)&rep, len, field, buf);            \
+       }
+
+#define FT260_SSTAT_ATTR_SHOW(name)                                           \
+               FT260_ATTR_SHOW(name, ft260_get_system_status_report,          \
+                               FT260_SYSTEM_SETTINGS, u8, ft260_byte_show)
+
+#define FT260_I2CST_ATTR_SHOW(name)                                           \
+               FT260_ATTR_SHOW(name, ft260_get_i2c_status_report,             \
+                               FT260_I2C_STATUS, u16, ft260_word_show)
+
+#define FT260_ATTR_STORE(name, reptype, id, req, type, func)                  \
+       static ssize_t name##_store(struct device *kdev,                       \
+                                   struct device_attribute *attr,             \
+                                   const char *buf, size_t count)             \
+       {                                                                      \
+               struct reptype rep;                                            \
+               struct hid_device *hdev = to_hid_device(kdev);                 \
+               type name;                                                     \
+               int ret;                                                       \
+                                                                              \
+               if (!func(buf, 10, &name)) {                                   \
+                       rep.name = name;                                       \
+                       rep.report = id;                                       \
+                       rep.request = req;                                     \
+                       ret = ft260_hid_feature_report_set(hdev, (u8 *)&rep,   \
+                                                          sizeof(rep));       \
+                       if (!ret)                                              \
+                               ret = count;                                   \
+               } else {                                                       \
+                       ret = -EINVAL;                                         \
+               }                                                              \
+               return ret;                                                    \
+       }
+
+#define FT260_BYTE_ATTR_STORE(name, reptype, req)                             \
+               FT260_ATTR_STORE(name, reptype, FT260_SYSTEM_SETTINGS, req,    \
+                                u8, kstrtou8)
+
+#define FT260_WORD_ATTR_STORE(name, reptype, req)                             \
+               FT260_ATTR_STORE(name, reptype, FT260_SYSTEM_SETTINGS, req,    \
+                                u16, kstrtou16)
+
+FT260_SSTAT_ATTR_SHOW(chip_mode);
+static DEVICE_ATTR_RO(chip_mode);
+
+FT260_SSTAT_ATTR_SHOW(pwren_status);
+static DEVICE_ATTR_RO(pwren_status);
+
+FT260_SSTAT_ATTR_SHOW(suspend_status);
+static DEVICE_ATTR_RO(suspend_status);
+
+FT260_SSTAT_ATTR_SHOW(hid_over_i2c_en);
+static DEVICE_ATTR_RO(hid_over_i2c_en);
+
+FT260_SSTAT_ATTR_SHOW(power_saving_en);
+static DEVICE_ATTR_RO(power_saving_en);
+
+FT260_SSTAT_ATTR_SHOW(i2c_enable);
+FT260_BYTE_ATTR_STORE(i2c_enable, ft260_set_i2c_mode_report,
+                     FT260_SET_I2C_MODE);
+static DEVICE_ATTR_RW(i2c_enable);
+
+FT260_SSTAT_ATTR_SHOW(uart_mode);
+FT260_BYTE_ATTR_STORE(uart_mode, ft260_set_uart_mode_report,
+                     FT260_SET_UART_MODE);
+static DEVICE_ATTR_RW(uart_mode);
+
+FT260_SSTAT_ATTR_SHOW(clock_ctl);
+FT260_BYTE_ATTR_STORE(clock_ctl, ft260_set_system_clock_report,
+                     FT260_SET_CLOCK);
+static DEVICE_ATTR_RW(clock_ctl);
+
+FT260_I2CST_ATTR_SHOW(clock);
+FT260_WORD_ATTR_STORE(clock, ft260_set_i2c_speed_report,
+                     FT260_SET_I2C_CLOCK_SPEED);
+static DEVICE_ATTR_RW(clock);
+
+static ssize_t i2c_reset_store(struct device *kdev,
+                              struct device_attribute *attr, const char *buf,
+                              size_t count)
+{
+       struct hid_device *hdev = to_hid_device(kdev);
+       int ret = ft260_i2c_reset(hdev);
+
+       if (ret)
+               return ret;
+       return count;
+}
+static DEVICE_ATTR_WO(i2c_reset);
+
+static const struct attribute_group ft260_attr_group = {
+       .attrs = (struct attribute *[]) {
+                 &dev_attr_chip_mode.attr,
+                 &dev_attr_pwren_status.attr,
+                 &dev_attr_suspend_status.attr,
+                 &dev_attr_hid_over_i2c_en.attr,
+                 &dev_attr_power_saving_en.attr,
+                 &dev_attr_i2c_enable.attr,
+                 &dev_attr_uart_mode.attr,
+                 &dev_attr_clock_ctl.attr,
+                 &dev_attr_i2c_reset.attr,
+                 &dev_attr_clock.attr,
+                 NULL
+       }
+};
+
+static int ft260_probe(struct hid_device *hdev, const struct hid_device_id *id)
+{
+       struct ft260_device *dev;
+       struct ft260_get_chip_version_report version;
+       int ret;
+
+       dev = devm_kzalloc(&hdev->dev, sizeof(*dev), GFP_KERNEL);
+       if (!dev)
+               return -ENOMEM;
+
+       ret = hid_parse(hdev);
+       if (ret) {
+               hid_err(hdev, "failed to parse HID\n");
+               return ret;
+       }
+
+       ret = hid_hw_start(hdev, HID_CONNECT_HIDRAW);
+       if (ret) {
+               hid_err(hdev, "failed to start HID HW\n");
+               return ret;
+       }
+
+       ret = hid_hw_open(hdev);
+       if (ret) {
+               hid_err(hdev, "failed to open HID HW\n");
+               goto err_hid_stop;
+       }
+
+       ret = ft260_hid_feature_report_get(hdev, FT260_CHIP_VERSION,
+                                          (u8 *)&version, sizeof(version));
+       if (ret != sizeof(version)) {
+               hid_err(hdev, "failed to retrieve chip version\n");
+               if (ret >= 0)
+                       ret = -EIO;
+               goto err_hid_close;
+       }
+
+       hid_info(hdev, "chip code: %02x%02x %02x%02x\n",
+                version.chip_code[0], version.chip_code[1],
+                version.chip_code[2], version.chip_code[3]);
+
+       ret = ft260_is_interface_enabled(hdev);
+       if (ret <= 0)
+               goto err_hid_close;
+
+       hid_set_drvdata(hdev, dev);
+       dev->hdev = hdev;
+       dev->adap.owner = THIS_MODULE;
+       dev->adap.class = I2C_CLASS_HWMON;
+       dev->adap.algo = &ft260_i2c_algo;
+       dev->adap.quirks = &ft260_i2c_quirks;
+       dev->adap.dev.parent = &hdev->dev;
+       snprintf(dev->adap.name, sizeof(dev->adap.name),
+                "FT260 usb-i2c bridge on hidraw%d",
+                ((struct hidraw *)hdev->hidraw)->minor);
+
+       mutex_init(&dev->lock);
+       init_completion(&dev->wait);
+
+       ret = i2c_add_adapter(&dev->adap);
+       if (ret) {
+               hid_err(hdev, "failed to add i2c adapter\n");
+               goto err_hid_close;
+       }
+
+       i2c_set_adapdata(&dev->adap, dev);
+
+       ret = sysfs_create_group(&hdev->dev.kobj, &ft260_attr_group);
+       if (ret < 0) {
+               hid_err(hdev, "failed to create sysfs attrs\n");
+               goto err_i2c_free;
+       }
+
+       ret = ft260_xfer_status(dev);
+       if (ret)
+               ft260_i2c_reset(hdev);
+
+       return 0;
+
+err_i2c_free:
+       i2c_del_adapter(&dev->adap);
+err_hid_close:
+       hid_hw_close(hdev);
+err_hid_stop:
+       hid_hw_stop(hdev);
+       return ret;
+}
+
+static void ft260_remove(struct hid_device *hdev)
+{
+       int ret;
+       struct ft260_device *dev = hid_get_drvdata(hdev);
+
+       ret = ft260_is_interface_enabled(hdev);
+       if (ret <= 0)
+               return;
+
+       sysfs_remove_group(&hdev->dev.kobj, &ft260_attr_group);
+       i2c_del_adapter(&dev->adap);
+
+       hid_hw_close(hdev);
+       hid_hw_stop(hdev);
+}
+
+static int ft260_raw_event(struct hid_device *hdev, struct hid_report *report,
+                          u8 *data, int size)
+{
+       struct ft260_device *dev = hid_get_drvdata(hdev);
+       struct ft260_i2c_input_report *xfer = (void *)data;
+
+       if (xfer->report >= FT260_I2C_REPORT_MIN &&
+           xfer->report <= FT260_I2C_REPORT_MAX) {
+               ft260_dbg("i2c resp: rep %#02x len %d\n", xfer->report,
+                         xfer->length);
+
+               memcpy(&dev->read_buf[dev->read_idx], &xfer->data,
+                      xfer->length);
+               dev->read_idx += xfer->length;
+
+               if (dev->read_idx == dev->read_len)
+                       complete(&dev->wait);
+
+       } else {
+               hid_err(hdev, "unknown report: %#02x\n", xfer->report);
+               return 0;
+       }
+       return 1;
+}
+
+static struct hid_driver ft260_driver = {
+       .name           = "ft260",
+       .id_table       = ft260_devices,
+       .probe          = ft260_probe,
+       .remove         = ft260_remove,
+       .raw_event      = ft260_raw_event,
+};
+
+module_hid_driver(ft260_driver);
+MODULE_DESCRIPTION("FTDI FT260 USB HID to I2C host bridge");
+MODULE_AUTHOR("Michael Zaidman <michael.zaidman@gmail.com>");
+MODULE_LICENSE("GPL v2");
index 67fd8a2..84b8da3 100644 (file)
@@ -93,6 +93,7 @@
 #define BT_VENDOR_ID_APPLE             0x004c
 #define USB_DEVICE_ID_APPLE_MIGHTYMOUSE        0x0304
 #define USB_DEVICE_ID_APPLE_MAGICMOUSE 0x030d
+#define USB_DEVICE_ID_APPLE_MAGICMOUSE2        0x0269
 #define USB_DEVICE_ID_APPLE_MAGICTRACKPAD      0x030e
 #define USB_DEVICE_ID_APPLE_MAGICTRACKPAD2     0x0265
 #define USB_DEVICE_ID_APPLE_FOUNTAIN_ANSI      0x020e
 
 #define USB_VENDOR_ID_FUTURE_TECHNOLOGY        0x0403
 #define USB_DEVICE_ID_RETRODE2         0x97c1
+#define USB_DEVICE_ID_FT260             0x6030
 
 #define USB_VENDOR_ID_ESSENTIAL_REALITY        0x0d7f
 #define USB_DEVICE_ID_ESSENTIAL_REALITY_P5 0x0100
 #define USB_DEVICE_ID_LOGITECH_27MHZ_MOUSE_RECEIVER    0xc51b
 #define USB_DEVICE_ID_LOGITECH_UNIFYING_RECEIVER       0xc52b
 #define USB_DEVICE_ID_LOGITECH_NANO_RECEIVER           0xc52f
+#define USB_DEVICE_ID_LOGITECH_G700_RECEIVER           0xc531
 #define USB_DEVICE_ID_LOGITECH_UNIFYING_RECEIVER_2     0xc532
 #define USB_DEVICE_ID_LOGITECH_NANO_RECEIVER_2         0xc534
 #define USB_DEVICE_ID_LOGITECH_NANO_RECEIVER_LIGHTSPEED_1      0xc539
 #define USB_DEVICE_ID_SPACETRAVELLER   0xc623
 #define USB_DEVICE_ID_SPACENAVIGATOR   0xc626
 #define USB_DEVICE_ID_DINOVO_DESKTOP   0xc704
-#define USB_DEVICE_ID_DINOVO_EDGE      0xc714
-#define USB_DEVICE_ID_DINOVO_MINI      0xc71f
+#define USB_DEVICE_ID_MX5000_RECEIVER_MOUSE_DEV                0xc70a
+#define USB_DEVICE_ID_MX5000_RECEIVER_KBD_DEV          0xc70e
+#define USB_DEVICE_ID_DINOVO_EDGE_RECEIVER_KBD_DEV     0xc713
+#define USB_DEVICE_ID_DINOVO_EDGE_RECEIVER_MOUSE_DEV   0xc714
+#define USB_DEVICE_ID_MX5500_RECEIVER_KBD_DEV          0xc71b
+#define USB_DEVICE_ID_MX5500_RECEIVER_MOUSE_DEV                0xc71c
+#define USB_DEVICE_ID_DINOVO_MINI_RECEIVER_KBD_DEV     0xc71e
+#define USB_DEVICE_ID_DINOVO_MINI_RECEIVER_MOUSE_DEV   0xc71f
 #define USB_DEVICE_ID_LOGITECH_MOMO_WHEEL2     0xca03
 #define USB_DEVICE_ID_LOGITECH_VIBRATION_WHEEL 0xca04
 
 #define USB_DEVICE_ID_ORTEK_IHOME_IMAC_A210S   0x8003
 
 #define USB_VENDOR_ID_PLANTRONICS      0x047f
+#define USB_DEVICE_ID_PLANTRONICS_BLACKWIRE_3220_SERIES        0xc056
 
 #define USB_VENDOR_ID_PANASONIC                0x04da
 #define USB_DEVICE_ID_PANABOARD_UBT780 0x1044
index 236bccd..18f5e28 100644 (file)
@@ -435,7 +435,8 @@ static int hidinput_get_battery_property(struct power_supply *psy,
        return ret;
 }
 
-static int hidinput_setup_battery(struct hid_device *dev, unsigned report_type, struct hid_field *field)
+static int hidinput_setup_battery(struct hid_device *dev, unsigned report_type,
+                                 struct hid_field *field, bool is_percentage)
 {
        struct power_supply_desc *psy_desc;
        struct power_supply_config psy_cfg = { .drv_data = dev, };
@@ -475,7 +476,7 @@ static int hidinput_setup_battery(struct hid_device *dev, unsigned report_type,
        min = field->logical_minimum;
        max = field->logical_maximum;
 
-       if (quirks & HID_BATTERY_QUIRK_PERCENT) {
+       if (is_percentage || (quirks & HID_BATTERY_QUIRK_PERCENT)) {
                min = 0;
                max = 100;
        }
@@ -552,7 +553,7 @@ static void hidinput_update_battery(struct hid_device *dev, int value)
 }
 #else  /* !CONFIG_HID_BATTERY_STRENGTH */
 static int hidinput_setup_battery(struct hid_device *dev, unsigned report_type,
-                                 struct hid_field *field)
+                                 struct hid_field *field, bool is_percentage)
 {
        return 0;
 }
@@ -806,7 +807,7 @@ static void hidinput_configure_usage(struct hid_input *hidinput, struct hid_fiel
                        break;
 
                case 0x3b: /* Battery Strength */
-                       hidinput_setup_battery(device, HID_INPUT_REPORT, field);
+                       hidinput_setup_battery(device, HID_INPUT_REPORT, field, false);
                        usage->type = EV_PWR;
                        return;
 
@@ -1068,7 +1069,16 @@ static void hidinput_configure_usage(struct hid_input *hidinput, struct hid_fiel
        case HID_UP_GENDEVCTRLS:
                switch (usage->hid) {
                case HID_DC_BATTERYSTRENGTH:
-                       hidinput_setup_battery(device, HID_INPUT_REPORT, field);
+                       hidinput_setup_battery(device, HID_INPUT_REPORT, field, false);
+                       usage->type = EV_PWR;
+                       return;
+               }
+               goto unknown;
+
+       case HID_UP_BATTERY:
+               switch (usage->hid) {
+               case HID_BAT_ABSOLUTESTATEOFCHARGE:
+                       hidinput_setup_battery(device, HID_INPUT_REPORT, field, true);
                        usage->type = EV_PWR;
                        return;
                }
@@ -1672,7 +1682,7 @@ static void report_features(struct hid_device *hid)
                                /* Verify if Battery Strength feature is available */
                                if (usage->hid == HID_DC_BATTERYSTRENGTH)
                                        hidinput_setup_battery(hid, HID_FEATURE_REPORT,
-                                                              rep->field[i]);
+                                                              rep->field[i], false);
 
                                if (drv->feature_mapping)
                                        drv->feature_mapping(hid, rep->field[i], usage);
index c8b40c0..f466163 100644 (file)
@@ -655,7 +655,7 @@ static __u8 *kye_report_fixup(struct hid_device *hdev, __u8 *rdesc,
 }
 
 /**
- * Enable fully-functional tablet mode by setting a special feature report.
+ * kye_tablet_enable() - Enable fully-functional tablet mode by setting a special feature report.
  *
  * @hdev:      HID device
  *
index c6c8e20..93b1f93 100644 (file)
@@ -33,6 +33,9 @@
 
 #include "hid-ids.h"
 
+/* Userspace expects F20 for mic-mute KEY_MICMUTE does not work */
+#define LENOVO_KEY_MICMUTE KEY_F20
+
 struct lenovo_drvdata {
        u8 led_report[3]; /* Must be first for proper alignment */
        int led_state;
@@ -62,8 +65,8 @@ struct lenovo_drvdata {
 #define TP10UBKBD_LED_OFF              1
 #define TP10UBKBD_LED_ON               2
 
-static void lenovo_led_set_tp10ubkbd(struct hid_device *hdev, u8 led_code,
-                                    enum led_brightness value)
+static int lenovo_led_set_tp10ubkbd(struct hid_device *hdev, u8 led_code,
+                                   enum led_brightness value)
 {
        struct lenovo_drvdata *data = hid_get_drvdata(hdev);
        int ret;
@@ -75,10 +78,18 @@ static void lenovo_led_set_tp10ubkbd(struct hid_device *hdev, u8 led_code,
        data->led_report[2] = value ? TP10UBKBD_LED_ON : TP10UBKBD_LED_OFF;
        ret = hid_hw_raw_request(hdev, data->led_report[0], data->led_report, 3,
                                 HID_OUTPUT_REPORT, HID_REQ_SET_REPORT);
-       if (ret)
-               hid_err(hdev, "Set LED output report error: %d\n", ret);
+       if (ret != 3) {
+               if (ret != -ENODEV)
+                       hid_err(hdev, "Set LED output report error: %d\n", ret);
+
+               ret = ret < 0 ? ret : -EIO;
+       } else {
+               ret = 0;
+       }
 
        mutex_unlock(&data->led_report_mutex);
+
+       return ret;
 }
 
 static void lenovo_tp10ubkbd_sync_fn_lock(struct work_struct *work)
@@ -126,7 +137,7 @@ static int lenovo_input_mapping_tpkbd(struct hid_device *hdev,
        if (usage->hid == (HID_UP_BUTTON | 0x0010)) {
                /* This sub-device contains trackpoint, mark it */
                hid_set_drvdata(hdev, (void *)1);
-               map_key_clear(KEY_MICMUTE);
+               map_key_clear(LENOVO_KEY_MICMUTE);
                return 1;
        }
        return 0;
@@ -141,7 +152,7 @@ static int lenovo_input_mapping_cptkbd(struct hid_device *hdev,
            (usage->hid & HID_USAGE_PAGE) == HID_UP_LNVENDOR) {
                switch (usage->hid & HID_USAGE) {
                case 0x00f1: /* Fn-F4: Mic mute */
-                       map_key_clear(KEY_MICMUTE);
+                       map_key_clear(LENOVO_KEY_MICMUTE);
                        return 1;
                case 0x00f2: /* Fn-F5: Brightness down */
                        map_key_clear(KEY_BRIGHTNESSDOWN);
@@ -231,7 +242,7 @@ static int lenovo_input_mapping_tp10_ultrabook_kbd(struct hid_device *hdev,
                        map_key_clear(KEY_FN_ESC);
                        return 1;
                case 9: /* Fn-F4: Mic mute */
-                       map_key_clear(KEY_MICMUTE);
+                       map_key_clear(LENOVO_KEY_MICMUTE);
                        return 1;
                case 10: /* Fn-F7: Control panel */
                        map_key_clear(KEY_CONFIG);
@@ -255,6 +266,54 @@ static int lenovo_input_mapping_tp10_ultrabook_kbd(struct hid_device *hdev,
        return 0;
 }
 
+static int lenovo_input_mapping_x1_tab_kbd(struct hid_device *hdev,
+               struct hid_input *hi, struct hid_field *field,
+               struct hid_usage *usage, unsigned long **bit, int *max)
+{
+       /*
+        * The ThinkPad X1 Tablet Thin Keyboard uses 0x000c0001 usage for
+        * a bunch of keys which have no standard consumer page code.
+        */
+       if (usage->hid == 0x000c0001) {
+               switch (usage->usage_index) {
+               case 0: /* Fn-F10: Enable/disable bluetooth */
+                       map_key_clear(KEY_BLUETOOTH);
+                       return 1;
+               case 1: /* Fn-F11: Keyboard settings */
+                       map_key_clear(KEY_KEYBOARD);
+                       return 1;
+               case 2: /* Fn-F12: User function / Cortana */
+                       map_key_clear(KEY_MACRO1);
+                       return 1;
+               case 3: /* Fn-PrtSc: Snipping tool */
+                       map_key_clear(KEY_SELECTIVE_SCREENSHOT);
+                       return 1;
+               case 8: /* Fn-Esc: Fn-lock toggle */
+                       map_key_clear(KEY_FN_ESC);
+                       return 1;
+               case 9: /* Fn-F4: Mute/unmute microphone */
+                       map_key_clear(KEY_MICMUTE);
+                       return 1;
+               case 10: /* Fn-F9: Settings */
+                       map_key_clear(KEY_CONFIG);
+                       return 1;
+               case 13: /* Fn-F7: Manage external displays */
+                       map_key_clear(KEY_SWITCHVIDEOMODE);
+                       return 1;
+               case 14: /* Fn-F8: Enable/disable wifi */
+                       map_key_clear(KEY_WLAN);
+                       return 1;
+               }
+       }
+
+       if (usage->hid == (HID_UP_KEYBOARD | 0x009a)) {
+               map_key_clear(KEY_SYSRQ);
+               return 1;
+       }
+
+       return 0;
+}
+
 static int lenovo_input_mapping(struct hid_device *hdev,
                struct hid_input *hi, struct hid_field *field,
                struct hid_usage *usage, unsigned long **bit, int *max)
@@ -278,6 +337,8 @@ static int lenovo_input_mapping(struct hid_device *hdev,
        case USB_DEVICE_ID_LENOVO_TP10UBKBD:
                return lenovo_input_mapping_tp10_ultrabook_kbd(hdev, hi, field,
                                                               usage, bit, max);
+       case USB_DEVICE_ID_LENOVO_X1_TAB:
+               return lenovo_input_mapping_x1_tab_kbd(hdev, hi, field, usage, bit, max);
        default:
                return 0;
        }
@@ -349,7 +410,7 @@ static ssize_t attr_fn_lock_store(struct device *dev,
 {
        struct hid_device *hdev = to_hid_device(dev);
        struct lenovo_drvdata *data = hid_get_drvdata(hdev);
-       int value;
+       int value, ret;
 
        if (kstrtoint(buf, 10, &value))
                return -EINVAL;
@@ -364,7 +425,10 @@ static ssize_t attr_fn_lock_store(struct device *dev,
                lenovo_features_set_cptkbd(hdev);
                break;
        case USB_DEVICE_ID_LENOVO_TP10UBKBD:
-               lenovo_led_set_tp10ubkbd(hdev, TP10UBKBD_FN_LOCK_LED, value);
+       case USB_DEVICE_ID_LENOVO_X1_TAB:
+               ret = lenovo_led_set_tp10ubkbd(hdev, TP10UBKBD_FN_LOCK_LED, value);
+               if (ret)
+                       return ret;
                break;
        }
 
@@ -498,11 +562,15 @@ static int lenovo_event_cptkbd(struct hid_device *hdev,
 static int lenovo_event(struct hid_device *hdev, struct hid_field *field,
                struct hid_usage *usage, __s32 value)
 {
+       if (!hid_get_drvdata(hdev))
+               return 0;
+
        switch (hdev->product) {
        case USB_DEVICE_ID_LENOVO_CUSBKBD:
        case USB_DEVICE_ID_LENOVO_CBTKBD:
                return lenovo_event_cptkbd(hdev, field, usage, value);
        case USB_DEVICE_ID_LENOVO_TP10UBKBD:
+       case USB_DEVICE_ID_LENOVO_X1_TAB:
                return lenovo_event_tp10ubkbd(hdev, field, usage, value);
        default:
                return 0;
@@ -761,23 +829,7 @@ static void lenovo_led_set_tpkbd(struct hid_device *hdev)
        hid_hw_request(hdev, report, HID_REQ_SET_REPORT);
 }
 
-static enum led_brightness lenovo_led_brightness_get(
-                       struct led_classdev *led_cdev)
-{
-       struct device *dev = led_cdev->dev->parent;
-       struct hid_device *hdev = to_hid_device(dev);
-       struct lenovo_drvdata *data_pointer = hid_get_drvdata(hdev);
-       int led_nr = 0;
-
-       if (led_cdev == &data_pointer->led_micmute)
-               led_nr = 1;
-
-       return data_pointer->led_state & (1 << led_nr)
-                               ? LED_FULL
-                               : LED_OFF;
-}
-
-static void lenovo_led_brightness_set(struct led_classdev *led_cdev,
+static int lenovo_led_brightness_set(struct led_classdev *led_cdev,
                        enum led_brightness value)
 {
        struct device *dev = led_cdev->dev->parent;
@@ -785,6 +837,7 @@ static void lenovo_led_brightness_set(struct led_classdev *led_cdev,
        struct lenovo_drvdata *data_pointer = hid_get_drvdata(hdev);
        u8 tp10ubkbd_led[] = { TP10UBKBD_MUTE_LED, TP10UBKBD_MICMUTE_LED };
        int led_nr = 0;
+       int ret = 0;
 
        if (led_cdev == &data_pointer->led_micmute)
                led_nr = 1;
@@ -799,9 +852,12 @@ static void lenovo_led_brightness_set(struct led_classdev *led_cdev,
                lenovo_led_set_tpkbd(hdev);
                break;
        case USB_DEVICE_ID_LENOVO_TP10UBKBD:
-               lenovo_led_set_tp10ubkbd(hdev, tp10ubkbd_led[led_nr], value);
+       case USB_DEVICE_ID_LENOVO_X1_TAB:
+               ret = lenovo_led_set_tp10ubkbd(hdev, tp10ubkbd_led[led_nr], value);
                break;
        }
+
+       return ret;
 }
 
 static int lenovo_register_leds(struct hid_device *hdev)
@@ -821,16 +877,20 @@ static int lenovo_register_leds(struct hid_device *hdev)
        snprintf(name_micm, name_sz, "%s:amber:micmute", dev_name(&hdev->dev));
 
        data->led_mute.name = name_mute;
-       data->led_mute.brightness_get = lenovo_led_brightness_get;
-       data->led_mute.brightness_set = lenovo_led_brightness_set;
+       data->led_mute.default_trigger = "audio-mute";
+       data->led_mute.brightness_set_blocking = lenovo_led_brightness_set;
+       data->led_mute.max_brightness = 1;
+       data->led_mute.flags = LED_HW_PLUGGABLE;
        data->led_mute.dev = &hdev->dev;
        ret = led_classdev_register(&hdev->dev, &data->led_mute);
        if (ret < 0)
                return ret;
 
        data->led_micmute.name = name_micm;
-       data->led_micmute.brightness_get = lenovo_led_brightness_get;
-       data->led_micmute.brightness_set = lenovo_led_brightness_set;
+       data->led_micmute.default_trigger = "audio-micmute";
+       data->led_micmute.brightness_set_blocking = lenovo_led_brightness_set;
+       data->led_micmute.max_brightness = 1;
+       data->led_micmute.flags = LED_HW_PLUGGABLE;
        data->led_micmute.dev = &hdev->dev;
        ret = led_classdev_register(&hdev->dev, &data->led_micmute);
        if (ret < 0) {
@@ -952,11 +1012,24 @@ static const struct attribute_group lenovo_attr_group_tp10ubkbd = {
 
 static int lenovo_probe_tp10ubkbd(struct hid_device *hdev)
 {
+       struct hid_report_enum *rep_enum;
        struct lenovo_drvdata *data;
+       struct hid_report *rep;
+       bool found;
        int ret;
 
-       /* All the custom action happens on the USBMOUSE device for USB */
-       if (hdev->type != HID_TYPE_USBMOUSE)
+       /*
+        * The LEDs and the Fn-lock functionality use output report 9,
+        * with an application of 0xffa0001, add the LEDs on the interface
+        * with this output report.
+        */
+       found = false;
+       rep_enum = &hdev->report_enum[HID_OUTPUT_REPORT];
+       list_for_each_entry(rep, &rep_enum->report_list, list) {
+               if (rep->application == 0xffa00001)
+                       found = true;
+       }
+       if (!found)
                return 0;
 
        data = devm_kzalloc(&hdev->dev, sizeof(*data), GFP_KERNEL);
@@ -1018,6 +1091,7 @@ static int lenovo_probe(struct hid_device *hdev,
                ret = lenovo_probe_cptkbd(hdev);
                break;
        case USB_DEVICE_ID_LENOVO_TP10UBKBD:
+       case USB_DEVICE_ID_LENOVO_X1_TAB:
                ret = lenovo_probe_tp10ubkbd(hdev);
                break;
        default:
@@ -1083,6 +1157,7 @@ static void lenovo_remove(struct hid_device *hdev)
                lenovo_remove_cptkbd(hdev);
                break;
        case USB_DEVICE_ID_LENOVO_TP10UBKBD:
+       case USB_DEVICE_ID_LENOVO_X1_TAB:
                lenovo_remove_tp10ubkbd(hdev);
                break;
        }
@@ -1122,6 +1197,12 @@ static const struct hid_device_id lenovo_devices[] = {
        { HID_USB_DEVICE(USB_VENDOR_ID_IBM, USB_DEVICE_ID_IBM_SCROLLPOINT_800DPI_OPTICAL_PRO) },
        { HID_USB_DEVICE(USB_VENDOR_ID_LENOVO, USB_DEVICE_ID_LENOVO_SCROLLPOINT_OPTICAL) },
        { HID_USB_DEVICE(USB_VENDOR_ID_LENOVO, USB_DEVICE_ID_LENOVO_TP10UBKBD) },
+       /*
+        * Note bind to the HID_GROUP_GENERIC group, so that we only bind to the keyboard
+        * part, while letting hid-multitouch.c handle the touchpad and trackpoint.
+        */
+       { HID_DEVICE(BUS_USB, HID_GROUP_GENERIC,
+                    USB_VENDOR_ID_LENOVO, USB_DEVICE_ID_LENOVO_X1_TAB) },
        { }
 };
 
index 0dc7cdf..d40af91 100644 (file)
@@ -568,22 +568,6 @@ static int lg_ultrax_remote_mapping(struct hid_input *hi,
        return 1;
 }
 
-static int lg_dinovo_mapping(struct hid_input *hi, struct hid_usage *usage,
-               unsigned long **bit, int *max)
-{
-       if ((usage->hid & HID_USAGE_PAGE) != HID_UP_LOGIVENDOR)
-               return 0;
-
-       switch (usage->hid & HID_USAGE) {
-
-       case 0x00d: lg_map_key_clear(KEY_MEDIA);        break;
-       default:
-               return 0;
-
-       }
-       return 1;
-}
-
 static int lg_wireless_mapping(struct hid_input *hi, struct hid_usage *usage,
                unsigned long **bit, int *max)
 {
@@ -668,10 +652,6 @@ static int lg_input_mapping(struct hid_device *hdev, struct hid_input *hi,
                        lg_ultrax_remote_mapping(hi, usage, bit, max))
                return 1;
 
-       if (hdev->product == USB_DEVICE_ID_DINOVO_MINI &&
-                       lg_dinovo_mapping(hi, usage, bit, max))
-               return 1;
-
        if ((drv_data->quirks & LG_WIRELESS) && lg_wireless_mapping(hi, usage, bit, max))
                return 1;
 
@@ -879,10 +859,6 @@ static const struct hid_device_id lg_devices[] = {
 
        { HID_USB_DEVICE(USB_VENDOR_ID_LOGITECH, USB_DEVICE_ID_DINOVO_DESKTOP),
                .driver_data = LG_DUPLICATE_USAGES },
-       { HID_USB_DEVICE(USB_VENDOR_ID_LOGITECH, USB_DEVICE_ID_DINOVO_EDGE),
-               .driver_data = LG_DUPLICATE_USAGES },
-       { HID_USB_DEVICE(USB_VENDOR_ID_LOGITECH, USB_DEVICE_ID_DINOVO_MINI),
-               .driver_data = LG_DUPLICATE_USAGES },
 
        { HID_USB_DEVICE(USB_VENDOR_ID_LOGITECH, USB_DEVICE_ID_LOGITECH_ELITE_KBD),
                .driver_data = LG_IGNORE_DOUBLED_WHEEL | LG_EXPANDED_KEYMAP },
index 271bd8d..fa835d5 100644 (file)
@@ -84,6 +84,7 @@
 #define STD_MOUSE                              BIT(2)
 #define MULTIMEDIA                             BIT(3)
 #define POWER_KEYS                             BIT(4)
+#define KBD_MOUSE                              BIT(5)
 #define MEDIA_CENTER                           BIT(8)
 #define KBD_LEDS                               BIT(14)
 /* Fake (bitnr > NUMBER_OF_HID_REPORTS) bit to track HID++ capability */
@@ -117,6 +118,7 @@ enum recvr_type {
        recvr_type_mouse_only,
        recvr_type_27mhz,
        recvr_type_bluetooth,
+       recvr_type_dinovo,
 };
 
 struct dj_report {
@@ -333,6 +335,47 @@ static const char mse_bluetooth_descriptor[] = {
        0xC0,                   /*  END_COLLECTION                      */
 };
 
+/* Mouse descriptor (5) for Bluetooth receiver, normal-res hwheel, 8 buttons */
+static const char mse5_bluetooth_descriptor[] = {
+       0x05, 0x01,             /*  USAGE_PAGE (Generic Desktop)        */
+       0x09, 0x02,             /*  Usage (Mouse)                       */
+       0xa1, 0x01,             /*  Collection (Application)            */
+       0x85, 0x05,             /*   Report ID (5)                      */
+       0x09, 0x01,             /*   Usage (Pointer)                    */
+       0xa1, 0x00,             /*   Collection (Physical)              */
+       0x05, 0x09,             /*    Usage Page (Button)               */
+       0x19, 0x01,             /*    Usage Minimum (1)                 */
+       0x29, 0x08,             /*    Usage Maximum (8)                 */
+       0x15, 0x00,             /*    Logical Minimum (0)               */
+       0x25, 0x01,             /*    Logical Maximum (1)               */
+       0x95, 0x08,             /*    Report Count (8)                  */
+       0x75, 0x01,             /*    Report Size (1)                   */
+       0x81, 0x02,             /*    Input (Data,Var,Abs)              */
+       0x05, 0x01,             /*    Usage Page (Generic Desktop)      */
+       0x16, 0x01, 0xf8,       /*    Logical Minimum (-2047)           */
+       0x26, 0xff, 0x07,       /*    Logical Maximum (2047)            */
+       0x75, 0x0c,             /*    Report Size (12)                  */
+       0x95, 0x02,             /*    Report Count (2)                  */
+       0x09, 0x30,             /*    Usage (X)                         */
+       0x09, 0x31,             /*    Usage (Y)                         */
+       0x81, 0x06,             /*    Input (Data,Var,Rel)              */
+       0x15, 0x81,             /*    Logical Minimum (-127)            */
+       0x25, 0x7f,             /*    Logical Maximum (127)             */
+       0x75, 0x08,             /*    Report Size (8)                   */
+       0x95, 0x01,             /*    Report Count (1)                  */
+       0x09, 0x38,             /*    Usage (Wheel)                     */
+       0x81, 0x06,             /*    Input (Data,Var,Rel)              */
+       0x05, 0x0c,             /*    Usage Page (Consumer Devices)     */
+       0x0a, 0x38, 0x02,       /*    Usage (AC Pan)                    */
+       0x15, 0x81,             /*    Logical Minimum (-127)            */
+       0x25, 0x7f,             /*    Logical Maximum (127)             */
+       0x75, 0x08,             /*    Report Size (8)                   */
+       0x95, 0x01,             /*    Report Count (1)                  */
+       0x81, 0x06,             /*    Input (Data,Var,Rel)              */
+       0xc0,                   /*   End Collection                     */
+       0xc0,                   /*  End Collection                      */
+};
+
 /* Gaming Mouse descriptor (2) */
 static const char mse_high_res_descriptor[] = {
        0x05, 0x01,             /*  USAGE_PAGE (Generic Desktop)        */
@@ -480,6 +523,7 @@ static const char hidpp_descriptor[] = {
 #define MAX_RDESC_SIZE                         \
        (sizeof(kbd_descriptor) +               \
         sizeof(mse_bluetooth_descriptor) +     \
+        sizeof(mse5_bluetooth_descriptor) +    \
         sizeof(consumer_descriptor) +          \
         sizeof(syscontrol_descriptor) +        \
         sizeof(media_descriptor) +     \
@@ -517,6 +561,11 @@ static void delayedwork_callback(struct work_struct *work);
 static LIST_HEAD(dj_hdev_list);
 static DEFINE_MUTEX(dj_hdev_list_lock);
 
+static bool recvr_type_is_bluetooth(enum recvr_type type)
+{
+       return type == recvr_type_bluetooth || type == recvr_type_dinovo;
+}
+
 /*
  * dj/HID++ receivers are really a single logical entity, but for BIOS/Windows
  * compatibility they have multiple USB interfaces. On HID++ receivers we need
@@ -534,7 +583,7 @@ static struct dj_receiver_dev *dj_find_receiver_dev(struct hid_device *hdev,
         * The bluetooth receiver contains a built-in hub and has separate
         * USB-devices for the keyboard and mouse interfaces.
         */
-       sep = (type == recvr_type_bluetooth) ? '.' : '/';
+       sep = recvr_type_is_bluetooth(type) ? '.' : '/';
 
        /* Try to find an already-probed interface from the same device */
        list_for_each_entry(djrcv_dev, &dj_hdev_list, list) {
@@ -872,6 +921,14 @@ static void logi_dj_recv_queue_notification(struct dj_receiver_dev *djrcv_dev,
  * touchpad to work we must also forward mouse input reports to the dj_hiddev
  * created for the keyboard (instead of forwarding them to a second paired
  * device with a device_type of REPORT_TYPE_MOUSE as we normally would).
+ *
+ * On Dinovo receivers the keyboard's touchpad and an optional paired actual
+ * mouse send separate input reports, INPUT(2) aka STD_MOUSE for the mouse
+ * and INPUT(5) aka KBD_MOUSE for the keyboard's touchpad.
+ *
+ * On MX5x00 receivers (which can also be paired with a Dinovo keyboard)
+ * INPUT(2) is used for both an optional paired actual mouse and for the
+ * keyboard's touchpad.
  */
 static const u16 kbd_builtin_touchpad_ids[] = {
        0xb309, /* Dinovo Edge */
@@ -898,7 +955,10 @@ static void logi_hidpp_dev_conn_notif_equad(struct hid_device *hdev,
                id = (workitem->quad_id_msb << 8) | workitem->quad_id_lsb;
                for (i = 0; i < ARRAY_SIZE(kbd_builtin_touchpad_ids); i++) {
                        if (id == kbd_builtin_touchpad_ids[i]) {
-                               workitem->reports_supported |= STD_MOUSE;
+                               if (djrcv_dev->type == recvr_type_dinovo)
+                                       workitem->reports_supported |= KBD_MOUSE;
+                               else
+                                       workitem->reports_supported |= STD_MOUSE;
                                break;
                        }
                }
@@ -1367,7 +1427,7 @@ static int logi_dj_ll_parse(struct hid_device *hid)
                else if (djdev->dj_receiver_dev->type == recvr_type_27mhz)
                        rdcat(rdesc, &rsize, mse_27mhz_descriptor,
                              sizeof(mse_27mhz_descriptor));
-               else if (djdev->dj_receiver_dev->type == recvr_type_bluetooth)
+               else if (recvr_type_is_bluetooth(djdev->dj_receiver_dev->type))
                        rdcat(rdesc, &rsize, mse_bluetooth_descriptor,
                              sizeof(mse_bluetooth_descriptor));
                else
@@ -1375,6 +1435,13 @@ static int logi_dj_ll_parse(struct hid_device *hid)
                              sizeof(mse_descriptor));
        }
 
+       if (djdev->reports_supported & KBD_MOUSE) {
+               dbg_hid("%s: sending a kbd-mouse descriptor, reports_supported: %llx\n",
+                       __func__, djdev->reports_supported);
+               rdcat(rdesc, &rsize, mse5_bluetooth_descriptor,
+                     sizeof(mse5_bluetooth_descriptor));
+       }
+
        if (djdev->reports_supported & MULTIMEDIA) {
                dbg_hid("%s: sending a multimedia report descriptor: %llx\n",
                        __func__, djdev->reports_supported);
@@ -1692,6 +1759,7 @@ static int logi_dj_probe(struct hid_device *hdev,
        case recvr_type_mouse_only:     no_dj_interfaces = 2; break;
        case recvr_type_27mhz:          no_dj_interfaces = 2; break;
        case recvr_type_bluetooth:      no_dj_interfaces = 2; break;
+       case recvr_type_dinovo:         no_dj_interfaces = 2; break;
        }
        if (hid_is_using_ll_driver(hdev, &usb_hid_driver)) {
                intf = to_usb_interface(hdev->dev.parent);
@@ -1857,23 +1925,27 @@ static void logi_dj_remove(struct hid_device *hdev)
 }
 
 static const struct hid_device_id logi_dj_receivers[] = {
-       {HID_USB_DEVICE(USB_VENDOR_ID_LOGITECH,
+       { /* Logitech unifying receiver (0xc52b) */
+         HID_USB_DEVICE(USB_VENDOR_ID_LOGITECH,
                USB_DEVICE_ID_LOGITECH_UNIFYING_RECEIVER),
         .driver_data = recvr_type_dj},
-       {HID_USB_DEVICE(USB_VENDOR_ID_LOGITECH,
+       { /* Logitech unifying receiver (0xc532) */
+         HID_USB_DEVICE(USB_VENDOR_ID_LOGITECH,
                USB_DEVICE_ID_LOGITECH_UNIFYING_RECEIVER_2),
         .driver_data = recvr_type_dj},
-       { /* Logitech Nano mouse only receiver */
+
+       { /* Logitech Nano mouse only receiver (0xc52f) */
          HID_USB_DEVICE(USB_VENDOR_ID_LOGITECH,
                         USB_DEVICE_ID_LOGITECH_NANO_RECEIVER),
         .driver_data = recvr_type_mouse_only},
-       { /* Logitech Nano (non DJ) receiver */
+       { /* Logitech Nano (non DJ) receiver (0xc534) */
          HID_USB_DEVICE(USB_VENDOR_ID_LOGITECH,
                         USB_DEVICE_ID_LOGITECH_NANO_RECEIVER_2),
         .driver_data = recvr_type_hidpp},
+
        { /* Logitech G700(s) receiver (0xc531) */
          HID_USB_DEVICE(USB_VENDOR_ID_LOGITECH,
-               0xc531),
+                        USB_DEVICE_ID_LOGITECH_G700_RECEIVER),
         .driver_data = recvr_type_gaming_hidpp},
        { /* Logitech G602 receiver (0xc537) */
          HID_USB_DEVICE(USB_VENDOR_ID_LOGITECH,
@@ -1883,17 +1955,18 @@ static const struct hid_device_id logi_dj_receivers[] = {
          HID_USB_DEVICE(USB_VENDOR_ID_LOGITECH,
                USB_DEVICE_ID_LOGITECH_NANO_RECEIVER_LIGHTSPEED_1),
         .driver_data = recvr_type_gaming_hidpp},
+       { /* Logitech powerplay receiver (0xc53a) */
+         HID_USB_DEVICE(USB_VENDOR_ID_LOGITECH,
+               USB_DEVICE_ID_LOGITECH_NANO_RECEIVER_POWERPLAY),
+        .driver_data = recvr_type_gaming_hidpp},
        { /* Logitech lightspeed receiver (0xc53f) */
          HID_USB_DEVICE(USB_VENDOR_ID_LOGITECH,
                USB_DEVICE_ID_LOGITECH_NANO_RECEIVER_LIGHTSPEED_1_1),
         .driver_data = recvr_type_gaming_hidpp},
+
        { /* Logitech 27 MHz HID++ 1.0 receiver (0xc513) */
          HID_USB_DEVICE(USB_VENDOR_ID_LOGITECH, USB_DEVICE_ID_MX3000_RECEIVER),
         .driver_data = recvr_type_27mhz},
-       { /* Logitech powerplay receiver (0xc53a) */
-         HID_USB_DEVICE(USB_VENDOR_ID_LOGITECH,
-               USB_DEVICE_ID_LOGITECH_NANO_RECEIVER_POWERPLAY),
-        .driver_data = recvr_type_gaming_hidpp},
        { /* Logitech 27 MHz HID++ 1.0 receiver (0xc517) */
          HID_USB_DEVICE(USB_VENDOR_ID_LOGITECH,
                USB_DEVICE_ID_S510_RECEIVER_2),
@@ -1902,22 +1975,40 @@ static const struct hid_device_id logi_dj_receivers[] = {
          HID_USB_DEVICE(USB_VENDOR_ID_LOGITECH,
                USB_DEVICE_ID_LOGITECH_27MHZ_MOUSE_RECEIVER),
         .driver_data = recvr_type_27mhz},
-       { /* Logitech MX5000 HID++ / bluetooth receiver keyboard intf. */
+
+       { /* Logitech MX5000 HID++ / bluetooth receiver keyboard intf. (0xc70e) */
          HID_USB_DEVICE(USB_VENDOR_ID_LOGITECH,
-               0xc70e),
+               USB_DEVICE_ID_MX5000_RECEIVER_KBD_DEV),
         .driver_data = recvr_type_bluetooth},
-       { /* Logitech MX5000 HID++ / bluetooth receiver mouse intf. */
+       { /* Logitech MX5000 HID++ / bluetooth receiver mouse intf. (0xc70a) */
          HID_USB_DEVICE(USB_VENDOR_ID_LOGITECH,
-               0xc70a),
+               USB_DEVICE_ID_MX5000_RECEIVER_MOUSE_DEV),
         .driver_data = recvr_type_bluetooth},
-       { /* Logitech MX5500 HID++ / bluetooth receiver keyboard intf. */
+       { /* Logitech MX5500 HID++ / bluetooth receiver keyboard intf. (0xc71b) */
          HID_USB_DEVICE(USB_VENDOR_ID_LOGITECH,
-               0xc71b),
+               USB_DEVICE_ID_MX5500_RECEIVER_KBD_DEV),
         .driver_data = recvr_type_bluetooth},
-       { /* Logitech MX5500 HID++ / bluetooth receiver mouse intf. */
+       { /* Logitech MX5500 HID++ / bluetooth receiver mouse intf. (0xc71c) */
          HID_USB_DEVICE(USB_VENDOR_ID_LOGITECH,
-               0xc71c),
+               USB_DEVICE_ID_MX5500_RECEIVER_MOUSE_DEV),
         .driver_data = recvr_type_bluetooth},
+
+       { /* Logitech Dinovo Edge HID++ / bluetooth receiver keyboard intf. (0xc713) */
+         HID_USB_DEVICE(USB_VENDOR_ID_LOGITECH,
+               USB_DEVICE_ID_DINOVO_EDGE_RECEIVER_KBD_DEV),
+        .driver_data = recvr_type_dinovo},
+       { /* Logitech Dinovo Edge HID++ / bluetooth receiver mouse intf. (0xc714) */
+         HID_USB_DEVICE(USB_VENDOR_ID_LOGITECH,
+               USB_DEVICE_ID_DINOVO_EDGE_RECEIVER_MOUSE_DEV),
+        .driver_data = recvr_type_dinovo},
+       { /* Logitech DiNovo Mini HID++ / bluetooth receiver mouse intf. (0xc71e) */
+         HID_USB_DEVICE(USB_VENDOR_ID_LOGITECH,
+               USB_DEVICE_ID_DINOVO_MINI_RECEIVER_KBD_DEV),
+        .driver_data = recvr_type_dinovo},
+       { /* Logitech DiNovo Mini HID++ / bluetooth receiver keyboard intf. (0xc71f) */
+         HID_USB_DEVICE(USB_VENDOR_ID_LOGITECH,
+               USB_DEVICE_ID_DINOVO_MINI_RECEIVER_MOUSE_DEV),
+        .driver_data = recvr_type_dinovo},
        {}
 };
 
index d459e2d..d598094 100644 (file)
@@ -261,7 +261,7 @@ static int __hidpp_send_report(struct hid_device *hdev,
        return ret == fields_count ? 0 : -1;
 }
 
-/**
+/*
  * hidpp_send_message_sync() returns 0 in case of success, and something else
  * in case of a failure.
  * - If ' something else' is positive, that means that an error has been raised
@@ -423,7 +423,7 @@ static inline bool hidpp_report_is_connect_event(struct hidpp_device *hidpp,
                (report->rap.sub_id == 0x41));
 }
 
-/**
+/*
  * hidpp_prefix_name() prefixes the current given name with "Logitech ".
  */
 static void hidpp_prefix_name(char **name, int name_length)
@@ -454,6 +454,7 @@ static void hidpp_prefix_name(char **name, int name_length)
  * hidpp_scroll_counter_handle_scroll() - Send high- and low-resolution scroll
  *                                        events given a high-resolution wheel
  *                                        movement.
+ * @input_dev: Pointer to the input device
  * @counter: a hid_scroll_counter struct describing the wheel.
  * @hi_res_value: the movement of the wheel, in the mouse's high-resolution
  *                units.
@@ -1884,7 +1885,7 @@ struct hidpp_touchpad_fw_items {
        uint8_t persistent;
 };
 
-/**
+/*
  * send a set state command to the device by reading the current items->state
  * field. items is then filled with the current state.
  */
index abd8690..2bb473d 100644 (file)
@@ -16,6 +16,7 @@
 #include <linux/input/mt.h>
 #include <linux/module.h>
 #include <linux/slab.h>
+#include <linux/workqueue.h>
 
 #include "hid-ids.h"
 
@@ -54,6 +55,7 @@ MODULE_PARM_DESC(report_undeciphered, "Report undeciphered multi-touch state fie
 #define TRACKPAD2_USB_REPORT_ID 0x02
 #define TRACKPAD2_BT_REPORT_ID 0x31
 #define MOUSE_REPORT_ID    0x29
+#define MOUSE2_REPORT_ID   0x12
 #define DOUBLE_REPORT_ID   0xf7
 /* These definitions are not precise, but they're close enough.  (Bits
  * 0x03 seem to indicate the aspect ratio of the touch, bits 0x70 seem
@@ -127,6 +129,9 @@ struct magicmouse_sc {
                u8 size;
        } touches[16];
        int tracking_ids[16];
+
+       struct hid_device *hdev;
+       struct delayed_work work;
 };
 
 static int magicmouse_firm_touch(struct magicmouse_sc *msc)
@@ -195,7 +200,8 @@ static void magicmouse_emit_touch(struct magicmouse_sc *msc, int raw_id, u8 *tda
        int id, x, y, size, orientation, touch_major, touch_minor, state, down;
        int pressure = 0;
 
-       if (input->id.product == USB_DEVICE_ID_APPLE_MAGICMOUSE) {
+       if (input->id.product == USB_DEVICE_ID_APPLE_MAGICMOUSE ||
+           input->id.product == USB_DEVICE_ID_APPLE_MAGICMOUSE2) {
                id = (tdata[6] << 2 | tdata[5] >> 6) & 0xf;
                x = (tdata[1] << 28 | tdata[0] << 20) >> 20;
                y = -((tdata[2] << 24 | tdata[1] << 16) >> 20);
@@ -296,7 +302,8 @@ static void magicmouse_emit_touch(struct magicmouse_sc *msc, int raw_id, u8 *tda
                        input_report_abs(input, ABS_MT_PRESSURE, pressure);
 
                if (report_undeciphered) {
-                       if (input->id.product == USB_DEVICE_ID_APPLE_MAGICMOUSE)
+                       if (input->id.product == USB_DEVICE_ID_APPLE_MAGICMOUSE ||
+                           input->id.product == USB_DEVICE_ID_APPLE_MAGICMOUSE2)
                                input_event(input, EV_MSC, MSC_RAW, tdata[7]);
                        else if (input->id.product !=
                                        USB_DEVICE_ID_APPLE_MAGICTRACKPAD2)
@@ -380,6 +387,34 @@ static int magicmouse_raw_event(struct hid_device *hdev,
                 * ts = data[3] >> 6 | data[4] << 2 | data[5] << 10;
                 */
                break;
+       case MOUSE2_REPORT_ID:
+               /* Size is either 8 or (14 + 8 * N) */
+               if (size != 8 && (size < 14 || (size - 14) % 8 != 0))
+                       return 0;
+               npoints = (size - 14) / 8;
+               if (npoints > 15) {
+                       hid_warn(hdev, "invalid size value (%d) for MOUSE2_REPORT_ID\n",
+                                       size);
+                       return 0;
+               }
+               msc->ntouches = 0;
+               for (ii = 0; ii < npoints; ii++)
+                       magicmouse_emit_touch(msc, ii, data + ii * 8 + 14);
+
+               /* When emulating three-button mode, it is important
+                * to have the current touch information before
+                * generating a click event.
+                */
+               x = (int)((data[3] << 24) | (data[2] << 16)) >> 16;
+               y = (int)((data[5] << 24) | (data[4] << 16)) >> 16;
+               clicks = data[1];
+
+               /* The following bits provide a device specific timestamp. They
+                * are unused here.
+                *
+                * ts = data[11] >> 6 | data[12] << 2 | data[13] << 10;
+                */
+               break;
        case DOUBLE_REPORT_ID:
                /* Sometimes the trackpad sends two touch reports in one
                 * packet.
@@ -392,7 +427,8 @@ static int magicmouse_raw_event(struct hid_device *hdev,
                return 0;
        }
 
-       if (input->id.product == USB_DEVICE_ID_APPLE_MAGICMOUSE) {
+       if (input->id.product == USB_DEVICE_ID_APPLE_MAGICMOUSE ||
+           input->id.product == USB_DEVICE_ID_APPLE_MAGICMOUSE2) {
                magicmouse_emit_buttons(msc, clicks & 3);
                input_report_rel(input, REL_X, x);
                input_report_rel(input, REL_Y, y);
@@ -408,6 +444,23 @@ static int magicmouse_raw_event(struct hid_device *hdev,
        return 1;
 }
 
+static int magicmouse_event(struct hid_device *hdev, struct hid_field *field,
+               struct hid_usage *usage, __s32 value)
+{
+       struct magicmouse_sc *msc = hid_get_drvdata(hdev);
+       if (msc->input->id.product == USB_DEVICE_ID_APPLE_MAGICMOUSE2 &&
+           field->report->id == MOUSE2_REPORT_ID) {
+               /*
+                * magic_mouse_raw_event has done all the work. Skip hidinput.
+                *
+                * Specifically, hidinput may modify BTN_LEFT and BTN_RIGHT,
+                * breaking emulate_3button.
+                */
+               return 1;
+       }
+       return 0;
+}
+
 static int magicmouse_setup_input(struct input_dev *input, struct hid_device *hdev)
 {
        int error;
@@ -415,7 +468,8 @@ static int magicmouse_setup_input(struct input_dev *input, struct hid_device *hd
 
        __set_bit(EV_KEY, input->evbit);
 
-       if (input->id.product == USB_DEVICE_ID_APPLE_MAGICMOUSE) {
+       if (input->id.product == USB_DEVICE_ID_APPLE_MAGICMOUSE ||
+           input->id.product == USB_DEVICE_ID_APPLE_MAGICMOUSE2) {
                __set_bit(BTN_LEFT, input->keybit);
                __set_bit(BTN_RIGHT, input->keybit);
                if (emulate_3button)
@@ -480,7 +534,8 @@ static int magicmouse_setup_input(struct input_dev *input, struct hid_device *hd
         * the origin at the same position, and just uses the additive
         * inverse of the reported Y.
         */
-       if (input->id.product == USB_DEVICE_ID_APPLE_MAGICMOUSE) {
+       if (input->id.product == USB_DEVICE_ID_APPLE_MAGICMOUSE ||
+           input->id.product == USB_DEVICE_ID_APPLE_MAGICMOUSE2) {
                input_set_abs_params(input, ABS_MT_ORIENTATION, -31, 32, 1, 0);
                input_set_abs_params(input, ABS_MT_POSITION_X,
                                     MOUSE_MIN_X, MOUSE_MAX_X, 4, 0);
@@ -580,19 +635,60 @@ static int magicmouse_input_configured(struct hid_device *hdev,
        return 0;
 }
 
-
-static int magicmouse_probe(struct hid_device *hdev,
-       const struct hid_device_id *id)
+static int magicmouse_enable_multitouch(struct hid_device *hdev)
 {
        const u8 *feature;
        const u8 feature_mt[] = { 0xD7, 0x01 };
+       const u8 feature_mt_mouse2[] = { 0xF1, 0x02, 0x01 };
        const u8 feature_mt_trackpad2_usb[] = { 0x02, 0x01 };
        const u8 feature_mt_trackpad2_bt[] = { 0xF1, 0x02, 0x01 };
        u8 *buf;
+       int ret;
+       int feature_size;
+
+       if (hdev->product == USB_DEVICE_ID_APPLE_MAGICTRACKPAD2) {
+               if (hdev->vendor == BT_VENDOR_ID_APPLE) {
+                       feature_size = sizeof(feature_mt_trackpad2_bt);
+                       feature = feature_mt_trackpad2_bt;
+               } else { /* USB_VENDOR_ID_APPLE */
+                       feature_size = sizeof(feature_mt_trackpad2_usb);
+                       feature = feature_mt_trackpad2_usb;
+               }
+       } else if (hdev->product == USB_DEVICE_ID_APPLE_MAGICMOUSE2) {
+               feature_size = sizeof(feature_mt_mouse2);
+               feature = feature_mt_mouse2;
+       } else {
+               feature_size = sizeof(feature_mt);
+               feature = feature_mt;
+       }
+
+       buf = kmemdup(feature, feature_size, GFP_KERNEL);
+       if (!buf)
+               return -ENOMEM;
+
+       ret = hid_hw_raw_request(hdev, buf[0], buf, feature_size,
+                               HID_FEATURE_REPORT, HID_REQ_SET_REPORT);
+       kfree(buf);
+       return ret;
+}
+
+static void magicmouse_enable_mt_work(struct work_struct *work)
+{
+       struct magicmouse_sc *msc =
+               container_of(work, struct magicmouse_sc, work.work);
+       int ret;
+
+       ret = magicmouse_enable_multitouch(msc->hdev);
+       if (ret < 0)
+               hid_err(msc->hdev, "unable to request touch data (%d)\n", ret);
+}
+
+static int magicmouse_probe(struct hid_device *hdev,
+       const struct hid_device_id *id)
+{
        struct magicmouse_sc *msc;
        struct hid_report *report;
        int ret;
-       int feature_size;
 
        if (id->vendor == USB_VENDOR_ID_APPLE &&
            id->product == USB_DEVICE_ID_APPLE_MAGICTRACKPAD2 &&
@@ -606,6 +702,8 @@ static int magicmouse_probe(struct hid_device *hdev,
        }
 
        msc->scroll_accel = SCROLL_ACCEL_DEFAULT;
+       msc->hdev = hdev;
+       INIT_DEFERRABLE_WORK(&msc->work, magicmouse_enable_mt_work);
 
        msc->quirks = id->driver_data;
        hid_set_drvdata(hdev, msc);
@@ -631,6 +729,9 @@ static int magicmouse_probe(struct hid_device *hdev,
        if (id->product == USB_DEVICE_ID_APPLE_MAGICMOUSE)
                report = hid_register_report(hdev, HID_INPUT_REPORT,
                        MOUSE_REPORT_ID, 0);
+       else if (id->product == USB_DEVICE_ID_APPLE_MAGICMOUSE2)
+               report = hid_register_report(hdev, HID_INPUT_REPORT,
+                       MOUSE2_REPORT_ID, 0);
        else if (id->product == USB_DEVICE_ID_APPLE_MAGICTRACKPAD2) {
                if (id->vendor == BT_VENDOR_ID_APPLE)
                        report = hid_register_report(hdev, HID_INPUT_REPORT,
@@ -652,25 +753,6 @@ static int magicmouse_probe(struct hid_device *hdev,
        }
        report->size = 6;
 
-       if (id->product == USB_DEVICE_ID_APPLE_MAGICTRACKPAD2) {
-               if (id->vendor == BT_VENDOR_ID_APPLE) {
-                       feature_size = sizeof(feature_mt_trackpad2_bt);
-                       feature = feature_mt_trackpad2_bt;
-               } else { /* USB_VENDOR_ID_APPLE */
-                       feature_size = sizeof(feature_mt_trackpad2_usb);
-                       feature = feature_mt_trackpad2_usb;
-               }
-       } else {
-               feature_size = sizeof(feature_mt);
-               feature = feature_mt;
-       }
-
-       buf = kmemdup(feature, feature_size, GFP_KERNEL);
-       if (!buf) {
-               ret = -ENOMEM;
-               goto err_stop_hw;
-       }
-
        /*
         * Some devices repond with 'invalid report id' when feature
         * report switching it into multitouch mode is sent to it.
@@ -679,13 +761,14 @@ static int magicmouse_probe(struct hid_device *hdev,
         * but there seems to be no other way of switching the mode.
         * Thus the super-ugly hacky success check below.
         */
-       ret = hid_hw_raw_request(hdev, buf[0], buf, feature_size,
-                               HID_FEATURE_REPORT, HID_REQ_SET_REPORT);
-       kfree(buf);
-       if (ret != -EIO && ret != feature_size) {
+       ret = magicmouse_enable_multitouch(hdev);
+       if (ret != -EIO && ret < 0) {
                hid_err(hdev, "unable to request touch data (%d)\n", ret);
                goto err_stop_hw;
        }
+       if (ret == -EIO && id->product == USB_DEVICE_ID_APPLE_MAGICMOUSE2) {
+               schedule_delayed_work(&msc->work, msecs_to_jiffies(500));
+       }
 
        return 0;
 err_stop_hw:
@@ -693,9 +776,18 @@ err_stop_hw:
        return ret;
 }
 
+static void magicmouse_remove(struct hid_device *hdev)
+{
+       struct magicmouse_sc *msc = hid_get_drvdata(hdev);
+       cancel_delayed_work_sync(&msc->work);
+       hid_hw_stop(hdev);
+}
+
 static const struct hid_device_id magic_mice[] = {
        { HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_APPLE,
                USB_DEVICE_ID_APPLE_MAGICMOUSE), .driver_data = 0 },
+       { HID_BLUETOOTH_DEVICE(BT_VENDOR_ID_APPLE,
+               USB_DEVICE_ID_APPLE_MAGICMOUSE2), .driver_data = 0 },
        { HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_APPLE,
                USB_DEVICE_ID_APPLE_MAGICTRACKPAD), .driver_data = 0 },
        { HID_BLUETOOTH_DEVICE(BT_VENDOR_ID_APPLE,
@@ -710,7 +802,9 @@ static struct hid_driver magicmouse_driver = {
        .name = "magicmouse",
        .id_table = magic_mice,
        .probe = magicmouse_probe,
+       .remove = magicmouse_remove,
        .raw_event = magicmouse_raw_event,
+       .event = magicmouse_event,
        .input_mapping = magicmouse_input_mapping,
        .input_configured = magicmouse_input_configured,
 };
index 1b5c632..bbda231 100644 (file)
@@ -329,7 +329,6 @@ static int picolcd_raw_event(struct hid_device *hdev,
 {
        struct picolcd_data *data = hid_get_drvdata(hdev);
        unsigned long flags;
-       int ret = 0;
 
        if (!data)
                return 1;
@@ -342,9 +341,9 @@ static int picolcd_raw_event(struct hid_device *hdev,
 
        if (report->id == REPORT_KEY_STATE) {
                if (data->input_keys)
-                       ret = picolcd_raw_keypad(data, report, raw_data+1, size-1);
+                       picolcd_raw_keypad(data, report, raw_data+1, size-1);
        } else if (report->id == REPORT_IR_DATA) {
-               ret = picolcd_raw_cir(data, report, raw_data+1, size-1);
+               picolcd_raw_cir(data, report, raw_data+1, size-1);
        } else {
                spin_lock_irqsave(&data->lock, flags);
                /*
index 85b685e..e81b7ce 100644 (file)
@@ -13,6 +13,7 @@
 
 #include <linux/hid.h>
 #include <linux/module.h>
+#include <linux/jiffies.h>
 
 #define PLT_HID_1_0_PAGE       0xffa00000
 #define PLT_HID_2_0_PAGE       0xffa20000
 #define PLT_ALLOW_CONSUMER (field->application == HID_CP_CONSUMERCONTROL && \
                            (usage->hid & HID_USAGE_PAGE) == HID_UP_CONSUMER)
 
+#define PLT_QUIRK_DOUBLE_VOLUME_KEYS BIT(0)
+
+#define PLT_DOUBLE_KEY_TIMEOUT 5 /* ms */
+
+struct plt_drv_data {
+       unsigned long device_type;
+       unsigned long last_volume_key_ts;
+       u32 quirks;
+};
+
 static int plantronics_input_mapping(struct hid_device *hdev,
                                     struct hid_input *hi,
                                     struct hid_field *field,
@@ -43,7 +54,8 @@ static int plantronics_input_mapping(struct hid_device *hdev,
                                     unsigned long **bit, int *max)
 {
        unsigned short mapped_key;
-       unsigned long plt_type = (unsigned long)hid_get_drvdata(hdev);
+       struct plt_drv_data *drv_data = hid_get_drvdata(hdev);
+       unsigned long plt_type = drv_data->device_type;
 
        /* special case for PTT products */
        if (field->application == HID_GD_JOYSTICK)
@@ -105,6 +117,30 @@ mapped:
        return 1;
 }
 
+static int plantronics_event(struct hid_device *hdev, struct hid_field *field,
+                            struct hid_usage *usage, __s32 value)
+{
+       struct plt_drv_data *drv_data = hid_get_drvdata(hdev);
+
+       if (drv_data->quirks & PLT_QUIRK_DOUBLE_VOLUME_KEYS) {
+               unsigned long prev_ts, cur_ts;
+
+               /* Usages are filtered in plantronics_usages. */
+
+               if (!value) /* Handle key presses only. */
+                       return 0;
+
+               prev_ts = drv_data->last_volume_key_ts;
+               cur_ts = jiffies;
+               if (jiffies_to_msecs(cur_ts - prev_ts) <= PLT_DOUBLE_KEY_TIMEOUT)
+                       return 1; /* Ignore the repeated key. */
+
+               drv_data->last_volume_key_ts = cur_ts;
+       }
+
+       return 0;
+}
+
 static unsigned long plantronics_device_type(struct hid_device *hdev)
 {
        unsigned i, col_page;
@@ -133,15 +169,24 @@ exit:
 static int plantronics_probe(struct hid_device *hdev,
                             const struct hid_device_id *id)
 {
+       struct plt_drv_data *drv_data;
        int ret;
 
+       drv_data = devm_kzalloc(&hdev->dev, sizeof(*drv_data), GFP_KERNEL);
+       if (!drv_data)
+               return -ENOMEM;
+
        ret = hid_parse(hdev);
        if (ret) {
                hid_err(hdev, "parse failed\n");
                goto err;
        }
 
-       hid_set_drvdata(hdev, (void *)plantronics_device_type(hdev));
+       drv_data->device_type = plantronics_device_type(hdev);
+       drv_data->quirks = id->driver_data;
+       drv_data->last_volume_key_ts = jiffies - msecs_to_jiffies(PLT_DOUBLE_KEY_TIMEOUT);
+
+       hid_set_drvdata(hdev, drv_data);
 
        ret = hid_hw_start(hdev, HID_CONNECT_DEFAULT |
                HID_CONNECT_HIDINPUT_FORCE | HID_CONNECT_HIDDEV_FORCE);
@@ -153,15 +198,26 @@ err:
 }
 
 static const struct hid_device_id plantronics_devices[] = {
+       { HID_USB_DEVICE(USB_VENDOR_ID_PLANTRONICS,
+                                        USB_DEVICE_ID_PLANTRONICS_BLACKWIRE_3220_SERIES),
+               .driver_data = PLT_QUIRK_DOUBLE_VOLUME_KEYS },
        { HID_USB_DEVICE(USB_VENDOR_ID_PLANTRONICS, HID_ANY_ID) },
        { }
 };
 MODULE_DEVICE_TABLE(hid, plantronics_devices);
 
+static const struct hid_usage_id plantronics_usages[] = {
+       { HID_CP_VOLUMEUP, EV_KEY, HID_ANY_ID },
+       { HID_CP_VOLUMEDOWN, EV_KEY, HID_ANY_ID },
+       { HID_TERMINATOR, HID_TERMINATOR, HID_TERMINATOR }
+};
+
 static struct hid_driver plantronics_driver = {
        .name = "plantronics",
        .id_table = plantronics_devices,
+       .usage_table = plantronics_usages,
        .input_mapping = plantronics_input_mapping,
+       .event = plantronics_event,
        .probe = plantronics_probe,
 };
 module_hid_driver(plantronics_driver);
index 1a9daf0..3dd6f15 100644 (file)
@@ -445,8 +445,6 @@ static const struct hid_device_id hid_have_special_driver[] = {
        { HID_USB_DEVICE(USB_VENDOR_ID_LOGITECH, USB_DEVICE_ID_S510_RECEIVER) },
        { HID_USB_DEVICE(USB_VENDOR_ID_LOGITECH, USB_DEVICE_ID_LOGITECH_RECEIVER) },
        { HID_USB_DEVICE(USB_VENDOR_ID_LOGITECH, USB_DEVICE_ID_DINOVO_DESKTOP) },
-       { HID_USB_DEVICE(USB_VENDOR_ID_LOGITECH, USB_DEVICE_ID_DINOVO_EDGE) },
-       { HID_USB_DEVICE(USB_VENDOR_ID_LOGITECH, USB_DEVICE_ID_DINOVO_MINI) },
        { HID_USB_DEVICE(USB_VENDOR_ID_LOGITECH, USB_DEVICE_ID_LOGITECH_ELITE_KBD) },
        { HID_USB_DEVICE(USB_VENDOR_ID_LOGITECH, USB_DEVICE_ID_LOGITECH_CORDLESS_DESKTOP_LX500) },
        { HID_USB_DEVICE(USB_VENDOR_ID_LOGITECH, USB_DEVICE_ID_LOGITECH_EXTREME_3D) },
@@ -661,6 +659,9 @@ static const struct hid_device_id hid_have_special_driver[] = {
        { HID_USB_DEVICE(USB_VENDOR_ID_THRUSTMASTER, 0xb654) },
        { HID_USB_DEVICE(USB_VENDOR_ID_THRUSTMASTER, 0xb65a) },
 #endif
+#if IS_ENABLED(CONFIG_HID_TMINIT)
+       { HID_USB_DEVICE(USB_VENDOR_ID_THRUSTMASTER, 0xb65d) },
+#endif
 #if IS_ENABLED(CONFIG_HID_TIVO)
        { 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) },
index 2628bc5..2e66621 100644 (file)
@@ -397,15 +397,14 @@ static ssize_t store_value(struct device *dev, struct device_attribute *attr,
 
        if (!strncmp(name, "value", strlen("value"))) {
                u32 report_id;
-               int ret;
 
                if (kstrtoint(buf, 0, &value) != 0)
                        return -EINVAL;
 
                report_id = sensor_inst->fields[field_index].attribute.
                                                                report_id;
-               ret = sensor_hub_set_feature(sensor_inst->hsdev, report_id,
-                                            index, sizeof(value), &value);
+               sensor_hub_set_feature(sensor_inst->hsdev, report_id,
+                                      index, sizeof(value), &value);
        } else
                return -EINVAL;
 
index 3dd7d32..95cf88f 100644 (file)
@@ -18,7 +18,6 @@
 
 /**
  * struct sensor_hub_data - Hold a instance data for a HID hub device
- * @hsdev:             Stored hid instance for current hub device.
  * @mutex:             Mutex to serialize synchronous request.
  * @lock:              Spin lock to protect pending request structure.
  * @dyn_callback_list: Holds callback function
@@ -34,7 +33,6 @@ struct sensor_hub_data {
        spinlock_t dyn_callback_lock;
        struct mfd_cell *hid_sensor_hub_client_devs;
        int hid_sensor_client_cnt;
-       unsigned long quirks;
        int ref_cnt;
 };
 
@@ -42,6 +40,7 @@ struct sensor_hub_data {
  * struct hid_sensor_hub_callbacks_list - Stores callback list
  * @list:              list head.
  * @usage_id:          usage id for a physical device.
+ * @hsdev:             Stored hid instance for current hub device.
  * @usage_callback:    Stores registered callback functions.
  * @priv:              Private data for a physical device.
  */
@@ -615,7 +614,6 @@ static int sensor_hub_probe(struct hid_device *hdev,
        }
 
        hid_set_drvdata(hdev, sd);
-       sd->quirks = id->driver_data;
 
        spin_lock_init(&sd->lock);
        spin_lock_init(&sd->dyn_callback_lock);
diff --git a/drivers/hid/hid-thrustmaster.c b/drivers/hid/hid-thrustmaster.c
new file mode 100644 (file)
index 0000000..2e452c6
--- /dev/null
@@ -0,0 +1,371 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * When connected to the machine, the Thrustmaster wheels appear as
+ * a Â«generic» hid gamepad called "Thrustmaster FFB Wheel".
+ *
+ * When in this mode not every functionality of the wheel, like the force feedback,
+ * are available. To enable all functionalities of a Thrustmaster wheel we have to send
+ * to it a specific USB CONTROL request with a code different for each wheel.
+ *
+ * This driver tries to understand which model of Thrustmaster wheel the generic
+ * "Thrustmaster FFB Wheel" really is and then sends the appropriate control code.
+ *
+ * Copyright (c) 2020-2021 Dario Pagani <dario.pagani.146+linuxk@gmail.com>
+ * Copyright (c) 2020-2021 Kim Kuparinen <kimi.h.kuparinen@gmail.com>
+ */
+#include <linux/hid.h>
+#include <linux/usb.h>
+#include <linux/input.h>
+#include <linux/slab.h>
+#include <linux/module.h>
+
+/*
+ * These interrupts are used to prevent a nasty crash when initializing the
+ * T300RS. Used in thrustmaster_interrupts().
+ */
+static const u8 setup_0[] = { 0x42, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 };
+static const u8 setup_1[] = { 0x0a, 0x04, 0x90, 0x03, 0x00, 0x00, 0x00, 0x00 };
+static const u8 setup_2[] = { 0x0a, 0x04, 0x00, 0x0c, 0x00, 0x00, 0x00, 0x00 };
+static const u8 setup_3[] = { 0x0a, 0x04, 0x12, 0x10, 0x00, 0x00, 0x00, 0x00 };
+static const u8 setup_4[] = { 0x0a, 0x04, 0x00, 0x06, 0x00, 0x00, 0x00, 0x00 };
+static const u8 *const setup_arr[] = { setup_0, setup_1, setup_2, setup_3, setup_4 };
+static const unsigned int setup_arr_sizes[] = {
+       ARRAY_SIZE(setup_0),
+       ARRAY_SIZE(setup_1),
+       ARRAY_SIZE(setup_2),
+       ARRAY_SIZE(setup_3),
+       ARRAY_SIZE(setup_4)
+};
+/*
+ * This struct contains for each type of
+ * Thrustmaster wheel
+ *
+ * Note: The values are stored in the CPU
+ * endianness, the USB protocols always use
+ * little endian; the macro cpu_to_le[BIT]()
+ * must be used when preparing USB packets
+ * and vice-versa
+ */
+struct tm_wheel_info {
+       uint16_t wheel_type;
+
+       /*
+        * See when the USB control out packet is prepared...
+        * @TODO The TMX seems to require multiple control codes to switch.
+        */
+       uint16_t switch_value;
+
+       char const *const wheel_name;
+};
+
+/*
+ * Known wheels.
+ * Note: TMX does not work as it requires 2 control packets
+ */
+static const struct tm_wheel_info tm_wheels_infos[] = {
+       {0x0306, 0x0006, "Thrustmaster T150RS"},
+       {0x0206, 0x0005, "Thrustmaster T300RS"},
+       {0x0204, 0x0005, "Thrustmaster T300 Ferrari Alcantara Edition"},
+       {0x0002, 0x0002, "Thrustmaster T500RS"}
+       //{0x0407, 0x0001, "Thrustmaster TMX"}
+};
+
+static const uint8_t tm_wheels_infos_length = 4;
+
+/*
+ * This structs contains (in little endian) the response data
+ * of the wheel to the request 73
+ *
+ * A sufficient research to understand what each field does is not
+ * beign conducted yet. The position and meaning of fields are a
+ * just a very optimistic guess based on instinct....
+ */
+struct __packed tm_wheel_response
+{
+       /*
+        * Seems to be the type of packet
+        * - 0x0049 if is data.a (15 bytes)
+        * - 0x0047 if is data.b (7 bytes)
+        */
+       uint16_t type;
+
+       union {
+               struct __packed {
+                       uint16_t field0;
+                       uint16_t field1;
+                       /*
+                        * Seems to be the model code of the wheel
+                        * Read table thrustmaster_wheels to values
+                        */
+                       uint16_t model;
+
+                       uint16_t field2;
+                       uint16_t field3;
+                       uint16_t field4;
+                       uint16_t field5;
+               } a;
+               struct __packed {
+                       uint16_t field0;
+                       uint16_t field1;
+                       uint16_t model;
+               } b;
+       } data;
+};
+
+struct tm_wheel {
+       struct usb_device *usb_dev;
+       struct urb *urb;
+
+       struct usb_ctrlrequest *model_request;
+       struct tm_wheel_response *response;
+
+       struct usb_ctrlrequest *change_request;
+};
+
+/* The control packet to send to wheel */
+static const struct usb_ctrlrequest model_request = {
+       .bRequestType = 0xc1,
+       .bRequest = 73,
+       .wValue = 0,
+       .wIndex = 0,
+       .wLength = cpu_to_le16(0x0010)
+};
+
+static const struct usb_ctrlrequest change_request = {
+       .bRequestType = 0x41,
+       .bRequest = 83,
+       .wValue = 0, // Will be filled by the driver
+       .wIndex = 0,
+       .wLength = 0
+};
+
+/*
+ * On some setups initializing the T300RS crashes the kernel,
+ * these interrupts fix that particular issue. So far they haven't caused any
+ * adverse effects in other wheels.
+ */
+static void thrustmaster_interrupts(struct hid_device *hdev)
+{
+       int ret, trans, i, b_ep;
+       u8 *send_buf = kmalloc(256, GFP_KERNEL);
+       struct usb_host_endpoint *ep;
+       struct device *dev = &hdev->dev;
+       struct usb_interface *usbif = to_usb_interface(dev->parent);
+       struct usb_device *usbdev = interface_to_usbdev(usbif);
+
+       if (!send_buf) {
+               hid_err(hdev, "failed allocating send buffer\n");
+               return;
+       }
+
+       ep = &usbif->cur_altsetting->endpoint[1];
+       b_ep = ep->desc.bEndpointAddress;
+
+       for (i = 0; i < ARRAY_SIZE(setup_arr); ++i) {
+               memcpy(send_buf, setup_arr[i], setup_arr_sizes[i]);
+
+               ret = usb_interrupt_msg(usbdev,
+                       usb_sndintpipe(usbdev, b_ep),
+                       send_buf,
+                       setup_arr_sizes[i],
+                       &trans,
+                       USB_CTRL_SET_TIMEOUT);
+
+               if (ret) {
+                       hid_err(hdev, "setup data couldn't be sent\n");
+                       return;
+               }
+       }
+
+       kfree(send_buf);
+}
+
+static void thrustmaster_change_handler(struct urb *urb)
+{
+       struct hid_device *hdev = urb->context;
+
+       // The wheel seems to kill himself before answering the host and therefore is violating the USB protocol...
+       if (urb->status == 0 || urb->status == -EPROTO || urb->status == -EPIPE)
+               hid_info(hdev, "Success?! The wheel should have been initialized!\n");
+       else
+               hid_warn(hdev, "URB to change wheel mode seems to have failed with error %d\n", urb->status);
+}
+
+/*
+ * Called by the USB subsystem when the wheel responses to our request
+ * to get [what it seems to be] the wheel's model.
+ *
+ * If the model id is recognized then we send an opportune USB CONTROL REQUEST
+ * to switch the wheel to its full capabilities
+ */
+static void thrustmaster_model_handler(struct urb *urb)
+{
+       struct hid_device *hdev = urb->context;
+       struct tm_wheel *tm_wheel = hid_get_drvdata(hdev);
+       uint16_t model = 0;
+       int i, ret;
+       const struct tm_wheel_info *twi = 0;
+
+       if (urb->status) {
+               hid_err(hdev, "URB to get model id failed with error %d\n", urb->status);
+               return;
+       }
+
+       if (tm_wheel->response->type == cpu_to_le16(0x49))
+               model = le16_to_cpu(tm_wheel->response->data.a.model);
+       else if (tm_wheel->response->type == cpu_to_le16(0x47))
+               model = le16_to_cpu(tm_wheel->response->data.b.model);
+       else {
+               hid_err(hdev, "Unknown packet type 0x%x, unable to proceed further with wheel init\n", tm_wheel->response->type);
+               return;
+       }
+
+       for (i = 0; i < tm_wheels_infos_length && !twi; i++)
+               if (tm_wheels_infos[i].wheel_type == model)
+                       twi = tm_wheels_infos + i;
+
+       if (twi)
+               hid_info(hdev, "Wheel with model id 0x%x is a %s\n", model, twi->wheel_name);
+       else {
+               hid_err(hdev, "Unknown wheel's model id 0x%x, unable to proceed further with wheel init\n", model);
+               return;
+       }
+
+       tm_wheel->change_request->wValue = cpu_to_le16(twi->switch_value);
+       usb_fill_control_urb(
+               tm_wheel->urb,
+               tm_wheel->usb_dev,
+               usb_sndctrlpipe(tm_wheel->usb_dev, 0),
+               (char *)tm_wheel->change_request,
+               0, 0, // We do not expect any response from the wheel
+               thrustmaster_change_handler,
+               hdev
+       );
+
+       ret = usb_submit_urb(tm_wheel->urb, GFP_ATOMIC);
+       if (ret)
+               hid_err(hdev, "Error %d while submitting the change URB. I am unable to initialize this wheel...\n", ret);
+}
+
+static void thrustmaster_remove(struct hid_device *hdev)
+{
+       struct tm_wheel *tm_wheel = hid_get_drvdata(hdev);
+
+       usb_kill_urb(tm_wheel->urb);
+
+       kfree(tm_wheel->response);
+       kfree(tm_wheel->model_request);
+       usb_free_urb(tm_wheel->urb);
+       kfree(tm_wheel);
+
+       hid_hw_stop(hdev);
+}
+
+/*
+ * Function called by HID when a hid Thrustmaster FFB wheel is connected to the host.
+ * This function starts the hid dev, tries to allocate the tm_wheel data structure and
+ * finally send an USB CONTROL REQUEST to the wheel to get [what it seems to be] its
+ * model type.
+ */
+static int thrustmaster_probe(struct hid_device *hdev, const struct hid_device_id *id)
+{
+       int ret = 0;
+       struct tm_wheel *tm_wheel = 0;
+
+       ret = hid_parse(hdev);
+       if (ret) {
+               hid_err(hdev, "parse failed with error %d\n", ret);
+               goto error0;
+       }
+
+       ret = hid_hw_start(hdev, HID_CONNECT_DEFAULT & ~HID_CONNECT_FF);
+       if (ret) {
+               hid_err(hdev, "hw start failed with error %d\n", ret);
+               goto error0;
+       }
+
+       // Now we allocate the tm_wheel
+       tm_wheel = kzalloc(sizeof(struct tm_wheel), GFP_KERNEL);
+       if (!tm_wheel) {
+               ret = -ENOMEM;
+               goto error1;
+       }
+
+       tm_wheel->urb = usb_alloc_urb(0, GFP_ATOMIC);
+       if (!tm_wheel->urb) {
+               ret = -ENOMEM;
+               goto error2;
+       }
+
+       tm_wheel->model_request = kmemdup(&model_request,
+                                         sizeof(struct usb_ctrlrequest),
+                                         GFP_KERNEL);
+       if (!tm_wheel->model_request) {
+               ret = -ENOMEM;
+               goto error3;
+       }
+
+       tm_wheel->response = kzalloc(sizeof(struct tm_wheel_response), GFP_KERNEL);
+       if (!tm_wheel->response) {
+               ret = -ENOMEM;
+               goto error4;
+       }
+
+       tm_wheel->change_request = kzalloc(sizeof(struct usb_ctrlrequest), GFP_KERNEL);
+       if (!tm_wheel->model_request) {
+               ret = -ENOMEM;
+               goto error5;
+       }
+       memcpy(tm_wheel->change_request, &change_request, sizeof(struct usb_ctrlrequest));
+
+       tm_wheel->usb_dev = interface_to_usbdev(to_usb_interface(hdev->dev.parent));
+       hid_set_drvdata(hdev, tm_wheel);
+
+       thrustmaster_interrupts(hdev);
+
+       usb_fill_control_urb(
+               tm_wheel->urb,
+               tm_wheel->usb_dev,
+               usb_rcvctrlpipe(tm_wheel->usb_dev, 0),
+               (char *)tm_wheel->model_request,
+               tm_wheel->response,
+               sizeof(struct tm_wheel_response),
+               thrustmaster_model_handler,
+               hdev
+       );
+
+       ret = usb_submit_urb(tm_wheel->urb, GFP_ATOMIC);
+       if (ret)
+               hid_err(hdev, "Error %d while submitting the URB. I am unable to initialize this wheel...\n", ret);
+
+       return ret;
+
+error5: kfree(tm_wheel->response);
+error4: kfree(tm_wheel->model_request);
+error3: usb_free_urb(tm_wheel->urb);
+error2: kfree(tm_wheel);
+error1: hid_hw_stop(hdev);
+error0:
+       return ret;
+}
+
+static const struct hid_device_id thrustmaster_devices[] = {
+       { HID_USB_DEVICE(0x044f, 0xb65d)},
+       {}
+};
+
+MODULE_DEVICE_TABLE(hid, thrustmaster_devices);
+
+static struct hid_driver thrustmaster_driver = {
+       .name = "hid-thrustmaster",
+       .id_table = thrustmaster_devices,
+       .probe = thrustmaster_probe,
+       .remove = thrustmaster_remove,
+};
+
+module_hid_driver(thrustmaster_driver);
+
+MODULE_AUTHOR("Dario Pagani <dario.pagani.146+linuxk@gmail.com>");
+MODULE_LICENSE("GPL");
+MODULE_DESCRIPTION("Driver to initialize some steering wheel joysticks from Thrustmaster");
+
index 6af25c3..3d67b74 100644 (file)
@@ -21,7 +21,8 @@
 #include <asm/unaligned.h>
 
 /**
- * Convert a pen in-range reporting type to a string.
+ * uclogic_params_pen_inrange_to_str() - Convert a pen in-range reporting type
+ *                                       to a string.
  *
  * @inrange:   The in-range reporting type to convert.
  *
@@ -516,7 +517,8 @@ void uclogic_params_cleanup(struct uclogic_params *params)
 }
 
 /**
- * Get a replacement report descriptor for a tablet's interface.
+ * uclogic_params_get_desc() - Get a replacement report descriptor for a
+ *                             tablet's interface.
  *
  * @params:    The parameters of a tablet interface to get report
  *             descriptor for. Cannot be NULL.
@@ -689,7 +691,7 @@ static void uclogic_params_init_with_pen_unused(struct uclogic_params *params)
 }
 
 /**
- * uclogic_params_init() - initialize a Huion tablet interface and discover
+ * uclogic_params_huion_init() - initialize a Huion tablet interface and discover
  * its parameters.
  *
  * @params:    Parameters to fill in (to be cleaned with
index bf5da6d..6dd6dcd 100644 (file)
@@ -641,7 +641,7 @@ const __u8 uclogic_rdesc_pen_v2_template_arr[] = {
 const size_t uclogic_rdesc_pen_v2_template_size =
                        sizeof(uclogic_rdesc_pen_v2_template_arr);
 
-/**
+/*
  * Expand to the contents of a generic buttonpad report descriptor.
  *
  * @_padding:  Padding from the end of button bits at bit 44, until
index bb8c00e..a6f0257 100644 (file)
 #include <linux/kernel.h>
 #include <linux/module.h>
 #include <linux/pm.h>
+#include <linux/uuid.h>
 
 #include "i2c-hid.h"
 
 struct i2c_hid_acpi {
        struct i2chid_ops ops;
-       struct i2c_client *client;
+       struct acpi_device *adev;
 };
 
 static const struct acpi_device_id i2c_hid_acpi_blacklist[] = {
@@ -42,29 +43,24 @@ static const struct acpi_device_id i2c_hid_acpi_blacklist[] = {
        { },
 };
 
-static int i2c_hid_acpi_get_descriptor(struct i2c_client *client)
+/* HID I²C Device: 3cdff6f7-4267-4555-ad05-b30a3d8938de */
+static guid_t i2c_hid_guid =
+       GUID_INIT(0x3CDFF6F7, 0x4267, 0x4555,
+                 0xAD, 0x05, 0xB3, 0x0A, 0x3D, 0x89, 0x38, 0xDE);
+
+static int i2c_hid_acpi_get_descriptor(struct acpi_device *adev)
 {
-       static guid_t i2c_hid_guid =
-               GUID_INIT(0x3CDFF6F7, 0x4267, 0x4555,
-                         0xAD, 0x05, 0xB3, 0x0A, 0x3D, 0x89, 0x38, 0xDE);
+       acpi_handle handle = acpi_device_handle(adev);
        union acpi_object *obj;
-       struct acpi_device *adev;
-       acpi_handle handle;
        u16 hid_descriptor_address;
 
-       handle = ACPI_HANDLE(&client->dev);
-       if (!handle || acpi_bus_get_device(handle, &adev)) {
-               dev_err(&client->dev, "Error could not get ACPI device\n");
-               return -ENODEV;
-       }
-
        if (acpi_match_device_ids(adev, i2c_hid_acpi_blacklist) == 0)
                return -ENODEV;
 
        obj = acpi_evaluate_dsm_typed(handle, &i2c_hid_guid, 1, 1, NULL,
                                      ACPI_TYPE_INTEGER);
        if (!obj) {
-               dev_err(&client->dev, "Error _DSM call to get HID descriptor address failed\n");
+               acpi_handle_err(handle, "Error _DSM call to get HID descriptor address failed\n");
                return -ENODEV;
        }
 
@@ -76,14 +72,12 @@ static int i2c_hid_acpi_get_descriptor(struct i2c_client *client)
 
 static void i2c_hid_acpi_shutdown_tail(struct i2chid_ops *ops)
 {
-       struct i2c_hid_acpi *ihid_acpi =
-               container_of(ops, struct i2c_hid_acpi, ops);
-       struct device *dev = &ihid_acpi->client->dev;
-       acpi_device_set_power(ACPI_COMPANION(dev), ACPI_STATE_D3_COLD);
+       struct i2c_hid_acpi *ihid_acpi = container_of(ops, struct i2c_hid_acpi, ops);
+
+       acpi_device_set_power(ihid_acpi->adev, ACPI_STATE_D3_COLD);
 }
 
-static int i2c_hid_acpi_probe(struct i2c_client *client,
-                             const struct i2c_device_id *dev_id)
+static int i2c_hid_acpi_probe(struct i2c_client *client)
 {
        struct device *dev = &client->dev;
        struct i2c_hid_acpi *ihid_acpi;
@@ -91,21 +85,25 @@ static int i2c_hid_acpi_probe(struct i2c_client *client,
        u16 hid_descriptor_address;
        int ret;
 
+       adev = ACPI_COMPANION(dev);
+       if (!adev) {
+               dev_err(&client->dev, "Error could not get ACPI device\n");
+               return -ENODEV;
+       }
+
        ihid_acpi = devm_kzalloc(&client->dev, sizeof(*ihid_acpi), GFP_KERNEL);
        if (!ihid_acpi)
                return -ENOMEM;
 
-       ihid_acpi->client = client;
+       ihid_acpi->adev = adev;
        ihid_acpi->ops.shutdown_tail = i2c_hid_acpi_shutdown_tail;
 
-       ret = i2c_hid_acpi_get_descriptor(client);
+       ret = i2c_hid_acpi_get_descriptor(adev);
        if (ret < 0)
                return ret;
        hid_descriptor_address = ret;
 
-       adev = ACPI_COMPANION(dev);
-       if (adev)
-               acpi_device_fix_up_power(adev);
+       acpi_device_fix_up_power(adev);
 
        if (acpi_gbl_FADT.flags & ACPI_FADT_LOW_POWER_S0) {
                device_set_wakeup_capable(dev, true);
@@ -128,10 +126,10 @@ static struct i2c_driver i2c_hid_acpi_driver = {
                .name   = "i2c_hid_acpi",
                .pm     = &i2c_hid_core_pm,
                .probe_type = PROBE_PREFER_ASYNCHRONOUS,
-               .acpi_match_table = ACPI_PTR(i2c_hid_acpi_match),
+               .acpi_match_table = i2c_hid_acpi_match,
        },
 
-       .probe          = i2c_hid_acpi_probe,
+       .probe_new      = i2c_hid_acpi_probe,
        .remove         = i2c_hid_core_remove,
        .shutdown       = i2c_hid_core_shutdown,
 };
diff --git a/drivers/hid/surface-hid/Kconfig b/drivers/hid/surface-hid/Kconfig
new file mode 100644 (file)
index 0000000..7ce9b5d
--- /dev/null
@@ -0,0 +1,42 @@
+# SPDX-License-Identifier: GPL-2.0+
+menu "Surface System Aggregator Module HID support"
+       depends on SURFACE_AGGREGATOR
+       depends on INPUT
+
+config SURFACE_HID
+       tristate "HID transport driver for Surface System Aggregator Module"
+       depends on SURFACE_AGGREGATOR_REGISTRY
+       select SURFACE_HID_CORE
+       help
+         Driver to support integrated HID devices on newer Microsoft Surface
+         models.
+
+         This driver provides support for the HID transport protocol provided
+         by the Surface Aggregator Module (i.e. the embedded controller) on
+         7th-generation Microsoft Surface devices, i.e. Surface Book 3 and
+         Surface Laptop 3. On those models, it is mainly used to connect the
+         integrated touchpad and keyboard.
+
+         Say M or Y here, if you want support for integrated HID devices, i.e.
+         integrated touchpad and keyboard, on 7th generation Microsoft Surface
+         models.
+
+config SURFACE_KBD
+       tristate "HID keyboard transport driver for Surface System Aggregator Module"
+       select SURFACE_HID_CORE
+       help
+         Driver to support HID keyboards on Surface Laptop 1 and 2 devices.
+
+         This driver provides support for the HID transport protocol provided
+         by the Surface Aggregator Module (i.e. the embedded controller) on
+         Microsoft Surface Laptops 1 and 2. It is used to connect the
+         integrated keyboard on those devices.
+
+         Say M or Y here, if you want support for the integrated keyboard on
+         Microsoft Surface Laptops 1 and 2.
+
+endmenu
+
+config SURFACE_HID_CORE
+       tristate
+       select HID
diff --git a/drivers/hid/surface-hid/Makefile b/drivers/hid/surface-hid/Makefile
new file mode 100644 (file)
index 0000000..4ae11cf
--- /dev/null
@@ -0,0 +1,7 @@
+# SPDX-License-Identifier: GPL-2.0+
+#
+# Makefile - Surface System Aggregator Module (SSAM) HID transport driver.
+#
+obj-$(CONFIG_SURFACE_HID_CORE) += surface_hid_core.o
+obj-$(CONFIG_SURFACE_HID)      += surface_hid.o
+obj-$(CONFIG_SURFACE_KBD)      += surface_kbd.o
diff --git a/drivers/hid/surface-hid/surface_hid.c b/drivers/hid/surface-hid/surface_hid.c
new file mode 100644 (file)
index 0000000..3477b31
--- /dev/null
@@ -0,0 +1,253 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * Surface System Aggregator Module (SSAM) HID transport driver for the
+ * generic HID interface (HID/TC=0x15 subsystem). Provides support for
+ * integrated HID devices on Surface Laptop 3, Book 3, and later.
+ *
+ * Copyright (C) 2019-2021 Blaž Hrastnik <blaz@mxxn.io>,
+ *                         Maximilian Luz <luzmaximilian@gmail.com>
+ */
+
+#include <asm/unaligned.h>
+#include <linux/hid.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/types.h>
+
+#include <linux/surface_aggregator/controller.h>
+#include <linux/surface_aggregator/device.h>
+
+#include "surface_hid_core.h"
+
+
+/* -- SAM interface. -------------------------------------------------------- */
+
+struct surface_hid_buffer_slice {
+       __u8 entry;
+       __le32 offset;
+       __le32 length;
+       __u8 end;
+       __u8 data[];
+} __packed;
+
+static_assert(sizeof(struct surface_hid_buffer_slice) == 10);
+
+enum surface_hid_cid {
+       SURFACE_HID_CID_OUTPUT_REPORT      = 0x01,
+       SURFACE_HID_CID_GET_FEATURE_REPORT = 0x02,
+       SURFACE_HID_CID_SET_FEATURE_REPORT = 0x03,
+       SURFACE_HID_CID_GET_DESCRIPTOR     = 0x04,
+};
+
+static int ssam_hid_get_descriptor(struct surface_hid_device *shid, u8 entry, u8 *buf, size_t len)
+{
+       u8 buffer[sizeof(struct surface_hid_buffer_slice) + 0x76];
+       struct surface_hid_buffer_slice *slice;
+       struct ssam_request rqst;
+       struct ssam_response rsp;
+       u32 buffer_len, offset, length;
+       int status;
+
+       /*
+        * Note: The 0x76 above has been chosen because that's what's used by
+        * the Windows driver. Together with the header, this leads to a 128
+        * byte payload in total.
+        */
+
+       buffer_len = ARRAY_SIZE(buffer) - sizeof(struct surface_hid_buffer_slice);
+
+       rqst.target_category = shid->uid.category;
+       rqst.target_id = shid->uid.target;
+       rqst.command_id = SURFACE_HID_CID_GET_DESCRIPTOR;
+       rqst.instance_id = shid->uid.instance;
+       rqst.flags = SSAM_REQUEST_HAS_RESPONSE;
+       rqst.length = sizeof(struct surface_hid_buffer_slice);
+       rqst.payload = buffer;
+
+       rsp.capacity = ARRAY_SIZE(buffer);
+       rsp.pointer = buffer;
+
+       slice = (struct surface_hid_buffer_slice *)buffer;
+       slice->entry = entry;
+       slice->end = 0;
+
+       offset = 0;
+       length = buffer_len;
+
+       while (!slice->end && offset < len) {
+               put_unaligned_le32(offset, &slice->offset);
+               put_unaligned_le32(length, &slice->length);
+
+               rsp.length = 0;
+
+               status = ssam_retry(ssam_request_sync_onstack, shid->ctrl, &rqst, &rsp,
+                                   sizeof(*slice));
+               if (status)
+                       return status;
+
+               offset = get_unaligned_le32(&slice->offset);
+               length = get_unaligned_le32(&slice->length);
+
+               /* Don't mess stuff up in case we receive garbage. */
+               if (length > buffer_len || offset > len)
+                       return -EPROTO;
+
+               if (offset + length > len)
+                       length = len - offset;
+
+               memcpy(buf + offset, &slice->data[0], length);
+
+               offset += length;
+               length = buffer_len;
+       }
+
+       if (offset != len) {
+               dev_err(shid->dev, "unexpected descriptor length: got %u, expected %zu\n",
+                       offset, len);
+               return -EPROTO;
+       }
+
+       return 0;
+}
+
+static int ssam_hid_set_raw_report(struct surface_hid_device *shid, u8 rprt_id, bool feature,
+                                  u8 *buf, size_t len)
+{
+       struct ssam_request rqst;
+       u8 cid;
+
+       if (feature)
+               cid = SURFACE_HID_CID_SET_FEATURE_REPORT;
+       else
+               cid = SURFACE_HID_CID_OUTPUT_REPORT;
+
+       rqst.target_category = shid->uid.category;
+       rqst.target_id = shid->uid.target;
+       rqst.instance_id = shid->uid.instance;
+       rqst.command_id = cid;
+       rqst.flags = 0;
+       rqst.length = len;
+       rqst.payload = buf;
+
+       buf[0] = rprt_id;
+
+       return ssam_retry(ssam_request_sync, shid->ctrl, &rqst, NULL);
+}
+
+static int ssam_hid_get_raw_report(struct surface_hid_device *shid, u8 rprt_id, u8 *buf, size_t len)
+{
+       struct ssam_request rqst;
+       struct ssam_response rsp;
+
+       rqst.target_category = shid->uid.category;
+       rqst.target_id = shid->uid.target;
+       rqst.instance_id = shid->uid.instance;
+       rqst.command_id = SURFACE_HID_CID_GET_FEATURE_REPORT;
+       rqst.flags = 0;
+       rqst.length = sizeof(rprt_id);
+       rqst.payload = &rprt_id;
+
+       rsp.capacity = len;
+       rsp.length = 0;
+       rsp.pointer = buf;
+
+       return ssam_retry(ssam_request_sync_onstack, shid->ctrl, &rqst, &rsp, sizeof(rprt_id));
+}
+
+static u32 ssam_hid_event_fn(struct ssam_event_notifier *nf, const struct ssam_event *event)
+{
+       struct surface_hid_device *shid = container_of(nf, struct surface_hid_device, notif);
+
+       if (event->command_id != 0x00)
+               return 0;
+
+       hid_input_report(shid->hid, HID_INPUT_REPORT, (u8 *)&event->data[0], event->length, 0);
+       return SSAM_NOTIF_HANDLED;
+}
+
+
+/* -- Transport driver. ----------------------------------------------------- */
+
+static int shid_output_report(struct surface_hid_device *shid, u8 rprt_id, u8 *buf, size_t len)
+{
+       int status;
+
+       status = ssam_hid_set_raw_report(shid, rprt_id, false, buf, len);
+       return status >= 0 ? len : status;
+}
+
+static int shid_get_feature_report(struct surface_hid_device *shid, u8 rprt_id, u8 *buf, size_t len)
+{
+       int status;
+
+       status = ssam_hid_get_raw_report(shid, rprt_id, buf, len);
+       return status >= 0 ? len : status;
+}
+
+static int shid_set_feature_report(struct surface_hid_device *shid, u8 rprt_id, u8 *buf, size_t len)
+{
+       int status;
+
+       status = ssam_hid_set_raw_report(shid, rprt_id, true, buf, len);
+       return status >= 0 ? len : status;
+}
+
+
+/* -- Driver setup. --------------------------------------------------------- */
+
+static int surface_hid_probe(struct ssam_device *sdev)
+{
+       struct surface_hid_device *shid;
+
+       shid = devm_kzalloc(&sdev->dev, sizeof(*shid), GFP_KERNEL);
+       if (!shid)
+               return -ENOMEM;
+
+       shid->dev = &sdev->dev;
+       shid->ctrl = sdev->ctrl;
+       shid->uid = sdev->uid;
+
+       shid->notif.base.priority = 1;
+       shid->notif.base.fn = ssam_hid_event_fn;
+       shid->notif.event.reg = SSAM_EVENT_REGISTRY_REG;
+       shid->notif.event.id.target_category = sdev->uid.category;
+       shid->notif.event.id.instance = sdev->uid.instance;
+       shid->notif.event.mask = SSAM_EVENT_MASK_STRICT;
+       shid->notif.event.flags = 0;
+
+       shid->ops.get_descriptor = ssam_hid_get_descriptor;
+       shid->ops.output_report = shid_output_report;
+       shid->ops.get_feature_report = shid_get_feature_report;
+       shid->ops.set_feature_report = shid_set_feature_report;
+
+       ssam_device_set_drvdata(sdev, shid);
+       return surface_hid_device_add(shid);
+}
+
+static void surface_hid_remove(struct ssam_device *sdev)
+{
+       surface_hid_device_destroy(ssam_device_get_drvdata(sdev));
+}
+
+static const struct ssam_device_id surface_hid_match[] = {
+       { SSAM_SDEV(HID, 0x02, SSAM_ANY_IID, 0x00) },
+       { },
+};
+MODULE_DEVICE_TABLE(ssam, surface_hid_match);
+
+static struct ssam_device_driver surface_hid_driver = {
+       .probe = surface_hid_probe,
+       .remove = surface_hid_remove,
+       .match_table = surface_hid_match,
+       .driver = {
+               .name = "surface_hid",
+               .pm = &surface_hid_pm_ops,
+               .probe_type = PROBE_PREFER_ASYNCHRONOUS,
+       },
+};
+module_ssam_device_driver(surface_hid_driver);
+
+MODULE_AUTHOR("Blaž Hrastnik <blaz@mxxn.io>");
+MODULE_AUTHOR("Maximilian Luz <luzmaximilian@gmail.com>");
+MODULE_DESCRIPTION("HID transport driver for Surface System Aggregator Module");
+MODULE_LICENSE("GPL");
diff --git a/drivers/hid/surface-hid/surface_hid_core.c b/drivers/hid/surface-hid/surface_hid_core.c
new file mode 100644 (file)
index 0000000..7b27ec3
--- /dev/null
@@ -0,0 +1,272 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * Common/core components for the Surface System Aggregator Module (SSAM) HID
+ * transport driver. Provides support for integrated HID devices on Microsoft
+ * Surface models.
+ *
+ * Copyright (C) 2019-2021 Maximilian Luz <luzmaximilian@gmail.com>
+ */
+
+#include <asm/unaligned.h>
+#include <linux/hid.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/types.h>
+#include <linux/usb/ch9.h>
+
+#include <linux/surface_aggregator/controller.h>
+
+#include "surface_hid_core.h"
+
+
+/* -- Device descriptor access. --------------------------------------------- */
+
+static int surface_hid_load_hid_descriptor(struct surface_hid_device *shid)
+{
+       int status;
+
+       status = shid->ops.get_descriptor(shid, SURFACE_HID_DESC_HID,
+                       (u8 *)&shid->hid_desc, sizeof(shid->hid_desc));
+       if (status)
+               return status;
+
+       if (shid->hid_desc.desc_len != sizeof(shid->hid_desc)) {
+               dev_err(shid->dev, "unexpected HID descriptor length: got %u, expected %zu\n",
+                       shid->hid_desc.desc_len, sizeof(shid->hid_desc));
+               return -EPROTO;
+       }
+
+       if (shid->hid_desc.desc_type != HID_DT_HID) {
+               dev_err(shid->dev, "unexpected HID descriptor type: got %#04x, expected %#04x\n",
+                       shid->hid_desc.desc_type, HID_DT_HID);
+               return -EPROTO;
+       }
+
+       if (shid->hid_desc.num_descriptors != 1) {
+               dev_err(shid->dev, "unexpected number of descriptors: got %u, expected 1\n",
+                       shid->hid_desc.num_descriptors);
+               return -EPROTO;
+       }
+
+       if (shid->hid_desc.report_desc_type != HID_DT_REPORT) {
+               dev_err(shid->dev, "unexpected report descriptor type: got %#04x, expected %#04x\n",
+                       shid->hid_desc.report_desc_type, HID_DT_REPORT);
+               return -EPROTO;
+       }
+
+       return 0;
+}
+
+static int surface_hid_load_device_attributes(struct surface_hid_device *shid)
+{
+       int status;
+
+       status = shid->ops.get_descriptor(shid, SURFACE_HID_DESC_ATTRS,
+                       (u8 *)&shid->attrs, sizeof(shid->attrs));
+       if (status)
+               return status;
+
+       if (get_unaligned_le32(&shid->attrs.length) != sizeof(shid->attrs)) {
+               dev_err(shid->dev, "unexpected attribute length: got %u, expected %zu\n",
+                       get_unaligned_le32(&shid->attrs.length), sizeof(shid->attrs));
+               return -EPROTO;
+       }
+
+       return 0;
+}
+
+
+/* -- Transport driver (common). -------------------------------------------- */
+
+static int surface_hid_start(struct hid_device *hid)
+{
+       struct surface_hid_device *shid = hid->driver_data;
+
+       return ssam_notifier_register(shid->ctrl, &shid->notif);
+}
+
+static void surface_hid_stop(struct hid_device *hid)
+{
+       struct surface_hid_device *shid = hid->driver_data;
+
+       /* Note: This call will log errors for us, so ignore them here. */
+       ssam_notifier_unregister(shid->ctrl, &shid->notif);
+}
+
+static int surface_hid_open(struct hid_device *hid)
+{
+       return 0;
+}
+
+static void surface_hid_close(struct hid_device *hid)
+{
+}
+
+static int surface_hid_parse(struct hid_device *hid)
+{
+       struct surface_hid_device *shid = hid->driver_data;
+       size_t len = get_unaligned_le16(&shid->hid_desc.report_desc_len);
+       u8 *buf;
+       int status;
+
+       buf = kzalloc(len, GFP_KERNEL);
+       if (!buf)
+               return -ENOMEM;
+
+       status = shid->ops.get_descriptor(shid, SURFACE_HID_DESC_REPORT, buf, len);
+       if (!status)
+               status = hid_parse_report(hid, buf, len);
+
+       kfree(buf);
+       return status;
+}
+
+static int surface_hid_raw_request(struct hid_device *hid, unsigned char reportnum, u8 *buf,
+                                  size_t len, unsigned char rtype, int reqtype)
+{
+       struct surface_hid_device *shid = hid->driver_data;
+
+       if (rtype == HID_OUTPUT_REPORT && reqtype == HID_REQ_SET_REPORT)
+               return shid->ops.output_report(shid, reportnum, buf, len);
+
+       else if (rtype == HID_FEATURE_REPORT && reqtype == HID_REQ_GET_REPORT)
+               return shid->ops.get_feature_report(shid, reportnum, buf, len);
+
+       else if (rtype == HID_FEATURE_REPORT && reqtype == HID_REQ_SET_REPORT)
+               return shid->ops.set_feature_report(shid, reportnum, buf, len);
+
+       return -EIO;
+}
+
+static struct hid_ll_driver surface_hid_ll_driver = {
+       .start       = surface_hid_start,
+       .stop        = surface_hid_stop,
+       .open        = surface_hid_open,
+       .close       = surface_hid_close,
+       .parse       = surface_hid_parse,
+       .raw_request = surface_hid_raw_request,
+};
+
+
+/* -- Common device setup. -------------------------------------------------- */
+
+int surface_hid_device_add(struct surface_hid_device *shid)
+{
+       int status;
+
+       status = surface_hid_load_hid_descriptor(shid);
+       if (status)
+               return status;
+
+       status = surface_hid_load_device_attributes(shid);
+       if (status)
+               return status;
+
+       shid->hid = hid_allocate_device();
+       if (IS_ERR(shid->hid))
+               return PTR_ERR(shid->hid);
+
+       shid->hid->dev.parent = shid->dev;
+       shid->hid->bus = BUS_HOST;
+       shid->hid->vendor = cpu_to_le16(shid->attrs.vendor);
+       shid->hid->product = cpu_to_le16(shid->attrs.product);
+       shid->hid->version = cpu_to_le16(shid->hid_desc.hid_version);
+       shid->hid->country = shid->hid_desc.country_code;
+
+       snprintf(shid->hid->name, sizeof(shid->hid->name), "Microsoft Surface %04X:%04X",
+                shid->hid->vendor, shid->hid->product);
+
+       strscpy(shid->hid->phys, dev_name(shid->dev), sizeof(shid->hid->phys));
+
+       shid->hid->driver_data = shid;
+       shid->hid->ll_driver = &surface_hid_ll_driver;
+
+       status = hid_add_device(shid->hid);
+       if (status)
+               hid_destroy_device(shid->hid);
+
+       return status;
+}
+EXPORT_SYMBOL_GPL(surface_hid_device_add);
+
+void surface_hid_device_destroy(struct surface_hid_device *shid)
+{
+       hid_destroy_device(shid->hid);
+}
+EXPORT_SYMBOL_GPL(surface_hid_device_destroy);
+
+
+/* -- PM ops. --------------------------------------------------------------- */
+
+#ifdef CONFIG_PM_SLEEP
+
+static int surface_hid_suspend(struct device *dev)
+{
+       struct surface_hid_device *d = dev_get_drvdata(dev);
+
+       if (d->hid->driver && d->hid->driver->suspend)
+               return d->hid->driver->suspend(d->hid, PMSG_SUSPEND);
+
+       return 0;
+}
+
+static int surface_hid_resume(struct device *dev)
+{
+       struct surface_hid_device *d = dev_get_drvdata(dev);
+
+       if (d->hid->driver && d->hid->driver->resume)
+               return d->hid->driver->resume(d->hid);
+
+       return 0;
+}
+
+static int surface_hid_freeze(struct device *dev)
+{
+       struct surface_hid_device *d = dev_get_drvdata(dev);
+
+       if (d->hid->driver && d->hid->driver->suspend)
+               return d->hid->driver->suspend(d->hid, PMSG_FREEZE);
+
+       return 0;
+}
+
+static int surface_hid_poweroff(struct device *dev)
+{
+       struct surface_hid_device *d = dev_get_drvdata(dev);
+
+       if (d->hid->driver && d->hid->driver->suspend)
+               return d->hid->driver->suspend(d->hid, PMSG_HIBERNATE);
+
+       return 0;
+}
+
+static int surface_hid_restore(struct device *dev)
+{
+       struct surface_hid_device *d = dev_get_drvdata(dev);
+
+       if (d->hid->driver && d->hid->driver->reset_resume)
+               return d->hid->driver->reset_resume(d->hid);
+
+       return 0;
+}
+
+const struct dev_pm_ops surface_hid_pm_ops = {
+       .freeze   = surface_hid_freeze,
+       .thaw     = surface_hid_resume,
+       .suspend  = surface_hid_suspend,
+       .resume   = surface_hid_resume,
+       .poweroff = surface_hid_poweroff,
+       .restore  = surface_hid_restore,
+};
+EXPORT_SYMBOL_GPL(surface_hid_pm_ops);
+
+#else /* CONFIG_PM_SLEEP */
+
+const struct dev_pm_ops surface_hid_pm_ops = { };
+EXPORT_SYMBOL_GPL(surface_hid_pm_ops);
+
+#endif /* CONFIG_PM_SLEEP */
+
+MODULE_AUTHOR("Maximilian Luz <luzmaximilian@gmail.com>");
+MODULE_DESCRIPTION("HID transport driver core for Surface System Aggregator Module");
+MODULE_LICENSE("GPL");
diff --git a/drivers/hid/surface-hid/surface_hid_core.h b/drivers/hid/surface-hid/surface_hid_core.h
new file mode 100644 (file)
index 0000000..4b1a7b5
--- /dev/null
@@ -0,0 +1,77 @@
+/* SPDX-License-Identifier: GPL-2.0+ */
+/*
+ * Common/core components for the Surface System Aggregator Module (SSAM) HID
+ * transport driver. Provides support for integrated HID devices on Microsoft
+ * Surface models.
+ *
+ * Copyright (C) 2019-2021 Maximilian Luz <luzmaximilian@gmail.com>
+ */
+
+#ifndef SURFACE_HID_CORE_H
+#define SURFACE_HID_CORE_H
+
+#include <linux/hid.h>
+#include <linux/pm.h>
+#include <linux/types.h>
+
+#include <linux/surface_aggregator/controller.h>
+#include <linux/surface_aggregator/device.h>
+
+enum surface_hid_descriptor_entry {
+       SURFACE_HID_DESC_HID    = 0,
+       SURFACE_HID_DESC_REPORT = 1,
+       SURFACE_HID_DESC_ATTRS  = 2,
+};
+
+struct surface_hid_descriptor {
+       __u8 desc_len;                  /* = 9 */
+       __u8 desc_type;                 /* = HID_DT_HID */
+       __le16 hid_version;
+       __u8 country_code;
+       __u8 num_descriptors;           /* = 1 */
+
+       __u8 report_desc_type;          /* = HID_DT_REPORT */
+       __le16 report_desc_len;
+} __packed;
+
+static_assert(sizeof(struct surface_hid_descriptor) == 9);
+
+struct surface_hid_attributes {
+       __le32 length;
+       __le16 vendor;
+       __le16 product;
+       __le16 version;
+       __u8 _unknown[22];
+} __packed;
+
+static_assert(sizeof(struct surface_hid_attributes) == 32);
+
+struct surface_hid_device;
+
+struct surface_hid_device_ops {
+       int (*get_descriptor)(struct surface_hid_device *shid, u8 entry, u8 *buf, size_t len);
+       int (*output_report)(struct surface_hid_device *shid, u8 rprt_id, u8 *buf, size_t len);
+       int (*get_feature_report)(struct surface_hid_device *shid, u8 rprt_id, u8 *buf, size_t len);
+       int (*set_feature_report)(struct surface_hid_device *shid, u8 rprt_id, u8 *buf, size_t len);
+};
+
+struct surface_hid_device {
+       struct device *dev;
+       struct ssam_controller *ctrl;
+       struct ssam_device_uid uid;
+
+       struct surface_hid_descriptor hid_desc;
+       struct surface_hid_attributes attrs;
+
+       struct ssam_event_notifier notif;
+       struct hid_device *hid;
+
+       struct surface_hid_device_ops ops;
+};
+
+int surface_hid_device_add(struct surface_hid_device *shid);
+void surface_hid_device_destroy(struct surface_hid_device *shid);
+
+extern const struct dev_pm_ops surface_hid_pm_ops;
+
+#endif /* SURFACE_HID_CORE_H */
diff --git a/drivers/hid/surface-hid/surface_kbd.c b/drivers/hid/surface-hid/surface_kbd.c
new file mode 100644 (file)
index 0000000..0635341
--- /dev/null
@@ -0,0 +1,300 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * Surface System Aggregator Module (SSAM) HID transport driver for the legacy
+ * keyboard interface (KBD/TC=0x08 subsystem). Provides support for the
+ * integrated HID keyboard on Surface Laptops 1 and 2.
+ *
+ * Copyright (C) 2019-2021 Maximilian Luz <luzmaximilian@gmail.com>
+ */
+
+#include <asm/unaligned.h>
+#include <linux/hid.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/types.h>
+
+#include <linux/surface_aggregator/controller.h>
+
+#include "surface_hid_core.h"
+
+
+/* -- SAM interface (KBD). -------------------------------------------------- */
+
+#define KBD_FEATURE_REPORT_SIZE                        7  /* 6 + report ID */
+
+enum surface_kbd_cid {
+       SURFACE_KBD_CID_GET_DESCRIPTOR          = 0x00,
+       SURFACE_KBD_CID_SET_CAPSLOCK_LED        = 0x01,
+       SURFACE_KBD_CID_EVT_INPUT_GENERIC       = 0x03,
+       SURFACE_KBD_CID_EVT_INPUT_HOTKEYS       = 0x04,
+       SURFACE_KBD_CID_GET_FEATURE_REPORT      = 0x0b,
+};
+
+static int ssam_kbd_get_descriptor(struct surface_hid_device *shid, u8 entry, u8 *buf, size_t len)
+{
+       struct ssam_request rqst;
+       struct ssam_response rsp;
+       int status;
+
+       rqst.target_category = shid->uid.category;
+       rqst.target_id = shid->uid.target;
+       rqst.command_id = SURFACE_KBD_CID_GET_DESCRIPTOR;
+       rqst.instance_id = shid->uid.instance;
+       rqst.flags = SSAM_REQUEST_HAS_RESPONSE;
+       rqst.length = sizeof(entry);
+       rqst.payload = &entry;
+
+       rsp.capacity = len;
+       rsp.length = 0;
+       rsp.pointer = buf;
+
+       status = ssam_retry(ssam_request_sync_onstack, shid->ctrl, &rqst, &rsp, sizeof(entry));
+       if (status)
+               return status;
+
+       if (rsp.length != len) {
+               dev_err(shid->dev, "invalid descriptor length: got %zu, expected, %zu\n",
+                       rsp.length, len);
+               return -EPROTO;
+       }
+
+       return 0;
+}
+
+static int ssam_kbd_set_caps_led(struct surface_hid_device *shid, bool value)
+{
+       struct ssam_request rqst;
+       u8 value_u8 = value;
+
+       rqst.target_category = shid->uid.category;
+       rqst.target_id = shid->uid.target;
+       rqst.command_id = SURFACE_KBD_CID_SET_CAPSLOCK_LED;
+       rqst.instance_id = shid->uid.instance;
+       rqst.flags = 0;
+       rqst.length = sizeof(value_u8);
+       rqst.payload = &value_u8;
+
+       return ssam_retry(ssam_request_sync_onstack, shid->ctrl, &rqst, NULL, sizeof(value_u8));
+}
+
+static int ssam_kbd_get_feature_report(struct surface_hid_device *shid, u8 *buf, size_t len)
+{
+       struct ssam_request rqst;
+       struct ssam_response rsp;
+       u8 payload = 0;
+       int status;
+
+       rqst.target_category = shid->uid.category;
+       rqst.target_id = shid->uid.target;
+       rqst.command_id = SURFACE_KBD_CID_GET_FEATURE_REPORT;
+       rqst.instance_id = shid->uid.instance;
+       rqst.flags = SSAM_REQUEST_HAS_RESPONSE;
+       rqst.length = sizeof(payload);
+       rqst.payload = &payload;
+
+       rsp.capacity = len;
+       rsp.length = 0;
+       rsp.pointer = buf;
+
+       status = ssam_retry(ssam_request_sync_onstack, shid->ctrl, &rqst, &rsp, sizeof(payload));
+       if (status)
+               return status;
+
+       if (rsp.length != len) {
+               dev_err(shid->dev, "invalid feature report length: got %zu, expected, %zu\n",
+                       rsp.length, len);
+               return -EPROTO;
+       }
+
+       return 0;
+}
+
+static bool ssam_kbd_is_input_event(const struct ssam_event *event)
+{
+       if (event->command_id == SURFACE_KBD_CID_EVT_INPUT_GENERIC)
+               return true;
+
+       if (event->command_id == SURFACE_KBD_CID_EVT_INPUT_HOTKEYS)
+               return true;
+
+       return false;
+}
+
+static u32 ssam_kbd_event_fn(struct ssam_event_notifier *nf, const struct ssam_event *event)
+{
+       struct surface_hid_device *shid = container_of(nf, struct surface_hid_device, notif);
+
+       /*
+        * Check against device UID manually, as registry and device target
+        * category doesn't line up.
+        */
+
+       if (shid->uid.category != event->target_category)
+               return 0;
+
+       if (shid->uid.target != event->target_id)
+               return 0;
+
+       if (shid->uid.instance != event->instance_id)
+               return 0;
+
+       if (!ssam_kbd_is_input_event(event))
+               return 0;
+
+       hid_input_report(shid->hid, HID_INPUT_REPORT, (u8 *)&event->data[0], event->length, 0);
+       return SSAM_NOTIF_HANDLED;
+}
+
+
+/* -- Transport driver (KBD). ----------------------------------------------- */
+
+static int skbd_get_caps_led_value(struct hid_device *hid, u8 rprt_id, u8 *buf, size_t len)
+{
+       struct hid_field *field;
+       unsigned int offset, size;
+       int i;
+
+       /* Get LED field. */
+       field = hidinput_get_led_field(hid);
+       if (!field)
+               return -ENOENT;
+
+       /* Check if we got the correct report. */
+       if (len != hid_report_len(field->report))
+               return -ENOENT;
+
+       if (rprt_id != field->report->id)
+               return -ENOENT;
+
+       /* Get caps lock LED index. */
+       for (i = 0; i < field->report_count; i++)
+               if ((field->usage[i].hid & 0xffff) == 0x02)
+                       break;
+
+       if (i == field->report_count)
+               return -ENOENT;
+
+       /* Extract value. */
+       size = field->report_size;
+       offset = field->report_offset + i * size;
+       return !!hid_field_extract(hid, buf + 1, size, offset);
+}
+
+static int skbd_output_report(struct surface_hid_device *shid, u8 rprt_id, u8 *buf, size_t len)
+{
+       int caps_led;
+       int status;
+
+       caps_led = skbd_get_caps_led_value(shid->hid, rprt_id, buf, len);
+       if (caps_led < 0)
+               return -EIO;  /* Only caps LED output reports are supported. */
+
+       status = ssam_kbd_set_caps_led(shid, caps_led);
+       if (status < 0)
+               return status;
+
+       return len;
+}
+
+static int skbd_get_feature_report(struct surface_hid_device *shid, u8 rprt_id, u8 *buf, size_t len)
+{
+       u8 report[KBD_FEATURE_REPORT_SIZE];
+       int status;
+
+       /*
+        * The keyboard only has a single hard-coded read-only feature report
+        * of size KBD_FEATURE_REPORT_SIZE. Try to load it and compare its
+        * report ID against the requested one.
+        */
+
+       if (len < ARRAY_SIZE(report))
+               return -ENOSPC;
+
+       status = ssam_kbd_get_feature_report(shid, report, ARRAY_SIZE(report));
+       if (status < 0)
+               return status;
+
+       if (rprt_id != report[0])
+               return -ENOENT;
+
+       memcpy(buf, report, ARRAY_SIZE(report));
+       return len;
+}
+
+static int skbd_set_feature_report(struct surface_hid_device *shid, u8 rprt_id, u8 *buf, size_t len)
+{
+       /* Not supported. See skbd_get_feature_report() for details. */
+       return -EIO;
+}
+
+
+/* -- Driver setup. --------------------------------------------------------- */
+
+static int surface_kbd_probe(struct platform_device *pdev)
+{
+       struct ssam_controller *ctrl;
+       struct surface_hid_device *shid;
+
+       /* Add device link to EC. */
+       ctrl = ssam_client_bind(&pdev->dev);
+       if (IS_ERR(ctrl))
+               return PTR_ERR(ctrl) == -ENODEV ? -EPROBE_DEFER : PTR_ERR(ctrl);
+
+       shid = devm_kzalloc(&pdev->dev, sizeof(*shid), GFP_KERNEL);
+       if (!shid)
+               return -ENOMEM;
+
+       shid->dev = &pdev->dev;
+       shid->ctrl = ctrl;
+
+       shid->uid.domain = SSAM_DOMAIN_SERIALHUB;
+       shid->uid.category = SSAM_SSH_TC_KBD;
+       shid->uid.target = 2;
+       shid->uid.instance = 0;
+       shid->uid.function = 0;
+
+       shid->notif.base.priority = 1;
+       shid->notif.base.fn = ssam_kbd_event_fn;
+       shid->notif.event.reg = SSAM_EVENT_REGISTRY_SAM;
+       shid->notif.event.id.target_category = shid->uid.category;
+       shid->notif.event.id.instance = shid->uid.instance;
+       shid->notif.event.mask = SSAM_EVENT_MASK_NONE;
+       shid->notif.event.flags = 0;
+
+       shid->ops.get_descriptor = ssam_kbd_get_descriptor;
+       shid->ops.output_report = skbd_output_report;
+       shid->ops.get_feature_report = skbd_get_feature_report;
+       shid->ops.set_feature_report = skbd_set_feature_report;
+
+       platform_set_drvdata(pdev, shid);
+       return surface_hid_device_add(shid);
+}
+
+static int surface_kbd_remove(struct platform_device *pdev)
+{
+       surface_hid_device_destroy(platform_get_drvdata(pdev));
+       return 0;
+}
+
+static const struct acpi_device_id surface_kbd_match[] = {
+       { "MSHW0096" },
+       { },
+};
+MODULE_DEVICE_TABLE(acpi, surface_kbd_match);
+
+static struct platform_driver surface_kbd_driver = {
+       .probe = surface_kbd_probe,
+       .remove = surface_kbd_remove,
+       .driver = {
+               .name = "surface_keyboard",
+               .acpi_match_table = surface_kbd_match,
+               .pm = &surface_hid_pm_ops,
+               .probe_type = PROBE_PREFER_ASYNCHRONOUS,
+       },
+};
+module_platform_driver(surface_kbd_driver);
+
+MODULE_AUTHOR("Maximilian Luz <luzmaximilian@gmail.com>");
+MODULE_DESCRIPTION("HID legacy transport driver for Surface System Aggregator Module");
+MODULE_LICENSE("GPL");
index fddac7c..ea126c5 100644 (file)
@@ -505,7 +505,7 @@ static void pidff_playback_pid(struct pidff_device *pidff, int pid_id, int n)
                        HID_REQ_SET_REPORT);
 }
 
-/**
+/*
  * Play the effect with effect id @effect_id for @value times
  */
 static int pidff_playback(struct input_dev *dev, int effect_id, int value)
@@ -997,7 +997,7 @@ static int pidff_find_special_fields(struct pidff_device *pidff)
        return 0;
 }
 
-/**
+/*
  * Find the implemented effect types
  */
 static int pidff_find_effects(struct pidff_device *pidff,
index 45e0b1c..2fb2991 100644 (file)
@@ -887,11 +887,11 @@ int hiddev_connect(struct hid_device *hid, unsigned int force)
                                break;
 
                if (i == hid->maxcollection)
-                       return -1;
+                       return -EINVAL;
        }
 
        if (!(hiddev = kzalloc(sizeof(struct hiddev), GFP_KERNEL)))
-               return -1;
+               return -ENOMEM;
 
        init_waitqueue_head(&hiddev->wait);
        INIT_LIST_HEAD(&hiddev->list);
@@ -905,7 +905,7 @@ int hiddev_connect(struct hid_device *hid, unsigned int force)
                hid_err(hid, "Not able to get a minor for this device\n");
                hid->hiddev = NULL;
                kfree(hiddev);
-               return -1;
+               return retval;
        }
 
        /*
index d5b7a69..e22434d 100644 (file)
@@ -63,7 +63,7 @@ static const unsigned char usb_kbd_keycode[256] = {
  *             new key is pressed or a key that was pressed is released.
  * @led:       URB for sending LEDs (e.g. numlock, ...)
  * @newleds:   data that will be sent with the @led URB representing which LEDs
              should be on
*             should be on
  * @name:      Name of the keyboard. @dev's name field points to this buffer
  * @phys:      Physical path of the keyboard. @dev's phys field points to this
  *             buffer
@@ -91,7 +91,7 @@ struct usb_kbd {
        unsigned char *leds;
        dma_addr_t new_dma;
        dma_addr_t leds_dma;
-       
+
        spinlock_t leds_lock;
        bool led_urb_submitted;
 
@@ -175,15 +175,15 @@ static int usb_kbd_event(struct input_dev *dev, unsigned int type,
        }
 
        *(kbd->leds) = kbd->newleds;
-       
+
        kbd->led->dev = kbd->usbdev;
        if (usb_submit_urb(kbd->led, GFP_ATOMIC))
                pr_err("usb_submit_urb(leds) failed\n");
        else
                kbd->led_urb_submitted = true;
-       
+
        spin_unlock_irqrestore(&kbd->leds_lock, flags);
-       
+
        return 0;
 }
 
@@ -205,14 +205,14 @@ static void usb_kbd_led(struct urb *urb)
        }
 
        *(kbd->leds) = kbd->newleds;
-       
+
        kbd->led->dev = kbd->usbdev;
        if (usb_submit_urb(kbd->led, GFP_ATOMIC)){
                hid_err(urb->dev, "usb_submit_urb(leds) failed\n");
                kbd->led_urb_submitted = false;
        }
        spin_unlock_irqrestore(&kbd->leds_lock, flags);
-       
+
 }
 
 static int usb_kbd_open(struct input_dev *dev)
@@ -358,9 +358,9 @@ static int usb_kbd_probe(struct usb_interface *iface,
        device_set_wakeup_enable(&dev->dev, 1);
        return 0;
 
-fail2: 
+fail2:
        usb_kbd_free_mem(dev, kbd);
-fail1: 
+fail1:
        input_free_device(input_dev);
        kfree(kbd);
        return error;
index 8328ef1..57bfa0a 100644 (file)
@@ -1495,7 +1495,7 @@ struct wacom_led *wacom_led_find(struct wacom *wacom, unsigned int group_id,
        return &group->leds[id];
 }
 
-/**
+/*
  * wacom_led_next: gives the next available led with a wacom trigger.
  *
  * returns the next available struct wacom_led which has its default trigger
index 2d70dc4..81d7d12 100644 (file)
@@ -1860,8 +1860,6 @@ static void wacom_map_usage(struct input_dev *input, struct hid_usage *usage,
        usage->type = type;
        usage->code = code;
 
-       set_bit(type, input->evbit);
-
        switch (type) {
        case EV_ABS:
                input_set_abs_params(input, code, fmin, fmax, fuzz, 0);
@@ -1869,13 +1867,9 @@ static void wacom_map_usage(struct input_dev *input, struct hid_usage *usage,
                                  hidinput_calc_abs_res(field, resolution_code));
                break;
        case EV_KEY:
-               input_set_capability(input, EV_KEY, code);
-               break;
        case EV_MSC:
-               input_set_capability(input, EV_MSC, code);
-               break;
        case EV_SW:
-               input_set_capability(input, EV_SW, code);
+               input_set_capability(input, type, code);
                break;
        }
 }
@@ -2187,6 +2181,18 @@ static void wacom_wac_pad_report(struct hid_device *hdev,
        }
 }
 
+static void wacom_set_barrel_switch3_usage(struct wacom_wac *wacom_wac)
+{
+       struct input_dev *input = wacom_wac->pen_input;
+       struct wacom_features *features = &wacom_wac->features;
+
+       if (!(features->quirks & WACOM_QUIRK_AESPEN) &&
+           wacom_wac->hid_data.barrelswitch &&
+           wacom_wac->hid_data.barrelswitch2 &&
+           wacom_wac->hid_data.serialhi)
+               input_set_capability(input, EV_KEY, BTN_STYLUS3);
+}
+
 static void wacom_wac_pen_usage_mapping(struct hid_device *hdev,
                struct hid_field *field, struct hid_usage *usage)
 {
@@ -2227,13 +2233,21 @@ static void wacom_wac_pen_usage_mapping(struct hid_device *hdev,
                wacom_map_usage(input, usage, field, EV_ABS, ABS_Z, 0);
                break;
        case HID_DG_ERASER:
+               input_set_capability(input, EV_KEY, BTN_TOOL_RUBBER);
+               wacom_map_usage(input, usage, field, EV_KEY, BTN_TOUCH, 0);
+               break;
        case HID_DG_TIPSWITCH:
+               input_set_capability(input, EV_KEY, BTN_TOOL_PEN);
                wacom_map_usage(input, usage, field, EV_KEY, BTN_TOUCH, 0);
                break;
        case HID_DG_BARRELSWITCH:
+               wacom_wac->hid_data.barrelswitch = true;
+               wacom_set_barrel_switch3_usage(wacom_wac);
                wacom_map_usage(input, usage, field, EV_KEY, BTN_STYLUS, 0);
                break;
        case HID_DG_BARRELSWITCH2:
+               wacom_wac->hid_data.barrelswitch2 = true;
+               wacom_set_barrel_switch3_usage(wacom_wac);
                wacom_map_usage(input, usage, field, EV_KEY, BTN_STYLUS2, 0);
                break;
        case HID_DG_TOOLSERIALNUMBER:
@@ -2245,22 +2259,12 @@ static void wacom_wac_pen_usage_mapping(struct hid_device *hdev,
                wacom_map_usage(input, usage, field, EV_KEY, BTN_TOOL_PEN, 0);
                break;
        case WACOM_HID_WD_SERIALHI:
+               wacom_wac->hid_data.serialhi = true;
+               wacom_set_barrel_switch3_usage(wacom_wac);
                wacom_map_usage(input, usage, field, EV_ABS, ABS_MISC, 0);
-
-               if (!(features->quirks & WACOM_QUIRK_AESPEN)) {
-                       set_bit(EV_KEY, input->evbit);
-                       input_set_capability(input, EV_KEY, BTN_TOOL_PEN);
-                       input_set_capability(input, EV_KEY, BTN_TOOL_RUBBER);
-                       input_set_capability(input, EV_KEY, BTN_TOOL_BRUSH);
-                       input_set_capability(input, EV_KEY, BTN_TOOL_PENCIL);
-                       input_set_capability(input, EV_KEY, BTN_TOOL_AIRBRUSH);
-                       if (!(features->device_type & WACOM_DEVICETYPE_DIRECT)) {
-                               input_set_capability(input, EV_KEY, BTN_TOOL_MOUSE);
-                               input_set_capability(input, EV_KEY, BTN_TOOL_LENS);
-                       }
-               }
                break;
        case WACOM_HID_WD_FINGERWHEEL:
+               input_set_capability(input, EV_KEY, BTN_TOOL_AIRBRUSH);
                wacom_map_usage(input, usage, field, EV_ABS, ABS_WHEEL, 0);
                break;
        }
@@ -3582,11 +3586,9 @@ int wacom_setup_pen_input_capabilities(struct input_dev *input_dev,
        else
                __set_bit(INPUT_PROP_POINTER, input_dev->propbit);
 
-       if (features->type == HID_GENERIC) {
-               /* setup has already been done; apply otherwise-undetectible quirks */
-               input_set_capability(input_dev, EV_KEY, BTN_STYLUS3);
+       if (features->type == HID_GENERIC)
+               /* setup has already been done */
                return 0;
-       }
 
        input_dev->evbit[0] |= BIT_MASK(EV_KEY) | BIT_MASK(EV_ABS);
        __set_bit(BTN_TOUCH, input_dev->keybit);
index 195910d..71c8862 100644 (file)
@@ -300,6 +300,7 @@ struct hid_data {
        bool tipswitch;
        bool barrelswitch;
        bool barrelswitch2;
+       bool serialhi;
        int x;
        int y;
        int pressure;
index ef702b3..271021e 100644 (file)
@@ -153,6 +153,7 @@ struct hid_item {
 #define HID_UP_CONSUMER                0x000c0000
 #define HID_UP_DIGITIZER       0x000d0000
 #define HID_UP_PID             0x000f0000
+#define HID_UP_BATTERY         0x00850000
 #define HID_UP_HPVENDOR         0xff7f0000
 #define HID_UP_HPVENDOR2        0xff010000
 #define HID_UP_MSVENDOR                0xff000000
@@ -262,6 +263,8 @@ struct hid_item {
 #define HID_CP_SELECTION       0x000c0080
 #define HID_CP_MEDIASELECTION  0x000c0087
 #define HID_CP_SELECTDISC      0x000c00ba
+#define HID_CP_VOLUMEUP                0x000c00e9
+#define HID_CP_VOLUMEDOWN      0x000c00ea
 #define HID_CP_PLAYBACKSPEED   0x000c00f1
 #define HID_CP_PROXIMITY       0x000c0109
 #define HID_CP_SPEAKERSYSTEM   0x000c0160
@@ -297,6 +300,8 @@ struct hid_item {
 #define HID_DG_TOOLSERIALNUMBER        0x000d005b
 #define HID_DG_LATENCYMODE     0x000d0060
 
+#define HID_BAT_ABSOLUTESTATEOFCHARGE  0x00850065
+
 #define HID_VD_ASUS_CUSTOM_MEDIA_KEYS  0xff310076
 /*
  * HID report types --- Ouch! HID spec says 1 2 3!