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