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) 2016 Intel Corporation.
 *
 * SPDX-License-Identifier: Apache-2.0
 */

#include <zephyr.h>
#include <power.h>
#include <soc_power.h>
#include <misc/printk.h>
#include <string.h>
#include <rtc.h>

#define SECONDS_TO_SLEEP	5
#define ALARM		(RTC_ALARM_SECOND * (SECONDS_TO_SLEEP - 1))
#define GPIO_IN_PIN	16
#define MAX_SYS_PM_STATES	2

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

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

static void create_device_list(void);
static void suspend_devices(void);
static void resume_devices(void);

static struct device *device_list;
static int device_count;
static int test_complete;

static int post_ops_done = 1;

/*
 * Example ordered list to store devices on which
 * device power policies would be executed.
 */
#define DEVICE_POLICY_MAX 15
static char device_ordered_list[DEVICE_POLICY_MAX];
static char device_retval[DEVICE_POLICY_MAX];

static struct device *rtc_dev;
static u32_t start_time, end_time;
static void setup_rtc(void);
static void enable_wake_event(void);

int pm_state;

void main(void)
{
	int i;

	printk("Power Management Demo on %s\n", CONFIG_ARCH);

	setup_rtc();

	create_device_list();

	for (i = 0; i < MAX_SYS_PM_STATES; i++) {
		printk("\nApplication main thread\n");
		k_sleep(SECONDS_TO_SLEEP * 1000);
	}
	test_complete = 1;

	printk("**Power States test complete**\n");
}

static int check_pm_policy(s32_t ticks)
{
	static int policy;
	int power_states[] = {SYS_POWER_STATE_MAX, SYS_POWER_STATE_CPU_LPS,
		SYS_POWER_STATE_DEEP_SLEEP};

	/*
	 * Compare time available with wake latencies and select
	 * appropriate power saving policy
	 *
	 * For the demo we will alternate between following policies
	 *
	 * 0 = no power saving operation
	 * 1 = low power state
	 * 2 = deep sleep
	 *
	 */

#if (defined(CONFIG_SYS_POWER_DEEP_SLEEP))
	policy = (++policy > 2 ? 0 : policy);
	/*
	 * If deep sleep was selected, we need to check if any device is in
	 * the middle of a transaction
	 *
	 * Use device_busy_check() to check specific devices
	 */
	if ((policy == 2) && device_any_busy_check()) {
		/* Devices are busy - do CPU LPS instead */
		policy = 1;
	}
#else
	policy = (++policy > 1 ? 0 : policy);
#endif

	return power_states[policy];
}

static void low_power_state_exit(void)
{
	/* Turn on suspended peripherals/clocks as necessary */

	end_time = rtc_read(rtc_dev);
	printk("\nLow power state exit!\n");
	printk("Total Elapsed From Suspend To Resume = %d RTC Cycles\n",
			end_time - start_time);
}

static void deep_sleep_exit(void)
{
	/* Turn on peripherals and restore device states as necessary */
	resume_devices();

	printk("Wake from Deep Sleep!\n");

	end_time = rtc_read(rtc_dev);
	printk("\nDeep sleep exit!\n");
	printk("Total Elapsed From Suspend To Resume = %d RTC Cycles\n",
			end_time - start_time);
}

static int low_power_state_entry(s32_t ticks)
{
	printk("\nLow power state entry!\n");

	start_time = rtc_read(rtc_dev);

	/* Turn off peripherals/clocks as necessary */

	enable_wake_event();

	_sys_soc_set_power_state(SYS_POWER_STATE_CPU_LPS);

	return SYS_PM_LOW_POWER_STATE;
}

static int deep_sleep_entry(s32_t ticks)
{
	printk("\nDeep sleep entry!\n");

	start_time = rtc_read(rtc_dev);

	/* 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();

	enable_wake_event();

	_sys_soc_set_power_state(SYS_POWER_STATE_DEEP_SLEEP);

	/*
	 * At this point system has woken up from
	 * deep sleep.
	 */

	deep_sleep_exit();

	return SYS_PM_DEEP_SLEEP;
}

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

	/* If test is comepleted then do not enter LPS states */
	if (test_complete) {
		return SYS_PM_NOT_HANDLED;
	}

	pm_state = check_pm_policy(ticks);

	switch (pm_state) {
	case SYS_POWER_STATE_CPU_LPS:
		/* Do CPU LPS operations */
		ret = low_power_state_entry(ticks);
		break;
	case SYS_POWER_STATE_DEEP_SLEEP:
		/* Do deep sleep operations */
		ret = deep_sleep_entry(ticks);
		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 this enables interrupts, then it should be done
		 * right before function return.
		 *
		 * Some CPU power states would require interrupts to be
		 * enabled at the time of entering the low power state.
		 * For such states the post operations need to be done
		 * at _sys_soc_resume. To avoid doing it twice, check a
		 * flag.
		 */
		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);
		}
	}

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

static void suspend_devices(void)
{
	int i;

	for (i = device_count - 1; i >= 0; i--) {
		int idx = device_ordered_list[i];

		/* If necessary  the policy manager can check if a specific
		 * device in the device list is busy as shown below :
		 * if(device_busy_check(&device_list[idx])) {do something}
		 */
		device_retval[i] = device_set_power_state(&device_list[idx],
						DEVICE_PM_SUSPEND_STATE);
	}
}

static void resume_devices(void)
{
	int i;

	for (i = 0; i < device_count; i++) {
		if (!device_retval[i]) {
			int idx = device_ordered_list[i];

			device_set_power_state(&device_list[idx],
						DEVICE_PM_ACTIVE_STATE);
		}
	}
}

static void create_device_list(void)
{
	int count;
	int i;

	/*
	 * Following is an example of how the device list can be used
	 * to suspend devices based on custom policies.
	 *
	 * Create an ordered list of devices that will be suspended.
	 * Ordering should be done based on dependencies. Devices
	 * in the beginning of the list will be resumed first.
	 *
	 * Other devices depend on APICs so ioapic and loapic devices
	 * will be placed first in the device ordered list. Move any
	 * other devices to the beginning as necessary. e.g. uart
	 * is useful to enable early prints.
	 */
	device_list_get(&device_list, &count);

#if (CONFIG_X86)
	device_count = 3; /* Reserve for ioapic, loapic and uart */

	for (i = 0; (i < count) && (device_count < DEVICE_POLICY_MAX); i++) {
		if (!strcmp(device_list[i].config->name, "loapic")) {
			device_ordered_list[0] = i;
		} else if (!strcmp(device_list[i].config->name, "ioapic")) {
			device_ordered_list[1] = i;
		} else if (!strcmp(device_list[i].config->name, "UART_0")) {
			device_ordered_list[2] = i;
		} else {
			device_ordered_list[device_count++] = i;
		}
	}
#elif (CONFIG_ARC)
	device_count = 1; /* Reserve for irq unit */

	for (i = 0; (i < count) && (device_count < DEVICE_POLICY_MAX); i++) {
		if (!strcmp(device_list[i].config->name, "arc_v2_irq_unit")) {
			device_ordered_list[0] = i;
		} else {
			device_ordered_list[device_count++] = i;
		}
	}
#endif
}

static void rtc_interrupt_fn(struct device *rtc_dev)
{
	printk("Wake up event handler\n");
}

static void setup_rtc(void)
{
	struct rtc_config config;

	rtc_dev = device_get_binding("RTC_0");

	config.init_val = 0;
	config.alarm_enable = 0;
	config.alarm_val = ALARM;
	config.cb_fn = rtc_interrupt_fn;

	rtc_enable(rtc_dev);

	rtc_set_config(rtc_dev, &config);

}

static void enable_wake_event(void)
{
	u32_t now = rtc_read(rtc_dev);
	u32_t alarm;

	alarm = (rtc_read(rtc_dev) + ALARM);
	rtc_set_alarm(rtc_dev, alarm);

	/* Wait a few ticks to ensure the alarm value gets loaded */
	while (rtc_read(rtc_dev) < now + 5)
		;
}