|
| 1 | +/* |
| 2 | + * Copyright 2025, NXP |
| 3 | + * |
| 4 | + * SPDX-License-Identifier: Apache-2.0 |
| 5 | + */ |
| 6 | + |
| 7 | +#include <zephyr/kernel.h> |
| 8 | +#include <zephyr/device.h> |
| 9 | +#include <zephyr/input/input.h> |
| 10 | +#include <zephyr/logging/log.h> |
| 11 | +#include <fsl_kpp.h> |
| 12 | +#include <zephyr/drivers/clock_control.h> |
| 13 | +#include <zephyr/dt-bindings/clock/imx_ccm.h> |
| 14 | +#include <zephyr/drivers/pinctrl.h> |
| 15 | +#include <zephyr/sys/util.h> |
| 16 | + |
| 17 | +LOG_MODULE_REGISTER(kpp, CONFIG_INPUT_LOG_LEVEL); |
| 18 | + |
| 19 | +#define DT_DRV_COMPAT nxp_mcux_kpp |
| 20 | + |
| 21 | +#define INPUT_KPP_COLUMNNUM_MAX KPP_KEYPAD_COLUMNNUM_MAX |
| 22 | +#define INPUT_KPP_ROWNUM_MAX KPP_KEYPAD_ROWNUM_MAX |
| 23 | +#define INPUT_KPP_ROWNUM_MAX KPP_KEYPAD_ROWNUM_MAX |
| 24 | + |
| 25 | +struct kpp_config { |
| 26 | + KPP_Type *base; |
| 27 | + const struct device *ccm_dev; |
| 28 | + clock_control_subsys_t clk_sub_sys; |
| 29 | + const struct pinctrl_dev_config *pcfg; |
| 30 | +}; |
| 31 | + |
| 32 | +struct kpp_data { |
| 33 | + uint32_t clock_rate; |
| 34 | + struct k_work_delayable work; |
| 35 | + uint8_t read_keys_old[KPP_KEYPAD_COLUMNNUM_MAX]; |
| 36 | + uint8_t read_keys_new[KPP_KEYPAD_COLUMNNUM_MAX]; |
| 37 | + uint8_t key_pressed_number; |
| 38 | + const struct device *dev; |
| 39 | +}; |
| 40 | + |
| 41 | +static void get_source_clk_rate(const struct device *dev, uint32_t *clk_rate) |
| 42 | +{ |
| 43 | + const struct kpp_config *dev_cfg = dev->config; |
| 44 | + const struct device *ccm_dev = dev_cfg->ccm_dev; |
| 45 | + clock_control_subsys_t clk_sub_sys = dev_cfg->clk_sub_sys; |
| 46 | + |
| 47 | + if (!device_is_ready(ccm_dev)) { |
| 48 | + LOG_ERR("CCM driver is not installed"); |
| 49 | + *clk_rate = 0; |
| 50 | + return; |
| 51 | + } |
| 52 | + |
| 53 | + clock_control_get_rate(ccm_dev, clk_sub_sys, clk_rate); |
| 54 | +} |
| 55 | + |
| 56 | +static void kpp_work_handler(struct k_work *work) |
| 57 | +{ |
| 58 | + struct k_work_delayable *dwork = k_work_delayable_from_work(work); |
| 59 | + struct kpp_data *drv_data = CONTAINER_OF(dwork, struct kpp_data, work); |
| 60 | + const struct device *dev = drv_data->dev; |
| 61 | + const struct kpp_config *config = dev->config; |
| 62 | + |
| 63 | + uint8_t read_keys_new[KPP_KEYPAD_COLUMNNUM_MAX]; |
| 64 | + |
| 65 | + /* Read the key press data */ |
| 66 | + KPP_keyPressScanning(config->base, read_keys_new, drv_data->clock_rate); |
| 67 | + |
| 68 | + /* Analyze the keypad data */ |
| 69 | + for (int col = 0; col < INPUT_KPP_COLUMNNUM_MAX; col++) { |
| 70 | + if (drv_data->read_keys_old[col] == read_keys_new[col]) { |
| 71 | + continue; |
| 72 | + } |
| 73 | + for (int row = 0; row < INPUT_KPP_ROWNUM_MAX; row++) { |
| 74 | + if (((drv_data->read_keys_old[col] ^ read_keys_new[col]) |
| 75 | + & BIT(row)) == 0) { |
| 76 | + continue; |
| 77 | + } |
| 78 | + if ((read_keys_new[col] & BIT(row)) != 0) { |
| 79 | + /* Key press event */ |
| 80 | + KPP_SetSynchronizeChain(config->base, |
| 81 | + kKPP_ClearKeyDepressSyncChain); |
| 82 | + input_report_abs(dev, INPUT_ABS_X, col, false, K_FOREVER); |
| 83 | + input_report_abs(dev, INPUT_ABS_Y, row, false, K_FOREVER); |
| 84 | + input_report_key(dev, INPUT_BTN_TOUCH, 1, true, K_FOREVER); |
| 85 | + drv_data->key_pressed_number++; |
| 86 | + } else { |
| 87 | + /* Key release event */ |
| 88 | + KPP_SetSynchronizeChain(config->base, |
| 89 | + kKPP_SetKeyReleasesSyncChain); |
| 90 | + input_report_abs(dev, INPUT_ABS_X, col, false, K_FOREVER); |
| 91 | + input_report_abs(dev, INPUT_ABS_Y, row, false, K_FOREVER); |
| 92 | + input_report_key(dev, INPUT_BTN_TOUCH, 0, true, K_FOREVER); |
| 93 | + drv_data->key_pressed_number--; |
| 94 | + } |
| 95 | + drv_data->read_keys_old[col] = read_keys_new[col]; |
| 96 | + } |
| 97 | + } |
| 98 | + |
| 99 | + if (drv_data->key_pressed_number == 0U) { |
| 100 | + KPP_ClearStatusFlag(config->base, kKPP_keyDepressInterrupt | |
| 101 | + kKPP_keyReleaseInterrupt); |
| 102 | + KPP_EnableInterrupts(config->base, kKPP_keyDepressInterrupt); |
| 103 | + } else { |
| 104 | + k_work_schedule(&drv_data->work, K_MSEC(CONFIG_INPUT_KPP_PERIOD_MS)); |
| 105 | + } |
| 106 | +} |
| 107 | + |
| 108 | +static void kpp_isr(const struct device *dev) |
| 109 | +{ |
| 110 | + const struct kpp_config *config = dev->config; |
| 111 | + struct kpp_data *drv_data = dev->data; |
| 112 | + |
| 113 | + uint16_t status = KPP_GetStatusFlag(config->base); |
| 114 | + |
| 115 | + if ((status & kKPP_keyDepressInterrupt) == 0) { |
| 116 | + LOG_ERR("No key press or release detected"); |
| 117 | + return; |
| 118 | + } |
| 119 | + |
| 120 | + drv_data->key_pressed_number = 0; |
| 121 | + /* Disable interrupts. */ |
| 122 | + KPP_DisableInterrupts(config->base, kKPP_keyDepressInterrupt | |
| 123 | + kKPP_keyReleaseInterrupt); |
| 124 | + /* Clear status. */ |
| 125 | + KPP_ClearStatusFlag(config->base, kKPP_keyDepressInterrupt | |
| 126 | + kKPP_keyReleaseInterrupt); |
| 127 | + /* Key depress report */ |
| 128 | + k_work_schedule(&drv_data->work, K_MSEC(0)); |
| 129 | +} |
| 130 | + |
| 131 | +static int input_kpp_init(const struct device *dev) |
| 132 | +{ |
| 133 | + const struct kpp_config *config = dev->config; |
| 134 | + struct kpp_data *drv_data = dev->data; |
| 135 | + kpp_config_t kppConfig; |
| 136 | + |
| 137 | + if (!device_is_ready(config->ccm_dev)) { |
| 138 | + LOG_ERR("CCM driver is not installed"); |
| 139 | + return -ENODEV; |
| 140 | + } |
| 141 | + |
| 142 | + int ret = pinctrl_apply_state(config->pcfg, PINCTRL_STATE_DEFAULT); |
| 143 | + |
| 144 | + if (ret < 0) { |
| 145 | + LOG_ERR("Failed to configure pin"); |
| 146 | + return ret; |
| 147 | + } |
| 148 | + |
| 149 | + kppConfig.activeRow = 0xFF; |
| 150 | + kppConfig.activeColumn = 0xFF; |
| 151 | + kppConfig.interrupt = kKPP_keyDepressInterrupt; |
| 152 | + |
| 153 | + KPP_Init(config->base, &kppConfig); |
| 154 | + |
| 155 | + get_source_clk_rate(dev, &drv_data->clock_rate); |
| 156 | + |
| 157 | + drv_data->dev = dev; |
| 158 | + |
| 159 | + /* Read the key press data */ |
| 160 | + KPP_keyPressScanning(config->base, drv_data->read_keys_old, drv_data->clock_rate); |
| 161 | + |
| 162 | + k_work_init_delayable(&drv_data->work, kpp_work_handler); |
| 163 | + |
| 164 | + IRQ_CONNECT(DT_INST_IRQN(0), DT_INST_IRQ(0, priority), |
| 165 | + kpp_isr, DEVICE_DT_INST_GET(0), 0); |
| 166 | + return 0; |
| 167 | +} |
| 168 | + |
| 169 | +#define INPUT_KPP_INIT(n) \ |
| 170 | + static struct kpp_data kpp_data_##n; \ |
| 171 | + \ |
| 172 | + PINCTRL_DT_INST_DEFINE(n); \ |
| 173 | + \ |
| 174 | + static const struct kpp_config kpp_config_##n = { \ |
| 175 | + .base = (KPP_Type *)DT_INST_REG_ADDR(n), \ |
| 176 | + .clk_sub_sys = \ |
| 177 | + (clock_control_subsys_t)DT_INST_CLOCKS_CELL_BY_IDX(n, 0, name), \ |
| 178 | + .ccm_dev = DEVICE_DT_GET(DT_INST_CLOCKS_CTLR(n)), \ |
| 179 | + .pcfg = PINCTRL_DT_INST_DEV_CONFIG_GET(n), \ |
| 180 | + }; \ |
| 181 | + \ |
| 182 | + DEVICE_DT_INST_DEFINE(n, \ |
| 183 | + input_kpp_init, \ |
| 184 | + NULL, \ |
| 185 | + &kpp_data_##n, \ |
| 186 | + &kpp_config_##n, \ |
| 187 | + POST_KERNEL, \ |
| 188 | + CONFIG_INPUT_INIT_PRIORITY, \ |
| 189 | + NULL); |
| 190 | + |
| 191 | +DT_INST_FOREACH_STATUS_OKAY(INPUT_KPP_INIT) |
0 commit comments