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 <CHLin56@nuvoton.com>
diff --git a/drivers/interrupt_controller/Kconfig.npcx b/drivers/interrupt_controller/Kconfig.npcx
index 5148347..d262285 100644
--- a/drivers/interrupt_controller/Kconfig.npcx
+++ b/drivers/interrupt_controller/Kconfig.npcx
@@ -11,3 +11,10 @@
 	  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 81c5c51..726a2f9 100644
--- a/drivers/interrupt_controller/intc_miwu.c
+++ b/drivers/interrupt_controller/intc_miwu.c
@@ -82,6 +82,10 @@
 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 @@
 	}
 }
 
+#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 @@
 	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 @@
 	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 @@
 {
 	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 @@
 	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 @@
 			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 @@
 			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 @@
 	 */
 	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 a4a23e5..3bcf8d9 100644
--- a/soc/nuvoton/npcx/common/reg/reg_def.h
+++ b/soc/nuvoton/npcx/common/reg/reg_def.h
@@ -396,6 +396,8 @@
 	(*(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