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 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 PSoC™ 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_clock.h"
49
50 #if (CYHAL_DRIVER_AVAILABLE_KEYSCAN)
51
52 #if defined(__cplusplus)
53 extern "C"
54 {
55 #endif
56
57 /* Internal defines */
58 #define _CYHAL_KEYSCAN_MAX_COLS MXKEYSCAN_NUM_COLS_OUT
59 #define _CYHAL_KEYSCAN_MAX_ROWS MXKEYSCAN_NUM_ROWS_IN
60
61 /* Internal prototypes */
62 static void _cyhal_keyscan_irq_handler(void);
63 static void _cyhal_keyscan_cb_wrapper(void);
64 static bool _cyhal_keyscan_pm_callback(cyhal_syspm_callback_state_t state, cyhal_syspm_callback_mode_t mode, void* callback_arg);
65 static cy_rslt_t _cyhal_keyscan_init_resources(cyhal_keyscan_t *obj, uint8_t num_columns, const cyhal_gpio_t *columns,
66 uint8_t num_rows, const cyhal_gpio_t *rows, const cyhal_clock_t *clock);
67 static cy_rslt_t _cyhal_keyscan_init_hw(cyhal_keyscan_t *obj, cy_stc_ks_config_t *cfg);
68
69 /* Default KeyScan configuration */
70 static const cy_stc_ks_config_t _cyhal_keyscan_default_config = {
71 .macroDownDebCnt = 3,
72 .macroUpDebCnt = 3,
73 .microDebCnt = 3,
74 .noofRows = 0,
75 .noofColumns = 0,
76 .ghostEnable = true,
77 .cpuWakeupEnable = true,
78 .clkStayOn = true,
79 .lfEnable = false
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_SYSTEM_HIBERNATE),
87 .next = NULL,
88 .args = NULL,
89 .ignore_modes = CYHAL_SYSPM_CHECK_READY
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;
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;
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 break;
153 }
154 case CYHAL_SYSPM_BEFORE_TRANSITION:
155 {
156 Cy_Keyscan_DisableClockStayOn(obj->base);
157 Cy_Keyscan_SetInterruptMask(obj->base, readMask | MXKEYSCAN_INTR_KEY_EDGE_DONE);
158 break;
159 }
160 default:
161 {
162 CY_ASSERT(false);
163 break;
164 }
165 }
166 return true;
167 }
168
_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)169 static cy_rslt_t _cyhal_keyscan_init_resources(cyhal_keyscan_t *obj, uint8_t num_columns, const cyhal_gpio_t *columns,
170 uint8_t num_rows, const cyhal_gpio_t *rows, const cyhal_clock_t *clock)
171 {
172 // Explicitly marked not allocated resources as invalid to prevent freeing them.
173 obj->resource.type = CYHAL_RSC_INVALID;
174 obj->irq_cause = CYHAL_KEYSCAN_EVENT_NONE;
175 for (uint8_t idx=0; idx < _CYHAL_KEYSCAN_MAX_COLS; idx++) obj->columns[idx] = NC;
176 for (uint8_t idx=0; idx < _CYHAL_KEYSCAN_MAX_ROWS; idx++) obj->rows[idx] = NC;
177
178 cy_rslt_t result = CY_RSLT_SUCCESS;
179
180 if ((num_columns > _CYHAL_KEYSCAN_MAX_COLS) || (num_rows > _CYHAL_KEYSCAN_MAX_ROWS ))
181 result = CYHAL_KEYSCAN_RSLT_ERR_INVALID_ARG;
182
183 // Reserve the column pins
184 for (uint8_t idx=0; idx < num_columns; idx++)
185 {
186 if (result == CY_RSLT_SUCCESS)
187 {
188 const cyhal_resource_pin_mapping_t* column_map = _CYHAL_UTILS_GET_RESOURCE(columns[idx], cyhal_pin_map_keyscan_ks_col);
189 result = (column_map == NULL) ? CYHAL_KEYSCAN_RSLT_ERR_INVALID_PIN : CY_RSLT_SUCCESS;
190 if (result == CY_RSLT_SUCCESS)
191 {
192 if(CYHAL_RSC_INVALID == obj->resource.type)
193 {
194 obj->resource.type = CYHAL_RSC_KEYSCAN;
195 obj->resource.block_num = column_map->block_num;
196 obj->resource.channel_num = 0;
197 result = cyhal_hwmgr_reserve(&(obj->resource));
198 }
199 /* Directly, instead of using _cyhal_utils_map_resource, checking whether pin belong to used block
200 * (and not checking channel_num) as cyhal_pin_map_keyscan_ks_row / cyhal_pin_map_keyscan_ks_col maps
201 * each column as separate channel */
202 else if (obj->resource.block_num != column_map->block_num)
203 {
204 result = CYHAL_KEYSCAN_RSLT_ERR_INVALID_PIN;
205 }
206 }
207 if (result == CY_RSLT_SUCCESS)
208 {
209 /* For this block, we reuse the channel_num field to store the bit index on the keyscan.
210 * Pull off and check that value */
211 uint8_t bit_index = column_map->channel_num;
212 if (bit_index != idx) /* PDL only support contiguous indices that start from 0 */
213 {
214 result = CYHAL_KEYSCAN_RSLT_ERR_INVALID_PIN;
215 }
216 }
217 if (result == CY_RSLT_SUCCESS)
218 {
219 result = _cyhal_utils_reserve_and_connect(column_map, CYHAL_PIN_MAP_DRIVE_MODE_KEYSCAN_KS_COL);
220 }
221 if (result == CY_RSLT_SUCCESS)
222 {
223 obj->columns[idx] = columns[idx];
224 }
225 }
226 }
227
228 // Reserve the row pins
229 for (uint8_t idx=0; idx < num_rows; idx++)
230 {
231 if (result == CY_RSLT_SUCCESS)
232 {
233 const cyhal_resource_pin_mapping_t* row_map = _CYHAL_UTILS_GET_RESOURCE(rows[idx], cyhal_pin_map_keyscan_ks_row);
234 result = (row_map == NULL) ? CYHAL_KEYSCAN_RSLT_ERR_INVALID_PIN : CY_RSLT_SUCCESS;
235 /* We must have at least one column, so we know that obj->resource will be set if we reach
236 * here in a non-error state. */
237 /* Directly, instead of using _cyhal_utils_map_resource, checking whether pin belong to used block
238 * (and not checking channel_num) as cyhal_pin_map_keyscan_ks_row / cyhal_pin_map_keyscan_ks_col maps
239 * each column as separate channel */
240 if (result == CY_RSLT_SUCCESS && (obj->resource.block_num != row_map->block_num))
241 {
242 result = CYHAL_KEYSCAN_RSLT_ERR_INVALID_PIN;
243 }
244 if (result == CY_RSLT_SUCCESS)
245 {
246 /* For this block, we reuse the channel num field to store the bit index on the keyscan.
247 * Pull off and check that value
248 */
249 uint8_t bit_index = row_map->channel_num;
250 if (bit_index != idx) /* PDL only support contiguous indices that start from 0 */
251 {
252 result = CYHAL_KEYSCAN_RSLT_ERR_INVALID_PIN;
253 }
254 }
255 if (result == CY_RSLT_SUCCESS)
256 {
257 result = _cyhal_utils_reserve_and_connect(row_map, CYHAL_PIN_MAP_DRIVE_MODE_KEYSCAN_KS_ROW);
258 }
259 if (result == CY_RSLT_SUCCESS)
260 {
261 obj->rows[idx] = rows[idx];
262 }
263 }
264 }
265
266 // Clock allocation
267 if (result == CY_RSLT_SUCCESS)
268 {
269 if (clock == NULL)
270 {
271 cyhal_clock_t clock_keyscan;
272 result = cyhal_clock_reserve(&clock_keyscan, &CYHAL_CLOCK_MF);
273 if (CY_RSLT_SUCCESS == result)
274 {
275 obj->is_clock_owned = true;
276 obj->clock = clock_keyscan;
277 result = cyhal_clock_set_enabled(&clock_keyscan, true, true);
278 }
279 }
280 else if(clock->block == CYHAL_CLOCK_BLOCK_MF)
281 {
282 obj->clock = *clock;
283 }
284 else /* CLK_MF is the only valid clock source */
285 {
286 result = CYHAL_KEYSCAN_RSLT_ERR_INVALID_ARG;
287 }
288 }
289
290 return result;
291 }
292
_cyhal_keyscan_init_hw(cyhal_keyscan_t * obj,cy_stc_ks_config_t * cfg)293 static cy_rslt_t _cyhal_keyscan_init_hw(cyhal_keyscan_t *obj, cy_stc_ks_config_t *cfg)
294 {
295 obj->base = _cyhal_keyscan_base[obj->resource.block_num];
296
297 cy_rslt_t result = Cy_Keyscan_Init(obj->base, cfg, &(obj->context));
298 if (result == CY_RSLT_SUCCESS)
299 {
300 _cyhal_keyscan_config_structs[0] = obj;
301 _cyhal_syspm_register_peripheral_callback(&_cyhal_keyscan_syspm_callback_data);
302
303 obj->callback_data.callback = NULL;
304 obj->callback_data.callback_arg = NULL;
305 obj->irq_cause = CYHAL_KEYSCAN_EVENT_NONE;
306
307 /* The PDL driver relies on being notified whenever FIFO_THRESH_DONE happens,
308 * so enable this and leave it enabled. The HAL-level event enable/disable
309 * just determines whether the application callback is invoked.
310 */
311 Cy_Keyscan_SetInterruptMask(obj->base, MXKEYSCAN_INTR_FIFO_THRESH_DONE);
312
313 result = Cy_Keyscan_Register_Callback(_cyhal_keyscan_cb_wrapper, &(obj->context));
314 }
315
316 if (result == CY_RSLT_SUCCESS)
317 {
318 cy_stc_sysint_t irqCfg = { keyscan_interrupt_IRQn, CYHAL_ISR_PRIORITY_DEFAULT };
319 Cy_SysInt_Init(&irqCfg, _cyhal_keyscan_irq_handler);
320 NVIC_EnableIRQ(irqCfg.intrSrc);
321
322 result = Cy_Keyscan_FlushEvents(obj->base, &(obj->context));
323 }
324
325 if (result == CY_RSLT_SUCCESS)
326 {
327 result = Cy_Keyscan_Enable(obj->base, &(obj->context));
328 }
329
330 return result;
331 }
332
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)333 cy_rslt_t cyhal_keyscan_init(cyhal_keyscan_t *obj, uint8_t num_columns, const cyhal_gpio_t *columns,
334 uint8_t num_rows, const cyhal_gpio_t *rows, const cyhal_clock_t *clock)
335 {
336 CY_ASSERT(NULL != obj);
337 memset(obj, 0, sizeof(cyhal_keyscan_t));
338
339 obj->dc_configured = false;
340
341 cy_rslt_t result = _cyhal_keyscan_init_resources(obj, num_columns, columns, num_rows, rows, clock);
342
343 if (result == CY_RSLT_SUCCESS)
344 {
345 cy_stc_ks_config_t config = _cyhal_keyscan_default_config;
346 config.noofRows = num_rows;
347 config.noofColumns = num_columns;
348 result = _cyhal_keyscan_init_hw(obj, &config);
349 }
350
351 if (result != CY_RSLT_SUCCESS)
352 {
353 cyhal_keyscan_free(obj);
354 }
355
356 return result;
357 }
358
cyhal_keyscan_init_cfg(cyhal_keyscan_t * obj,const cyhal_keyscan_configurator_t * cfg)359 cy_rslt_t cyhal_keyscan_init_cfg(cyhal_keyscan_t *obj, const cyhal_keyscan_configurator_t *cfg)
360 {
361 CY_ASSERT(NULL != obj);
362 CY_ASSERT(NULL != cfg);
363 CY_ASSERT(NULL != cfg->config);
364 memset(obj, 0, sizeof(cyhal_keyscan_t));
365
366 obj->resource = *cfg->resource;
367 obj->clock = *cfg->clock;
368 obj->is_clock_owned = false;
369 obj->dc_configured = true;
370
371 for (uint8_t idx=0; idx < _CYHAL_KEYSCAN_MAX_COLS; idx++) obj->columns[idx] = NC;
372 for (uint8_t idx=0; idx < _CYHAL_KEYSCAN_MAX_ROWS; idx++) obj->rows[idx] = NC;
373
374 cy_rslt_t result = _cyhal_keyscan_init_hw(obj, cfg->config);
375
376 if (result != CY_RSLT_SUCCESS)
377 {
378 cyhal_keyscan_free(obj);
379 }
380
381 return result;
382 }
383
cyhal_keyscan_free(cyhal_keyscan_t * obj)384 void cyhal_keyscan_free(cyhal_keyscan_t *obj)
385 {
386 CY_ASSERT(NULL != obj);
387
388 if (obj->resource.type != CYHAL_RSC_INVALID)
389 {
390 NVIC_DisableIRQ(keyscan_interrupt_IRQn);
391 _cyhal_keyscan_config_structs[0] = NULL;
392
393 if (obj->base != NULL)
394 {
395 Cy_Keyscan_SetInterruptMask(obj->base, 0);
396 Cy_Keyscan_Disable(obj->base, &(obj->context));
397 Cy_Keyscan_DeInit(obj->base, &(obj->context));
398 }
399
400 if (false == obj->dc_configured)
401 {
402 cyhal_hwmgr_free(&(obj->resource));
403 }
404 }
405
406 if (obj->is_clock_owned)
407 {
408 cyhal_clock_free(&(obj->clock));
409 }
410
411 if (false == obj->dc_configured)
412 {
413 // Free the column pins
414 for (uint8_t idx=0; idx < _CYHAL_KEYSCAN_MAX_COLS; idx++)
415 {
416 _cyhal_utils_release_if_used(&(obj->columns[idx]));
417 }
418 // Free the row pins
419 for (uint8_t idx=0; idx < _CYHAL_KEYSCAN_MAX_ROWS; idx++)
420 {
421 _cyhal_utils_release_if_used(&(obj->rows[idx]));
422 }
423 }
424 }
425
cyhal_keyscan_read(cyhal_keyscan_t * obj,uint8_t * count,cyhal_keyscan_action_t * keys)426 cy_rslt_t cyhal_keyscan_read(cyhal_keyscan_t *obj, uint8_t* count, cyhal_keyscan_action_t* keys)
427 {
428 cy_en_ks_status_t status = CY_KEYSCAN_SUCCESS;
429 bool hasPending = true;
430 uint8_t eventCount = 0;
431 cy_stc_key_event eventNext;
432
433 while ((CY_KEYSCAN_SUCCESS == status) && (hasPending) && (eventCount < *count))
434 {
435 status = Cy_Keyscan_EventsPending(obj->base, &hasPending, &(obj->context));
436 if (CY_KEYSCAN_SUCCESS == status && hasPending)
437 {
438 status = Cy_Keyscan_GetNextEvent(obj->base, &eventNext, &(obj->context));
439 if (CY_KEYSCAN_SUCCESS == status)
440 {
441 /* There are several special keycode IDs that aren't currently exposed through the HAL;
442 * don't include those in the events we give the user. See cy_en_ks_keycode_t */
443 bool isSpecialKeycode = (KEYSCAN_KEYCODE_GHOST == eventNext.keyCode)
444 || (KEYSCAN_KEYCODE_NONE == eventNext.keyCode)
445 || (KEYSCAN_KEYCODE_END_OF_SCAN_CYCLE == eventNext.keyCode)
446 || (KEYSCAN_KEYCODE_ROLLOVER == eventNext.keyCode);
447 if (false == isSpecialKeycode)
448 {
449 // Note: Discard eventNext.scanCycleFlag
450 keys[eventCount].keycode = eventNext.keyCode;
451 keys[eventCount].action = (eventNext.upDownFlag == 0)
452 ? CYHAL_KEYSCAN_ACTION_PRESS
453 : CYHAL_KEYSCAN_ACTION_RELEASE;
454
455 eventCount++;
456 }
457 }
458 }
459 }
460 *count = eventCount;
461
462 return status;
463 }
464
cyhal_keyscan_register_callback(cyhal_keyscan_t * obj,cyhal_keyscan_event_callback_t callback,void * callback_arg)465 void cyhal_keyscan_register_callback(cyhal_keyscan_t *obj, cyhal_keyscan_event_callback_t callback, void *callback_arg)
466 {
467 uint32_t savedIntrStatus = cyhal_system_critical_section_enter();
468 obj->callback_data.callback = (cy_israddress) callback;
469 obj->callback_data.callback_arg = callback_arg;
470 cyhal_system_critical_section_exit(savedIntrStatus);
471 }
472
cyhal_keyscan_enable_event(cyhal_keyscan_t * obj,cyhal_keyscan_event_t event,uint8_t intr_priority,bool enable)473 void cyhal_keyscan_enable_event(cyhal_keyscan_t *obj, cyhal_keyscan_event_t event, uint8_t intr_priority, bool enable)
474 {
475 if (enable)
476 {
477 obj->irq_cause |= event;
478 }
479 else
480 {
481 obj->irq_cause &= ~event;
482 }
483
484 NVIC_SetPriority(keyscan_interrupt_IRQn, intr_priority);
485 }
486
487 #if defined(__cplusplus)
488 }
489 #endif
490
491 #endif /* CYHAL_DRIVER_AVAILABLE_KEYSCAN */
492