258 lines
7.7 KiB
C
258 lines
7.7 KiB
C
/*
|
||
* 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());
|
||
}
|
||
}
|