| /* |
| * Copyright (c) 2020 Intel Corporation. |
| * |
| * SPDX-License-Identifier: Apache-2.0 |
| */ |
| |
| #include <sys/printk.h> |
| #include <zephyr.h> |
| #include <zephyr/types.h> |
| #include <ztest.h> |
| #include <ksched.h> |
| #include <kernel.h> |
| #include <power/power.h> |
| #include "dummy_driver.h" |
| |
| #define SLEEP_MSEC 100 |
| #define SLEEP_TIMEOUT K_MSEC(SLEEP_MSEC) |
| |
| /* for checking power suspend and resume order between system and devices */ |
| static bool enter_low_power; |
| static bool notify_app_entry; |
| static bool notify_app_exit; |
| static bool set_pm; |
| static bool leave_idle; |
| static bool idle_entered; |
| |
| static const struct device *dev; |
| static struct dummy_driver_api *api; |
| /* |
| * Weak power hook functions. Used on systems that have not implemented |
| * power management. |
| */ |
| __weak void pm_power_state_set(struct pm_state_info info) |
| { |
| /* at this point, notify_pm_state_entry() implemented in |
| * this file has been called and set_pm should have been set |
| */ |
| zassert_true(set_pm == true, |
| "Notification to enter suspend was not sent to the App"); |
| |
| /* this function is called after devices enter low power state */ |
| uint32_t device_power_state; |
| /* at this point, devices have been deactivated */ |
| device_get_power_state(dev, &device_power_state); |
| zassert_false(device_power_state == DEVICE_PM_ACTIVE_STATE, NULL); |
| |
| /* this function is called when system entering low power state, so |
| * parameter state should not be POWER_STATE_ACTIVE |
| */ |
| zassert_false(info.state == PM_STATE_ACTIVE, |
| "Entering low power state with a wrong parameter"); |
| } |
| |
| __weak void pm_power_state_exit_post_ops(struct pm_state_info info) |
| { |
| /* pm_system_suspend is entered with irq locked |
| * unlock irq before leave pm_system_suspend |
| */ |
| irq_unlock(0); |
| } |
| |
| __weak bool pm_policy_low_power_devices(enum pm_state state) |
| { |
| return pm_is_sleep_state(state); |
| } |
| |
| /* Our PM policy handler */ |
| struct pm_state_info pm_policy_next_state(int ticks) |
| { |
| struct pm_state_info info; |
| |
| /* make sure this is idle thread */ |
| zassert_true(z_is_idle_thread_object(_current), NULL); |
| zassert_true(ticks == _kernel.idle, NULL); |
| idle_entered = true; |
| |
| if (enter_low_power) { |
| enter_low_power = false; |
| notify_app_entry = true; |
| info.state = PM_STATE_RUNTIME_IDLE; |
| } else { |
| /* only test pm_policy_next_state() |
| * no PM operation done |
| */ |
| info.state = PM_STATE_ACTIVE; |
| } |
| return info; |
| } |
| |
| /* implement in application, called by idle thread */ |
| static void notify_pm_state_entry(enum pm_state state) |
| { |
| uint32_t device_power_state; |
| |
| /* enter suspend */ |
| zassert_true(notify_app_entry == true, |
| "Notification to enter suspend was not sent to the App"); |
| zassert_true(z_is_idle_thread_object(_current), NULL); |
| zassert_equal(state, PM_STATE_RUNTIME_IDLE, NULL); |
| |
| /* at this point, devices are active */ |
| device_get_power_state(dev, &device_power_state); |
| zassert_equal(device_power_state, DEVICE_PM_ACTIVE_STATE, NULL); |
| set_pm = true; |
| notify_app_exit = true; |
| } |
| |
| /* implement in application, called by idle thread */ |
| static void notify_pm_state_exit(enum pm_state state) |
| { |
| uint32_t device_power_state; |
| |
| /* leave suspend */ |
| zassert_true(notify_app_exit == true, |
| "Notification to leave suspend was not sent to the App"); |
| zassert_true(z_is_idle_thread_object(_current), NULL); |
| zassert_equal(state, PM_STATE_RUNTIME_IDLE, NULL); |
| |
| /* at this point, devices are active again*/ |
| device_get_power_state(dev, &device_power_state); |
| zassert_equal(device_power_state, DEVICE_PM_ACTIVE_STATE, NULL); |
| leave_idle = true; |
| |
| } |
| |
| /* |
| * @brief test power idle |
| * |
| * @details |
| * - The global idle routine executes when no other work is available |
| * - The idle routine provide a timeout parameter to the suspend routine |
| * indicating the amount of time guaranteed to expire before the next |
| * timeout, pm_policy_next_state() handle this parameter. |
| * - In this case, pm_policy_next_sate() return POWER_STATE_ACTIVE, |
| * so there is no low power operation happen. |
| * |
| * @see pm_policy_next_state() |
| * |
| * @ingroup power_tests |
| */ |
| void test_power_idle(void) |
| { |
| TC_PRINT("give way to idle thread\n"); |
| k_sleep(SLEEP_TIMEOUT); |
| zassert_true(idle_entered, "Never entered idle thread"); |
| } |
| |
| /* |
| * @brief test power state transition |
| * |
| * @details |
| * - The system support control of power state ordering between |
| * subsystems and devices |
| * - The application can control system power state transitions in idle thread |
| * through pm_notify_pm_state_entry and pm_notify_pm_state_exit |
| * |
| * @see pm_notify_pm_state_entry(), pm_notify_pm_state_exit() |
| * |
| * @ingroup power_tests |
| */ |
| void test_power_state_trans(void) |
| { |
| enter_low_power = true; |
| /* give way to idle thread */ |
| k_sleep(SLEEP_TIMEOUT); |
| zassert_true(leave_idle, NULL); |
| } |
| |
| /* |
| * @brief notification between system and device |
| * |
| * @details |
| * - device driver notify its power state change by device_pm_get and |
| * device_pm_put |
| * - system inform device system power state change through device interface |
| * device_pm_control |
| * |
| * @see device_pm_get(), device_pm_put(), device_set_power_state(), |
| * device_get_power_state() |
| * |
| * @ingroup power_tests |
| */ |
| void test_power_state_notification(void) |
| { |
| uint32_t device_power_state; |
| |
| device_get_power_state(dev, &device_power_state); |
| zassert_equal(device_power_state, DEVICE_PM_ACTIVE_STATE, NULL); |
| |
| api->close(dev); |
| device_get_power_state(dev, &device_power_state); |
| zassert_equal(device_power_state, DEVICE_PM_SUSPEND_STATE, NULL); |
| /* reopen device as it will be closed in teardown */ |
| api->open(dev); |
| } |
| |
| void test_setup(void) |
| { |
| int ret; |
| |
| dev = device_get_binding(DUMMY_DRIVER_NAME); |
| api = (struct dummy_driver_api *)dev->api; |
| ret = api->open(dev); |
| zassert_true(ret == 0, "Fail to open device"); |
| } |
| |
| void test_teardown(void) |
| { |
| api->close(dev); |
| } |
| |
| void test_main(void) |
| { |
| struct pm_notifier notifier = { |
| .state_entry = notify_pm_state_entry, |
| .state_exit = notify_pm_state_exit, |
| }; |
| |
| pm_notifier_register(¬ifier); |
| |
| ztest_test_suite(power_management_test, |
| ztest_1cpu_unit_test(test_power_idle), |
| ztest_unit_test_setup_teardown(test_power_state_trans, |
| test_setup, |
| test_teardown), |
| ztest_unit_test_setup_teardown( |
| test_power_state_notification, |
| test_setup, |
| test_teardown)); |
| ztest_run_test_suite(power_management_test); |
| pm_notifier_unregister(¬ifier); |
| } |