blob: c030ddee2673d37f179972d5b097dfd3aa236c19 [file] [log] [blame]
// 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".
///
/// `pw::i2c::Initiator` isn't required to support 10-bit addressing. If only
/// 7-bit addressing is supported, `pw::i2c::Initiator` fails a runtime
/// assertion 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 and disabling, unsticking SDA, and detecting device
/// address registration collisions.
///
/// @note `pw::i2c::Initiator` uses internal synchronization, so it's safe to
/// initiate transactions from multiple threads. However, a combined write and
/// read transaction may not be atomic when there are multiple initiators 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;
/// Writes bytes to an I2C device and then reads bytes from that same
/// device as either one atomic I2C transaction or two independent I2C
/// transactions.
///
/// If the I2C bus is a multi-initiator then the implementer of this
/// class **must** ensure it's a single-atomic transaction.
///
/// The signal on the bus for the atomic transaction should look like this:
///
/// @code
/// START + I2C_ADDRESS + WRITE(0) + TX_BUFFER_BYTES +
/// START + I2C_ADDRESS + READ(1) + RX_BUFFER_BYTES + STOP
/// @endcode
///
/// The signal on the bus for the two independent transactions should look
/// like this:
///
/// @code
/// START + I2C_ADDRESS + WRITE(0) + TX_BUFFER_BYTES + STOP
/// START + I2C_ADDRESS + READ(1) + RX_BUFFER_BYTES + STOP
/// @endcode
///
/// @param[in] device_address The address of the I2C device.
///
/// @param[in] tx_buffer The transmit buffer.
///
/// @param[out] rx_buffer The receive buffer.
///
/// @param[in] timeout The maximum duration to block waiting for both
/// exclusive bus access and the completion of the I2C transaction or
/// transactions.
///
/// @pre The provided address must be supported by the initiator. I.e.
/// don't use a 10-bit address if the initiator only supports 7-bit
/// addresses. This method fails a runtime assertion if this precondition
/// isn't met.
///
/// @returns @rst
///
/// .. pw-status-codes::
///
/// OK: The transaction or transactions succeeded.
///
/// INVALID_ARGUMENT: The device address provided is bigger than 10 bits.
///
/// DEADLINE_EXCEEDED: Was unable to acquire exclusive initiator access
/// and complete the I2C transaction in time.
///
/// UNAVAILABLE: A NACK condition occurred, meaning the addressed device
/// didn't respond or was unable to process the request.
///
/// FAILED_PRECONDITION: The interface isn't initialized or enabled.
///
/// @endrst
Status WriteReadFor(Address device_address,
ConstByteSpan tx_buffer,
ByteSpan rx_buffer,
chrono::SystemClock::duration timeout) {
return DoWriteReadFor(device_address, tx_buffer, rx_buffer, timeout);
}
/// A variation of `pw::i2c::Initiator::WriteReadFor` that accepts explicit
/// sizes for the amount of bytes to write to the device and read from the
/// device.
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 to the I2C device.
///
/// The signal on the bus should look like this:
///
/// @code
/// START + I2C_ADDRESS + WRITE(0) + TX_BUFFER_BYTES + STOP
/// @endcode
///
/// @param[in] device_address The address of the I2C device.
///
/// @param[in] tx_buffer The transmit buffer.
///
/// @param[out] rx_buffer The receive buffer.
///
/// @param[in] timeout The maximum duration to block waiting for both
/// exclusive bus access and the completion of the I2C transaction.
///
/// @pre The provided address must be supported by the initiator. I.e.
/// don't use a 10-bit address if the initiator only supports 7-bit
/// addresses. This method fails a runtime assertion if this precondition
/// isn't met.
///
/// @returns @rst
///
/// .. pw-status-codes::
///
/// OK: The transaction succeeded.
///
/// INVALID_ARGUMENT: The device address provided is bigger than 10 bits.
///
/// DEADLINE_EXCEEDED: Was unable to acquire exclusive initiator access
/// and complete the I2C transaction in time.
///
/// UNAVAILABLE: A NACK condition occurred, meaning the addressed device
/// didn't respond or was unable to process the request.
///
/// FAILED_PRECONDITION: The interface isn't initialized or enabled.
///
/// @endrst
Status WriteFor(Address device_address,
ConstByteSpan tx_buffer,
chrono::SystemClock::duration timeout) {
return WriteReadFor(device_address, tx_buffer, ByteSpan(), timeout);
}
/// A variation of `pw::i2c::Initiator::WriteFor` that accepts an explicit
/// size for the amount of bytes to write to the device.
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);
}
/// Reads bytes from an I2C device.
///
/// The signal on the bus should look like this:
///
/// @code
/// START + I2C_ADDRESS + READ(1) + RX_BUFFER_BYTES + STOP
/// @endcode
///
/// @param[in] device_address The address of the I2C device.
///
/// @param[out] rx_buffer The receive buffer.
///
/// @param[in] timeout The maximum duration to block waiting for both
/// exclusive bus access and the completion of the I2C transaction.
///
/// @pre The provided address must be supported by the initiator. I.e.
/// don't use a 10-bit address if the initiator only supports 7-bit
/// addresses. This method fails a runtime assertion if this precondition
/// isn't met.
///
/// @returns @rst
///
/// .. pw-status-codes::
///
/// OK: The transaction succeeded.
///
/// INVALID_ARGUMENT: The device address provided is bigger than 10 bits.
///
/// DEADLINE_EXCEEDED: Was unable to acquire exclusive initiator access
/// and complete the I2C transaction in time.
///
/// UNAVAILABLE: A NACK condition occurred, meaning the addressed device
/// didn't respond or was unable to process the request.
///
/// FAILED_PRECONDITION: The interface isn't initialized or enabled.
///
/// @endrst
Status ReadFor(Address device_address,
ByteSpan rx_buffer,
chrono::SystemClock::duration timeout) {
return WriteReadFor(device_address, ConstByteSpan(), rx_buffer, timeout);
}
/// A variation of `pw::i2c::Initiator::ReadFor` that accepts an explicit
/// size for the amount of bytes to read from the device.
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.
///
/// @param[in] device_address The address of the I2C device.
///
/// @param[in] timeout The maximum duration to block waiting for both
/// exclusive bus access and the completion of the I2C transaction.
///
/// @pre The provided address must be supported by the initiator. I.e.
/// don't use a 10-bit address if the initiator only supports 7-bit
/// addresses. This method fails a runtime assertion if this precondition
/// isn't met.
///
/// @returns @rst
///
/// .. pw-status-codes::
///
/// OK: The transaction succeeded.
///
/// INVALID_ARGUMENT: The device address provided is bigger than 10 bits.
///
/// DEADLINE_EXCEEDED: Was unable to acquire exclusive initiator access
/// and complete the I2C transaction in time.
///
/// UNAVAILABLE: A NACK condition occurred, meaning the addressed device
/// didn't respond or was unable to process the request.
///
/// FAILED_PRECONDITION: The interface isn't initialized or enabled.
///
/// @endrst
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