/* * Kneron Power Manager driver * * Copyright (C) 2019 Kneron, Inc. All rights reserved. * */ #include #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(¤t_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); }