HID: lenovo: Support for ThinkPad-X12-TAB-1/2 Kbd Fn keys
authorVishnu Sankar <vishnuocv@gmail.com>
Wed, 18 Dec 2024 14:33:09 +0000 (23:33 +0900)
committerJiri Kosina <jkosina@suse.com>
Thu, 9 Jan 2025 09:04:20 +0000 (10:04 +0100)
Fn Keys like Mic mute, Power Modes/Airplane mode,Selective
screenshot/Pickup Phone, KBD Backlight and
star/Favourites is emitted as HID raw events in X12 Tab1 and Tab2.
This support has been added.

Thinkpad X12 TAB 2 and TAB 1 Folio keyboard's raw events will get
detected as Fn keys with this patch.

Default fn_lock state for these Keyboards are OFF.

Other than these changes, we follow TP10UKBD's processes.

Tested on X12 Tab 2.

Signed-off-by: Vishnu Sankar <vishnuocv@gmail.com>
Signed-off-by: Vishnu Sankar <vsankar@lenovo.com>
Suggested-by: Mark Pearson <mpearson-lenovo@squebb.ca>
Signed-off-by: Jiri Kosina <jkosina@suse.com>
drivers/hid/hid-lenovo.c

index f66194fde8912ac1b223e0092d5589e2a351269b..bfaadd54cba1f3e27b8fb6765c442b1e70655f1c 100644 (file)
 #include <linux/input.h>
 #include <linux/leds.h>
 #include <linux/workqueue.h>
+#include <linux/platform_profile.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                0x00020003
+#define TP_X12_RAW_HOTKEY_FN_F8                0x38001003
+#define TP_X12_RAW_HOTKEY_FN_F10       0x00000803
+#define TP_X12_RAW_HOTKEY_FN_F12       0x00000403
+#define TP_X12_RAW_HOTKEY_FN_SPACE     0x18001003
+
 struct lenovo_drvdata {
        u8 led_report[3]; /* Must be first for proper alignment */
        int led_state;
@@ -71,6 +79,14 @@ struct lenovo_drvdata {
 #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)
 {
@@ -472,6 +488,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_X12_TAB:
+       case USB_DEVICE_ID_LENOVO_X12_TAB2:
        case USB_DEVICE_ID_LENOVO_X1_TAB:
        case USB_DEVICE_ID_LENOVO_X1_TAB3:
                return lenovo_input_mapping_x1_tab_kbd(hdev, hi, field, usage, bit, max);
@@ -582,6 +600,8 @@ static ssize_t attr_fn_lock_store(struct device *dev,
        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_TAB3:
@@ -680,6 +700,55 @@ 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
+                        */
+                       (hdev->product == USB_DEVICE_ID_LENOVO_X12_TAB) ?
+                               report_key_event(input, KEY_RFKILL) :
+                               platform_profile_cycle();
+                       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)
 {
@@ -697,6 +766,15 @@ static int lenovo_raw_event(struct hid_device *hdev,
                data[2] = 0x01;
        }
 
+       /*
+        * Lenovo TP X12 Tab KBD's Fn+XX is HID raw data defined. Report ID is 0x03
+        * e.g.: Raw data received for MIC mute is 0x00020003.
+        */
+       if (unlikely((hdev->product == USB_DEVICE_ID_LENOVO_X12_TAB
+                       || hdev->product == USB_DEVICE_ID_LENOVO_X12_TAB2)
+                       && size >= 3 && report->id == 0x03))
+               return lenovo_raw_event_TP_X12_tab(hdev, le32_to_cpu(*(u32 *)data));
+
        return 0;
 }
 
@@ -776,6 +854,8 @@ static int lenovo_event(struct hid_device *hdev, struct hid_field *field,
        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_TAB3:
@@ -1057,6 +1137,8 @@ static int lenovo_led_brightness_set(struct led_classdev *led_cdev,
        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_TAB3:
@@ -1243,8 +1325,15 @@ static int lenovo_probe_tp10ubkbd(struct hid_device *hdev)
         * 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 = true;
+
+       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);
@@ -1288,6 +1377,8 @@ static int lenovo_probe(struct hid_device *hdev,
        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_TAB3:
@@ -1375,6 +1466,8 @@ static void lenovo_remove(struct hid_device *hdev)
        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_TAB3:
@@ -1429,6 +1522,10 @@ static const struct hid_device_id lenovo_devices[] = {
                     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_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) },
        { }
 };