* After the move to `sys_poweroff`, restore the behavior of suspending devices before entering sleep state.
164 lines
4.6 KiB
C
164 lines
4.6 KiB
C
/*
|
|
* Copyright (c) 2020 The ZMK Contributors
|
|
*
|
|
* SPDX-License-Identifier: MIT
|
|
*/
|
|
|
|
#include <zephyr/device.h>
|
|
#include <zephyr/init.h>
|
|
#include <zephyr/kernel.h>
|
|
#include <zephyr/pm/device.h>
|
|
#include <zephyr/pm/device_runtime.h>
|
|
#include <zephyr/sys/poweroff.h>
|
|
|
|
#include <zephyr/logging/log.h>
|
|
|
|
LOG_MODULE_DECLARE(zmk, CONFIG_ZMK_LOG_LEVEL);
|
|
|
|
#include <zmk/event_manager.h>
|
|
#include <zmk/events/activity_state_changed.h>
|
|
#include <zmk/events/position_state_changed.h>
|
|
#include <zmk/events/sensor_event.h>
|
|
|
|
#include <zmk/activity.h>
|
|
|
|
#if IS_ENABLED(CONFIG_USB_DEVICE_STACK)
|
|
#include <zmk/usb.h>
|
|
#endif
|
|
|
|
// Reimplement some of the device work from Zephyr PM to work with the new `sys_poweroff` API.
|
|
// TODO: Tweak this to smarter runtime PM of subsystems on sleep.
|
|
|
|
#ifdef CONFIG_PM_DEVICE
|
|
TYPE_SECTION_START_EXTERN(const struct device *, zmk_pm_device_slots);
|
|
|
|
#if !defined(CONFIG_PM_DEVICE_RUNTIME_EXCLUSIVE)
|
|
/* Number of devices successfully suspended. */
|
|
static size_t zmk_num_susp;
|
|
|
|
static int zmk_pm_suspend_devices(void) {
|
|
const struct device *devs;
|
|
size_t devc;
|
|
|
|
devc = z_device_get_all_static(&devs);
|
|
|
|
zmk_num_susp = 0;
|
|
|
|
for (const struct device *dev = devs + devc - 1; dev >= devs; dev--) {
|
|
int ret;
|
|
|
|
/*
|
|
* Ignore uninitialized devices, busy devices, wake up sources, and
|
|
* devices with runtime PM enabled.
|
|
*/
|
|
if (!device_is_ready(dev) || pm_device_is_busy(dev) || pm_device_state_is_locked(dev) ||
|
|
pm_device_wakeup_is_enabled(dev) || pm_device_runtime_is_enabled(dev)) {
|
|
continue;
|
|
}
|
|
|
|
ret = pm_device_action_run(dev, PM_DEVICE_ACTION_SUSPEND);
|
|
/* ignore devices not supporting or already at the given state */
|
|
if ((ret == -ENOSYS) || (ret == -ENOTSUP) || (ret == -EALREADY)) {
|
|
continue;
|
|
} else if (ret < 0) {
|
|
LOG_ERR("Device %s did not enter %s state (%d)", dev->name,
|
|
pm_device_state_str(PM_DEVICE_STATE_SUSPENDED), ret);
|
|
return ret;
|
|
}
|
|
|
|
TYPE_SECTION_START(zmk_pm_device_slots)[zmk_num_susp] = dev;
|
|
zmk_num_susp++;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void zmk_pm_resume_devices(void) {
|
|
for (int i = (zmk_num_susp - 1); i >= 0; i--) {
|
|
pm_device_action_run(TYPE_SECTION_START(zmk_pm_device_slots)[i], PM_DEVICE_ACTION_RESUME);
|
|
}
|
|
|
|
zmk_num_susp = 0;
|
|
}
|
|
#endif /* !CONFIG_PM_DEVICE_RUNTIME_EXCLUSIVE */
|
|
#endif /* CONFIG_PM_DEVICE */
|
|
|
|
bool is_usb_power_present(void) {
|
|
#if IS_ENABLED(CONFIG_USB_DEVICE_STACK)
|
|
return zmk_usb_is_powered();
|
|
#else
|
|
return false;
|
|
#endif /* IS_ENABLED(CONFIG_USB_DEVICE_STACK) */
|
|
}
|
|
|
|
static enum zmk_activity_state activity_state;
|
|
|
|
static uint32_t activity_last_uptime;
|
|
|
|
#define MAX_IDLE_MS CONFIG_ZMK_IDLE_TIMEOUT
|
|
|
|
#if IS_ENABLED(CONFIG_ZMK_SLEEP)
|
|
#define MAX_SLEEP_MS CONFIG_ZMK_IDLE_SLEEP_TIMEOUT
|
|
#endif
|
|
|
|
int raise_event(void) {
|
|
return raise_zmk_activity_state_changed(
|
|
(struct zmk_activity_state_changed){.state = activity_state});
|
|
}
|
|
|
|
int set_state(enum zmk_activity_state state) {
|
|
if (activity_state == state)
|
|
return 0;
|
|
|
|
activity_state = state;
|
|
return raise_event();
|
|
}
|
|
|
|
enum zmk_activity_state zmk_activity_get_state(void) { return activity_state; }
|
|
|
|
int activity_event_listener(const zmk_event_t *eh) {
|
|
activity_last_uptime = k_uptime_get();
|
|
|
|
return set_state(ZMK_ACTIVITY_ACTIVE);
|
|
}
|
|
|
|
void activity_work_handler(struct k_work *work) {
|
|
int32_t current = k_uptime_get();
|
|
int32_t inactive_time = current - activity_last_uptime;
|
|
#if IS_ENABLED(CONFIG_ZMK_SLEEP)
|
|
if (inactive_time > MAX_SLEEP_MS && !is_usb_power_present()) {
|
|
// Put devices in suspend power mode before sleeping
|
|
set_state(ZMK_ACTIVITY_SLEEP);
|
|
|
|
if (zmk_pm_suspend_devices() < 0) {
|
|
LOG_ERR("Failed to suspend all the devices");
|
|
zmk_pm_resume_devices();
|
|
return;
|
|
}
|
|
|
|
sys_poweroff();
|
|
} else
|
|
#endif /* IS_ENABLED(CONFIG_ZMK_SLEEP) */
|
|
if (inactive_time > MAX_IDLE_MS) {
|
|
set_state(ZMK_ACTIVITY_IDLE);
|
|
}
|
|
}
|
|
|
|
K_WORK_DEFINE(activity_work, activity_work_handler);
|
|
|
|
void activity_expiry_function(struct k_timer *_timer) { k_work_submit(&activity_work); }
|
|
|
|
K_TIMER_DEFINE(activity_timer, activity_expiry_function, NULL);
|
|
|
|
static int activity_init(void) {
|
|
activity_last_uptime = k_uptime_get();
|
|
|
|
k_timer_start(&activity_timer, K_SECONDS(1), K_SECONDS(1));
|
|
return 0;
|
|
}
|
|
|
|
ZMK_LISTENER(activity, activity_event_listener);
|
|
ZMK_SUBSCRIPTION(activity, zmk_position_state_changed);
|
|
ZMK_SUBSCRIPTION(activity, zmk_sensor_event);
|
|
|
|
SYS_INIT(activity_init, APPLICATION, CONFIG_APPLICATION_INIT_PRIORITY);
|