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

/* Port 80 Timer Interrupt for MEC1322 */

#include "common.h"
#include "console.h"
#include "hooks.h"
#include "lpc.h"
#include "port80.h"
#include "registers.h"
#include "task.h"

/* Fire timer interrupt every 1000 usec to check for port80 data. */
#define POLL_PERIOD_USEC 1000
/* After 30 seconds of no port 80 data, disable the timer interrupt. */
#define INTERRUPT_DISABLE_TIMEOUT_SEC 30
#define INTERRUPT_DISABLE_IDLE_COUNT (INTERRUPT_DISABLE_TIMEOUT_SEC \
				      * 1000000 \
				      / POLL_PERIOD_USEC)

/* Count the number of consecutive interrupts with no port 80 data. */
static int idle_count;

static void port_80_interrupt_enable(void)
{
	idle_count = 0;

	/* Enable the interrupt. */
	task_enable_irq(MEC1322_IRQ_TIMER16_1);
	/* Enable and start the timer. */
	MEC1322_TMR16_CTL(1) |= 1 | BIT(5);
}
DECLARE_HOOK(HOOK_CHIPSET_RESUME, port_80_interrupt_enable, HOOK_PRIO_DEFAULT);
DECLARE_HOOK(HOOK_CHIPSET_RESET, port_80_interrupt_enable, HOOK_PRIO_DEFAULT);

static void port_80_interrupt_disable(void)
{
	/* Disable the timer block. */
	MEC1322_TMR16_CTL(1) &= ~1;
	/* Disable the interrupt. */
	task_disable_irq(MEC1322_IRQ_TIMER16_1);
}
DECLARE_HOOK(HOOK_CHIPSET_SUSPEND, port_80_interrupt_disable,
	     HOOK_PRIO_DEFAULT);

/*
 * The port 80 interrupt will use TIMER16 instance 1 for a 1ms countdown
 * timer.  This timer is on GIRQ23, bit 1.
 */
static void port_80_interrupt_init(void)
{
	uint32_t val = 0;

	/*
	 * The timers are driven by a 48MHz oscillator.  Prescale down to
	 * 1MHz. 48MHz/48 -> 1MHz
	 */
	val = MEC1322_TMR16_CTL(1);
	val = (val & 0xFFFF) | (47 << 16);
	/* Automatically restart the timer. */
	val |= BIT(3);
	/* The counter should decrement. */
	val &= ~BIT(2);
	MEC1322_TMR16_CTL(1) = val;

	/* Set the reload value(us). */
	MEC1322_TMR16_PRE(1) = POLL_PERIOD_USEC;

	/* Clear the status if any. */
	MEC1322_TMR16_STS(1) |= 1;

	/* Clear any pending interrupt. */
	MEC1322_INT_SOURCE(23) = BIT(1);
	/* Enable IRQ vector 23. */
	MEC1322_INT_BLK_EN |= BIT(23);
	/* Enable the interrupt. */
	MEC1322_TMR16_IEN(1) |= 1;
	MEC1322_INT_ENABLE(23) = BIT(1);

	port_80_interrupt_enable();
}
DECLARE_HOOK(HOOK_INIT, port_80_interrupt_init, HOOK_PRIO_DEFAULT);

void port_80_interrupt(void)
{
	int data;

	MEC1322_TMR16_STS(1) = 1; /* Ack the interrupt */
	if (BIT(1) & MEC1322_INT_RESULT(23)) {
		data = port_80_read();

		if (data != PORT_80_IGNORE) {
			idle_count = 0;
			port_80_write(data);
		}
	}

	if (++idle_count >= INTERRUPT_DISABLE_IDLE_COUNT)
		port_80_interrupt_disable();
}
DECLARE_IRQ(MEC1322_IRQ_TIMER16_1, port_80_interrupt, 2);
