blob: f34ea8d629d69acc1917300c2255ad07d594b37f [file] [log] [blame]
/*
* Copyright (c) 2020 Intel Corporation.
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <zephyr/sys/printk.h>
#include <zephyr/types.h>
#include <zephyr/pm/device.h>
#include <zephyr/pm/device_runtime.h>
#include <zephyr/ztest.h>
#include <ksched.h>
#include <zephyr/kernel.h>
#include <zephyr/pm/pm.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 bool testing_device_runtime;
static bool testing_device_order;
static bool testing_device_lock;
static const struct device *device_dummy;
static struct dummy_driver_api *api;
static const struct device *const device_a =
DEVICE_DT_GET(DT_INST(0, test_device_pm));
static const struct device *const device_c =
DEVICE_DT_GET(DT_INST(2, test_device_pm));
/*
* According with the initialization level, devices A, B and C are
* initialized in the following order A -> B -> C.
*
* The power management subsystem uses this order to suspend and resume
* devices. Devices are suspended in the reverse order:
*
* C -> B -> A
*
* While resuming uses the initialization order:
*
* A -> B -> C
*
* This test checks if these order is correct checking devices A and C states
* when suspending / resuming device B.
*/
/* Common init function for devices A,B and C */
static int device_init(const struct device *dev)
{
ARG_UNUSED(dev);
return 0;
}
static int device_a_pm_action(const struct device *dev,
enum pm_device_action pm_action)
{
ARG_UNUSED(dev);
ARG_UNUSED(pm_action);
return 0;
}
PM_DEVICE_DT_DEFINE(DT_INST(0, test_device_pm), device_a_pm_action);
DEVICE_DT_DEFINE(DT_INST(0, test_device_pm), device_init,
PM_DEVICE_DT_GET(DT_INST(0, test_device_pm)), NULL, NULL,
PRE_KERNEL_1, CONFIG_KERNEL_INIT_PRIORITY_DEVICE,
NULL);
static int device_b_pm_action(const struct device *dev,
enum pm_device_action pm_action)
{
enum pm_device_state state_a;
enum pm_device_state state_c;
if (!testing_device_order) {
return 0;
}
(void)pm_device_state_get(device_a, &state_a);
(void)pm_device_state_get(device_c, &state_c);
switch (pm_action) {
case PM_DEVICE_ACTION_RESUME:
/* Check if device C is still suspended */
zassert_equal(state_c, PM_DEVICE_STATE_SUSPENDED,
"Inconsistent states");
/* Check if device A is already active */
zassert_equal(state_a, PM_DEVICE_STATE_ACTIVE,
"Inconsistent states");
break;
case PM_DEVICE_ACTION_SUSPEND:
/* Check if device C is already suspended */
zassert_equal(state_c, PM_DEVICE_STATE_SUSPENDED,
"Inconsistent states");
/* Check if device A is still active */
zassert_equal(state_a, PM_DEVICE_STATE_ACTIVE,
"Inconsistent states");
break;
default:
break;
}
return 0;
}
PM_DEVICE_DT_DEFINE(DT_INST(1, test_device_pm), device_b_pm_action);
DEVICE_DT_DEFINE(DT_INST(1, test_device_pm), device_init,
PM_DEVICE_DT_GET(DT_INST(1, test_device_pm)), NULL, NULL,
PRE_KERNEL_2, CONFIG_KERNEL_INIT_PRIORITY_DEVICE,
NULL);
static int device_c_pm_action(const struct device *dev,
enum pm_device_action pm_action)
{
ARG_UNUSED(dev);
ARG_UNUSED(pm_action);
return 0;
}
PM_DEVICE_DT_DEFINE(DT_INST(2, test_device_pm), device_c_pm_action);
DEVICE_DT_DEFINE(DT_INST(2, test_device_pm), device_init,
PM_DEVICE_DT_GET(DT_INST(2, test_device_pm)), NULL, NULL,
POST_KERNEL, CONFIG_KERNEL_INIT_PRIORITY_DEVICE,
NULL);
void pm_state_set(enum pm_state state, uint8_t substate_id)
{
ARG_UNUSED(substate_id);
ARG_UNUSED(state);
enum pm_device_state device_power_state;
/* If testing device order this function does not need to anything */
if (testing_device_order) {
return;
}
if (testing_device_lock) {
pm_device_state_get(device_a, &device_power_state);
/*
* If the device has its state locked the device has
* to be ACTIVE
*/
zassert_true(device_power_state == PM_DEVICE_STATE_ACTIVE,
NULL);
return;
}
/* 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 */
pm_device_state_get(device_dummy, &device_power_state);
if (testing_device_runtime) {
/* If device runtime is enable, the device should still be
* active
*/
zassert_true(device_power_state == PM_DEVICE_STATE_ACTIVE);
} else {
/* at this point, devices have been deactivated */
zassert_false(device_power_state == PM_DEVICE_STATE_ACTIVE);
}
/* this function is called when system entering low power state, so
* parameter state should not be PM_STATE_ACTIVE
*/
zassert_false(state == PM_STATE_ACTIVE,
"Entering low power state with a wrong parameter");
}
void pm_state_exit_post_ops(enum pm_state state, uint8_t substate_id)
{
ARG_UNUSED(state);
ARG_UNUSED(substate_id);
/* pm_system_suspend is entered with irq locked
* unlock irq before leave pm_system_suspend
*/
irq_unlock(0);
}
/* Our PM policy handler */
const struct pm_state_info *pm_policy_next_state(uint8_t cpu, int32_t ticks)
{
static struct pm_state_info info;
ARG_UNUSED(cpu);
/* make sure this is idle thread */
zassert_true(z_is_idle_thread_object(_current));
zassert_true(ticks == _kernel.idle);
zassert_false(k_can_yield());
idle_entered = true;
if (enter_low_power) {
enter_low_power = false;
notify_app_entry = true;
info.state = PM_STATE_SUSPEND_TO_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)
{
enum pm_device_state 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));
zassert_equal(state, PM_STATE_SUSPEND_TO_IDLE);
pm_device_state_get(device_dummy, &device_power_state);
if (testing_device_runtime) {
/* If device runtime is enable, the device should still be
* active
*/
zassert_true(device_power_state == PM_DEVICE_STATE_ACTIVE);
} else {
/* at this point, devices should not be active */
zassert_false(device_power_state == PM_DEVICE_STATE_ACTIVE);
}
set_pm = true;
notify_app_exit = true;
}
/* implement in application, called by idle thread */
static void notify_pm_state_exit(enum pm_state state)
{
enum pm_device_state 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));
zassert_equal(state, PM_STATE_SUSPEND_TO_IDLE);
/* at this point, devices are active again*/
pm_device_state_get(device_dummy, &device_power_state);
zassert_equal(device_power_state, PM_DEVICE_STATE_ACTIVE);
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
*/
ZTEST(power_management_1cpu, test_power_idle)
{
TC_PRINT("give way to idle thread\n");
k_sleep(SLEEP_TIMEOUT);
zassert_true(idle_entered, "Never entered idle thread");
}
static struct pm_notifier notifier = {
.state_entry = notify_pm_state_entry,
.state_exit = notify_pm_state_exit,
};
/*
* @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
*/
ZTEST(power_management_1cpu, test_power_state_trans)
{
int ret;
pm_notifier_register(&notifier);
enter_low_power = true;
ret = pm_device_runtime_disable(device_dummy);
zassert_true(ret == 0, "Failed to disable device runtime PM");
/* give way to idle thread */
k_sleep(SLEEP_TIMEOUT);
zassert_true(leave_idle);
ret = pm_device_runtime_enable(device_dummy);
zassert_true(ret == 0, "Failed to enable device runtime PM");
pm_notifier_unregister(&notifier);
}
/*
* @brief notification between system and device
*
* @details
* - device driver notify its power state change by pm_device_runtime_get and
* pm_device_runtime_put_async
* - system inform device system power state change through device interface
* pm_action_cb
*
* @see pm_device_runtime_get(), pm_device_runtime_put_async(),
* pm_device_action_run(), pm_device_state_get()
*
* @ingroup power_tests
*/
ZTEST(power_management_1cpu, test_power_state_notification)
{
int ret;
enum pm_device_state device_power_state;
pm_notifier_register(&notifier);
enter_low_power = true;
ret = api->open(device_dummy);
zassert_true(ret == 0, "Fail to open device");
pm_device_state_get(device_dummy, &device_power_state);
zassert_equal(device_power_state, PM_DEVICE_STATE_ACTIVE);
/* The device should be kept active even when the system goes idle */
testing_device_runtime = true;
k_sleep(SLEEP_TIMEOUT);
zassert_true(leave_idle);
api->close(device_dummy);
pm_device_state_get(device_dummy, &device_power_state);
zassert_equal(device_power_state, PM_DEVICE_STATE_SUSPENDED);
pm_notifier_unregister(&notifier);
testing_device_runtime = false;
}
ZTEST(power_management_1cpu, test_device_order)
{
zassert_true(device_is_ready(device_a), "device a not ready");
zassert_true(device_is_ready(device_c), "device c not ready");
testing_device_order = true;
enter_low_power = true;
k_sleep(SLEEP_TIMEOUT);
testing_device_order = false;
}
/**
* @brief Test the device busy APIs.
*/
ZTEST(power_management_1cpu, test_busy)
{
bool busy;
busy = pm_device_is_any_busy();
zassert_false(busy);
pm_device_busy_set(device_dummy);
busy = pm_device_is_any_busy();
zassert_true(busy);
busy = pm_device_is_busy(device_dummy);
zassert_true(busy);
pm_device_busy_clear(device_dummy);
busy = pm_device_is_any_busy();
zassert_false(busy);
busy = pm_device_is_busy(device_dummy);
zassert_false(busy);
}
ZTEST(power_management_1cpu, test_device_state_lock)
{
pm_device_state_lock(device_a);
zassert_true(pm_device_state_is_locked(device_a));
testing_device_lock = true;
enter_low_power = true;
k_sleep(SLEEP_TIMEOUT);
pm_device_state_unlock(device_a);
testing_device_lock = false;
}
void power_management_1cpu_teardown(void *data)
{
pm_notifier_unregister(&notifier);
}
static void *power_management_1cpu_setup(void)
{
device_dummy = device_get_binding(DUMMY_DRIVER_NAME);
api = (struct dummy_driver_api *)device_dummy->api;
return NULL;
}
ZTEST_SUITE(power_management_1cpu, NULL, power_management_1cpu_setup,
ztest_simple_1cpu_before, ztest_simple_1cpu_after,
power_management_1cpu_teardown);