blob: b4cc319339baa5307e664b4a3d9618e1992bfe92 [file] [log] [blame]
/*
* Copyright (c) 2018 Intel Corporation.
* Copyright (c) 2022 Nordic Semiconductor ASA
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <zephyr/pm/policy.h>
#include <zephyr/pm/state.h>
#include <zephyr/sys/__assert.h>
#include <zephyr/sys/atomic.h>
#include <zephyr/toolchain.h>
#include <zephyr/spinlock.h>
#if DT_HAS_COMPAT_STATUS_OKAY(zephyr_power_state)
#define DT_SUB_LOCK_INIT(node_id) \
{ .state = PM_STATE_DT_INIT(node_id), \
.substate_id = DT_PROP_OR(node_id, substate_id, 0), \
.exit_latency_us = DT_PROP_OR(node_id, exit_latency_us, 0), \
},
/**
* State and substate lock structure.
*
* Struct holds all power states defined in the device tree. Array with counter
* variables is in RAM and n-th counter is used for n-th power state. Structure
* also holds exit latency for each state. It is used to disable power states
* based on current latency requirement.
*
* Operations on this array are in the order of O(n) with the number of power
* states and this is mostly due to the random nature of the substate value
* (that can be anything from a small integer value to a bitmask). We can
* probably do better with an hashmap.
*/
static const struct {
enum pm_state state;
uint8_t substate_id;
uint32_t exit_latency_us;
} substates[] = {
DT_FOREACH_STATUS_OKAY(zephyr_power_state, DT_SUB_LOCK_INIT)
};
static atomic_t lock_cnt[ARRAY_SIZE(substates)];
static atomic_t latency_mask = BIT_MASK(ARRAY_SIZE(substates));
static atomic_t unlock_mask = BIT_MASK(ARRAY_SIZE(substates));
static struct k_spinlock lock;
#endif
void pm_policy_state_lock_get(enum pm_state state, uint8_t substate_id)
{
#if DT_HAS_COMPAT_STATUS_OKAY(zephyr_power_state)
for (size_t i = 0; i < ARRAY_SIZE(substates); i++) {
if (substates[i].state == state &&
(substates[i].substate_id == substate_id || substate_id == PM_ALL_SUBSTATES)) {
k_spinlock_key_t key = k_spin_lock(&lock);
if (lock_cnt[i] == 0) {
unlock_mask &= ~BIT(i);
}
lock_cnt[i]++;
k_spin_unlock(&lock, key);
}
}
#endif
}
void pm_policy_state_constraints_get(struct pm_state_constraints *constraints)
{
for (int i = 0; i < constraints->count; i++) {
pm_policy_state_lock_get(constraints->list[i].state,
constraints->list[i].substate_id);
}
}
void pm_policy_state_lock_put(enum pm_state state, uint8_t substate_id)
{
#if DT_HAS_COMPAT_STATUS_OKAY(zephyr_power_state)
for (size_t i = 0; i < ARRAY_SIZE(substates); i++) {
if (substates[i].state == state &&
(substates[i].substate_id == substate_id || substate_id == PM_ALL_SUBSTATES)) {
k_spinlock_key_t key = k_spin_lock(&lock);
__ASSERT(lock_cnt[i] > 0, "Unbalanced state lock get/put");
lock_cnt[i]--;
if (lock_cnt[i] == 0) {
unlock_mask |= BIT(i);
}
k_spin_unlock(&lock, key);
}
}
#endif
}
void pm_policy_state_constraints_put(struct pm_state_constraints *constraints)
{
for (int i = 0; i < constraints->count; i++) {
pm_policy_state_lock_put(constraints->list[i].state,
constraints->list[i].substate_id);
}
}
bool pm_policy_state_lock_is_active(enum pm_state state, uint8_t substate_id)
{
#if DT_HAS_COMPAT_STATUS_OKAY(zephyr_power_state)
for (size_t i = 0; i < ARRAY_SIZE(substates); i++) {
if (substates[i].state == state &&
(substates[i].substate_id == substate_id || substate_id == PM_ALL_SUBSTATES)) {
return atomic_get(&lock_cnt[i]) != 0;
}
}
#endif
return false;
}
bool pm_policy_state_is_available(enum pm_state state, uint8_t substate_id)
{
#if DT_HAS_COMPAT_STATUS_OKAY(zephyr_power_state)
for (size_t i = 0; i < ARRAY_SIZE(substates); i++) {
if (substates[i].state == state &&
(substates[i].substate_id == substate_id || substate_id == PM_ALL_SUBSTATES)) {
return (atomic_get(&lock_cnt[i]) == 0) &&
(atomic_get(&latency_mask) & BIT(i));
}
}
#endif
return false;
}
bool pm_policy_state_any_active(void)
{
#if DT_HAS_COMPAT_STATUS_OKAY(zephyr_power_state)
/* Check if there is any power state that is not locked and not disabled due
* to latency requirements.
*/
return atomic_get(&unlock_mask) & atomic_get(&latency_mask);
#endif
return true;
}
#if DT_HAS_COMPAT_STATUS_OKAY(zephyr_power_state)
/* Callback is called whenever latency requirement changes. It is called under lock. */
static void pm_policy_latency_update_locked(int32_t max_latency_us)
{
for (size_t i = 0; i < ARRAY_SIZE(substates); i++) {
if (substates[i].exit_latency_us >= max_latency_us) {
latency_mask &= ~BIT(i);
} else {
latency_mask |= BIT(i);
}
}
}
static int pm_policy_latency_init(void)
{
static struct pm_policy_latency_subscription sub;
pm_policy_latency_changed_subscribe(&sub, pm_policy_latency_update_locked);
return 0;
}
SYS_INIT(pm_policy_latency_init, PRE_KERNEL_1, 0);
#endif