blob: 1b0cc96600c8a9db0e7d9823c005ed3d3dd58167 [file] [log] [blame]
/*
* Copyright (c) 2019 Microchip Technology Inc.
* Copyright (c) 2016 Intel Corporation.
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <zephyr/zephyr.h>
#include <zephyr/sys/sys_io.h>
#include <zephyr/sys/__assert.h>
#include <zephyr/pm/pm.h>
#include <soc.h>
#include <zephyr/arch/cpu.h>
#include <zephyr/arch/arm/aarch32/cortex_m/cmsis.h>
#ifndef CONFIG_PM_DEVICE
#include "device_power.h"
#endif
#include "soc_power_debug.h"
#define HTMR_0_XEC_REG_BASE \
((struct htmr_regs *)(DT_REG_ADDR(DT_NODELABEL(hibtimer0))))
#define PCR_XEC_REG_BASE \
((struct pcr_regs *)(DT_REG_ADDR(DT_NODELABEL(pcr))))
/*
* Deep Sleep
* Pros:
* Lower power dissipation, 48MHz PLL is off
* Cons:
* Longer wake latency. CPU start running on ring oscillator
* between 16 to 25 MHz. Minimum 3ms until PLL reaches lock
* frequency of 48MHz.
*
* Implementation Notes:
* We touch the Cortex-M's primary mask and base priority registers
* because we do not want to enter an ISR immediately upon wake.
* We must restore any hardware state that was modified upon sleep
* entry before allowing interrupts to be serviced. Zephyr arch level
* does not provide API's to manipulate both primary mask and base priority.
*
* DEBUG NOTES:
* If a JTAG/SWD debug probe is connected driving TRST# high and
* possibly polling the DUT then MEC1501 will not shut off its 48MHz
* PLL. Firmware should not disable JTAG/SWD in the EC subsystem
* while a probe is using the interface. This can leave the JTAG/SWD
* TAP controller in a state of requesting clocks preventing the PLL
* from being shut off.
*/
/* NOTE: Zephyr masks interrupts using BASEPRI before calling PM subsystem */
static void z_power_soc_deep_sleep(void)
{
struct pcr_regs *pcr = PCR_XEC_REG_BASE;
struct htmr_regs *htmr0 = HTMR_0_XEC_REG_BASE;
uint32_t basepri = 0U;
uint32_t temp = 0U;
PM_DP_ENTER();
__disable_irq();
basepri = __get_BASEPRI();
#ifndef CONFIG_PM_DEVICE
soc_deep_sleep_periph_save();
soc_deep_sleep_wake_en();
soc_deep_sleep_non_wake_en();
#endif
/*
* Enable deep sleep mode in CM4 and MEC172x.
* Enable CM4 deep sleep and sleep signals assertion on WFI.
* Set MCHP Heavy sleep (PLL OFF when all CLK_REQ clear) and SLEEP_ALL
* to assert SLP_EN to all peripherals on WFI.
* Set PRIMASK = 1 so on wake the CPU will not vector to any ISR.
* Set BASEPRI = 0 to allow any priority to wake.
*/
SCB->SCR |= BIT(2);
pcr->SYS_SLP_CTRL = MCHP_PCR_SYS_SLP_HEAVY;
pcr->OSC_ID = pcr->SYS_SLP_CTRL;
#ifdef DEBUG_DEEP_SLEEP_CLK_REQ
soc_debug_sleep_clk_req();
#endif
__set_BASEPRI(0);
__WFI(); /* triggers sleep hardware */
__NOP();
__NOP();
/*
* Clear SLEEP_ALL manually since we are not vectoring to an ISR until
* PM post ops. This de-asserts peripheral SLP_EN signals.
*/
pcr->SYS_SLP_CTRL = 0U;
SCB->SCR &= ~BIT(2);
/* Wait for PLL to lock with timeout */
htmr0->PRLD = 0U; /* make sure its stopped */
htmr0->CTRL = 0U; /* 30.5 us per tick */
htmr0->PRLD = 216U; /* ~6.6 ms 2x the expected lock time */
temp = htmr0->PRLD;
while ((pcr->OSC_ID & MCHP_PCR_OSC_ID_PLL_LOCK) == 0) {
temp = htmr0->PRLD;
if (!temp) {
break;
}
}
htmr0->PRLD = 0U; /* stop */
__set_BASEPRI(basepri);
#ifndef CONFIG_PM_DEVICE
soc_deep_sleep_non_wake_dis();
soc_deep_sleep_wake_dis();
soc_deep_sleep_periph_restore();
#endif
PM_DP_EXIT();
}
/*
* Light Sleep
* Pros:
* Fast wake response:
* Cons:
* Higher power dissipation, 48MHz PLL remains on.
*
* When the kernel calls this it has masked interrupt by setting NVIC BASEPRI
* equal to a value equal to the highest Zephyr ISR priority. Only NVIC
* exceptions will be served.
*/
static void z_power_soc_sleep(void)
{
struct pcr_regs *pcr = PCR_XEC_REG_BASE;
__disable_irq();
SCB->SCR &= ~BIT(2);
pcr->SYS_SLP_CTRL = MCHP_PCR_SYS_SLP_LIGHT;
pcr->OSC_ID = pcr->SYS_SLP_CTRL;
__set_BASEPRI(0);
__WFI(); /* triggers sleep hardware */
__NOP();
__NOP();
pcr->SYS_SLP_CTRL = 0U;
}
/*
* Called from pm_system_suspend(int32_t ticks) in subsys/power.c
* For deep sleep pm_system_suspend has executed all the driver
* power management call backs.
*/
__weak void pm_state_set(enum pm_state state, uint8_t substate_id)
{
ARG_UNUSED(substate_id);
switch (state) {
case PM_STATE_SUSPEND_TO_IDLE:
z_power_soc_sleep();
break;
case PM_STATE_SUSPEND_TO_RAM:
z_power_soc_deep_sleep();
break;
default:
break;
}
}
/*
* Zephyr PM code expects us to enabled interrupt at post op exit. Zephyr used
* arch_irq_lock() which sets BASEPRI to a non-zero value masking all interrupts
* preventing wake. MCHP z_power_soc_(deep)_sleep sets PRIMASK=1 and BASEPRI=0
* allowing wake from any enabled interrupt and prevent CPU from entering any
* ISR on wake except for faults. We re-enable interrupt by setting PRIMASK to 0.
* Side-effect is we set BASEPRI=0. Is this the same value as Zephyr uses during
* NVIC initialization?
*/
__weak void pm_state_exit_post_ops(enum pm_state state, uint8_t substate_id)
{
switch (state) {
case PM_STATE_SUSPEND_TO_IDLE:
case PM_STATE_SUSPEND_TO_RAM:
__enable_irq();
break;
default:
irq_unlock(0); /* this writes CM4 BASEPRI=0 */
break;
}
}