/* Copyright 2016 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.
 */

#include <string.h>
#include "common.h"
#include "gpio.h"
#include "registers.h"
#include "task.h"
#include "dut_common.h"
#include "timer.h"
#include "watchdog.h"

static int got_interrupt;
static int wake_me_up;
static int state_index;
static char state[4];

/*
 * Raw busy loop. Returns 1 if loop finishes before interrupt is triggered.
 * Loop length is controlled by busy_loop_timeout. It has to be set to the
 * value which makes the loop last longer than CTS_INTERRUPT_TRIGGER_DELAY_US.
 */
static int busy_loop(void)
{
	/* TODO: Derive a proper value from clock speed */
	const uint32_t busy_loop_timeout = 0xfffff;
	uint32_t counter = 0;

	while (counter++ < busy_loop_timeout) {
		if (got_interrupt)
			break;
		watchdog_reload();
	}
	if (counter > busy_loop_timeout)
		return 1;

	return 0;
}

/*
 * Interrupt handler.
 */
void cts_irq1(enum gpio_signal signal)
{
	state[state_index++] = 'B';

	got_interrupt = in_interrupt_context();

	/* Wake up the CTS task */
	if (wake_me_up)
		task_wake(TASK_ID_CTS);

	busy_loop();

	state[state_index++] = 'C';
}

void cts_irq2(enum gpio_signal signal)
{
	state[state_index++] = 'A';
	busy_loop();
	state[state_index++] = 'D';
}

static void clear_state(void)
{
	uint32_t *event;

	got_interrupt = 0;
	wake_me_up = 0;
	state_index = 0;
	memset(state, '_', sizeof(state));
	event = task_get_event_bitmap(TASK_ID_CTS);
	*event = 0;
}

enum cts_rc test_task_wait_event(void)
{
	uint32_t event;

	wake_me_up = 1;

	/* Sleep and wait for interrupt. This shouldn't time out. */
	event = task_wait_event(CTS_INTERRUPT_TRIGGER_DELAY_US * 2);
	if (event != TASK_EVENT_WAKE) {
		CPRINTS("Woken up by unexpected event: 0x%08x", event);
		return CTS_RC_FAILURE;
	}
	if (!got_interrupt) {
		CPRINTS("Interrupt context not detected");
		return CTS_RC_TIMEOUT;
	}

	return CTS_RC_SUCCESS;
}

enum cts_rc test_task_disable_irq(void)
{
	uint32_t event;

	wake_me_up = 1;

	task_disable_irq(CTS_IRQ_NUMBER);
	/* Sleep and wait for interrupt. This should time out. */
	event = task_wait_event(CTS_INTERRUPT_TRIGGER_DELAY_US * 2);
	if (event != TASK_EVENT_TIMER) {
		CPRINTS("Woken up by unexpected event: 0x%08x", event);
		return CTS_RC_FAILURE;
	}
	task_enable_irq(CTS_IRQ_NUMBER);

	return CTS_RC_SUCCESS;
}

enum cts_rc test_interrupt_enable(void)
{
	if (busy_loop()) {
		CPRINTS("Timeout before interrupt");
		return CTS_RC_TIMEOUT;
	}
	return CTS_RC_SUCCESS;
}

enum cts_rc test_interrupt_disable(void)
{
	interrupt_disable();
	if (!busy_loop()) {
		CPRINTS("Expected timeout but didn't");
		return CTS_RC_FAILURE;
	}
	return CTS_RC_SUCCESS;
}

enum cts_rc test_nested_interrupt_low_high(void)
{
	uint32_t event;

	event = task_wait_event(CTS_INTERRUPT_TRIGGER_DELAY_US * 4);
	if (event != TASK_EVENT_TIMER) {
		CPRINTS("Woken up by unexpected event: 0x%08x", event);
		return CTS_RC_FAILURE;
	}
	if (!got_interrupt) {
		CPRINTS("Interrupt context not detected");
		return CTS_RC_TIMEOUT;
	}
	if (memcmp(state, "ABCD", sizeof(state))) {
		CPRINTS("State transition differs from expectation");
		return CTS_RC_FAILURE;
	}

	return CTS_RC_SUCCESS;
}

enum cts_rc test_nested_interrupt_high_low(void)
{
	uint32_t event;

	event = task_wait_event(CTS_INTERRUPT_TRIGGER_DELAY_US * 4);
	if (event != TASK_EVENT_TIMER) {
		CPRINTS("Woken up by unexpected event: 0x%08x", event);
		return CTS_RC_FAILURE;
	}

	if (memcmp(state, "BCAD", sizeof(state))) {
		CPRINTS("State transition differs from expectation");
		return CTS_RC_FAILURE;
	}

	return CTS_RC_SUCCESS;
}

#include "cts_testlist.h"

void cts_task(void)
{
	enum cts_rc rc;
	int i;

	gpio_enable_interrupt(GPIO_CTS_IRQ1);
	gpio_enable_interrupt(GPIO_CTS_IRQ2);
	interrupt_enable();
	for (i = 0; i < CTS_TEST_ID_COUNT; i++) {
		clear_state();
		sync();
		rc = tests[i].run();
		interrupt_enable();
		CPRINTF("\n%s %d\n", tests[i].name, rc);
		cflush();
	}

	CPRINTS("Interrupt test suite finished");
	cflush();

	while (1) {
		watchdog_reload();
		sleep(1);
	}
}
