| /* |
| * Copyright (c) 2016 Intel Corporation. |
| * |
| * SPDX-License-Identifier: Apache-2.0 |
| */ |
| |
| #include <zephyr.h> |
| #include <kernel.h> |
| #include <power.h> |
| #include <misc/printk.h> |
| #include <rtc.h> |
| #include <gpio.h> |
| #include <counter.h> |
| #include <aio_comparator.h> |
| |
| #include <power.h> |
| #include <soc_power.h> |
| #include <string.h> |
| #include "soc_watch_logger.h" |
| |
| static enum power_states states_list[] = { |
| SYS_POWER_STATE_CPU_LPS, |
| SYS_POWER_STATE_CPU_LPS_1, |
| SYS_POWER_STATE_CPU_LPS_2, |
| #if (CONFIG_SYS_POWER_DEEP_SLEEP) |
| SYS_POWER_STATE_DEEP_SLEEP, |
| SYS_POWER_STATE_DEEP_SLEEP_1, |
| #endif |
| }; |
| |
| #define TIMEOUT 5 /* in seconds */ |
| #define MAX_SUSPEND_DEVICE_COUNT 15 |
| #define NB_STATES ARRAY_SIZE(states_list) |
| |
| static struct device *suspend_devices[MAX_SUSPEND_DEVICE_COUNT]; |
| static int suspend_device_count; |
| static unsigned int current_state = NB_STATES - 1; |
| static int post_ops_done = 1; |
| |
| static enum power_states get_next_state(void) |
| { |
| current_state = (current_state + 1) % NB_STATES; |
| return states_list[current_state]; |
| } |
| |
| static const char *state_to_string(int state) |
| { |
| switch (state) { |
| case SYS_POWER_STATE_CPU_LPS: |
| return "SYS_POWER_STATE_CPU_LPS"; |
| case SYS_POWER_STATE_CPU_LPS_1: |
| return "SYS_POWER_STATE_CPU_LPS_1"; |
| case SYS_POWER_STATE_CPU_LPS_2: |
| return "SYS_POWER_STATE_CPU_LPS_2"; |
| case SYS_POWER_STATE_DEEP_SLEEP: |
| return "SYS_POWER_STATE_DEEP_SLEEP"; |
| case SYS_POWER_STATE_DEEP_SLEEP_1: |
| return "SYS_POWER_STATE_DEEP_SLEEP_1"; |
| default: |
| return "Unknown state"; |
| } |
| } |
| |
| #if (CONFIG_RTC) |
| static struct device *rtc_dev; |
| |
| static void setup_rtc(void) |
| { |
| struct rtc_config cfg; |
| |
| /* Configure RTC device. RTC interrupt is used as 'wake event' when we |
| * are in C2LP state. |
| */ |
| cfg.init_val = 0; |
| cfg.alarm_enable = 0; |
| cfg.alarm_val = 0; |
| cfg.cb_fn = NULL; |
| rtc_dev = device_get_binding(CONFIG_RTC_0_NAME); |
| rtc_enable(rtc_dev); |
| rtc_set_config(rtc_dev, &cfg); |
| } |
| |
| static void set_rtc_alarm(void) |
| { |
| u32_t now = rtc_read(rtc_dev); |
| u32_t alarm = now + (RTC_ALARM_SECOND * (TIMEOUT - 1)); |
| |
| rtc_set_alarm(rtc_dev, alarm); |
| |
| /* Wait a few ticks to ensure the 'Counter Match Register' was loaded |
| * with the 'alarm' value. |
| * Refer to the documentation in qm_rtc.h for more details. |
| */ |
| while (rtc_read(rtc_dev) < now + 5) |
| ; |
| } |
| #elif (CONFIG_COUNTER) |
| static struct device *counter_dev; |
| |
| static void setup_counter(void) |
| { |
| volatile u32_t delay = 0; |
| |
| counter_dev = device_get_binding("AON_TIMER"); |
| |
| if (!counter_dev) { |
| printk("Timer device not found\n"); |
| return; |
| } |
| |
| counter_start(counter_dev); |
| |
| /* The AON timer runs from the RTC clock at 32KHz (rather than |
| * the system clock which is 32MHz) so we need to spin for a few cycles |
| * to allow the register change to propagate. |
| */ |
| for (delay = 5000; delay--;) { |
| } |
| } |
| |
| static void set_counter_alarm(void) |
| { |
| u32_t timer_initial_value = (RTC_ALARM_SECOND * (TIMEOUT - 1)); |
| |
| if (counter_set_alarm(counter_dev, NULL, |
| timer_initial_value, NULL) |
| != 0) { |
| printk("Periodic Timer was not started yet\n"); |
| } |
| } |
| #elif (CONFIG_GPIO_QMSI_1) |
| static struct device *gpio_dev; |
| #define GPIO_INTERRUPT_PIN 4 |
| |
| static void setup_aon_gpio(void) |
| { |
| gpio_dev = device_get_binding("GPIO_1"); |
| if (!gpio_dev) { |
| printk("gpio device not found.\n"); |
| return; |
| } |
| |
| gpio_pin_configure(gpio_dev, GPIO_INTERRUPT_PIN, |
| GPIO_DIR_IN | GPIO_INT | GPIO_INT_EDGE | |
| GPIO_INT_ACTIVE_LOW | GPIO_INT_DEBOUNCE); |
| } |
| #elif (CONFIG_AIO_COMPARATOR) |
| static struct device *cmp_dev; |
| #define CMP_INTERRUPT_PIN 13 |
| |
| static void setup_aon_comparator(void) |
| { |
| volatile u32_t delay = 0; |
| |
| cmp_dev = device_get_binding("AIO_CMP_0"); |
| if (!cmp_dev) { |
| printk("comparator device not found.\n"); |
| return; |
| } |
| |
| /* Wait for the comparator to be grounded. */ |
| printk("USER_ACTION: Ground the comparator pin.\n"); |
| for (delay = 0; delay < 5000000; delay++) { |
| } |
| |
| aio_cmp_configure(cmp_dev, CMP_INTERRUPT_PIN, |
| AIO_CMP_POL_RISE, 0, |
| NULL, NULL); |
| |
| printk("USER_ACTION: Set the comparator pin to 3.3V/1.8V.\n"); |
| } |
| #endif |
| |
| static void setup_wake_event(void) |
| { |
| #if (CONFIG_RTC) |
| set_rtc_alarm(); |
| #elif (CONFIG_COUNTER) |
| set_counter_alarm(); |
| #elif (CONFIG_GPIO_QMSI_1) |
| printk("USER_ACTION: Press AON_GPIO 4.\n"); |
| #elif (CONFIG_AIO_COMPARATOR) |
| setup_aon_comparator(); |
| #endif |
| } |
| |
| static void do_soc_sleep(enum power_states state) |
| { |
| int woken_up = 0; |
| int i, devices_retval[suspend_device_count]; |
| |
| setup_wake_event(); |
| |
| for (i = suspend_device_count - 1; i >= 0; i--) { |
| devices_retval[i] = device_set_power_state(suspend_devices[i], |
| DEVICE_PM_SUSPEND_STATE); |
| } |
| |
| _sys_soc_set_power_state(state); |
| |
| /* |
| * Before enabling the interrupts, check the wake source |
| * as it will get cleared after. |
| */ |
| #if (CONFIG_RTC) |
| woken_up = rtc_get_pending_int(rtc_dev); |
| const char source[] = "RTC"; |
| #elif (CONFIG_COUNTER) |
| woken_up = counter_get_pending_int(counter_dev); |
| const char source[] = "counter"; |
| #elif (CONFIG_GPIO_QMSI_1) |
| woken_up = gpio_get_pending_int(gpio_dev); |
| const char source[] = "GPIO"; |
| #elif (CONFIG_AIO_COMPARATOR) |
| woken_up = aio_cmp_get_pending_int(cmp_dev); |
| const char source[] = "AON compare"; |
| #endif |
| |
| for (i = 0; i < suspend_device_count; i++) { |
| if (!devices_retval[i]) { |
| device_set_power_state(suspend_devices[i], |
| DEVICE_PM_ACTIVE_STATE); |
| } |
| } |
| |
| if (woken_up) { |
| printk("Woke up with %s (pin:%x)\n", source, woken_up); |
| } |
| } |
| |
| int _sys_soc_suspend(s32_t ticks) |
| { |
| enum power_states state; |
| int pm_operation = SYS_PM_NOT_HANDLED; |
| post_ops_done = 0; |
| |
| if (ticks < (TIMEOUT * CONFIG_SYS_CLOCK_TICKS_PER_SEC)) { |
| printk("Not enough time for PM operations (ticks: %d).\n", |
| ticks); |
| return SYS_PM_NOT_HANDLED; |
| } |
| |
| state = get_next_state(); |
| |
| printk("Entering %s state\n", state_to_string(state)); |
| |
| switch (state) { |
| case SYS_POWER_STATE_CPU_LPS: |
| case SYS_POWER_STATE_CPU_LPS_1: |
| case SYS_POWER_STATE_CPU_LPS_2: |
| /* |
| * A wake event is needed in the following cases: |
| * |
| * On Quark SE C1000 x86: |
| * - SYS_POWER_STATE_CPU_LPS: |
| * The PIC timer is gated and cannot wake the core from |
| * that state. |
| * |
| * - SYS_POWER_STATE_CPU_LPS_1: |
| * If the ARC enables LPSS, the PIC timer will |
| * not wake us up from SYS_POWER_STATE_CPU_LPS_1 |
| * which is mapped to C2. |
| * |
| * As the ARC enables LPSS, it should as well take care of |
| * setting up the relevant wake event or communicate |
| * to the x86 that information. |
| * |
| * On Quark SE C1000 ARC: |
| * - SYS_POWER_STATE_CPU_LPS: |
| * The ARC timer is gated and cannot wake the core from |
| * that state. |
| * |
| * - SYS_POWER_STATE_CPU_LPS_1: |
| * The ARC timer is gated and cannot wake the core from |
| * that state. |
| */ |
| setup_wake_event(); |
| pm_operation = SYS_PM_LOW_POWER_STATE; |
| _sys_soc_set_power_state(state); |
| break; |
| case SYS_POWER_STATE_DEEP_SLEEP: |
| case SYS_POWER_STATE_DEEP_SLEEP_1: |
| /* Don't need pm idle exit notification */ |
| _sys_soc_pm_idle_exit_notification_disable(); |
| |
| pm_operation = SYS_PM_DEEP_SLEEP; |
| do_soc_sleep(state); |
| break; |
| default: |
| printk("State not supported\n"); |
| break; |
| } |
| |
| if (pm_operation != SYS_PM_NOT_HANDLED) { |
| if (!post_ops_done) { |
| post_ops_done = 1; |
| printk("Exiting %s state\n", state_to_string(state)); |
| _sys_soc_power_state_post_ops(state); |
| } |
| } |
| |
| return pm_operation; |
| } |
| |
| void _sys_soc_resume(void) |
| { |
| enum power_states state = states_list[current_state]; |
| |
| switch (state) { |
| case SYS_POWER_STATE_CPU_LPS: |
| case SYS_POWER_STATE_CPU_LPS_1: |
| case SYS_POWER_STATE_CPU_LPS_2: |
| if (!post_ops_done) { |
| post_ops_done = 1; |
| printk("Exiting %s state\n", state_to_string(state)); |
| _sys_soc_power_state_post_ops(state); |
| } |
| break; |
| case SYS_POWER_STATE_DEEP_SLEEP: |
| case SYS_POWER_STATE_DEEP_SLEEP_1: |
| /* Do not perform post_ops in _sys_soc_resume for deep sleep. |
| * This would make the application task run without the full |
| * context restored. |
| */ |
| break; |
| default: |
| break; |
| } |
| } |
| |
| static void build_suspend_device_list(void) |
| { |
| int i, devcount; |
| struct device *devices; |
| |
| device_list_get(&devices, &devcount); |
| if (devcount > MAX_SUSPEND_DEVICE_COUNT) { |
| printk("Error: List of devices exceeds what we can track " |
| "for suspend. Built: %d, Max: %d\n", |
| devcount, MAX_SUSPEND_DEVICE_COUNT); |
| return; |
| } |
| |
| #if (CONFIG_X86) |
| suspend_device_count = 3; |
| for (i = 0; i < devcount; i++) { |
| if (!strcmp(devices[i].config->name, "loapic")) { |
| suspend_devices[0] = &devices[i]; |
| } else if (!strcmp(devices[i].config->name, "ioapic")) { |
| suspend_devices[1] = &devices[i]; |
| } else if (!strcmp(devices[i].config->name, |
| CONFIG_UART_CONSOLE_ON_DEV_NAME)) { |
| suspend_devices[2] = &devices[i]; |
| } else { |
| suspend_devices[suspend_device_count++] = &devices[i]; |
| } |
| } |
| #elif (CONFIG_ARC) |
| suspend_device_count = 2; |
| for (i = 0; i < devcount; i++) { |
| if (!strcmp(devices[i].config->name, "arc_v2_irq_unit")) { |
| suspend_devices[0] = &devices[i]; |
| } else if (!strcmp(devices[i].config->name, "sys_clock")) { |
| suspend_devices[1] = &devices[i]; |
| } else { |
| suspend_devices[suspend_device_count++] = &devices[i]; |
| } |
| } |
| #endif |
| } |
| |
| void main(void) |
| { |
| printk("Quark SE: Power Management sample application\n"); |
| |
| #if (CONFIG_RTC) |
| setup_rtc(); |
| #elif (CONFIG_COUNTER) |
| setup_counter(); |
| #elif (CONFIG_GPIO_QMSI_1) |
| setup_aon_gpio(); |
| #endif |
| |
| build_suspend_device_list(); |
| |
| #ifdef CONFIG_SOC_WATCH |
| /* Start the event monitoring thread */ |
| soc_watch_logger_thread_start(); |
| #endif |
| |
| /* All our application does is putting the task to sleep so the kernel |
| * triggers the suspend operation. |
| */ |
| while (1) { |
| k_sleep(TIMEOUT * 1000); |
| printk("Back to the application\n"); |
| } |
| } |