// 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)
*
* 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 800dpi
* - IBM Scrollpoint Optical 800dpi 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"
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;
u8 middlebutton_state; /* 0:Up, 1:Down (undecided), 2:Scrolling */
bool fn_lock;
};
#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 0x54
#define TP10UBKBD_MUTE_LED 0x64
#define TP10UBKBD_MICMUTE_LED 0x74
#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)
{
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)
hid_err(hdev, "Set LED output report error: %d\n", ret);
mutex_unlock(&data->led_report_mutex);
}
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[] = {
0x05, 0x88, /* Usage Page (Vendor Usage Page 0x88) */
0x09, 0x01, /* Usage (Vendor Usage 0x01) */
0xa1, 0x01, /* Collection (Application) */
0x85, 0x04, /* Report ID (4) */
0x19, 0x00, /* Usage Minimum (0) */
0x2a, 0xff, 0xff, /* Usage Maximum (65535) */
};
static __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
* 0x8801 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] = 0x01;
rdesc[152] = 0x00;
}
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 | 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) {
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