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