// SPDX-License-Identifier: GPL-2.0-or-later
/*
* HID driver for Lenovo :
* - ThinkPad USB Keyboard with TrackPoint ( tpkbd )
* - ThinkPad Compact Bluetooth Keyboard with TrackPoint ( cptkbd )
* - ThinkPad Compact USB Keyboard with TrackPoint ( cptkbd )
* - ThinkPad TrackPoint Keyboard II USB / Bluetooth ( cptkbd / tpIIkbd )
*
* Copyright ( c ) 2012 Bernhard Seibold
* Copyright ( c ) 2014 Jamie Lentin < jm @ lentin . co . uk >
*
* Linux IBM / Lenovo Scrollpoint mouse driver :
* - IBM Scrollpoint III
* - IBM Scrollpoint Pro
* - IBM Scrollpoint Optical
* - IBM Scrollpoint Optical 800 dpi
* - IBM Scrollpoint Optical 800 dpi Pro
* - Lenovo Scrollpoint Optical
*
* Copyright ( c ) 2012 Peter De Wachter < pdewacht @ gmail . com >
* Copyright ( c ) 2018 Peter Ganzhorn < peter . ganzhorn @ gmail . com >
*/
/*
*/
#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 <linux/workqueue.h>
#include "hid-ids.h"
/* Userspace expects F20 for mic-mute KEY_MICMUTE does not work */
#define LENOVO_KEY_MICMUTE KEY_F20
/* HID raw events for ThinkPad X12 Tabs*/
#define TP_X12_RAW_HOTKEY_FN_F4 0 x00020003
#define TP_X12_RAW_HOTKEY_FN_F8 0 x38001003
#define TP_X12_RAW_HOTKEY_FN_F10 0 x00000803
#define TP_X12_RAW_HOTKEY_FN_F12 0 x00000403
#define TP_X12_RAW_HOTKEY_FN_SPACE 0 x18001003
struct lenovo_drvdata {
u8 led_report[3 ]; /* Must be first for proper alignment */
int led_state;
struct mutex led_report_mutex;
struct led_classdev led_mute;
struct led_classdev led_micmute;
struct work_struct fn_lock_sync_work;
struct hid_device *hdev;
int press_to_select;
int dragging;
int release_to_select;
int select_right;
int sensitivity;
int press_speed;
/* 0: Up
* 1 : Down ( undecided )
* 2 : Scrolling
*/
u8 middlebutton_state;
bool fn_lock;
bool middleclick_workaround_cptkbd;
};
#define map_key_clear(c) hid_map_usage_clear(hi, usage, bit, max, EV_KEY, (c))
#define TP10UBKBD_LED_OUTPUT_REPORT 9
#define TP10UBKBD_FN_LOCK_LED 0 x54
#define TP10UBKBD_MUTE_LED 0 x64
#define TP10UBKBD_MICMUTE_LED 0 x74
#define TP10UBKBD_LED_OFF 1
#define TP10UBKBD_LED_ON 2
/* Function to report raw_events as key events*/
static inline void report_key_event(struct input_dev *input, int keycode)
{
input_report_key(input, keycode, 1 );
input_report_key(input, keycode, 0 );
input_sync(input);
}
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;
mutex_lock(&data->led_report_mutex);
data->led_report[0 ] = TP10UBKBD_LED_OUTPUT_REPORT;
data->led_report[1 ] = 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 != 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)
{
struct lenovo_drvdata *data =
container_of(work, struct lenovo_drvdata, fn_lock_sync_work);
lenovo_led_set_tp10ubkbd(data->hdev, TP10UBKBD_FN_LOCK_LED,
data->fn_lock);
}
static const __u8 lenovo_pro_dock_need_fixup_collection[] = {
0 x05, 0 x88, /* Usage Page (Vendor Usage Page 0x88) */
0 x09, 0 x01, /* Usage (Vendor Usage 0x01) */
0 xa1, 0 x01, /* Collection (Application) */
0 x85, 0 x04, /* Report ID (4) */
0 x19, 0 x00, /* Usage Minimum (0) */
0 x2a, 0 xff, 0 xff, /* Usage Maximum (65535) */
};
/* Broken ThinkPad TrackPoint II collection (Bluetooth mode) */
static const __u8 lenovo_tpIIbtkbd_need_fixup_collection[] = {
0 x06, 0 x00, 0 xFF, /* Usage Page (Vendor Defined 0xFF00) */
0 x09, 0 x01, /* Usage (0x01) */
0 xA1, 0 x01, /* Collection (Application) */
0 x85, 0 x05, /* Report ID (5) */
0 x1A, 0 xF1, 0 x00, /* Usage Minimum (0xF1) */
0 x2A, 0 xFC, 0 x00, /* Usage Maximum (0xFC) */
0 x15, 0 x00, /* Logical Minimum (0) */
0 x25, 0 x01, /* Logical Maximum (1) */
0 x75, 0 x01, /* Report Size (1) */
0 x95, 0 x0D, /* Report Count (13) */
0 x81, 0 x02, /* Input (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position) */
0 x95, 0 x03, /* Report Count (3) */
0 x81, 0 x01, /* Input (Const,Array,Abs,No Wrap,Linear,Preferred State,No Null Position) */
};
static const __u8 *lenovo_report_fixup(struct hid_device *hdev, __u8 *rdesc,
unsigned int *rsize)
{
switch (hdev->product) {
case USB_DEVICE_ID_LENOVO_TPPRODOCK:
/* the fixups that need to be done:
* - get a reasonable usage max for the vendor collection
* 0 x8801 from the report ID 4
*/
if (*rsize >= 153 &&
memcmp(&rdesc[140 ], lenovo_pro_dock_need_fixup_collection,
sizeof (lenovo_pro_dock_need_fixup_collection)) == 0 ) {
rdesc[151 ] = 0 x01;
rdesc[152 ] = 0 x00;
}
break ;
case USB_DEVICE_ID_LENOVO_TPIIBTKBD:
if (*rsize >= 263 &&
memcmp(&rdesc[234 ], lenovo_tpIIbtkbd_need_fixup_collection,
sizeof (lenovo_tpIIbtkbd_need_fixup_collection)) == 0 ) {
rdesc[244 ] = 0 x00; /* usage minimum = 0x00 */
rdesc[247 ] = 0 xff; /* usage maximum = 0xff */
rdesc[252 ] = 0 xff; /* logical maximum = 0xff */
rdesc[254 ] = 0 x08; /* report size = 0x08 */
rdesc[256 ] = 0 x01; /* report count = 0x01 */
rdesc[258 ] = 0 x00; /* input = 0x00 */
rdesc[260 ] = 0 x01; /* report count (2) = 0x01 */
}
break ;
}
return rdesc;
}
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 | 0 x0010)) {
/* This sub-device contains trackpoint, mark it */
hid_set_drvdata(hdev, (void *)1 );
map_key_clear(LENOVO_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) {
switch (usage->hid & HID_USAGE) {
case 0 x00f1: /* Fn-F4: Mic mute */
map_key_clear(LENOVO_KEY_MICMUTE);
return 1 ;
case 0 x00f2: /* Fn-F5: Brightness down */
map_key_clear(KEY_BRIGHTNESSDOWN);
return 1 ;
case 0 x00f3: /* Fn-F6: Brightness up */
map_key_clear(KEY_BRIGHTNESSUP);
return 1 ;
case 0 x00f4: /* Fn-F7: External display (projector) */
map_key_clear(KEY_SWITCHVIDEOMODE);
return 1 ;
case 0 x00f5: /* Fn-F8: Wireless */
map_key_clear(KEY_WLAN);
return 1 ;
case 0 x00f6: /* Fn-F9: Control panel */
map_key_clear(KEY_CONFIG);
return 1 ;
case 0 x00f8: /* Fn-F11: View open applications (3 boxes) */
map_key_clear(KEY_SCALE);
return 1 ;
case 0 x00f9: /* 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 ;
case 0 x00fa: /* Fn-Esc: Fn-lock toggle */
map_key_clear(KEY_FN_ESC);
return 1 ;
case 0 x00fb: /* Middle mouse button (in native mode) */
map_key_clear(BTN_MIDDLE);
return 1 ;
}
}
/* Compatibility middle/wheel mappings should be ignored */
if (usage->hid == HID_GD_WHEEL)
return -1 ;
if ((usage->hid & HID_USAGE_PAGE) == HID_UP_BUTTON &&
(usage->hid & HID_USAGE) == 0 x003)
return -1 ;
if ((usage->hid & HID_USAGE_PAGE) == HID_UP_CONSUMER &&
(usage->hid & HID_USAGE) == 0 x238)
return -1 ;
/* Map wheel emulation reports: 0xffa1 = USB, 0xff10 = BT */
if ((usage->hid & HID_USAGE_PAGE) == 0 xff100000 ||
(usage->hid & HID_USAGE_PAGE) == 0 xffa10000) {
field->flags |= HID_MAIN_ITEM_RELATIVE | HID_MAIN_ITEM_VARIABLE;
field->logical_minimum = -127 ;
field->logical_maximum = 127 ;
switch (usage->hid & HID_USAGE) {
case 0 x0000:
hid_map_usage(hi, usage, bit, max, EV_REL, REL_HWHEEL);
return 1 ;
case 0 x0001:
hid_map_usage(hi, usage, bit, max, EV_REL, REL_WHEEL);
return 1 ;
default :
return -1 ;
}
}
return 0 ;
}
static int lenovo_input_mapping_tpIIkbd(struct hid_device *hdev,
struct hid_input *hi, struct hid_field *field,
struct hid_usage *usage, unsigned long **bit, int *max)
{
/*
* 0 xff0a0000 = USB , HID_UP_MSVENDOR = BT .
*
* In BT mode , there are two HID_UP_MSVENDOR pages .
* Use only the page that contains report ID = = 5 .
*/
if (((usage->hid & HID_USAGE_PAGE) == 0 xff0a0000 ||
(usage->hid & HID_USAGE_PAGE) == HID_UP_MSVENDOR) &&
field->report->id == 5 ) {
switch (usage->hid & HID_USAGE) {
case 0 x00bb: /* Fn-F4: Mic mute */
map_key_clear(LENOVO_KEY_MICMUTE);
return 1 ;
case 0 x00c3: /* Fn-F5: Brightness down */
map_key_clear(KEY_BRIGHTNESSDOWN);
return 1 ;
case 0 x00c4: /* Fn-F6: Brightness up */
map_key_clear(KEY_BRIGHTNESSUP);
return 1 ;
case 0 x00c1: /* Fn-F8: Notification center */
map_key_clear(KEY_NOTIFICATION_CENTER);
return 1 ;
case 0 x00bc: /* Fn-F9: Control panel */
map_key_clear(KEY_CONFIG);
return 1 ;
case 0 x00b6: /* Fn-F10: Bluetooth */
map_key_clear(KEY_BLUETOOTH);
return 1 ;
case 0 x00b7: /* Fn-F11: Keyboard config */
map_key_clear(KEY_KEYBOARD);
return 1 ;
case 0 x00b8: /* Fn-F12: User function */
map_key_clear(KEY_PROG1);
return 1 ;
case 0 x00b9: /* Fn-PrtSc: Snipping tool */
map_key_clear(KEY_SELECTIVE_SCREENSHOT);
return 1 ;
case 0 x00b5: /* Fn-Esc: Fn-lock toggle */
map_key_clear(KEY_FN_ESC);
return 1 ;
}
}
if ((usage->hid & HID_USAGE_PAGE) == 0 xffa00000) {
switch (usage->hid & HID_USAGE) {
case 0 x00fb: /* Middle mouse (in native USB mode) */
map_key_clear(BTN_MIDDLE);
return 1 ;
}
}
if ((usage->hid & HID_USAGE_PAGE) == HID_UP_MSVENDOR &&
field->report->id == 21 ) {
switch (usage->hid & HID_USAGE) {
case 0 x0004: /* Middle mouse (in native Bluetooth mode) */
map_key_clear(BTN_MIDDLE);
return 1 ;
}
}
/* Compatibility middle/wheel mappings should be ignored */
if (usage->hid == HID_GD_WHEEL)
return -1 ;
if ((usage->hid & HID_USAGE_PAGE) == HID_UP_BUTTON &&
(usage->hid & HID_USAGE) == 0 x003)
return -1 ;
if ((usage->hid & HID_USAGE_PAGE) == HID_UP_CONSUMER &&
(usage->hid & HID_USAGE) == 0 x238)
return -1 ;
/* Map wheel emulation reports: 0xff10 */
if ((usage->hid & HID_USAGE_PAGE) == 0 xff100000) {
field->flags |= HID_MAIN_ITEM_RELATIVE | HID_MAIN_ITEM_VARIABLE;
field->logical_minimum = -127 ;
field->logical_maximum = 127 ;
switch (usage->hid & HID_USAGE) {
case 0 x0000:
hid_map_usage(hi, usage, bit, max, EV_REL, REL_HWHEEL);
return 1 ;
case 0 x0001:
hid_map_usage(hi, usage, bit, max, EV_REL, REL_WHEEL);
return 1 ;
default :
return -1 ;
}
}
return 0 ;
}
static int lenovo_input_mapping_scrollpoint(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_GD_Z) {
hid_map_usage(hi, usage, bit, max, EV_REL, REL_HWHEEL);
return 1 ;
}
return 0 ;
}
static int lenovo_input_mapping_tp10_ultrabook_kbd(struct hid_device *hdev,
struct hid_input *hi, struct hid_field *field,
struct hid_usage *usage, unsigned long **bit, int *max)
{
/*
* The ThinkPad 10 Ultrabook Keyboard uses 0 x000c0001 usage for
* a bunch of keys which have no standard consumer page code .
*/
if (usage->hid == 0 x000c0001) {
switch (usage->usage_index) {
case 8 : /* Fn-Esc: Fn-lock toggle */
map_key_clear(KEY_FN_ESC);
return 1 ;
case 9 : /* Fn-F4: Mic mute */
map_key_clear(LENOVO_KEY_MICMUTE);
return 1 ;
case 10 : /* Fn-F7: Control panel */
map_key_clear(KEY_CONFIG);
return 1 ;
case 11 : /* Fn-F8: Search (magnifier glass) */
map_key_clear(KEY_SEARCH);
return 1 ;
case 12 : /* Fn-F10: Open My computer (6 boxes) */
map_key_clear(KEY_FILE);
return 1 ;
}
}
/*
* The Ultrabook Keyboard sends a spurious F23 key - press when resuming
* from suspend and it does not actually have a F23 key , ignore it .
*/
if (usage->hid == 0 x00070072)
return -1 ;
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 0 x000c0001 usage for
* a bunch of keys which have no standard consumer page code .
*/
if (usage->hid == 0 x000c0001) {
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 | 0 x009a)) {
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)
{
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);
case USB_DEVICE_ID_LENOVO_TPIIUSBKBD:
case USB_DEVICE_ID_LENOVO_TPIIBTKBD:
return lenovo_input_mapping_tpIIkbd(hdev, hi, field,
usage, bit, max);
case USB_DEVICE_ID_IBM_SCROLLPOINT_III:
case USB_DEVICE_ID_IBM_SCROLLPOINT_PRO:
case USB_DEVICE_ID_IBM_SCROLLPOINT_OPTICAL:
case USB_DEVICE_ID_IBM_SCROLLPOINT_800DPI_OPTICAL:
case USB_DEVICE_ID_IBM_SCROLLPOINT_800DPI_OPTICAL_PRO:
case USB_DEVICE_ID_LENOVO_SCROLLPOINT_OPTICAL:
return lenovo_input_mapping_scrollpoint(hdev, hi, field,
usage, bit, max);
case USB_DEVICE_ID_LENOVO_TP10UBKBD:
return lenovo_input_mapping_tp10_ultrabook_kbd(hdev, hi, field,
usage, bit, max);
case USB_DEVICE_ID_LENOVO_X12_TAB:
case USB_DEVICE_ID_LENOVO_X12_TAB2:
case USB_DEVICE_ID_LENOVO_X1_TAB:
case USB_DEVICE_ID_LENOVO_X1_TAB2:
case USB_DEVICE_ID_LENOVO_X1_TAB3:
return lenovo_input_mapping_x1_tab_kbd(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;
buf = kzalloc(3 , GFP_KERNEL);
if (!buf)
return -ENOMEM;
/*
* Feature report 0 x13 is used for USB ,
* output report 0 x18 is used for Bluetooth .
* buf [ 0 ] is ignored by hid_hw_raw_request .
*/
buf[0 ] = 0 x18;
buf[1 ] = byte2;
buf[2 ] = byte3;
switch (hdev->product) {
case USB_DEVICE_ID_LENOVO_CUSBKBD:
case USB_DEVICE_ID_LENOVO_TPIIUSBKBD:
ret = hid_hw_raw_request(hdev, 0 x13, buf, 3 ,
HID_FEATURE_REPORT, HID_REQ_SET_REPORT);
break ;
case USB_DEVICE_ID_LENOVO_CBTKBD:
case USB_DEVICE_ID_LENOVO_TPIIBTKBD:
ret = hid_hw_output_report(hdev, buf, 3 );
break ;
default :
ret = -EINVAL;
break ;
}
kfree(buf);
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_data = hid_get_drvdata(hdev);
/*
* Tell the keyboard a driver understands it , and turn F7 , F9 , F11 into
* regular keys ( Compact only )
*/
if (hdev->product == USB_DEVICE_ID_LENOVO_CUSBKBD ||
hdev->product == USB_DEVICE_ID_LENOVO_CBTKBD) {
ret = lenovo_send_cmd_cptkbd(hdev, 0 x01, 0 x03);
if (ret)
hid_warn(hdev, "Failed to switch F7/9/11 mode: %d\n" , ret);
}
/* Switch middle button to native mode */
ret = lenovo_send_cmd_cptkbd(hdev, 0 x09, 0 x01);
if (ret)
hid_warn(hdev, "Failed to switch middle button: %d\n" , ret);
ret = lenovo_send_cmd_cptkbd(hdev, 0 x05, cptkbd_data->fn_lock);
if (ret)
hid_err(hdev, "Fn-lock setting failed: %d\n" , ret);
ret = lenovo_send_cmd_cptkbd(hdev, 0 x02, cptkbd_data->sensitivity);
if (ret)
hid_err(hdev, "Sensitivity setting failed: %d\n" , ret);
}
static ssize_t attr_fn_lock_show(struct device *dev,
struct device_attribute *attr,
char *buf)
{
struct hid_device *hdev = to_hid_device(dev);
struct lenovo_drvdata *data = hid_get_drvdata(hdev);
return sysfs_emit(buf, "%u\n" , data->fn_lock);
}
static ssize_t attr_fn_lock_store(struct device *dev,
struct device_attribute *attr,
const char *buf,
size_t count)
{
struct hid_device *hdev = to_hid_device(dev);
struct lenovo_drvdata *data = hid_get_drvdata(hdev);
int value, ret;
if (kstrtoint(buf, 10 , &value))
return -EINVAL;
if (value < 0 || value > 1 )
return -EINVAL;
data->fn_lock = !!value;
switch (hdev->product) {
case USB_DEVICE_ID_LENOVO_CUSBKBD:
case USB_DEVICE_ID_LENOVO_CBTKBD:
case USB_DEVICE_ID_LENOVO_TPIIUSBKBD:
case USB_DEVICE_ID_LENOVO_TPIIBTKBD:
lenovo_features_set_cptkbd(hdev);
break ;
case USB_DEVICE_ID_LENOVO_X12_TAB:
case USB_DEVICE_ID_LENOVO_X12_TAB2:
case USB_DEVICE_ID_LENOVO_TP10UBKBD:
case USB_DEVICE_ID_LENOVO_X1_TAB:
case USB_DEVICE_ID_LENOVO_X1_TAB2:
case USB_DEVICE_ID_LENOVO_X1_TAB3:
ret = lenovo_led_set_tp10ubkbd(hdev, TP10UBKBD_FN_LOCK_LED, value);
if (ret)
return ret;
break ;
}
return count;
}
static ssize_t attr_sensitivity_show_cptkbd(struct device *dev,
struct device_attribute *attr,
char *buf)
{
struct hid_device *hdev = to_hid_device(dev);
struct lenovo_drvdata *cptkbd_data = hid_get_drvdata(hdev);
return sysfs_emit(buf, "%u\n" , cptkbd_data->sensitivity);
}
static ssize_t attr_sensitivity_store_cptkbd(struct device *dev,
struct device_attribute *attr,
const char *buf,
size_t count)
{
struct hid_device *hdev = to_hid_device(dev);
struct lenovo_drvdata *cptkbd_data = hid_get_drvdata(hdev);
int value;
if (kstrtoint(buf, 10 , &value) || value < 1 || value > 255 )
return -EINVAL;
cptkbd_data->sensitivity = value;
lenovo_features_set_cptkbd(hdev);
return count;
}
static ssize_t attr_middleclick_workaround_show_cptkbd(struct device *dev,
struct device_attribute *attr,
char *buf)
{
struct hid_device *hdev = to_hid_device(dev);
struct lenovo_drvdata *cptkbd_data = hid_get_drvdata(hdev);
return sysfs_emit(buf, "%u\n" ,
cptkbd_data->middleclick_workaround_cptkbd);
}
static ssize_t attr_middleclick_workaround_store_cptkbd(struct device *dev,
struct device_attribute *attr,
const char *buf,
size_t count)
{
struct hid_device *hdev = to_hid_device(dev);
struct lenovo_drvdata *cptkbd_data = hid_get_drvdata(hdev);
int value;
if (kstrtoint(buf, 10 , &value))
return -EINVAL;
if (value < 0 || value > 1 )
return -EINVAL;
cptkbd_data->middleclick_workaround_cptkbd = !!value;
return count;
}
static struct device_attribute dev_attr_fn_lock =
__ATTR(fn_lock, S_IWUSR | S_IRUGO,
attr_fn_lock_show,
attr_fn_lock_store);
static struct device_attribute dev_attr_sensitivity_cptkbd =
__ATTR(sensitivity, S_IWUSR | S_IRUGO,
attr_sensitivity_show_cptkbd,
attr_sensitivity_store_cptkbd);
static struct device_attribute dev_attr_middleclick_workaround_cptkbd =
__ATTR(middleclick_workaround, S_IWUSR | S_IRUGO,
attr_middleclick_workaround_show_cptkbd,
attr_middleclick_workaround_store_cptkbd);
static struct attribute *lenovo_attributes_cptkbd[] = {
&dev_attr_fn_lock.attr,
&dev_attr_sensitivity_cptkbd.attr,
&dev_attr_middleclick_workaround_cptkbd.attr,
NULL
};
static const struct attribute_group lenovo_attr_group_cptkbd = {
.attrs = lenovo_attributes_cptkbd,
};
/* Function to handle Lenovo Thinkpad TAB X12's HID raw inputs for fn keys*/
static int lenovo_raw_event_TP_X12_tab(struct hid_device *hdev, u32 raw_data)
{
struct hid_input *hidinput;
struct input_dev *input = NULL;
/* Iterate through all associated input devices */
list_for_each_entry(hidinput, &hdev->inputs, list) {
input = hidinput->input;
if (!input)
continue ;
switch (raw_data) {
/* fn-F20 being used here for MIC mute*/
case TP_X12_RAW_HOTKEY_FN_F4:
report_key_event(input, LENOVO_KEY_MICMUTE);
return 1 ;
/* Power-mode or Airplane mode will be called based on the device*/
case TP_X12_RAW_HOTKEY_FN_F8:
/*
* TP X12 TAB uses Fn - F8 calls Airplanemode
* Whereas TP X12 TAB2 uses Fn - F8 for toggling
* Power modes
*/
if (hdev->product == USB_DEVICE_ID_LENOVO_X12_TAB) {
report_key_event(input, KEY_RFKILL);
return 1 ;
}
report_key_event(input, KEY_PERFORMANCE);
return 1 ;
case TP_X12_RAW_HOTKEY_FN_F10:
/* TAB1 has PICKUP Phone and TAB2 use Snipping tool*/
(hdev->product == USB_DEVICE_ID_LENOVO_X12_TAB) ?
report_key_event(input, KEY_PICKUP_PHONE) :
report_key_event(input, KEY_SELECTIVE_SCREENSHOT);
return 1 ;
case TP_X12_RAW_HOTKEY_FN_F12:
/* BookMarks/STAR key*/
report_key_event(input, KEY_BOOKMARKS);
return 1 ;
case TP_X12_RAW_HOTKEY_FN_SPACE:
/* Keyboard LED backlight toggle*/
report_key_event(input, KEY_KBDILLUMTOGGLE);
return 1 ;
default :
break ;
}
}
return 0 ;
}
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 ] == 0 x15
&& data[1 ] == 0 x94
&& data[2 ] == 0 x01)) {
data[1 ] = 0 x00;
data[2 ] = 0 x01;
}
/*
* Lenovo TP X12 Tab KBD ' s Fn + XX is HID raw data defined . Report ID is 0 x03
* e . g . : Raw data received for MIC mute is 0 x00020003 .
*/
if (unlikely((hdev->product == USB_DEVICE_ID_LENOVO_X12_TAB
|| hdev->product == USB_DEVICE_ID_LENOVO_X12_TAB2)
&& size >= 3 && report->id == 0 x03))
return lenovo_raw_event_TP_X12_tab(hdev, le32_to_cpu(*(__le32 *)data));
return 0 ;
}
static int lenovo_event_tp10ubkbd(struct hid_device *hdev,
struct hid_field *field, struct hid_usage *usage, __s32 value)
{
struct lenovo_drvdata *data = hid_get_drvdata(hdev);
if (usage->type == EV_KEY && usage->code == KEY_FN_ESC && value == 1 ) {
/*
* The user has toggled the Fn - lock state . Toggle our own
* cached value of it and sync our value to the keyboard to
* ensure things are in sync ( the sycning should be a no - op ) .
*/
data->fn_lock = !data->fn_lock;
schedule_work(&data->fn_lock_sync_work);
}
return 0 ;
}
static int lenovo_event_cptkbd(struct hid_device *hdev,
struct hid_field *field, struct hid_usage *usage, __s32 value)
{
struct lenovo_drvdata *cptkbd_data = hid_get_drvdata(hdev);
if (cptkbd_data->middleclick_workaround_cptkbd) {
/* "wheel" scroll events */
if (usage->type == EV_REL && (usage->code == REL_WHEEL ||
usage->code == REL_HWHEEL)) {
/* Scroll events disable middle-click event */
cptkbd_data->middlebutton_state = 2 ;
return 0 ;
}
/* Middle click events */
if (usage->type == EV_KEY && usage->code == BTN_MIDDLE) {
if (value == 1 ) {
cptkbd_data->middlebutton_state = 1 ;
} else if (value == 0 ) {
if (cptkbd_data->middlebutton_state == 1 ) {
/* No scrolling inbetween, send middle-click */
input_event(field->hidinput->input,
EV_KEY, BTN_MIDDLE, 1 );
input_sync(field->hidinput->input);
input_event(field->hidinput->input,
EV_KEY, BTN_MIDDLE, 0 );
input_sync(field->hidinput->input);
}
cptkbd_data->middlebutton_state = 0 ;
}
return 1 ;
}
}
if (usage->type == EV_KEY && usage->code == KEY_FN_ESC && value == 1 ) {
/*
* The user has toggled the Fn - lock state . Toggle our own
* cached value of it and sync our value to the keyboard to
* ensure things are in sync ( the syncing should be a no - op ) .
*/
cptkbd_data->fn_lock = !cptkbd_data->fn_lock;
}
return 0 ;
}
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:
case USB_DEVICE_ID_LENOVO_TPIIUSBKBD:
case USB_DEVICE_ID_LENOVO_TPIIBTKBD:
return lenovo_event_cptkbd(hdev, field, usage, value);
case USB_DEVICE_ID_LENOVO_X12_TAB:
case USB_DEVICE_ID_LENOVO_X12_TAB2:
case USB_DEVICE_ID_LENOVO_TP10UBKBD:
case USB_DEVICE_ID_LENOVO_X1_TAB:
case USB_DEVICE_ID_LENOVO_X1_TAB2:
case USB_DEVICE_ID_LENOVO_X1_TAB3:
return lenovo_event_tp10ubkbd(hdev, field, usage, value);
default :
return 0 ;
}
}
static int lenovo_features_set_tpkbd(struct hid_device *hdev)
{
struct hid_report *report;
struct lenovo_drvdata *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 ? 0 x01 : 0 x02;
report->field[0 ]->value[0 ] |= data_pointer->dragging ? 0 x04 : 0 x08;
report->field[0 ]->value[0 ] |= data_pointer->release_to_select ? 0 x10 : 0 x20;
report->field[0 ]->value[0 ] |= data_pointer->select_right ? 0 x80 : 0 x40;
report->field[1 ]->value[0 ] = 0 x03; // 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 attr_press_to_select_show_tpkbd(struct device *dev,
struct device_attribute *attr,
char *buf)
{
struct hid_device *hdev = to_hid_device(dev);
struct lenovo_drvdata *data_pointer = hid_get_drvdata(hdev);
return sysfs_emit(buf, "%u\n" , data_pointer->press_to_select);
}
static ssize_t attr_press_to_select_store_tpkbd(struct device *dev,
struct device_attribute *attr,
const char *buf,
size_t count)
{
struct hid_device *hdev = to_hid_device(dev);
struct lenovo_drvdata *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;
lenovo_features_set_tpkbd(hdev);
return count;
}
static ssize_t attr_dragging_show_tpkbd(struct device *dev,
struct device_attribute *attr,
char *buf)
{
struct hid_device *hdev = to_hid_device(dev);
struct lenovo_drvdata *data_pointer = hid_get_drvdata(hdev);
return sysfs_emit(buf, "%u\n" , data_pointer->dragging);
}
static ssize_t attr_dragging_store_tpkbd(struct device *dev,
struct device_attribute *attr,
const char *buf,
size_t count)
{
struct hid_device *hdev = to_hid_device(dev);
struct lenovo_drvdata *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;
lenovo_features_set_tpkbd(hdev);
return count;
}
static ssize_t attr_release_to_select_show_tpkbd(struct device *dev,
struct device_attribute *attr,
char *buf)
{
struct hid_device *hdev = to_hid_device(dev);
struct lenovo_drvdata *data_pointer = hid_get_drvdata(hdev);
return sysfs_emit(buf, "%u\n" , data_pointer->release_to_select);
}
static ssize_t attr_release_to_select_store_tpkbd(struct device *dev,
struct device_attribute *attr,
const char *buf,
size_t count)
{
struct hid_device *hdev = to_hid_device(dev);
struct lenovo_drvdata *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;
lenovo_features_set_tpkbd(hdev);
return count;
}
static ssize_t attr_select_right_show_tpkbd(struct device *dev,
struct device_attribute *attr,
char *buf)
{
struct hid_device *hdev = to_hid_device(dev);
struct lenovo_drvdata *data_pointer = hid_get_drvdata(hdev);
return sysfs_emit(buf, "%u\n" , data_pointer->select_right);
}
static ssize_t attr_select_right_store_tpkbd(struct device *dev,
struct device_attribute *attr,
const char *buf,
size_t count)
{
struct hid_device *hdev = to_hid_device(dev);
struct lenovo_drvdata *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;
lenovo_features_set_tpkbd(hdev);
return count;
}
static ssize_t attr_sensitivity_show_tpkbd(struct device *dev,
struct device_attribute *attr,
char *buf)
{
struct hid_device *hdev = to_hid_device(dev);
struct lenovo_drvdata *data_pointer = hid_get_drvdata(hdev);
return sysfs_emit(buf, "%u\n" , data_pointer->sensitivity);
}
static ssize_t attr_sensitivity_store_tpkbd(struct device *dev,
struct device_attribute *attr,
const char *buf,
size_t count)
{
struct hid_device *hdev = to_hid_device(dev);
struct lenovo_drvdata *data_pointer = hid_get_drvdata(hdev);
int value;
if (kstrtoint(buf, 10 , &value) || value < 1 || value > 255 )
return -EINVAL;
data_pointer->sensitivity = value;
lenovo_features_set_tpkbd(hdev);
return count;
}
static ssize_t attr_press_speed_show_tpkbd(struct device *dev,
struct device_attribute *attr,
char *buf)
{
struct hid_device *hdev = to_hid_device(dev);
struct lenovo_drvdata *data_pointer = hid_get_drvdata(hdev);
return sysfs_emit(buf, "%u\n" , data_pointer->press_speed);
}
static ssize_t attr_press_speed_store_tpkbd(struct device *dev,
struct device_attribute *attr,
const char *buf,
size_t count)
{
struct hid_device *hdev = to_hid_device(dev);
struct lenovo_drvdata *data_pointer = hid_get_drvdata(hdev);
int value;
if (kstrtoint(buf, 10 , &value) || value < 1 || value > 255 )
return -EINVAL;
data_pointer->press_speed = value;
lenovo_features_set_tpkbd(hdev);
return count;
}
static struct device_attribute dev_attr_press_to_select_tpkbd =
__ATTR(press_to_select, S_IWUSR | S_IRUGO,
attr_press_to_select_show_tpkbd,
attr_press_to_select_store_tpkbd);
static struct device_attribute dev_attr_dragging_tpkbd =
__ATTR(dragging, S_IWUSR | S_IRUGO,
attr_dragging_show_tpkbd,
attr_dragging_store_tpkbd);
static struct device_attribute dev_attr_release_to_select_tpkbd =
__ATTR(release_to_select, S_IWUSR | S_IRUGO,
attr_release_to_select_show_tpkbd,
attr_release_to_select_store_tpkbd);
static struct device_attribute dev_attr_select_right_tpkbd =
__ATTR(select_right, S_IWUSR | S_IRUGO,
attr_select_right_show_tpkbd,
attr_select_right_store_tpkbd);
static struct device_attribute dev_attr_sensitivity_tpkbd =
__ATTR(sensitivity, S_IWUSR | S_IRUGO,
attr_sensitivity_show_tpkbd,
attr_sensitivity_store_tpkbd);
static struct device_attribute dev_attr_press_speed_tpkbd =
__ATTR(press_speed, S_IWUSR | S_IRUGO,
attr_press_speed_show_tpkbd,
attr_press_speed_store_tpkbd);
static struct attribute *lenovo_attributes_tpkbd[] = {
&dev_attr_press_to_select_tpkbd.attr,
&dev_attr_dragging_tpkbd.attr,
&dev_attr_release_to_select_tpkbd.attr,
&dev_attr_select_right_tpkbd.attr,
&dev_attr_sensitivity_tpkbd.attr,
&dev_attr_press_speed_tpkbd.attr,
NULL
};
static const struct attribute_group lenovo_attr_group_tpkbd = {
.attrs = lenovo_attributes_tpkbd,
};
static void lenovo_led_set_tpkbd(struct hid_device *hdev)
{
struct lenovo_drvdata *data_pointer = hid_get_drvdata(hdev);
struct hid_report *report;
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 lenovo_led_brightness_set(struct led_classdev *led_cdev,
enum led_brightness value)
{
struct device *dev = led_cdev->dev->parent;
struct hid_device *hdev = to_hid_device(dev);
struct lenovo_drvdata *data_pointer = hid_get_drvdata(hdev);
static const 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 ;
if (value == LED_OFF)
data_pointer->led_state &= ~(1 << led_nr);
else
data_pointer->led_state |= 1 << led_nr;
switch (hdev->product) {
case USB_DEVICE_ID_LENOVO_TPKBD:
lenovo_led_set_tpkbd(hdev);
break ;
case USB_DEVICE_ID_LENOVO_X12_TAB:
case USB_DEVICE_ID_LENOVO_X12_TAB2:
case USB_DEVICE_ID_LENOVO_TP10UBKBD:
case USB_DEVICE_ID_LENOVO_X1_TAB:
case USB_DEVICE_ID_LENOVO_X1_TAB2:
case USB_DEVICE_ID_LENOVO_X1_TAB3:
ret = lenovo_led_set_tp10ubkbd(hdev, tp10ubkbd_led[led_nr], value);
break ;
}
return ret;
}
static int lenovo_register_leds(struct hid_device *hdev)
{
struct lenovo_drvdata *data = hid_get_drvdata(hdev);
size_t name_sz = strlen(dev_name(&hdev->dev)) + 16 ;
char *name_mute, *name_micm;
int ret;
name_mute = devm_kzalloc(&hdev->dev, name_sz, GFP_KERNEL);
name_micm = devm_kzalloc(&hdev->dev, name_sz, GFP_KERNEL);
if (name_mute == NULL || name_micm == NULL) {
hid_err(hdev, "Could not allocate memory for led data\n" );
return -ENOMEM;
}
snprintf(name_mute, name_sz, "%s:amber:mute" , dev_name(&hdev->dev));
snprintf(name_micm, name_sz, "%s:amber:micmute" , dev_name(&hdev->dev));
data->led_mute.name = name_mute;
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.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 ) {
led_classdev_unregister(&data->led_mute);
return ret;
}
return 0 ;
}
static int lenovo_probe_tpkbd(struct hid_device *hdev)
{
struct lenovo_drvdata *data_pointer;
int i, ret;
/*
* Only register extra settings against subdevice where input_mapping
* set drvdata to 1 , i . e . the trackpoint .
*/
if (!hid_get_drvdata(hdev))
return 0 ;
hid_set_drvdata(hdev, NULL);
/* 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;
ret = sysfs_create_group(&hdev->dev.kobj, &lenovo_attr_group_tpkbd);
if (ret)
hid_warn(hdev, "Could not create sysfs group: %d\n" , ret);
data_pointer = devm_kzalloc(&hdev->dev,
sizeof (struct lenovo_drvdata),
GFP_KERNEL);
if (data_pointer == NULL) {
hid_err(hdev, "Could not allocate memory for driver data\n" );
ret = -ENOMEM;
goto err;
}
// set same default values as windows driver
data_pointer->sensitivity = 0 xa0;
data_pointer->press_speed = 0 x38;
hid_set_drvdata(hdev, data_pointer);
ret = lenovo_register_leds(hdev);
if (ret)
goto err;
lenovo_features_set_tpkbd(hdev);
return 0 ;
err:
sysfs_remove_group(&hdev->dev.kobj, &lenovo_attr_group_tpkbd);
return ret;
}
static int lenovo_probe_cptkbd(struct hid_device *hdev)
{
int ret;
struct lenovo_drvdata *cptkbd_data;
/* All the custom action happens on the USBMOUSE device for USB */
if (((hdev->product == USB_DEVICE_ID_LENOVO_CUSBKBD) ||
(hdev->product == USB_DEVICE_ID_LENOVO_TPIIUSBKBD)) &&
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);
/* Set keyboard settings to known state */
cptkbd_data->middlebutton_state = 0 ;
cptkbd_data->fn_lock = true ;
cptkbd_data->sensitivity = 0 x05;
cptkbd_data->middleclick_workaround_cptkbd = 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 struct attribute *lenovo_attributes_tp10ubkbd[] = {
&dev_attr_fn_lock.attr,
NULL
};
static const struct attribute_group lenovo_attr_group_tp10ubkbd = {
.attrs = lenovo_attributes_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;
/*
* The LEDs and the Fn - lock functionality use output report 9 ,
* with an application of 0 xffa0001 , 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 == 0 xffa00001)
found = true ;
}
if (!found)
return 0 ;
data = devm_kzalloc(&hdev->dev, sizeof (*data), GFP_KERNEL);
if (!data)
return -ENOMEM;
mutex_init(&data->led_report_mutex);
INIT_WORK(&data->fn_lock_sync_work, lenovo_tp10ubkbd_sync_fn_lock);
data->hdev = hdev;
hid_set_drvdata(hdev, data);
/*
* The Thinkpad 10 ultrabook USB kbd dock ' s Fn - lock defaults to on .
* We cannot read the state , only set it , so we force it to on here
* ( which should be a no - op ) to make sure that our state matches the
* keyboard ' s FN - lock state . This is the same as what Windows does .
*
* For X12 TAB and TAB2 , the default windows behaviour Fn - lock Off .
* Adding additional check to ensure the behaviour in case of
* Thinkpad X12 Tabs .
*/
data->fn_lock = !(hdev->product == USB_DEVICE_ID_LENOVO_X12_TAB ||
hdev->product == USB_DEVICE_ID_LENOVO_X12_TAB2);
lenovo_led_set_tp10ubkbd(hdev, TP10UBKBD_FN_LOCK_LED, data->fn_lock);
ret = sysfs_create_group(&hdev->dev.kobj, &lenovo_attr_group_tp10ubkbd);
if (ret)
return ret;
ret = lenovo_register_leds(hdev);
if (ret)
goto err;
return 0 ;
err:
sysfs_remove_group(&hdev->dev.kobj, &lenovo_attr_group_tp10ubkbd);
return ret;
}
static int lenovo_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;
}
switch (hdev->product) {
case USB_DEVICE_ID_LENOVO_TPKBD:
ret = lenovo_probe_tpkbd(hdev);
break ;
case USB_DEVICE_ID_LENOVO_CUSBKBD:
case USB_DEVICE_ID_LENOVO_CBTKBD:
case USB_DEVICE_ID_LENOVO_TPIIUSBKBD:
case USB_DEVICE_ID_LENOVO_TPIIBTKBD:
ret = lenovo_probe_cptkbd(hdev);
break ;
case USB_DEVICE_ID_LENOVO_X12_TAB:
case USB_DEVICE_ID_LENOVO_X12_TAB2:
case USB_DEVICE_ID_LENOVO_TP10UBKBD:
case USB_DEVICE_ID_LENOVO_X1_TAB:
case USB_DEVICE_ID_LENOVO_X1_TAB2:
case USB_DEVICE_ID_LENOVO_X1_TAB3:
ret = lenovo_probe_tp10ubkbd(hdev);
break ;
default :
ret = 0 ;
break ;
}
if (ret)
goto err_hid;
return 0 ;
err_hid:
hid_hw_stop(hdev);
err:
return ret;
}
#ifdef CONFIG_PM
static int lenovo_reset_resume(struct hid_device *hdev)
{
switch (hdev->product) {
case USB_DEVICE_ID_LENOVO_CUSBKBD:
case USB_DEVICE_ID_LENOVO_TPIIUSBKBD:
if (hdev->type == HID_TYPE_USBMOUSE)
lenovo_features_set_cptkbd(hdev);
break ;
default :
break ;
}
return 0 ;
}
#endif
static void lenovo_remove_tpkbd(struct hid_device *hdev)
{
struct lenovo_drvdata *data_pointer = hid_get_drvdata(hdev);
/*
* Only the trackpoint half of the keyboard has drvdata and stuff that
* needs unregistering .
*/
if (data_pointer == NULL)
return ;
sysfs_remove_group(&hdev->dev.kobj,
&lenovo_attr_group_tpkbd);
led_classdev_unregister(&data_pointer->led_micmute);
led_classdev_unregister(&data_pointer->led_mute);
}
static void lenovo_remove_cptkbd(struct hid_device *hdev)
{
sysfs_remove_group(&hdev->dev.kobj,
&lenovo_attr_group_cptkbd);
}
static void lenovo_remove_tp10ubkbd(struct hid_device *hdev)
{
struct lenovo_drvdata *data = hid_get_drvdata(hdev);
if (data == NULL)
return ;
led_classdev_unregister(&data->led_micmute);
led_classdev_unregister(&data->led_mute);
sysfs_remove_group(&hdev->dev.kobj, &lenovo_attr_group_tp10ubkbd);
cancel_work_sync(&data->fn_lock_sync_work);
}
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:
case USB_DEVICE_ID_LENOVO_TPIIUSBKBD:
case USB_DEVICE_ID_LENOVO_TPIIBTKBD:
lenovo_remove_cptkbd(hdev);
break ;
case USB_DEVICE_ID_LENOVO_X12_TAB:
case USB_DEVICE_ID_LENOVO_X12_TAB2:
case USB_DEVICE_ID_LENOVO_TP10UBKBD:
case USB_DEVICE_ID_LENOVO_X1_TAB:
case USB_DEVICE_ID_LENOVO_X1_TAB2:
case USB_DEVICE_ID_LENOVO_X1_TAB3:
lenovo_remove_tp10ubkbd(hdev);
break ;
}
hid_hw_stop(hdev);
}
static int lenovo_input_configured(struct hid_device *hdev,
struct hid_input *hi)
{
switch (hdev->product) {
case USB_DEVICE_ID_LENOVO_TPKBD:
case USB_DEVICE_ID_LENOVO_CUSBKBD:
case USB_DEVICE_ID_LENOVO_CBTKBD:
case USB_DEVICE_ID_LENOVO_TPIIUSBKBD:
case USB_DEVICE_ID_LENOVO_TPIIBTKBD:
if (test_bit(EV_REL, hi->input->evbit)) {
/* set only for trackpoint device */
__set_bit(INPUT_PROP_POINTER, hi->input->propbit);
__set_bit(INPUT_PROP_POINTING_STICK,
hi->input->propbit);
}
break ;
}
return 0 ;
}
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_USB_DEVICE(USB_VENDOR_ID_LENOVO, USB_DEVICE_ID_LENOVO_TPIIUSBKBD) },
{ HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_LENOVO, USB_DEVICE_ID_LENOVO_CBTKBD) },
{ HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_LENOVO, USB_DEVICE_ID_LENOVO_TPIIBTKBD) },
{ HID_USB_DEVICE(USB_VENDOR_ID_LENOVO, USB_DEVICE_ID_LENOVO_TPPRODOCK) },
{ HID_USB_DEVICE(USB_VENDOR_ID_IBM, USB_DEVICE_ID_IBM_SCROLLPOINT_III) },
{ HID_USB_DEVICE(USB_VENDOR_ID_IBM, USB_DEVICE_ID_IBM_SCROLLPOINT_PRO) },
{ HID_USB_DEVICE(USB_VENDOR_ID_IBM, USB_DEVICE_ID_IBM_SCROLLPOINT_OPTICAL) },
{ HID_USB_DEVICE(USB_VENDOR_ID_IBM, USB_DEVICE_ID_IBM_SCROLLPOINT_800DPI_OPTICAL) },
{ 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) },
{ HID_DEVICE(BUS_USB, HID_GROUP_GENERIC,
USB_VENDOR_ID_LENOVO, USB_DEVICE_ID_LENOVO_X1_TAB2) },
{ HID_DEVICE(BUS_USB, HID_GROUP_GENERIC,
USB_VENDOR_ID_LENOVO, USB_DEVICE_ID_LENOVO_X1_TAB3) },
{ HID_DEVICE(BUS_USB, HID_GROUP_GENERIC,
USB_VENDOR_ID_LENOVO, USB_DEVICE_ID_LENOVO_X12_TAB) },
{ HID_DEVICE(BUS_USB, HID_GROUP_GENERIC,
USB_VENDOR_ID_LENOVO, USB_DEVICE_ID_LENOVO_X12_TAB2) },
{ }
};
MODULE_DEVICE_TABLE(hid, lenovo_devices);
static struct hid_driver lenovo_driver = {
.name = "lenovo" ,
.id_table = lenovo_devices,
.input_configured = lenovo_input_configured,
.input_mapping = lenovo_input_mapping,
.probe = lenovo_probe,
.remove = lenovo_remove,
.raw_event = lenovo_raw_event,
.event = lenovo_event,
.report_fixup = lenovo_report_fixup,
#ifdef CONFIG_PM
.reset_resume = lenovo_reset_resume,
#endif
};
module_hid_driver(lenovo_driver);
MODULE_DESCRIPTION("HID driver for IBM/Lenovo" );
MODULE_LICENSE("GPL" );
Messung V0.5 in Prozent C=93 H=99 G=95
¤ Dauer der Verarbeitung: 0.25 Sekunden
(vorverarbeitet am 2026-09-29)
¤
*© Formatika GbR, Deutschland