blob: 6c707ddc7a878c8dcf4ab4657f53864d7474e6d2 [file] [log] [blame]
// 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 <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 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<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 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