Skip to content

Commit 56e691c

Browse files
marcanjannau
authored andcommitted
input: apple: Add support for Apple MTP keyboard
Apple M2 devices have an MTP coprocessor in charge of keyboard/trackpad handling, communicating over a DockChannel interface. Add a simple driver for this. The keyboard does not require any initialization messages, but we have a problem: we cannot reset the MTP so Linux can start it fresh, and it delivers a number of informative packets on startup. To work around this, we buffer those messages and re-inject them into the FIFO (which is big enough to hold all of them) on shutdown, so Linux finds them when it initializes its driver. The actual MTP coprocessor is quiesced, which does work properly. Signed-off-by: Hector Martin <[email protected]>
1 parent 29a672b commit 56e691c

4 files changed

Lines changed: 272 additions & 1 deletion

File tree

drivers/input/Kconfig

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,15 @@ config CPCAP_POWER_BUTTON
6666
help
6767
Enable support for a dedicated power button of the CPCAP PMIC.
6868

69+
config APPLE_MTP_KEYB
70+
bool "Enable Apple MTP keyboard support"
71+
select APPLE_KEYB
72+
depends on DM_KEYBOARD && MISC
73+
help
74+
This adds a driver for the keyboards found on various
75+
laptops based on Apple M2 and newer SoCs. These keyboards use an
76+
Apple-specific HID-over-DockChannel protocol.
77+
6978
config CROS_EC_KEYB
7079
bool "Enable Chrome OS EC keyboard support"
7180
depends on INPUT

drivers/input/Makefile

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ ifndef CONFIG_XPL_BUILD
1313

1414
obj-$(CONFIG_APPLE_KEYB) += apple_kbd.o
1515
obj-$(CONFIG_APPLE_SPI_KEYB) += apple_spi_kbd.o
16+
obj-$(CONFIG_APPLE_MTP_KEYB) += apple_mtp_kbd.o
1617
obj-$(CONFIG_I8042_KEYB) += i8042.o
1718
obj-$(CONFIG_TEGRA_KEYBOARD) += input.o tegra-kbc.o
1819
obj-$(CONFIG_TWL4030_INPUT) += twl4030.o

drivers/input/apple_mtp_kbd.c

Lines changed: 261 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,261 @@
1+
// SPDX-License-Identifier: GPL-2.0+ OR MIT
2+
/*
3+
* Copyright The Asahi Linux Contributors
4+
*/
5+
6+
#include <dm.h>
7+
#include <dm/simple_bus.h>
8+
#include <dm/device_compat.h>
9+
#include <dm/device-internal.h>
10+
#include <mailbox.h>
11+
#include <keyboard.h>
12+
#include <stdio_dev.h>
13+
#include <asm/arch/rtkit.h>
14+
#include <asm/io.h>
15+
#include <linux/input.h>
16+
#include "apple_kbd.h"
17+
18+
struct apple_mtp_kbd_priv {
19+
struct apple_kbd_priv kbd;
20+
struct udevice *helper;
21+
void *local;
22+
void *rmt;
23+
24+
u8 *init_data;
25+
u32 init_size;
26+
u32 fifo_size;
27+
};
28+
29+
#define DATA_TX8 0x4
30+
#define DATA_TX_FREE 0x14
31+
#define DATA_RX8 0x1c
32+
#define DATA_RX_COUNT 0x2c
33+
34+
struct dchid_hdr {
35+
u8 hdr_len;
36+
u8 channel;
37+
__le16 length;
38+
u8 seq;
39+
u8 iface;
40+
__le16 pad;
41+
} __packed;
42+
43+
static int dockchannel_read(struct udevice *dev, void *buf, size_t size)
44+
{
45+
struct apple_mtp_kbd_priv *priv = dev_get_priv(dev);
46+
int ret = 0;
47+
u8 b;
48+
u8 *p = buf;
49+
50+
while (size--) {
51+
ulong start;
52+
start = get_timer(0);
53+
while (get_timer(start) < 100) {
54+
if (readl(priv->local + DATA_RX_COUNT) != 0)
55+
break;
56+
}
57+
58+
if (readl(priv->local + DATA_RX_COUNT) == 0) {
59+
return -ETIME;
60+
}
61+
62+
b = readl(priv->local + DATA_RX8) >> 8;
63+
if (buf)
64+
*p++ = b;
65+
66+
ret++;
67+
}
68+
69+
return ret;
70+
}
71+
72+
static int apple_mtp_kbd_check(struct input_config *input)
73+
{
74+
struct udevice *dev = input->dev;
75+
struct apple_mtp_kbd_priv *priv = dev_get_priv(dev);
76+
struct dchid_hdr hdr;
77+
int ret;
78+
79+
/* Poll for syslogs if RTKit is up */
80+
if (priv->helper)
81+
apple_rtkit_helper_poll(priv->helper, 0);
82+
83+
u32 pending = readl(priv->local + DATA_RX_COUNT);
84+
if (pending < 8)
85+
return 0;
86+
87+
ret = dockchannel_read(dev, &hdr, sizeof(hdr));
88+
if (ret < 0) {
89+
dev_err(dev, "failed to read packet header\n");
90+
return ret;
91+
}
92+
93+
/* Save comm init messages for the next stage */
94+
if (hdr.iface == 0) {
95+
int space = priv->fifo_size - priv->init_size;
96+
int need = hdr.length + sizeof(hdr) + 4;
97+
98+
if (space < need) {
99+
dev_err(dev, "out of buf space (%d > %d)\n",
100+
need, space);
101+
ret = dockchannel_read(dev, NULL, hdr.length + 4);
102+
if (ret < 0)
103+
return ret;
104+
} else {
105+
u8 *p = &priv->init_data[priv->init_size];
106+
107+
memcpy(p, &hdr, sizeof(hdr));
108+
ret = dockchannel_read(dev, p + sizeof(hdr), hdr.length + 4);
109+
if (ret < 0)
110+
return ret;
111+
priv->init_size += need;
112+
}
113+
} else if (hdr.channel == 0x12 && hdr.length == 0x14) {
114+
u8 buf[0x18];
115+
116+
ret = dockchannel_read(dev, buf, 0x18);
117+
if (ret < 0)
118+
return ret;
119+
120+
if (!priv->helper)
121+
return 1; /* Ignore if shutting down */
122+
123+
/* Just assume it's a keyboard report */
124+
return apple_kbd_handle_report(input, &priv->kbd, buf + 8, 0xc);
125+
} else {
126+
printk("mtp: unknown message ch=%02x l=%04x if=%02x\n",
127+
hdr.channel, hdr.length, hdr.iface);
128+
ret = dockchannel_read(dev, NULL, hdr.length + 4);
129+
if (ret < 0)
130+
return ret;
131+
}
132+
133+
return 0;
134+
}
135+
136+
static int get_rtkit_helper(struct udevice *dev)
137+
{
138+
struct apple_mtp_kbd_priv *priv = dev_get_priv(dev);
139+
int ret;
140+
u32 phandle;
141+
ofnode of_mtp;
142+
143+
ret = dev_read_u32(dev, "apple,helper-cpu", &phandle);
144+
if (ret < 0)
145+
return ret;
146+
147+
of_mtp = ofnode_get_by_phandle(phandle);
148+
ret = uclass_get_device_by_ofnode(UCLASS_MISC, of_mtp, &priv->helper);
149+
if (ret < 0)
150+
return ret;
151+
152+
return 0;
153+
}
154+
155+
static int apple_mtp_kbd_probe(struct udevice *dev)
156+
{
157+
struct apple_mtp_kbd_priv *priv = dev_get_priv(dev);
158+
struct keyboard_priv *uc_priv = dev_get_uclass_priv(dev);
159+
struct stdio_dev *sdev = &uc_priv->sdev;
160+
struct input_config *input = &uc_priv->input;
161+
int ret;
162+
fdt_addr_t reg;
163+
164+
printf("mtp_kbd_probe\n");
165+
166+
reg = dev_read_addr_name(dev, "data");
167+
if (reg == FDT_ADDR_T_NONE) {
168+
dev_err(dev, "no reg property for local FIFO data registers\n");
169+
return -EINVAL;
170+
}
171+
priv->local = (void *)reg;
172+
173+
reg = dev_read_addr_name(dev, "rmt-data");
174+
if (reg == FDT_ADDR_T_NONE) {
175+
dev_err(dev, "no reg property for remote FIFO data registers\n");
176+
return -EINVAL;
177+
}
178+
priv->rmt = (void *)reg;
179+
180+
ret = dev_read_u32(dev, "apple,fifo-size", &priv->fifo_size);
181+
if (ret < 0 || !priv->fifo_size) {
182+
dev_err(dev, "no apple,fifo-size property\n");
183+
return ret;
184+
}
185+
186+
ret = get_rtkit_helper(dev);
187+
if (ret < 0) {
188+
dev_err(dev, "Failed to get helper device (%d)\n", ret);
189+
return ret;
190+
}
191+
192+
priv->init_data = malloc(priv->fifo_size);
193+
if (!priv->init_data)
194+
return -ENOMEM;
195+
196+
input->dev = dev;
197+
input->read_keys = apple_mtp_kbd_check;
198+
input_add_tables(input, false);
199+
strcpy(sdev->name, "mtpkbd");
200+
201+
return input_stdio_register(sdev);
202+
}
203+
204+
static int apple_mtp_kbd_remove(struct udevice *dev)
205+
{
206+
struct apple_mtp_kbd_priv *priv = dev_get_priv(dev);
207+
struct keyboard_priv *uc_priv = dev_get_uclass_priv(dev);
208+
struct input_config *input = &uc_priv->input;
209+
int i;
210+
211+
if (priv->helper) {
212+
device_remove(priv->helper, DM_REMOVE_NORMAL);
213+
priv->helper = NULL;
214+
}
215+
216+
/* Drain the FIFO */
217+
while (readl(priv->local + DATA_RX_COUNT)) {
218+
if (apple_mtp_kbd_check(input) < 0) {
219+
dev_err(dev, "Failed to drain FIFO\n");
220+
break;
221+
}
222+
}
223+
224+
/* Stuff init messages back into FIFO for the next stage to find */
225+
for (i = 0; i < priv->init_size; i++)
226+
writel(priv->init_data[i], priv->rmt + DATA_TX8);
227+
228+
return 0;
229+
}
230+
231+
static const struct keyboard_ops apple_mtp_kbd_ops = {
232+
};
233+
234+
static const struct udevice_id apple_mtp_kbd_of_match[] = {
235+
{ .compatible = "apple,dockchannel-hid" },
236+
{ /* sentinel */ }
237+
};
238+
239+
U_BOOT_DRIVER(apple_mtp_kbd) = {
240+
.name = "apple_mtp_kbd",
241+
.id = UCLASS_KEYBOARD,
242+
.of_match = apple_mtp_kbd_of_match,
243+
.probe = apple_mtp_kbd_probe,
244+
.remove = apple_mtp_kbd_remove,
245+
.priv_auto = sizeof(struct apple_mtp_kbd_priv),
246+
.ops = &apple_mtp_kbd_ops,
247+
.flags = DM_FLAG_OS_PREPARE,
248+
};
249+
250+
/* Treat dockchannel as a simple-bus, since we don't use the IRQ stuff */
251+
252+
static const struct udevice_id dockchannel_bus_ids[] = {
253+
{ .compatible = "apple,dockchannel" },
254+
{ }
255+
};
256+
257+
U_BOOT_DRIVER(dockchannel) = {
258+
.name = "dockchannel",
259+
.id = UCLASS_SIMPLE_BUS,
260+
.of_match = of_match_ptr(dockchannel_bus_ids),
261+
};

include/configs/apple.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55

66
/* Environment */
77
#define ENV_DEVICE_SETTINGS \
8-
"stdin=serial,usbkbd,spikbd\0" \
8+
"stdin=serial,usbkbd,spikbd,mtpkbd\0" \
99
"stdout=vidconsole,serial\0" \
1010
"stderr=vidconsole,serial\0"
1111

0 commit comments

Comments
 (0)