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

/* Clocks and power management settings */

#include "clock.h"
#include "common.h"
#include "console.h"
#include "cpu.h"
#include "hooks.h"
#include "hwtimer.h"
#include "pwm.h"
#include "pwm_chip.h"
#include "registers.h"
#include "shared_mem.h"
#include "system.h"
#include "task.h"
#include "timer.h"
#include "uart.h"
#include "util.h"
#include "vboot_hash.h"

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

#ifdef CONFIG_LOW_POWER_IDLE
/* Recovery time for HvySlp2 is 0 usec */
#define HEAVY_SLEEP_RECOVER_TIME_USEC   75

#define SET_HTIMER_DELAY_USEC           200

static int idle_sleep_cnt;
static int idle_dsleep_cnt;
static uint64_t total_idle_dsleep_time_us;

/*
 * Fixed amount of time to keep the console in use flag true after boot in
 * order to give a permanent window in which the heavy sleep mode is not used.
 */
#define CONSOLE_IN_USE_ON_BOOT_TIME (15*SECOND)
static int console_in_use_timeout_sec = 60;
static timestamp_t console_expire_time;
#endif /*CONFIG_LOW_POWER_IDLE */

static int freq = 48000000;

void clock_wait_cycles(uint32_t cycles)
{
	asm volatile("1: subs %0, #1\n"
		     "   bne 1b\n" : "+r"(cycles));
}

int clock_get_freq(void)
{
	return freq;
}

void clock_init(void)
{
#ifdef CONFIG_CLOCK_CRYSTAL
	/* XOSEL: 0 = Parallel resonant crystal */
	MEC1322_VBAT_CE &= ~0x1;
#else
	/* XOSEL: 1 = Single ended clock source */
	MEC1322_VBAT_CE |= 0x1;
#endif

	/* 32K clock enable */
	MEC1322_VBAT_CE |= 0x2;

#ifdef CONFIG_CLOCK_CRYSTAL
	/* Wait for crystal to stabilize (OSC_LOCK == 1) */
	while (!(MEC1322_PCR_CHIP_OSC_ID & 0x100))
		;
#endif
}

/**
 * Speed through boot + vboot hash calculation, dropping our processor clock
 * only after vboot hashing is completed.
 */
static void clock_turbo_disable(void);
DECLARE_DEFERRED(clock_turbo_disable);

static void clock_turbo_disable(void)
{
#ifdef CONFIG_VBOOT_HASH
	if (vboot_hash_in_progress())
		hook_call_deferred(&clock_turbo_disable_data, 100 * MSEC);
	else
#endif
		/* Use 12 MHz processor clock for power savings */
		MEC1322_PCR_PROC_CLK_CTL = 4;
}
DECLARE_HOOK(HOOK_INIT, clock_turbo_disable, HOOK_PRIO_INIT_VBOOT_HASH + 1);

#ifdef CONFIG_LOW_POWER_IDLE
/**
 * initialization of Hibernation timer
 */
static void htimer_init(void)
{
	MEC1322_INT_BLK_EN |= BIT(17);
	MEC1322_INT_ENABLE(17) |= BIT(20);  /* GIRQ=17, aggregator bit = 20 */
	MEC1322_HTIMER_PRELOAD = 0;  /* disable at beginning */

	task_enable_irq(MEC1322_IRQ_HTIMER);
}

/**
 * Use hibernate module to set up an htimer interrupt at a given
 * time from now
 *
 * @param seconds      Number of seconds before htimer interrupt
 * @param microseconds Number of microseconds before htimer interrupt
 */
static void system_set_htimer_alarm(uint32_t seconds, uint32_t microseconds)
{
	if (seconds || microseconds) {

		if (seconds > 2) {
			/* count from 2 sec to 2 hrs, mec1322 sec 18.10.2 */
			ASSERT(seconds <= 0xffff / 8);
			MEC1322_HTIMER_CONTROL = 1; /* 0.125(=1/8) per clock */
			/* (number of counts to be loaded)
			 * = seconds * ( 8 clocks per second )
			 *   + microseconds / 125000
			 *   ---> (0 if (microseconds < 125000)
			 */
			MEC1322_HTIMER_PRELOAD =
				(seconds * 8 + microseconds / 125000);

		} else { /* count up to 2 sec. */

			MEC1322_HTIMER_CONTROL = 0; /* 30.5(= 2/61) usec */

			/* (number of counts to be loaded)
			 * = (total microseconds) / 30.5;
			 */
			MEC1322_HTIMER_PRELOAD =
				(seconds * 1000000 + microseconds) * 2 / 61;
		}
	}
}

/**
 * return time slept in micro-seconds
 */
static timestamp_t system_get_htimer(void)
{
	uint16_t count;
	timestamp_t time;

	count =  MEC1322_HTIMER_COUNT;


	if (MEC1322_HTIMER_CONTROL == 1) /* if > 2 sec */
		/* 0.125 sec per count */
		time.le.lo = (uint32_t)(count * 125000);
	else    /* if < 2 sec */
		/* 30.5(=61/2)usec per count */
		time.le.lo = (uint32_t)(count * 61 / 2);

	time.le.hi = 0;

	return time;  /* in uSec */
}

/**
 * Disable and clear hibernation timer interrupt
 */
static void system_reset_htimer_alarm(void)
{
	MEC1322_HTIMER_PRELOAD = 0;
}

/**
 * This is mec1322 specific and equivalent to ARM Cortex's
 * 'DeepSleep' via system control block register, CPU_SCB_SYSCTRL
 */
static void prepare_for_deep_sleep(void)
{
	uint32_t ec_slp_en = MEC1322_PCR_EC_SLP_EN |
			     MEC1322_PCR_EC_SLP_EN_SLEEP;

	/* sysTick timer */
	CPU_NVIC_ST_CTRL &= ~ST_ENABLE;
	CPU_NVIC_ST_CTRL &= ~ST_COUNTFLAG;

	/* Disable JTAG */
	MEC1322_EC_JTAG_EN &= ~1;
	/* Power down ADC VREF, ADC_VREF overrides ADC_CTRL. */
	MEC1322_EC_ADC_VREF_PD |= 1;

	/* Stop watchdog */
	MEC1322_WDG_CTL &= ~1;

	/* Stop timers */
	MEC1322_TMR32_CTL(0) &= ~1;
	MEC1322_TMR32_CTL(1) &= ~1;
	MEC1322_TMR16_CTL(0) &= ~1;

	MEC1322_PCR_CHIP_SLP_EN |= 0x3;
#ifdef CONFIG_PWM
	if (pwm_get_keep_awake_mask())
		ec_slp_en &= ~pwm_get_keep_awake_mask();
	else
#endif
		/* Disable 100 Khz clock */
		MEC1322_PCR_SLOW_CLK_CTL &= 0xFFFFFC00;

	MEC1322_PCR_EC_SLP_EN = ec_slp_en;
	MEC1322_PCR_HOST_SLP_EN |= MEC1322_PCR_HOST_SLP_EN_SLEEP;
	MEC1322_PCR_EC_SLP_EN2 |= MEC1322_PCR_EC_SLP_EN2_SLEEP;

#ifndef CONFIG_POWER_S0IX
	MEC1322_LPC_ACT = 0x0;
#endif

	MEC1322_PCR_SYS_SLP_CTL = 0x2;  /* heavysleep 2 */

	CPU_NVIC_ST_CTRL &= ~ST_TICKINT; /* SYS_TICK_INT_DISABLE */
}

static void resume_from_deep_sleep(void)
{
	CPU_NVIC_ST_CTRL |= ST_TICKINT; /* SYS_TICK_INT_ENABLE */
	CPU_NVIC_ST_CTRL |= ST_ENABLE;

	MEC1322_EC_JTAG_EN = 1;
	MEC1322_EC_ADC_VREF_PD &= ~1;
	/* ADC_VREF_PD overrides ADC_CTRL ! */

	/* Enable timer */
	MEC1322_TMR32_CTL(0) |= 1;
	MEC1322_TMR32_CTL(1) |= 1;
	MEC1322_TMR16_CTL(0) |= 1;

	/* Enable watchdog */
	MEC1322_WDG_CTL |= 1;

	MEC1322_PCR_SLOW_CLK_CTL |= 0x1e0;
	MEC1322_PCR_CHIP_SLP_EN &= ~0x3;
	MEC1322_PCR_EC_SLP_EN &= MEC1322_PCR_EC_SLP_EN_WAKE;
	MEC1322_PCR_HOST_SLP_EN &= MEC1322_PCR_HOST_SLP_EN_WAKE;
	MEC1322_PCR_EC_SLP_EN2 &= MEC1322_PCR_EC_SLP_EN2_WAKE;

	MEC1322_PCR_SYS_SLP_CTL = 0xF8;  /* default */

#ifndef CONFIG_POWER_S0IX
	/* Enable LPC */
	MEC1322_LPC_ACT |= 1;
#endif
}


void clock_refresh_console_in_use(void)
{
	disable_sleep(SLEEP_MASK_CONSOLE);

	/* Set console in use expire time. */
	console_expire_time = get_time();
	console_expire_time.val += console_in_use_timeout_sec * SECOND;
}

/**
 * Low power idle task.  Executed when no tasks are ready to be scheduled.
 */
void __idle(void)
{
	timestamp_t t0;
	timestamp_t t1;
	timestamp_t ht_t1;
	uint32_t next_delay;
	uint32_t max_sleep_time;
	int time_for_dsleep;
	int uart_ready_for_deepsleep;

	htimer_init(); /* hibernation timer initialize */

	disable_sleep(SLEEP_MASK_CONSOLE);
	console_expire_time.val = get_time().val + CONSOLE_IN_USE_ON_BOOT_TIME;


	/*
	 * Print when the idle task starts.  This is the lowest priority task,
	 * so this only starts once all other tasks have gotten a chance to do
	 * their task inits and have gone to sleep.
	 */
	CPRINTS("low power idle task started");

	while (1) {
		/* Disable interrupts */
		interrupt_disable();

		t0 = get_time();  /* uSec */

		/* __hw_clock_event_get() is next programmed timer event */
		next_delay = __hw_clock_event_get() - t0.le.lo;

		time_for_dsleep = next_delay > (HEAVY_SLEEP_RECOVER_TIME_USEC +
						SET_HTIMER_DELAY_USEC);

		max_sleep_time = next_delay - HEAVY_SLEEP_RECOVER_TIME_USEC;

		/* check if there enough time for deep sleep */
		if (DEEP_SLEEP_ALLOWED && time_for_dsleep) {


			/*
			 * Check if the console use has expired and console
			 * sleep is masked by GPIO(UART-RX) interrupt.
			 */
			if ((sleep_mask & SLEEP_MASK_CONSOLE) &&
					t0.val > console_expire_time.val) {
				/* allow console to sleep. */
				enable_sleep(SLEEP_MASK_CONSOLE);

				/*
				 * Wait one clock before checking if heavy sleep
				 * is allowed to give time for sleep mask
				 * to be updated.
				 */
				clock_wait_cycles(1);

				if (LOW_SPEED_DEEP_SLEEP_ALLOWED)
					CPRINTS("Disable console in deepsleep");
			}


			/* UART is not being used  */
			uart_ready_for_deepsleep = LOW_SPEED_DEEP_SLEEP_ALLOWED
						&& !uart_tx_in_progress()
						&& uart_buffer_empty();

			/*
			 * Since MEC1322's heavysleep modes requires all block
			 * to be sleepable, UART/console's readiness is final
			 * decision factor of heavysleep of EC.
			 */
			if (uart_ready_for_deepsleep) {

				idle_dsleep_cnt++;

				/*
				 * config UART Rx as GPIO wakeup interrupt
				 * source
				 */
				uart_enter_dsleep();

				/* MEC1322 specific deep-sleep mode */
				prepare_for_deep_sleep();

				/*
				 * 'max_sleep_time' value should be big
				 * enough so that hibernation timer's interrupt
				 * triggers only after 'wfi' completes its
				 * excution.
				 */
				max_sleep_time -= (get_time().le.lo - t0.le.lo);

				/* setup/enable htimer wakeup interrupt */
				system_set_htimer_alarm(0, max_sleep_time);
			} else {
				idle_sleep_cnt++;
			}

			/* Wait for interrupt: goes into deep sleep. */
			asm("wfi");

			if (uart_ready_for_deepsleep) {

				resume_from_deep_sleep();

				/*
				 * Fast forward timer according to htimer
				 * counter:
				 * Since all blocks including timers will be in
				 * sleep mode, timers stops except hibernate
				 * timer.
				 * And system schedule timer should be corrected
				 * after wakeup by either hibernate timer or
				 * GPIO_UART_RX interrupt.
				 */
				ht_t1 = system_get_htimer();

				/* disable/clear htimer wakeup interrupt */
				system_reset_htimer_alarm();

				t1.val = t0.val +
				       (uint64_t)(max_sleep_time - ht_t1.le.lo);

				force_time(t1);

				/* re-eanble UART */
				uart_exit_dsleep();

				/* Record time spent in deep sleep. */
				total_idle_dsleep_time_us +=
				       (uint64_t)(max_sleep_time - ht_t1.le.lo);
			}

		} else { /* CPU 'Sleep' mode */

			idle_sleep_cnt++;

			asm("wfi");

		}

		interrupt_enable();
	} /* while(1) */
}

#ifdef CONFIG_CMD_IDLE_STATS
/**
 * Print low power idle statistics
 */
static int command_idle_stats(int argc, char **argv)
{
	timestamp_t ts = get_time();

	ccprintf("Num idle calls that sleep:           %d\n", idle_sleep_cnt);
	ccprintf("Num idle calls that deep-sleep:      %d\n", idle_dsleep_cnt);

	ccprintf("Total Time spent in deep-sleep(sec): %.6lld(s)\n",
						total_idle_dsleep_time_us);
	ccprintf("Total time on:                       %.6llds\n\n", ts.val);
	return EC_SUCCESS;
}
DECLARE_CONSOLE_COMMAND(idlestats, command_idle_stats,
			"",
			"Print last idle stats");
#endif /* defined(CONFIG_CMD_IDLE_STATS) */

/**
 * Configure deep sleep clock settings.
 */
static int command_dsleep(int argc, char **argv)
{
	int v;

	if (argc > 1) {
		if (parse_bool(argv[1], &v)) {
			/*
			 * Force deep sleep not to use heavy sleep mode or
			 * allow it to use the heavy sleep mode.
			 */
			if (v)  /* 'on' */
				disable_sleep(SLEEP_MASK_FORCE_NO_LOW_SPEED);
			else    /* 'off' */
				enable_sleep(SLEEP_MASK_FORCE_NO_LOW_SPEED);
		} else {
			/* Set console in use timeout. */
			char *e;
			v = strtoi(argv[1], &e, 10);
			if (*e)
				return EC_ERROR_PARAM1;

			console_in_use_timeout_sec = v;

			/* Refresh console in use to use new timeout. */
			clock_refresh_console_in_use();
		}
	}

	ccprintf("Sleep mask: %08x\n", sleep_mask);
	ccprintf("Console in use timeout:   %d sec\n",
			console_in_use_timeout_sec);

	return EC_SUCCESS;
}
DECLARE_CONSOLE_COMMAND(dsleep, command_dsleep,
			"[ on | off | <timeout> sec]",
			"Deep sleep clock settings:\nUse 'on' to force deep "
			"sleep NOT to enter heavysleep mode.\nUse 'off' to "
			"allow deep sleep to use heavysleep whenever conditions"
			"allow.\n"
			"Give a timeout value for the console in use timeout.\n"
			"See also 'sleepmask'.");
#endif /* CONFIG_LOW_POWER_IDLE */
