[sw/lib] Merge flash_ctrl from boot_rom into lib

This is the first commit in a series of changes to move the boot_rom
device interface function modules into the lib folder.

* Updated sw/lib/flash_ctrl.c to follow API naming conventions from
  boot_rom.
* Added Doxygen doc strings.
* Updated enums to use DEFINE_FORMAT.
* Renamed sw/tests/hello_flash/hello_flash.c as
  sw/tests/flash_ctrl/flash_test.c.
* Updated flash_test.c to use new flash_ctrl API.
diff --git a/sw/lib/common.h b/sw/lib/common.h
index 7908d6e..f7f2840 100644
--- a/sw/lib/common.h
+++ b/sw/lib/common.h
@@ -30,6 +30,8 @@
 #define SETBIT(val, bit) (val | 1 << bit)
 #define CLRBIT(val, bit) (val & ~(1 << bit))
 
+#define ARRAYSIZE(x) (sizeof(x) / sizeof(x[0]))
+
 /* Hamming weight */
 #define BITLENGTH_1(X) ((X) - (((X) >> 1) & 0x55555555))
 #define BITLENGTH_2(X) (((X)&0x33333333) + (((X) >> 2) & 0x33333333))
diff --git a/sw/lib/flash_ctrl.c b/sw/lib/flash_ctrl.c
index 0add8c4..163c772 100644
--- a/sw/lib/flash_ctrl.c
+++ b/sw/lib/flash_ctrl.c
@@ -1,164 +1,153 @@
 // Copyright lowRISC contributors.
 // Licensed under the Apache License, Version 2.0, see LICENSE for details.
 // SPDX-License-Identifier: Apache-2.0
-
 #include "flash_ctrl.h"
 
-#include <stdint.h>
-
 #include "common.h"
+#include "flash_ctrl_regs.h"
 
-static uint32_t get_clr_err(void) {
-  uint32_t err_status;
+#define FLASH_CTRL0_BASE_ADDR 0x40030000
 
-  // extract error status
-  err_status =
-      REG32(FLASH_CTRL_INTR_STATE(0)) & (0x1 << FLASH_CTRL_INTR_STATE_OP_ERROR);
+typedef enum flash_op {
+  FLASH_READ = 0,
+  FLASH_PROG = 1,
+  FLASH_ERASE = 2
+} flash_op_t;
 
-  // clear error if set
-  REG32(FLASH_CTRL_INTR_STATE(0)) = err_status;
+typedef enum erase_type {
+  FLASH_PAGE_ERASE = 0,
+  FLASH_BANK_ERASE = 1
+} erase_type_t;
 
-  // return status
-  return err_status;
+/* Wait for flash command to complete and set ACK in controller */
+static inline void wait_done_and_ack(void) {
+  while ((REG32(FLASH_CTRL_OP_STATUS(0)) & (1 << FLASH_CTRL_OP_STATUS_DONE)) ==
+         0) {
+  }
+  REG32(FLASH_CTRL_OP_STATUS(0)) = 0;
 }
 
-// flash initialization done
-void wait_flash_init(void) {
+void flash_init_block(void) {
   while ((REG32(FLASH_CTRL_STATUS(0)) & (1 << FLASH_CTRL_STATUS_INIT_WIP)) >
          0) {
   }
 }
 
-// wait for flash done and ack
-void wait_done_and_ack(void) {
-  while ((REG32(FLASH_CTRL_OP_STATUS(0)) & (1 << FLASH_CTRL_OP_STATUS_DONE)) ==
-         0)
-    ;
-
-  REG32(FLASH_CTRL_OP_STATUS(0)) = 0;
+/* Return status error and clear internal status register */
+static int get_clr_err(void) {
+  uint32_t err_status =
+      REG32(FLASH_CTRL_INTR_STATE(0)) & (0x1 << FLASH_CTRL_INTR_STATE_OP_ERROR);
+  REG32(FLASH_CTRL_INTR_STATE(0)) = err_status;
+  return err_status;
 }
 
-// setup flash prog
-void setup_flash_prog(uint32_t addr, uint32_t num) {
-  uint32_t val;
-
-  val = FlashProg << FLASH_CTRL_CONTROL_OP_OFFSET |
-        (num - 1) << FLASH_CTRL_CONTROL_NUM_OFFSET |
-        0x1 << FLASH_CTRL_CONTROL_START;
-
-  REG32(FLASH_CTRL_ADDR(0)) = addr;
-
-  REG32(FLASH_CTRL_CONTROL(0)) = val;
-}
-
-// program data
-uint32_t prog_flash(uint32_t addr, uint32_t num, uint32_t *data) {
-  uint32_t i = 0;
-
-  // setup flash programming
-  setup_flash_prog(addr, num);
-
-  // beginning filling up the fifo
-  for (i = 0; i < num; i++) {
-    REG32(FLASH_CTRL_PROG_FIFO(0)) = *data;
-    data++;
+int flash_check_empty(void) {
+  uint32_t mask = -1u;
+  uint32_t *p = (uint32_t *)FLASH_MEM_BASE_ADDR;
+  // TODO: Update range to cover entire flash. Limited now to one bank while
+  // we debu initialization.
+  for (; p < (uint32_t *)(FLASH_MEM_BASE_ADDR + FLASH_BANK_SZ);) {
+    mask &= *p++;
+    mask &= *p++;
+    mask &= *p++;
+    mask &= *p++;
+    mask &= *p++;
+    mask &= *p++;
+    mask &= *p++;
+    mask &= *p++;
+    if (mask != -1u) {
+      return 0;
+    }
   }
+  return 1;
+}
 
-  // wait for operation finish
+int flash_bank_erase(bank_index_t idx) {
+  REG32(FLASH_CTRL_MP_BANK_CFG(0)) =
+      0x1 << ((idx == FLASH_BANK_0) ? FLASH_CTRL_MP_BANK_CFG_ERASE_EN0
+                                    : FLASH_CTRL_MP_BANK_CFG_ERASE_EN1);
+
+  // TODO: Add timeout conditions and add error codes.
+  REG32(FLASH_CTRL_ADDR(0)) = (idx == FLASH_BANK_0)
+                                  ? FLASH_MEM_BASE_ADDR
+                                  : (FLASH_MEM_BASE_ADDR + FLASH_BANK_SZ);
+  REG32(FLASH_CTRL_CONTROL(0)) =
+      (FLASH_ERASE << FLASH_CTRL_CONTROL_OP_OFFSET |
+       FLASH_BANK_ERASE << FLASH_CTRL_CONTROL_ERASE_SEL |
+       0x1 << FLASH_CTRL_CONTROL_START);
   wait_done_and_ack();
 
-  // return error status
+  REG32(FLASH_CTRL_MP_BANK_CFG(0)) =
+      0x0 << ((idx == FLASH_BANK_0) ? FLASH_CTRL_MP_BANK_CFG_ERASE_EN0
+                                    : FLASH_CTRL_MP_BANK_CFG_ERASE_EN1);
   return get_clr_err();
 }
 
-// read data
-uint32_t read_flash(uint32_t addr, uint32_t num, uint32_t *data) {
-  uint32_t val;
-  uint32_t i = 0;
-
-  // kick off flash operation
-  val = FlashRead << FLASH_CTRL_CONTROL_OP_OFFSET |
-        (num - 1) << FLASH_CTRL_CONTROL_NUM_OFFSET |
-        0x1 << FLASH_CTRL_CONTROL_START;
-
+int flash_page_erase(uint32_t addr) {
   REG32(FLASH_CTRL_ADDR(0)) = addr;
+  REG32(FLASH_CTRL_CONTROL(0)) = FLASH_ERASE << FLASH_CTRL_CONTROL_OP_OFFSET |
+                                 FLASH_PAGE_ERASE
+                                     << FLASH_CTRL_CONTROL_ERASE_SEL |
+                                 0x1 << FLASH_CTRL_CONTROL_START;
+  wait_done_and_ack();
+  return get_clr_err();
+}
 
-  REG32(FLASH_CTRL_CONTROL(0)) = val;
+static int flash_write_internal(uint32_t addr, const uint32_t *data,
+                                uint32_t size) {
+  // TODO: Do we need to select bank as part of the write?
+  // TODO: Update with address alignment requirements.
+  REG32(FLASH_CTRL_ADDR(0)) = addr;
+  REG32(FLASH_CTRL_CONTROL(0)) = (FLASH_PROG << FLASH_CTRL_CONTROL_OP_OFFSET |
+                                  (size - 1) << FLASH_CTRL_CONTROL_NUM_OFFSET |
+                                  0x1 << FLASH_CTRL_CONTROL_START);
+  for (int i = 0; i < size; ++i) {
+    REG32(FLASH_CTRL_PROG_FIFO(0)) = data[i];
+  }
+  wait_done_and_ack();
+  return get_clr_err();
+}
 
-  while (i < num) {
-    // if not empty, read a word
+int flash_write(uint32_t addr, const uint32_t *data, uint32_t size) {
+  // TODO: Breakdown into FIFO chunks if needed.
+  return flash_write_internal(addr, data, size);
+}
+
+int flash_read(uint32_t addr, uint32_t size, uint32_t *data) {
+  REG32(FLASH_CTRL_ADDR(0)) = addr;
+  REG32(FLASH_CTRL_CONTROL(0)) = FLASH_READ << FLASH_CTRL_CONTROL_OP_OFFSET |
+                                 (size - 1) << FLASH_CTRL_CONTROL_NUM_OFFSET |
+                                 0x1 << FLASH_CTRL_CONTROL_START;
+  for (uint32_t i = 0; i < size;) {
     if (((REG32(FLASH_CTRL_STATUS(0)) >> FLASH_CTRL_STATUS_RD_EMPTY) & 0x1) ==
         0) {
       *data++ = REG32(FLASH_CTRL_RD_FIFO(0));
       i++;
     }
   }
-
-  // wait for operation finish
   wait_done_and_ack();
-
-  // return error status
   return get_clr_err();
 }
 
-// page erase flash
-// wrap down to closest down to page boundary
-uint32_t page_erase(uint32_t addr) {
-  uint32_t val;
-  uint32_t data[ERASE_CHECK_WORDS];
-  uint32_t verify_rounds;
-  uint32_t error;
-
-  error = 0;
-  verify_rounds = WORDS_PER_PAGE / ERASE_CHECK_WORDS;
-
-  // kick off flash operation
-  val = FlashErase << FLASH_CTRL_CONTROL_OP_OFFSET |
-        PageErase << FLASH_CTRL_CONTROL_ERASE_SEL |
-        0x1 << FLASH_CTRL_CONTROL_START;
-
-  REG32(FLASH_CTRL_ADDR(0)) = addr;
-
-  REG32(FLASH_CTRL_CONTROL(0)) = val;
-
-  // wait for operation finish
-  wait_done_and_ack();
-
-  error += get_clr_err();
-
-  // verify erase
-  for (uint32_t i = 0; i < verify_rounds; i++) {
-    error += read_flash(addr + i * ERASE_CHECK_WORDS * BYTES_PER_WORD,
-                        ERASE_CHECK_WORDS, data);
-
-    for (uint32_t j = 0; j < ERASE_CHECK_WORDS; j++) {
-      if (data[i] != 0xFFFFFFFF) {
-        REG32(FLASH_CTRL_SCRATCH(0)) = data[i];
-
-        // re-init array
-        data[i] = 0;
-        error++;
-      }
-    }
-  }
-
-  // return error status
-  return error;
-}
-
-void flash_default_region(uint32_t rd_en, uint32_t prog_en, uint32_t erase_en) {
+void flash_default_region_access(bool rd_en, bool prog_en, bool erase_en) {
   REG32(FLASH_CTRL_DEFAULT_REGION(0)) =
       rd_en << FLASH_CTRL_DEFAULT_REGION_RD_EN |
       prog_en << FLASH_CTRL_DEFAULT_REGION_PROG_EN |
       erase_en << FLASH_CTRL_DEFAULT_REGION_ERASE_EN;
 }
 
-void flash_cfg_region(mp_region_t region_cfg) {
-  REG32(FLASH_CTRL_MP_REGION_CFG0(0) + region_cfg.num * 4) =
-      region_cfg.base << FLASH_CTRL_MP_REGION_CFG0_BASE0_OFFSET |
-      region_cfg.size << FLASH_CTRL_MP_REGION_CFG0_SIZE0_OFFSET |
-      region_cfg.rd_en << FLASH_CTRL_MP_REGION_CFG0_RD_EN0 |
-      region_cfg.prog_en << FLASH_CTRL_MP_REGION_CFG0_PROG_EN0 |
-      region_cfg.erase_en << FLASH_CTRL_MP_REGION_CFG0_ERASE_EN0 |
+void flash_cfg_region(const mp_region_t *region_cfg) {
+  REG32(FLASH_CTRL_MP_REGION_CFG0(0) + region_cfg->num * 4) =
+      region_cfg->base << FLASH_CTRL_MP_REGION_CFG0_BASE0_OFFSET |
+      region_cfg->size << FLASH_CTRL_MP_REGION_CFG0_SIZE0_OFFSET |
+      region_cfg->rd_en << FLASH_CTRL_MP_REGION_CFG0_RD_EN0 |
+      region_cfg->prog_en << FLASH_CTRL_MP_REGION_CFG0_PROG_EN0 |
+      region_cfg->erase_en << FLASH_CTRL_MP_REGION_CFG0_ERASE_EN0 |
       0x1 << FLASH_CTRL_MP_REGION_CFG0_EN0;
 }
+
+void flash_write_scratch_reg(uint32_t value) {
+  REG32(FLASH_CTRL_SCRATCH(0)) = value;
+}
+
+uint32_t flash_read_scratch_reg(void) { return REG32(FLASH_CTRL_SCRATCH(0)); }
diff --git a/sw/lib/flash_ctrl.h b/sw/lib/flash_ctrl.h
index 21e90a1..43eb18f 100644
--- a/sw/lib/flash_ctrl.h
+++ b/sw/lib/flash_ctrl.h
@@ -2,42 +2,94 @@
 // Licensed under the Apache License, Version 2.0, see LICENSE for details.
 // SPDX-License-Identifier: Apache-2.0
 
-#ifndef _FLASH_H_
-#define _FLASH_H_
+#ifndef _F_FLASH_CTRL_H__
+#define _F_FLASH_CTRL_H__
 
+#include <stdbool.h>
 #include <stdint.h>
 
-#include "flash_ctrl_regs.h"
+/**
+ * Flash bank IDs
+ */
+typedef enum bank_index { FLASH_BANK_0 = 0, FLASH_BANK_1 = 1 } bank_index_t;
 
-#define FLASH_CTRL0_BASE_ADDR 0x40030000
-#define WORDS_PER_PAGE 256
-#define ERASE_CHECK_WORDS 16
-#define BYTES_PER_WORD 4
-
-typedef enum flash_op {
-  FlashRead = 0,
-  FlashProg = 1,
-  FlashErase = 2
-} flash_op_t;
-
-typedef enum erase_type { PageErase = 0, BankErase = 1 } erase_type_t;
-
+/**
+ * Memory protection configuration options.
+ */
 typedef struct mp_region {
-  uint32_t num;  // which region to program
+  /** Which region to program. */
+  uint32_t num;
+  /** Region offset. */
   uint32_t base;
+  /** Region config size. */
   uint32_t size;
+  /** Read enable flag. */
   uint32_t rd_en;
+  /** Program enable flag. */
   uint32_t prog_en;
+  /** Erase enable flag. */
   uint32_t erase_en;
 } mp_region_t;
 
-void wait_flash_init(void);
-void wait_done_and_ack(void);
-void setup_flash_prog(uint32_t addr, uint32_t num);
-uint32_t prog_flash(uint32_t addr, uint32_t num, uint32_t *data);
-uint32_t read_flash(uint32_t addr, uint32_t num, uint32_t *data);
-uint32_t page_erase(uint32_t addr);
-void flash_default_region(uint32_t rd_en, uint32_t prog_en, uint32_t erase_en);
-void flash_cfg_region(mp_region_t region_cfg);
+/**
+ * Block until flash is initialized.
+ */
+void flash_init_block(void);
 
-#endif
+/**
+ * Returns 1 if flash is empty, otherwise 0.
+ */
+int flash_check_empty(void);
+
+/*
+ * Erase flash bank |bank_idx|. Blocks until erase is complete.
+ *
+ * @param idx Flash bank index.
+ * @return Non zero on failure.
+ */
+int flash_bank_erase(bank_index_t idx);
+int flash_page_erase(uint32_t addr);
+
+/**
+ * Write |data| at |addr| offset with |size| in 4B words
+ *
+ * @param addr Flash address 32bit aligned.
+ * @param data Data to write.
+ * @param size Number of 4B words to write from |data| buffer.
+ * @return Non zero on failure.
+ */
+int flash_write(uint32_t addr, const uint32_t *data, uint32_t size);
+
+/**
+ * Read |size| 4B words and write result to |data|.
+ *
+ * @param addr Read start address.
+ * @param size Number of 4B words to read.
+ * @param data Output buffer.
+ * @return Non zero on failure.
+ */
+int flash_read(uint32_t addr, uint32_t size, uint32_t *data);
+
+/**
+ * Set flash controller default permissions.
+ *
+ * @param rd_end Read enable.
+ * @param prog_en Write enable.
+ * @param erase_en Erase enable.
+ */
+void flash_default_region_access(bool rd_en, bool prog_en, bool erase_en);
+
+/**
+ * Configure memory protection region.
+ *
+ * @param region_cfg Region configuration.
+ */
+void flash_cfg_region(const mp_region_t *region_cfg);
+
+/** Write value to flash scratch register */
+void flash_write_scratch_reg(uint32_t value);
+
+/** Read scratch register */
+uint32_t flash_read_scratch_reg(void);
+
+#endif  // _F_FLASH_CTRL_H__
diff --git a/sw/tests/hello_flash/Makefile b/sw/tests/flash_ctrl/Makefile
similarity index 87%
rename from sw/tests/hello_flash/Makefile
rename to sw/tests/flash_ctrl/Makefile
index 4f4c6c1..6f595dd 100644
--- a/sw/tests/hello_flash/Makefile
+++ b/sw/tests/flash_ctrl/Makefile
@@ -4,8 +4,8 @@
 #
 # Generate a baremetal application for the microcontroller
 
-NAME         = hello_flash
-SRCS         = hello_flash.c
+NAME         = flash_test
+SRCS         = flash_test.c
 PROGRAM_DIR := $(shell dirname $(realpath $(lastword $(MAKEFILE_LIST))))
 
 include ${PROGRAM_DIR}/../../exts/common/options.mk
diff --git a/sw/tests/flash_ctrl/flash_test.c b/sw/tests/flash_ctrl/flash_test.c
new file mode 100644
index 0000000..c527795
--- /dev/null
+++ b/sw/tests/flash_ctrl/flash_test.c
@@ -0,0 +1,153 @@
+// Copyright lowRISC contributors.
+// Licensed under the Apache License, Version 2.0, see LICENSE for details.
+// SPDX-License-Identifier: Apache-2.0
+
+#include "common.h"
+#include "flash_ctrl.h"
+#include "gpio.h"
+#include "uart.h"
+
+/**
+ * Delay loop executing within 8 cycles on ibex
+ */
+static void delay_loop_ibex(unsigned long loops) {
+  int out; /* only to notify compiler of modifications to |loops| */
+  asm volatile(
+      "1: nop             \n"  // 1 cycle
+      "   nop             \n"  // 1 cycle
+      "   nop             \n"  // 1 cycle
+      "   nop             \n"  // 1 cycle
+      "   addi %1, %1, -1 \n"  // 1 cycle
+      "   bnez %1, 1b     \n"  // 3 cycles
+      : "=&r"(out)
+      : "0"(loops));
+}
+
+static int usleep_ibex(unsigned long usec) {
+  unsigned long usec_cycles;
+  usec_cycles = CLK_FIXED_FREQ_HZ * usec / 1000 / 1000 / 8;
+
+  delay_loop_ibex(usec_cycles);
+  return 0;
+}
+
+static int usleep(unsigned long usec) { return usleep_ibex(usec); }
+
+static void break_on_error(uint32_t error) {
+  if (error) {
+    // inifinitely fetch instructions, will flag an assertion error
+    uart_send_str("FAIL!\r\n");
+    while (1) {
+      usleep(100);
+    }
+  }
+}
+
+/* Returns 1 if |a| and |b| are equal. */
+int check_arr_eq(const uint32_t *a, const uint32_t *b, uint32_t len) {
+  for (int i = 0; i < len; ++i) {
+    if (a[i] != b[i]) {
+      return 0;
+    }
+  }
+  return 1;
+}
+
+int main(int argc, char **argv) {
+  uint32_t i;
+  uint32_t prog_array[FLASH_WORDS_PER_PAGE];
+  uint32_t rd_array[FLASH_WORDS_PER_PAGE];
+  uint32_t bank1_addr = FLASH_MEM_BASE_ADDR + FLASH_BANK_SZ;
+
+  uart_init(UART_BAUD_RATE);
+  flash_init_block();
+
+  // enable all access
+  flash_default_region_access(1, 1, 1);
+  break_on_error(flash_page_erase(bank1_addr));
+  flash_write_scratch_reg(0xFACEDEAD);
+  // read flash back via host to ensure everything is cleared
+  for (i = 0; i < FLASH_WORDS_PER_PAGE; i++) {
+    if (REG32(bank1_addr + i * 4) != 0xFFFFFFFF) {
+      flash_write_scratch_reg(0xDEADBEEF);
+      break_on_error(1);
+    }
+  }
+
+  // do 4K programming
+  // employ the live programming method where overall payload >> flash fifo size
+  for (i = 0; i < ARRAYSIZE(prog_array); i++) {
+    prog_array[i] = i + (i % 2) ? 0xA5A5A5A5 : 0x5A5A5A5A;
+  }
+  break_on_error(flash_write(bank1_addr, prog_array, ARRAYSIZE(prog_array)));
+  break_on_error(flash_read(bank1_addr, ARRAYSIZE(rd_array), rd_array));
+  break_on_error(!check_arr_eq(rd_array, prog_array, ARRAYSIZE(rd_array)));
+
+  /////////////////////////////////////////////////////////////
+  // Begin flash memory protection testing
+  /////////////////////////////////////////////////////////////
+  uint32_t region_base_page = FLASH_PAGES_PER_BANK;
+  uint32_t region_size = 1;
+  uint32_t good_addr_start =
+      FLASH_MEM_BASE_ADDR + region_base_page * FLASH_PAGE_SZ;
+  uint32_t good_addr_end = good_addr_start + region_size * FLASH_PAGE_SZ - 1;
+  uint32_t bad_addr_start =
+      good_addr_end + 1;  // this is always aligned to a page
+  uint32_t good_words = 3;
+  uint32_t bad_words = 3;
+  uint32_t chk_addr = bad_addr_start - (FLASH_WORD_SZ * good_words);
+
+  mp_region_t region0 = {
+      0,                 // region 0
+      region_base_page,  // page 1 of bank1
+      region_size,       // size of 1 page
+      1,                 // allow read
+      1,                 // allow program
+      1                  // allow erase
+  };
+
+  // initialize good and bad regions.
+  break_on_error(flash_page_erase(good_addr_start));
+  break_on_error(flash_page_erase(bad_addr_start));
+
+  // turn off default region all access
+  flash_default_region_access(0, 0, 0);
+  flash_cfg_region(&region0);
+
+  // expect write to fail.
+  for (uint32_t i = 0; i < good_words + bad_words; i++) {
+    prog_array[i] = 0xA5A5A5A5 + i;
+  }
+  break_on_error(!flash_write(chk_addr, prog_array, good_words + bad_words));
+
+  // the good words should match
+  for (uint32_t i = 0; i < good_words; i++) {
+    if (REG32(chk_addr + i * 4) != prog_array[i]) {
+      break_on_error(1);
+    }
+  }
+
+  // the bad word contents should not have gone through
+  for (uint32_t i = good_words; i < good_words + bad_words; i++) {
+    if (REG32(chk_addr + i * 4) != 0xFFFFFFFF) {
+      break_on_error(1);
+    }
+  }
+
+  // attempt to erase bad page, should error
+  break_on_error(!flash_page_erase(bad_addr_start));
+
+  // erase the good page
+  break_on_error(flash_page_erase(good_addr_start));
+
+  // double check erase results
+  for (uint32_t i = 0; i < FLASH_WORDS_PER_PAGE; i++) {
+    if (REG32(good_addr_start + i * 4) != 0xFFFFFFFF) {
+      break_on_error(1);
+    }
+  }
+
+  // cleanly terminate execution
+  uart_send_str("PASS!\r\n");
+  __asm__ volatile("wfi;");
+}
diff --git a/sw/tests/hello_flash/hello_flash.c b/sw/tests/hello_flash/hello_flash.c
deleted file mode 100644
index db38731..0000000
--- a/sw/tests/hello_flash/hello_flash.c
+++ /dev/null
@@ -1,192 +0,0 @@
-// Copyright lowRISC contributors.
-// Licensed under the Apache License, Version 2.0, see LICENSE for details.
-// SPDX-License-Identifier: Apache-2.0
-
-#include "common.h"
-#include "flash_ctrl.h"
-#include "gpio.h"
-#include "uart.h"
-
-#define DATA_MAX 32
-
-/**
- * Delay loop executing within 8 cycles on ibex
- */
-static void delay_loop_ibex(unsigned long loops) {
-  int out; /* only to notify compiler of modifications to |loops| */
-  asm volatile(
-      "1: nop             \n"  // 1 cycle
-      "   nop             \n"  // 1 cycle
-      "   nop             \n"  // 1 cycle
-      "   nop             \n"  // 1 cycle
-      "   addi %1, %1, -1 \n"  // 1 cycle
-      "   bnez %1, 1b     \n"  // 3 cycles
-      : "=&r"(out)
-      : "0"(loops));
-}
-
-static int usleep_ibex(unsigned long usec) {
-  unsigned long usec_cycles;
-  usec_cycles = CLK_FIXED_FREQ_HZ * usec / 1000 / 1000 / 8;
-
-  delay_loop_ibex(usec_cycles);
-  return 0;
-}
-
-static int usleep(unsigned long usec) { return usleep_ibex(usec); }
-
-static void break_on_error(uint32_t error) {
-  if (error) {
-    // inifinitely fetch instructions, will flag an assertion error
-    uart_send_str("Test Failed!\r\n");
-    while (1) {
-      usleep_ibex(100);
-    }
-  }
-
-  // otherwise do nothing and continue
-}
-
-#define MK_PRINT(c) (((c < 32) || (c > 126)) ? '_' : c)
-
-int main(int argc, char **argv) {
-  uart_init(UART_BAUD_RATE);
-
-  // Stupid flash testing
-  uint32_t num_words;
-  uint32_t i;
-  uint32_t prog_array[DATA_MAX];
-  uint32_t rd_array[DATA_MAX];
-  uint32_t max_size;
-  uint32_t start_addr;
-  uint32_t data_pat;
-  uint32_t flash_bank1_loc = FLASH_MEM_BASE_ADDR + FLASH_BANK_SZ;
-
-  // wait for flash to finish "initializing"
-  wait_flash_init();
-
-  // enable all access
-  flash_default_region(1, 1, 1);
-
-  // program flash
-  num_words = 32;
-  for (i = 0; i < num_words; i++) {
-    prog_array[i] = i;
-  }
-
-  break_on_error(prog_flash(flash_bank1_loc, num_words, prog_array));
-
-  // read flash back
-  num_words = 32;
-  break_on_error(read_flash(flash_bank1_loc, num_words, rd_array));
-
-  for (i = 0; i < num_words; i++) {
-    if (prog_array[i] != rd_array[i]) {
-      REG32(FLASH_CTRL_SCRATCH(0)) = rd_array[i];
-      break_on_error(1);
-    }
-  }
-
-  // erase flash and verify
-  // The controller auto rounds down to the closest page for page erase
-  break_on_error(page_erase(flash_bank1_loc + FLASH_PAGE_SZ - 1));
-
-  REG32(FLASH_CTRL_SCRATCH(0)) = 0xFACEDEAD;
-
-  // read flash back via host to ensure everything is cleared
-  for (i = 0; i < 256; i++) {
-    if (REG32(flash_bank1_loc + i * 4) != 0xFFFFFFFF) {
-      REG32(FLASH_CTRL_SCRATCH(0)) = 0xDEADBEEF;
-      break_on_error(1);
-    }
-  }
-
-  // do 4K programming
-  // employ the live programming method where overall payload >> flash fifo size
-  max_size = 1024;
-  start_addr = flash_bank1_loc + 0x8c68;
-  setup_flash_prog(start_addr, max_size);
-
-  for (i = 0; i < max_size; i++) {
-    data_pat = (i % 2) ? 0xA5A5A5A5 : 0x5A5A5A5A;
-    REG32(FLASH_CTRL_PROG_FIFO(0)) = data_pat + i;
-  }
-
-  // wait for operation finish
-  wait_done_and_ack();
-
-  // read back 4K programming
-  for (i = 0; i < max_size; i++) {
-    data_pat = (i % 2) ? 0xA5A5A5A5 : 0x5A5A5A5A;
-
-    if (REG32(start_addr + i * 4) != data_pat + i) {
-      REG32(FLASH_CTRL_SCRATCH(0)) = i << 16 | 0xDEAD;
-      break_on_error(1);
-    }
-  }
-
-  /////////////////////////////////////////////////////////////
-  // Begin flash memory protection testing
-  /////////////////////////////////////////////////////////////
-
-  // turn off default region all access
-  flash_default_region(0, 0, 0);
-
-  uint32_t region_base_page = FLASH_PAGES_PER_BANK;
-  uint32_t region_size = 1;
-  uint32_t good_addr_start =
-      FLASH_MEM_BASE_ADDR + region_base_page * FLASH_PAGE_SZ;
-  uint32_t good_addr_end = good_addr_start + region_size * FLASH_PAGE_SZ - 1;
-  uint32_t bad_addr_start =
-      good_addr_end + 1;  // this is always aligned to a page
-  uint32_t good_words = 3;
-  uint32_t bad_words = 3;
-  uint32_t chk_addr = bad_addr_start - (FLASH_WORD_SZ * good_words);
-
-  mp_region_t region0 = {
-      0,                 // region 0
-      region_base_page,  // page 1 of bank1
-      region_size,       // size of 1 page
-      1,                 // allow read
-      1,                 // allow program
-      1                  // allow erase
-  };
-  flash_cfg_region(region0);
-
-  for (uint32_t i = 0; i < good_words + bad_words; i++) {
-    prog_array[i] = 0xA5A5A5A5 + i;
-  }
-
-  break_on_error(!prog_flash(chk_addr, good_words + bad_words, prog_array));
-
-  // the good words should match
-  for (uint32_t i = 0; i < good_words; i++) {
-    if (REG32(chk_addr + i * 4) != prog_array[i]) {
-      break_on_error(1);
-    }
-  }
-
-  // the bad word contents should not have gone through
-  for (uint32_t i = good_words; i < good_words + bad_words; i++) {
-    if (REG32(chk_addr + i * 4) != 0xFFFFFFFF) {
-      break_on_error(1);
-    }
-  }
-
-  // attempt to erase bad page, should error
-  break_on_error(!page_erase(bad_addr_start));
-
-  // erase the good page
-  break_on_error(page_erase(good_addr_start));
-
-  // double check erase results
-  for (uint32_t i = 0; i < FLASH_WORDS_PER_PAGE; i++) {
-    if (REG32(good_addr_start + i * 4) != 0xFFFFFFFF) {
-      break_on_error(1);
-    }
-  }
-
-  // cleanly terminiate execution
-  uart_send_str("Test Passed!\r\n");
-  __asm__ volatile("wfi;");
-}