blob: 2f3090b51b30d5128dcdef8469051811f533bbb8 [file] [log] [blame]
/*
* Copyright (c) 2016 Intel Corporation.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include <zephyr.h>
#include <power.h>
#include <soc_power.h>
#include <gpio.h>
#include <misc/printk.h>
#include <string.h>
#include <rtc.h>
#define ALARM (RTC_ALARM_MINUTE / 12)
#define SLEEPTICKS SECONDS(5)
#define GPIO_IN_PIN 16
static void create_device_list(void);
static void suspend_devices(int pm_policy);
static void resume_devices(int pm_policy);
static struct device *device_list;
static int device_count;
/*
* Example ordered list to store devices on which
* device power policies would be executed.
*/
#define DEVICE_POLICY_MAX 15
static char device_policy_list[DEVICE_POLICY_MAX];
static char device_retval[DEVICE_POLICY_MAX];
static struct device *gpio_dev;
static int setup_gpio(void);
static int wait_gpio_low(void);
static struct device *rtc_dev;
static uint32_t start_time, end_time;
static void setup_rtc(void);
static void enable_wake_event(void);
static int test_started;
void main(void)
{
printk("Power Management Demo\n");
if (setup_gpio()) {
printk("Test aborted\n");
return;
}
/*
* This is a safety measure to avoid
* bricking boards if anything went wrong.
* The pause here will allow re-flashing.
*
* Toggle GPIO pin 16 in following sequence:-
* (GPIO Pin 16 is DIO 8 in arduino_101 and
* DIO 4 in quark_se_devboard.)
* 1) Start connected to GND during power on or reset.
* 2) Disconnect from GND and connect to 3.3V
* 3) Disconnect from 3.3V. Test should start now.
*
*/
printk("Toggle gpio pin 16 to start test\n");
if (wait_gpio_low()) {
printk("Test aborted\n");
return;
}
printk("PM test started\n");
setup_rtc();
test_started = 1;
create_device_list();
while (1) {
task_sleep(SLEEPTICKS);
}
}
static int check_pm_policy(int32_t ticks)
{
static int policy;
/*
* Compare time available with wake latencies and select
* appropriate power saving policy
*
* For the demo we will alternate between following states
*
* 0 = no power saving operation
* 1 = low power state
* 2 = device suspend only
* 3 = deep sleep
*
*/
/* Set the max val to 2 if deep sleep is not supported */
policy = (policy > 3 ? 0 : policy);
return policy++;
}
static void low_power_state_exit(void)
{
resume_devices(SYS_PM_LOW_POWER_STATE);
end_time = rtc_read(rtc_dev);
printk("\nLow power state policy exit!\n");
printk("Total Elapsed From Suspend To Resume = %d RTC Cycles\n",
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);
printk("Wake from Deep Sleep!\n");
end_time = rtc_read(rtc_dev);
printk("\nDeep sleep policy exit!\n");
printk("Total Elapsed From Suspend To Resume = %d RTC Cycles\n",
end_time - start_time);
}
static int low_power_state_entry(int32_t ticks)
{
printk("\n\nLow power state policy entry!\n");
/* Turn off peripherals/clocks here */
suspend_devices(SYS_PM_LOW_POWER_STATE);
_sys_soc_set_power_policy(SYS_PM_LOW_POWER_STATE);
_sys_soc_put_low_power_state();
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");
/* Turn off peripherals/clocks here */
suspend_devices(SYS_PM_DEEP_SLEEP);
_sys_soc_set_power_policy(SYS_PM_DEEP_SLEEP);
/*
* Returns 0 when context is saved.
* Returns 1 when context was restored and control was
* transferred to it during DS resume.
*/
if (!_sys_soc_save_cpu_context()) {
_sys_soc_put_deep_sleep();
}
/*
* At this point system has woken up from
* deep sleep.
*/
deep_sleep_exit();
/* Clear current power policy */
_sys_soc_set_power_policy(SYS_PM_NOT_HANDLED);
return SYS_PM_DEEP_SLEEP;
}
int _sys_soc_suspend(int32_t ticks)
{
int pm_state;
int ret = SYS_PM_NOT_HANDLED;
pm_state = check_pm_policy(ticks);
switch (pm_state) {
case 1: /* LPS */
start_time = rtc_read(rtc_dev);
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 */
/*
* if the policy manager chooses to go to deep sleep, we need to
* check if any device is in the middle of a transaction
*/
if (!device_any_busy_check()) {
/* Do deep sleep operations */
start_time = rtc_read(rtc_dev);
enable_wake_event();
ret = deep_sleep_entry(ticks);
if (ret == SYS_PM_DEEP_SLEEP) {
/*
* Do any arch or soc specific post
* operations specific to deep sleep.
*
* This would enable interrupts so
* it should be done right before
* function return
*/
_sys_soc_deep_sleep_post_ops();
}
}
break;
default:
/* No PM operations */
ret = SYS_PM_NOT_HANDLED;
break;
}
return ret;
}
void _sys_soc_resume(void)
{
uint32_t pm_policy;
pm_policy = _sys_soc_get_power_policy();
/* Clear current power policy */
_sys_soc_set_power_policy(SYS_PM_NOT_HANDLED);
switch (pm_policy) {
case SYS_PM_DEEP_SLEEP:
/*
* This should transfer control to the point
* where CPU context was saved. Context was
* saved in _sys_power_save_cpu_context(), which
* was called in deep_sleep_entry();
*
* deep_sleep_exit() will be called at the point
* of resume inside deep_sleep_entry().
*/
_sys_soc_restore_cpu_context();
break;
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;
}
}
static void suspend_devices(int pm_policy)
{
int i;
for (i = device_count - 1; i >= 0; i--) {
int idx = device_policy_list[i];
/* If necessary the policy manager can check if a specific
* device in the policy list is busy as shown below :
* if(device_busy_check(&device_list[idx])) {do something}
*/
device_retval[i] = device_suspend(&device_list[idx], pm_policy);
}
}
static void resume_devices(int pm_policy)
{
int i;
for (i = 0; i < device_count; i++) {
if (!device_retval[i]) {
int idx = device_policy_list[i];
device_resume(&device_list[idx], pm_policy);
}
}
}
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 policy 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);
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_policy_list[0] = i;
} else if (!strcmp(device_list[i].config->name, "ioapic")) {
device_policy_list[1] = i;
} else if (!strcmp(device_list[i].config->name, "UART_0")) {
device_policy_list[2] = i;
} else {
device_policy_list[device_count++] = i;
}
}
}
static int setup_gpio(void)
{
int ret;
gpio_dev = device_get_binding("GPIO_0");
if (!gpio_dev) {
printk("Cannot find %s!\n", "GPIO_0");
return 1;
}
/* Setup GPIO input */
ret = gpio_pin_configure(gpio_dev, GPIO_IN_PIN, (GPIO_DIR_IN));
if (ret) {
printk("Error configuring GPIO!\n");
return ret;
}
return 0;
}
static int wait_gpio_low(void)
{
int ret;
int v;
/* Start with high */
do {
ret = gpio_pin_read(gpio_dev, GPIO_IN_PIN, &v);
if (ret) {
printk("Error reading GPIO!\n");
return ret;
}
} while (!v);
/* Wait till low */
do {
ret = gpio_pin_read(gpio_dev, GPIO_IN_PIN, &v);
if (ret) {
printk("Error reading GPIO!\n");
return ret;
}
} while (v);
return 0;
}
void rtc_interrupt_fn(struct device *rtc_dev)
{
printk("Deep Sleep 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)
{
uint32_t alarm;
alarm = (rtc_read(rtc_dev) + ALARM);
rtc_set_alarm(rtc_dev, alarm);
}