xref: /aosp_15_r20/external/pigweed/pw_i2c/public/pw_i2c/initiator.h (revision 61c4878ac05f98d0ceed94b57d316916de578985)
1 // Copyright 2020 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 #pragma once
15 
16 #include <cstdint>
17 
18 #include "pw_bytes/span.h"
19 #include "pw_chrono/system_clock.h"
20 #include "pw_i2c/address.h"
21 #include "pw_status/status.h"
22 
23 namespace pw::i2c {
24 
25 /// @brief The common, base driver interface for initiating thread-safe
26 /// transactions with devices on an I2C bus. Other documentation may call this
27 /// style of interface an I2C "master", <!-- inclusive-language: disable -->
28 /// "central", or "controller".
29 ///
30 /// `pw::i2c::Initiator` isn't required to support 10-bit addressing. If only
31 /// 7-bit addressing is supported, `pw::i2c::Initiator` fails a runtime
32 /// assertion when given an address that is out of 7-bit address range.
33 ///
34 /// The implementer of this pure virtual interface is responsible for ensuring
35 /// thread safety and enabling functionality such as initialization,
36 /// configuration, enabling and disabling, unsticking SDA, and detecting device
37 /// address registration collisions.
38 ///
39 /// @note `pw::i2c::Initiator` uses internal synchronization, so it's safe to
40 /// initiate transactions from multiple threads. However, a combined write and
41 /// read transaction may not be atomic when there are multiple initiators on
42 /// the bus. Furthermore, devices may require specific sequences of
43 /// transactions, and application logic must provide the synchronization to
44 /// execute these sequences correctly.
45 class Initiator {
46  public:
47   virtual ~Initiator() = default;
48 
49   /// Writes bytes to an I2C device and then reads bytes from that same
50   /// device as either one atomic I2C transaction or two independent I2C
51   /// transactions.
52   ///
53   /// If the I2C bus is a multi-initiator then the implementer of this
54   /// class **must** ensure it's a single-atomic transaction.
55   ///
56   /// The signal on the bus for the atomic transaction should look like this:
57   ///
58   /// @code
59   ///   START + I2C_ADDRESS + WRITE(0) + TX_BUFFER_BYTES +
60   ///   START + I2C_ADDRESS + READ(1) + RX_BUFFER_BYTES + STOP
61   /// @endcode
62   ///
63   /// The signal on the bus for the two independent transactions should look
64   /// like this:
65   ///
66   /// @code
67   ///   START + I2C_ADDRESS + WRITE(0) + TX_BUFFER_BYTES + STOP
68   ///   START + I2C_ADDRESS + READ(1) + RX_BUFFER_BYTES + STOP
69   /// @endcode
70   ///
71   /// @param[in] device_address The address of the I2C device.
72   ///
73   /// @param[in] tx_buffer The transmit buffer.
74   ///
75   /// @param[out] rx_buffer The receive buffer.
76   ///
77   /// @param[in] timeout The maximum duration to block waiting for both
78   /// exclusive bus access and the completion of the I2C transaction or
79   /// transactions.
80   ///
81   /// @pre The provided address must be supported by the initiator. I.e.
82   /// don't use a 10-bit address if the initiator only supports 7-bit
83   /// addresses. This method fails a runtime assertion if this precondition
84   /// isn't met.
85   ///
86   /// @returns @rst
87   ///
88   /// .. pw-status-codes::
89   ///
90   ///    OK: The transaction or transactions succeeded.
91   ///
92   ///    INVALID_ARGUMENT: The device address provided is bigger than 10 bits.
93   ///
94   ///    DEADLINE_EXCEEDED: Was unable to acquire exclusive initiator access
95   ///    and complete the I2C transaction in time.
96   ///
97   ///    UNAVAILABLE: A NACK condition occurred, meaning the addressed device
98   ///    didn't respond or was unable to process the request.
99   ///
100   ///    FAILED_PRECONDITION: The interface isn't initialized or enabled.
101   ///
102   /// @endrst
WriteReadFor(Address device_address,ConstByteSpan tx_buffer,ByteSpan rx_buffer,chrono::SystemClock::duration timeout)103   Status WriteReadFor(Address device_address,
104                       ConstByteSpan tx_buffer,
105                       ByteSpan rx_buffer,
106                       chrono::SystemClock::duration timeout) {
107     return DoWriteReadFor(device_address, tx_buffer, rx_buffer, timeout);
108   }
109 
110   /// A variation of `pw::i2c::Initiator::WriteReadFor` that accepts explicit
111   /// sizes for the amount of bytes to write to the device and read from the
112   /// device.
WriteReadFor(Address device_address,const void * tx_buffer,size_t tx_size_bytes,void * rx_buffer,size_t rx_size_bytes,chrono::SystemClock::duration timeout)113   Status WriteReadFor(Address device_address,
114                       const void* tx_buffer,
115                       size_t tx_size_bytes,
116                       void* rx_buffer,
117                       size_t rx_size_bytes,
118                       chrono::SystemClock::duration timeout) {
119     return WriteReadFor(
120         device_address,
121         span(static_cast<const std::byte*>(tx_buffer), tx_size_bytes),
122         span(static_cast<std::byte*>(rx_buffer), rx_size_bytes),
123         timeout);
124   }
125 
126   /// Write bytes to the I2C device.
127   ///
128   /// The signal on the bus should look like this:
129   ///
130   /// @code
131   ///   START + I2C_ADDRESS + WRITE(0) + TX_BUFFER_BYTES + STOP
132   /// @endcode
133   ///
134   /// @param[in] device_address The address of the I2C device.
135   ///
136   /// @param[in] tx_buffer The transmit buffer.
137   ///
138   /// @param[in] timeout The maximum duration to block waiting for both
139   /// exclusive bus access and the completion of the I2C transaction.
140   ///
141   /// @pre The provided address must be supported by the initiator. I.e.
142   /// don't use a 10-bit address if the initiator only supports 7-bit
143   /// addresses. This method fails a runtime assertion if this precondition
144   /// isn't met.
145   ///
146   /// @returns @rst
147   ///
148   /// .. pw-status-codes::
149   ///
150   ///    OK: The transaction succeeded.
151   ///
152   ///    INVALID_ARGUMENT: The device address provided is bigger than 10 bits.
153   ///
154   ///    DEADLINE_EXCEEDED: Was unable to acquire exclusive initiator access
155   ///    and complete the I2C transaction in time.
156   ///
157   ///    UNAVAILABLE: A NACK condition occurred, meaning the addressed device
158   ///    didn't respond or was unable to process the request.
159   ///
160   ///    FAILED_PRECONDITION: The interface isn't initialized or enabled.
161   ///
162   /// @endrst
WriteFor(Address device_address,ConstByteSpan tx_buffer,chrono::SystemClock::duration timeout)163   Status WriteFor(Address device_address,
164                   ConstByteSpan tx_buffer,
165                   chrono::SystemClock::duration timeout) {
166     return WriteReadFor(device_address, tx_buffer, ByteSpan(), timeout);
167   }
168   /// A variation of `pw::i2c::Initiator::WriteFor` that accepts an explicit
169   /// size for the amount of bytes to write to the device.
WriteFor(Address device_address,const void * tx_buffer,size_t tx_size_bytes,chrono::SystemClock::duration timeout)170   Status WriteFor(Address device_address,
171                   const void* tx_buffer,
172                   size_t tx_size_bytes,
173                   chrono::SystemClock::duration timeout) {
174     return WriteFor(
175         device_address,
176         span(static_cast<const std::byte*>(tx_buffer), tx_size_bytes),
177         timeout);
178   }
179 
180   /// Reads bytes from an I2C device.
181   ///
182   /// The signal on the bus should look like this:
183   ///
184   /// @code
185   ///   START + I2C_ADDRESS + READ(1) + RX_BUFFER_BYTES + STOP
186   /// @endcode
187   ///
188   /// @param[in] device_address The address of the I2C device.
189   ///
190   /// @param[out] rx_buffer The receive buffer.
191   ///
192   /// @param[in] timeout The maximum duration to block waiting for both
193   /// exclusive bus access and the completion of the I2C transaction.
194   ///
195   /// @pre The provided address must be supported by the initiator. I.e.
196   /// don't use a 10-bit address if the initiator only supports 7-bit
197   /// addresses. This method fails a runtime assertion if this precondition
198   /// isn't met.
199   ///
200   /// @returns @rst
201   ///
202   /// .. pw-status-codes::
203   ///
204   ///    OK: The transaction succeeded.
205   ///
206   ///    INVALID_ARGUMENT: The device address provided is bigger than 10 bits.
207   ///
208   ///    DEADLINE_EXCEEDED: Was unable to acquire exclusive initiator access
209   ///    and complete the I2C transaction in time.
210   ///
211   ///    UNAVAILABLE: A NACK condition occurred, meaning the addressed device
212   ///    didn't respond or was unable to process the request.
213   ///
214   ///    FAILED_PRECONDITION: The interface isn't initialized or enabled.
215   ///
216   /// @endrst
ReadFor(Address device_address,ByteSpan rx_buffer,chrono::SystemClock::duration timeout)217   Status ReadFor(Address device_address,
218                  ByteSpan rx_buffer,
219                  chrono::SystemClock::duration timeout) {
220     return WriteReadFor(device_address, ConstByteSpan(), rx_buffer, timeout);
221   }
222   /// A variation of `pw::i2c::Initiator::ReadFor` that accepts an explicit
223   /// size for the amount of bytes to read from the device.
ReadFor(Address device_address,void * rx_buffer,size_t rx_size_bytes,chrono::SystemClock::duration timeout)224   Status ReadFor(Address device_address,
225                  void* rx_buffer,
226                  size_t rx_size_bytes,
227                  chrono::SystemClock::duration timeout) {
228     return ReadFor(device_address,
229                    span(static_cast<std::byte*>(rx_buffer), rx_size_bytes),
230                    timeout);
231   }
232 
233   /// Probes the device for an I2C ACK after only writing the address.
234   /// This is done by attempting to read a single byte from the specified
235   /// device.
236   ///
237   /// @warning This method is not compatible with all devices. For example, some
238   /// I2C devices require a device_address in W mode before they can ack the
239   /// device_address in R mode. In this case, use WriteReadFor to read a
240   /// register with known value.
241   ///
242   /// @param[in] device_address The address of the I2C device.
243   ///
244   /// @param[in] timeout The maximum duration to block waiting for both
245   /// exclusive bus access and the completion of the I2C transaction.
246   ///
247   /// @pre The provided address must be supported by the initiator. I.e.
248   /// don't use a 10-bit address if the initiator only supports 7-bit
249   /// addresses. This method fails a runtime assertion if this precondition
250   /// isn't met.
251   ///
252   /// @returns @rst
253   ///
254   /// .. pw-status-codes::
255   ///
256   ///    OK: The transaction succeeded.
257   ///
258   ///    INVALID_ARGUMENT: The device address provided is bigger than 10 bits.
259   ///
260   ///    DEADLINE_EXCEEDED: Was unable to acquire exclusive initiator access
261   ///    and complete the I2C transaction in time.
262   ///
263   ///    UNAVAILABLE: A NACK condition occurred, meaning the addressed device
264   ///    didn't respond or was unable to process the request.
265   ///
266   ///    FAILED_PRECONDITION: The interface isn't initialized or enabled.
267   ///
268   /// @endrst
ProbeDeviceFor(Address device_address,chrono::SystemClock::duration timeout)269   Status ProbeDeviceFor(Address device_address,
270                         chrono::SystemClock::duration timeout) {
271     std::byte ignored_buffer[1] = {};  // Read a byte to probe.
272     return WriteReadFor(
273         device_address, ConstByteSpan(), ignored_buffer, timeout);
274   }
275 
276  private:
277   virtual Status DoWriteReadFor(Address device_address,
278                                 ConstByteSpan tx_buffer,
279                                 ByteSpan rx_buffer,
280                                 chrono::SystemClock::duration timeout) = 0;
281 };
282 
283 }  // namespace pw::i2c
284