diff options
author | Xin Li <delphij@google.com> | 2024-01-17 22:13:58 -0800 |
---|---|---|
committer | Xin Li <delphij@google.com> | 2024-01-17 22:13:58 -0800 |
commit | 28d03a2a1cabbe01d7bcb6cf5166c10e50d3c2c6 (patch) | |
tree | c1643be8ab17fc607cea748a8bb1d621a5964873 /pw_spi_mcuxpresso | |
parent | ec2628a6ba2d0ecbe3ac10c8c772f6fc6acc345d (diff) | |
parent | f054515492af5132f685cb23fe11891ee77104c9 (diff) | |
download | pigweed-temp_319669529.tar.gz |
Merge Android 24Q1 Release (ab/11220357)temp_319669529
Bug: 319669529
Merged-In: Iba357b308a79d0c8b560acd4f72b5423c9c83294
Change-Id: Icdf552029fb97a34e83c6dd7799433fc473a2506
Diffstat (limited to 'pw_spi_mcuxpresso')
-rw-r--r-- | pw_spi_mcuxpresso/BUILD.bazel | 61 | ||||
-rw-r--r-- | pw_spi_mcuxpresso/BUILD.gn | 114 | ||||
-rw-r--r-- | pw_spi_mcuxpresso/OWNERS | 2 | ||||
-rw-r--r-- | pw_spi_mcuxpresso/docs.rst | 61 | ||||
-rw-r--r-- | pw_spi_mcuxpresso/flexspi.cc | 221 | ||||
-rw-r--r-- | pw_spi_mcuxpresso/flexspi_test.cc | 97 | ||||
-rw-r--r-- | pw_spi_mcuxpresso/public/pw_spi_mcuxpresso/flexspi.h | 86 | ||||
-rw-r--r-- | pw_spi_mcuxpresso/public/pw_spi_mcuxpresso/spi.h | 99 | ||||
-rw-r--r-- | pw_spi_mcuxpresso/spi.cc | 184 | ||||
-rw-r--r-- | pw_spi_mcuxpresso/spi_test.cc | 102 |
10 files changed, 1027 insertions, 0 deletions
diff --git a/pw_spi_mcuxpresso/BUILD.bazel b/pw_spi_mcuxpresso/BUILD.bazel new file mode 100644 index 000000000..78b54f71f --- /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//targets: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 |