From 0bf4ec6d7b488e01c0497224ec364596a751d39f Mon Sep 17 00:00:00 2001 From: Jun Lin Date: Tue, 30 Jan 2024 16:33:32 +0800 Subject: [PATCH] drivers: gpio: npcx: workaround both trigger issue for npcx9m7fb Apply the workaround for the issue "MIWU Any Edge Trigger Condition" in the NPCX99nFB_Errata. Signed-off-by: Jun Lin --- drivers/interrupt_controller/Kconfig.npcx | 7 ++ drivers/interrupt_controller/intc_miwu.c | 99 ++++++++++++++++++++++- soc/nuvoton/npcx/common/reg/reg_def.h | 2 + 3 files changed, 105 insertions(+), 3 deletions(-) diff --git a/drivers/interrupt_controller/Kconfig.npcx b/drivers/interrupt_controller/Kconfig.npcx index 514834754e7..d262285bc77 100644 --- a/drivers/interrupt_controller/Kconfig.npcx +++ b/drivers/interrupt_controller/Kconfig.npcx @@ -11,3 +11,10 @@ config NPCX_MIWU This option enables the Multi-Input Wake-Up Unit (MIWU) driver for NPCX family of processors. This is required for GPIO, RTC, LPC/eSPI interrupt support. + +config NPCX_MIWU_BOTH_EDGE_TRIG_WORKAROUND + bool + default y if SOC_NPCX9M7FB + help + Workaround the issue "MIWU Any Edge Trigger Condition" + in the npcx9m7fb SoC errata. diff --git a/drivers/interrupt_controller/intc_miwu.c b/drivers/interrupt_controller/intc_miwu.c index 81c5c515b17..726a2f93345 100644 --- a/drivers/interrupt_controller/intc_miwu.c +++ b/drivers/interrupt_controller/intc_miwu.c @@ -82,6 +82,10 @@ struct intc_miwu_config { 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), @@ -121,6 +125,23 @@ static void intc_miwu_dispatch_isr(sys_slist_t *cb_list, uint8_t mask) } } +#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; @@ -128,10 +149,26 @@ static void intc_miwu_isr_pri(int wui_table, int wui_group) 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); @@ -143,7 +180,21 @@ 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) @@ -182,10 +233,26 @@ 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; @@ -197,10 +264,19 @@ int npcx_miwu_interrupt_configure(const struct npcx_wui *wui, 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 */ @@ -215,7 +291,8 @@ int npcx_miwu_interrupt_configure(const struct npcx_wui *wui, NPCX_WKEDG(base, wui->group) |= pmask; break; default: - return -EINVAL; + ret = -EINVAL; + goto early_exit; } /* Handle interrupt for edge trigger */ } else { @@ -234,11 +311,17 @@ int npcx_miwu_interrupt_configure(const struct npcx_wui *wui, 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: - return -EINVAL; + ret = -EINVAL; + goto early_exit; } } @@ -251,7 +334,17 @@ int npcx_miwu_interrupt_configure(const struct npcx_wui *wui, */ NPCX_WKPCL(base, wui->group) |= pmask; - return 0; +#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, diff --git a/soc/nuvoton/npcx/common/reg/reg_def.h b/soc/nuvoton/npcx/common/reg/reg_def.h index a4a23e543e6..3bcf8d9a0e3 100644 --- a/soc/nuvoton/npcx/common/reg/reg_def.h +++ b/soc/nuvoton/npcx/common/reg/reg_def.h @@ -396,6 +396,8 @@ struct uart_reg { (*(volatile uint8_t *)(base + NPCX_WKINEN_OFFSET(group))) #define NPCX_WKMOD(base, group) \ (*(volatile uint8_t *)(base + NPCX_WKMOD_OFFSET(group))) +#define NPCX_WKST(base, group) \ + (*(volatile uint8_t *)(base + NPCX_WKST_OFFSET(group))) /* * General-Purpose I/O (GPIO) device registers