1 /*
2 * Copyright 2024 Kelly Helmut Lord
3 *
4 * SPDX-License-Identifier: Apache-2.0
5 */
6
7 #define DT_DRV_COMPAT zephyr_input_double_tap
8
9 #include <zephyr/device.h>
10 #include <zephyr/input/input.h>
11 #include <zephyr/kernel.h>
12
13 #include <zephyr/logging/log.h>
14 LOG_MODULE_REGISTER(input_double_tap, CONFIG_INPUT_LOG_LEVEL);
15
16 struct double_tap_config {
17 const struct device *input_dev;
18 struct double_tap_data_entry *entries;
19 const uint16_t *input_codes;
20 const uint16_t *double_tap_codes;
21 uint32_t double_tap_delay_ms;
22 uint8_t num_codes;
23 };
24
25 struct double_tap_data_entry {
26 const struct device *dev;
27 struct k_work_delayable work;
28 uint8_t index;
29 bool first_tap;
30 };
31
double_tap_deferred(struct k_work * work)32 static void double_tap_deferred(struct k_work *work)
33 {
34 struct k_work_delayable *dwork = k_work_delayable_from_work(work);
35 struct double_tap_data_entry *entry =
36 CONTAINER_OF(dwork, struct double_tap_data_entry, work);
37
38 entry->first_tap = false;
39 }
40
double_tap_cb(struct input_event * evt,void * user_data)41 static void double_tap_cb(struct input_event *evt, void *user_data)
42 {
43 const struct device *dev = user_data;
44 const struct double_tap_config *cfg = dev->config;
45 struct double_tap_data_entry *entry;
46 int i;
47
48 if (evt->type != INPUT_EV_KEY) {
49 return;
50 }
51
52 for (i = 0; i < cfg->num_codes; i++) {
53 if (evt->code == cfg->input_codes[i]) {
54 break;
55 }
56 }
57 if (i == cfg->num_codes) {
58 LOG_DBG("ignored code %d", evt->code);
59 return;
60 }
61
62 entry = &cfg->entries[i];
63
64 if (evt->value) {
65 if (entry->first_tap) {
66 k_work_cancel_delayable(&entry->work);
67 input_report_key(dev, cfg->double_tap_codes[i], 1, true, K_FOREVER);
68 input_report_key(dev, cfg->double_tap_codes[i], 0, true, K_FOREVER);
69 entry->first_tap = false;
70 } else {
71 entry->first_tap = true;
72 k_work_schedule(&entry->work, K_MSEC(cfg->double_tap_delay_ms));
73 }
74 }
75 }
76
double_tap_init(const struct device * dev)77 static int double_tap_init(const struct device *dev)
78 {
79 const struct double_tap_config *cfg = dev->config;
80
81 if (cfg->input_dev && !device_is_ready(cfg->input_dev)) {
82 LOG_ERR("input device not ready");
83 return -ENODEV;
84 }
85
86 for (int i = 0; i < cfg->num_codes; i++) {
87 struct double_tap_data_entry *entry = &cfg->entries[i];
88
89 entry->dev = dev;
90 entry->index = i;
91 entry->first_tap = false;
92 k_work_init_delayable(&entry->work, double_tap_deferred);
93 }
94
95 return 0;
96 }
97
98 #define INPUT_DOUBLE_TAP_DEFINE(inst) \
99 BUILD_ASSERT(DT_INST_PROP_LEN(inst, input_codes) == \
100 DT_INST_PROP_LEN(inst, double_tap_codes)); \
101 \
102 INPUT_CALLBACK_DEFINE_NAMED(DEVICE_DT_GET_OR_NULL(DT_INST_PHANDLE(inst, input)), \
103 double_tap_cb, (void *)DEVICE_DT_INST_GET(inst), \
104 double_tap_cb_##inst); \
105 \
106 static const uint16_t double_tap_input_codes_##inst[] = DT_INST_PROP(inst, input_codes); \
107 \
108 static const uint16_t double_tap_codes_##inst[] = DT_INST_PROP(inst, double_tap_codes); \
109 \
110 static struct double_tap_data_entry \
111 double_tap_data_entries_##inst[DT_INST_PROP_LEN(inst, input_codes)]; \
112 \
113 static const struct double_tap_config double_tap_config_##inst = { \
114 .input_dev = DEVICE_DT_GET_OR_NULL(DT_INST_PHANDLE(inst, input)), \
115 .entries = double_tap_data_entries_##inst, \
116 .input_codes = double_tap_input_codes_##inst, \
117 .double_tap_codes = double_tap_codes_##inst, \
118 .num_codes = DT_INST_PROP_LEN(inst, input_codes), \
119 .double_tap_delay_ms = DT_INST_PROP(inst, double_tap_delay_ms), \
120 }; \
121 \
122 DEVICE_DT_INST_DEFINE(inst, double_tap_init, NULL, NULL, &double_tap_config_##inst, \
123 POST_KERNEL, CONFIG_INPUT_INIT_PRIORITY, NULL);
124
125 DT_INST_FOREACH_STATUS_OKAY(INPUT_DOUBLE_TAP_DEFINE)
126