diff --git a/boards/arm/mec15xxevb_assy6853/Kconfig.defconfig b/boards/arm/mec15xxevb_assy6853/Kconfig.defconfig index 5fc7ac3ff19..49e1bb51d13 100644 --- a/boards/arm/mec15xxevb_assy6853/Kconfig.defconfig +++ b/boards/arm/mec15xxevb_assy6853/Kconfig.defconfig @@ -134,4 +134,15 @@ endif # SPI_XEC_QMSPI endif # SPI +# power management stuff +if SYS_POWER_MANAGEMENT + +config SYS_PM_MIN_RESIDENCY_SLEEP_1 + default 1000 + +config SYS_PM_MIN_RESIDENCY_DEEP_SLEEP_1 + default 2000 + +endif # SYS_POWER_MANAGEMENT + endif # BOARD_MEC15XXEVB_ASSY6853 diff --git a/boards/arm/mec15xxevb_assy6853/mec15xxevb_assy6853_defconfig b/boards/arm/mec15xxevb_assy6853/mec15xxevb_assy6853_defconfig index 37b4b49c130..7bfa173ec02 100644 --- a/boards/arm/mec15xxevb_assy6853/mec15xxevb_assy6853_defconfig +++ b/boards/arm/mec15xxevb_assy6853/mec15xxevb_assy6853_defconfig @@ -24,3 +24,6 @@ CONFIG_I2C_INIT_PRIORITY=60 CONFIG_ESPI=y CONFIG_SPI=y + +# power management stuff +CONFIG_SOC_POWER_MANAGEMENT=y diff --git a/boards/arm/mec15xxevb_assy6853/pinmux.c b/boards/arm/mec15xxevb_assy6853/pinmux.c index 6e84ff2cad0..d028bdb2424 100644 --- a/boards/arm/mec15xxevb_assy6853/pinmux.c +++ b/boards/arm/mec15xxevb_assy6853/pinmux.c @@ -295,6 +295,14 @@ static int board_pinmux_init(struct device *dev) #endif /* DT_INST_0_MICROCHIP_XEC_QMSPI */ #endif /* CONFIG_SPI_XEC_QMSPI */ +#ifdef CONFIG_SYS_PM_DEBUG + /* + * Deep sleep testing: Enable TEST_CLK_OUT on GPIO_060 function 2. + * TEST_CLK_OUT is the PLL 48MHz conditioned output. + */ + pinmux_pin_set(portb, MCHP_GPIO_060, MCHP_GPIO_CTRL_MUX_F2); +#endif + return 0; } diff --git a/soc/arm/microchip_mec/mec1501/CMakeLists.txt b/soc/arm/microchip_mec/mec1501/CMakeLists.txt index 0c23c624954..74c0d9003a8 100644 --- a/soc/arm/microchip_mec/mec1501/CMakeLists.txt +++ b/soc/arm/microchip_mec/mec1501/CMakeLists.txt @@ -7,3 +7,8 @@ zephyr_sources( soc.c ) + +zephyr_sources_ifdef(CONFIG_SYS_POWER_MANAGEMENT + device_power.c + power.c + ) diff --git a/soc/arm/microchip_mec/mec1501/Kconfig.defconfig.mec1501hsz b/soc/arm/microchip_mec/mec1501/Kconfig.defconfig.mec1501hsz index ea23305024b..27370fa4750 100644 --- a/soc/arm/microchip_mec/mec1501/Kconfig.defconfig.mec1501hsz +++ b/soc/arm/microchip_mec/mec1501/Kconfig.defconfig.mec1501hsz @@ -88,4 +88,26 @@ config SPI_XEC_QMSPI endif # SPI +if SOC_POWER_MANAGEMENT + +config SYS_POWER_MANAGEMENT + default y + +config SYS_POWER_SLEEP_STATES + default y + +config HAS_SYS_POWER_STATE_SLEEP_1 + default y + +config SYS_POWER_DEEP_SLEEP_STATES + default y + +config HAS_SYS_POWER_STATE_DEEP_SLEEP_1 + default y + +config DEVICE_POWER_MANAGEMENT + default n + +endif # SOC_POWER_MANAGEMENT + endif # SOC_MEC1501_HSZ diff --git a/soc/arm/microchip_mec/mec1501/Kconfig.soc b/soc/arm/microchip_mec/mec1501/Kconfig.soc index d903abda60a..606a63c6b63 100644 --- a/soc/arm/microchip_mec/mec1501/Kconfig.soc +++ b/soc/arm/microchip_mec/mec1501/Kconfig.soc @@ -16,6 +16,9 @@ endchoice config RTOS_TIMER bool "MEC1501 RTOS timer" +config SOC_POWER_MANAGEMENT + bool "MEC1501 Power Management" + config SOC_MEC1501_PROC_CLK_DIV int "PROC_CLK_DIV" default 1 diff --git a/soc/arm/microchip_mec/mec1501/device_power.c b/soc/arm/microchip_mec/mec1501/device_power.c new file mode 100644 index 00000000000..f1301f93d43 --- /dev/null +++ b/soc/arm/microchip_mec/mec1501/device_power.c @@ -0,0 +1,219 @@ +/* + * Copyright (c) 2019 Microchip Technology Inc. + * Copyright (c) 2016 Intel Corporation. + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include +#include +#include +#include + +/* + * CPU will spin up to DEEP_SLEEP_WAIT_SPIN_CLK_REQ times + * waiting for PCR CLK_REQ bits to clear except for the + * CPU bit itself. This is not necessary as the sleep hardware + * will wait for all CLK_REQ to clear once WFI has executed. + * Once all CLK_REQ signals are clear the hardware will transition + * to the low power state. + */ +/* #define DEEP_SLEEP_WAIT_ON_CLK_REQ_ENABLE */ +#define DEEP_SLEEP_WAIT_SPIN_CLK_REQ 1000 + + +/* + * Some peripherals if enabled always assert their CLK_REQ bits. + * For example, any peripheral with a clock generator such as + * timers, counters, UART, etc. We save the enables for these + * peripherals, disable them, and restore the enabled state upon + * wake. + */ +#define DEEP_SLEEP_PERIPH_SAVE_RESTORE + + +/* + * Light sleep: PLL remains on. Fastest wake latency. + */ +void soc_lite_sleep_enable(void) +{ + SCB->SCR &= ~(1ul << 2); + PCR_REGS->SYS_SLP_CTRL = MCHP_PCR_SYS_SLP_LIGHT; +} + +/* + * Deep sleep: PLL is turned off. Wake is fast. PLL requires + * a minimum of 3ms to lock. During this time the main clock + * will be ramping up from ~16 to 24 MHz. + */ + +#if defined(CONFIG_SYS_POWER_DEEP_SLEEP_STATES) + +void soc_deep_sleep_enable(void) +{ + SCB->SCR = (1ul << 2); /* Cortex-M4 SLEEPDEEP */ + PCR_REGS->SYS_SLP_CTRL = MCHP_PCR_SYS_SLP_HEAVY; +} + +void soc_deep_sleep_disable(void) +{ + SCB->SCR &= ~(1ul << 2); /* disable Cortex-M4 SLEEPDEEP */ +} + + +void soc_deep_sleep_wait_clk_idle(void) +{ +#ifdef DEEP_SLEEP_WAIT_ON_CLK_REQ_ENABLE + u32_t clkreq, cnt; + + cnt = DEEP_SLEEP_WAIT_CLK_REQ; + do { + clkreq = PCR_REGS->CLK_REQ0 | PCR_REGS->CLK_REQ1 + | PCR_REGS->CLK_REQ2 | PCR_REGS->CLK_REQ3 + | PCR_REGS->CLK_REQ4; + } while ((clkreq != (1ul << MCHP_PCR1_CPU_POS)) && (cnt-- != 0)); +#endif +} + + +/* + * Allow peripherals connected to external masters to wake the PLL but not + * the EC. Once the peripheral has serviced the external master the PLL + * will be turned back off. For example, if the eSPI master requests eSPI + * configuration information or state of virtual wires the EC doesn't need + * to be involved. The hardware can power on the PLL long enough to service + * the request and then turn the PLL back off. The SMBus and I2C peripherals + * in slave mode can also make use of this feature. + */ +void soc_deep_sleep_non_wake_en(void) +{ +#ifdef CONFIG_ESPI_XEC + GIRQ22_REGS->SRC = 0xfffffffful; + GIRQ22_REGS->EN_SET = (1ul << 9); +#endif +} + +void soc_deep_sleep_non_wake_dis(void) +{ +#ifdef CONFIG_ESPI_XEC + GIRQ22_REGS->EN_CLR = 0xfffffffful; + GIRQ22_REGS->SRC = 0xfffffffful; +#endif +} + +/* Variables used to save various HW state */ +#ifdef DEEP_SLEEP_PERIPH_SAVE_RESTORE + +static u32_t ecs[1]; + +static void deep_sleep_save_ecs(void) +{ + ecs[0] = ECS_REGS->ETM_CTRL; + ECS_REGS->ETM_CTRL = 0; +} + +struct ds_timer_info { + uintptr_t addr; + u32_t restore_mask; +}; + +const struct ds_timer_info ds_timer_tbl[] = { + { + (uintptr_t)&B16TMR0_REGS->CTRL, 0 + }, + { + (uintptr_t)&B16TMR1_REGS->CTRL, 0 + }, + { + (uintptr_t)&B32TMR0_REGS->CTRL, 0 + }, + { + (uintptr_t)&B32TMR1_REGS->CTRL, 0 + }, + { + (uintptr_t)&CCT_REGS->CTRL, + (MCHP_CCT_CTRL_COMP1_SET | MCHP_CCT_CTRL_COMP0_SET), + }, +}; +#define NUM_DS_TIMER_ENTRIES \ + (sizeof(ds_timer_tbl) / sizeof(struct ds_timer_info)) + + +static u32_t timers[NUM_DS_TIMER_ENTRIES]; +static u8_t uart_activate[3]; + +static void deep_sleep_save_uarts(void) +{ + uart_activate[0] = UART0_REGS->ACTV; + UART0_REGS->ACTV = 0; + uart_activate[1] = UART1_REGS->ACTV; + UART1_REGS->ACTV = 0; + uart_activate[2] = UART2_REGS->ACTV; + UART2_REGS->ACTV = 0; +} + +static void deep_sleep_save_timers(void) +{ + const struct ds_timer_info *p; + u32_t i; + + p = &ds_timer_tbl[0]; + for (i = 0; i < NUM_DS_TIMER_ENTRIES; i++) { + timers[i] = REG32(p->addr); + REG32(p->addr) = 0; + p++; + } +} + +static void deep_sleep_restore_ecs(void) +{ + ECS_REGS->ETM_CTRL = ecs[0]; +} + +static void deep_sleep_restore_uarts(void) +{ + UART0_REGS->ACTV = uart_activate[0]; + UART1_REGS->ACTV = uart_activate[1]; + UART2_REGS->ACTV = uart_activate[2]; +} + +static void deep_sleep_restore_timers(void) +{ + const struct ds_timer_info *p; + u32_t i; + + p = &ds_timer_tbl[0]; + for (i = 0; i < NUM_DS_TIMER_ENTRIES; i++) { + REG32(p->addr) = timers[i] & ~p->restore_mask; + p++; + } +} + +void soc_deep_sleep_periph_save(void) +{ + deep_sleep_save_uarts(); + deep_sleep_save_ecs(); + deep_sleep_save_timers(); +} + +void soc_deep_sleep_periph_restore(void) +{ + deep_sleep_restore_ecs(); + deep_sleep_restore_uarts(); + deep_sleep_restore_timers(); +} + +#else + +void soc_deep_sleep_periph_save(void) +{ +} + +void soc_deep_sleep_periph_restore(void) +{ +} + +#endif /* DEEP_SLEEP_PERIPH_SAVE_RESTORE */ + +#endif /* CONFIG_SYS_POWER_DEEP_SLEEP_STATES */ diff --git a/soc/arm/microchip_mec/mec1501/device_power.h b/soc/arm/microchip_mec/mec1501/device_power.h new file mode 100644 index 00000000000..3755f5df164 --- /dev/null +++ b/soc/arm/microchip_mec/mec1501/device_power.h @@ -0,0 +1,26 @@ +/* + * Copyright (c) 2019 Microchip Technology Inc. + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#ifndef __DEVICE_POWER_H +#define __DEVICE_POWER_H + +#ifndef _ASMLANGUAGE + +void soc_lite_sleep_enable(void); + +#if defined(CONFIG_SYS_POWER_DEEP_SLEEP_STATES) +void soc_deep_sleep_enable(void); +void soc_deep_sleep_disable(void); +void soc_deep_sleep_periph_save(void); +void soc_deep_sleep_periph_restore(void); +void soc_deep_sleep_wait_clk_idle(void); +void soc_deep_sleep_non_wake_en(void); +void soc_deep_sleep_non_wake_dis(void); +#endif + +#endif + +#endif diff --git a/soc/arm/microchip_mec/mec1501/power.c b/soc/arm/microchip_mec/mec1501/power.c new file mode 100644 index 00000000000..bea02124ec8 --- /dev/null +++ b/soc/arm/microchip_mec/mec1501/power.c @@ -0,0 +1,141 @@ +/* + * Copyright (c) 2019 Microchip Technology Inc. + * Copyright (c) 2016 Intel Corporation. + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include +#include +#include +#include +#include "device_power.h" + +#if defined(CONFIG_SYS_POWER_DEEP_SLEEP_STATES) + +/* + * Deep Sleep + * Pros: + * Lower power dissipation, 48MHz PLL is off + * Cons: + * Longer wake latency. CPU start running on ring oscillator + * between 16 to 25 MHz. Minimum 3ms until PLL reaches lock + * frequency of 48MHz. + * + * Implementation Notes: + * We touch the Cortex-M's primary mask and base priority registers + * because we do not want to enter an ISR immediately upon wake. + * We must restore any hardware state that was modified upon sleep + * entry before allowing interrupts to be serviced. Zephyr arch level + * does not provide API's to manipulate both primary mask and base priority. + * + * DEBUG NOTES: + * If a JTAG/SWD debug probe is connected driving TRST# high and + * possibly polling the DUT then MEC1501 will not shut off its 48MHz + * PLL. Firmware should not disable JTAG/SWD in the EC subsystem + * while a probe is using the interface. This can leave the JTAG/SWD + * TAP controller in a state of requesting clocks preventing the PLL + * from being shut off. + */ +static void z_power_soc_deep_sleep(void) +{ + u32_t base_pri; + + /* Mask all exceptions and interrupts except NMI and HardFault */ + __set_PRIMASK(1); + + soc_deep_sleep_periph_save(); + + soc_deep_sleep_enable(); + + soc_deep_sleep_wait_clk_idle(); + soc_deep_sleep_non_wake_en(); + + /* + * Unmask all interrupts in BASEPRI. PRIMASK is used above to + * prevent entering an ISR after unmasking in BASEPRI. + * We clear PRIMASK in exit post ops. + */ + base_pri = __get_BASEPRI(); + __set_BASEPRI(0); + __DSB(); + __WFI(); /* triggers sleep hardware */ + __NOP(); + __NOP(); + + if (base_pri != 0) { + __set_BASEPRI(base_pri); + } + + soc_deep_sleep_disable(); + + soc_deep_sleep_non_wake_dis(); + + soc_deep_sleep_periph_restore(); + +} + +#endif + +/* + * Light Sleep + * Pros: + * Fast wake response: + * Cons: + * Higher power dissipation, 48MHz PLL remains on. + */ +static void z_power_soc_sleep(void) +{ + __set_PRIMASK(1); + + soc_lite_sleep_enable(); + + __set_BASEPRI(0); /* Make sure wake interrupts are not masked! */ + __DSB(); + __WFI(); /* triggers sleep hardware */ + __NOP(); + __NOP(); +} + +/* + * Called from _sys_suspend(s32_t ticks) in subsys/power.c + * For deep sleep _sys_suspend has executed all the driver + * power management call backs. + */ +void sys_set_power_state(enum power_states state) +{ + switch (state) { +#if (defined(CONFIG_SYS_POWER_SLEEP_STATES)) + case SYS_POWER_STATE_SLEEP_1: + z_power_soc_sleep(); + break; +#endif +#if (defined(CONFIG_SYS_POWER_DEEP_SLEEP_STATES)) + case SYS_POWER_STATE_DEEP_SLEEP_1: + z_power_soc_deep_sleep(); + break; +#endif + default: + break; + } +} + +void _sys_pm_power_state_exit_post_ops(enum power_states state) +{ + switch (state) { +#if (defined(CONFIG_SYS_POWER_SLEEP_STATES)) + case SYS_POWER_STATE_SLEEP_1: + __enable_irq(); + break; +#endif +#if (defined(CONFIG_SYS_POWER_DEEP_SLEEP_STATES)) + case SYS_POWER_STATE_DEEP_SLEEP_1: + __enable_irq(); + break; +#endif + default: + break; + } +} +