zephyr/soc/nuvoton/npcx/common/power.c

258 lines
7.7 KiB
C
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/*
* 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 chips 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 OFMCLK.
*
* Based on the following 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 <cmsis_core.h>
#include <zephyr/kernel.h>
#include <zephyr/drivers/gpio.h>
#include <zephyr/drivers/espi.h>
#include <zephyr/pm/pm.h>
#include <soc.h>
#include "soc_gpio.h"
#include "soc_host.h"
#include "soc_power.h"
#include <zephyr/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,
};
#define NODE_LEAKAGE_IO DT_INST(0, nuvoton_npcx_leakage_io)
#if DT_NODE_HAS_PROP(NODE_LEAKAGE_IO, leak_gpios)
struct npcx_leak_gpio {
const struct device *gpio;
gpio_pin_t pin;
};
#define NPCX_POWER_LEAKAGE_IO_INIT(node_id, prop, idx) { \
.gpio = DEVICE_DT_GET(DT_GPIO_CTLR_BY_IDX(node_id, prop, idx)), \
.pin = DT_GPIO_PIN_BY_IDX(node_id, prop, idx), \
},
/*
* Get io array which have leakage current from 'leak-gpios' property of
* 'power_leakage_io' DT node. User can overwrite this prop. at board DT file to
* save power consumption when ec enter deep sleep.
*
* &power_leakage_io {
* leak-gpios = <&gpio0 0 0
* &gpiob 1 0>;
* };
*/
static struct npcx_leak_gpio leak_gpios[] = {
DT_FOREACH_PROP_ELEM(NODE_LEAKAGE_IO, leak_gpios, NPCX_POWER_LEAKAGE_IO_INIT)
};
static void npcx_power_suspend_leak_io_pads(void)
{
for (int i = 0; i < ARRAY_SIZE(leak_gpios); i++) {
npcx_gpio_disable_io_pads(leak_gpios[i].gpio, leak_gpios[i].pin);
}
}
static void npcx_power_restore_leak_io_pads(void)
{
for (int i = 0; i < ARRAY_SIZE(leak_gpios); i++) {
npcx_gpio_enable_io_pads(leak_gpios[i].gpio, leak_gpios[i].pin);
}
}
#else
void npcx_power_suspend_leak_io_pads(void)
{
/* do nothing */
}
void npcx_power_restore_leak_io_pads(void)
{
/* do nothing */
}
#endif /* DT_NODE_HAS_PROP(NODE_LEAKAGE_IO, leak_gpios) */
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);
/*
* Disable the connection between io pads that have leakage current and
* input buffer to save power consumption.
*/
npcx_power_suspend_leak_io_pads();
/* Turn on eSPI/LPC host access wake-up interrupt. */
if (IS_ENABLED(CONFIG_ESPI_NPCX)) {
npcx_host_enable_access_interrupt();
}
/* Turn on UART RX wake-up interrupt. */
if (IS_ENABLED(CONFIG_UART_NPCX)) {
npcx_uart_enable_access_interrupt();
}
/*
* 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 elapsed time of low-freq timer during
* system sleep mode.
*/
npcx_clock_compensate_system_timer();
/* Turn off eSPI/LPC host access wake-up interrupt. */
if (IS_ENABLED(CONFIG_ESPI_NPCX)) {
npcx_host_disable_access_interrupt();
}
/*
* Restore the connection between io pads that have leakage current and
* input buffer.
*/
npcx_power_restore_leak_io_pads();
/* Turn off system sleep mode. */
npcx_clock_control_turn_off_system_sleep();
}
/* Invoke when enter "Suspend/Low Power" mode. */
void pm_state_set(enum pm_state state, uint8_t substate_id)
{
if (state != PM_STATE_SUSPEND_TO_IDLE) {
LOG_DBG("Unsupported power state %u", state);
} else {
switch (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_NPCX_PM_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_NPCX_PM_TRACE)) {
cnt_sleep1++;
}
break;
default:
LOG_DBG("Unsupported power substate-id %u",
substate_id);
break;
}
}
}
/* Handle soc specific activity after exiting "Suspend/Low Power" mode. */
void pm_state_exit_post_ops(enum pm_state state, uint8_t substate_id)
{
if (state != PM_STATE_SUSPEND_TO_IDLE) {
LOG_DBG("Unsupported power state %u", state);
} else {
switch (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",
substate_id);
break;
}
}
if (IS_ENABLED(CONFIG_NPCX_PM_TRACE)) {
LOG_DBG("sleep: %d, deep sleep: %d", cnt_sleep0, cnt_sleep1);
LOG_INF("total ticks in sleep: %lld",
npcx_clock_get_sleep_ticks());
}
}