/* * Copyright (c) 2018 Intel Corporation. * Copyright (c) 2022 Nordic Semiconductor ASA * * SPDX-License-Identifier: Apache-2.0 */ #include #include #include #include #include #include #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 atomic_t global_lock_cnt; static struct k_spinlock lock; #endif void pm_policy_state_all_lock_get(void) { #if DT_HAS_COMPAT_STATUS_OKAY(zephyr_power_state) (void)atomic_inc(&global_lock_cnt); #endif } void pm_policy_state_all_lock_put(void) { #if DT_HAS_COMPAT_STATUS_OKAY(zephyr_power_state) __ASSERT(global_lock_cnt > 0, "Unbalanced state lock get/put"); (void)atomic_dec(&global_lock_cnt); #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(&global_lock_cnt) != 0) || (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) if (atomic_get(&global_lock_cnt) != 0) { return false; } 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(&global_lock_cnt) == 0) && (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