On Aug 17 2024, Aditya Garg wrote:
> From: Kerem Karabay <kek...@gmail.com>
> 
> The Touch Bars found on x86 Macs support two USB configurations: one
> where the device presents itself as a HID keyboard and can display
> predefined sets of keys, and one where the operating system has full
> control over what is displayed. This commit adds a driver for the
> display functionality of the first configuration.
> 
> Note that currently only T2 Macs are supported.
> 
> This driver is based on previous work done by Ronald Tschalär
> <ron...@innovation.ch>.
> 
> Signed-off-by: Kerem Karabay <kek...@gmail.com>
> Co-developed-by: Aditya Garg <gargadity...@live.com>
> Signed-off-by: Aditya Garg <gargadity...@live.com>
> ---
>  .../ABI/testing/sysfs-driver-hid-appletb-kbd  |  13 +
>  drivers/hid/Kconfig                           |  11 +
>  drivers/hid/Makefile                          |   1 +
>  drivers/hid/hid-appletb-kbd.c                 | 304 ++++++++++++++++++
>  drivers/hid/hid-quirks.c                      |   4 +-
>  5 files changed, 332 insertions(+), 1 deletion(-)
>  create mode 100644 Documentation/ABI/testing/sysfs-driver-hid-appletb-kbd
>  create mode 100644 drivers/hid/hid-appletb-kbd.c
> 
> diff --git a/Documentation/ABI/testing/sysfs-driver-hid-appletb-kbd 
> b/Documentation/ABI/testing/sysfs-driver-hid-appletb-kbd
> new file mode 100644
> index 000000000..2a19584d0
> --- /dev/null
> +++ b/Documentation/ABI/testing/sysfs-driver-hid-appletb-kbd
> @@ -0,0 +1,13 @@
> +What:                /sys/bus/hid/drivers/hid-appletb-kbd/<dev>/mode
> +Date:                September, 2023
> +KernelVersion:       6.5
> +Contact:     linux-in...@vger.kernel.org
> +Description:
> +             The set of keys displayed on the Touch Bar.
> +             Valid values are:
> +             == =================
> +             0  Escape key only
> +             1  Function keys
> +             2  Media/brightness keys
> +             3  None
> +             == =================
> diff --git a/drivers/hid/Kconfig b/drivers/hid/Kconfig
> index 4988c1fb2..72b665eda 100644
> --- a/drivers/hid/Kconfig
> +++ b/drivers/hid/Kconfig
> @@ -158,6 +158,17 @@ config HID_APPLETB_BL
>         To compile this driver as a module, choose M here: the
>         module will be called hid-appletb-bl.
>  
> +config HID_APPLETB_KBD
> +     tristate "Apple Touch Bar Keyboard Mode"
> +     depends on USB_HID
> +     help
> +       Say Y here if you want support for the keyboard mode (escape,
> +       function, media and brightness keys) of Touch Bars on x86 MacBook
> +       Pros.
> +
> +       To compile this driver as a module, choose M here: the
> +       module will be called hid-appletb-kbd.
> +
>  config HID_ASUS
>       tristate "Asus"
>       depends on USB_HID
> diff --git a/drivers/hid/Makefile b/drivers/hid/Makefile
> index 1d825a474..d903c9a26 100644
> --- a/drivers/hid/Makefile
> +++ b/drivers/hid/Makefile
> @@ -30,6 +30,7 @@ obj-$(CONFIG_HID_ACRUX)             += hid-axff.o
>  obj-$(CONFIG_HID_APPLE)              += hid-apple.o
>  obj-$(CONFIG_HID_APPLEIR)    += hid-appleir.o
>  obj-$(CONFIG_HID_APPLETB_BL) += hid-appletb-bl.o
> +obj-$(CONFIG_HID_APPLETB_KBD)        += hid-appletb-kbd.o
>  obj-$(CONFIG_HID_CREATIVE_SB0540)    += hid-creative-sb0540.o
>  obj-$(CONFIG_HID_ASUS)               += hid-asus.o
>  obj-$(CONFIG_HID_AUREAL)     += hid-aureal.o
> diff --git a/drivers/hid/hid-appletb-kbd.c b/drivers/hid/hid-appletb-kbd.c
> new file mode 100644
> index 000000000..ecac68fc7
> --- /dev/null
> +++ b/drivers/hid/hid-appletb-kbd.c
> @@ -0,0 +1,304 @@
> +// SPDX-License-Identifier: GPL-2.0
> +/*
> + * Apple Touch Bar Keyboard Mode Driver
> + *
> + * Copyright (c) 2017-2018 Ronald Tschalär
> + * Copyright (c) 2022-2023 Kerem Karabay <kek...@gmail.com>
> + * Copyright (c) 2024 Aditya Garg <gargadity...@live.com>
> + */
> +
> +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
> +
> +#include <linux/hid.h>
> +#include <linux/usb.h>
> +#include <linux/input.h>
> +#include <linux/sysfs.h>
> +#include <linux/bitops.h>
> +#include <linux/module.h>
> +#include <linux/string.h>
> +#include <linux/input/sparse-keymap.h>
> +
> +#include "hid-ids.h"
> +
> +#define APPLETB_KBD_MODE_ESC 0
> +#define APPLETB_KBD_MODE_FN  1
> +#define APPLETB_KBD_MODE_SPCL        2
> +#define APPLETB_KBD_MODE_OFF 3
> +#define APPLETB_KBD_MODE_MAX APPLETB_KBD_MODE_OFF
> +
> +#define HID_USAGE_MODE               0x00ff0004
> +
> +static int appletb_tb_def_mode = APPLETB_KBD_MODE_SPCL;
> +module_param_named(mode, appletb_tb_def_mode, int, 0444);
> +MODULE_PARM_DESC(mode, "Default touchbar mode:\n"
> +                      "    0 - escape key only\n"
> +                      "    1 - function-keys\n"
> +                      "    [2] - special keys");
> +
> +struct appletb_kbd {
> +     struct hid_field *mode_field;
> +
> +     u8 saved_mode;
> +     u8 current_mode;
> +};
> +
> +static const struct key_entry appletb_kbd_keymap[] = {
> +     { KE_KEY, KEY_ESC, { KEY_ESC } },
> +     { KE_KEY, KEY_F1,  { KEY_BRIGHTNESSDOWN } },
> +     { KE_KEY, KEY_F2,  { KEY_BRIGHTNESSUP } },
> +     { KE_KEY, KEY_F3,  { KEY_RESERVED } },
> +     { KE_KEY, KEY_F4,  { KEY_RESERVED } },
> +     { KE_KEY, KEY_F5,  { KEY_KBDILLUMDOWN } },
> +     { KE_KEY, KEY_F6,  { KEY_KBDILLUMUP } },
> +     { KE_KEY, KEY_F7,  { KEY_PREVIOUSSONG } },
> +     { KE_KEY, KEY_F8,  { KEY_PLAYPAUSE } },
> +     { KE_KEY, KEY_F9,  { KEY_NEXTSONG } },
> +     { KE_KEY, KEY_F10, { KEY_MUTE } },
> +     { KE_KEY, KEY_F11, { KEY_VOLUMEDOWN } },
> +     { KE_KEY, KEY_F12, { KEY_VOLUMEUP } },
> +     { KE_END, 0 }
> +};
> +
> +static int appletb_kbd_set_mode(struct appletb_kbd *kbd, u8 mode)
> +{
> +     struct hid_report *report = kbd->mode_field->report;
> +     struct hid_device *hdev = report->device;
> +     int ret;
> +
> +     ret = hid_hw_power(hdev, PM_HINT_FULLON);
> +     if (ret) {
> +             hid_err(hdev, "Device didn't resume (%pe)\n", ERR_PTR(ret));
> +             return ret;
> +     }
> +
> +     ret = hid_set_field(kbd->mode_field, 0, mode);
> +     if (ret) {
> +             hid_err(hdev, "Failed to set mode field to %u (%pe)\n", mode, 
> ERR_PTR(ret));
> +             goto power_normal;
> +     }
> +
> +     hid_hw_request(hdev, report, HID_REQ_SET_REPORT);
> +
> +     kbd->current_mode = mode;
> +
> +power_normal:
> +     hid_hw_power(hdev, PM_HINT_NORMAL);
> +
> +     return ret;
> +}
> +
> +static ssize_t mode_show(struct device *dev,
> +                      struct device_attribute *attr, char *buf)
> +{
> +     struct appletb_kbd *kbd = dev_get_drvdata(dev);
> +
> +     return sysfs_emit(buf, "%d\n", kbd->current_mode);
> +}
> +
> +static ssize_t mode_store(struct device *dev,
> +                       struct device_attribute *attr,
> +                       const char *buf, size_t size)
> +{
> +     struct appletb_kbd *kbd = dev_get_drvdata(dev);
> +     u8 mode;
> +     int ret;
> +
> +     ret = kstrtou8(buf, 0, &mode);
> +     if (ret)
> +             return ret;
> +
> +     if (mode > APPLETB_KBD_MODE_MAX)
> +             return -EINVAL;
> +
> +     ret = appletb_kbd_set_mode(kbd, mode);
> +
> +     return ret < 0 ? ret : size;
> +}
> +static DEVICE_ATTR_RW(mode);
> +
> +struct attribute *appletb_kbd_attrs[] = {
> +     &dev_attr_mode.attr,
> +     NULL
> +};
> +ATTRIBUTE_GROUPS(appletb_kbd);
> +
> +static int appletb_tb_key_to_slot(unsigned int code)
> +{
> +     switch (code) {
> +     case KEY_ESC:
> +             return 0;
> +     case KEY_F1 ... KEY_F10:
> +             return code - KEY_F1 + 1;
> +     case KEY_F11 ... KEY_F12:
> +             return code - KEY_F11 + 11;
> +
> +     default:
> +             return -EINVAL;
> +     }
> +}
> +
> +static int appletb_kbd_hid_event(struct hid_device *hdev, struct hid_field 
> *field,
> +                                   struct hid_usage *usage, __s32 value)
> +{
> +     struct appletb_kbd *kbd = hid_get_drvdata(hdev);
> +     struct key_entry *translation;
> +     struct input_dev *input;
> +     int slot;
> +
> +     if ((usage->hid & HID_USAGE_PAGE) != HID_UP_KEYBOARD || usage->type != 
> EV_KEY)
> +             return 0;
> +
> +     input = field->hidinput->input;
> +
> +     /*
> +      * Skip non-touch-bar keys.
> +      *
> +      * Either the touch bar itself or usbhid generate a slew of key-down
> +      * events for all the meta keys. None of which we're at all interested
> +      * in.
> +      */
> +     slot = appletb_tb_key_to_slot(usage->code);
> +     if (slot < 0)
> +             return 0;
> +
> +     translation = sparse_keymap_entry_from_scancode(input, usage->code);

You are missing a Kconfig depends or select here because when enabling
this module, we can get

ERROR: modpost: "sparse_keymap_setup" [drivers/hid/hid-appletb-kbd.ko] 
undefined!
ERROR: modpost: "sparse_keymap_entry_from_scancode" 
[drivers/hid/hid-appletb-kbd.ko] undefined!

FWIW, I tried "depend on INPUT_SPARSEKMAP" and "select
INPUT_SPARSEKMAP", but in both cases the error was there, so I suspect
there is one extra config to select (could be my script but if I can
enter this state, I believe others will get into it as well).

Cheers,
Benjamin

> +
> +     if (translation && kbd->current_mode == APPLETB_KBD_MODE_SPCL) {
> +             input_event(input, usage->type, translation->keycode, value);
> +
> +             return 1;
> +     }
> +
> +     return kbd->current_mode == APPLETB_KBD_MODE_OFF;
> +}
> +
> +static int appletb_kbd_input_configured(struct hid_device *hdev, struct 
> hid_input *hidinput)
> +{
> +     int idx;
> +     struct input_dev *input = hidinput->input;
> +
> +     /*
> +      * Clear various input capabilities that are blindly set by the hid
> +      * driver (usbkbd.c)
> +      */
> +     memset(input->evbit, 0, sizeof(input->evbit));
> +     memset(input->keybit, 0, sizeof(input->keybit));
> +     memset(input->ledbit, 0, sizeof(input->ledbit));
> +
> +     __set_bit(EV_REP, input->evbit);
> +
> +     sparse_keymap_setup(input, appletb_kbd_keymap, NULL);
> +
> +     for (idx = 0; appletb_kbd_keymap[idx].type != KE_END; idx++) {
> +             input_set_capability(input, EV_KEY, 
> appletb_kbd_keymap[idx].code);
> +     }
> +
> +     return 0;
> +}
> +
> +static int appletb_kbd_probe(struct hid_device *hdev, const struct 
> hid_device_id *id)
> +{
> +     struct appletb_kbd *kbd;
> +     struct device *dev = &hdev->dev;
> +     struct hid_field *mode_field;
> +     int ret;
> +
> +     ret = hid_parse(hdev);
> +     if (ret)
> +             return dev_err_probe(dev, ret, "HID parse failed\n");
> +
> +     mode_field = hid_find_field(hdev, HID_OUTPUT_REPORT,
> +                                 HID_GD_KEYBOARD, HID_USAGE_MODE);
> +     if (!mode_field)
> +             return -ENODEV;
> +
> +     kbd = devm_kzalloc(dev, sizeof(*kbd), GFP_KERNEL);
> +     if (!kbd)
> +             return -ENOMEM;
> +
> +     kbd->mode_field = mode_field;
> +
> +     ret = hid_hw_start(hdev, HID_CONNECT_HIDINPUT);
> +     if (ret)
> +             return dev_err_probe(dev, ret, "HID hw start failed\n");
> +
> +     ret = hid_hw_open(hdev);
> +     if (ret) {
> +             dev_err_probe(dev, ret, "HID hw open failed\n");
> +             goto stop_hw;
> +     }
> +
> +     ret = appletb_kbd_set_mode(kbd, appletb_tb_def_mode);
> +     if (ret) {
> +             dev_err_probe(dev, ret, "Failed to set touchbar mode\n");
> +             goto close_hw;
> +     }
> +
> +     hid_set_drvdata(hdev, kbd);
> +
> +     return 0;
> +
> +close_hw:
> +     hid_hw_close(hdev);
> +stop_hw:
> +     hid_hw_stop(hdev);
> +     return ret;
> +}
> +
> +static void appletb_kbd_remove(struct hid_device *hdev)
> +{
> +     struct appletb_kbd *kbd = hid_get_drvdata(hdev);
> +
> +     appletb_kbd_set_mode(kbd, APPLETB_KBD_MODE_OFF);
> +
> +     hid_hw_close(hdev);
> +     hid_hw_stop(hdev);
> +}
> +
> +#ifdef CONFIG_PM
> +static int appletb_kbd_suspend(struct hid_device *hdev, pm_message_t msg)
> +{
> +     struct appletb_kbd *kbd = hid_get_drvdata(hdev);
> +
> +     kbd->saved_mode = kbd->current_mode;
> +     appletb_kbd_set_mode(kbd, APPLETB_KBD_MODE_OFF);
> +
> +     return 0;
> +}
> +
> +static int appletb_kbd_reset_resume(struct hid_device *hdev)
> +{
> +     struct appletb_kbd *kbd = hid_get_drvdata(hdev);
> +
> +     appletb_kbd_set_mode(kbd, kbd->saved_mode);
> +
> +     return 0;
> +}
> +#endif
> +
> +static const struct hid_device_id appletb_kbd_hid_ids[] = {
> +     /* MacBook Pro's 2018, 2019, with T2 chip: iBridge Display */
> +     { HID_USB_DEVICE(USB_VENDOR_ID_APPLE, 
> USB_DEVICE_ID_APPLE_TOUCHBAR_DISPLAY) },
> +     { }
> +};
> +MODULE_DEVICE_TABLE(hid, appletb_kbd_hid_ids);
> +
> +static struct hid_driver appletb_kbd_hid_driver = {
> +     .name = "hid-appletb-kbd",
> +     .id_table = appletb_kbd_hid_ids,
> +     .probe = appletb_kbd_probe,
> +     .remove = appletb_kbd_remove,
> +     .event = appletb_kbd_hid_event,
> +     .input_configured = appletb_kbd_input_configured,
> +#ifdef CONFIG_PM
> +     .suspend = appletb_kbd_suspend,
> +     .reset_resume = appletb_kbd_reset_resume,
> +#endif
> +     .driver.dev_groups = appletb_kbd_groups,
> +};
> +module_hid_driver(appletb_kbd_hid_driver);
> +
> +MODULE_AUTHOR("Ronald Tschalär");
> +MODULE_AUTHOR("Kerem Karabay <kek...@gmail.com>");
> +MODULE_DESCRIPTION("MacBookPro Touch Bar Keyboard Mode Driver");
> +MODULE_LICENSE("GPL");
> diff --git a/drivers/hid/hid-quirks.c b/drivers/hid/hid-quirks.c
> index 818d41a35..7c576d654 100644
> --- a/drivers/hid/hid-quirks.c
> +++ b/drivers/hid/hid-quirks.c
> @@ -328,7 +328,6 @@ static const struct hid_device_id 
> hid_have_special_driver[] = {
>       { HID_USB_DEVICE(USB_VENDOR_ID_APPLE, 
> USB_DEVICE_ID_APPLE_GEYSER1_TP_ONLY) },
>       { HID_USB_DEVICE(USB_VENDOR_ID_APPLE, 
> USB_DEVICE_ID_APPLE_MAGIC_KEYBOARD_2021) },
>       { HID_USB_DEVICE(USB_VENDOR_ID_APPLE, 
> USB_DEVICE_ID_APPLE_MAGIC_KEYBOARD_FINGERPRINT_2021) },
> -     { HID_USB_DEVICE(USB_VENDOR_ID_APPLE, 
> USB_DEVICE_ID_APPLE_TOUCHBAR_DISPLAY) },
>  #endif
>  #if IS_ENABLED(CONFIG_HID_APPLEIR)
>       { HID_USB_DEVICE(USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_IRCONTROL) },
> @@ -340,6 +339,9 @@ static const struct hid_device_id 
> hid_have_special_driver[] = {
>  #if IS_ENABLED(CONFIG_HID_APPLETB_BL)
>       { HID_USB_DEVICE(USB_VENDOR_ID_APPLE, 
> USB_DEVICE_ID_APPLE_TOUCHBAR_BACKLIGHT) },
>  #endif
> +#if IS_ENABLED(CONFIG_HID_APPLETB_KBD)
> +     { HID_USB_DEVICE(USB_VENDOR_ID_APPLE, 
> USB_DEVICE_ID_APPLE_TOUCHBAR_DISPLAY) },
> +#endif
>  #if IS_ENABLED(CONFIG_HID_ASUS)
>       { HID_I2C_DEVICE(USB_VENDOR_ID_ASUSTEK, 
> USB_DEVICE_ID_ASUSTEK_I2C_KEYBOARD) },
>       { HID_I2C_DEVICE(USB_VENDOR_ID_ASUSTEK, 
> USB_DEVICE_ID_ASUSTEK_I2C_TOUCHPAD) },
> -- 
> 2.43.0
> 

Reply via email to