From 843b99b69bfb50356187f43c008ab0c6b9a3ed56 Mon Sep 17 00:00:00 2001 From: Kim Streich Date: Mon, 9 May 2022 21:25:05 +0400 Subject: [PATCH] feat(dev-pm-policy): Added auto-off-on-idle and usb-auto-toggle --- app/CMakeLists.txt | 1 + app/dts/bindings/zmk,dev-pm-policy.yaml | 32 ++++ app/src/dev_pm_policy.c | 220 ++++++++++++++++++++++++ app/src/power_domain.c | 8 +- 4 files changed, 258 insertions(+), 3 deletions(-) create mode 100644 app/dts/bindings/zmk,dev-pm-policy.yaml create mode 100644 app/src/dev_pm_policy.c diff --git a/app/CMakeLists.txt b/app/CMakeLists.txt index dad0ea38205..8e092fc4d59 100644 --- a/app/CMakeLists.txt +++ b/app/CMakeLists.txt @@ -30,6 +30,7 @@ target_sources(app PRIVATE src/sensors.c) target_sources_ifdef(CONFIG_ZMK_WPM app PRIVATE src/wpm.c) target_sources(app PRIVATE src/event_manager.c) target_sources_ifdef(CONFIG_ZMK_EXT_POWER app PRIVATE src/power_domain.c) +target_sources_ifdef(CONFIG_ZMK_EXT_POWER app PRIVATE src/dev_pm_policy.c) target_sources(app PRIVATE src/events/activity_state_changed.c) target_sources(app PRIVATE src/events/position_state_changed.c) target_sources(app PRIVATE src/events/sensor_event.c) diff --git a/app/dts/bindings/zmk,dev-pm-policy.yaml b/app/dts/bindings/zmk,dev-pm-policy.yaml new file mode 100644 index 00000000000..c00a032a05f --- /dev/null +++ b/app/dts/bindings/zmk,dev-pm-policy.yaml @@ -0,0 +1,32 @@ +# Copyright (c) 2020 The ZMK Contributors +# SPDX-License-Identifier: MIT + +description: Device Power Management Policy + +compatible: "zmk,dev-pm-policy" + +properties: + label: + type: string + required: true + + device: + type: phandle + required: true + description: | + + The device or power domain on which the power management policy should be run on. + + auto-off-on-idle: + type: boolean + required: false + description: | + + Automatically turn off a device or power domain when the keyboard is idle. + + usb-auto-toggle: + type: boolean + required: false + description: | + + Automatically turn off a device or power domain when usb is disconnected and turn on when usb is connected. diff --git a/app/src/dev_pm_policy.c b/app/src/dev_pm_policy.c new file mode 100644 index 00000000000..ee6f236db22 --- /dev/null +++ b/app/src/dev_pm_policy.c @@ -0,0 +1,220 @@ +/* + * Copyright (c) 2022, Commonwealth Scientific and Industrial Research + * Organisation (CSIRO) ABN 41 687 119 230. + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#define DT_DRV_COMPAT zmk_dev_pm_policy + +#include +#include +#include +#include +#include +#include + +#include +LOG_MODULE_DECLARE(zmk, CONFIG_ZMK_LOG_LEVEL); + +// TODO: Add check if dev is power domain or device + +#if DT_HAS_COMPAT_STATUS_OKAY(DT_DRV_COMPAT) + +// Device PM Policy config struct +struct zmk_dev_pm_policy_config { + const struct device *device; + const char *device_compatible; + bool auto_off_on_idle; + bool usb_auto_toggle; +}; + +// Array that contains all policy devices that we can loop through during +// events. +// Limited to 10 policies. +#define MAX_POLICY_NUM 10 +int policy_count = 0; +const struct device *policies[MAX_POLICY_NUM]; + +/* + * Enabling & Disabling of devices + */ + +void zmk_dev_pm_policy_enable_device(const struct device *dev, bool save_state) { + const struct zmk_dev_pm_policy_config *cfg = dev->config; + + LOG_DBG("Enabling device `%s` with compatible `%s`.", cfg->device->name, cfg->device_compatible); + + if(strcmp(cfg->device_compatible, "power-domain-gpio") == 0) { + + zmk_power_domain_enable(cfg->device, save_state); + } else { // Regular Device + + pm_device_action_run(cfg->device, PM_DEVICE_ACTION_RESUME); + } +} + +void zmk_dev_pm_policy_disable_device(const struct device *dev, bool save_state) { + const struct zmk_dev_pm_policy_config *cfg = dev->config; + + LOG_DBG("Disabling device `%s` with compatible `%s`.", cfg->device->name, cfg->device_compatible); + + if(strcmp(cfg->device_compatible, "power-domain-gpio") == 0) { + + zmk_power_domain_disable(cfg->device, save_state); + } else { // Regular Device + + pm_device_action_run(cfg->device, PM_DEVICE_ACTION_TURN_OFF); + } +} + +const int zmk_dev_pm_policy_get_device_state(const struct device *dev) { + const struct zmk_dev_pm_policy_config *cfg = dev->config; + + if(strcmp(cfg->device_compatible, "power-domain-gpio") == 0) { + + return zmk_power_domain_get_state_user_intended(cfg->device); + } else { // Regular Device + + return 1; + } +} + +/* + * USB Event Handler + */ + +void zmk_dev_pm_policy_auto_toggle_usb(const struct device *dev) { + LOG_DBG("Doing usb auto toggling for `%s`.", dev->name); + + if (zmk_usb_is_powered() == true) { + LOG_INF("USB power was connected. Enabling external power for device `%s`.", dev->name); + zmk_dev_pm_policy_enable_device(dev, true); + + } else { + LOG_INF("USB power was removed. Disabling external power for device `%s`.", dev->name); + zmk_dev_pm_policy_disable_device(dev, true); + } +} + +static int zmk_dev_pm_policy_usb_event_handler(const zmk_event_t *eh) { + if (as_zmk_usb_conn_state_changed(eh)) { + LOG_DBG("USB conn state changed: %d", zmk_usb_is_powered()); + + for(int i=0; i < policy_count; i++) { + const struct device *dev = policies[i]; + const struct zmk_dev_pm_policy_config *cfg = dev->config; + + if(cfg->usb_auto_toggle == true) { + zmk_dev_pm_policy_auto_toggle_usb(dev); + } + } + } + + return 0; +} +ZMK_LISTENER(zmk_dev_pm_policy_usb, zmk_dev_pm_policy_usb_event_handler); +ZMK_SUBSCRIPTION(zmk_dev_pm_policy_usb, zmk_usb_conn_state_changed); + + +/* + * Activity Event Handler + */ + +void zmk_dev_pm_policy_auto_activity_toggle(const struct device *dev, struct zmk_activity_state_changed *ev) { + + if(zmk_dev_pm_policy_get_device_state(dev) != 1) { + // User has turned off the power domain. + // Therefore we don't need want to do activity toggling. + return; + } + + const struct zmk_dev_pm_policy_config *cfg = dev->config; + + // Enable or disable the device / power domain, but don't save the new + // state in the settings + switch (ev->state) { + case ZMK_ACTIVITY_ACTIVE: + LOG_INF("Device became active - Enabling power for device `%s`.", cfg->device->name); + zmk_dev_pm_policy_enable_device(dev, false); + break; + case ZMK_ACTIVITY_IDLE: + LOG_INF("Device became idle - Disabling power for device `%s`.", cfg->device->name); + zmk_dev_pm_policy_disable_device(dev, false); + break; + case ZMK_ACTIVITY_SLEEP: + LOG_DBG("Device going to sleep - Doing nothing."); + break; + default: + LOG_WRN("Unhandled activity state: %d", ev->state); + } +} + +int zmk_dev_pm_policy_activity_event_handler(const zmk_event_t *eh) { + struct zmk_activity_state_changed *ev = as_zmk_activity_state_changed(eh); + if (ev == NULL) { + return -ENOTSUP; + } + + LOG_DBG("Activity state changed to: %d", ev->state); + + for(int i=0; i < policy_count; i++) { + const struct device *dev = policies[i]; + const struct zmk_dev_pm_policy_config *cfg = dev->config; + + if(cfg->auto_off_on_idle == true) { + zmk_dev_pm_policy_auto_activity_toggle(dev, ev); + } + } + + return 0; +} +ZMK_LISTENER(zmk_dev_pm_policy_activity, zmk_dev_pm_policy_activity_event_handler); +ZMK_SUBSCRIPTION(zmk_dev_pm_policy_activity, zmk_activity_state_changed); + + +/* + * Initialization + */ + +static int zmk_dev_pm_policy_init(const struct device *dev) +{ + LOG_DBG("Initing zmk_dev_pm_policy_init: %s", dev->name); + const struct zmk_dev_pm_policy_config *cfg = dev->config; + + if(policy_count < MAX_POLICY_NUM) { + policies[policy_count] = dev; + policy_count++; + + if(cfg->usb_auto_toggle == true) { + zmk_dev_pm_policy_auto_toggle_usb(dev); + } + } else { + LOG_ERR("Could not add dev_pm_policy `%s` to policies list, because the number of policies exceeds the maximum number of %d.", dev->name, MAX_POLICY_NUM); + } + + return 0; +} + +#define CONFIG_zmk_dev_pm_policy_INIT_PRIORITY 99 +#define KP_INST(id) \ + static const struct zmk_dev_pm_policy_config zmk_dev_pm_policy_##id = { \ + .device = DEVICE_DT_GET(DT_INST_PHANDLE(id, device)), \ + .device_compatible = DT_PROP_BY_IDX(DT_INST_PHANDLE(id, device), compatible, 0), \ + .auto_off_on_idle = DT_INST_PROP(id, auto_off_on_idle), \ + .usb_auto_toggle = DT_INST_PROP(id, usb_auto_toggle), \ + }; \ + DEVICE_DT_INST_DEFINE( \ + id, \ + zmk_dev_pm_policy_init, \ + NULL, \ + NULL, \ + &zmk_dev_pm_policy_##id, \ + APPLICATION, \ + CONFIG_zmk_dev_pm_policy_INIT_PRIORITY, \ + NULL \ + ); + +DT_INST_FOREACH_STATUS_OKAY(KP_INST) + +#endif /* DT_HAS_COMPAT_STATUS_OKAY(DT_DRV_COMPAT) */ diff --git a/app/src/power_domain.c b/app/src/power_domain.c index 490c4591d4e..655681e6f02 100644 --- a/app/src/power_domain.c +++ b/app/src/power_domain.c @@ -400,9 +400,11 @@ static int zmk_power_domain_manager_post_boot_init(const struct device *dev) { return 0; } -// The lowest priority. Ensures this function is runs after all other devices -// have been initialized -#define ZMK_POWER_DOMAIN_MANAGER_POST_BOOT_INIT_PRIORITY 99 +// Run this post_boot_init function at the second lowest priority. +// This ensures this function is run after all other devices have been +// initialized, except for the dev_pm_policy devices, which must be run after +// this. +#define ZMK_POWER_DOMAIN_MANAGER_POST_BOOT_INIT_PRIORITY 98 SYS_INIT(zmk_power_domain_manager_post_boot_init, APPLICATION, ZMK_POWER_DOMAIN_MANAGER_POST_BOOT_INIT_PRIORITY);