1 /*
2 * Copyright 2023 Google LLC
3 *
4 * SPDX-License-Identifier: Apache-2.0
5 */
6
7 #define DT_DRV_COMPAT gpio_kbd_matrix
8
9 #include <stdint.h>
10 #include <stdlib.h>
11
12 #include <zephyr/device.h>
13 #include <zephyr/drivers/gpio.h>
14 #include <zephyr/input/input_kbd_matrix.h>
15 #include <zephyr/kernel.h>
16 #include <zephyr/sys/util.h>
17
18 #include <zephyr/logging/log.h>
19 LOG_MODULE_REGISTER(input_gpio_kbd_matrix, CONFIG_INPUT_LOG_LEVEL);
20
21 struct gpio_kbd_matrix_config {
22 struct input_kbd_matrix_common_config common;
23 const struct gpio_dt_spec *row_gpio;
24 const struct gpio_dt_spec *col_gpio;
25
26 struct gpio_callback *gpio_cb;
27 gpio_callback_handler_t gpio_cb_handler;
28
29 struct k_work_delayable *idle_poll_dwork;
30 k_work_handler_t idle_poll_handler;
31
32 bool col_drive_inactive;
33 };
34
35 struct gpio_kbd_matrix_data {
36 struct input_kbd_matrix_common_data common;
37 uint32_t last_col_state;
38 bool direct_read;
39 bool direct_write;
40 };
41
42 INPUT_KBD_STRUCT_CHECK(struct gpio_kbd_matrix_config,
43 struct gpio_kbd_matrix_data);
44
gpio_kbd_matrix_drive_column(const struct device * dev,int col)45 static void gpio_kbd_matrix_drive_column(const struct device *dev, int col)
46 {
47 const struct gpio_kbd_matrix_config *cfg = dev->config;
48 const struct input_kbd_matrix_common_config *common = &cfg->common;
49 struct gpio_kbd_matrix_data *data = dev->data;
50 uint32_t state;
51
52 if (col == INPUT_KBD_MATRIX_COLUMN_DRIVE_NONE) {
53 state = 0;
54 } else if (col == INPUT_KBD_MATRIX_COLUMN_DRIVE_ALL) {
55 state = BIT_MASK(common->col_size);
56 } else {
57 state = BIT(col);
58 }
59
60 if (data->direct_write) {
61 const struct gpio_dt_spec *gpio0 = &cfg->col_gpio[0];
62 gpio_port_pins_t gpio_mask;
63 gpio_port_value_t gpio_val;
64
65 gpio_mask = BIT_MASK(common->col_size) << gpio0->pin;
66 gpio_val = state << gpio0->pin;
67
68 gpio_port_set_masked(gpio0->port, gpio_mask, gpio_val);
69
70 return;
71 }
72
73 for (int i = 0; i < common->col_size; i++) {
74 const struct gpio_dt_spec *gpio = &cfg->col_gpio[i];
75
76 if ((data->last_col_state ^ state) & BIT(i)) {
77 if (cfg->col_drive_inactive) {
78 gpio_pin_set_dt(gpio, state & BIT(i));
79 } else if (state & BIT(i)) {
80 gpio_pin_configure_dt(gpio, GPIO_OUTPUT_ACTIVE);
81 } else {
82 gpio_pin_configure_dt(gpio, GPIO_INPUT);
83 }
84 }
85 }
86
87 data->last_col_state = state;
88 }
89
gpio_kbd_matrix_read_row(const struct device * dev)90 static kbd_row_t gpio_kbd_matrix_read_row(const struct device *dev)
91 {
92 const struct gpio_kbd_matrix_config *cfg = dev->config;
93 const struct input_kbd_matrix_common_config *common = &cfg->common;
94 struct gpio_kbd_matrix_data *data = dev->data;
95 kbd_row_t val = 0;
96
97 if (data->direct_read) {
98 const struct gpio_dt_spec *gpio0 = &cfg->row_gpio[0];
99 gpio_port_value_t gpio_val;
100
101 gpio_port_get(gpio0->port, &gpio_val);
102
103 return (gpio_val >> gpio0->pin) & BIT_MASK(common->row_size);
104 }
105
106 for (int i = 0; i < common->row_size; i++) {
107 const struct gpio_dt_spec *gpio = &cfg->row_gpio[i];
108
109 if (gpio_pin_get_dt(gpio)) {
110 val |= BIT(i);
111 }
112 }
113
114 return val;
115 }
116
gpio_kbd_matrix_idle_poll_handler(const struct device * dev)117 static __maybe_unused void gpio_kbd_matrix_idle_poll_handler(const struct device *dev)
118 {
119 const struct gpio_kbd_matrix_config *cfg = dev->config;
120 const struct input_kbd_matrix_common_config *common = &cfg->common;
121
122 if (gpio_kbd_matrix_read_row(dev) == 0) {
123 k_work_reschedule(cfg->idle_poll_dwork,
124 K_USEC(common->poll_period_us));
125 return;
126 }
127
128 input_kbd_matrix_poll_start(dev);
129 }
130
gpio_kbd_matrix_set_detect_mode(const struct device * dev,bool enabled)131 static void gpio_kbd_matrix_set_detect_mode(const struct device *dev, bool enabled)
132 {
133 const struct gpio_kbd_matrix_config *cfg = dev->config;
134 const struct input_kbd_matrix_common_config *common = &cfg->common;
135 int ret;
136
137 if (cfg->idle_poll_dwork != NULL) {
138 if (enabled) {
139 k_work_reschedule(cfg->idle_poll_dwork,
140 K_USEC(common->poll_period_us));
141 }
142 return;
143 }
144
145 if (cfg->gpio_cb == NULL) {
146 return;
147 }
148
149 for (int i = 0; i < common->row_size; i++) {
150 const struct gpio_dt_spec *gpio = &cfg->row_gpio[i];
151 gpio_flags_t flags = enabled ? GPIO_INT_EDGE_TO_ACTIVE : GPIO_INT_DISABLE;
152
153 ret = gpio_pin_interrupt_configure_dt(gpio, flags);
154 if (ret != 0) {
155 LOG_ERR("Pin %d interrupt configuration failed: %d", i, ret);
156 return;
157 }
158 }
159 }
160
gpio_kbd_matrix_is_gpio_coherent(const struct gpio_dt_spec * gpio,int gpio_count)161 static bool gpio_kbd_matrix_is_gpio_coherent(
162 const struct gpio_dt_spec *gpio, int gpio_count)
163 {
164 const struct gpio_dt_spec *gpio0 = &gpio[0];
165
166 for (int i = 1; i < gpio_count; i++) {
167 if (gpio[i].port != gpio0->port ||
168 gpio[i].dt_flags != gpio0->dt_flags ||
169 gpio[i].pin != gpio0->pin + i) {
170 return false;
171 }
172 }
173
174 return true;
175 }
176
gpio_kbd_continuous_scan_mode(const struct device * dev)177 static bool gpio_kbd_continuous_scan_mode(const struct device *dev)
178 {
179 const struct gpio_kbd_matrix_config *cfg = dev->config;
180
181 if (cfg->gpio_cb == NULL && cfg->idle_poll_dwork == NULL) {
182 return true;
183 }
184
185 return false;
186 }
187
gpio_kbd_matrix_init(const struct device * dev)188 static int gpio_kbd_matrix_init(const struct device *dev)
189 {
190 const struct gpio_kbd_matrix_config *cfg = dev->config;
191 const struct input_kbd_matrix_common_config *common = &cfg->common;
192 struct gpio_kbd_matrix_data *data = dev->data;
193 int ret;
194 int i;
195
196 for (i = 0; i < common->col_size; i++) {
197 const struct gpio_dt_spec *gpio = &cfg->col_gpio[i];
198
199 if (!gpio_is_ready_dt(gpio)) {
200 LOG_ERR("%s is not ready", gpio->port->name);
201 return -ENODEV;
202 }
203
204 if (cfg->col_drive_inactive) {
205 ret = gpio_pin_configure_dt(gpio, GPIO_OUTPUT_INACTIVE);
206 } else {
207 ret = gpio_pin_configure_dt(gpio, GPIO_INPUT);
208 }
209 if (ret != 0) {
210 LOG_ERR("Pin %d configuration failed: %d", i, ret);
211 return ret;
212 }
213 }
214
215 for (i = 0; i < common->row_size; i++) {
216 const struct gpio_dt_spec *gpio = &cfg->row_gpio[i];
217 struct gpio_callback *gpio_cb;
218
219 if (!gpio_is_ready_dt(gpio)) {
220 LOG_ERR("%s is not ready", gpio->port->name);
221 return -ENODEV;
222 }
223
224 ret = gpio_pin_configure_dt(gpio, GPIO_INPUT);
225 if (ret != 0) {
226 LOG_ERR("Pin %d configuration failed: %d", i, ret);
227 return ret;
228 }
229
230 if (cfg->gpio_cb == NULL) {
231 continue;
232 }
233 gpio_cb = &cfg->gpio_cb[i];
234
235 gpio_init_callback(gpio_cb, cfg->gpio_cb_handler,
236 BIT(gpio->pin));
237
238 ret = gpio_add_callback_dt(gpio, gpio_cb);
239 if (ret < 0) {
240 LOG_ERR("Could not set gpio callback");
241 return ret;
242 }
243 }
244
245 if (cfg->idle_poll_dwork != NULL) {
246 k_work_init_delayable(cfg->idle_poll_dwork,
247 cfg->idle_poll_handler);
248 }
249
250 data->direct_read = gpio_kbd_matrix_is_gpio_coherent(
251 cfg->row_gpio, common->row_size);
252
253 if (cfg->col_drive_inactive) {
254 data->direct_write = gpio_kbd_matrix_is_gpio_coherent(
255 cfg->col_gpio, common->col_size);
256 }
257
258 LOG_DBG("direct_read: %d direct_write: %d",
259 data->direct_read, data->direct_write);
260
261 ret = input_kbd_matrix_common_init(dev);
262 if (ret != 0) {
263 return ret;
264 }
265
266 if (gpio_kbd_continuous_scan_mode(dev)) {
267 input_kbd_matrix_poll_start(dev);
268 }
269
270 return 0;
271 }
272
273 static const struct input_kbd_matrix_api gpio_kbd_matrix_api = {
274 .drive_column = gpio_kbd_matrix_drive_column,
275 .read_row = gpio_kbd_matrix_read_row,
276 .set_detect_mode = gpio_kbd_matrix_set_detect_mode,
277 };
278
279 #define INPUT_GPIO_KBD_MATRIX_INIT(n) \
280 BUILD_ASSERT(DT_INST_PROP_LEN(n, col_gpios) <= 32, "invalid col-size"); \
281 \
282 INPUT_KBD_MATRIX_DT_INST_DEFINE_ROW_COL( \
283 n, DT_INST_PROP_LEN(n, row_gpios), DT_INST_PROP_LEN(n, col_gpios)); \
284 \
285 static const struct gpio_dt_spec gpio_kbd_matrix_row_gpio_##n[DT_INST_PROP_LEN( \
286 n, row_gpios)] = { \
287 DT_INST_FOREACH_PROP_ELEM_SEP(n, row_gpios, GPIO_DT_SPEC_GET_BY_IDX, (,)) \
288 }; \
289 static const struct gpio_dt_spec gpio_kbd_matrix_col_gpio_##n[DT_INST_PROP_LEN( \
290 n, col_gpios)] = { \
291 DT_INST_FOREACH_PROP_ELEM_SEP(n, col_gpios, GPIO_DT_SPEC_GET_BY_IDX, (,)) \
292 }; \
293 \
294 IF_ENABLED(DT_INST_ENUM_HAS_VALUE(n, idle_mode, interrupt), ( \
295 static struct gpio_callback gpio_kbd_matrix_gpio_cb_##n[DT_INST_PROP_LEN(n, row_gpios)];\
296 static void gpio_kbd_matrix_cb_##n(const struct device *gpio_dev, \
297 struct gpio_callback *cb, uint32_t pins) \
298 { \
299 input_kbd_matrix_poll_start(DEVICE_DT_INST_GET(n)); \
300 } \
301 )) \
302 IF_ENABLED(DT_INST_ENUM_HAS_VALUE(n, idle_mode, poll), ( \
303 static struct k_work_delayable gpio_kbd_matrix_idle_poll_dwork_##n; \
304 static void gpio_kbd_matrix_idle_poll_handler_##n(struct k_work *work) \
305 { \
306 gpio_kbd_matrix_idle_poll_handler(DEVICE_DT_INST_GET(n)); \
307 } \
308 )) \
309 IF_ENABLED(DT_INST_ENUM_HAS_VALUE(n, idle_mode, scan), ( \
310 BUILD_ASSERT(DT_INST_PROP(n, poll_timeout_ms) == 0, \
311 "poll-timeout-ms must be set to 0 for scan mode to work correctly"); \
312 )) \
313 \
314 static const struct gpio_kbd_matrix_config gpio_kbd_matrix_cfg_##n = { \
315 .common = INPUT_KBD_MATRIX_DT_INST_COMMON_CONFIG_INIT_ROW_COL( \
316 n, &gpio_kbd_matrix_api, \
317 DT_INST_PROP_LEN(n, row_gpios), DT_INST_PROP_LEN(n, col_gpios)), \
318 .row_gpio = gpio_kbd_matrix_row_gpio_##n, \
319 .col_gpio = gpio_kbd_matrix_col_gpio_##n, \
320 IF_ENABLED(DT_INST_ENUM_HAS_VALUE(n, idle_mode, interrupt), ( \
321 .gpio_cb = gpio_kbd_matrix_gpio_cb_##n, \
322 .gpio_cb_handler = gpio_kbd_matrix_cb_##n, \
323 )) \
324 IF_ENABLED(DT_INST_ENUM_HAS_VALUE(n, idle_mode, poll), ( \
325 .idle_poll_dwork = &gpio_kbd_matrix_idle_poll_dwork_##n, \
326 .idle_poll_handler = gpio_kbd_matrix_idle_poll_handler_##n, \
327 )) \
328 .col_drive_inactive = DT_INST_PROP(n, col_drive_inactive), \
329 }; \
330 \
331 static struct gpio_kbd_matrix_data gpio_kbd_matrix_data_##n; \
332 \
333 DEVICE_DT_INST_DEFINE(n, gpio_kbd_matrix_init, NULL, \
334 &gpio_kbd_matrix_data_##n, &gpio_kbd_matrix_cfg_##n, \
335 POST_KERNEL, CONFIG_INPUT_INIT_PRIORITY, \
336 NULL);
337
338 DT_INST_FOREACH_STATUS_OKAY(INPUT_GPIO_KBD_MATRIX_INIT)
339