1 // Copyright 2020 Espressif Systems (Shanghai) PTE LTD
2 //
3 // Licensed under the Apache License, Version 2.0 (the "License");
4 // you may not use this file except in compliance with the License.
5 // You may obtain a copy of the License at
6 //
7 //     http://www.apache.org/licenses/LICENSE-2.0
8 //
9 // Unless required by applicable law or agreed to in writing, software
10 // distributed under the License is distributed on an "AS IS" BASIS,
11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 // See the License for the specific language governing permissions and
13 // limitations under the License.
14 #include <stdlib.h>
15 #include "freertos/FreeRTOS.h"
16 #include "freertos/task.h"
17 #include "freertos/timers.h"
18 #include "esp_compiler.h"
19 #include "esp_log.h"
20 #include "driver/dedic_gpio.h"
21 #include "driver/gpio.h"
22 #include "matrix_keyboard.h"
23 #include "esp_rom_sys.h"
24 
25 static const char *TAG = "mkbd";
26 
27 #define MKBD_CHECK(a, msg, tag, ret, ...)                                         \
28     do {                                                                          \
29         if (unlikely(!(a))) {                                                     \
30             ESP_LOGE(TAG, "%s(%d): " msg, __FUNCTION__, __LINE__, ##__VA_ARGS__); \
31             ret_code = ret;                                                       \
32             goto tag;                                                             \
33         }                                                                         \
34     } while (0)
35 
36 typedef struct matrix_kbd_t matrix_kbd_t;
37 
38 struct matrix_kbd_t {
39     dedic_gpio_bundle_handle_t row_bundle;
40     dedic_gpio_bundle_handle_t col_bundle;
41     uint32_t nr_row_gpios;
42     uint32_t nr_col_gpios;
43     TimerHandle_t debounce_timer;
44     matrix_kbd_event_handler event_handler;
45     void *event_handler_args;
46     uint32_t row_state[0];
47 };
48 
matrix_kbd_row_isr_callback(dedic_gpio_bundle_handle_t row_bundle,uint32_t row_index,void * args)49 static IRAM_ATTR bool matrix_kbd_row_isr_callback(dedic_gpio_bundle_handle_t row_bundle, uint32_t row_index, void *args)
50 {
51     BaseType_t high_task_wakeup = pdFALSE;
52     matrix_kbd_t *mkbd = (matrix_kbd_t *)args;
53 
54     // temporarily disable interrupt
55     dedic_gpio_bundle_set_interrupt_and_callback(row_bundle, (1 << mkbd->nr_row_gpios) - 1, DEDIC_GPIO_INTR_NONE, NULL, NULL);
56     // get row id, start to check the col id
57     dedic_gpio_bundle_write(row_bundle, 1 << row_index, 0);
58     dedic_gpio_bundle_write(mkbd->col_bundle, (1 << mkbd->nr_col_gpios) - 1, (1 << mkbd->nr_col_gpios) - 1);
59     xTimerStartFromISR(mkbd->debounce_timer, &high_task_wakeup);
60     return high_task_wakeup == pdTRUE;
61 }
62 
matrix_kbd_debounce_timer_callback(TimerHandle_t xTimer)63 static void matrix_kbd_debounce_timer_callback(TimerHandle_t xTimer)
64 {
65     matrix_kbd_t *mkbd = (matrix_kbd_t *)pvTimerGetTimerID(xTimer);
66 
67     uint32_t row_out = dedic_gpio_bundle_read_out(mkbd->row_bundle);
68     uint32_t col_in = dedic_gpio_bundle_read_in(mkbd->col_bundle);
69     row_out = (~row_out) & ((1 << mkbd->nr_row_gpios) - 1);
70     ESP_LOGD(TAG, "row_out=%x, col_in=%x", row_out, col_in);
71     int row = -1;
72     int col = -1;
73     uint32_t key_code = 0;
74     while (row_out) {
75         row = __builtin_ffs(row_out) - 1;
76         uint32_t changed_col_bits = mkbd->row_state[row] ^ col_in;
77         while (changed_col_bits) {
78             col = __builtin_ffs(changed_col_bits) - 1;
79             ESP_LOGD(TAG, "row=%d, col=%d", row, col);
80             key_code = MAKE_KEY_CODE(row, col);
81             if (col_in & (1 << col)) {
82                 mkbd->event_handler(mkbd, MATRIX_KBD_EVENT_UP, (void *)key_code, mkbd->event_handler_args);
83             } else {
84                 mkbd->event_handler(mkbd, MATRIX_KBD_EVENT_DOWN, (void *)key_code, mkbd->event_handler_args);
85             }
86             changed_col_bits = changed_col_bits & (changed_col_bits - 1);
87         }
88         mkbd->row_state[row] = col_in;
89         row_out = row_out & (row_out - 1);
90     }
91 
92     // row lines set to high level
93     dedic_gpio_bundle_write(mkbd->row_bundle, (1 << mkbd->nr_row_gpios) - 1, (1 << mkbd->nr_row_gpios) - 1);
94     // col lines set to low level
95     dedic_gpio_bundle_write(mkbd->col_bundle, (1 << mkbd->nr_col_gpios) - 1, 0);
96     dedic_gpio_bundle_set_interrupt_and_callback(mkbd->row_bundle, (1 << mkbd->nr_row_gpios) - 1,
97             DEDIC_GPIO_INTR_BOTH_EDGE, matrix_kbd_row_isr_callback, mkbd);
98 }
99 
matrix_kbd_install(const matrix_kbd_config_t * config,matrix_kbd_handle_t * mkbd_handle)100 esp_err_t matrix_kbd_install(const matrix_kbd_config_t *config, matrix_kbd_handle_t *mkbd_handle)
101 {
102     esp_err_t ret_code = ESP_OK;
103     matrix_kbd_t *mkbd = NULL;
104     MKBD_CHECK(config, "matrix keyboard configuration can't be null", err, ESP_ERR_INVALID_ARG);
105     MKBD_CHECK(mkbd_handle, "matrix keyboard handle can't be null", err, ESP_ERR_INVALID_ARG);
106 
107     mkbd = calloc(1, sizeof(matrix_kbd_t) + (config->nr_row_gpios) * sizeof(uint32_t));
108     MKBD_CHECK(mkbd, "allocate matrix keyboard context failed", err, ESP_ERR_NO_MEM);
109 
110     mkbd->nr_col_gpios = config->nr_col_gpios;
111     mkbd->nr_row_gpios = config->nr_row_gpios;
112 
113     // GPIO pad configuration
114     // Each GPIO used in matrix key board should be able to input and output
115     // In case the keyboard doesn't design a resister to pull up row/col line
116     // We enable the internal pull up resister, enable Open Drain as well
117     gpio_config_t io_conf = {
118         .mode = GPIO_MODE_INPUT_OUTPUT_OD,
119         .pull_up_en = 1
120     };
121 
122     for (int i = 0; i < config->nr_row_gpios; i++) {
123         io_conf.pin_bit_mask = 1ULL << config->row_gpios[i];
124         gpio_config(&io_conf);
125     }
126 
127     dedic_gpio_bundle_config_t bundle_row_config = {
128         .gpio_array = config->row_gpios,
129         .array_size = config->nr_row_gpios,
130         .flags = {
131             .in_en = 1,
132             .out_en = 1,
133         },
134     };
135     MKBD_CHECK(dedic_gpio_new_bundle(&bundle_row_config, &mkbd->row_bundle) == ESP_OK,
136                "create row bundle failed", err, ESP_FAIL);
137 
138     for (int i = 0; i < config->nr_col_gpios; i++) {
139         io_conf.pin_bit_mask = 1ULL << config->col_gpios[i];
140         gpio_config(&io_conf);
141     }
142 
143     dedic_gpio_bundle_config_t bundle_col_config = {
144         .gpio_array = config->col_gpios,
145         .array_size = config->nr_col_gpios,
146         .flags = {
147             .in_en = 1,
148             .out_en = 1,
149         },
150     };
151     MKBD_CHECK(dedic_gpio_new_bundle(&bundle_col_config, &mkbd->col_bundle) == ESP_OK,
152                "create col bundle failed", err, ESP_FAIL);
153 
154     // Disable interrupt
155     dedic_gpio_bundle_set_interrupt_and_callback(mkbd->row_bundle, (1 << config->nr_row_gpios) - 1,
156             DEDIC_GPIO_INTR_NONE, NULL, NULL);
157     dedic_gpio_bundle_set_interrupt_and_callback(mkbd->col_bundle, (1 << config->nr_col_gpios) - 1,
158             DEDIC_GPIO_INTR_NONE, NULL, NULL);
159 
160     // Create a ont-shot os timer, used for key debounce
161     mkbd->debounce_timer = xTimerCreate("kb_debounce", pdMS_TO_TICKS(config->debounce_ms), pdFALSE, mkbd, matrix_kbd_debounce_timer_callback);
162     MKBD_CHECK(mkbd->debounce_timer, "create debounce timer failed", err, ESP_FAIL);
163 
164     * mkbd_handle = mkbd;
165     return ESP_OK;
166 err:
167     if (mkbd) {
168         if (mkbd->debounce_timer) {
169             xTimerDelete(mkbd->debounce_timer, 0);
170         }
171         if (mkbd->col_bundle) {
172             dedic_gpio_del_bundle(mkbd->col_bundle);
173         }
174         if (mkbd->row_bundle) {
175             dedic_gpio_del_bundle(mkbd->row_bundle);
176         }
177         free(mkbd);
178     }
179     return ret_code;
180 }
181 
matrix_kbd_uninstall(matrix_kbd_handle_t mkbd_handle)182 esp_err_t matrix_kbd_uninstall(matrix_kbd_handle_t mkbd_handle)
183 {
184     esp_err_t ret_code = ESP_OK;
185     MKBD_CHECK(mkbd_handle, "matrix keyboard handle can't be null", err, ESP_ERR_INVALID_ARG);
186     xTimerDelete(mkbd_handle->debounce_timer, 0);
187     dedic_gpio_del_bundle(mkbd_handle->col_bundle);
188     dedic_gpio_del_bundle(mkbd_handle->row_bundle);
189     free(mkbd_handle);
190     return ESP_OK;
191 err:
192     return ret_code;
193 }
194 
matrix_kbd_start(matrix_kbd_handle_t mkbd_handle)195 esp_err_t matrix_kbd_start(matrix_kbd_handle_t mkbd_handle)
196 {
197     esp_err_t ret_code = ESP_OK;
198     MKBD_CHECK(mkbd_handle, "matrix keyboard handle can't be null", err, ESP_ERR_INVALID_ARG);
199 
200     // row lines set to high level
201     dedic_gpio_bundle_write(mkbd_handle->row_bundle, (1 << mkbd_handle->nr_row_gpios) - 1, (1 << mkbd_handle->nr_row_gpios) - 1);
202     // col lines set to low level
203     dedic_gpio_bundle_write(mkbd_handle->col_bundle, (1 << mkbd_handle->nr_col_gpios) - 1, 0);
204 
205     for (int i = 0; i < mkbd_handle->nr_row_gpios; i++) {
206         mkbd_handle->row_state[i] = (1 << mkbd_handle->nr_col_gpios) - 1;
207     }
208 
209     // only enable row line interrupt
210     dedic_gpio_bundle_set_interrupt_and_callback(mkbd_handle->row_bundle, (1 << mkbd_handle->nr_row_gpios) - 1,
211             DEDIC_GPIO_INTR_BOTH_EDGE, matrix_kbd_row_isr_callback, mkbd_handle);
212 
213     return ESP_OK;
214 err:
215     return ret_code;
216 }
217 
matrix_kbd_stop(matrix_kbd_handle_t mkbd_handle)218 esp_err_t matrix_kbd_stop(matrix_kbd_handle_t mkbd_handle)
219 {
220     esp_err_t ret_code = ESP_OK;
221     MKBD_CHECK(mkbd_handle, "matrix keyboard handle can't be null", err, ESP_ERR_INVALID_ARG);
222 
223     xTimerStop(mkbd_handle->debounce_timer, 0);
224 
225     // Disable interrupt
226     dedic_gpio_bundle_set_interrupt_and_callback(mkbd_handle->row_bundle, (1 << mkbd_handle->nr_row_gpios) - 1,
227             DEDIC_GPIO_INTR_NONE, NULL, NULL);
228     dedic_gpio_bundle_set_interrupt_and_callback(mkbd_handle->col_bundle, (1 << mkbd_handle->nr_col_gpios) - 1,
229             DEDIC_GPIO_INTR_NONE, NULL, NULL);
230 
231     return ESP_OK;
232 err:
233     return ret_code;
234 }
235 
matrix_kbd_register_event_handler(matrix_kbd_handle_t mkbd_handle,matrix_kbd_event_handler handler,void * args)236 esp_err_t matrix_kbd_register_event_handler(matrix_kbd_handle_t mkbd_handle, matrix_kbd_event_handler handler, void *args)
237 {
238     esp_err_t ret_code = ESP_OK;
239     MKBD_CHECK(mkbd_handle, "matrix keyboard handle can't be null", err, ESP_ERR_INVALID_ARG);
240     mkbd_handle->event_handler = handler;
241     mkbd_handle->event_handler_args = args;
242     return ESP_OK;
243 err:
244     return ret_code;
245 }
246