1 /*
2 * Copyright (c) 2019 Intel Corporation
3 * Copyright (c) 2022 Nuvoton Technology Corporation.
4 *
5 * SPDX-License-Identifier: Apache-2.0
6 */
7
8 #define DT_DRV_COMPAT nuvoton_npcx_kbd
9
10 #include "soc_miwu.h"
11
12 #include <zephyr/drivers/clock_control.h>
13 #include <zephyr/drivers/pinctrl.h>
14 #include <zephyr/input/input_kbd_matrix.h>
15 #include <zephyr/kernel.h>
16 #include <zephyr/logging/log.h>
17 #include <zephyr/sys/util.h>
18 #include <zephyr/toolchain.h>
19
20 #include <soc.h>
21 LOG_MODULE_REGISTER(input_npcx_kbd, CONFIG_INPUT_LOG_LEVEL);
22
23 #define ROW_SIZE DT_INST_PROP(0, row_size)
24
25 /* Driver config */
26 struct npcx_kbd_config {
27 struct input_kbd_matrix_common_config common;
28 /* Keyboard scan controller base address */
29 struct kbs_reg *base;
30 /* Clock configuration */
31 struct npcx_clk_cfg clk_cfg;
32 /* Pinmux configuration */
33 const struct pinctrl_dev_config *pcfg;
34 /* Keyboard scan input (KSI) wake-up irq */
35 int irq;
36 /* Size of keyboard inputs-wui mapping array */
37 int wui_size;
38 /* Mapping table between keyboard inputs and wui */
39 struct npcx_wui wui_maps[];
40 };
41
42 struct npcx_kbd_data {
43 struct input_kbd_matrix_common_data common;
44 struct miwu_callback ksi_callback[ROW_SIZE];
45 };
46
47 INPUT_KBD_STRUCT_CHECK(struct npcx_kbd_config, struct npcx_kbd_data);
48
49 /* Keyboard scan local functions */
npcx_kbd_ksi_isr(const struct device * dev,struct npcx_wui * wui)50 static void npcx_kbd_ksi_isr(const struct device *dev, struct npcx_wui *wui)
51 {
52 ARG_UNUSED(wui);
53
54 input_kbd_matrix_poll_start(dev);
55 }
56
npcx_kbd_set_detect_mode(const struct device * dev,bool enabled)57 static void npcx_kbd_set_detect_mode(const struct device *dev, bool enabled)
58 {
59 const struct npcx_kbd_config *const config = dev->config;
60 const struct input_kbd_matrix_common_config *common = &config->common;
61
62 if (enabled) {
63 for (int i = 0; i < common->row_size; i++) {
64 npcx_miwu_irq_get_and_clear_pending(&config->wui_maps[i]);
65 }
66
67 irq_enable(config->irq);
68 } else {
69 irq_disable(config->irq);
70 }
71 }
72
npcx_kbd_drive_column(const struct device * dev,int col)73 static void npcx_kbd_drive_column(const struct device *dev, int col)
74 {
75 const struct npcx_kbd_config *config = dev->config;
76 const struct input_kbd_matrix_common_config *common = &config->common;
77 struct kbs_reg *const inst = config->base;
78 uint32_t mask;
79
80 if (col >= common->col_size) {
81 LOG_ERR("invalid column: %d", col);
82 return;
83 }
84
85 if (col == INPUT_KBD_MATRIX_COLUMN_DRIVE_NONE) {
86 /* Drive all lines to high: key detection is disabled */
87 mask = ~0;
88 } else if (col == INPUT_KBD_MATRIX_COLUMN_DRIVE_ALL) {
89 /* Drive all lines to low for detection any key press */
90 mask = ~BIT_MASK(common->col_size);
91 } else {
92 /*
93 * Drive one line to low for determining which key's state
94 * changed.
95 */
96 mask = ~BIT(col);
97 }
98
99 LOG_DBG("Drive col mask: %x", mask);
100
101 inst->KBSOUT0 = (mask & 0xFFFF);
102 inst->KBSOUT1 = ((mask >> 16) & 0x03);
103 }
104
npcx_kbd_read_row(const struct device * dev)105 static kbd_row_t npcx_kbd_read_row(const struct device *dev)
106 {
107 const struct npcx_kbd_config *config = dev->config;
108 const struct input_kbd_matrix_common_config *common = &config->common;
109 struct kbs_reg *const inst = config->base;
110 kbd_row_t val;
111
112 val = inst->KBSIN;
113
114 /* 1 means key pressed, otherwise means key released. */
115 val = ~val & BIT_MASK(common->row_size);
116
117 return val;
118 }
119
npcx_kbd_init_ksi_wui_callback(const struct device * dev,struct miwu_callback * callback,const struct npcx_wui * wui,miwu_dev_callback_handler_t handler)120 static void npcx_kbd_init_ksi_wui_callback(const struct device *dev,
121 struct miwu_callback *callback,
122 const struct npcx_wui *wui,
123 miwu_dev_callback_handler_t handler)
124 {
125 /* KSI signal which has no wake-up input source */
126 if (wui->table == NPCX_MIWU_TABLE_NONE) {
127 return;
128 }
129
130 /* Install callback function */
131 npcx_miwu_init_dev_callback(callback, wui, handler, dev);
132 npcx_miwu_manage_callback(callback, 1);
133
134 /* Configure MIWU setting and enable its interrupt */
135 npcx_miwu_interrupt_configure(wui, NPCX_MIWU_MODE_EDGE, NPCX_MIWU_TRIG_LOW);
136 npcx_miwu_irq_enable(wui);
137 }
138
npcx_kbd_init(const struct device * dev)139 static int npcx_kbd_init(const struct device *dev)
140 {
141 const struct device *clk_dev = DEVICE_DT_GET(NPCX_CLK_CTRL_NODE);
142 const struct npcx_kbd_config *const config = dev->config;
143 const struct input_kbd_matrix_common_config *common = &config->common;
144 struct npcx_kbd_data *const data = dev->data;
145 struct kbs_reg *const inst = config->base;
146 int ret;
147
148 if (!device_is_ready(clk_dev)) {
149 LOG_ERR("%s device not ready", clk_dev->name);
150 return -ENODEV;
151 }
152
153 /* Turn on KBSCAN controller device clock */
154 ret = clock_control_on(clk_dev, (clock_control_subsys_t)&config->clk_cfg);
155 if (ret < 0) {
156 LOG_ERR("Turn on KBSCAN clock fail %d", ret);
157 return -EIO;
158 }
159
160 /* Pull-up KBSIN0-7 internally */
161 inst->KBSINPU = 0xFF;
162
163 /*
164 * Keyboard Scan Control Register
165 *
166 * [6:7] - KBHDRV KBSOUTn signals output buffers are open-drain.
167 * [3] - KBSINC Auto-increment of Buffer Data register is disabled
168 * [2] - KBSIEN Interrupt of Auto-Scan is disabled
169 * [1] - KBSMODE Key detection mechanism is implemented by firmware
170 * [0] - START Write 0 to this field is not affected
171 */
172 inst->KBSCTL = 0x00;
173
174 /*
175 * Select quasi-bidirectional buffers for KSO pins. It reduces the
176 * low-to-high transition time. This feature only supports in npcx7.
177 */
178 if (IS_ENABLED(CONFIG_INPUT_NPCX_KBD_KSO_HIGH_DRIVE)) {
179 SET_FIELD(inst->KBSCTL, NPCX_KBSCTL_KBHDRV_FIELD, 0x01);
180 }
181
182 /* Drive all column lines to low for detection any key press */
183 npcx_kbd_drive_column(dev, INPUT_KBD_MATRIX_COLUMN_DRIVE_NONE);
184
185 if (common->row_size != ROW_SIZE) {
186 LOG_ERR("Unexpected ROW_SIZE: %d != %d", common->row_size, ROW_SIZE);
187 return -EINVAL;
188 }
189
190 /* Configure wake-up input and callback for keyboard input signal */
191 for (int i = 0; i < common->row_size; i++) {
192 npcx_kbd_init_ksi_wui_callback(
193 dev, &data->ksi_callback[i], &config->wui_maps[i],
194 npcx_kbd_ksi_isr);
195 }
196
197 /* Configure pin-mux for keyboard scan device */
198 ret = pinctrl_apply_state(config->pcfg, PINCTRL_STATE_DEFAULT);
199 if (ret < 0) {
200 LOG_ERR("keyboard scan pinctrl setup failed (%d)", ret);
201 return ret;
202 }
203
204 return input_kbd_matrix_common_init(dev);
205 }
206
207 PINCTRL_DT_INST_DEFINE(0);
208
209 INPUT_KBD_MATRIX_DT_INST_DEFINE(0);
210
211 static const struct input_kbd_matrix_api npcx_kbd_api = {
212 .drive_column = npcx_kbd_drive_column,
213 .read_row = npcx_kbd_read_row,
214 .set_detect_mode = npcx_kbd_set_detect_mode,
215 };
216
217 static const struct npcx_kbd_config npcx_kbd_cfg_0 = {
218 .common = INPUT_KBD_MATRIX_DT_INST_COMMON_CONFIG_INIT(0, &npcx_kbd_api),
219 .base = (struct kbs_reg *)DT_INST_REG_ADDR(0),
220 .pcfg = PINCTRL_DT_INST_DEV_CONFIG_GET(0),
221 .clk_cfg = NPCX_DT_CLK_CFG_ITEM(0),
222 .irq = DT_INST_IRQN(0),
223 .wui_size = NPCX_DT_WUI_ITEMS_LEN(0),
224 .wui_maps = NPCX_DT_WUI_ITEMS_LIST(0),
225 };
226
227 static struct npcx_kbd_data npcx_kbd_data_0;
228
229 PM_DEVICE_DT_INST_DEFINE(0, input_kbd_matrix_pm_action);
230
231 DEVICE_DT_INST_DEFINE(0, npcx_kbd_init, PM_DEVICE_DT_INST_GET(0),
232 &npcx_kbd_data_0, &npcx_kbd_cfg_0,
233 POST_KERNEL, CONFIG_INPUT_INIT_PRIORITY, NULL);
234
235 BUILD_ASSERT(!IS_ENABLED(CONFIG_PM_DEVICE_SYSTEM_MANAGED) ||
236 IS_ENABLED(CONFIG_PM_DEVICE_RUNTIME),
237 "CONFIG_PM_DEVICE_RUNTIME must be enabled when using CONFIG_PM_DEVICE_SYSTEM_MANAGED");
238
239 BUILD_ASSERT(DT_NUM_INST_STATUS_OKAY(DT_DRV_COMPAT) == 1,
240 "only one nuvoton,npcx-kbd compatible node can be supported");
241 BUILD_ASSERT(IN_RANGE(DT_INST_PROP(0, row_size), 1, 8), "invalid row-size");
242 BUILD_ASSERT(IN_RANGE(DT_INST_PROP(0, col_size), 1, 18), "invalid col-size");
243