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

/* MAX32660 HW Timer module for Chrome EC */

#include "clock.h"
#include "console.h"
#include "common.h"
#include "hooks.h"
#include "hwtimer.h"
#include "task.h"
#include "timer.h"
#include "registers.h"
#include "tmr_regs.h"
#include "gcr_regs.h"

/* Define the rollover timer */
#define TMR_ROLLOVER MXC_TMR0
#define TMR_ROLLOVER_IRQ EC_TMR0_IRQn

/* Define the event timer */
#define TMR_EVENT MXC_TMR1
#define TMR_EVENT_IRQ EC_TMR1_IRQn

#define ROLLOVER_EVENT 1
#define NOT_ROLLOVER_EVENT 0

#define TMR_PRESCALER MXC_V_TMR_CN_PRES_DIV8
#define TMR_DIV (1 << TMR_PRESCALER)

/* The frequency of timer using the prescaler */
#define TIMER_FREQ_HZ (PeripheralClock / TMR_DIV)

/* Console output macros */
#define CPUTS(outstr) cputs(CC_SYSTEM, outstr)
#define CPRINTS(format, args...) cprints(CC_SYSTEM, format, ##args)

static uint32_t last_deadline;

/* brief Timer prescaler values */
enum tmr_pres {
	TMR_PRES_1 = MXC_V_TMR_CN_PRES_DIV1,     /// Divide input clock by 1
	TMR_PRES_2 = MXC_V_TMR_CN_PRES_DIV2,     /// Divide input clock by 2
	TMR_PRES_4 = MXC_V_TMR_CN_PRES_DIV4,     /// Divide input clock by 4
	TMR_PRES_8 = MXC_V_TMR_CN_PRES_DIV8,     /// Divide input clock by 8
	TMR_PRES_16 = MXC_V_TMR_CN_PRES_DIV16,   /// Divide input clock by 16
	TMR_PRES_32 = MXC_V_TMR_CN_PRES_DIV32,   /// Divide input clock by 32
	TMR_PRES_64 = MXC_V_TMR_CN_PRES_DIV64,   /// Divide input clock by 64
	TMR_PRES_128 = MXC_V_TMR_CN_PRES_DIV128, /// Divide input clock by 128
	TMR_PRES_256 =
		(0x20 << MXC_F_TMR_CN_PRES_POS), /// Divide input clock by 256
	TMR_PRES_512 =
		(0x21 << MXC_F_TMR_CN_PRES_POS), /// Divide input clock by 512
	TMR_PRES_1024 =
		(0x22 << MXC_F_TMR_CN_PRES_POS), /// Divide input clock by 1024
	TMR_PRES_2048 =
		(0x23 << MXC_F_TMR_CN_PRES_POS), /// Divide input clock by 2048
	TMR_PRES_4096 =
		(0x24 << MXC_F_TMR_CN_PRES_POS), /// Divide input clock by 4096
};

/* Timer modes */
enum tmr_mode {
	TMR_MODE_ONESHOT = MXC_V_TMR_CN_TMODE_ONESHOT, /// Timer Mode ONESHOT
	TMR_MODE_CONTINUOUS =
		MXC_V_TMR_CN_TMODE_CONTINUOUS,	 /// Timer Mode CONTINUOUS
	TMR_MODE_COUNTER = MXC_V_TMR_CN_TMODE_COUNTER, /// Timer Mode COUNTER
	TMR_MODE_PWM = MXC_V_TMR_CN_TMODE_PWM,	 /// Timer Mode PWM
	TMR_MODE_CAPTURE = MXC_V_TMR_CN_TMODE_CAPTURE, /// Timer Mode CAPTURE
	TMR_MODE_COMPARE = MXC_V_TMR_CN_TMODE_COMPARE, /// Timer Mode COMPARE
	TMR_MODE_GATED = MXC_V_TMR_CN_TMODE_GATED,     /// Timer Mode GATED
	TMR_MODE_CAPTURE_COMPARE =
		MXC_V_TMR_CN_TMODE_CAPTURECOMPARE /// Timer Mode CAPTURECOMPARE
};

/*
 * Calculate the number of microseconds for a given timer tick
 */
static inline uint32_t ticks_to_usecs(uint32_t ticks)
{
	return (uint64_t)ticks * SECOND / TIMER_FREQ_HZ;
}

/*
 * Calculate the number of timer ticks for a given microsecond value
 */
static inline uint32_t usecs_to_ticks(uint32_t usecs)
{
	return ((uint64_t)(usecs)*TIMER_FREQ_HZ / SECOND);
}

void __hw_clock_event_set(uint32_t deadline)
{
	uint32_t event_time_us;
	uint32_t event_time_ticks;
	uint32_t time_now;

	last_deadline = deadline;
	time_now = __hw_clock_source_read();

	/* check if the deadline has rolled over */
	if (deadline < time_now) {
		event_time_us = (0xFFFFFFFF - time_now) + deadline;
	} else {
		/* How long from the current time to the deadline? */
		event_time_us = (deadline - __hw_clock_source_read());
	}

	/* Convert event_time to ticks rounding up */
	event_time_ticks = usecs_to_ticks(event_time_us) + 1;

	/* set the event time into the timer compare */
	TMR_EVENT->cmp = event_time_ticks;
	/* zero out the timer */
	TMR_EVENT->cnt = 0;
	TMR_EVENT->cn |= MXC_F_TMR_CN_TEN;
}

uint32_t __hw_clock_event_get(void)
{
	return last_deadline;
}

void __hw_clock_event_clear(void)
{
	TMR_EVENT->cn &= ~(MXC_F_TMR_CN_TEN);
}

uint32_t __hw_clock_source_read(void)
{
	uint32_t timer_count_ticks;

	/* Read the timer value and return the results in microseconds */
	timer_count_ticks = TMR_ROLLOVER->cnt;
	return ticks_to_usecs(timer_count_ticks);
}

void __hw_clock_source_set(uint32_t ts)
{
	uint32_t timer_count_ticks;
	timer_count_ticks = usecs_to_ticks(ts);
	TMR_ROLLOVER->cnt = timer_count_ticks;
}

/**
 * Interrupt handler for Timer
 */
static void __timer_event_isr(void)
{
	/* Clear the event timer */
	TMR_EVENT->intr = MXC_F_TMR_INTR_IRQ_CLR;
	/* Process the timer, pass in that this was NOT a rollover event */
	if (TMR_ROLLOVER->intr) {
		TMR_ROLLOVER->intr = MXC_F_TMR_INTR_IRQ_CLR;
		process_timers(ROLLOVER_EVENT);
	} else {
		process_timers(NOT_ROLLOVER_EVENT);
	}
}
DECLARE_IRQ(EC_TMR1_IRQn, __timer_event_isr, 1);

static void init_timer(mxc_tmr_regs_t *timer, enum tmr_pres prescaler,
		       enum tmr_mode mode, uint32_t count)
{
	/* Disable the Timer */
	timer->cn &= ~(MXC_F_TMR_CN_TEN);

	if (timer == MXC_TMR0) {
		/* Enable Timer 0 Clock */
		MXC_GCR->perckcn0 &= ~(MXC_F_GCR_PERCKCN0_T0D);
	} else if (timer == MXC_TMR1) {
		/* Enable Timer 1 Clock */
		MXC_GCR->perckcn0 &= ~(MXC_F_GCR_PERCKCN0_T1D);
	} else if (timer == MXC_TMR2) {
		/* Enable Timer 2 Clock */
		MXC_GCR->perckcn0 &= ~(MXC_F_GCR_PERCKCN0_T2D);
	}

	/* Disable timer and clear settings */
	timer->cn = 0;

	/* Clear interrupt flag */
	timer->intr = MXC_F_TMR_INTR_IRQ_CLR;

	/* Set the prescaler */
	timer->cn = (prescaler << MXC_F_TMR_CN_PRES_POS);

	/* Configure the timer */
	timer->cn = (timer->cn & ~(MXC_F_TMR_CN_TMODE | MXC_F_TMR_CN_TPOL)) |
		    ((mode << MXC_F_TMR_CN_TMODE_POS) & MXC_F_TMR_CN_TMODE) |
		    ((0 << MXC_F_TMR_CN_TPOL_POS) & MXC_F_TMR_CN_TPOL);

	timer->cnt = 0x1;
	timer->cmp = count;
}

int __hw_clock_source_init(uint32_t start_t)
{
	/* Initialize two timers, one for the OS Rollover and one for the OS
	 * Events */
	init_timer(TMR_ROLLOVER, TMR_PRESCALER, TMR_MODE_CONTINUOUS,
		   0xffffffff);
	init_timer(TMR_EVENT, TMR_PRESCALER, TMR_MODE_COMPARE, 0x0);
	__hw_clock_source_set(start_t);

	/* Enable the timers */
	TMR_ROLLOVER->cn |= MXC_F_TMR_CN_TEN;
	TMR_EVENT->cn |= MXC_F_TMR_CN_TEN;

	/* Enable the IRQ */
	task_enable_irq(TMR_EVENT_IRQ);

	/* Return the Event timer IRQ number (NOT the Rollover IRQ) */
	return TMR_EVENT_IRQ;
}

static int hwtimer_display(int argc, char **argv)
{
	CPRINTS(" TMR_EVENT count 0x%08x", TMR_EVENT->cnt);
	CPRINTS(" TMR_ROLLOVER count 0x%08x", TMR_ROLLOVER->cnt);
	return EC_SUCCESS;
}
DECLARE_CONSOLE_COMMAND(hwtimer, hwtimer_display, "hwtimer",
			"Display hwtimer counts");
