#include "project.h" #define KBCLK (1UL << 0) #define KBCLK_PORT GPIOA #define KBCLK_IRQ NVIC_EXTI0_IRQ #define KBDAT (1UL << 1) #define KBDAT_PORT GPIOA /*Cope with lost sync */ /*If we've had no bits for this long, it's definately the start of a new word, 72MHz units */ #define RESYNC_GAP (100 * MS) #define ATKBD_TIMEOUT (1000 * MS) /*how long to wait for the keyboard to respond to a command in ms */ #define ATKBD_CLOCK_SEIZE_DELAY 200 /* how many us from pulling clock low to pulling data low, when we xmit*/ #define ATKBD_BIT_DELAY 10 /* how many us we allow from the KBD to pull clock back up */ typedef enum { STATE_START = 0, STATE_BIT0, STATE_BIT1, STATE_BIT2, STATE_BIT3, STATE_BIT4, STATE_BIT5, STATE_BIT6, STATE_BIT7, STATE_PARITY, STATE_STOP, } atkbd_state; #define ATKBD_CMD_SETLEDS 0xed #define ATKBD_CMD_GSCANSET 0xf0 #define ATKBD_CMD_SSCANSET 0xf0 #define ATKBD_CMD_GETID 0xf2 #define ATKBD_CMD_SETREP 0xf3 #define ATKBD_CMD_ENABLE 0xf4 #define ATKBD_CMD_RESET_DIS 0xf5 #define ATKBD_CMD_RESET_DEF 0xf6 #define ATKBD_CMD_SETALL_MB 0xf8 #define ATKBD_CMD_SETALL_MBR 0xfa #define ATKBD_CMD_RESET_BAT 0xff #define ATKBD_CMD_RESEND 0xfe #define ATKBD_CMD_EX_ENABLE 0xea #define ATKBD_CMD_EX_SETLEDS 0xeb #define ATKBD_CMD_OK_GETID 0xe8 #define ATKBD_CMD_ECHO 0xee #define ATKBD_RET_ECHO 0xee #define ATKBD_RET_ACK 0xfa #define ATKBD_RET_NAK 0xfe #define ATKBD_RET_BAT 0xaa #define ATKBD_RET_EMUL0 0xe0 #define ATKBD_RET_EMUL1 0xe1 #define ATKBD_RET_RELEASE 0xf0 #define ATKBD_RET_HANJA 0xf1 #define ATKBD_RET_HANGEUL 0xf2 #define ATKBD_RET_ERR 0xff #define ATKBD_KEY_UNKNOWN 0 #define ATKBD_KEY_NULL 255 static int ready; static uint8_t saved_leds; static int atkbd_ack; static int atkbd_nack; static int atkbd_bat; static int atkbd_echo; static void atkbd_mask_irq (void) { nvic_disable_irq (KBCLK_IRQ); } static void atkbd_unmask_flush_irq (void) { exti_reset_request (KBCLK); nvic_enable_irq (KBCLK_IRQ); } static uint32_t cycle_diff (uint32_t a, uint32_t b) { return b - a; } static int fake_ctrl, pause_down; void atkbd_dispatch_error (void) { pause_down = 0; fake_ctrl = 0; scancode_error (); } static void atkbd_dispatch (int key, int updown) { /* the logic here is batshit, consult scancode.doc in the DOCS dir */ switch (key) { case AT_SC_LEFTCTRL | AT_BS_EMUL1: fake_ctrl = updown; break; } if (key == (AT_BS_EMUL0 | AT_SC_KPASTERISK)) key = AT_SC_SYSRQ; if (key == AT_SC_NUMLOCK) { /*Grr broken MS state machine in docs */ if ((!updown) && (pause_down)) { key = AT_SC_PAUSE; pause_down = 0; } if (fake_ctrl && updown) { key = AT_SC_PAUSE; pause_down = 1; } } /* Filter fakes */ switch (key) { case AT_SC_LEFTCTRL | AT_BS_EMUL1: case AT_SC_LEFTSHIFT | AT_BS_EMUL0: case AT_SC_RIGHTSHIFT | AT_BS_EMUL0: break; default: scancode_dispatch (key, updown); } } static void atkbd_data_dispatch (uint8_t byte) { static int release; static int emul; #ifdef DEBUG printf ("ATKBD_RX < 0x%02x\r\n", byte); #endif switch (byte) { case ATKBD_RET_ACK: atkbd_ack++; break; case ATKBD_RET_NAK: atkbd_nack++; break; case ATKBD_RET_BAT: atkbd_dispatch_error (); atkbd_bat++; break; case ATKBD_RET_ECHO: atkbd_echo++; break; case ATKBD_RET_ERR: case ATKBD_KEY_UNKNOWN: /*All these need no action */ break; case AT_SC_EMUL0: emul = AT_BS_EMUL0; break; case AT_SC_EMUL1: emul = AT_BS_EMUL1; break; case ATKBD_RET_RELEASE: release = 1; break; default: atkbd_dispatch (emul | byte, !release); emul = 0; release = 0; } } void exti0_isr (void) { static uint32_t last_interrupt = 0; static atkbd_state state = STATE_START; static uint8_t atkbd_byte; static int parity; uint32_t now, diff; int d; d = ! !GET (KBDAT); exti_reset_request (KBCLK); now = dwt_read_cycle_counter (); diff = cycle_diff (last_interrupt, now); last_interrupt = now; if (diff > RESYNC_GAP) state = STATE_START; switch (state) { case STATE_START: atkbd_byte = 0; parity = 0; if (!d) { state++; } else { atkbd_dispatch_error (); #ifdef DEBUG printf ("ATKBD_ISR ! start bit non-zero\r\n"); #endif } break; case STATE_BIT0: case STATE_BIT1: case STATE_BIT2: case STATE_BIT3: case STATE_BIT4: case STATE_BIT5: case STATE_BIT6: case STATE_BIT7: atkbd_byte |= d << (state - STATE_BIT0); /* fall through */ case STATE_PARITY: parity ^= d; state++; break; case STATE_STOP: if (d) { if (parity) atkbd_data_dispatch (atkbd_byte); if (!parity) { atkbd_dispatch_error (); #ifdef DEBUG printf ("ATKBD_ISR ! bad parity - issuing resend\r\n"); #endif atkbd_send (ATKBD_CMD_RESEND); } } else { atkbd_dispatch_error (); #ifdef DEBUG printf ("ATKBD_ISR ! stop bit zero\r\n"); #endif } state = STATE_START; break; } } void atkbd_set (int clk, int dat) { if (clk) { MAP_INPUT_PU (KBCLK); } else { CLEAR (KBCLK); MAP_OUTPUT_PP (KBCLK); } if (dat) { MAP_INPUT_PU (KBDAT); } else { CLEAR (KBDAT); MAP_OUTPUT_PP (KBDAT); } } int atkbd_send (uint8_t d) { uint32_t then = dwt_read_cycle_counter ();; int parity = 1; uint32_t c; atkbd_mask_irq (); atkbd_set (0, 1); delay_us (ATKBD_CLOCK_SEIZE_DELAY); atkbd_set (0, 0); delay_us (ATKBD_BIT_DELAY); atkbd_set (1, 0); delay_us (ATKBD_BIT_DELAY); /* 8 data bits */ for (c = 1; c < 0x100; c <<= 1) { while (GET (KBCLK)) if (timed_out_cycles (then, ATKBD_TIMEOUT)) goto err; atkbd_set (1, c & d); parity ^= ! !(c & d); while (!GET (KBCLK)) if (timed_out_cycles (then, ATKBD_TIMEOUT)) goto err; } /* A parity bit */ while (GET (KBCLK)) if (timed_out_cycles (then, ATKBD_TIMEOUT)) goto err; atkbd_set (1, parity); while (!GET (KBCLK)) if (timed_out_cycles (then, ATKBD_TIMEOUT)) goto err; /* two stop bits */ while (GET (KBCLK)) if (timed_out_cycles (then, ATKBD_TIMEOUT)) goto err; atkbd_set (1, 1); while (!GET (KBCLK)) if (timed_out_cycles (then, ATKBD_TIMEOUT)) goto err; while (GET (KBCLK)) if (timed_out_cycles (then, ATKBD_TIMEOUT)) goto err; atkbd_set (1, 1); while (!GET (KBCLK)) if (timed_out_cycles (then, ATKBD_TIMEOUT)) goto err; while (!GET (KBDAT)) if (timed_out_cycles (then, ATKBD_TIMEOUT)) goto err; while (!GET (KBCLK)) if (timed_out_cycles (then, ATKBD_TIMEOUT)) goto err; atkbd_unmask_flush_irq (); #ifdef DEBUG printf ("ATKBD > 0x%02x\r\n", d); #endif return 0; err: atkbd_set (1, 1); while (!GET (KBDAT)) if (timed_out_cycles (then, ATKBD_TIMEOUT)) goto err; while (!GET (KBCLK)) if (timed_out_cycles (then, ATKBD_TIMEOUT)) goto err; atkbd_unmask_flush_irq (); #ifdef DEBUG printf ("ATKBD >! 0x%02x\r\n", d); #endif return -1; } static int atkbd_reset (void) { uint32_t then = dwt_read_cycle_counter ();; atkbd_bat = 0; atkbd_send (ATKBD_CMD_RESET_BAT); while (!atkbd_bat) if (timed_out_cycles (then, ATKBD_TIMEOUT)) return -1; then = dwt_read_cycle_counter ();; atkbd_ack = 0; atkbd_send (ATKBD_CMD_SETALL_MBR); while (!atkbd_ack) if (timed_out_cycles (then, ATKBD_TIMEOUT)) return -1; return 0; } int atkbd_request_echo (void) { uint32_t then = dwt_read_cycle_counter ();; atkbd_ack = 0; atkbd_send (ATKBD_CMD_ECHO); while (!atkbd_echo) if (timed_out_cycles (then, ATKBD_TIMEOUT)) return -1; return 0; } int atkbd_set_leds (uint8_t leds) { uint32_t then; saved_leds = leds; if (!ready) return 0; then = dwt_read_cycle_counter ();; atkbd_ack = 0; atkbd_send (ATKBD_CMD_SETLEDS); while (!atkbd_ack) if (timed_out_cycles (then, ATKBD_TIMEOUT)) return -1; then = dwt_read_cycle_counter ();; atkbd_ack = 0; atkbd_send (leds); while (!atkbd_ack) if (timed_out_cycles (then, ATKBD_TIMEOUT)) return -1; return 0; } int atkbd_set_scanset (uint8_t scanset) { uint32_t then = dwt_read_cycle_counter ();; atkbd_ack = 0; atkbd_send (ATKBD_CMD_SSCANSET); while (!atkbd_ack) if (timed_out_cycles (then, ATKBD_TIMEOUT)) return -1; then = dwt_read_cycle_counter ();; atkbd_ack = 0; atkbd_send (scanset); while (!atkbd_ack) if (timed_out_cycles (then, ATKBD_TIMEOUT)) return -1; return 0; } int atkbd_set_mb (void) { uint32_t then = dwt_read_cycle_counter ();; atkbd_ack = 0; atkbd_send (ATKBD_CMD_SETALL_MB); while (!atkbd_ack) if (timed_out_cycles (then, ATKBD_TIMEOUT)) return -1; return 0; } int atkbd_set_mbr (void) { uint32_t then = dwt_read_cycle_counter ();; atkbd_ack = 0; atkbd_send (ATKBD_CMD_SETALL_MBR); while (!atkbd_ack) if (timed_out_cycles (then, ATKBD_TIMEOUT)) return -1; return 0; } void atkbd_init (void) { atkbd_set (1, 1); } void atkbd_start (void) { if (ready) return; nvic_enable_irq (NVIC_EXTI0_IRQ); exti_select_source (KBCLK, KBCLK_PORT); exti_set_trigger (KBCLK, EXTI_TRIGGER_FALLING); exti_enable_request (KBCLK); exti_reset_request (KBCLK); nvic_enable_irq (KBCLK_IRQ); delay_ms (AT_KBD_INIT_TIME); atkbd_reset (); atkbd_request_echo (); atkbd_set_mbr (); atkbd_set_scanset (2); atkbd_set_leds (saved_leds); ready++; }