• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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