diff options
| author | Keith Packard <keithp@keithp.com> | 2017-02-20 12:19:42 -0800 | 
|---|---|---|
| committer | Keith Packard <keithp@keithp.com> | 2017-02-20 12:34:02 -0800 | 
| commit | c1d52178ce63ebdc44c83d1bca5027942e2d778c (patch) | |
| tree | b8e4d366a9f46c235e2c9a69bdc85100714a18e1 | |
| parent | 6b39d3093c3b87689717bb03988d160473c53c64 (diff) | |
altos: Add PS/2 keyboard driver
Interrupt driven, includes standard US keymap.
Signed-off-by: Keith Packard <keithp@keithp.com>
| -rw-r--r-- | src/drivers/ao_ps2.c | 419 | ||||
| -rw-r--r-- | src/drivers/ao_ps2.h | 220 | 
2 files changed, 639 insertions, 0 deletions
| diff --git a/src/drivers/ao_ps2.c b/src/drivers/ao_ps2.c new file mode 100644 index 00000000..29eecea8 --- /dev/null +++ b/src/drivers/ao_ps2.c @@ -0,0 +1,419 @@ +/* + * 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; +} diff --git a/src/drivers/ao_ps2.h b/src/drivers/ao_ps2.h new file mode 100644 index 00000000..f1f05ee5 --- /dev/null +++ b/src/drivers/ao_ps2.h @@ -0,0 +1,220 @@ +/* + * 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_ */ | 
