ish-pm: support GPIO both edge interrupt

(cherry picked from commit e69df34314fae734b6ce898d759a1a4ad11fd2c8)

Original-Change-Id: I471a20c61fa2d3085e96e901af6afc6be72a77be
Original-Signed-off-by: Li Feng <li1.feng@intel.com>
GitOrigin-RevId: e69df34314fae734b6ce898d759a1a4ad11fd2c8
Change-Id: I11f1c5834420db30f20b085a2222d416313607b4
diff --git a/bsp_sedi/soc/intel_ish/pm/ish_pm.c b/bsp_sedi/soc/intel_ish/pm/ish_pm.c
index 48d0700..cf20ba1 100644
--- a/bsp_sedi/soc/intel_ish/pm/ish_pm.c
+++ b/bsp_sedi/soc/intel_ish/pm/ish_pm.c
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2023 Intel Corporation
+ * Copyright (c) 2024 Intel Corporation
  *
  * SPDX-License-Identifier: BSD-3-Clause
  */
@@ -81,6 +81,52 @@
 	write32(CCU_BCG_GPIO, 0);
 }
 
+/**
+ * ISH PMU does not support both-edge interrupt triggered gpio configuration.
+ * If both edges are configured, then the ISH can't stay in low power mode
+ * because it will exit immediately.
+ *
+ * To keep LPM fucntions intact and still support both edge configuration,
+ * the alternative way is:
+ * Before entering LPM, scan all gpio pins which are configured to be
+ * triggered by both-edge, and temporarily set each gpio pin to the single
+ * edge expected to be triggered next time, that is, opposite to its value.
+ * After exiting LPM, then restore the both-edge trigger configuration.
+ **/
+static uint32_t convert_both_edge_gpio_to_single_edge(void)
+{
+	uint32_t both_edge_pins = 0;
+	int i = 0;
+
+	/*
+	 * scan GPIO GFER, GRER and GIMR registers to find the both edge
+	 * interrupt trigger mode enabled pins.
+	 */
+	for (i = 0; i < 32; i++) {
+		if (read32(ISH_GPIO_GIMR) & BIT(i) && read32(ISH_GPIO_GRER) & BIT(i) &&
+		    read32(ISH_GPIO_GFER & BIT(i))) {
+			/* Record the pin so we can restore it later */
+			both_edge_pins |= BIT(i);
+
+			if (read32(ISH_GPIO_GPLR) & BIT(i)) {
+				/* pin is high, just keep falling edge mode */
+				write32(ISH_GPIO_GRER, read32(ISH_GPIO_GRER) & ~BIT(i));
+			} else {
+				/* pin is low, just keep rising edge mode */
+				write32(ISH_GPIO_GFER, read32(ISH_GPIO_GFER) & ~BIT(i));
+			}
+		}
+	}
+
+	return both_edge_pins;
+}
+
+static void restore_both_edge_gpio_config(uint32_t both_edge_pin_map)
+{
+	write32(ISH_GPIO_GRER, read32(ISH_GPIO_GRER) | both_edge_pin_map);
+	write32(ISH_GPIO_GFER, read32(ISH_GPIO_GFER) | both_edge_pin_map);
+}
+
 /* power management internal context data structure */
 struct pm_context {
 	/* aontask image valid flag */
@@ -322,6 +368,7 @@
 	uint64_t ioapic_state;
 #endif
 	uint64_t t0, t1;
+	uint32_t both_edge_gpio_pins;
 
 #ifndef CONFIG_SOC_INTEL_ISH_5_6_0
 	ioapic_state = sedi_core_get_irq_map();
@@ -333,6 +380,8 @@
 	t0 = sedi_rtc_get_us();
 	pm_ctx.aon_share->pm_state = ISH_PM_STATE_D0I1;
 
+	both_edge_gpio_pins = convert_both_edge_gpio_to_single_edge();
+
 #ifndef CONFIG_SOC_INTEL_ISH_5_6_0
 	/* enable Trunk Clock Gating (TCG) of ISH */
 	write32(CCU_TCG_EN, 1);
@@ -350,6 +399,8 @@
 	write32(CCU_BCG_MIA, read32(CCU_BCG_MIA) & (~CCU_BCG_BIT_MIA));
 #endif
 
+	restore_both_edge_gpio_config(both_edge_gpio_pins);
+
 	pm_ctx.aon_share->pm_state = ISH_PM_STATE_D0;
 	t1 = sedi_rtc_get_us();
 	log_pm_stat(&pm_stats.d0i1, t0, t1);
@@ -365,6 +416,7 @@
 {
 	uint64_t ioapic_state;
 	uint64_t t0, t1;
+	uint32_t both_edge_gpio_pins;
 
 	ioapic_state = sedi_core_get_irq_map();
 	pm_disable_irqs(ioapic_state);
@@ -374,6 +426,8 @@
 	t0 = sedi_rtc_get_us();
 	pm_ctx.aon_share->pm_state = ISH_PM_STATE_D0I2;
 
+	both_edge_gpio_pins = convert_both_edge_gpio_to_single_edge();
+
 	/* enable Trunk Clock Gating (TCG) of ISH */
 	write32(CCU_TCG_EN, 1);
 
@@ -397,6 +451,8 @@
 	pm_ctx.aon_share->pm_state = ISH_PM_STATE_D0;
 	log_pm_stat(&pm_stats.d0i2, t0, t1);
 
+	restore_both_edge_gpio_config(both_edge_gpio_pins);
+
 	if (pm_ctx.aon_share->pg_exit)
 		log_pm_stat(&pm_stats.pg, t0, t1);
 
@@ -409,6 +465,7 @@
 {
 	uint64_t ioapic_state;
 	uint64_t t0, t1;
+	uint32_t both_edge_gpio_pins;
 
 	ioapic_state = sedi_core_get_irq_map();
 	pm_disable_irqs(ioapic_state);
@@ -418,6 +475,8 @@
 	t0 = sedi_rtc_get_us();
 	pm_ctx.aon_share->pm_state = ISH_PM_STATE_D0I3;
 
+	both_edge_gpio_pins = convert_both_edge_gpio_to_single_edge();
+
 	/* enable Trunk Clock Gating (TCG) of ISH */
 	write32(CCU_TCG_EN, 1);
 
@@ -437,6 +496,8 @@
 	/* disable Trunk Clock Gating (TCG) of ISH */
 	write32(CCU_TCG_EN, 0);
 
+	restore_both_edge_gpio_config(both_edge_gpio_pins);
+
 	t1 = sedi_rtc_get_us();
 	pm_ctx.aon_share->pm_state = ISH_PM_STATE_D0;
 	log_pm_stat(&pm_stats.d0i3, t0, t1);