KL520_SDK_2.2/mdw/power/kmdw_power_manager.c
2025-12-17 15:55:25 +08:00

572 lines
16 KiB
C

/*
* Kneron Power Manager driver
*
* Copyright (C) 2019 Kneron, Inc. All rights reserved.
*
*/
#include <string.h>
#include "cmsis_os2.h"
#include "os_tick.h"
#include "RTX_Config.h"
#include "kmdw_power_manager.h"
#include "kdrv_scu.h"
#include "kdrv_power.h"
#include "kdrv_clock.h"
#include "kdrv_system.h"
#include "kmdw_system.h"
#include "rtc.h" // TODO: need to rewrite to kdrv_rtc.h
#include "kdrv_ddr.h"
#include "kmdw_console.h"
#include "kdrv_cmsis_core.h"
#define FLAG_SYSTEM_RESET BIT0
#define FLAG_SYSTEM_NAP BIT1
#define FLAG_SYSTEM_NAP2 BIT2
#define FLAG_SYSTEM_SLEEP BIT3
#define FLAG_SYSTEM_DEEP_SLEEP BIT4
#define FLAG_SYSTEM_TIMER BIT5
#define FLAG_SYSTEM_SHUTDOWN BIT6
#define FLAG_SYSTEM_ERROR BIT8
#define FLAG_SYSTEM_PWRBTN_FALL BIT16
#define FLAG_SYSTEM_PWRBTN_RISE BIT17
#define FLAGS_ALL (FLAG_SYSTEM_RESET | FLAG_SYSTEM_SHUTDOWN \
| FLAG_SYSTEM_NAP | FLAG_SYSTEM_NAP2 \
| FLAG_SYSTEM_SLEEP | FLAG_SYSTEM_DEEP_SLEEP \
| FLAG_SYSTEM_TIMER | FLAG_SYSTEM_ERROR \
| FLAG_SYSTEM_PWRBTN_FALL | FLAG_SYSTEM_PWRBTN_RISE)
#define PERIOD_PRINT (3 * OS_TICK_FREQ) // 3 secs
#define PERIOD_COUNT (PERIOD_PRINT + PERIOD_PRINT/100) // add 1% margin
/* SCU_REG_INT_EN & SCU_REG_INT_STS */
#define SCU_INT_RTC_PERIODIC BIT17
#define SCU_INT_RTC_ALARM BIT16
#define SCU_INT_PLL_UPDATE BIT8
#define SCU_INT_FCS BIT6
#define SCU_INT_BUSSPEED BIT5
#define SCU_INT_WAKEUP BIT3
#define SCU_INT_PWRBTN_RISE BIT1
#define SCU_INT_PWRBTN_FALL BIT0
/* Inactivity timers in seconds */
#define NAP_TIME_1 30
#define NAP_TIME_2 60
osThreadId_t power_tid;
uint32_t cpu_idle_counter = 0;
uint32_t idle_entry_time_in_secs;
uint32_t idle_exit_time_in_secs;
uint32_t sleep_state;
kmdw_power_manager_ptn_handler ptn_cb;
static struct pm_device_func_s {
enum kmdw_power_manager_device_id dev_id;
int inuse;
struct kmdw_power_manager_s pm;
} _pm_dev_fns[KMDW_POWER_MANAGER_DEVICE_MAX];
static void scu_system_isr(void)
{
static int pwr_button_wakeup = 1; // = 1 for cold boot by PWR button
uint32_t status;
status = inw(SCU_REG_INT_STS);
if (status & SCU_INT_RTC_ALARM)
osThreadFlagsSet(power_tid, FLAG_SYSTEM_NAP);
if (status & SCU_INT_RTC_PERIODIC)
osThreadFlagsSet(power_tid, FLAG_SYSTEM_TIMER);
if (status & SCU_INT_PWRBTN_FALL) {
if (pwr_button_wakeup) {
pwr_button_wakeup = 0;
} else {
osThreadFlagsSet(power_tid, FLAG_SYSTEM_PWRBTN_FALL);
}
}
if (status & SCU_INT_PWRBTN_RISE) {
if (sleep_state == 1) {
pwr_button_wakeup = 1;
} else {
pwr_button_wakeup = 0;
osThreadFlagsSet(power_tid, FLAG_SYSTEM_PWRBTN_RISE);
}
}
outw(SCU_REG_INT_STS, status);
NVIC_ClearPendingIRQ(SYS_SYSTEM_IRQ);
}
static void _scu_system_init(void)
{
NVIC_DisableIRQ(SYS_SYSTEM_IRQ);
rtc_init(NULL, NULL);
outw(SCU_REG_INT_STS, 0xffffffff); // Clear all old ones
/* Enable PWR button interrupt and wakeup */
outw(SCU_REG_INT_EN, SCU_INT_PWRBTN_FALL | SCU_INT_PWRBTN_RISE | SCU_INT_WAKEUP);
/* Enable nap alarm interrupt */
uint32_t nap_time = NAP_TIME_1;
rtc_alarm_enable(ALARM_IN_SECS, &nap_time, NULL);
masked_outw(SCU_REG_INT_EN, SCU_INT_RTC_ALARM, SCU_INT_RTC_ALARM);
NVIC_SetVector(SYS_SYSTEM_IRQ, (uint32_t)scu_system_isr);
NVIC_EnableIRQ(SYS_SYSTEM_IRQ);
}
#define MMFAR 0xE000ED34
#define FLAG_WAIT_FOREVER 0x40000000
static void _scpu_wait_reset(void)
{
osThreadId_t calling_tid = osThreadGetId();
if ((calling_tid == 0) || (calling_tid == power_tid)){ // no os or if power mgmnt thread is in trouble
#if 0
kdrv_power_sw_reset();
#else
for (;;);
#endif
}
else // let power mgmnt thread handles the reset
osThreadFlagsWait((u32)calling_tid , FLAG_WAIT_FOREVER, osWaitForever);
}
register unsigned int _msp __asm("msp");
register unsigned int _psp __asm("psp");
register unsigned int _lr __asm("lr");
static unsigned int stack, pc;
static void _scpu_hard_fault(void)
{
if (_lr & 4) {
stack = _psp;
pc = stack + 24;
}
else {
stack = _msp;
pc = stack + 40;
}
err_msg("scpu: hard fault @ %08X, PC = %08X, LR = %08X, SP = %08X\n", *(u32*)MMFAR,
*(u32*)pc, *(u32*)(pc-4), (u32)pc+8);
if (power_tid)
osThreadFlagsSet(power_tid, FLAG_SYSTEM_ERROR);
*(u32*)pc = (u32)&_scpu_wait_reset; // modify stack to go to the wait forever loop
}
static void _scpu_mem_mnmt(void)
{
if (_lr & 4) {
stack = _psp;
pc = stack + 24;
}
else {
stack = _msp;
pc = stack + 40;
}
err_msg("scpu: memory fault @ %08X, PC = %08X, LR = %08X, SP = %08X\n", *(u32*)MMFAR,
*(u32*)pc, *(u32*)(pc-4), (u32)pc+8);
if (power_tid)
osThreadFlagsSet(power_tid, FLAG_SYSTEM_ERROR);
*(u32*)pc = (u32)&_scpu_wait_reset; // modify stack to go to the wait forever loop
}
static void _scpu_bus_fault(void)
{
err_msg("scpu: _scpu_bus_fault !\n");
if (power_tid)
osThreadFlagsSet(power_tid, FLAG_SYSTEM_ERROR);
}
static void _scpu_usage_fault(void)
{
err_msg("scpu: _scpu_usage_fault !\n");
if (power_tid)
osThreadFlagsSet(power_tid, FLAG_SYSTEM_ERROR);
}
void kmdw_power_manager_error_notify(uint32_t code, void *object_id)
{
err_msg("scpu: exception: code=%d, object_id=0x%p\n", code, object_id);
if (power_tid)
osThreadFlagsSet(power_tid, FLAG_SYSTEM_ERROR);
}
void kmdw_power_manager_reset(void)
{
if (power_tid)
osThreadFlagsSet(power_tid, FLAG_SYSTEM_RESET);
}
void kmdw_power_manager_sleep(void)
{
if (power_tid)
osThreadFlagsSet(power_tid, FLAG_SYSTEM_SLEEP);
}
void kmdw_power_manager_deep_sleep(void)
{
if (power_tid)
osThreadFlagsSet(power_tid, FLAG_SYSTEM_DEEP_SLEEP);
}
void kmdw_power_manager_shutdown(void)
{
if (power_tid)
osThreadFlagsSet(power_tid, FLAG_SYSTEM_SHUTDOWN);
}
static void _power_mgr_cpu_usage(void)
{
static uint32_t last_record=0, print_count=0, diff;
diff = (cpu_idle_counter - last_record);
last_record = cpu_idle_counter;
if (diff > PERIOD_COUNT)
diff = PERIOD_COUNT;
info_msg("#%04d cpu loading %d %%\n", ++print_count,
(PERIOD_COUNT - diff) * 100 / PERIOD_COUNT);
}
__NO_RETURN void kmdw_power_manager_cpu_idle(void)
{
uint32_t tick_start, tick_end, tick_idle;
while(1) {
rtc_get_date_time_in_secs(&idle_entry_time_in_secs);
tick_start = osKernelGetTickCount();
__WFI();
tick_end = osKernelGetTickCount();
tick_idle = tick_end - tick_start;
cpu_idle_counter += tick_idle;
rtc_get_date_time_in_secs(&idle_exit_time_in_secs);
}
}
static void _power_manager_do_nap(void)
{
int i;
for (i = KMDW_POWER_MANAGER_DEVICE_MAX - 1; i >= 0; i--) {
if (_pm_dev_fns[i].inuse && _pm_dev_fns[i].pm.nap)
{
if(_pm_dev_fns[i].pm.nap(_pm_dev_fns[i].dev_id) < 0)
{
info_msg("Can't take a nap\n");
return;
}
}
}
dbg_msg("Take a nap\n");
/* Disable npu/ncpu clocks */
kdrv_clock_disable(CLK_NPU);
kdrv_clock_disable(CLK_NCPU);
__WFI();
kdrv_clock_enable(CLK_NCPU);
kdrv_clock_enable(CLK_NPU);
for (i = 0; i < KMDW_POWER_MANAGER_DEVICE_MAX; i++) {
if (_pm_dev_fns[i].inuse && _pm_dev_fns[i].pm.wakeup_nap)
_pm_dev_fns[i].pm.wakeup_nap(_pm_dev_fns[i].dev_id);
}
}
static void _power_manager_do_deep_nap(void)
{
int i;
for (i = KMDW_POWER_MANAGER_DEVICE_MAX - 1; i >= 0; i--) {
if (_pm_dev_fns[i].inuse && _pm_dev_fns[i].pm.deep_nap)
{
if(_pm_dev_fns[i].pm.deep_nap(_pm_dev_fns[i].dev_id) < 0)
{
info_msg("Can't take a deep nap\n");
return;
}
}
}
dbg_msg("Take a deep nap\n");
/* Disable npu/ncpu clocks + DDR self refresh */
kdrv_clock_disable(CLK_NPU);
kdrv_clock_disable(CLK_NCPU);
kdrv_ddr_self_refresh_enter();
__WFI();
kdrv_ddr_self_refresh_exit();
kdrv_clock_enable(CLK_NCPU);
kdrv_clock_enable(CLK_NPU);
for (i = 0; i < KMDW_POWER_MANAGER_DEVICE_MAX; i++) {
if (_pm_dev_fns[i].inuse && _pm_dev_fns[i].pm.wakeup_deep_nap)
_pm_dev_fns[i].pm.wakeup_deep_nap(_pm_dev_fns[i].dev_id);
}
}
static void _power_manager_do_sleep(void)
{
int i;
for (i = KMDW_POWER_MANAGER_DEVICE_MAX - 1; i >= 0; i--) {
if (_pm_dev_fns[i].inuse && _pm_dev_fns[i].pm.sleep)
{
if(_pm_dev_fns[i].pm.sleep(_pm_dev_fns[i].dev_id) < 0)
{
info_msg("Can't sleep\n");
return;
}
}
}
dbg_msg("!!! sleep\n");
sleep_state = 1;
/* Retention: NPU power domain off */
kdrv_clock_disable(CLK_NPU);
kdrv_clock_disable(CLK_NCPU);
kdrv_ddr_self_refresh_enter();
__WFI();
kdrv_ddr_self_refresh_exit();
// kdrv_clock_enable(CLK_NCPU); // these are done in system_wakeup_ncpu();
// kdrv_clock_enable(CLK_NPU);
load_ncpu_fw(0); // reload ncpu fw but don't start it yet
system_wakeup_ncpu(0, 1);
// ddr doesn't seemed correct, reload default models for testing ???
sleep_state = 0;
dbg_msg("!!! sleep -> wakeup\n");
for (i = 0; i < KMDW_POWER_MANAGER_DEVICE_MAX; i++) {
if (_pm_dev_fns[i].inuse && _pm_dev_fns[i].pm.wakeup_sleep)
_pm_dev_fns[i].pm.wakeup_sleep(_pm_dev_fns[i].dev_id);
}
}
static void _power_manager_do_deep_sleep(void)
{
int i;
for (i = KMDW_POWER_MANAGER_DEVICE_MAX - 1; i >= 0; i--) {
if (_pm_dev_fns[i].inuse && _pm_dev_fns[i].pm.deep_sleep)
{
if(_pm_dev_fns[i].pm.deep_sleep(_pm_dev_fns[i].dev_id) < 0)
{
info_msg("Can't deep sleep\n");
return;
}
}
}
dbg_msg("!!! deep sleep\n\n");
/* Deep Retention: NPU+Default domain off */
kdrv_power_softoff(POWER_MODE_DEEP_RETENTION);
__WFI();
err_msg("!!! deep sleep failed!\n");
/* TODO: resume here */
for (i = 0; i < KMDW_POWER_MANAGER_DEVICE_MAX; i++) {
if (_pm_dev_fns[i].inuse && _pm_dev_fns[i].pm.wakeup_deep_sleep)
_pm_dev_fns[i].pm.wakeup_deep_sleep(_pm_dev_fns[i].dev_id);
}
}
static void _power_manager_do_shutdown(void)
{
dbg_msg("!!! shutdown ...\n\n");
/* Disable alarm */
rtc_alarm_disable(ALARM_IN_SECS);
/* Power off everything except RTC */
kdrv_power_softoff(POWER_MODE_RTC);
__WFI();
err_msg("!!! shutdown failed!\n");
for (;;);
}
//#define PRINT_CPU_USAGE
void _kmdw_power_manager_thread(void *arg)
{
uint32_t status, timeout;
uint32_t current_time, elapsed_time, nap_time, pwrbtn_press_time, pwrbtn_release_time;
/* Init system/power/rtc control on SCU */
_scu_system_init();
#ifdef PRINT_CPU_USAGE
timeout = PERIOD_PRINT;
#else
timeout = osWaitForever;
#endif
while(1)
{
status = osThreadFlagsWait(FLAGS_ALL, osFlagsWaitAny, timeout);
if (status == osFlagsErrorTimeout) {
_power_mgr_cpu_usage();
continue;
}
if (status & FLAG_SYSTEM_SLEEP) {
_power_manager_do_sleep();
}
if (status & FLAG_SYSTEM_DEEP_SLEEP) {
_power_manager_do_deep_sleep();
}
if (status & FLAG_SYSTEM_RESET) {
info_msg("!!! reset\r\n");
// will not come back
kdrv_delay_us(50*1000);
kdrv_power_sw_reset();
}
if (status & FLAG_SYSTEM_SHUTDOWN) {
// will not come back
_power_manager_do_shutdown();
}
if (status & FLAG_SYSTEM_NAP) {
if (idle_exit_time_in_secs > idle_entry_time_in_secs)
elapsed_time = idle_exit_time_in_secs - idle_entry_time_in_secs;
else
elapsed_time = 0;
/* update */
rtc_get_date_time_in_secs(&idle_entry_time_in_secs);
/* set next alarm */
if (elapsed_time < NAP_TIME_1) {
nap_time = NAP_TIME_1;
rtc_alarm_enable(ALARM_IN_SECS, &nap_time, NULL);
} else if (elapsed_time < NAP_TIME_2) {
//rtc_current_time_info();
dbg_msg("Idle: %d seconds -> nap\n", elapsed_time);
/* Set longer nap time */
nap_time = NAP_TIME_2;
rtc_alarm_enable(ALARM_IN_SECS, &nap_time, NULL);
/* Take nap */
_power_manager_do_nap();
/* regular nap time upon wakeup */
nap_time = NAP_TIME_1;
rtc_alarm_enable(ALARM_IN_SECS, &nap_time, NULL);
} else {
//rtc_current_time_info();
dbg_msg("Idle: %d seconds -> deep nap\n", elapsed_time);
/* Set even longer nap time */
nap_time = NAP_TIME_2 * 10;
rtc_alarm_enable(ALARM_IN_SECS, &nap_time, NULL);
/* Take deep nap */
_power_manager_do_deep_nap();
/* regular nap time upon wakeup */
nap_time = NAP_TIME_1;
rtc_alarm_enable(ALARM_IN_SECS, &nap_time, NULL);
}
}
if (status & FLAG_SYSTEM_TIMER) {
rtc_current_time_info();
rtc_get_date_time_in_secs(&current_time);
elapsed_time = current_time - idle_entry_time_in_secs;
dbg_msg("Idle: %d\n", elapsed_time);
}
if (status & FLAG_SYSTEM_ERROR) {
err_msg("!!! scpu: error\n");
#if 0
kdrv_power_sw_reset();
#else
// for debug
for (;;)
osDelay(10);
#endif
}
if (status & FLAG_SYSTEM_PWRBTN_FALL) {
rtc_get_date_time_in_secs(&pwrbtn_release_time);
elapsed_time = pwrbtn_release_time - pwrbtn_press_time;
info_msg("!!! PWR Button pressed for %d seconds:\n", elapsed_time);
if (elapsed_time > 6)
_power_manager_do_shutdown();
else if (ptn_cb)
ptn_cb(1);
}
if (status & FLAG_SYSTEM_PWRBTN_RISE) {
rtc_get_date_time_in_secs(&pwrbtn_press_time);
info_msg("!!! PWR Button Press&Hold 7+ seconds for shutdown (RTC mode)\n");
}
}
}
/* Registration APIs */
int kmdw_power_manager_register(enum kmdw_power_manager_device_id dev_id, struct kmdw_power_manager_s *pm_p)
{
int i;
if (dev_id >= KMDW_POWER_MANAGER_DEVICE_MAX || pm_p == NULL)
return -1;
for (i = 0; i < KMDW_POWER_MANAGER_DEVICE_MAX; i++) {
if (_pm_dev_fns[i].inuse == 0) {
memcpy(&_pm_dev_fns[i].pm, pm_p, sizeof(struct kmdw_power_manager_s));
_pm_dev_fns[i].dev_id = dev_id;
_pm_dev_fns[i].inuse = 1;
break;
}
}
return 0;
}
void kmdw_power_manager_unregister(enum kmdw_power_manager_device_id dev_id, struct kmdw_power_manager_s *pm_p)
{
int i;
if (dev_id >= KMDW_POWER_MANAGER_DEVICE_MAX || pm_p == NULL)
return;
for (i = 0; i < KMDW_POWER_MANAGER_DEVICE_MAX; i++) {
if (_pm_dev_fns[i].dev_id == dev_id && _pm_dev_fns[i].pm.sleep == pm_p->sleep && _pm_dev_fns[i].inuse) {
memset(&_pm_dev_fns[i].pm, 0, sizeof(struct kmdw_power_manager_s));
_pm_dev_fns[i].dev_id = KMDW_POWER_MANAGER_DEVICE_NONE;
_pm_dev_fns[i].inuse = 0;
return;
}
}
}
void kmdw_power_manager_power_button_register(kmdw_power_manager_ptn_handler button_handler)
{
ptn_cb = button_handler;
}
void kmdw_power_manager_init(void)
{
osThreadAttr_t attr;
memset(&attr, 0, sizeof(attr));
attr.stack_size = 512;
attr.priority = osPriorityRealtime7;
power_tid = osThreadNew(_kmdw_power_manager_thread, NULL, &attr);
NVIC_SetVector(HardFault_IRQn, (uint32_t)_scpu_hard_fault);
NVIC_SetVector(MemoryManagement_IRQn, (uint32_t)_scpu_mem_mnmt);
NVIC_SetVector(BusFault_IRQn, (uint32_t)_scpu_bus_fault);
NVIC_SetVector(UsageFault_IRQn, (uint32_t)_scpu_usage_fault);
}