blob: 0c2edde2e192c2587f1b3c6ef94c5dde37107c22 [file]
// Copyright 2026 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.
#define PW_LOG_MODULE_NAME "FLEXIO_I2C"
#include "pw_i2c_mcuxpresso/flexio_initiator.h"
#include <mutex>
// inclusive-language: disable
#include "fsl_flexio_i2c_master.h"
#include "pw_assert/check.h"
#include "pw_chrono/system_clock.h"
#include "pw_function/scope_guard.h"
#include "pw_log/log.h"
#include "pw_status/status.h"
#include "pw_status/try.h"
namespace pw::i2c {
namespace {
Status HalStatusToPwStatus(status_t status) {
switch (status) {
case kStatus_Success:
return OkStatus();
case kStatus_FLEXIO_I2C_Busy:
case kStatus_FLEXIO_I2C_Nak:
return Status::Unavailable();
case kStatus_InvalidArgument:
return Status::Internal();
case kStatus_FLEXIO_I2C_Timeout:
return Status::DeadlineExceeded();
default:
return Status::Internal();
}
}
} // namespace
void FlexIoMcuxpressoInitiator::Enable() {
std::lock_guard lock(mutex_);
// Acquire the clock_tree element. Note that this function only requires the
// IP clock and not the functional clock. However, ClockMcuxpressoClockIp
// only provides the combined element, so that's what we use here.
// Make sure it's released on any function exits through a scoped guard.
PW_CHECK_OK(clock_tree_element_.Acquire());
pw::ScopeGuard guard([this] { clock_tree_element_.Release().IgnoreError(); });
// Initiate FlexIO peripheral.
flexio_config_t flexio_config;
FLEXIO_GetDefaultConfig(&flexio_config);
FLEXIO_Init(base_.flexioBase, &flexio_config);
flexio_i2c_master_config_t masterConfig;
FLEXIO_I2C_MasterGetDefaultConfig(&masterConfig);
masterConfig.baudRate_Bps = config_.baud_rate_bps;
// Initiate FlexIo I2C Master.
status_t status = FLEXIO_I2C_MasterInit(
&base_, &masterConfig, CLOCK_GetFreq(config_.clock_name));
if (status != kStatus_Success) {
PW_LOG_ERROR("FLEXIO_I2C_MasterInit failed: %d", status);
FLEXIO_Deinit(base_.flexioBase);
return;
}
// Create the handle for the non-blocking transfer and register callback.
status = FLEXIO_I2C_MasterTransferCreateHandle(
&base_,
&handle_,
FlexIoMcuxpressoInitiator::TransferCompleteCallback,
this);
if (status != kStatus_Success) {
PW_LOG_ERROR("FLEXIO_I2C_MasterTransferCreateHandle failed: %d", status);
FLEXIO_I2C_MasterDeinit(&base_);
FLEXIO_Deinit(base_.flexioBase);
return;
}
enabled_ = true;
}
void FlexIoMcuxpressoInitiator::Disable() {
std::lock_guard lock(mutex_);
// Acquire the clock_tree element. Note that this function only requires the
// IP clock and not the functional clock. However, ClockMcuxpressoClockIp
// only provides the combined element, so that's what we use here.
// Make sure it's released on any function exits through a scoped guard.
PW_CHECK_OK(clock_tree_element_.Acquire());
pw::ScopeGuard guard([this] { clock_tree_element_.Release().IgnoreError(); });
FLEXIO_I2C_MasterDeinit(&base_);
FLEXIO_Deinit(base_.flexioBase);
enabled_ = false;
}
FlexIoMcuxpressoInitiator::~FlexIoMcuxpressoInitiator() { Disable(); }
void FlexIoMcuxpressoInitiator::TransferCompleteCallback(
FLEXIO_I2C_Type* /*&base*/,
flexio_i2c_master_handle_t* /*handle*/,
status_t status,
void* initiator_ptr) {
FlexIoMcuxpressoInitiator* initiator =
static_cast<FlexIoMcuxpressoInitiator*>(initiator_ptr);
initiator->transfer_status_ = status;
// We cannot release clock_tree_element_ here since we are in an ISR.
// It is released where callback_complete_notification_ is waited on.
initiator->callback_complete_notification_.release();
}
Status FlexIoMcuxpressoInitiator::InitiateTransferUntil(
chrono::SystemClock::time_point deadline,
flexio_i2c_master_transfer_t* transfer) {
// Acquire the clock_tree_element. Use a scoped guard so it's released from
// any function return.
PW_CHECK_OK(clock_tree_element_.Acquire());
pw::ScopeGuard guard([this] { clock_tree_element_.Release().IgnoreError(); });
if (const auto status =
FLEXIO_I2C_MasterTransferNonBlocking(&base_, &handle_, transfer);
status != kStatus_Success) {
PW_LOG_DEBUG("Failed to initiate FLEXIO I2C transfer: %d", status);
return HalStatusToPwStatus(status);
}
if (!callback_complete_notification_.try_acquire_until(deadline)) {
FLEXIO_I2C_MasterTransferAbort(&base_, &handle_);
return Status::DeadlineExceeded();
}
const status_t transfer_status = transfer_status_;
const Status pw_status = HalStatusToPwStatus(transfer_status);
if (!pw_status.ok()) {
PW_LOG_DEBUG("FLEXIO I2C transfer failed with status: %d", transfer_status);
}
return pw_status;
}
// Performs blocking I2C write, read and read-after-write depending on the tx
// and rx buffer states.
Status FlexIoMcuxpressoInitiator::DoWriteReadFor(
Address device_address,
ConstByteSpan tx_buffer,
ByteSpan rx_buffer,
chrono::SystemClock::duration timeout) {
chrono::SystemClock::time_point deadline =
chrono::SystemClock::TimePointAfterAtLeast(timeout);
std::lock_guard lock(mutex_);
if (!enabled_) {
return Status::FailedPrecondition();
}
auto transfer = flexio_i2c_master_transfer_t{
.flags = 0, // Transfer flag is reserved for FlexIO I2C.
.slaveAddress = device_address.GetSevenBit(), // Will CHECK if >7 bits.
.subaddress = 0,
.subaddressSize = 0,
};
if (!tx_buffer.empty() && rx_buffer.empty()) {
// Write
transfer.direction = kFLEXIO_I2C_Write;
transfer.data = const_cast<uint8_t*>(
reinterpret_cast<const uint8_t*>(tx_buffer.data()));
transfer.dataSize = tx_buffer.size();
} else if (tx_buffer.empty() && !rx_buffer.empty()) {
// Read
transfer.direction = kFLEXIO_I2C_Read;
transfer.data = reinterpret_cast<uint8_t*>(rx_buffer.data());
transfer.dataSize = rx_buffer.size();
} else if (!tx_buffer.empty() && !rx_buffer.empty()) {
// Read-after-write can be implemented as a FlexIo Read transfer with
// subaddress.
if (tx_buffer.size() > 4) {
PW_LOG_WARN(
"FLEXIO I2C read-after-write supports a max of 4 bytes write.");
return Status::InvalidArgument();
}
uint32_t subaddress = 0;
for (size_t i = 0; i < tx_buffer.size(); ++i) {
subaddress =
(subaddress << 8) | static_cast<uint8_t>(tx_buffer.data()[i]);
}
transfer.direction = kFLEXIO_I2C_Read;
transfer.subaddress = subaddress;
transfer.subaddressSize = static_cast<uint8_t>(tx_buffer.size());
transfer.data = reinterpret_cast<uint8_t*>(rx_buffer.data());
transfer.dataSize = rx_buffer.size();
} else {
PW_LOG_WARN(
"FLEXIO I2C transfer called with both tx and rx buffers empty.");
return Status::InvalidArgument();
}
return InitiateTransferUntil(deadline, &transfer);
}
// inclusive-language: enable
} // namespace pw::i2c