diff options
author | Linus Torvalds | 2021-04-30 12:53:02 -0700 |
---|---|---|
committer | Linus Torvalds | 2021-04-30 12:53:02 -0700 |
commit | efd8929b9eec1cde120abb36d76dd00ff6711023 (patch) | |
tree | b55c7ea687b952bb133485c2ec4d6c936508a64f | |
parent | b71428d7ab333a157216a1d73c8c82a178efada9 (diff) | |
parent | e16e9f1184181a874cf432302ffe4689cc56b9e2 (diff) |
Merge branch 'for-linus' of git://git.kernel.org/pub/scm/linux/kernel/git/hid/hid
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, 2948 insertions, 222 deletions
diff --git a/MAINTAINERS b/MAINTAINERS index 9a8e2c2c706c..30aa8253bf3a 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -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 diff --git a/drivers/hid/Kconfig b/drivers/hid/Kconfig index 786b71ef7738..4bf263c2d61a 100644 --- a/drivers/hid/Kconfig +++ b/drivers/hid/Kconfig @@ -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 diff --git a/drivers/hid/Makefile b/drivers/hid/Makefile index c4f6d5c613dc..193431ec4db8 100644 --- a/drivers/hid/Makefile +++ b/drivers/hid/Makefile @@ -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/ diff --git a/drivers/hid/hid-alps.c b/drivers/hid/hid-alps.c index 6b665931147d..2b986d0dbde4 100644 --- a/drivers/hid/hid-alps.c +++ b/drivers/hid/hid-alps.c @@ -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 diff --git a/drivers/hid/hid-core.c b/drivers/hid/hid-core.c index 097cb1ee3126..0ae9f6df59d1 100644 --- a/drivers/hid/hid-core.c +++ b/drivers/hid/hid-core.c @@ -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 diff --git a/drivers/hid/hid-debug.c b/drivers/hid/hid-debug.c index d7eaf9100370..59f8d716d78f 100644 --- a/drivers/hid/hid-debug.c +++ b/drivers/hid/hid-debug.c @@ -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" }, diff --git a/drivers/hid/hid-elan.c b/drivers/hid/hid-elan.c index dae193749d44..021049805bb7 100644 --- a/drivers/hid/hid-elan.c +++ b/drivers/hid/hid-elan.c @@ -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 index 000000000000..a5751607ce24 --- /dev/null +++ b/drivers/hid/hid-ft260.c @@ -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"); diff --git a/drivers/hid/hid-ids.h b/drivers/hid/hid-ids.h index 67fd8a2f5aba..84b8da3e7d09 100644 --- a/drivers/hid/hid-ids.h +++ b/drivers/hid/hid-ids.h @@ -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 @@ -431,6 +432,7 @@ #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 @@ -808,6 +810,7 @@ #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 @@ -816,8 +819,14 @@ #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 @@ -946,6 +955,7 @@ #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 diff --git a/drivers/hid/hid-input.c b/drivers/hid/hid-input.c index 236bccd37760..18f5e28d475c 100644 --- a/drivers/hid/hid-input.c +++ b/drivers/hid/hid-input.c @@ -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); diff --git a/drivers/hid/hid-kye.c b/drivers/hid/hid-kye.c index c8b40c07eca6..f46616390a98 100644 --- a/drivers/hid/hid-kye.c +++ b/drivers/hid/hid-kye.c @@ -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 * diff --git a/drivers/hid/hid-lenovo.c b/drivers/hid/hid-lenovo.c index c6c8e20f3e8d..93b1f935e526 100644 --- a/drivers/hid/hid-lenovo.c +++ b/drivers/hid/hid-lenovo.c @@ -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) }, { } }; diff --git a/drivers/hid/hid-lg.c b/drivers/hid/hid-lg.c index 0dc7cdfc56f7..d40af911df63 100644 --- a/drivers/hid/hid-lg.c +++ b/drivers/hid/hid-lg.c @@ -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 }, diff --git a/drivers/hid/hid-logitech-dj.c b/drivers/hid/hid-logitech-dj.c index 271bd8d24339..fa835d565982 100644 --- a/drivers/hid/hid-logitech-dj.c +++ b/drivers/hid/hid-logitech-dj.c @@ -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}, {} }; diff --git a/drivers/hid/hid-logitech-hidpp.c b/drivers/hid/hid-logitech-hidpp.c index d459e2dbe647..d598094dadd0 100644 --- a/drivers/hid/hid-logitech-hidpp.c +++ b/drivers/hid/hid-logitech-hidpp.c @@ -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. */ diff --git a/drivers/hid/hid-magicmouse.c b/drivers/hid/hid-magicmouse.c index abd86903875f..2bb473d8c424 100644 --- a/drivers/hid/hid-magicmouse.c +++ b/drivers/hid/hid-magicmouse.c @@ -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, }; diff --git a/drivers/hid/hid-picolcd_core.c b/drivers/hid/hid-picolcd_core.c index 1b5c63241af0..bbda231a7ce3 100644 --- a/drivers/hid/hid-picolcd_core.c +++ b/drivers/hid/hid-picolcd_core.c @@ -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); /* diff --git a/drivers/hid/hid-plantronics.c b/drivers/hid/hid-plantronics.c index 85b685efc12f..e81b7cec2d12 100644 --- a/drivers/hid/hid-plantronics.c +++ b/drivers/hid/hid-plantronics.c @@ -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 @@ -36,6 +37,16 @@ #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); diff --git a/drivers/hid/hid-quirks.c b/drivers/hid/hid-quirks.c index 1a9daf03dbfa..3dd6f15f2a67 100644 --- a/drivers/hid/hid-quirks.c +++ b/drivers/hid/hid-quirks.c @@ -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) }, diff --git a/drivers/hid/hid-sensor-custom.c b/drivers/hid/hid-sensor-custom.c index 2628bc53ed80..2e6662173a79 100644 --- a/drivers/hid/hid-sensor-custom.c +++ b/drivers/hid/hid-sensor-custom.c @@ -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; diff --git a/drivers/hid/hid-sensor-hub.c b/drivers/hid/hid-sensor-hub.c index 3dd7d3246737..95cf88f3bafb 100644 --- a/drivers/hid/hid-sensor-hub.c +++ b/drivers/hid/hid-sensor-hub.c @@ -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 index 000000000000..2e452c6e8ef4 --- /dev/null +++ b/drivers/hid/hid-thrustmaster.c @@ -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"); + diff --git a/drivers/hid/hid-uclogic-params.c b/drivers/hid/hid-uclogic-params.c index 6af25c38b9cc..3d67b748a3b9 100644 --- a/drivers/hid/hid-uclogic-params.c +++ b/drivers/hid/hid-uclogic-params.c @@ -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 diff --git a/drivers/hid/hid-uclogic-rdesc.c b/drivers/hid/hid-uclogic-rdesc.c index bf5da6de7bba..6dd6dcd09c8b 100644 --- a/drivers/hid/hid-uclogic-rdesc.c +++ b/drivers/hid/hid-uclogic-rdesc.c @@ -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 diff --git a/drivers/hid/i2c-hid/i2c-hid-acpi.c b/drivers/hid/i2c-hid/i2c-hid-acpi.c index bb8c00e6be78..a6f0257a26de 100644 --- a/drivers/hid/i2c-hid/i2c-hid-acpi.c +++ b/drivers/hid/i2c-hid/i2c-hid-acpi.c @@ -25,12 +25,13 @@ #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 index 000000000000..7ce9b5d641eb --- /dev/null +++ b/drivers/hid/surface-hid/Kconfig @@ -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 index 000000000000..4ae11cf09b25 --- /dev/null +++ b/drivers/hid/surface-hid/Makefile @@ -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 index 000000000000..3477b31611ae --- /dev/null +++ b/drivers/hid/surface-hid/surface_hid.c @@ -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 index 000000000000..7b27ec392232 --- /dev/null +++ b/drivers/hid/surface-hid/surface_hid_core.c @@ -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 index 000000000000..4b1a7b57e035 --- /dev/null +++ b/drivers/hid/surface-hid/surface_hid_core.h @@ -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 index 000000000000..0635341bc517 --- /dev/null +++ b/drivers/hid/surface-hid/surface_kbd.c @@ -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"); diff --git a/drivers/hid/usbhid/hid-pidff.c b/drivers/hid/usbhid/hid-pidff.c index fddac7c72f64..ea126c50acc3 100644 --- a/drivers/hid/usbhid/hid-pidff.c +++ b/drivers/hid/usbhid/hid-pidff.c @@ -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, diff --git a/drivers/hid/usbhid/hiddev.c b/drivers/hid/usbhid/hiddev.c index 45e0b1c75cb1..2fb2991dbe4c 100644 --- a/drivers/hid/usbhid/hiddev.c +++ b/drivers/hid/usbhid/hiddev.c @@ -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; } /* diff --git a/drivers/hid/usbhid/usbkbd.c b/drivers/hid/usbhid/usbkbd.c index d5b7a696a68c..e22434dfc9ef 100644 --- a/drivers/hid/usbhid/usbkbd.c +++ b/drivers/hid/usbhid/usbkbd.c @@ -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; diff --git a/drivers/hid/wacom_sys.c b/drivers/hid/wacom_sys.c index 8328ef155c46..57bfa0ae9836 100644 --- a/drivers/hid/wacom_sys.c +++ b/drivers/hid/wacom_sys.c @@ -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 diff --git a/drivers/hid/wacom_wac.c b/drivers/hid/wacom_wac.c index 2d70dc4bea65..81d7d12bcf34 100644 --- a/drivers/hid/wacom_wac.c +++ b/drivers/hid/wacom_wac.c @@ -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); diff --git a/drivers/hid/wacom_wac.h b/drivers/hid/wacom_wac.h index 195910dd2154..71c886245dbf 100644 --- a/drivers/hid/wacom_wac.h +++ b/drivers/hid/wacom_wac.h @@ -300,6 +300,7 @@ struct hid_data { bool tipswitch; bool barrelswitch; bool barrelswitch2; + bool serialhi; int x; int y; int pressure; diff --git a/include/linux/hid.h b/include/linux/hid.h index ef702b3f56e3..271021e20a3f 100644 --- a/include/linux/hid.h +++ b/include/linux/hid.h @@ -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! |