// SPDX-License-Identifier: GPL-3.0-only #include #include #include #include #include #include #include void kbc_init(void) { // Disable interrupts *(KBC.control) = 0; #if CONFIG_BUS_ESPI // Set IRQ mode to edge-triggered, 1-cycle pulse width *(KBC.irq) = BIT(3); #else // Set IRQ mode to level-triggered *(KBC.irq) = 0; #endif kbc_reset(); } void kbc_reset(void) { // Set "key lock" to disabled *(KBC.status) = BIT(4); } #define KBC_TIMEOUT 1000 // Enable first port static bool kbc_first = false; // Enable second port static bool kbc_second = false; // Second port input timeout static uint8_t kbc_second_wait = 0; // Translate from scancode set 2 to scancode set 1 // for basically no good reason static bool kbc_translate = true; // LED state uint8_t kbc_leds = 0; // clang-format off // Values from linux/drivers/input/keyboard/atkbd.c static const uint16_t kbc_typematic_period[32] = { 33, // 30.0 cps = ~33.33ms 37, // 26.7 cps = ~37.45ms 42, // 24.0 cps = ~41.67ms 46, // 21.8 cps = ~45.87ms 50, // 20.7 cps = ~48.30ms 54, // 18.5 cps = ~54.05ms 58, // 17.1 cps = ~58.48ms 63, // 16.0 cps = ~62.50ms 67, // 15.0 cps = ~66.67ms 75, // 13.3 cps = ~75.19ms 83, // 12.0 cps = ~83.33ms 92, // 10.9 cps = ~91.74ms 100, // 10.0 cps = 100ms 109, // 9.2 cps = ~108.70ms 116, // 8.6 cps = ~116.28ms 125, // 8.0 cps = 125ms 133, // 7.5 cps = ~133.33ms 149, // 6.7 cps = ~149.25ms 167, // 6.0 cps = ~166.67ms 182, // 5.5 cps = ~181.82ms 200, // 5.0 cps = 200ms 217, // 4.6 cps = ~217.39ms 232, // 4.3 cps = ~232.56ms 250, // 4.0 cps = 250ms 270, // 3.7 cps = ~270.27ms 303, // 3.3 cps = ~303.03ms 333, // 3.0 cps = ~333.33ms 370, // 2.7 cps = ~370.37ms 400, // 2.5 cps = 400ms 435, // 2.3 cps = ~434.78ms 470, // 2.1 cps = ~478.19ms 500, // 2.0 cps = 500ms }; // clang-format on static uint8_t kbc_buffer[16] = { 0 }; static uint8_t kbc_buffer_head = 0; static uint8_t kbc_buffer_tail = 0; static bool kbc_buffer_pop(uint8_t *const scancode) { if (kbc_buffer_head == kbc_buffer_tail) { return false; } *scancode = kbc_buffer[kbc_buffer_head]; kbc_buffer_head = (kbc_buffer_head + 1U) % ARRAY_SIZE(kbc_buffer); return true; } static bool kbc_buffer_push(const uint8_t *const scancodes, uint8_t len) { //TODO: make this test more efficient for (uint8_t i = 0; i < len; i++) { if ((kbc_buffer_tail + i + 1U) % ARRAY_SIZE(kbc_buffer) == kbc_buffer_head) { return false; } } for (uint8_t i = 0; i < len; i++) { kbc_buffer[kbc_buffer_tail] = scancodes[i]; kbc_buffer_tail = (kbc_buffer_tail + 1U) % ARRAY_SIZE(kbc_buffer); } return true; } bool kbc_scancode(uint16_t key, bool pressed) { if (!kbc_first) return true; if (kbc_translate) { key = keymap_translate(key); } if (!key) return true; uint8_t scancodes[3] = { 0, 0, 0 }; uint8_t scancodes_len = 0; switch (key & 0xFF00) { case KF_E0: scancodes[scancodes_len++] = 0xE0; key &= 0xFF; // Fall through case 0x00: if (!pressed) { if (kbc_translate) { key |= 0x80; } else { scancodes[scancodes_len++] = 0xF0; } } scancodes[scancodes_len++] = (uint8_t)key; break; } return kbc_buffer_push(scancodes, scancodes_len); } enum KbcState { // Input buffer states KBC_STATE_NORMAL, KBC_STATE_WRITE_CONFIG, KBC_STATE_SET_LEDS, KBC_STATE_SCANCODE, KBC_STATE_TYPEMATIC, KBC_STATE_WRITE_PORT, KBC_STATE_FIRST_PORT_OUTPUT, KBC_STATE_SECOND_PORT_OUTPUT, KBC_STATE_SECOND_PORT_INPUT, // Output buffer states KBC_STATE_KEYBOARD, KBC_STATE_TOUCHPAD, KBC_STATE_MOUSE, // After output buffer states KBC_STATE_IDENTIFY_0, KBC_STATE_IDENTIFY_1, KBC_STATE_SELF_TEST, }; // TODO: state per KBC (we only have one KBC so low priority) static enum KbcState state = KBC_STATE_NORMAL; static uint8_t state_data = 0; static enum KbcState state_next = KBC_STATE_NORMAL; // Clear output buffer static void kbc_clear_output(struct Kbc *const kbc) { *(kbc->control) |= BIT(5); *(kbc->control) |= BIT(6); *(kbc->control) &= ~BIT(5); } static void kbc_on_input_command(struct Kbc *const kbc, uint8_t data) { TRACE("kbc cmd: %02X\n", data); // Controller commands always reset the state state = KBC_STATE_NORMAL; // Controller commands clear the output buffer kbc_clear_output(kbc); switch (data) { case 0x20: TRACE(" read configuration byte\n"); state = KBC_STATE_KEYBOARD; // Interrupt enable flags state_data = *kbc->control & 0x03; // System flag if (*kbc->status & BIT(2)) { state_data |= BIT(2); } if (!kbc_first) { state_data |= BIT(4); } if (!kbc_second) { state_data |= BIT(5); } if (kbc_translate) { state_data |= BIT(6); } break; case 0x60: TRACE(" write configuration byte\n"); state = KBC_STATE_WRITE_CONFIG; break; case 0xA7: TRACE(" disable second port\n"); kbc_second = false; break; case 0xA8: TRACE(" enable second port\n"); kbc_second = true; break; case 0xA9: TRACE(" test second port\n"); // TODO: communicate with touchpad? state = KBC_STATE_KEYBOARD; state_data = 0x00; break; case 0xAA: TRACE(" test controller\n"); // Why not pass the test? state = KBC_STATE_KEYBOARD; state_data = 0x55; break; case 0xAB: TRACE(" test first port\n"); // We _ARE_ the keyboard, so everything is good. state = KBC_STATE_KEYBOARD; state_data = 0x00; break; case 0xAD: TRACE(" disable first port\n"); kbc_first = false; break; case 0xAE: TRACE(" enable first port\n"); kbc_first = true; break; case 0xD1: TRACE(" write port byte\n"); state = KBC_STATE_WRITE_PORT; break; case 0xD2: TRACE(" write first port output\n"); state = KBC_STATE_FIRST_PORT_OUTPUT; break; case 0xD3: TRACE(" write second port output\n"); state = KBC_STATE_SECOND_PORT_OUTPUT; break; case 0xD4: TRACE(" write second port input\n"); state = KBC_STATE_SECOND_PORT_INPUT; break; } } static void kbc_on_input_data(struct Kbc *const kbc, uint8_t data) { TRACE("kbc data: %02X\n", data); switch (state) { case KBC_STATE_TOUCHPAD: // Interrupt touchpad command state = KBC_STATE_NORMAL; // Fall through case KBC_STATE_NORMAL: TRACE(" keyboard command\n"); // Keyboard commands clear output buffer kbc_clear_output(kbc); switch (data) { case 0xED: TRACE(" set leds\n"); state = KBC_STATE_KEYBOARD; state_data = 0xFA; state_next = KBC_STATE_SET_LEDS; break; case 0xEE: TRACE(" echo\n"); // Hey, this is easy. I like easy commands state = KBC_STATE_KEYBOARD; state_data = 0xEE; break; case 0xF0: TRACE(" get/set scancode\n"); state = KBC_STATE_KEYBOARD; state_data = 0xFA; state_next = KBC_STATE_SCANCODE; break; case 0xF2: TRACE(" identify keyboard\n"); state = KBC_STATE_KEYBOARD; state_data = 0xFA; state_next = KBC_STATE_IDENTIFY_0; break; case 0xF3: TRACE(" set typematic rate/delay\n"); state = KBC_STATE_KEYBOARD; state_data = 0xFA; state_next = KBC_STATE_TYPEMATIC; break; case 0xF4: TRACE(" enable scanning\n"); kbscan_enabled = true; state = KBC_STATE_KEYBOARD; state_data = 0xFA; break; case 0xF5: TRACE(" disable scanning\n"); kbscan_enabled = false; state = KBC_STATE_KEYBOARD; state_data = 0xFA; break; case 0xF6: TRACE(" set default parameters\n"); kbc_leds = 0; kbscan_repeat_period = 91; kbscan_repeat_delay = 500; state = KBC_STATE_KEYBOARD; state_data = 0xFA; break; case 0xFF: TRACE(" self test\n"); state = KBC_STATE_KEYBOARD; state_data = 0xFA; state_next = KBC_STATE_SELF_TEST; break; } break; case KBC_STATE_WRITE_CONFIG: TRACE(" write configuration byte\n"); state = KBC_STATE_NORMAL; // Enable keyboard interrupt if (data & BIT(0)) { *kbc->control |= BIT(0); } else { *kbc->control &= ~BIT(0); } // Enable mouse interrupt if (data & BIT(1)) { *kbc->control |= BIT(1); } else { *kbc->control &= ~BIT(1); } // System flag if (data & BIT(2)) { *kbc->status |= BIT(2); } else { *kbc->status &= ~BIT(2); } kbc_first = (bool)(!(data & BIT(4))); kbc_second = (bool)(!(data & BIT(5))); kbc_translate = (bool)(data & BIT(6)); break; case KBC_STATE_SET_LEDS: TRACE(" set leds\n"); kbc_leds = data; state = KBC_STATE_KEYBOARD; state_data = 0xFA; break; case KBC_STATE_SCANCODE: TRACE(" get/set scancode\n"); #if LEVEL >= LEVEL_TRACE switch (data) { case 0x02: TRACE(" set scancode set 2\n"); break; } #endif state = KBC_STATE_KEYBOARD; state_data = 0xFA; break; case KBC_STATE_TYPEMATIC: TRACE(" set typematic rate/delay\n"); { // Rate: bits 0-4 uint16_t period = kbc_typematic_period[data & 0x1F]; kbscan_repeat_period = period; // Delay: bits 5-6 static const uint16_t delay[4] = { 250, 500, 750, 1000 }; uint8_t idx = (data & 0x60) >> 5; kbscan_repeat_delay = delay[idx]; } state = KBC_STATE_KEYBOARD; state_data = 0xFA; break; case KBC_STATE_WRITE_PORT: TRACE(" write port byte\n"); state = KBC_STATE_NORMAL; break; case KBC_STATE_FIRST_PORT_OUTPUT: TRACE(" write first port output\n"); state = KBC_STATE_KEYBOARD; state_data = data; break; case KBC_STATE_SECOND_PORT_OUTPUT: TRACE(" write second port output\n"); state = KBC_STATE_MOUSE; state_data = data; break; case KBC_STATE_SECOND_PORT_INPUT: TRACE(" write second port input\n"); state = KBC_STATE_NORMAL; // Begin write *(PS2_TOUCHPAD.control) = 0x1D; // Write the data *(PS2_TOUCHPAD.data) = data; // Pull data line low *(PS2_TOUCHPAD.control) = 0x1C; // Pull clock line high *(PS2_TOUCHPAD.control) = 0x1E; // Set wait timeout of 100 cycles kbc_second_wait = 100; break; } } static void kbc_on_output_empty(struct Kbc *const kbc) { switch (state) { case KBC_STATE_KEYBOARD: TRACE("kbc keyboard: %02X\n", state_data); if (kbc_keyboard(kbc, state_data, KBC_TIMEOUT)) { state = state_next; state_next = KBC_STATE_NORMAL; } break; case KBC_STATE_TOUCHPAD: state_data = *(PS2_TOUCHPAD.data); // Fall through case KBC_STATE_MOUSE: TRACE("kbc mouse: %02X\n", state_data); if (kbc_mouse(kbc, state_data, KBC_TIMEOUT)) { state = state_next; state_next = KBC_STATE_NORMAL; } break; } switch (state) { case KBC_STATE_IDENTIFY_0: state = KBC_STATE_KEYBOARD; state_data = 0xAB; state_next = KBC_STATE_IDENTIFY_1; break; case KBC_STATE_IDENTIFY_1: state = KBC_STATE_KEYBOARD; state_data = 0x83; break; case KBC_STATE_SELF_TEST: // Yep, everything is still good, I promise state = KBC_STATE_KEYBOARD; state_data = 0xAA; break; } } void kbc_event(struct Kbc *const kbc) { uint8_t sts; // Read from scancode buffer when possible if (state == KBC_STATE_NORMAL && kbc_buffer_pop(&state_data)) { state = KBC_STATE_KEYBOARD; } // Read from touchpad when possible if (kbc_second) { if (kbc_second_wait > 0) { // Wait for touchpad write transaction to finish kbc_second_wait -= 1; uint8_t sts = *(PS2_TOUCHPAD.status); *(PS2_TOUCHPAD.status) = sts; // If transaction is done, stop waiting if (sts & PSSTS_DONE) { kbc_second_wait = 0; } // If an error happened, clear status, print error, and stop waiting else if (sts & PSSTS_ALL_ERR) { ps2_reset(&PS2_TOUCHPAD); TRACE(" write second port input ERROR %02X\n", sts); kbc_second_wait = 0; } // If a timeout occurs, clear status, print error, and stop waiting else if (kbc_second_wait == 0) { ps2_reset(&PS2_TOUCHPAD); TRACE(" write second port input TIMEOUT\n"); kbc_second_wait = 0; } } if (kbc_second_wait == 0) { // Attempt to read from touchpad *(PS2_TOUCHPAD.control) = 0x17; if (state == KBC_STATE_NORMAL) { uint8_t sts = *(PS2_TOUCHPAD.status); *(PS2_TOUCHPAD.status) = sts; if (sts & PSSTS_DONE) { state = KBC_STATE_TOUCHPAD; } } } } else { ps2_reset(&PS2_TOUCHPAD); } // Read command/data while available sts = kbc_status(kbc); if (sts & KBC_STS_IBF) { uint8_t data = kbc_read(kbc); if (sts & KBC_STS_CMD) { kbc_on_input_command(kbc, data); } else { kbc_on_input_data(kbc, data); } } // Write data if possible sts = kbc_status(kbc); if (!(sts & KBC_STS_OBF)) { kbc_on_output_empty(kbc); } }