1 /***************************************************************************//**
2 * \file cyhal_keyscan.c
3 *
4 * \brief
5 * Provides a high level interface for interacting with the Infineon KeyScan.
6 * This is a wrapper around the lower level PDL API.
7 *
8 ********************************************************************************
9 * \copyright
10 * Copyright 2021-2022 Cypress Semiconductor Corporation (an Infineon company) or
11 * an affiliate of Cypress Semiconductor Corporation
12 *
13 * SPDX-License-Identifier: Apache-2.0
14 *
15 * Licensed under the Apache License, Version 2.0 (the "License");
16 * you may not use this file except in compliance with the License.
17 * You may obtain a copy of the License at
18 *
19 * http://www.apache.org/licenses/LICENSE-2.0
20 *
21 * Unless required by applicable law or agreed to in writing, software
22 * distributed under the License is distributed on an "AS IS" BASIS,
23 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
24 * See the License for the specific language governing permissions and
25 * limitations under the License.
26 *******************************************************************************/
27
28 /**
29 * \addtogroup group_hal_impl_keyscan KeyScan
30 * \ingroup group_hal_impl
31 * \{
32 * On CAT1 devices, the KeyScan peripheral is clocked from the shared source CLK_MF.
33 * If `NULL` is passed for the `clk` argument to \ref cyhal_keyscan_init, the KeyScan
34 * HAL will automatically reserve and enable CLK_MF. If the KeyScan driver needs to be
35 * used in combination with another driver that also requires CLK_MF, use the Clock
36 * driver to initialize CLK_MF, then pass the resulting \ref cyhal_clock_t value
37 * to \ref cyhal_keyscan_init and any other HAL driver that needs to share this clock.
38 * \} group_hal_impl_keyscan
39 */
40 #include <stdlib.h>
41 #include <string.h>
42 #include "cyhal_keyscan.h"
43 #include "cyhal_gpio.h"
44 #include "cyhal_hwmgr.h"
45 #include "cyhal_system.h"
46 #include "cyhal_syspm.h"
47 #include "cyhal_utils.h"
48 #include "cyhal_irq_impl.h"
49 #include "cyhal_clock.h"
50
51 #if (CYHAL_DRIVER_AVAILABLE_KEYSCAN)
52
53 #if defined(__cplusplus)
54 extern "C"
55 {
56 #endif
57
58 /* Internal defines */
59 #define _CYHAL_KEYSCAN_MAX_COLS MXKEYSCAN_NUM_COLS_OUT
60 #define _CYHAL_KEYSCAN_MAX_ROWS MXKEYSCAN_NUM_ROWS_IN
61
62 /* Internal prototypes */
63 static void _cyhal_keyscan_irq_handler(void);
64 static void _cyhal_keyscan_cb_wrapper(void);
65 static bool _cyhal_keyscan_pm_callback(cyhal_syspm_callback_state_t state, cyhal_syspm_callback_mode_t mode, void* callback_arg);
66 static cy_rslt_t _cyhal_keyscan_init_resources(cyhal_keyscan_t *obj, uint8_t num_columns, const cyhal_gpio_t *columns,
67 uint8_t num_rows, const cyhal_gpio_t *rows, const cyhal_clock_t *clock);
68 static cy_rslt_t _cyhal_keyscan_init_hw(cyhal_keyscan_t *obj, const cy_stc_ks_config_t *cfg);
69
70 /* Default KeyScan configuration */
71 static const cy_stc_ks_config_t _cyhal_keyscan_default_config = {
72 .macroDownDebCnt = 3,
73 .macroUpDebCnt = 3,
74 .microDebCnt = 3,
75 .noofRows = 0,
76 .noofColumns = 0,
77 .ghostEnable = true,
78 .cpuWakeupEnable = true,
79 .clkStayOn = true
80 };
81
82 /* LPM transition callback data */
83 static cyhal_syspm_callback_data_t _cyhal_keyscan_syspm_callback_data =
84 {
85 .callback = &_cyhal_keyscan_pm_callback,
86 .states = (cyhal_syspm_callback_state_t)(CYHAL_SYSPM_CB_CPU_DEEPSLEEP | CYHAL_SYSPM_CB_CPU_DEEPSLEEP_RAM | CYHAL_SYSPM_CB_SYSTEM_HIBERNATE),
87 .next = NULL,
88 .args = NULL,
89 .ignore_modes = (cyhal_syspm_callback_mode_t)(CYHAL_SYSPM_CHECK_READY | CYHAL_SYSPM_AFTER_DS_WFI_TRANSITION),
90 };
91
92 /* Base addresses */
93 static MXKEYSCAN_Type *const _cyhal_keyscan_base[] =
94 {
95 #if (CY_IP_MXKEYSCAN_INSTANCES == 1)
96 MXKEYSCAN,
97 #else
98 #error "Unhandled keyscan instance count"
99 #endif
100 };
101
102 /* Record of init structs */
103 static cyhal_keyscan_t *_cyhal_keyscan_config_structs[1];
104
_cyhal_keyscan_irq_handler(void)105 static void _cyhal_keyscan_irq_handler(void)
106 {
107 cyhal_keyscan_t *obj = _cyhal_keyscan_config_structs[0];
108 uint32_t int_status = 0;
109 cy_rslt_t result = Cy_Keyscan_GetInterruptMaskedStatus(MXKEYSCAN, &int_status);
110 CY_ASSERT(CY_RSLT_SUCCESS == result);
111 CY_UNUSED_PARAMETER(result);
112 if(0u != (MXKEYSCAN_INTR_FIFO_THRESH_DONE & int_status))
113 {
114 Cy_Keyscan_Interrupt_Handler(obj->base, &(obj->context));
115 }
116
117 if(0u != (MXKEYSCAN_INTR_KEY_EDGE_DONE & int_status))
118 {
119 /* KEY_EDGE_DONE is just to wake the CPU from sleep, we should ignore it */
120 Cy_Keyscan_ClearInterrupt(MXKEYSCAN, MXKEYSCAN_INTR_KEY_EDGE_DONE);
121 }
122 }
123
_cyhal_keyscan_cb_wrapper(void)124 static void _cyhal_keyscan_cb_wrapper(void)
125 {
126 cyhal_keyscan_t *obj = _cyhal_keyscan_config_structs[0];
127 uint32_t hal_event = CYHAL_KEYSCAN_EVENT_ACTION_DETECTED |
128 ((obj->context.curNumElements == obj->context.maxNumElements) ? CYHAL_KEYSCAN_EVENT_BUFFER_FULL : 0);
129 cyhal_keyscan_event_t anded_events = (cyhal_keyscan_event_t)(obj->irq_cause & hal_event);
130 if (anded_events)
131 {
132 cyhal_keyscan_event_callback_t callback = (cyhal_keyscan_event_callback_t) obj->callback_data.callback;
133 callback(obj->callback_data.callback_arg, anded_events);
134 }
135 }
136
_cyhal_keyscan_pm_callback(cyhal_syspm_callback_state_t state,cyhal_syspm_callback_mode_t mode,void * callback_arg)137 static bool _cyhal_keyscan_pm_callback(cyhal_syspm_callback_state_t state, cyhal_syspm_callback_mode_t mode, void* callback_arg)
138 {
139 CY_UNUSED_PARAMETER(state);
140 CY_UNUSED_PARAMETER(callback_arg);
141
142 uint32_t readMask = 0;
143 cyhal_keyscan_t *obj = _cyhal_keyscan_config_structs[0];
144 Cy_Keyscan_GetInterruptMask(obj->base, &readMask);
145
146 switch(mode)
147 {
148 case CYHAL_SYSPM_AFTER_TRANSITION:
149 {
150 Cy_Keyscan_EnableClockStayOn(obj->base);
151 Cy_Keyscan_SetInterruptMask(obj->base, readMask & ~MXKEYSCAN_INTR_KEY_EDGE_DONE);
152 // Reenable MF clock after exiting DeepSleep
153 Cy_SysClk_MfoEnable(false);
154 Cy_SysClk_ClkMfEnable();
155 break;
156 }
157 case CYHAL_SYSPM_BEFORE_TRANSITION:
158 {
159 Cy_Keyscan_DisableClockStayOn(obj->base);
160 Cy_Keyscan_SetInterruptMask(obj->base, readMask | MXKEYSCAN_INTR_KEY_EDGE_DONE);
161 // Disable MF clock to reduce power consumption in DeepSleep
162 Cy_SysClk_MfoDisable();
163 Cy_SysClk_ClkMfDisable();
164 break;
165 }
166 default:
167 {
168 CY_ASSERT(false);
169 break;
170 }
171 }
172 return true;
173 }
174
_cyhal_keyscan_init_resources(cyhal_keyscan_t * obj,uint8_t num_columns,const cyhal_gpio_t * columns,uint8_t num_rows,const cyhal_gpio_t * rows,const cyhal_clock_t * clock)175 static cy_rslt_t _cyhal_keyscan_init_resources(cyhal_keyscan_t *obj, uint8_t num_columns, const cyhal_gpio_t *columns,
176 uint8_t num_rows, const cyhal_gpio_t *rows, const cyhal_clock_t *clock)
177 {
178 // Explicitly marked not allocated resources as invalid to prevent freeing them.
179 obj->resource.type = CYHAL_RSC_INVALID;
180 obj->irq_cause = CYHAL_KEYSCAN_EVENT_NONE;
181 for (uint8_t idx=0; idx < _CYHAL_KEYSCAN_MAX_COLS; idx++) obj->columns[idx] = NC;
182 for (uint8_t idx=0; idx < _CYHAL_KEYSCAN_MAX_ROWS; idx++) obj->rows[idx] = NC;
183
184 cy_rslt_t result = CY_RSLT_SUCCESS;
185
186 if ((num_columns > _CYHAL_KEYSCAN_MAX_COLS) || (num_rows > _CYHAL_KEYSCAN_MAX_ROWS ))
187 result = CYHAL_KEYSCAN_RSLT_ERR_INVALID_ARG;
188
189 // Reserve the column pins
190 for (uint8_t idx=0; idx < num_columns; idx++)
191 {
192 if (result == CY_RSLT_SUCCESS)
193 {
194 const cyhal_resource_pin_mapping_t* column_map = _CYHAL_UTILS_GET_RESOURCE(columns[idx], cyhal_pin_map_keyscan_ks_col);
195 result = (column_map == NULL) ? CYHAL_KEYSCAN_RSLT_ERR_INVALID_PIN : CY_RSLT_SUCCESS;
196 if (result == CY_RSLT_SUCCESS)
197 {
198 if(CYHAL_RSC_INVALID == obj->resource.type)
199 {
200 obj->resource.type = CYHAL_RSC_KEYSCAN;
201 obj->resource.block_num = column_map->block_num;
202 obj->resource.channel_num = 0;
203 result = cyhal_hwmgr_reserve(&(obj->resource));
204 }
205 /* Directly, instead of using _cyhal_utils_map_resource, checking whether pin belong to used block
206 * (and not checking channel_num) as cyhal_pin_map_keyscan_ks_row / cyhal_pin_map_keyscan_ks_col maps
207 * each column as separate channel */
208 else if (obj->resource.block_num != column_map->block_num)
209 {
210 result = CYHAL_KEYSCAN_RSLT_ERR_INVALID_PIN;
211 }
212 }
213 if (result == CY_RSLT_SUCCESS)
214 {
215 /* For this block, we reuse the channel_num field to store the bit index on the keyscan.
216 * Pull off and check that value */
217 uint8_t bit_index = column_map->channel_num;
218 if (bit_index != idx) /* PDL only support contiguous indices that start from 0 */
219 {
220 result = CYHAL_KEYSCAN_RSLT_ERR_INVALID_PIN;
221 }
222 }
223 if (result == CY_RSLT_SUCCESS)
224 {
225 result = _cyhal_utils_reserve_and_connect(column_map, CYHAL_PIN_MAP_DRIVE_MODE_KEYSCAN_KS_COL);
226 }
227 if (result == CY_RSLT_SUCCESS)
228 {
229 obj->columns[idx] = columns[idx];
230 }
231 }
232 }
233
234 // Reserve the row pins
235 for (uint8_t idx=0; idx < num_rows; idx++)
236 {
237 if (result == CY_RSLT_SUCCESS)
238 {
239 const cyhal_resource_pin_mapping_t* row_map = _CYHAL_UTILS_GET_RESOURCE(rows[idx], cyhal_pin_map_keyscan_ks_row);
240 result = (row_map == NULL) ? CYHAL_KEYSCAN_RSLT_ERR_INVALID_PIN : CY_RSLT_SUCCESS;
241 /* We must have at least one column, so we know that obj->resource will be set if we reach
242 * here in a non-error state. */
243 /* Directly, instead of using _cyhal_utils_map_resource, checking whether pin belong to used block
244 * (and not checking channel_num) as cyhal_pin_map_keyscan_ks_row / cyhal_pin_map_keyscan_ks_col maps
245 * each column as separate channel */
246 if (result == CY_RSLT_SUCCESS && (obj->resource.block_num != row_map->block_num))
247 {
248 result = CYHAL_KEYSCAN_RSLT_ERR_INVALID_PIN;
249 }
250 if (result == CY_RSLT_SUCCESS)
251 {
252 /* For this block, we reuse the channel num field to store the bit index on the keyscan.
253 * Pull off and check that value
254 */
255 uint8_t bit_index = row_map->channel_num;
256 if (bit_index != idx) /* PDL only support contiguous indices that start from 0 */
257 {
258 result = CYHAL_KEYSCAN_RSLT_ERR_INVALID_PIN;
259 }
260 }
261 if (result == CY_RSLT_SUCCESS)
262 {
263 result = _cyhal_utils_reserve_and_connect(row_map, CYHAL_PIN_MAP_DRIVE_MODE_KEYSCAN_KS_ROW);
264 }
265 if (result == CY_RSLT_SUCCESS)
266 {
267 obj->rows[idx] = rows[idx];
268 }
269 }
270 }
271
272 // Clock allocation
273 if (result == CY_RSLT_SUCCESS)
274 {
275 if (clock == NULL)
276 {
277 cyhal_clock_t clock_keyscan;
278 result = cyhal_clock_reserve(&clock_keyscan, &CYHAL_CLOCK_MF);
279 if (CY_RSLT_SUCCESS == result)
280 {
281 obj->is_clock_owned = true;
282 obj->clock = clock_keyscan;
283 result = cyhal_clock_set_enabled(&clock_keyscan, true, true);
284 }
285 }
286 else if(clock->block == CYHAL_CLOCK_BLOCK_MF)
287 {
288 obj->clock = *clock;
289 }
290 else /* CLK_MF is the only valid clock source */
291 {
292 result = CYHAL_KEYSCAN_RSLT_ERR_INVALID_ARG;
293 }
294 }
295
296 return result;
297 }
298
_cyhal_keyscan_init_hw(cyhal_keyscan_t * obj,const cy_stc_ks_config_t * cfg)299 static cy_rslt_t _cyhal_keyscan_init_hw(cyhal_keyscan_t *obj, const cy_stc_ks_config_t *cfg)
300 {
301 obj->base = _cyhal_keyscan_base[obj->resource.block_num];
302
303 cy_rslt_t result = Cy_Keyscan_Init(obj->base, cfg, &(obj->context));
304 if (result == CY_RSLT_SUCCESS)
305 {
306 _cyhal_keyscan_config_structs[0] = obj;
307 _cyhal_syspm_register_peripheral_callback(&_cyhal_keyscan_syspm_callback_data);
308
309 obj->callback_data.callback = NULL;
310 obj->callback_data.callback_arg = NULL;
311 obj->irq_cause = CYHAL_KEYSCAN_EVENT_NONE;
312
313 /* The PDL driver relies on being notified whenever FIFO_THRESH_DONE happens,
314 * so enable this and leave it enabled. The HAL-level event enable/disable
315 * just determines whether the application callback is invoked.
316 */
317 Cy_Keyscan_SetInterruptMask(obj->base, MXKEYSCAN_INTR_FIFO_THRESH_DONE);
318
319 result = Cy_Keyscan_Register_Callback(_cyhal_keyscan_cb_wrapper, &(obj->context));
320 }
321
322 if (result == CY_RSLT_SUCCESS)
323 {
324 _cyhal_irq_register(keyscan_interrupt_IRQn, CYHAL_ISR_PRIORITY_DEFAULT, _cyhal_keyscan_irq_handler);
325 _cyhal_irq_enable(keyscan_interrupt_IRQn);
326
327 result = Cy_Keyscan_FlushEvents(obj->base, &(obj->context));
328 }
329
330 if (result == CY_RSLT_SUCCESS)
331 {
332 result = Cy_Keyscan_Enable(obj->base, &(obj->context));
333 }
334
335 return result;
336 }
337
cyhal_keyscan_init(cyhal_keyscan_t * obj,uint8_t num_columns,const cyhal_gpio_t * columns,uint8_t num_rows,const cyhal_gpio_t * rows,const cyhal_clock_t * clock)338 cy_rslt_t cyhal_keyscan_init(cyhal_keyscan_t *obj, uint8_t num_columns, const cyhal_gpio_t *columns,
339 uint8_t num_rows, const cyhal_gpio_t *rows, const cyhal_clock_t *clock)
340 {
341 CY_ASSERT(NULL != obj);
342 memset(obj, 0, sizeof(cyhal_keyscan_t));
343
344 obj->dc_configured = false;
345
346 cy_rslt_t result = _cyhal_keyscan_init_resources(obj, num_columns, columns, num_rows, rows, clock);
347
348 if (result == CY_RSLT_SUCCESS)
349 {
350 cy_stc_ks_config_t config = _cyhal_keyscan_default_config;
351 config.noofRows = num_rows;
352 config.noofColumns = num_columns;
353 result = _cyhal_keyscan_init_hw(obj, &config);
354 }
355
356 if (result != CY_RSLT_SUCCESS)
357 {
358 cyhal_keyscan_free(obj);
359 }
360
361 return result;
362 }
363
cyhal_keyscan_init_cfg(cyhal_keyscan_t * obj,const cyhal_keyscan_configurator_t * cfg)364 cy_rslt_t cyhal_keyscan_init_cfg(cyhal_keyscan_t *obj, const cyhal_keyscan_configurator_t *cfg)
365 {
366 CY_ASSERT(NULL != obj);
367 CY_ASSERT(NULL != cfg);
368 CY_ASSERT(NULL != cfg->config);
369 memset(obj, 0, sizeof(cyhal_keyscan_t));
370
371 obj->resource = *cfg->resource;
372 obj->clock = *cfg->clock;
373 obj->is_clock_owned = false;
374 obj->dc_configured = true;
375
376 for (uint8_t idx=0; idx < _CYHAL_KEYSCAN_MAX_COLS; idx++) obj->columns[idx] = NC;
377 for (uint8_t idx=0; idx < _CYHAL_KEYSCAN_MAX_ROWS; idx++) obj->rows[idx] = NC;
378
379 cy_rslt_t result = _cyhal_keyscan_init_hw(obj, cfg->config);
380
381 if (result != CY_RSLT_SUCCESS)
382 {
383 cyhal_keyscan_free(obj);
384 }
385
386 return result;
387 }
388
cyhal_keyscan_free(cyhal_keyscan_t * obj)389 void cyhal_keyscan_free(cyhal_keyscan_t *obj)
390 {
391 CY_ASSERT(NULL != obj);
392
393 if (obj->resource.type != CYHAL_RSC_INVALID)
394 {
395 _cyhal_irq_free(keyscan_interrupt_IRQn);
396 _cyhal_keyscan_config_structs[0] = NULL;
397
398 if (obj->base != NULL)
399 {
400 Cy_Keyscan_SetInterruptMask(obj->base, 0);
401 Cy_Keyscan_Disable(obj->base, &(obj->context));
402 Cy_Keyscan_DeInit(obj->base, &(obj->context));
403 }
404
405 if (false == obj->dc_configured)
406 {
407 cyhal_hwmgr_free(&(obj->resource));
408 }
409 }
410
411 if (obj->is_clock_owned)
412 {
413 cyhal_clock_free(&(obj->clock));
414 }
415
416 if (false == obj->dc_configured)
417 {
418 // Free the column pins
419 for (uint8_t idx=0; idx < _CYHAL_KEYSCAN_MAX_COLS; idx++)
420 {
421 _cyhal_utils_release_if_used(&(obj->columns[idx]));
422 }
423 // Free the row pins
424 for (uint8_t idx=0; idx < _CYHAL_KEYSCAN_MAX_ROWS; idx++)
425 {
426 _cyhal_utils_release_if_used(&(obj->rows[idx]));
427 }
428 }
429 }
430
cyhal_keyscan_read(cyhal_keyscan_t * obj,uint8_t * count,cyhal_keyscan_action_t * keys)431 cy_rslt_t cyhal_keyscan_read(cyhal_keyscan_t *obj, uint8_t* count, cyhal_keyscan_action_t* keys)
432 {
433 cy_en_ks_status_t status = CY_KEYSCAN_SUCCESS;
434 bool hasPending = true;
435 uint8_t eventCount = 0;
436 cy_stc_key_event eventNext;
437
438 while ((CY_KEYSCAN_SUCCESS == status) && (hasPending) && (eventCount < *count))
439 {
440 status = Cy_Keyscan_EventsPending(obj->base, &hasPending, &(obj->context));
441 if (CY_KEYSCAN_SUCCESS == status && hasPending)
442 {
443 status = Cy_Keyscan_GetNextEvent(obj->base, &eventNext, &(obj->context));
444 if (CY_KEYSCAN_SUCCESS == status)
445 {
446 /* There are several special keycode IDs that aren't currently exposed through the HAL;
447 * don't include those in the events we give the user. See cy_en_ks_keycode_t */
448 bool isSpecialKeycode = (KEYSCAN_KEYCODE_GHOST == eventNext.keyCode)
449 || (KEYSCAN_KEYCODE_NONE == eventNext.keyCode)
450 || (KEYSCAN_KEYCODE_END_OF_SCAN_CYCLE == eventNext.keyCode)
451 || (KEYSCAN_KEYCODE_ROLLOVER == eventNext.keyCode);
452 if (false == isSpecialKeycode)
453 {
454 // Note: Discard eventNext.scanCycleFlag
455 keys[eventCount].keycode = eventNext.keyCode;
456 keys[eventCount].action = (eventNext.upDownFlag == 0)
457 ? CYHAL_KEYSCAN_ACTION_PRESS
458 : CYHAL_KEYSCAN_ACTION_RELEASE;
459
460 eventCount++;
461 }
462 }
463 }
464 }
465 *count = eventCount;
466
467 return status;
468 }
469
cyhal_keyscan_register_callback(cyhal_keyscan_t * obj,cyhal_keyscan_event_callback_t callback,void * callback_arg)470 void cyhal_keyscan_register_callback(cyhal_keyscan_t *obj, cyhal_keyscan_event_callback_t callback, void *callback_arg)
471 {
472 uint32_t savedIntrStatus = cyhal_system_critical_section_enter();
473 obj->callback_data.callback = (cy_israddress) callback;
474 obj->callback_data.callback_arg = callback_arg;
475 cyhal_system_critical_section_exit(savedIntrStatus);
476 }
477
cyhal_keyscan_enable_event(cyhal_keyscan_t * obj,cyhal_keyscan_event_t event,uint8_t intr_priority,bool enable)478 void cyhal_keyscan_enable_event(cyhal_keyscan_t *obj, cyhal_keyscan_event_t event, uint8_t intr_priority, bool enable)
479 {
480 if (enable)
481 {
482 obj->irq_cause |= event;
483 }
484 else
485 {
486 obj->irq_cause &= ~event;
487 }
488
489 _cyhal_irq_set_priority(keyscan_interrupt_IRQn, intr_priority);
490 }
491
492 #if defined(__cplusplus)
493 }
494 #endif
495
496 #endif /* CYHAL_DRIVER_AVAILABLE_KEYSCAN */
497