Mark Kettenis | d42f107 | 2022-01-23 16:48:13 +0100 | [diff] [blame] | 1 | // SPDX-License-Identifier: GPL-2.0+ |
| 2 | /* |
| 3 | * Copyright (C) 2021 Mark Kettenis <kettenis@openbsd.org> |
| 4 | */ |
| 5 | |
| 6 | #include <common.h> |
| 7 | #include <dm.h> |
| 8 | #include <keyboard.h> |
| 9 | #include <spi.h> |
| 10 | #include <stdio_dev.h> |
| 11 | #include <asm-generic/gpio.h> |
| 12 | #include <linux/delay.h> |
| 13 | #include <linux/input.h> |
| 14 | |
| 15 | /* |
| 16 | * The Apple SPI keyboard controller implements a protocol that |
| 17 | * closely resembles HID Keyboard Boot protocol. The key codes are |
| 18 | * mapped according to the HID Keyboard/Keypad Usage Table. |
| 19 | */ |
| 20 | |
| 21 | /* Modifier key bits */ |
| 22 | #define HID_MOD_LEFTCTRL BIT(0) |
| 23 | #define HID_MOD_LEFTSHIFT BIT(1) |
| 24 | #define HID_MOD_LEFTALT BIT(2) |
| 25 | #define HID_MOD_LEFTGUI BIT(3) |
| 26 | #define HID_MOD_RIGHTCTRL BIT(4) |
| 27 | #define HID_MOD_RIGHTSHIFT BIT(5) |
| 28 | #define HID_MOD_RIGHTALT BIT(6) |
| 29 | #define HID_MOD_RIGHTGUI BIT(7) |
| 30 | |
| 31 | static const u8 hid_kbd_keymap[] = { |
| 32 | KEY_RESERVED, 0xff, 0xff, 0xff, |
| 33 | KEY_A, KEY_B, KEY_C, KEY_D, |
| 34 | KEY_E, KEY_F, KEY_G, KEY_H, |
| 35 | KEY_I, KEY_J, KEY_K, KEY_L, |
| 36 | KEY_M, KEY_N, KEY_O, KEY_P, |
| 37 | KEY_Q, KEY_R, KEY_S, KEY_T, |
| 38 | KEY_U, KEY_V, KEY_W, KEY_X, |
| 39 | KEY_Y, KEY_Z, KEY_1, KEY_2, |
| 40 | KEY_3, KEY_4, KEY_5, KEY_6, |
| 41 | KEY_7, KEY_8, KEY_9, KEY_0, |
| 42 | KEY_ENTER, KEY_ESC, KEY_BACKSPACE, KEY_TAB, |
| 43 | KEY_SPACE, KEY_MINUS, KEY_EQUAL, KEY_LEFTBRACE, |
| 44 | KEY_RIGHTBRACE, KEY_BACKSLASH, 0xff, KEY_SEMICOLON, |
| 45 | KEY_APOSTROPHE, KEY_GRAVE, KEY_COMMA, KEY_DOT, |
| 46 | KEY_SLASH, KEY_CAPSLOCK, KEY_F1, KEY_F2, |
| 47 | KEY_F3, KEY_F4, KEY_F5, KEY_F6, |
| 48 | KEY_F7, KEY_F8, KEY_F9, KEY_F10, |
| 49 | KEY_F11, KEY_F12, KEY_SYSRQ, KEY_SCROLLLOCK, |
| 50 | KEY_PAUSE, KEY_INSERT, KEY_HOME, KEY_PAGEUP, |
| 51 | KEY_DELETE, KEY_END, KEY_PAGEDOWN, KEY_RIGHT, |
| 52 | KEY_LEFT, KEY_DOWN, KEY_UP, KEY_NUMLOCK, |
| 53 | KEY_KPSLASH, KEY_KPASTERISK, KEY_KPMINUS, KEY_KPPLUS, |
| 54 | KEY_KPENTER, KEY_KP1, KEY_KP2, KEY_KP3, |
| 55 | KEY_KP4, KEY_KP5, KEY_KP6, KEY_KP7, |
| 56 | KEY_KP8, KEY_KP9, KEY_KP0, KEY_KPDOT, |
| 57 | KEY_BACKSLASH, KEY_COMPOSE, KEY_POWER, KEY_KPEQUAL, |
| 58 | }; |
| 59 | |
| 60 | /* Report ID used for keyboard input reports. */ |
| 61 | #define KBD_REPORTID 0x01 |
| 62 | |
| 63 | struct apple_spi_kbd_report { |
| 64 | u8 reportid; |
| 65 | u8 modifiers; |
| 66 | u8 reserved; |
| 67 | u8 keycode[6]; |
| 68 | u8 fn; |
| 69 | }; |
| 70 | |
| 71 | struct apple_spi_kbd_priv { |
| 72 | struct gpio_desc enable; |
| 73 | struct apple_spi_kbd_report old; /* previous keyboard input report */ |
| 74 | struct apple_spi_kbd_report new; /* current keyboard input report */ |
| 75 | }; |
| 76 | |
| 77 | /* Keyboard device. */ |
| 78 | #define KBD_DEVICE 0x01 |
| 79 | |
| 80 | /* The controller sends us fixed-size packets of 256 bytes. */ |
| 81 | struct apple_spi_kbd_packet { |
| 82 | u8 flags; |
| 83 | #define PACKET_READ 0x20 |
| 84 | u8 device; |
| 85 | u16 offset; |
| 86 | u16 remaining; |
| 87 | u16 len; |
| 88 | u8 data[246]; |
| 89 | u16 crc; |
| 90 | }; |
| 91 | |
| 92 | /* Packets contain a single variable-sized message. */ |
| 93 | struct apple_spi_kbd_msg { |
| 94 | u8 type; |
| 95 | #define MSG_REPORT 0x10 |
| 96 | u8 device; |
| 97 | u8 unknown; |
| 98 | u8 msgid; |
| 99 | u16 rsplen; |
| 100 | u16 cmdlen; |
| 101 | u8 data[0]; |
| 102 | }; |
| 103 | |
| 104 | static void apple_spi_kbd_service_modifiers(struct input_config *input) |
| 105 | { |
| 106 | struct apple_spi_kbd_priv *priv = dev_get_priv(input->dev); |
| 107 | u8 new = priv->new.modifiers; |
| 108 | u8 old = priv->old.modifiers; |
| 109 | |
| 110 | if ((new ^ old) & HID_MOD_LEFTCTRL) |
| 111 | input_add_keycode(input, KEY_LEFTCTRL, |
| 112 | old & HID_MOD_LEFTCTRL); |
| 113 | if ((new ^ old) & HID_MOD_RIGHTCTRL) |
| 114 | input_add_keycode(input, KEY_RIGHTCTRL, |
| 115 | old & HID_MOD_RIGHTCTRL); |
| 116 | if ((new ^ old) & HID_MOD_LEFTSHIFT) |
| 117 | input_add_keycode(input, KEY_LEFTSHIFT, |
| 118 | old & HID_MOD_LEFTSHIFT); |
| 119 | if ((new ^ old) & HID_MOD_RIGHTSHIFT) |
| 120 | input_add_keycode(input, KEY_RIGHTSHIFT, |
| 121 | old & HID_MOD_RIGHTSHIFT); |
| 122 | if ((new ^ old) & HID_MOD_LEFTALT) |
| 123 | input_add_keycode(input, KEY_LEFTALT, |
| 124 | old & HID_MOD_LEFTALT); |
| 125 | if ((new ^ old) & HID_MOD_RIGHTALT) |
| 126 | input_add_keycode(input, KEY_RIGHTALT, |
| 127 | old & HID_MOD_RIGHTALT); |
| 128 | if ((new ^ old) & HID_MOD_LEFTGUI) |
| 129 | input_add_keycode(input, KEY_LEFTMETA, |
| 130 | old & HID_MOD_LEFTGUI); |
| 131 | if ((new ^ old) & HID_MOD_RIGHTGUI) |
| 132 | input_add_keycode(input, KEY_RIGHTMETA, |
| 133 | old & HID_MOD_RIGHTGUI); |
| 134 | } |
| 135 | |
| 136 | static void apple_spi_kbd_service_key(struct input_config *input, int i, |
| 137 | int released) |
| 138 | { |
| 139 | struct apple_spi_kbd_priv *priv = dev_get_priv(input->dev); |
| 140 | u8 *new; |
| 141 | u8 *old; |
| 142 | |
| 143 | if (released) { |
| 144 | new = priv->new.keycode; |
| 145 | old = priv->old.keycode; |
| 146 | } else { |
| 147 | new = priv->old.keycode; |
| 148 | old = priv->new.keycode; |
| 149 | } |
| 150 | |
| 151 | if (memscan(new, old[i], sizeof(priv->new.keycode)) == |
| 152 | new + sizeof(priv->new.keycode) && |
| 153 | old[i] < ARRAY_SIZE(hid_kbd_keymap)) |
| 154 | input_add_keycode(input, hid_kbd_keymap[old[i]], released); |
| 155 | } |
| 156 | |
| 157 | static int apple_spi_kbd_check(struct input_config *input) |
| 158 | { |
| 159 | struct udevice *dev = input->dev; |
| 160 | struct apple_spi_kbd_priv *priv = dev_get_priv(dev); |
| 161 | struct apple_spi_kbd_packet packet; |
| 162 | struct apple_spi_kbd_msg *msg; |
| 163 | struct apple_spi_kbd_report *report; |
| 164 | int i, ret; |
| 165 | |
| 166 | memset(&packet, 0, sizeof(packet)); |
| 167 | |
| 168 | ret = dm_spi_claim_bus(dev); |
| 169 | if (ret < 0) |
| 170 | return ret; |
| 171 | |
| 172 | /* |
| 173 | * The keyboard controller needs delays after asserting CS# |
| 174 | * and before deasserting CS#. |
| 175 | */ |
| 176 | ret = dm_spi_xfer(dev, 0, NULL, NULL, SPI_XFER_BEGIN); |
| 177 | if (ret < 0) |
| 178 | goto fail; |
| 179 | udelay(100); |
| 180 | ret = dm_spi_xfer(dev, sizeof(packet) * 8, NULL, &packet, 0); |
| 181 | if (ret < 0) |
| 182 | goto fail; |
| 183 | udelay(100); |
| 184 | ret = dm_spi_xfer(dev, 0, NULL, NULL, SPI_XFER_END); |
| 185 | if (ret < 0) |
| 186 | goto fail; |
| 187 | |
| 188 | dm_spi_release_bus(dev); |
| 189 | |
| 190 | /* |
| 191 | * The keyboard controller needs a delay between subsequent |
| 192 | * SPI transfers. |
| 193 | */ |
| 194 | udelay(250); |
| 195 | |
| 196 | msg = (struct apple_spi_kbd_msg *)packet.data; |
| 197 | report = (struct apple_spi_kbd_report *)msg->data; |
| 198 | if (packet.flags == PACKET_READ && packet.device == KBD_DEVICE && |
| 199 | msg->type == MSG_REPORT && msg->device == KBD_DEVICE && |
| 200 | msg->cmdlen == sizeof(struct apple_spi_kbd_report) && |
| 201 | report->reportid == KBD_REPORTID) { |
| 202 | memcpy(&priv->new, report, |
| 203 | sizeof(struct apple_spi_kbd_report)); |
| 204 | apple_spi_kbd_service_modifiers(input); |
| 205 | for (i = 0; i < sizeof(priv->new.keycode); i++) { |
| 206 | apple_spi_kbd_service_key(input, i, 1); |
| 207 | apple_spi_kbd_service_key(input, i, 0); |
| 208 | } |
| 209 | memcpy(&priv->old, &priv->new, |
| 210 | sizeof(struct apple_spi_kbd_report)); |
| 211 | return 1; |
| 212 | } |
| 213 | |
| 214 | return 0; |
| 215 | |
| 216 | fail: |
| 217 | /* |
| 218 | * Make sure CS# is deasserted. If this fails there is nothing |
| 219 | * we can do, so ignore any errors. |
| 220 | */ |
| 221 | dm_spi_xfer(dev, 0, NULL, NULL, SPI_XFER_END); |
| 222 | dm_spi_release_bus(dev); |
| 223 | return ret; |
| 224 | } |
| 225 | |
| 226 | static int apple_spi_kbd_probe(struct udevice *dev) |
| 227 | { |
| 228 | struct apple_spi_kbd_priv *priv = dev_get_priv(dev); |
| 229 | struct keyboard_priv *uc_priv = dev_get_uclass_priv(dev); |
| 230 | struct stdio_dev *sdev = &uc_priv->sdev; |
| 231 | struct input_config *input = &uc_priv->input; |
| 232 | int ret; |
| 233 | |
| 234 | ret = gpio_request_by_name(dev, "spien-gpios", 0, &priv->enable, |
| 235 | GPIOD_IS_OUT); |
| 236 | if (ret < 0) |
| 237 | return ret; |
| 238 | |
| 239 | /* Reset the keyboard controller. */ |
| 240 | dm_gpio_set_value(&priv->enable, 1); |
| 241 | udelay(5000); |
| 242 | dm_gpio_set_value(&priv->enable, 0); |
| 243 | udelay(5000); |
| 244 | |
| 245 | /* Enable the keyboard controller. */ |
| 246 | dm_gpio_set_value(&priv->enable, 1); |
| 247 | |
| 248 | input->dev = dev; |
| 249 | input->read_keys = apple_spi_kbd_check; |
| 250 | input_add_tables(input, false); |
| 251 | strcpy(sdev->name, "spikbd"); |
| 252 | |
| 253 | return input_stdio_register(sdev); |
| 254 | } |
| 255 | |
| 256 | static const struct keyboard_ops apple_spi_kbd_ops = { |
| 257 | }; |
| 258 | |
| 259 | static const struct udevice_id apple_spi_kbd_of_match[] = { |
| 260 | { .compatible = "apple,spi-hid-transport" }, |
| 261 | { /* sentinel */ } |
| 262 | }; |
| 263 | |
| 264 | U_BOOT_DRIVER(apple_spi_kbd) = { |
| 265 | .name = "apple_spi_kbd", |
| 266 | .id = UCLASS_KEYBOARD, |
| 267 | .of_match = apple_spi_kbd_of_match, |
| 268 | .probe = apple_spi_kbd_probe, |
| 269 | .priv_auto = sizeof(struct apple_spi_kbd_priv), |
| 270 | .ops = &apple_spi_kbd_ops, |
| 271 | }; |