Files
zmk/app/module/drivers/kscan/kscan_composite.c
svek1 a8f5ab67b5 fix: allow kscan-composite to wake up device. (#2682)
* include kscan.yaml so we can set kscan-composite as a wakeup source
* modify enable and disable callback to check for wakeup capabilities of
composite and children
* disable children wakeup source

The disable function is only called
when the composite is not an enabled wakeup source.
In that case the children should also not be an enabled
wakeup source, so they can get suspended
2024-12-11 14:46:22 -05:00

185 lines
6.8 KiB
C

/*
* Copyright (c) 2020 The ZMK Contributors
*
* SPDX-License-Identifier: MIT
*/
#define DT_DRV_COMPAT zmk_kscan_composite
#include <zephyr/device.h>
#include <zephyr/pm/device.h>
#include <zephyr/drivers/kscan.h>
#include <zephyr/logging/log.h>
LOG_MODULE_DECLARE(zmk, CONFIG_ZMK_LOG_LEVEL);
#define MATRIX_NODE_ID DT_DRV_INST(0)
#define MATRIX_ROWS DT_PROP(MATRIX_NODE_ID, rows)
#define MATRIX_COLS DT_PROP(MATRIX_NODE_ID, columns)
struct kscan_composite_child_config {
const struct device *child;
uint8_t row_offset;
uint8_t column_offset;
};
#define CHILD_CONFIG(inst) \
{.child = DEVICE_DT_GET(DT_PHANDLE(inst, kscan)), \
.row_offset = DT_PROP(inst, row_offset), \
.column_offset = DT_PROP_OR(inst, col_offset, DT_PROP(inst, column_offset))},
struct kscan_composite_config {
const struct kscan_composite_child_config *children;
size_t children_len;
};
struct kscan_composite_data {
kscan_callback_t callback;
const struct device *dev;
};
static int kscan_composite_enable_callback(const struct device *dev) {
const struct kscan_composite_config *cfg = dev->config;
for (int i = 0; i < cfg->children_len; i++) {
const struct kscan_composite_child_config *child_cfg = &cfg->children[i];
#if IS_ENABLED(CONFIG_PM_DEVICE_RUNTIME) || IS_ENABLED(CONFIG_PM_DEVICE)
if (pm_device_wakeup_is_enabled(dev) && pm_device_wakeup_is_capable(child_cfg->child) &&
!pm_device_wakeup_enable(child_cfg->child, true)) {
LOG_ERR("Failed to enable wakeup for %s", child_cfg->child->name);
}
#endif // IS_ENABLED(CONFIG_PM_DEVICE_RUNTIME) || IS_ENABLED(CONFIG_PM_DEVICE)
#if IS_ENABLED(CONFIG_PM_DEVICE_RUNTIME)
if (!pm_device_runtime_is_enabled(dev) && pm_device_runtime_is_enabled(child_cfg->child)) {
pm_device_runtime_get(child_cfg->child);
}
#elif IS_ENABLED(CONFIG_PM_DEVICE)
pm_device_action_run(child_cfg->child, PM_DEVICE_ACTION_RESUME);
#endif // IS_ENABLED(CONFIG_PM_DEVICE)
kscan_enable_callback(child_cfg->child);
}
return 0;
}
static int kscan_composite_disable_callback(const struct device *dev) {
const struct kscan_composite_config *cfg = dev->config;
for (int i = 0; i < cfg->children_len; i++) {
const struct kscan_composite_child_config *child_cfg = &cfg->children[i];
#if IS_ENABLED(CONFIG_PM_DEVICE_RUNTIME) || IS_ENABLED(CONFIG_PM_DEVICE)
if (pm_device_wakeup_is_capable(child_cfg->child) &&
pm_device_wakeup_is_enabled(child_cfg->child) &&
!pm_device_wakeup_enable(child_cfg->child, false)) {
LOG_ERR("Failed to disable wakeup for %s", child_cfg->child->name);
}
#endif // IS_ENABLED(CONFIG_PM_DEVICE_RUNTIME) || IS_ENABLED(CONFIG_PM_DEVICE)
kscan_disable_callback(child_cfg->child);
#if IS_ENABLED(CONFIG_PM_DEVICE_RUNTIME)
if (!pm_device_runtime_is_enabled(dev) && pm_device_runtime_is_enabled(child_cfg->child)) {
pm_device_runtime_put(child_cfg->child);
}
#elif IS_ENABLED(CONFIG_PM_DEVICE)
pm_device_action_run(child_cfg->child, PM_DEVICE_ACTION_SUSPEND);
#endif // IS_ENABLED(CONFIG_PM_DEVICE)
}
return 0;
}
#define KSCAN_COMP_INST_DEV(n) DEVICE_DT_GET(DT_DRV_INST(n)),
static const struct device *all_instances[] = {DT_INST_FOREACH_STATUS_OKAY(KSCAN_COMP_INST_DEV)};
static void kscan_composite_child_callback(const struct device *child_dev, uint32_t row,
uint32_t column, bool pressed) {
// TODO: Ideally we can get this passed into our callback!
for (int i = 0; i < ARRAY_SIZE(all_instances); i++) {
const struct device *dev = all_instances[i];
const struct kscan_composite_config *cfg = dev->config;
struct kscan_composite_data *data = dev->data;
for (int c = 0; c < cfg->children_len; c++) {
const struct kscan_composite_child_config *child_cfg = &cfg->children[c];
if (child_cfg->child != child_dev) {
continue;
}
data->callback(dev, row + child_cfg->row_offset, column + child_cfg->column_offset,
pressed);
}
}
}
static int kscan_composite_configure(const struct device *dev, kscan_callback_t callback) {
const struct kscan_composite_config *cfg = dev->config;
struct kscan_composite_data *data = dev->data;
if (!callback) {
return -EINVAL;
}
for (int i = 0; i < cfg->children_len; i++) {
const struct kscan_composite_child_config *child_cfg = &cfg->children[i];
kscan_config(child_cfg->child, &kscan_composite_child_callback);
}
data->callback = callback;
return 0;
}
static int kscan_composite_init(const struct device *dev) {
struct kscan_composite_data *data = dev->data;
data->dev = dev;
#if IS_ENABLED(CONFIG_PM_DEVICE)
pm_device_init_suspended(dev);
#endif
return 0;
}
static const struct kscan_driver_api mock_driver_api = {
.config = kscan_composite_configure,
.enable_callback = kscan_composite_enable_callback,
.disable_callback = kscan_composite_disable_callback,
};
#if IS_ENABLED(CONFIG_PM_DEVICE)
static int kscan_composite_pm_action(const struct device *dev, enum pm_device_action action) {
switch (action) {
case PM_DEVICE_ACTION_SUSPEND:
return kscan_composite_disable_callback(dev);
case PM_DEVICE_ACTION_RESUME:
return kscan_composite_enable_callback(dev);
default:
return -ENOTSUP;
}
}
#endif // IS_ENABLED(CONFIG_PM_DEVICE)
#define KSCAN_COMP_DEV(n) \
static const struct kscan_composite_child_config kscan_composite_children_##n[] = { \
DT_INST_FOREACH_CHILD(n, CHILD_CONFIG)}; \
static const struct kscan_composite_config kscan_composite_config_##n = { \
.children = kscan_composite_children_##n, \
.children_len = ARRAY_SIZE(kscan_composite_children_##n), \
}; \
static struct kscan_composite_data kscan_composite_data_##n; \
PM_DEVICE_DT_INST_DEFINE(n, kscan_composite_pm_action); \
DEVICE_DT_INST_DEFINE(n, kscan_composite_init, PM_DEVICE_DT_INST_GET(n), \
&kscan_composite_data_##n, &kscan_composite_config_##n, POST_KERNEL, \
CONFIG_ZMK_KSCAN_COMPOSITE_INIT_PRIORITY, &mock_driver_api);
DT_INST_FOREACH_STATUS_OKAY(KSCAN_COMP_DEV)