• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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
McuxpressoInitiator(I2C_Type * base,uint32_t baud_rate_bps,uint32_t src_clock_hz)44 McuxpressoInitiator::McuxpressoInitiator(I2C_Type* base,
45                                          uint32_t baud_rate_bps,
46                                          uint32_t src_clock_hz)
47     : base_(base) {
48   i2c_master_config_t master_config;
49   I2C_MasterGetDefaultConfig(&master_config);
50   master_config.baudRate_Bps = baud_rate_bps;
51   I2C_MasterInit(base_, &master_config, src_clock_hz);
52 
53   // Create the handle for the non-blocking transfer and register callback.
54   I2C_MasterTransferCreateHandle(
55       base_, &handle_, McuxpressoInitiator::TransferCompleteCallback, this);
56 }
57 
~McuxpressoInitiator()58 McuxpressoInitiator::~McuxpressoInitiator() { I2C_MasterDeinit(base_); }
59 
TransferCompleteCallback(I2C_Type * base,i2c_master_handle_t * handle,status_t status,void * initiator_ptr)60 void McuxpressoInitiator::TransferCompleteCallback(I2C_Type* base,
61                                                    i2c_master_handle_t* handle,
62                                                    status_t status,
63                                                    void* initiator_ptr) {
64   McuxpressoInitiator& initiator =
65       *static_cast<McuxpressoInitiator*>(initiator_ptr);
66   initiator.callback_isl_.lock();
67   initiator.transfer_status_ = status;
68   initiator.callback_isl_.unlock();
69   initiator.callback_complete_notification_.release();
70 }
71 
InitiateNonBlockingTransfer(chrono::SystemClock::duration rw_timeout,i2c_master_transfer_t * transfer)72 Status McuxpressoInitiator::InitiateNonBlockingTransfer(
73     chrono::SystemClock::duration rw_timeout, i2c_master_transfer_t* transfer) {
74   const status_t status =
75       I2C_MasterTransferNonBlocking(base_, &handle_, transfer);
76   if (status != kStatus_Success) {
77     return HalStatusToPwStatus(status);
78   }
79 
80   if (!callback_complete_notification_.try_acquire_for(rw_timeout)) {
81     I2C_MasterTransferAbort(base_, &handle_);
82     return Status::DeadlineExceeded();
83   }
84 
85   callback_isl_.lock();
86   const status_t transfer_status = transfer_status_;
87   callback_isl_.unlock();
88 
89   return HalStatusToPwStatus(transfer_status);
90 }
91 
92 // Performs non-blocking I2C write, read and read-after-write depending on the
93 // tx and rx buffer states.
DoWriteReadFor(Address device_address,ConstByteSpan tx_buffer,ByteSpan rx_buffer,chrono::SystemClock::duration timeout)94 Status McuxpressoInitiator::DoWriteReadFor(
95     Address device_address,
96     ConstByteSpan tx_buffer,
97     ByteSpan rx_buffer,
98     chrono::SystemClock::duration timeout) {
99   if (timeout <= chrono::SystemClock::duration::zero()) {
100     return Status::DeadlineExceeded();
101   }
102 
103   const uint8_t address = device_address.GetSevenBit();
104   std::lock_guard lock(mutex_);
105 
106   if (!tx_buffer.empty() && rx_buffer.empty()) {
107     i2c_master_transfer_t transfer{kI2C_TransferDefaultFlag,
108                                    address,
109                                    kI2C_Write,
110                                    0,
111                                    0,
112                                    const_cast<std::byte*>(tx_buffer.data()),
113                                    tx_buffer.size()};
114     return InitiateNonBlockingTransfer(timeout, &transfer);
115   } else if (tx_buffer.empty() && !rx_buffer.empty()) {
116     i2c_master_transfer_t transfer{kI2C_TransferDefaultFlag,
117                                    address,
118                                    kI2C_Read,
119                                    0,
120                                    0,
121                                    rx_buffer.data(),
122                                    rx_buffer.size()};
123     return InitiateNonBlockingTransfer(timeout, &transfer);
124   } else if (!tx_buffer.empty() && !rx_buffer.empty()) {
125     i2c_master_transfer_t w_transfer{kI2C_TransferNoStopFlag,
126                                      address,
127                                      kI2C_Write,
128                                      0,
129                                      0,
130                                      const_cast<std::byte*>(tx_buffer.data()),
131                                      tx_buffer.size()};
132     const chrono::SystemClock::time_point deadline =
133         chrono::SystemClock::TimePointAfterAtLeast(timeout);
134     PW_TRY(InitiateNonBlockingTransfer(timeout, &w_transfer));
135     i2c_master_transfer_t r_transfer{kI2C_TransferRepeatedStartFlag,
136                                      address,
137                                      kI2C_Read,
138                                      0,
139                                      0,
140                                      rx_buffer.data(),
141                                      rx_buffer.size()};
142     const chrono::SystemClock::duration time_remaining =
143         deadline - chrono::SystemClock::now();
144     if (time_remaining <= chrono::SystemClock::duration::zero()) {
145       // Abort transfer in an unlikely scenario of timeout even with
146       // successful write.
147       I2C_MasterTransferAbort(base_, &handle_);
148       return Status::DeadlineExceeded();
149     }
150     return InitiateNonBlockingTransfer(time_remaining, &r_transfer);
151   } else {
152     return Status::InvalidArgument();
153   }
154 }
155 // inclusive-language: enable
156 }  // namespace pw::i2c
157