diff --git a/arch/x86/soc/intel_quark/quark_se/power.c b/arch/x86/soc/intel_quark/quark_se/power.c index 5c55330f83d..7d2816575b5 100644 --- a/arch/x86/soc/intel_quark/quark_se/power.c +++ b/arch/x86/soc/intel_quark/quark_se/power.c @@ -22,7 +22,6 @@ #define _DEEP_SLEEP_MODE 0xDEEBDEEB #define _LOW_POWER_MODE 0xD02ED02E -#define _DEVICE_SUSPEND_ONLY_MODE 0x1D1E1D1E /* GPS1 is reserved for PM use */ #define _GPS1 0xb0800104 @@ -44,10 +43,7 @@ void _sys_soc_set_power_policy(uint32_t pm_policy) case SYS_PM_LOW_POWER_STATE: sys_write32(_LOW_POWER_MODE, _GPS1); break; - case SYS_PM_DEVICE_SUSPEND_ONLY: - sys_write32(_DEVICE_SUSPEND_ONLY_MODE, _GPS1); - break; - case SYS_PM_NOT_HANDLED: + case SYS_PM_ACTIVE_STATE: sys_write32(0, _GPS1); break; default: @@ -67,10 +63,8 @@ int _sys_soc_get_power_policy(void) return SYS_PM_DEEP_SLEEP; case _LOW_POWER_MODE: return SYS_PM_LOW_POWER_STATE; - case _DEVICE_SUSPEND_ONLY_MODE: - return SYS_PM_DEVICE_SUSPEND_ONLY; } - return SYS_PM_NOT_HANDLED; + return SYS_PM_ACTIVE_STATE; } #endif diff --git a/include/power.h b/include/power.h index e49741c44eb..a02aa9035b4 100644 --- a/include/power.h +++ b/include/power.h @@ -23,11 +23,14 @@ extern "C" { #ifdef CONFIG_SYS_POWER_MANAGEMENT -/* Constants identifying power policies */ -#define SYS_PM_NOT_HANDLED 0 /* No PM operations */ -#define SYS_PM_DEVICE_SUSPEND_ONLY 1 /* Only Devices are suspended */ -#define SYS_PM_LOW_POWER_STATE 2 /* Low Power State */ -#define SYS_PM_DEEP_SLEEP 4 /* Deep Sleep */ +/* Constants identifying power state categories */ +#define SYS_PM_ACTIVE_STATE 0 /* SOC and CPU are in active state */ +#define SYS_PM_LOW_POWER_STATE 1 /* CPU low power state */ +#define SYS_PM_DEEP_SLEEP 2 /* SOC low power state */ + +#define SYS_PM_NOT_HANDLED SYS_PM_ACTIVE_STATE + +extern unsigned char _sys_soc_notify_wake_event; /** * @brief Power Management Hook Interface @@ -38,100 +41,74 @@ extern "C" { */ /** - * @brief Hook function to notify exit of a power policy + * @brief Function to disable wake event notification + * + * _sys_soc_resume() would be called from the ISR that caused exit from + * low power state. This function can be called at _sys_soc_suspend to disable + * this notification. + */ +static inline void _sys_soc_disable_wake_event_notification(void) +{ + _sys_soc_notify_wake_event = 0; +} + +/** + * @brief Hook function to notify exit from low power state * * The purpose of this function is to notify exit from - * deep sleep, low power state or device suspend only policy. - * States altered at _sys_soc_suspend() should be restored in this - * function. Exit from each policy requires different handling as - * follows. + * low power states. The implementation of this function can vary + * depending on the soc specific boot flow. * - * Deep sleep policy exit: - * App should save information in SoC at _sys_soc_suspend() that - * will persist across deep sleep. This function should check - * that information to identify deep sleep recovery. In this case - * this function will restore states and resume execution at the - * point were system entered deep sleep. In this mode, this - * function is called with the interrupt stack. It is important - * that this function, before interrupts are enabled, restores - * the stack that was in use when system went to deep sleep. This - * is to avoid interfering interrupt handlers use of this stack. + * In the case of recovery from soc low power states like deep sleep, + * this function would switch cpu context to the execution point at the time + * system entered the soc low power state. * - * Cold boot and deep sleep recovery happen at the same location. - * Since kernel does not store deep sleep state, kernel will call - * this function in both cases. It is the responsibility of the - * power manager application to identify whether it is cold boot - * or deep sleep exit using state information that it stores. - * If the function detects cold boot, then it returns immediately. + * In boot flows where this function gets called even at cold boot, the + * function should return immediately. * - * Low power state policy exit: - * Low power state policy does a CPU idle wait using a low power - * CPU idle state supported by the processor. This state is exited - * by an interrupt. In this case this function would be called - * from the interrupt's ISR context. Any state altered at - * _sys_soc_suspend should be restored and the function should - * return quickly. + * Wake event notification: + * This function would also be called from the ISR context of the event + * that caused exit from the low power state. This will be called immediately + * after interrupts are enabled. This is called to give a chance to do + * any operations before the kernel would switch tasks or processes nested + * interrupts. This is required for cpu low power states that would require + * interrupts to be enabled while entering low power states. e.g. C1 in x86. In + * those cases, the ISR would be invoked immediately after the event wakes up + * the CPU, before code following the CPU wait, gets a chance to execute. This + * can be ignored if no operation needs to be done at the wake event + * notification. Alternatively _sys_soc_disable_wake_event_notification() can + * be called in _sys_soc_suspend to disable this notification. * - * Device suspend only policy exit: - * This function will be called at the exit of kernel's CPU idle - * wait if device suspend only policy was used. Resume operations - * should be done for devices that were suspended in _sys_soc_suspend(). - * This function is called in ISR context and it should return quickly. - * - * @return will not return to caller in deep sleep recovery + * @note A dedicated function may be created in future to notify wake + * events, instead of overloading this one. */ extern void _sys_soc_resume(void); /** - * @brief Hook function to allow power policy entry + * @brief Hook function to allow entry to low power state * * This function is called by the kernel when it is about to idle. * It is passed the number of clock ticks that the kernel calculated - * as available time to idle. This function should compare this time - * with the wake latency of various power saving schemes that the - * power manager application implements and use the one that fits best. - * The power saving schemes can be mapped to following policies. + * as available time to idle. * - * Deep sleep policy: - * This turns off the core voltage rail and system clock, while RAM is - * retained. This would save most power but would also have a high wake - * latency. CPU loses state so this function should save CPU states in RAM - * and the location in this function where system should resume execution at - * resume. It should re-enable interrupts and return SYS_PM_DEEP_SLEEP. + * The implementation of this function is dependent on the soc specific + * components and the various schemes they support. Some implementations + * may choose to do device PM operations in this function, while others + * would not need to, because they would have done it at other places. * - * Low power state policy: - * Peripherals can be turned off and clocks can be gated depending on - * time available. Then switches to CPU low power state. In this state - * the CPU is still active but in a low power state and does not lose - * any state. This state is exited by an interrupt from where the - * _sys_soc_resume() will be called. To allow interrupts to occur, - * this function should ensure that interrupts are atomically enabled - * before going to the low power CPU idle state. The atomicity of enabling - * interrupts before entering cpu idle wait is essential to avoid a task - * switch away from the kernel idle task before the cpu idle wait is reached. - * This function should return SYS_PM_LOW_POWER_STATE. + * Typically a wake event is set and the soc or cpu is put to any of the + * supported low power states. The wake event should be set to wake up + * the soc or cpu before the available idle time expires to avoid disrupting + * the kernel's scheduling. * - * Device suspend only policy: - * This function can take advantage of the kernel's idling logic - * by turning off peripherals and clocks depending on available time. - * It can return SYS_PM_DEVICE_SUSPEND_ONLY to indicate the kernel should - * do its own CPU idle wait. After the Kernel's idle wait is completed or if - * any interrupt occurs, the _sys_soc_resume() function will be called to - * allow restoring of altered states. Interrupts should not be turned on in - * this case. - * - * If this function decides to not do any operation then it should - * return SYS_PM_NOT_HANDLED to let kernel do its normal idle processing. - * - * This function is entered with interrupts disabled. It should - * re-enable interrupts if it does CPU low power wait or deep sleep. + * This function is entered with interrupts disabled. It should + * re-enable interrupts if it had entered a low power state. * * @param ticks the upcoming kernel idle time * - * @retval SYS_PM_NOT_HANDLED If No PM operations done. - * @retval SYS_PM_DEVICE_SUSPEND_ONLY If only devices were suspended. - * @retval SYS_PM_LOW_POWER_STATE If LPS policy entered. - * @retval SYS_PM_DEEP_SLEEP If Deep Sleep policy entered. + * @retval SYS_PM_NOT_HANDLED If low power state was not entered. + * @retval SYS_PM_LOW_POWER_STATE If CPU low power state was entered. + * @retval SYS_PM_DEEP_SLEEP If SOC low power state was entered. */ extern int _sys_soc_suspend(int32_t ticks); diff --git a/kernel/microkernel/k_idle.c b/kernel/microkernel/k_idle.c index 99bf482097e..830db8c0be9 100644 --- a/kernel/microkernel/k_idle.c +++ b/kernel/microkernel/k_idle.c @@ -243,6 +243,11 @@ unsigned char _sys_power_save_flag = 1; defined(CONFIG_SYS_POWER_DEEP_SLEEP) || \ defined(CONFIG_DEVICE_POWER_MANAGEMENT)) #include +/* + * Used to allow _sys_soc_suspend() implementation to control notification + * of the wake event that caused exit from low power state + */ +unsigned char _sys_soc_notify_wake_event; #endif #if defined(CONFIG_TICKLESS_IDLE) #include @@ -285,29 +290,25 @@ void _sys_power_save_idle(int32_t ticks) #if (defined(CONFIG_SYS_POWER_LOW_POWER_STATE) || \ defined(CONFIG_SYS_POWER_DEEP_SLEEP) || \ defined(CONFIG_DEVICE_POWER_MANAGEMENT)) + + /* This assignment will be controlled by Kconfig flag in future */ + _sys_soc_notify_wake_event = 1; + /* - * Call the suspend hook function, which checks if the system should - * enter deep sleep, low power state or only suspend devices. - * If the time available is too short for any PM operation then - * the function returns SYS_PM_NOT_HANDLED immediately and kernel - * does normal idle processing. Otherwise it will return the code - * corresponding to the action taken. + * Call the suspend hook function of the soc interface to allow + * entry into a low power state. The function returns + * SYS_PM_NOT_HANDLED if low power state was not entered, in which + * case, kernel does normal idle processing. * - * This function can just suspend devices without entering - * deep sleep or cpu low power state. In this case it should return - * SYS_PM_DEVICE_SUSPEND_ONLY and kernel would do normal idle - * processing. - * - * This function is entered with interrupts disabled. If the function - * returns either SYS_PM_LOW_POWER_STATE or SYS_PM_DEEP_SLEEP then - * it should ensure interrupts are re-enabled before returning. - * This is because the kernel does not do its own idle processing in - * these cases i.e. skips nano_cpu_idle(). The kernel's idle - * processing re-enables interrupts which is essential for kernel's - * scheduling logic. + * This function is entered with interrupts disabled. If a low power + * state was entered, then the hook function should enable inerrupts + * before exiting. This is because the kernel does not do its own idle + * processing in those cases i.e. skips nano_cpu_idle(). The kernel's + * idle processing re-enables interrupts which is essential for + * the kernel's scheduling logic. */ - if (!(_sys_soc_suspend(ticks) & - (SYS_PM_DEEP_SLEEP | SYS_PM_LOW_POWER_STATE))) { + if (_sys_soc_suspend(ticks) == SYS_PM_NOT_HANDLED) { + _sys_soc_notify_wake_event = 0; nano_cpu_idle(); } #else @@ -330,16 +331,15 @@ void _sys_power_save_idle_exit(int32_t ticks) #if (defined(CONFIG_SYS_POWER_LOW_POWER_STATE) || \ defined(CONFIG_SYS_POWER_DEEP_SLEEP) || \ defined(CONFIG_DEVICE_POWER_MANAGEMENT)) - /* Any idle wait based on CPU low power state will be exited by - * interrupt. This function is called within that interrupt's - * ISR context. _sys_soc_resume() needs to be called here - * to handle exit from CPU low power states. This gives an - * opportunity for device states altered in _sys_soc_suspend() - * to be restored before the kernel schedules another thread. - * _sys_soc_resume() is not called from here for deep sleep - * exit. Deep sleep recovery happens at cold boot path. + /* Some CPU low power states require notification at the ISR + * to allow any operations that needs to be done before kernel + * switches task or processes nested interrupts. This can be + * disabled by calling _sys_soc_disable_wake_event_notification(). + * Alternatively it can be simply ignored if not required. */ - _sys_soc_resume(); + if (_sys_soc_notify_wake_event) { + _sys_soc_resume(); + } #endif #ifdef CONFIG_TICKLESS_IDLE if ((ticks == TICKS_UNLIMITED) || ticks >= _sys_idle_threshold_ticks) { diff --git a/samples/power/power_mgr/src/main.c b/samples/power/power_mgr/src/main.c index 100385583b8..eab248eee0b 100644 --- a/samples/power/power_mgr/src/main.c +++ b/samples/power/power_mgr/src/main.c @@ -22,8 +22,9 @@ #include #include -#define ALARM (RTC_ALARM_MINUTE / 12) -#define SLEEPTICKS SECONDS(5) +#define SECONDS_TO_SLEEP 5 +#define ALARM (RTC_ALARM_SECOND * (SECONDS_TO_SLEEP - 1)) +#define SLEEPTICKS SECONDS(SECONDS_TO_SLEEP) #define GPIO_IN_PIN 16 static void create_device_list(void); @@ -103,13 +104,12 @@ static int check_pm_policy(int32_t ticks) * * 0 = no power saving operation * 1 = low power state - * 2 = device suspend only - * 3 = deep sleep + * 2 = deep sleep * */ - /* Set the max val to 2 if deep sleep is not supported */ - policy = (policy > 3 ? 0 : policy); + /* Set the max val to 1 if deep sleep is not supported */ + policy = (policy > 2 ? 0 : policy); return policy++; } @@ -124,16 +124,6 @@ static void low_power_state_exit(void) end_time - start_time); } -static void device_suspend_only_exit(void) -{ - resume_devices(SYS_PM_DEVICE_SUSPEND_ONLY); - - end_time = rtc_read(rtc_dev); - printk("\nDevice suspend only policy exit!\n"); - printk("Total Elapsed From Suspend To Resume = %d RTC Cycles\n", - end_time - start_time); -} - static void deep_sleep_exit(void) { resume_devices(SYS_PM_DEEP_SLEEP); @@ -160,22 +150,13 @@ static int low_power_state_entry(int32_t ticks) return SYS_PM_LOW_POWER_STATE; } -static int device_suspend_only_entry(int32_t ticks) -{ - printk("Device suspend only policy entry!\n"); - - /* Turn off peripherals/clocks here */ - suspend_devices(SYS_PM_DEVICE_SUSPEND_ONLY); - - _sys_soc_set_power_policy(SYS_PM_DEVICE_SUSPEND_ONLY); - - return SYS_PM_DEVICE_SUSPEND_ONLY; -} - static int deep_sleep_entry(int32_t ticks) { printk("\n\nDeep sleep policy entry!\n"); + /* Don't need wake event notification */ + _sys_soc_disable_wake_event_notification(); + /* Turn off peripherals/clocks here */ suspend_devices(SYS_PM_DEEP_SLEEP); @@ -198,7 +179,7 @@ static int deep_sleep_entry(int32_t ticks) deep_sleep_exit(); /* Clear current power policy */ - _sys_soc_set_power_policy(SYS_PM_NOT_HANDLED); + _sys_soc_set_power_policy(SYS_PM_ACTIVE_STATE); return SYS_PM_DEEP_SLEEP; } @@ -211,15 +192,12 @@ int _sys_soc_suspend(int32_t ticks) pm_state = check_pm_policy(ticks); switch (pm_state) { - case 1: /* LPS */ + case 1: /* CPU LPS */ start_time = rtc_read(rtc_dev); + enable_wake_event(); ret = low_power_state_entry(ticks); break; - case 2: /* Device Suspend Only */ - start_time = rtc_read(rtc_dev); - ret = device_suspend_only_entry(ticks); - break; - case 3: /* Deep Sleep */ + case 2: /* Deep Sleep */ /* * if the policy manager chooses to go to deep sleep, we need to * check if any device is in the middle of a transaction @@ -258,7 +236,7 @@ void _sys_soc_resume(void) pm_policy = _sys_soc_get_power_policy(); /* Clear current power policy */ - _sys_soc_set_power_policy(SYS_PM_NOT_HANDLED); + _sys_soc_set_power_policy(SYS_PM_ACTIVE_STATE); switch (pm_policy) { case SYS_PM_DEEP_SLEEP: @@ -276,9 +254,6 @@ void _sys_soc_resume(void) case SYS_PM_LOW_POWER_STATE: low_power_state_exit(); break; - case SYS_PM_DEVICE_SUSPEND_ONLY: - device_suspend_only_exit(); - break; default: /* cold boot */ break; @@ -397,7 +372,7 @@ static int wait_gpio_low(void) void rtc_interrupt_fn(struct device *rtc_dev) { - printk("Deep Sleep wake up event handler\n"); + printk("Wake up event handler\n"); } static void setup_rtc(void) diff --git a/samples/power/quark_se/src/main.c b/samples/power/quark_se/src/main.c index 4700e8b7f3e..542bf824462 100644 --- a/samples/power/quark_se/src/main.c +++ b/samples/power/quark_se/src/main.c @@ -185,6 +185,9 @@ int _sys_soc_suspend(int32_t ticks) */ break; case POWER_STATE_CPU_C2: + /* Don't need wake event notification */ + _sys_soc_disable_wake_event_notification(); + pm_operation = SYS_PM_LOW_POWER_STATE; /* Any interrupt works for taking the core out of plain C2, * but if the ARC core is set to SS2, by going to C2 here @@ -195,6 +198,9 @@ int _sys_soc_suspend(int32_t ticks) power_cpu_c2(); break; case POWER_STATE_CPU_C2LP: + /* Don't need wake event notification */ + _sys_soc_disable_wake_event_notification(); + pm_operation = SYS_PM_LOW_POWER_STATE; /* Local APIC interrupts are not delivered in C2LP state so * we set up the RTC interrupt as 'wake event'. @@ -203,10 +209,16 @@ int _sys_soc_suspend(int32_t ticks) power_cpu_c2lp(); break; case POWER_STATE_SOC_SLEEP: + /* Don't need wake event notification */ + _sys_soc_disable_wake_event_notification(); + pm_operation = SYS_PM_DEEP_SLEEP; do_soc_sleep(0); break; case POWER_STATE_SOC_DEEP_SLEEP: + /* Don't need wake event notification */ + _sys_soc_disable_wake_event_notification(); + pm_operation = SYS_PM_DEEP_SLEEP; do_soc_sleep(1); break;