diff --git a/boards/arm/npcx7m6fb_evb/npcx7m6fb_evb.dts b/boards/arm/npcx7m6fb_evb/npcx7m6fb_evb.dts index 2c879ac996e..606861043a8 100644 --- a/boards/arm/npcx7m6fb_evb/npcx7m6fb_evb.dts +++ b/boards/arm/npcx7m6fb_evb/npcx7m6fb_evb.dts @@ -34,6 +34,25 @@ }; }; + power-states { + suspend_to_idle0: suspend_to_idle0 { + compatible = "zephyr,power-state"; + power-state-name = "suspend-to-idle"; + substate-id = <0>; + min-residency-us = <1000>; + }; + + suspend_to_idle1: suspend_to_idle1 { + compatible = "zephyr,power-state"; + power-state-name = "suspend-to-idle"; + substate-id = <1>; + min-residency-us = <201000>; + }; + }; +}; + +&cpu0 { + cpu-power-states = <&suspend_to_idle0 &suspend_to_idle1>; }; /* Overwirte default device properties with overlays in board dt file here. */ diff --git a/boards/arm/npcx7m6fb_evb/npcx7m6fb_evb_defconfig b/boards/arm/npcx7m6fb_evb/npcx7m6fb_evb_defconfig index af2621bc06a..3cc7f5bc2db 100644 --- a/boards/arm/npcx7m6fb_evb/npcx7m6fb_evb_defconfig +++ b/boards/arm/npcx7m6fb_evb/npcx7m6fb_evb_defconfig @@ -51,3 +51,6 @@ CONFIG_I2C=y # Console Driver CONFIG_CONSOLE=y CONFIG_UART_CONSOLE=y + +# Power Management +CONFIG_SOC_POWER_MANAGEMENT=y diff --git a/drivers/clock_control/clock_control_npcx.c b/drivers/clock_control/clock_control_npcx.c index d6c65d22b0b..9b52af4ae8a 100644 --- a/drivers/clock_control/clock_control_npcx.c +++ b/drivers/clock_control/clock_control_npcx.c @@ -100,6 +100,37 @@ static int npcx_clock_control_get_subsys_rate(const struct device *dev, return 0; } +/* Platform specific clock controller functions */ +#if defined(CONFIG_PM) +void npcx_clock_control_turn_on_system_sleep(bool is_deep, bool is_instant) +{ + const struct device *const clk_dev = + device_get_binding(NPCX_CLK_CTRL_NAME); + struct pmc_reg *const inst_pmc = HAL_PMC_INST(clk_dev); + /* Configure that ec enters system sleep mode if receiving 'wfi' */ + uint8_t pm_flags = BIT(NPCX_PMCSR_IDLE); + + /* Add 'Disable High-Frequency' flag (ie. 'deep sleep' mode) */ + if (is_deep) { + pm_flags |= BIT(NPCX_PMCSR_DHF); + /* Add 'Instant Wake-up' flag if sleep time is within 200 ms */ + if (is_instant) + pm_flags |= BIT(NPCX_PMCSR_DI_INSTW); + } + + inst_pmc->PMCSR = pm_flags; +} + +void npcx_clock_control_turn_off_system_sleep(void) +{ + const struct device *const clk_dev = + device_get_binding(NPCX_CLK_CTRL_NAME); + struct pmc_reg *const inst_pmc = HAL_PMC_INST(clk_dev); + + inst_pmc->PMCSR = 0; +} +#endif /* CONFIG_PM */ + /* Clock controller driver registration */ static struct clock_control_driver_api npcx_clock_control_api = { .on = npcx_clock_control_on, diff --git a/drivers/espi/host_subs_npcx.c b/drivers/espi/host_subs_npcx.c index 875e5b32959..d1ad4629e16 100644 --- a/drivers/espi/host_subs_npcx.c +++ b/drivers/espi/host_subs_npcx.c @@ -1054,5 +1054,17 @@ int npcx_host_init_subs_core_domain(const struct device *host_bus_dev, irq_enable(DT_INST_IRQ_BY_NAME(0, p80_fifo, irq)); #endif + if (IS_ENABLED(CONFIG_PM)) { + /* + * Configure the host access wake-up event triggered from a host + * transaction on eSPI/LPC bus. No need for callback function. + */ + npcx_miwu_interrupt_configure(&host_sub_cfg.host_acc_wui, + NPCX_MIWU_MODE_EDGE, NPCX_MIWU_TRIG_HIGH); + + /* Enable irq of interrupt-input module */ + npcx_miwu_irq_enable(&host_sub_cfg.host_acc_wui); + } + return 0; } diff --git a/drivers/serial/uart_npcx.c b/drivers/serial/uart_npcx.c index 76f55fb3d4c..3fd1262051c 100644 --- a/drivers/serial/uart_npcx.c +++ b/drivers/serial/uart_npcx.c @@ -356,7 +356,6 @@ static int uart_npcx_init(const struct device *dev) npcx_miwu_irq_enable(&config->uart_rx_wui); #endif - /* Configure pin-mux for uart device */ npcx_pinctrl_mux_configure(config->alts_list, config->alts_size, 1); diff --git a/drivers/timer/npcx_itim_timer.c b/drivers/timer/npcx_itim_timer.c index 55de0cf42ab..215355c2347 100644 --- a/drivers/timer/npcx_itim_timer.c +++ b/drivers/timer/npcx_itim_timer.c @@ -66,6 +66,10 @@ static struct k_spinlock lock; static uint64_t cyc_sys_announced; /* Current target cycles of time-out signal in event timer */ static uint32_t cyc_evt_timeout; +/* Total cycles of system timer stopped in "sleep/deep sleep" mode */ +__unused static uint64_t cyc_sys_compensated; +/* Current cycles in event timer when ec entered "sleep/deep sleep" mode */ +__unused static uint32_t cyc_evt_enter_deep_idle; /* ITIM local inline functions */ static inline uint64_t npcx_itim_get_sys_cyc64(void) @@ -83,7 +87,12 @@ static inline uint64_t npcx_itim_get_sys_cyc64(void) cnt64l = NPCX_ITIM64_MAX_HALF_CNT - cnt64l + 1; /* Return current value of 64-bit counter value of system timer */ - return (((uint64_t)cnt64h) << 32) | cnt64l; + if (IS_ENABLED(CONFIG_PM)) { + return ((((uint64_t)cnt64h) << 32) | cnt64l) + + cyc_sys_compensated; + } else { + return (((uint64_t)cnt64h) << 32) | cnt64l; + } } static inline void npcx_itim_evt_enable(void) @@ -167,6 +176,41 @@ static void npcx_itim_evt_isr(const struct device *dev) } } +#if defined(CONFIG_PM) +static inline uint32_t npcx_itim_get_evt_cyc32(void) +{ + uint32_t cnt1, cnt2; + + cnt1 = evt_tmr->ITCNT32; + /* + * Wait for two consecutive equal values are read since the source clock + * of event timer is 32KHz. + */ + while ((cnt2 = evt_tmr->ITCNT32) != cnt1) + cnt1 = cnt2; + + /* Return current value of 32-bit counter of event timer */ + return cnt2; +} + +static uint32_t npcx_itim_evt_elapsed_cyc32(void) +{ + uint32_t cnt1 = npcx_itim_get_evt_cyc32(); + uint8_t sys_cts = evt_tmr->ITCTS32; + uint16_t cnt2 = npcx_itim_get_evt_cyc32(); + + /* Event has been triggered but timer ISR doesn't handle it */ + if (IS_BIT_SET(sys_cts, NPCX_ITCTSXX_TO_STS) || (cnt2 > cnt1)) { + cnt2 = cyc_evt_timeout; + } else { + cnt2 = cyc_evt_timeout - cnt2; + } + + /* Return elapsed cycles of 32-bit counter of event timer */ + return cnt2; +} +#endif /* CONFIG_PM */ + /* System timer api functions */ void z_clock_set_timeout(int32_t ticks, bool idle) { @@ -270,3 +314,27 @@ int z_clock_driver_init(const struct device *device) return 0; } + +/* Platform specific systme timer functions */ +#if defined(CONFIG_PM) +void npcx_clock_capture_low_freq_timer(void) +{ + cyc_evt_enter_deep_idle = npcx_itim_evt_elapsed_cyc32(); +} + +void npcx_clock_compensate_system_timer(void) +{ + uint32_t cyc_evt_elapsed_in_deep = npcx_itim_evt_elapsed_cyc32() - + cyc_evt_enter_deep_idle; + + cyc_sys_compensated += ((uint64_t)cyc_evt_elapsed_in_deep * + sys_clock_hw_cycles_per_sec()) / EVT_CYCLES_PER_SEC; +} + +#if defined(CONFIG_SOC_POWER_MANAGEMENT_TRACE) +uint64_t npcx_clock_get_sleep_ticks(void) +{ + return cyc_sys_compensated / SYS_CYCLES_PER_TICK; +} +#endif /* CONFIG_SOC_POWER_MANAGEMENT_TRACE */ +#endif /* CONFIG_PM */ diff --git a/dts/arm/nuvoton/npcx7.dtsi b/dts/arm/nuvoton/npcx7.dtsi index 02c18304486..3c4e32f987d 100644 --- a/dts/arm/nuvoton/npcx7.dtsi +++ b/dts/arm/nuvoton/npcx7.dtsi @@ -28,7 +28,7 @@ #address-cells = <1>; #size-cells = <0>; - cpu@0 { + cpu0: cpu@0 { device_type = "cpu"; compatible = "arm,cortex-m4f"; reg = <0>; diff --git a/soc/arm/nuvoton_npcx/common/soc_clock.h b/soc/arm/nuvoton_npcx/common/soc_clock.h index 07007cc6d68..a13f3cac750 100644 --- a/soc/arm/nuvoton_npcx/common/soc_clock.h +++ b/soc/arm/nuvoton_npcx/common/soc_clock.h @@ -120,6 +120,42 @@ struct npcx_clk_cfg { #error "Unsupported OSC_CLK Frequency" #endif +/** + * @brief Function to notify clock driver that backup the counter value of + * low-frequency timer before ec entered deep idle state. + */ +void npcx_clock_capture_low_freq_timer(void); + +/** + * @brief Function to notify clock driver that compensate the counter value of + * system timer by low-frequency timer after ec left deep idle state. + * + */ +void npcx_clock_compensate_system_timer(void); + +/** + * @brief Function to get time ticks in system sleep/deep sleep state. The unit + * is ticks. + * + */ +uint64_t npcx_clock_get_sleep_ticks(void); + +/** + * @brief Function to configure system sleep settings. After ec received "wfi" + * instruction, ec will enter sleep/deep sleep state for better power + * consumption. + * + * @param is_deep A boolean indicating ec enters deep sleep or sleep state + * @param is_instant A boolean indicating 'Instant Wake-up' from deep idle is + * enabled + */ +void npcx_clock_control_turn_on_system_sleep(bool is_deep, bool is_instant); + +/** + * @brief Function to turn off system sleep mode. + */ +void npcx_clock_control_turn_off_system_sleep(void); + #ifdef __cplusplus } #endif diff --git a/soc/arm/nuvoton_npcx/npcx7/CMakeLists.txt b/soc/arm/nuvoton_npcx/npcx7/CMakeLists.txt index ee55c1c2a5b..314fb67da77 100644 --- a/soc/arm/nuvoton_npcx/npcx7/CMakeLists.txt +++ b/soc/arm/nuvoton_npcx/npcx7/CMakeLists.txt @@ -1,6 +1,7 @@ # SPDX-License-Identifier: Apache-2.0 zephyr_include_directories(${ZEPHYR_BASE}/drivers) +zephyr_library_sources_ifdef(CONFIG_PM power.c) zephyr_sources( soc.c diff --git a/soc/arm/nuvoton_npcx/npcx7/Kconfig.defconfig.series b/soc/arm/nuvoton_npcx/npcx7/Kconfig.defconfig.series index 2c062b3601d..9492c438f64 100644 --- a/soc/arm/nuvoton_npcx/npcx7/Kconfig.defconfig.series +++ b/soc/arm/nuvoton_npcx/npcx7/Kconfig.defconfig.series @@ -88,6 +88,21 @@ config I2C_NPCX both Intel SMBus and Philips I2C physical layer. There are 8 SMBus modules and 10 buses in NPCX7 series. +if SOC_POWER_MANAGEMENT + +config PM + default y if SYS_CLOCK_EXISTS + help + Enable the kernel handles extra power management policies whenever + system enters idle state. + +config PM_DEVICE + default n + help + Disable device power management so far. + +endif # SOC_POWER_MANAGEMENT + source "soc/arm/nuvoton_npcx/npcx7/Kconfig.defconfig.npcx7*" endif # SOC_SERIES_NPCX7 diff --git a/soc/arm/nuvoton_npcx/npcx7/Kconfig.soc b/soc/arm/nuvoton_npcx/npcx7/Kconfig.soc index e3fd81e2d70..2781f5705c4 100644 --- a/soc/arm/nuvoton_npcx/npcx7/Kconfig.soc +++ b/soc/arm/nuvoton_npcx/npcx7/Kconfig.soc @@ -11,3 +11,19 @@ config SOC_NPCX7M6FB bool "NPCX7M6FB" endchoice + +config SOC_POWER_MANAGEMENT + bool "System Power Management in NPCX series" + depends on SOC_SERIES_NPCX7 + help + This option enables the board to implement SoC specific power + management policies whenever the kernel becomes idle. The power + management subsystem will restore to the active state until an + wake-up event is received no matter the system timer is expired or + the other signals occurred such as GPIO, host access, and so on. + +config SOC_POWER_MANAGEMENT_TRACE + bool "Trace System Power Management in NPCX series" + depends on SOC_POWER_MANAGEMENT + help + Internal config to enable runtime power management traces. diff --git a/soc/arm/nuvoton_npcx/npcx7/power.c b/soc/arm/nuvoton_npcx/npcx7/power.c new file mode 100644 index 00000000000..328b6768aeb --- /dev/null +++ b/soc/arm/nuvoton_npcx/npcx7/power.c @@ -0,0 +1,172 @@ +/* + * Copyright (c) 2021 Nuvoton Technology Corporation. + * + * SPDX-License-Identifier: Apache-2.0 + */ + +/** + * @file + * @brief Nuvoton NPCX power management driver + * + * This file contains the drivers of NPCX Power Manager Modules that improves + * the efficiency of ec operation by adjusting the chip’s power consumption to + * the level of activity required by the application. The following table + * summarizes the main properties of the various power states and shows the + * activity levels of the various clocks while in these power states. + * + * +--------------------------------------------------------------------------+ + * | Power State | LFCLK | HFCLK | APB/AHB | Core | RAM/Regs | VCC | VSBY | + * |--------------------------------------------------------------------------| + * | Active | On | On | On | Active | Active | On | On | + * | Idle (wfi) | On | On | On | Wait | Active | On | On | + * | Sleep | On | On | Stop | Stop | Preserved | On | On | + * | Deep Sleep | On | Stop | Stop | Stop | Power Down | On | On | + * | Stand-By | Off | Off | Off | Off | Off | Off | On | + * +--------------------------------------------------------------------------+ + * + * LFCLK - Low-Frequency Clock. Its frequency is fixed to 32kHz. + * HFCLK - High-Frequency (PLL) Clock. Its frequency is configured to OSC_CLK. + * + * Based on the follwoing criteria: + * + * - A delay of 'Instant' wake-up from 'Deep Sleep' is 20 us. + * - A delay of 'Standard' wake-up from 'Deep Sleep' is 3.43 ms. + * - Max residency time in Deep Sleep for 'Instant' wake-up is 200 ms + * - Min Residency time in Deep Sleep for 'Instant' wake-up is 61 us + * - The unit to determine power state residency policy is tick. + * + * this driver implements one power state, PM_STATE_SUSPEND_TO_IDLE, with + * two sub-states for power management system. + * Sub-state 0 - "Deep Sleep" mode with “Instant” wake-up if residency time + * is greater or equal to 1 ms + * Sub-state 1 - "Deep Sleep" mode with "Standard" wake-up if residency time + * is greater or equal to 201 ms + * + * INCLUDE FILES: soc_clock.h + */ + +#include +#include +#include + +#include +LOG_MODULE_DECLARE(soc, CONFIG_SOC_LOG_LEVEL); + +/* The steps that npcx ec enters sleep/deep mode and leaves it. */ +#define NPCX_ENTER_SYSTEM_SLEEP() ({ \ + __asm__ volatile ( \ + "push {r0-r5}\n" /* Save the registers used for delay */ \ + "wfi\n" /* Enter sleep mode after receiving wfi */ \ + "ldm %0, {r0-r5}\n" /* Add a delay before instructions fetching */ \ + "pop {r0-r5}\n" /* Restore the registers used for delay */ \ + "isb\n" /* Flush the cpu pipelines */ \ + :: "r" (CONFIG_SRAM_BASE_ADDRESS)); /* A valid addr used for delay */ \ + }) + +/* Variables for tracing */ +static uint32_t cnt_sleep0; +static uint32_t cnt_sleep1; + +/* Supported sleep mode in npcx series */ +enum { + NPCX_SLEEP, + NPCX_DEEP_SLEEP, +}; + +/* Supported wake-up mode in npcx series */ +enum { + NPCX_INSTANT_WAKE_UP, + NPCX_STANDARD_WAKE_UP, +}; + +static void npcx_power_enter_system_sleep(int slp_mode, int wk_mode) +{ + /* Disable interrupts */ + __disable_irq(); + + /* + * Disable priority mask temporarily to make sure that wake-up events + * are visible to the WFI instruction. + */ + __set_BASEPRI(0); + + /* Configure sleep/deep sleep settings in clock control module. */ + npcx_clock_control_turn_on_system_sleep(slp_mode == NPCX_DEEP_SLEEP, + wk_mode == NPCX_INSTANT_WAKE_UP); + + /* + * Capture the reading of low-freq timer for compensation before ec + * enters system sleep mode. + */ + npcx_clock_capture_low_freq_timer(); + + /* Enter system sleep mode */ + NPCX_ENTER_SYSTEM_SLEEP(); + + /* + * Compensate system timer by the elasped time of low-freq timer during + * system sleep mode. + */ + npcx_clock_compensate_system_timer(); + + /* Turn off system sleep mode. */ + npcx_clock_control_turn_off_system_sleep(); +} + +/* Invoke when enter "Suspend/Low Power" mode. */ +void pm_power_state_set(struct pm_state_info info) +{ + if (info.state != PM_STATE_SUSPEND_TO_IDLE) { + LOG_DBG("Unsupported power state %u", info.state); + } else { + switch (info.substate_id) { + case 0: /* Sub-state 0: Deep sleep with instant wake-up */ + npcx_power_enter_system_sleep(NPCX_DEEP_SLEEP, + NPCX_INSTANT_WAKE_UP); + if (IS_ENABLED(CONFIG_SOC_POWER_MANAGEMENT_TRACE)) { + cnt_sleep0++; + } + break; + case 1: /* Sub-state 1: Deep sleep with standard wake-up */ + npcx_power_enter_system_sleep(NPCX_DEEP_SLEEP, + NPCX_STANDARD_WAKE_UP); + if (IS_ENABLED(CONFIG_SOC_POWER_MANAGEMENT_TRACE)) { + cnt_sleep1++; + } + break; + default: + LOG_DBG("Unsupported power substate-id %u", + info.substate_id); + break; + } + } +} + +/* Handle soc specific activity after exiting "Suspend/Low Power" mode. */ +void pm_power_state_exit_post_ops(struct pm_state_info info) +{ + if (info.state != PM_STATE_SUSPEND_TO_IDLE) { + LOG_DBG("Unsupported power state %u", info.state); + } else { + switch (info.substate_id) { + case 0: /* Sub-state 0: Deep sleep with instant wake-up */ + /* Restore interrupts */ + __enable_irq(); + break; + case 1: /* Sub-state 1: Deep sleep with standard wake-up */ + /* Restore interrupts */ + __enable_irq(); + break; + default: + LOG_DBG("Unsupported power substate-id %u", + info.substate_id); + break; + } + } + + if (IS_ENABLED(CONFIG_SOC_POWER_MANAGEMENT_TRACE)) { + LOG_DBG("sleep: %d, deep sleep: %d", cnt_sleep0, cnt_sleep1); + LOG_INF("total ticks in sleep: %lld", + npcx_clock_get_sleep_ticks()); + } +}