forked from kofal.net/zmk
refactor: Move drivers into properly module.
* Align our driver module layout to properly match Zephyr conventions, allowing proper CMake setup to amend the library for each type of driver.
This commit is contained in:
committed by
Pete Johanson
parent
eaeea4bdfa
commit
690bc1bb44
12
app/module/drivers/kscan/CMakeLists.txt
Normal file
12
app/module/drivers/kscan/CMakeLists.txt
Normal file
@@ -0,0 +1,12 @@
|
||||
# Copyright (c) 2020 The ZMK Contributors
|
||||
# SPDX-License-Identifier: MIT
|
||||
|
||||
zephyr_library_amend()
|
||||
|
||||
zephyr_library_sources_ifdef(CONFIG_ZMK_KSCAN_GPIO_DRIVER debounce.c)
|
||||
zephyr_library_sources_ifdef(CONFIG_ZMK_KSCAN_GPIO_DRIVER kscan_gpio.c)
|
||||
zephyr_library_sources_ifdef(CONFIG_ZMK_KSCAN_GPIO_MATRIX kscan_gpio_matrix.c)
|
||||
zephyr_library_sources_ifdef(CONFIG_ZMK_KSCAN_GPIO_DIRECT kscan_gpio_direct.c)
|
||||
zephyr_library_sources_ifdef(CONFIG_ZMK_KSCAN_GPIO_DEMUX kscan_gpio_demux.c)
|
||||
zephyr_library_sources_ifdef(CONFIG_ZMK_KSCAN_MOCK_DRIVER kscan_mock.c)
|
||||
zephyr_library_sources_ifdef(CONFIG_ZMK_KSCAN_COMPOSITE_DRIVER kscan_composite.c)
|
||||
98
app/module/drivers/kscan/Kconfig
Normal file
98
app/module/drivers/kscan/Kconfig
Normal file
@@ -0,0 +1,98 @@
|
||||
# Copyright (c) 2020 The ZMK Contributors
|
||||
# SPDX-License-Identifier: MIT
|
||||
|
||||
DT_COMPAT_ZMK_KSCAN_COMPOSITE := zmk,kscan-composite
|
||||
DT_COMPAT_ZMK_KSCAN_GPIO_DEMUX := zmk,kscan-gpio-demux
|
||||
DT_COMPAT_ZMK_KSCAN_GPIO_DIRECT := zmk,kscan-gpio-direct
|
||||
DT_COMPAT_ZMK_KSCAN_GPIO_MATRIX := zmk,kscan-gpio-matrix
|
||||
DT_COMPAT_ZMK_KSCAN_MOCK := zmk,kscan-mock
|
||||
|
||||
if KSCAN
|
||||
|
||||
config ZMK_KSCAN_COMPOSITE_DRIVER
|
||||
bool
|
||||
default $(dt_compat_enabled,$(DT_COMPAT_ZMK_KSCAN_COMPOSITE))
|
||||
|
||||
config ZMK_KSCAN_GPIO_DRIVER
|
||||
bool
|
||||
select GPIO
|
||||
|
||||
config ZMK_KSCAN_GPIO_DEMUX
|
||||
bool
|
||||
default $(dt_compat_enabled,$(DT_COMPAT_ZMK_KSCAN_GPIO_DEMUX))
|
||||
select ZMK_KSCAN_GPIO_DRIVER
|
||||
|
||||
config ZMK_KSCAN_GPIO_DIRECT
|
||||
bool
|
||||
default $(dt_compat_enabled,$(DT_COMPAT_ZMK_KSCAN_GPIO_DIRECT))
|
||||
select ZMK_KSCAN_GPIO_DRIVER
|
||||
|
||||
config ZMK_KSCAN_GPIO_MATRIX
|
||||
bool
|
||||
default $(dt_compat_enabled,$(DT_COMPAT_ZMK_KSCAN_GPIO_MATRIX))
|
||||
select ZMK_KSCAN_GPIO_DRIVER
|
||||
|
||||
if ZMK_KSCAN_GPIO_MATRIX
|
||||
|
||||
config ZMK_KSCAN_MATRIX_WAIT_BEFORE_INPUTS
|
||||
int "Ticks to wait before reading inputs after an output set active"
|
||||
default 0
|
||||
help
|
||||
When iterating over each output to drive it active, read inputs, then set
|
||||
inactive again, some boards may take time for output to propagate to the
|
||||
inputs. In that scenario, set this value to a positive value to configure
|
||||
the number of ticks to wait after setting an output active before reading
|
||||
the inputs for their active state.
|
||||
|
||||
config ZMK_KSCAN_MATRIX_WAIT_BETWEEN_OUTPUTS
|
||||
int "Ticks to wait between each output when scanning"
|
||||
default 1 if SOC_RP2040
|
||||
default 0
|
||||
help
|
||||
When iterating over each output to drive it active, read inputs, then set
|
||||
inactive again, some boards may take time for the previous output to
|
||||
"settle" before reading inputs for the next active output column. In that
|
||||
scenario, set this value to a positive value to configure the number of
|
||||
ticks to wait after reading each column of keys.
|
||||
|
||||
endif # ZMK_KSCAN_GPIO_MATRIX
|
||||
|
||||
config ZMK_KSCAN_MOCK_DRIVER
|
||||
bool
|
||||
default $(dt_compat_enabled,$(DT_COMPAT_ZMK_KSCAN_MOCK))
|
||||
|
||||
if ZMK_KSCAN_GPIO_DRIVER
|
||||
|
||||
config ZMK_KSCAN_MATRIX_POLLING
|
||||
bool "Poll for key event triggers instead of using interrupts on matrix boards."
|
||||
|
||||
config ZMK_KSCAN_DIRECT_POLLING
|
||||
bool "Poll for key event triggers instead of using interrupts on direct wired boards."
|
||||
|
||||
config ZMK_KSCAN_DEBOUNCE_PRESS_MS
|
||||
int "Debounce time for key press in milliseconds."
|
||||
default -1
|
||||
help
|
||||
Global debounce time for key press in milliseconds.
|
||||
If this is -1, the debounce time is controlled by the debounce-press-ms
|
||||
Devicetree property, which defaults to 5 ms. Otherwise this overrides the
|
||||
debounce time for all key scan drivers to the chosen value.
|
||||
|
||||
config ZMK_KSCAN_DEBOUNCE_RELEASE_MS
|
||||
int "Debounce time for key release in milliseconds."
|
||||
default -1
|
||||
help
|
||||
Global debounce time for key release in milliseconds.
|
||||
If this is -1, the debounce time is controlled by the debounce-release-ms
|
||||
Devicetree property, which defaults to 5 ms. Otherwise this overrides the
|
||||
debounce time for all key scan drivers to the chosen value.
|
||||
|
||||
endif
|
||||
|
||||
# config ZMK_KSCAN_INIT_PRIORITY
|
||||
# int "Keyboard scan driver init priority"
|
||||
# default 40
|
||||
# help
|
||||
# Keyboard scan device driver initialization priority.
|
||||
|
||||
endif # KSCAN
|
||||
62
app/module/drivers/kscan/debounce.c
Normal file
62
app/module/drivers/kscan/debounce.c
Normal file
@@ -0,0 +1,62 @@
|
||||
/*
|
||||
* Copyright (c) 2021 The ZMK Contributors
|
||||
*
|
||||
* SPDX-License-Identifier: MIT
|
||||
*/
|
||||
|
||||
#include "debounce.h"
|
||||
|
||||
static uint32_t get_threshold(const struct debounce_state *state,
|
||||
const struct debounce_config *config) {
|
||||
return state->pressed ? config->debounce_release_ms : config->debounce_press_ms;
|
||||
}
|
||||
|
||||
static void increment_counter(struct debounce_state *state, const int elapsed_ms) {
|
||||
if (state->counter + elapsed_ms > DEBOUNCE_COUNTER_MAX) {
|
||||
state->counter = DEBOUNCE_COUNTER_MAX;
|
||||
} else {
|
||||
state->counter += elapsed_ms;
|
||||
}
|
||||
}
|
||||
|
||||
static void decrement_counter(struct debounce_state *state, const int elapsed_ms) {
|
||||
if (state->counter < elapsed_ms) {
|
||||
state->counter = 0;
|
||||
} else {
|
||||
state->counter -= elapsed_ms;
|
||||
}
|
||||
}
|
||||
|
||||
void debounce_update(struct debounce_state *state, const bool active, const int elapsed_ms,
|
||||
const struct debounce_config *config) {
|
||||
// This uses a variation of the integrator debouncing described at
|
||||
// https://www.kennethkuhn.com/electronics/debounce.c
|
||||
// Every update where "active" does not match the current state, we increment
|
||||
// a counter, otherwise we decrement it. When the counter reaches a
|
||||
// threshold, the state flips and we reset the counter.
|
||||
state->changed = false;
|
||||
|
||||
if (active == state->pressed) {
|
||||
decrement_counter(state, elapsed_ms);
|
||||
return;
|
||||
}
|
||||
|
||||
const uint32_t flip_threshold = get_threshold(state, config);
|
||||
|
||||
if (state->counter < flip_threshold) {
|
||||
increment_counter(state, elapsed_ms);
|
||||
return;
|
||||
}
|
||||
|
||||
state->pressed = !state->pressed;
|
||||
state->counter = 0;
|
||||
state->changed = true;
|
||||
}
|
||||
|
||||
bool debounce_is_active(const struct debounce_state *state) {
|
||||
return state->pressed || state->counter > 0;
|
||||
}
|
||||
|
||||
bool debounce_is_pressed(const struct debounce_state *state) { return state->pressed; }
|
||||
|
||||
bool debounce_get_changed(const struct debounce_state *state) { return state->changed; }
|
||||
56
app/module/drivers/kscan/debounce.h
Normal file
56
app/module/drivers/kscan/debounce.h
Normal file
@@ -0,0 +1,56 @@
|
||||
/*
|
||||
* Copyright (c) 2021 The ZMK Contributors
|
||||
*
|
||||
* SPDX-License-Identifier: MIT
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <stdint.h>
|
||||
#include <zephyr/sys/util.h>
|
||||
|
||||
#define DEBOUNCE_COUNTER_BITS 14
|
||||
#define DEBOUNCE_COUNTER_MAX BIT_MASK(DEBOUNCE_COUNTER_BITS)
|
||||
|
||||
struct debounce_state {
|
||||
bool pressed : 1;
|
||||
bool changed : 1;
|
||||
uint16_t counter : DEBOUNCE_COUNTER_BITS;
|
||||
};
|
||||
|
||||
struct debounce_config {
|
||||
/** Duration a switch must be pressed to latch as pressed. */
|
||||
uint32_t debounce_press_ms;
|
||||
/** Duration a switch must be released to latch as released. */
|
||||
uint32_t debounce_release_ms;
|
||||
};
|
||||
|
||||
/**
|
||||
* Debounces one switch.
|
||||
*
|
||||
* @param state The state for the switch to debounce.
|
||||
* @param active Is the switch currently pressed?
|
||||
* @param elapsed_ms Time elapsed since the previous update in milliseconds.
|
||||
* @param config Debounce settings.
|
||||
*/
|
||||
void debounce_update(struct debounce_state *state, const bool active, const int elapsed_ms,
|
||||
const struct debounce_config *config);
|
||||
|
||||
/**
|
||||
* @returns whether the switch is either latched as pressed or it is potentially
|
||||
* pressed but the debouncer has not yet made a decision. If this returns true,
|
||||
* the kscan driver should continue to poll quickly.
|
||||
*/
|
||||
bool debounce_is_active(const struct debounce_state *state);
|
||||
|
||||
/**
|
||||
* @returns whether the switch is latched as pressed.
|
||||
*/
|
||||
bool debounce_is_pressed(const struct debounce_state *state);
|
||||
|
||||
/**
|
||||
* @returns whether the pressed state of the switch changed in the last call to
|
||||
* debounce_update.
|
||||
*/
|
||||
bool debounce_get_changed(const struct debounce_state *state);
|
||||
112
app/module/drivers/kscan/kscan_composite.c
Normal file
112
app/module/drivers/kscan/kscan_composite.c
Normal file
@@ -0,0 +1,112 @@
|
||||
/*
|
||||
* Copyright (c) 2020 The ZMK Contributors
|
||||
*
|
||||
* SPDX-License-Identifier: MIT
|
||||
*/
|
||||
|
||||
#define DT_DRV_COMPAT zmk_kscan_composite
|
||||
|
||||
#include <zephyr/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(inst, column_offset)},
|
||||
|
||||
const struct kscan_composite_child_config kscan_composite_children[] = {
|
||||
DT_FOREACH_CHILD(MATRIX_NODE_ID, CHILD_CONFIG)};
|
||||
|
||||
struct kscan_composite_config {};
|
||||
|
||||
struct kscan_composite_data {
|
||||
kscan_callback_t callback;
|
||||
|
||||
const struct device *dev;
|
||||
};
|
||||
|
||||
static int kscan_composite_enable_callback(const struct device *dev) {
|
||||
for (int i = 0; i < ARRAY_SIZE(kscan_composite_children); i++) {
|
||||
const struct kscan_composite_child_config *cfg = &kscan_composite_children[i];
|
||||
|
||||
kscan_enable_callback(cfg->child);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int kscan_composite_disable_callback(const struct device *dev) {
|
||||
for (int i = 0; i < ARRAY_SIZE(kscan_composite_children); i++) {
|
||||
const struct kscan_composite_child_config *cfg = &kscan_composite_children[i];
|
||||
|
||||
kscan_disable_callback(cfg->child);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
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!
|
||||
const struct device *dev = DEVICE_DT_GET(DT_DRV_INST(0));
|
||||
struct kscan_composite_data *data = dev->data;
|
||||
|
||||
for (int i = 0; i < ARRAY_SIZE(kscan_composite_children); i++) {
|
||||
const struct kscan_composite_child_config *cfg = &kscan_composite_children[i];
|
||||
|
||||
if (cfg->child != child_dev) {
|
||||
continue;
|
||||
}
|
||||
|
||||
data->callback(dev, row + cfg->row_offset, column + cfg->column_offset, pressed);
|
||||
}
|
||||
}
|
||||
|
||||
static int kscan_composite_configure(const struct device *dev, kscan_callback_t callback) {
|
||||
struct kscan_composite_data *data = dev->data;
|
||||
|
||||
if (!callback) {
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
for (int i = 0; i < ARRAY_SIZE(kscan_composite_children); i++) {
|
||||
const struct kscan_composite_child_config *cfg = &kscan_composite_children[i];
|
||||
|
||||
kscan_config(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;
|
||||
|
||||
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,
|
||||
};
|
||||
|
||||
static const struct kscan_composite_config kscan_composite_config = {};
|
||||
|
||||
static struct kscan_composite_data kscan_composite_data;
|
||||
|
||||
DEVICE_DT_INST_DEFINE(0, kscan_composite_init, NULL, &kscan_composite_data, &kscan_composite_config,
|
||||
APPLICATION, CONFIG_KERNEL_INIT_PRIORITY_DEFAULT, &mock_driver_api);
|
||||
33
app/module/drivers/kscan/kscan_gpio.c
Normal file
33
app/module/drivers/kscan/kscan_gpio.c
Normal file
@@ -0,0 +1,33 @@
|
||||
/*
|
||||
* Copyright (c) 2022 The ZMK Contributors
|
||||
*
|
||||
* SPDX-License-Identifier: MIT
|
||||
*/
|
||||
|
||||
#include "kscan_gpio.h"
|
||||
|
||||
#include <stdlib.h>
|
||||
|
||||
static int compare_ports(const void *a, const void *b) {
|
||||
const struct kscan_gpio *gpio_a = a;
|
||||
const struct kscan_gpio *gpio_b = b;
|
||||
|
||||
return gpio_a->spec.port - gpio_b->spec.port;
|
||||
}
|
||||
|
||||
void kscan_gpio_list_sort_by_port(struct kscan_gpio_list *list) {
|
||||
qsort(list->gpios, list->len, sizeof(list->gpios[0]), compare_ports);
|
||||
}
|
||||
|
||||
int kscan_gpio_pin_get(const struct kscan_gpio *gpio, struct kscan_gpio_port_state *state) {
|
||||
if (gpio->spec.port != state->port) {
|
||||
state->port = gpio->spec.port;
|
||||
|
||||
const int err = gpio_port_get(state->port, &state->value);
|
||||
if (err) {
|
||||
return err;
|
||||
}
|
||||
}
|
||||
|
||||
return (state->value & BIT(gpio->spec.pin)) != 0;
|
||||
}
|
||||
61
app/module/drivers/kscan/kscan_gpio.h
Normal file
61
app/module/drivers/kscan/kscan_gpio.h
Normal file
@@ -0,0 +1,61 @@
|
||||
/*
|
||||
* Copyright (c) 2022 The ZMK Contributors
|
||||
*
|
||||
* SPDX-License-Identifier: MIT
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <stdint.h>
|
||||
#include <zephyr/device.h>
|
||||
#include <zephyr/drivers/gpio.h>
|
||||
#include <zephyr/dt-bindings/gpio/gpio.h>
|
||||
#include <zephyr/sys/util.h>
|
||||
|
||||
struct kscan_gpio {
|
||||
struct gpio_dt_spec spec;
|
||||
/** The index of the GPIO in the devicetree *-gpios array. */
|
||||
size_t index;
|
||||
};
|
||||
|
||||
/** GPIO_DT_SPEC_GET_BY_IDX(), but for a struct kscan_gpio. */
|
||||
#define KSCAN_GPIO_GET_BY_IDX(node_id, prop, idx) \
|
||||
((struct kscan_gpio){.spec = GPIO_DT_SPEC_GET_BY_IDX(node_id, prop, idx), .index = idx})
|
||||
|
||||
struct kscan_gpio_list {
|
||||
struct kscan_gpio *gpios;
|
||||
size_t len;
|
||||
};
|
||||
|
||||
/** Define a kscan_gpio_list from a compile-time GPIO array. */
|
||||
#define KSCAN_GPIO_LIST(gpio_array) \
|
||||
((struct kscan_gpio_list){.gpios = gpio_array, .len = ARRAY_SIZE(gpio_array)})
|
||||
|
||||
struct kscan_gpio_port_state {
|
||||
const struct device *port;
|
||||
gpio_port_value_t value;
|
||||
};
|
||||
|
||||
/**
|
||||
* Sorts a GPIO list by port so it can be used with kscan_gpio_pin_get().
|
||||
*/
|
||||
void kscan_gpio_list_sort_by_port(struct kscan_gpio_list *list);
|
||||
|
||||
/**
|
||||
* Get logical level of an input pin.
|
||||
*
|
||||
* This is equivalent to gpio_pin_get() except that, when iterating through the
|
||||
* pins in a list which is sorted by kscan_gpio_list_sort_by_port(), it only
|
||||
* performs one read per port instead of one read per pin.
|
||||
*
|
||||
* @param gpio The input pin to read.
|
||||
* @param state An object to track state between reads. Must be zero-initialized before the first
|
||||
* use.
|
||||
*
|
||||
* @retval 1 If pin logical value is 1 / active.
|
||||
* @retval 0 If pin logical value is 0 / inactive.
|
||||
* @retval -EIO I/O error when accessing an external GPIO chip.
|
||||
* @retval -EWOULDBLOCK if operation would block.
|
||||
*/
|
||||
int kscan_gpio_pin_get(const struct kscan_gpio *gpio, struct kscan_gpio_port_state *state);
|
||||
207
app/module/drivers/kscan/kscan_gpio_demux.c
Normal file
207
app/module/drivers/kscan/kscan_gpio_demux.c
Normal file
@@ -0,0 +1,207 @@
|
||||
/*
|
||||
* Copyright (c) 2020 The ZMK Contributors
|
||||
*
|
||||
* SPDX-License-Identifier: MIT
|
||||
*/
|
||||
|
||||
#define DT_DRV_COMPAT zmk_kscan_gpio_demux
|
||||
|
||||
#include <zephyr/device.h>
|
||||
#include <zephyr/drivers/kscan.h>
|
||||
#include <zephyr/drivers/gpio.h>
|
||||
#include <zephyr/logging/log.h>
|
||||
|
||||
LOG_MODULE_DECLARE(zmk, CONFIG_ZMK_LOG_LEVEL);
|
||||
|
||||
// Helper macro
|
||||
#define PWR_TWO(x) (1 << (x))
|
||||
|
||||
// Define row and col cfg
|
||||
#define _KSCAN_GPIO_CFG_INIT(n, prop, idx) GPIO_DT_SPEC_GET_BY_IDX(n, prop, idx),
|
||||
|
||||
// Check debounce config
|
||||
#define CHECK_DEBOUNCE_CFG(n, a, b) COND_CODE_0(DT_INST_PROP(n, debounce_period), a, b)
|
||||
|
||||
// Define the row and column lengths
|
||||
#define INST_MATRIX_INPUTS(n) DT_INST_PROP_LEN(n, input_gpios)
|
||||
#define INST_DEMUX_GPIOS(n) DT_INST_PROP_LEN(n, output_gpios)
|
||||
#define INST_MATRIX_OUTPUTS(n) PWR_TWO(INST_DEMUX_GPIOS(n))
|
||||
#define POLL_INTERVAL(n) DT_INST_PROP(n, polling_interval_msec)
|
||||
|
||||
#define GPIO_INST_INIT(n) \
|
||||
struct kscan_gpio_irq_callback_##n { \
|
||||
struct CHECK_DEBOUNCE_CFG(n, (k_work), (k_work_delayable)) * work; \
|
||||
struct gpio_callback callback; \
|
||||
const struct device *dev; \
|
||||
}; \
|
||||
\
|
||||
struct kscan_gpio_config_##n { \
|
||||
const struct gpio_dt_spec rows[INST_MATRIX_INPUTS(n)]; \
|
||||
const struct gpio_dt_spec cols[INST_DEMUX_GPIOS(n)]; \
|
||||
}; \
|
||||
\
|
||||
struct kscan_gpio_data_##n { \
|
||||
kscan_callback_t callback; \
|
||||
struct k_timer poll_timer; \
|
||||
struct CHECK_DEBOUNCE_CFG(n, (k_work), (k_work_delayable)) work; \
|
||||
bool matrix_state[INST_MATRIX_INPUTS(n)][INST_MATRIX_OUTPUTS(n)]; \
|
||||
const struct device *dev; \
|
||||
}; \
|
||||
/* IO/GPIO SETUP */ \
|
||||
static const struct gpio_dt_spec *kscan_gpio_input_specs_##n(const struct device *dev) { \
|
||||
const struct kscan_gpio_config_##n *cfg = dev->config; \
|
||||
return cfg->rows; \
|
||||
} \
|
||||
\
|
||||
static const struct gpio_dt_spec *kscan_gpio_output_specs_##n(const struct device *dev) { \
|
||||
const struct kscan_gpio_config_##n *cfg = dev->config; \
|
||||
return cfg->cols; \
|
||||
} \
|
||||
/* POLLING SETUP */ \
|
||||
static void kscan_gpio_timer_handler(struct k_timer *timer) { \
|
||||
struct kscan_gpio_data_##n *data = \
|
||||
CONTAINER_OF(timer, struct kscan_gpio_data_##n, poll_timer); \
|
||||
k_work_submit(&data->work.work); \
|
||||
} \
|
||||
\
|
||||
/* Read the state of the input GPIOs */ \
|
||||
/* This is the core matrix_scan func */ \
|
||||
static int kscan_gpio_read_##n(const struct device *dev) { \
|
||||
bool submit_follow_up_read = false; \
|
||||
struct kscan_gpio_data_##n *data = dev->data; \
|
||||
static bool read_state[INST_MATRIX_INPUTS(n)][INST_MATRIX_OUTPUTS(n)]; \
|
||||
for (int o = 0; o < INST_MATRIX_OUTPUTS(n); o++) { \
|
||||
/* Iterate over bits and set GPIOs accordingly */ \
|
||||
for (uint8_t bit = 0; bit < INST_DEMUX_GPIOS(n); bit++) { \
|
||||
uint8_t state = (o & (0b1 << bit)) >> bit; \
|
||||
const struct gpio_dt_spec *out_spec = &kscan_gpio_output_specs_##n(dev)[bit]; \
|
||||
gpio_pin_set_dt(out_spec, state); \
|
||||
} \
|
||||
/* Let the col settle before reading the rows */ \
|
||||
k_usleep(1); \
|
||||
\
|
||||
for (int i = 0; i < INST_MATRIX_INPUTS(n); i++) { \
|
||||
/* Get the input spec */ \
|
||||
const struct gpio_dt_spec *in_spec = &kscan_gpio_input_specs_##n(dev)[i]; \
|
||||
read_state[i][o] = gpio_pin_get_dt(in_spec) > 0; \
|
||||
} \
|
||||
} \
|
||||
for (int r = 0; r < INST_MATRIX_INPUTS(n); r++) { \
|
||||
for (int c = 0; c < INST_MATRIX_OUTPUTS(n); c++) { \
|
||||
bool pressed = read_state[r][c]; \
|
||||
submit_follow_up_read = (submit_follow_up_read || pressed); \
|
||||
if (pressed != data->matrix_state[r][c]) { \
|
||||
LOG_DBG("Sending event at %d,%d state %s", r, c, (pressed ? "on" : "off")); \
|
||||
data->matrix_state[r][c] = pressed; \
|
||||
data->callback(dev, r, c, pressed); \
|
||||
} \
|
||||
} \
|
||||
} \
|
||||
if (submit_follow_up_read) { \
|
||||
CHECK_DEBOUNCE_CFG(n, ({ k_work_submit(&data->work); }), \
|
||||
({ k_work_reschedule(&data->work, K_MSEC(5)); })) \
|
||||
} \
|
||||
return 0; \
|
||||
} \
|
||||
\
|
||||
static void kscan_gpio_work_handler_##n(struct k_work *work) { \
|
||||
struct kscan_gpio_data_##n *data = CONTAINER_OF(work, struct kscan_gpio_data_##n, work); \
|
||||
kscan_gpio_read_##n(data->dev); \
|
||||
} \
|
||||
\
|
||||
static struct kscan_gpio_data_##n kscan_gpio_data_##n = {}; \
|
||||
\
|
||||
/* KSCAN API configure function */ \
|
||||
static int kscan_gpio_configure_##n(const struct device *dev, kscan_callback_t callback) { \
|
||||
LOG_DBG("KSCAN API configure"); \
|
||||
struct kscan_gpio_data_##n *data = dev->data; \
|
||||
if (!callback) { \
|
||||
return -EINVAL; \
|
||||
} \
|
||||
data->callback = callback; \
|
||||
LOG_DBG("Configured GPIO %d", n); \
|
||||
return 0; \
|
||||
}; \
|
||||
\
|
||||
/* KSCAN API enable function */ \
|
||||
static int kscan_gpio_enable_##n(const struct device *dev) { \
|
||||
LOG_DBG("KSCAN API enable"); \
|
||||
struct kscan_gpio_data_##n *data = dev->data; \
|
||||
/* TODO: we might want a follow up to hook into the sleep state hooks in Zephyr, */ \
|
||||
/* and disable this timer when we enter a sleep state */ \
|
||||
k_timer_start(&data->poll_timer, K_MSEC(POLL_INTERVAL(n)), K_MSEC(POLL_INTERVAL(n))); \
|
||||
return 0; \
|
||||
}; \
|
||||
\
|
||||
/* KSCAN API disable function */ \
|
||||
static int kscan_gpio_disable_##n(const struct device *dev) { \
|
||||
LOG_DBG("KSCAN API disable"); \
|
||||
struct kscan_gpio_data_##n *data = dev->data; \
|
||||
k_timer_stop(&data->poll_timer); \
|
||||
return 0; \
|
||||
}; \
|
||||
\
|
||||
/* GPIO init function*/ \
|
||||
static int kscan_gpio_init_##n(const struct device *dev) { \
|
||||
LOG_DBG("KSCAN GPIO init"); \
|
||||
struct kscan_gpio_data_##n *data = dev->data; \
|
||||
int err; \
|
||||
/* configure input devices*/ \
|
||||
for (int i = 0; i < INST_MATRIX_INPUTS(n); i++) { \
|
||||
const struct gpio_dt_spec *in_spec = &kscan_gpio_input_specs_##n(dev)[i]; \
|
||||
if (!device_is_ready(in_spec->port)) { \
|
||||
LOG_ERR("Unable to find input GPIO device"); \
|
||||
return -EINVAL; \
|
||||
} \
|
||||
err = gpio_pin_configure_dt(in_spec, GPIO_INPUT); \
|
||||
if (err) { \
|
||||
LOG_ERR("Unable to configure pin %d for input", in_spec->pin); \
|
||||
return err; \
|
||||
} else { \
|
||||
LOG_DBG("Configured pin %d for input", in_spec->pin); \
|
||||
} \
|
||||
if (err) { \
|
||||
LOG_ERR("Error adding the callback to the column device"); \
|
||||
return err; \
|
||||
} \
|
||||
} \
|
||||
/* configure output devices*/ \
|
||||
for (int o = 0; o < INST_DEMUX_GPIOS(n); o++) { \
|
||||
const struct gpio_dt_spec *out_spec = &kscan_gpio_output_specs_##n(dev)[o]; \
|
||||
if (!device_is_ready(out_spec->port)) { \
|
||||
LOG_ERR("Unable to find output GPIO device"); \
|
||||
return -EINVAL; \
|
||||
} \
|
||||
err = gpio_pin_configure_dt(out_spec, GPIO_OUTPUT_ACTIVE); \
|
||||
if (err) { \
|
||||
LOG_ERR("Unable to configure pin %d for output", out_spec->pin); \
|
||||
return err; \
|
||||
} else { \
|
||||
LOG_DBG("Configured pin %d for output", out_spec->pin); \
|
||||
} \
|
||||
} \
|
||||
data->dev = dev; \
|
||||
\
|
||||
k_timer_init(&data->poll_timer, kscan_gpio_timer_handler, NULL); \
|
||||
\
|
||||
(CHECK_DEBOUNCE_CFG(n, (k_work_init), (k_work_init_delayable)))( \
|
||||
&data->work, kscan_gpio_work_handler_##n); \
|
||||
return 0; \
|
||||
} \
|
||||
\
|
||||
static const struct kscan_driver_api gpio_driver_api_##n = { \
|
||||
.config = kscan_gpio_configure_##n, \
|
||||
.enable_callback = kscan_gpio_enable_##n, \
|
||||
.disable_callback = kscan_gpio_disable_##n, \
|
||||
}; \
|
||||
\
|
||||
static const struct kscan_gpio_config_##n kscan_gpio_config_##n = { \
|
||||
.rows = {DT_FOREACH_PROP_ELEM(DT_DRV_INST(n), input_gpios, _KSCAN_GPIO_CFG_INIT)}, \
|
||||
.cols = {DT_FOREACH_PROP_ELEM(DT_DRV_INST(n), output_gpios, _KSCAN_GPIO_CFG_INIT)}, \
|
||||
}; \
|
||||
\
|
||||
DEVICE_DT_INST_DEFINE(n, kscan_gpio_init_##n, NULL, &kscan_gpio_data_##n, \
|
||||
&kscan_gpio_config_##n, APPLICATION, CONFIG_APPLICATION_INIT_PRIORITY, \
|
||||
&gpio_driver_api_##n);
|
||||
|
||||
DT_INST_FOREACH_STATUS_OKAY(GPIO_INST_INIT)
|
||||
360
app/module/drivers/kscan/kscan_gpio_direct.c
Normal file
360
app/module/drivers/kscan/kscan_gpio_direct.c
Normal file
@@ -0,0 +1,360 @@
|
||||
/*
|
||||
* Copyright (c) 2020 The ZMK Contributors
|
||||
*
|
||||
* SPDX-License-Identifier: MIT
|
||||
*/
|
||||
|
||||
#include "debounce.h"
|
||||
#include "kscan_gpio.h"
|
||||
|
||||
#include <zephyr/device.h>
|
||||
#include <zephyr/devicetree.h>
|
||||
#include <zephyr/drivers/gpio.h>
|
||||
#include <zephyr/drivers/kscan.h>
|
||||
#include <zephyr/kernel.h>
|
||||
#include <zephyr/logging/log.h>
|
||||
#include <zephyr/sys/util.h>
|
||||
|
||||
LOG_MODULE_DECLARE(zmk, CONFIG_ZMK_LOG_LEVEL);
|
||||
|
||||
#define DT_DRV_COMPAT zmk_kscan_gpio_direct
|
||||
|
||||
#if CONFIG_ZMK_KSCAN_DEBOUNCE_PRESS_MS >= 0
|
||||
#define INST_DEBOUNCE_PRESS_MS(n) CONFIG_ZMK_KSCAN_DEBOUNCE_PRESS_MS
|
||||
#else
|
||||
#define INST_DEBOUNCE_PRESS_MS(n) \
|
||||
DT_INST_PROP_OR(n, debounce_period, DT_INST_PROP(n, debounce_press_ms))
|
||||
#endif
|
||||
|
||||
#if CONFIG_ZMK_KSCAN_DEBOUNCE_RELEASE_MS >= 0
|
||||
#define INST_DEBOUNCE_RELEASE_MS(n) CONFIG_ZMK_KSCAN_DEBOUNCE_RELEASE_MS
|
||||
#else
|
||||
#define INST_DEBOUNCE_RELEASE_MS(n) \
|
||||
DT_INST_PROP_OR(n, debounce_period, DT_INST_PROP(n, debounce_release_ms))
|
||||
#endif
|
||||
|
||||
#define USE_POLLING IS_ENABLED(CONFIG_ZMK_KSCAN_DIRECT_POLLING)
|
||||
#define USE_INTERRUPTS (!USE_POLLING)
|
||||
|
||||
#define COND_INTERRUPTS(code) COND_CODE_1(CONFIG_ZMK_KSCAN_DIRECT_POLLING, (), code)
|
||||
#define COND_POLL_OR_INTERRUPTS(pollcode, intcode) \
|
||||
COND_CODE_1(CONFIG_ZMK_KSCAN_DIRECT_POLLING, pollcode, intcode)
|
||||
|
||||
#define INST_INPUTS_LEN(n) DT_INST_PROP_LEN(n, input_gpios)
|
||||
#define KSCAN_DIRECT_INPUT_CFG_INIT(idx, inst_idx) \
|
||||
KSCAN_GPIO_GET_BY_IDX(DT_DRV_INST(inst_idx), input_gpios, idx)
|
||||
|
||||
struct kscan_direct_irq_callback {
|
||||
const struct device *dev;
|
||||
struct gpio_callback callback;
|
||||
};
|
||||
|
||||
struct kscan_direct_data {
|
||||
const struct device *dev;
|
||||
struct kscan_gpio_list inputs;
|
||||
kscan_callback_t callback;
|
||||
struct k_work_delayable work;
|
||||
#if USE_INTERRUPTS
|
||||
/** Array of length config->inputs.len */
|
||||
struct kscan_direct_irq_callback *irqs;
|
||||
#endif
|
||||
/** Timestamp of the current or scheduled scan. */
|
||||
int64_t scan_time;
|
||||
/** Current state of the inputs as an array of length config->inputs.len */
|
||||
struct debounce_state *pin_state;
|
||||
};
|
||||
|
||||
struct kscan_direct_config {
|
||||
struct debounce_config debounce_config;
|
||||
int32_t debounce_scan_period_ms;
|
||||
int32_t poll_period_ms;
|
||||
bool toggle_mode;
|
||||
};
|
||||
|
||||
#if USE_INTERRUPTS
|
||||
static int kscan_direct_interrupt_configure(const struct device *dev, const gpio_flags_t flags) {
|
||||
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_interrupt_configure_dt(gpio, flags);
|
||||
if (err) {
|
||||
LOG_ERR("Unable to configure interrupt for pin %u on %s", gpio->pin, gpio->port->name);
|
||||
return err;
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
#endif
|
||||
|
||||
#if USE_INTERRUPTS
|
||||
static int kscan_direct_interrupt_enable(const struct device *dev) {
|
||||
return kscan_direct_interrupt_configure(dev, GPIO_INT_LEVEL_ACTIVE);
|
||||
}
|
||||
#endif
|
||||
|
||||
#if USE_INTERRUPTS
|
||||
static int kscan_direct_interrupt_disable(const struct device *dev) {
|
||||
return kscan_direct_interrupt_configure(dev, GPIO_INT_DISABLE);
|
||||
}
|
||||
#endif
|
||||
|
||||
#if USE_INTERRUPTS
|
||||
static void kscan_direct_irq_callback_handler(const struct device *port, struct gpio_callback *cb,
|
||||
const gpio_port_pins_t pin) {
|
||||
struct kscan_direct_irq_callback *irq_data =
|
||||
CONTAINER_OF(cb, struct kscan_direct_irq_callback, callback);
|
||||
struct kscan_direct_data *data = irq_data->dev->data;
|
||||
|
||||
// Disable our interrupts temporarily to avoid re-entry while we scan.
|
||||
kscan_direct_interrupt_disable(data->dev);
|
||||
|
||||
data->scan_time = k_uptime_get();
|
||||
|
||||
k_work_reschedule(&data->work, K_NO_WAIT);
|
||||
}
|
||||
#endif
|
||||
|
||||
static gpio_flags_t kscan_gpio_get_extra_flags(const struct gpio_dt_spec *gpio, bool active) {
|
||||
if (!active) {
|
||||
return ((BIT(0) & gpio->dt_flags) ? GPIO_PULL_UP : GPIO_PULL_DOWN);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int kscan_inputs_set_flags(const struct kscan_gpio_list *inputs,
|
||||
const struct gpio_dt_spec *active_gpio) {
|
||||
for (int i = 0; i < inputs->len; i++) {
|
||||
const bool active = &inputs->gpios[i].spec == active_gpio;
|
||||
const gpio_flags_t extra_flags =
|
||||
GPIO_INPUT | kscan_gpio_get_extra_flags(&inputs->gpios[i].spec, active);
|
||||
LOG_DBG("Extra flags equal to: %d", extra_flags);
|
||||
|
||||
int err = gpio_pin_configure_dt(&inputs->gpios[i].spec, extra_flags);
|
||||
if (err) {
|
||||
LOG_ERR("Unable to configure flags on pin %d on %s", inputs->gpios[i].spec.pin,
|
||||
inputs->gpios[i].spec.port->name);
|
||||
return err;
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void kscan_direct_read_continue(const struct device *dev) {
|
||||
const struct kscan_direct_config *config = dev->config;
|
||||
struct kscan_direct_data *data = dev->data;
|
||||
|
||||
data->scan_time += config->debounce_scan_period_ms;
|
||||
|
||||
k_work_reschedule(&data->work, K_TIMEOUT_ABS_MS(data->scan_time));
|
||||
}
|
||||
|
||||
static void kscan_direct_read_end(const struct device *dev) {
|
||||
#if USE_INTERRUPTS
|
||||
// Return to waiting for an interrupt.
|
||||
kscan_direct_interrupt_enable(dev);
|
||||
#else
|
||||
struct kscan_direct_data *data = dev->data;
|
||||
const struct kscan_direct_config *config = dev->config;
|
||||
|
||||
data->scan_time += config->poll_period_ms;
|
||||
|
||||
// Return to polling slowly.
|
||||
k_work_reschedule(&data->work, K_TIMEOUT_ABS_MS(data->scan_time));
|
||||
#endif
|
||||
}
|
||||
|
||||
static int kscan_direct_read(const struct device *dev) {
|
||||
struct kscan_direct_data *data = dev->data;
|
||||
const struct kscan_direct_config *config = dev->config;
|
||||
|
||||
// Read the inputs.
|
||||
struct kscan_gpio_port_state state = {0};
|
||||
|
||||
for (int i = 0; i < data->inputs.len; i++) {
|
||||
const struct kscan_gpio *gpio = &data->inputs.gpios[i];
|
||||
|
||||
const int active = kscan_gpio_pin_get(gpio, &state);
|
||||
if (active < 0) {
|
||||
LOG_ERR("Failed to read port %s: %i", gpio->spec.port->name, active);
|
||||
return active;
|
||||
}
|
||||
|
||||
debounce_update(&data->pin_state[gpio->index], active, config->debounce_scan_period_ms,
|
||||
&config->debounce_config);
|
||||
}
|
||||
|
||||
// Process the new state.
|
||||
bool continue_scan = false;
|
||||
|
||||
for (int i = 0; i < data->inputs.len; i++) {
|
||||
const struct kscan_gpio *gpio = &data->inputs.gpios[i];
|
||||
struct debounce_state *state = &data->pin_state[gpio->index];
|
||||
|
||||
if (debounce_get_changed(state)) {
|
||||
const bool pressed = debounce_is_pressed(state);
|
||||
|
||||
LOG_DBG("Sending event at 0,%i state %s", gpio->index, pressed ? "on" : "off");
|
||||
data->callback(dev, 0, gpio->index, pressed);
|
||||
if (config->toggle_mode && pressed) {
|
||||
kscan_inputs_set_flags(&data->inputs, &gpio->spec);
|
||||
}
|
||||
}
|
||||
|
||||
continue_scan = continue_scan || debounce_is_active(state);
|
||||
}
|
||||
|
||||
if (continue_scan) {
|
||||
// At least one key is pressed or the debouncer has not yet decided if
|
||||
// it is pressed. Poll quickly until everything is released.
|
||||
kscan_direct_read_continue(dev);
|
||||
} else {
|
||||
// All keys are released. Return to normal.
|
||||
kscan_direct_read_end(dev);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void kscan_direct_work_handler(struct k_work *work) {
|
||||
struct k_work_delayable *dwork = CONTAINER_OF(work, struct k_work_delayable, work);
|
||||
struct kscan_direct_data *data = CONTAINER_OF(dwork, struct kscan_direct_data, work);
|
||||
kscan_direct_read(data->dev);
|
||||
}
|
||||
|
||||
static int kscan_direct_configure(const struct device *dev, kscan_callback_t callback) {
|
||||
struct kscan_direct_data *data = dev->data;
|
||||
|
||||
if (!callback) {
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
data->callback = callback;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int kscan_direct_enable(const struct device *dev) {
|
||||
struct kscan_direct_data *data = dev->data;
|
||||
|
||||
data->scan_time = k_uptime_get();
|
||||
|
||||
// Read will automatically start interrupts/polling once done.
|
||||
return kscan_direct_read(dev);
|
||||
}
|
||||
|
||||
static int kscan_direct_disable(const struct device *dev) {
|
||||
struct kscan_direct_data *data = dev->data;
|
||||
|
||||
k_work_cancel_delayable(&data->work);
|
||||
|
||||
#if USE_INTERRUPTS
|
||||
return kscan_direct_interrupt_disable(dev);
|
||||
#else
|
||||
return 0;
|
||||
#endif
|
||||
}
|
||||
|
||||
static int kscan_direct_init_input_inst(const struct device *dev, const struct gpio_dt_spec *gpio,
|
||||
const int index, bool toggle_mode) {
|
||||
if (!device_is_ready(gpio->port)) {
|
||||
LOG_ERR("GPIO is not ready: %s", gpio->port->name);
|
||||
return -ENODEV;
|
||||
}
|
||||
int err = gpio_pin_configure_dt(
|
||||
gpio, GPIO_INPUT | (toggle_mode ? kscan_gpio_get_extra_flags(gpio, false) : 0));
|
||||
if (err) {
|
||||
LOG_ERR("Unable to configure pin %u on %s for input", gpio->pin, gpio->port->name);
|
||||
return err;
|
||||
}
|
||||
|
||||
LOG_DBG("Configured pin %u on %s for input", gpio->pin, gpio->port->name);
|
||||
|
||||
#if USE_INTERRUPTS
|
||||
struct kscan_direct_data *data = dev->data;
|
||||
struct kscan_direct_irq_callback *irq = &data->irqs[index];
|
||||
|
||||
irq->dev = dev;
|
||||
gpio_init_callback(&irq->callback, kscan_direct_irq_callback_handler, BIT(gpio->pin));
|
||||
err = gpio_add_callback(gpio->port, &irq->callback);
|
||||
if (err) {
|
||||
LOG_ERR("Error adding the callback to the input device: %i", err);
|
||||
return err;
|
||||
}
|
||||
#endif
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
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;
|
||||
|
||||
for (int i = 0; i < data->inputs.len; i++) {
|
||||
const struct gpio_dt_spec *gpio = &data->inputs.gpios[i].spec;
|
||||
int err = kscan_direct_init_input_inst(dev, gpio, i, config->toggle_mode);
|
||||
if (err) {
|
||||
return err;
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int kscan_direct_init(const struct device *dev) {
|
||||
struct kscan_direct_data *data = dev->data;
|
||||
|
||||
data->dev = dev;
|
||||
|
||||
// Sort inputs by port so we can read each port just once per scan.
|
||||
kscan_gpio_list_sort_by_port(&data->inputs);
|
||||
|
||||
kscan_direct_init_inputs(dev);
|
||||
|
||||
k_work_init_delayable(&data->work, kscan_direct_work_handler);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const struct kscan_driver_api kscan_direct_api = {
|
||||
.config = kscan_direct_configure,
|
||||
.enable_callback = kscan_direct_enable,
|
||||
.disable_callback = kscan_direct_disable,
|
||||
};
|
||||
|
||||
#define KSCAN_DIRECT_INIT(n) \
|
||||
BUILD_ASSERT(INST_DEBOUNCE_PRESS_MS(n) <= DEBOUNCE_COUNTER_MAX, \
|
||||
"ZMK_KSCAN_DEBOUNCE_PRESS_MS or debounce-press-ms is too large"); \
|
||||
BUILD_ASSERT(INST_DEBOUNCE_RELEASE_MS(n) <= DEBOUNCE_COUNTER_MAX, \
|
||||
"ZMK_KSCAN_DEBOUNCE_RELEASE_MS or debounce-release-ms is too large"); \
|
||||
\
|
||||
static struct kscan_gpio kscan_direct_inputs_##n[] = { \
|
||||
LISTIFY(INST_INPUTS_LEN(n), KSCAN_DIRECT_INPUT_CFG_INIT, (, ), n)}; \
|
||||
\
|
||||
static struct debounce_state kscan_direct_state_##n[INST_INPUTS_LEN(n)]; \
|
||||
\
|
||||
COND_INTERRUPTS( \
|
||||
(static struct kscan_direct_irq_callback kscan_direct_irqs_##n[INST_INPUTS_LEN(n)];)) \
|
||||
\
|
||||
static struct kscan_direct_data kscan_direct_data_##n = { \
|
||||
.inputs = KSCAN_GPIO_LIST(kscan_direct_inputs_##n), \
|
||||
.pin_state = kscan_direct_state_##n, \
|
||||
COND_INTERRUPTS((.irqs = kscan_direct_irqs_##n, ))}; \
|
||||
\
|
||||
static struct kscan_direct_config kscan_direct_config_##n = { \
|
||||
.debounce_config = \
|
||||
{ \
|
||||
.debounce_press_ms = INST_DEBOUNCE_PRESS_MS(n), \
|
||||
.debounce_release_ms = INST_DEBOUNCE_RELEASE_MS(n), \
|
||||
}, \
|
||||
.debounce_scan_period_ms = DT_INST_PROP(n, debounce_scan_period_ms), \
|
||||
.poll_period_ms = DT_INST_PROP(n, poll_period_ms), \
|
||||
.toggle_mode = DT_INST_PROP(n, toggle_mode), \
|
||||
}; \
|
||||
\
|
||||
DEVICE_DT_INST_DEFINE(n, &kscan_direct_init, NULL, &kscan_direct_data_##n, \
|
||||
&kscan_direct_config_##n, APPLICATION, CONFIG_APPLICATION_INIT_PRIORITY, \
|
||||
&kscan_direct_api);
|
||||
|
||||
DT_INST_FOREACH_STATUS_OKAY(KSCAN_DIRECT_INIT);
|
||||
471
app/module/drivers/kscan/kscan_gpio_matrix.c
Normal file
471
app/module/drivers/kscan/kscan_gpio_matrix.c
Normal file
@@ -0,0 +1,471 @@
|
||||
/*
|
||||
* Copyright (c) 2020-2021 The ZMK Contributors
|
||||
*
|
||||
* SPDX-License-Identifier: MIT
|
||||
*/
|
||||
|
||||
#include "debounce.h"
|
||||
#include "kscan_gpio.h"
|
||||
|
||||
#include <zephyr/device.h>
|
||||
#include <zephyr/devicetree.h>
|
||||
#include <zephyr/drivers/gpio.h>
|
||||
#include <zephyr/drivers/kscan.h>
|
||||
#include <zephyr/kernel.h>
|
||||
#include <zephyr/logging/log.h>
|
||||
#include <zephyr/sys/__assert.h>
|
||||
#include <zephyr/sys/util.h>
|
||||
|
||||
LOG_MODULE_DECLARE(zmk, CONFIG_ZMK_LOG_LEVEL);
|
||||
|
||||
#define DT_DRV_COMPAT zmk_kscan_gpio_matrix
|
||||
|
||||
#define INST_DIODE_DIR(n) DT_ENUM_IDX(DT_DRV_INST(n), diode_direction)
|
||||
#define COND_DIODE_DIR(n, row2col_code, col2row_code) \
|
||||
COND_CODE_0(INST_DIODE_DIR(n), row2col_code, col2row_code)
|
||||
|
||||
#define INST_ROWS_LEN(n) DT_INST_PROP_LEN(n, row_gpios)
|
||||
#define INST_COLS_LEN(n) DT_INST_PROP_LEN(n, col_gpios)
|
||||
#define INST_MATRIX_LEN(n) (INST_ROWS_LEN(n) * INST_COLS_LEN(n))
|
||||
#define INST_INPUTS_LEN(n) COND_DIODE_DIR(n, (INST_COLS_LEN(n)), (INST_ROWS_LEN(n)))
|
||||
|
||||
#if CONFIG_ZMK_KSCAN_DEBOUNCE_PRESS_MS >= 0
|
||||
#define INST_DEBOUNCE_PRESS_MS(n) CONFIG_ZMK_KSCAN_DEBOUNCE_PRESS_MS
|
||||
#else
|
||||
#define INST_DEBOUNCE_PRESS_MS(n) \
|
||||
DT_INST_PROP_OR(n, debounce_period, DT_INST_PROP(n, debounce_press_ms))
|
||||
#endif
|
||||
|
||||
#if CONFIG_ZMK_KSCAN_DEBOUNCE_RELEASE_MS >= 0
|
||||
#define INST_DEBOUNCE_RELEASE_MS(n) CONFIG_ZMK_KSCAN_DEBOUNCE_RELEASE_MS
|
||||
#else
|
||||
#define INST_DEBOUNCE_RELEASE_MS(n) \
|
||||
DT_INST_PROP_OR(n, debounce_period, DT_INST_PROP(n, debounce_release_ms))
|
||||
#endif
|
||||
|
||||
#define USE_POLLING IS_ENABLED(CONFIG_ZMK_KSCAN_MATRIX_POLLING)
|
||||
#define USE_INTERRUPTS (!USE_POLLING)
|
||||
|
||||
#define COND_INTERRUPTS(code) COND_CODE_1(CONFIG_ZMK_KSCAN_MATRIX_POLLING, (), code)
|
||||
#define COND_POLL_OR_INTERRUPTS(pollcode, intcode) \
|
||||
COND_CODE_1(CONFIG_ZMK_KSCAN_MATRIX_POLLING, pollcode, intcode)
|
||||
|
||||
#define KSCAN_GPIO_ROW_CFG_INIT(idx, inst_idx) \
|
||||
KSCAN_GPIO_GET_BY_IDX(DT_DRV_INST(inst_idx), row_gpios, idx)
|
||||
#define KSCAN_GPIO_COL_CFG_INIT(idx, inst_idx) \
|
||||
KSCAN_GPIO_GET_BY_IDX(DT_DRV_INST(inst_idx), col_gpios, idx)
|
||||
|
||||
enum kscan_diode_direction {
|
||||
KSCAN_ROW2COL,
|
||||
KSCAN_COL2ROW,
|
||||
};
|
||||
|
||||
struct kscan_matrix_irq_callback {
|
||||
const struct device *dev;
|
||||
struct gpio_callback callback;
|
||||
};
|
||||
|
||||
struct kscan_matrix_data {
|
||||
const struct device *dev;
|
||||
struct kscan_gpio_list inputs;
|
||||
kscan_callback_t callback;
|
||||
struct k_work_delayable work;
|
||||
#if USE_INTERRUPTS
|
||||
/** Array of length config->inputs.len */
|
||||
struct kscan_matrix_irq_callback *irqs;
|
||||
#endif
|
||||
/** Timestamp of the current or scheduled scan. */
|
||||
int64_t scan_time;
|
||||
/**
|
||||
* Current state of the matrix as a flattened 2D array of length
|
||||
* (config->rows * config->cols)
|
||||
*/
|
||||
struct debounce_state *matrix_state;
|
||||
};
|
||||
|
||||
struct kscan_matrix_config {
|
||||
struct kscan_gpio_list outputs;
|
||||
struct debounce_config debounce_config;
|
||||
size_t rows;
|
||||
size_t cols;
|
||||
int32_t debounce_scan_period_ms;
|
||||
int32_t poll_period_ms;
|
||||
enum kscan_diode_direction diode_direction;
|
||||
};
|
||||
|
||||
/**
|
||||
* Get the index into a matrix state array from a row and column.
|
||||
*/
|
||||
static int state_index_rc(const struct kscan_matrix_config *config, const int row, const int col) {
|
||||
__ASSERT(row < config->rows, "Invalid row %i", row);
|
||||
__ASSERT(col < config->cols, "Invalid column %i", col);
|
||||
|
||||
return (col * config->rows) + row;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the index into a matrix state array from input/output pin indices.
|
||||
*/
|
||||
static int state_index_io(const struct kscan_matrix_config *config, const int input_idx,
|
||||
const int output_idx) {
|
||||
return (config->diode_direction == KSCAN_ROW2COL)
|
||||
? state_index_rc(config, output_idx, input_idx)
|
||||
: state_index_rc(config, input_idx, output_idx);
|
||||
}
|
||||
|
||||
static int kscan_matrix_set_all_outputs(const struct device *dev, const int value) {
|
||||
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_set_dt(gpio, value);
|
||||
if (err) {
|
||||
LOG_ERR("Failed to set output %i to %i: %i", i, value, err);
|
||||
return err;
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
#if USE_INTERRUPTS
|
||||
static int kscan_matrix_interrupt_configure(const struct device *dev, const gpio_flags_t flags) {
|
||||
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_interrupt_configure_dt(gpio, flags);
|
||||
if (err) {
|
||||
LOG_ERR("Unable to configure interrupt for pin %u on %s", gpio->pin, gpio->port->name);
|
||||
return err;
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
#endif
|
||||
|
||||
#if USE_INTERRUPTS
|
||||
static int kscan_matrix_interrupt_enable(const struct device *dev) {
|
||||
int err = kscan_matrix_interrupt_configure(dev, GPIO_INT_LEVEL_ACTIVE);
|
||||
if (err) {
|
||||
return err;
|
||||
}
|
||||
|
||||
// While interrupts are enabled, set all outputs active so a pressed key
|
||||
// will trigger an interrupt.
|
||||
return kscan_matrix_set_all_outputs(dev, 1);
|
||||
}
|
||||
#endif
|
||||
|
||||
#if USE_INTERRUPTS
|
||||
static int kscan_matrix_interrupt_disable(const struct device *dev) {
|
||||
int err = kscan_matrix_interrupt_configure(dev, GPIO_INT_DISABLE);
|
||||
if (err) {
|
||||
return err;
|
||||
}
|
||||
|
||||
// While interrupts are disabled, set all outputs inactive so
|
||||
// kscan_matrix_read() can scan them one by one.
|
||||
return kscan_matrix_set_all_outputs(dev, 0);
|
||||
}
|
||||
#endif
|
||||
|
||||
#if USE_INTERRUPTS
|
||||
static void kscan_matrix_irq_callback_handler(const struct device *port, struct gpio_callback *cb,
|
||||
const gpio_port_pins_t pin) {
|
||||
struct kscan_matrix_irq_callback *irq_data =
|
||||
CONTAINER_OF(cb, struct kscan_matrix_irq_callback, callback);
|
||||
struct kscan_matrix_data *data = irq_data->dev->data;
|
||||
|
||||
// Disable our interrupts temporarily to avoid re-entry while we scan.
|
||||
kscan_matrix_interrupt_disable(data->dev);
|
||||
|
||||
data->scan_time = k_uptime_get();
|
||||
|
||||
k_work_reschedule(&data->work, K_NO_WAIT);
|
||||
}
|
||||
#endif
|
||||
|
||||
static void kscan_matrix_read_continue(const struct device *dev) {
|
||||
const struct kscan_matrix_config *config = dev->config;
|
||||
struct kscan_matrix_data *data = dev->data;
|
||||
|
||||
data->scan_time += config->debounce_scan_period_ms;
|
||||
|
||||
k_work_reschedule(&data->work, K_TIMEOUT_ABS_MS(data->scan_time));
|
||||
}
|
||||
|
||||
static void kscan_matrix_read_end(const struct device *dev) {
|
||||
#if USE_INTERRUPTS
|
||||
// Return to waiting for an interrupt.
|
||||
kscan_matrix_interrupt_enable(dev);
|
||||
#else
|
||||
struct kscan_matrix_data *data = dev->data;
|
||||
const struct kscan_matrix_config *config = dev->config;
|
||||
|
||||
data->scan_time += config->poll_period_ms;
|
||||
|
||||
// Return to polling slowly.
|
||||
k_work_reschedule(&data->work, K_TIMEOUT_ABS_MS(data->scan_time));
|
||||
#endif
|
||||
}
|
||||
|
||||
static int kscan_matrix_read(const struct device *dev) {
|
||||
struct kscan_matrix_data *data = dev->data;
|
||||
const struct kscan_matrix_config *config = dev->config;
|
||||
|
||||
// Scan the matrix.
|
||||
for (int i = 0; i < config->outputs.len; i++) {
|
||||
const struct kscan_gpio *out_gpio = &config->outputs.gpios[i];
|
||||
|
||||
int err = gpio_pin_set_dt(&out_gpio->spec, 1);
|
||||
if (err) {
|
||||
LOG_ERR("Failed to set output %i active: %i", out_gpio->index, err);
|
||||
return err;
|
||||
}
|
||||
|
||||
#if CONFIG_ZMK_KSCAN_MATRIX_WAIT_BEFORE_INPUTS > 0
|
||||
k_busy_wait(CONFIG_ZMK_KSCAN_MATRIX_WAIT_BEFORE_INPUTS);
|
||||
#endif
|
||||
struct kscan_gpio_port_state state = {0};
|
||||
|
||||
for (int j = 0; j < data->inputs.len; j++) {
|
||||
const struct kscan_gpio *in_gpio = &data->inputs.gpios[j];
|
||||
|
||||
const int index = state_index_io(config, in_gpio->index, out_gpio->index);
|
||||
const int active = kscan_gpio_pin_get(in_gpio, &state);
|
||||
if (active < 0) {
|
||||
LOG_ERR("Failed to read port %s: %i", in_gpio->spec.port->name, active);
|
||||
return active;
|
||||
}
|
||||
|
||||
debounce_update(&data->matrix_state[index], active, config->debounce_scan_period_ms,
|
||||
&config->debounce_config);
|
||||
}
|
||||
|
||||
err = gpio_pin_set_dt(&out_gpio->spec, 0);
|
||||
if (err) {
|
||||
LOG_ERR("Failed to set output %i inactive: %i", out_gpio->index, err);
|
||||
return err;
|
||||
}
|
||||
|
||||
#if CONFIG_ZMK_KSCAN_MATRIX_WAIT_BETWEEN_OUTPUTS > 0
|
||||
k_busy_wait(CONFIG_ZMK_KSCAN_MATRIX_WAIT_BETWEEN_OUTPUTS);
|
||||
#endif
|
||||
}
|
||||
|
||||
// Process the new state.
|
||||
bool continue_scan = false;
|
||||
|
||||
for (int r = 0; r < config->rows; r++) {
|
||||
for (int c = 0; c < config->cols; c++) {
|
||||
const int index = state_index_rc(config, r, c);
|
||||
struct debounce_state *state = &data->matrix_state[index];
|
||||
|
||||
if (debounce_get_changed(state)) {
|
||||
const bool pressed = debounce_is_pressed(state);
|
||||
|
||||
LOG_DBG("Sending event at %i,%i state %s", r, c, pressed ? "on" : "off");
|
||||
data->callback(dev, r, c, pressed);
|
||||
}
|
||||
|
||||
continue_scan = continue_scan || debounce_is_active(state);
|
||||
}
|
||||
}
|
||||
|
||||
if (continue_scan) {
|
||||
// At least one key is pressed or the debouncer has not yet decided if
|
||||
// it is pressed. Poll quickly until everything is released.
|
||||
kscan_matrix_read_continue(dev);
|
||||
} else {
|
||||
// All keys are released. Return to normal.
|
||||
kscan_matrix_read_end(dev);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void kscan_matrix_work_handler(struct k_work *work) {
|
||||
struct k_work_delayable *dwork = CONTAINER_OF(work, struct k_work_delayable, work);
|
||||
struct kscan_matrix_data *data = CONTAINER_OF(dwork, struct kscan_matrix_data, work);
|
||||
kscan_matrix_read(data->dev);
|
||||
}
|
||||
|
||||
static int kscan_matrix_configure(const struct device *dev, const kscan_callback_t callback) {
|
||||
struct kscan_matrix_data *data = dev->data;
|
||||
|
||||
if (!callback) {
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
data->callback = callback;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int kscan_matrix_enable(const struct device *dev) {
|
||||
struct kscan_matrix_data *data = dev->data;
|
||||
|
||||
data->scan_time = k_uptime_get();
|
||||
|
||||
// Read will automatically start interrupts/polling once done.
|
||||
return kscan_matrix_read(dev);
|
||||
}
|
||||
|
||||
static int kscan_matrix_disable(const struct device *dev) {
|
||||
struct kscan_matrix_data *data = dev->data;
|
||||
|
||||
k_work_cancel_delayable(&data->work);
|
||||
|
||||
#if USE_INTERRUPTS
|
||||
return kscan_matrix_interrupt_disable(dev);
|
||||
#else
|
||||
return 0;
|
||||
#endif
|
||||
}
|
||||
|
||||
static int kscan_matrix_init_input_inst(const struct device *dev, const struct kscan_gpio *gpio) {
|
||||
if (!device_is_ready(gpio->spec.port)) {
|
||||
LOG_ERR("GPIO is not ready: %s", gpio->spec.port->name);
|
||||
return -ENODEV;
|
||||
}
|
||||
|
||||
int err = gpio_pin_configure_dt(&gpio->spec, GPIO_INPUT);
|
||||
if (err) {
|
||||
LOG_ERR("Unable to configure pin %u on %s for input", gpio->spec.pin,
|
||||
gpio->spec.port->name);
|
||||
return err;
|
||||
}
|
||||
|
||||
LOG_DBG("Configured pin %u on %s for input", gpio->spec.pin, gpio->spec.port->name);
|
||||
|
||||
#if USE_INTERRUPTS
|
||||
struct kscan_matrix_data *data = dev->data;
|
||||
struct kscan_matrix_irq_callback *irq = &data->irqs[gpio->index];
|
||||
|
||||
irq->dev = dev;
|
||||
gpio_init_callback(&irq->callback, kscan_matrix_irq_callback_handler, BIT(gpio->spec.pin));
|
||||
err = gpio_add_callback(gpio->spec.port, &irq->callback);
|
||||
if (err) {
|
||||
LOG_ERR("Error adding the callback to the input device: %i", err);
|
||||
return err;
|
||||
}
|
||||
#endif
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int kscan_matrix_init_inputs(const struct device *dev) {
|
||||
const struct kscan_matrix_data *data = dev->data;
|
||||
|
||||
for (int i = 0; i < data->inputs.len; i++) {
|
||||
const struct kscan_gpio *gpio = &data->inputs.gpios[i];
|
||||
int err = kscan_matrix_init_input_inst(dev, gpio);
|
||||
if (err) {
|
||||
return err;
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int kscan_matrix_init_output_inst(const struct device *dev,
|
||||
const struct gpio_dt_spec *gpio) {
|
||||
if (!device_is_ready(gpio->port)) {
|
||||
LOG_ERR("GPIO is not ready: %s", gpio->port->name);
|
||||
return -ENODEV;
|
||||
}
|
||||
|
||||
int err = gpio_pin_configure_dt(gpio, GPIO_OUTPUT);
|
||||
if (err) {
|
||||
LOG_ERR("Unable to configure pin %u on %s for output", gpio->pin, gpio->port->name);
|
||||
return err;
|
||||
}
|
||||
|
||||
LOG_DBG("Configured pin %u on %s for output", gpio->pin, gpio->port->name);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int kscan_matrix_init_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 = kscan_matrix_init_output_inst(dev, gpio);
|
||||
if (err) {
|
||||
return err;
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int kscan_matrix_init(const struct device *dev) {
|
||||
struct kscan_matrix_data *data = dev->data;
|
||||
|
||||
data->dev = 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);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const struct kscan_driver_api kscan_matrix_api = {
|
||||
.config = kscan_matrix_configure,
|
||||
.enable_callback = kscan_matrix_enable,
|
||||
.disable_callback = kscan_matrix_disable,
|
||||
};
|
||||
|
||||
#define KSCAN_MATRIX_INIT(n) \
|
||||
BUILD_ASSERT(INST_DEBOUNCE_PRESS_MS(n) <= DEBOUNCE_COUNTER_MAX, \
|
||||
"ZMK_KSCAN_DEBOUNCE_PRESS_MS or debounce-press-ms is too large"); \
|
||||
BUILD_ASSERT(INST_DEBOUNCE_RELEASE_MS(n) <= DEBOUNCE_COUNTER_MAX, \
|
||||
"ZMK_KSCAN_DEBOUNCE_RELEASE_MS or debounce-release-ms is too large"); \
|
||||
\
|
||||
static struct kscan_gpio kscan_matrix_rows_##n[] = { \
|
||||
LISTIFY(INST_ROWS_LEN(n), KSCAN_GPIO_ROW_CFG_INIT, (, ), n)}; \
|
||||
\
|
||||
static struct kscan_gpio kscan_matrix_cols_##n[] = { \
|
||||
LISTIFY(INST_COLS_LEN(n), KSCAN_GPIO_COL_CFG_INIT, (, ), n)}; \
|
||||
\
|
||||
static struct debounce_state kscan_matrix_state_##n[INST_MATRIX_LEN(n)]; \
|
||||
\
|
||||
COND_INTERRUPTS( \
|
||||
(static struct kscan_matrix_irq_callback kscan_matrix_irqs_##n[INST_INPUTS_LEN(n)];)) \
|
||||
\
|
||||
static struct kscan_matrix_data kscan_matrix_data_##n = { \
|
||||
.inputs = \
|
||||
KSCAN_GPIO_LIST(COND_DIODE_DIR(n, (kscan_matrix_cols_##n), (kscan_matrix_rows_##n))), \
|
||||
.matrix_state = kscan_matrix_state_##n, \
|
||||
COND_INTERRUPTS((.irqs = kscan_matrix_irqs_##n, ))}; \
|
||||
\
|
||||
static struct kscan_matrix_config kscan_matrix_config_##n = { \
|
||||
.rows = ARRAY_SIZE(kscan_matrix_rows_##n), \
|
||||
.cols = ARRAY_SIZE(kscan_matrix_cols_##n), \
|
||||
.outputs = \
|
||||
KSCAN_GPIO_LIST(COND_DIODE_DIR(n, (kscan_matrix_rows_##n), (kscan_matrix_cols_##n))), \
|
||||
.debounce_config = \
|
||||
{ \
|
||||
.debounce_press_ms = INST_DEBOUNCE_PRESS_MS(n), \
|
||||
.debounce_release_ms = INST_DEBOUNCE_RELEASE_MS(n), \
|
||||
}, \
|
||||
.debounce_scan_period_ms = DT_INST_PROP(n, debounce_scan_period_ms), \
|
||||
.poll_period_ms = DT_INST_PROP(n, poll_period_ms), \
|
||||
.diode_direction = INST_DIODE_DIR(n), \
|
||||
}; \
|
||||
\
|
||||
DEVICE_DT_INST_DEFINE(n, &kscan_matrix_init, NULL, &kscan_matrix_data_##n, \
|
||||
&kscan_matrix_config_##n, APPLICATION, CONFIG_APPLICATION_INIT_PRIORITY, \
|
||||
&kscan_matrix_api);
|
||||
|
||||
DT_INST_FOREACH_STATUS_OKAY(KSCAN_MATRIX_INIT);
|
||||
95
app/module/drivers/kscan/kscan_mock.c
Normal file
95
app/module/drivers/kscan/kscan_mock.c
Normal file
@@ -0,0 +1,95 @@
|
||||
/*
|
||||
* Copyright (c) 2020 The ZMK Contributors
|
||||
*
|
||||
* SPDX-License-Identifier: MIT
|
||||
*/
|
||||
|
||||
#define DT_DRV_COMPAT zmk_kscan_mock
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <zephyr/device.h>
|
||||
#include <zephyr/drivers/kscan.h>
|
||||
#include <zephyr/logging/log.h>
|
||||
|
||||
LOG_MODULE_DECLARE(zmk, CONFIG_ZMK_LOG_LEVEL);
|
||||
|
||||
#include <dt-bindings/zmk/kscan_mock.h>
|
||||
|
||||
struct kscan_mock_data {
|
||||
kscan_callback_t callback;
|
||||
|
||||
uint32_t event_index;
|
||||
struct k_work_delayable work;
|
||||
const struct device *dev;
|
||||
};
|
||||
|
||||
static int kscan_mock_disable_callback(const struct device *dev) {
|
||||
struct kscan_mock_data *data = dev->data;
|
||||
|
||||
k_work_cancel_delayable(&data->work);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int kscan_mock_configure(const struct device *dev, kscan_callback_t callback) {
|
||||
struct kscan_mock_data *data = dev->data;
|
||||
|
||||
if (!callback) {
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
data->event_index = 0;
|
||||
data->callback = callback;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
#define MOCK_INST_INIT(n) \
|
||||
struct kscan_mock_config_##n { \
|
||||
uint32_t events[DT_INST_PROP_LEN(n, events)]; \
|
||||
bool exit_after; \
|
||||
}; \
|
||||
static void kscan_mock_schedule_next_event_##n(const struct device *dev) { \
|
||||
struct kscan_mock_data *data = dev->data; \
|
||||
const struct kscan_mock_config_##n *cfg = dev->config; \
|
||||
if (data->event_index < DT_INST_PROP_LEN(n, events)) { \
|
||||
uint32_t ev = cfg->events[data->event_index]; \
|
||||
LOG_DBG("delaying next keypress: %d", ZMK_MOCK_MSEC(ev)); \
|
||||
k_work_schedule(&data->work, K_MSEC(ZMK_MOCK_MSEC(ev))); \
|
||||
} else if (cfg->exit_after) { \
|
||||
LOG_DBG("Exiting"); \
|
||||
exit(0); \
|
||||
} \
|
||||
} \
|
||||
static void kscan_mock_work_handler_##n(struct k_work *work) { \
|
||||
struct kscan_mock_data *data = CONTAINER_OF(work, struct kscan_mock_data, work); \
|
||||
const struct kscan_mock_config_##n *cfg = data->dev->config; \
|
||||
uint32_t ev = cfg->events[data->event_index]; \
|
||||
LOG_DBG("ev %u row %d column %d state %d\n", ev, ZMK_MOCK_ROW(ev), ZMK_MOCK_COL(ev), \
|
||||
ZMK_MOCK_IS_PRESS(ev)); \
|
||||
data->callback(data->dev, ZMK_MOCK_ROW(ev), ZMK_MOCK_COL(ev), ZMK_MOCK_IS_PRESS(ev)); \
|
||||
kscan_mock_schedule_next_event_##n(data->dev); \
|
||||
data->event_index++; \
|
||||
} \
|
||||
static int kscan_mock_init_##n(const struct device *dev) { \
|
||||
struct kscan_mock_data *data = dev->data; \
|
||||
data->dev = dev; \
|
||||
k_work_init_delayable(&data->work, kscan_mock_work_handler_##n); \
|
||||
return 0; \
|
||||
} \
|
||||
static int kscan_mock_enable_callback_##n(const struct device *dev) { \
|
||||
kscan_mock_schedule_next_event_##n(dev); \
|
||||
return 0; \
|
||||
} \
|
||||
static const struct kscan_driver_api mock_driver_api_##n = { \
|
||||
.config = kscan_mock_configure, \
|
||||
.enable_callback = kscan_mock_enable_callback_##n, \
|
||||
.disable_callback = kscan_mock_disable_callback, \
|
||||
}; \
|
||||
static struct kscan_mock_data kscan_mock_data_##n; \
|
||||
static const struct kscan_mock_config_##n kscan_mock_config_##n = { \
|
||||
.events = DT_INST_PROP(n, events), .exit_after = DT_INST_PROP(n, exit_after)}; \
|
||||
DEVICE_DT_INST_DEFINE(n, kscan_mock_init_##n, NULL, &kscan_mock_data_##n, \
|
||||
&kscan_mock_config_##n, APPLICATION, \
|
||||
CONFIG_KERNEL_INIT_PRIORITY_DEFAULT, &mock_driver_api_##n);
|
||||
|
||||
DT_INST_FOREACH_STATUS_OKAY(MOCK_INST_INIT)
|
||||
Reference in New Issue
Block a user