// SPDX-License-Identifier: GPL-2.0+ /* * Copyright (C) 2021 Mark Kettenis */ #include #include #include #include #include #include #include #include /* * The Apple SPI keyboard controller implements a protocol that * closely resembles HID Keyboard Boot protocol. The key codes are * mapped according to the HID Keyboard/Keypad Usage Table. */ /* Modifier key bits */ #define HID_MOD_LEFTCTRL BIT(0) #define HID_MOD_LEFTSHIFT BIT(1) #define HID_MOD_LEFTALT BIT(2) #define HID_MOD_LEFTGUI BIT(3) #define HID_MOD_RIGHTCTRL BIT(4) #define HID_MOD_RIGHTSHIFT BIT(5) #define HID_MOD_RIGHTALT BIT(6) #define HID_MOD_RIGHTGUI BIT(7) static const u8 hid_kbd_keymap[] = { KEY_RESERVED, 0xff, 0xff, 0xff, KEY_A, KEY_B, KEY_C, KEY_D, KEY_E, KEY_F, KEY_G, KEY_H, KEY_I, KEY_J, KEY_K, KEY_L, KEY_M, KEY_N, KEY_O, KEY_P, KEY_Q, KEY_R, KEY_S, KEY_T, KEY_U, KEY_V, KEY_W, KEY_X, KEY_Y, KEY_Z, KEY_1, KEY_2, KEY_3, KEY_4, KEY_5, KEY_6, KEY_7, KEY_8, KEY_9, KEY_0, KEY_ENTER, KEY_ESC, KEY_BACKSPACE, KEY_TAB, KEY_SPACE, KEY_MINUS, KEY_EQUAL, KEY_LEFTBRACE, KEY_RIGHTBRACE, KEY_BACKSLASH, 0xff, KEY_SEMICOLON, KEY_APOSTROPHE, KEY_GRAVE, KEY_COMMA, KEY_DOT, KEY_SLASH, KEY_CAPSLOCK, KEY_F1, KEY_F2, KEY_F3, KEY_F4, KEY_F5, KEY_F6, KEY_F7, KEY_F8, KEY_F9, KEY_F10, KEY_F11, KEY_F12, KEY_SYSRQ, KEY_SCROLLLOCK, KEY_PAUSE, KEY_INSERT, KEY_HOME, KEY_PAGEUP, KEY_DELETE, KEY_END, KEY_PAGEDOWN, KEY_RIGHT, KEY_LEFT, KEY_DOWN, KEY_UP, KEY_NUMLOCK, KEY_KPSLASH, KEY_KPASTERISK, KEY_KPMINUS, KEY_KPPLUS, KEY_KPENTER, KEY_KP1, KEY_KP2, KEY_KP3, KEY_KP4, KEY_KP5, KEY_KP6, KEY_KP7, KEY_KP8, KEY_KP9, KEY_KP0, KEY_KPDOT, KEY_BACKSLASH, KEY_COMPOSE, KEY_POWER, KEY_KPEQUAL, }; /* Report ID used for keyboard input reports. */ #define KBD_REPORTID 0x01 struct apple_spi_kbd_report { u8 reportid; u8 modifiers; u8 reserved; u8 keycode[6]; u8 fn; }; struct apple_spi_kbd_priv { struct gpio_desc enable; struct apple_spi_kbd_report old; /* previous keyboard input report */ struct apple_spi_kbd_report new; /* current keyboard input report */ }; /* Keyboard device. */ #define KBD_DEVICE 0x01 /* The controller sends us fixed-size packets of 256 bytes. */ struct apple_spi_kbd_packet { u8 flags; #define PACKET_READ 0x20 u8 device; u16 offset; u16 remaining; u16 len; u8 data[246]; u16 crc; }; /* Packets contain a single variable-sized message. */ struct apple_spi_kbd_msg { u8 type; #define MSG_REPORT 0x10 u8 device; u8 unknown; u8 msgid; u16 rsplen; u16 cmdlen; u8 data[0]; }; static void apple_spi_kbd_service_modifiers(struct input_config *input) { struct apple_spi_kbd_priv *priv = dev_get_priv(input->dev); u8 new = priv->new.modifiers; u8 old = priv->old.modifiers; if ((new ^ old) & HID_MOD_LEFTCTRL) input_add_keycode(input, KEY_LEFTCTRL, old & HID_MOD_LEFTCTRL); if ((new ^ old) & HID_MOD_RIGHTCTRL) input_add_keycode(input, KEY_RIGHTCTRL, old & HID_MOD_RIGHTCTRL); if ((new ^ old) & HID_MOD_LEFTSHIFT) input_add_keycode(input, KEY_LEFTSHIFT, old & HID_MOD_LEFTSHIFT); if ((new ^ old) & HID_MOD_RIGHTSHIFT) input_add_keycode(input, KEY_RIGHTSHIFT, old & HID_MOD_RIGHTSHIFT); if ((new ^ old) & HID_MOD_LEFTALT) input_add_keycode(input, KEY_LEFTALT, old & HID_MOD_LEFTALT); if ((new ^ old) & HID_MOD_RIGHTALT) input_add_keycode(input, KEY_RIGHTALT, old & HID_MOD_RIGHTALT); if ((new ^ old) & HID_MOD_LEFTGUI) input_add_keycode(input, KEY_LEFTMETA, old & HID_MOD_LEFTGUI); if ((new ^ old) & HID_MOD_RIGHTGUI) input_add_keycode(input, KEY_RIGHTMETA, old & HID_MOD_RIGHTGUI); } static void apple_spi_kbd_service_key(struct input_config *input, int i, int released) { struct apple_spi_kbd_priv *priv = dev_get_priv(input->dev); u8 *new; u8 *old; if (released) { new = priv->new.keycode; old = priv->old.keycode; } else { new = priv->old.keycode; old = priv->new.keycode; } if (memscan(new, old[i], sizeof(priv->new.keycode)) == new + sizeof(priv->new.keycode) && old[i] < ARRAY_SIZE(hid_kbd_keymap)) input_add_keycode(input, hid_kbd_keymap[old[i]], released); } static int apple_spi_kbd_check(struct input_config *input) { struct udevice *dev = input->dev; struct apple_spi_kbd_priv *priv = dev_get_priv(dev); struct apple_spi_kbd_packet packet; struct apple_spi_kbd_msg *msg; struct apple_spi_kbd_report *report; int i, ret; memset(&packet, 0, sizeof(packet)); ret = dm_spi_claim_bus(dev); if (ret < 0) return ret; /* * The keyboard controller needs delays after asserting CS# * and before deasserting CS#. */ ret = dm_spi_xfer(dev, 0, NULL, NULL, SPI_XFER_BEGIN); if (ret < 0) goto fail; udelay(100); ret = dm_spi_xfer(dev, sizeof(packet) * 8, NULL, &packet, 0); if (ret < 0) goto fail; udelay(100); ret = dm_spi_xfer(dev, 0, NULL, NULL, SPI_XFER_END); if (ret < 0) goto fail; dm_spi_release_bus(dev); /* * The keyboard controller needs a delay between subsequent * SPI transfers. */ udelay(250); msg = (struct apple_spi_kbd_msg *)packet.data; report = (struct apple_spi_kbd_report *)msg->data; if (packet.flags == PACKET_READ && packet.device == KBD_DEVICE && msg->type == MSG_REPORT && msg->device == KBD_DEVICE && msg->cmdlen == sizeof(struct apple_spi_kbd_report) && report->reportid == KBD_REPORTID) { memcpy(&priv->new, report, sizeof(struct apple_spi_kbd_report)); apple_spi_kbd_service_modifiers(input); for (i = 0; i < sizeof(priv->new.keycode); i++) { apple_spi_kbd_service_key(input, i, 1); apple_spi_kbd_service_key(input, i, 0); } memcpy(&priv->old, &priv->new, sizeof(struct apple_spi_kbd_report)); return 1; } return 0; fail: /* * Make sure CS# is deasserted. If this fails there is nothing * we can do, so ignore any errors. */ dm_spi_xfer(dev, 0, NULL, NULL, SPI_XFER_END); dm_spi_release_bus(dev); return ret; } static int apple_spi_kbd_probe(struct udevice *dev) { struct apple_spi_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; ret = gpio_request_by_name(dev, "spien-gpios", 0, &priv->enable, GPIOD_IS_OUT); if (ret < 0) return ret; /* Reset the keyboard controller. */ dm_gpio_set_value(&priv->enable, 1); udelay(5000); dm_gpio_set_value(&priv->enable, 0); udelay(5000); /* Enable the keyboard controller. */ dm_gpio_set_value(&priv->enable, 1); input->dev = dev; input->read_keys = apple_spi_kbd_check; input_add_tables(input, false); strcpy(sdev->name, "spikbd"); return input_stdio_register(sdev); } static const struct keyboard_ops apple_spi_kbd_ops = { }; static const struct udevice_id apple_spi_kbd_of_match[] = { { .compatible = "apple,spi-hid-transport" }, { /* sentinel */ } }; U_BOOT_DRIVER(apple_spi_kbd) = { .name = "apple_spi_kbd", .id = UCLASS_KEYBOARD, .of_match = apple_spi_kbd_of_match, .probe = apple_spi_kbd_probe, .priv_auto = sizeof(struct apple_spi_kbd_priv), .ops = &apple_spi_kbd_ops, };