From c2d3d5f69fead8d660413b4c105522975882aa52 Mon Sep 17 00:00:00 2001 From: Hector Martin Date: Mon, 1 Aug 2022 22:14:12 +0900 Subject: [PATCH] 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 --- drivers/input/Kconfig | 9 ++ drivers/input/Makefile | 1 + drivers/input/apple_mtp_kbd.c | 261 ++++++++++++++++++++++++++++++++++ include/configs/apple.h | 2 +- 4 files changed, 272 insertions(+), 1 deletion(-) create mode 100644 drivers/input/apple_mtp_kbd.c diff --git a/drivers/input/Kconfig b/drivers/input/Kconfig index 8c708ca4c3..41ad04e132 100644 --- a/drivers/input/Kconfig +++ b/drivers/input/Kconfig @@ -60,6 +60,15 @@ config BUTTON_KEYBOARD dt node to define button-event mapping. For example, an arrows and enter may be implemented to navigate boot menu. +config APPLE_MTP_KEYB + bool "Enable Apple MTP keyboard support" + select APPLE_KEYB + depends on DM_KEYBOARD && MISC + help + This adds a driver for the keyboards found on various + laptops based on Apple M2 and newer SoCs. These keyboards use an + Apple-specific HID-over-DockChannel protocol. + config CROS_EC_KEYB bool "Enable Chrome OS EC keyboard support" depends on INPUT diff --git a/drivers/input/Makefile b/drivers/input/Makefile index 8100d0c098..a8dcd5a90f 100644 --- a/drivers/input/Makefile +++ b/drivers/input/Makefile @@ -12,6 +12,7 @@ ifndef CONFIG_SPL_BUILD obj-$(CONFIG_APPLE_KEYB) += apple_kbd.o obj-$(CONFIG_APPLE_SPI_KEYB) += apple_spi_kbd.o +obj-$(CONFIG_APPLE_MTP_KEYB) += apple_mtp_kbd.o obj-$(CONFIG_I8042_KEYB) += i8042.o obj-$(CONFIG_TEGRA_KEYBOARD) += input.o tegra-kbc.o obj-$(CONFIG_TWL4030_INPUT) += twl4030.o diff --git a/drivers/input/apple_mtp_kbd.c b/drivers/input/apple_mtp_kbd.c new file mode 100644 index 0000000000..e80e9a01c6 --- /dev/null +++ b/drivers/input/apple_mtp_kbd.c @@ -0,0 +1,261 @@ +// SPDX-License-Identifier: GPL-2.0+ OR MIT +/* + * Copyright The Asahi Linux Contributors + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "apple_kbd.h" + +struct apple_mtp_kbd_priv { + struct apple_kbd_priv kbd; + struct udevice *helper; + void *local; + void *rmt; + + u8 *init_data; + u32 init_size; + u32 fifo_size; +}; + +#define DATA_TX8 0x4 +#define DATA_TX_FREE 0x14 +#define DATA_RX8 0x1c +#define DATA_RX_COUNT 0x2c + +struct dchid_hdr { + u8 hdr_len; + u8 channel; + __le16 length; + u8 seq; + u8 iface; + __le16 pad; +} __packed; + +static int dockchannel_read(struct udevice *dev, void *buf, size_t size) +{ + struct apple_mtp_kbd_priv *priv = dev_get_priv(dev); + int ret = 0; + u8 b; + u8 *p = buf; + + while (size--) { + ulong start; + start = get_timer(0); + while (get_timer(start) < 100) { + if (readl(priv->local + DATA_RX_COUNT) != 0) + break; + } + + if (readl(priv->local + DATA_RX_COUNT) == 0) { + return -ETIME; + } + + b = readl(priv->local + DATA_RX8) >> 8; + if (buf) + *p++ = b; + + ret++; + } + + return ret; +} + +static int apple_mtp_kbd_check(struct input_config *input) +{ + struct udevice *dev = input->dev; + struct apple_mtp_kbd_priv *priv = dev_get_priv(dev); + struct dchid_hdr hdr; + int ret; + + /* Poll for syslogs if RTKit is up */ + if (priv->helper) + apple_rtkit_helper_poll(priv->helper, 0); + + u32 pending = readl(priv->local + DATA_RX_COUNT); + if (pending < 8) + return 0; + + ret = dockchannel_read(dev, &hdr, sizeof(hdr)); + if (ret < 0) { + dev_err(dev, "failed to read packet header\n"); + return ret; + } + + /* Save comm init messages for the next stage */ + if (hdr.iface == 0) { + int space = priv->fifo_size - priv->init_size; + int need = hdr.length + sizeof(hdr) + 4; + + if (space < need) { + dev_err(dev, "out of buf space (%d > %d)\n", + need, space); + ret = dockchannel_read(dev, NULL, hdr.length + 4); + if (ret < 0) + return ret; + } else { + u8 *p = &priv->init_data[priv->init_size]; + + memcpy(p, &hdr, sizeof(hdr)); + ret = dockchannel_read(dev, p + sizeof(hdr), hdr.length + 4); + if (ret < 0) + return ret; + priv->init_size += need; + } + } else if (hdr.channel == 0x12 && hdr.length == 0x14) { + u8 buf[0x18]; + + ret = dockchannel_read(dev, buf, 0x18); + if (ret < 0) + return ret; + + if (!priv->helper) + return 1; /* Ignore if shutting down */ + + /* Just assume it's a keyboard report */ + return apple_kbd_handle_report(input, &priv->kbd, buf + 8, 0xc); + } else { + printk("mtp: unknown message ch=%02x l=%04x if=%02x\n", + hdr.channel, hdr.length, hdr.iface); + ret = dockchannel_read(dev, NULL, hdr.length + 4); + if (ret < 0) + return ret; + } + + return 0; +} + +static int get_rtkit_helper(struct udevice *dev) +{ + struct apple_mtp_kbd_priv *priv = dev_get_priv(dev); + int ret; + u32 phandle; + ofnode of_mtp; + + ret = dev_read_u32(dev, "apple,helper-cpu", &phandle); + if (ret < 0) + return ret; + + of_mtp = ofnode_get_by_phandle(phandle); + ret = uclass_get_device_by_ofnode(UCLASS_MISC, of_mtp, &priv->helper); + if (ret < 0) + return ret; + + return 0; +} + +static int apple_mtp_kbd_probe(struct udevice *dev) +{ + struct apple_mtp_kbd_priv *priv = dev_get_priv(dev); + struct keyboard_priv *uc_priv = dev_get_uclass_priv(dev); + struct stdio_dev *sdev = &uc_priv->sdev; + struct input_config *input = &uc_priv->input; + int ret; + fdt_addr_t reg; + + printf("mtp_kbd_probe\n"); + + reg = dev_read_addr_name(dev, "data"); + if (reg == FDT_ADDR_T_NONE) { + dev_err(dev, "no reg property for local FIFO data registers\n"); + return -EINVAL; + } + priv->local = (void *)reg; + + reg = dev_read_addr_name(dev, "rmt-data"); + if (reg == FDT_ADDR_T_NONE) { + dev_err(dev, "no reg property for remote FIFO data registers\n"); + return -EINVAL; + } + priv->rmt = (void *)reg; + + ret = dev_read_u32(dev, "apple,fifo-size", &priv->fifo_size); + if (ret < 0 || !priv->fifo_size) { + dev_err(dev, "no apple,fifo-size property\n"); + return ret; + } + + ret = get_rtkit_helper(dev); + if (ret < 0) { + dev_err(dev, "Failed to get helper device (%d)\n", ret); + return ret; + } + + priv->init_data = malloc(priv->fifo_size); + if (!priv->init_data) + return -ENOMEM; + + input->dev = dev; + input->read_keys = apple_mtp_kbd_check; + input_add_tables(input, false); + strcpy(sdev->name, "mtpkbd"); + + return input_stdio_register(sdev); +} + +static int apple_mtp_kbd_remove(struct udevice *dev) +{ + struct apple_mtp_kbd_priv *priv = dev_get_priv(dev); + struct keyboard_priv *uc_priv = dev_get_uclass_priv(dev); + struct input_config *input = &uc_priv->input; + int i; + + if (priv->helper) { + device_remove(priv->helper, DM_REMOVE_NORMAL); + priv->helper = NULL; + } + + /* Drain the FIFO */ + while (readl(priv->local + DATA_RX_COUNT)) { + if (apple_mtp_kbd_check(input) < 0) { + dev_err(dev, "Failed to drain FIFO\n"); + break; + } + } + + /* Stuff init messages back into FIFO for the next stage to find */ + for (i = 0; i < priv->init_size; i++) + writel(priv->init_data[i], priv->rmt + DATA_TX8); + + return 0; +} + +static const struct keyboard_ops apple_mtp_kbd_ops = { +}; + +static const struct udevice_id apple_mtp_kbd_of_match[] = { + { .compatible = "apple,dockchannel-hid" }, + { /* sentinel */ } +}; + +U_BOOT_DRIVER(apple_mtp_kbd) = { + .name = "apple_mtp_kbd", + .id = UCLASS_KEYBOARD, + .of_match = apple_mtp_kbd_of_match, + .probe = apple_mtp_kbd_probe, + .remove = apple_mtp_kbd_remove, + .priv_auto = sizeof(struct apple_mtp_kbd_priv), + .ops = &apple_mtp_kbd_ops, + .flags = DM_FLAG_OS_PREPARE, +}; + +/* Treat dockchannel as a simple-bus, since we don't use the IRQ stuff */ + +static const struct udevice_id dockchannel_bus_ids[] = { + { .compatible = "apple,dockchannel" }, + { } +}; + +U_BOOT_DRIVER(dockchannel) = { + .name = "dockchannel", + .id = UCLASS_SIMPLE_BUS, + .of_match = of_match_ptr(dockchannel_bus_ids), +}; diff --git a/include/configs/apple.h b/include/configs/apple.h index 1e08b11448..52513a2f49 100644 --- a/include/configs/apple.h +++ b/include/configs/apple.h @@ -5,7 +5,7 @@ /* Environment */ #define ENV_DEVICE_SETTINGS \ - "stdin=serial,usbkbd,spikbd\0" \ + "stdin=serial,usbkbd,spikbd,mtpkbd\0" \ "stdout=vidconsole,serial\0" \ "stderr=vidconsole,serial\0"