aboutsummaryrefslogtreecommitdiff
path: root/pw_i2c/public/pw_i2c/initiator.h
blob: 7d9d6ddded6e14edd8c3f3ba8219de87eca0d6fd (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
// Copyright 2020 The Pigweed Authors
//
// Licensed under the Apache License, Version 2.0 (the "License"); you may not
// use this file except in compliance with the License. You may obtain a copy of
// the License at
//
//     https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
// License for the specific language governing permissions and limitations under
// the License.
#pragma once

#include <cstdint>

#include "pw_bytes/span.h"
#include "pw_chrono/system_clock.h"
#include "pw_i2c/address.h"
#include "pw_status/status.h"

namespace pw::i2c {

/// @brief The common, base driver interface for initiating thread-safe
/// transactions with devices on an I2C bus. Other documentation may call this
/// style of interface an I2C "master", <!-- inclusive-language: disable -->
/// "central", or "controller".
///
/// `Initiator` isn't required to support 10-bit addressing. If only 7-bit
/// addressing is supported, `Initiator` asserts when given an address
/// that is out of 7-bit address range.
///
/// The implementer of this pure virtual interface is responsible for ensuring
/// thread safety and enabling functionality such as initialization,
/// configuration, enabling/disabling, unsticking SDA, and detecting device
/// address registration collisions.
///
/// @note `Initiator` uses internal synchronization, so it's safe to
/// initiate transactions from multiple threads. However, write+read
/// transactions may not be atomic with multiple controllers on the bus.
/// Furthermore, devices may require specific sequences of transactions, and
/// application logic must provide the synchronization to execute these
/// sequences correctly.
class Initiator {
 public:
  virtual ~Initiator() = default;

  // Write bytes and then read bytes as either one atomic or two independent I2C
  // transaction. If the I2C bus is a multi-initiator bus then the implementer
  // MUST ensure it is a single atomic I2C transaction.
  // The signal on the bus should appear as follows:
  // 1) Write Only:
  //   START + I2C Address + WRITE(0) + TX_BUFFER_BYTES + STOP
  // 2) Read Only:
  //   START + I2C Address + READ(1) + RX_BUFFER_BYTES + STOP
  // 3A) Write + Read (atomic):
  //   START + I2C Address + WRITE(0) + TX_BUFFER_BYTES +
  //   START + I2C Address + READ(1) + RX_BUFFER_BYTES + STOP
  // 3B) Write + Read (separate):
  //   START + I2C Address + WRITE(0) + TX_BUFFER_BYTES + STOP
  //   START + I2C Address + READ(1) + RX_BUFFER_BYTES + STOP
  //
  // The timeout defines the minimum duration one may block waiting for both
  // exclusive bus access and the completion of the I2C transaction.
  //
  // Preconditions:
  // The Address must be supported by the Initiator, i.e. do not use a 10
  //     address if the Initiator only supports 7 bit. This will assert.
  //
  // Returns:
  // Ok - Success.
  // InvalidArgument - device_address is larger than the 10 bit address space.
  // DeadlineExceeded - Was unable to acquire exclusive Initiator access
  //   and complete the I2C transaction in time.
  // Unavailable - NACK condition occurred, meaning the addressed device did
  //   not respond or was unable to process the request.
  // FailedPrecondition - The interface is not currently initialized and/or
  //    enabled.
  Status WriteReadFor(Address device_address,
                      ConstByteSpan tx_buffer,
                      ByteSpan rx_buffer,
                      chrono::SystemClock::duration timeout) {
    return DoWriteReadFor(device_address, tx_buffer, rx_buffer, timeout);
  }
  Status 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) {
    return WriteReadFor(
        device_address,
        span(static_cast<const std::byte*>(tx_buffer), tx_size_bytes),
        span(static_cast<std::byte*>(rx_buffer), rx_size_bytes),
        timeout);
  }

  // Write bytes. The signal on the bus should appear as follows:
  //   START + I2C Address + WRITE(0) + TX_BUFFER_BYTES + STOP
  //
  // The timeout defines the minimum duration one may block waiting for both
  // exclusive bus access and the completion of the I2C transaction.
  //
  // Preconditions:
  // The Address must be supported by the Initiator, i.e. do not use a 10
  //     address if the Initiator only supports 7 bit. This will assert.
  //
  // Returns:
  // Ok - Success.
  // InvalidArgument - device_address is larger than the 10 bit address space.
  // DeadlineExceeded - Was unable to acquire exclusive Initiator access
  //   and complete the I2C transaction in time.
  // Unavailable - NACK condition occurred, meaning the addressed device did
  //   not respond or was unable to process the request.
  // FailedPrecondition - The interface is not currently initialized and/or
  //    enabled.
  Status WriteFor(Address device_address,
                  ConstByteSpan tx_buffer,
                  chrono::SystemClock::duration timeout) {
    return WriteReadFor(device_address, tx_buffer, ByteSpan(), timeout);
  }
  Status WriteFor(Address device_address,
                  const void* tx_buffer,
                  size_t tx_size_bytes,
                  chrono::SystemClock::duration timeout) {
    return WriteFor(
        device_address,
        span(static_cast<const std::byte*>(tx_buffer), tx_size_bytes),
        timeout);
  }

  // Read bytes. The signal on the bus should appear as follows:
  //   START + I2C Address + READ(1) + RX_BUFFER_BYTES + STOP
  //
  // The timeout defines the minimum duration one may block waiting for both
  // exclusive bus access and the completion of the I2C transaction.
  //
  // Preconditions:
  // The Address must be supported by the Initiator, i.e. do not use a 10
  //     address if the Initiator only supports 7 bit. This will assert.
  //
  // Returns:
  // Ok - Success.
  // InvalidArgument - device_address is larger than the 10 bit address space.
  // DeadlineExceeded - Was unable to acquire exclusive Initiator access
  //   and complete the I2C transaction in time.
  // Unavailable - NACK condition occurred, meaning the addressed device did
  //   not respond or was unable to process the request.
  // FailedPrecondition - The interface is not currently initialized and/or
  //    enabled.
  Status ReadFor(Address device_address,
                 ByteSpan rx_buffer,
                 chrono::SystemClock::duration timeout) {
    return WriteReadFor(device_address, ConstByteSpan(), rx_buffer, timeout);
  }
  Status ReadFor(Address device_address,
                 void* rx_buffer,
                 size_t rx_size_bytes,
                 chrono::SystemClock::duration timeout) {
    return ReadFor(device_address,
                   span(static_cast<std::byte*>(rx_buffer), rx_size_bytes),
                   timeout);
  }

  // Probes the device for an I2C ACK after only writing the address.
  // This is done by attempting to read a single byte from the specified device.
  //
  // The timeout defines the minimum duration one may block waiting for both
  // exclusive bus access and the completion of the I2C transaction.
  //
  // Preconditions:
  // The Address must be supported by the Initiator, i.e. do not use a 10
  //     address if the Initiator only supports 7 bit. This will assert.
  //
  // Returns:
  // Ok - Success.
  // InvalidArgument - device_address is larger than the 10 bit address space.
  // DeadlineExceeded - Was unable to acquire exclusive Initiator access
  //   and complete the I2C transaction in time.
  // Unavailable - NACK condition occurred, meaning the addressed device did
  //   not respond or was unable to process the request.
  // FailedPrecondition - The interface is not currently initialized and/or
  //    enabled.
  Status ProbeDeviceFor(Address device_address,
                        chrono::SystemClock::duration timeout) {
    std::byte ignored_buffer[1] = {};  // Read a byte to probe.
    return WriteReadFor(
        device_address, ConstByteSpan(), ignored_buffer, timeout);
  }

 private:
  virtual Status DoWriteReadFor(Address device_address,
                                ConstByteSpan tx_buffer,
                                ByteSpan rx_buffer,
                                chrono::SystemClock::duration timeout) = 0;
};

}  // namespace pw::i2c