Boot Linux faster!

Check our new training course

Boot Linux faster!

Check our new training course
and Creative Commons CC-BY-SA
lecture and lab materials

Bootlin logo

Elixir Cross Referencer

/*
 * Copyright (c) 2018 Intel Corporation.
 *
 * SPDX-License-Identifier: Apache-2.0
 */

#include "test.h"

/* In Tickless Kernel mode, time is passed in milliseconds instead of ticks */
#define TICKS_TO_SECONDS_MULTIPLIER 1000
#define TIME_UNIT_STRING "milliseconds"

#define MIN_TIME_TO_SUSPEND	((SECONDS_TO_SLEEP * \
				  TICKS_TO_SECONDS_MULTIPLIER) - \
				  (TICKS_TO_SECONDS_MULTIPLIER / 2))

#ifdef CONFIG_TICKLESS_KERNEL
#define SECS_TO_TICKS		1000
#else
#define SECS_TO_TICKS		100
#endif

#define LPS_MAX_THR		(60 * SECS_TO_TICKS)
#define LPS_1_MAX_THR		(120 * SECS_TO_TICKS)

/* Suspend Devices based on Ordered List */
void suspend_devices(void);
/* Resume Devices */
void resume_devices(void);

static int post_ops_done = 1;
static int pm_state;

/* This Function decides which Low Power Mode to Enter based on Idleness.
 * In actual Application this decision can be made using time (ticks)
 * And RTC timer can be programmed to wake the device after time elapsed.
 */
static int pm_policy_get_state(s32_t ticks)
{
	/*
	 * Power Management Policy:
	 * 0 - 60 Sec:  Enter Constant Latency Mode
	 * 61 - 120 Sec: Enter Low Power state
	 * 121 and above : Enter Deep Sleep state
	 */
	if ((ticks != K_FOREVER) && ticks <= LPS_MAX_THR) {
		return SYS_POWER_STATE_CPU_LPS;
	} else if ((ticks > LPS_MAX_THR) &&
			(ticks <= LPS_1_MAX_THR)) {
		return SYS_POWER_STATE_CPU_LPS_1;
	} else {
		return SYS_POWER_STATE_DEEP_SLEEP;
	}
}

static void low_power_state_exit(void)
{
	/* Perform some Application specific task on Low Power Mode Exit */

	/* Turn on suspended peripherals/clocks as necessary */
	printk("-- Low Power State exit !\n");
}

static int low_power_state_entry(void)
{
	if (pm_state == SYS_POWER_STATE_CPU_LPS) {
		printk("--> Entering into Constant Latency State -");
	} else {
		printk("--> Entering into Low Power State -");
	}
	_sys_soc_set_power_state(pm_state);
	return SYS_PM_LOW_POWER_STATE;
}

static void low_power_suspend_exit(void)
{
	/* Turn on peripherals and restore device states as necessary */
	resume_devices();
	printk("-- Low Power Suspend State exit!\n");
}

static int low_power_suspend_entry(void)
{
	printk("--> Entering into Deep Sleep State -");
	/* Don't need pm idle exit event notification */
	_sys_soc_pm_idle_exit_notification_disable();
	/* Save device states and turn off peripherals as necessary */
	suspend_devices();
	_sys_soc_set_power_state(SYS_POWER_STATE_DEEP_SLEEP);
	 /* Exiting from Deep Sleep State */
	low_power_suspend_exit();

	return SYS_PM_DEEP_SLEEP;
}

/* Function name : _sys_soc_suspend
 * Return Value : Power Mode Entered Success/Failure
 * Input : Idleness in number of Ticks
 *
 * Description: All request from Idle thread to Enter into
 * Low Power Mode or Deep Sleep State land in this function
 */
int _sys_soc_suspend(s32_t ticks)
{
	int ret = SYS_PM_NOT_HANDLED;

	post_ops_done = 0;

	if ((ticks != K_FOREVER) && (ticks < MIN_TIME_TO_SUSPEND)) {
		printk("Not enough time for PM operations " TIME_UNIT_STRING
							" : %d).\n", ticks);
		return SYS_PM_NOT_HANDLED;
	}

	pm_state = pm_policy_get_state(ticks);

	switch (pm_state) {
	case SYS_POWER_STATE_CPU_LPS:
	case SYS_POWER_STATE_CPU_LPS_1:
		/* Do CPU LPS operations */
		ret = low_power_state_entry();
		break;
	case SYS_POWER_STATE_DEEP_SLEEP:
		/* Do Low Power Suspend operations */
		ret = low_power_suspend_entry();
		break;
	default:
		/* No PM operations */
		printk("\nNo PM operations done\n");
		ret = SYS_PM_NOT_HANDLED;
		break;
	}

	if (ret != SYS_PM_NOT_HANDLED) {
		/*
		 * Do any arch or soc specific post operations specific to the
		 * power state.
		 */
		if (!post_ops_done) {
			if ((pm_state == SYS_POWER_STATE_CPU_LPS_1) ||
					(pm_state == SYS_POWER_STATE_CPU_LPS)) {
				low_power_state_exit();
			}
			post_ops_done = 1;
			_sys_soc_power_state_post_ops(pm_state);
		}
	}

	return ret;
}

void _sys_soc_resume(void)
{
	/*
	 * This notification is called from the ISR of the event
	 * that caused exit from kernel idling after PM operations.
	 *
	 * Some CPU low power states require enabling of interrupts
	 * atomically when entering those states. The wake up from
	 * such a state first executes code in the ISR of the interrupt
	 * that caused the wake. This hook will be called from the ISR.
	 * For such CPU LPS states, do post operations and restores here.
	 * The kernel scheduler will get control after the ISR finishes
	 * and it may schedule another thread.
	 *
	 * Call _sys_soc_pm_idle_exit_notification_disable() if this
	 * notification is not required.
	 */
	if (!post_ops_done) {
		if (pm_state == SYS_POWER_STATE_CPU_LPS) {
			low_power_state_exit();
		}
		post_ops_done = 1;
		_sys_soc_power_state_post_ops(pm_state);
	}
}