aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAustin Foxley <afoxley@google.com>2023-06-27 21:31:20 +0000
committerCQ Bot Account <pigweed-scoped@luci-project-accounts.iam.gserviceaccount.com>2023-06-27 21:31:20 +0000
commitff8202f165e33943965290acdf78006b1271d52f (patch)
tree14d38fb4ca5d3dd5529810baee5fc9ec81947bcd
parent446b404746f214f0e6d631b5452f6babaf982678 (diff)
downloadpigweed-ff8202f165e33943965290acdf78006b1271d52f.tar.gz
pw_spi: Add mcuxpresso backends for pw_spi
Change-Id: I1851decf8257bebf33c2670e8060af11aa3f213b Reviewed-on: https://pigweed-review.googlesource.com/c/pigweed/pigweed/+/150971 Reviewed-by: Carlos Chinchilla <cachinchilla@google.com> Reviewed-by: Anthony DiGirolamo <tonymd@google.com> Commit-Queue: Austin Foxley <afoxley@google.com> Presubmit-Verified: CQ Bot Account <pigweed-scoped@luci-project-accounts.iam.gserviceaccount.com>
-rw-r--r--PIGWEED_MODULES1
-rw-r--r--pw_build/generated_pigweed_modules_lists.gni4
-rw-r--r--pw_spi/public/pw_spi/initiator.h5
-rw-r--r--pw_spi_mcuxpresso/BUILD.bazel61
-rw-r--r--pw_spi_mcuxpresso/BUILD.gn114
-rw-r--r--pw_spi_mcuxpresso/OWNERS2
-rw-r--r--pw_spi_mcuxpresso/docs.rst61
-rw-r--r--pw_spi_mcuxpresso/flexspi.cc221
-rw-r--r--pw_spi_mcuxpresso/flexspi_test.cc97
-rw-r--r--pw_spi_mcuxpresso/public/pw_spi_mcuxpresso/flexspi.h86
-rw-r--r--pw_spi_mcuxpresso/public/pw_spi_mcuxpresso/spi.h99
-rw-r--r--pw_spi_mcuxpresso/spi.cc184
-rw-r--r--pw_spi_mcuxpresso/spi_test.cc102
-rw-r--r--targets/mimxrt595_evk_freertos/BUILD.gn2
14 files changed, 1039 insertions, 0 deletions
diff --git a/PIGWEED_MODULES b/PIGWEED_MODULES
index bb8631858..ab34fe976 100644
--- a/PIGWEED_MODULES
+++ b/PIGWEED_MODULES
@@ -90,6 +90,7 @@ pw_snapshot
pw_software_update
pw_span
pw_spi
+pw_spi_mcuxpresso
pw_status
pw_stm32cube_build
pw_stream
diff --git a/pw_build/generated_pigweed_modules_lists.gni b/pw_build/generated_pigweed_modules_lists.gni
index a38c43138..d377a0f11 100644
--- a/pw_build/generated_pigweed_modules_lists.gni
+++ b/pw_build/generated_pigweed_modules_lists.gni
@@ -125,6 +125,7 @@ declare_args() {
dir_pw_software_update = get_path_info("../pw_software_update", "abspath")
dir_pw_span = get_path_info("../pw_span", "abspath")
dir_pw_spi = get_path_info("../pw_spi", "abspath")
+ dir_pw_spi_mcuxpresso = get_path_info("../pw_spi_mcuxpresso", "abspath")
dir_pw_status = get_path_info("../pw_status", "abspath")
dir_pw_stm32cube_build = get_path_info("../pw_stm32cube_build", "abspath")
dir_pw_stream = get_path_info("../pw_stream", "abspath")
@@ -274,6 +275,7 @@ declare_args() {
dir_pw_software_update,
dir_pw_span,
dir_pw_spi,
+ dir_pw_spi_mcuxpresso,
dir_pw_status,
dir_pw_stm32cube_build,
dir_pw_stream,
@@ -415,6 +417,7 @@ declare_args() {
"$dir_pw_software_update:tests",
"$dir_pw_span:tests",
"$dir_pw_spi:tests",
+ "$dir_pw_spi_mcuxpresso:tests",
"$dir_pw_status:tests",
"$dir_pw_stm32cube_build:tests",
"$dir_pw_stream:tests",
@@ -556,6 +559,7 @@ declare_args() {
"$dir_pw_software_update:docs",
"$dir_pw_span:docs",
"$dir_pw_spi:docs",
+ "$dir_pw_spi_mcuxpresso:docs",
"$dir_pw_status:docs",
"$dir_pw_stm32cube_build:docs",
"$dir_pw_stream:docs",
diff --git a/pw_spi/public/pw_spi/initiator.h b/pw_spi/public/pw_spi/initiator.h
index a750b03be..51f123774 100644
--- a/pw_spi/public/pw_spi/initiator.h
+++ b/pw_spi/public/pw_spi/initiator.h
@@ -67,6 +67,11 @@ struct Config {
ClockPhase phase;
BitsPerWord bits_per_word;
BitOrder bit_order;
+
+ bool operator==(const Config& rhs) const {
+ return polarity == rhs.polarity && phase == rhs.phase &&
+ bits_per_word() == rhs.bits_per_word() && bit_order == rhs.bit_order;
+ }
};
static_assert(sizeof(Config) == sizeof(uint32_t),
"Ensure that the config struct fits in 32-bits");
diff --git a/pw_spi_mcuxpresso/BUILD.bazel b/pw_spi_mcuxpresso/BUILD.bazel
new file mode 100644
index 000000000..78c1d0d02
--- /dev/null
+++ b/pw_spi_mcuxpresso/BUILD.bazel
@@ -0,0 +1,61 @@
+# 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.
+
+load(
+ "//pw_build:pigweed.bzl",
+ "pw_cc_library",
+ "pw_cc_test",
+)
+
+package(default_visibility = ["//visibility:public"])
+
+licenses(["notice"])
+
+pw_cc_library(
+ name = "pw_spi_mcuxpresso",
+ srcs = [
+ "flexspi.cc",
+ "spi.cc",
+ ],
+ hdrs = [
+ "public/pw_spi_mcuxpresso/flexspi.h",
+ "public/pw_spi_mcuxpresso/spi.h",
+ ],
+ target_compatible_with = [
+ "//pw_build/constraints/board:mimxrt595_evk",
+ ],
+ deps = [
+ "//pw_digital_io:facade",
+ "//pw_preprocessor",
+ "@pigweed_config//:mcuxpresso_sdk",
+ ],
+)
+
+pw_cc_test(
+ name = "spi_test",
+ srcs = ["spi_test.cc"],
+ target_compatible_with = [
+ "//pw_build/constraints/board:mimxrt595_evk",
+ ],
+ deps = [":pw_digital_io_mcuxpresso"],
+)
+
+pw_cc_test(
+ name = "flexspi_test",
+ srcs = ["flexspi_test.cc"],
+ target_compatible_with = [
+ "//pw_build/constraints/board:mimxrt595_evk",
+ ],
+ deps = [":pw_digital_io_mcuxpresso"],
+)
diff --git a/pw_spi_mcuxpresso/BUILD.gn b/pw_spi_mcuxpresso/BUILD.gn
new file mode 100644
index 000000000..67889d0a5
--- /dev/null
+++ b/pw_spi_mcuxpresso/BUILD.gn
@@ -0,0 +1,114 @@
+# 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")
+import("$dir_pw_docgen/docs.gni")
+import("$dir_pw_third_party/mcuxpresso/mcuxpresso.gni")
+import("$dir_pw_unit_test/test.gni")
+
+config("default_config") {
+ include_dirs = [ "public" ]
+}
+
+group("pw_spi_mcuxpresso") {
+ deps = [
+ ":flexspi",
+ ":spi",
+ ]
+}
+
+if (pw_third_party_mcuxpresso_SDK != "") {
+ pw_source_set("spi") {
+ public_configs = [ ":default_config" ]
+ public = [ "public/pw_spi_mcuxpresso/spi.h" ]
+ public_deps = [
+ "$dir_pw_spi:device",
+ "$dir_pw_status",
+ "$dir_pw_sync:binary_semaphore",
+ "$dir_pw_sync:lock_annotations",
+ "$dir_pw_sync:mutex",
+ "$pw_third_party_mcuxpresso_SDK",
+ ]
+ deps = [
+ "$dir_pw_assert",
+ "$dir_pw_chrono:system_clock",
+ "$dir_pw_log",
+ ]
+ sources = [ "spi.cc" ]
+ }
+
+ pw_source_set("flexspi") {
+ public_configs = [ ":default_config" ]
+ public = [ "public/pw_spi_mcuxpresso/flexspi.h" ]
+ public_deps = [
+ "$dir_pw_digital_io",
+ "$dir_pw_spi:device",
+ "$dir_pw_status",
+ "$dir_pw_sync:binary_semaphore",
+ "$dir_pw_sync:lock_annotations",
+ "$dir_pw_sync:mutex",
+ "$pw_third_party_mcuxpresso_SDK",
+ ]
+ deps = [
+ "$dir_pw_chrono:system_clock",
+ "$dir_pw_log",
+ ]
+ sources = [ "flexspi.cc" ]
+ }
+
+ pw_test("spi_test") {
+ enable_if =
+ pw_third_party_mcuxpresso_SDK ==
+ "//targets/mimxrt595_evk_freertos:sdk" &&
+ (pw_toolchain_SCOPE.name == "mimxrt595_evk_freertos_debug" ||
+ pw_toolchain_SCOPE.name == "mimxrt595_evk_freertos_size_optimized" ||
+ pw_toolchain_SCOPE.name == "mimxrt595_evk_freertos_speed_optimized")
+ sources = [ "spi_test.cc" ]
+ deps = [
+ ":spi",
+ "//targets/mimxrt595_evk_freertos:sdk",
+ ]
+ }
+
+ pw_test("flexspi_test") {
+ enable_if =
+ pw_third_party_mcuxpresso_SDK ==
+ "//targets/mimxrt595_evk_freertos:sdk" &&
+ (pw_toolchain_SCOPE.name == "mimxrt595_evk_freertos_debug" ||
+ pw_toolchain_SCOPE.name == "mimxrt595_evk_freertos_size_optimized" ||
+ pw_toolchain_SCOPE.name == "mimxrt595_evk_freertos_speed_optimized")
+ sources = [ "flexspi_test.cc" ]
+ deps = [
+ ":flexspi",
+ "//targets/mimxrt595_evk_freertos:sdk",
+ ]
+ }
+
+ pw_test_group("tests") {
+ tests = [
+ ":spi_test",
+ ":flexspi_test",
+ ]
+ }
+} else {
+ pw_test_group("tests") {
+ tests = []
+ }
+}
+
+pw_doc_group("docs") {
+ sources = [ "docs.rst" ]
+}
diff --git a/pw_spi_mcuxpresso/OWNERS b/pw_spi_mcuxpresso/OWNERS
new file mode 100644
index 000000000..a9c2709ad
--- /dev/null
+++ b/pw_spi_mcuxpresso/OWNERS
@@ -0,0 +1,2 @@
+afoxley@google.com
+tonymd@google.com
diff --git a/pw_spi_mcuxpresso/docs.rst b/pw_spi_mcuxpresso/docs.rst
new file mode 100644
index 000000000..c286dfe73
--- /dev/null
+++ b/pw_spi_mcuxpresso/docs.rst
@@ -0,0 +1,61 @@
+.. _module-pw_spi_mcuxpresso:
+
+=================
+pw_spi_mcuxpresso
+=================
+``pw_spi_mcuxpresso`` implements the :ref:`module-pw_spi` interface using the
+NXP MCUXpresso SDK.
+
+There are two implementations corresponding to the SPI and FLEXIO_SPI drivers in the
+SDK. SPI transfer can be configured to use a blocking (by polling) method or
+non-blocking under the covers. The API is synchronous regardless.
+
+-----
+Setup
+-----
+Use of this module requires setting up the MCUXpresso SDK for use with Pigweed. Follow
+the steps in :ref:`module-pw_build_mcuxpresso` to create a ``pw_source_set`` for an
+MCUXpresso SDK. Include the GPIO and PINT driver components in this SDK definition.
+
+This example shows what your SDK setup would look like if using an RT595 EVK.
+
+.. code-block:: text
+
+ import("$dir_pw_third_party/mcuxpresso/mcuxpresso.gni")
+
+ pw_mcuxpresso_sdk("sample_project_sdk") {
+ manifest = "$dir_pw_third_party/mcuxpresso/evkmimxrt595/EVK-MIMXRT595_manifest_v3_8.xml"
+ include = [
+ "component.serial_manager_uart.MIMXRT595S",
+ "platform.drivers.flexio_spi.MIMXRT595S",
+ "platform.drivers.flexspi.MIMXRT595S",
+ "project_template.evkmimxrt595.MIMXRT595S",
+ "utility.debug_console.MIMXRT595S",
+ ]
+ }
+
+Next, specify the ``pw_third_party_mcuxpresso_SDK`` GN global variable to specify
+the name of this source set. Edit your GN args with ``gn args out``.
+
+.. code-block:: text
+
+ pw_third_party_mcuxpresso_SDK = "//targets/mimxrt595_evk:sample_project_sdk"
+
+Then, depend on this module in your BUILD.gn to use.
+
+.. code-block:: text
+
+ deps = [ dir_pw_spi_mcuxpresso ]
+
+-------
+Example
+-------
+Example write using the FLEXIO_SPI initiator:
+
+.. code-block:: text
+
+ McuxpressoFlexIoInitiator spi(
+ flexio_spi_config, CLOCK_GetFlexioClkFreq(), baud_rate_bps, blocking);
+ spi.Configure(configuration);
+
+ spi.WriteRead(source, destination);
diff --git a/pw_spi_mcuxpresso/flexspi.cc b/pw_spi_mcuxpresso/flexspi.cc
new file mode 100644
index 000000000..d0472592e
--- /dev/null
+++ b/pw_spi_mcuxpresso/flexspi.cc
@@ -0,0 +1,221 @@
+// 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.
+
+#include "pw_spi_mcuxpresso/flexspi.h"
+
+#include <cinttypes>
+#include <mutex>
+
+#include "fsl_flexio_spi.h"
+#include "fsl_gpio.h"
+#include "pw_chrono/system_clock.h"
+#include "pw_log/log.h"
+#include "pw_spi/initiator.h"
+#include "pw_status/status.h"
+#include "pw_status/try.h"
+
+namespace pw::spi {
+namespace {
+
+using namespace ::std::literals::chrono_literals;
+constexpr auto kMaxWait = chrono::SystemClock::for_at_least(1000ms);
+
+Status ToPwStatus(int32_t status) {
+ switch (status) {
+ // Intentional fall-through
+ case kStatus_Success:
+ case kStatus_FLEXIO_SPI_Idle:
+ return OkStatus();
+ case kStatus_ReadOnly:
+ return Status::PermissionDenied();
+ case kStatus_OutOfRange:
+ return Status::OutOfRange();
+ case kStatus_InvalidArgument:
+ return Status::InvalidArgument();
+ case kStatus_Timeout:
+ return Status::DeadlineExceeded();
+ case kStatus_NoTransferInProgress:
+ return Status::FailedPrecondition();
+ // Intentional fall-through
+ case kStatus_Fail:
+ default:
+ PW_LOG_ERROR("Mcuxpresso FlexSPI unknown error code: %" PRId32, status);
+ return Status::Unknown();
+ }
+}
+
+} // namespace
+
+// inclusive-language: disable
+
+McuxpressoFlexIoInitiator::~McuxpressoFlexIoInitiator() {
+ if (is_initialized()) {
+ FLEXIO_SPI_MasterDeinit(&flexio_spi_config_);
+ }
+}
+
+void McuxpressoFlexIoInitiator::ConfigureClock(
+ flexio_spi_master_config_t* masterConfig, ClockPolarity clockPolarity) {
+ flexio_timer_config_t timerConfig = {};
+ uint16_t timerDiv = 0;
+ uint16_t timerCmp = 0;
+
+ // Rather than modify the flexio_spi driver code to support negative clock
+ // polarity, we duplicate the clock setup here to add support for inverting
+ // the output for SPI mode CPOL=1.
+ timerConfig.triggerSelect =
+ FLEXIO_TIMER_TRIGGER_SEL_SHIFTnSTAT(flexio_spi_config_.shifterIndex[0]);
+ timerConfig.triggerPolarity = kFLEXIO_TimerTriggerPolarityActiveLow;
+ timerConfig.triggerSource = kFLEXIO_TimerTriggerSourceInternal;
+ timerConfig.pinConfig = kFLEXIO_PinConfigOutput;
+ timerConfig.pinSelect = flexio_spi_config_.SCKPinIndex;
+ if (clockPolarity == ClockPolarity::kActiveLow) {
+ timerConfig.pinPolarity = kFLEXIO_PinActiveLow;
+ } else {
+ timerConfig.pinPolarity = kFLEXIO_PinActiveHigh;
+ }
+
+ timerConfig.timerMode = kFLEXIO_TimerModeDual8BitBaudBit;
+ timerConfig.timerOutput = kFLEXIO_TimerOutputZeroNotAffectedByReset;
+ timerConfig.timerDecrement = kFLEXIO_TimerDecSrcOnFlexIOClockShiftTimerOutput;
+ timerConfig.timerReset = kFLEXIO_TimerResetNever;
+ timerConfig.timerDisable = kFLEXIO_TimerDisableOnTimerCompare;
+ timerConfig.timerEnable = kFLEXIO_TimerEnableOnTriggerHigh;
+ timerConfig.timerStop = kFLEXIO_TimerStopBitEnableOnTimerDisable;
+ timerConfig.timerStart = kFLEXIO_TimerStartBitEnabled;
+
+ timerDiv = static_cast<uint16_t>(src_clock_hz_ / masterConfig->baudRate_Bps);
+ timerDiv = timerDiv / 2U - 1U;
+
+ timerCmp = (static_cast<uint16_t>(masterConfig->dataMode) * 2U - 1U) << 8U;
+ timerCmp |= timerDiv;
+
+ timerConfig.timerCompare = timerCmp;
+
+ FLEXIO_SetTimerConfig(flexio_spi_config_.flexioBase,
+ flexio_spi_config_.timerIndex[0],
+ &timerConfig);
+}
+
+void McuxpressoFlexIoInitiator::SpiCallback(FLEXIO_SPI_Type*,
+ flexio_spi_master_handle_t*,
+ status_t status,
+ void* context) {
+ auto* driver = static_cast<McuxpressoFlexIoInitiator*>(context);
+ driver->last_transfer_status_ = ToPwStatus(status);
+ driver->transfer_semaphore_.release();
+}
+
+Status McuxpressoFlexIoInitiator::Configure(const Config& config) {
+ if (current_config_ && config == *current_config_) {
+ return OkStatus();
+ }
+
+ flexio_spi_master_config_t master_config = {};
+ FLEXIO_SPI_MasterGetDefaultConfig(&master_config);
+
+ RESET_ClearPeripheralReset(kFLEXIO_RST_SHIFT_RSTn);
+
+ if (config.phase == ClockPhase::kRisingEdge) {
+ master_config.phase = kFLEXIO_SPI_ClockPhaseFirstEdge;
+ } else {
+ master_config.phase = kFLEXIO_SPI_ClockPhaseSecondEdge;
+ }
+
+ master_config.enableMaster = true;
+ master_config.baudRate_Bps = baud_rate_bps_;
+
+ switch (config.bits_per_word()) {
+ case 8:
+ master_config.dataMode = kFLEXIO_SPI_8BitMode;
+ if (config.bit_order == BitOrder::kMsbFirst) {
+ transfer_flags_ = kFLEXIO_SPI_8bitMsb;
+ } else {
+ transfer_flags_ = kFLEXIO_SPI_8bitLsb;
+ }
+ break;
+ case 16:
+ master_config.dataMode = kFLEXIO_SPI_16BitMode;
+ if (config.bit_order == BitOrder::kMsbFirst) {
+ transfer_flags_ = kFLEXIO_SPI_16bitMsb;
+ } else {
+ transfer_flags_ = kFLEXIO_SPI_16bitLsb;
+ }
+ break;
+ default:
+ return Status::InvalidArgument();
+ }
+
+ std::lock_guard lock(mutex_);
+ FLEXIO_SPI_MasterInit(&flexio_spi_config_, &master_config, src_clock_hz_);
+ ConfigureClock(&master_config, config.polarity);
+
+ const auto status = ToPwStatus(FLEXIO_SPI_MasterTransferCreateHandle(
+ &flexio_spi_config_,
+ &driver_handle_,
+ McuxpressoFlexIoInitiator::SpiCallback,
+ this));
+
+ if (status == OkStatus()) {
+ current_config_.emplace(config);
+ }
+ return status;
+}
+
+Status McuxpressoFlexIoInitiator::WriteRead(ConstByteSpan write_buffer,
+ ByteSpan read_buffer) {
+ flexio_spi_transfer_t transfer = {};
+ transfer.txData =
+ reinterpret_cast<uint8_t*>(const_cast<std::byte*>(write_buffer.data()));
+ transfer.rxData = reinterpret_cast<uint8_t*>(read_buffer.data());
+ if (write_buffer.data() == nullptr && read_buffer.data() != nullptr) {
+ // Read only transaction
+ transfer.dataSize = read_buffer.size();
+ } else if (read_buffer.data() == nullptr && write_buffer.data() != nullptr) {
+ // Write only transaction
+ transfer.dataSize = write_buffer.size();
+ } else {
+ // Take smallest as size of transaction
+ transfer.dataSize = write_buffer.size() < read_buffer.size()
+ ? write_buffer.size()
+ : read_buffer.size();
+ }
+ transfer.flags = transfer_flags_;
+
+ std::lock_guard lock(mutex_);
+ if (!current_config_) {
+ PW_LOG_ERROR("Mcuxpresso FlexSPI must be configured before use.");
+ return Status::FailedPrecondition();
+ }
+ if (blocking_) {
+ return ToPwStatus(
+ FLEXIO_SPI_MasterTransferBlocking(&flexio_spi_config_, &transfer));
+ }
+
+ PW_TRY(ToPwStatus(FLEXIO_SPI_MasterTransferNonBlocking(
+ &flexio_spi_config_, &driver_handle_, &transfer)));
+
+ if (!transfer_semaphore_.try_acquire_for(kMaxWait)) {
+ return Status::DeadlineExceeded();
+ }
+ return last_transfer_status_;
+}
+
+Status McuxpressoFlexIoChipSelector::SetActive(bool active) {
+ return pin_.SetState(active ? digital_io::State::kInactive
+ : digital_io::State::kActive);
+}
+// inclusive-language: enable
+
+} // namespace pw::spi
diff --git a/pw_spi_mcuxpresso/flexspi_test.cc b/pw_spi_mcuxpresso/flexspi_test.cc
new file mode 100644
index 000000000..755c2a00d
--- /dev/null
+++ b/pw_spi_mcuxpresso/flexspi_test.cc
@@ -0,0 +1,97 @@
+// 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.
+
+#include "pw_spi_mcuxpresso/flexspi.h"
+
+#include "board.h"
+#include "gtest/gtest.h"
+#include "pw_bytes/array.h"
+#include "pw_status/status.h"
+
+namespace pw::spi {
+namespace {
+
+FLEXIO_SPI_Type flexio_spi_config = {
+ .flexioBase = reinterpret_cast<FLEXIO_Type*>(FLEXIO0),
+ .SDOPinIndex = 13,
+ .SDIPinIndex = 14,
+ .SCKPinIndex = 15,
+ .CSnPinIndex = 12,
+ .shifterIndex = {0, 2},
+ .timerIndex = {0, 1}};
+constexpr uint32_t baud_rate_bps = 500000;
+constexpr Config configuration{.polarity = ClockPolarity::kActiveLow,
+ .phase = ClockPhase::kFallingEdge,
+ .bits_per_word = BitsPerWord(8),
+ .bit_order = BitOrder::kMsbFirst};
+
+TEST(Configure, ConfigurationSuccess) {
+ McuxpressoFlexIoInitiator spi(
+ flexio_spi_config, CLOCK_GetFlexioClkFreq(), baud_rate_bps);
+ auto status = spi.Configure(configuration);
+
+ EXPECT_EQ(status, OkStatus());
+}
+
+TEST(Configure, RepeatedConfigurationSuccess) {
+ McuxpressoFlexIoInitiator spi(
+ flexio_spi_config, CLOCK_GetFlexioClkFreq(), baud_rate_bps);
+ auto status = spi.Configure(configuration);
+ EXPECT_EQ(status, OkStatus());
+
+ status = spi.Configure(configuration);
+ EXPECT_EQ(status, OkStatus());
+}
+
+TEST(ReadWrite, PollingWriteSuccess) {
+ const auto blocking = true;
+ constexpr auto source = bytes::Array<0x01, 0x02, 0x03, 0x04, 0x05>();
+ static auto destination = bytes::Array<0xff, 0xff, 0xff, 0xff, 0xff>();
+
+ McuxpressoFlexIoInitiator spi(
+ flexio_spi_config, CLOCK_GetFlexioClkFreq(), baud_rate_bps, blocking);
+ auto status = spi.Configure(configuration);
+ ASSERT_EQ(status, OkStatus());
+
+ status = spi.WriteRead(source, destination);
+ EXPECT_EQ(status, OkStatus());
+}
+
+TEST(ReadWrite, IRQWriteSuccess) {
+ const auto blocking = false;
+ constexpr auto source = bytes::Array<0x01, 0x02, 0x03, 0x04, 0x05>();
+ static auto destination = bytes::Array<0xff, 0xff, 0xff, 0xff, 0xff>();
+ McuxpressoFlexIoInitiator spi(
+ flexio_spi_config, CLOCK_GetFlexioClkFreq(), baud_rate_bps, blocking);
+ auto status = spi.Configure(configuration);
+ ASSERT_EQ(status, OkStatus());
+
+ status = spi.WriteRead(source, destination);
+ EXPECT_EQ(status, OkStatus());
+}
+
+TEST(ReadWrite, WriteOnlySuccess) {
+ const auto blocking = false;
+ constexpr auto source = bytes::Array<0x01, 0x02, 0x03, 0x04, 0x05>();
+ McuxpressoFlexIoInitiator spi(
+ flexio_spi_config, CLOCK_GetFlexioClkFreq(), baud_rate_bps, blocking);
+ auto status = spi.Configure(configuration);
+ ASSERT_EQ(status, OkStatus());
+
+ status = spi.WriteRead(source, {});
+ EXPECT_EQ(status, OkStatus());
+}
+
+} // namespace
+} // namespace pw::spi
diff --git a/pw_spi_mcuxpresso/public/pw_spi_mcuxpresso/flexspi.h b/pw_spi_mcuxpresso/public/pw_spi_mcuxpresso/flexspi.h
new file mode 100644
index 000000000..a7f14eeb2
--- /dev/null
+++ b/pw_spi_mcuxpresso/public/pw_spi_mcuxpresso/flexspi.h
@@ -0,0 +1,86 @@
+// 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.
+
+#pragma once
+
+#include "fsl_flexio_spi.h"
+#include "pw_digital_io/digital_io.h"
+#include "pw_spi/chip_selector.h"
+#include "pw_spi/initiator.h"
+#include "pw_status/status.h"
+#include "pw_sync/binary_semaphore.h"
+#include "pw_sync/lock_annotations.h"
+#include "pw_sync/mutex.h"
+
+namespace pw::spi {
+
+// Mcuxpresso SDK implementation of the FLEXIO SPI Initiator
+class McuxpressoFlexIoInitiator : public Initiator {
+ public:
+ McuxpressoFlexIoInitiator(FLEXIO_SPI_Type flexio_spi_config,
+ uint32_t src_clock_hz,
+ uint32_t baud_rate_bps,
+ bool blocking = true)
+ : flexio_spi_config_(flexio_spi_config),
+ src_clock_hz_(src_clock_hz),
+ baud_rate_bps_(baud_rate_bps),
+ blocking_(blocking) {}
+ ~McuxpressoFlexIoInitiator();
+
+ // Implements pw::spi::Initiator
+ pw::Status Configure(const Config& config) PW_LOCKS_EXCLUDED(mutex_) override;
+ pw::Status WriteRead(ConstByteSpan write_buffer, ByteSpan read_buffer)
+ PW_LOCKS_EXCLUDED(mutex_) override;
+
+ private:
+ // inclusive-language: disable
+ static void SpiCallback(FLEXIO_SPI_Type*,
+ flexio_spi_master_handle_t*,
+ status_t status,
+ void* context);
+ // Add support to FLEXIO_SPI for negative clock polarity.
+ void ConfigureClock(flexio_spi_master_config_t* masterConfig,
+ ClockPolarity clockPolarity);
+
+ bool is_initialized() { return !!current_config_; }
+
+ std::optional<const Config> current_config_;
+ FLEXIO_SPI_Type flexio_spi_config_;
+ flexio_spi_master_handle_t driver_handle_;
+ // inclusive-language: enable
+ sync::BinarySemaphore transfer_semaphore_;
+ sync::Mutex mutex_;
+ Status last_transfer_status_;
+ uint32_t src_clock_hz_;
+ uint32_t baud_rate_bps_;
+ bool blocking_;
+ uint8_t transfer_flags_;
+};
+
+// Mcuxpresso userspace implementation of SPI ChipSelector. Implemented using
+// GPIO so as to support manual control of chip select. GPIO pin passed in
+// should be already initialized and ungated.
+class McuxpressoFlexIoChipSelector : public ChipSelector {
+ public:
+ explicit McuxpressoFlexIoChipSelector(digital_io::DigitalOut& pin)
+ : pin_(pin) {}
+
+ // Implements pw::spi::ChipSelector
+ pw::Status SetActive(bool active) override;
+
+ private:
+ digital_io::DigitalOut& pin_;
+};
+
+} // namespace pw::spi
diff --git a/pw_spi_mcuxpresso/public/pw_spi_mcuxpresso/spi.h b/pw_spi_mcuxpresso/public/pw_spi_mcuxpresso/spi.h
new file mode 100644
index 000000000..8b245f381
--- /dev/null
+++ b/pw_spi_mcuxpresso/public/pw_spi_mcuxpresso/spi.h
@@ -0,0 +1,99 @@
+// 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.
+
+#pragma once
+
+#include <cinttypes>
+#include <mutex>
+#include <optional>
+
+#include "fsl_spi.h"
+#include "pw_spi/chip_selector.h"
+#include "pw_spi/initiator.h"
+#include "pw_status/status.h"
+#include "pw_sync/binary_semaphore.h"
+#include "pw_sync/lock_annotations.h"
+#include "pw_sync/mutex.h"
+
+namespace pw::spi {
+
+// Mcuxpresso SDK implementation of the SPI Initiator
+class McuxpressoInitiator : public Initiator {
+ public:
+ McuxpressoInitiator(SPI_Type* register_map,
+ uint32_t max_speed_hz,
+ uint32_t baud_rate_bps,
+ bool blocking = true)
+ : register_map_(register_map),
+ max_speed_hz_(max_speed_hz),
+ baud_rate_bps_(baud_rate_bps),
+ blocking_(blocking) {}
+ ~McuxpressoInitiator();
+
+ // Implements pw::spi::Initiator
+ Status Configure(const Config& config) PW_LOCKS_EXCLUDED(mutex_) override;
+ Status WriteRead(ConstByteSpan write_buffer, ByteSpan read_buffer)
+ PW_LOCKS_EXCLUDED(mutex_) override;
+
+ Status SetChipSelect(uint32_t pin) PW_LOCKS_EXCLUDED(mutex_);
+
+ private:
+ // inclusive-language: disable
+ static void SpiCallback(SPI_Type* base,
+ spi_master_handle_t* driver_handle,
+ status_t status,
+ void* context);
+
+ Status DoConfigure(const Config& config,
+ const std::lock_guard<sync::Mutex>& lock);
+
+ bool is_initialized() { return !!current_config_; }
+
+ SPI_Type* register_map_;
+ spi_master_handle_t driver_handle_;
+ // inclusive-language: enable
+ sync::BinarySemaphore transfer_semaphore_;
+ sync::Mutex mutex_;
+ Status last_transfer_status_;
+ uint32_t max_speed_hz_;
+ uint32_t baud_rate_bps_;
+ bool blocking_;
+ std::optional<const Config> current_config_;
+ uint32_t pin_ = 0;
+};
+
+// Mcuxpresso userspace implementation of SPI ChipSelector
+// NOTE: This implementation deviates from the expected for this interface.
+// It only specifies which chipselect pin should be activated and does not
+// activate the pin itself. Activation of the pin is handled at a lower level by
+// the Mcuxpresso vendor driver.
+// This chipselector may only be used with a single McuxpressoInitiator
+class McuxpressoChipSelector : public ChipSelector {
+ public:
+ McuxpressoChipSelector(McuxpressoInitiator& initiator, uint32_t pin)
+ : initiator_(initiator), pin_(pin) {}
+
+ // Implements pw::spi::ChipSelector
+ // Instead of directly activating the cs line, this informs the underlying
+ // driver to do so.
+ Status SetActive(bool active) override {
+ return initiator_.SetChipSelect(pin_);
+ }
+
+ private:
+ McuxpressoInitiator& initiator_;
+ uint32_t pin_;
+};
+
+} // namespace pw::spi
diff --git a/pw_spi_mcuxpresso/spi.cc b/pw_spi_mcuxpresso/spi.cc
new file mode 100644
index 000000000..d3735521e
--- /dev/null
+++ b/pw_spi_mcuxpresso/spi.cc
@@ -0,0 +1,184 @@
+// 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.
+
+#include "public/pw_spi_mcuxpresso/spi.h"
+
+#include <cinttypes>
+#include <mutex>
+
+#include "pw_assert/check.h"
+#include "pw_chrono/system_clock.h"
+#include "pw_log/log.h"
+#include "pw_spi_mcuxpresso/spi.h"
+#include "pw_status/status.h"
+#include "pw_status/try.h"
+
+namespace pw::spi {
+namespace {
+
+using namespace ::std::literals::chrono_literals;
+constexpr auto kMaxWait = pw::chrono::SystemClock::for_at_least(1000ms);
+
+pw::Status ToPwStatus(int32_t status) {
+ switch (status) {
+ // Intentional fall-through
+ case kStatus_Success:
+ case kStatus_SPI_Idle:
+ return pw::OkStatus();
+ case kStatus_ReadOnly:
+ return pw::Status::PermissionDenied();
+ case kStatus_OutOfRange:
+ return pw::Status::OutOfRange();
+ case kStatus_InvalidArgument:
+ return pw::Status::InvalidArgument();
+ case kStatus_Timeout:
+ return pw::Status::DeadlineExceeded();
+ case kStatus_NoTransferInProgress:
+ return pw::Status::FailedPrecondition();
+ // Intentional fall-through
+ case kStatus_Fail:
+ default:
+ PW_LOG_ERROR("Mcuxpresso SPI unknown error code: %d",
+ static_cast<int>(status));
+ return pw::Status::Unknown();
+ }
+}
+
+} // namespace
+
+McuxpressoInitiator::~McuxpressoInitiator() {
+ if (is_initialized()) {
+ SPI_Deinit(register_map_);
+ }
+}
+
+// inclusive-language: disable
+void McuxpressoInitiator::SpiCallback(SPI_Type*,
+ spi_master_handle_t*,
+ status_t status,
+ void* context) {
+ auto* driver = static_cast<McuxpressoInitiator*>(context);
+ driver->last_transfer_status_ = ToPwStatus(status);
+ driver->transfer_semaphore_.release();
+}
+
+Status McuxpressoInitiator::Configure(const Config& config) {
+ std::lock_guard lock(mutex_);
+ if (current_config_ && config == *current_config_) {
+ return OkStatus();
+ }
+ return DoConfigure(config, lock);
+}
+
+Status McuxpressoInitiator::DoConfigure(const Config& config,
+ const std::lock_guard<sync::Mutex>&) {
+ spi_master_config_t master_config = {};
+ SPI_MasterGetDefaultConfig(&master_config);
+
+ if (config.polarity == ClockPolarity::kActiveLow) {
+ master_config.polarity = kSPI_ClockPolarityActiveLow;
+ } else {
+ master_config.polarity = kSPI_ClockPolarityActiveHigh;
+ }
+ if (config.phase == ClockPhase::kRisingEdge) {
+ master_config.phase = kSPI_ClockPhaseFirstEdge;
+ } else {
+ master_config.phase = kSPI_ClockPhaseSecondEdge;
+ }
+
+ if (config.bit_order == BitOrder::kMsbFirst) {
+ master_config.direction = kSPI_MsbFirst;
+ } else {
+ master_config.direction = kSPI_LsbFirst;
+ }
+
+ master_config.enableMaster = true;
+ master_config.baudRate_Bps = baud_rate_bps_;
+
+ master_config.sselNum = static_cast<spi_ssel_t>(pin_);
+ master_config.sselPol = static_cast<spi_spol_t>(kSPI_SpolActiveAllLow);
+
+ // Data width enum value is 1 value below bits_per_word. i.e. 0 = 1;
+ constexpr uint8_t kMinBitsPerWord = 4;
+ constexpr uint8_t kMaxBitsPerWord = 16;
+ PW_CHECK(config.bits_per_word() >= kMinBitsPerWord &&
+ config.bits_per_word() <= kMaxBitsPerWord);
+ master_config.dataWidth =
+ static_cast<_spi_data_width>(config.bits_per_word() - 1);
+
+ SPI_MasterInit(register_map_, &master_config, max_speed_hz_);
+ const auto status = ToPwStatus(SPI_MasterTransferCreateHandle(
+ register_map_, &driver_handle_, McuxpressoInitiator::SpiCallback, this));
+
+ if (status == OkStatus()) {
+ current_config_.emplace(config);
+ }
+ return status;
+}
+
+Status McuxpressoInitiator::WriteRead(ConstByteSpan write_buffer,
+ ByteSpan read_buffer) {
+ spi_transfer_t transfer = {};
+
+ transfer.txData =
+ reinterpret_cast<uint8_t*>(const_cast<std::byte*>(write_buffer.data()));
+ transfer.rxData = reinterpret_cast<uint8_t*>(read_buffer.data());
+ if (write_buffer.data() == nullptr && read_buffer.data() != nullptr) {
+ // Read only transaction
+ transfer.dataSize = read_buffer.size();
+ } else if (read_buffer.data() == nullptr && write_buffer.data() != nullptr) {
+ // Write only transaction
+ transfer.dataSize = write_buffer.size();
+ } else {
+ // Take the smallest as the size of transaction
+ transfer.dataSize = write_buffer.size() < read_buffer.size()
+ ? write_buffer.size()
+ : read_buffer.size();
+ }
+ transfer.configFlags = kSPI_FrameAssert;
+
+ std::lock_guard lock(mutex_);
+ if (!current_config_) {
+ PW_LOG_ERROR("Mcuxpresso SPI must be configured before use.");
+ return Status::FailedPrecondition();
+ }
+ if (blocking_) {
+ return ToPwStatus(SPI_MasterTransferBlocking(register_map_, &transfer));
+ }
+
+ PW_TRY(ToPwStatus(SPI_MasterTransferNonBlocking(
+ register_map_, &driver_handle_, &transfer)));
+
+ if (!transfer_semaphore_.try_acquire_for(kMaxWait)) {
+ return Status::DeadlineExceeded();
+ }
+ return last_transfer_status_;
+}
+// inclusive-language: enable
+
+Status McuxpressoInitiator::SetChipSelect(uint32_t pin) {
+ std::lock_guard lock(mutex_);
+ if (pin == pin_) {
+ return OkStatus();
+ }
+ pin_ = pin;
+ // As configuration has not been called, must be called prior to use, and must
+ // itself set chipselect, set will be delayed until configuration.
+ if (!current_config_) {
+ return OkStatus();
+ }
+ return DoConfigure(*current_config_, lock);
+}
+
+} // namespace pw::spi
diff --git a/pw_spi_mcuxpresso/spi_test.cc b/pw_spi_mcuxpresso/spi_test.cc
new file mode 100644
index 000000000..13d82459c
--- /dev/null
+++ b/pw_spi_mcuxpresso/spi_test.cc
@@ -0,0 +1,102 @@
+// 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.
+
+#include "pw_spi_mcuxpresso/spi.h"
+
+#include "board.h"
+#include "gtest/gtest.h"
+#include "pw_bytes/array.h"
+#include "pw_spi/device.h"
+#include "pw_status/status.h"
+
+namespace pw::spi {
+namespace {
+
+auto* spi_base = SPI14;
+constexpr auto kClockNumber = 14;
+constexpr uint32_t baud_rate_bps = 10000000;
+constexpr Config configuration{.polarity = ClockPolarity::kActiveHigh,
+ .phase = ClockPhase::kRisingEdge,
+ .bits_per_word = BitsPerWord(8),
+ .bit_order = BitOrder::kMsbFirst};
+
+TEST(Configure, ConfigurationSuccess) {
+ McuxpressoInitiator spi(
+ spi_base, CLOCK_GetFlexcommClkFreq(kClockNumber), baud_rate_bps);
+ auto status = spi.Configure(configuration);
+
+ EXPECT_EQ(status, OkStatus());
+}
+
+TEST(ReadWrite, PollingWriteSuccess) {
+ const auto blocking = true;
+ constexpr auto source = bytes::Array<0x01, 0x02, 0x03, 0x04, 0x05>();
+ static auto destination = bytes::Array<0xff, 0xff, 0xff, 0xff, 0xff>();
+
+ McuxpressoInitiator spi(spi_base,
+ CLOCK_GetFlexcommClkFreq(kClockNumber),
+ baud_rate_bps,
+ blocking);
+ auto status = spi.Configure(configuration);
+ ASSERT_EQ(status, OkStatus());
+
+ status = spi.WriteRead(source, destination);
+ EXPECT_EQ(status, OkStatus());
+}
+
+TEST(ReadWrite, IRQWriteSuccess) {
+ const auto blocking = false;
+ constexpr auto source = bytes::Array<0x01, 0x02, 0x03, 0x04, 0x05>();
+ static auto destination = bytes::Array<0xff, 0xff, 0xff, 0xff, 0xff>();
+ McuxpressoInitiator spi(spi_base,
+ CLOCK_GetFlexcommClkFreq(kClockNumber),
+ baud_rate_bps,
+ blocking);
+ auto status = spi.Configure(configuration);
+ ASSERT_EQ(status, OkStatus());
+
+ status = spi.WriteRead(source, destination);
+ EXPECT_EQ(status, OkStatus());
+}
+
+TEST(ReadWrite, WriteOnlySuccess) {
+ const auto blocking = false;
+ constexpr auto source = bytes::Array<0x01, 0x02, 0x03, 0x04, 0x05>();
+ McuxpressoInitiator spi(spi_base,
+ CLOCK_GetFlexcommClkFreq(kClockNumber),
+ baud_rate_bps,
+ blocking);
+ auto status = spi.Configure(configuration);
+ ASSERT_EQ(status, OkStatus());
+
+ status = spi.WriteRead(source, {});
+ EXPECT_EQ(status, OkStatus());
+}
+
+TEST(ReadWrite, UseDeviceWriteOnlySuccess) {
+ const auto blocking = false;
+ constexpr auto source = bytes::Array<0x01, 0x02, 0x03, 0x04, 0x05>();
+ McuxpressoInitiator initiator(spi_base,
+ CLOCK_GetFlexcommClkFreq(kClockNumber),
+ baud_rate_bps,
+ blocking);
+ McuxpressoChipSelector spi_selector(initiator, 0);
+ sync::VirtualMutex spi_lock;
+ sync::Borrowable<Initiator> spi(initiator, spi_lock);
+ Device device(spi, configuration, spi_selector);
+
+ EXPECT_EQ(device.WriteRead(source, {}), OkStatus());
+}
+} // namespace
+} // namespace pw::spi
diff --git a/targets/mimxrt595_evk_freertos/BUILD.gn b/targets/mimxrt595_evk_freertos/BUILD.gn
index d3b0cee02..afda494c2 100644
--- a/targets/mimxrt595_evk_freertos/BUILD.gn
+++ b/targets/mimxrt595_evk_freertos/BUILD.gn
@@ -111,6 +111,8 @@ if (pw_third_party_mcuxpresso_SDK == "//targets/mimxrt595_evk_freertos:sdk") {
include = [
"project_template.evkmimxrt595.MIMXRT595S",
"component.serial_manager_uart.MIMXRT595S",
+ "platform.drivers.flexcomm_spi.MIMXRT595S",
+ "platform.drivers.flexio_spi.MIMXRT595S",
"platform.drivers.power.MIMXRT595S",
"platform.drivers.lpc_gpio.MIMXRT595S",
"platform.drivers.pint.MIMXRT595S",