summaryrefslogtreecommitdiff
path: root/chip/lm4/clock.c
diff options
context:
space:
mode:
Diffstat (limited to 'chip/lm4/clock.c')
-rw-r--r--chip/lm4/clock.c242
1 files changed, 225 insertions, 17 deletions
diff --git a/chip/lm4/clock.c b/chip/lm4/clock.c
index 2d7365f9ab..478cb796eb 100644
--- a/chip/lm4/clock.c
+++ b/chip/lm4/clock.c
@@ -11,6 +11,7 @@
#include "cpu.h"
#include "gpio.h"
#include "hooks.h"
+#include "hwtimer.h"
#include "registers.h"
#include "system.h"
#include "task.h"
@@ -18,8 +19,26 @@
#include "util.h"
#include "watchdog.h"
+/* Console output macros */
+#define CPUTS(outstr) cputs(CC_CLOCK, outstr)
+#define CPRINTF(format, args...) cprintf(CC_CLOCK, format, ## args)
+
#define PLL_CLOCK 66666667 /* System clock = 200MHz PLL/3 = 66.667MHz */
+/*
+ * Length of time for the processor to wake up from deep sleep. Actual
+ * measurement gives anywhere from 75-200us, so this is conservative.
+ */
+#define DEEP_SLEEP_RECOVER_TIME_USEC 300
+
+/* 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;
+#endif
+
static int freq;
/**
@@ -33,6 +52,17 @@ static void disable_pll(void)
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;
@@ -56,6 +86,16 @@ static void enable_pll(void)
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))
@@ -155,6 +195,105 @@ void clock_disable_peripheral(uint32_t offset, uint32_t mask, uint32_t mode)
*(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
+
+/* 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;
+
+ /* 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;
+
+ /*
+ * 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.
+ */
+ CPRINTF("[%T low power idle task started]\n");
+
+ 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 (!sleep_mask && time_for_dsleep) {
+ /* Deep-sleep in STOP mode. */
+ idle_dsleep_cnt++;
+
+ /* 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);
+
+ /* 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);
+
+ /* 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 */
@@ -169,36 +308,56 @@ void clock_disable_peripheral(uint32_t offset, uint32_t mask, uint32_t 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) {
+ if (argc >= 2)
level = strtoi(argv[1], NULL, 10);
- }
- if (argc >= 3) {
+ 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 */
+ /* Remove LED current sink. */
gpio_set_level(GPIO_DEBUG_LED, 0);
#endif
- ccprintf("Going to sleep : level %d clock %d...\n", level, clock);
+ ccprintf("Sleep : level %d, clock %d, sram pm %d, flash_pm %d...\n",
+ level, clock, sram_pm, flash_pm);
cflush();
- /* clock setting */
+ /* 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 */
+ /* Disable interrupts. */
asm volatile("cpsid i");
switch (clock) {
@@ -219,18 +378,20 @@ static int command_sleep(int argc, char **argv)
break;
}
- /* TODO: move this to the UART module; ugly to have
- UARTisms here. Also note this only fixes UART0,
- not UART1. */
+ /*
+ * TODO: move this to the UART module; ugly to have
+ * UARTisms here. Also note this only fixes UART0,
+ * not UART1.
+ */
if (uartfbrd) {
- /* Disable the port via UARTCTL and add HSE */
+ /* Disable the port via UARTCTL and add HSE. */
LM4_UART_CTL(0) = 0x0320;
- /* Set the baud rate divisor */
+ /* 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 */
+ /* Enable the port. */
LM4_UART_CTL(0) |= 0x0001;
}
asm volatile("cpsie i");
@@ -241,27 +402,50 @@ static int command_sleep(int argc, char **argv)
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 */
+
+ /* Set deep sleep bit. */
if (level >= 4)
CPU_SCB_SYSCTRL |= 0x4;
- /* go to low power mode (forever ...) */
+
+ /* 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");
@@ -274,7 +458,7 @@ static int command_sleep(int argc, char **argv)
return EC_SUCCESS;
}
DECLARE_CONSOLE_COMMAND(sleep, command_sleep,
- "[level [clock]]",
+ "[level [clock] [sram pm] [flash pm]]",
"Drop into sleep",
NULL);
#endif /* CONFIG_CMD_SLEEP */
@@ -409,3 +593,27 @@ DECLARE_CONSOLE_COMMAND(clockgates, command_clock_gating,
NULL);
#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: %.6lds\n",
+ idle_dsleep_time_us);
+ ccprintf("Total time on: %.6lds\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",
+ NULL);
+#endif /* CONFIG_LOW_POWER_IDLE */
+