xref: /aosp_15_r20/external/pigweed/pw_i2c_mcuxpresso/initiator.cc (revision 61c4878ac05f98d0ceed94b57d316916de578985)
1 // Copyright 2022 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/initiator.h"
15 
16 #include <mutex>
17 
18 #include "fsl_i2c.h"
19 #include "pw_chrono/system_clock.h"
20 #include "pw_status/status.h"
21 #include "pw_status/try.h"
22 
23 namespace pw::i2c {
24 namespace {
25 
HalStatusToPwStatus(status_t status)26 Status HalStatusToPwStatus(status_t status) {
27   switch (status) {
28     case kStatus_Success:
29       return OkStatus();
30     case kStatus_I2C_Nak:
31     case kStatus_I2C_Addr_Nak:
32       return Status::Unavailable();
33     case kStatus_I2C_InvalidParameter:
34       return Status::InvalidArgument();
35     case kStatus_I2C_Timeout:
36       return Status::DeadlineExceeded();
37     default:
38       return Status::Unknown();
39   }
40 }
41 }  // namespace
42 
43 // inclusive-language: disable
Enable()44 void McuxpressoInitiator::Enable() {
45   std::lock_guard lock(mutex_);
46 
47   i2c_master_config_t master_config;
48   I2C_MasterGetDefaultConfig(&master_config);
49   master_config.baudRate_Bps = config_.baud_rate_bps;
50   I2C_MasterInit(base_, &master_config, CLOCK_GetFreq(config_.clock_name));
51 
52   // Create the handle for the non-blocking transfer and register callback.
53   I2C_MasterTransferCreateHandle(
54       base_, &handle_, McuxpressoInitiator::TransferCompleteCallback, this);
55 
56   enabled_ = true;
57 }
58 
Disable()59 void McuxpressoInitiator::Disable() {
60   std::lock_guard lock(mutex_);
61   I2C_MasterDeinit(base_);
62   enabled_ = false;
63 }
64 
~McuxpressoInitiator()65 McuxpressoInitiator::~McuxpressoInitiator() { Disable(); }
66 
TransferCompleteCallback(I2C_Type *,i2c_master_handle_t *,status_t status,void * initiator_ptr)67 void McuxpressoInitiator::TransferCompleteCallback(I2C_Type*,
68                                                    i2c_master_handle_t*,
69                                                    status_t status,
70                                                    void* initiator_ptr) {
71   McuxpressoInitiator& initiator =
72       *static_cast<McuxpressoInitiator*>(initiator_ptr);
73   initiator.callback_isl_.lock();
74   initiator.transfer_status_ = status;
75   initiator.callback_isl_.unlock();
76   initiator.callback_complete_notification_.release();
77 }
78 
InitiateNonBlockingTransfer(chrono::SystemClock::duration rw_timeout,i2c_master_transfer_t * transfer)79 Status McuxpressoInitiator::InitiateNonBlockingTransfer(
80     chrono::SystemClock::duration rw_timeout, i2c_master_transfer_t* transfer) {
81   const status_t status =
82       I2C_MasterTransferNonBlocking(base_, &handle_, transfer);
83   if (status != kStatus_Success) {
84     return HalStatusToPwStatus(status);
85   }
86 
87   if (!callback_complete_notification_.try_acquire_for(rw_timeout)) {
88     I2C_MasterTransferAbort(base_, &handle_);
89     return Status::DeadlineExceeded();
90   }
91 
92   callback_isl_.lock();
93   const status_t transfer_status = transfer_status_;
94   callback_isl_.unlock();
95 
96   return HalStatusToPwStatus(transfer_status);
97 }
98 
99 // Performs non-blocking I2C write, read and read-after-write depending on the
100 // tx and rx buffer states.
DoWriteReadFor(Address device_address,ConstByteSpan tx_buffer,ByteSpan rx_buffer,chrono::SystemClock::duration timeout)101 Status McuxpressoInitiator::DoWriteReadFor(
102     Address device_address,
103     ConstByteSpan tx_buffer,
104     ByteSpan rx_buffer,
105     chrono::SystemClock::duration timeout) {
106   if (timeout <= chrono::SystemClock::duration::zero()) {
107     return Status::DeadlineExceeded();
108   }
109 
110   const uint8_t address = device_address.GetSevenBit();
111   std::lock_guard lock(mutex_);
112 
113   if (!enabled_) {
114     return Status::FailedPrecondition();
115   }
116 
117   if (!tx_buffer.empty() && rx_buffer.empty()) {
118     i2c_master_transfer_t transfer{kI2C_TransferDefaultFlag,
119                                    address,
120                                    kI2C_Write,
121                                    0,
122                                    0,
123                                    const_cast<std::byte*>(tx_buffer.data()),
124                                    tx_buffer.size()};
125     return InitiateNonBlockingTransfer(timeout, &transfer);
126   } else if (tx_buffer.empty() && !rx_buffer.empty()) {
127     i2c_master_transfer_t transfer{kI2C_TransferDefaultFlag,
128                                    address,
129                                    kI2C_Read,
130                                    0,
131                                    0,
132                                    rx_buffer.data(),
133                                    rx_buffer.size()};
134     return InitiateNonBlockingTransfer(timeout, &transfer);
135   } else if (!tx_buffer.empty() && !rx_buffer.empty()) {
136     i2c_master_transfer_t w_transfer{kI2C_TransferNoStopFlag,
137                                      address,
138                                      kI2C_Write,
139                                      0,
140                                      0,
141                                      const_cast<std::byte*>(tx_buffer.data()),
142                                      tx_buffer.size()};
143     const chrono::SystemClock::time_point deadline =
144         chrono::SystemClock::TimePointAfterAtLeast(timeout);
145     PW_TRY(InitiateNonBlockingTransfer(timeout, &w_transfer));
146     i2c_master_transfer_t r_transfer{kI2C_TransferRepeatedStartFlag,
147                                      address,
148                                      kI2C_Read,
149                                      0,
150                                      0,
151                                      rx_buffer.data(),
152                                      rx_buffer.size()};
153     const chrono::SystemClock::duration time_remaining =
154         deadline - chrono::SystemClock::now();
155     if (time_remaining <= chrono::SystemClock::duration::zero()) {
156       // Abort transfer in an unlikely scenario of timeout even with
157       // successful write.
158       I2C_MasterTransferAbort(base_, &handle_);
159       return Status::DeadlineExceeded();
160     }
161     return InitiateNonBlockingTransfer(time_remaining, &r_transfer);
162   } else {
163     return Status::InvalidArgument();
164   }
165 }
166 // inclusive-language: enable
167 }  // namespace pw::i2c
168