diff options
author | Jamie Lentin <jm@lentin.co.uk> | 2014-07-23 23:30:48 +0100 |
---|---|---|
committer | Jiri Kosina <jkosina@suse.cz> | 2014-07-29 11:24:47 +0200 |
commit | f3d4ff0e04cc4450bdc7a4140020913b1280d205 (patch) | |
tree | f4298002a0fd85a9ec39f595e1d1e8bea6e87894 /drivers/hid/hid-lenovo.c | |
parent | e0a6aad6014be2d4fbb24dfb8e046308da1df8c8 (diff) | |
download | linux-f3d4ff0e04cc4450bdc7a4140020913b1280d205.tar.gz linux-f3d4ff0e04cc4450bdc7a4140020913b1280d205.tar.bz2 linux-f3d4ff0e04cc4450bdc7a4140020913b1280d205.zip |
HID: lenovo: Add support for Compact (BT|USB) keyboard
Add support for both ThinkPad Compact Bluetooth Keyboard with
TrackPoint and ThinkPad Compact USB Keyboard with TrackPoint.
Signed-off-by: Jamie Lentin <jm@lentin.co.uk>
Reviewed-by: Antonio Ospite <ao2@ao2.it>
Signed-off-by: Jiri Kosina <jkosina@suse.cz>
Diffstat (limited to 'drivers/hid/hid-lenovo.c')
-rw-r--r-- | drivers/hid/hid-lenovo.c | 208 |
1 files changed, 208 insertions, 0 deletions
diff --git a/drivers/hid/hid-lenovo.c b/drivers/hid/hid-lenovo.c index f96bf095670a..bf227f7679af 100644 --- a/drivers/hid/hid-lenovo.c +++ b/drivers/hid/hid-lenovo.c @@ -1,8 +1,11 @@ /* * 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> */ /* @@ -33,6 +36,10 @@ struct lenovo_drvdata_tpkbd { 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, @@ -48,6 +55,49 @@ static int lenovo_input_mapping_tpkbd(struct hid_device *hdev, 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) @@ -56,6 +106,10 @@ static int lenovo_input_mapping(struct hid_device *hdev, 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; } @@ -63,6 +117,103 @@ static int lenovo_input_mapping(struct hid_device *hdev, #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; @@ -415,6 +566,46 @@ static int lenovo_probe_tpkbd(struct hid_device *hdev) return 0; } +static int lenovo_probe_cptkbd(struct hid_device *hdev) +{ + int ret; + struct lenovo_drvdata_cptkbd *cptkbd_data; + + /* All the custom action happens on the USBMOUSE device for USB */ + if (hdev->product == USB_DEVICE_ID_LENOVO_CUSBKBD + && hdev->type != HID_TYPE_USBMOUSE) { + hid_dbg(hdev, "Ignoring keyboard half of device\n"); + return 0; + } + + cptkbd_data = devm_kzalloc(&hdev->dev, + sizeof(*cptkbd_data), + GFP_KERNEL); + if (cptkbd_data == NULL) { + hid_err(hdev, "can't alloc keyboard descriptor\n"); + return -ENOMEM; + } + hid_set_drvdata(hdev, cptkbd_data); + + /* + * Tell the keyboard a driver understands it, and turn F7, F9, F11 into + * regular keys + */ + ret = lenovo_send_cmd_cptkbd(hdev, 0x01, 0x03); + if (ret) + hid_warn(hdev, "Failed to switch F7/9/11 mode: %d\n", ret); + + /* Turn Fn-Lock on by default */ + cptkbd_data->fn_lock = true; + lenovo_features_set_cptkbd(hdev); + + ret = sysfs_create_group(&hdev->dev.kobj, &lenovo_attr_group_cptkbd); + if (ret) + hid_warn(hdev, "Could not create sysfs group: %d\n", ret); + + return 0; +} + static int lenovo_probe(struct hid_device *hdev, const struct hid_device_id *id) { @@ -436,6 +627,10 @@ static int lenovo_probe(struct hid_device *hdev, case USB_DEVICE_ID_LENOVO_TPKBD: ret = lenovo_probe_tpkbd(hdev); break; + case USB_DEVICE_ID_LENOVO_CUSBKBD: + case USB_DEVICE_ID_LENOVO_CBTKBD: + ret = lenovo_probe_cptkbd(hdev); + break; default: ret = 0; break; @@ -470,12 +665,22 @@ static void lenovo_remove_tpkbd(struct hid_device *hdev) hid_set_drvdata(hdev, NULL); } +static void lenovo_remove_cptkbd(struct hid_device *hdev) +{ + sysfs_remove_group(&hdev->dev.kobj, + &lenovo_attr_group_cptkbd); +} + static void lenovo_remove(struct hid_device *hdev) { switch (hdev->product) { case USB_DEVICE_ID_LENOVO_TPKBD: lenovo_remove_tpkbd(hdev); break; + case USB_DEVICE_ID_LENOVO_CUSBKBD: + case USB_DEVICE_ID_LENOVO_CBTKBD: + lenovo_remove_cptkbd(hdev); + break; } hid_hw_stop(hdev); @@ -483,6 +688,8 @@ static void lenovo_remove(struct hid_device *hdev) static const struct hid_device_id lenovo_devices[] = { { 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) }, { } }; @@ -494,6 +701,7 @@ static struct hid_driver lenovo_driver = { .input_mapping = lenovo_input_mapping, .probe = lenovo_probe, .remove = lenovo_remove, + .raw_event = lenovo_raw_event, }; module_hid_driver(lenovo_driver); |