system76/common/kbc: Refactor to remove delays and improve readability

This commit is contained in:
Jeremy Soller 2020-12-29 10:14:13 -07:00 committed by Jeremy Soller
parent 5559d4e2f6
commit 31a908556b

View File

@ -4,18 +4,26 @@
#include <board/kbscan.h> #include <board/kbscan.h>
#include <board/keymap.h> #include <board/keymap.h>
#include <common/debug.h> #include <common/debug.h>
#include <common/macro.h>
#include <ec/espi.h>
#include <ec/ps2.h> #include <ec/ps2.h>
void kbc_init(void) { void kbc_init(void) {
// Disable interrupts // Disable interrupts
*(KBC.irq) = 0;
*(KBC.control) = 0; *(KBC.control) = 0;
#if EC_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
// Set "key lock" to disabled
*(KBC.status) = BIT(4);
} }
#define KBC_TIMEOUT 10000 #define KBC_TIMEOUT 10000
// System flag
static bool kbc_system = false;
// Enable first port - TODO // Enable first port - TODO
bool kbc_first = false; bool kbc_first = false;
// Enable second port - TODO // Enable second port - TODO
@ -91,6 +99,7 @@ bool kbc_scancode(struct Kbc * kbc, uint16_t key, bool pressed) {
} }
enum KbcState { enum KbcState {
// Input buffer states
KBC_STATE_NORMAL, KBC_STATE_NORMAL,
KBC_STATE_WRITE_CONFIG, KBC_STATE_WRITE_CONFIG,
KBC_STATE_SET_LEDS, KBC_STATE_SET_LEDS,
@ -100,36 +109,53 @@ enum KbcState {
KBC_STATE_FIRST_PORT_OUTPUT, KBC_STATE_FIRST_PORT_OUTPUT,
KBC_STATE_SECOND_PORT_OUTPUT, KBC_STATE_SECOND_PORT_OUTPUT,
KBC_STATE_SECOND_PORT_INPUT, 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,
}; };
void kbc_event(struct Kbc * kbc) { // TODO: state per KBC (we only have one KBC so low priority)
// TODO: state per KBC (we only have one KBC so low priority) static enum KbcState state = KBC_STATE_NORMAL;
static enum KbcState state = KBC_STATE_NORMAL; static uint8_t state_data = 0;
static enum KbcState state_next = KBC_STATE_NORMAL;
uint8_t sts = kbc_status(kbc); // Clear output buffer
if (sts & KBC_STS_IBF) { static void kbc_clear_output(struct Kbc * kbc) {
uint8_t data = kbc_read(kbc); *(kbc->control) |= BIT(5);
if (sts & KBC_STS_CMD) { *(kbc->control) |= BIT(6);
*(kbc->control) &= ~BIT(5);
}
static void kbc_on_input_command(struct Kbc * kbc, uint8_t data) {
TRACE("kbc cmd: %02X\n", data); TRACE("kbc cmd: %02X\n", data);
// Controller commands always reset the state
state = KBC_STATE_NORMAL; state = KBC_STATE_NORMAL;
// Controller commands clear the output buffer
kbc_clear_output(kbc);
switch (data) { switch (data) {
case 0x20: case 0x20:
TRACE(" read configuration byte\n"); TRACE(" read configuration byte\n");
uint8_t config = *kbc->control & 0x03; state = KBC_STATE_KEYBOARD;
if (kbc_system) { // Interrupt enable flags
config |= (1 << 2); state_data = *kbc->control & 0x03;
// System flag
if (*kbc->status & BIT(2)) {
state_data |= BIT(2);
} }
if (!kbc_first) { if (!kbc_first) {
config |= (1 << 4); state_data |= BIT(4);
} }
if (!kbc_second) { if (!kbc_second) {
config |= (1 << 5); state_data |= BIT(5);
} }
if (kbc_translate) { if (kbc_translate) {
config |= (1 << 6); state_data |= BIT(6);
} }
kbc_keyboard(kbc, config, KBC_TIMEOUT);
break; break;
case 0x60: case 0x60:
TRACE(" write configuration byte\n"); TRACE(" write configuration byte\n");
@ -146,17 +172,20 @@ void kbc_event(struct Kbc * kbc) {
case 0xA9: case 0xA9:
TRACE(" test second port\n"); TRACE(" test second port\n");
// TODO: communicate with touchpad? // TODO: communicate with touchpad?
kbc_keyboard(kbc, 0x00, KBC_TIMEOUT); state = KBC_STATE_KEYBOARD;
state_data = 0x00;
break; break;
case 0xAA: case 0xAA:
TRACE(" test controller\n"); TRACE(" test controller\n");
// Why not pass the test? // Why not pass the test?
kbc_keyboard(kbc, 0x55, KBC_TIMEOUT); state = KBC_STATE_KEYBOARD;
state_data = 0x55;
break; break;
case 0xAB: case 0xAB:
TRACE(" test first port\n"); TRACE(" test first port\n");
// We _ARE_ the keyboard, so everything is good. // We _ARE_ the keyboard, so everything is good.
kbc_keyboard(kbc, 0x00, KBC_TIMEOUT); state = KBC_STATE_KEYBOARD;
state_data = 0x00;
break; break;
case 0xAD: case 0xAD:
TRACE(" disable first port\n"); TRACE(" disable first port\n");
@ -183,96 +212,111 @@ void kbc_event(struct Kbc * kbc) {
state = KBC_STATE_SECOND_PORT_INPUT; state = KBC_STATE_SECOND_PORT_INPUT;
break; break;
} }
} else { }
TRACE("kbc data: %02X\n", data);
static void kbc_on_input_data(struct Kbc * kbc, uint8_t data) {
TRACE("kbc data: %02X\n", data);
switch (state) { switch (state) {
case KBC_STATE_TOUCHPAD:
// Interrupt touchpad command
state = KBC_STATE_NORMAL;
// Fall through
case KBC_STATE_NORMAL: case KBC_STATE_NORMAL:
TRACE(" keyboard command\n"); TRACE(" keyboard command\n");
// Keyboard commands clear output buffer
kbc_clear_output(kbc);
switch (data) { switch (data) {
case 0xED: case 0xED:
TRACE(" set leds\n"); TRACE(" set leds\n");
state = KBC_STATE_SET_LEDS; state = KBC_STATE_KEYBOARD;
kbc_keyboard(kbc, 0xFA, KBC_TIMEOUT); state_data = 0xFA;
state_next = KBC_STATE_SET_LEDS;
break; break;
case 0xEE: case 0xEE:
TRACE(" echo\n"); TRACE(" echo\n");
// Hey, this is easy. I like easy commands // Hey, this is easy. I like easy commands
kbc_keyboard(kbc, 0xEE, KBC_TIMEOUT); state = KBC_STATE_KEYBOARD;
state_data = 0xEE;
break; break;
case 0xF0: case 0xF0:
TRACE(" get/set scancode\n"); TRACE(" get/set scancode\n");
state = KBC_STATE_SCANCODE; state = KBC_STATE_KEYBOARD;
kbc_keyboard(kbc, 0xFA, KBC_TIMEOUT); state_data = 0xFA;
state_next = KBC_STATE_SCANCODE;
break; break;
case 0xF2: case 0xF2:
TRACE(" identify keyboard\n"); TRACE(" identify keyboard\n");
if (kbc_keyboard(kbc, 0xFA, KBC_TIMEOUT)) { state = KBC_STATE_KEYBOARD;
if (kbc_keyboard(kbc, 0xAB, KBC_TIMEOUT)) { state_data = 0xFA;
kbc_keyboard(kbc, 0x83, KBC_TIMEOUT); state_next = KBC_STATE_IDENTIFY_0;
}
}
break; break;
case 0xF3: case 0xF3:
TRACE(" set typematic rate/delay\n"); TRACE(" set typematic rate/delay\n");
state = KBC_STATE_TYPEMATIC; state = KBC_STATE_KEYBOARD;
kbc_keyboard(kbc, 0xFA, KBC_TIMEOUT); state_data = 0xFA;
state_next = KBC_STATE_TYPEMATIC;
break; break;
case 0xF4: case 0xF4:
TRACE(" enable scanning\n"); TRACE(" enable scanning\n");
kbscan_enabled = true; kbscan_enabled = true;
kbc_keyboard(kbc, 0xFA, KBC_TIMEOUT); state = KBC_STATE_KEYBOARD;
state_data = 0xFA;
break; break;
case 0xF5: case 0xF5:
TRACE(" disable scanning\n"); TRACE(" disable scanning\n");
kbscan_enabled = false; kbscan_enabled = false;
kbc_keyboard(kbc, 0xFA, KBC_TIMEOUT); state = KBC_STATE_KEYBOARD;
state_data = 0xFA;
break; break;
case 0xF6: case 0xF6:
TRACE(" set default parameters\n"); TRACE(" set default parameters\n");
kbc_leds = 0; kbc_leds = 0;
kbscan_repeat_period = 91; kbscan_repeat_period = 91;
kbscan_repeat_delay = 500; kbscan_repeat_delay = 500;
kbc_keyboard(kbc, 0xFA, KBC_TIMEOUT); state = KBC_STATE_KEYBOARD;
state_data = 0xFA;
break; break;
case 0xFF: case 0xFF:
TRACE(" self test\n"); TRACE(" self test\n");
if (kbc_keyboard(kbc, 0xFA, KBC_TIMEOUT)) { state = KBC_STATE_KEYBOARD;
// Yep, everything is still good, I promise state_data = 0xFA;
kbc_keyboard(kbc, 0xAA, KBC_TIMEOUT); state_next = KBC_STATE_SELF_TEST;
}
break; break;
} }
break; break;
case KBC_STATE_WRITE_CONFIG: case KBC_STATE_WRITE_CONFIG:
TRACE(" write configuration byte\n"); TRACE(" write configuration byte\n");
state = KBC_STATE_NORMAL; state = KBC_STATE_NORMAL;
uint8_t control = *kbc->control; // Enable keyboard interrupt
if (data & 1) { if (data & BIT(0)) {
control |= 1; *kbc->control |= BIT(0);
} else { } else {
control &= ~1; *kbc->control &= ~BIT(0);
} }
if (data & (1 << 1)) { // Enable mouse interrupt
control |= (1 << 1); if (data & BIT(1)) {
*kbc->control |= BIT(1);
} else { } else {
control &= ~(1 << 1); *kbc->control &= ~BIT(1);
} }
kbc_system = (bool)(data & (1 << 2)); // System flag
kbc_first = (bool)(!(data & (1 << 4))); if (data & BIT(2)) {
kbc_second = (bool)(!(data & (1 << 5))); *kbc->status |= BIT(2);
kbc_translate = (bool)(data & (1 << 6)); } else {
*kbc->control = control; *kbc->status &= ~BIT(2);
}
kbc_first = (bool)(!(data & BIT(4)));
kbc_second = (bool)(!(data & BIT(5)));
kbc_translate = (bool)(data & BIT(6));
break; break;
case KBC_STATE_SET_LEDS: case KBC_STATE_SET_LEDS:
TRACE(" set leds\n"); TRACE(" set leds\n");
state = KBC_STATE_NORMAL;
kbc_leds = data; kbc_leds = data;
kbc_keyboard(kbc, 0xFA, KBC_TIMEOUT); state = KBC_STATE_KEYBOARD;
state_data = 0xFA;
break; break;
case KBC_STATE_SCANCODE: case KBC_STATE_SCANCODE:
TRACE(" get/set scancode\n"); TRACE(" get/set scancode\n");
state = KBC_STATE_NORMAL;
#if LEVEL >= LEVEL_TRACE #if LEVEL >= LEVEL_TRACE
switch (data) { switch (data) {
case 0x02: case 0x02:
@ -280,11 +324,11 @@ void kbc_event(struct Kbc * kbc) {
break; break;
} }
#endif #endif
kbc_keyboard(kbc, 0xFA, KBC_TIMEOUT); state = KBC_STATE_KEYBOARD;
state_data = 0xFA;
break; break;
case KBC_STATE_TYPEMATIC: case KBC_STATE_TYPEMATIC:
TRACE(" set typematic rate/delay\n"); TRACE(" set typematic rate/delay\n");
state = KBC_STATE_NORMAL;
{ {
// Rate: bits 0-4 // Rate: bits 0-4
uint16_t period = kbc_typematic_period[data & 0x1F]; uint16_t period = kbc_typematic_period[data & 0x1F];
@ -295,7 +339,8 @@ void kbc_event(struct Kbc * kbc) {
uint8_t idx = (data & 0x60) >> 5; uint8_t idx = (data & 0x60) >> 5;
kbscan_repeat_delay = delay[idx]; kbscan_repeat_delay = delay[idx];
} }
kbc_keyboard(kbc, 0xFA, KBC_TIMEOUT); state = KBC_STATE_KEYBOARD;
state_data = 0xFA;
break; break;
case KBC_STATE_WRITE_PORT: case KBC_STATE_WRITE_PORT:
TRACE(" write port byte\n"); TRACE(" write port byte\n");
@ -303,20 +348,78 @@ void kbc_event(struct Kbc * kbc) {
break; break;
case KBC_STATE_FIRST_PORT_OUTPUT: case KBC_STATE_FIRST_PORT_OUTPUT:
TRACE(" write first port output\n"); TRACE(" write first port output\n");
state = KBC_STATE_NORMAL; state = KBC_STATE_KEYBOARD;
kbc_keyboard(kbc, data, KBC_TIMEOUT); state_data = data;
break; break;
case KBC_STATE_SECOND_PORT_OUTPUT: case KBC_STATE_SECOND_PORT_OUTPUT:
TRACE(" write second port output\n"); TRACE(" write second port output\n");
state = KBC_STATE_NORMAL; state = KBC_STATE_MOUSE;
kbc_mouse(kbc, data, KBC_TIMEOUT); state_data = data;
break; break;
case KBC_STATE_SECOND_PORT_INPUT: case KBC_STATE_SECOND_PORT_INPUT:
TRACE(" write second port input\n"); TRACE(" write second port input\n");
state = KBC_STATE_NORMAL; state = KBC_STATE_NORMAL;
ps2_write(&PS2_3, &data, 1); ps2_write(&PS2_TOUCHPAD, &data, 1);
break; break;
} }
}
static void kbc_on_output_empty(struct Kbc * 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 * kbc) {
uint8_t sts;
// 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);
} }
} }