Merge branch 'for-5.13/magicmouse' into for-linus
authorJiri Kosina <jkosina@suse.cz>
Thu, 29 Apr 2021 19:43:43 +0000 (21:43 +0200)
committerJiri Kosina <jkosina@suse.cz>
Thu, 29 Apr 2021 19:43:43 +0000 (21:43 +0200)
- Apple Magic Mouse 2 support from John Chen

19 files changed:
MAINTAINERS
drivers/hid/Kconfig
drivers/hid/Makefile
drivers/hid/amd-sfh-hid/amd_sfh_pcie.c
drivers/hid/amd-sfh-hid/amd_sfh_pcie.h
drivers/hid/hid-alps.c
drivers/hid/hid-asus.c
drivers/hid/hid-cp2112.c
drivers/hid/hid-elan.c
drivers/hid/hid-ft260.c [new file with mode: 0644]
drivers/hid/hid-google-hammer.c
drivers/hid/hid-ids.h
drivers/hid/hid-lenovo.c
drivers/hid/hid-lg.c
drivers/hid/hid-logitech-dj.c
drivers/hid/hid-quirks.c
drivers/hid/i2c-hid/i2c-hid-acpi.c
drivers/hid/usbhid/hiddev.c
drivers/hid/wacom_wac.c

index dace4bd..cbfe57e 100644 (file)
@@ -7278,6 +7278,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
index 786b71e..b279c61 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
index c4f6d5c..6e24c37 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
index dbac166..ddecc84 100644 (file)
@@ -10,6 +10,7 @@
 #include <linux/bitops.h>
 #include <linux/delay.h>
 #include <linux/dma-mapping.h>
+#include <linux/dmi.h>
 #include <linux/interrupt.h>
 #include <linux/io-64-nonatomic-lo-hi.h>
 #include <linux/module.h>
 
 #define ACEL_EN                BIT(0)
 #define GYRO_EN                BIT(1)
-#define MAGNO_EN               BIT(2)
+#define MAGNO_EN       BIT(2)
 #define ALS_EN         BIT(19)
 
+static int sensor_mask_override = -1;
+module_param_named(sensor_mask, sensor_mask_override, int, 0444);
+MODULE_PARM_DESC(sensor_mask, "override the detected sensors mask");
+
 void amd_start_sensor(struct amd_mp2_dev *privdata, struct amd_mp2_sensor_info info)
 {
        union sfh_cmd_param cmd_param;
@@ -73,12 +78,41 @@ void amd_stop_all_sensors(struct amd_mp2_dev *privdata)
        writel(cmd_base.ul, privdata->mmio + AMD_C2P_MSG0);
 }
 
+static const struct dmi_system_id dmi_sensor_mask_overrides[] = {
+       {
+               .matches = {
+                       DMI_MATCH(DMI_PRODUCT_NAME, "HP ENVY x360 Convertible 13-ag0xxx"),
+               },
+               .driver_data = (void *)(ACEL_EN | MAGNO_EN),
+       },
+       {
+               .matches = {
+                       DMI_MATCH(DMI_PRODUCT_NAME, "HP ENVY x360 Convertible 15-cp0xxx"),
+               },
+               .driver_data = (void *)(ACEL_EN | MAGNO_EN),
+       },
+       { }
+};
+
 int amd_mp2_get_sensor_num(struct amd_mp2_dev *privdata, u8 *sensor_id)
 {
        int activestatus, num_of_sensors = 0;
+       const struct dmi_system_id *dmi_id;
+       u32 activecontrolstatus;
+
+       if (sensor_mask_override == -1) {
+               dmi_id = dmi_first_match(dmi_sensor_mask_overrides);
+               if (dmi_id)
+                       sensor_mask_override = (long)dmi_id->driver_data;
+       }
+
+       if (sensor_mask_override >= 0) {
+               activestatus = sensor_mask_override;
+       } else {
+               activecontrolstatus = readl(privdata->mmio + AMD_P2C_MSG3);
+               activestatus = activecontrolstatus >> 4;
+       }
 
-       privdata->activecontrolstatus = readl(privdata->mmio + AMD_P2C_MSG3);
-       activestatus = privdata->activecontrolstatus >> 4;
        if (ACEL_EN  & activestatus)
                sensor_id[num_of_sensors++] = accel_idx;
 
index 8f8d19b..489415f 100644 (file)
@@ -61,7 +61,6 @@ struct amd_mp2_dev {
        struct pci_dev *pdev;
        struct amdtp_cl_data *cl_data;
        void __iomem *mmio;
-       u32 activecontrolstatus;
 };
 
 struct amd_mp2_sensor_info {
index 3feaece..6b66593 100644 (file)
@@ -761,6 +761,7 @@ static int alps_input_configured(struct hid_device *hdev, struct hid_input *hi)
 
                if (input_register_device(data->input2)) {
                        input_free_device(input2);
+                       ret = -ENOENT;
                        goto exit;
                }
        }
index 1dfe184..2ab22b9 100644 (file)
@@ -1221,6 +1221,9 @@ static const struct hid_device_id asus_devices[] = {
        { HID_USB_DEVICE(USB_VENDOR_ID_ASUSTEK,
            USB_DEVICE_ID_ASUSTEK_ROG_NKEY_KEYBOARD),
          QUIRK_USE_KBD_BACKLIGHT | QUIRK_ROG_NKEY_KEYBOARD },
+       { HID_USB_DEVICE(USB_VENDOR_ID_ASUSTEK,
+           USB_DEVICE_ID_ASUSTEK_ROG_NKEY_KEYBOARD2),
+         QUIRK_USE_KBD_BACKLIGHT | QUIRK_ROG_NKEY_KEYBOARD },
        { HID_USB_DEVICE(USB_VENDOR_ID_ASUSTEK,
                USB_DEVICE_ID_ASUSTEK_T100TA_KEYBOARD),
          QUIRK_T100_KEYBOARD | QUIRK_NO_CONSUMER_USAGES },
index 21e1562..477baa3 100644 (file)
@@ -161,6 +161,7 @@ struct cp2112_device {
        atomic_t read_avail;
        atomic_t xfer_avail;
        struct gpio_chip gc;
+       struct irq_chip irq;
        u8 *in_out_buffer;
        struct mutex lock;
 
@@ -1175,16 +1176,6 @@ static int cp2112_gpio_irq_type(struct irq_data *d, unsigned int type)
        return 0;
 }
 
-static struct irq_chip cp2112_gpio_irqchip = {
-       .name = "cp2112-gpio",
-       .irq_startup = cp2112_gpio_irq_startup,
-       .irq_shutdown = cp2112_gpio_irq_shutdown,
-       .irq_ack = cp2112_gpio_irq_ack,
-       .irq_mask = cp2112_gpio_irq_mask,
-       .irq_unmask = cp2112_gpio_irq_unmask,
-       .irq_set_type = cp2112_gpio_irq_type,
-};
-
 static int __maybe_unused cp2112_allocate_irq(struct cp2112_device *dev,
                                              int pin)
 {
@@ -1339,8 +1330,17 @@ static int cp2112_probe(struct hid_device *hdev, const struct hid_device_id *id)
        dev->gc.can_sleep               = 1;
        dev->gc.parent                  = &hdev->dev;
 
+       dev->irq.name = "cp2112-gpio";
+       dev->irq.irq_startup = cp2112_gpio_irq_startup;
+       dev->irq.irq_shutdown = cp2112_gpio_irq_shutdown;
+       dev->irq.irq_ack = cp2112_gpio_irq_ack;
+       dev->irq.irq_mask = cp2112_gpio_irq_mask;
+       dev->irq.irq_unmask = cp2112_gpio_irq_unmask;
+       dev->irq.irq_set_type = cp2112_gpio_irq_type;
+       dev->irq.flags = IRQCHIP_MASK_ON_SUSPEND;
+
        girq = &dev->gc.irq;
-       girq->chip = &cp2112_gpio_irqchip;
+       girq->chip = &dev->irq;
        /* The event comes from the outside so no parent handler */
        girq->parent_handler = NULL;
        girq->num_parents = 0;
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 d931962..e60c31d 100644 (file)
@@ -573,6 +573,8 @@ static void hammer_remove(struct hid_device *hdev)
 }
 
 static const struct hid_device_id hammer_devices[] = {
+       { HID_DEVICE(BUS_USB, HID_GROUP_GENERIC,
+                    USB_VENDOR_ID_GOOGLE, USB_DEVICE_ID_GOOGLE_DON) },
        { HID_DEVICE(BUS_USB, HID_GROUP_GENERIC,
                     USB_VENDOR_ID_GOOGLE, USB_DEVICE_ID_GOOGLE_HAMMER) },
        { HID_DEVICE(BUS_USB, HID_GROUP_GENERIC,
index fa0edf0..158f6ba 100644 (file)
 #define USB_DEVICE_ID_ASUSTEK_ROG_KEYBOARD2 0x1837
 #define USB_DEVICE_ID_ASUSTEK_ROG_KEYBOARD3 0x1822
 #define USB_DEVICE_ID_ASUSTEK_ROG_NKEY_KEYBOARD        0x1866
+#define USB_DEVICE_ID_ASUSTEK_ROG_NKEY_KEYBOARD2       0x19b6
 #define USB_DEVICE_ID_ASUSTEK_FX503VD_KEYBOARD 0x1869
 
 #define USB_VENDOR_ID_ATEN             0x0557
 
 #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_GOOGLE_MASTERBALL        0x503c
 #define USB_DEVICE_ID_GOOGLE_MAGNEMITE 0x503d
 #define USB_DEVICE_ID_GOOGLE_MOONBALL  0x5044
+#define USB_DEVICE_ID_GOOGLE_DON       0x5050
 
 #define USB_VENDOR_ID_GOTOP            0x08f2
 #define USB_DEVICE_ID_SUPER_Q2         0x007f
 #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
 
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 1a9daf0..3816be7 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) },
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,
 };
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 44d715c..2d70dc4 100644 (file)
@@ -2533,7 +2533,7 @@ static void wacom_wac_finger_slot(struct wacom_wac *wacom_wac,
            !wacom_wac->shared->is_touch_on) {
                if (!wacom_wac->shared->touch_down)
                        return;
-               prox = 0;
+               prox = false;
        }
 
        wacom_wac->hid_data.num_received++;
@@ -3574,8 +3574,6 @@ int wacom_setup_pen_input_capabilities(struct input_dev *input_dev,
 {
        struct wacom_features *features = &wacom_wac->features;
 
-       input_dev->evbit[0] |= BIT_MASK(EV_KEY) | BIT_MASK(EV_ABS);
-
        if (!(features->device_type & WACOM_DEVICETYPE_PEN))
                return -ENODEV;
 
@@ -3590,6 +3588,7 @@ int wacom_setup_pen_input_capabilities(struct input_dev *input_dev,
                return 0;
        }
 
+       input_dev->evbit[0] |= BIT_MASK(EV_KEY) | BIT_MASK(EV_ABS);
        __set_bit(BTN_TOUCH, input_dev->keybit);
        __set_bit(ABS_MISC, input_dev->absbit);
 
@@ -3742,8 +3741,6 @@ int wacom_setup_touch_input_capabilities(struct input_dev *input_dev,
 {
        struct wacom_features *features = &wacom_wac->features;
 
-       input_dev->evbit[0] |= BIT_MASK(EV_KEY) | BIT_MASK(EV_ABS);
-
        if (!(features->device_type & WACOM_DEVICETYPE_TOUCH))
                return -ENODEV;
 
@@ -3756,6 +3753,7 @@ int wacom_setup_touch_input_capabilities(struct input_dev *input_dev,
                /* 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);
 
        if (features->touch_max == 1) {