/* Copyright (c) 2014 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.
 */

/* Hardware timers driver */

#include "clock.h"
#include "clock_chip.h"
#include "common.h"
#include "hooks.h"
#include "hwtimer.h"
#include "hwtimer_chip.h"
#include "math_util.h"
#include "registers.h"
#include "console.h"
#include "task.h"
#include "timer.h"
#include "util.h"

/* Depth of event timer */
#define TICK_EVT_DEPTH         16 /* Depth of event timer Unit: bits */
#define TICK_EVT_INTERVAL      (1 << TICK_EVT_DEPTH) /* Unit: us */
#define TICK_EVT_INTERVAL_MASK (TICK_EVT_INTERVAL - 1) /* Mask of interval */
#define TICK_EVT_MAX_CNT     (TICK_EVT_INTERVAL - 1) /* Maximum event counter */

/* Time when event will be expired unit:us */
static volatile uint32_t evt_expired_us;
/* 32-bits event counter */
static volatile uint32_t evt_cnt;
/* Debugger information */
#if DEBUG_TMR
static volatile uint32_t evt_cnt_us_dbg;
static volatile uint32_t cur_cnt_us_dbg;
#endif

#if !(DEBUG_TMR)
#define CPUTS(...)
#define CPRINTS(...)
#else
#define CPUTS(outstr) cputs(CC_CLOCK, outstr)
#define CPRINTS(format, args...) cprints(CC_CLOCK, format, ## args)
#endif

/*****************************************************************************/
/* Internal functions */
void init_hw_timer(int itim_no, enum ITIM_SOURCE_CLOCK_T source)
{
	/* Use internal 32K clock/APB2 for ITIM16 */
	UPDATE_BIT(NPCX_ITCTS(itim_no), NPCX_ITCTS_CKSEL,
			source != ITIM_SOURCE_CLOCK_APB2);

	/* Clear timeout status */
	SET_BIT(NPCX_ITCTS(itim_no), NPCX_ITCTS_TO_STS);

	/* ITIM timeout interrupt enable */
	SET_BIT(NPCX_ITCTS(itim_no), NPCX_ITCTS_TO_IE);

	/* ITIM timeout wake-up enable */
	SET_BIT(NPCX_ITCTS(itim_no), NPCX_ITCTS_TO_WUE);
}

/*****************************************************************************/
/* HWTimer event handlers */
void __hw_clock_event_set(uint32_t deadline)
{
	fp_t inv_evt_tick = FLOAT_TO_FP(INT_32K_CLOCK/(float)SECOND);
	int32_t  evt_cnt_us;
	/* Is deadline min value? */
	if (evt_expired_us != 0 && evt_expired_us < deadline)
		return;

	/* mark min event value */
	evt_expired_us = deadline;
	evt_cnt_us = deadline - __hw_clock_source_read();
#if DEBUG_TMR
	evt_cnt_us_dbg = deadline - __hw_clock_source_read();
#endif
	/* Deadline is behind current timer */
	if (evt_cnt_us < 0)
		evt_cnt_us = 1;

	/* Event module disable */
	CLEAR_BIT(NPCX_ITCTS(ITIM_EVENT_NO), NPCX_ITCTS_ITEN);

	/*
	 * ITIM count down : event expired : Unit: 1/32768 sec
	 * It must exceed evt_expired_us for process_timers function
	 */
	evt_cnt = FP_TO_INT((fp_inter_t)(evt_cnt_us) * inv_evt_tick);
	if (evt_cnt > TICK_EVT_MAX_CNT) {
		CPRINTS("Event overflow! 0x%08x, us is %d\r\n",
				evt_cnt, evt_cnt_us);
		evt_cnt = TICK_EVT_MAX_CNT;
	}

	/* Wait for module disable to take effect before updating count */
	while (IS_BIT_SET(NPCX_ITCTS(ITIM_EVENT_NO), NPCX_ITCTS_ITEN))
		;

	NPCX_ITCNT16(ITIM_EVENT_NO) = MAX(evt_cnt, 1);

	/* Event module enable */
	SET_BIT(NPCX_ITCTS(ITIM_EVENT_NO), NPCX_ITCTS_ITEN);

	/* Wait for module enable */
	while (!IS_BIT_SET(NPCX_ITCTS(ITIM_EVENT_NO), NPCX_ITCTS_ITEN))
		;

	/* Enable interrupt of ITIM */
	task_enable_irq(ITIM16_INT(ITIM_EVENT_NO));
}

/* Returns the time-stamp of the next programmed event */
uint32_t __hw_clock_event_get(void)
{
	if (evt_expired_us)
		return evt_expired_us;
	else /* No events. Give maximum deadline */
		return EVT_MAX_EXPIRED_US;
}

/* Get current counter value of event timer */
uint16_t __hw_clock_event_count(void)
{
	uint16_t cnt, cnt2;

	cnt = NPCX_ITCNT16(ITIM_EVENT_NO);
	/* Wait for two consecutive equal values are read */
	while ((cnt2 = NPCX_ITCNT16(ITIM_EVENT_NO)) != cnt)
		cnt = cnt2;

	return cnt;
}

/* Returns time delay cause of deep idle */
uint32_t __hw_clock_get_sleep_time(uint16_t pre_evt_cnt)
{
	fp_t evt_tick = FLOAT_TO_FP(SECOND/(float)INT_32K_CLOCK);
	uint32_t sleep_time;
	uint16_t cnt = __hw_clock_event_count();

	/* Event has been triggered but timer ISR doesn't handle it */
	if (IS_BIT_SET(NPCX_ITCTS(ITIM_EVENT_NO), NPCX_ITCTS_TO_STS))
		sleep_time = FP_TO_INT((fp_inter_t)(pre_evt_cnt+1) * evt_tick);
	/* Event hasn't been triggered */
	else
		sleep_time = FP_TO_INT((fp_inter_t)(pre_evt_cnt+1 - cnt) *
				       evt_tick);

	return sleep_time;
}

/* Cancel the next event programmed by __hw_clock_event_set */
void __hw_clock_event_clear(void)
{
	/* ITIM event module disable */
	CLEAR_BIT(NPCX_ITCTS(ITIM_EVENT_NO), NPCX_ITCTS_ITEN);

	/* Disable interrupt of Event */
	task_disable_irq(ITIM16_INT(ITIM_EVENT_NO));

	/* Clear event parameters */
	evt_expired_us = 0;
	evt_cnt = 0;
}

/* Irq for hwtimer event */
void __hw_clock_event_irq(void)
{
	/* ITIM event module disable */
	CLEAR_BIT(NPCX_ITCTS(ITIM_EVENT_NO), NPCX_ITCTS_ITEN);

	/* Disable interrupt of event */
	task_disable_irq(ITIM16_INT(ITIM_EVENT_NO));

	/* Clear timeout status for event */
	SET_BIT(NPCX_ITCTS(ITIM_EVENT_NO), NPCX_ITCTS_TO_STS);

	/* Clear event parameters */
	evt_expired_us = 0;
	evt_cnt = 0;

	/* handle upper driver */
	process_timers(0);

#ifdef CONFIG_LOW_POWER_IDLE
	/*
	 * Set event for ITIM32 after process_timers() since no events set if
	 * event's deadline is over 32 bits but current source clock isn't.
	 * ITIM32 is based on apb2 and ec won't wake-up in deep-idle even if it
	 * expires.
	 */
	if (evt_expired_us == 0)
		__hw_clock_event_set(EVT_MAX_EXPIRED_US);
#endif

}
DECLARE_IRQ(ITIM16_INT(ITIM_EVENT_NO), __hw_clock_event_irq, 2);


/*****************************************************************************/
/* HWTimer tick handlers */

/* Modify preload counter of source clock. */
void hw_clock_source_set_preload(uint32_t ts, uint8_t clear)
{
	/* ITIM32 module disable */
	CLEAR_BIT(NPCX_ITCTS(ITIM32), NPCX_ITCTS_ITEN);
	CLEAR_BIT(NPCX_ITCTS(ITIM32), NPCX_ITCTS_CKSEL);

	/* Set preload counter to current time */
	NPCX_ITCNT32 = TICK_ITIM32_MAX_CNT - ts;
	/* Clear timeout status or not */
	if (clear)
		SET_BIT(NPCX_ITCTS(ITIM32), NPCX_ITCTS_TO_STS);
	/* ITIM32 module enable */
	SET_BIT(NPCX_ITCTS(ITIM32), NPCX_ITCTS_ITEN);
}

/* Returns the value of the free-running counter used as clock. */
uint32_t __hw_clock_source_read(void)
{
	uint32_t cnt, cnt2;

	cnt = NPCX_ITCNT32;
	/*
	 * Wait for two consecutive equal values are read no matter
	 * ITIM's source clock is APB2 or 32K since mux's delay.
	 */
	while ((cnt2 = NPCX_ITCNT32) != cnt)
		cnt = cnt2;

#if DEBUG_TMR
	cur_cnt_us_dbg = TICK_ITIM32_MAX_CNT - cnt;
#endif
	return TICK_ITIM32_MAX_CNT - cnt;
}

/* Override the current value of the hardware counter */
void __hw_clock_source_set(uint32_t ts)
{
#if DEBUG_TMR
	cur_cnt_us_dbg = TICK_ITIM32_MAX_CNT - ts;
#endif
	hw_clock_source_set_preload(ts, 0);
}

/* Irq for hwtimer tick */
void __hw_clock_source_irq(void)
{
	/* Is timeout trigger trigger? */
	if (IS_BIT_SET(NPCX_ITCTS(ITIM32), NPCX_ITCTS_TO_STS)) {
		/* Restore ITIM32 preload counter value to maximum value */
		hw_clock_source_set_preload(0, 1);
		/* 32-bits timer count overflow */
		process_timers(1);

	} else { /* Handle soft trigger */
		process_timers(0);
#ifdef CONFIG_LOW_POWER_IDLE
		/* Set event for ITIM32. Please see above for detail */
		if (evt_expired_us == 0)
			__hw_clock_event_set(EVT_MAX_EXPIRED_US);
#endif
	}
}
DECLARE_IRQ(NPCX_IRQ_ITIM32, __hw_clock_source_irq, 2);

static void update_prescaler(void)
{
	/*
	 * prescaler to time tick
	 * Ttick_unit = (PRE_8+1) * Tapb2_clk
	 * PRE_8 = (Ttick_unit/Tapb2_clk) -1
	 */
	NPCX_ITPRE(ITIM32)  = (clock_get_apb2_freq() / SECOND) - 1;
	/* Set event tick unit = 1/32768 sec */
	NPCX_ITPRE(ITIM_EVENT_NO) = 0;

}
DECLARE_HOOK(HOOK_FREQ_CHANGE, update_prescaler, HOOK_PRIO_DEFAULT);

int __hw_clock_source_init(uint32_t start_t)
{
	/*
	 * 1. Use ITIM16-1 as internal time reading
	 * 2. Use ITIM16-2 for event handling
	 */

	/* Enable clock for ITIM peripheral */
	clock_enable_peripheral(CGC_OFFSET_TIMER, CGC_TIMER_MASK,
			CGC_MODE_RUN | CGC_MODE_SLEEP);

	/* init tick & event timer first */
	init_hw_timer(ITIM32,  ITIM_SOURCE_CLOCK_APB2);
	init_hw_timer(ITIM_EVENT_NO, ITIM_SOURCE_CLOCK_32K);

	/* Set initial prescaler */
	update_prescaler();

	/*
	 * Override the count with the start value now that counting has
	 * started.
	 */
	hw_clock_source_set_preload(start_t, 1);

	/* Enable interrupt of ITIM */
	task_enable_irq(NPCX_IRQ_ITIM32);

	return NPCX_IRQ_ITIM32;
}
