blob: ea36d4b37f7d60118401076a4a09748d5ed9a8f6 [file] [log] [blame]
/*
* 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 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());
}
}