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