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