diff options
Diffstat (limited to 'keyboards/system76/system76_ec.c')
| -rw-r--r-- | keyboards/system76/system76_ec.c | 416 |
1 files changed, 416 insertions, 0 deletions
diff --git a/keyboards/system76/system76_ec.c b/keyboards/system76/system76_ec.c new file mode 100644 index 000000000..7fff780e5 --- /dev/null +++ b/keyboards/system76/system76_ec.c | |||
| @@ -0,0 +1,416 @@ | |||
| 1 | /* | ||
| 2 | * Copyright (C) 2021 System76 | ||
| 3 | * Copyright (C) 2021 Jimmy Cassis <KernelOops@outlook.com> | ||
| 4 | * | ||
| 5 | * This program is free software: you can redistribute it and/or modify | ||
| 6 | * it under the terms of the GNU General Public License as published by | ||
| 7 | * the Free Software Foundation, either version 3 of the License, or | ||
| 8 | * (at your option) any later version. | ||
| 9 | * | ||
| 10 | * This program is distributed in the hope that it will be useful, | ||
| 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
| 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
| 13 | * GNU General Public License for more details. | ||
| 14 | * | ||
| 15 | * You should have received a copy of the GNU General Public License | ||
| 16 | * along with this program. If not, see <https://www.gnu.org/licenses/>. | ||
| 17 | */ | ||
| 18 | |||
| 19 | #include <string.h> | ||
| 20 | |||
| 21 | #include "dynamic_keymap.h" | ||
| 22 | #include "raw_hid.h" | ||
| 23 | #include "rgb_matrix.h" | ||
| 24 | #include "version.h" | ||
| 25 | |||
| 26 | enum Command { | ||
| 27 | CMD_PROBE = 1, // Probe for System76 EC protocol | ||
| 28 | CMD_BOARD = 2, // Read board string | ||
| 29 | CMD_VERSION = 3, // Read version string | ||
| 30 | CMD_RESET = 6, // Reset to bootloader | ||
| 31 | CMD_KEYMAP_GET = 9, // Get keyboard map index | ||
| 32 | CMD_KEYMAP_SET = 10, // Set keyboard map index | ||
| 33 | CMD_LED_GET_VALUE = 11, // Get LED value by index | ||
| 34 | CMD_LED_SET_VALUE = 12, // Set LED value by index | ||
| 35 | CMD_LED_GET_COLOR = 13, // Get LED color by index | ||
| 36 | CMD_LED_SET_COLOR = 14, // Set LED color by index | ||
| 37 | CMD_LED_GET_MODE = 15, // Get LED matrix mode and speed | ||
| 38 | CMD_LED_SET_MODE = 16, // Set LED matrix mode and speed | ||
| 39 | CMD_MATRIX_GET = 17, // Get currently pressed keys | ||
| 40 | CMD_LED_SAVE = 18, // Save LED settings to ROM | ||
| 41 | CMD_SET_NO_INPUT = 19, // Enable/disable no input mode | ||
| 42 | }; | ||
| 43 | |||
| 44 | bool input_disabled = false; | ||
| 45 | |||
| 46 | #define CMD_LED_INDEX_ALL 0xFF | ||
| 47 | |||
| 48 | static bool keymap_get(uint8_t layer, uint8_t output, uint8_t input, uint16_t *value) { | ||
| 49 | if (layer < dynamic_keymap_get_layer_count()) { | ||
| 50 | if (output < MATRIX_ROWS) { | ||
| 51 | if (input < MATRIX_COLS) { | ||
| 52 | *value = dynamic_keymap_get_keycode(layer, output, input); | ||
| 53 | return true; | ||
| 54 | } | ||
| 55 | } | ||
| 56 | } | ||
| 57 | return false; | ||
| 58 | } | ||
| 59 | |||
| 60 | static bool keymap_set(uint8_t layer, uint8_t output, uint8_t input, uint16_t value) { | ||
| 61 | if (layer < dynamic_keymap_get_layer_count()) { | ||
| 62 | if (output < MATRIX_ROWS) { | ||
| 63 | if (input < MATRIX_COLS) { | ||
| 64 | dynamic_keymap_set_keycode(layer, output, input, value); | ||
| 65 | return true; | ||
| 66 | } | ||
| 67 | } | ||
| 68 | } | ||
| 69 | return false; | ||
| 70 | } | ||
| 71 | |||
| 72 | static bool bootloader_reset = false; | ||
| 73 | static bool bootloader_unlocked = false; | ||
| 74 | |||
| 75 | void system76_ec_unlock(void) { | ||
| 76 | #ifdef RGB_MATRIX_CUSTOM_KB | ||
| 77 | rgb_matrix_mode_noeeprom(RGB_MATRIX_CUSTOM_unlocked); | ||
| 78 | #endif | ||
| 79 | #ifdef SYSTEM76_EC | ||
| 80 | bootloader_unlocked = true; | ||
| 81 | #endif | ||
| 82 | } | ||
| 83 | |||
| 84 | bool system76_ec_is_unlocked(void) { return bootloader_unlocked; } | ||
| 85 | |||
| 86 | #ifdef RGB_MATRIX_CUSTOM_KB | ||
| 87 | enum Mode { | ||
| 88 | MODE_SOLID_COLOR = 0, | ||
| 89 | MODE_PER_KEY, | ||
| 90 | MODE_CYCLE_ALL, | ||
| 91 | MODE_CYCLE_LEFT_RIGHT, | ||
| 92 | MODE_CYCLE_UP_DOWN, | ||
| 93 | MODE_CYCLE_OUT_IN, | ||
| 94 | MODE_CYCLE_OUT_IN_DUAL, | ||
| 95 | MODE_RAINBOW_MOVING_CHEVRON, | ||
| 96 | MODE_CYCLE_PINWHEEL, | ||
| 97 | MODE_CYCLE_SPIRAL, | ||
| 98 | MODE_RAINDROPS, | ||
| 99 | MODE_SPLASH, | ||
| 100 | MODE_MULTISPLASH, | ||
| 101 | MODE_ACTIVE_KEYS, | ||
| 102 | MODE_DISABLED, | ||
| 103 | MODE_LAST, | ||
| 104 | }; | ||
| 105 | |||
| 106 | // clang-format off | ||
| 107 | static enum rgb_matrix_effects mode_map[] = { | ||
| 108 | RGB_MATRIX_SOLID_COLOR, | ||
| 109 | RGB_MATRIX_CUSTOM_raw_rgb, | ||
| 110 | RGB_MATRIX_CYCLE_ALL, | ||
| 111 | RGB_MATRIX_CYCLE_LEFT_RIGHT, | ||
| 112 | RGB_MATRIX_CYCLE_UP_DOWN, | ||
| 113 | RGB_MATRIX_CYCLE_OUT_IN, | ||
| 114 | RGB_MATRIX_CYCLE_OUT_IN_DUAL, | ||
| 115 | RGB_MATRIX_RAINBOW_MOVING_CHEVRON, | ||
| 116 | RGB_MATRIX_CYCLE_PINWHEEL, | ||
| 117 | RGB_MATRIX_CYCLE_SPIRAL, | ||
| 118 | RGB_MATRIX_RAINDROPS, | ||
| 119 | RGB_MATRIX_SPLASH, | ||
| 120 | RGB_MATRIX_MULTISPLASH, | ||
| 121 | RGB_MATRIX_CUSTOM_active_keys, | ||
| 122 | RGB_MATRIX_NONE, | ||
| 123 | }; | ||
| 124 | // clang-format on | ||
| 125 | |||
| 126 | _Static_assert(sizeof(mode_map) == MODE_LAST, "mode_map_length"); | ||
| 127 | |||
| 128 | RGB raw_rgb_data[DRIVER_LED_TOTAL]; | ||
| 129 | |||
| 130 | // clang-format off | ||
| 131 | rgb_config_t layer_rgb[DYNAMIC_KEYMAP_LAYER_COUNT] = { | ||
| 132 | // Layer 0 | ||
| 133 | { | ||
| 134 | .enable = 1, | ||
| 135 | .mode = RGB_MATRIX_STARTUP_MODE, | ||
| 136 | .hsv = { | ||
| 137 | .h = RGB_MATRIX_STARTUP_HUE, | ||
| 138 | .s = RGB_MATRIX_STARTUP_SAT, | ||
| 139 | .v = RGB_MATRIX_STARTUP_VAL, | ||
| 140 | }, | ||
| 141 | .speed = RGB_MATRIX_STARTUP_SPD, | ||
| 142 | .flags = LED_FLAG_KEYLIGHT, | ||
| 143 | }, | ||
| 144 | // Layer 1 | ||
| 145 | { | ||
| 146 | .enable = 1, | ||
| 147 | .mode = RGB_MATRIX_CUSTOM_active_keys, | ||
| 148 | .hsv = { | ||
| 149 | .h = RGB_MATRIX_STARTUP_HUE, | ||
| 150 | .s = RGB_MATRIX_STARTUP_SAT, | ||
| 151 | .v = RGB_MATRIX_STARTUP_VAL, | ||
| 152 | }, | ||
| 153 | .speed = RGB_MATRIX_STARTUP_SPD, | ||
| 154 | .flags = LED_FLAG_KEYLIGHT, | ||
| 155 | }, | ||
| 156 | // Layer 2 | ||
| 157 | { | ||
| 158 | .enable = 1, | ||
| 159 | .mode = RGB_MATRIX_CUSTOM_active_keys, | ||
| 160 | .hsv = { | ||
| 161 | .h = RGB_MATRIX_STARTUP_HUE, | ||
| 162 | .s = RGB_MATRIX_STARTUP_SAT, | ||
| 163 | .v = RGB_MATRIX_STARTUP_VAL, | ||
| 164 | }, | ||
| 165 | .speed = RGB_MATRIX_STARTUP_SPD, | ||
| 166 | .flags = LED_FLAG_KEYLIGHT, | ||
| 167 | }, | ||
| 168 | // Layer 3 | ||
| 169 | { | ||
| 170 | .enable = 1, | ||
| 171 | .mode = RGB_MATRIX_CUSTOM_active_keys, | ||
| 172 | .hsv = { | ||
| 173 | .h = RGB_MATRIX_STARTUP_HUE, | ||
| 174 | .s = RGB_MATRIX_STARTUP_SAT, | ||
| 175 | .v = RGB_MATRIX_STARTUP_VAL, | ||
| 176 | }, | ||
| 177 | .speed = RGB_MATRIX_STARTUP_SPD, | ||
| 178 | .flags = LED_FLAG_KEYLIGHT, | ||
| 179 | }, | ||
| 180 | }; | ||
| 181 | // clang-format on | ||
| 182 | |||
| 183 | // Read or write EEPROM data with checks for being inside System76 EC region. | ||
| 184 | static bool system76_ec_eeprom_op(void *buf, uint16_t size, uint16_t offset, bool write) { | ||
| 185 | uint16_t addr = SYSTEM76_EC_EEPROM_ADDR + offset; | ||
| 186 | uint16_t end = addr + size; | ||
| 187 | // Check for overflow and zero size | ||
| 188 | if ((end > addr) && (addr >= SYSTEM76_EC_EEPROM_ADDR) && (end <= (SYSTEM76_EC_EEPROM_ADDR + SYSTEM76_EC_EEPROM_SIZE))) { | ||
| 189 | if (write) { | ||
| 190 | eeprom_update_block((const void *)buf, (void *)addr, size); | ||
| 191 | } else { | ||
| 192 | eeprom_read_block((void *)buf, (const void *)addr, size); | ||
| 193 | } | ||
| 194 | return true; | ||
| 195 | } else { | ||
| 196 | return false; | ||
| 197 | } | ||
| 198 | } | ||
| 199 | |||
| 200 | // Read or write EEPROM RGB parameters. | ||
| 201 | void system76_ec_rgb_eeprom(bool write) { | ||
| 202 | uint16_t layer_rgb_size = sizeof(layer_rgb); | ||
| 203 | system76_ec_eeprom_op((void *)layer_rgb, layer_rgb_size, 0, write); | ||
| 204 | system76_ec_eeprom_op((void *)raw_rgb_data, sizeof(raw_rgb_data), layer_rgb_size, write); | ||
| 205 | } | ||
| 206 | |||
| 207 | // Update RGB parameters on layer change. | ||
| 208 | void system76_ec_rgb_layer(layer_state_t layer_state) { | ||
| 209 | if (!bootloader_unlocked) { | ||
| 210 | uint8_t layer = get_highest_layer(layer_state); | ||
| 211 | if (layer < DYNAMIC_KEYMAP_LAYER_COUNT) { | ||
| 212 | rgb_matrix_config = layer_rgb[layer]; | ||
| 213 | } | ||
| 214 | } | ||
| 215 | } | ||
| 216 | #endif // RGB_MATRIX_CUSTOM_KB | ||
| 217 | |||
| 218 | void raw_hid_receive(uint8_t *data, uint8_t length) { | ||
| 219 | // Error response by default, set to success by commands | ||
| 220 | data[1] = 1; | ||
| 221 | |||
| 222 | switch (data[0]) { | ||
| 223 | case CMD_PROBE: | ||
| 224 | // Signature | ||
| 225 | data[2] = 0x76; | ||
| 226 | data[3] = 0xEC; | ||
| 227 | // Version | ||
| 228 | data[4] = 0x01; | ||
| 229 | data[1] = 0; | ||
| 230 | break; | ||
| 231 | case CMD_BOARD: | ||
| 232 | strncpy((char *)&data[2], QMK_KEYBOARD, length - 2); | ||
| 233 | data[1] = 0; | ||
| 234 | break; | ||
| 235 | case CMD_VERSION: | ||
| 236 | strncpy((char *)&data[2], QMK_VERSION, length - 2); | ||
| 237 | data[1] = 0; | ||
| 238 | break; | ||
| 239 | case CMD_RESET: | ||
| 240 | if (bootloader_unlocked) { | ||
| 241 | data[1] = 0; | ||
| 242 | bootloader_reset = true; | ||
| 243 | } | ||
| 244 | break; | ||
| 245 | case CMD_KEYMAP_GET: { | ||
| 246 | uint16_t value = 0; | ||
| 247 | if (keymap_get(data[2], data[3], data[4], &value)) { | ||
| 248 | data[5] = (uint8_t)value; | ||
| 249 | data[6] = (uint8_t)(value >> 8); | ||
| 250 | data[1] = 0; | ||
| 251 | } | ||
| 252 | } break; | ||
| 253 | case CMD_KEYMAP_SET: { | ||
| 254 | uint16_t value = ((uint16_t)data[5]) | (((uint16_t)data[6]) << 8); | ||
| 255 | if (keymap_set(data[2], data[3], data[4], value)) { | ||
| 256 | data[1] = 0; | ||
| 257 | } | ||
| 258 | } break; | ||
| 259 | #ifdef RGB_MATRIX_CUSTOM_KB | ||
| 260 | case CMD_LED_GET_VALUE: | ||
| 261 | if (!bootloader_unlocked) { | ||
| 262 | uint8_t index = data[2]; | ||
| 263 | for (uint8_t layer = 0; layer < DYNAMIC_KEYMAP_LAYER_COUNT; layer++) { | ||
| 264 | if (index == (0xF0 | layer)) { | ||
| 265 | data[3] = layer_rgb[layer].hsv.v; | ||
| 266 | data[4] = RGB_MATRIX_MAXIMUM_BRIGHTNESS; | ||
| 267 | data[1] = 0; | ||
| 268 | break; | ||
| 269 | } | ||
| 270 | } | ||
| 271 | } | ||
| 272 | break; | ||
| 273 | case CMD_LED_SET_VALUE: | ||
| 274 | if (!bootloader_unlocked) { | ||
| 275 | uint8_t index = data[2]; | ||
| 276 | uint8_t value = data[3]; | ||
| 277 | if (value >= RGB_MATRIX_MAXIMUM_BRIGHTNESS) { | ||
| 278 | value = RGB_MATRIX_MAXIMUM_BRIGHTNESS; | ||
| 279 | } | ||
| 280 | for (uint8_t layer = 0; layer < DYNAMIC_KEYMAP_LAYER_COUNT; layer++) { | ||
| 281 | if (index == (0xF0 | layer)) { | ||
| 282 | layer_rgb[layer].hsv.v = value; | ||
| 283 | data[1] = 0; | ||
| 284 | system76_ec_rgb_layer(layer_state); | ||
| 285 | break; | ||
| 286 | } | ||
| 287 | } | ||
| 288 | } | ||
| 289 | break; | ||
| 290 | case CMD_LED_GET_COLOR: | ||
| 291 | if (!bootloader_unlocked) { | ||
| 292 | uint8_t index = data[2]; | ||
| 293 | if (index < DRIVER_LED_TOTAL) { | ||
| 294 | data[3] = raw_rgb_data[index].r; | ||
| 295 | data[4] = raw_rgb_data[index].g; | ||
| 296 | data[5] = raw_rgb_data[index].b; | ||
| 297 | data[1] = 0; | ||
| 298 | } else { | ||
| 299 | for (uint8_t layer = 0; layer < DYNAMIC_KEYMAP_LAYER_COUNT; layer++) { | ||
| 300 | if (index == (0xF0 | layer)) { | ||
| 301 | data[3] = layer_rgb[layer].hsv.h; | ||
| 302 | data[4] = layer_rgb[layer].hsv.s; | ||
| 303 | data[5] = 0; | ||
| 304 | data[1] = 0; | ||
| 305 | break; | ||
| 306 | } | ||
| 307 | } | ||
| 308 | } | ||
| 309 | } | ||
| 310 | break; | ||
| 311 | case CMD_LED_SET_COLOR: | ||
| 312 | if (!bootloader_unlocked) { | ||
| 313 | uint8_t index = data[2]; | ||
| 314 | |||
| 315 | RGB rgb = { | ||
| 316 | .r = data[3], | ||
| 317 | .g = data[4], | ||
| 318 | .b = data[5], | ||
| 319 | }; | ||
| 320 | |||
| 321 | if (index < DRIVER_LED_TOTAL) { | ||
| 322 | raw_rgb_data[index] = rgb; | ||
| 323 | data[1] = 0; | ||
| 324 | } else { | ||
| 325 | for (uint8_t layer = 0; layer < DYNAMIC_KEYMAP_LAYER_COUNT; layer++) { | ||
| 326 | if (index == (0xF0 | layer)) { | ||
| 327 | layer_rgb[layer].hsv.h = rgb.r; | ||
| 328 | layer_rgb[layer].hsv.s = rgb.g; | ||
| 329 | // Ignore rgb.b | ||
| 330 | data[1] = 0; | ||
| 331 | system76_ec_rgb_layer(layer_state); | ||
| 332 | break; | ||
| 333 | } | ||
| 334 | } | ||
| 335 | } | ||
| 336 | } | ||
| 337 | break; | ||
| 338 | case CMD_LED_GET_MODE: | ||
| 339 | if (!bootloader_unlocked) { | ||
| 340 | uint8_t layer = data[2]; | ||
| 341 | if (layer < DYNAMIC_KEYMAP_LAYER_COUNT) { | ||
| 342 | enum rgb_matrix_effects mode = layer_rgb[layer].mode; | ||
| 343 | for (uint8_t i = 0; i < MODE_LAST; i++) { | ||
| 344 | if (mode_map[i] == mode) { | ||
| 345 | data[3] = i; | ||
| 346 | data[4] = layer_rgb[layer].speed; | ||
| 347 | data[1] = 0; | ||
| 348 | break; | ||
| 349 | } | ||
| 350 | } | ||
| 351 | } | ||
| 352 | } | ||
| 353 | break; | ||
| 354 | case CMD_LED_SET_MODE: | ||
| 355 | if (!bootloader_unlocked) { | ||
| 356 | uint8_t layer = data[2]; | ||
| 357 | uint8_t mode = data[3]; | ||
| 358 | uint8_t speed = data[4]; | ||
| 359 | if (layer < DYNAMIC_KEYMAP_LAYER_COUNT && mode < MODE_LAST) { | ||
| 360 | layer_rgb[layer].mode = mode_map[mode]; | ||
| 361 | layer_rgb[layer].speed = speed; | ||
| 362 | data[1] = 0; | ||
| 363 | system76_ec_rgb_layer(layer_state); | ||
| 364 | } | ||
| 365 | } | ||
| 366 | break; | ||
| 367 | case CMD_LED_SAVE: | ||
| 368 | if (!bootloader_unlocked) { | ||
| 369 | system76_ec_rgb_eeprom(true); | ||
| 370 | data[1] = 0; | ||
| 371 | } | ||
| 372 | break; | ||
| 373 | #endif // RGB_MATRIX_CUSTOM_KB | ||
| 374 | case CMD_MATRIX_GET: { | ||
| 375 | // TODO: Improve performance? | ||
| 376 | data[2] = matrix_rows(); | ||
| 377 | data[3] = matrix_cols(); | ||
| 378 | |||
| 379 | uint8_t byte = 4; | ||
| 380 | uint8_t bit = 0; | ||
| 381 | |||
| 382 | for (uint8_t row = 0; row < matrix_rows(); row++) { | ||
| 383 | for (uint8_t col = 0; col < matrix_cols(); col++) { | ||
| 384 | if (byte < length) { | ||
| 385 | if (matrix_is_on(row, col)) { | ||
| 386 | data[byte] |= (1 << bit); | ||
| 387 | } else { | ||
| 388 | data[byte] &= ~(1 << bit); | ||
| 389 | } | ||
| 390 | } | ||
| 391 | |||
| 392 | bit++; | ||
| 393 | if (bit >= 8) { | ||
| 394 | byte++; | ||
| 395 | bit = 0; | ||
| 396 | } | ||
| 397 | } | ||
| 398 | } | ||
| 399 | data[1] = 0; | ||
| 400 | } break; | ||
| 401 | case CMD_SET_NO_INPUT: { | ||
| 402 | clear_keyboard(); | ||
| 403 | input_disabled = data[2] != 0; | ||
| 404 | data[1] = 0; | ||
| 405 | } break; | ||
| 406 | } | ||
| 407 | |||
| 408 | raw_hid_send(data, length); | ||
| 409 | |||
| 410 | if (bootloader_reset) { | ||
| 411 | // Give host time to read response | ||
| 412 | wait_ms(100); | ||
| 413 | // Jump to the bootloader | ||
| 414 | bootloader_jump(); | ||
| 415 | } | ||
| 416 | } | ||
