/* * Copyright (c) 2021 ITE Corporation. All Rights Reserved. * SPDX-License-Identifier: Apache-2.0 */ #define DT_DRV_COMPAT ite_it8xxx2_kscan #include #include #include #include #include #include #include #include #include #include #include #include #define LOG_LEVEL CONFIG_KSCAN_LOG_LEVEL LOG_MODULE_REGISTER(kscan_ite_it8xxx2); #define KEYBOARD_KSI_PIN_COUNT IT8XXX2_DT_INST_WUCCTRL_LEN(0) #define KEYBOARD_COLUMN_DRIVE_ALL -2 #define KEYBOARD_COLUMN_DRIVE_NONE -1 /* Free run timer counts transform to micro-seconds (clock source is 32768Hz) */ #define CLOCK_32K_HW_CYCLES_TO_US(X) \ (uint32_t)((((uint64_t)(X) * 1000000U) / \ sys_clock_hw_cycles_per_sec())) /* Milli-second transform to micro-second */ #define MS_TO_US 1000U /* Number of tracked scan times */ #define SCAN_OCURRENCES 30U /* Thread stack size */ #define TASK_STACK_SIZE 1024 /* Device config */ enum kscan_pin_func { KSO16 = 0, KSO17, }; struct kscan_wuc_map_cfg { /* WUC control device structure */ const struct device *wucs; /* WUC pin mask */ uint8_t mask; }; struct kscan_it8xxx2_config { /* Keyboard scan controller base address */ struct kscan_it8xxx2_regs *base; /* Keyboard scan input (KSI) wake-up irq */ int irq; /* KSI[7:0] wake-up input source configuration list */ const struct kscan_wuc_map_cfg *wuc_map_list; /* KSI[7:0]/KSO[17:0] keyboard scan alternate configuration */ const struct pinctrl_dev_config *pcfg; /* KSO16 GPIO cells */ struct gpio_dt_spec kso16_gpios; /* KSO17 GPIO cells */ struct gpio_dt_spec kso17_gpios; }; /* Device data */ struct kscan_it8xxx2_data { /* Variables in usec units */ uint32_t deb_time_press; uint32_t deb_time_rel; int32_t poll_timeout; uint32_t poll_period; uint8_t matrix_stable_state[CONFIG_KSCAN_ITE_IT8XXX2_COLUMN_SIZE]; uint8_t matrix_unstable_state[CONFIG_KSCAN_ITE_IT8XXX2_COLUMN_SIZE]; uint8_t matrix_previous_state[CONFIG_KSCAN_ITE_IT8XXX2_COLUMN_SIZE]; /* Index in to the scan_clock_cycle to indicate start of debouncing */ uint8_t scan_cycle_idx[CONFIG_KSCAN_ITE_IT8XXX2_COLUMN_SIZE] [CONFIG_KSCAN_ITE_IT8XXX2_ROW_SIZE]; /* * 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; /* KSI[7:0] wake-up interrupt status mask */ uint8_t ksi_pin_mask; K_KERNEL_STACK_MEMBER(thread_stack, TASK_STACK_SIZE); }; static void drive_keyboard_column(const struct device *dev, int col) { const struct kscan_it8xxx2_config *const config = dev->config; struct kscan_it8xxx2_regs *const inst = config->base; int mask; /* Tri-state all outputs */ if (col == KEYBOARD_COLUMN_DRIVE_NONE) { mask = 0x3ffff; /* Assert all outputs */ } else if (col == KEYBOARD_COLUMN_DRIVE_ALL) { mask = 0; /* Assert a single output */ } else { mask = 0x3ffff ^ BIT(col); } /* Set KSO[17:0] output data */ inst->KBS_KSOL = (uint8_t) (mask & 0xff); inst->KBS_KSOH1 = (uint8_t) ((mask >> 8) & 0xff); #if (CONFIG_KSCAN_ITE_IT8XXX2_COLUMN_SIZE > 16) inst->KBS_KSOH2 = (uint8_t) ((mask >> 16) & 0xff); #endif } static uint8_t read_keyboard_row(const struct device *dev) { const struct kscan_it8xxx2_config *const config = dev->config; struct kscan_it8xxx2_regs *const inst = config->base; /* Bits are active-low, so toggle it (return 1 means key pressed) */ return (inst->KBS_KSI ^ 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 < CONFIG_KSCAN_ITE_IT8XXX2_COLUMN_SIZE; c++) { if (!state[c]) continue; for (int c_n = c + 1; c_n < CONFIG_KSCAN_ITE_IT8XXX2_COLUMN_SIZE; 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 < CONFIG_KSCAN_ITE_IT8XXX2_COLUMN_SIZE; col++) { /* Drive specific column low and others high */ 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 keyboard_raw_interrupt(const struct device *dev) { const struct kscan_it8xxx2_config *const config = dev->config; struct kscan_it8xxx2_data *data = dev->data; /* * W/C wakeup interrupt status of KSI[7:0] pins * * NOTE: We want to clear the status as soon as possible, * so clear KSI[7:0] pins at a time. */ it8xxx2_wuc_clear_status(config->wuc_map_list[0].wucs, data->ksi_pin_mask); /* W/C interrupt status of KSI[7:0] pins */ ite_intc_isr_clear(config->irq); /* Release poll lock semaphore */ k_sem_give(&data->poll_lock); } void keyboard_raw_enable_interrupt(const struct device *dev, int enable) { const struct kscan_it8xxx2_config *const config = dev->config; struct kscan_it8xxx2_data *data = dev->data; if (enable) { /* * W/C wakeup interrupt status of KSI[7:0] pins * * NOTE: We want to clear the status as soon as possible, * so clear KSI[7:0] pins at a time. */ it8xxx2_wuc_clear_status(config->wuc_map_list[0].wucs, data->ksi_pin_mask); /* W/C interrupt status of KSI[7:0] pins */ ite_intc_isr_clear(config->irq); irq_enable(config->irq); } else { irq_disable(config->irq); } } static bool check_key_events(const struct device *dev) { struct kscan_it8xxx2_data *data = dev->data; uint8_t matrix_new_state[CONFIG_KSCAN_ITE_IT8XXX2_COLUMN_SIZE] = {0U}; bool key_pressed = false; uint32_t cycles_now = k_cycle_get_32(); uint8_t row_changed = 0U; uint8_t deb_col; 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; } /* * The intent of this loop is to gather information related to key * changes */ for (int c = 0; c < CONFIG_KSCAN_ITE_IT8XXX2_COLUMN_SIZE; 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 < CONFIG_KSCAN_ITE_IT8XXX2_ROW_SIZE; 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 < CONFIG_KSCAN_ITE_IT8XXX2_COLUMN_SIZE; c++) { deb_col = data->matrix_unstable_state[c]; if (!deb_col) continue; /* Debouncing for each row key occurs here */ for (int r = 0; r < CONFIG_KSCAN_ITE_IT8XXX2_ROW_SIZE; 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 != NULL)) { data->callback(dev, r, c, row_bit ? true : false); } } } return key_pressed; } /** * @brief Determine if a timer is expired. * * @param start_cycles The starting time of HW cycle. * @param timeout Pointer to the period time. * * @retval true If timer is expired; * false If timer isn't expired. */ static bool poll_expired(uint32_t start_cycles, int32_t *timeout) { uint32_t now_cycles; uint32_t microsecs_spent; now_cycles = k_cycle_get_32(); microsecs_spent = CLOCK_32K_HW_CYCLES_TO_US(now_cycles - start_cycles); /* Update the timeout value */ *timeout -= microsecs_spent; return !(*timeout >= 0); } void polling_task(const struct device *dev, void *dummy2, void *dummy3) { struct kscan_it8xxx2_data *data = dev->data; int32_t local_poll_timeout = data->poll_timeout; uint32_t current_cycles; uint32_t cycles_delta; uint32_t wait_period; ARG_UNUSED(dummy2); ARG_UNUSED(dummy3); while (true) { /* Init all KSO output low */ drive_keyboard_column(dev, KEYBOARD_COLUMN_DRIVE_ALL); /* Enable wakeup and interrupt of KSI pins */ keyboard_raw_enable_interrupt(dev, 1); /* Wait poll lock semaphore */ k_sem_take(&data->poll_lock, K_FOREVER); /* Disable wakeup and interrupt of KSI pins after fired */ keyboard_raw_enable_interrupt(dev, 0); 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(dev)) { 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_delta = current_cycles - start_period_cycles; wait_period = data->poll_period - CLOCK_32K_HW_CYCLES_TO_US(cycles_delta); /* Override wait_period in case it's less than 1000 us */ if (wait_period < MS_TO_US) { wait_period = MS_TO_US; } /* * 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_it8xxx2_init(const struct device *dev) { const struct kscan_it8xxx2_config *const config = dev->config; struct kscan_it8xxx2_data *data = dev->data; struct kscan_it8xxx2_regs *const inst = config->base; int status; /* Disable wakeup and interrupt of KSI pins before configuring */ keyboard_raw_enable_interrupt(dev, 0); #if (CONFIG_KSCAN_ITE_IT8XXX2_COLUMN_SIZE > 16) /* * For KSO[16] and KSO[17]: * 1.GPOTRC: * Bit[x] = 1b: Enable the open-drain mode of KSO pin * 2.GPCRCx: * Bit[7:6] = 00b: Select alternate KSO function * Bit[2] = 1b: Enable the internal pull-up of KSO pin * * NOTE: Set input temporarily for gpio_pin_configure(), after that * pinctrl_apply_state() set to alternate function immediately. */ gpio_pin_configure_dt(&config->kso16_gpios, GPIO_INPUT); gpio_pin_configure_dt(&config->kso17_gpios, GPIO_INPUT); #endif /* * Enable the internal pull-up and kbs mode of the KSI[7:0] pins. * Enable the internal pull-up and kbs mode of the KSO[15:0] pins. * Enable the open-drain mode of the KSO[17:0] pins. */ status = pinctrl_apply_state(config->pcfg, PINCTRL_STATE_DEFAULT); if (status < 0) { LOG_ERR("Failed to configure KSI[7:0] and KSO[17:0] pins"); return status; } /* KSO[17:0] pins output low */ inst->KBS_KSOL = 0x00; inst->KBS_KSOH1 = 0x00; #if (CONFIG_KSCAN_ITE_IT8XXX2_COLUMN_SIZE > 16) inst->KBS_KSOH2 = 0x00; #endif for (int i = 0; i < KEYBOARD_KSI_PIN_COUNT; i++) { /* Select wakeup interrupt falling-edge triggered of KSI[7:0] pins */ it8xxx2_wuc_set_polarity(config->wuc_map_list[i].wucs, config->wuc_map_list[i].mask, WUC_TYPE_EDGE_FALLING); /* W/C wakeup interrupt status of KSI[7:0] pins */ it8xxx2_wuc_clear_status(config->wuc_map_list[i].wucs, config->wuc_map_list[i].mask); /* Enable wakeup interrupt of KSI[7:0] pins */ it8xxx2_wuc_enable(config->wuc_map_list[i].wucs, config->wuc_map_list[i].mask); /* * We want to clear KSI[7:0] pins status at a time when wakeup * interrupt fire, so gather the KSI[7:0] pin mask value here. */ if (IS_ENABLED(CONFIG_LOG)) { if (config->wuc_map_list[i].wucs != config->wuc_map_list[0].wucs) { LOG_ERR("KSI%d pin isn't in the same wuc node!", i); } } data->ksi_pin_mask |= config->wuc_map_list[i].mask; } /* W/C interrupt status of KSI[7:0] pins */ ite_intc_isr_clear(config->irq); /* Kconfig.it8xxx2 time figures are transformed from msec to usec */ data->deb_time_press = (uint32_t) (CONFIG_KSCAN_ITE_IT8XXX2_DEBOUNCE_DOWN * MS_TO_US); data->deb_time_rel = (uint32_t) (CONFIG_KSCAN_ITE_IT8XXX2_DEBOUNCE_UP * MS_TO_US); data->poll_period = (uint32_t) (CONFIG_KSCAN_ITE_IT8XXX2_POLL_PERIOD * MS_TO_US); data->poll_timeout = 100 * MS_TO_US; /* Init null for callback function */ data->callback = NULL; /* Create poll lock semaphore */ k_sem_init(&data->poll_lock, 0, 1); /* Enable keyboard scan loop */ atomic_set(&data->enable_scan, 1); irq_connect_dynamic(DT_INST_IRQN(0), 0, (void (*)(const void *))keyboard_raw_interrupt, (const void *)dev, 0); /* Create keyboard scan task */ 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); return 0; } static int kscan_it8xxx2_configure(const struct device *dev, kscan_callback_t callback) { struct kscan_it8xxx2_data *data = dev->data; if (!callback) { return -EINVAL; } /* Setup callback function */ data->callback = callback; return 0; } static int kscan_it8xxx2_disable_callback(const struct device *dev) { struct kscan_it8xxx2_data *data = dev->data; /* Disable keyboard scan loop */ atomic_set(&data->enable_scan, 0); return 0; } static int kscan_it8xxx2_enable_callback(const struct device *dev) { struct kscan_it8xxx2_data *data = dev->data; /* Enable keyboard scan loop */ atomic_set(&data->enable_scan, 1); return 0; } static const struct kscan_driver_api kscan_it8xxx2_driver_api = { .config = kscan_it8xxx2_configure, .disable_callback = kscan_it8xxx2_disable_callback, .enable_callback = kscan_it8xxx2_enable_callback, }; static const struct kscan_wuc_map_cfg kscan_wuc_0[IT8XXX2_DT_INST_WUCCTRL_LEN(0)] = IT8XXX2_DT_WUC_ITEMS_LIST(0); PINCTRL_DT_INST_DEFINE(0); static const struct kscan_it8xxx2_config kscan_it8xxx2_cfg_0 = { .base = (struct kscan_it8xxx2_regs *)DT_INST_REG_ADDR_BY_IDX(0, 0), .irq = DT_INST_IRQN(0), .wuc_map_list = kscan_wuc_0, .pcfg = PINCTRL_DT_INST_DEV_CONFIG_GET(0), .kso16_gpios = GPIO_DT_SPEC_INST_GET(0, kso16_gpios), .kso17_gpios = GPIO_DT_SPEC_INST_GET(0, kso17_gpios), }; static struct kscan_it8xxx2_data kscan_it8xxx2_kbd_data; DEVICE_DT_INST_DEFINE(0, &kscan_it8xxx2_init, NULL, &kscan_it8xxx2_kbd_data, &kscan_it8xxx2_cfg_0, POST_KERNEL, CONFIG_KSCAN_INIT_PRIORITY, &kscan_it8xxx2_driver_api);