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
120 // Initialize I3C master with low I3C speed to match I3C timing requirement
121 // (mipi_I3C-Basic_specification_v1-1-1 section 6.2 Table 86 I3C Open Drain
122 // Timing Parameters)
123 I3C_MasterGetDefaultConfig(&masterConfig);
124 masterConfig.baudRate_Hz.i2cBaud = config_.i2c_baud_rate;
125 masterConfig.baudRate_Hz.i3cOpenDrainBaud = kI3cInitOpenDrainBaudRate;
126 masterConfig.baudRate_Hz.i3cPushPullBaud = kI3cInitPushPullBaudRate;
127 masterConfig.enableOpenDrainStop = config_.enable_open_drain_stop;
128 masterConfig.enableOpenDrainHigh = kI3cInitEnableOpenDrainHigh;
129 I3C_MasterInit(base_, &masterConfig, CLOCK_GetI3cClkFreq());
130
131 // Broadcast RSTDAA
132 // TODO: b/312487906 - First broadcast CCC receives NANK randomly on random
133 // devices.
134 if (pw::Status status = DoTransferCcc(I3cCccAction::kWrite,
135 I3cCcc::kRstdaaBroadcast,
136 kBroadcastAddress,
137 pw::ByteSpan());
138 !(status.ok())) {
139 if (status != pw::Status::Unavailable()) {
140 return status;
141 }
142 PW_LOG_WARN("Failed to broadcast first CCC, trying again...");
143 PW_TRY(DoTransferCcc(I3cCccAction::kWrite,
144 I3cCcc::kRstdaaBroadcast,
145 kBroadcastAddress,
146 pw::ByteSpan()));
147 }
148 // Broadcast DISEC 0x0b
149 PW_TRY(DoTransferCcc(I3cCccAction::kWrite,
150 I3cCcc::kDisecBroadcast,
151 kBroadcastAddress,
152 kDisecBuffer));
153 // DAA
154 status_t hal_status = I3C_MasterProcessDAA(base_,
155 i3c_dynamic_address_list_->data(),
156 i3c_dynamic_address_list_->size());
157 if (hal_status != kStatus_Success) {
158 PW_LOG_ERROR("Failed to initialize the I3C bus...");
159 }
160
161 // Re-initialize I3C master with user provided speed.
162 I3C_MasterGetDefaultConfig(&masterConfig);
163 masterConfig.baudRate_Hz.i2cBaud = config_.i2c_baud_rate;
164 masterConfig.baudRate_Hz.i3cOpenDrainBaud = config_.i3c_open_drain_baud_rate;
165 masterConfig.baudRate_Hz.i3cPushPullBaud = config_.i3c_push_pull_baud_rate;
166 masterConfig.enableOpenDrainStop = config_.enable_open_drain_stop;
167 masterConfig.enableOpenDrainHigh = config_.enable_open_drain_high;
168 I3C_MasterInit(base_, &masterConfig, CLOCK_GetI3cClkFreq());
169
170 return HalStatusToPwStatus(hal_status);
171 }
172
DoTransferCcc(I3cCccAction rnw,I3cCcc ccc_id,pw::i2c::Address address,pw::ByteSpan buffer)173 pw::Status I3cMcuxpressoInitiator::DoTransferCcc(I3cCccAction rnw,
174 I3cCcc ccc_id,
175 pw::i2c::Address address,
176 pw::ByteSpan buffer) {
177 status_t status;
178 i3c_master_transfer_t transfer;
179
180 if (!enabled_) {
181 return pw::Status::FailedPrecondition();
182 }
183
184 if (!(to_underlying(ccc_id) & kCccDirectBit)) { // broadcast
185 transfer.flags = kI3C_TransferDefaultFlag;
186 transfer.slaveAddress = kBroadcastAddressRaw;
187 transfer.direction = kI3C_Write;
188 transfer.subaddress = static_cast<uint32_t>(ccc_id);
189 transfer.subaddressSize = 1;
190 transfer.data = buffer.data();
191 transfer.dataSize = buffer.size();
192 transfer.busType = kI3C_TypeI3CSdr;
193 status = I3C_MasterTransferBlocking(base_, &transfer);
194 } else { // direct
195 transfer.flags = kI3C_TransferDefaultFlag;
196 transfer.slaveAddress = kBroadcastAddressRaw;
197 transfer.direction = kI3C_Write;
198 transfer.subaddress = static_cast<uint32_t>(ccc_id);
199 transfer.subaddressSize = 1;
200 transfer.data = nullptr;
201 transfer.dataSize = 0;
202 transfer.busType = kI3C_TypeI3CSdr;
203 status = I3C_MasterTransferBlocking(base_, &transfer);
204 if (status != kStatus_Success) {
205 return HalStatusToPwStatus(status);
206 }
207
208 transfer.flags = kI3C_TransferRepeatedStartFlag;
209 transfer.slaveAddress = uint32_t{address.GetSevenBit()};
210 transfer.direction = (rnw == I3cCccAction::kWrite) ? kI3C_Write : kI3C_Read;
211 transfer.subaddress = 0;
212 transfer.subaddressSize = 0;
213 transfer.data = buffer.data();
214 transfer.dataSize = buffer.size();
215 transfer.busType = kI3C_TypeI3CSdr;
216 status |= I3C_MasterTransferBlocking(base_, &transfer);
217 }
218 return HalStatusToPwStatus(status);
219 }
220
DoWriteReadFor(pw::i2c::Address address,pw::ConstByteSpan tx_buffer,pw::ByteSpan rx_buffer,pw::chrono::SystemClock::duration)221 pw::Status I3cMcuxpressoInitiator::DoWriteReadFor(
222 pw::i2c::Address address,
223 pw::ConstByteSpan tx_buffer,
224 pw::ByteSpan rx_buffer,
225 pw::chrono::SystemClock::duration) {
226 std::lock_guard lock(mutex_);
227 status_t status;
228 i3c_master_transfer_t transfer;
229
230 if (!enabled_) {
231 return pw::Status::FailedPrecondition();
232 }
233
234 if (std::find(i3c_dynamic_address_list_->begin(),
235 i3c_dynamic_address_list_->end(),
236 address.GetSevenBit()) == i3c_dynamic_address_list_->end()) {
237 transfer.busType = kI3C_TypeI2C;
238 } else {
239 transfer.busType = kI3C_TypeI3CSdr;
240 }
241
242 if (!tx_buffer.empty() && rx_buffer.empty()) { // write only
243 transfer.flags = kI3C_TransferDefaultFlag;
244 transfer.slaveAddress = address.GetSevenBit();
245 transfer.direction = kI3C_Write;
246 transfer.subaddress = 0;
247 transfer.subaddressSize = 0;
248 transfer.data = const_cast<std::byte*>(tx_buffer.data());
249 transfer.dataSize = tx_buffer.size();
250 transfer.ibiResponse = kI3C_IbiRespNack;
251 status = I3C_MasterTransferBlocking(base_, &transfer);
252 } else if (tx_buffer.empty() && !rx_buffer.empty()) { // read only
253 transfer.flags = kI3C_TransferDefaultFlag;
254 transfer.slaveAddress = address.GetSevenBit();
255 transfer.direction = kI3C_Read;
256 transfer.subaddress = 0;
257 transfer.subaddressSize = 0;
258 transfer.data = rx_buffer.data();
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_TransferNoStopFlag;
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_TransferRepeatedStartFlag;
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