| // SPDX-License-Identifier: GPL-2.0+ | |
| /* | |
| * Copyright (C) 2021 Mark Kettenis <kettenis@openbsd.org> | |
| */ | |
| #include <common.h> | |
| #include <dm.h> | |
| #include <keyboard.h> | |
| #include <spi.h> | |
| #include <stdio_dev.h> | |
| #include <asm-generic/gpio.h> | |
| #include <linux/delay.h> | |
| #include <linux/input.h> | |
| /* | |
| * 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, | |
| }; |