|  | /* | 
|  | * Copyright (c) 2020 Nuvoton Technology Corporation. | 
|  | * | 
|  | * SPDX-License-Identifier: Apache-2.0 | 
|  | */ | 
|  |  | 
|  | #define DT_DRV_COMPAT nuvoton_npcx_miwu | 
|  |  | 
|  | /** | 
|  | * @file | 
|  | * @brief Nuvoton NPCX MIWU driver | 
|  | * | 
|  | * The device Multi-Input Wake-Up Unit (MIWU) supports the Nuvoton embedded | 
|  | * controller (EC) to exit 'Sleep' or 'Deep Sleep' power state which allows chip | 
|  | * has better power consumption. Also, it provides signal conditioning such as | 
|  | * 'Level' and 'Edge' trigger type and grouping of external interrupt sources | 
|  | * of NVIC. The NPCX series has three identical MIWU modules: MIWU0, MIWU1, | 
|  | * MIWU2. Together, they support a total of over 140 internal and/or external | 
|  | * wake-up input (WUI) sources. | 
|  | * | 
|  | * This driver uses device tree files to present the relationship between | 
|  | * MIWU and the other devices in different npcx series. For npcx7 series, | 
|  | * it include: | 
|  | *  1. npcxn-miwus-wui-map.dtsi: it presents relationship between wake-up inputs | 
|  | *     (WUI) and its source device such as gpio, timer, eSPI VWs and so on. | 
|  | *  2. npcxn-miwus-int-map.dtsi: it presents relationship between MIWU group | 
|  | *     and NVIC interrupt in npcx series. Please notice it isn't 1-to-1 mapping. | 
|  | *     For example, here is the mapping between miwu0's group a & d and IRQ7: | 
|  | * | 
|  | *     map_miwu0_groups: { | 
|  | *         parent = <&miwu0>; | 
|  | *         group_ad0: group_ad0_map { | 
|  | *             irq        = <7>; | 
|  | *             group_mask = <0x09>; | 
|  | *         }; | 
|  | *         ... | 
|  | *     }; | 
|  | * | 
|  | *     It will connect IRQ 7 and intc_miwu_isr0() with the argument, group_mask, | 
|  | *     by IRQ_CONNECT() during driver initialization function. With group_mask, | 
|  | *     0x09, the driver checks the pending bits of group a and group d in ISR. | 
|  | *     Then it will execute related callback functions if they have been | 
|  | *     registered properly. | 
|  | * | 
|  | * INCLUDE FILES: soc_miwu.h | 
|  | * | 
|  | */ | 
|  |  | 
|  | #include <zephyr/device.h> | 
|  | #include <zephyr/kernel.h> | 
|  | #include <soc.h> | 
|  | #include <zephyr/sys/__assert.h> | 
|  | #include <zephyr/irq_nextlevel.h> | 
|  | #include <zephyr/drivers/gpio.h> | 
|  |  | 
|  | #include "soc_miwu.h" | 
|  | #include "soc_gpio.h" | 
|  |  | 
|  | #include <zephyr/logging/log.h> | 
|  | #include <zephyr/irq.h> | 
|  | LOG_MODULE_REGISTER(intc_miwu, LOG_LEVEL_ERR); | 
|  |  | 
|  | /* MIWU module instances */ | 
|  | #define NPCX_MIWU_DEV(inst) DEVICE_DT_INST_GET(inst), | 
|  |  | 
|  | static const struct device *const miwu_devs[] = { | 
|  | DT_INST_FOREACH_STATUS_OKAY(NPCX_MIWU_DEV) | 
|  | }; | 
|  |  | 
|  | BUILD_ASSERT(ARRAY_SIZE(miwu_devs) == NPCX_MIWU_TABLE_COUNT, | 
|  | "Size of miwu_devs array must equal to NPCX_MIWU_TABLE_COUNT"); | 
|  |  | 
|  | /* Driver config */ | 
|  | struct intc_miwu_config { | 
|  | /* miwu controller base address */ | 
|  | uintptr_t base; | 
|  | /* index of miwu controller */ | 
|  | uint8_t index; | 
|  | }; | 
|  |  | 
|  | /* Driver data */ | 
|  | struct intc_miwu_data { | 
|  | /* Callback functions list for each MIWU group */ | 
|  | sys_slist_t cb_list_grp[8]; | 
|  | #ifdef CONFIG_NPCX_MIWU_BOTH_EDGE_TRIG_WORKAROUND | 
|  | uint8_t both_edge_pins[8]; | 
|  | struct k_spinlock lock; | 
|  | #endif | 
|  | }; | 
|  |  | 
|  | BUILD_ASSERT(sizeof(struct miwu_io_params) == sizeof(gpio_port_pins_t), | 
|  | "Size of struct miwu_io_params must equal to struct gpio_port_pins_t"); | 
|  |  | 
|  | BUILD_ASSERT(offsetof(struct miwu_callback, io_cb.params) + | 
|  | sizeof(struct miwu_io_params) == sizeof(struct gpio_callback), | 
|  | "Failed in size check of miwu_callback and gpio_callback structures!"); | 
|  |  | 
|  | BUILD_ASSERT(offsetof(struct miwu_callback, io_cb.params.cb_type) == | 
|  | offsetof(struct miwu_callback, dev_cb.params.cb_type), | 
|  | "Failed in offset check of cb_type field of miwu_callback structure"); | 
|  |  | 
|  | /* MIWU local functions */ | 
|  | static void intc_miwu_dispatch_isr(sys_slist_t *cb_list, uint8_t mask) | 
|  | { | 
|  | struct miwu_callback *cb, *tmp; | 
|  |  | 
|  | SYS_SLIST_FOR_EACH_CONTAINER_SAFE(cb_list, cb, tmp, node) { | 
|  |  | 
|  | if (cb->io_cb.params.cb_type == NPCX_MIWU_CALLBACK_GPIO) { | 
|  | if (BIT(cb->io_cb.params.wui.bit) & mask) { | 
|  | __ASSERT(cb->io_cb.handler, "No GPIO callback handler!"); | 
|  | cb->io_cb.handler( | 
|  | npcx_get_gpio_dev(cb->io_cb.params.gpio_port), | 
|  | (struct gpio_callback *)cb, | 
|  | cb->io_cb.params.pin_mask); | 
|  | } | 
|  | } else { | 
|  | if (BIT(cb->dev_cb.params.wui.bit) & mask) { | 
|  | __ASSERT(cb->dev_cb.handler, "No device callback handler!"); | 
|  |  | 
|  | cb->dev_cb.handler(cb->dev_cb.params.source, | 
|  | &cb->dev_cb.params.wui); | 
|  | } | 
|  | } | 
|  | } | 
|  | } | 
|  |  | 
|  | #ifdef CONFIG_NPCX_MIWU_BOTH_EDGE_TRIG_WORKAROUND | 
|  | static void npcx_miwu_set_pseudo_both_edge(uint8_t table, uint8_t group, uint8_t bit) | 
|  | { | 
|  | const struct intc_miwu_config *config = miwu_devs[table]->config; | 
|  | const uint32_t base = config->base; | 
|  | uint8_t pmask = BIT(bit); | 
|  |  | 
|  | if (IS_BIT_SET(NPCX_WKST(base, group), bit)) { | 
|  | /* Current signal level is high, set falling edge triger. */ | 
|  | NPCX_WKEDG(base, group) |= pmask; | 
|  | } else { | 
|  | /* Current signal level is low, set rising edge triger. */ | 
|  | NPCX_WKEDG(base, group) &= ~pmask; | 
|  | } | 
|  | } | 
|  | #endif | 
|  |  | 
|  | static void intc_miwu_isr_pri(int wui_table, int wui_group) | 
|  | { | 
|  | const struct intc_miwu_config *config = miwu_devs[wui_table]->config; | 
|  | struct intc_miwu_data *data = miwu_devs[wui_table]->data; | 
|  | const uint32_t base = config->base; | 
|  | uint8_t mask = NPCX_WKPND(base, wui_group) & NPCX_WKEN(base, wui_group); | 
|  |  | 
|  | #ifdef CONFIG_NPCX_MIWU_BOTH_EDGE_TRIG_WORKAROUND | 
|  | uint8_t new_mask = mask; | 
|  |  | 
|  | while (new_mask != 0) { | 
|  | uint8_t pending_bit = find_lsb_set(new_mask) - 1; | 
|  | uint8_t pending_mask = BIT(pending_bit); | 
|  |  | 
|  | NPCX_WKPCL(base, wui_group) = pending_mask; | 
|  | if ((data->both_edge_pins[wui_group] & pending_mask) != 0) { | 
|  | npcx_miwu_set_pseudo_both_edge(wui_table, wui_group, pending_bit); | 
|  | } | 
|  |  | 
|  | new_mask &= ~pending_mask; | 
|  | }; | 
|  | #else | 
|  | /* Clear pending bits before dispatch ISR */ | 
|  | if (mask) { | 
|  | NPCX_WKPCL(base, wui_group) = mask; | 
|  | } | 
|  | #endif | 
|  |  | 
|  | /* Dispatch registered gpio isrs */ | 
|  | intc_miwu_dispatch_isr(&data->cb_list_grp[wui_group], mask); | 
|  | } | 
|  |  | 
|  | /* Platform specific MIWU functions */ | 
|  | void npcx_miwu_irq_enable(const struct npcx_wui *wui) | 
|  | { | 
|  | const struct intc_miwu_config *config = miwu_devs[wui->table]->config; | 
|  | const uint32_t base = config->base; | 
|  |  | 
|  | #ifdef CONFIG_NPCX_MIWU_BOTH_EDGE_TRIG_WORKAROUND | 
|  | k_spinlock_key_t key; | 
|  | struct intc_miwu_data *data = miwu_devs[wui->table]->data; | 
|  |  | 
|  | key = k_spin_lock(&data->lock); | 
|  | #endif | 
|  |  | 
|  | NPCX_WKEN(base, wui->group) |= BIT(wui->bit); | 
|  |  | 
|  | #ifdef CONFIG_NPCX_MIWU_BOTH_EDGE_TRIG_WORKAROUND | 
|  | if ((data->both_edge_pins[wui->group] & BIT(wui->bit)) != 0) { | 
|  | npcx_miwu_set_pseudo_both_edge(wui->table, wui->group, wui->bit); | 
|  | } | 
|  | k_spin_unlock(&data->lock, key); | 
|  | #endif | 
|  | } | 
|  |  | 
|  | void npcx_miwu_irq_disable(const struct npcx_wui *wui) | 
|  | { | 
|  | const struct intc_miwu_config *config = miwu_devs[wui->table]->config; | 
|  | const uint32_t base = config->base; | 
|  |  | 
|  | NPCX_WKEN(base, wui->group) &= ~BIT(wui->bit); | 
|  | } | 
|  |  | 
|  | void npcx_miwu_io_enable(const struct npcx_wui *wui) | 
|  | { | 
|  | const struct intc_miwu_config *config = miwu_devs[wui->table]->config; | 
|  | const uint32_t base = config->base; | 
|  |  | 
|  | NPCX_WKINEN(base, wui->group) |= BIT(wui->bit); | 
|  | } | 
|  |  | 
|  | void npcx_miwu_io_disable(const struct npcx_wui *wui) | 
|  | { | 
|  | const struct intc_miwu_config *config = miwu_devs[wui->table]->config; | 
|  | const uint32_t base = config->base; | 
|  |  | 
|  | NPCX_WKINEN(base, wui->group) &= ~BIT(wui->bit); | 
|  | } | 
|  |  | 
|  | bool npcx_miwu_irq_get_state(const struct npcx_wui *wui) | 
|  | { | 
|  | const struct intc_miwu_config *config = miwu_devs[wui->table]->config; | 
|  | const uint32_t base = config->base; | 
|  |  | 
|  | return IS_BIT_SET(NPCX_WKEN(base, wui->group), wui->bit); | 
|  | } | 
|  |  | 
|  | bool npcx_miwu_irq_get_and_clear_pending(const struct npcx_wui *wui) | 
|  | { | 
|  | const struct intc_miwu_config *config = miwu_devs[wui->table]->config; | 
|  | const uint32_t base = config->base; | 
|  | #ifdef CONFIG_NPCX_MIWU_BOTH_EDGE_TRIG_WORKAROUND | 
|  | k_spinlock_key_t key; | 
|  | struct intc_miwu_data *data = miwu_devs[wui->table]->data; | 
|  | #endif | 
|  |  | 
|  | bool pending = IS_BIT_SET(NPCX_WKPND(base, wui->group), wui->bit); | 
|  |  | 
|  | if (pending) { | 
|  | #ifdef CONFIG_NPCX_MIWU_BOTH_EDGE_TRIG_WORKAROUND | 
|  | key = k_spin_lock(&data->lock); | 
|  |  | 
|  | NPCX_WKPCL(base, wui->group) = BIT(wui->bit); | 
|  |  | 
|  | if ((data->both_edge_pins[wui->group] & BIT(wui->bit)) != 0) { | 
|  | npcx_miwu_set_pseudo_both_edge(wui->table, wui->group, wui->bit); | 
|  | } | 
|  | k_spin_unlock(&data->lock, key); | 
|  | #else | 
|  | NPCX_WKPCL(base, wui->group) = BIT(wui->bit); | 
|  | #endif | 
|  | } | 
|  |  | 
|  | return pending; | 
|  | } | 
|  |  | 
|  | int npcx_miwu_interrupt_configure(const struct npcx_wui *wui, | 
|  | enum miwu_int_mode mode, enum miwu_int_trig trig) | 
|  | { | 
|  | const struct intc_miwu_config *config = miwu_devs[wui->table]->config; | 
|  | const uint32_t base = config->base; | 
|  | uint8_t pmask = BIT(wui->bit); | 
|  | int ret = 0; | 
|  | #ifdef CONFIG_NPCX_MIWU_BOTH_EDGE_TRIG_WORKAROUND | 
|  | struct intc_miwu_data *data = miwu_devs[wui->table]->data; | 
|  | k_spinlock_key_t key; | 
|  | #endif | 
|  |  | 
|  | /* Disable interrupt of wake-up input source before configuring it */ | 
|  | npcx_miwu_irq_disable(wui); | 
|  |  | 
|  | #ifdef CONFIG_NPCX_MIWU_BOTH_EDGE_TRIG_WORKAROUND | 
|  | key = k_spin_lock(&data->lock); | 
|  | data->both_edge_pins[wui->group] &= ~BIT(wui->bit); | 
|  | #endif | 
|  | /* Handle interrupt for level trigger */ | 
|  | if (mode == NPCX_MIWU_MODE_LEVEL) { | 
|  | /* Set detection mode to level */ | 
|  | NPCX_WKMOD(base, wui->group) |= pmask; | 
|  | switch (trig) { | 
|  | /* Enable interrupting on level high */ | 
|  | case NPCX_MIWU_TRIG_HIGH: | 
|  | NPCX_WKEDG(base, wui->group) &= ~pmask; | 
|  | break; | 
|  | /* Enable interrupting on level low */ | 
|  | case NPCX_MIWU_TRIG_LOW: | 
|  | NPCX_WKEDG(base, wui->group) |= pmask; | 
|  | break; | 
|  | default: | 
|  | ret = -EINVAL; | 
|  | goto early_exit; | 
|  | } | 
|  | /* Handle interrupt for edge trigger */ | 
|  | } else { | 
|  | /* Set detection mode to edge */ | 
|  | NPCX_WKMOD(base, wui->group) &= ~pmask; | 
|  | switch (trig) { | 
|  | /* Handle interrupting on falling edge */ | 
|  | case NPCX_MIWU_TRIG_LOW: | 
|  | NPCX_WKAEDG(base, wui->group) &= ~pmask; | 
|  | NPCX_WKEDG(base, wui->group) |= pmask; | 
|  | break; | 
|  | /* Handle interrupting on rising edge */ | 
|  | case NPCX_MIWU_TRIG_HIGH: | 
|  | NPCX_WKAEDG(base, wui->group) &= ~pmask; | 
|  | NPCX_WKEDG(base, wui->group) &= ~pmask; | 
|  | break; | 
|  | /* Handle interrupting on both edges */ | 
|  | case NPCX_MIWU_TRIG_BOTH: | 
|  | #ifdef CONFIG_NPCX_MIWU_BOTH_EDGE_TRIG_WORKAROUND | 
|  | NPCX_WKAEDG(base, wui->group) &= ~pmask; | 
|  | data->both_edge_pins[wui->group] |= BIT(wui->bit); | 
|  | #else | 
|  | /* Enable any edge */ | 
|  | NPCX_WKAEDG(base, wui->group) |= pmask; | 
|  | #endif | 
|  | break; | 
|  | default: | 
|  | ret = -EINVAL; | 
|  | goto early_exit; | 
|  | } | 
|  | } | 
|  |  | 
|  | /* Enable wake-up input sources */ | 
|  | NPCX_WKINEN(base, wui->group) |= pmask; | 
|  |  | 
|  | /* | 
|  | * Clear pending bit since it might be set if WKINEN bit is | 
|  | * changed. | 
|  | */ | 
|  | NPCX_WKPCL(base, wui->group) |= pmask; | 
|  |  | 
|  | #ifdef CONFIG_NPCX_MIWU_BOTH_EDGE_TRIG_WORKAROUND | 
|  | if ((data->both_edge_pins[wui->group] & BIT(wui->bit)) != 0) { | 
|  | npcx_miwu_set_pseudo_both_edge(wui->table, wui->group, wui->bit); | 
|  | } | 
|  | #endif | 
|  |  | 
|  | early_exit: | 
|  | #ifdef CONFIG_NPCX_MIWU_BOTH_EDGE_TRIG_WORKAROUND | 
|  | k_spin_unlock(&data->lock, key); | 
|  | #endif | 
|  | return ret; | 
|  | } | 
|  |  | 
|  | void npcx_miwu_init_gpio_callback(struct miwu_callback *callback, | 
|  | const struct npcx_wui *io_wui, int port) | 
|  | { | 
|  | /* Initialize WUI and GPIO settings in unused bits field */ | 
|  | callback->io_cb.params.wui.table = io_wui->table; | 
|  | callback->io_cb.params.wui.bit   = io_wui->bit; | 
|  | callback->io_cb.params.gpio_port = port; | 
|  | callback->io_cb.params.cb_type = NPCX_MIWU_CALLBACK_GPIO; | 
|  | callback->io_cb.params.wui.group = io_wui->group; | 
|  | } | 
|  |  | 
|  | void 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) | 
|  | { | 
|  | /* Initialize WUI and input device settings */ | 
|  | callback->dev_cb.params.wui.table = dev_wui->table; | 
|  | callback->dev_cb.params.wui.group = dev_wui->group; | 
|  | callback->dev_cb.params.wui.bit   = dev_wui->bit; | 
|  | callback->dev_cb.params.source = source; | 
|  | callback->dev_cb.params.cb_type = NPCX_MIWU_CALLBACK_DEV; | 
|  | callback->dev_cb.handler = handler; | 
|  | } | 
|  |  | 
|  | int npcx_miwu_manage_callback(struct miwu_callback *cb, bool set) | 
|  | { | 
|  | struct npcx_wui *wui; | 
|  | struct intc_miwu_data *data; | 
|  | sys_slist_t *cb_list; | 
|  |  | 
|  | if (cb->io_cb.params.cb_type == NPCX_MIWU_CALLBACK_GPIO) { | 
|  | wui = &cb->io_cb.params.wui; | 
|  | } else { | 
|  | wui = &cb->dev_cb.params.wui; | 
|  | } | 
|  |  | 
|  | data = miwu_devs[wui->table]->data; | 
|  | cb_list = &data->cb_list_grp[wui->group]; | 
|  | if (!sys_slist_is_empty(cb_list)) { | 
|  | if (!sys_slist_find_and_remove(cb_list, &cb->node)) { | 
|  | if (!set) { | 
|  | return -EINVAL; | 
|  | } | 
|  | } | 
|  | } | 
|  |  | 
|  | if (set) { | 
|  | sys_slist_prepend(cb_list, &cb->node); | 
|  | } | 
|  |  | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | /* MIWU driver registration */ | 
|  | #define NPCX_MIWU_ISR_FUNC(index) _CONCAT(intc_miwu_isr, index) | 
|  | #define NPCX_MIWU_INIT_FUNC(inst) _CONCAT(intc_miwu_init, inst) | 
|  | #define NPCX_MIWU_INIT_FUNC_DECL(inst) \ | 
|  | static int intc_miwu_init##inst(const struct device *dev) | 
|  |  | 
|  | /* MIWU ISR implementation */ | 
|  | #define NPCX_MIWU_ISR_FUNC_IMPL(inst)                                          \ | 
|  | static void intc_miwu_isr##inst(void *arg)                             \ | 
|  | {                                                                      \ | 
|  | uint8_t grp_mask = (uint32_t)arg;                              \ | 
|  | int group = 0;                                                 \ | 
|  | \ | 
|  | /* Check all MIWU groups belong to the same irq */             \ | 
|  | do {                                                           \ | 
|  | if (grp_mask & 0x01)                                   \ | 
|  | intc_miwu_isr_pri(inst, group);                \ | 
|  | group++;                                               \ | 
|  | grp_mask = grp_mask >> 1;                              \ | 
|  | \ | 
|  | } while (grp_mask != 0);                                       \ | 
|  | } | 
|  |  | 
|  | /* MIWU init function implementation */ | 
|  | #define NPCX_MIWU_INIT_FUNC_IMPL(inst)                                         \ | 
|  | static int intc_miwu_init##inst(const struct device *dev)              \ | 
|  | {                                                                      \ | 
|  | int i;                                                         \ | 
|  | const struct intc_miwu_config *config = dev->config;           \ | 
|  | const uint32_t base = config->base;                            \ | 
|  | \ | 
|  | /* Clear all MIWUs' pending and enable bits of MIWU device */  \ | 
|  | for (i = 0; i < NPCX_MIWU_GROUP_COUNT; i++) {                  \ | 
|  | NPCX_WKEN(base, i) = 0;                                \ | 
|  | NPCX_WKPCL(base, i) = 0xFF;                            \ | 
|  | }                                                              \ | 
|  | \ | 
|  | /* Config IRQ and MWIU group directly */                       \ | 
|  | DT_FOREACH_CHILD(NPCX_DT_NODE_FROM_MIWU_MAP(inst),             \ | 
|  | NPCX_DT_MIWU_IRQ_CONNECT_IMPL_CHILD_FUNC)              \ | 
|  | return 0;                                                      \ | 
|  | }                                                                      \ | 
|  |  | 
|  | #define NPCX_MIWU_INIT(inst)                                                   \ | 
|  | NPCX_MIWU_INIT_FUNC_DECL(inst);                                        \ | 
|  | \ | 
|  | static const struct intc_miwu_config miwu_config_##inst = {	       \ | 
|  | .base = DT_REG_ADDR(DT_NODELABEL(miwu##inst)),                 \ | 
|  | .index = DT_PROP(DT_NODELABEL(miwu##inst), index),             \ | 
|  | };                                                                     \ | 
|  | struct intc_miwu_data miwu_data_##inst;				       \ | 
|  | \ | 
|  | DEVICE_DT_INST_DEFINE(inst,					       \ | 
|  | NPCX_MIWU_INIT_FUNC(inst),                         \ | 
|  | NULL,					       \ | 
|  | &miwu_data_##inst, &miwu_config_##inst,            \ | 
|  | PRE_KERNEL_1,                                      \ | 
|  | CONFIG_INTC_INIT_PRIORITY, NULL);                  \ | 
|  | \ | 
|  | NPCX_MIWU_ISR_FUNC_IMPL(inst)                                          \ | 
|  | \ | 
|  | NPCX_MIWU_INIT_FUNC_IMPL(inst) | 
|  |  | 
|  | DT_INST_FOREACH_STATUS_OKAY(NPCX_MIWU_INIT) |