xref: /aosp_15_r20/external/pigweed/pw_i2c_rp2040/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_rp2040/initiator.h"
15 
16 #include <chrono>
17 #include <mutex>
18 
19 #include "hardware/gpio.h"
20 #include "hardware/i2c.h"
21 #include "pico/error.h"
22 #include "pico/types.h"
23 #include "pw_chrono/system_clock.h"
24 #include "pw_status/status.h"
25 #include "pw_status/try.h"
26 
27 namespace pw::i2c {
28 
29 namespace {
30 
PicoStatusToPwStatus(int status)31 Status PicoStatusToPwStatus(int status) {
32   if (status > 0)
33     return OkStatus();
34 
35   switch (status) {
36     case PICO_ERROR_TIMEOUT:
37       return Status::DeadlineExceeded();
38     default:
39       return Status::Unavailable();
40   }
41 }
42 }  // namespace
43 
Enable()44 void Rp2040Initiator::Enable() {
45   std::lock_guard lock(mutex_);
46 
47   i2c_init(instance_, config_.clock_frequency_hz);
48   gpio_set_function(config_.sda_pin, GPIO_FUNC_I2C);
49   gpio_set_function(config_.scl_pin, GPIO_FUNC_I2C);
50 
51   enabled_ = true;
52 }
53 
Disable()54 void Rp2040Initiator::Disable() {
55   std::lock_guard lock(mutex_);
56   i2c_deinit(instance_);
57   enabled_ = false;
58 }
59 
~Rp2040Initiator()60 Rp2040Initiator::~Rp2040Initiator() { Disable(); }
61 
62 // Performs blocking I2C write, read and read-after-write depending on the tx
63 // and rx buffer states.
DoWriteReadFor(Address device_address,ConstByteSpan tx_buffer,ByteSpan rx_buffer,chrono::SystemClock::duration timeout)64 Status Rp2040Initiator::DoWriteReadFor(Address device_address,
65                                        ConstByteSpan tx_buffer,
66                                        ByteSpan rx_buffer,
67                                        chrono::SystemClock::duration timeout) {
68   if (timeout <= chrono::SystemClock::duration::zero()) {
69     return Status::DeadlineExceeded();
70   }
71   // The number of us to wait plus + 1 to ensure we wait at least the full
72   // duration. Ideally we would add one additional tick but the pico_sdk only
73   // supports timeouts specified in us.
74   const int64_t timeout_us = std::chrono::microseconds(timeout).count() + 1;
75 
76   if (timeout_us > std::numeric_limits<uint>::max()) {
77     return Status::InvalidArgument();
78   }
79 
80   const uint8_t address = device_address.GetSevenBit();
81   std::lock_guard lock(mutex_);
82 
83   if (!enabled_) {
84     return Status::FailedPrecondition();
85   }
86 
87   if (!tx_buffer.empty() && rx_buffer.empty()) {
88     // Write
89     int result = i2c_write_timeout_us(
90         instance_,
91         address,
92         /*src=*/reinterpret_cast<const uint8_t*>(tx_buffer.data()),
93         /*len=*/tx_buffer.size(),
94         /*nostop=*/false,
95         static_cast<uint>(timeout_us));
96 
97     return PicoStatusToPwStatus(result);
98 
99   } else if (tx_buffer.empty() && !rx_buffer.empty()) {
100     // Read
101     int result = i2c_read_timeout_us(
102         instance_,
103         address,
104         /*src=*/reinterpret_cast<uint8_t*>(rx_buffer.data()),
105         /*len=*/rx_buffer.size(),
106         /*nostop=*/false,
107         static_cast<uint>(timeout_us));
108     return PicoStatusToPwStatus(result);
109 
110   } else if (!tx_buffer.empty() && !rx_buffer.empty()) {
111     absolute_time_t timeout_absolute = make_timeout_time_us(timeout_us);
112     // Write then Read
113     int write_result = i2c_write_blocking_until(
114         instance_,
115         address,
116         /*src=*/reinterpret_cast<const uint8_t*>(tx_buffer.data()),
117         /*len=*/tx_buffer.size(),
118         /*nostop=*/true,
119         /*until=*/timeout_absolute);
120     pw::Status write_status(PicoStatusToPwStatus(write_result));
121 
122     if (write_status != OkStatus()) {
123       return write_status;
124     }
125 
126     int read_result = i2c_read_blocking_until(
127         instance_,
128         address,
129         /*src=*/reinterpret_cast<uint8_t*>(rx_buffer.data()),
130         /*len=*/rx_buffer.size(),
131         /*nostop=*/false,
132         /*until=*/timeout_absolute);
133 
134     return PicoStatusToPwStatus(read_result);
135 
136   } else {
137     return Status::InvalidArgument();
138   }
139 }
140 }  // namespace pw::i2c
141