lib/spi_flash: New library for SPI flash commands

Change-Id: I81fb53eef259aa67fe27e96aef603933f04b060c
Reviewed-on: https://pigweed-review.googlesource.com/c/gonk/+/183214
Reviewed-by: Eric Holland <hollande@google.com>
Commit-Queue: Anthony DiGirolamo <tonymd@google.com>
diff --git a/applications/spi_flash_test/BUILD.gn b/applications/spi_flash_test/BUILD.gn
index dd3cd63..6714ab6 100644
--- a/applications/spi_flash_test/BUILD.gn
+++ b/applications/spi_flash_test/BUILD.gn
@@ -31,6 +31,7 @@
     "$dir_pw_string",
     "$dir_pw_third_party/arduino:arduino_core_sources",
     "//lib/pin_config",
+    "//lib/spi_flash",
   ]
 
   ldflags = [ "-Wl,--print-memory-usage" ]
diff --git a/applications/spi_flash_test/main.cc b/applications/spi_flash_test/main.cc
index 514817c..daaaa04 100644
--- a/applications/spi_flash_test/main.cc
+++ b/applications/spi_flash_test/main.cc
@@ -12,23 +12,28 @@
 // License for the specific language governing permissions and limitations under
 // the License.
 
+#include <bitset>
 #include <cstdint>
 
 #include <Arduino.h>
 #include <SPI.h>
 
 #include "pw_log/log.h"
+#include "pw_result/result.h"
 #include "pw_string/format.h"
 
 #include "gonk/pin_config.h"
+#include "gonk/spi_flash.h"
 
 using gonk::pin_config::FlashCS;
 using gonk::pin_config::PinConfig;
 using gonk::pin_config::StatusLed;
+using gonk::spi_flash::SpiFlash;
 
 namespace {
 
 PinConfig pin_config = PinConfig();
+SpiFlash spi_flash = SpiFlash(FlashCS, /*baudrate=*/1000000);
 
 } // namespace
 
@@ -44,25 +49,17 @@
 
   char buffer[32];
   uint16_t update_count = 0;
-  uint8_t manufacturer_id[4];
 
   while (true) {
+    PW_LOG_INFO("Update Count: %d", update_count);
 
-    // Read the SPI Flash JEDEC ID.
-    manufacturer_id[0] = 0x9f;
-    manufacturer_id[1] = 0;
-    manufacturer_id[2] = 0;
-    manufacturer_id[3] = 0;
-    SPI.beginTransaction(SPISettings(1000000, MSBFIRST, SPI_MODE0));
-    digitalWrite(FlashCS, LOW);
-    SPI.transfer(manufacturer_id, 4);
-    digitalWrite(FlashCS, HIGH);
-    SPI.endTransaction();
+    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);
+    }
 
-    pw::string::Format(buffer, "SPI Flash JEDEC ID: %x %x %x",
-                       manufacturer_id[1], manufacturer_id[2],
-                       manufacturer_id[3]);
-    PW_LOG_INFO("%s", buffer);
     delay(1000);
 
     update_count = (update_count + 1) % UINT16_MAX;
diff --git a/lib/spi_flash/BUILD.gn b/lib/spi_flash/BUILD.gn
new file mode 100644
index 0000000..902e66e
--- /dev/null
+++ b/lib/spi_flash/BUILD.gn
@@ -0,0 +1,38 @@
+# Copyright 2023 The Pigweed Authors
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may not
+# use this file except in compliance with the License. You may obtain a copy of
+# the License at
+#
+#     https://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations under
+# the License.
+
+import("//build_overrides/pigweed.gni")
+
+import("$dir_pw_build/target_types.gni")
+
+config("default_config") {
+  include_dirs = [ "public" ]
+}
+
+pw_source_set("spi_flash") {
+  public_configs = [ ":default_config" ]
+  public = [ "public/gonk/spi_flash.h" ]
+
+  include_dirs = [ "//third_party/stm32duino/arduino-core/libraries/SPI/src" ]
+
+  sources = [ "spi_flash.cc" ]
+
+  deps = [
+    "$dir_pw_log",
+    "$dir_pw_result",
+    "$dir_pw_span",
+    "$dir_pw_status",
+    "$dir_pw_third_party/arduino:arduino_core_sources",
+  ]
+}
diff --git a/lib/spi_flash/public/gonk/spi_flash.h b/lib/spi_flash/public/gonk/spi_flash.h
new file mode 100644
index 0000000..86a0fc5
--- /dev/null
+++ b/lib/spi_flash/public/gonk/spi_flash.h
@@ -0,0 +1,38 @@
+#pragma once
+
+#include <Arduino.h>
+#include <stdint.h>
+
+#include "pw_result/result.h"
+#include "pw_span/span.h"
+#include "pw_status/status.h"
+
+using pw::span;
+using pw::Status;
+
+namespace gonk::spi_flash {
+
+class SpiFlash {
+public:
+  struct DeviceId {
+    uint8_t manufacturer_id;
+    uint8_t family_code;
+    uint8_t product_version;
+  };
+
+  SpiFlash(uint16_t flash_cs, int baudrate);
+
+  Status WriteEnable();
+  Status WriteDisable();
+  pw::Result<DeviceId> GetDeviceIds();
+  std::bitset<8> StatusRegister();
+  bool IsBusy();
+  bool WritingIsEnabled();
+  Status Erase();
+
+private:
+  uint16_t flash_cs_;
+  SPISettings spi_settings_;
+};
+
+} // namespace gonk::spi_flash
diff --git a/lib/spi_flash/spi_flash.cc b/lib/spi_flash/spi_flash.cc
new file mode 100644
index 0000000..d518211
--- /dev/null
+++ b/lib/spi_flash/spi_flash.cc
@@ -0,0 +1,125 @@
+#include <Arduino.h>
+#include <SPI.h>
+#include <bitset>
+#include <cstddef>
+#include <stdint.h>
+
+#include "gonk/spi_flash.h"
+
+#define PW_LOG_LEVEL PW_LOG_LEVEL_DEBUG
+#define PW_LOG_MODULE_NAME "SpiFlash"
+
+#include "pw_log/log.h"
+#include "pw_result/result.h"
+#include "pw_span/span.h"
+#include "pw_status/status.h"
+
+namespace gonk::spi_flash {
+
+SpiFlash::SpiFlash(uint16_t flash_cs, int baudrate)
+    : flash_cs_(flash_cs), spi_settings_(baudrate, MSBFIRST, SPI_MODE0) {}
+
+Status SpiFlash::WriteEnable() {
+  SPI.beginTransaction(spi_settings_);
+  digitalWrite(flash_cs_, LOW);
+  SPI.transfer(0x06);
+  digitalWrite(flash_cs_, HIGH);
+  SPI.endTransaction();
+
+  return pw::OkStatus();
+}
+
+Status SpiFlash::WriteDisable() {
+  SPI.beginTransaction(spi_settings_);
+  digitalWrite(flash_cs_, LOW);
+  SPI.transfer(0x04);
+  digitalWrite(flash_cs_, HIGH);
+  SPI.endTransaction();
+
+  return pw::OkStatus();
+}
+
+std::bitset<8> SpiFlash::StatusRegister() {
+  uint8_t status_register[2] = {
+      0x05, // Read Status Register
+      0,    // Result value
+  };
+
+  SPI.beginTransaction(spi_settings_);
+  digitalWrite(flash_cs_, LOW);
+  SPI.transfer(status_register, 2);
+  digitalWrite(flash_cs_, HIGH);
+  SPI.endTransaction();
+
+  return (std::bitset<8>)status_register[1];
+}
+
+bool SpiFlash::IsBusy() {
+  // 1 in the first bit of the status register indicates the device is busy.
+  return StatusRegister()[0];
+}
+
+bool SpiFlash::WritingIsEnabled() {
+  // 1 in the second bit of the status register indicates the device is busy.
+  return StatusRegister()[1];
+}
+
+Status SpiFlash::Erase() {
+  if (!WritingIsEnabled()) {
+    return pw::Status::FailedPrecondition();
+  }
+
+  PW_LOG_DEBUG("Start Flash Erase");
+  // Begin chip erase.
+  SPI.beginTransaction(spi_settings_);
+  digitalWrite(flash_cs_, LOW);
+  SPI.transfer(0x60);
+  digitalWrite(flash_cs_, HIGH);
+  SPI.endTransaction();
+
+  int wait_time = 100; // Wait at most 1 second.
+  while (IsBusy() && wait_time > 0) {
+    PW_LOG_DEBUG("Device is busy");
+    delay(10);
+    wait_time--;
+  }
+
+  if (wait_time < 0) {
+    return pw::Status::DeadlineExceeded();
+  }
+
+  return pw::OkStatus();
+}
+
+pw::Result<SpiFlash::DeviceId> SpiFlash::GetDeviceIds() {
+  uint8_t manufacturer_id[4] = {
+      0x9f,    // Read Manufacturer and Device ID Command
+      0, 0, 0, // Result values
+  };
+
+  // Read the SPI Flash JEDEC ID.
+  SPI.beginTransaction(spi_settings_);
+  digitalWrite(flash_cs_, LOW);
+  SPI.transfer(manufacturer_id, 4);
+  digitalWrite(flash_cs_, HIGH);
+  SPI.endTransaction();
+
+  PW_LOG_DEBUG("JEDEC ID: %x %x %x", manufacturer_id[1], manufacturer_id[2],
+               manufacturer_id[3]);
+
+  // Check for expected values
+  if (manufacturer_id[1] == 0xff && manufacturer_id[2] == 0xff &&
+      manufacturer_id[3] == 0xff) {
+    PW_LOG_ERROR("Error: Unexpected device ID values. SPI bus may not be "
+                 "released by the FPGA.");
+    return pw::Status::NotFound();
+  }
+
+  return SpiFlash::DeviceId{
+      .manufacturer_id = manufacturer_id[1],
+      .family_code = manufacturer_id[2],
+      .product_version = manufacturer_id[3],
+  };
+}
+
+} // namespace gonk::spi_flash