/* 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.
 *
 * Tests for keyboard MKBP protocol
 */

#include "common.h"
#include "console.h"
#include "ec_commands.h"
#include "gpio.h"
#include "i8042_protocol.h"
#include "keyboard_8042.h"
#include "keyboard_protocol.h"
#include "keyboard_scan.h"
#include "lpc.h"
#include "power_button.h"
#include "system.h"
#include "test_util.h"
#include "timer.h"
#include "util.h"

static const char *action[2] = {"release", "press"};

#define BUF_SIZE 16
static char lpc_char_buf[BUF_SIZE];
static unsigned int lpc_char_cnt;

/*****************************************************************************/
/* Mock functions */

int lid_is_open(void)
{
	return 1;
}

void lpc_keyboard_put_char(uint8_t chr, int send_irq)
{
	lpc_char_buf[lpc_char_cnt++] = chr;
}

/*****************************************************************************/
/* Test utilities */

static void press_key(int c, int r, int pressed)
{
	ccprintf("Input %s (%d, %d)\n", action[pressed], c, r);
	keyboard_state_changed(r, c, pressed);
}

static void enable_keystroke(int enabled)
{
	uint8_t data = enabled ? I8042_CMD_ENABLE : I8042_CMD_RESET_DIS;
	keyboard_host_write(data, 0);
	msleep(30);
}

static void reset_8042(void)
{
	keyboard_host_write(I8042_CMD_RESET_DEF, 0);
	msleep(30);
}

static void set_typematic(uint8_t val)
{
	keyboard_host_write(I8042_CMD_SETREP, 0);
	msleep(30);
	keyboard_host_write(val, 0);
	msleep(30);
}

static void set_scancode(uint8_t s)
{
	keyboard_host_write(I8042_CMD_SSCANSET, 0);
	msleep(30);
	keyboard_host_write(s, 0);
	msleep(30);
}

static void write_cmd_byte(uint8_t val)
{
	keyboard_host_write(I8042_WRITE_CMD_BYTE, 1);
	msleep(30);
	keyboard_host_write(val, 0);
	msleep(30);
}

static uint8_t read_cmd_byte(void)
{
	lpc_char_cnt = 0;
	keyboard_host_write(I8042_READ_CMD_BYTE, 1);
	msleep(30);
	return lpc_char_buf[0];
}

static int __verify_lpc_char(char *arr, unsigned int sz, int delay_ms)
{
	int i;

	lpc_char_cnt = 0;
	for (i = 0; i < sz; ++i)
		lpc_char_buf[i] = 0;
	msleep(delay_ms);
	TEST_ASSERT_ARRAY_EQ(arr, lpc_char_buf, sz);
	return EC_SUCCESS;
}

#define VERIFY_LPC_CHAR(s) \
	TEST_ASSERT(__verify_lpc_char(s, strlen(s), 30) == EC_SUCCESS)
#define VERIFY_LPC_CHAR_DELAY(s, t) \
	TEST_ASSERT(__verify_lpc_char(s, strlen(s), t) == EC_SUCCESS)

static int __verify_no_char(void)
{
	lpc_char_cnt = 0;
	msleep(30);
	TEST_CHECK(lpc_char_cnt == 0);
}

#define VERIFY_NO_CHAR() TEST_ASSERT(__verify_no_char() == EC_SUCCESS)

/*****************************************************************************/
/* Tests */

static int test_single_key_press(void)
{
	enable_keystroke(1);
	press_key(1, 1, 1);
	VERIFY_LPC_CHAR("\x01");
	press_key(1, 1, 0);
	VERIFY_LPC_CHAR("\x81");

	press_key(12, 6, 1);
	VERIFY_LPC_CHAR("\xe0\x4d");
	press_key(12, 6, 0);
	VERIFY_LPC_CHAR("\xe0\xcd");

	return EC_SUCCESS;
}

static int test_disable_keystroke(void)
{
	enable_keystroke(0);
	press_key(1, 1, 1);
	VERIFY_NO_CHAR();
	press_key(1, 1, 0);
	VERIFY_NO_CHAR();

	return EC_SUCCESS;
}

static int test_typematic(void)
{
	enable_keystroke(1);

	/*
	 * 250ms delay, 8 chars / sec.
	 */
	set_typematic(0xf);

	press_key(1, 1, 1);
	VERIFY_LPC_CHAR_DELAY("\x01\x01\x01\x01\x01", 650);
	press_key(1, 1, 0);
	VERIFY_LPC_CHAR_DELAY("\x81", 300);

	/*
	 * 500ms delay, 10.9 chars / sec.
	 */
	reset_8042();

	press_key(1, 1, 1);
	VERIFY_LPC_CHAR_DELAY("\x01\x01\x01", 650);
	press_key(1, 1, 0);
	VERIFY_LPC_CHAR_DELAY("\x81", 200);

	return EC_SUCCESS;
}

static int test_scancode_set2(void)
{
	set_scancode(2);

	write_cmd_byte(read_cmd_byte() | I8042_XLATE);
	press_key(1, 1, 1);
	VERIFY_LPC_CHAR("\x01");
	press_key(1, 1, 0);
	VERIFY_LPC_CHAR("\x81");

	write_cmd_byte(read_cmd_byte() & ~I8042_XLATE);
	press_key(1, 1, 1);
	VERIFY_LPC_CHAR("\x76");
	press_key(1, 1, 0);
	VERIFY_LPC_CHAR("\xf0\x76");

	return EC_SUCCESS;
}

static int test_power_button(void)
{
	gpio_set_level(GPIO_POWER_BUTTON_L, 1);
	set_scancode(1);
	test_chipset_on();

	gpio_set_level(GPIO_POWER_BUTTON_L, 0);
	VERIFY_LPC_CHAR_DELAY("\xe0\x5e", 100);

	gpio_set_level(GPIO_POWER_BUTTON_L, 1);
	VERIFY_LPC_CHAR_DELAY("\xe0\xde", 100);

	set_scancode(2);
	write_cmd_byte(read_cmd_byte() & ~I8042_XLATE);

	gpio_set_level(GPIO_POWER_BUTTON_L, 0);
	VERIFY_LPC_CHAR_DELAY("\xe0\x37", 100);

	gpio_set_level(GPIO_POWER_BUTTON_L, 1);
	VERIFY_LPC_CHAR_DELAY("\xe0\xf0\x37", 100);

	test_chipset_off();

	gpio_set_level(GPIO_POWER_BUTTON_L, 0);
	VERIFY_NO_CHAR();

	gpio_set_level(GPIO_POWER_BUTTON_L, 1);
	VERIFY_NO_CHAR();

	return EC_SUCCESS;
}

static int test_sysjump(void)
{
	set_scancode(2);
	enable_keystroke(1);

	system_run_image_copy(SYSTEM_IMAGE_RW);

	/* Shouldn't reach here */
	return EC_ERROR_UNKNOWN;
}

static int test_sysjump_cont(void)
{
	write_cmd_byte(read_cmd_byte() | I8042_XLATE);
	press_key(1, 1, 1);
	VERIFY_LPC_CHAR("\x01");
	press_key(1, 1, 0);
	VERIFY_LPC_CHAR("\x81");

	write_cmd_byte(read_cmd_byte() & ~I8042_XLATE);
	press_key(1, 1, 1);
	VERIFY_LPC_CHAR("\x76");
	press_key(1, 1, 0);
	VERIFY_LPC_CHAR("\xf0\x76");

	return EC_SUCCESS;
}

void run_test(void)
{
	test_reset();
	wait_for_task_started();

	if (system_get_image_copy() == SYSTEM_IMAGE_RO) {
		RUN_TEST(test_single_key_press);
		RUN_TEST(test_disable_keystroke);
		RUN_TEST(test_typematic);
		RUN_TEST(test_scancode_set2);
		RUN_TEST(test_power_button);
		RUN_TEST(test_sysjump);
	} else {
		RUN_TEST(test_sysjump_cont);
	}

	test_print_result();
}
