// 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/flexio_spi.h" #include #include #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 FlexIO_SPI 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(src_clock_hz_ / masterConfig->baudRate_Bps); timerDiv = timerDiv / 2U - 1U; timerCmp = (static_cast(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(context); driver->last_transfer_status_ = ToPwStatus(status); driver->transfer_semaphore_.release(); } Status McuxpressoFlexIoInitiator::DoConfigure(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::DoWriteRead(ConstByteSpan write_buffer, ByteSpan read_buffer) { flexio_spi_transfer_t transfer = {}; transfer.txData = reinterpret_cast(const_cast(write_buffer.data())); transfer.rxData = reinterpret_cast(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 FlexIO_SPI 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