| /* |
| * Copyright 2018, Data61 |
| * Commonwealth Scientific and Industrial Research Organisation (CSIRO) |
| * ABN 41 687 119 230. |
| * |
| * This software may be distributed and modified according to the terms of |
| * the BSD 2-Clause license. Note that NO WARRANTY is provided. |
| * See "LICENSE_BSD2.txt" for details. |
| * |
| * @TAG(DATA61_BSD) |
| */ |
| |
| #include <stdlib.h> |
| #include <keyboard/keyboard.h> |
| #include <assert.h> |
| |
| /* This code only provides very basic control of the keyboard controller. If |
| * you are intending on extending this driver, more information about the |
| * keyboard controller can be found at |
| * http://www.win.tue.nl/~aeb/linux/kbd/scancodes-11.html. |
| * This driver takes complete control of both ps/2 ports, and assumes that the keyboard |
| * is attached to the first port. It places the keyboard in mode 2 scancode generator |
| */ |
| |
| /* Directives can be written to this IO port to inform the keyboard controller |
| * of how your next write to one of the keyboard ports should be interpreted. |
| */ |
| #define KEYBOARD_INPUT_CONTROL 0x64 |
| |
| /* This byte can be written to port KEYBOARD_INPUT_CONTROL to tell the keyboard |
| * controller that the next byte you write to port KEYBOARD_OUTPUT_BUFFER |
| * should be interpreted as a write to the Controller Command Byte. |
| */ |
| #define KEYBOARD_CONTROL_WRITE_CCB 0x60 |
| |
| /* This IO port can be written to in order to pass data to the keyboard |
| * controller. Its data is interpreted differently depending on what you |
| * previously wrote to the port KEYBOARD_INPUT_CONTROL. It can be read from to |
| * get the last character received from the keyboard. |
| */ |
| #define KEYBOARD_OUTPUT_BUFFER 0x60 |
| |
| /* The following values can be written to the Controller Command Byte to enable |
| * or disable keyboard interrupt delivery respectively. Without interrupts |
| * enabled, the controller registers need to be polled for input which is not |
| * ideal. Note that every bit in the Controller Command Byte is significant and |
| * so these writes may have unintended side-effects if you are using this |
| * driver in combination with, say a mouse driver. If this is the case I |
| * suggest you improve this driver to read the CCB before writing to it in |
| * order to only modify bit 0. |
| */ |
| #define KEYBOARD_CCB_IRQ_ENABLE 0x01 |
| #define KEYBOARD_CCB_IRQ_DISABLE 0x00 |
| |
| static in8_fn io_in8; |
| static out8_fn io_out8; |
| |
| static inline uint8_t ps2_poll_output(void) |
| { |
| return io_in8(KEYBOARD_OUTPUT_BUFFER); |
| } |
| |
| static uint8_t _ps2_read_control(void) |
| { |
| return io_in8(KEYBOARD_INPUT_CONTROL); |
| } |
| |
| static inline uint8_t ps2_read_output(void) |
| { |
| while ((_ps2_read_control() & 0x1) == 0); |
| return ps2_poll_output(); |
| } |
| |
| /* some helper macros for manipulating io ports |
| * your opinion on macros may vary. but the goal is to make the keyboard setup code look somewhat |
| * readable */ |
| #define ps2_single_control(byte) io_out8(KEYBOARD_INPUT_CONTROL, byte) |
| #define ps2_dual_control(byte1, byte2) do {ps2_single_control(byte1);io_out8(KEYBOARD_CONTROL_WRITE_CCB, byte2);} while(0) |
| #define ps2_write_output(byte) io_out8(KEYBOARD_OUTPUT_BUFFER, byte) |
| |
| void sel4keyboard_init(int enable_interrupt, in8_fn in8, out8_fn out8) |
| { |
| io_in8 = in8; |
| io_out8 = out8; |
| |
| int error; |
| uint8_t config; |
| /* initialize the keyboard */ |
| //disable both ps2 devices |
| ps2_single_control(0xAD); |
| ps2_single_control(0xA7); |
| // flush the output buffer by reading and discarding |
| ps2_poll_output(); |
| // get old internal configuration |
| ps2_single_control(0x20); |
| config = ps2_read_output(); |
| // disable irqs and translation (bits 0, 1, 6) |
| config &= 0xBC; |
| ps2_dual_control(0x60, config); |
| // perform a test |
| ps2_single_control(0xAA); |
| error = ps2_read_output(); |
| assert(error == 0x55); |
| // test port |
| ps2_single_control(0xAB); |
| error = ps2_read_output(); |
| assert(error == 0x00); |
| // enable port |
| ps2_single_control(0xAE); |
| if (enable_interrupt) { |
| config |= 0x1; |
| ps2_dual_control(0x60, config); |
| } |
| // switch to mode 2 scancode |
| ps2_write_output(0xf0); |
| ps2_write_output(2); |
| error = ps2_read_output(); |
| assert(error == 0xfa); |
| } |
| |
| void sel4keyboard_reset() |
| { |
| uint8_t config; |
| /* disable ports and interrupts */ |
| // get old internal configuration |
| ps2_single_control(0x20); |
| config = ps2_read_output(); |
| config &= 0xBC; |
| ps2_dual_control(0x60, config); |
| //disable both ps2 devices |
| ps2_single_control(0xAD); |
| ps2_single_control(0xA7); |
| } |
| |
| int sel4keyboard_get_scancode(int *scancode) |
| { |
| /* see if there is a waiting scancode */ |
| if ((_ps2_read_control() & 0x1) == 0) { |
| return 0; |
| } |
| *scancode = ps2_poll_output(); |
| return 1; |
| } |