1 /*
2  * Copyright 2023 Google LLC
3  *
4  * SPDX-License-Identifier: Apache-2.0
5  */
6 
7 #define DT_DRV_COMPAT zephyr_input_longpress
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_longpress, CONFIG_INPUT_LOG_LEVEL);
15 
16 struct longpress_config {
17 	const struct device *input_dev;
18 	struct longpress_data_entry *entries;
19 	const uint16_t *input_codes;
20 	const uint16_t *short_codes;
21 	const uint16_t *long_codes;
22 	uint32_t long_delays_ms;
23 	uint8_t num_codes;
24 };
25 
26 struct longpress_data_entry {
27 	const struct device *dev;
28 	struct k_work_delayable work;
29 	uint8_t index;
30 	bool long_fired;
31 };
32 
longpress_deferred(struct k_work * work)33 static void longpress_deferred(struct k_work *work)
34 {
35 	struct k_work_delayable *dwork = k_work_delayable_from_work(work);
36 	struct longpress_data_entry *entry = CONTAINER_OF(
37 			dwork, struct longpress_data_entry, work);
38 	const struct device *dev = entry->dev;
39 	const struct longpress_config *cfg = dev->config;
40 	uint16_t code;
41 
42 	code = cfg->long_codes[entry->index];
43 
44 	input_report_key(dev, code, 1, true, K_FOREVER);
45 
46 	entry->long_fired = true;
47 }
48 
longpress_cb(struct input_event * evt,void * user_data)49 static void longpress_cb(struct input_event *evt, void *user_data)
50 {
51 	const struct device *dev = user_data;
52 	const struct longpress_config *cfg = dev->config;
53 	struct longpress_data_entry *entry;
54 	int i;
55 
56 	if (evt->type != INPUT_EV_KEY) {
57 		return;
58 	}
59 
60 	for (i = 0; i < cfg->num_codes; i++) {
61 		if (evt->code == cfg->input_codes[i]) {
62 			break;
63 		}
64 	}
65 	if (i == cfg->num_codes) {
66 		LOG_DBG("ignored code %d", evt->code);
67 		return;
68 	}
69 
70 	entry = &cfg->entries[i];
71 
72 	if (evt->value) {
73 		entry->long_fired = false;
74 		k_work_schedule(&entry->work, K_MSEC(cfg->long_delays_ms));
75 	} else {
76 		k_work_cancel_delayable(&entry->work);
77 		if (entry->long_fired) {
78 			input_report_key(dev, cfg->long_codes[i], 0, true, K_FOREVER);
79 		} else if (cfg->short_codes != NULL) {
80 			input_report_key(dev, cfg->short_codes[i], 1, true, K_FOREVER);
81 			input_report_key(dev, cfg->short_codes[i], 0, true, K_FOREVER);
82 		}
83 	}
84 }
85 
longpress_init(const struct device * dev)86 static int longpress_init(const struct device *dev)
87 {
88 	const struct longpress_config *cfg = dev->config;
89 
90 	if (cfg->input_dev && !device_is_ready(cfg->input_dev)) {
91 		LOG_ERR("input device not ready");
92 		return -ENODEV;
93 	}
94 
95 	for (int i = 0; i < cfg->num_codes; i++) {
96 		struct longpress_data_entry *entry = &cfg->entries[i];
97 
98 		entry->dev = dev;
99 		entry->index = i;
100 		k_work_init_delayable(&entry->work, longpress_deferred);
101 	}
102 
103 	return 0;
104 }
105 
106 #define INPUT_LONGPRESS_DEFINE(inst)                                                               \
107 	BUILD_ASSERT((DT_INST_PROP_LEN(inst, input_codes) ==                                       \
108 		      DT_INST_PROP_LEN_OR(inst, short_codes, 0)) ||                                \
109 		      !DT_INST_NODE_HAS_PROP(inst, short_codes));                                  \
110 	BUILD_ASSERT(DT_INST_PROP_LEN(inst, input_codes) == DT_INST_PROP_LEN(inst, long_codes));   \
111 	                                                                                           \
112 	INPUT_CALLBACK_DEFINE_NAMED(DEVICE_DT_GET_OR_NULL(DT_INST_PHANDLE(inst, input)),           \
113 				    longpress_cb, (void *)DEVICE_DT_INST_GET(inst),                \
114 				    longpress_cb_##inst);                                          \
115 	                                                                                           \
116 	static const uint16_t longpress_input_codes_##inst[] = DT_INST_PROP(inst, input_codes);    \
117 	                                                                                           \
118 	IF_ENABLED(DT_INST_NODE_HAS_PROP(inst, short_codes), (                                     \
119 	static const uint16_t longpress_short_codes_##inst[] = DT_INST_PROP(inst, short_codes);    \
120 	));                                                                                        \
121 	                                                                                           \
122 	static const uint16_t longpress_long_codes_##inst[] = DT_INST_PROP(inst, long_codes);      \
123 	                                                                                           \
124 	static struct longpress_data_entry longpress_data_entries_##inst[DT_INST_PROP_LEN(         \
125 			inst, input_codes)];                                                       \
126 	                                                                                           \
127 	static const struct longpress_config longpress_config_##inst = {                           \
128 		.input_dev = DEVICE_DT_GET_OR_NULL(DT_INST_PHANDLE(inst, input)),                  \
129 		.entries = longpress_data_entries_##inst,                                          \
130 		.input_codes = longpress_input_codes_##inst,                                       \
131 		IF_ENABLED(DT_INST_NODE_HAS_PROP(inst, short_codes), (                             \
132 		.short_codes = longpress_short_codes_##inst,                                       \
133 		))                                                                                 \
134 		.long_codes = longpress_long_codes_##inst,                                         \
135 		.num_codes = DT_INST_PROP_LEN(inst, input_codes),                                  \
136 		.long_delays_ms = DT_INST_PROP(inst, long_delay_ms),                               \
137 	};                                                                                         \
138 	                                                                                           \
139 	DEVICE_DT_INST_DEFINE(inst, longpress_init, NULL,                                          \
140 			      NULL, &longpress_config_##inst,                                      \
141 			      POST_KERNEL, CONFIG_INPUT_INIT_PRIORITY, NULL);
142 
143 DT_INST_FOREACH_STATUS_OKAY(INPUT_LONGPRESS_DEFINE)
144