diff --git a/app/CMakeLists.txt b/app/CMakeLists.txt index a647e883..60867589 100644 --- a/app/CMakeLists.txt +++ b/app/CMakeLists.txt @@ -51,6 +51,7 @@ if ((NOT CONFIG_ZMK_SPLIT) OR CONFIG_ZMK_SPLIT_ROLE_CENTRAL) target_sources(app PRIVATE src/behaviors/behavior_to_layer.c) target_sources(app PRIVATE src/behaviors/behavior_transparent.c) target_sources(app PRIVATE src/behaviors/behavior_none.c) + target_sources(app PRIVATE src/behaviors/behavior_soft_poweroff.c) target_sources_ifdef(CONFIG_ZMK_BEHAVIOR_SENSOR_ROTATE app PRIVATE src/behaviors/behavior_sensor_rotate.c) target_sources_ifdef(CONFIG_ZMK_BEHAVIOR_SENSOR_ROTATE_VAR app PRIVATE src/behaviors/behavior_sensor_rotate_var.c) target_sources_ifdef(CONFIG_ZMK_BEHAVIOR_SENSOR_ROTATE_COMMON app PRIVATE src/behaviors/behavior_sensor_rotate_common.c) diff --git a/app/dts/behaviors.dtsi b/app/dts/behaviors.dtsi index b3502cbb..54cd194a 100644 --- a/app/dts/behaviors.dtsi +++ b/app/dts/behaviors.dtsi @@ -18,4 +18,5 @@ #include #include #include -#include \ No newline at end of file +#include +#include \ No newline at end of file diff --git a/app/dts/behaviors/soft_poweroff.dtsi b/app/dts/behaviors/soft_poweroff.dtsi new file mode 100644 index 00000000..fb1ebea4 --- /dev/null +++ b/app/dts/behaviors/soft_poweroff.dtsi @@ -0,0 +1,24 @@ +/* + * Copyright (c) 2023 The ZMK Contributors + * + * SPDX-License-Identifier: MIT + */ + +#include + +/ { + behaviors { + /omit-if-no-ref/ soft_poweroff: behavior_soft_poweroff { + compatible = "zmk,behavior-soft-poweroff"; + label = "SOFT_POWEROFF"; + #binding-cells = <0>; + }; + + /omit-if-no-ref/ sleep: behavior_sleep { + compatible = "zmk,behavior-soft-poweroff"; + label = "SLEEP"; + type = ; + #binding-cells = <0>; + }; + }; +}; diff --git a/app/dts/bindings/behaviors/zmk,behavior-soft-poweroff.yaml b/app/dts/bindings/behaviors/zmk,behavior-soft-poweroff.yaml new file mode 100644 index 00000000..56b2d424 --- /dev/null +++ b/app/dts/bindings/behaviors/zmk,behavior-soft-poweroff.yaml @@ -0,0 +1,13 @@ +# Copyright (c) 2023 The ZMK Contributors +# SPDX-License-Identifier: MIT + +description: Keyboard System Soft Off Behavior + +compatible: "zmk,behavior-soft-poweroff" + +include: zero_param.yaml + +properties: + type: + type: int + default: 0 diff --git a/app/include/dt-bindings/zmk/soft_poweroff.h b/app/include/dt-bindings/zmk/soft_poweroff.h new file mode 100644 index 00000000..55062b30 --- /dev/null +++ b/app/include/dt-bindings/zmk/soft_poweroff.h @@ -0,0 +1,8 @@ +/* + * Copyright (c) 2023 The ZMK Contributors + * + * SPDX-License-Identifier: MIT + */ + +#define LOCKED 0x00 +#define SLEEP 0x01 diff --git a/app/include/zmk/activity.h b/app/include/zmk/activity.h index 9c858b15..05f77493 100644 --- a/app/include/zmk/activity.h +++ b/app/include/zmk/activity.h @@ -8,4 +8,6 @@ enum zmk_activity_state { ZMK_ACTIVITY_ACTIVE, ZMK_ACTIVITY_IDLE, ZMK_ACTIVITY_SLEEP }; -enum zmk_activity_state zmk_activity_get_state(); \ No newline at end of file +enum zmk_activity_state zmk_activity_get_state(); + +int zmk_activity_set_state(enum zmk_activity_state state); \ No newline at end of file diff --git a/app/src/activity.c b/app/src/activity.c index 41fe2e15..26c8320e 100644 --- a/app/src/activity.c +++ b/app/src/activity.c @@ -47,7 +47,7 @@ int raise_event() { (struct zmk_activity_state_changed){.state = activity_state})); } -int set_state(enum zmk_activity_state state) { +int zmk_activity_set_state(enum zmk_activity_state state) { if (activity_state == state) return 0; @@ -60,7 +60,7 @@ enum zmk_activity_state zmk_activity_get_state() { return activity_state; } int activity_event_listener(const zmk_event_t *eh) { activity_last_uptime = k_uptime_get(); - return set_state(ZMK_ACTIVITY_ACTIVE); + return zmk_activity_set_state(ZMK_ACTIVITY_ACTIVE); } void activity_work_handler(struct k_work *work) { @@ -69,12 +69,12 @@ void activity_work_handler(struct k_work *work) { #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); + zmk_activity_set_state(ZMK_ACTIVITY_SLEEP); pm_state_force(0U, &(struct pm_state_info){PM_STATE_SOFT_OFF, 0, 0}); } else #endif /* IS_ENABLED(CONFIG_ZMK_SLEEP) */ if (inactive_time > MAX_IDLE_MS) { - set_state(ZMK_ACTIVITY_IDLE); + zmk_activity_set_state(ZMK_ACTIVITY_IDLE); } } diff --git a/app/src/behaviors/behavior_soft_poweroff.c b/app/src/behaviors/behavior_soft_poweroff.c new file mode 100644 index 00000000..fdc18969 --- /dev/null +++ b/app/src/behaviors/behavior_soft_poweroff.c @@ -0,0 +1,93 @@ +/* + * Copyright (c) 2023 The ZMK Contributors + * + * SPDX-License-Identifier: MIT + */ + +#define DT_DRV_COMPAT zmk_behavior_soft_poweroff +#define SLEEP_S 2u + +#include +#include +#include +#include + +#include + +#include +#include + +#include + +LOG_MODULE_DECLARE(zmk, CONFIG_ZMK_LOG_LEVEL); + +#if DT_HAS_COMPAT_STATUS_OKAY(DT_DRV_COMPAT) + +struct behavior_softoff_config { + int type; +}; + +static void enter_deep_sleep() { + // Turn off the system. See `zephyr/samples/boards/nrf/system_off` or `app/src/activity.c`. + // Equivalent to ACPI S5. + // Put devices in suspend power mode before sleeping. + zmk_activity_set_state(ZMK_ACTIVITY_SLEEP); + pm_state_force(0u, &(struct pm_state_info){PM_STATE_SOFT_OFF, 0, 0}); + k_sleep(K_SECONDS(SLEEP_S)); + + // Normally the code below will not be reached. + pm_state_force(0u, &(struct pm_state_info){PM_STATE_ACTIVE, 0, 0}); + zmk_activity_set_state(ZMK_ACTIVITY_ACTIVE); + LOG_WRN("The keyboard is not powered off!"); +} + +static void enter_deep_sleep_process(struct k_work *item) { enter_deep_sleep(); } + +#if IS_ENABLED(CONFIG_ZMK_SLEEP) +K_WORK_DELAYABLE_DEFINE(enter_deep_sleep_work, enter_deep_sleep_process); +#endif + +static int behavior_softoff_init(const struct device *dev) { return 0; }; + +static int on_keymap_binding_released(struct zmk_behavior_binding *binding, + struct zmk_behavior_binding_event event) { +#if IS_ENABLED(CONFIG_ZMK_SLEEP) + const struct device *dev = device_get_binding(binding->behavior_dev); + const struct behavior_softoff_config *cfg = dev->config; + + switch (cfg->type) { + case LOCKED: + // sleep before kscan interrupts enabled, so keys are locked + enter_deep_sleep(); + break; + case SLEEP: + // sleep after kscan interrupts enabled + // so the keyboard can be waked up by typing(on platforms with PORT events, gpiote, + // interrupts enabled) limitation: any type before the actual sleep will make it a poweroff + // behavior how to improve: introduce something to terminate the kscan(but keep the + // interrupts) after sleep requested + k_work_reschedule(&enter_deep_sleep_work, K_SECONDS(SLEEP_S)); + break; + default: + break; + } +#endif + + return ZMK_BEHAVIOR_OPAQUE; +} + +static const struct behavior_driver_api behavior_softoff_driver_api = { + .binding_released = on_keymap_binding_released, + .locality = BEHAVIOR_LOCALITY_GLOBAL, +}; + +#define SOFTOFF_INST(n) \ + static const struct behavior_softoff_config behavior_softoff_config_##n = { \ + .type = DT_INST_PROP(n, type)}; \ + DEVICE_DT_INST_DEFINE(n, behavior_softoff_init, NULL, NULL, &behavior_softoff_config_##n, \ + APPLICATION, CONFIG_KERNEL_INIT_PRIORITY_DEFAULT, \ + &behavior_softoff_driver_api); + +DT_INST_FOREACH_STATUS_OKAY(SOFTOFF_INST) + +#endif /* DT_HAS_COMPAT_STATUS_OKAY(DT_DRV_COMPAT) */ diff --git a/docs/docs/behaviors/soft-poweroff.md b/docs/docs/behaviors/soft-poweroff.md new file mode 100644 index 00000000..33244500 --- /dev/null +++ b/docs/docs/behaviors/soft-poweroff.md @@ -0,0 +1,57 @@ +--- +title: Soft Power Off Behavior +sidebar_label: Soft Power Off +--- + +## Summary + +There are two available behaviors that can be used to manually control the power state of the keyboard. Both options will put the keyboard into deep sleep mode, but they differ in timing. + +The first behavior is a soft power-off, which will immediately lock and power off the keyboard. +The second behavior adds a delay before entering the deep sleep mode. This delay allows +the kscan driver to enable interrupts (if `CONFIG_ZMK_KSCAN_DIRECT_POLLING` set to `n`), so +the keyboard can be activated simply by typing. It's important to note that the wake-up feature may not be available on all platforms. + +## Soft Power Off + +The soft power-off behavior will immediately put the keyboard into deep sleep mode, disabling the RGB underglow, backlight, and display. + +However, please note that this behavior may not function as expected if USB logging is enabled and the keyboard is not connected to a host via USB. + +### Behavior Binding + +- Reference: `&soft_poweroff` +- Parameters: None + +Example: + +``` +&soft_poweroff +``` + +## Sleep + +The sleep behavior functions similarly to the soft power-off, with the addition of a two-second delay before entering the deep sleep state. This delay is particularly useful for keyboards that support wake by PORT events/interrupts, such as those based on NRF52840, as it gives user time to release all keys and allows the kscan driver to configure the necessary interrupts for waking up the keyboard if polling is disabled. + +When the sleep behavior is triggered, the keyboard will enter the same deep sleep mode +as it would if `CONFIG_ZMK_IDLE_SLEEP_TIMEOUT` was exceeded. For more information +on this mode, please refer to [Idle/Sleep](../config/power.md). + +A known limitation is that if any key is pressed just before going into deep sleep mode, +the kscan driver may not switch to interrupt mode, preventing the keyboard from waking up by typing. +In such cases, the sleep behavior is identical to the soft power-off behavior. + +### Behavior Binding + +- Reference: `&sleep` +- Parameters: None + +Example: + +``` +&sleep +``` + +## Split Keyboards + +Soft power off behaviors are global: This means that when triggered, they affect both the central and peripheral side of split keyboards.