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