1 /*
2 * Copyright (c) 2020 Nuvoton Technology Corporation.
3 *
4 * SPDX-License-Identifier: Apache-2.0
5 */
6
7 #define DT_DRV_COMPAT nuvoton_npcx_miwu
8
9 /**
10 * @file
11 * @brief Nuvoton NPCX MIWU driver
12 *
13 * The device Multi-Input Wake-Up Unit (MIWU) supports the Nuvoton embedded
14 * controller (EC) to exit 'Sleep' or 'Deep Sleep' power state which allows chip
15 * has better power consumption. Also, it provides signal conditioning such as
16 * 'Level' and 'Edge' trigger type and grouping of external interrupt sources
17 * of NVIC. The NPCX series has three identical MIWU modules: MIWU0, MIWU1,
18 * MIWU2. Together, they support a total of over 140 internal and/or external
19 * wake-up input (WUI) sources.
20 *
21 * This driver uses device tree files to present the relationship between
22 * MIWU and the other devices in different npcx series. For npcx7 series,
23 * it include:
24 * 1. npcxn-miwus-wui-map.dtsi: it presents relationship between wake-up inputs
25 * (WUI) and its source device such as gpio, timer, eSPI VWs and so on.
26 * 2. npcxn-miwus-int-map.dtsi: it presents relationship between MIWU group
27 * and NVIC interrupt in npcx series. Please notice it isn't 1-to-1 mapping.
28 * For example, here is the mapping between miwu0's group a & d and IRQ7:
29 *
30 * map_miwu0_groups: {
31 * parent = <&miwu0>;
32 * group_ad0: group_ad0_map {
33 * irq = <7>;
34 * group_mask = <0x09>;
35 * };
36 * ...
37 * };
38 *
39 * It will connect IRQ 7 and intc_miwu_isr0() with the argument, group_mask,
40 * by IRQ_CONNECT() during driver initialization function. With group_mask,
41 * 0x09, the driver checks the pending bits of group a and group d in ISR.
42 * Then it will execute related callback functions if they have been
43 * registered properly.
44 *
45 * INCLUDE FILES: soc_miwu.h
46 *
47 */
48
49 #include <zephyr/device.h>
50 #include <zephyr/kernel.h>
51 #include <soc.h>
52 #include <zephyr/sys/__assert.h>
53 #include <zephyr/irq_nextlevel.h>
54 #include <zephyr/drivers/gpio.h>
55
56 #include "soc_miwu.h"
57 #include "soc_gpio.h"
58
59 #include <zephyr/logging/log.h>
60 #include <zephyr/irq.h>
61 LOG_MODULE_REGISTER(intc_miwu, LOG_LEVEL_ERR);
62
63 /* MIWU module instances */
64 #define NPCX_MIWU_DEV(inst) DEVICE_DT_INST_GET(inst),
65
66 static const struct device *const miwu_devs[] = {
67 DT_INST_FOREACH_STATUS_OKAY(NPCX_MIWU_DEV)
68 };
69
70 BUILD_ASSERT(ARRAY_SIZE(miwu_devs) == NPCX_MIWU_TABLE_COUNT,
71 "Size of miwu_devs array must equal to NPCX_MIWU_TABLE_COUNT");
72
73 /* Driver config */
74 struct intc_miwu_config {
75 /* miwu controller base address */
76 uintptr_t base;
77 /* index of miwu controller */
78 uint8_t index;
79 };
80
81 /* Driver data */
82 struct intc_miwu_data {
83 /* Callback functions list for each MIWU group */
84 sys_slist_t cb_list_grp[8];
85 };
86
87 BUILD_ASSERT(sizeof(struct miwu_io_params) == sizeof(gpio_port_pins_t),
88 "Size of struct miwu_io_params must equal to struct gpio_port_pins_t");
89
90 BUILD_ASSERT(offsetof(struct miwu_callback, io_cb.params) +
91 sizeof(struct miwu_io_params) == sizeof(struct gpio_callback),
92 "Failed in size check of miwu_callback and gpio_callback structures!");
93
94 BUILD_ASSERT(offsetof(struct miwu_callback, io_cb.params.cb_type) ==
95 offsetof(struct miwu_callback, dev_cb.params.cb_type),
96 "Failed in offset check of cb_type field of miwu_callback structure");
97
98 /* MIWU local functions */
intc_miwu_dispatch_isr(sys_slist_t * cb_list,uint8_t mask)99 static void intc_miwu_dispatch_isr(sys_slist_t *cb_list, uint8_t mask)
100 {
101 struct miwu_callback *cb, *tmp;
102
103 SYS_SLIST_FOR_EACH_CONTAINER_SAFE(cb_list, cb, tmp, node) {
104
105 if (cb->io_cb.params.cb_type == NPCX_MIWU_CALLBACK_GPIO) {
106 if (BIT(cb->io_cb.params.wui.bit) & mask) {
107 __ASSERT(cb->io_cb.handler, "No GPIO callback handler!");
108 cb->io_cb.handler(
109 npcx_get_gpio_dev(cb->io_cb.params.gpio_port),
110 (struct gpio_callback *)cb,
111 cb->io_cb.params.pin_mask);
112 }
113 } else {
114 if (BIT(cb->dev_cb.params.wui.bit) & mask) {
115 __ASSERT(cb->dev_cb.handler, "No device callback handler!");
116
117 cb->dev_cb.handler(cb->dev_cb.params.source,
118 &cb->dev_cb.params.wui);
119 }
120 }
121 }
122 }
123
intc_miwu_isr_pri(int wui_table,int wui_group)124 static void intc_miwu_isr_pri(int wui_table, int wui_group)
125 {
126 const struct intc_miwu_config *config = miwu_devs[wui_table]->config;
127 struct intc_miwu_data *data = miwu_devs[wui_table]->data;
128 const uint32_t base = config->base;
129 uint8_t mask = NPCX_WKPND(base, wui_group) & NPCX_WKEN(base, wui_group);
130
131 /* Clear pending bits before dispatch ISR */
132 if (mask) {
133 NPCX_WKPCL(base, wui_group) = mask;
134 }
135
136 /* Dispatch registered gpio isrs */
137 intc_miwu_dispatch_isr(&data->cb_list_grp[wui_group], mask);
138 }
139
140 /* Platform specific MIWU functions */
npcx_miwu_irq_enable(const struct npcx_wui * wui)141 void npcx_miwu_irq_enable(const struct npcx_wui *wui)
142 {
143 const struct intc_miwu_config *config = miwu_devs[wui->table]->config;
144 const uint32_t base = config->base;
145
146 NPCX_WKEN(base, wui->group) |= BIT(wui->bit);
147 }
148
npcx_miwu_irq_disable(const struct npcx_wui * wui)149 void npcx_miwu_irq_disable(const struct npcx_wui *wui)
150 {
151 const struct intc_miwu_config *config = miwu_devs[wui->table]->config;
152 const uint32_t base = config->base;
153
154 NPCX_WKEN(base, wui->group) &= ~BIT(wui->bit);
155 }
156
npcx_miwu_io_enable(const struct npcx_wui * wui)157 void npcx_miwu_io_enable(const struct npcx_wui *wui)
158 {
159 const struct intc_miwu_config *config = miwu_devs[wui->table]->config;
160 const uint32_t base = config->base;
161
162 NPCX_WKINEN(base, wui->group) |= BIT(wui->bit);
163 }
164
npcx_miwu_io_disable(const struct npcx_wui * wui)165 void npcx_miwu_io_disable(const struct npcx_wui *wui)
166 {
167 const struct intc_miwu_config *config = miwu_devs[wui->table]->config;
168 const uint32_t base = config->base;
169
170 NPCX_WKINEN(base, wui->group) &= ~BIT(wui->bit);
171 }
172
npcx_miwu_irq_get_state(const struct npcx_wui * wui)173 bool npcx_miwu_irq_get_state(const struct npcx_wui *wui)
174 {
175 const struct intc_miwu_config *config = miwu_devs[wui->table]->config;
176 const uint32_t base = config->base;
177
178 return IS_BIT_SET(NPCX_WKEN(base, wui->group), wui->bit);
179 }
180
npcx_miwu_irq_get_and_clear_pending(const struct npcx_wui * wui)181 bool npcx_miwu_irq_get_and_clear_pending(const struct npcx_wui *wui)
182 {
183 const struct intc_miwu_config *config = miwu_devs[wui->table]->config;
184 const uint32_t base = config->base;
185 bool pending = IS_BIT_SET(NPCX_WKPND(base, wui->group), wui->bit);
186
187 if (pending) {
188 NPCX_WKPCL(base, wui->group) = BIT(wui->bit);
189 }
190
191 return pending;
192 }
193
npcx_miwu_interrupt_configure(const struct npcx_wui * wui,enum miwu_int_mode mode,enum miwu_int_trig trig)194 int npcx_miwu_interrupt_configure(const struct npcx_wui *wui,
195 enum miwu_int_mode mode, enum miwu_int_trig trig)
196 {
197 const struct intc_miwu_config *config = miwu_devs[wui->table]->config;
198 const uint32_t base = config->base;
199 uint8_t pmask = BIT(wui->bit);
200
201 /* Disable interrupt of wake-up input source before configuring it */
202 npcx_miwu_irq_disable(wui);
203
204 /* Handle interrupt for level trigger */
205 if (mode == NPCX_MIWU_MODE_LEVEL) {
206 /* Set detection mode to level */
207 NPCX_WKMOD(base, wui->group) |= pmask;
208 switch (trig) {
209 /* Enable interrupting on level high */
210 case NPCX_MIWU_TRIG_HIGH:
211 NPCX_WKEDG(base, wui->group) &= ~pmask;
212 break;
213 /* Enable interrupting on level low */
214 case NPCX_MIWU_TRIG_LOW:
215 NPCX_WKEDG(base, wui->group) |= pmask;
216 break;
217 default:
218 return -EINVAL;
219 }
220 /* Handle interrupt for edge trigger */
221 } else {
222 /* Set detection mode to edge */
223 NPCX_WKMOD(base, wui->group) &= ~pmask;
224 switch (trig) {
225 /* Handle interrupting on falling edge */
226 case NPCX_MIWU_TRIG_LOW:
227 NPCX_WKAEDG(base, wui->group) &= ~pmask;
228 NPCX_WKEDG(base, wui->group) |= pmask;
229 break;
230 /* Handle interrupting on rising edge */
231 case NPCX_MIWU_TRIG_HIGH:
232 NPCX_WKAEDG(base, wui->group) &= ~pmask;
233 NPCX_WKEDG(base, wui->group) &= ~pmask;
234 break;
235 /* Handle interrupting on both edges */
236 case NPCX_MIWU_TRIG_BOTH:
237 /* Enable any edge */
238 NPCX_WKAEDG(base, wui->group) |= pmask;
239 break;
240 default:
241 return -EINVAL;
242 }
243 }
244
245 /* Enable wake-up input sources */
246 NPCX_WKINEN(base, wui->group) |= pmask;
247
248 /*
249 * Clear pending bit since it might be set if WKINEN bit is
250 * changed.
251 */
252 NPCX_WKPCL(base, wui->group) |= pmask;
253
254 return 0;
255 }
256
npcx_miwu_init_gpio_callback(struct miwu_callback * callback,const struct npcx_wui * io_wui,int port)257 void npcx_miwu_init_gpio_callback(struct miwu_callback *callback,
258 const struct npcx_wui *io_wui, int port)
259 {
260 /* Initialize WUI and GPIO settings in unused bits field */
261 callback->io_cb.params.wui.table = io_wui->table;
262 callback->io_cb.params.wui.bit = io_wui->bit;
263 callback->io_cb.params.gpio_port = port;
264 callback->io_cb.params.cb_type = NPCX_MIWU_CALLBACK_GPIO;
265 callback->io_cb.params.wui.group = io_wui->group;
266 }
267
npcx_miwu_init_dev_callback(struct miwu_callback * callback,const struct npcx_wui * dev_wui,miwu_dev_callback_handler_t handler,const struct device * source)268 void npcx_miwu_init_dev_callback(struct miwu_callback *callback,
269 const struct npcx_wui *dev_wui,
270 miwu_dev_callback_handler_t handler,
271 const struct device *source)
272 {
273 /* Initialize WUI and input device settings */
274 callback->dev_cb.params.wui.table = dev_wui->table;
275 callback->dev_cb.params.wui.group = dev_wui->group;
276 callback->dev_cb.params.wui.bit = dev_wui->bit;
277 callback->dev_cb.params.source = source;
278 callback->dev_cb.params.cb_type = NPCX_MIWU_CALLBACK_DEV;
279 callback->dev_cb.handler = handler;
280 }
281
npcx_miwu_manage_callback(struct miwu_callback * cb,bool set)282 int npcx_miwu_manage_callback(struct miwu_callback *cb, bool set)
283 {
284 struct npcx_wui *wui;
285 struct intc_miwu_data *data;
286 sys_slist_t *cb_list;
287
288 if (cb->io_cb.params.cb_type == NPCX_MIWU_CALLBACK_GPIO) {
289 wui = &cb->io_cb.params.wui;
290 } else {
291 wui = &cb->dev_cb.params.wui;
292 }
293
294 data = miwu_devs[wui->table]->data;
295 cb_list = &data->cb_list_grp[wui->group];
296 if (!sys_slist_is_empty(cb_list)) {
297 if (!sys_slist_find_and_remove(cb_list, &cb->node)) {
298 if (!set) {
299 return -EINVAL;
300 }
301 }
302 }
303
304 if (set) {
305 sys_slist_prepend(cb_list, &cb->node);
306 }
307
308 return 0;
309 }
310
311 /* MIWU driver registration */
312 #define NPCX_MIWU_ISR_FUNC(index) _CONCAT(intc_miwu_isr, index)
313 #define NPCX_MIWU_INIT_FUNC(inst) _CONCAT(intc_miwu_init, inst)
314 #define NPCX_MIWU_INIT_FUNC_DECL(inst) \
315 static int intc_miwu_init##inst(const struct device *dev)
316
317 /* MIWU ISR implementation */
318 #define NPCX_MIWU_ISR_FUNC_IMPL(inst) \
319 static void intc_miwu_isr##inst(void *arg) \
320 { \
321 uint8_t grp_mask = (uint32_t)arg; \
322 int group = 0; \
323 \
324 /* Check all MIWU groups belong to the same irq */ \
325 do { \
326 if (grp_mask & 0x01) \
327 intc_miwu_isr_pri(inst, group); \
328 group++; \
329 grp_mask = grp_mask >> 1; \
330 \
331 } while (grp_mask != 0); \
332 }
333
334 /* MIWU init function implementation */
335 #define NPCX_MIWU_INIT_FUNC_IMPL(inst) \
336 static int intc_miwu_init##inst(const struct device *dev) \
337 { \
338 int i; \
339 const struct intc_miwu_config *config = dev->config; \
340 const uint32_t base = config->base; \
341 \
342 /* Clear all MIWUs' pending and enable bits of MIWU device */ \
343 for (i = 0; i < NPCX_MIWU_GROUP_COUNT; i++) { \
344 NPCX_WKEN(base, i) = 0; \
345 NPCX_WKPCL(base, i) = 0xFF; \
346 } \
347 \
348 /* Config IRQ and MWIU group directly */ \
349 DT_FOREACH_CHILD(NPCX_DT_NODE_FROM_MIWU_MAP(inst), \
350 NPCX_DT_MIWU_IRQ_CONNECT_IMPL_CHILD_FUNC) \
351 return 0; \
352 } \
353
354 #define NPCX_MIWU_INIT(inst) \
355 NPCX_MIWU_INIT_FUNC_DECL(inst); \
356 \
357 static const struct intc_miwu_config miwu_config_##inst = { \
358 .base = DT_REG_ADDR(DT_NODELABEL(miwu##inst)), \
359 .index = DT_PROP(DT_NODELABEL(miwu##inst), index), \
360 }; \
361 struct intc_miwu_data miwu_data_##inst; \
362 \
363 DEVICE_DT_INST_DEFINE(inst, \
364 NPCX_MIWU_INIT_FUNC(inst), \
365 NULL, \
366 &miwu_data_##inst, &miwu_config_##inst, \
367 PRE_KERNEL_1, \
368 CONFIG_INTC_INIT_PRIORITY, NULL); \
369 \
370 NPCX_MIWU_ISR_FUNC_IMPL(inst) \
371 \
372 NPCX_MIWU_INIT_FUNC_IMPL(inst)
373
374 DT_INST_FOREACH_STATUS_OKAY(NPCX_MIWU_INIT)
375