fpga_config: Check SPI Flash communication

- Check the SPI Flash chip is readable after the FPGA is configured
  over serial.

- This fixes SPI flash communication assuming
  ./out/gn/obj/fpga/toplevel/toplevel.bin is used to configure the
  FPGA.

  As of https://pigweed-review.googlesource.com/c/gonk/+/185890
  the SPI bus pins are set to inputs on the FPGA side which allows MCU
  to SPI flash chip communication.

- Small refactor removing global Arduino `SPI` usage in spi_flash.cc.

- Add ResumeFromPowerDown() to SpiFlash class.

Change-Id: Ibb3a32f3e762c6402f3ed0e4e07e639334e17e2a
Reviewed-on: https://pigweed-review.googlesource.com/c/gonk/+/185870
Reviewed-by: Eric Holland <hollande@google.com>
Presubmit-Verified: CQ Bot Account <pigweed-scoped@luci-project-accounts.iam.gserviceaccount.com>
Commit-Queue: Anthony DiGirolamo <tonymd@google.com>
diff --git a/README.md b/README.md
index 3e166f1..8094b68 100644
--- a/README.md
+++ b/README.md
@@ -176,19 +176,19 @@
 ## Misc
 
 | Net    | STM32 Pin | STM32 Function | Function |
-|--------+-----------+----------------+----------|
+|--------|-----------|----------------|----------|
 | STATUS | PB13      | GPIO_Output    | STAT LED |
 
 ## SPI Flash Connection
 
-| Net          | STM32 Pin | STM32 Function | Flash Pin  |
-|--------------|-----------|----------------|------------|
-| ICE_SPI_SS   | PD2       | GPIO_Output    | S#         |
-| ICE_SPI_MISO | PB4       | SPI1_MISO      | DQ1        |
-| ICE_SPI_MOSI | PB5       | SPI1_MOSI      | DQ0        |
-| ICE_SPI_SCK  | PB3       | SPI1_SCK       | C          |
-| FLASH_HOLD   | PC11      | GPIO_Output    | HOLD#/DQ3  |
-| FLASH_WP     | PC12      | GPIO_Output    | W#/VPP/DQ2 |
+| Net          | FPGA IO# | STM32 Pin | STM32 Function | Flash Pin  |
+|--------------|----------|-----------|----------------|------------|
+| ICE_SPI_SS   |       71 | PD2       | GPIO_Output    | S#         |
+| ICE_SPI_MISO |       68 | PB4       | SPI1_MISO      | DQ1        |
+| ICE_SPI_MOSI |       67 | PB5       | SPI1_MOSI      | DQ0        |
+| ICE_SPI_SCK  |       70 | PB3       | SPI1_SCK       | C          |
+| FLASH_HOLD   |       63 | PC11      | GPIO_Output    | HOLD#/DQ3  |
+| FLASH_WP     |       64 | PC12      | GPIO_Output    | W#/VPP/DQ2 |
 
 ## FPGA Connection
 
diff --git a/applications/fpga_config/main.cc b/applications/fpga_config/main.cc
index d028721..a2828a9 100644
--- a/applications/fpga_config/main.cc
+++ b/applications/fpga_config/main.cc
@@ -37,7 +37,8 @@
 
 PinConfig pin_config = PinConfig();
 FpgaControl fpga_control = FpgaControl(&pin_config);
-SpiFlash spi_flash = SpiFlash(FlashCS, /*baudrate=*/1000000);
+SpiFlash spi_flash =
+    SpiFlash(FlashCS, /*baudrate=*/1000000, pin_config.flash_spi);
 
 void ice40_done_rising_isr() { PW_LOG_DEBUG("ICE40 Done: HIGH"); }
 
@@ -81,10 +82,24 @@
   }
 }
 
+void CheckSpiFlash() {
+  PW_LOG_INFO("Checking SPI Flash");
+  spi_flash.StartFlashSpi();
+
+  pw::Result<SpiFlash::DeviceId> result = spi_flash.GetDeviceIds();
+  if (result.ok()) {
+    PW_LOG_INFO("SPI Flash JEDEC ID: %x %x %x", result.value().manufacturer_id,
+                result.value().family_code, result.value().product_version);
+  }
+}
+
 void FpgaConfigTask() {
   auto result = fpga_control.StartConfig();
   if (result.ok()) {
+    // FPGA has been configure successfully.
     current_task = &IdleTask;
+    // Check if SPI flash is readable.
+    CheckSpiFlash();
   } else {
     PW_LOG_INFO("Restarting FPGA Config.");
     current_task = &FpgaConfigTask;
diff --git a/applications/spi_flash_test/main.cc b/applications/spi_flash_test/main.cc
index daaaa04..70463be 100644
--- a/applications/spi_flash_test/main.cc
+++ b/applications/spi_flash_test/main.cc
@@ -33,7 +33,8 @@
 namespace {
 
 PinConfig pin_config = PinConfig();
-SpiFlash spi_flash = SpiFlash(FlashCS, /*baudrate=*/1000000);
+SpiFlash spi_flash =
+    SpiFlash(FlashCS, /*baudrate=*/1000000, pin_config.flash_spi);
 
 } // namespace
 
@@ -46,6 +47,7 @@
   delay(10);
 
   pin_config.SPIEnable();
+  spi_flash.StartFlashSpi();
 
   char buffer[32];
   uint16_t update_count = 0;
diff --git a/lib/fpga_control/fpga_control.cc b/lib/fpga_control/fpga_control.cc
index 7d4ff6a..090ad54 100644
--- a/lib/fpga_control/fpga_control.cc
+++ b/lib/fpga_control/fpga_control.cc
@@ -43,10 +43,12 @@
   uint32_t last_update = millis();
   uint32_t this_update = millis();
 
+  const uint32_t update_interval_ms = 1000;
+
   while (true) {
     // Output a heartbeat message while waiting for data.
     this_update = millis();
-    if (bytes_written == 0 && this_update > last_update + 1000) {
+    if (bytes_written == 0 && this_update > last_update + update_interval_ms) {
       last_update = this_update;
       PW_LOG_INFO("Waiting for bitstream");
     }
@@ -112,7 +114,7 @@
   return pw::OkStatus();
 }
 
-Status FpgaControl::SendBitstreamToFpga() {
+Status FpgaControl::SendBitstreamToFpga(const uint32_t config_timeout_ms) {
   PW_LOG_INFO("Sending bitstream file to the FPGA.");
 
   pin_config_->SPIDisable();
@@ -163,7 +165,7 @@
 
     this_update = millis();
     // If more than two seconds have passed something likely went wrong.
-    if (this_update > last_update + 2000) {
+    if (this_update > last_update + config_timeout_ms) {
       PW_LOG_ERROR("FPGA Config failed.");
       last_update = this_update;
       return pw::Status::Aborted();
@@ -173,11 +175,14 @@
   // Send 50 more clock pulses to release release the SPI bus to user control.
   for (int i = 0; i < 50; i++) {
     digitalWrite(FlashCLK, LOW);
-    delayMicroseconds(200);
+    delayMicroseconds(100);
     digitalWrite(FlashCLK, HIGH);
-    delayMicroseconds(200);
+    delayMicroseconds(100);
   }
 
+  // Re-enable SPI
+  pin_config_->SPIEnable();
+
   return pw::OkStatus();
 }
 
diff --git a/lib/fpga_control/public/gonk/fpga_control.h b/lib/fpga_control/public/gonk/fpga_control.h
index 3aa35f6..7493da9 100644
--- a/lib/fpga_control/public/gonk/fpga_control.h
+++ b/lib/fpga_control/public/gonk/fpga_control.h
@@ -24,7 +24,7 @@
   Status WaitForBitstreamStart();
   Status ReadBitstream();
   Status VerifyBitstream();
-  Status SendBitstreamToFpga();
+  Status SendBitstreamToFpga(const uint32_t config_timeout_ms = 2000);
 
 private:
   PinConfig *pin_config_;
diff --git a/lib/pin_config/BUILD.gn b/lib/pin_config/BUILD.gn
index ef9b399..42cbc01 100644
--- a/lib/pin_config/BUILD.gn
+++ b/lib/pin_config/BUILD.gn
@@ -17,15 +17,16 @@
 import("$dir_pw_build/target_types.gni")
 
 config("default_config") {
-  include_dirs = [ "public" ]
+  include_dirs = [
+    "public",
+    "//third_party/stm32duino/arduino-core/libraries/SPI/src",
+  ]
 }
 
 pw_source_set("pin_config") {
   public_configs = [ ":default_config" ]
   public = [ "public/gonk/pin_config.h" ]
 
-  include_dirs = [ "//third_party/stm32duino/arduino-core/libraries/SPI/src" ]
-
   sources = [ "pin_config.cc" ]
 
   deps = [
diff --git a/lib/pin_config/pin_config.cc b/lib/pin_config/pin_config.cc
index 5489b52..9debc76 100644
--- a/lib/pin_config/pin_config.cc
+++ b/lib/pin_config/pin_config.cc
@@ -10,7 +10,7 @@
 
 namespace gonk::pin_config {
 
-PinConfig::PinConfig() {}
+PinConfig::PinConfig() { flash_spi = SPIClass(FlashMOSI, FlashMISO, FlashCLK); }
 
 void PinConfig::Init() {
   // Set STATUS LED mode and initial state.
@@ -87,24 +87,14 @@
 void PinConfig::FpgaEnable() { digitalWrite(ICE40ResetN, HIGH); }
 
 void PinConfig::SPIEnable() {
-  // SPI pin definitions
-  SPI.setMISO(FlashMISO);
-  SPI.setMOSI(FlashMOSI);
-  SPI.setSCLK(FlashCLK);
-  SPI.begin();
-
-  // Set FlashCS as output with initial state.
-  pinMode(FlashCS, OUTPUT);
-  digitalWrite(FlashCS, HIGH);
-
-  pinMode(FlashCLK, OUTPUT);
-  pinMode(FlashMOSI, OUTPUT);
-  pinMode(FlashMISO, INPUT_FLOATING);
+  // Disable Flash hold and write protect.
+  pinMode(FlashHold, OUTPUT);
+  pinMode(FlashWP, OUTPUT);
+  digitalWrite(FlashHold, HIGH);
+  digitalWrite(FlashWP, HIGH);
 }
 
 void PinConfig::SPIDisable() {
-  SPI.end();
-
   pinMode(FlashCS, OUTPUT);
   pinMode(FlashCLK, OUTPUT);
   pinMode(FlashMOSI, OUTPUT);
diff --git a/lib/pin_config/public/gonk/pin_config.h b/lib/pin_config/public/gonk/pin_config.h
index 06c541f..c3488ec 100644
--- a/lib/pin_config/public/gonk/pin_config.h
+++ b/lib/pin_config/public/gonk/pin_config.h
@@ -1,6 +1,7 @@
 #pragma once
 
 #include <Arduino.h>
+#include <SPI.h>
 
 namespace gonk::pin_config {
 
@@ -32,6 +33,8 @@
   void SPIEnable();
   void SPIDisable();
 
+  SPIClass flash_spi;
+
 private:
 };
 
diff --git a/lib/spi_flash/public/gonk/spi_flash.h b/lib/spi_flash/public/gonk/spi_flash.h
index 86a0fc5..f84d842 100644
--- a/lib/spi_flash/public/gonk/spi_flash.h
+++ b/lib/spi_flash/public/gonk/spi_flash.h
@@ -20,7 +20,7 @@
     uint8_t product_version;
   };
 
-  SpiFlash(uint16_t flash_cs, int baudrate);
+  SpiFlash(uint16_t flash_cs, int baudrate, SPIClass &flash_spi);
 
   Status WriteEnable();
   Status WriteDisable();
@@ -29,10 +29,14 @@
   bool IsBusy();
   bool WritingIsEnabled();
   Status Erase();
+  void StartFlashSpi();
+  void StopFlashSpi();
+  void ResumeFromPowerDown();
 
 private:
   uint16_t flash_cs_;
   SPISettings spi_settings_;
+  SPIClass &flash_spi_;
 };
 
 } // namespace gonk::spi_flash
diff --git a/lib/spi_flash/spi_flash.cc b/lib/spi_flash/spi_flash.cc
index d518211..9202ed1 100644
--- a/lib/spi_flash/spi_flash.cc
+++ b/lib/spi_flash/spi_flash.cc
@@ -16,25 +16,26 @@
 
 namespace gonk::spi_flash {
 
-SpiFlash::SpiFlash(uint16_t flash_cs, int baudrate)
-    : flash_cs_(flash_cs), spi_settings_(baudrate, MSBFIRST, SPI_MODE0) {}
+SpiFlash::SpiFlash(uint16_t flash_cs, int baudrate, SPIClass &flash_spi)
+    : flash_cs_(flash_cs), spi_settings_(baudrate, MSBFIRST, SPI_MODE0),
+      flash_spi_(flash_spi) {}
 
 Status SpiFlash::WriteEnable() {
-  SPI.beginTransaction(spi_settings_);
+  flash_spi_.beginTransaction(spi_settings_);
   digitalWrite(flash_cs_, LOW);
-  SPI.transfer(0x06);
+  flash_spi_.transfer(0x06);
   digitalWrite(flash_cs_, HIGH);
-  SPI.endTransaction();
+  flash_spi_.endTransaction();
 
   return pw::OkStatus();
 }
 
 Status SpiFlash::WriteDisable() {
-  SPI.beginTransaction(spi_settings_);
+  flash_spi_.beginTransaction(spi_settings_);
   digitalWrite(flash_cs_, LOW);
-  SPI.transfer(0x04);
+  flash_spi_.transfer(0x04);
   digitalWrite(flash_cs_, HIGH);
-  SPI.endTransaction();
+  flash_spi_.endTransaction();
 
   return pw::OkStatus();
 }
@@ -45,11 +46,11 @@
       0,    // Result value
   };
 
-  SPI.beginTransaction(spi_settings_);
+  flash_spi_.beginTransaction(spi_settings_);
   digitalWrite(flash_cs_, LOW);
-  SPI.transfer(status_register, 2);
+  flash_spi_.transfer(status_register, 2);
   digitalWrite(flash_cs_, HIGH);
-  SPI.endTransaction();
+  flash_spi_.endTransaction();
 
   return (std::bitset<8>)status_register[1];
 }
@@ -71,11 +72,11 @@
 
   PW_LOG_DEBUG("Start Flash Erase");
   // Begin chip erase.
-  SPI.beginTransaction(spi_settings_);
+  flash_spi_.beginTransaction(spi_settings_);
   digitalWrite(flash_cs_, LOW);
-  SPI.transfer(0x60);
+  flash_spi_.transfer(0x60);
   digitalWrite(flash_cs_, HIGH);
-  SPI.endTransaction();
+  flash_spi_.endTransaction();
 
   int wait_time = 100; // Wait at most 1 second.
   while (IsBusy() && wait_time > 0) {
@@ -98,11 +99,11 @@
   };
 
   // Read the SPI Flash JEDEC ID.
-  SPI.beginTransaction(spi_settings_);
+  flash_spi_.beginTransaction(spi_settings_);
   digitalWrite(flash_cs_, LOW);
-  SPI.transfer(manufacturer_id, 4);
+  flash_spi_.transfer(manufacturer_id, 4);
   digitalWrite(flash_cs_, HIGH);
-  SPI.endTransaction();
+  flash_spi_.endTransaction();
 
   PW_LOG_DEBUG("JEDEC ID: %x %x %x", manufacturer_id[1], manufacturer_id[2],
                manufacturer_id[3]);
@@ -122,4 +123,21 @@
   };
 }
 
+void SpiFlash::ResumeFromPowerDown() {
+  // Resume from Deep Power-Down
+  flash_spi_.beginTransaction(spi_settings_);
+  digitalWrite(flash_cs_, LOW);
+  flash_spi_.transfer(0xAB);
+  digitalWrite(flash_cs_, HIGH);
+  flash_spi_.endTransaction();
+}
+
+void SpiFlash::StartFlashSpi() {
+  flash_spi_.begin();
+  delay(10);
+  ResumeFromPowerDown();
+}
+
+void SpiFlash::StopFlashSpi() { flash_spi_.end(); }
+
 } // namespace gonk::spi_flash