[sw,tests] Verify flash_idle signaling to pwrmgr

For test: chip_sw_flash_idle_low_power.

Checks that when low power entry is enabled and a flash operation is in progress that the
low power entry is cancelled upon receiving the WFI instruction.
The watchdog timer barks to exit the WFI and a check is done to ensure the flash operation has completed.

Signed-off-by: Dave Williams <dave.williams@ensilica.com>
diff --git a/hw/top_earlgrey/data/chip_testplan.hjson b/hw/top_earlgrey/data/chip_testplan.hjson
index 952c125..8eb295e 100644
--- a/hw/top_earlgrey/data/chip_testplan.hjson
+++ b/hw/top_earlgrey/data/chip_testplan.hjson
@@ -2355,10 +2355,10 @@
             - Initiate flash program or erase over the controller.
             - Program the pwrmgr to go into deep sleep.
             - Issue a WFI.
-            - Ensure that the low power entry is gated until the flash operation completes.
+            - Ensure that the low power entry does not happen due to the ongoing flash operation.
             '''
       milestone: V2
-      tests: []
+      tests: ["chip_sw_flash_ctrl_idle_low_power"]
     }
     {
       name: chip_sw_flash_keymgr_seeds
diff --git a/hw/top_earlgrey/dv/chip_sim_cfg.hjson b/hw/top_earlgrey/dv/chip_sim_cfg.hjson
index 1bd8785..35cd318 100644
--- a/hw/top_earlgrey/dv/chip_sim_cfg.hjson
+++ b/hw/top_earlgrey/dv/chip_sim_cfg.hjson
@@ -299,6 +299,12 @@
       en_run_modes: ["sw_test_mode_test_rom"]
     }
     {
+      name: chip_sw_flash_ctrl_idle_low_power
+      uvm_test_seq: chip_sw_base_vseq
+      sw_images: ["sw/device/tests/flash_ctrl_idle_low_power_test:1"]
+      en_run_modes: ["sw_test_mode_test_rom"]
+    }    
+    {
       name: chip_sw_lc_ctrl_otp_hw_cfg
       uvm_test_seq: chip_sw_base_vseq
       sw_images: ["sw/device/tests/lc_ctrl_otp_hw_cfg_test:1"]
diff --git a/sw/device/tests/flash_ctrl_idle_low_power_test.c b/sw/device/tests/flash_ctrl_idle_low_power_test.c
new file mode 100644
index 0000000..3020fe3
--- /dev/null
+++ b/sw/device/tests/flash_ctrl_idle_low_power_test.c
@@ -0,0 +1,168 @@
+// Copyright lowRISC contributors.
+// Licensed under the Apache License, Version 2.0, see LICENSE for details.
+// SPDX-License-Identifier: Apache-2.0
+
+#include "sw/device/lib/base/mmio.h"
+#include "sw/device/lib/dif/dif_aon_timer.h"
+#include "sw/device/lib/dif/dif_flash_ctrl.h"
+#include "sw/device/lib/dif/dif_pwrmgr.h"
+#include "sw/device/lib/dif/dif_rstmgr.h"
+#include "sw/device/lib/dif/dif_rv_plic.h"
+#include "sw/device/lib/irq.h"
+#include "sw/device/lib/runtime/ibex.h"
+#include "sw/device/lib/runtime/log.h"
+#include "sw/device/lib/testing/check.h"
+#include "sw/device/lib/testing/flash_ctrl_testutils.h"
+#include "sw/device/lib/testing/pwrmgr_testutils.h"
+#include "sw/device/lib/testing/rand_testutils.h"
+#include "sw/device/lib/testing/test_framework/ottf.h"
+
+#include "hw/top_earlgrey/sw/autogen/top_earlgrey.h"
+#include "sw/device/lib/testing/autogen/isr_testutils.h"
+
+const test_config_t kTestConfig;
+
+static dif_rv_plic_t plic;
+static dif_aon_timer_t aon;
+
+static plic_isr_ctx_t plic_ctx = {
+    .rv_plic = &plic,
+    .hart_id = kTopEarlgreyPlicTargetIbex0,
+};
+
+static aon_timer_isr_ctx_t aon_timer_ctx = {
+    .aon_timer = &aon,
+    .plic_aon_timer_start_irq_id =
+        kTopEarlgreyPlicIrqIdAonTimerAonWkupTimerExpired,
+    .is_only_irq = false,
+};
+
+static top_earlgrey_plic_peripheral_t peripheral_serviced;
+static dif_aon_timer_irq_t irq_serviced;
+
+enum {
+  kFlashDataRegion = 0,
+  kRegionBasePageIndex = 256,  // First page in bank 1 (avoids program code.)
+  kPartitionId = 0,
+  kRegionSize = 1,
+  kNumWords = 128,
+};
+
+/**
+ * External interrupt handler.
+ */
+void ottf_external_isr(void) {
+  isr_testutils_aon_timer_isr(plic_ctx, aon_timer_ctx, &peripheral_serviced,
+                              &irq_serviced);
+}
+
+static void enable_irqs(void) {
+  // Enable the AON bark interrupt.
+  CHECK_DIF_OK(dif_rv_plic_irq_set_enabled(
+      &plic, kTopEarlgreyPlicIrqIdAonTimerAonWdogTimerBark,
+      kTopEarlgreyPlicTargetIbex0, kDifToggleEnabled));
+  CHECK_DIF_OK(dif_rv_plic_irq_set_priority(
+      &plic, kTopEarlgreyPlicIrqIdAonTimerAonWdogTimerBark,
+      kDifRvPlicMaxPriority));
+  CHECK_DIF_OK(dif_rv_plic_target_set_threshold(
+      &plic, kTopEarlgreyPlicTargetIbex0, 0x0));
+  // Enable the external IRQ at Ibex.
+  irq_global_ctrl(true);
+  irq_external_ctrl(true);
+}
+
+bool test_main(void) {
+  dif_flash_ctrl_state_t flash;
+  dif_pwrmgr_t pwrmgr;
+  dif_rstmgr_t rstmgr;
+
+  CHECK_DIF_OK(dif_rv_plic_init(
+      mmio_region_from_addr(TOP_EARLGREY_RV_PLIC_BASE_ADDR), &plic));
+  CHECK_DIF_OK(dif_flash_ctrl_init_state(
+      &flash, mmio_region_from_addr(TOP_EARLGREY_FLASH_CTRL_CORE_BASE_ADDR)));
+  CHECK_DIF_OK(dif_pwrmgr_init(
+      mmio_region_from_addr(TOP_EARLGREY_PWRMGR_AON_BASE_ADDR), &pwrmgr));
+  CHECK_DIF_OK(dif_rstmgr_init(
+      mmio_region_from_addr(TOP_EARLGREY_RSTMGR_AON_BASE_ADDR), &rstmgr));
+  CHECK_DIF_OK(dif_aon_timer_init(
+      mmio_region_from_addr(TOP_EARLGREY_AON_TIMER_AON_BASE_ADDR), &aon));
+
+  enable_irqs();
+
+  CHECK_DIF_OK(dif_aon_timer_watchdog_stop(&aon));
+  CHECK_DIF_OK(dif_pwrmgr_set_request_sources(&pwrmgr, kDifPwrmgrReqTypeReset,
+                                              kDifPwrmgrResetRequestSourceTwo,
+                                              kDifToggleEnabled));
+
+  dif_rstmgr_reset_info_bitfield_t rstmgr_reset_info;
+  CHECK_DIF_OK(dif_rstmgr_reset_info_get(&rstmgr, &rstmgr_reset_info));
+
+  uint32_t address = flash_ctrl_testutils_data_region_setup(
+      &flash, kRegionBasePageIndex, kFlashDataRegion, kRegionSize);
+
+  if (rstmgr_reset_info == kDifRstmgrResetInfoPor) {
+    // Create data. Random data will be different than
+    // the 0xFFFFFFFF that is created with an erase.
+    uint32_t data[kNumWords];
+    for (int i = 0; i < kNumWords; ++i) {
+      data[i] = rand_testutils_gen32();
+    }
+
+    // Erasing the page and writing data to it followed
+    // by a read back and compare to sanity check basic operation.
+    CHECK(flash_ctrl_testutils_erase_and_write_page(
+              &flash, address, kPartitionId, data,
+              kDifFlashCtrlPartitionTypeData, kNumWords) == 0);
+    uint32_t readback_data[kNumWords];
+    CHECK(flash_ctrl_testutils_read_page(
+              &flash, address, kPartitionId, readback_data,
+              kDifFlashCtrlPartitionTypeData, kNumWords, 0) == 0);
+    CHECK_BUFFER(data, readback_data, kNumWords);
+
+    // Setting up low power hint and starting watchdog timer followed by
+    // a flash operation (page erase) and WFI. This will create a bite
+    // interrupt at some time following the start of the flash operation.
+    pwrmgr_testutils_enable_low_power(&pwrmgr, kDifPwrmgrWakeupRequestSourceTwo,
+                                      0);
+
+    CHECK_DIF_OK(dif_aon_timer_watchdog_stop(&aon));
+    CHECK_DIF_OK(dif_aon_timer_watchdog_start(&aon, 0x40, 0x80, false, false));
+
+    dif_flash_ctrl_transaction_t transaction = {
+        .byte_address = address,
+        .op = kDifFlashCtrlOpPageErase,
+        .partition_type = kDifFlashCtrlPartitionTypeData,
+        .partition_id = kPartitionId,
+        .word_count = 0x0};
+
+    CHECK_DIF_OK(dif_flash_ctrl_start(&flash, transaction));
+    wait_for_interrupt();
+
+    // Return from interrupt. Stop the watchdog. Check the reset info
+    // is still POR and the interrupt came from the correct source.
+    // Check the erase operation completed successfully.
+    CHECK_DIF_OK(dif_aon_timer_watchdog_stop(&aon));
+
+    CHECK_DIF_OK(dif_rstmgr_reset_info_get(&rstmgr, &rstmgr_reset_info));
+    CHECK(rstmgr_reset_info == kDifRstmgrResetInfoPor);
+    CHECK(peripheral_serviced == kTopEarlgreyPlicPeripheralAonTimerAon);
+    CHECK(irq_serviced == kDifAonTimerIrqWdogTimerBark);
+
+    CHECK(flash_ctrl_testutils_wait_transaction_end(&flash) == 0);
+
+    CHECK(flash_ctrl_testutils_read_page(
+              &flash, address, kPartitionId, readback_data,
+              kDifFlashCtrlPartitionTypeData, kNumWords, 0) == 0);
+    uint32_t expected_data[kNumWords];
+    memset(expected_data, 0xff, sizeof(expected_data));
+    CHECK_BUFFER(readback_data, expected_data, kNumWords);
+
+    CHECK_DIF_OK(dif_rstmgr_reset_info_clear(&rstmgr));
+  } else {
+    LOG_ERROR("Unexepected reset type detected. Reset info = %0x",
+              rstmgr_reset_info);
+    return false;
+  }
+
+  return true;
+}
diff --git a/sw/device/tests/meson.build b/sw/device/tests/meson.build
index 98d67fd..1aac6a6 100644
--- a/sw/device/tests/meson.build
+++ b/sw/device/tests/meson.build
@@ -487,6 +487,32 @@
   }
 }
 
+flash_ctrl_idle_low_power_test_lib = declare_dependency(
+  link_with: static_library(
+    'flash_ctrl_idle_low_power_test_lib',
+    sources: ['flash_ctrl_idle_low_power_test.c'],
+    dependencies: [
+      sw_lib_mmio,
+      sw_lib_dif_rstmgr,
+      sw_lib_dif_pwrmgr,
+      sw_lib_dif_aon_timer,
+      sw_lib_dif_flash_ctrl,
+      sw_lib_dif_rv_plic,
+      sw_lib_testing_flash_ctrl_testutils,
+      sw_lib_testing_isr_testutils,
+      sw_lib_testing_pwrmgr_testutils,
+      sw_lib_testing_rand_testutils,
+      sw_lib_runtime_log,
+      top_earlgrey,
+    ],
+  ),
+)
+sw_tests += {
+  'flash_ctrl_idle_low_power_test': {
+    'library': flash_ctrl_idle_low_power_test_lib,
+  }
+}
+
 # KMAC Tests
 kmac_mode_kmac_test_lib = declare_dependency(
   link_with: static_library(