blob: 964c6dd16b4c6c90929e263e411f0c9a9aabf3af [file] [log] [blame]
/*
* Copyright 2017, Data61, CSIRO (ABN 41 687 119 230)
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#include "keyboard_ps2.h"
#include "keyboard_vkey.h"
#include <stdbool.h>
#include <stdint.h>
#include <stdlib.h>
#include <string.h>
#include <assert.h>
static void
ps2_single_control(ps_io_ops_t *ops, int8_t byte)
{
ps_io_port_out(&ops->io_port_ops, PS2_IOPORT_CONTROL, 1, byte);
}
static void
ps2_dual_control(ps_io_ops_t *ops, int8_t byte1, int8_t byte2)
{
ps_io_port_out(&ops->io_port_ops, PS2_IOPORT_CONTROL, 1, byte1);
ps_io_port_out(&ops->io_port_ops, PS2_IOPORT_DATA, 1, byte2);
}
static uint8_t
ps2_read_control_status(ps_io_ops_t *ops)
{
uint32_t res = 0;
int error = ps_io_port_in(&ops->io_port_ops, PS2_IOPORT_CONTROL, 1, &res);
assert(!error);
(void) error;
return (uint8_t) res;
}
static uint8_t
ps2_read_data(ps_io_ops_t *ops)
{
uint32_t res = 0;
int error = ps_io_port_in(&ops->io_port_ops, PS2_IOPORT_DATA, 1, &res);
assert(!error);
(void) error;
return (uint8_t) res;
}
static void
ps2_write_output(ps_io_ops_t *ops, uint8_t byte)
{
while ( (ps2_read_control_status(ops) & 0x2) != 0);
ps_io_port_out(&ops->io_port_ops, PS2_IOPORT_DATA, 1, byte);
}
static uint8_t
ps2_read_output(ps_io_ops_t *ops)
{
while ( (ps2_read_control_status(ops) & 0x1) == 0);
return ps2_read_data(ops);
}
static void
ps2_send_keyboard_cmd(ps_io_ops_t *ops, uint8_t cmd)
{
do {
ps2_write_output(ops, cmd);
} while (ps2_read_output(ops) != KEYBOARD_ACK);
}
static void
ps2_send_keyboard_cmd_param(ps_io_ops_t *ops, uint8_t cmd, uint8_t param)
{
do {
ps2_write_output(ops, cmd);
ps2_write_output(ops, param);
} while (ps2_read_output(ops) != KEYBOARD_ACK);
}
/* ---------------------------------------------------------------------------------------------- */
static keyboard_key_event_t
keyboard_state_push_ps2_keyevent(struct keyboard_state *s, uint16_t ps2_keyevent)
{
keyboard_key_event_t ev_none = { .vkey = -1, .pressed = false };
if (s->state == KEYBOARD_PS2_STATE_IGNORE) {
s->num_ignore--;
if (s->num_ignore == 0) {
s->state = KEYBOARD_PS2_STATE_NORMAL;
}
return ev_none;
}
assert(s->state & KEYBOARD_PS2_STATE_NORMAL);
/* Handle release / extended mode keys. */
switch (ps2_keyevent) {
case KEYBOARD_PS2_EVENTCODE_RELEASE:
s->state |= KEYBOARD_PS2_STATE_RELEASE_KEY;
return ev_none;
case KEYBOARD_PS2_EVENTCODE_EXTENDED:
s->state |= KEYBOARD_PS2_STATE_EXTENDED_MODE;
return ev_none;
case KEYBOARD_PS2_EVENTCODE_EXTENDED_PAUSE:
s->state = KEYBOARD_PS2_STATE_IGNORE;
s->num_ignore = 7; /* Ignore the next 7 characters of pause seq. */
keyboard_key_event_t ev = { .vkey = VK_PAUSE, .pressed = true };
return ev;
}
/* Prepend 0xE0 to ps2 keycode if in extended mode. */
if (s->state & KEYBOARD_PS2_STATE_EXTENDED_MODE) {
ps2_keyevent = 0xE000 + (ps2_keyevent & 0xFF);
s->state &= ~KEYBOARD_PS2_STATE_EXTENDED_MODE;
}
int16_t vkey = keycode_ps2_to_vkey(ps2_keyevent);
if (vkey < 0) {
/* No associated vkey with this PS2 key. */
s->state = KEYBOARD_PS2_STATE_NORMAL;
return ev_none;
}
/* Set keystate according to press or release. */
if (s->state & KEYBOARD_PS2_STATE_RELEASE_KEY) {
/* Release event. */
keyboard_key_event_t ev = { .vkey = vkey, .pressed = false };
s->state &= ~KEYBOARD_PS2_STATE_RELEASE_KEY;
return ev;
}
/* Press event. */
keyboard_key_event_t ev = { .vkey = vkey, .pressed = true };
return ev;
}
/* ---------------------------------------------------------------------------------------------- */
int
keyboard_init(struct keyboard_state *state, const ps_io_ops_t* ops,
void (*handle_event_callback)(keyboard_key_event_t ev, void *cookie))
{
assert(state && ops);
memset(state, 0, sizeof(struct keyboard_state));
state->state = KEYBOARD_PS2_STATE_NORMAL;
state->ops = *ops;
state->handle_event_callback = handle_event_callback;
/* Initialise the PS2 keyboard device. */
/* Disable both PS2 devices. */
ps2_single_control(&state->ops, PS2_CMD_DISABLE_KEYBOARD_INTERFACE);
ps2_single_control(&state->ops, PS2_CMD_DISABLE_MOUSE_INTERFACE);
/* Flush the output buffer. */
ps2_read_data(&state->ops);
/* Enable IRQs and disable translation (IRQ bits 0, 1, translation 6). */
ps2_single_control(&state->ops, PS2_READ_CMD_BYTE);
uint8_t config = ps2_read_output(&state->ops);
config |= 0x1;
config &= 0xBF;
ps2_dual_control(&state->ops, PS2_WRITE_CMD_BYTE, config);
/* Run a controller self test. */
ps2_single_control(&state->ops, PS2_CMD_CONTROLLER_SELF_TEST);
uint8_t res = ps2_read_output(&state->ops);
if (res != PS2_CONTROLLER_SELF_TEST_OK) {
return -1;
}
/* Run keyboard interface test. */
ps2_single_control(&state->ops, PS2_CMD_KEYBOARD_INTERFACE_TEST);
res = ps2_read_output(&state->ops);
if (res != 0) {
return -1;
}
/* Enable keyboard interface. */
ps2_single_control(&state->ops, PS2_CMD_ENABLE_KEYBOARD_INTERFACE);
ps2_read_data(&state->ops);
/* Reset the keyboard device. */
if (keyboard_reset(state)) {
return -1;
}
/* Set scanmode 2. */
keyboard_set_scanmode(state, 2);
return 0;
}
void
keyboard_set_led(struct keyboard_state *state, char scroll_lock, char num_lock, char caps_lock)
{
ps2_send_keyboard_cmd_param(&state->ops, KEYBOARD_SET_LEDS,
scroll_lock | num_lock << 1 | caps_lock << 2);
}
void
keyboard_set_scanmode(struct keyboard_state *state, uint8_t mode)
{
ps2_send_keyboard_cmd(&state->ops, KEYBOARD_DISABLE_SCAN); /* Disable scanning. */
ps2_send_keyboard_cmd_param(&state->ops, KEYBOARD_SET_SCANCODE_MODE, mode); /* Set scan code. */
ps2_send_keyboard_cmd(&state->ops, KEYBOARD_ENABLE_SCAN); /* Re-Enable scanning. */
}
int
keyboard_reset(struct keyboard_state *state)
{
/* Reset the keyboard device. */
ps2_send_keyboard_cmd(&state->ops, KEYBOARD_RESET);
/* Wait for the Basic Assurance Test. */
while (1) {
uint8_t res = ps2_read_output(&state->ops);
if (res == KEYBOARD_BAT_SUCCESSFUL) {
break;
}
if (res == KEYBOARD_ERROR) {
assert(!"keyboard init keyboard BAT failed.");
return -1;
}
}
return 0;
}
keyboard_key_event_t
keyboard_poll_ps2_keyevent(struct keyboard_state *state)
{
if ((ps2_read_control_status(&state->ops) & 0x1) == 0) {
/* No key events generated. */
keyboard_key_event_t ev = { .vkey = -1, .pressed = false };
return ev;
}
return keyboard_state_push_ps2_keyevent(state, ps2_read_data(&state->ops));
}
void
keyboard_poll_ps2_keyevents(struct keyboard_state *state, void *cookie)
{
keyboard_key_event_t ev = { .vkey = -1, .pressed = false };
do {
ev = keyboard_poll_ps2_keyevent(state);
if (ev.vkey != -1 && state->handle_event_callback) {
state->handle_event_callback(ev, cookie);
}
} while (ev.vkey != -1);
}