/*************************************************************************** * * Copyright 2015-2019 BES. * All rights reserved. All unpublished rights reserved. * * No part of this work may be used or reproduced in any form or by any * means, or stored in a database or retrieval system, without prior written * permission of BES. * * Use of this work is governed by a license granted by BES. * This work contains confidential and proprietary information of * BES. which is protected by copyright, trade secret, * trademark and other intellectual property rights. * ****************************************************************************/ #include "hal_sleep.h" #include "hal_cmu.h" #include "hal_location.h" #include "hal_trace.h" #include "hal_timer.h" #include "hal_sysfreq.h" #include "hal_dma.h" #include "hal_norflash.h" #include "hal_gpadc.h" #include "analog.h" #include "pmu.h" #include "cmsis.h" //static uint8_t SRAM_STACK_LOC sleep_stack[128]; static HAL_SLEEP_HOOK_HANDLER sleep_hook_handler[HAL_SLEEP_HOOK_USER_QTY]; static HAL_DEEP_SLEEP_HOOK_HANDLER deep_sleep_hook_handler[HAL_DEEP_SLEEP_HOOK_USER_QTY]; static uint32_t cpu_wake_lock_map; static uint32_t sys_wake_lock_map; static uint32_t chip_wake_lock_map; #ifdef SLEEP_STATS_TRACE static uint32_t stats_trace_interval; static uint32_t stats_trace_time; #endif static uint32_t stats_interval; static uint32_t stats_start_time; static uint32_t light_sleep_time; static uint32_t sys_deep_sleep_time; static uint32_t chip_deep_sleep_time; static bool stats_started; static bool stats_valid; static uint8_t light_sleep_ratio; static uint8_t sys_deep_sleep_ratio; static uint8_t chip_deep_sleep_ratio; void hal_sleep_start_stats(uint32_t stats_interval_ms, uint32_t trace_interval_ms) { uint32_t lock; lock = int_lock(); if (stats_interval_ms) { stats_interval = MS_TO_TICKS(stats_interval_ms); stats_start_time = hal_sys_timer_get(); light_sleep_time = 0; sys_deep_sleep_time = 0; chip_deep_sleep_time = 0; stats_valid = false; stats_started = true; } else { stats_started = false; } int_unlock(lock); #ifdef SLEEP_STATS_TRACE if (stats_interval_ms && trace_interval_ms) { stats_trace_interval = MS_TO_TICKS(trace_interval_ms); } else { stats_trace_interval = 0; } #endif } int hal_sleep_get_stats(struct CPU_USAGE_T *usage) { int ret; uint32_t lock; lock = int_lock(); if (stats_valid) { usage->light_sleep = light_sleep_ratio; usage->sys_deep_sleep = sys_deep_sleep_ratio; usage->chip_deep_sleep = chip_deep_sleep_ratio; usage->busy = 100 - (light_sleep_ratio + sys_deep_sleep_ratio + chip_deep_sleep_ratio); ret = 0; } else { ret = 1; } int_unlock(lock); return ret; } static int hal_sleep_cpu_busy(void) { if (cpu_wake_lock_map || hal_cmu_lpu_busy()) { return 1; } else { return 0; } } static int hal_sleep_sys_busy(void) { if (sys_wake_lock_map || hal_sysfreq_busy() || hal_dma_busy()) { return 1; } else { return 0; } } int hal_sleep_set_sleep_hook(enum HAL_SLEEP_HOOK_USER_T user, HAL_SLEEP_HOOK_HANDLER handler) { if (user >= ARRAY_SIZE(sleep_hook_handler)) { return 1; } sleep_hook_handler[user] = handler; return 0; } static int SRAM_TEXT_LOC hal_sleep_exec_sleep_hook(void) { int i; int ret; for (i = 0; i < ARRAY_SIZE(sleep_hook_handler); i++) { if (sleep_hook_handler[i]) { ret = sleep_hook_handler[i](); if (ret) { return ret; } } } return 0; } int hal_sleep_set_deep_sleep_hook(enum HAL_DEEP_SLEEP_HOOK_USER_T user, HAL_DEEP_SLEEP_HOOK_HANDLER handler) { if (user >= ARRAY_SIZE(deep_sleep_hook_handler)) { return 1; } deep_sleep_hook_handler[user] = handler; return 0; } static int SRAM_TEXT_LOC hal_sleep_exec_deep_sleep_hook(void) { int i; int ret; for (i = 0; i < ARRAY_SIZE(deep_sleep_hook_handler); i++) { if (deep_sleep_hook_handler[i]) { ret = deep_sleep_hook_handler[i](); if (ret) { return ret; } } } return 0; } int SRAM_TEXT_LOC hal_sleep_irq_pending(void) { #if defined(__GIC_PRESENT) && (__GIC_PRESENT) int i; for (i = 0; i < (USER_IRQn_QTY + 31) / 32; i++) { if (GICDistributor->ICPENDR[i] & GICDistributor->ISENABLER[i]) { return 1; } } #else #if 0 int i; for (i = 0; i < (USER_IRQn_QTY + 31) / 32; i++) { if (NVIC->ICPR[i] & NVIC->ISER[i]) { return 1; } } #else // If there is any pending and enabled exception (including sysTick) if (SCB->ICSR & SCB_ICSR_VECTPENDING_Msk) { return 1; } #endif #endif return 0; } int SRAM_TEXT_LOC hal_sleep_specific_irq_pending(const uint32_t *irq, uint32_t cnt) { int i; uint32_t check_cnt; check_cnt = (USER_IRQn_QTY + 31) / 32; if (check_cnt > cnt) { check_cnt = cnt; } #if defined(__GIC_PRESENT) && (__GIC_PRESENT) for (i = 0; i < check_cnt; i++) { if (GICDistributor->ICPENDR[i] & GICDistributor->ISENABLER[i] & irq[i]) { return 1; } } #else for (i = 0; i < check_cnt; i++) { if (NVIC->ICPR[i] & NVIC->ISER[i] & irq[i]) { return 1; } } #endif return 0; } void WEAK bt_drv_sleep(void) { } void WEAK bt_drv_wakeup(void) { } static enum HAL_SLEEP_STATUS_T SRAM_TEXT_LOC hal_sleep_lowpower_mode(void) { enum HAL_SLEEP_STATUS_T ret; enum HAL_CMU_LPU_SLEEP_MODE_T mode; uint32_t time = 0; ret = HAL_SLEEP_STATUS_LIGHT; // Deep sleep hook if (hal_sleep_exec_deep_sleep_hook() || hal_trace_busy()) { return ret; } if (stats_started) { time = hal_sys_timer_get(); } // Modules (except for psram and flash) sleep hal_gpadc_sleep(); if (chip_wake_lock_map == 0) { analog_sleep(); pmu_sleep(); } bt_drv_sleep(); // End of sleep //psram_sleep(); hal_norflash_sleep(HAL_NORFLASH_ID_0); // Now neither psram nor flash are usable if (!hal_sleep_irq_pending()) { if (chip_wake_lock_map) { mode = HAL_CMU_LPU_SLEEP_MODE_SYS; } else { mode = HAL_CMU_LPU_SLEEP_MODE_CHIP; } hal_cmu_lpu_sleep(mode); ret = HAL_SLEEP_STATUS_DEEP; } hal_norflash_wakeup(HAL_NORFLASH_ID_0); //psram_wakeup(); // Now both psram and flash are usable if (chip_wake_lock_map == 0) { pmu_wakeup(); analog_wakeup(); } bt_drv_wakeup(); hal_gpadc_wakeup(); // Modules (except for psram and flash) wakeup // End of wakeup if (stats_started) { time = hal_sys_timer_get() - time; if (chip_wake_lock_map) { sys_deep_sleep_time += time; } else { chip_deep_sleep_time += time; } } return ret; } // GCC has trouble in detecting static function usage in embedded ASM statements. // The following function might be optimized away if there is no explicted call in C codes. // Specifying "used" (or "noclone") attribute on the function can avoid the mistaken optimization. static enum HAL_SLEEP_STATUS_T SRAM_TEXT_LOC NOINLINE USED hal_sleep_proc(int light_sleep) { enum HAL_SLEEP_STATUS_T ret; uint32_t time = 0; uint32_t interval; ret = HAL_SLEEP_STATUS_LIGHT; // Check the sleep conditions in interrupt-locked context if (hal_sleep_cpu_busy()) { // Cannot sleep } else { // Sleep hook if (hal_sleep_exec_sleep_hook()) { goto _exit_sleep; } if (hal_sleep_sys_busy()) { // Light sleep if (stats_started) { time = hal_sys_timer_get(); } #ifdef NO_LIGHT_SLEEP // WFI during USB ISO transfer might generate very weak (0.1 mV) 1K tone interference ??? while (!hal_sleep_irq_pending()); #else #ifndef __ARM_ARCH_ISA_ARM SCB->SCR = 0; #endif __DSB(); __WFI(); #endif if (stats_started) { light_sleep_time += hal_sys_timer_get() - time; } #ifdef DEBUG } else if (hal_trace_busy()) { // Light sleep with trace busy only if (stats_started) { time = hal_sys_timer_get(); } // No irq will be generated when trace becomes idle, so the trace status should // be kept polling actively instead of entering WFI while (!hal_sleep_irq_pending() && hal_trace_busy()); if (stats_started) { light_sleep_time += hal_sys_timer_get() - time; } if (!hal_sleep_irq_pending()) { goto _deep_sleep; } #endif } else { // Deep sleep _deep_sleep: POSSIBLY_UNUSED; if (light_sleep) { ret = HAL_SLEEP_STATUS_DEEP; } else { ret = hal_sleep_lowpower_mode(); } } } _exit_sleep: if (stats_started) { time = hal_sys_timer_get(); interval = time - stats_start_time; if (interval >= stats_interval) { if (light_sleep_time > UINT32_MAX / 100) { light_sleep_ratio = (uint64_t)light_sleep_time * 100 / interval; } else { light_sleep_ratio = light_sleep_time * 100 / interval; } if (sys_deep_sleep_time > UINT32_MAX / 100) { sys_deep_sleep_ratio = (uint64_t)sys_deep_sleep_time * 100 / interval; } else { sys_deep_sleep_ratio = sys_deep_sleep_time * 100 / interval; } if (chip_deep_sleep_time > UINT32_MAX / 100) { chip_deep_sleep_ratio = (uint64_t)chip_deep_sleep_time * 100 / interval; } else { chip_deep_sleep_ratio = chip_deep_sleep_time * 100 / interval; } stats_valid = true; light_sleep_time = 0; sys_deep_sleep_time = 0; chip_deep_sleep_time = 0; stats_start_time = time; } #ifdef SLEEP_STATS_TRACE if (stats_valid && stats_trace_interval) { uint32_t time = hal_sys_timer_get(); if (time - stats_trace_time >= stats_trace_interval) { TRACE(4,"CPU USAGE: busy=%d light=%d sys_deep=%d chip_deep=%d", 100 - (light_sleep_ratio + sys_deep_sleep_ratio + chip_deep_sleep_ratio), light_sleep_ratio, sys_deep_sleep_ratio, chip_deep_sleep_ratio); stats_trace_time = time; #ifdef DEBUG_SLEEP_USER TRACE(4,"SLEEP_USER: cpulock=0x%X syslock=0x%X irq=0x%08X_%08X", cpu_wake_lock_map, sys_wake_lock_map, (NVIC->ICPR[1] & NVIC->ISER[1]), (NVIC->ICPR[0] & NVIC->ISER[0])); hal_sysfreq_print(); #endif } } #endif } return ret; } #ifndef __ARM_ARCH_ISA_ARM static enum HAL_SLEEP_STATUS_T SRAM_TEXT_LOC NOINLINE USED NAKED hal_sleep_deep_sleep_wrapper(void) { asm volatile( "push {r4, lr} \n" // Switch current stack pointer to MSP "mrs r4, control \n" "bic r4, #2 \n" "msr control, r4 \n" "isb \n" "movs r0, #0 \n" "bl hal_sleep_proc \n" // Switch current stack pointer back to PSP "orr r4, #2 \n" "msr control, r4 \n" "isb \n" "pop {r4, pc} \n" ); #ifndef __ARMCC_VERSION return HAL_SLEEP_STATUS_LIGHT; #endif } #endif enum HAL_SLEEP_STATUS_T SRAM_TEXT_LOC hal_sleep_enter_sleep(void) { enum HAL_SLEEP_STATUS_T ret; uint32_t lock; ret = HAL_SLEEP_STATUS_LIGHT; #ifdef NO_SLEEP return ret; #endif lock = int_lock_global(); #ifndef __ARM_ARCH_ISA_ARM if (__get_CONTROL() & 0x02) { ret = hal_sleep_deep_sleep_wrapper(); } else #endif { ret = hal_sleep_proc(false); } int_unlock_global(lock); return ret; } enum HAL_SLEEP_STATUS_T SRAM_TEXT_LOC hal_sleep_light_sleep(void) { enum HAL_SLEEP_STATUS_T ret; uint32_t lock; ret = HAL_SLEEP_STATUS_LIGHT; #ifdef NO_SLEEP return ret; #endif lock = int_lock_global(); ret = hal_sleep_proc(true); int_unlock_global(lock); return ret; } int hal_cpu_wake_lock(enum HAL_CPU_WAKE_LOCK_USER_T user) { uint32_t lock; if (user >= HAL_CPU_WAKE_LOCK_USER_QTY) { return 1; } lock = int_lock(); cpu_wake_lock_map |= (1 << user); int_unlock(lock); return 0; } int hal_cpu_wake_unlock(enum HAL_CPU_WAKE_LOCK_USER_T user) { uint32_t lock; if (user >= HAL_CPU_WAKE_LOCK_USER_QTY) { return 1; } lock = int_lock(); cpu_wake_lock_map &= ~(1 << user); int_unlock(lock); return 0; } int hal_sys_wake_lock(enum HAL_SYS_WAKE_LOCK_USER_T user) { uint32_t lock; if (user >= HAL_SYS_WAKE_LOCK_USER_QTY) { return 1; } lock = int_lock(); sys_wake_lock_map |= (1 << user); int_unlock(lock); return 0; } int hal_sys_wake_unlock(enum HAL_SYS_WAKE_LOCK_USER_T user) { uint32_t lock; if (user >= HAL_SYS_WAKE_LOCK_USER_QTY) { return 1; } lock = int_lock(); sys_wake_lock_map &= ~(1 << user); int_unlock(lock); return 0; } int hal_chip_wake_lock(enum HAL_CHIP_WAKE_LOCK_USER_T user) { uint32_t lock; if (user >= HAL_CHIP_WAKE_LOCK_USER_QTY) { return 1; } lock = int_lock(); chip_wake_lock_map |= (1 << user); int_unlock(lock); return 0; } int hal_chip_wake_unlock(enum HAL_CHIP_WAKE_LOCK_USER_T user) { uint32_t lock; if (user >= HAL_CHIP_WAKE_LOCK_USER_QTY) { return 1; } lock = int_lock(); chip_wake_lock_map &= ~(1 << user); int_unlock(lock); return 0; }