dif_i2c.h

To use this DIF, include the following C header:

#include "/workspace/sw/device/lib/dif/dif_i2c.h"

This header provides the following device interface functions:

Generated from dif_i2c.h
// Copyright lowRISC contributors.
// Licensed under the Apache License, Version 2.0, see LICENSE for details.
// SPDX-License-Identifier: Apache-2.0

#ifndef OPENTITAN_SW_DEVICE_LIB_DIF_DIF_I2C_H_
#define OPENTITAN_SW_DEVICE_LIB_DIF_DIF_I2C_H_

/**
 * @file
 * @brief <a href="/hw/ip/i2c/doc/">I2C</a> Device Interface Functions
 */

#include <stdbool.h>
#include <stdint.h>

#include "sw/device/lib/base/macros.h"
#include "sw/device/lib/base/mmio.h"
#include "sw/device/lib/dif/dif_base.h"

#include "sw/device/lib/dif/autogen/dif_i2c_autogen.h"

#ifdef __cplusplus
extern "C" {
#endif  // __cplusplus

/**
 * Represents a speed setting for an I2C component: standard, fast, and
 * fast plus, corresponding to 100 kbaud, 400 kbaud, and 1 Mbaud,
 * respectively.
 */
typedef enum dif_i2c_speed {
  /**
   * Standard speed, 100 kilobaud.
   */
  kDifI2cSpeedStandard,
  /**
   * Fast speed, 400 kilobaud.
   */
  kDifI2cSpeedFast,
  /**
   * Fast plus speed, 1 megabaud.
   */
  kDifI2cSpeedFastPlus,
} dif_i2c_speed_t;

/**
 * Timing configuration parameters for I2C.
 *
 * While the I2C device requires ten parameters to describe its timing
 * configuration, the degrees of freedom of those parameters is constrained to
 * the ones in this struct.
 *
 * See `dif_i2c_compute_timing()`
 */
typedef struct dif_i2c_timing_config {
  /**
   * The lowest speed at which an I2C target connected to this host will
   * operate.
   *
   * In other words, this is the maximum speed at which the host can operate
   * without going over what the target devices can handle.
   */
  dif_i2c_speed_t lowest_target_device_speed;
  /**
   * The period of the clock driving this device, in nanoseconds.
   *
   * This value should not be zero, since it is used as a divisor for
   * division.
   */
  uint32_t clock_period_nanos;
  /**
   * The expected time it takes for the I2C bus signal to rise, in nanoseconds.
   *
   * This value is dependent on properties of the hardware's interconnect, and
   * not under actual firmware control.
   */
  uint32_t sda_rise_nanos;
  /**
   * The expected time for the bus signal to fall, similar to `sda_rise_nanos`.
   */
  uint32_t sda_fall_nanos;
  /**
   * The desired period of the SCL line, in nanoseconds.
   *
   * Normally, this should just be `1'000'000 / lowest_target_device_speed`,
   * but the period may be larger if desired.
   *
   * Setting this value to zero will result in the minimum period being used.
   */
  uint32_t scl_period_nanos;
} dif_i2c_timing_config_t;

/**
 * Configuration for the addressing behavior of the I2C, can be disabled or
 * configured to look for multiple addresses by masking certain bits. A mask of
 * 0x7f will match only a single address.
 */
typedef struct dif_i2c_id {
  /**
   * Mask the recieved I2C address before checking for a match. Received Address
   * & mask must equal the programmed address to activate I2C Device. If Address
   * & ~mask != 0, this will not match any addresses. A mask of 0x7f will cause
   * device to only respond to an exact match. The mask is 7 bits and LSB
   * aligned.
   */
  uint8_t mask;
  /**
   * The 7-bit I2C address that should be matched after masking to cause the
   * activated I2C Target device to begin to act in a transaction. Address is
   * LSB aligned.
   */
  uint8_t address;
} dif_i2c_id_t;

/**
 * Runtime configuration for I2C.
 *
 * This struct describes runtime timing parameters. Computing these values is
 * somewhat complicated, so these fields should be initialized using the
 * `dif_i2c_compute_timing()` function. A caller is, however, free to compute
 * these values themselves if they prefer, so long as the I2C spec is
 * respected.
 *
 * These values correspond to those in Table 10 of the I2C spec, and are given
 * in units of input clock cycles.
 */
typedef struct dif_i2c_config {
  uint16_t scl_time_high_cycles;
  uint16_t scl_time_low_cycles;
  uint16_t rise_cycles;
  uint16_t fall_cycles;
  uint16_t start_signal_setup_cycles;
  uint16_t start_signal_hold_cycles;
  uint16_t data_signal_setup_cycles;
  uint16_t data_signal_hold_cycles;
  uint16_t stop_signal_setup_cycles;
  /**
   * This parameter is referred to in the I2C documents as the
   * "bus free time".
   */
  uint16_t stop_signal_hold_cycles;
} dif_i2c_config_t;

/**
 * Represents a valid watermark level for one of the I2C FIFOs.
 */
typedef enum dif_i2c_watermark_level {
  /**
   * A one-byte watermark.
   */
  kDifI2cLevel1Byte = 0,
  /**
   * A four-byte watermark.
   */
  kDifI2cLevel4Byte,
  /**
   * An eight-byte watermark.
   */
  kDifI2cLevel8Byte,
  /**
   * A sixteen-byte watermark.
   */
  kDifI2cLevel16Byte,
  /**
   * A thirty-byte watermark.
   *
   * Note that this watermark is only supported for RX, and not for FMT.
   */
  kDifI2cLevel30Byte,
} dif_i2c_level_t;

/**
 * Flags for a formatted I2C byte, used by the `dif_i2c_write_byte_raw()`
 * function.
 */
typedef struct dif_i2c_fmt_flags {
  /**
   * Causes a start signal to be sent before the byte.
   *
   * If a start has been issued during the current transaction, this will issue
   * a repeated start.
   */
  bool start;
  /**
   * Causes a stop signal to be sent after the byte.
   *
   * This flag cannot be set when both `read` and `read_cont` are set.
   */
  bool stop;
  /**
   * Causes the byte to be interpreted as an unsigned number of bytes to read
   * from the target; 0 is interpreted as 256.
   */
  bool read;
  /**
   * Requires `read` to be set; if so, once the final byte associated with this
   * read is received, it will be acknowledged, allowing the read operation to
   * continue.
   */
  bool read_cont;
  /**
   * By default, the hardware expects an ACK after every byte sent, and raises
   * an exception (surfaced as the `kDifi2cIrqNak` interrupt). This flag
   * disables that behavior.
   *
   * This flag cannot be set along with `read` or `read_cont`.
   */
  bool suppress_nak_irq;
} dif_i2c_fmt_flags_t;

/**
 * The I2C Target device records the following signals with received data
 */
typedef enum dif_i2c_signal {
  /**
   * The associated byte was received with a START signal, and should be the
   * matching address and R/W bit
   */
  kDifI2cSignalStart = 1,
  /**
   * The associated byte was received after a STOP signal, so the transaction is
   * over and the byte is junk
   */
  kDifI2cSignalStop = 2,
  /**
   * The associated byte was received with a repeated START signal and
   * represents the address for the subsequent transaction.
   */
  kDifI2cSignalRepeat = 3,
  /**
   * There's no associated STOP or START signal this is just a byte that's been
   * written to the I2C target in an ongoing transaction
   */
  kDifI2cSignalNone = 0,
} dif_i2c_signal_t;

/**
 * Available formatting codes for `dif_i2c_write_byte_raw()`.
 *
 * Each code describes how to interpret the `byte` parameter, referred to below
 * as "the byte".
 *
 * It is the caller's responsibility to observe the state transitions in the
 * comments below.
 */
typedef enum dif_i2c_fmt {
  /**
   * Start a transaction. This sends a START signal followed by the byte.
   * The byte sent will form (potentially part of) the target address for the
   * transaction.
   *
   * May be followed by any format code.
   */
  kDifI2cFmtStart,
  /**
   * Transmit byte. This simply sends the byte. It may need to be used in
   * conjunction with `Start` to send a multi-byte target address.
   *
   * May be followed by any format code.
   */
  kDifI2cFmtTx,
  /**
   * Transmit byte and stop. This sends the byte, and then sends a stop
   * signal, completing a transaction.
   *
   * Only `Start` may follow this code.
   */
  kDifI2cFmtTxStop,
  /**
   * Request `n` bytes, where `n` is the byte interpreted as an unsigned
   * integer; a byte value of `0` will be interpreted as requesting `256`
   * bytes. This will NAK the last byte.
   *
   * Only `Start` may follow this code (this code does not stop a transaction;
   * see `RxStop`).
   */
  kDifI2cFmtRx,
  /**
   * Request `n` bytes, same as `Rx`, but ACK the last byte so that more data
   * can be requested.
   *
   * May be followed by `RxContinue`, `Rx`, or `RxStop`.
   */
  kDifI2cFmtRxContinue,
  /**
   * Request `n` bytes, same as `Rx`, but, after NAKing the last byte, send a
   * stop signal to end the transaction.
   *
   * Only `Start` may follow this code.
   */
  kDifI2cFmtRxStop,
} dif_i2c_fmt_t;

/**
 * Flags representing the status of an I2C block
 */
typedef struct dif_i2c_status {
  /**
   * Enable Host, I2C block has been initialized and enabled to act as a host,
   * consuming entries from the FMT FIFO to perform I2C transactions, and
   * writing the results of I2C Reads to the RX FIFO
   */
  bool enable_host;
  /**
   * Enable Target, I2C block has been initialized and enabled to act as a
   * target device, using the TX FIFO to respond to I2C Reads and the ACQ FIFO
   * to store data received from I2C Writes
   */
  bool enable_target;
  /**
   * Line Loopback enabled
   */
  bool line_loopback;
  /**
   * Format FIFO is full, SW cannot write commands to transact into the FIFO
   * until I2C host is able to act on the contents.
   */
  bool fmt_fifo_full;
  /**
   * RX FIFO is full, I2C cannot continue to read data until SW reads from the
   * FIFO.
   */
  bool rx_fifo_full;
  /**
   * Format FIFO is empty, I2C host will stop transacting until the FIFO is
   * written to.
   */
  bool fmt_fifo_empty;
  /**
   * RX FIFO Empty, Software has handled all bytes read by the I2C Host.
   */
  bool rx_fifo_empty;
  /**
   * I2C Host is not carrying out a transaction
   */
  bool host_idle;
  /**
   * I2C Device is not carrying out a transaction
   */
  bool target_idle;
  /**
   * TX FIFO Full, I2C device must respond to I2C reads or have the FIFO cleared
   * before SW can write to the FIFO.
   */
  bool tx_fifo_full;
  /**
   * Acquire FIFO is full of data from I2C writes to the target device. Software
   * must handle data before I2C device can continue
   */
  bool acq_fifo_full;
  /**
   * TX FIFO is empty, device is unprepared to respond to I2C Reads until SW
   * writes to FIFO
   */
  bool tx_fifo_empty;
  /**
   * Aquire FIFO is empty and will remain so until I2C Device recieves a Write
   */
  bool acq_fifo_empty;
} dif_i2c_status_t;

/**
 * Get I2C status.
 *
 * @param i2c An I2C handle.
 * @param[out] status I2C status as understood by the block.
 * @return The result of the operation.
 */
OT_WARN_UNUSED_RESULT
dif_result_t dif_i2c_get_status(const dif_i2c_t *i2c, dif_i2c_status_t *status);

/**
 * Computes timing parameters for an I2C host and stores them in `config`.
 *
 * Timing is based on requirements for devices attached to OpenTitan
 *
 * The values returned may be tweaked by callers that require finer control over
 * some of the calculations, such as how the allocation of a lengthened SCL
 * period.
 *
 * @param timing_config Configuration values for producing timing parameters.
 * @param[out] config I2C configuration to which to apply the computed
 * parameters.
 * @return The result of the operation.
 */
OT_WARN_UNUSED_RESULT
dif_result_t dif_i2c_compute_timing(dif_i2c_timing_config_t timing_config,
                                    dif_i2c_config_t *config);

/**
 * Configures I2C with runtime information.
 *
 * This function should need to be called once for the lifetime of `handle`.
 *
 * @param i2c An I2C handle.
 * @param config Runtime configuration parameters.
 * @return The result of the operation.
 */
OT_WARN_UNUSED_RESULT
dif_result_t dif_i2c_configure(const dif_i2c_t *i2c, dif_i2c_config_t config);

/**
 * Resets the state of the RX FIFO, essentially dropping all received bytes for
 * the host.
 *
 * @param i2c An I2c handle.
 * @return The result of the operation.
 */
OT_WARN_UNUSED_RESULT
dif_result_t dif_i2c_reset_rx_fifo(const dif_i2c_t *i2c);

/**
 * Resets the state of the FMT FIFO, essentially dropping all scheduled
 * operations.
 *
 * @param i2c An I2c handle.
 * @return The result of the operation.
 */
OT_WARN_UNUSED_RESULT
dif_result_t dif_i2c_reset_fmt_fifo(const dif_i2c_t *i2c);

/**
 * Resets the state of the TX FIFO, essentially dropping all scheduled
 * responses.
 *
 * @param i2c An I2c handle.
 * @return The result of the operation.
 */
OT_WARN_UNUSED_RESULT
dif_result_t dif_i2c_reset_tx_fifo(const dif_i2c_t *i2c);

/**
 * Resets the state of the ACQ FIFO, essentially dropping all received bytes for
 * the target device.
 *
 * @param i2c An I2c handle.
 * @return The result of the operation.
 */
OT_WARN_UNUSED_RESULT
dif_result_t dif_i2c_reset_acq_fifo(const dif_i2c_t *i2c);

/**
 * Sets watermarks for for the RX and FMT FIFOs, which will fire the respective
 * interrupts when each fifo exceeds, or falls below, the set level.
 *
 * Note that the 30-byte level is only supported for the RX FIFO: trying to use
 * it with the FMT FIFO is an error.
 *
 * @param i2c An I2C handle.
 * @param rx_level The desired watermark level for the RX FIFO.
 * @param fmt_level The desired watermark level for the FMT FIFO.
 * @return The result of the operation.
 */
OT_WARN_UNUSED_RESULT
dif_result_t dif_i2c_set_watermarks(const dif_i2c_t *i2c,
                                    dif_i2c_level_t rx_level,
                                    dif_i2c_level_t fmt_level);

/**
 * Enables or disables the "Host I2C" functionality,
 * This function should be called to enable the device
 * once timings, interrupts, and watermarks are all configured.
 *
 * @param i2c An I2C handle.
 * @param state The new toggle state for the host functionality.
 * @return The result of the operation.
 */
OT_WARN_UNUSED_RESULT
dif_result_t dif_i2c_host_set_enabled(const dif_i2c_t *i2c, dif_toggle_t state);

/**
 * Enables or disables the "Device I2C" functionality,
 * This function should be called to enable the device
 * once address, and interrupts are all configured.
 *
 * @param i2c An I2C handle.
 * @param state The new toggle state for the device functionality.
 * @return The result of the operation.
 */
OT_WARN_UNUSED_RESULT
dif_result_t dif_i2c_device_set_enabled(const dif_i2c_t *i2c,
                                        dif_toggle_t state);

/**
 * Enables or disables the Line Loopback functionality,
 * This function should be called to assist debugging by setting the i2c block
 * or host to use received transactions to populate outgoing transactions.
 *
 * @param i2c An I2C handle.
 * @param state The new toggle state for the host functionality.
 * @return The result of the operation.
 */
OT_WARN_UNUSED_RESULT
dif_result_t dif_i2c_line_loopback_set_enabled(const dif_i2c_t *i2c,
                                               dif_toggle_t state);

/**
 * Enables or disables the "override mode". In override mode, software is able
 * to directly control the driven values of the SCL and SDA lines using
 * `dif_i2c_override_drive_pins()`.
 *
 * @param i2c An I2C handle.
 * @param state The new toggle state for override mode.'
 * @return The result of the operation.
 */
OT_WARN_UNUSED_RESULT
dif_result_t dif_i2c_override_set_enabled(const dif_i2c_t *i2c,
                                          dif_toggle_t state);

/**
 * Drives the SCL and SDA pins to the given values when "override mode" is
 * enabled.
 *
 * @param i2c An I2C handle.
 * @param scl The value to drive SCL to.
 * @param sda The value to drive SDA to.
 * @return The result of the operation.
 */
OT_WARN_UNUSED_RESULT
dif_result_t dif_i2c_override_drive_pins(const dif_i2c_t *i2c, bool scl,
                                         bool sda);

/**
 * Returns oversampling of the last 16 values of the SCL and SDA pins, with the
 * zeroth bit being the most recent.
 *
 * @param i2c An I2C handle.
 * @param[out] scl_samples SCL sample bits; may be `NULL`.
 * @param[out] sda_samples SDA sample bits; may be `NULL`.
 * @return The result of the operation.
 */
OT_WARN_UNUSED_RESULT
dif_result_t dif_i2c_override_sample_pins(const dif_i2c_t *i2c,
                                          uint16_t *scl_samples,
                                          uint16_t *sda_samples);

/**
 * Returns the current levels, i.e., number of entries, in the FMT, RX, TX and
 * ACQ FIFOs.
 * These values represent the number of entries pending for send by host
 * hardware, entries pending for read by host software, entries pending for send
 * by device hardware, and entries pending for read by device software
 * respectively.
 *
 * @param i2c An I2C handle.
 * @param[out] fmt_fifo_level The number of unsent FMT bytes; may be `NULL`.
 * @param[out] rx_fifo_level The number of unread RX bytes; may be `NULL`.
 * @param[out] tx_fifo_level The number of unread TX bytes; may be `NULL`.
 * @param[out] acq_fifo_level The number of unread ACQ bytes; may be `NULL`.
 * @return The result of the operation.
 */
OT_WARN_UNUSED_RESULT
dif_result_t dif_i2c_get_fifo_levels(const dif_i2c_t *i2c,
                                     uint8_t *fmt_fifo_level,
                                     uint8_t *rx_fifo_level,
                                     uint8_t *tx_fifo_level,
                                     uint8_t *acq_fifo_level);

/**
 * Pops an entry (a byte) off of the RX FIFO. Passing in `NULL` to the out-param
 * will still trigger a byte pop.
 *
 * @param i2c An I2C handle.
 * @param[out] byte The popped byte; may be `NULL`.
 * @return The result of the operation.
 */
OT_WARN_UNUSED_RESULT
dif_result_t dif_i2c_read_byte(const dif_i2c_t *i2c, uint8_t *byte);

/**
 * Pushes a raw write entry onto the FMT FIFO, consisting of a byte and format
 * flags. This function can be called in sequence to enqueue an I2C
 * transmission.
 *
 * Callers should prefer `dif_i2c_write_byte()` instead, since that function
 * provides clearer semantics. This function should only really be used for
 * testing or troubleshooting a device.
 *
 * @param i2c An I2C handle.
 * @param byte The value to push onto the FIFO.
 * @param flags The flags to use for this write.
 * @return The result of the operation.
 */
OT_WARN_UNUSED_RESULT
dif_result_t dif_i2c_write_byte_raw(const dif_i2c_t *i2c, uint8_t byte,
                                    dif_i2c_fmt_flags_t flags);

/**
 * Pushes a write entry onto the FMT FIFO, consisting of a byte and a format
 * code. This function can be called in sequence to enqueue an I2C
 * transmission.
 *
 * @param i2c An I2C handle.
 * @param byte The value to push onto the FIFO.
 * @param code The code to use for this write.
 * @param suppress_nak_irq Whether to supress the NAK IRQ for this one byte.
 *        May not be used in combination with `Rx` codes.
 * @return The result of the operation.
 */
OT_WARN_UNUSED_RESULT
dif_result_t dif_i2c_write_byte(const dif_i2c_t *i2c, uint8_t byte,
                                dif_i2c_fmt_t code, bool suppress_nak_irq);

/**
 * Pushes a byte into the TX FIFO to make it available when this I2C block
 * responds to an I2C Read as a target device.
 *
 * @param i2c handle.
 * @param byte to write to FIFO
 * @return The result of the operation.
 */
OT_WARN_UNUSED_RESULT
dif_result_t dif_i2c_transmit_byte(const dif_i2c_t *i2c, uint8_t byte);

/**
 * Read acquired data from the ACQ FIFO, including record of starts, stops,
 * address and written data
 *
 * @param i2c handle.
 * @param[out] byte, Data received in the transaction, Could be the address or
 * junk
 * @param[out] signal, Signal received in the transaction
 * @return The result of the operation.
 */
OT_WARN_UNUSED_RESULT
dif_result_t dif_i2c_acquire_byte(const dif_i2c_t *i2c, uint8_t *byte,
                                  dif_i2c_signal_t *signal);

/**
 * Enables clock stretching timeout after a number of I2C block clock cycles
 * when I2C block is configured as host.
 *
 * @param i2c An I2C handle,
 * @param enable the timeout
 * @param cycles How many cycles to wait before timing out
 * @return The result of the operation.
 */
OT_WARN_UNUSED_RESULT
dif_result_t dif_i2c_enable_clock_stretching_timeout(const dif_i2c_t *i2c,
                                                     dif_toggle_t enable,
                                                     uint32_t cycles);

/**
 * Sets the I2C device to listen for a pair of masked addresses
 *
 * @param i2c handle,
 * @param id0 address and mask pair to listen for can be null
 * @param id1 address and mask pair to listen for can be null
 * @return The result of the operation.
 */
OT_WARN_UNUSED_RESULT
dif_result_t dif_i2c_set_device_id(const dif_i2c_t *i2c, dif_i2c_id_t *id0,
                                   dif_i2c_id_t *id1);

/**
 * Set host timeout. When OT is acting as target device, set the number of
 * counts after which to trigger a host_timeout interrupt
 *
 * @param i2c handle,
 * @param duration in clock counts
 * @return The result of the operation.
 */
OT_WARN_UNUSED_RESULT
dif_result_t dif_i2c_set_host_timeout(const dif_i2c_t *i2c, uint32_t duration);

#ifdef __cplusplus
}  // extern "C"
#endif  // __cplusplus

#endif  // OPENTITAN_SW_DEVICE_LIB_DIF_DIF_I2C_H_