|  | /* | 
|  | * Copyright (c) 2019 Intel Corporation | 
|  | * Copyright (c) 2022 Microchip Technology Inc. | 
|  | * SPDX-License-Identifier: Apache-2.0 | 
|  | */ | 
|  |  | 
|  | #define DT_DRV_COMPAT microchip_xec_kscan | 
|  |  | 
|  | #include <zephyr/arch/arm/aarch32/cortex_m/cmsis.h> | 
|  | #include <errno.h> | 
|  | #include <zephyr/device.h> | 
|  | #ifdef CONFIG_SOC_SERIES_MEC172X | 
|  | #include <zephyr/drivers/clock_control/mchp_xec_clock_control.h> | 
|  | #include <zephyr/drivers/interrupt_controller/intc_mchp_xec_ecia.h> | 
|  | #endif | 
|  | #include <zephyr/drivers/kscan.h> | 
|  | #ifdef CONFIG_PINCTRL | 
|  | #include <zephyr/drivers/pinctrl.h> | 
|  | #endif | 
|  | #include <zephyr/kernel.h> | 
|  | #include <soc.h> | 
|  | #include <zephyr/sys/atomic.h> | 
|  | #include <zephyr/logging/log.h> | 
|  | #include <zephyr/irq.h> | 
|  |  | 
|  | #define LOG_LEVEL CONFIG_KSCAN_LOG_LEVEL | 
|  | LOG_MODULE_REGISTER(kscan_mchp_xec); | 
|  |  | 
|  | #define MAX_MATRIX_KEY_COLS CONFIG_KSCAN_XEC_COLUMN_SIZE | 
|  | #define MAX_MATRIX_KEY_ROWS CONFIG_KSCAN_XEC_ROW_SIZE | 
|  |  | 
|  | #define KEYBOARD_COLUMN_DRIVE_ALL       -2 | 
|  | #define KEYBOARD_COLUMN_DRIVE_NONE      -1 | 
|  |  | 
|  | /* Poll period/debouncing rely on the 32KHz clock with 30 usec clock cycles */ | 
|  | #define CLOCK_32K_HW_CYCLES_TO_US(X) \ | 
|  | (uint32_t)((((uint64_t)(X) * 1000000U) / sys_clock_hw_cycles_per_sec())) | 
|  | /* Milliseconds in microseconds */ | 
|  | #define MSEC_PER_MS 1000U | 
|  | /* Number of tracked scan times */ | 
|  | #define SCAN_OCURRENCES 30U | 
|  | /* Thread stack size */ | 
|  | #define TASK_STACK_SIZE 1024 | 
|  |  | 
|  | struct kscan_xec_config { | 
|  | struct kscan_regs *regs; | 
|  | uint8_t girq; | 
|  | uint8_t girq_pos; | 
|  | uint8_t irq_pri; | 
|  | uint8_t pcr_idx; | 
|  | uint8_t pcr_pos; | 
|  | uint8_t rsvd[3]; | 
|  | #ifdef CONFIG_PINCTRL | 
|  | const struct pinctrl_dev_config *pcfg; | 
|  | #endif | 
|  | }; | 
|  |  | 
|  | struct kscan_xec_data { | 
|  | /* variables in usec units */ | 
|  | uint32_t deb_time_press; | 
|  | uint32_t deb_time_rel; | 
|  | int64_t poll_timeout; | 
|  | uint32_t poll_period; | 
|  | uint8_t matrix_stable_state[MAX_MATRIX_KEY_COLS]; | 
|  | uint8_t matrix_unstable_state[MAX_MATRIX_KEY_COLS]; | 
|  | uint8_t matrix_previous_state[MAX_MATRIX_KEY_COLS]; | 
|  | /* Index in to the scan_clock_cycle to indicate start of debouncing */ | 
|  | uint8_t scan_cycle_idx[MAX_MATRIX_KEY_COLS][MAX_MATRIX_KEY_ROWS]; | 
|  | /* Track previous "elapsed clock cycles" per matrix scan. This | 
|  | * is used to calculate the debouncing time for every key | 
|  | */ | 
|  | uint8_t scan_clk_cycle[SCAN_OCURRENCES]; | 
|  | struct k_sem poll_lock; | 
|  | uint8_t scan_cycles_idx; | 
|  | kscan_callback_t callback; | 
|  | struct k_thread thread; | 
|  | atomic_t enable_scan; | 
|  |  | 
|  | K_KERNEL_STACK_MEMBER(thread_stack, TASK_STACK_SIZE); | 
|  | }; | 
|  |  | 
|  | #ifdef CONFIG_SOC_SERIES_MEC172X | 
|  | static void kscan_clear_girq_status(const struct device *dev) | 
|  | { | 
|  | struct kscan_xec_config const *cfg = dev->config; | 
|  |  | 
|  | mchp_xec_ecia_girq_src_clr(cfg->girq, cfg->girq_pos); | 
|  | } | 
|  |  | 
|  | static void kscan_configure_girq(const struct device *dev, bool enable) | 
|  | { | 
|  | struct kscan_xec_config const *cfg = dev->config; | 
|  |  | 
|  | if (enable) { | 
|  | mchp_xec_ecia_enable(cfg->girq, cfg->girq_pos); | 
|  | } else { | 
|  | mchp_xec_ecia_disable(cfg->girq, cfg->girq_pos); | 
|  | } | 
|  | } | 
|  |  | 
|  | static void kscan_clr_slp_en(const struct device *dev) | 
|  | { | 
|  | struct kscan_xec_config const *cfg = dev->config; | 
|  |  | 
|  | z_mchp_xec_pcr_periph_sleep(cfg->pcr_idx, cfg->pcr_pos, 0); | 
|  | } | 
|  |  | 
|  | #else | 
|  | static void kscan_clear_girq_status(const struct device *dev) | 
|  | { | 
|  | struct kscan_xec_config const *cfg = dev->config; | 
|  |  | 
|  | MCHP_GIRQ_SRC(cfg->girq) = BIT(cfg->girq_pos); | 
|  | } | 
|  |  | 
|  | static void kscan_configure_girq(const struct device *dev, bool enable) | 
|  | { | 
|  | struct kscan_xec_config const *cfg = dev->config; | 
|  |  | 
|  | if (enable) { | 
|  | MCHP_GIRQ_ENSET(cfg->girq) = BIT(cfg->girq_pos); | 
|  | } else { | 
|  | MCHP_GIRQ_ENCLR(cfg->girq) = BIT(cfg->girq_pos); | 
|  | } | 
|  | } | 
|  |  | 
|  | static void kscan_clr_slp_en(const struct device *dev) | 
|  | { | 
|  | ARG_UNUSED(dev); | 
|  |  | 
|  | mchp_pcr_periph_slp_ctrl(PCR_KEYSCAN, 0); | 
|  | } | 
|  | #endif | 
|  |  | 
|  |  | 
|  | static void drive_keyboard_column(const struct device *dev, int data) | 
|  | { | 
|  | struct kscan_xec_config const *cfg = dev->config; | 
|  | struct kscan_regs *regs = cfg->regs; | 
|  |  | 
|  | if (data == KEYBOARD_COLUMN_DRIVE_ALL) { | 
|  | /* KSO output controlled by the KSO_SELECT field */ | 
|  | regs->KSO_SEL = MCHP_KSCAN_KSO_ALL; | 
|  | } else if (data == KEYBOARD_COLUMN_DRIVE_NONE) { | 
|  | /* Keyboard scan disabled. All KSO output buffers disabled */ | 
|  | regs->KSO_SEL = MCHP_KSCAN_KSO_EN; | 
|  | } else { | 
|  | /* It is assumed, KEYBOARD_COLUMN_DRIVE_ALL was | 
|  | * previously set | 
|  | */ | 
|  | regs->KSO_SEL = data; | 
|  | } | 
|  | } | 
|  |  | 
|  | static uint8_t read_keyboard_row(const struct device *dev) | 
|  | { | 
|  | struct kscan_xec_config const *cfg = dev->config; | 
|  | struct kscan_regs *regs = cfg->regs; | 
|  |  | 
|  | /* In this implementation a 1 means key pressed */ | 
|  | return ~(regs->KSI_IN & 0xFF); | 
|  | } | 
|  |  | 
|  | static bool is_matrix_ghosting(const uint8_t *state) | 
|  | { | 
|  | /* matrix keyboard designs are susceptible to ghosting. | 
|  | * An extra key appears to be pressed when 3 keys | 
|  | * belonging to the same block are pressed. | 
|  | * for example, in the following block | 
|  | * | 
|  | * . . w . q . | 
|  | * . . . . . . | 
|  | * . . . . . . | 
|  | * . . m . a . | 
|  | * | 
|  | * the key m would look as pressed if the user pressed keys | 
|  | * w, q and a simultaneously. A block can also be formed, | 
|  | * with not adjacent columns. | 
|  | */ | 
|  | for (int c = 0; c <  MAX_MATRIX_KEY_COLS; c++) { | 
|  | if (!state[c]) | 
|  | continue; | 
|  |  | 
|  | for (int c_n = c + 1; c_n <  MAX_MATRIX_KEY_COLS; c_n++) { | 
|  | /* we and the columns to detect a "block". | 
|  | * this is an indication of ghosting, due to current | 
|  | * flowing from a key which was never pressed. in our | 
|  | * case, current flowing is a bit set to 1 as we | 
|  | * flipped the bits when the matrix was scanned. | 
|  | * now we or the columns using z&(z-1) which is | 
|  | * non-zero only if z has more than one bit set. | 
|  | */ | 
|  | uint8_t common_row_bits = state[c] & state[c_n]; | 
|  |  | 
|  | if (common_row_bits & (common_row_bits - 1)) | 
|  | return true; | 
|  | } | 
|  | } | 
|  |  | 
|  | return false; | 
|  | } | 
|  |  | 
|  | static bool read_keyboard_matrix(const struct device *dev, uint8_t *new_state) | 
|  | { | 
|  | uint8_t row; | 
|  | uint8_t key_event = 0U; | 
|  |  | 
|  | for (int col = 0; col < MAX_MATRIX_KEY_COLS; col++) { | 
|  | drive_keyboard_column(dev, col); | 
|  |  | 
|  | /* Allow the matrix to stabilize before reading it */ | 
|  | k_busy_wait(50U); | 
|  | row = read_keyboard_row(dev); | 
|  | new_state[col] = row; | 
|  | key_event |= row; | 
|  | } | 
|  |  | 
|  | drive_keyboard_column(dev, KEYBOARD_COLUMN_DRIVE_NONE); | 
|  |  | 
|  | return key_event != 0U ? true : false; | 
|  | } | 
|  |  | 
|  | static void scan_matrix_xec_isr(const struct device *dev) | 
|  | { | 
|  | struct kscan_xec_data *const data = dev->data; | 
|  |  | 
|  | kscan_clear_girq_status(dev); | 
|  | irq_disable(DT_INST_IRQN(0)); | 
|  | k_sem_give(&data->poll_lock); | 
|  | LOG_DBG(" "); | 
|  | } | 
|  |  | 
|  | static bool check_key_events(const struct device *dev) | 
|  | { | 
|  | struct kscan_xec_data *const data = dev->data; | 
|  | uint8_t matrix_new_state[MAX_MATRIX_KEY_COLS] = {0U}; | 
|  | bool key_pressed = false; | 
|  | uint32_t cycles_now  = k_cycle_get_32(); | 
|  |  | 
|  | if (++data->scan_cycles_idx >= SCAN_OCURRENCES) { | 
|  | data->scan_cycles_idx = 0U; | 
|  | } | 
|  |  | 
|  | data->scan_clk_cycle[data->scan_cycles_idx] = cycles_now; | 
|  |  | 
|  | /* Scan the matrix */ | 
|  | key_pressed = read_keyboard_matrix(dev, matrix_new_state); | 
|  |  | 
|  | /* Abort if ghosting is detected */ | 
|  | if (is_matrix_ghosting(matrix_new_state)) { | 
|  | return false; | 
|  | } | 
|  |  | 
|  | uint8_t row_changed = 0U; | 
|  | uint8_t deb_col; | 
|  |  | 
|  | /* The intent of this loop is to gather information related to key | 
|  | * changes. | 
|  | */ | 
|  | for (int c = 0; c < MAX_MATRIX_KEY_COLS; c++) { | 
|  | /* Check if there was an update from the previous scan */ | 
|  | row_changed = matrix_new_state[c] ^ | 
|  | data->matrix_previous_state[c]; | 
|  |  | 
|  | if (!row_changed) { | 
|  | continue; | 
|  | } | 
|  |  | 
|  | for (int r = 0; r < MAX_MATRIX_KEY_ROWS; r++) { | 
|  | /* Index all they keys that changed for each row | 
|  | * in order to debounce each key in terms of it | 
|  | */ | 
|  | if (row_changed & BIT(r)) { | 
|  | data->scan_cycle_idx[c][r] = | 
|  | data->scan_cycles_idx; | 
|  | } | 
|  | } | 
|  |  | 
|  | data->matrix_unstable_state[c] |= row_changed; | 
|  | data->matrix_previous_state[c] = matrix_new_state[c]; | 
|  | } | 
|  |  | 
|  | for (int c = 0; c < MAX_MATRIX_KEY_COLS; c++) { | 
|  | deb_col = data->matrix_unstable_state[c]; | 
|  |  | 
|  | if (!deb_col) { | 
|  | continue; | 
|  | } | 
|  |  | 
|  | /* Debouncing for each row key occurs here */ | 
|  | for (int r = 0; r < MAX_MATRIX_KEY_ROWS; r++) { | 
|  | uint8_t mask = BIT(r); | 
|  | uint8_t row_bit = matrix_new_state[c] & mask; | 
|  |  | 
|  | /* Continue if we already debounce a key */ | 
|  | if (!(deb_col & mask)) { | 
|  | continue; | 
|  | } | 
|  |  | 
|  | /* Convert the clock cycle differences to usec */ | 
|  | uint32_t debt = CLOCK_32K_HW_CYCLES_TO_US(cycles_now - | 
|  | data->scan_clk_cycle[data->scan_cycle_idx[c][r]]); | 
|  |  | 
|  | /* Does the key requires more time to be debounced? */ | 
|  | if (debt < (row_bit ? data->deb_time_press : | 
|  | data->deb_time_rel)) { | 
|  | /* Need more time to debounce */ | 
|  | continue; | 
|  | } | 
|  |  | 
|  | data->matrix_unstable_state[c] &= ~row_bit; | 
|  |  | 
|  | /* Check if there was a change in the stable state */ | 
|  | if ((data->matrix_stable_state[c] & mask) | 
|  | == row_bit) { | 
|  | /* Key state did not change */ | 
|  | continue; | 
|  |  | 
|  | } | 
|  |  | 
|  | /* The current row has been debounced, therefore update | 
|  | * the stable state. Then, proceed to notify the | 
|  | * application about the keys pressed. | 
|  | */ | 
|  | data->matrix_stable_state[c] ^= mask; | 
|  | if (atomic_get(&data->enable_scan) == 1U) { | 
|  | data->callback(dev, r, c, | 
|  | row_bit ? true : false); | 
|  | } | 
|  | } | 
|  | } | 
|  |  | 
|  | return key_pressed; | 
|  | } | 
|  |  | 
|  | static bool poll_expired(uint32_t start_cycles, int64_t *timeout) | 
|  | { | 
|  | uint32_t stop_cycles; | 
|  | uint32_t cycles_spent; | 
|  | uint32_t microsecs_spent; | 
|  |  | 
|  | stop_cycles = k_cycle_get_32(); | 
|  | cycles_spent =  stop_cycles - start_cycles; | 
|  | microsecs_spent = CLOCK_32K_HW_CYCLES_TO_US(cycles_spent); | 
|  |  | 
|  | /* Update the timeout value */ | 
|  | *timeout -= microsecs_spent; | 
|  |  | 
|  | return *timeout >= 0; | 
|  |  | 
|  | } | 
|  |  | 
|  | void polling_task(const struct device *dev, void *dummy2, void *dummy3) | 
|  | { | 
|  | struct kscan_xec_config const *cfg = dev->config; | 
|  | struct kscan_xec_data *const data = dev->data; | 
|  | struct kscan_regs *regs = cfg->regs; | 
|  | uint32_t current_cycles; | 
|  | uint32_t cycles_diff; | 
|  | uint32_t wait_period; | 
|  | int64_t local_poll_timeout = data->poll_timeout; | 
|  |  | 
|  | ARG_UNUSED(dummy2); | 
|  | ARG_UNUSED(dummy3); | 
|  |  | 
|  | while (true) { | 
|  | regs->KSI_STS = MCHP_KSCAN_KSO_SEL_REG_MASK; | 
|  |  | 
|  | /* Ignore isr when releasing a key as we are polling */ | 
|  | kscan_clear_girq_status(dev); | 
|  | NVIC_ClearPendingIRQ(DT_INST_IRQN(0)); | 
|  | irq_enable(DT_INST_IRQN(0)); | 
|  |  | 
|  | drive_keyboard_column(dev, KEYBOARD_COLUMN_DRIVE_ALL); | 
|  |  | 
|  | k_sem_take(&data->poll_lock, K_FOREVER); | 
|  |  | 
|  | uint32_t start_poll_cycles = k_cycle_get_32(); | 
|  |  | 
|  | while (atomic_get(&data->enable_scan) == 1U) { | 
|  | uint32_t start_period_cycles = k_cycle_get_32(); | 
|  |  | 
|  | if (check_key_events(DEVICE_DT_INST_GET(0))) { | 
|  | local_poll_timeout = data->poll_timeout; | 
|  | start_poll_cycles = k_cycle_get_32(); | 
|  | } else if (!poll_expired(start_poll_cycles, | 
|  | &local_poll_timeout)) { | 
|  | break; | 
|  | } | 
|  |  | 
|  | /* Subtract the time invested from the sleep period | 
|  | * in order to compensate for the time invested | 
|  | * in debouncing a key | 
|  | */ | 
|  | current_cycles = k_cycle_get_32(); | 
|  | cycles_diff = current_cycles - start_period_cycles; | 
|  | wait_period =  data->poll_period - | 
|  | CLOCK_32K_HW_CYCLES_TO_US(cycles_diff); | 
|  |  | 
|  | /* Override wait_period in case it is less than 1 ms */ | 
|  | if (wait_period < MSEC_PER_MS) { | 
|  | wait_period = MSEC_PER_MS; | 
|  | } | 
|  |  | 
|  | /* wait period results in a larger number when | 
|  | * current cycles counter wrap. In this case, the | 
|  | * whole poll period is used | 
|  | */ | 
|  | if (wait_period > data->poll_period) { | 
|  | LOG_DBG("wait_period : %u", wait_period); | 
|  |  | 
|  | wait_period = data->poll_period; | 
|  | } | 
|  |  | 
|  | /* Allow other threads to run while we sleep */ | 
|  | k_usleep(wait_period); | 
|  | } | 
|  | } | 
|  | } | 
|  |  | 
|  | static int kscan_xec_configure(const struct device *dev, | 
|  | kscan_callback_t callback) | 
|  | { | 
|  | struct kscan_xec_data *const data = dev->data; | 
|  |  | 
|  | if (!callback) { | 
|  | return -EINVAL; | 
|  | } | 
|  |  | 
|  | data->callback = callback; | 
|  |  | 
|  | kscan_clear_girq_status(dev); | 
|  | kscan_configure_girq(dev, 1); | 
|  |  | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | static int kscan_xec_inhibit_interface(const struct device *dev) | 
|  | { | 
|  | struct kscan_xec_data *const data = dev->data; | 
|  |  | 
|  | atomic_set(&data->enable_scan, 0); | 
|  |  | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | static int kscan_xec_enable_interface(const struct device *dev) | 
|  | { | 
|  | struct kscan_xec_data *const data = dev->data; | 
|  |  | 
|  | atomic_set(&data->enable_scan, 1); | 
|  |  | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | static const struct kscan_driver_api kscan_xec_driver_api = { | 
|  | .config = kscan_xec_configure, | 
|  | .disable_callback = kscan_xec_inhibit_interface, | 
|  | .enable_callback = kscan_xec_enable_interface, | 
|  | }; | 
|  |  | 
|  | static int kscan_xec_init(const struct device *dev) | 
|  | { | 
|  | struct kscan_xec_config const *cfg = dev->config; | 
|  | struct kscan_xec_data *const data = dev->data; | 
|  | struct kscan_regs *regs = cfg->regs; | 
|  |  | 
|  | #ifdef CONFIG_PINCTRL | 
|  | int ret = pinctrl_apply_state(cfg->pcfg, PINCTRL_STATE_DEFAULT); | 
|  |  | 
|  | if (ret != 0) { | 
|  | LOG_ERR("XEC KSCAN pinctrl init failed (%d)", ret); | 
|  | return ret; | 
|  | } | 
|  | #endif | 
|  |  | 
|  | kscan_clr_slp_en(dev); | 
|  |  | 
|  | /* Enable predrive */ | 
|  | regs->KSO_SEL |= BIT(MCHP_KSCAN_KSO_EN_POS); | 
|  | regs->EXT_CTRL = MCHP_KSCAN_EXT_CTRL_PREDRV_EN; | 
|  | regs->KSO_SEL &= ~BIT(MCHP_KSCAN_KSO_EN_POS); | 
|  | regs->KSI_IEN = MCHP_KSCAN_KSI_IEN_REG_MASK; | 
|  |  | 
|  | /* Time figures are transformed from msec to usec */ | 
|  | data->deb_time_press = (uint32_t) | 
|  | (CONFIG_KSCAN_XEC_DEBOUNCE_DOWN * MSEC_PER_MS); | 
|  | data->deb_time_rel = (uint32_t) | 
|  | (CONFIG_KSCAN_XEC_DEBOUNCE_UP * MSEC_PER_MS); | 
|  | data->poll_period = (uint32_t) | 
|  | (CONFIG_KSCAN_XEC_POLL_PERIOD * MSEC_PER_MS); | 
|  | data->poll_timeout = 100 * MSEC_PER_MS; | 
|  |  | 
|  | k_sem_init(&data->poll_lock, 0, 1); | 
|  | atomic_set(&data->enable_scan, 1); | 
|  |  | 
|  | k_thread_create(&data->thread, data->thread_stack, | 
|  | TASK_STACK_SIZE, | 
|  | (void (*)(void *, void *, void *))polling_task, | 
|  | (void *)dev, NULL, NULL, | 
|  | K_PRIO_COOP(4), 0, K_NO_WAIT); | 
|  |  | 
|  | /* Interrupts are enabled in the thread function */ | 
|  | IRQ_CONNECT(DT_INST_IRQN(0), DT_INST_IRQ(0, priority), | 
|  | scan_matrix_xec_isr, DEVICE_DT_INST_GET(0), 0); | 
|  |  | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | static struct kscan_xec_data kbd_data; | 
|  |  | 
|  | #ifdef CONFIG_PINCTRL | 
|  | PINCTRL_DT_INST_DEFINE(0); | 
|  | #endif | 
|  |  | 
|  | static struct kscan_xec_config kscan_xec_cfg_0 = { | 
|  | .regs = (struct kscan_regs *)(DT_INST_REG_ADDR(0)), | 
|  | .girq = (uint8_t)(DT_INST_PROP_BY_IDX(0, girqs, 0)), | 
|  | .girq_pos = (uint8_t)(DT_INST_PROP_BY_IDX(0, girqs, 1)), | 
|  | .pcr_idx = (uint8_t)(DT_INST_PROP_BY_IDX(0, pcrs, 0)), | 
|  | .pcr_pos = (uint8_t)(DT_INST_PROP_BY_IDX(0, pcrs, 1)), | 
|  | #ifdef CONFIG_PINCTRL | 
|  | .pcfg = PINCTRL_DT_INST_DEV_CONFIG_GET(0), | 
|  | #endif | 
|  | }; | 
|  |  | 
|  | DEVICE_DT_INST_DEFINE(0, kscan_xec_init, | 
|  | NULL, &kbd_data, &kscan_xec_cfg_0, | 
|  | POST_KERNEL, CONFIG_KSCAN_INIT_PRIORITY, | 
|  | &kscan_xec_driver_api); |