diff --git a/app/boards/shields/zmk_uno/zmk_uno.overlay b/app/boards/shields/zmk_uno/zmk_uno.overlay index 3d105abf8d7..6948112b8b0 100644 --- a/app/boards/shields/zmk_uno/zmk_uno.overlay +++ b/app/boards/shields/zmk_uno/zmk_uno.overlay @@ -38,6 +38,8 @@ endpoint_sideband_behaviors { compatible = "zmk,kscan-sideband-behaviors"; + + auto-enable; kscan = <&kscan_sp3t_toggle>; first_toggle_sideband: first_toggle_sideband { diff --git a/app/dts/bindings/kscan/zmk,kscan-sideband-behaviors.yaml b/app/dts/bindings/kscan/zmk,kscan-sideband-behaviors.yaml index f3ed180d14b..e38beeb437c 100644 --- a/app/dts/bindings/kscan/zmk,kscan-sideband-behaviors.yaml +++ b/app/dts/bindings/kscan/zmk,kscan-sideband-behaviors.yaml @@ -11,6 +11,9 @@ compatible: "zmk,kscan-sideband-behaviors" include: kscan.yaml properties: + auto-enable: + type: boolean + kscan: type: phandle required: true diff --git a/app/module/drivers/kscan/kscan_gpio_charlieplex.c b/app/module/drivers/kscan/kscan_gpio_charlieplex.c index 3ecbcd6a0e1..f48a6a2f40c 100644 --- a/app/module/drivers/kscan/kscan_gpio_charlieplex.c +++ b/app/module/drivers/kscan/kscan_gpio_charlieplex.c @@ -12,6 +12,7 @@ #include #include #include +#include #include #include @@ -167,6 +168,21 @@ static int kscan_charlieplex_set_all_outputs(const struct device *dev, const int return 0; } +static int kscan_charlieplex_disconnect_all(const struct device *dev) { + const struct kscan_charlieplex_config *config = dev->config; + + for (int i = 0; i < config->cells.len; i++) { + const struct gpio_dt_spec *gpio = &config->cells.gpios[i]; + int err = gpio_pin_configure_dt(gpio, GPIO_DISCONNECTED); + if (err) { + LOG_ERR("Unable to configure pin %u on %s for input", gpio->pin, gpio->port->name); + return err; + } + } + + return 0; +} + static int kscan_charlieplex_interrupt_configure(const struct device *dev, const gpio_flags_t flags) { const struct kscan_charlieplex_config *config = dev->config; @@ -359,11 +375,7 @@ static int kscan_charlieplex_init_interrupt(const struct device *dev) { return err; } -static int kscan_charlieplex_init(const struct device *dev) { - struct kscan_charlieplex_data *data = dev->data; - - data->dev = dev; - +static void kscan_charlieplex_setup_pins(const struct device *dev) { kscan_charlieplex_init_inputs(dev); kscan_charlieplex_set_all_outputs(dev, 0); @@ -371,7 +383,46 @@ static int kscan_charlieplex_init(const struct device *dev) { if (config->use_interrupt) { kscan_charlieplex_init_interrupt(dev); } +} + +#if IS_ENABLED(CONFIG_PM_DEVICE) + +static int kscan_charlieplex_pm_action(const struct device *dev, enum pm_device_action action) { + switch (action) { + case PM_DEVICE_ACTION_SUSPEND: + kscan_charlieplex_interrupt_configure(dev, GPIO_INT_DISABLE); + kscan_charlieplex_disconnect_all(dev); + + return kscan_charlieplex_disable(dev); + case PM_DEVICE_ACTION_RESUME: + kscan_charlieplex_setup_pins(dev); + + return kscan_charlieplex_enable(dev); + default: + return -ENOTSUP; + } +} + +#endif // IS_ENABLED(CONFIG_PM_DEVICE) + +static int kscan_charlieplex_init(const struct device *dev) { + struct kscan_charlieplex_data *data = dev->data; + + data->dev = dev; + k_work_init_delayable(&data->work, kscan_charlieplex_work_handler); + +#if IS_ENABLED(CONFIG_PM_DEVICE) + pm_device_init_suspended(dev); + +#if IS_ENABLED(CONFIG_PM_DEVICE_RUNTIME) + pm_device_runtime_enable(dev); +#endif + +#else + kscan_charlieplex_setup_pins(dev); +#endif + return 0; } @@ -406,8 +457,10 @@ static const struct kscan_driver_api kscan_charlieplex_api = { COND_THIS_INTERRUPT(n, (.use_interrupt = INST_INTR_DEFINED(n), )) \ COND_THIS_INTERRUPT(n, (.interrupt = KSCAN_INTR_CFG_INIT(n), ))}; \ \ - DEVICE_DT_INST_DEFINE(n, &kscan_charlieplex_init, NULL, &kscan_charlieplex_data_##n, \ - &kscan_charlieplex_config_##n, POST_KERNEL, CONFIG_KSCAN_INIT_PRIORITY, \ - &kscan_charlieplex_api); + PM_DEVICE_DT_INST_DEFINE(n, kscan_charlieplex_pm_action); \ + \ + DEVICE_DT_INST_DEFINE(n, &kscan_charlieplex_init, PM_DEVICE_DT_INST_GET(n), \ + &kscan_charlieplex_data_##n, &kscan_charlieplex_config_##n, POST_KERNEL, \ + CONFIG_KSCAN_INIT_PRIORITY, &kscan_charlieplex_api); DT_INST_FOREACH_STATUS_OKAY(KSCAN_CHARLIEPLEX_INIT); diff --git a/app/module/drivers/kscan/kscan_gpio_direct.c b/app/module/drivers/kscan/kscan_gpio_direct.c index fa24e69e895..245e78b50cc 100644 --- a/app/module/drivers/kscan/kscan_gpio_direct.c +++ b/app/module/drivers/kscan/kscan_gpio_direct.c @@ -294,6 +294,24 @@ static int kscan_direct_init_input_inst(const struct device *dev, const struct g return 0; } +#if IS_ENABLED(CONFIG_PM_DEVICE) + +static int kscan_direct_disconnect_inputs(const struct device *dev) { + const struct kscan_direct_data *data = dev->data; + + for (int i = 0; i < data->inputs.len; i++) { + const struct gpio_dt_spec *gpio = &data->inputs.gpios[i].spec; + int err = gpio_pin_configure_dt(gpio, GPIO_DISCONNECTED); + if (err) { + return err; + } + } + + return 0; +} + +#endif // IS_ENABLED(CONFIG_PM_DEVICE) + static int kscan_direct_init_inputs(const struct device *dev) { const struct kscan_direct_data *data = dev->data; const struct kscan_direct_config *config = dev->config; @@ -317,9 +335,20 @@ static int kscan_direct_init(const struct device *dev) { // Sort inputs by port so we can read each port just once per scan. kscan_gpio_list_sort_by_port(&data->inputs); + k_work_init_delayable(&data->work, kscan_direct_work_handler); + +#if IS_ENABLED(CONFIG_PM_DEVICE) + pm_device_init_suspended(dev); + +#if IS_ENABLED(CONFIG_PM_DEVICE_RUNTIME) + pm_device_runtime_enable(dev); +#endif + +#else + kscan_direct_init_inputs(dev); - k_work_init_delayable(&data->work, kscan_direct_work_handler); +#endif return 0; } @@ -329,8 +358,10 @@ static int kscan_direct_init(const struct device *dev) { static int kscan_direct_pm_action(const struct device *dev, enum pm_device_action action) { switch (action) { case PM_DEVICE_ACTION_SUSPEND: + kscan_direct_disconnect_inputs(dev); return kscan_direct_disable(dev); case PM_DEVICE_ACTION_RESUME: + kscan_direct_init_inputs(dev); return kscan_direct_enable(dev); default: return -ENOTSUP; diff --git a/app/module/drivers/kscan/kscan_gpio_matrix.c b/app/module/drivers/kscan/kscan_gpio_matrix.c index 8a3c39f2ba9..e0c76395f63 100644 --- a/app/module/drivers/kscan/kscan_gpio_matrix.c +++ b/app/module/drivers/kscan/kscan_gpio_matrix.c @@ -405,6 +405,44 @@ static int kscan_matrix_init_outputs(const struct device *dev) { return 0; } +#if IS_ENABLED(CONFIG_PM_DEVICE) + +static int kscan_matrix_disconnect_inputs(const struct device *dev) { + const struct kscan_matrix_data *data = dev->data; + + for (int i = 0; i < data->inputs.len; i++) { + const struct gpio_dt_spec *gpio = &data->inputs.gpios[i].spec; + int err = gpio_pin_configure_dt(gpio, GPIO_DISCONNECTED); + if (err) { + return err; + } + } + + return 0; +} + +static int kscan_matrix_disconnect_outputs(const struct device *dev) { + const struct kscan_matrix_config *config = dev->config; + + for (int i = 0; i < config->outputs.len; i++) { + const struct gpio_dt_spec *gpio = &config->outputs.gpios[i].spec; + int err = gpio_pin_configure_dt(gpio, GPIO_DISCONNECTED); + if (err) { + return err; + } + } + + return 0; +} + +#endif // IS_ENABLED(CONFIG_PM_DEVICE) + +static void kscan_matrix_setup_pins(const struct device *dev) { + kscan_matrix_init_inputs(dev); + kscan_matrix_init_outputs(dev); + kscan_matrix_set_all_outputs(dev, 0); +} + static int kscan_matrix_init(const struct device *dev) { struct kscan_matrix_data *data = dev->data; @@ -413,12 +451,19 @@ static int kscan_matrix_init(const struct device *dev) { // Sort inputs by port so we can read each port just once per scan. kscan_gpio_list_sort_by_port(&data->inputs); - kscan_matrix_init_inputs(dev); - kscan_matrix_init_outputs(dev); - kscan_matrix_set_all_outputs(dev, 0); - k_work_init_delayable(&data->work, kscan_matrix_work_handler); +#if IS_ENABLED(CONFIG_PM_DEVICE) + pm_device_init_suspended(dev); + +#if IS_ENABLED(CONFIG_PM_DEVICE_RUNTIME) + pm_device_runtime_enable(dev); +#endif + +#else + kscan_matrix_setup_pins(dev); +#endif + return 0; } @@ -427,8 +472,12 @@ static int kscan_matrix_init(const struct device *dev) { static int kscan_matrix_pm_action(const struct device *dev, enum pm_device_action action) { switch (action) { case PM_DEVICE_ACTION_SUSPEND: + kscan_matrix_disconnect_inputs(dev); + kscan_matrix_disconnect_outputs(dev); + return kscan_matrix_disable(dev); case PM_DEVICE_ACTION_RESUME: + kscan_matrix_setup_pins(dev); return kscan_matrix_enable(dev); default: return -ENOTSUP; diff --git a/app/src/kscan_sideband_behaviors.c b/app/src/kscan_sideband_behaviors.c index f3992ebc512..602cae12af4 100644 --- a/app/src/kscan_sideband_behaviors.c +++ b/app/src/kscan_sideband_behaviors.c @@ -26,6 +26,7 @@ struct ksbb_entry { struct ksbb_config { const struct device *kscan; + bool auto_enable; struct ksbb_entry *entries; size_t entries_len; }; @@ -93,34 +94,65 @@ void ksbb_inner_kscan_callback(const struct device *dev, uint32_t row, uint32_t } static int ksbb_configure(const struct device *dev, kscan_callback_t callback) { - const struct ksbb_config *cfg = dev->config; struct ksbb_data *data = dev->data; data->callback = callback; -#if IS_ENABLED(CONFIG_PM_DEVICE) - if (pm_device_wakeup_is_enabled(dev) && pm_device_wakeup_is_capable(cfg->kscan)) { - pm_device_wakeup_enable(cfg->kscan, true); - } -#endif // IS_ENABLED(CONFIG_PM_DEVICE) - return 0; } static int ksbb_enable(const struct device *dev) { struct ksbb_data *data = dev->data; + const struct ksbb_config *config = dev->config; data->enabled = true; +#if IS_ENABLED(CONFIG_PM_DEVICE_RUNTIME) + if (!pm_device_runtime_is_enabled(dev) && pm_device_runtime_is_enabled(config->kscan)) { + pm_device_runtime_get(config->kscan); + } +#elif IS_ENABLED(CONFIG_PM_DEVICE) + pm_device_action_run(config->kscan, PM_DEVICE_ACTION_RESUME); +#endif // IS_ENABLED(CONFIG_PM_DEVICE) + + kscan_config(config->kscan, &ksbb_inner_kscan_callback); + kscan_enable_callback(config->kscan); + return 0; } static int ksbb_disable(const struct device *dev) { struct ksbb_data *data = dev->data; + const struct ksbb_config *config = dev->config; data->enabled = false; + kscan_disable_callback(config->kscan); + +#if IS_ENABLED(CONFIG_PM_DEVICE_RUNTIME) + if (!pm_device_runtime_is_enabled(dev) && pm_device_runtime_is_enabled(config->kscan)) { + pm_device_runtime_put(config->kscan); + } +#elif IS_ENABLED(CONFIG_PM_DEVICE) + pm_device_action_run(config->kscan, PM_DEVICE_ACTION_SUSPEND); +#endif // IS_ENABLED(CONFIG_PM_DEVICE) + return 0; } +#if IS_ENABLED(CONFIG_PM_DEVICE) + +static int ksbb_pm_action(const struct device *dev, enum pm_device_action action) { + switch (action) { + case PM_DEVICE_ACTION_SUSPEND: + return ksbb_disable(dev); + case PM_DEVICE_ACTION_RESUME: + return ksbb_enable(dev); + default: + return -ENOTSUP; + } +} + +#endif // IS_ENABLED(CONFIG_PM_DEVICE) + static int ksbb_init(const struct device *dev) { const struct ksbb_config *config = dev->config; @@ -129,8 +161,16 @@ static int ksbb_init(const struct device *dev) { return -ENODEV; } - kscan_config(config->kscan, &ksbb_inner_kscan_callback); - kscan_enable_callback(config->kscan); + if (config->auto_enable) { +#if !IS_ENABLED(CONFIG_PM_DEVICE) + kscan_config(config->kscan, &ksbb_inner_kscan_callback); + kscan_enable_callback(config->kscan); +#else + ksbb_pm_action(dev, PM_DEVICE_ACTION_RESUME); + } else { + pm_device_init_suspended(dev); +#endif + } return 0; } @@ -141,21 +181,6 @@ static const struct kscan_driver_api ksbb_api = { .disable_callback = ksbb_disable, }; -#if IS_ENABLED(CONFIG_PM_DEVICE) - -static int ksbb_pm_action(const struct device *dev, enum pm_device_action action) { - switch (action) { - case PM_DEVICE_ACTION_SUSPEND: - return ksbb_disable(dev); - case PM_DEVICE_ACTION_RESUME: - return ksbb_disable(dev); - default: - return -ENOTSUP; - } -} - -#endif // IS_ENABLED(CONFIG_PM_DEVICE) - #define ENTRY(e) \ { \ .row = DT_PROP(e, row), .column = DT_PROP(e, column), \ @@ -167,13 +192,14 @@ static int ksbb_pm_action(const struct device *dev, enum pm_device_action action DT_INST_FOREACH_CHILD_STATUS_OKAY_SEP(n, ENTRY, (, ))}; \ const struct ksbb_config ksbb_config_##n = { \ .kscan = DEVICE_DT_GET(DT_INST_PHANDLE(n, kscan)), \ + .auto_enable = DT_INST_PROP_OR(n, auto_enable, false), \ .entries = entries_##n, \ .entries_len = ARRAY_SIZE(entries_##n), \ }; \ struct ksbb_data ksbb_data_##n = {}; \ PM_DEVICE_DT_INST_DEFINE(n, ksbb_pm_action); \ DEVICE_DT_INST_DEFINE(n, ksbb_init, PM_DEVICE_DT_INST_GET(n), &ksbb_data_##n, \ - &ksbb_config_##n, APPLICATION, \ + &ksbb_config_##n, POST_KERNEL, \ CONFIG_ZMK_KSCAN_SIDEBAND_BEHAVIORS_INIT_PRIORITY, &ksbb_api); DT_INST_FOREACH_STATUS_OKAY(KSBB_INST) diff --git a/docs/docs/features/soft-off.md b/docs/docs/features/soft-off.md index 7018afa08f2..207bb13f723 100644 --- a/docs/docs/features/soft-off.md +++ b/docs/docs/features/soft-off.md @@ -121,6 +121,7 @@ With that in place, the kscan sideband behavior will wrap the new driver: compatible = "zmk,kscan-sideband-behaviors"; kscan = <&soft_off_direct_scan>; + auto-enable; wakeup-source; soft_off {