pw_spi: Introduce SPI HAL

This change introduces a new SPI HAL API, featuring abstractions
enabling a target device to communicate with SPI peripherals in
a portable and consistent fashion.

Change-Id: Idc085c4727abb9247732c40cc84d05ee4113bd78
Reviewed-on: https://pigweed-review.googlesource.com/c/pigweed/pigweed/+/70140
Reviewed-by: Ewout van Bekkum <ewout@google.com>
Reviewed-by: Keir Mierle <keir@google.com>
Commit-Queue: Mark Slevinsky <markslevinsky@google.com>
diff --git a/pw_spi/linux_spi_test.cc b/pw_spi/linux_spi_test.cc
new file mode 100644
index 0000000..23a881b
--- /dev/null
+++ b/pw_spi/linux_spi_test.cc
@@ -0,0 +1,101 @@
+// Copyright 2021 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.
+
+#include "pw_spi/linux_spi.h"
+
+#include <array>
+#include <optional>
+
+#include "gtest/gtest.h"
+#include "pw_spi/chip_selector.h"
+#include "pw_spi/device.h"
+#include "pw_spi/initiator.h"
+#include "pw_status/status.h"
+#include "pw_sync/borrow.h"
+
+namespace pw::spi {
+namespace {
+
+const pw::spi::Config kConfig = {.polarity = ClockPolarity::kActiveHigh,
+                                 .phase = ClockPhase::kFallingEdge,
+                                 .bits_per_word = BitsPerWord(8),
+                                 .bit_order = BitOrder::kMsbFirst};
+
+class LinuxSpi : public ::testing::Test {
+ public:
+  LinuxSpi()
+      : initiator_(LinuxInitiator("/dev/spidev0.0", 1000000)),
+        chip_selector_(),
+        initiator_lock_(),
+        borrowable_initiator_(initiator_, initiator_lock_),
+        device_(borrowable_initiator_, kConfig, chip_selector_) {}
+
+  Device& device() { return device_; }
+
+ private:
+  LinuxInitiator initiator_;
+  LinuxChipSelector chip_selector_;
+  sync::VirtualMutex initiator_lock_;
+  sync::Borrowable<Initiator> borrowable_initiator_;
+  [[maybe_unused]] Device device_;
+};
+
+TEST_F(LinuxSpi, StartTransaction_Succeeds) {
+  // arrange
+  std::optional<Device::Transaction> transaction =
+      device().StartTransaction(ChipSelectBehavior::kPerWriteRead);
+
+  // act
+
+  // assert
+  EXPECT_TRUE(transaction.has_value());
+}
+
+TEST_F(LinuxSpi, HalfDuplexTransaction_Succeeds) {
+  // arrange
+  std::optional<Device::Transaction> transaction =
+      device().StartTransaction(ChipSelectBehavior::kPerWriteRead);
+
+  // act
+  ASSERT_TRUE(transaction.has_value());
+
+  std::array write_data{std::byte(1), std::byte(2), std::byte(3), std::byte(4)};
+  auto write_status = transaction->Write(ConstByteSpan(write_data));
+
+  std::array read_data{std::byte(1), std::byte(2), std::byte(3), std::byte(4)};
+  auto read_status = transaction->Read(read_data);
+
+  // assert
+  EXPECT_TRUE(write_status.ok());
+  EXPECT_TRUE(read_status.ok());
+}
+
+TEST_F(LinuxSpi, FullDuplexTransaction_Succeeds) {
+  // arrange
+  std::optional<Device::Transaction> transaction =
+      device().StartTransaction(ChipSelectBehavior::kPerWriteRead);
+
+  // act
+  ASSERT_TRUE(transaction.has_value());
+
+  std::array write_data{std::byte(1), std::byte(2), std::byte(3), std::byte(4)};
+  std::array read_data{std::byte(0), std::byte(0), std::byte(0), std::byte(0)};
+  auto wr_status = transaction->WriteRead(ConstByteSpan(write_data), read_data);
+
+  // assert
+  EXPECT_TRUE(wr_status.ok());
+}
+
+}  // namespace
+}  // namespace pw::spi