228 lines
6.1 KiB
C
228 lines
6.1 KiB
C
/*
|
|
* 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 PM_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);
|
|
}
|
|
|
|
/* 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 should not be active */
|
|
device_get_power_state(dev, &device_power_state);
|
|
zassert_false(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 PM_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);
|
|
}
|