/* Copyright 2012 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 "gpio.h"
#include "hooks.h"
#include "hwtimer.h"
#include "registers.h"
#include "system.h"
#include "task.h"
#include "timer.h"
#include "uart.h"
#include "util.h"
#include "watchdog.h"

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

#define PLL_CLOCK 66666667  /* System clock = 200MHz PLL/3 = 66.667MHz */

#ifdef CONFIG_LOW_POWER_USE_LFIOSC
/*
 * Length of time for the processor to wake up from deep sleep. Actual
 * measurement gives anywhere up to 780us, depending on the mode it is coming
 * out of. The datasheet gives a maximum of 846us, for coming out of deep
 * sleep in our worst case deep sleep mode.
 */
#define DEEP_SLEEP_RECOVER_TIME_USEC 850
#else
/*
 * Length of time for the processor to wake up from deep sleep. Datasheet
 * maximum is 145us, but in practice have seen as much as 336us.
 */
#define DEEP_SLEEP_RECOVER_TIME_USEC 400
#endif

/* Low power idle statistics */
#ifdef CONFIG_LOW_POWER_IDLE
static int idle_sleep_cnt;
static int idle_dsleep_cnt;
static uint64_t idle_dsleep_time_us;
static int dsleep_recovery_margin_us = 1000000;

/*
 * Fixed amount of time to keep the console in use flag true after boot in
 * order to give a permanent window in which the low speed clock 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

static int freq;

/**
 * Disable the PLL; run off internal oscillator.
 */
static void disable_pll(void)
{
	/* Switch to 16MHz internal oscillator and power down the PLL */
	LM4_SYSTEM_RCC = LM4_SYSTEM_RCC_SYSDIV(0) |
		LM4_SYSTEM_RCC_BYPASS |
		LM4_SYSTEM_RCC_PWRDN |
		LM4_SYSTEM_RCC_OSCSRC(1) |
		LM4_SYSTEM_RCC_MOSCDIS;

#ifdef CONFIG_LOW_POWER_IDLE
	/*
	 * If using the low power idle, then set the ACG bit, which specifies
	 * that the sleep and deep sleep modes are using their own clock gating
	 * registers SCGC and DCGS respectively instead of using the run mode
	 * clock gating registers RCGC.
	 */
	LM4_SYSTEM_RCC |= LM4_SYSTEM_RCC_ACG;
#endif

	LM4_SYSTEM_RCC2 &= ~LM4_SYSTEM_RCC2_USERCC2;

	freq = INTERNAL_CLOCK;
}

/**
 * Enable the PLL to run at full clock speed.
 */
static void enable_pll(void)
{
	/* Disable the PLL so we can reconfigure it */
	disable_pll();

	/*
	 * Enable the PLL (PWRDN is no longer set) and set divider.  PLL is
	 * still bypassed, since it hasn't locked yet.
	 */
	LM4_SYSTEM_RCC = LM4_SYSTEM_RCC_SYSDIV(2) |
		LM4_SYSTEM_RCC_USESYSDIV |
		LM4_SYSTEM_RCC_BYPASS |
		LM4_SYSTEM_RCC_OSCSRC(1) |
		LM4_SYSTEM_RCC_MOSCDIS;

#ifdef CONFIG_LOW_POWER_IDLE
	/*
	 * If using the low power idle, then set the ACG bit, which specifies
	 * that the sleep and deep sleep modes are using their own clock gating
	 * registers SCGC and DCGS respectively instead of using the run mode
	 * clock gating registers RCGC.
	 */
	LM4_SYSTEM_RCC |= LM4_SYSTEM_RCC_ACG;
#endif

	/* Wait for the PLL to lock */
	clock_wait_cycles(1024);
	while (!(LM4_SYSTEM_PLLSTAT & 1))
		;

	/* Remove bypass on PLL */
	LM4_SYSTEM_RCC &= ~LM4_SYSTEM_RCC_BYPASS;
	freq = PLL_CLOCK;
}

void clock_enable_pll(int enable, int notify)
{
	if (enable)
		enable_pll();
	else
		disable_pll();

	/* Notify modules of frequency change */
	if (notify)
		hook_notify(HOOK_FREQ_CHANGE);
}

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 BOARD_BDS
	/*
	 * Perform an auto calibration of the internal oscillator using the
	 * 32.768KHz hibernate clock, unless we've already done so.  This is
	 * only necessary on A2 silicon as on BDS; A3 silicon is all
	 * factory-trimmed.
	 */
	if ((LM4_SYSTEM_PIOSCSTAT & 0x300) != 0x100) {
		/* Start calibration */
		LM4_SYSTEM_PIOSCCAL = 0x80000000;
		LM4_SYSTEM_PIOSCCAL = 0x80000200;
		/* Wait for result */
		clock_wait_cycles(16);
		while (!(LM4_SYSTEM_PIOSCSTAT & 0x300))
			;
	}
#else
	/*
	 * Only BDS has an external crystal; other boards don't have one, and
	 * can disable main oscillator control to reduce power consumption.
	 */
	LM4_SYSTEM_MOSCCTL = 0x04;
#endif

	/* Make sure PLL is disabled */
	disable_pll();
}

void clock_enable_peripheral(uint32_t offset, uint32_t mask, uint32_t mode)
{
	if (mode & CGC_MODE_RUN)
		*(LM4_SYSTEM_RCGC_BASE + offset) |= mask;

	if (mode & CGC_MODE_SLEEP)
		*(LM4_SYSTEM_SCGC_BASE + offset) |= mask;

	if (mode & CGC_MODE_DSLEEP)
		*(LM4_SYSTEM_DCGC_BASE + offset) |= mask;

	/* Wait for clock change to take affect. */
	clock_wait_cycles(3);
}

void clock_disable_peripheral(uint32_t offset, uint32_t mask, uint32_t mode)
{
	if (mode & CGC_MODE_RUN)
		*(LM4_SYSTEM_RCGC_BASE + offset) &= ~mask;

	if (mode & CGC_MODE_SLEEP)
		*(LM4_SYSTEM_SCGC_BASE + offset) &= ~mask;

	if (mode & CGC_MODE_DSLEEP)
		*(LM4_SYSTEM_DCGC_BASE + offset) &= ~mask;
}

/*
 * The low power idle task does not support using the EEPROM,
 * because it is dangerous to go to deep sleep while EEPROM
 * transaction is in progress. To fix, LM4_EEPROM_EEDONE, should
 * be checked before going in to deep sleep.
 */
#if defined(CONFIG_LOW_POWER_IDLE) && defined(CONFIG_EEPROM)
#error "Low power idle mode does not support use of EEPROM"
#endif

#ifdef CONFIG_LOW_POWER_IDLE

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, t1, rtc_t0, rtc_t1;
	int next_delay = 0;
	int time_for_dsleep, margin_us;
	int use_low_speed_clock;

	/* Enable the hibernate IRQ used to wake up from deep sleep */
	system_enable_hib_interrupt();

	/* Set SRAM and flash power management to 'low power' in deep sleep. */
	LM4_SYSTEM_DSLPPWRCFG = 0x23;

	/* Enable JTAG interrupt which will notify us when JTAG is in use. */
	gpio_enable_interrupt(GPIO_JTAG_TCK);

	/*
	 * Initialize console in use to true and specify the console expire
	 * time in order to give a fixed window on boot in which the low speed
	 * clock will not be used in idle.
	 */
	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 before going to deep sleep in order to
		 * calculate the appropriate time to wake up. Note: the wfi
		 * instruction waits until an interrupt is pending, so it
		 * will still wake up even with interrupts disabled.
		 */
		interrupt_disable();

		t0 = get_time();
		next_delay = __hw_clock_event_get() - t0.le.lo;

		/* Do we have enough time before next event to deep sleep. */
		time_for_dsleep = next_delay > (DEEP_SLEEP_RECOVER_TIME_USEC +
						HIB_SET_RTC_MATCH_DELAY_USEC);

		if (DEEP_SLEEP_ALLOWED && time_for_dsleep) {
			/* Deep-sleep in STOP mode. */
			idle_dsleep_cnt++;

			/* Check if the console use has expired. */
			if ((sleep_mask & SLEEP_MASK_CONSOLE) &&
					t0.val > console_expire_time.val) {
				/* Enable low speed deep sleep. */
				enable_sleep(SLEEP_MASK_CONSOLE);

				/*
				 * Wait one clock before checking if low speed
				 * deep sleep is allowed to give time for
				 * sleep mask to update.
				 */
				clock_wait_cycles(1);

				if (LOW_SPEED_DEEP_SLEEP_ALLOWED)
					CPRINTS("Disabling console in "
						"deep sleep");
			}

			/*
			 * Determine if we should use a lower clock speed or
			 * keep the same (16MHz) clock in deep sleep. Use the
			 * lower speed only if the sleep mask specifies that low
			 * speed sleep is allowed, the console UART TX is not
			 * busy, and the console UART buffer is empty.
			 */
			use_low_speed_clock = LOW_SPEED_DEEP_SLEEP_ALLOWED &&
				!uart_tx_in_progress() && uart_buffer_empty();

#ifdef CONFIG_LOW_POWER_USE_LFIOSC
			/* Set the deep sleep clock register. Use either the
			 *  normal PIOSC (16MHz) or the LFIOSC (32kHz). */
			LM4_SYSTEM_DSLPCLKCFG = use_low_speed_clock ?
					0x32 : 0x10;
#else
			/*
			 * Set the deep sleep clock register. Use either the
			 * PIOSC with no divider (16MHz) or the PIOSC with
			 * a /64 divider (250kHz).
			 */
			LM4_SYSTEM_DSLPCLKCFG = use_low_speed_clock ?
					0x1f800010 : 0x10;
#endif

			/*
			 * If using low speed clock, disable console.
			 * This will also convert the console RX pin to a GPIO
			 * and set an edge interrupt to wake us from deep sleep
			 * if any action occurs on console.
			 */
			if (use_low_speed_clock)
				uart_enter_dsleep();

			/* Set deep sleep bit. */
			CPU_SCB_SYSCTRL |= 0x4;

			/* Record real time before sleeping. */
			rtc_t0 = system_get_rtc();

			/*
			 * Set RTC interrupt in time to wake up before
			 * next event.
			 */
			system_set_rtc_alarm(0, next_delay -
						DEEP_SLEEP_RECOVER_TIME_USEC);

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

			/* Clear deep sleep bit. */
			CPU_SCB_SYSCTRL &= ~0x4;

			/* Disable and clear RTC interrupt. */
			system_reset_rtc_alarm();

			/* Fast forward timer according to RTC counter. */
			rtc_t1 = system_get_rtc();
			t1.val = t0.val + (rtc_t1.val - rtc_t0.val);
			force_time(t1);

			/* If using low speed clock, re-enable the console. */
			if (use_low_speed_clock)
				uart_exit_dsleep();

			/* Record time spent in deep sleep. */
			idle_dsleep_time_us += (rtc_t1.val - rtc_t0.val);

			/* Calculate how close we were to missing deadline */
			margin_us = next_delay - (int)(rtc_t1.val - rtc_t0.val);
			if (margin_us < 0)
				CPRINTS("overslept by %dus", -margin_us);

			/* Record the closest to missing a deadline. */
			if (margin_us < dsleep_recovery_margin_us)
				dsleep_recovery_margin_us = margin_us;
		} else {
			idle_sleep_cnt++;

			/* Normal idle : only CPU clock stopped. */
			asm("wfi");
		}
		interrupt_enable();
	}
}
#endif /* CONFIG_LOW_POWER_IDLE */

/*****************************************************************************/
/* Console commands */

#ifdef CONFIG_CMD_SLEEP
/**
 * Measure baseline for power consumption.
 *
 * Levels :
 *   0 : CPU running in tight loop
 *   1 : CPU running in tight loop but peripherals gated
 *   2 : CPU in sleep mode
 *   3 : CPU in sleep mode and peripherals gated
 *   4 : CPU in deep sleep mode
 *   5 : CPU in deep sleep mode and peripherals gated
 *
 * Clocks :
 *   0 : No change
 *   1 : 16MHz
 *   2 : 1 MHz
 *   3 : 30kHz
 *
 * SRAM Power Management:
 *   0 : Active
 *   1 : Standby
 *   3 : Low Power
 *
 * Flash Power Management:
 *   0 : Active
 *   2 : Low Power
 */
static int command_sleep(int argc, char **argv)
{
	int level = 0;
	int clock = 0;
	int sram_pm = 0;
	int flash_pm = 0;
	uint32_t uartibrd = 0;
	uint32_t uartfbrd = 0;

	if (argc >= 2)
		level = strtoi(argv[1], NULL, 10);
	if (argc >= 3)
		clock = strtoi(argv[2], NULL, 10);
	if (argc >= 4)
		sram_pm = strtoi(argv[3], NULL, 10);
	if (argc >= 5)
		flash_pm = strtoi(argv[4], NULL, 10);

#ifdef BOARD_BDS
	/* Remove LED current sink. */
	gpio_set_level(GPIO_DEBUG_LED, 0);
#endif

	ccprintf("Sleep : level %d, clock %d, sram pm %d, flash_pm %d...\n",
			level, clock, sram_pm, flash_pm);
	cflush();

	/* Set clock speed. */
	if (clock) {
		/* Use ROM code function to set the clock */
		void **func_table = (void **)*(uint32_t *)0x01000044;
		void (*rom_clock_set)(uint32_t rcc) = func_table[23];

		/* Disable interrupts. */
		asm volatile("cpsid i");

		switch (clock) {
		case 1: /* 16MHz IOSC */
			uartibrd = 17;
			uartfbrd = 23;
			rom_clock_set(0x00000d51);
			break;
		case 2: /* 1MHz IOSC */
			uartibrd = 1;
			uartfbrd = 5;
			rom_clock_set(0x07C00d51);
			break;
		case 3: /* 30 kHz */
			uartibrd = 0;
			uartfbrd = 0;
			rom_clock_set(0x00000d71);
			break;
		}

		/*
		 * TODO(crosbug.com/p/23795): move this to the UART module;
		 * ugly to have UARTisms here.  Also note this only fixes
		 * UART0, not UART1.  Should just be able to trigger
		 * HOOK_FREQ_CHANGE and have that take care of it.
		 */
		if (uartfbrd) {
			/* Disable the port via UARTCTL and add HSE. */
			LM4_UART_CTL(0) = 0x0320;
			/* Set the baud rate divisor. */
			LM4_UART_IBRD(0) = uartibrd;
			LM4_UART_FBRD(0) = uartfbrd;
			/* Poke UARTLCRH to make the new divisor take effect. */
			LM4_UART_LCRH(0) = LM4_UART_LCRH(0);
			/* Enable the port. */
			LM4_UART_CTL(0) |= 0x0001;
		}
		asm volatile("cpsie i");
	}

	if (uartfbrd) {
		ccprintf("We are still alive. RCC=%08x\n", LM4_SYSTEM_RCC);
		cflush();
	}

	/* Enable interrupts. */
	asm volatile("cpsid i");

	/* gate peripheral clocks */
	if (level & 1) {
		clock_disable_peripheral(CGC_OFFSET_WD,     0xffffffff,
				CGC_MODE_ALL);
		clock_disable_peripheral(CGC_OFFSET_TIMER,  0xffffffff,
				CGC_MODE_ALL);
		clock_disable_peripheral(CGC_OFFSET_GPIO,   0xffffffff,
				CGC_MODE_ALL);
		clock_disable_peripheral(CGC_OFFSET_DMA,    0xffffffff,
				CGC_MODE_ALL);
		clock_disable_peripheral(CGC_OFFSET_HIB,    0xffffffff,
				CGC_MODE_ALL);
		clock_disable_peripheral(CGC_OFFSET_UART,   0xffffffff,
				CGC_MODE_ALL);
		clock_disable_peripheral(CGC_OFFSET_SSI,    0xffffffff,
				CGC_MODE_ALL);
		clock_disable_peripheral(CGC_OFFSET_I2C,    0xffffffff,
				CGC_MODE_ALL);
		clock_disable_peripheral(CGC_OFFSET_ADC,    0xffffffff,
				CGC_MODE_ALL);
		clock_disable_peripheral(CGC_OFFSET_LPC,    0xffffffff,
				CGC_MODE_ALL);
		clock_disable_peripheral(CGC_OFFSET_PECI,   0xffffffff,
				CGC_MODE_ALL);
		clock_disable_peripheral(CGC_OFFSET_FAN,    0xffffffff,
				CGC_MODE_ALL);
		clock_disable_peripheral(CGC_OFFSET_EEPROM, 0xffffffff,
				CGC_MODE_ALL);
		clock_disable_peripheral(CGC_OFFSET_WTIMER, 0xffffffff,
				CGC_MODE_ALL);
	}

	/* Set deep sleep bit. */
	if (level >= 4)
		CPU_SCB_SYSCTRL |= 0x4;

	/* Set SRAM and flash PM for sleep and deep sleep. */
	LM4_SYSTEM_SLPPWRCFG = (flash_pm << 4) | sram_pm;
	LM4_SYSTEM_DSLPPWRCFG = (flash_pm << 4) | sram_pm;

	/* Go to low power mode (forever ...) */
	if (level > 1)
		while (1) {
			asm("wfi");
			watchdog_reload();
		}
	else
		while (1)
			watchdog_reload();

	return EC_SUCCESS;
}
DECLARE_CONSOLE_COMMAND(sleep, command_sleep,
			"[level [clock] [sram pm] [flash pm]]",
			"Drop into sleep");
#endif /* CONFIG_CMD_SLEEP */

#ifdef CONFIG_CMD_PLL

static int command_pll(int argc, char **argv)
{
	int v;

	/* Toggle the PLL */
	if (argc > 1) {
		if (parse_bool(argv[1], &v)) {
			clock_enable_pll(v, 1);
		} else {
			/* Disable PLL and set extra divider */
			char *e;
			v = strtoi(argv[1], &e, 10);
			if (*e)
				return EC_ERROR_PARAM1;

			LM4_SYSTEM_RCC = LM4_SYSTEM_RCC_SYSDIV(v - 1) |
				LM4_SYSTEM_RCC_BYPASS |
				LM4_SYSTEM_RCC_PWRDN |
				LM4_SYSTEM_RCC_OSCSRC(1) |
				LM4_SYSTEM_RCC_MOSCDIS;

			freq = INTERNAL_CLOCK / v;

			/* Notify modules of frequency change */
			hook_notify(HOOK_FREQ_CHANGE);
		}
	}

	/* Print current PLL state */
	ccprintf("RCC:     0x%08x\n", LM4_SYSTEM_RCC);
	ccprintf("RCC2:    0x%08x\n", LM4_SYSTEM_RCC2);
	ccprintf("PLLSTAT: 0x%08x\n", LM4_SYSTEM_PLLSTAT);
	ccprintf("Clock:   %d Hz\n", clock_get_freq());
	return EC_SUCCESS;
}
DECLARE_CONSOLE_COMMAND(pll, command_pll,
			"[ on | off | <div> ]",
			"Get/set PLL state");

#endif /* CONFIG_CMD_PLL */

#ifdef CONFIG_CMD_CLOCKGATES
/**
 * Print all clock gating registers
 */
static int command_clock_gating(int argc, char **argv)
{
	ccprintf("         Run       , Sleep     , Deep Sleep\n");

	ccprintf("WD:      0x%08x, ",
				*(LM4_SYSTEM_RCGC_BASE + CGC_OFFSET_WD));
	ccprintf("0x%08x, ",	*(LM4_SYSTEM_SCGC_BASE + CGC_OFFSET_WD));
	ccprintf("0x%08x\n",	*(LM4_SYSTEM_DCGC_BASE + CGC_OFFSET_WD));

	ccprintf("TIMER:   0x%08x, ",
				*(LM4_SYSTEM_RCGC_BASE + CGC_OFFSET_TIMER));
	ccprintf("0x%08x, ",	*(LM4_SYSTEM_SCGC_BASE + CGC_OFFSET_TIMER));
	ccprintf("0x%08x\n",	*(LM4_SYSTEM_DCGC_BASE + CGC_OFFSET_TIMER));

	ccprintf("GPIO:    0x%08x, ",
				*(LM4_SYSTEM_RCGC_BASE + CGC_OFFSET_GPIO));
	ccprintf("0x%08x, ",	*(LM4_SYSTEM_SCGC_BASE + CGC_OFFSET_GPIO));
	ccprintf("0x%08x\n",	*(LM4_SYSTEM_DCGC_BASE + CGC_OFFSET_GPIO));

	ccprintf("DMA:     0x%08x, ",
				*(LM4_SYSTEM_RCGC_BASE + CGC_OFFSET_DMA));
	ccprintf("0x%08x, ",	*(LM4_SYSTEM_SCGC_BASE + CGC_OFFSET_DMA));
	ccprintf("0x%08x\n",	*(LM4_SYSTEM_DCGC_BASE + CGC_OFFSET_DMA));

	ccprintf("HIB:     0x%08x, ",
				*(LM4_SYSTEM_RCGC_BASE + CGC_OFFSET_HIB));
	ccprintf("0x%08x, ",	*(LM4_SYSTEM_SCGC_BASE + CGC_OFFSET_HIB));
	ccprintf("0x%08x\n",	*(LM4_SYSTEM_DCGC_BASE + CGC_OFFSET_HIB));

	ccprintf("UART:    0x%08x, ",
				*(LM4_SYSTEM_RCGC_BASE + CGC_OFFSET_UART));
	ccprintf("0x%08x, ",	*(LM4_SYSTEM_SCGC_BASE + CGC_OFFSET_UART));
	ccprintf("0x%08x\n",	*(LM4_SYSTEM_DCGC_BASE + CGC_OFFSET_UART));

	ccprintf("SSI:     0x%08x, ",
				*(LM4_SYSTEM_RCGC_BASE + CGC_OFFSET_SSI));
	ccprintf("0x%08x, ",	*(LM4_SYSTEM_SCGC_BASE + CGC_OFFSET_SSI));
	ccprintf("0x%08x\n",	*(LM4_SYSTEM_DCGC_BASE + CGC_OFFSET_SSI));

	ccprintf("I2C:     0x%08x, ",
				*(LM4_SYSTEM_RCGC_BASE + CGC_OFFSET_I2C));
	ccprintf("0x%08x, ",	*(LM4_SYSTEM_SCGC_BASE + CGC_OFFSET_I2C));
	ccprintf("0x%08x\n",	*(LM4_SYSTEM_DCGC_BASE + CGC_OFFSET_I2C));

	ccprintf("ADC:     0x%08x, ",
				*(LM4_SYSTEM_RCGC_BASE + CGC_OFFSET_ADC));
	ccprintf("0x%08x, ",	*(LM4_SYSTEM_SCGC_BASE + CGC_OFFSET_ADC));
	ccprintf("0x%08x\n",	*(LM4_SYSTEM_DCGC_BASE + CGC_OFFSET_ADC));

	ccprintf("LPC:     0x%08x, ",
				*(LM4_SYSTEM_RCGC_BASE + CGC_OFFSET_LPC));
	ccprintf("0x%08x, ",	*(LM4_SYSTEM_SCGC_BASE + CGC_OFFSET_LPC));
	ccprintf("0x%08x\n",	*(LM4_SYSTEM_DCGC_BASE + CGC_OFFSET_LPC));

	ccprintf("PECI:    0x%08x, ",
				*(LM4_SYSTEM_RCGC_BASE + CGC_OFFSET_PECI));
	ccprintf("0x%08x, ",	*(LM4_SYSTEM_SCGC_BASE + CGC_OFFSET_PECI));
	ccprintf("0x%08x\n",	*(LM4_SYSTEM_DCGC_BASE + CGC_OFFSET_PECI));

	ccprintf("FAN:     0x%08x, ",
				*(LM4_SYSTEM_RCGC_BASE + CGC_OFFSET_FAN));
	ccprintf("0x%08x, ",	*(LM4_SYSTEM_SCGC_BASE + CGC_OFFSET_FAN));
	ccprintf("0x%08x\n",	*(LM4_SYSTEM_DCGC_BASE + CGC_OFFSET_FAN));

	ccprintf("EEPROM:  0x%08x, ",
				*(LM4_SYSTEM_RCGC_BASE + CGC_OFFSET_EEPROM));
	ccprintf("0x%08x, ",	*(LM4_SYSTEM_SCGC_BASE + CGC_OFFSET_EEPROM));
	ccprintf("0x%08x\n",	*(LM4_SYSTEM_DCGC_BASE + CGC_OFFSET_EEPROM));

	ccprintf("WTIMER:  0x%08x, ",
				*(LM4_SYSTEM_RCGC_BASE + CGC_OFFSET_WTIMER));
	ccprintf("0x%08x, ",	*(LM4_SYSTEM_SCGC_BASE + CGC_OFFSET_WTIMER));
	ccprintf("0x%08x\n",	*(LM4_SYSTEM_DCGC_BASE + CGC_OFFSET_WTIMER));

	return EC_SUCCESS;
}
DECLARE_CONSOLE_COMMAND(clockgates, command_clock_gating,
			"",
			"Get state of the clock gating controls regs");
#endif /* CONFIG_CMD_CLOCKGATES */

#ifdef CONFIG_LOW_POWER_IDLE
/**
 * 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("Time spent in deep-sleep:            %.6llds\n",
			idle_dsleep_time_us);
	ccprintf("Total time on:                       %.6llds\n", ts.val);
	ccprintf("Deep-sleep closest to wake deadline: %dus\n",
			dsleep_recovery_margin_us);

	return EC_SUCCESS;
}
DECLARE_CONSOLE_COMMAND(idlestats, command_idle_stats,
			"",
			"Print last 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 low speed clock or
			 * allow it to use the low speed clock.
			 */
			if (v)
				disable_sleep(SLEEP_MASK_FORCE_NO_LOW_SPEED);
			else
				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);
	ccprintf("DSLPCLKCFG register:      0x%08x\n", LM4_SYSTEM_DSLPCLKCFG);

	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 use low speed clock.\nUse 'off' to "
			"allow deep sleep to auto-select using the low speed "
			"clock.\n"
			"Give a timeout value for the console in use timeout.\n"
			"See also 'sleepmask'.");
#endif /* CONFIG_LOW_POWER_IDLE */
