Skip to content

Commit 8e9b915

Browse files
kekrbyJiri Kosina
authored andcommitted
HID: hid-appletb-kbd: add driver for the keyboard mode of Apple Touch Bars
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 <[email protected]>. Signed-off-by: Kerem Karabay <[email protected]> Co-developed-by: Aditya Garg <[email protected]> Signed-off-by: Aditya Garg <[email protected]> Signed-off-by: Jiri Kosina <[email protected]>
1 parent 1fd41e5 commit 8e9b915

File tree

5 files changed

+333
-1
lines changed

5 files changed

+333
-1
lines changed
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
What: /sys/bus/hid/drivers/hid-appletb-kbd/<dev>/mode
2+
Date: September, 2023
3+
KernelVersion: 6.5
4+
5+
Description:
6+
The set of keys displayed on the Touch Bar.
7+
Valid values are:
8+
== =================
9+
0 Escape key only
10+
1 Function keys
11+
2 Media/brightness keys
12+
3 None
13+
== =================

drivers/hid/Kconfig

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -158,6 +158,19 @@ config HID_APPLETB_BL
158158
To compile this driver as a module, choose M here: the
159159
module will be called hid-appletb-bl.
160160

161+
config HID_APPLETB_KBD
162+
tristate "Apple Touch Bar Keyboard Mode"
163+
depends on USB_HID
164+
depends on INPUT
165+
select INPUT_SPARSEKMAP
166+
help
167+
Say Y here if you want support for the keyboard mode (escape,
168+
function, media and brightness keys) of Touch Bars on x86 MacBook
169+
Pros.
170+
171+
To compile this driver as a module, choose M here: the
172+
module will be called hid-appletb-kbd.
173+
161174
config HID_ASUS
162175
tristate "Asus"
163176
depends on USB_HID

drivers/hid/Makefile

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ obj-$(CONFIG_HID_ACRUX) += hid-axff.o
3030
obj-$(CONFIG_HID_APPLE) += hid-apple.o
3131
obj-$(CONFIG_HID_APPLEIR) += hid-appleir.o
3232
obj-$(CONFIG_HID_APPLETB_BL) += hid-appletb-bl.o
33+
obj-$(CONFIG_HID_APPLETB_KBD) += hid-appletb-kbd.o
3334
obj-$(CONFIG_HID_CREATIVE_SB0540) += hid-creative-sb0540.o
3435
obj-$(CONFIG_HID_ASUS) += hid-asus.o
3536
obj-$(CONFIG_HID_AUREAL) += hid-aureal.o

drivers/hid/hid-appletb-kbd.c

Lines changed: 303 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,303 @@
1+
// SPDX-License-Identifier: GPL-2.0
2+
/*
3+
* Apple Touch Bar Keyboard Mode Driver
4+
*
5+
* Copyright (c) 2017-2018 Ronald Tschalär
6+
* Copyright (c) 2022-2023 Kerem Karabay <[email protected]>
7+
* Copyright (c) 2024 Aditya Garg <[email protected]>
8+
*/
9+
10+
#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
11+
12+
#include <linux/hid.h>
13+
#include <linux/usb.h>
14+
#include <linux/input.h>
15+
#include <linux/sysfs.h>
16+
#include <linux/bitops.h>
17+
#include <linux/module.h>
18+
#include <linux/string.h>
19+
#include <linux/input/sparse-keymap.h>
20+
21+
#include "hid-ids.h"
22+
23+
#define APPLETB_KBD_MODE_ESC 0
24+
#define APPLETB_KBD_MODE_FN 1
25+
#define APPLETB_KBD_MODE_SPCL 2
26+
#define APPLETB_KBD_MODE_OFF 3
27+
#define APPLETB_KBD_MODE_MAX APPLETB_KBD_MODE_OFF
28+
29+
#define HID_USAGE_MODE 0x00ff0004
30+
31+
static int appletb_tb_def_mode = APPLETB_KBD_MODE_SPCL;
32+
module_param_named(mode, appletb_tb_def_mode, int, 0444);
33+
MODULE_PARM_DESC(mode, "Default touchbar mode:\n"
34+
" 0 - escape key only\n"
35+
" 1 - function-keys\n"
36+
" [2] - special keys");
37+
38+
struct appletb_kbd {
39+
struct hid_field *mode_field;
40+
41+
u8 saved_mode;
42+
u8 current_mode;
43+
};
44+
45+
static const struct key_entry appletb_kbd_keymap[] = {
46+
{ KE_KEY, KEY_ESC, { KEY_ESC } },
47+
{ KE_KEY, KEY_F1, { KEY_BRIGHTNESSDOWN } },
48+
{ KE_KEY, KEY_F2, { KEY_BRIGHTNESSUP } },
49+
{ KE_KEY, KEY_F3, { KEY_RESERVED } },
50+
{ KE_KEY, KEY_F4, { KEY_RESERVED } },
51+
{ KE_KEY, KEY_F5, { KEY_KBDILLUMDOWN } },
52+
{ KE_KEY, KEY_F6, { KEY_KBDILLUMUP } },
53+
{ KE_KEY, KEY_F7, { KEY_PREVIOUSSONG } },
54+
{ KE_KEY, KEY_F8, { KEY_PLAYPAUSE } },
55+
{ KE_KEY, KEY_F9, { KEY_NEXTSONG } },
56+
{ KE_KEY, KEY_F10, { KEY_MUTE } },
57+
{ KE_KEY, KEY_F11, { KEY_VOLUMEDOWN } },
58+
{ KE_KEY, KEY_F12, { KEY_VOLUMEUP } },
59+
{ KE_END, 0 }
60+
};
61+
62+
static int appletb_kbd_set_mode(struct appletb_kbd *kbd, u8 mode)
63+
{
64+
struct hid_report *report = kbd->mode_field->report;
65+
struct hid_device *hdev = report->device;
66+
int ret;
67+
68+
ret = hid_hw_power(hdev, PM_HINT_FULLON);
69+
if (ret) {
70+
hid_err(hdev, "Device didn't resume (%pe)\n", ERR_PTR(ret));
71+
return ret;
72+
}
73+
74+
ret = hid_set_field(kbd->mode_field, 0, mode);
75+
if (ret) {
76+
hid_err(hdev, "Failed to set mode field to %u (%pe)\n", mode, ERR_PTR(ret));
77+
goto power_normal;
78+
}
79+
80+
hid_hw_request(hdev, report, HID_REQ_SET_REPORT);
81+
82+
kbd->current_mode = mode;
83+
84+
power_normal:
85+
hid_hw_power(hdev, PM_HINT_NORMAL);
86+
87+
return ret;
88+
}
89+
90+
static ssize_t mode_show(struct device *dev,
91+
struct device_attribute *attr, char *buf)
92+
{
93+
struct appletb_kbd *kbd = dev_get_drvdata(dev);
94+
95+
return sysfs_emit(buf, "%d\n", kbd->current_mode);
96+
}
97+
98+
static ssize_t mode_store(struct device *dev,
99+
struct device_attribute *attr,
100+
const char *buf, size_t size)
101+
{
102+
struct appletb_kbd *kbd = dev_get_drvdata(dev);
103+
u8 mode;
104+
int ret;
105+
106+
ret = kstrtou8(buf, 0, &mode);
107+
if (ret)
108+
return ret;
109+
110+
if (mode > APPLETB_KBD_MODE_MAX)
111+
return -EINVAL;
112+
113+
ret = appletb_kbd_set_mode(kbd, mode);
114+
115+
return ret < 0 ? ret : size;
116+
}
117+
static DEVICE_ATTR_RW(mode);
118+
119+
struct attribute *appletb_kbd_attrs[] = {
120+
&dev_attr_mode.attr,
121+
NULL
122+
};
123+
ATTRIBUTE_GROUPS(appletb_kbd);
124+
125+
static int appletb_tb_key_to_slot(unsigned int code)
126+
{
127+
switch (code) {
128+
case KEY_ESC:
129+
return 0;
130+
case KEY_F1 ... KEY_F10:
131+
return code - KEY_F1 + 1;
132+
case KEY_F11 ... KEY_F12:
133+
return code - KEY_F11 + 11;
134+
135+
default:
136+
return -EINVAL;
137+
}
138+
}
139+
140+
static int appletb_kbd_hid_event(struct hid_device *hdev, struct hid_field *field,
141+
struct hid_usage *usage, __s32 value)
142+
{
143+
struct appletb_kbd *kbd = hid_get_drvdata(hdev);
144+
struct key_entry *translation;
145+
struct input_dev *input;
146+
int slot;
147+
148+
if ((usage->hid & HID_USAGE_PAGE) != HID_UP_KEYBOARD || usage->type != EV_KEY)
149+
return 0;
150+
151+
input = field->hidinput->input;
152+
153+
/*
154+
* Skip non-touch-bar keys.
155+
*
156+
* Either the touch bar itself or usbhid generate a slew of key-down
157+
* events for all the meta keys. None of which we're at all interested
158+
* in.
159+
*/
160+
slot = appletb_tb_key_to_slot(usage->code);
161+
if (slot < 0)
162+
return 0;
163+
164+
translation = sparse_keymap_entry_from_scancode(input, usage->code);
165+
166+
if (translation && kbd->current_mode == APPLETB_KBD_MODE_SPCL) {
167+
input_event(input, usage->type, translation->keycode, value);
168+
169+
return 1;
170+
}
171+
172+
return kbd->current_mode == APPLETB_KBD_MODE_OFF;
173+
}
174+
175+
static int appletb_kbd_input_configured(struct hid_device *hdev, struct hid_input *hidinput)
176+
{
177+
int idx;
178+
struct input_dev *input = hidinput->input;
179+
180+
/*
181+
* Clear various input capabilities that are blindly set by the hid
182+
* driver (usbkbd.c)
183+
*/
184+
memset(input->evbit, 0, sizeof(input->evbit));
185+
memset(input->keybit, 0, sizeof(input->keybit));
186+
memset(input->ledbit, 0, sizeof(input->ledbit));
187+
188+
__set_bit(EV_REP, input->evbit);
189+
190+
sparse_keymap_setup(input, appletb_kbd_keymap, NULL);
191+
192+
for (idx = 0; appletb_kbd_keymap[idx].type != KE_END; idx++)
193+
input_set_capability(input, EV_KEY, appletb_kbd_keymap[idx].code);
194+
195+
return 0;
196+
}
197+
198+
static int appletb_kbd_probe(struct hid_device *hdev, const struct hid_device_id *id)
199+
{
200+
struct appletb_kbd *kbd;
201+
struct device *dev = &hdev->dev;
202+
struct hid_field *mode_field;
203+
int ret;
204+
205+
ret = hid_parse(hdev);
206+
if (ret)
207+
return dev_err_probe(dev, ret, "HID parse failed\n");
208+
209+
mode_field = hid_find_field(hdev, HID_OUTPUT_REPORT,
210+
HID_GD_KEYBOARD, HID_USAGE_MODE);
211+
if (!mode_field)
212+
return -ENODEV;
213+
214+
kbd = devm_kzalloc(dev, sizeof(*kbd), GFP_KERNEL);
215+
if (!kbd)
216+
return -ENOMEM;
217+
218+
kbd->mode_field = mode_field;
219+
220+
ret = hid_hw_start(hdev, HID_CONNECT_HIDINPUT);
221+
if (ret)
222+
return dev_err_probe(dev, ret, "HID hw start failed\n");
223+
224+
ret = hid_hw_open(hdev);
225+
if (ret) {
226+
dev_err_probe(dev, ret, "HID hw open failed\n");
227+
goto stop_hw;
228+
}
229+
230+
ret = appletb_kbd_set_mode(kbd, appletb_tb_def_mode);
231+
if (ret) {
232+
dev_err_probe(dev, ret, "Failed to set touchbar mode\n");
233+
goto close_hw;
234+
}
235+
236+
hid_set_drvdata(hdev, kbd);
237+
238+
return 0;
239+
240+
close_hw:
241+
hid_hw_close(hdev);
242+
stop_hw:
243+
hid_hw_stop(hdev);
244+
return ret;
245+
}
246+
247+
static void appletb_kbd_remove(struct hid_device *hdev)
248+
{
249+
struct appletb_kbd *kbd = hid_get_drvdata(hdev);
250+
251+
appletb_kbd_set_mode(kbd, APPLETB_KBD_MODE_OFF);
252+
253+
hid_hw_close(hdev);
254+
hid_hw_stop(hdev);
255+
}
256+
257+
#ifdef CONFIG_PM
258+
static int appletb_kbd_suspend(struct hid_device *hdev, pm_message_t msg)
259+
{
260+
struct appletb_kbd *kbd = hid_get_drvdata(hdev);
261+
262+
kbd->saved_mode = kbd->current_mode;
263+
appletb_kbd_set_mode(kbd, APPLETB_KBD_MODE_OFF);
264+
265+
return 0;
266+
}
267+
268+
static int appletb_kbd_reset_resume(struct hid_device *hdev)
269+
{
270+
struct appletb_kbd *kbd = hid_get_drvdata(hdev);
271+
272+
appletb_kbd_set_mode(kbd, kbd->saved_mode);
273+
274+
return 0;
275+
}
276+
#endif
277+
278+
static const struct hid_device_id appletb_kbd_hid_ids[] = {
279+
/* MacBook Pro's 2018, 2019, with T2 chip: iBridge Display */
280+
{ HID_USB_DEVICE(USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_TOUCHBAR_DISPLAY) },
281+
{ }
282+
};
283+
MODULE_DEVICE_TABLE(hid, appletb_kbd_hid_ids);
284+
285+
static struct hid_driver appletb_kbd_hid_driver = {
286+
.name = "hid-appletb-kbd",
287+
.id_table = appletb_kbd_hid_ids,
288+
.probe = appletb_kbd_probe,
289+
.remove = appletb_kbd_remove,
290+
.event = appletb_kbd_hid_event,
291+
.input_configured = appletb_kbd_input_configured,
292+
#ifdef CONFIG_PM
293+
.suspend = appletb_kbd_suspend,
294+
.reset_resume = appletb_kbd_reset_resume,
295+
#endif
296+
.driver.dev_groups = appletb_kbd_groups,
297+
};
298+
module_hid_driver(appletb_kbd_hid_driver);
299+
300+
MODULE_AUTHOR("Ronald Tschalär");
301+
MODULE_AUTHOR("Kerem Karabay <[email protected]>");
302+
MODULE_DESCRIPTION("MacBookPro Touch Bar Keyboard Mode Driver");
303+
MODULE_LICENSE("GPL");

drivers/hid/hid-quirks.c

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -328,7 +328,6 @@ static const struct hid_device_id hid_have_special_driver[] = {
328328
{ HID_USB_DEVICE(USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_GEYSER1_TP_ONLY) },
329329
{ HID_USB_DEVICE(USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_MAGIC_KEYBOARD_2021) },
330330
{ HID_USB_DEVICE(USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_MAGIC_KEYBOARD_FINGERPRINT_2021) },
331-
{ HID_USB_DEVICE(USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_TOUCHBAR_DISPLAY) },
332331
#endif
333332
#if IS_ENABLED(CONFIG_HID_APPLEIR)
334333
{ 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[] = {
340339
#if IS_ENABLED(CONFIG_HID_APPLETB_BL)
341340
{ HID_USB_DEVICE(USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_TOUCHBAR_BACKLIGHT) },
342341
#endif
342+
#if IS_ENABLED(CONFIG_HID_APPLETB_KBD)
343+
{ HID_USB_DEVICE(USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_TOUCHBAR_DISPLAY) },
344+
#endif
343345
#if IS_ENABLED(CONFIG_HID_ASUS)
344346
{ HID_I2C_DEVICE(USB_VENDOR_ID_ASUSTEK, USB_DEVICE_ID_ASUSTEK_I2C_KEYBOARD) },
345347
{ HID_I2C_DEVICE(USB_VENDOR_ID_ASUSTEK, USB_DEVICE_ID_ASUSTEK_I2C_TOUCHPAD) },

0 commit comments

Comments
 (0)