/* * Copyright (c) 2020 The ZMK Contributors * * SPDX-License-Identifier: MIT */ #include #include #include #include #include #include #include LOG_MODULE_DECLARE(zmk, CONFIG_ZMK_LOG_LEVEL); #include #include #include #include #include #if IS_ENABLED(CONFIG_USB_DEVICE_STACK) #include #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);