--- /dev/null
+/*
+ * Copyright © 2016 Keith Packard <keithp@keithp.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ */
+
+#include "ao.h"
+#include "ao_ps2.h"
+#include "ao_exti.h"
+
+static struct ao_fifo ao_ps2_rx_fifo;
+
+static uint16_t ao_ps2_tx;
+static uint8_t ao_ps2_tx_count;
+
+static AO_TICK_TYPE ao_ps2_tick;
+static uint16_t ao_ps2_value;
+static uint8_t ao_ps2_count;
+
+uint8_t ao_ps2_stdin;
+
+uint8_t ao_ps2_scancode_set;
+
+#define AO_PS2_CLOCK_MODE(pull) ((pull) | AO_EXTI_MODE_FALLING | AO_EXTI_PRIORITY_MED)
+
+static void
+ao_ps2_isr(void);
+
+static uint8_t
+_ao_ps2_parity(uint8_t value)
+{
+ uint8_t parity = 1;
+ uint8_t b;
+
+ for (b = 0; b < 8; b++) {
+ parity ^= (value & 1);
+ value >>= 1;
+ }
+ return parity;
+}
+
+static int
+_ao_ps2_poll(void)
+{
+ uint8_t u;
+ if (ao_fifo_empty(ao_ps2_rx_fifo)) {
+ return AO_READ_AGAIN;
+ }
+ ao_fifo_remove(ao_ps2_rx_fifo, u);
+
+ return (int) u;
+}
+
+uint8_t
+ao_ps2_get(void)
+{
+ int c;
+ ao_arch_block_interrupts();
+ while ((c = _ao_ps2_poll()) == AO_READ_AGAIN)
+ ao_sleep(&ao_ps2_rx_fifo);
+ ao_arch_release_interrupts();
+ return (uint8_t) c;
+}
+
+
+int
+ao_ps2_poll(void)
+{
+ int c;
+ ao_arch_block_interrupts();
+ c = _ao_ps2_poll();
+ ao_arch_release_interrupts();
+ return (uint8_t) c;
+}
+
+void
+ao_ps2_put(uint8_t c)
+{
+ ao_arch_block_interrupts();
+ ao_ps2_tx = ((uint16_t) c) | (_ao_ps2_parity(c) << 8) | (3 << 9);
+ ao_ps2_tx_count = 11;
+ ao_exti_disable(AO_PS2_CLOCK_PORT, AO_PS2_CLOCK_BIT);
+ ao_arch_release_interrupts();
+
+ /* pull the clock pin down */
+ ao_enable_output(AO_PS2_CLOCK_PORT, AO_PS2_CLOCK_BIT, AO_PS2_CLOCK_PIN, 0);
+ ao_delay(0);
+
+ /* pull the data pin down for the start bit */
+ ao_enable_output(AO_PS2_DATA_PORT, AO_PS2_DATA_BIT, AO_PS2_DATA_PIN, 0);
+ ao_delay(0);
+
+ /* switch back to input mode for the interrupt to work */
+ ao_exti_setup(AO_PS2_CLOCK_PORT, AO_PS2_CLOCK_BIT,
+ AO_PS2_CLOCK_MODE(AO_EXTI_MODE_PULL_UP),
+ ao_ps2_isr);
+ ao_exti_enable(AO_PS2_CLOCK_PORT, AO_PS2_CLOCK_BIT);
+
+ /* wait for the bits to drain */
+ while (ao_ps2_tx_count)
+ ao_sleep(&ao_ps2_tx_count);
+
+}
+
+static uint8_t ao_ps2_down[128 / 8];
+
+static void
+ao_ps2_set_down(uint8_t code, uint8_t value)
+{
+ uint8_t shift = (code & 0x07);
+ uint8_t byte = code >> 3;
+
+ ao_ps2_down[byte] = (ao_ps2_down[byte] & ~(1 << shift)) | (value << shift);
+}
+
+uint8_t
+ao_ps2_is_down(uint8_t code)
+{
+ uint8_t shift = (code & 0x07);
+ uint8_t byte = code >> 3;
+
+ return (ao_ps2_down[byte] >> shift) & 1;
+}
+
+static void
+_ao_ps2_set_leds(void)
+{
+ uint8_t led = 0;
+ if (ao_ps2_is_down(AO_PS2_CAPS_LOCK))
+ led |= AO_PS2_SET_LEDS_CAPS;
+ if (ao_ps2_is_down(AO_PS2_NUM_LOCK))
+ led |= AO_PS2_SET_LEDS_NUM;
+ if (ao_ps2_is_down(AO_PS2_SCROLL_LOCK))
+ led |= AO_PS2_SET_LEDS_SCROLL;
+ ao_arch_release_interrupts();
+ ao_ps2_put(AO_PS2_SET_LEDS);
+ while (ao_ps2_get() != 0xfa);
+ ao_ps2_put(led);
+ ao_arch_block_interrupts();
+}
+
+static uint8_t
+ao_ps2_is_lock(uint8_t code) {
+ switch (code) {
+ case AO_PS2_CAPS_LOCK:
+ case AO_PS2_NUM_LOCK:
+ case AO_PS2_SCROLL_LOCK:
+ return 1;
+ }
+ return 0;
+}
+
+static void
+_ao_ps2_set_scancode_set(uint8_t set)
+{
+ ao_ps2_scancode_set = set;
+ ao_arch_release_interrupts();
+ ao_ps2_put(AO_PS2_SET_SCAN_CODE_SET);
+ while (ao_ps2_get() != 0xfa);
+ ao_ps2_put(set);
+ ao_ps2_put(AO_PS2_SET_KEY_TYPEMATIC_MAKE_BREAK);
+ while (ao_ps2_get() != 0xfa);
+ ao_arch_block_interrupts();
+}
+
+static int
+_ao_ps2_poll_key(void)
+{
+ int c;
+ uint8_t set_led = 0;
+ static uint8_t saw_break;
+
+ c = _ao_ps2_poll();
+ if (c < 0) {
+ if (ao_ps2_scancode_set != 3) {
+ _ao_ps2_set_scancode_set(3);
+ }
+ return c;
+ }
+
+ if (c == AO_PS2_BREAK) {
+ saw_break = 1;
+ return AO_READ_AGAIN;
+ }
+ if (c & 0x80)
+ return AO_READ_AGAIN;
+
+ if (ao_ps2_is_lock(c)) {
+ if (saw_break) {
+ saw_break = 0;
+ return AO_READ_AGAIN;
+ }
+ if (ao_ps2_is_down(c))
+ saw_break = 1;
+ set_led = 1;
+ }
+ if (saw_break) {
+ saw_break = 0;
+ ao_ps2_set_down(c, 0);
+ c |= 0x80;
+ } else
+ ao_ps2_set_down(c, 1);
+ if (set_led)
+ _ao_ps2_set_leds();
+
+ if (ao_ps2_scancode_set != 3)
+ _ao_ps2_set_scancode_set(3);
+
+ return c;
+}
+
+int
+ao_ps2_poll_key(void)
+{
+ int c;
+ ao_arch_block_interrupts();
+ c = _ao_ps2_poll_key();
+ ao_arch_release_interrupts();
+ return c;
+}
+
+uint8_t
+ao_ps2_get_key(void)
+{
+ int c;
+ ao_arch_block_interrupts();
+ while ((c = _ao_ps2_poll_key()) == AO_READ_AGAIN)
+ ao_sleep(&ao_ps2_rx_fifo);
+ ao_arch_release_interrupts();
+ return (uint8_t) c;
+}
+
+static const uint8_t ao_ps2_asciimap[128][2] = {
+ [AO_PS2_A] = { 'a', 'A' },
+ [AO_PS2_B] = { 'b', 'B' },
+ [AO_PS2_C] = { 'c', 'C' },
+ [AO_PS2_D] = { 'd', 'D' },
+ [AO_PS2_E] = { 'e', 'E' },
+ [AO_PS2_F] = { 'f', 'F' },
+ [AO_PS2_G] = { 'g', 'G' },
+ [AO_PS2_H] = { 'h', 'H' },
+ [AO_PS2_I] = { 'i', 'I' },
+ [AO_PS2_J] = { 'j', 'J' },
+ [AO_PS2_K] = { 'k', 'K' },
+ [AO_PS2_L] = { 'l', 'L' },
+ [AO_PS2_M] = { 'm', 'M' },
+ [AO_PS2_N] = { 'n', 'N' },
+ [AO_PS2_O] = { 'o', 'O' },
+ [AO_PS2_P] = { 'p', 'P' },
+ [AO_PS2_Q] = { 'q', 'Q' },
+ [AO_PS2_R] = { 'r', 'R' },
+ [AO_PS2_S] = { 's', 'S' },
+ [AO_PS2_T] = { 't', 'T' },
+ [AO_PS2_U] = { 'u', 'U' },
+ [AO_PS2_V] = { 'v', 'V' },
+ [AO_PS2_W] = { 'w', 'W' },
+ [AO_PS2_X] = { 'x', 'X' },
+ [AO_PS2_Y] = { 'y', 'Y' },
+ [AO_PS2_Z] = { 'z', 'Z' },
+
+ [AO_PS2_0] = { '0', ')' },
+ [AO_PS2_1] = { '1', '!' },
+ [AO_PS2_2] = { '2', '@' },
+ [AO_PS2_3] = { '3', '#' },
+ [AO_PS2_4] = { '4', '$' },
+ [AO_PS2_5] = { '5', '%' },
+ [AO_PS2_6] = { '6', '^' },
+ [AO_PS2_7] = { '7', '&' },
+ [AO_PS2_8] = { '8', '*' },
+ [AO_PS2_9] = { '9', '(' },
+
+ [AO_PS2_GRAVE] = { '`', '~' },
+ [AO_PS2_HYPHEN] = { '-', '_' },
+ [AO_PS2_EQUAL] = { '=', '+' },
+ [AO_PS2_BACKSLASH] = { '\\', '|' },
+ [AO_PS2_BACKSPACE] = { '\010', '\010' },
+ [AO_PS2_SPACE] = { ' ', ' ' },
+ [AO_PS2_TAB] = { '\t', '\t' },
+
+ [AO_PS2_ENTER] = { '\r', '\r' },
+ [AO_PS2_ESC] = { '\033', '\033' },
+
+ [AO_PS2_OPEN_SQ] = { '[', '{' },
+ [AO_PS2_DELETE] = { '\177', '\177' },
+
+ [AO_PS2_KP_TIMES] = { '*', '*' },
+ [AO_PS2_KP_PLUS] = { '+', '+' },
+ [AO_PS2_KP_ENTER] = { '\r', '\r' },
+ [AO_PS2_KP_DECIMAL] = { '.', '.' },
+ [AO_PS2_KP_0] = { '0', '0' },
+ [AO_PS2_KP_1] = { '1', '1' },
+ [AO_PS2_KP_2] = { '2', '2' },
+ [AO_PS2_KP_3] = { '3', '3' },
+ [AO_PS2_KP_4] = { '4', '4' },
+ [AO_PS2_KP_5] = { '5', '5' },
+ [AO_PS2_KP_6] = { '6', '6' },
+ [AO_PS2_KP_7] = { '7', '7' },
+ [AO_PS2_KP_8] = { '8', '8' },
+ [AO_PS2_KP_9] = { '9', '9' },
+ [AO_PS2_CLOSE_SQ] = { ']', '}' },
+ [AO_PS2_SEMICOLON] = { ';', ':' },
+ [AO_PS2_ACUTE] = { '\'', '"' },
+ [AO_PS2_COMMA] = { ',', '<' },
+ [AO_PS2_PERIOD] = { '.', '>' },
+ [AO_PS2_SLASH] = { '/', '?' },
+};
+
+int
+ao_ps2_ascii(uint8_t key)
+{
+ uint8_t col;
+ char a;
+
+ /* Skip key releases */
+ if (key & 0x80)
+ return AO_READ_AGAIN;
+
+ col = 0;
+ if (ao_ps2_is_down(AO_PS2_L_SHIFT) || ao_ps2_is_down(AO_PS2_R_SHIFT))
+ col = 1;
+
+ /* caps lock */
+ a = ao_ps2_asciimap[key][0];
+ if (!a)
+ return AO_READ_AGAIN;
+
+ if ('a' <= a && a <= 'z')
+ if (ao_ps2_is_down(AO_PS2_CAPS_LOCK))
+ col ^= 1;
+ a = ao_ps2_asciimap[key][col];
+ if ('@' <= a && a <= 0x7f && (ao_ps2_is_down(AO_PS2_L_CTRL) || ao_ps2_is_down(AO_PS2_R_CTRL)))
+ a &= 0x1f;
+ return a;
+}
+
+int
+_ao_ps2_pollchar(void)
+{
+ int key;
+
+ key = _ao_ps2_poll_key();
+ if (key < 0)
+ return key;
+ return ao_ps2_ascii(key);
+}
+
+char
+ao_ps2_getchar(void)
+{
+ int c;
+ ao_arch_block_interrupts();
+ while ((c = _ao_ps2_pollchar()) == AO_READ_AGAIN)
+ ao_sleep(&ao_ps2_rx_fifo);
+ ao_arch_release_interrupts();
+ return (char) c;
+}
+
+static void
+ao_ps2_isr(void)
+{
+ uint8_t bit;
+
+ if (ao_ps2_tx_count) {
+ ao_gpio_set(AO_PS2_DATA_PORT, AO_PS2_DATA_BIT, AO_PS2_DATA_PIN, ao_ps2_tx&1);
+ ao_ps2_tx >>= 1;
+ ao_ps2_tx_count--;
+ if (!ao_ps2_tx_count) {
+ ao_enable_input(AO_PS2_DATA_PORT, AO_PS2_DATA_BIT, AO_EXTI_MODE_PULL_UP);
+ ao_wakeup(&ao_ps2_tx_count);
+ }
+ return;
+ }
+ /* reset if its been a while */
+ if ((ao_tick_count - ao_ps2_tick) > AO_MS_TO_TICKS(100))
+ ao_ps2_count = 0;
+ ao_ps2_tick = ao_tick_count;
+
+ bit = ao_gpio_get(AO_PS2_DATA_PORT, AO_PS2_DATA_BIT, AO_PS2_DATA_PIN);
+ if (ao_ps2_count == 0) {
+ /* check for start bit, ignore if not zero */
+ if (bit)
+ return;
+ ao_ps2_value = 0;
+ } else if (ao_ps2_count < 9) {
+ ao_ps2_value |= (bit << (ao_ps2_count - 1));
+ } else if (ao_ps2_count == 10) {
+ ao_fifo_insert(ao_ps2_rx_fifo, ao_ps2_value);
+ ao_wakeup(&ao_ps2_rx_fifo);
+ if (ao_ps2_stdin)
+ ao_wakeup(&ao_stdin_ready);
+ ao_ps2_count = 0;
+ return;
+ }
+ ao_ps2_count++;
+}
+
+void
+ao_ps2_init(void)
+{
+ ao_enable_input(AO_PS2_DATA_PORT, AO_PS2_DATA_BIT,
+ AO_EXTI_MODE_PULL_UP);
+
+ ao_enable_port(AO_PS2_CLOCK_PORT);
+
+ ao_exti_setup(AO_PS2_CLOCK_PORT, AO_PS2_CLOCK_BIT,
+ AO_PS2_CLOCK_MODE(AO_EXTI_MODE_PULL_UP),
+ ao_ps2_isr);
+ ao_exti_enable(AO_PS2_CLOCK_PORT, AO_PS2_CLOCK_BIT);
+
+ ao_ps2_scancode_set = 2;
+}
--- /dev/null
+/*
+ * Copyright © 2016 Keith Packard <keithp@keithp.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ */
+
+#ifndef _AO_PS2_H_
+#define _AO_PS2_H_
+
+extern uint8_t ao_ps2_stdin;
+
+int
+ao_ps2_poll(void);
+
+uint8_t
+ao_ps2_get(void);
+
+void
+ao_ps2_put(uint8_t b);
+
+uint8_t
+ao_ps2_is_down(uint8_t code);
+
+int
+ao_ps2_poll_key(void);
+
+uint8_t
+ao_ps2_get_key(void);
+
+int
+ao_ps2_ascii(uint8_t key);
+
+int
+_ao_ps2_pollchar(void);
+
+char
+ao_ps2_getchar(void);
+
+void
+ao_ps2_init(void);
+
+/* From http://computer-engineering.org/ps2keyboard/ */
+
+/* Device responds with ACK and then resets */
+#define AO_PS2_RESET 0xff
+
+/* Device retransmits last byte */
+#define AO_PS2_RESEND 0xfe
+
+/* Setting key report only works in mode 3 */
+
+/* Disable break and typematic for specified mode 3 keys. Terminate with invalid key */
+#define AO_PS2_SET_KEY_MAKE 0xfd
+
+/* Disable typematic for keys */
+#define AO_PS2_SET_KEY_MAKE_BREAK 0xfc
+
+/* Disable break code for keys */
+#define AO_PS2_SET_KEY_TYPEMATIC 0xfb
+
+/* Enable make, break and typematic */
+#define AO_PS2_SET_KEY_TYPEMATIC_MAKE_BREAK 0xfa
+
+/* Disable break and typematic for all */
+#define AO_PS2_SET_ALL_MAKE 0xf9
+
+/* Disable typematic for all */
+#define AO_PS2_SET_ALL_MAKE_BREAK 0xf8
+
+/* Disable break for all */
+#define AO_PS2_SET_ALL_TYPEMATIC 0xf7
+
+/* Set keyboard to default (repeat, report and scan code set 2) */
+#define AO_PS2_SET_DEFAULT 0xf6
+
+/* Disable and reset to default */
+#define AO_PS2_DISABLE 0xf5
+
+/* Enable */
+#define AO_PS2_ENABLE 0xf4
+
+/* Set repeat rate. Bytes 5-6 are the start delay, bits 0-4 are the rate */
+#define AO_PS2_SET_REPEAT_RATE 0xf3
+
+/* Read keyboard id. Returns two bytes */
+#define AO_PS2_GETID 0xf2
+
+/* Set scan code (1, 2, or 3) */
+#define AO_PS2_SET_SCAN_CODE_SET 0xf0
+
+/* Echo. Keyboard replies with Echo */
+#define AO_PS2_ECHO 0xee
+
+/* Set LEDs */
+#define AO_PS2_SET_LEDS 0xed
+# define AO_PS2_SET_LEDS_SCROLL 0x01
+# define AO_PS2_SET_LEDS_NUM 0x02
+# define AO_PS2_SET_LEDS_CAPS 0x04
+
+#define AO_PS2_BREAK 0xf0
+#define AO_PS2_ACK 0xfa
+#define AO_PS2_ERROR 0xfc
+#define AO_PS2_NAK 0xfe
+
+/* Scan code set 3 */
+
+#define AO_PS2_A 0x1c
+#define AO_PS2_B 0x32
+#define AO_PS2_C 0x21
+#define AO_PS2_D 0x23
+#define AO_PS2_E 0x24
+#define AO_PS2_F 0x2b
+#define AO_PS2_G 0x34
+#define AO_PS2_H 0x33
+#define AO_PS2_I 0x43
+#define AO_PS2_J 0x3b
+#define AO_PS2_K 0x42
+#define AO_PS2_L 0x4b
+#define AO_PS2_M 0x3a
+#define AO_PS2_N 0x31
+#define AO_PS2_O 0x44
+#define AO_PS2_P 0x4d
+#define AO_PS2_Q 0x15
+#define AO_PS2_R 0x2d
+#define AO_PS2_S 0x1b
+#define AO_PS2_T 0x2c
+#define AO_PS2_U 0x3c
+#define AO_PS2_V 0x2a
+#define AO_PS2_W 0x1d
+#define AO_PS2_X 0x22
+#define AO_PS2_Y 0x35
+#define AO_PS2_Z 0x1a
+#define AO_PS2_0 0x45
+#define AO_PS2_1 0x16
+#define AO_PS2_2 0x1e
+#define AO_PS2_3 0x26
+#define AO_PS2_4 0x25
+#define AO_PS2_5 0x2e
+#define AO_PS2_6 0x36
+#define AO_PS2_7 0x3d
+#define AO_PS2_8 0x3e
+#define AO_PS2_9 0x46
+#define AO_PS2_GRAVE 0x0e
+#define AO_PS2_HYPHEN 0x4e
+#define AO_PS2_EQUAL 0x55
+#define AO_PS2_BACKSLASH 0x5c
+#define AO_PS2_BACKSPACE 0x66
+#define AO_PS2_SPACE 0x29
+#define AO_PS2_TAB 0x0d
+#define AO_PS2_CAPS_LOCK 0x14
+#define AO_PS2_L_SHIFT 0x12
+#define AO_PS2_L_CTRL 0x11
+#define AO_PS2_L_WIN 0x8b
+#define AO_PS2_L_ALT 0x19
+#define AO_PS2_R_SHIFT 0x59
+#define AO_PS2_R_CTRL 0x58
+#define AO_PS2_R_WIN 0x8c
+#define AO_PS2_R_ALT 0x39
+#define AO_PS2_APPS 0x8d
+#define AO_PS2_ENTER 0x5a
+#define AO_PS2_ESC 0x08
+#define AO_PS2_F1 0x07
+#define AO_PS2_F2 0x0f
+#define AO_PS2_F3 0x17
+#define AO_PS2_F4 0x1f
+#define AO_PS2_F5 0x27
+#define AO_PS2_F6 0x2f
+#define AO_PS2_F7 0x37
+#define AO_PS2_F8 0x3f
+#define AO_PS2_F9 0x47
+#define AO_PS2_F10 0x4f
+#define AO_PS2_F11 0x56
+#define AO_PS2_F12 0x5e
+#define AO_PS2_PRNT_SCRN 0x57
+#define AO_PS2_SCROLL_LOCK 0x5f
+#define AO_PS2_PAUSE 0x62
+#define AO_PS2_OPEN_SQ 0x54
+#define AO_PS2_INSERT 0x67
+#define AO_PS2_HOME 0x6e
+#define AO_PS2_PG_UP 0x6f
+#define AO_PS2_DELETE 0x64
+#define AO_PS2_END 0x65
+#define AO_PS2_PG_DN 0x6d
+#define AO_PS2_UP 0x63
+#define AO_PS2_LEFT 0x61
+#define AO_PS2_DOWN 0x60
+#define AO_PS2_RIGHT 0x6a
+#define AO_PS2_NUM_LOCK 0x76
+#define AO_PS2_KP_TIMES 0x7e
+#define AO_PS2_KP_PLUS 0x7c
+#define AO_PS2_KP_ENTER 0x79
+#define AO_PS2_KP_DECIMAL 0x71
+#define AO_PS2_KP_0 0x70
+#define AO_PS2_KP_1 0x69
+#define AO_PS2_KP_2 0x72
+#define AO_PS2_KP_3 0x7a
+#define AO_PS2_KP_4 0x6b
+#define AO_PS2_KP_5 0x73
+#define AO_PS2_KP_6 0x74
+#define AO_PS2_KP_7 0x6c
+#define AO_PS2_KP_8 0x75
+#define AO_PS2_KP_9 0x7d
+#define AO_PS2_CLOSE_SQ 0x5b
+#define AO_PS2_SEMICOLON 0x4c
+#define AO_PS2_ACUTE 0x52
+#define AO_PS2_COMMA 0x41
+#define AO_PS2_PERIOD 0x49
+#define AO_PS2_SLASH 0x4a
+
+#define AO_PS2_RELEASE_FLAG 0x80
+
+#endif /* _AO_PS2_H_ */