power_mgmt: Reduce complexity in handling of power hooks

Simplified some documentation, removing inconsistencies and making
it easier to understand by separating PM infrastructure areas and
soc specific components that implement the hooks.

Removed the DEVICE_SUSPEND_ONLY policy as it is redundant and
causes high complexity in the flow. It is also not practical
to use it because it was meant to be used without doing CPU or SOC
low power state operations. This means it would do device PM
operations in the ISR of the system timer used by the scheduler.
This can disrupt the scheduler time.

Added a check of a flag around the notification sent from the ISR
of the wake event and created APIs to set/clear it. This will
allow disabling the notification when not needed from
_sys_soc_suspend().

Jira: ZEP-972
Change-Id: Id7aa7d2683384eabed518d4efac446ecc84c3498
Signed-off-by: Ramesh Thomas <ramesh.thomas@intel.com>
This commit is contained in:
Ramesh Thomas 2016-09-25 19:17:45 -07:00 committed by Anas Nashif
commit c4c0ed414b
5 changed files with 115 additions and 157 deletions

View file

@ -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

View file

@ -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);

View file

@ -243,6 +243,11 @@ unsigned char _sys_power_save_flag = 1;
defined(CONFIG_SYS_POWER_DEEP_SLEEP) || \
defined(CONFIG_DEVICE_POWER_MANAGEMENT))
#include <power.h>
/*
* 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 <drivers/system_timer.h>
@ -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) {

View file

@ -22,8 +22,9 @@
#include <string.h>
#include <rtc.h>
#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)

View file

@ -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;