From ca4824aa7f6fba87ad5782e577fa0d0e636b9a57 Mon Sep 17 00:00:00 2001 From: Kim Streich Date: Wed, 18 May 2022 18:52:57 +0400 Subject: [PATCH 1/2] feat(power-domain): Replace ext_power with power domain --- app/CMakeLists.txt | 2 +- app/Kconfig | 33 +- app/boards/arm/nice_nano/nice_nano.dts | 7 + app/boards/arm/nice_nano/nice_nano.dtsi | 1 + app/boards/arm/nice_nano/nice_nano_v2.dts | 11 +- .../behaviors/zmk,behavior-ext-power.yaml | 8 + app/include/drivers/ext_power.h | 104 ---- app/include/zmk/power_domain.h | 89 +++ app/include/zmk/rgb_underglow.h | 6 +- app/src/behaviors/behavior_ext_power.c | 58 +- app/src/display/main.c | 88 ++- app/src/ext_power_generic.c | 214 ------- app/src/main.c | 1 - app/src/power_domain.c | 545 ++++++++++++++++++ app/src/rgb_underglow.c | 127 +++- 15 files changed, 913 insertions(+), 381 deletions(-) delete mode 100644 app/include/drivers/ext_power.h create mode 100644 app/include/zmk/power_domain.h delete mode 100644 app/src/ext_power_generic.c create mode 100644 app/src/power_domain.c diff --git a/app/CMakeLists.txt b/app/CMakeLists.txt index 734a1e59ebc..dad0ea38205 100644 --- a/app/CMakeLists.txt +++ b/app/CMakeLists.txt @@ -29,7 +29,7 @@ target_sources(app PRIVATE src/matrix_transform.c) 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/ext_power_generic.c) +target_sources_ifdef(CONFIG_ZMK_EXT_POWER app PRIVATE src/power_domain.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/Kconfig b/app/Kconfig index 0879e5dc60e..5ccab4cc2b6 100644 --- a/app/Kconfig +++ b/app/Kconfig @@ -381,6 +381,10 @@ endmenu menu "Power Management" +config ZMK_EXT_POWER + bool "Enable support to control external power output" + default y + config ZMK_IDLE_TIMEOUT int "Milliseconds of inactivity before entering idle state (OLED shutoff, etc)" default 30000 @@ -389,22 +393,35 @@ config ZMK_SLEEP bool "Enable deep sleep support" imply USB -if ZMK_SLEEP +config POWER_DOMAIN + default y + +if ZMK_EXT_POWER + +config POWER_DOMAIN_GPIO + default y + +endif config PM_DEVICE default y -config ZMK_IDLE_SLEEP_TIMEOUT - int "Milliseconds of inactivity before entering deep sleep" - default 900000 +config PM_DEVICE_POWER_DOMAIN + default y -#ZMK_SLEEP -endif +config PM_DEVICE_POWER_DOMAIN_DYNAMIC + default y -config ZMK_EXT_POWER - bool "Enable support to control external power output" +config PM_DEVICE_POWER_DOMAIN_DYNAMIC_NUM + default 10 + +config PM_DEVICE_RUNTIME default y +config ZMK_IDLE_SLEEP_TIMEOUT + int "Milliseconds of inactivity before entering deep sleep" + default 900000 + #Power Management endmenu diff --git a/app/boards/arm/nice_nano/nice_nano.dts b/app/boards/arm/nice_nano/nice_nano.dts index e29df2057a6..bb2048352ed 100644 --- a/app/boards/arm/nice_nano/nice_nano.dts +++ b/app/boards/arm/nice_nano/nice_nano.dts @@ -10,6 +10,7 @@ / { chosen { zmk,battery = &vbatt; + zmk,default-power-domain = &pd_ext_power; }; ext-power { @@ -18,6 +19,12 @@ control-gpios = <&gpio0 13 GPIO_ACTIVE_LOW>; }; + pd_ext_power: pd_ext_power { + compatible = "power-domain-gpio"; + label = "PD_EXT_POWER"; + enable-gpios = <&gpio0 13 GPIO_ACTIVE_LOW>; + }; + vbatt: vbatt { compatible = "zmk,battery-voltage-divider"; label = "BATTERY"; diff --git a/app/boards/arm/nice_nano/nice_nano.dtsi b/app/boards/arm/nice_nano/nice_nano.dtsi index 6c9d081c75b..72a961f0eca 100644 --- a/app/boards/arm/nice_nano/nice_nano.dtsi +++ b/app/boards/arm/nice_nano/nice_nano.dtsi @@ -45,6 +45,7 @@ &i2c0 { compatible = "nordic,nrf-twi"; + power-domain = <&pd_ext_power>; sda-pin = <17>; scl-pin = <20>; }; diff --git a/app/boards/arm/nice_nano/nice_nano_v2.dts b/app/boards/arm/nice_nano/nice_nano_v2.dts index ed2b35f4ebf..aed5c50ed0e 100644 --- a/app/boards/arm/nice_nano/nice_nano_v2.dts +++ b/app/boards/arm/nice_nano/nice_nano_v2.dts @@ -10,13 +10,14 @@ / { chosen { zmk,battery = &vbatt; + zmk,default-power-domain = &pd_ext_power; }; - ext-power { - compatible = "zmk,ext-power-generic"; - label = "EXT_POWER"; - control-gpios = <&gpio0 13 GPIO_ACTIVE_HIGH>; - init-delay-ms = <50>; + pd_ext_power: pd_ext_power { + compatible = "power-domain-gpio"; + label = "PD_EXT_POWER"; + enable-gpios = <&gpio0 13 GPIO_ACTIVE_HIGH>; + startup-delay-us = <50000>; }; vbatt: vbatt { diff --git a/app/dts/bindings/behaviors/zmk,behavior-ext-power.yaml b/app/dts/bindings/behaviors/zmk,behavior-ext-power.yaml index 69949d7fafa..bdc72061bc5 100644 --- a/app/dts/bindings/behaviors/zmk,behavior-ext-power.yaml +++ b/app/dts/bindings/behaviors/zmk,behavior-ext-power.yaml @@ -6,3 +6,11 @@ description: External power control Behavior compatible: "zmk,behavior-ext-power" include: one_param.yaml + +properties: + power-domain: + required: false + type: phandle + description: | + + Power domain the behavior should execute the action on. diff --git a/app/include/drivers/ext_power.h b/app/include/drivers/ext_power.h deleted file mode 100644 index b422750c60e..00000000000 --- a/app/include/drivers/ext_power.h +++ /dev/null @@ -1,104 +0,0 @@ -/* - * Copyright (c) 2020 The ZMK Contributors - * - * SPDX-License-Identifier: MIT - */ - -#pragma once - -#include -#include -#include - -#ifdef __cplusplus -extern "C" { -#endif - -/** - * @cond INTERNAL_HIDDEN - * - * Behavior driver API definition and system call entry points. - * - * (Internal use only.) - */ - -typedef int (*ext_power_enable_t)(const struct device *dev); -typedef int (*ext_power_disable_t)(const struct device *dev); -typedef int (*ext_power_get_t)(const struct device *dev); - -__subsystem struct ext_power_api { - ext_power_enable_t enable; - ext_power_disable_t disable; - ext_power_get_t get; -}; -/** - * @endcond - */ - -/** - * @brief Enable the external power output - * @param dev Pointer to the device structure for the driver instance. - * - * @retval 0 If successful. - * @retval Negative errno code if failure. - */ -__syscall int ext_power_enable(const struct device *dev); - -static inline int z_impl_ext_power_enable(const struct device *dev) { - const struct ext_power_api *api = (const struct ext_power_api *)dev->api; - - if (api->enable == NULL) { - return -ENOTSUP; - } - - return api->enable(dev); -} - -/** - * @brief Disable the external power output - * @param dev Pointer to the device structure for the driver instance. - * - * @retval 0 If successful. - * @retval Negative errno code if failure. - */ -__syscall int ext_power_disable(const struct device *dev); - -static inline int z_impl_ext_power_disable(const struct device *dev) { - const struct ext_power_api *api = (const struct ext_power_api *)dev->api; - - if (api->disable == NULL) { - return -ENOTSUP; - } - - return api->disable(dev); -} - -/** - * @brief Get the current status of the external power output - * @param dev Pointer to the device structure for the driver instance. - * - * @retval 0 If ext power is disabled. - * @retval 1 if ext power is enabled. - * @retval Negative errno code if failure. - */ -__syscall int ext_power_get(const struct device *dev); - -static inline int z_impl_ext_power_get(const struct device *dev) { - const struct ext_power_api *api = (const struct ext_power_api *)dev->api; - - if (api->get == NULL) { - return -ENOTSUP; - } - - return api->get(dev); -} - -#ifdef __cplusplus -} -#endif - -/** - * @} - */ - -#include diff --git a/app/include/zmk/power_domain.h b/app/include/zmk/power_domain.h new file mode 100644 index 00000000000..3429a484ad5 --- /dev/null +++ b/app/include/zmk/power_domain.h @@ -0,0 +1,89 @@ +/* + * Copyright (c) 2020 The ZMK Contributors + * + * SPDX-License-Identifier: MIT + */ + +#include + +#pragma once + +#if CONFIG_ZMK_EXT_POWER + +const int zmk_power_domain_disable(const struct device *pd_dev, bool save_state); +const int zmk_power_domain_enable(const struct device *pd_dev, bool save_state); +const int zmk_power_domain_toggle(const struct device *pd_dev, bool save_state); + + +/* + * What's the difference between get_state_actual and get_state_user_intended? + * + * We distinguish whether power is actually on and whether the user wants the + * power to be on. + * + * For example, when the user turns on the power, the user intended state is + * ON, bt internal features may despite that turn the power off temporarily. + * + * For example, the auto-off-on-idle feature cuts the power on idle and changes + * the actual state to on, but the intended state stays as on. When activity + * resumes, it checks whether the user wants the power to be on, and if yes, + * enables the actual power again. + * + */ +const int zmk_power_domain_get_state_actual(const struct device *pd_dev); +const int zmk_power_domain_get_state_user_intended(const struct device *pd_dev); + + +/* + * Power Domain manager helper function + * + * In order to receive power domain notifications in ZMK devices, such as the + * display and rgb_underglow, we need to create a power domain manager device + * that has a power domain configured. + * + * This sets the power domain to a dynamically created device and syncs the + * state of the device to the power domain. + */ +int zmk_power_domain_init_power_domain_manager_helper(const struct device *dev, const struct device *pd_dev); + +/** + * @cond INTERNAL_HIDDEN + * + * Internal helper functions + */ + +#define DEFAULT_POWER_DOMAIN DT_CHOSEN(zmk_default_power_domain) + +/** @brief ZMK Power Domain Actions. */ +enum zmk_power_domain_action { + /** Turn the Power Domain off. */ + ZMK_PD_ACTION_TURN_OFF, + /** Turn the Power Domain on. */ + ZMK_PD_ACTION_TURN_ON, + /** Toggle the Power Domain*/ + ZMK_PD_ACTION_TOGGLE, +}; +const int zmk_power_domain_run_action(const struct device *pd_dev, enum zmk_power_domain_action zmk_action, bool save_state); +int zmk_power_domain_save_state(); + +const struct device * zmk_power_domain_get_default(); +struct zmk_power_domain_data *zmk_power_domain_get_pd_data_for_pd(const struct device *pd_dev); +struct zmk_power_domain_data *zmk_power_domain_get_pd_data_by_name(const char *name); +void zmk_power_domain_set_pd_data_default(); +const char *zmk_pm_device_action_str(enum pm_device_action action); +const char *zmk_pm_action_str(enum zmk_power_domain_action action); + +/** + * @endcond + */ + +#else + +const inline int zmk_power_domain_disable(const struct device *pd_dev, bool save_state) { return 0; }; +const inline int zmk_power_domain_enable(const struct device *pd_dev, bool save_state) { return 0; }; +const inline int zmk_power_domain_toggle(const struct device *pd_dev, bool save_state) { return 0; }; +const inline int zmk_power_domain_get_state_actual(const struct device *pd_dev) { return 0; }; +const inline int zmk_power_domain_get_state_user_intended(const struct device *pd_dev) { return 0; }; +inline int zmk_power_domain_init_power_domain_manager_helper(const struct device *dev, const struct device *pd_dev) { return 0; }; + +#endif diff --git a/app/include/zmk/rgb_underglow.h b/app/include/zmk/rgb_underglow.h index 797f0b19f1d..34c869e16e7 100644 --- a/app/include/zmk/rgb_underglow.h +++ b/app/include/zmk/rgb_underglow.h @@ -16,6 +16,8 @@ int zmk_rgb_underglow_toggle(); int zmk_rgb_underglow_get_state(bool *state); int zmk_rgb_underglow_on(); int zmk_rgb_underglow_off(); +int zmk_rgb_underglow_resume(); +int zmk_rgb_underglow_pause(); int zmk_rgb_underglow_cycle_effect(int direction); int zmk_rgb_underglow_calc_effect(int direction); int zmk_rgb_underglow_select_effect(int effect); @@ -26,4 +28,6 @@ int zmk_rgb_underglow_change_hue(int direction); int zmk_rgb_underglow_change_sat(int direction); int zmk_rgb_underglow_change_brt(int direction); int zmk_rgb_underglow_change_spd(int direction); -int zmk_rgb_underglow_set_hsb(struct zmk_led_hsb color); \ No newline at end of file +int zmk_rgb_underglow_set_hsb(struct zmk_led_hsb color); + +int zmk_rgb_underglow_init_power_domain_manager(const struct device *dev); diff --git a/app/src/behaviors/behavior_ext_power.c b/app/src/behaviors/behavior_ext_power.c index 5db8aac2b26..b40586be07f 100644 --- a/app/src/behaviors/behavior_ext_power.c +++ b/app/src/behaviors/behavior_ext_power.c @@ -9,7 +9,7 @@ #include #include #include -#include +#include #include @@ -18,17 +18,18 @@ LOG_MODULE_DECLARE(zmk, CONFIG_ZMK_LOG_LEVEL); #if DT_HAS_COMPAT_STATUS_OKAY(DT_DRV_COMPAT) +struct zmk_behavior_ext_power_config { + const struct device *pd; +}; + static int on_keymap_binding_convert_central_state_dependent_params(struct zmk_behavior_binding *binding, struct zmk_behavior_binding_event event) { - const struct device *ext_power = device_get_binding("EXT_POWER"); - if (ext_power == NULL) { - LOG_ERR("Unable to retrieve ext_power device: %d", binding->param1); - return -EIO; - } + const struct device *dev = device_get_binding(binding->behavior_dev); + const struct zmk_behavior_ext_power_config *config = (struct zmk_behavior_ext_power_config *)dev->config; if (binding->param1 == EXT_POWER_TOGGLE_CMD) { - binding->param1 = ext_power_get(ext_power) > 0 ? EXT_POWER_OFF_CMD : EXT_POWER_ON_CMD; + binding->param1 = zmk_power_domain_get_state_actual(config->pd) == 1 ? EXT_POWER_OFF_CMD : EXT_POWER_ON_CMD; } return 0; @@ -36,22 +37,17 @@ on_keymap_binding_convert_central_state_dependent_params(struct zmk_behavior_bin static int on_keymap_binding_pressed(struct zmk_behavior_binding *binding, struct zmk_behavior_binding_event event) { - const struct device *ext_power = device_get_binding("EXT_POWER"); - if (ext_power == NULL) { - LOG_ERR("Unable to retrieve ext_power device: %d", binding->param1); - return -EIO; - } + + const struct device *dev = device_get_binding(binding->behavior_dev); + const struct zmk_behavior_ext_power_config *config = (struct zmk_behavior_ext_power_config *)dev->config; switch (binding->param1) { case EXT_POWER_OFF_CMD: - return ext_power_disable(ext_power); + return zmk_power_domain_disable(config->pd, true); case EXT_POWER_ON_CMD: - return ext_power_enable(ext_power); + return zmk_power_domain_enable(config->pd, true); case EXT_POWER_TOGGLE_CMD: - if (ext_power_get(ext_power) > 0) - return ext_power_disable(ext_power); - else - return ext_power_enable(ext_power); + zmk_power_domain_toggle(config->pd, true); default: LOG_ERR("Unknown ext_power command: %d", binding->param1); } @@ -64,7 +60,9 @@ static int on_keymap_binding_released(struct zmk_behavior_binding *binding, return ZMK_BEHAVIOR_OPAQUE; } -static int behavior_ext_power_init(const struct device *dev) { return 0; }; +static int behavior_ext_power_init(const struct device *dev) { + return 0; +}; static const struct behavior_driver_api behavior_ext_power_driver_api = { .binding_convert_central_state_dependent_params = @@ -74,7 +72,25 @@ static const struct behavior_driver_api behavior_ext_power_driver_api = { .locality = BEHAVIOR_LOCALITY_GLOBAL, }; -DEVICE_DT_INST_DEFINE(0, behavior_ext_power_init, NULL, NULL, NULL, APPLICATION, - CONFIG_APPLICATION_INIT_PRIORITY, &behavior_ext_power_driver_api); +#define KP_INST(n) \ + static struct zmk_behavior_ext_power_config zmk_behavior_ext_power_config_##n = { \ + .pd = COND_CODE_1( \ + DT_INST_NODE_HAS_PROP(n, power_domain), \ + DEVICE_DT_GET(DT_INST_PHANDLE(n, power_domain)), \ + NULL \ + ), \ + }; \ + DEVICE_DT_INST_DEFINE( \ + n, \ + behavior_ext_power_init, \ + NULL, \ + NULL, \ + &zmk_behavior_ext_power_config_##n, \ + APPLICATION, \ + CONFIG_APPLICATION_INIT_PRIORITY, \ + &behavior_ext_power_driver_api \ + ); + +DT_INST_FOREACH_STATUS_OKAY(KP_INST) #endif /* DT_HAS_COMPAT_STATUS_OKAY(DT_DRV_COMPAT) */ diff --git a/app/src/display/main.c b/app/src/display/main.c index 5dfad910a4e..1b45b367e06 100644 --- a/app/src/display/main.c +++ b/app/src/display/main.c @@ -8,6 +8,7 @@ #include #include #include +#include #include LOG_MODULE_DECLARE(zmk, CONFIG_ZMK_LOG_LEVEL); @@ -18,11 +19,20 @@ LOG_MODULE_DECLARE(zmk, CONFIG_ZMK_LOG_LEVEL); #include #include #include +#include #define ZMK_DISPLAY_NAME CONFIG_LVGL_DISPLAY_DEV_NAME +#if CONFIG_ZMK_EXT_POWER + +#define DISPLAY_POWER_DOMAIN DT_LABEL(DT_PHANDLE(DT_CHOSEN(zephyr_display), power_domain)) + +#endif + static const struct device *display; static bool initialized = false; +bool power_domain_enabled = true; +bool keyboard_active = true; static lv_obj_t *screen; @@ -32,6 +42,8 @@ void display_tick_cb(struct k_work *work) { lv_task_handler(); } #define TICK_MS 10 +int zmk_display_init_power_domain_manager(const struct device *dev); + K_WORK_DEFINE(display_tick_work, display_tick_cb); #if IS_ENABLED(CONFIG_ZMK_DISPLAY_WORK_QUEUE_DEDICATED) @@ -85,6 +97,8 @@ static void stop_display_updates() { int zmk_display_is_initialized() { return initialized; } +// This function must be called from `main()`, otherwise the firmware will +// crash in zmk_display_status_screen. int zmk_display_init() { LOG_DBG(""); @@ -109,7 +123,10 @@ int zmk_display_init() { lv_scr_load(screen); - start_display_updates(); + if(power_domain_enabled == true) { + LOG_DBG("Starting display updates"); + start_display_updates(); + } initialized = true; @@ -125,11 +142,25 @@ int display_event_handler(const zmk_event_t *eh) { switch (ev->state) { case ZMK_ACTIVITY_ACTIVE: - start_display_updates(); + keyboard_active = true; + + if(power_domain_enabled) { + LOG_DBG("Starting display updates, because keyboard is active and power domain enabled."); + start_display_updates(); + } else { + LOG_DBG("Not enabling display updates even though keyboard is active, because power domain is disabled."); + } break; case ZMK_ACTIVITY_IDLE: case ZMK_ACTIVITY_SLEEP: - stop_display_updates(); + keyboard_active = false; + + if(power_domain_enabled) { + LOG_DBG("Stopping display updates, because keyboard is not active and power domain enabled."); + stop_display_updates(); + } else { + LOG_DBG("Not stopping display updates even though keyboard is idle, because power domain is disabled."); + } break; default: LOG_WRN("Unhandled activity state: %d", ev->state); @@ -140,3 +171,54 @@ int display_event_handler(const zmk_event_t *eh) { ZMK_LISTENER(display, display_event_handler); ZMK_SUBSCRIPTION(display, zmk_activity_state_changed); + +#if CONFIG_ZMK_EXT_POWER + +static int zmk_display_pm_action(const struct device *dev, enum pm_device_action action) { + LOG_DBG("In zmk_display_pm_action on dev %s with action: %d", dev->name, action); + + switch (action) { + case PM_DEVICE_ACTION_RESUME: + power_domain_enabled = true; + + if(initialized == true) { + LOG_DBG("Power domain enabled - Enabling display updates"); + start_display_updates(); + } + + break; + case PM_DEVICE_ACTION_TURN_OFF: + power_domain_enabled = false; + + if(initialized == true) { + LOG_DBG("Power domain disabled - Disabling display updates"); + stop_display_updates(); + } + + break; + default: + LOG_ERR("PM Action %d not implemented", action); + return -ENOTSUP; + } + + return 0; +} + + +int zmk_display_init_power_domain_manager(const struct device *dev) { + + LOG_DBG(""); + const struct device *power_domain = device_get_binding(DISPLAY_POWER_DOMAIN); + if (power_domain == NULL) { + + LOG_ERR("Unable to retrieve display's power_domain device... is the power-domain property set?"); + return -1; + } + + return zmk_power_domain_init_power_domain_manager_helper(dev, power_domain); +} + +PM_DEVICE_DEFINE(pd_manager_display, zmk_display_pm_action); +DEVICE_DEFINE(pd_manager_display, "pd_manager_display", &zmk_display_init_power_domain_manager, PM_DEVICE_GET(pd_manager_display), NULL, NULL, APPLICATION, CONFIG_APPLICATION_INIT_PRIORITY, NULL); + +#endif // CONFIG_ZMK_EXT_POWER diff --git a/app/src/ext_power_generic.c b/app/src/ext_power_generic.c deleted file mode 100644 index aab09cecbbc..00000000000 --- a/app/src/ext_power_generic.c +++ /dev/null @@ -1,214 +0,0 @@ -/* - * Copyright (c) 2020 The ZMK Contributors - * - * SPDX-License-Identifier: MIT - */ - -#define DT_DRV_COMPAT zmk_ext_power_generic - -#include -#include -#include -#include -#include -#include -#include -#include - -#if DT_HAS_COMPAT_STATUS_OKAY(DT_DRV_COMPAT) - -#include -LOG_MODULE_DECLARE(zmk, CONFIG_ZMK_LOG_LEVEL); - -struct ext_power_generic_config { - const char *label; - const uint8_t pin; - const uint8_t flags; - const uint16_t init_delay_ms; -}; - -struct ext_power_generic_data { - const struct device *gpio; - bool status; -#if IS_ENABLED(CONFIG_SETTINGS) - bool settings_init; -#endif -}; - -#if IS_ENABLED(CONFIG_SETTINGS) -static void ext_power_save_state_work(struct k_work *work) { - char setting_path[40]; - const struct device *ext_power = device_get_binding(DT_INST_LABEL(0)); - struct ext_power_generic_data *data = ext_power->data; - - snprintf(setting_path, 40, "ext_power/state/%s", DT_INST_LABEL(0)); - settings_save_one(setting_path, &data->status, sizeof(data->status)); -} - -static struct k_work_delayable ext_power_save_work; -#endif - -int ext_power_save_state() { -#if IS_ENABLED(CONFIG_SETTINGS) - int ret = k_work_reschedule(&ext_power_save_work, K_MSEC(CONFIG_ZMK_SETTINGS_SAVE_DEBOUNCE)); - return MIN(ret, 0); -#else - return 0; -#endif -} - -static int ext_power_generic_enable(const struct device *dev) { - struct ext_power_generic_data *data = dev->data; - const struct ext_power_generic_config *config = dev->config; - - if (gpio_pin_set(data->gpio, config->pin, 1)) { - LOG_WRN("Failed to set ext-power control pin"); - return -EIO; - } - data->status = true; - return ext_power_save_state(); -} - -static int ext_power_generic_disable(const struct device *dev) { - struct ext_power_generic_data *data = dev->data; - const struct ext_power_generic_config *config = dev->config; - - if (gpio_pin_set(data->gpio, config->pin, 0)) { - LOG_WRN("Failed to clear ext-power control pin"); - return -EIO; - } - data->status = false; - return ext_power_save_state(); -} - -static int ext_power_generic_get(const struct device *dev) { - struct ext_power_generic_data *data = dev->data; - return data->status; -} - -#if IS_ENABLED(CONFIG_SETTINGS) -static int ext_power_settings_set(const char *name, size_t len, settings_read_cb read_cb, - void *cb_arg) { - const char *next; - int rc; - - if (settings_name_steq(name, DT_INST_LABEL(0), &next) && !next) { - const struct device *ext_power = DEVICE_DT_GET(DT_DRV_INST(0)); - struct ext_power_generic_data *data = ext_power->data; - - if (len != sizeof(data->status)) { - return -EINVAL; - } - - rc = read_cb(cb_arg, &data->status, sizeof(data->status)); - if (rc >= 0) { - data->settings_init = true; - - if (ext_power == NULL) { - LOG_ERR("Unable to retrieve ext_power device: %s", DT_INST_LABEL(0)); - return -EIO; - } - - if (data->status) { - ext_power_generic_enable(ext_power); - } else { - ext_power_generic_disable(ext_power); - } - - return 0; - } - return rc; - } - return -ENOENT; -} - -struct settings_handler ext_power_conf = {.name = "ext_power/state", - .h_set = ext_power_settings_set}; -#endif - -static int ext_power_generic_init(const struct device *dev) { - struct ext_power_generic_data *data = dev->data; - const struct ext_power_generic_config *config = dev->config; - - data->gpio = device_get_binding(config->label); - if (data->gpio == NULL) { - LOG_ERR("Failed to get ext-power control device"); - return -EINVAL; - } - - if (gpio_pin_configure(data->gpio, config->pin, config->flags | GPIO_OUTPUT)) { - LOG_ERR("Failed to configure ext-power control pin"); - return -EIO; - } - -#if IS_ENABLED(CONFIG_SETTINGS) - settings_subsys_init(); - - int err = settings_register(&ext_power_conf); - if (err) { - LOG_ERR("Failed to register the ext_power settings handler (err %d)", err); - return err; - } - - k_work_init_delayable(&ext_power_save_work, ext_power_save_state_work); - - // Set default value (on) if settings isn't set - settings_load_subtree("ext_power"); - if (!data->settings_init) { - - data->status = true; - k_work_schedule(&ext_power_save_work, K_NO_WAIT); - - ext_power_enable(dev); - } -#else - // Default to the ext_power being open when no settings - ext_power_enable(dev); -#endif - - if (config->init_delay_ms) { - k_msleep(config->init_delay_ms); - } - - return 0; -} - -#ifdef CONFIG_PM_DEVICE -static int ext_power_generic_pm_action(const struct device *dev, enum pm_device_action action) { - switch (action) { - case PM_DEVICE_ACTION_RESUME: - ext_power_generic_enable(dev); - return 0; - case PM_DEVICE_ACTION_SUSPEND: - ext_power_generic_disable(dev); - return 0; - default: - return -ENOTSUP; - } -} -#endif /* CONFIG_PM_DEVICE */ - -static const struct ext_power_generic_config config = { - .label = DT_INST_GPIO_LABEL(0, control_gpios), - .pin = DT_INST_GPIO_PIN(0, control_gpios), - .flags = DT_INST_GPIO_FLAGS(0, control_gpios), - .init_delay_ms = DT_INST_PROP_OR(0, init_delay_ms, 0)}; - -static struct ext_power_generic_data data = { - .status = false, -#if IS_ENABLED(CONFIG_SETTINGS) - .settings_init = false, -#endif -}; - -static const struct ext_power_api api = {.enable = ext_power_generic_enable, - .disable = ext_power_generic_disable, - .get = ext_power_generic_get}; - -#define ZMK_EXT_POWER_INIT_PRIORITY 81 - -PM_DEVICE_DT_INST_DEFINE(0, ext_power_generic_pm_action); -DEVICE_DT_INST_DEFINE(0, ext_power_generic_init, PM_DEVICE_DT_INST_GET(0), &data, &config, - POST_KERNEL, ZMK_EXT_POWER_INIT_PRIORITY, &api); - -#endif /* DT_HAS_COMPAT_STATUS_OKAY(DT_DRV_COMPAT) */ diff --git a/app/src/main.c b/app/src/main.c index ae604a7b9ee..74f92329dac 100644 --- a/app/src/main.c +++ b/app/src/main.c @@ -15,7 +15,6 @@ LOG_MODULE_REGISTER(zmk, CONFIG_ZMK_LOG_LEVEL); #include #include #include -#include #define ZMK_KSCAN_DEV DT_LABEL(ZMK_MATRIX_NODE_ID) diff --git a/app/src/power_domain.c b/app/src/power_domain.c new file mode 100644 index 00000000000..490c4591d4e --- /dev/null +++ b/app/src/power_domain.c @@ -0,0 +1,545 @@ +/* + * Copyright (c) 2020 The ZMK Contributors + * + * SPDX-License-Identifier: MIT + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +LOG_MODULE_DECLARE(zmk, CONFIG_ZMK_LOG_LEVEL); + +/* + * Macros to get a list of power domains + */ + +// Generates a count of power_domain_gpio devices +// For example, if you have 2 power domains, then `PD_COUNT()`, will generate: +// 0 + 1 + 1 +// Which is 2 :) +#define _PD_COUNT_F(id) \ + + 1 +#define PD_COUNT() \ + 0 DT_FOREACH_STATUS_OKAY(power_domain_gpio, _PD_COUNT_F) + +// Generate the power_domains array definition. +// Should be used with DT_FOREACH_STATUS_OKAY. +#define GEN_PD_ARRAY(id) \ + { \ + .pd = DEVICE_DT_GET(id), \ + .state_user_intended = false, \ + .settings_init = false, \ + }, + + +/* + * Global Variables + */ + +// Struct that stores both power domain device pointers as well as whether +// the user desires for the domain to be on or off +struct zmk_power_domain_data { + const struct device *pd; + bool state_user_intended; + bool settings_init; +}; + +// Count of available power domains +#define POWER_DOMAIN_COUNT PD_COUNT() + +// Initialize array of structs with available power domains using above macro +struct zmk_power_domain_data pd_data_list[] = { + DT_FOREACH_STATUS_OKAY(power_domain_gpio, GEN_PD_ARRAY) +}; + +// Default power domain. Set by chosen node `zmk,default-power-domain` +// and if not present to first initialized power domain. +struct zmk_power_domain_data *pd_data_default = NULL; + +// Whether settings have been initialized yet +bool zmk_pd_settings_init = false; + +/* + * End-User Functions + * + * These functions call `pm_device_action_run()`... + * - which in turn calls the callback `zmk_power_domain_pm_action()`... + * - which then calls `zmk_power_domain_set_gpio_pin()`, + * - which actually sets the control pin that enables or disables the power. + * + * `pm_device_action_run()` also calls `_pm_action()` callbacks of the devices + * assigned to the power-domain so that they can shutdown down and + * re-initialize. + */ + +const int zmk_power_domain_disable(const struct device *pd_dev, bool save_state) { + return zmk_power_domain_run_action(pd_dev, ZMK_PD_ACTION_TURN_OFF, save_state); +} + +const int zmk_power_domain_enable(const struct device *pd_dev, bool save_state) { + return zmk_power_domain_run_action(pd_dev, ZMK_PD_ACTION_TURN_ON, save_state); +} + +const int zmk_power_domain_toggle(const struct device *pd_dev, bool save_state) { + return zmk_power_domain_run_action(pd_dev, ZMK_PD_ACTION_TOGGLE, save_state); +} + +const int zmk_power_domain_get_state_actual(const struct device *pd_dev) { + if(pd_dev == NULL) { + pd_dev = pd_data_default->pd; + if(pd_dev == NULL) { + LOG_ERR("Could not get power domain state: Found now power domain."); + return -1; + } + } + + enum pm_device_state pm_state; + if(pm_device_state_get(pd_dev, &pm_state) != 0) { + + LOG_ERR("Could not get pm device state for power domain `%s`", pd_dev->name); + return -1; + } + + if(pm_state == PM_DEVICE_STATE_ACTIVE) { + return 1; + } else { + return 0; + } +} + +const int zmk_power_domain_get_state_user_intended(const struct device *pd_dev) { + + if(pd_dev == NULL) { + pd_dev = pd_data_default->pd; + if(pd_dev == NULL) { + return -1; + } + } + struct zmk_power_domain_data *pd_data = zmk_power_domain_get_pd_data_for_pd(pd_dev); + return (const int)pd_data->state_user_intended; +} + + +/* + * Power Domain Run Action Helper Function + */ + +const int zmk_power_domain_run_action(const struct device *pd_dev, enum zmk_power_domain_action zmk_action, bool save_state) { + if(pd_dev == NULL) { + pd_dev = pd_data_default->pd; + if(pd_dev == NULL) { + + LOG_ERR("Could not run power domain action: Found no power domains."); + return -1; + } + } + + LOG_DBG("In zmk_power_domain_run_action %s on pd `%s`.", zmk_pm_action_str(zmk_action), pd_dev->name); + + enum pm_device_state pm_state; + if(pm_device_state_get(pd_dev, &pm_state) != 0) { + LOG_ERR("Could not get pm device state for power domain `%s`", pd_dev->name); + return -EIO; + } + + LOG_DBG("Current pm_device_state of power domain `%s`: %s.", pd_dev->name, pm_device_state_str(pm_state)); + + if(zmk_action == ZMK_PD_ACTION_TOGGLE) { + if(pm_state == PM_DEVICE_STATE_ACTIVE) { + zmk_action = ZMK_PD_ACTION_TURN_OFF; + } else { + zmk_action = ZMK_PD_ACTION_TURN_ON; + } + } + + enum pm_device_action zephyr_action; + switch (zmk_action) { + case ZMK_PD_ACTION_TURN_OFF: + zephyr_action = PM_DEVICE_ACTION_TURN_OFF; + break; + + case ZMK_PD_ACTION_TURN_ON: + // Only PM_DEVICE_ACTION_RESUME is a valid option for turning on. + // While PM_DEVICE_ACTION_TURN_ON may seem like the correct action, + // it actually sets the power domain to state `suspended`, which + // doesn't allow further ON / OFF actions. + zephyr_action = PM_DEVICE_ACTION_RESUME; + break; + + default: + LOG_ERR("Action %d not implemented", zmk_action); + return -ENOTSUP; + } + + LOG_INF("Running pm_device_action %s on pd `%s`.", pm_device_action_str(zephyr_action), pd_dev->name); + + pm_device_action_run(pd_dev, zephyr_action); + + LOG_DBG("Finished running pm_device_action %s on pd `%s`.", pm_device_action_str(zephyr_action), pd_dev->name); + + if(save_state == true) { + struct zmk_power_domain_data *pd_data = zmk_power_domain_get_pd_data_for_pd(pd_dev); + pd_data->state_user_intended = (bool) zmk_action; + + return zmk_power_domain_save_state(); + } + + return 0; +} + + +/* + * State Saving + */ +#if IS_ENABLED(CONFIG_SETTINGS) + +static struct k_work_delayable zmk_power_domain_save_work; + +static void zmk_power_domain_save_state_work(struct k_work *work) { + LOG_DBG(""); + + for(int i=0; i < POWER_DOMAIN_COUNT; i++) { + struct zmk_power_domain_data *pd_data = &pd_data_list[i]; + + char setting_path[40]; + snprintf(setting_path, sizeof(setting_path), "power_domain/state/%s", pd_data->pd->name); + + LOG_DBG("Saving `%d` to `%s`", pd_data->state_user_intended, setting_path); + + settings_save_one(setting_path, &pd_data->state_user_intended, sizeof(pd_data->state_user_intended)); + } +} +#endif + +int zmk_power_domain_save_state() { + LOG_DBG(""); + +#if IS_ENABLED(CONFIG_SETTINGS) + int ret = k_work_reschedule(&zmk_power_domain_save_work, K_MSEC(CONFIG_ZMK_SETTINGS_SAVE_DEBOUNCE)); + return MIN(ret, 0); +#else + return 0; +#endif +} + +// This function is called when settings are loaded from flash by +// `settings_load_subtree`. +// It's called once for each power domain state that has been stored. +static int zmk_power_domain_settings_set(const char *name, size_t len, settings_read_cb read_cb, void *cb_arg) { + LOG_DBG("Restoring settings for power domain %s", name); + + // Since we used `.name = "power_domain/state",` in `zmk_pd_settings_conf`, + // the name variable contains only the last part of the key, which is the + // name of the power domain. + struct zmk_power_domain_data *pd_data = zmk_power_domain_get_pd_data_by_name(name); + + if(pd_data == NULL) { + LOG_WRN("Could not restore state for power domain %s, because it does not exist in device tree.", name); + return -EIO; + } + + if (len != sizeof(pd_data->state_user_intended)) { + LOG_WRN("Could not restore state for power domain %s, because the size of the value is invalid.", name); + return -EINVAL; + } + + int rc = read_cb( + cb_arg, + &pd_data->state_user_intended, + sizeof(pd_data->state_user_intended) + ); + if (rc >= 0) { + LOG_INF("Restored state for power domain %s: %d", name, pd_data->state_user_intended); + + pd_data->settings_init = true; + + if(pd_data->state_user_intended == true) { + zmk_power_domain_enable(pd_data->pd, false); + } else { + zmk_power_domain_disable(pd_data->pd, false); + } + + return 0; + } + + LOG_WRN("Could not restore state for power domain %s, because value could not be read: %d.", name, rc); + return -ENOENT; +} + +struct settings_handler zmk_pd_settings_conf = { + .name = "power_domain/state", + .h_set = zmk_power_domain_settings_set, +}; + +int zmk_power_domain_init_state_saving(const struct device *pd_dev) { +#if IS_ENABLED(CONFIG_SETTINGS) + LOG_DBG(""); + + settings_subsys_init(); + + int err = settings_register(&zmk_pd_settings_conf); + if (err) { + LOG_ERR("Failed to register the power domain settings handler (err %d)", err); + return err; + } + + k_work_init_delayable(&zmk_power_domain_save_work, zmk_power_domain_save_state_work); + + // This will load the settings and then call + // `zmk_power_domain_settings_set`, which will enable power + settings_load_subtree("power_domain"); + + // Here we check if any of the power domains didn't have a saved state + // and enable them as the default action. + for(int i=0; i < POWER_DOMAIN_COUNT; i++) { + if(pd_data_list[i].settings_init != true) { + LOG_INF("Found no existing settings for power domain %s. Setting it to enabled.", pd_data_list[i].pd->name); + + k_work_schedule(&zmk_power_domain_save_work, K_NO_WAIT); + + zmk_power_domain_enable(pd_data_list[i].pd, true); + pd_data_list[i].settings_init = true; + } + } +#endif + + return 0; +} + + +/* + * Init Functions + */ + +// Zephyr's power_domain_gpio device is initialized at POST_KERNEL, 75. +// +// This function is ran right after that at POST_KERNEL, 76. +// +// Here we enable the power control pins so that power is available to all +// devices connected to the power domains during their initialization. +// +// This is important for some devices, such as the ssd1306 oled display driver. +// Without power, it will fail initialization. +// +// Once all devices are successfuly initialized, +// `zmk_power_domain_manager_post_boot_init` is run, which loads the user +// settings and turns on or off the power domains based on the user's +// preference. +// +// To catch log output add the following to your config: +// `CONFIG_LOG_PROCESS_THREAD_STARTUP_DELAY_MS=4000` +static int zmk_power_domain_manager_init(const struct device *dev) { + LOG_DBG(""); + + if(POWER_DOMAIN_COUNT == 0) { + LOG_WRN("No power domain devices configured. Exiting."); + + return -1; + } + + zmk_power_domain_set_pd_data_default(); + + for(int i=0; i < POWER_DOMAIN_COUNT; i++) { + const struct device *pd = pd_data_list[i].pd; + + LOG_DBG("Turning on domain: %s",pd->name); + zmk_power_domain_enable(pd, false); + } + + return 0; +} + +// power_domain_gpio inits at 75, so we init right after that +#define ZMK_POWER_DOMAIN_MANAGER_INIT_PRIORITY 76 +SYS_INIT(zmk_power_domain_manager_init, POST_KERNEL, ZMK_POWER_DOMAIN_MANAGER_INIT_PRIORITY); + + +// This function is run after all other devices have initialized. +// Before this function is run `zmk_power_domain_init` is run, which activates +// the power domain's control pins to ensure all devices that depend on them +// have power to initialize. +// +// After they are all initialized, this function is run, which loads the user +// settings and enables or disables power depending on the last state before +// the boot. +static int zmk_power_domain_manager_post_boot_init(const struct device *dev) { + LOG_DBG(""); + + if(POWER_DOMAIN_COUNT == 0) { + LOG_WRN("No power domain devices configured. Exiting."); + + return -1; + } + +#if IS_ENABLED(CONFIG_SETTINGS) + + // This will load the previous power state from the settings and then + // enable or disable the power in `zmk_power_domain_settings_set`. + zmk_power_domain_init_state_saving(dev); + +#else + LOG_INF("Settings are disabled. Setting all power domains to enabled"); + + for(int i=0; i < POWER_DOMAIN_COUNT; i++) { + const struct device *pd = pd_data_list[i].pd; + + LOG_DBG("Turning on domain: %s",pd->name); + zmk_power_domain_enable(pd, true); + } +#endif + + 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 +SYS_INIT(zmk_power_domain_manager_post_boot_init, APPLICATION, ZMK_POWER_DOMAIN_MANAGER_POST_BOOT_INIT_PRIORITY); + + +/* + * Activity Event Handler + */ + +int zmk_power_domain_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; + } + + switch (ev->state) { + case ZMK_ACTIVITY_ACTIVE: + LOG_DBG("Device became active - Doing nothing."); + break; + case ZMK_ACTIVITY_IDLE: + LOG_DBG("Device became idle - Doing nothing."); + break; + case ZMK_ACTIVITY_SLEEP: + LOG_DBG("Device going to sleep - Disabling power."); + + for(int i=0; i < POWER_DOMAIN_COUNT; i++) { + const struct device *pd_dev = pd_data_list[i].pd; + + zmk_power_domain_disable(pd_dev, false); + } + + break; + default: + LOG_WRN("Unhandled activity state: %d", ev->state); + return -EINVAL; + } + return 0; +} +ZMK_LISTENER(zmk_power_domain, zmk_power_domain_activity_event_handler); +ZMK_SUBSCRIPTION(zmk_power_domain, zmk_activity_state_changed); + + +/* + * Helper functions + */ + +int zmk_power_domain_init_power_domain_manager_helper(const struct device *dev, const struct device *pd_dev) { + + LOG_DBG("Setting `%s` power domain to `%s`", dev->name, pd_dev->name); + + // This function requires a zephyr fork with this commit: + // https://github.com/zephyrproject-rtos/zephyr/commit/0b13b44a662a1baadd4ed370441fbe8a74b4d531 + // + // It dynamically adds a power domain to a device. + // This way the user doesn't need to manually create power domain + // management device in the device tree. + if(pm_device_power_domain_add(dev, pd_dev) != 0) { + LOG_ERR("Could not set power domain of %s.", dev->name); + return -1; + } + + // Get the state of the actual power domain and then set + // the state of the manager device to the same state. + // We have to do this, because this device is initialized + // after the power domain is initialized and runs it's actions + int domain_state = zmk_power_domain_get_state_actual(pd_dev); + if(domain_state == 0) { + + pm_device_action_run(dev, PM_DEVICE_ACTION_TURN_OFF); + } else if(domain_state == 1) { + + // By default the power domain manager device is initialized as + // on / active. + // If we run RESUME, it will not run the callback, because it's + // already in the on / active / resumed state. + // So, first we set it to off without triggering the off action + // and then we run the RESUME action. + pm_device_runtime_init_off(dev); + pm_device_action_run(dev, PM_DEVICE_ACTION_RESUME); + } else { + LOG_ERR("Could not determine power domain state to correctly init power domain manager %s...", dev->name); + return -1; + } + + return 0; +} + +const char *zmk_pm_action_str(enum zmk_power_domain_action action) { + switch (action) { + case ZMK_PD_ACTION_TURN_OFF: + return "turn_off"; + case ZMK_PD_ACTION_TURN_ON: + return "turn_on"; + case ZMK_PD_ACTION_TOGGLE: + return "toggle"; + default: + return "unknown_action"; + } +} + +struct zmk_power_domain_data *zmk_power_domain_get_pd_data_for_pd(const struct device *pd_dev) { + LOG_DBG(""); + + struct zmk_power_domain_data *pd_data = NULL; + + for(int i=0; i < POWER_DOMAIN_COUNT; i++) { + if(pd_dev == pd_data_list[i].pd) { + pd_data = &pd_data_list[i]; + LOG_DBG("Found desired pd_data for power domain: %s", pd_data->pd->name); + + break; + } + } + + return pd_data; +} + +struct zmk_power_domain_data *zmk_power_domain_get_pd_data_by_name(const char *name) { + + for(int i=0; i < POWER_DOMAIN_COUNT; i++) { + if(strcmp(name, pd_data_list[i].pd->name) == 0) { + LOG_DBG("Found PD for name `%s`: %p", name, &pd_data_list[i]); + return &pd_data_list[i]; + } + } + + return NULL; +} + +void zmk_power_domain_set_pd_data_default() { + const struct device *pd_dev_default = DEVICE_DT_GET(DEFAULT_POWER_DOMAIN); + + if(pd_dev_default != NULL) { + pd_data_default = zmk_power_domain_get_pd_data_for_pd(pd_dev_default); + } else { + pd_data_default = &pd_data_list[0]; + + LOG_ERR("Default power domain chosen node is not set. Setting to first power domain."); + } + + LOG_INF("Set default power domain to: %s", pd_data_default->pd->name); +} diff --git a/app/src/rgb_underglow.c b/app/src/rgb_underglow.c index 517da1b81b6..932b8cd5a36 100644 --- a/app/src/rgb_underglow.c +++ b/app/src/rgb_underglow.c @@ -15,8 +15,9 @@ #include #include -#include +#include +#include #include LOG_MODULE_DECLARE(zmk, CONFIG_ZMK_LOG_LEVEL); @@ -24,6 +25,12 @@ LOG_MODULE_DECLARE(zmk, CONFIG_ZMK_LOG_LEVEL); #define STRIP_LABEL DT_LABEL(DT_CHOSEN(zmk_underglow)) #define STRIP_NUM_PIXELS DT_PROP(DT_CHOSEN(zmk_underglow), chain_length) +#if CONFIG_ZMK_EXT_POWER + +#define STRIP_POWER_DOMAIN DT_LABEL(DT_PHANDLE(DT_CHOSEN(zmk_underglow), power_domain)) + +#endif + #define HUE_MAX 360 #define SAT_MAX 100 #define BRT_MAX 100 @@ -53,9 +60,7 @@ static struct led_rgb pixels[STRIP_NUM_PIXELS]; static struct rgb_underglow_state state; -#if IS_ENABLED(CONFIG_ZMK_RGB_UNDERGLOW_EXT_POWER) -static const struct device *ext_power; -#endif +static const struct device *power_domain; static struct zmk_led_hsb hsb_scale_min_max(struct zmk_led_hsb hsb) { hsb.b = CONFIG_ZMK_RGB_UNDERGLOW_BRT_MIN + @@ -223,7 +228,7 @@ static void zmk_rgb_underglow_save_state_work() { static struct k_work_delayable underglow_save_work; #endif -static int zmk_rgb_underglow_init(const struct device *_arg) { +static int zmk_rgb_underglow_init(const struct device *dev) { led_strip = device_get_binding(STRIP_LABEL); if (led_strip) { LOG_INF("Found LED strip device %s", STRIP_LABEL); @@ -232,13 +237,6 @@ static int zmk_rgb_underglow_init(const struct device *_arg) { return -EINVAL; } -#if IS_ENABLED(CONFIG_ZMK_RGB_UNDERGLOW_EXT_POWER) - ext_power = device_get_binding("EXT_POWER"); - if (ext_power == NULL) { - LOG_ERR("Unable to retrieve ext_power device: EXT_POWER"); - } -#endif - state = (struct rgb_underglow_state){ color : { h : CONFIG_ZMK_RGB_UNDERGLOW_HUE_START, @@ -256,7 +254,7 @@ static int zmk_rgb_underglow_init(const struct device *_arg) { int err = settings_register(&rgb_conf); if (err) { - LOG_ERR("Failed to register the ext_power settings handler (err %d)", err); + LOG_ERR("Failed to register the rgb_underglow settings handler (err %d)", err); return err; } @@ -267,6 +265,12 @@ static int zmk_rgb_underglow_init(const struct device *_arg) { k_timer_start(&underglow_tick, K_NO_WAIT, K_MSEC(50)); +#if CONFIG_ZMK_EXT_POWER + + zmk_rgb_underglow_init_power_domain_manager(dev); + +#endif + return 0; } @@ -292,17 +296,17 @@ int zmk_rgb_underglow_on() { return -ENODEV; #if IS_ENABLED(CONFIG_ZMK_RGB_UNDERGLOW_EXT_POWER) - if (ext_power != NULL) { - int rc = ext_power_enable(ext_power); + if (power_domain != NULL) { + int rc = zmk_power_domain_enable(power_domain, true); if (rc != 0) { - LOG_ERR("Unable to enable EXT_POWER: %d", rc); + LOG_ERR("Unable to enable power domain: %d", rc); } } #endif state.on = true; state.animation_step = 0; - k_timer_start(&underglow_tick, K_NO_WAIT, K_MSEC(50)); + zmk_rgb_underglow_resume(); return zmk_rgb_underglow_save_state(); } @@ -312,14 +316,28 @@ int zmk_rgb_underglow_off() { return -ENODEV; #if IS_ENABLED(CONFIG_ZMK_RGB_UNDERGLOW_EXT_POWER) - if (ext_power != NULL) { - int rc = ext_power_disable(ext_power); + if (power_domain != NULL) { + int rc = zmk_power_domain_disable(power_domain, true); if (rc != 0) { - LOG_ERR("Unable to disable EXT_POWER: %d", rc); + LOG_ERR("Unable to disable power domain: %d", rc); } } #endif + zmk_rgb_underglow_pause(); + + state.on = false; + + return zmk_rgb_underglow_save_state(); +} + +int zmk_rgb_underglow_resume() { + k_timer_start(&underglow_tick, K_NO_WAIT, K_MSEC(50)); + + return 0; +} + +int zmk_rgb_underglow_pause() { for (int i = 0; i < STRIP_NUM_PIXELS; i++) { pixels[i] = (struct led_rgb){r : 0, g : 0, b : 0}; } @@ -327,9 +345,8 @@ int zmk_rgb_underglow_off() { led_strip_update_rgb(led_strip, pixels, STRIP_NUM_PIXELS); k_timer_stop(&underglow_tick); - state.on = false; - return zmk_rgb_underglow_save_state(); + return 0; } int zmk_rgb_underglow_calc_effect(int direction) { @@ -444,4 +461,68 @@ int zmk_rgb_underglow_change_spd(int direction) { return zmk_rgb_underglow_save_state(); } -SYS_INIT(zmk_rgb_underglow_init, APPLICATION, CONFIG_APPLICATION_INIT_PRIORITY); +#if CONFIG_ZMK_EXT_POWER + +static int zmk_rgb_underglow_pm_action(const struct device *dev, enum pm_device_action action) { + LOG_DBG("In zmk_rgb_underglow_pm_action on dev %s with action: %d", dev->name, action); + + switch (action) { + case PM_DEVICE_ACTION_RESUME: + if(state.on == true) { + LOG_DBG("Resuming underglow tick"); + zmk_rgb_underglow_resume(); + } else { + LOG_DBG("Underglow state off, therefore not resuming."); + } + + break; + case PM_DEVICE_ACTION_TURN_OFF: + if(state.on == true) { + LOG_DBG("Pausing underglow tick"); + zmk_rgb_underglow_pause(); + } else { + LOG_DBG("Underglow state off, therefore not pausing."); + } + + break; + default: + LOG_ERR("PM Action %d not implemented", action); + return -ENOTSUP; + } + + return 0; +} + + +int zmk_rgb_underglow_init_power_domain_manager(const struct device *dev) { + power_domain = device_get_binding(STRIP_POWER_DOMAIN); + if (power_domain == NULL) { + + LOG_ERR("Unable to retrieve power_domain device... is the power-domain property set?"); + return -1; + } + + return zmk_power_domain_init_power_domain_manager_helper(dev, power_domain); +} + +PM_DEVICE_DEFINE(pd_manager_rgb_underglow, zmk_rgb_underglow_pm_action); + +#endif // CONFIG_ZMK_EXT_POWER + +DEVICE_DEFINE( + pd_manager_rgb_underglow, + "pd_manager_rgb_underglow", + &zmk_rgb_underglow_init, + +#if CONFIG_ZMK_EXT_POWER + PM_DEVICE_GET(pd_manager_rgb_underglow), +#else + NULL, +#endif + + NULL, + NULL, + APPLICATION, + CONFIG_APPLICATION_INIT_PRIORITY, + NULL +); From 843b99b69bfb50356187f43c008ab0c6b9a3ed56 Mon Sep 17 00:00:00 2001 From: Kim Streich Date: Mon, 9 May 2022 21:25:05 +0400 Subject: [PATCH 2/2] 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);