xref: /aosp_15_r20/external/pigweed/pw_i2c_mcuxpresso/i3c_initiator.cc (revision 61c4878ac05f98d0ceed94b57d316916de578985)
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