diff options
Diffstat (limited to 'drivers')
-rw-r--r-- | drivers/hid/Kconfig | 18 | ||||
-rw-r--r-- | drivers/hid/Makefile | 2 | ||||
-rw-r--r-- | drivers/hid/hid-core.c | 9 | ||||
-rw-r--r-- | drivers/hid/hid-cp2112.c | 111 | ||||
-rw-r--r-- | drivers/hid/hid-hyperv.c | 6 | ||||
-rw-r--r-- | drivers/hid/hid-ids.h | 4 | ||||
-rw-r--r-- | drivers/hid/hid-lenovo-tpkbd.c | 462 | ||||
-rw-r--r-- | drivers/hid/hid-lenovo.c | 708 | ||||
-rw-r--r-- | drivers/hid/hid-picolcd_debugfs.c | 9 | ||||
-rw-r--r-- | drivers/hid/hid-rmi.c | 67 | ||||
-rw-r--r-- | drivers/hid/hid-roccat-lua.c | 2 | ||||
-rw-r--r-- | drivers/hid/hid-sony.c | 132 | ||||
-rw-r--r-- | drivers/hid/i2c-hid/i2c-hid.c | 15 | ||||
-rw-r--r-- | drivers/hid/usbhid/hid-core.c | 8 | ||||
-rw-r--r-- | drivers/hid/usbhid/hid-quirks.c | 1 |
15 files changed, 1004 insertions, 550 deletions
diff --git a/drivers/hid/Kconfig b/drivers/hid/Kconfig index 5e79c6ad914f..e02cf59b048d 100644 --- a/drivers/hid/Kconfig +++ b/drivers/hid/Kconfig @@ -331,18 +331,20 @@ config HID_LCPOWER ---help--- Support for LC-Power RC1000MCE RF remote control. -config HID_LENOVO_TPKBD - tristate "Lenovo ThinkPad USB Keyboard with TrackPoint" +config HID_LENOVO + tristate "Lenovo / Thinkpad devices" depends on HID select NEW_LEDS select LEDS_CLASS ---help--- - Support for the Lenovo ThinkPad USB Keyboard with TrackPoint. + Support for Lenovo devices that are not fully compliant with HID standard. - Say Y here if you have a Lenovo ThinkPad USB Keyboard with TrackPoint - and would like to use device-specific features like changing the - sensitivity of the trackpoint, using the microphone mute button or - controlling the mute and microphone mute LEDs. + Say Y if you want support for the non-compliant features of the Lenovo + Thinkpad standalone keyboards, e.g: + - ThinkPad USB Keyboard with TrackPoint (supports extra LEDs and trackpoint + configuration) + - ThinkPad Compact Bluetooth Keyboard with TrackPoint (supports Fn keys) + - ThinkPad Compact USB Keyboard with TrackPoint (supports Fn keys) config HID_LOGITECH tristate "Logitech devices" if EXPERT @@ -785,7 +787,7 @@ config HID_XINMO depends on HID ---help--- Support for Xin-Mo devices that are not fully compliant with the HID - standard. Currently only supports the Xin-Mo Dual Arcade. Say Y here + standard. Currently only supports the Xin-Mo Dual Arcade. Say Y here if you have a Xin-Mo Dual Arcade controller. config HID_ZEROPLUS diff --git a/drivers/hid/Makefile b/drivers/hid/Makefile index a6fa6baf368e..5e96be3ab280 100644 --- a/drivers/hid/Makefile +++ b/drivers/hid/Makefile @@ -59,7 +59,7 @@ obj-$(CONFIG_HID_KENSINGTON) += hid-kensington.o obj-$(CONFIG_HID_KEYTOUCH) += hid-keytouch.o obj-$(CONFIG_HID_KYE) += hid-kye.o obj-$(CONFIG_HID_LCPOWER) += hid-lcpower.o -obj-$(CONFIG_HID_LENOVO_TPKBD) += hid-lenovo-tpkbd.o +obj-$(CONFIG_HID_LENOVO) += hid-lenovo.o obj-$(CONFIG_HID_LOGITECH) += hid-logitech.o obj-$(CONFIG_HID_LOGITECH_DJ) += hid-logitech-dj.o obj-$(CONFIG_HID_MAGICMOUSE) += hid-magicmouse.o diff --git a/drivers/hid/hid-core.c b/drivers/hid/hid-core.c index 84ed7d4ba03d..6c813c6092f8 100644 --- a/drivers/hid/hid-core.c +++ b/drivers/hid/hid-core.c @@ -783,7 +783,9 @@ static int hid_scan_report(struct hid_device *hid) * Vendor specific handlings */ if ((hid->vendor == USB_VENDOR_ID_SYNAPTICS) && - (hid->group == HID_GROUP_GENERIC)) + (hid->group == HID_GROUP_GENERIC) && + /* only bind to the mouse interface of composite USB devices */ + (hid->bus != BUS_USB || hid->type == HID_TYPE_USBMOUSE)) /* hid-rmi should take care of them, not hid-generic */ hid->group = HID_GROUP_RMI; @@ -1796,8 +1798,10 @@ static const struct hid_device_id hid_have_special_driver[] = { { HID_USB_DEVICE(USB_VENDOR_ID_KYE, USB_DEVICE_ID_KYE_EASYPEN_M610X) }, { HID_USB_DEVICE(USB_VENDOR_ID_LABTEC, USB_DEVICE_ID_LABTEC_WIRELESS_KEYBOARD) }, { HID_USB_DEVICE(USB_VENDOR_ID_LCPOWER, USB_DEVICE_ID_LCPOWER_LC1000 ) }, -#if IS_ENABLED(CONFIG_HID_LENOVO_TPKBD) +#if IS_ENABLED(CONFIG_HID_LENOVO) { HID_USB_DEVICE(USB_VENDOR_ID_LENOVO, USB_DEVICE_ID_LENOVO_TPKBD) }, + { HID_USB_DEVICE(USB_VENDOR_ID_LENOVO, USB_DEVICE_ID_LENOVO_CUSBKBD) }, + { HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_LENOVO, USB_DEVICE_ID_LENOVO_CBTKBD) }, #endif { HID_USB_DEVICE(USB_VENDOR_ID_LOGITECH, USB_DEVICE_ID_MX3000_RECEIVER) }, { HID_USB_DEVICE(USB_VENDOR_ID_LOGITECH, USB_DEVICE_ID_S510_RECEIVER) }, @@ -2266,6 +2270,7 @@ static const struct hid_device_id hid_ignore_list[] = { { HID_USB_DEVICE(USB_VENDOR_ID_IMATION, USB_DEVICE_ID_DISC_STAKKA) }, { HID_USB_DEVICE(USB_VENDOR_ID_JABRA, USB_DEVICE_ID_JABRA_SPEAK_410) }, { HID_USB_DEVICE(USB_VENDOR_ID_JABRA, USB_DEVICE_ID_JABRA_SPEAK_510) }, + { HID_USB_DEVICE(USB_VENDOR_ID_JABRA, USB_DEVICE_ID_JABRA_GN9350E) }, { HID_USB_DEVICE(USB_VENDOR_ID_KBGEAR, USB_DEVICE_ID_KBGEAR_JAMSTUDIO) }, { HID_USB_DEVICE(USB_VENDOR_ID_KWORLD, USB_DEVICE_ID_KWORLD_RADIO_FM700) }, { HID_USB_DEVICE(USB_VENDOR_ID_KYE, USB_DEVICE_ID_KYE_GPEN_560) }, diff --git a/drivers/hid/hid-cp2112.c b/drivers/hid/hid-cp2112.c index 56be85a9a77c..a822db5a8338 100644 --- a/drivers/hid/hid-cp2112.c +++ b/drivers/hid/hid-cp2112.c @@ -240,8 +240,6 @@ static int cp2112_gpio_direction_output(struct gpio_chip *chip, u8 buf[5]; int ret; - cp2112_gpio_set(chip, offset, value); - ret = hid_hw_raw_request(hdev, CP2112_GPIO_CONFIG, buf, sizeof(buf), HID_FEATURE_REPORT, HID_REQ_GET_REPORT); @@ -260,6 +258,12 @@ static int cp2112_gpio_direction_output(struct gpio_chip *chip, return ret; } + /* + * Set gpio value when output direction is already set, + * as specified in AN495, Rev. 0.2, cpt. 4.4 + */ + cp2112_gpio_set(chip, offset, value); + return 0; } @@ -425,6 +429,105 @@ static int cp2112_write_req(void *buf, u8 slave_address, u8 command, u8 *data, return data_length + 4; } +static int cp2112_i2c_write_req(void *buf, u8 slave_address, u8 *data, + u8 data_length) +{ + struct cp2112_write_req_report *report = buf; + + if (data_length > sizeof(report->data)) + return -EINVAL; + + report->report = CP2112_DATA_WRITE_REQUEST; + report->slave_address = slave_address << 1; + report->length = data_length; + memcpy(report->data, data, data_length); + return data_length + 3; +} + +static int cp2112_i2c_xfer(struct i2c_adapter *adap, struct i2c_msg *msgs, + int num) +{ + struct cp2112_device *dev = (struct cp2112_device *)adap->algo_data; + struct hid_device *hdev = dev->hdev; + u8 buf[64]; + ssize_t count; + unsigned int retries; + int ret; + + hid_dbg(hdev, "I2C %d messages\n", num); + + if (num != 1) { + hid_err(hdev, + "Multi-message I2C transactions not supported\n"); + return -EOPNOTSUPP; + } + + if (msgs->flags & I2C_M_RD) + count = cp2112_read_req(buf, msgs->addr, msgs->len); + else + count = cp2112_i2c_write_req(buf, msgs->addr, msgs->buf, + msgs->len); + + if (count < 0) + return count; + + ret = hid_hw_power(hdev, PM_HINT_FULLON); + if (ret < 0) { + hid_err(hdev, "power management error: %d\n", ret); + return ret; + } + + ret = cp2112_hid_output(hdev, buf, count, HID_OUTPUT_REPORT); + if (ret < 0) { + hid_warn(hdev, "Error starting transaction: %d\n", ret); + goto power_normal; + } + + for (retries = 0; retries < XFER_STATUS_RETRIES; ++retries) { + ret = cp2112_xfer_status(dev); + if (-EBUSY == ret) + continue; + if (ret < 0) + goto power_normal; + break; + } + + if (XFER_STATUS_RETRIES <= retries) { + hid_warn(hdev, "Transfer timed out, cancelling.\n"); + buf[0] = CP2112_CANCEL_TRANSFER; + buf[1] = 0x01; + + ret = cp2112_hid_output(hdev, buf, 2, HID_OUTPUT_REPORT); + if (ret < 0) + hid_warn(hdev, "Error cancelling transaction: %d\n", + ret); + + ret = -ETIMEDOUT; + goto power_normal; + } + + if (!(msgs->flags & I2C_M_RD)) + goto finish; + + ret = cp2112_read(dev, msgs->buf, msgs->len); + if (ret < 0) + goto power_normal; + if (ret != msgs->len) { + hid_warn(hdev, "short read: %d < %d\n", ret, msgs->len); + ret = -EIO; + goto power_normal; + } + +finish: + /* return the number of transferred messages */ + ret = 1; + +power_normal: + hid_hw_power(hdev, PM_HINT_NORMAL); + hid_dbg(hdev, "I2C transfer finished: %d\n", ret); + return ret; +} + static int cp2112_xfer(struct i2c_adapter *adap, u16 addr, unsigned short flags, char read_write, u8 command, int size, union i2c_smbus_data *data) @@ -591,7 +694,8 @@ power_normal: static u32 cp2112_functionality(struct i2c_adapter *adap) { - return I2C_FUNC_SMBUS_BYTE | + return I2C_FUNC_I2C | + I2C_FUNC_SMBUS_BYTE | I2C_FUNC_SMBUS_BYTE_DATA | I2C_FUNC_SMBUS_WORD_DATA | I2C_FUNC_SMBUS_BLOCK_DATA | @@ -601,6 +705,7 @@ static u32 cp2112_functionality(struct i2c_adapter *adap) } static const struct i2c_algorithm smbus_algorithm = { + .master_xfer = cp2112_i2c_xfer, .smbus_xfer = cp2112_xfer, .functionality = cp2112_functionality, }; diff --git a/drivers/hid/hid-hyperv.c b/drivers/hid/hid-hyperv.c index f52dbcb7133b..31fad641b744 100644 --- a/drivers/hid/hid-hyperv.c +++ b/drivers/hid/hid-hyperv.c @@ -308,6 +308,9 @@ static void mousevsc_on_receive(struct hv_device *device, memcpy(input_dev->input_buf, input_report->buffer, len); hid_input_report(input_dev->hid_device, HID_INPUT_REPORT, input_dev->input_buf, len, 1); + + pm_wakeup_event(&input_dev->device->device, 0); + break; default: pr_err("unsupported hid msg type - type %d len %d", @@ -549,6 +552,8 @@ static int mousevsc_probe(struct hv_device *device, goto probe_err2; } + device_init_wakeup(&device->device, true); + input_dev->connected = true; input_dev->init_complete = true; @@ -571,6 +576,7 @@ static int mousevsc_remove(struct hv_device *dev) { struct mousevsc_dev *input_dev = hv_get_drvdata(dev); + device_init_wakeup(&dev->device, false); vmbus_close(dev->channel); hid_hw_stop(input_dev->hid_device); hid_destroy_device(input_dev->hid_device); diff --git a/drivers/hid/hid-ids.h b/drivers/hid/hid-ids.h index fe2f163b2b0c..d53bdda26207 100644 --- a/drivers/hid/hid-ids.h +++ b/drivers/hid/hid-ids.h @@ -479,6 +479,7 @@ #define USB_DEVICE_ID_HOLTEK_ALT_MOUSE_A070 0xa070 #define USB_DEVICE_ID_HOLTEK_ALT_MOUSE_A072 0xa072 #define USB_DEVICE_ID_HOLTEK_ALT_MOUSE_A081 0xa081 +#define USB_DEVICE_ID_HOLTEK_ALT_KEYBOARD_A096 0xa096 #define USB_VENDOR_ID_IMATION 0x0718 #define USB_DEVICE_ID_DISC_STAKKA 0xd000 @@ -489,6 +490,7 @@ #define USB_VENDOR_ID_JABRA 0x0b0e #define USB_DEVICE_ID_JABRA_SPEAK_410 0x0412 #define USB_DEVICE_ID_JABRA_SPEAK_510 0x0420 +#define USB_DEVICE_ID_JABRA_GN9350E 0x9350 #define USB_VENDOR_ID_JESS 0x0c45 #define USB_DEVICE_ID_JESS_YUREX 0x1010 @@ -561,6 +563,8 @@ #define USB_VENDOR_ID_LENOVO 0x17ef #define USB_DEVICE_ID_LENOVO_TPKBD 0x6009 +#define USB_DEVICE_ID_LENOVO_CUSBKBD 0x6047 +#define USB_DEVICE_ID_LENOVO_CBTKBD 0x6048 #define USB_VENDOR_ID_LG 0x1fd2 #define USB_DEVICE_ID_LG_MULTITOUCH 0x0064 diff --git a/drivers/hid/hid-lenovo-tpkbd.c b/drivers/hid/hid-lenovo-tpkbd.c deleted file mode 100644 index 2d25b6cbbc05..000000000000 --- a/drivers/hid/hid-lenovo-tpkbd.c +++ /dev/null @@ -1,462 +0,0 @@ -/* - * HID driver for Lenovo ThinkPad USB Keyboard with TrackPoint - * - * Copyright (c) 2012 Bernhard Seibold - */ - -/* - * This program is free software; you can redistribute it and/or modify it - * under the terms of the GNU General Public License as published by the Free - * Software Foundation; either version 2 of the License, or (at your option) - * any later version. - */ - -#include <linux/module.h> -#include <linux/sysfs.h> -#include <linux/device.h> -#include <linux/hid.h> -#include <linux/input.h> -#include <linux/leds.h> - -#include "hid-ids.h" - -/* This is only used for the trackpoint part of the driver, hence _tp */ -struct tpkbd_data_pointer { - int led_state; - struct led_classdev led_mute; - struct led_classdev led_micmute; - int press_to_select; - int dragging; - int release_to_select; - int select_right; - int sensitivity; - int press_speed; -}; - -#define map_key_clear(c) hid_map_usage_clear(hi, usage, bit, max, EV_KEY, (c)) - -static int tpkbd_input_mapping(struct hid_device *hdev, - struct hid_input *hi, struct hid_field *field, - struct hid_usage *usage, unsigned long **bit, int *max) -{ - if (usage->hid == (HID_UP_BUTTON | 0x0010)) { - /* mark the device as pointer */ - hid_set_drvdata(hdev, (void *)1); - map_key_clear(KEY_MICMUTE); - return 1; - } - return 0; -} - -#undef map_key_clear - -static int tpkbd_features_set(struct hid_device *hdev) -{ - struct hid_report *report; - struct tpkbd_data_pointer *data_pointer = hid_get_drvdata(hdev); - - report = hdev->report_enum[HID_FEATURE_REPORT].report_id_hash[4]; - - report->field[0]->value[0] = data_pointer->press_to_select ? 0x01 : 0x02; - report->field[0]->value[0] |= data_pointer->dragging ? 0x04 : 0x08; - report->field[0]->value[0] |= data_pointer->release_to_select ? 0x10 : 0x20; - report->field[0]->value[0] |= data_pointer->select_right ? 0x80 : 0x40; - report->field[1]->value[0] = 0x03; // unknown setting, imitate windows driver - report->field[2]->value[0] = data_pointer->sensitivity; - report->field[3]->value[0] = data_pointer->press_speed; - - hid_hw_request(hdev, report, HID_REQ_SET_REPORT); - return 0; -} - -static ssize_t pointer_press_to_select_show(struct device *dev, - struct device_attribute *attr, - char *buf) -{ - struct hid_device *hdev = container_of(dev, struct hid_device, dev); - struct tpkbd_data_pointer *data_pointer = hid_get_drvdata(hdev); - - return snprintf(buf, PAGE_SIZE, "%u\n", data_pointer->press_to_select); -} - -static ssize_t pointer_press_to_select_store(struct device *dev, - struct device_attribute *attr, - const char *buf, - size_t count) -{ - struct hid_device *hdev = container_of(dev, struct hid_device, dev); - struct tpkbd_data_pointer *data_pointer = hid_get_drvdata(hdev); - int value; - - if (kstrtoint(buf, 10, &value)) - return -EINVAL; - if (value < 0 || value > 1) - return -EINVAL; - - data_pointer->press_to_select = value; - tpkbd_features_set(hdev); - - return count; -} - -static ssize_t pointer_dragging_show(struct device *dev, - struct device_attribute *attr, - char *buf) -{ - struct hid_device *hdev = container_of(dev, struct hid_device, dev); - struct tpkbd_data_pointer *data_pointer = hid_get_drvdata(hdev); - - return snprintf(buf, PAGE_SIZE, "%u\n", data_pointer->dragging); -} - -static ssize_t pointer_dragging_store(struct device *dev, - struct device_attribute *attr, - const char *buf, - size_t count) -{ - struct hid_device *hdev = container_of(dev, struct hid_device, dev); - struct tpkbd_data_pointer *data_pointer = hid_get_drvdata(hdev); - int value; - - if (kstrtoint(buf, 10, &value)) - return -EINVAL; - if (value < 0 || value > 1) - return -EINVAL; - - data_pointer->dragging = value; - tpkbd_features_set(hdev); - - return count; -} - -static ssize_t pointer_release_to_select_show(struct device *dev, - struct device_attribute *attr, - char *buf) -{ - struct hid_device *hdev = container_of(dev, struct hid_device, dev); - struct tpkbd_data_pointer *data_pointer = hid_get_drvdata(hdev); - - return snprintf(buf, PAGE_SIZE, "%u\n", data_pointer->release_to_select); -} - -static ssize_t pointer_release_to_select_store(struct device *dev, - struct device_attribute *attr, - const char *buf, - size_t count) -{ - struct hid_device *hdev = container_of(dev, struct hid_device, dev); - struct tpkbd_data_pointer *data_pointer = hid_get_drvdata(hdev); - int value; - - if (kstrtoint(buf, 10, &value)) - return -EINVAL; - if (value < 0 || value > 1) - return -EINVAL; - - data_pointer->release_to_select = value; - tpkbd_features_set(hdev); - - return count; -} - -static ssize_t pointer_select_right_show(struct device *dev, - struct device_attribute *attr, - char *buf) -{ - struct hid_device *hdev = container_of(dev, struct hid_device, dev); - struct tpkbd_data_pointer *data_pointer = hid_get_drvdata(hdev); - - return snprintf(buf, PAGE_SIZE, "%u\n", data_pointer->select_right); -} - -static ssize_t pointer_select_right_store(struct device *dev, - struct device_attribute *attr, - const char *buf, - size_t count) -{ - struct hid_device *hdev = container_of(dev, struct hid_device, dev); - struct tpkbd_data_pointer *data_pointer = hid_get_drvdata(hdev); - int value; - - if (kstrtoint(buf, 10, &value)) - return -EINVAL; - if (value < 0 || value > 1) - return -EINVAL; - - data_pointer->select_right = value; - tpkbd_features_set(hdev); - - return count; -} - -static ssize_t pointer_sensitivity_show(struct device *dev, - struct device_attribute *attr, - char *buf) -{ - struct hid_device *hdev = container_of(dev, struct hid_device, dev); - struct tpkbd_data_pointer *data_pointer = hid_get_drvdata(hdev); - - return snprintf(buf, PAGE_SIZE, "%u\n", - data_pointer->sensitivity); -} - -static ssize_t pointer_sensitivity_store(struct device *dev, - struct device_attribute *attr, - const char *buf, - size_t count) -{ - struct hid_device *hdev = container_of(dev, struct hid_device, dev); - struct tpkbd_data_pointer *data_pointer = hid_get_drvdata(hdev); - int value; - - if (kstrtoint(buf, 10, &value) || value < 1 || value > 255) - return -EINVAL; - - data_pointer->sensitivity = value; - tpkbd_features_set(hdev); - - return count; -} - -static ssize_t pointer_press_speed_show(struct device *dev, - struct device_attribute *attr, - char *buf) -{ - struct hid_device *hdev = container_of(dev, struct hid_device, dev); - struct tpkbd_data_pointer *data_pointer = hid_get_drvdata(hdev); - - return snprintf(buf, PAGE_SIZE, "%u\n", - data_pointer->press_speed); -} - -static ssize_t pointer_press_speed_store(struct device *dev, - struct device_attribute *attr, - const char *buf, - size_t count) -{ - struct hid_device *hdev = container_of(dev, struct hid_device, dev); - struct tpkbd_data_pointer *data_pointer = hid_get_drvdata(hdev); - int value; - - if (kstrtoint(buf, 10, &value) || value < 1 || value > 255) - return -EINVAL; - - data_pointer->press_speed = value; - tpkbd_features_set(hdev); - - return count; -} - -static struct device_attribute dev_attr_pointer_press_to_select = - __ATTR(press_to_select, S_IWUSR | S_IRUGO, - pointer_press_to_select_show, - pointer_press_to_select_store); - -static struct device_attribute dev_attr_pointer_dragging = - __ATTR(dragging, S_IWUSR | S_IRUGO, - pointer_dragging_show, - pointer_dragging_store); - -static struct device_attribute dev_attr_pointer_release_to_select = - __ATTR(release_to_select, S_IWUSR | S_IRUGO, - pointer_release_to_select_show, - pointer_release_to_select_store); - -static struct device_attribute dev_attr_pointer_select_right = - __ATTR(select_right, S_IWUSR | S_IRUGO, - pointer_select_right_show, - pointer_select_right_store); - -static struct device_attribute dev_attr_pointer_sensitivity = - __ATTR(sensitivity, S_IWUSR | S_IRUGO, - pointer_sensitivity_show, - pointer_sensitivity_store); - -static struct device_attribute dev_attr_pointer_press_speed = - __ATTR(press_speed, S_IWUSR | S_IRUGO, - pointer_press_speed_show, - pointer_press_speed_store); - -static struct attribute *tpkbd_attributes_pointer[] = { - &dev_attr_pointer_press_to_select.attr, - &dev_attr_pointer_dragging.attr, - &dev_attr_pointer_release_to_select.attr, - &dev_attr_pointer_select_right.attr, - &dev_attr_pointer_sensitivity.attr, - &dev_attr_pointer_press_speed.attr, - NULL -}; - -static const struct attribute_group tpkbd_attr_group_pointer = { - .attrs = tpkbd_attributes_pointer, -}; - -static enum led_brightness tpkbd_led_brightness_get( - struct led_classdev *led_cdev) -{ - struct device *dev = led_cdev->dev->parent; - struct hid_device *hdev = container_of(dev, struct hid_device, dev); - struct tpkbd_data_pointer *data_pointer = hid_get_drvdata(hdev); - int led_nr = 0; - - if (led_cdev == &data_pointer->led_micmute) - led_nr = 1; - - return data_pointer->led_state & (1 << led_nr) - ? LED_FULL - : LED_OFF; -} - -static void tpkbd_led_brightness_set(struct led_classdev *led_cdev, - enum led_brightness value) -{ - struct device *dev = led_cdev->dev->parent; - struct hid_device *hdev = container_of(dev, struct hid_device, dev); - struct tpkbd_data_pointer *data_pointer = hid_get_drvdata(hdev); - struct hid_report *report; - int led_nr = 0; - - if (led_cdev == &data_pointer->led_micmute) - led_nr = 1; - - if (value == LED_OFF) - data_pointer->led_state &= ~(1 << led_nr); - else - data_pointer->led_state |= 1 << led_nr; - - report = hdev->report_enum[HID_OUTPUT_REPORT].report_id_hash[3]; - report->field[0]->value[0] = (data_pointer->led_state >> 0) & 1; - report->field[0]->value[1] = (data_pointer->led_state >> 1) & 1; - hid_hw_request(hdev, report, HID_REQ_SET_REPORT); -} - -static int tpkbd_probe_tp(struct hid_device *hdev) -{ - struct device *dev = &hdev->dev; - struct tpkbd_data_pointer *data_pointer; - size_t name_sz = strlen(dev_name(dev)) + 16; - char *name_mute, *name_micmute; - int i; - - /* Validate required reports. */ - for (i = 0; i < 4; i++) { - if (!hid_validate_values(hdev, HID_FEATURE_REPORT, 4, i, 1)) - return -ENODEV; - } - if (!hid_validate_values(hdev, HID_OUTPUT_REPORT, 3, 0, 2)) - return -ENODEV; - - if (sysfs_create_group(&hdev->dev.kobj, - &tpkbd_attr_group_pointer)) { - hid_warn(hdev, "Could not create sysfs group\n"); - } - - data_pointer = devm_kzalloc(&hdev->dev, - sizeof(struct tpkbd_data_pointer), - GFP_KERNEL); - if (data_pointer == NULL) { - hid_err(hdev, "Could not allocate memory for driver data\n"); - return -ENOMEM; - } - - // set same default values as windows driver - data_pointer->sensitivity = 0xa0; - data_pointer->press_speed = 0x38; - - name_mute = devm_kzalloc(&hdev->dev, name_sz, GFP_KERNEL); - name_micmute = devm_kzalloc(&hdev->dev, name_sz, GFP_KERNEL); - if (name_mute == NULL || name_micmute == NULL) { - hid_err(hdev, "Could not allocate memory for led data\n"); - return -ENOMEM; - } - snprintf(name_mute, name_sz, "%s:amber:mute", dev_name(dev)); - snprintf(name_micmute, name_sz, "%s:amber:micmute", dev_name(dev)); - - hid_set_drvdata(hdev, data_pointer); - - data_pointer->led_mute.name = name_mute; - data_pointer->led_mute.brightness_get = tpkbd_led_brightness_get; - data_pointer->led_mute.brightness_set = tpkbd_led_brightness_set; - data_pointer->led_mute.dev = dev; - led_classdev_register(dev, &data_pointer->led_mute); - - data_pointer->led_micmute.name = name_micmute; - data_pointer->led_micmute.brightness_get = tpkbd_led_brightness_get; - data_pointer->led_micmute.brightness_set = tpkbd_led_brightness_set; - data_pointer->led_micmute.dev = dev; - led_classdev_register(dev, &data_pointer->led_micmute); - - tpkbd_features_set(hdev); - - return 0; -} - -static int tpkbd_probe(struct hid_device *hdev, - const struct hid_device_id *id) -{ - int ret; - - ret = hid_parse(hdev); - if (ret) { - hid_err(hdev, "hid_parse failed\n"); - goto err; - } - - ret = hid_hw_start(hdev, HID_CONNECT_DEFAULT); - if (ret) { - hid_err(hdev, "hid_hw_start failed\n"); - goto err; - } - - if (hid_get_drvdata(hdev)) { - hid_set_drvdata(hdev, NULL); - ret = tpkbd_probe_tp(hdev); - if (ret) - goto err_hid; - } - - return 0; -err_hid: - hid_hw_stop(hdev); -err: - return ret; -} - -static void tpkbd_remove_tp(struct hid_device *hdev) -{ - struct tpkbd_data_pointer *data_pointer = hid_get_drvdata(hdev); - - sysfs_remove_group(&hdev->dev.kobj, - &tpkbd_attr_group_pointer); - - led_classdev_unregister(&data_pointer->led_micmute); - led_classdev_unregister(&data_pointer->led_mute); - - hid_set_drvdata(hdev, NULL); -} - -static void tpkbd_remove(struct hid_device *hdev) -{ - if (hid_get_drvdata(hdev)) - tpkbd_remove_tp(hdev); - - hid_hw_stop(hdev); -} - -static const struct hid_device_id tpkbd_devices[] = { - { HID_USB_DEVICE(USB_VENDOR_ID_LENOVO, USB_DEVICE_ID_LENOVO_TPKBD) }, - { } -}; - -MODULE_DEVICE_TABLE(hid, tpkbd_devices); - -static struct hid_driver tpkbd_driver = { - .name = "lenovo_tpkbd", - .id_table = tpkbd_devices, - .input_mapping = tpkbd_input_mapping, - .probe = tpkbd_probe, - .remove = tpkbd_remove, -}; -module_hid_driver(tpkbd_driver); - -MODULE_LICENSE("GPL"); diff --git a/drivers/hid/hid-lenovo.c b/drivers/hid/hid-lenovo.c new file mode 100644 index 000000000000..bf227f7679af --- /dev/null +++ b/drivers/hid/hid-lenovo.c @@ -0,0 +1,708 @@ +/* + * HID driver for Lenovo: + * - ThinkPad USB Keyboard with TrackPoint (tpkbd) + * - ThinkPad Compact Bluetooth Keyboard with TrackPoint (cptkbd) + * - ThinkPad Compact USB Keyboard with TrackPoint (cptkbd) + * + * Copyright (c) 2012 Bernhard Seibold + * Copyright (c) 2014 Jamie Lentin <jm@lentin.co.uk> + */ + +/* + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the Free + * Software Foundation; either version 2 of the License, or (at your option) + * any later version. + */ + +#include <linux/module.h> +#include <linux/sysfs.h> +#include <linux/device.h> +#include <linux/hid.h> +#include <linux/input.h> +#include <linux/leds.h> + +#include "hid-ids.h" + +struct lenovo_drvdata_tpkbd { + int led_state; + struct led_classdev led_mute; + struct led_classdev led_micmute; + int press_to_select; + int dragging; + int release_to_select; + int select_right; + int sensitivity; + int press_speed; +}; + +struct lenovo_drvdata_cptkbd { + bool fn_lock; +}; + +#define map_key_clear(c) hid_map_usage_clear(hi, usage, bit, max, EV_KEY, (c)) + +static int lenovo_input_mapping_tpkbd(struct hid_device *hdev, + struct hid_input *hi, struct hid_field *field, + struct hid_usage *usage, unsigned long **bit, int *max) +{ + if (usage->hid == (HID_UP_BUTTON | 0x0010)) { + /* This sub-device contains trackpoint, mark it */ + hid_set_drvdata(hdev, (void *)1); + map_key_clear(KEY_MICMUTE); + return 1; + } + return 0; +} + +static int lenovo_input_mapping_cptkbd(struct hid_device *hdev, + struct hid_input *hi, struct hid_field *field, + struct hid_usage *usage, unsigned long **bit, int *max) +{ + /* HID_UP_LNVENDOR = USB, HID_UP_MSVENDOR = BT */ + if ((usage->hid & HID_USAGE_PAGE) == HID_UP_MSVENDOR || + (usage->hid & HID_USAGE_PAGE) == HID_UP_LNVENDOR) { + set_bit(EV_REP, hi->input->evbit); + switch (usage->hid & HID_USAGE) { + case 0x00f1: /* Fn-F4: Mic mute */ + map_key_clear(KEY_MICMUTE); + return 1; + case 0x00f2: /* Fn-F5: Brightness down */ + map_key_clear(KEY_BRIGHTNESSDOWN); + return 1; + case 0x00f3: /* Fn-F6: Brightness up */ + map_key_clear(KEY_BRIGHTNESSUP); + return 1; + case 0x00f4: /* Fn-F7: External display (projector) */ + map_key_clear(KEY_SWITCHVIDEOMODE); + return 1; + case 0x00f5: /* Fn-F8: Wireless */ + map_key_clear(KEY_WLAN); + return 1; + case 0x00f6: /* Fn-F9: Control panel */ + map_key_clear(KEY_CONFIG); + return 1; + case 0x00f8: /* Fn-F11: View open applications (3 boxes) */ + map_key_clear(KEY_SCALE); + return 1; + case 0x00fa: /* Fn-Esc: Fn-lock toggle */ + map_key_clear(KEY_FN_ESC); + return 1; + case 0x00fb: /* Fn-F12: Open My computer (6 boxes) USB-only */ + /* NB: This mapping is invented in raw_event below */ + map_key_clear(KEY_FILE); + return 1; + } + } + + return 0; +} + +static int lenovo_input_mapping(struct hid_device *hdev, + struct hid_input *hi, struct hid_field *field, + struct hid_usage *usage, unsigned long **bit, int *max) +{ + switch (hdev->product) { + case USB_DEVICE_ID_LENOVO_TPKBD: + return lenovo_input_mapping_tpkbd(hdev, hi, field, + usage, bit, max); + case USB_DEVICE_ID_LENOVO_CUSBKBD: + case USB_DEVICE_ID_LENOVO_CBTKBD: + return lenovo_input_mapping_cptkbd(hdev, hi, field, + usage, bit, max); + default: + return 0; + } +} + +#undef map_key_clear + +/* Send a config command to the keyboard */ +static int lenovo_send_cmd_cptkbd(struct hid_device *hdev, + unsigned char byte2, unsigned char byte3) +{ + int ret; + unsigned char buf[] = {0x18, byte2, byte3}; + + switch (hdev->product) { + case USB_DEVICE_ID_LENOVO_CUSBKBD: + ret = hid_hw_raw_request(hdev, 0x13, buf, sizeof(buf), + HID_FEATURE_REPORT, HID_REQ_SET_REPORT); + break; + case USB_DEVICE_ID_LENOVO_CBTKBD: + ret = hid_hw_output_report(hdev, buf, sizeof(buf)); + break; + default: + ret = -EINVAL; + break; + } + + return ret < 0 ? ret : 0; /* BT returns 0, USB returns sizeof(buf) */ +} + +static void lenovo_features_set_cptkbd(struct hid_device *hdev) +{ + int ret; + struct lenovo_drvdata_cptkbd *cptkbd_data = hid_get_drvdata(hdev); + + ret = lenovo_send_cmd_cptkbd(hdev, 0x05, cptkbd_data->fn_lock); + if (ret) + hid_err(hdev, "Fn-lock setting failed: %d\n", ret); +} + +static ssize_t attr_fn_lock_show_cptkbd(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct hid_device *hdev = container_of(dev, struct hid_device, dev); + struct lenovo_drvdata_cptkbd *cptkbd_data = hid_get_drvdata(hdev); + + return snprintf(buf, PAGE_SIZE, "%u\n", cptkbd_data->fn_lock); +} + +static ssize_t attr_fn_lock_store_cptkbd(struct device *dev, + struct device_attribute *attr, + const char *buf, + size_t count) +{ + struct hid_device *hdev = container_of(dev, struct hid_device, dev); + struct lenovo_drvdata_cptkbd *cptkbd_data = hid_get_drvdata(hdev); + int value; + + if (kstrtoint(buf, 10, &value)) + return -EINVAL; + if (value < 0 || value > 1) + return -EINVAL; + + cptkbd_data->fn_lock = !!value; + lenovo_features_set_cptkbd(hdev); + + return count; +} + +static struct device_attribute dev_attr_fn_lock_cptkbd = + __ATTR(fn_lock, S_IWUSR | S_IRUGO, + attr_fn_lock_show_cptkbd, + attr_fn_lock_store_cptkbd); + +static struct attribute *lenovo_attributes_cptkbd[] = { + &dev_attr_fn_lock_cptkbd.attr, + NULL +}; + +static const struct attribute_group lenovo_attr_group_cptkbd = { + .attrs = lenovo_attributes_cptkbd, +}; + +static int lenovo_raw_event(struct hid_device *hdev, + struct hid_report *report, u8 *data, int size) +{ + /* + * Compact USB keyboard's Fn-F12 report holds down many other keys, and + * its own key is outside the usage page range. Remove extra + * keypresses and remap to inside usage page. + */ + if (unlikely(hdev->product == USB_DEVICE_ID_LENOVO_CUSBKBD + && size == 3 + && data[0] == 0x15 + && data[1] == 0x94 + && data[2] == 0x01)) { + data[1] = 0x0; + data[2] = 0x4; + } + + return 0; +} + +static int lenovo_features_set_tpkbd(struct hid_device *hdev) +{ + struct hid_report *report; + struct lenovo_drvdata_tpkbd *data_pointer = hid_get_drvdata(hdev); + + report = hdev->report_enum[HID_FEATURE_REPORT].report_id_hash[4]; + + report->field[0]->value[0] = data_pointer->press_to_select ? 0x01 : 0x02; + report->field[0]->value[0] |= data_pointer->dragging ? 0x04 : 0x08; |