blob: 808d183ee2f90ed8be03939c73a89e38279da575 [file] [log] [blame]
// Copyright 2024 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/i3c_initiator.h"
#include <mutex>
#include "lib/stdcompat/utility.h"
#include "pw_bytes/span.h"
#include "pw_log/log.h"
#include "pw_status/status.h"
#include "pw_status/try.h"
using cpp23::to_underlying;
namespace pw::i2c {
namespace {
pw::Status HalStatusToPwStatus(status_t status) {
switch (status) {
case kStatus_Success:
return pw::OkStatus();
case kStatus_I3C_Timeout:
return pw::Status::DeadlineExceeded();
case kStatus_I3C_Nak:
case kStatus_I3C_Busy:
case kStatus_I3C_IBIWon:
case kStatus_I3C_WriteAbort:
return pw::Status::Unavailable();
case kStatus_I3C_HdrParityError:
case kStatus_I3C_CrcError:
case kStatus_I3C_MsgError:
return pw::Status::DataLoss();
default:
return pw::Status::Unknown();
}
}
constexpr uint32_t kI3cInitOpenDrainBaudRate = 2000000;
constexpr uint32_t kI3cInitPushPullBaudRate = 4000000;
constexpr bool kI3cInitEnableOpenDrainHigh = false;
constexpr uint8_t kBroadcastAddressRaw = 0x7E;
constexpr pw::i2c::Address kBroadcastAddress =
pw::i2c::Address::SevenBit<kBroadcastAddressRaw>();
std::array kDisecBuffer = {std::byte{0x0b}};
} // namespace
// inclusive-language: disable
void I3cMcuxpressoInitiator::Enable() {
std::lock_guard lock(mutex_);
if (enabled_) {
return;
}
i3c_master_config_t masterConfig;
I3C_MasterGetDefaultConfig(&masterConfig);
masterConfig.baudRate_Hz.i2cBaud = config_.i2c_baud_rate;
masterConfig.baudRate_Hz.i3cOpenDrainBaud = config_.i3c_open_drain_baud_rate;
masterConfig.baudRate_Hz.i3cPushPullBaud = config_.i3c_push_pull_baud_rate;
masterConfig.enableOpenDrainStop = config_.enable_open_drain_stop;
masterConfig.enableOpenDrainHigh = config_.enable_open_drain_high;
I3C_MasterInit(base_, &masterConfig, CLOCK_GetI3cClkFreq());
enabled_ = true;
}
void I3cMcuxpressoInitiator::Disable() {
std::lock_guard lock(mutex_);
if (!enabled_) {
return;
}
I3C_MasterDeinit(base_);
enabled_ = false;
}
pw::Status I3cMcuxpressoInitiator::SetDynamicAddressList(
pw::span<const uint8_t> dynamic_address_list) {
if (i3c_dynamic_address_list_.has_value()) {
PW_LOG_ERROR("i3c_dynamic_address_list_ can only be set once");
return pw::Status::AlreadyExists();
}
pw::Vector<uint8_t, I3C_MAX_DEVCNT> address_list_temp;
size_t dynamic_address_num = dynamic_address_list.size();
if (dynamic_address_num > I3C_MAX_DEVCNT) {
PW_LOG_WARN("Only the first %d dynamic addresses are accepted",
I3C_MAX_DEVCNT);
dynamic_address_num = I3C_MAX_DEVCNT;
}
address_list_temp.resize(dynamic_address_num);
std::copy(dynamic_address_list.begin(),
dynamic_address_list.begin() + dynamic_address_num,
address_list_temp.begin());
i3c_dynamic_address_list_.emplace(address_list_temp);
return pw::OkStatus();
}
pw::Status I3cMcuxpressoInitiator::Initialize() {
std::lock_guard lock(mutex_);
i3c_master_config_t masterConfig;
if (!i3c_dynamic_address_list_.has_value() ||
i3c_dynamic_address_list_->empty()) {
PW_LOG_ERROR("Cannot initialize the bus without dynamic address");
return pw::Status::FailedPrecondition();
}
// Initialize I3C master with low I3C speed to match I3C timing requirement
// (mipi_I3C-Basic_specification_v1-1-1 section 6.2 Table 86 I3C Open Drain
// Timing Parameters)
I3C_MasterGetDefaultConfig(&masterConfig);
masterConfig.baudRate_Hz.i2cBaud = config_.i2c_baud_rate;
masterConfig.baudRate_Hz.i3cOpenDrainBaud = kI3cInitOpenDrainBaudRate;
masterConfig.baudRate_Hz.i3cPushPullBaud = kI3cInitPushPullBaudRate;
masterConfig.enableOpenDrainStop = config_.enable_open_drain_stop;
masterConfig.enableOpenDrainHigh = kI3cInitEnableOpenDrainHigh;
I3C_MasterInit(base_, &masterConfig, CLOCK_GetI3cClkFreq());
// Broadcast RSTDAA
// TODO: b/312487906 - First broadcast CCC receives NANK randomly on random
// devices.
if (pw::Status status = DoTransferCcc(I3cCccAction::kWrite,
I3cCcc::kRstdaaBroadcast,
kBroadcastAddress,
pw::ByteSpan());
!(status.ok())) {
if (status != pw::Status::Unavailable()) {
return status;
}
PW_LOG_WARN("Failed to broadcast first CCC, trying again...");
PW_TRY(DoTransferCcc(I3cCccAction::kWrite,
I3cCcc::kRstdaaBroadcast,
kBroadcastAddress,
pw::ByteSpan()));
}
// Broadcast DISEC 0x0b
PW_TRY(DoTransferCcc(I3cCccAction::kWrite,
I3cCcc::kDisecBroadcast,
kBroadcastAddress,
kDisecBuffer));
// DAA
status_t hal_status = I3C_MasterProcessDAA(base_,
i3c_dynamic_address_list_->data(),
i3c_dynamic_address_list_->size());
if (hal_status != kStatus_Success) {
PW_LOG_ERROR("Failed to initialize the I3C bus...");
}
// Re-initialize I3C master with user provided speed.
I3C_MasterGetDefaultConfig(&masterConfig);
masterConfig.baudRate_Hz.i2cBaud = config_.i2c_baud_rate;
masterConfig.baudRate_Hz.i3cOpenDrainBaud = config_.i3c_open_drain_baud_rate;
masterConfig.baudRate_Hz.i3cPushPullBaud = config_.i3c_push_pull_baud_rate;
masterConfig.enableOpenDrainStop = config_.enable_open_drain_stop;
masterConfig.enableOpenDrainHigh = config_.enable_open_drain_high;
I3C_MasterInit(base_, &masterConfig, CLOCK_GetI3cClkFreq());
return HalStatusToPwStatus(hal_status);
}
pw::Status I3cMcuxpressoInitiator::DoTransferCcc(I3cCccAction rnw,
I3cCcc ccc_id,
pw::i2c::Address address,
pw::ByteSpan buffer) {
status_t status;
i3c_master_transfer_t transfer;
if (!enabled_) {
return pw::Status::FailedPrecondition();
}
if (!(to_underlying(ccc_id) & kCccDirectBit)) { // broadcast
transfer.flags = kI3C_TransferDefaultFlag;
transfer.slaveAddress = kBroadcastAddressRaw;
transfer.direction = kI3C_Write;
transfer.subaddress = static_cast<uint32_t>(ccc_id);
transfer.subaddressSize = 1;
transfer.data = buffer.data();
transfer.dataSize = buffer.size();
transfer.busType = kI3C_TypeI3CSdr;
status = I3C_MasterTransferBlocking(base_, &transfer);
} else { // direct
transfer.flags = kI3C_TransferDefaultFlag;
transfer.slaveAddress = kBroadcastAddressRaw;
transfer.direction = kI3C_Write;
transfer.subaddress = static_cast<uint32_t>(ccc_id);
transfer.subaddressSize = 1;
transfer.data = nullptr;
transfer.dataSize = 0;
transfer.busType = kI3C_TypeI3CSdr;
status = I3C_MasterTransferBlocking(base_, &transfer);
if (status != kStatus_Success) {
return HalStatusToPwStatus(status);
}
transfer.flags = kI3C_TransferRepeatedStartFlag;
transfer.slaveAddress = uint32_t{address.GetSevenBit()};
transfer.direction = (rnw == I3cCccAction::kWrite) ? kI3C_Write : kI3C_Read;
transfer.subaddress = 0;
transfer.subaddressSize = 0;
transfer.data = buffer.data();
transfer.dataSize = buffer.size();
transfer.busType = kI3C_TypeI3CSdr;
status |= I3C_MasterTransferBlocking(base_, &transfer);
}
return HalStatusToPwStatus(status);
}
pw::Status I3cMcuxpressoInitiator::DoWriteReadFor(
pw::i2c::Address address,
pw::ConstByteSpan tx_buffer,
pw::ByteSpan rx_buffer,
pw::chrono::SystemClock::duration) {
std::lock_guard lock(mutex_);
status_t status;
i3c_master_transfer_t transfer;
if (!enabled_) {
return pw::Status::FailedPrecondition();
}
if (std::find(i3c_dynamic_address_list_->begin(),
i3c_dynamic_address_list_->end(),
address.GetSevenBit()) == i3c_dynamic_address_list_->end()) {
transfer.busType = kI3C_TypeI2C;
} else {
transfer.busType = kI3C_TypeI3CSdr;
}
if (!tx_buffer.empty() && rx_buffer.empty()) { // write only
transfer.flags = kI3C_TransferDefaultFlag;
transfer.slaveAddress = address.GetSevenBit();
transfer.direction = kI3C_Write;
transfer.subaddress = 0;
transfer.subaddressSize = 0;
transfer.data = const_cast<std::byte*>(tx_buffer.data());
transfer.dataSize = tx_buffer.size();
transfer.ibiResponse = kI3C_IbiRespNack;
status = I3C_MasterTransferBlocking(base_, &transfer);
} else if (tx_buffer.empty() && !rx_buffer.empty()) { // read only
transfer.flags = kI3C_TransferDefaultFlag;
transfer.slaveAddress = address.GetSevenBit();
transfer.direction = kI3C_Read;
transfer.subaddress = 0;
transfer.subaddressSize = 0;
transfer.data = rx_buffer.data();
transfer.dataSize = rx_buffer.size();
transfer.dataSize = rx_buffer.size();
transfer.ibiResponse = kI3C_IbiRespNack;
status = I3C_MasterTransferBlocking(base_, &transfer);
} else if (!tx_buffer.empty() && !rx_buffer.empty()) { // write and read
transfer.flags = kI3C_TransferDefaultFlag;
transfer.slaveAddress = address.GetSevenBit();
transfer.direction = kI3C_Write;
transfer.subaddress = 0;
transfer.subaddressSize = 0;
transfer.data = const_cast<std::byte*>(tx_buffer.data());
transfer.dataSize = tx_buffer.size();
transfer.ibiResponse = kI3C_IbiRespNack;
status = I3C_MasterTransferBlocking(base_, &transfer);
if (status != kStatus_Success) {
return HalStatusToPwStatus(status);
}
transfer.flags = kI3C_TransferDefaultFlag;
transfer.slaveAddress = address.GetSevenBit();
transfer.direction = kI3C_Read;
transfer.subaddress = 0;
transfer.subaddressSize = 0;
transfer.data = rx_buffer.data();
transfer.dataSize = rx_buffer.size();
transfer.ibiResponse = kI3C_IbiRespNack;
status = I3C_MasterTransferBlocking(base_, &transfer);
} else {
return pw::Status::InvalidArgument();
}
return HalStatusToPwStatus(status);
}
// inclusive-language: enable
} // namespace pw::i2c