Files
OpenCellular/common/keyboard_8042.c
Randall Spangler 078dfabb68 Remove unneeded includes of registers.h
The registers.h file should only be included by code in the chip/ and
board/ directories.  Code outside those directories should not access
chip-specific registers.

(This change doesn't completely fix that, because
common/extpower_usb.c uses STM32-specific regs, but we'll fix that in
a separate CL.)

BUG=chrome-os-partner:18343
BRANCH=none
TEST=compile all platforms

Change-Id: Ic499f56690c38663083423b0593800161a68e6e9
Signed-off-by: Randall Spangler <rspangler@chromium.org>
Reviewed-on: https://gerrit.chromium.org/gerrit/64382
Reviewed-by: Vic Yang <victoryang@chromium.org>
2013-08-02 17:32:25 -07:00

1123 lines
27 KiB
C

/* Copyright (c) 2013 The Chromium OS Authors. All rights reserved.
* Use of this source code is governed by a BSD-style license that can be
* found in the LICENSE file.
*
* 8042 keyboard protocol
*/
#include "chipset.h"
#include "common.h"
#include "console.h"
#include "hooks.h"
#include "host_command.h"
#include "i8042_protocol.h"
#include "keyboard_config.h"
#include "keyboard_protocol.h"
#include "lightbar.h"
#include "lpc.h"
#include "power_button.h"
#include "queue.h"
#include "shared_mem.h"
#include "system.h"
#include "task.h"
#include "timer.h"
#include "util.h"
/* Console output macros */
#define CPUTS(outstr) cputs(CC_KEYBOARD, outstr)
#define CPRINTF(format, args...) cprintf(CC_KEYBOARD, format, ## args)
#ifdef CONFIG_KEYBOARD_DEBUG
#define CPUTS5(outstr) cputs(CC_KEYBOARD, outstr)
#define CPRINTF5(format, args...) cprintf(CC_KEYBOARD, format, ## args)
#else
#define CPUTS5(outstr)
#define CPRINTF5(format, args...)
#endif
static enum {
STATE_NORMAL = 0,
STATE_SCANCODE,
STATE_SETLEDS,
STATE_EX_SETLEDS_1, /* Expect 2-byte parameter */
STATE_EX_SETLEDS_2,
STATE_WRITE_CMD_BYTE,
STATE_WRITE_OUTPUT_PORT,
STATE_ECHO_MOUSE,
STATE_SETREP,
STATE_SEND_TO_MOUSE,
} data_port_state = STATE_NORMAL;
enum scancode_set_list {
SCANCODE_GET_SET = 0,
SCANCODE_SET_1,
SCANCODE_SET_2,
SCANCODE_SET_3,
SCANCODE_MAX = SCANCODE_SET_3,
};
#define MAX_SCAN_CODE_LEN 4
/*
* Mutex to control write access to the to-host buffer head. Don't need to
* mutex the tail because reads are only done in one place.
*/
static struct mutex to_host_mutex;
static uint8_t to_host_buffer[16];
static struct queue to_host = {
.buf_bytes = sizeof(to_host_buffer),
.unit_bytes = sizeof(uint8_t),
.buf = to_host_buffer,
};
/* Queue command/data from the host */
enum {
HOST_COMMAND = 0,
HOST_DATA,
};
struct host_byte {
uint8_t type;
uint8_t byte;
};
/*
* The buffer for i8042 command from host. So far the largest command
* we see from kernel is:
*
* d1 -> i8042 (command) # enable A20 in i8042_platform_init() of
* df -> i8042 (parameter) # serio/i8042-x86ia64io.h file.
* ff -> i8042 (command)
* 20 -> i8042 (command) # read CTR
*
* Hence, 5 (actually 4 plus one spare) is large enough, but use 8 for safety.
*/
static uint8_t from_host_buffer[8 * sizeof(struct host_byte)];
static struct queue from_host = {
.buf_bytes = sizeof(from_host_buffer),
.unit_bytes = sizeof(struct host_byte),
.buf = from_host_buffer,
};
static int i8042_irq_enabled;
/* i8042 global settings */
static int keyboard_enabled; /* default the keyboard is disabled. */
static int keystroke_enabled; /* output keystrokes */
static uint8_t resend_command[MAX_SCAN_CODE_LEN];
static uint8_t resend_command_len;
static uint8_t controller_ram_address;
static uint8_t controller_ram[0x20] = {
/* the so called "command byte" */
I8042_XLATE | I8042_AUX_DIS | I8042_KBD_DIS,
/* 0x01 - 0x1f are controller RAM */
};
static uint8_t A20_status;
static int power_button_pressed;
static void keyboard_special(uint16_t k);
/*
* Scancode settings
*/
static enum scancode_set_list scancode_set = SCANCODE_SET_2;
/*
* Typematic delay, rate and counter variables.
*
* 7 6 5 4 3 2 1 0
* +-----+-----+-----+-----+-----+-----+-----+-----+
* |un- | delay | B | D |
* | used| 0 1 | 0 1 | 0 1 1 |
* +-----+-----+-----+-----+-----+-----+-----+-----+
* Formula:
* the inter-char delay = (2 ** B) * (D + 8) / 240 (sec)
* Default: 500ms delay, 10.9 chars/sec.
*/
#define DEFAULT_TYPEMATIC_VALUE ((1 << 5) | (1 << 3) | (3 << 0))
static uint8_t typematic_value_from_host;
static int typematic_first_delay;
static int typematic_inter_delay;
static int typematic_len; /* length of typematic_scan_code */
static uint8_t typematic_scan_code[MAX_SCAN_CODE_LEN];
static timestamp_t typematic_deadline;
#define KB_SYSJUMP_TAG 0x4b42 /* "KB" */
#define KB_HOOK_VERSION 2
/* the previous keyboard state before reboot_ec. */
struct kb_state {
uint8_t codeset;
uint8_t ctlram;
uint8_t keystroke_enabled;
uint8_t pad; /* Pad to 4 bytes for system_add_jump_tag(). */
};
/* The standard Chrome OS keyboard matrix table. */
static const uint16_t scancode_set1[KEYBOARD_ROWS][KEYBOARD_COLS] = {
{0x0000, 0xe05b, 0x003b, 0x0030, 0x0044, 0x0073, 0x0031, 0x0000, 0x000d,
0x0000, 0xe038, 0x0000, 0x0000},
{0x0000, 0x0001, 0x003e, 0x0022, 0x0041, 0x0000, 0x0023, 0x0000, 0x0028,
0x0043, 0x0000, 0x000e, 0x0078},
{0x001d, 0x000f, 0x003d, 0x0014, 0x0040, 0x001b, 0x0015, 0x0056, 0x001a,
0x0042, 0x0073, 0x0000, 0x0000},
{0x0000, 0x0029, 0x003c, 0x0006, 0x003f, 0x0000, 0x0007, 0x0000, 0x000c,
0x0000, 0x0000, 0x002b, 0x0079},
{0xe01d, 0x001e, 0x0020, 0x0021, 0x001f, 0x0025, 0x0024, 0x0000, 0x0027,
0x0026, 0x002b, 0x001c, 0x0000},
{0x0000, 0x002c, 0x002e, 0x002f, 0x002d, 0x0033, 0x0032, 0x002a, 0x0035,
0x0034, 0x0000, 0x0039, 0x0000},
{0x0000, 0x0002, 0x0004, 0x0005, 0x0003, 0x0009, 0x0008, 0x0000, 0x000b,
0x000a, 0x0038, 0xe050, 0xe04d},
{0x0000, 0x0010, 0x0012, 0x0013, 0x0011, 0x0017, 0x0016, 0x0036, 0x0019,
0x0018, 0x0000, 0xe048, 0xe04b},
};
static const uint16_t scancode_set2[KEYBOARD_ROWS][KEYBOARD_COLS] = {
{0x0000, 0xe01f, 0x0005, 0x0032, 0x0009, 0x0051, 0x0031, 0x0000, 0x0055,
0x0000, 0xe011, 0x0000, 0x0000},
{0x0000, 0x0076, 0x000c, 0x0034, 0x0083, 0x0000, 0x0033, 0x0000, 0x0052,
0x0001, 0x0000, 0x0066, 0x0067},
{0x0014, 0x000d, 0x0004, 0x002c, 0x000b, 0x005b, 0x0035, 0x0061, 0x0054,
0x000a, 0x0051, 0x0000, 0x0000},
{0x0000, 0x000e, 0x0006, 0x002e, 0x0003, 0x0000, 0x0036, 0x0000, 0x004e,
0x0000, 0x0000, 0x005d, 0x0064},
{0xe014, 0x001c, 0x0023, 0x002b, 0x001b, 0x0042, 0x003b, 0x0000, 0x004c,
0x004b, 0x005d, 0x005a, 0x0000},
{0x0000, 0x001a, 0x0021, 0x002a, 0x0022, 0x0041, 0x003a, 0x0012, 0x004a,
0x0049, 0x0000, 0x0029, 0x0000},
{0x0000, 0x0016, 0x0026, 0x0025, 0x001e, 0x003e, 0x003d, 0x0000, 0x0045,
0x0046, 0x0011, 0xe072, 0xe074},
{0x0000, 0x0015, 0x0024, 0x002d, 0x001d, 0x0043, 0x003c, 0x0059, 0x004d,
0x0044, 0x0000, 0xe075, 0xe06b},
};
/*****************************************************************************/
/* Keyboard event log */
/* Log the traffic between EC and host -- for debug only */
#define MAX_KBLOG 512 /* Max events in keyboard log */
struct kblog_t {
/*
* Type:
*
* s = byte enqueued to send to host
* t = to-host queue tail pointer before type='s' bytes enqueued
*
* d = data byte from host
* c = command byte from host
*
* k = to-host queue head pointer before byte dequeued
* K = byte actually sent to host via LPC
*/
uint8_t type;
uint8_t byte;
};
static struct kblog_t *kblog_buf; /* Log buffer; NULL if not logging */
static int kblog_len; /* Current log length */
/**
* Add event to keyboard log.
*/
static void kblog_put(char type, uint8_t byte)
{
if (kblog_buf && kblog_len < MAX_KBLOG) {
kblog_buf[kblog_len].type = type;
kblog_buf[kblog_len].byte = byte;
kblog_len++;
}
}
/*****************************************************************************/
void keyboard_host_write(int data, int is_cmd)
{
struct host_byte h;
h.type = is_cmd ? HOST_COMMAND : HOST_DATA;
h.byte = data;
queue_add_units(&from_host, &h, 1);
task_wake(TASK_ID_KEYPROTO);
}
/**
* Enable keyboard IRQ generation.
*
* @param enable Enable (!=0) or disable (0) IRQ generation.
*/
static void keyboard_enable_irq(int enable)
{
i8042_irq_enabled = enable;
if (enable)
lpc_keyboard_resume_irq();
}
/**
* Send a scan code to the host.
*
* The EC lib will push the scan code bytes to host via port 0x60 and assert
* the IBF flag to trigger an interrupt. The EC lib must queue them if the
* host cannot read the previous byte away in time.
*
* @param len Number of bytes to send to the host
* @param to_host Data to send
*/
static void i8042_send_to_host(int len, const uint8_t *bytes)
{
int i;
for (i = 0; i < len; i++)
kblog_put('s', bytes[i]);
/* Enqueue output data if there's space */
mutex_lock(&to_host_mutex);
if (queue_has_space(&to_host, len)) {
kblog_put('t', to_host.tail);
queue_add_units(&to_host, bytes, len);
}
mutex_unlock(&to_host_mutex);
/* Wake up the task to move from queue to host */
task_wake(TASK_ID_KEYPROTO);
}
/* Change to set 1 if the I8042_XLATE flag is set. */
static enum scancode_set_list acting_code_set(enum scancode_set_list set)
{
/* Always generate set 1 if keyboard translation is enabled */
if (controller_ram[0] & I8042_XLATE)
return SCANCODE_SET_1;
return set;
}
static enum ec_error_list matrix_callback(int8_t row, int8_t col,
int8_t pressed,
enum scancode_set_list code_set,
uint8_t *scan_code, int32_t *len)
{
uint16_t make_code;
ASSERT(scan_code);
ASSERT(len);
if (row > KEYBOARD_ROWS || col > KEYBOARD_COLS)
return EC_ERROR_INVAL;
if (pressed)
keyboard_special(scancode_set1[row][col]);
*len = 0;
code_set = acting_code_set(code_set);
switch (code_set) {
case SCANCODE_SET_1:
make_code = scancode_set1[row][col];
break;
case SCANCODE_SET_2:
make_code = scancode_set2[row][col];
break;
default:
CPRINTF("[%T KB scancode set %d unsupported]\n", code_set);
return EC_ERROR_UNIMPLEMENTED;
}
if (!make_code) {
CPRINTF("[%T KB scancode %d:%d missing]\n", row, col);
return EC_ERROR_UNIMPLEMENTED;
}
/* Output the make code (from table) */
if (make_code >= 0x0100) {
*len += 2;
scan_code[0] = make_code >> 8;
scan_code[1] = make_code & 0xff;
} else {
*len += 1;
scan_code[0] = make_code & 0xff;
}
switch (code_set) {
case SCANCODE_SET_1:
/* OR 0x80 for the last byte. */
if (!pressed) {
ASSERT(*len >= 1);
scan_code[*len - 1] |= 0x80;
}
break;
case SCANCODE_SET_2:
/*
* Insert the break byte, move back the last byte and insert a
* 0xf0 byte before that.
*/
if (!pressed) {
ASSERT(*len >= 1);
scan_code[*len] = scan_code[*len - 1];
scan_code[*len - 1] = 0xf0;
*len += 1;
}
break;
default:
break;
}
return EC_SUCCESS;
}
/**
* Set typematic delays based on host data byte.
*/
static void set_typematic_delays(uint8_t data)
{
typematic_value_from_host = data;
typematic_first_delay = MSEC *
(((typematic_value_from_host & 0x60) >> 5) + 1) * 250;
typematic_inter_delay = SECOND *
(1 << ((typematic_value_from_host & 0x18) >> 3)) *
((typematic_value_from_host & 0x7) + 8) / 240;
}
static void reset_rate_and_delay(void)
{
set_typematic_delays(DEFAULT_TYPEMATIC_VALUE);
}
void keyboard_clear_buffer(void)
{
mutex_lock(&to_host_mutex);
queue_reset(&to_host);
mutex_unlock(&to_host_mutex);
lpc_keyboard_clear_buffer();
}
static void keyboard_wakeup(void)
{
host_set_single_event(EC_HOST_EVENT_KEY_PRESSED);
}
void keyboard_state_changed(int row, int col, int is_pressed)
{
uint8_t scan_code[MAX_SCAN_CODE_LEN];
int32_t len;
enum ec_error_list ret;
CPRINTF5("[%T KB (%d,%d)=%d]\n", row, col, is_pressed);
ret = matrix_callback(row, col, is_pressed, scancode_set, scan_code,
&len);
if (ret == EC_SUCCESS) {
ASSERT(len > 0);
if (keystroke_enabled)
i8042_send_to_host(len, scan_code);
}
if (is_pressed) {
keyboard_wakeup();
typematic_deadline.val = get_time().val + typematic_first_delay;
memcpy(typematic_scan_code, scan_code, len);
typematic_len = len;
task_wake(TASK_ID_KEYPROTO);
} else {
typematic_len = 0;
}
}
static void keystroke_enable(int enable)
{
if (!keystroke_enabled && enable)
CPRINTF("[%T KS enable]\n");
else if (keystroke_enabled && !enable)
CPRINTF("[%T KS disable]\n");
keystroke_enabled = enable;
}
static void keyboard_enable(int enable)
{
if (!keyboard_enabled && enable) {
CPRINTF("[%T KB enable]\n");
} else if (keyboard_enabled && !enable) {
CPRINTF("[%T KB disable]\n");
reset_rate_and_delay();
typematic_len = 0; /* stop typematic */
/* Disable keystroke as well in case the BIOS doesn't
* disable keystroke where repeated strokes are queued
* before kernel initializes keyboard. Hence the kernel
* is unable to get stable CTR read (get key codes
* instead).
*/
keystroke_enable(0);
keyboard_clear_buffer();
}
keyboard_enabled = enable;
}
static uint8_t read_ctl_ram(uint8_t addr)
{
if (addr < ARRAY_SIZE(controller_ram))
return controller_ram[addr];
else
return 0;
}
/**
* Manipulate the controller_ram[].
*
* Some bits change may trigger internal state change.
*/
static void update_ctl_ram(uint8_t addr, uint8_t data)
{
uint8_t orig;
if (addr >= ARRAY_SIZE(controller_ram))
return;
orig = controller_ram[addr];
controller_ram[addr] = data;
CPRINTF5("[%T KB set CTR_RAM(0x%02x)=0x%02x (old:0x%02x)]\n",
addr, data, orig);
if (addr == 0x00) {
/* Keyboard enable/disable */
/* Enable IRQ before enable keyboard (queue chars to host) */
if (!(orig & I8042_ENIRQ1) && (data & I8042_ENIRQ1))
keyboard_enable_irq(1);
/* Handle the I8042_KBD_DIS bit */
keyboard_enable(!(data & I8042_KBD_DIS));
/*
* Disable IRQ after disable keyboard so that every char must
* have informed the host.
*/
if ((orig & I8042_ENIRQ1) && !(data & I8042_ENIRQ1))
keyboard_enable_irq(0);
}
}
/**
* Handle the port 0x60 writes from host.
*
* This functions returns the number of bytes stored in *output buffer.
*/
static int handle_keyboard_data(uint8_t data, uint8_t *output)
{
int out_len = 0;
int save_for_resend = 1;
int i;
CPRINTF5("[%T KB recv data: 0x%02x]\n", data);
kblog_put('d', data);
switch (data_port_state) {
case STATE_SCANCODE:
CPRINTF5("[%T KB eaten by STATE_SCANCODE: 0x%02x]\n", data);
if (data == SCANCODE_GET_SET) {
output[out_len++] = I8042_RET_ACK;
output[out_len++] = scancode_set;
} else {
scancode_set = data;
CPRINTF("[%T KB scancode set to %d]\n", scancode_set);
output[out_len++] = I8042_RET_ACK;
}
data_port_state = STATE_NORMAL;
break;
case STATE_SETLEDS:
CPUTS5("[%T KB eaten by STATE_SETLEDS]\n");
output[out_len++] = I8042_RET_ACK;
data_port_state = STATE_NORMAL;
break;
case STATE_EX_SETLEDS_1:
CPUTS5("[%T KB eaten by STATE_EX_SETLEDS_1]\n");
output[out_len++] = I8042_RET_ACK;
data_port_state = STATE_EX_SETLEDS_2;
break;
case STATE_EX_SETLEDS_2:
CPUTS5("[%T KB eaten by STATE_EX_SETLEDS_2]\n");
output[out_len++] = I8042_RET_ACK;
data_port_state = STATE_NORMAL;
break;
case STATE_WRITE_CMD_BYTE:
CPRINTF5("[%T KB eaten by STATE_WRITE_CMD_BYTE: 0x%02x]\n",
data);
update_ctl_ram(controller_ram_address, data);
data_port_state = STATE_NORMAL;
break;
case STATE_WRITE_OUTPUT_PORT:
CPRINTF5("[%T KB eaten by STATE_WRITE_OUTPUT_PORT: 0x%02x]\n",
data);
A20_status = (data & (1 << 1)) ? 1 : 0;
data_port_state = STATE_NORMAL;
break;
case STATE_ECHO_MOUSE:
CPRINTF5("[%T KB eaten by STATE_ECHO_MOUSE: 0x%02x]\n", data);
output[out_len++] = data;
data_port_state = STATE_NORMAL;
break;
case STATE_SETREP:
CPRINTF5("[%T KB eaten by STATE_SETREP: 0x%02x]\n", data);
set_typematic_delays(data);
output[out_len++] = I8042_RET_ACK;
data_port_state = STATE_NORMAL;
break;
case STATE_SEND_TO_MOUSE:
CPRINTF5("[%T KB eaten by STATE_SEND_TO_MOUSE: 0x%02x]\n",
data);
data_port_state = STATE_NORMAL;
break;
default: /* STATE_NORMAL */
switch (data) {
case I8042_CMD_GSCANSET: /* also I8042_CMD_SSCANSET */
output[out_len++] = I8042_RET_ACK;
data_port_state = STATE_SCANCODE;
break;
case I8042_CMD_SETLEDS:
/* Chrome OS doesn't have keyboard LEDs, so ignore */
output[out_len++] = I8042_RET_ACK;
data_port_state = STATE_SETLEDS;
break;
case I8042_CMD_EX_SETLEDS:
output[out_len++] = I8042_RET_ACK;
data_port_state = STATE_EX_SETLEDS_1;
break;
case I8042_CMD_DIAG_ECHO:
output[out_len++] = I8042_RET_ACK;
output[out_len++] = I8042_CMD_DIAG_ECHO;
break;
case I8042_CMD_GETID: /* fall-thru */
case I8042_CMD_OK_GETID:
output[out_len++] = I8042_RET_ACK;
output[out_len++] = 0xab; /* Regular keyboards */
output[out_len++] = 0x83;
break;
case I8042_CMD_SETREP:
output[out_len++] = I8042_RET_ACK;
data_port_state = STATE_SETREP;
break;
case I8042_CMD_ENABLE:
output[out_len++] = I8042_RET_ACK;
keystroke_enable(1);
keyboard_clear_buffer();
break;
case I8042_CMD_RESET_DIS:
output[out_len++] = I8042_RET_ACK;
keystroke_enable(0);
reset_rate_and_delay();
keyboard_clear_buffer();
break;
case I8042_CMD_RESET_DEF:
output[out_len++] = I8042_RET_ACK;
reset_rate_and_delay();
keyboard_clear_buffer();
break;
case I8042_CMD_RESET_BAT:
reset_rate_and_delay();
keyboard_clear_buffer();
output[out_len++] = I8042_RET_ACK;
output[out_len++] = I8042_RET_BAT;
output[out_len++] = I8042_RET_BAT;
break;
case I8042_CMD_RESEND:
save_for_resend = 0;
for (i = 0; i < resend_command_len; ++i)
output[out_len++] = resend_command[i];
break;
case 0x60: /* fall-thru */
case 0x45:
/* U-boot hack. Just ignore; don't reply. */
break;
case I8042_CMD_SETALL_MB: /* fall-thru */
case I8042_CMD_SETALL_MBR:
case I8042_CMD_EX_ENABLE:
default:
output[out_len++] = I8042_RET_NAK;
CPRINTF("[%T KB Unsupported i8042 data 0x%02x]\n",
data);
break;
}
}
/* For resend, keep output before leaving. */
if (out_len && save_for_resend) {
ASSERT(out_len <= MAX_SCAN_CODE_LEN);
for (i = 0; i < out_len; ++i)
resend_command[i] = output[i];
resend_command_len = out_len;
}
ASSERT(out_len <= MAX_SCAN_CODE_LEN);
return out_len;
}
/**
* Handle the port 0x64 writes from host.
*
* This functions returns the number of bytes stored in *output buffer.
* BUT those bytes will appear at port 0x60.
*/
static int handle_keyboard_command(uint8_t command, uint8_t *output)
{
int out_len = 0;
CPRINTF5("[%T KB recv cmd: 0x%02x]\n", command);
kblog_put('c', command);
switch (command) {
case I8042_READ_CMD_BYTE:
output[out_len++] = read_ctl_ram(0);
break;
case I8042_WRITE_CMD_BYTE:
data_port_state = STATE_WRITE_CMD_BYTE;
controller_ram_address = command - 0x60;
break;
case I8042_DIS_KB:
update_ctl_ram(0, read_ctl_ram(0) | I8042_KBD_DIS);
break;
case I8042_ENA_KB:
update_ctl_ram(0, read_ctl_ram(0) & ~I8042_KBD_DIS);
break;
case I8042_READ_OUTPUT_PORT:
output[out_len++] =
(lpc_keyboard_input_pending() ? (1 << 5) : 0) |
(lpc_keyboard_has_char() ? (1 << 4) : 0) |
(A20_status ? (1 << 1) : 0) |
1; /* Main processor in normal mode */
break;
case I8042_WRITE_OUTPUT_PORT:
data_port_state = STATE_WRITE_OUTPUT_PORT;
break;
case I8042_RESET_SELF_TEST:
output[out_len++] = 0x55; /* Self test success */
break;
case I8042_TEST_KB_PORT:
output[out_len++] = 0x00;
break;
case I8042_DIS_MOUSE:
update_ctl_ram(0, read_ctl_ram(0) | I8042_AUX_DIS);
break;
case I8042_ENA_MOUSE:
update_ctl_ram(0, read_ctl_ram(0) & ~I8042_AUX_DIS);
break;
case I8042_TEST_MOUSE:
output[out_len++] = 0; /* No error detected */
break;
case I8042_ECHO_MOUSE:
data_port_state = STATE_ECHO_MOUSE;
break;
case I8042_SEND_TO_MOUSE:
data_port_state = STATE_SEND_TO_MOUSE;
break;
case I8042_SYSTEM_RESET:
chipset_reset(0);
break;
default:
if (command >= I8042_READ_CTL_RAM &&
command <= I8042_READ_CTL_RAM_END) {
output[out_len++] = read_ctl_ram(command - 0x20);
} else if (command >= I8042_WRITE_CTL_RAM &&
command <= I8042_WRITE_CTL_RAM_END) {
data_port_state = STATE_WRITE_CMD_BYTE;
controller_ram_address = command - 0x60;
} else if (command == I8042_DISABLE_A20) {
A20_status = 0;
} else if (command == I8042_ENABLE_A20) {
A20_status = 1;
} else if (command >= I8042_PULSE_START &&
command <= I8042_PULSE_END) {
/* Pulse Output Bits,
* b0=0 to reset CPU, see I8042_SYSTEM_RESET above
* b1=0 to disable A20 line
*/
A20_status = command & (1 << 1) ? 1 : 0;
} else {
CPRINTF("[%T KB unsupported cmd: 0x%02x]\n", command);
reset_rate_and_delay();
keyboard_clear_buffer();
output[out_len++] = I8042_RET_NAK;
data_port_state = STATE_NORMAL;
}
break;
}
return out_len;
}
static void i8042_handle_from_host(void)
{
struct host_byte h;
int ret_len;
uint8_t output[MAX_SCAN_CODE_LEN];
while (queue_remove_unit(&from_host, &h)) {
if (h.type == HOST_COMMAND)
ret_len = handle_keyboard_command(h.byte, output);
else
ret_len = handle_keyboard_data(h.byte, output);
i8042_send_to_host(ret_len, output);
}
}
/* U U D D L R L R b a */
static void keyboard_special(uint16_t k)
{
static uint8_t s;
static const uint16_t a[] = {0xe048, 0xe048, 0xe050, 0xe050, 0xe04b,
0xe04d, 0xe04b, 0xe04d, 0x0030, 0x001e};
#ifdef HAS_TASK_LIGHTBAR
/* Lightbar demo mode: keyboard can fake the battery state */
switch (k) {
case 0xe048: /* up */
demo_battery_level(1);
break;
case 0xe050: /* down */
demo_battery_level(-1);
break;
case 0xe04b: /* left */
demo_is_charging(0);
break;
case 0xe04d: /* right */
demo_is_charging(1);
break;
case 0x0040: /* dim */
demo_brightness(-1);
break;
case 0x0041: /* bright */
demo_brightness(1);
break;
}
#endif
if (k == a[s])
s++;
else if (k != 0xe048)
s = 0;
else if (s != 2)
s = 1;
if (s == ARRAY_SIZE(a)) {
s = 0;
#ifdef HAS_TASK_LIGHTBAR
lightbar_sequence(LIGHTBAR_KONAMI);
#endif
}
}
void keyboard_protocol_task(void)
{
int wait = -1;
reset_rate_and_delay();
while (1) {
/* Wait for next host read/write */
task_wait_event(wait);
while (1) {
timestamp_t t = get_time();
uint8_t chr;
/* Handle typematic */
if (!typematic_len) {
/* Typematic disabled; wait for enable */
wait = -1;
} else if (timestamp_expired(typematic_deadline, &t)) {
/* Ready for next typematic keystroke */
if (keystroke_enabled)
i8042_send_to_host(typematic_len,
typematic_scan_code);
typematic_deadline.val = t.val +
typematic_inter_delay;
wait = typematic_inter_delay;
} else {
/* Wait for remaining interval */
wait = typematic_deadline.val - t.val;
}
/* Handle command/data write from host */
i8042_handle_from_host();
/* Check if we have data to send to host */
if (queue_is_empty(&to_host))
break;
/* Host interface must have space */
if (lpc_keyboard_has_char())
break;
/* Get a char from buffer. */
kblog_put('k', to_host.head);
queue_remove_unit(&to_host, &chr);
kblog_put('K', chr);
/* Write to host. */
lpc_keyboard_put_char(chr, i8042_irq_enabled);
}
}
}
/*****************************************************************************/
/* Console commands */
static int command_typematic(int argc, char **argv)
{
int i;
if (argc == 3) {
typematic_first_delay = strtoi(argv[1], NULL, 0) * MSEC;
typematic_inter_delay = strtoi(argv[2], NULL, 0) * MSEC;
}
ccprintf("From host: 0x%02x\n", typematic_value_from_host);
ccprintf("First delay: %d ms\n", typematic_first_delay / 1000);
ccprintf("Inter delay: %d ms\n", typematic_inter_delay / 1000);
ccprintf("Now: %.6ld\n", get_time().val);
ccprintf("Deadline: %.6ld\n", typematic_deadline.val);
ccputs("Repeat scan code:");
for (i = 0; i < typematic_len; ++i)
ccprintf(" 0x%02x", typematic_scan_code[i]);
ccputs("\n");
return EC_SUCCESS;
}
DECLARE_CONSOLE_COMMAND(typematic, command_typematic,
"[first] [inter]",
"Get/set typematic delays",
NULL);
static int command_codeset(int argc, char **argv)
{
if (argc == 2) {
int set = strtoi(argv[1], NULL, 0);
switch (set) {
case SCANCODE_SET_1: /* fall-thru */
case SCANCODE_SET_2: /* fall-thru */
scancode_set = set;
break;
default:
return EC_ERROR_PARAM1;
}
}
ccprintf("Set: %d\n", scancode_set);
ccprintf("I8042_XLATE: %d\n", controller_ram[0] & I8042_XLATE ? 1 : 0);
return EC_SUCCESS;
}
DECLARE_CONSOLE_COMMAND(codeset, command_codeset,
"[set]",
"Get/set keyboard codeset",
NULL);
static int command_controller_ram(int argc, char **argv)
{
int index;
if (argc < 2)
return EC_ERROR_PARAM_COUNT;
index = strtoi(argv[1], NULL, 0);
if (index >= ARRAY_SIZE(controller_ram))
return EC_ERROR_PARAM1;
if (argc >= 3)
update_ctl_ram(index, strtoi(argv[2], NULL, 0));
ccprintf("%d = 0x%02x\n", index, controller_ram[index]);
return EC_SUCCESS;
}
DECLARE_CONSOLE_COMMAND(ctrlram, command_controller_ram,
"index [value]",
"Get/set keyboard controller RAM",
NULL);
static int command_keyboard_log(int argc, char **argv)
{
int i;
/* If no args, print log */
if (argc == 1) {
ccprintf("KBC log (len=%d):\n", kblog_len);
for (i = 0; kblog_buf && i < kblog_len; ++i) {
ccprintf("%c.%02x ",
kblog_buf[i].type, kblog_buf[i].byte);
if ((i & 15) == 15) {
ccputs("\n");
cflush();
}
}
ccputs("\n");
return EC_SUCCESS;
}
/* Otherwise, enable/disable */
if (!parse_bool(argv[1], &i))
return EC_ERROR_PARAM1;
if (i) {
if (!kblog_buf) {
int rv = shared_mem_acquire(
sizeof(*kblog_buf) * MAX_KBLOG,
(char **)&kblog_buf);
if (rv != EC_SUCCESS)
kblog_buf = NULL;
kblog_len = 0;
return rv;
}
} else {
kblog_len = 0;
if (kblog_buf)
shared_mem_release(kblog_buf);
kblog_buf = NULL;
}
return EC_SUCCESS;
}
DECLARE_CONSOLE_COMMAND(kblog, command_keyboard_log,
"[on | off]",
"Print or toggle keyboard event log",
NULL);
static int command_keyboard(int argc, char **argv)
{
int ena;
if (argc > 1) {
if (!parse_bool(argv[1], &ena))
return EC_ERROR_PARAM1;
keyboard_enable(ena);
}
ccprintf("Enabled: %d\n", keyboard_enabled);
return EC_SUCCESS;
}
DECLARE_CONSOLE_COMMAND(kbd, command_keyboard,
"[0 | 1]",
"Print or toggle keyboard info",
NULL);
/*****************************************************************************/
/* Hooks */
/**
* Preserve the states of keyboard controller to keep the initialized states
* between reboot_ec commands. Saving info include:
*
* - code set
* - controller_ram[0]:
* - XLATE
* - KB/TP disabled
* - KB/TP IRQ enabled
*/
static void keyboard_preserve_state(void)
{
struct kb_state state;
state.codeset = scancode_set;
state.ctlram = controller_ram[0];
state.keystroke_enabled = keystroke_enabled;
system_add_jump_tag(KB_SYSJUMP_TAG, KB_HOOK_VERSION,
sizeof(state), &state);
}
DECLARE_HOOK(HOOK_SYSJUMP, keyboard_preserve_state, HOOK_PRIO_DEFAULT);
/**
* Restore the keyboard states after reboot_ec command. See above function.
*/
static void keyboard_restore_state(void)
{
const struct kb_state *prev;
int version, size;
prev = (const struct kb_state *)system_get_jump_tag(KB_SYSJUMP_TAG,
&version, &size);
if (prev && version == KB_HOOK_VERSION && size == sizeof(*prev)) {
/* Coming back from a sysjump, so restore settings. */
scancode_set = prev->codeset;
update_ctl_ram(0, prev->ctlram);
keystroke_enabled = prev->keystroke_enabled;
}
}
DECLARE_HOOK(HOOK_INIT, keyboard_restore_state, HOOK_PRIO_DEFAULT);
/**
* Handle power button changing state.
*/
static void keyboard_power_button(void)
{
enum scancode_set_list code_set;
uint8_t code[2][2][3] = {
{ /* set 1 */
{0xe0, 0xde}, /* break */
{0xe0, 0x5e}, /* make */
}, { /* set 2 */
{0xe0, 0xf0, 0x37}, /* break */
{0xe0, 0x37}, /* make */
}
};
power_button_pressed = power_button_is_pressed();
/*
* Only send the scan code if main chipset is fully awake and
* keystrokes are enabled.
*/
if (!chipset_in_state(CHIPSET_STATE_ON) || !keystroke_enabled)
return;
code_set = acting_code_set(scancode_set);
i8042_send_to_host(
(code_set == SCANCODE_SET_2 && !power_button_pressed) ? 3 : 2,
code[code_set - SCANCODE_SET_1][power_button_pressed]);
}
DECLARE_HOOK(HOOK_POWER_BUTTON_CHANGE, keyboard_power_button,
HOOK_PRIO_DEFAULT);