This commit is contained in:
Kim 2022-06-22 22:06:32 +00:00 committed by GitHub
commit 3661b18d4f
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
17 changed files with 1168 additions and 381 deletions

View file

@ -27,7 +27,8 @@ 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_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)

View file

@ -280,6 +280,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
@ -288,22 +292,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 PM_DEVICE_POWER_DOMAIN
default y
config PM_DEVICE_POWER_DOMAIN_DYNAMIC
default y
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
#ZMK_SLEEP
endif
config ZMK_EXT_POWER
bool "Enable support to control external power output"
default y
#Power Management
endmenu

View file

@ -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";

View file

@ -45,6 +45,7 @@
&i2c0 {
compatible = "nordic,nrf-twi";
power-domain = <&pd_ext_power>;
sda-pin = <17>;
scl-pin = <20>;
};

View file

@ -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 {

View file

@ -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.

View file

@ -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.

View file

@ -1,104 +0,0 @@
/*
* Copyright (c) 2020 The ZMK Contributors
*
* SPDX-License-Identifier: MIT
*/
#pragma once
#include <zephyr/types.h>
#include <stddef.h>
#include <device.h>
#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 <syscalls/ext_power.h>

View file

@ -0,0 +1,89 @@
/*
* Copyright (c) 2020 The ZMK Contributors
*
* SPDX-License-Identifier: MIT
*/
#include <pm/device.h>
#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

View file

@ -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);
int zmk_rgb_underglow_set_hsb(struct zmk_led_hsb color);
int zmk_rgb_underglow_init_power_domain_manager(const struct device *dev);

View file

@ -9,7 +9,7 @@
#include <device.h>
#include <devicetree.h>
#include <drivers/behavior.h>
#include <drivers/ext_power.h>
#include <zmk/power_domain.h>
#include <dt-bindings/zmk/ext_power.h>
@ -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) */

220
app/src/dev_pm_policy.c Normal file
View file

@ -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 <device.h>
#include <devicetree.h>
#include <zmk/power_domain.h>
#include <zmk/usb.h>
#include <zmk/events/activity_state_changed.h>
#include <zmk/events/usb_conn_state_changed.h>
#include <logging/log.h>
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) */

View file

@ -8,6 +8,7 @@
#include <init.h>
#include <device.h>
#include <devicetree.h>
#include <pm/device.h>
#include <logging/log.h>
LOG_MODULE_DECLARE(zmk, CONFIG_ZMK_LOG_LEVEL);
@ -18,11 +19,20 @@ LOG_MODULE_DECLARE(zmk, CONFIG_ZMK_LOG_LEVEL);
#include <zmk/event_manager.h>
#include <zmk/events/activity_state_changed.h>
#include <zmk/display/status_screen.h>
#include <zmk/power_domain.h>
#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

View file

@ -1,214 +0,0 @@
/*
* Copyright (c) 2020 The ZMK Contributors
*
* SPDX-License-Identifier: MIT
*/
#define DT_DRV_COMPAT zmk_ext_power_generic
#include <stdio.h>
#include <device.h>
#include <pm/device.h>
#include <init.h>
#include <kernel.h>
#include <settings/settings.h>
#include <drivers/gpio.h>
#include <drivers/ext_power.h>
#if DT_HAS_COMPAT_STATUS_OKAY(DT_DRV_COMPAT)
#include <logging/log.h>
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) */

View file

@ -15,7 +15,6 @@ LOG_MODULE_REGISTER(zmk, CONFIG_ZMK_LOG_LEVEL);
#include <zmk/matrix.h>
#include <zmk/kscan.h>
#include <zmk/display.h>
#include <drivers/ext_power.h>
#define ZMK_KSCAN_DEV DT_LABEL(ZMK_MATRIX_NODE_ID)

547
app/src/power_domain.c Normal file
View file

@ -0,0 +1,547 @@
/*
* Copyright (c) 2020 The ZMK Contributors
*
* SPDX-License-Identifier: MIT
*/
#include <stdio.h>
#include <device.h>
#include <pm/device.h>
#include <pm/device_runtime.h>
#include <init.h>
#include <kernel.h>
#include <settings/settings.h>
#include <drivers/gpio.h>
#include <zmk/power_domain.h>
#include <zmk/events/activity_state_changed.h>
#include <logging/log.h>
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;
}
// 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);
/*
* 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);
}

View file

@ -15,8 +15,9 @@
#include <logging/log.h>
#include <drivers/led_strip.h>
#include <drivers/ext_power.h>
#include <pm/device.h>
#include <zmk/power_domain.h>
#include <zmk/rgb_underglow.h>
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
);