summaryrefslogtreecommitdiff
path: root/app/drivers/zephyr/kscan_gpio.c
blob: a53e2f912015255f7dada446265ae0c240a9f2fb (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
/*
 * Copyright (c) 2020 Peter Johanson <peter@peterjohanson.com>
 *
 * SPDX-License-Identifier: Apache-2.0
 */

#define DT_DRV_COMPAT gpio_kscan

#include <device.h>
#include <drivers/kscan.h>
#include <drivers/gpio.h>
#include <logging/log.h>

LOG_MODULE_DECLARE(zmk, CONFIG_ZMK_LOG_LEVEL);

struct kscan_gpio_item_config
{
	char *label;
	gpio_pin_t pin;
	gpio_flags_t flags;
};

#define _KSCAN_GPIO_ITEM_CFG_INIT(n, prop, idx)           \
	{                                                     \
		.label = DT_INST_GPIO_LABEL_BY_IDX(n, prop, idx), \
		.pin = DT_INST_GPIO_PIN_BY_IDX(n, prop, idx),     \
		.flags = DT_INST_GPIO_FLAGS_BY_IDX(n, prop, idx), \
	},

static int kscan_gpio_config_interrupts(struct device **devices,
										const struct kscan_gpio_item_config *configs,
										size_t len, gpio_flags_t flags)
{
	for (int i = 0; i < len; i++)
	{
		struct device *dev = devices[i];
		const struct kscan_gpio_item_config *cfg = &configs[i];

		int err = gpio_pin_interrupt_configure(dev, cfg->pin, flags);

		if (err)
		{
			LOG_ERR("Unable to enable matrix GPIO interrupt");
			return err;
		}
	}

	return 0;
}
#define INST_MATRIX_ROWS(n) DT_INST_PROP_LEN(n, row_gpios)
#define INST_MATRIX_COLS(n) DT_INST_PROP_LEN(n, col_gpios)
#define INST_OUTPUT_LEN(n) COND_CODE_0(DT_ENUM_IDX(DT_DRV_INST(n), diode_direction), (INST_MATRIX_COLS(n)), (INST_MATRIX_ROWS(n)))
#define INST_INPUT_LEN(n) COND_CODE_0(DT_ENUM_IDX(DT_DRV_INST(n), diode_direction), (INST_MATRIX_ROWS(n)), (INST_MATRIX_COLS(n)))

#define GPIO_INST_INIT(n)                                                                                                                                                                                                                   \
	struct kscan_gpio_irq_callback_##n                                                                                                                                                                                                      \
	{                                                                                                                                                                                                                                       \
		struct COND_CODE_0(DT_INST_PROP(n, debounce_period), (k_work), (k_delayed_work)) * work;                                                                                                                                            \
		struct gpio_callback callback;                                                                                                                                                                                                      \
	};                                                                                                                                                                                                                                      \
	static struct kscan_gpio_irq_callback_##n                                                                                                                                                                                               \
		irq_callbacks_##n[INST_INPUT_LEN(n)];                                                                                                                                                                                               \
	struct kscan_gpio_config_##n                                                                                                                                                                                                            \
	{                                                                                                                                                                                                                                       \
		struct kscan_gpio_item_config rows[INST_MATRIX_ROWS(n)];                                                                                                                                                                            \
		struct kscan_gpio_item_config cols[INST_MATRIX_COLS(n)];                                                                                                                                                                            \
	};                                                                                                                                                                                                                                      \
	struct kscan_gpio_data_##n                                                                                                                                                                                                              \
	{                                                                                                                                                                                                                                       \
		kscan_callback_t callback;                                                                                                                                                                                                          \
		struct COND_CODE_0(DT_INST_PROP(n, debounce_period), (k_work), (k_delayed_work)) work;                                                                                                                                              \
		bool matrix_state[INST_MATRIX_ROWS(n)][INST_MATRIX_COLS(n)];                                                                                                                                                                        \
		struct device *rows[INST_MATRIX_ROWS(n)];                                                                                                                                                                                           \
		struct device *cols[INST_MATRIX_COLS(n)];                                                                                                                                                                                           \
		struct device *dev;                                                                                                                                                                                                                 \
	};                                                                                                                                                                                                                                      \
	static struct device **kscan_gpio_input_devices_##n(struct device *dev)                                                                                                                                                                 \
	{                                                                                                                                                                                                                                       \
		struct kscan_gpio_data_##n *data = dev->driver_data;                                                                                                                                                                                \
		return (COND_CODE_0(DT_ENUM_IDX(DT_DRV_INST(n), diode_direction), (data->cols), (data->rows)));                                                                                                                                     \
	}                                                                                                                                                                                                                                       \
	static const struct kscan_gpio_item_config *kscan_gpio_input_configs_##n(struct device *dev)                                                                                                                                            \
	{                                                                                                                                                                                                                                       \
		const struct kscan_gpio_config_##n *cfg = dev->config_info;                                                                                                                                                                         \
		return ((COND_CODE_0(DT_ENUM_IDX(DT_DRV_INST(n), diode_direction), (cfg->cols), (cfg->rows))));                                                                                                                                     \
	}                                                                                                                                                                                                                                       \
	static struct device **kscan_gpio_output_devices_##n(struct device *dev)                                                                                                                                                                \
	{                                                                                                                                                                                                                                       \
		struct kscan_gpio_data_##n *data = dev->driver_data;                                                                                                                                                                                \
		return (COND_CODE_0(DT_ENUM_IDX(DT_DRV_INST(n), diode_direction), (data->rows), (data->cols)));                                                                                                                                     \
	}                                                                                                                                                                                                                                       \
	static const struct kscan_gpio_item_config *kscan_gpio_output_configs_##n(struct device *dev)                                                                                                                                           \
	{                                                                                                                                                                                                                                       \
		const struct kscan_gpio_config_##n *cfg = dev->config_info;                                                                                                                                                                         \
		return (COND_CODE_0(DT_ENUM_IDX(DT_DRV_INST(n), diode_direction), (cfg->rows), (cfg->cols)));                                                                                                                                       \
	}                                                                                                                                                                                                                                       \
	static int kscan_gpio_enable_interrupts_##n(struct device *dev)                                                                                                                                                                         \
	{                                                                                                                                                                                                                                       \
		return kscan_gpio_config_interrupts(kscan_gpio_input_devices_##n(dev), kscan_gpio_input_configs_##n(dev), INST_INPUT_LEN(n),                                                                                                        \
											GPIO_INT_DEBOUNCE | GPIO_INT_EDGE_BOTH);                                                                                                                                                        \
	}                                                                                                                                                                                                                                       \
	static int kscan_gpio_disable_interrupts_##n(struct device *dev)                                                                                                                                                                        \
	{                                                                                                                                                                                                                                       \
		return kscan_gpio_config_interrupts(kscan_gpio_input_devices_##n(dev), kscan_gpio_input_configs_##n(dev), INST_INPUT_LEN(n),                                                                                                        \
											GPIO_INT_DISABLE);                                                                                                                                                                              \
	}                                                                                                                                                                                                                                       \
	static void kscan_gpio_set_output_state_##n(struct device *dev, int value)                                                                                                                                                              \
	{                                                                                                                                                                                                                                       \
		for (int i = 0; i < INST_OUTPUT_LEN(n); i++)                                                                                                                                                                                        \
		{                                                                                                                                                                                                                                   \
			struct device *in_dev = kscan_gpio_output_devices_##n(dev)[i];                                                                                                                                                                  \
			const struct kscan_gpio_item_config *cfg = &kscan_gpio_output_configs_##n(dev)[i];                                                                                                                                              \
			gpio_pin_set(in_dev, cfg->pin, value);                                                                                                                                                                                          \
		}                                                                                                                                                                                                                                   \
	}                                                                                                                                                                                                                                       \
	static void kscan_gpio_set_matrix_state_##n(bool state[INST_MATRIX_ROWS(n)][INST_MATRIX_COLS(n)], u32_t input_index, u32_t output_index, bool value)                                                                                    \
	{                                                                                                                                                                                                                                       \
		state[COND_CODE_0(DT_ENUM_IDX(DT_DRV_INST(n), diode_direction), (output_index), (input_index))][COND_CODE_0(DT_ENUM_IDX(DT_DRV_INST(n), diode_direction), (input_index), (output_index))] = value;                                  \
	}                                                                                                                                                                                                                                       \
	static int kscan_gpio_read_##n(struct device *dev)                                                                                                                                                                                      \
	{                                                                                                                                                                                                                                       \
		struct kscan_gpio_data_##n *data = dev->driver_data;                                                                                                                                                                                \
		static bool read_state[INST_MATRIX_ROWS(n)][INST_MATRIX_COLS(n)];                                                                                                                                                                   \
		LOG_DBG("Scanning the matrix for updated state");                                                                                                                                                                                   \
		/* Disable our interrupts temporarily while we scan, to avoid       */                                                                                                                                                              \
		/* re-entry while we iterate columns and set them active one by one */                                                                                                                                                              \
		/* to get pressed state for each matrix cell.                       */                                                                                                                                                              \
		kscan_gpio_disable_interrupts_##n(dev);                                                                                                                                                                                             \
		kscan_gpio_set_output_state_##n(dev, 0);                                                                                                                                                                                            \
		for (int o = 0; o < INST_OUTPUT_LEN(n); o++)                                                                                                                                                                                        \
		{                                                                                                                                                                                                                                   \
			struct device *out_dev = kscan_gpio_output_devices_##n(dev)[o];                                                                                                                                                                 \
			const struct kscan_gpio_item_config *out_cfg = &kscan_gpio_output_configs_##n(dev)[o];                                                                                                                                          \
			gpio_pin_set(out_dev, out_cfg->pin, 1);                                                                                                                                                                                         \
			for (int i = 0; i < INST_INPUT_LEN(n); i++)                                                                                                                                                                                     \
			{                                                                                                                                                                                                                               \
				struct device *in_dev = kscan_gpio_input_devices_##n(dev)[i];                                                                                                                                                               \
				const struct kscan_gpio_item_config *in_cfg = &kscan_gpio_input_configs_##n(dev)[i];                                                                                                                                        \
				kscan_gpio_set_matrix_state_##n(read_state, i, o, gpio_pin_get(in_dev, in_cfg->pin) > 0);                                                                                                                                   \
			}                                                                                                                                                                                                                               \
			gpio_pin_set(out_dev, out_cfg->pin, 0);                                                                                                                                                                                         \
		}                                                                                                                                                                                                                                   \
		/* Set all our outputs as active again, then re-enable interrupts, */                                                                                                                                                               \
		/* so we can trigger interrupts again for future press/release     */                                                                                                                                                               \
		kscan_gpio_set_output_state_##n(dev, 1);                                                                                                                                                                                            \
		kscan_gpio_enable_interrupts_##n(dev);                                                                                                                                                                                              \
		for (int r = 0; r < INST_MATRIX_ROWS(n); r++)                                                                                                                                                                                       \
		{                                                                                                                                                                                                                                   \
			for (int c = 0; c < INST_MATRIX_COLS(n); c++)                                                                                                                                                                                   \
			{                                                                                                                                                                                                                               \
				bool pressed = read_state[r][c];                                                                                                                                                                                            \
				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);                                                                                                                                                                                     \
				}                                                                                                                                                                                                                           \
			}                                                                                                                                                                                                                               \
		}                                                                                                                                                                                                                                   \
		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 void kscan_gpio_irq_callback_handler_##n(struct device *dev,                                                                                                                                                                     \
													struct gpio_callback *cb, gpio_port_pins_t pin)                                                                                                                                         \
	{                                                                                                                                                                                                                                       \
		struct kscan_gpio_irq_callback_##n *data =                                                                                                                                                                                          \
			CONTAINER_OF(cb, struct kscan_gpio_irq_callback_##n, callback);                                                                                                                                                                 \
		COND_CODE_0(DT_INST_PROP(n, debounce_period),                                                                                                                                                                                       \
					({ k_work_submit(data->work); }),                                                                                                                                                                                       \
					({                                                                                                                                                                                                                                                                                    \
						k_delayed_work_cancel(data->work);                                                                                                                                                                                                                                               \
						k_delayed_work_submit(data->work, K_MSEC(DT_INST_PROP(n, debounce_period))); }))                                                                                                                                                                                                                  \
	}                                                                                                                                                                                                                                       \
	static struct kscan_gpio_data_##n kscan_gpio_data_##n;                                                                                                                                                                                  \
	static int kscan_gpio_configure_##n(struct device *dev, kscan_callback_t callback)                                                                                                                                                      \
	{                                                                                                                                                                                                                                       \
		struct kscan_gpio_data_##n *data = dev->driver_data;                                                                                                                                                                                \
		if (!callback)                                                                                                                                                                                                                      \
		{                                                                                                                                                                                                                                   \
			return -EINVAL;                                                                                                                                                                                                                 \
		}                                                                                                                                                                                                                                   \
		data->callback = callback;                                                                                                                                                                                                          \
		return 0;                                                                                                                                                                                                                           \
	}                                                                                                                                                                                                                                       \
	static int kscan_gpio_init_##n(struct device *dev)                                                                                                                                                                                      \
	{                                                                                                                                                                                                                                       \
		struct kscan_gpio_data_##n *data = dev->driver_data;                                                                                                                                                                                \
		int err;                                                                                                                                                                                                                            \
		struct device **input_devices = kscan_gpio_input_devices_##n(dev);                                                                                                                                                                  \
		for (int i = 0; i < INST_INPUT_LEN(n); i++)                                                                                                                                                                                         \
		{                                                                                                                                                                                                                                   \
			const struct kscan_gpio_item_config *in_cfg = &kscan_gpio_input_configs_##n(dev)[i];                                                                                                                                            \
			input_devices[i] = device_get_binding(in_cfg->label);                                                                                                                                                                           \
			if (!input_devices[i])                                                                                                                                                                                                          \
			{                                                                                                                                                                                                                               \
				LOG_ERR("Unable to find input GPIO device");                                                                                                                                                                                \
				return -EINVAL;                                                                                                                                                                                                             \
			}                                                                                                                                                                                                                               \
			err = gpio_pin_configure(input_devices[i], in_cfg->pin, GPIO_INPUT | in_cfg->flags);                                                                                                                                            \
			if (err)                                                                                                                                                                                                                        \
			{                                                                                                                                                                                                                               \
				LOG_ERR("Unable to configure pin %d on %s for input", in_cfg->pin, in_cfg->label);                                                                                                                                          \
				return err;                                                                                                                                                                                                                 \
			}                                                                                                                                                                                                                               \
			irq_callbacks_##n[i].work = &data->work;                                                                                                                                                                                        \
			gpio_init_callback(&irq_callbacks_##n[i].callback, kscan_gpio_irq_callback_handler_##n, BIT(in_cfg->pin));                                                                                                                      \
			err = gpio_add_callback(input_devices[i], &irq_callbacks_##n[i].callback);                                                                                                                                                      \
			if (err)                                                                                                                                                                                                                        \
			{                                                                                                                                                                                                                               \
				LOG_ERR("Error adding the callback to the column device");                                                                                                                                                                  \
				return err;                                                                                                                                                                                                                 \
			}                                                                                                                                                                                                                               \
		}                                                                                                                                                                                                                                   \
		struct device **output_devices = kscan_gpio_output_devices_##n(dev);                                                                                                                                                                \
		for (int o = 0; o < INST_OUTPUT_LEN(n); o++)                                                                                                                                                                                        \
		{                                                                                                                                                                                                                                   \
			const struct kscan_gpio_item_config *out_cfg = &kscan_gpio_output_configs_##n(dev)[o];                                                                                                                                          \
			output_devices[o] = device_get_binding(out_cfg->label);                                                                                                                                                                         \
			if (!output_devices[o])                                                                                                                                                                                                         \
			{                                                                                                                                                                                                                               \
				LOG_ERR("Unable to find output GPIO device");                                                                                                                                                                               \
				return -EINVAL;                                                                                                                                                                                                             \
			}                                                                                                                                                                                                                               \
			err = gpio_pin_configure(output_devices[o], out_cfg->pin, GPIO_OUTPUT_ACTIVE | out_cfg->flags);                                                                                                                                 \
			if (err)                                                                                                                                                                                                                        \
			{                                                                                                                                                                                                                               \
				LOG_ERR("Unable to configure pin %d on %s for output", out_cfg->pin, out_cfg->label);                                                                                                                                       \
				return err;                                                                                                                                                                                                                 \
			}                                                                                                                                                                                                                               \
		}                                                                                                                                                                                                                                   \
		data->dev = dev;                                                                                                                                                                                                                    \
		(COND_CODE_0(DT_INST_PROP(n, debounce_period), (k_work_init), (k_delayed_work_init)))(&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_interrupts_##n,                                                                                                                                                                                \
		.disable_callback = kscan_gpio_disable_interrupts_##n,                                                                                                                                                                              \
	};                                                                                                                                                                                                                                      \
	static const struct kscan_gpio_config_##n kscan_gpio_config_##n = {                                                                                                                                                                     \
		.rows = {                                                                                                                                                                                                                           \
			IF_ENABLED(DT_INST_PHA_HAS_CELL_AT_IDX(n, row_gpios, 0, pin), (_KSCAN_GPIO_ITEM_CFG_INIT(n, row_gpios, 0)))                                                                                                                     \
				IF_ENABLED(DT_INST_PHA_HAS_CELL_AT_IDX(n, row_gpios, 1, pin), (_KSCAN_GPIO_ITEM_CFG_INIT(n, row_gpios, 1)))},                                                                                                               \
		.cols = {IF_ENABLED(DT_INST_PHA_HAS_CELL_AT_IDX(n, col_gpios, 0, pin), (_KSCAN_GPIO_ITEM_CFG_INIT(n, col_gpios, 0))) IF_ENABLED(DT_INST_PHA_HAS_CELL_AT_IDX(n, col_gpios, 1, pin), (_KSCAN_GPIO_ITEM_CFG_INIT(n, col_gpios, 1)))}}; \
	DEVICE_AND_API_INIT(kscan_gpio_##n, DT_INST_LABEL(n), kscan_gpio_init_##n,                                                                                                                                                              \
						&kscan_gpio_data_##n, &kscan_gpio_config_##n,                                                                                                                                                                       \
						POST_KERNEL, CONFIG_ZMK_KSCAN_INIT_PRIORITY,                                                                                                                                                                        \
						&gpio_driver_api_##n);

DT_INST_FOREACH_STATUS_OKAY(GPIO_INST_INIT)