driver: soc: power: npcx: Add power managerment support.
This CL introduces power management driver that improves the efficiency of ec operation by adjusting the chip’s power consumption to the level of activity required by the application in npcx series. The following list summarizes the main properties of the various chip power states. Please refer the power.c file for more detail. Main power states in npcx series include: - Active: Core, RAM and modules operate at the clocks generated by PLL. - Idle: Enter this state when the Core executes WFI or WFE instruction. - Sleep: clock is stopped for most of modules but PLL is enabled. - Deep Sleep: As Sleep mode but PLL is disabled. - Standby: All power rails are turned off besides standby and battery power rails. And this CL implements one power state, PM_STATE_SUSPEND_TO_IDLE, with two sub-states for Zephyr 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 Signed-off-by: Mulin Chao <mlchao@nuvoton.com>
This commit is contained in:
parent
e5caae8e0a
commit
1f731c6c02
12 changed files with 375 additions and 3 deletions
|
@ -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. */
|
||||
|
|
|
@ -51,3 +51,6 @@ CONFIG_I2C=y
|
|||
# Console Driver
|
||||
CONFIG_CONSOLE=y
|
||||
CONFIG_UART_CONSOLE=y
|
||||
|
||||
# Power Management
|
||||
CONFIG_SOC_POWER_MANAGEMENT=y
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
|
|
@ -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 */
|
||||
|
|
|
@ -28,7 +28,7 @@
|
|||
#address-cells = <1>;
|
||||
#size-cells = <0>;
|
||||
|
||||
cpu@0 {
|
||||
cpu0: cpu@0 {
|
||||
device_type = "cpu";
|
||||
compatible = "arm,cortex-m4f";
|
||||
reg = <0>;
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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.
|
||||
|
|
172
soc/arm/nuvoton_npcx/npcx7/power.c
Normal file
172
soc/arm/nuvoton_npcx/npcx7/power.c
Normal file
|
@ -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 <zephyr.h>
|
||||
#include <power/power.h>
|
||||
#include <soc.h>
|
||||
|
||||
#include <logging/log.h>
|
||||
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());
|
||||
}
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue