| /* |
| * 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) |