// Copyright 2022 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_i2c_mcuxpresso/initiator.h" #include #include "fsl_i2c.h" #include "pw_chrono/system_clock.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_I2C_Nak: case kStatus_I2C_Addr_Nak: return Status::Unavailable(); case kStatus_I2C_InvalidParameter: return Status::InvalidArgument(); case kStatus_I2C_Timeout: return Status::DeadlineExceeded(); default: return Status::Unknown(); } } } // namespace // inclusive-language: disable McuxpressoInitiator::McuxpressoInitiator(I2C_Type* base, uint32_t baud_rate_bps, uint32_t src_clock_hz) : base_(base) { i2c_master_config_t master_config; I2C_MasterGetDefaultConfig(&master_config); master_config.baudRate_Bps = baud_rate_bps; I2C_MasterInit(base_, &master_config, src_clock_hz); // Create the handle for the non-blocking transfer and register callback. I2C_MasterTransferCreateHandle( base_, &handle_, McuxpressoInitiator::TransferCompleteCallback, this); } McuxpressoInitiator::~McuxpressoInitiator() { I2C_MasterDeinit(base_); } void McuxpressoInitiator::TransferCompleteCallback(I2C_Type* base, i2c_master_handle_t* handle, status_t status, void* initiator_ptr) { McuxpressoInitiator& initiator = *static_cast(initiator_ptr); initiator.callback_isl_.lock(); initiator.transfer_status_ = status; initiator.callback_isl_.unlock(); initiator.callback_complete_notification_.release(); } Status McuxpressoInitiator::InitiateNonBlockingTransfer( chrono::SystemClock::duration rw_timeout, i2c_master_transfer_t* transfer) { const status_t status = I2C_MasterTransferNonBlocking(base_, &handle_, transfer); if (status != kStatus_Success) { return HalStatusToPwStatus(status); } if (!callback_complete_notification_.try_acquire_for(rw_timeout)) { I2C_MasterTransferAbort(base_, &handle_); return Status::DeadlineExceeded(); } callback_isl_.lock(); const status_t transfer_status = transfer_status_; callback_isl_.unlock(); return HalStatusToPwStatus(transfer_status); } // Performs non-blocking I2C write, read and read-after-write depending on the // tx and rx buffer states. Status McuxpressoInitiator::DoWriteReadFor( Address device_address, ConstByteSpan tx_buffer, ByteSpan rx_buffer, chrono::SystemClock::duration timeout) { if (timeout <= chrono::SystemClock::duration::zero()) { return Status::DeadlineExceeded(); } const uint8_t address = device_address.GetSevenBit(); std::lock_guard lock(mutex_); if (!tx_buffer.empty() && rx_buffer.empty()) { i2c_master_transfer_t transfer{kI2C_TransferDefaultFlag, address, kI2C_Write, 0, 0, const_cast(tx_buffer.data()), tx_buffer.size()}; return InitiateNonBlockingTransfer(timeout, &transfer); } else if (tx_buffer.empty() && !rx_buffer.empty()) { i2c_master_transfer_t transfer{kI2C_TransferDefaultFlag, address, kI2C_Read, 0, 0, rx_buffer.data(), rx_buffer.size()}; return InitiateNonBlockingTransfer(timeout, &transfer); } else if (!tx_buffer.empty() && !rx_buffer.empty()) { i2c_master_transfer_t w_transfer{kI2C_TransferNoStopFlag, address, kI2C_Write, 0, 0, const_cast(tx_buffer.data()), tx_buffer.size()}; const chrono::SystemClock::time_point deadline = chrono::SystemClock::TimePointAfterAtLeast(timeout); PW_TRY(InitiateNonBlockingTransfer(timeout, &w_transfer)); i2c_master_transfer_t r_transfer{kI2C_TransferRepeatedStartFlag, address, kI2C_Read, 0, 0, rx_buffer.data(), rx_buffer.size()}; const chrono::SystemClock::duration time_remaining = deadline - chrono::SystemClock::now(); if (time_remaining <= chrono::SystemClock::duration::zero()) { // Abort transfer in an unlikely scenario of timeout even with // successful write. I2C_MasterTransferAbort(base_, &handle_); return Status::DeadlineExceeded(); } return InitiateNonBlockingTransfer(time_remaining, &r_transfer); } else { return Status::InvalidArgument(); } } // inclusive-language: enable } // namespace pw::i2c