1 // Copyright 2024 The Pigweed Authors
2 //
3 // Licensed under the Apache License, Version 2.0 (the "License"); you may not
4 // use this file except in compliance with the License. You may obtain a copy of
5 // the License at
6 //
7 // https://www.apache.org/licenses/LICENSE-2.0
8 //
9 // Unless required by applicable law or agreed to in writing, software
10 // distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
11 // WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
12 // License for the specific language governing permissions and limitations under
13 // the License.
14 #include "pw_i2c_mcuxpresso/i3c_initiator.h"
15
16 #include <mutex>
17
18 #include "lib/stdcompat/utility.h"
19 #include "pw_bytes/span.h"
20 #include "pw_log/log.h"
21 #include "pw_status/status.h"
22 #include "pw_status/try.h"
23
24 using cpp23::to_underlying;
25
26 namespace pw::i2c {
27 namespace {
HalStatusToPwStatus(status_t status)28 pw::Status HalStatusToPwStatus(status_t status) {
29 switch (status) {
30 case kStatus_Success:
31 return pw::OkStatus();
32 case kStatus_I3C_Timeout:
33 return pw::Status::DeadlineExceeded();
34 case kStatus_I3C_Nak:
35 case kStatus_I3C_Busy:
36 case kStatus_I3C_IBIWon:
37 case kStatus_I3C_WriteAbort:
38 return pw::Status::Unavailable();
39 case kStatus_I3C_HdrParityError:
40 case kStatus_I3C_CrcError:
41 case kStatus_I3C_MsgError:
42 return pw::Status::DataLoss();
43 default:
44 return pw::Status::Unknown();
45 }
46 }
47
48 constexpr uint32_t kI3cInitOpenDrainBaudRate = 2000000;
49 constexpr uint32_t kI3cInitPushPullBaudRate = 4000000;
50 constexpr bool kI3cInitEnableOpenDrainHigh = false;
51 constexpr uint8_t kBroadcastAddressRaw = 0x7E;
52 constexpr pw::i2c::Address kBroadcastAddress =
53 pw::i2c::Address::SevenBit<kBroadcastAddressRaw>();
54 std::array kDisecBuffer = {std::byte{0x0b}};
55 } // namespace
56
57 // inclusive-language: disable
Enable()58 void I3cMcuxpressoInitiator::Enable() {
59 std::lock_guard lock(mutex_);
60 if (enabled_) {
61 return;
62 }
63
64 i3c_master_config_t masterConfig;
65 I3C_MasterGetDefaultConfig(&masterConfig);
66
67 masterConfig.baudRate_Hz.i2cBaud = config_.i2c_baud_rate;
68 masterConfig.baudRate_Hz.i3cOpenDrainBaud = config_.i3c_open_drain_baud_rate;
69 masterConfig.baudRate_Hz.i3cPushPullBaud = config_.i3c_push_pull_baud_rate;
70 masterConfig.enableOpenDrainStop = config_.enable_open_drain_stop;
71 masterConfig.enableOpenDrainHigh = config_.enable_open_drain_high;
72
73 I3C_MasterInit(base_, &masterConfig, CLOCK_GetI3cClkFreq());
74 enabled_ = true;
75 }
76
Disable()77 void I3cMcuxpressoInitiator::Disable() {
78 std::lock_guard lock(mutex_);
79 if (!enabled_) {
80 return;
81 }
82
83 I3C_MasterDeinit(base_);
84 enabled_ = false;
85 }
86
SetDynamicAddressList(pw::span<const uint8_t> dynamic_address_list)87 pw::Status I3cMcuxpressoInitiator::SetDynamicAddressList(
88 pw::span<const uint8_t> dynamic_address_list) {
89 if (i3c_dynamic_address_list_.has_value()) {
90 PW_LOG_ERROR("i3c_dynamic_address_list_ can only be set once");
91 return pw::Status::AlreadyExists();
92 }
93
94 pw::Vector<uint8_t, I3C_MAX_DEVCNT> address_list_temp;
95 size_t dynamic_address_num = dynamic_address_list.size();
96 if (dynamic_address_num > I3C_MAX_DEVCNT) {
97 PW_LOG_WARN("Only the first %d dynamic addresses are accepted",
98 I3C_MAX_DEVCNT);
99 dynamic_address_num = I3C_MAX_DEVCNT;
100 }
101 address_list_temp.resize(dynamic_address_num);
102 std::copy(dynamic_address_list.begin(),
103 dynamic_address_list.begin() + dynamic_address_num,
104 address_list_temp.begin());
105 i3c_dynamic_address_list_.emplace(address_list_temp);
106
107 return pw::OkStatus();
108 }
109
Initialize()110 pw::Status I3cMcuxpressoInitiator::Initialize() {
111 std::lock_guard lock(mutex_);
112 i3c_master_config_t masterConfig;
113
114 if (!i3c_dynamic_address_list_.has_value() ||
115 i3c_dynamic_address_list_->empty()) {
116 PW_LOG_ERROR("Cannot initialize the bus without dynamic address");
117 return pw::Status::FailedPrecondition();
118 }
119 // Initialize I3C master with low I3C speed to match I3C timing requirement
120 // (mipi_I3C-Basic_specification_v1-1-1 section 6.2 Table 86 I3C Open Drain
121 // Timing Parameters)
122 I3C_MasterGetDefaultConfig(&masterConfig);
123 masterConfig.baudRate_Hz.i2cBaud = config_.i2c_baud_rate;
124 masterConfig.baudRate_Hz.i3cOpenDrainBaud = kI3cInitOpenDrainBaudRate;
125 masterConfig.baudRate_Hz.i3cPushPullBaud = kI3cInitPushPullBaudRate;
126 masterConfig.enableOpenDrainStop = config_.enable_open_drain_stop;
127 masterConfig.enableOpenDrainHigh = kI3cInitEnableOpenDrainHigh;
128 I3C_MasterInit(base_, &masterConfig, CLOCK_GetI3cClkFreq());
129
130 // Broadcast RSTDAA
131 // TODO: b/312487906 - First broadcast CCC receives NANK randomly on random
132 // devices.
133 if (pw::Status status = DoTransferCcc(I3cCccAction::kWrite,
134 I3cCcc::kRstdaaBroadcast,
135 kBroadcastAddress,
136 pw::ByteSpan());
137 !(status.ok())) {
138 if (status != pw::Status::Unavailable()) {
139 return status;
140 }
141 PW_LOG_WARN("Failed to broadcast first CCC, trying again...");
142 PW_TRY(DoTransferCcc(I3cCccAction::kWrite,
143 I3cCcc::kRstdaaBroadcast,
144 kBroadcastAddress,
145 pw::ByteSpan()));
146 }
147 // Broadcast DISEC 0x0b
148 PW_TRY(DoTransferCcc(I3cCccAction::kWrite,
149 I3cCcc::kDisecBroadcast,
150 kBroadcastAddress,
151 kDisecBuffer));
152 // DAA
153 status_t hal_status = I3C_MasterProcessDAA(base_,
154 i3c_dynamic_address_list_->data(),
155 i3c_dynamic_address_list_->size());
156 if (hal_status != kStatus_Success) {
157 PW_LOG_ERROR("Failed to initialize the I3C bus...");
158 }
159
160 // Re-initialize I3C master with user provided speed.
161 I3C_MasterGetDefaultConfig(&masterConfig);
162 masterConfig.baudRate_Hz.i2cBaud = config_.i2c_baud_rate;
163 masterConfig.baudRate_Hz.i3cOpenDrainBaud = config_.i3c_open_drain_baud_rate;
164 masterConfig.baudRate_Hz.i3cPushPullBaud = config_.i3c_push_pull_baud_rate;
165 masterConfig.enableOpenDrainStop = config_.enable_open_drain_stop;
166 masterConfig.enableOpenDrainHigh = config_.enable_open_drain_high;
167 I3C_MasterInit(base_, &masterConfig, CLOCK_GetI3cClkFreq());
168
169 return HalStatusToPwStatus(hal_status);
170 }
171
DoTransferCcc(I3cCccAction rnw,I3cCcc ccc_id,pw::i2c::Address address,pw::ByteSpan buffer)172 pw::Status I3cMcuxpressoInitiator::DoTransferCcc(I3cCccAction rnw,
173 I3cCcc ccc_id,
174 pw::i2c::Address address,
175 pw::ByteSpan buffer) {
176 status_t status;
177 i3c_master_transfer_t transfer;
178
179 if (!enabled_) {
180 return pw::Status::FailedPrecondition();
181 }
182
183 if (!(to_underlying(ccc_id) & kCccDirectBit)) { // broadcast
184 transfer.flags = kI3C_TransferDefaultFlag;
185 transfer.slaveAddress = kBroadcastAddressRaw;
186 transfer.direction = kI3C_Write;
187 transfer.subaddress = static_cast<uint32_t>(ccc_id);
188 transfer.subaddressSize = 1;
189 transfer.data = buffer.data();
190 transfer.dataSize = buffer.size();
191 transfer.busType = kI3C_TypeI3CSdr;
192 status = I3C_MasterTransferBlocking(base_, &transfer);
193 } else { // direct
194 transfer.flags = kI3C_TransferDefaultFlag;
195 transfer.slaveAddress = kBroadcastAddressRaw;
196 transfer.direction = kI3C_Write;
197 transfer.subaddress = static_cast<uint32_t>(ccc_id);
198 transfer.subaddressSize = 1;
199 transfer.data = nullptr;
200 transfer.dataSize = 0;
201 transfer.busType = kI3C_TypeI3CSdr;
202 status = I3C_MasterTransferBlocking(base_, &transfer);
203 if (status != kStatus_Success) {
204 return HalStatusToPwStatus(status);
205 }
206
207 transfer.flags = kI3C_TransferRepeatedStartFlag;
208 transfer.slaveAddress = uint32_t{address.GetSevenBit()};
209 transfer.direction = (rnw == I3cCccAction::kWrite) ? kI3C_Write : kI3C_Read;
210 transfer.subaddress = 0;
211 transfer.subaddressSize = 0;
212 transfer.data = buffer.data();
213 transfer.dataSize = buffer.size();
214 transfer.busType = kI3C_TypeI3CSdr;
215 status |= I3C_MasterTransferBlocking(base_, &transfer);
216 }
217 return HalStatusToPwStatus(status);
218 }
219
DoWriteReadFor(pw::i2c::Address address,pw::ConstByteSpan tx_buffer,pw::ByteSpan rx_buffer,pw::chrono::SystemClock::duration)220 pw::Status I3cMcuxpressoInitiator::DoWriteReadFor(
221 pw::i2c::Address address,
222 pw::ConstByteSpan tx_buffer,
223 pw::ByteSpan rx_buffer,
224 pw::chrono::SystemClock::duration) {
225 std::lock_guard lock(mutex_);
226 status_t status;
227 i3c_master_transfer_t transfer;
228
229 if (!enabled_) {
230 return pw::Status::FailedPrecondition();
231 }
232
233 if (std::find(i3c_dynamic_address_list_->begin(),
234 i3c_dynamic_address_list_->end(),
235 address.GetSevenBit()) == i3c_dynamic_address_list_->end()) {
236 transfer.busType = kI3C_TypeI2C;
237 } else {
238 transfer.busType = kI3C_TypeI3CSdr;
239 }
240
241 if (!tx_buffer.empty() && rx_buffer.empty()) { // write only
242 transfer.flags = kI3C_TransferDefaultFlag;
243 transfer.slaveAddress = address.GetSevenBit();
244 transfer.direction = kI3C_Write;
245 transfer.subaddress = 0;
246 transfer.subaddressSize = 0;
247 transfer.data = const_cast<std::byte*>(tx_buffer.data());
248 transfer.dataSize = tx_buffer.size();
249 transfer.ibiResponse = kI3C_IbiRespNack;
250 status = I3C_MasterTransferBlocking(base_, &transfer);
251 } else if (tx_buffer.empty() && !rx_buffer.empty()) { // read only
252 transfer.flags = kI3C_TransferDefaultFlag;
253 transfer.slaveAddress = address.GetSevenBit();
254 transfer.direction = kI3C_Read;
255 transfer.subaddress = 0;
256 transfer.subaddressSize = 0;
257 transfer.data = rx_buffer.data();
258 transfer.dataSize = rx_buffer.size();
259 transfer.dataSize = rx_buffer.size();
260 transfer.ibiResponse = kI3C_IbiRespNack;
261 status = I3C_MasterTransferBlocking(base_, &transfer);
262 } else if (!tx_buffer.empty() && !rx_buffer.empty()) { // write and read
263 transfer.flags = kI3C_TransferDefaultFlag;
264 transfer.slaveAddress = address.GetSevenBit();
265 transfer.direction = kI3C_Write;
266 transfer.subaddress = 0;
267 transfer.subaddressSize = 0;
268 transfer.data = const_cast<std::byte*>(tx_buffer.data());
269 transfer.dataSize = tx_buffer.size();
270 transfer.ibiResponse = kI3C_IbiRespNack;
271 status = I3C_MasterTransferBlocking(base_, &transfer);
272 if (status != kStatus_Success) {
273 return HalStatusToPwStatus(status);
274 }
275
276 transfer.flags = kI3C_TransferDefaultFlag;
277 transfer.slaveAddress = address.GetSevenBit();
278 transfer.direction = kI3C_Read;
279 transfer.subaddress = 0;
280 transfer.subaddressSize = 0;
281 transfer.data = rx_buffer.data();
282 transfer.dataSize = rx_buffer.size();
283 transfer.ibiResponse = kI3C_IbiRespNack;
284 status = I3C_MasterTransferBlocking(base_, &transfer);
285 } else {
286 return pw::Status::InvalidArgument();
287 }
288
289 return HalStatusToPwStatus(status);
290 }
291 // inclusive-language: enable
292
293 } // namespace pw::i2c
294