blob: 03f04d8a29ba0b78b15c3c37a67c8879c1b8477f [file] [log] [blame]
// 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 <stdint.h>
#include "sw/device/lib/base/mmio.h"
#include "sw/device/lib/dif/dif_warn_unused_result.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 parameters for an I2C device. Actually computing these values is
* somewhat complicated, so this struct should be assembled using the
* `dif_i2c_compute_timing()` function. A caller is, however, free to assemble
* this struct or modify a return value of `dif_i2c_compute_timing()`, so long
* as the 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_timing_params {
uint16_t scl_time_high;
uint16_t scl_time_low;
uint16_t rise_time;
uint16_t fall_time;
uint16_t start_signal_setup_time;
uint16_t start_signal_hold_time;
uint16_t data_signal_setup_time;
uint16_t data_signal_hold_time;
uint16_t stop_signal_setup_time;
/**
* Note: This field is referred to in the IP documents as the,
* "bus free time".
*/
uint16_t stop_signal_hold_time;
} dif_i2c_timing_params_t;
/**
* Configuration for computing a value of `dif_i2c_timing_params`, which itself
* is used to configure an I2C device.
*/
typedef struct dif_i2c_timing_config {
/**
* Indicates the lowest speed at which an I2C target connected to this host
* will operate.
*
* In other words, this is the maxiumum speed at which the host can operate
* without going over what the target devices can handle.
*/
dif_i2c_speed_t lowest_target_device_speed;
/**
* Indicates 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;
/**
* Indicates 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;
/**
* Indicates the expected time for the bus singal to fall, similar to
* `sda_rise_nanos`.
*/
uint32_t sda_fall_nanos;
/**
* Indicates 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;
/**
* Represents an I2C device. This struct should only be initialized using
* `dif_i2c_init()`.
*/
typedef struct dif_i2c { mmio_region_t base_addr; } dif_i2c_t;
typedef enum dif_i2c_result {
kDifI2cOk = 0,
kDifI2cBadArg,
kDifI2cError,
} dif_i2c_result_t;
/**
* Represents a valid watermark level for one of the I2C FIFOs.
*/
typedef enum dif_i2c_watermark_level {
kDifI2cLevel1Byte = 0,
kDifI2cLevel4Byte,
kDifI2cLevel8Byte,
kDifI2cLevel16Byte,
kDifI2cLevel30Byte,
} dif_i2c_level_t;
/**
* Represents an I2C-related interrupt type.
*/
typedef enum dif_i2c_irq_type {
/**
* Fired when the FMT FIFO underflows its watermark.
*/
kDifI2cIrqTypeFmtWatermarkUnderflow = 0,
/**
* Fired when the RX FIFO overflows its watermark.
*/
kDifI2cIrqTypeRxWatermarkOverflow,
/**
* Fired when the FMT FIFO overflows.
*/
kDifI2cIrqTypeFmtFifoOverflow,
/**
* Fired when the RX FIFO overflows.
*/
kDifI2cIrqTypeRxFifoOverflow,
/**
* Fired when there is no ACK in response to an address or data write.
*/
kDifI2cIrqTypeNak,
/**
* Fired when the SCL line seems to have interference.
*/
kDifI2cIrqTypeSclInterference,
/**
* Fired when the SDA line seems to have interference.
*/
kDifI2cIrqTypeSdaInterference,
/**
* Fired when the target streches the clock beyond the allowed period.
*/
kDifI2cIrqTypeClockStretchTimeout,
/**
* Fired when the target does not maintain a stable SDA line.
*/
kDifI2cIrqTypeSdaUnstable,
} dif_i2c_irq_type_t;
/**
* Represents an enablement state.
*/
typedef enum dif_i2c_enable {
kDifI2cEnabled,
kDifI2cDisabled,
} dif_i2c_enable_t;
/**
* Computes timing parameters for an I2C device using the input parameters in
* `config`.
*
* The value 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 config Configuration values for producing timing parameters.
* @param out Out-param for the actual timing parameters.
* @return The result of the operation.
*/
DIF_WARN_UNUSED_RESULT
dif_i2c_result_t dif_i2c_compute_timing(const dif_i2c_timing_config_t *config,
dif_i2c_timing_params_t *out);
/**
* Initializes an I2C device using the given timing parameters.
*
* `dif_i2c_compute_timing()` can be used to generate default values for the
* timing parameters based off of a small number of hardware parameters.
*
* @oaram base_addr The start of the I2C device register.
* @param timing Timing parameters to initialize with.
* @param i2c Out param for the initialized device.
* @return The result of the operation.
*/
DIF_WARN_UNUSED_RESULT
dif_i2c_result_t dif_i2c_init(mmio_region_t base_addr,
const dif_i2c_timing_params_t *timing,
dif_i2c_t *i2c);
/**
* Resets the state of the RX FIFO, essentially dropping all recieved bytes.
*
* @param i2c An I2C devce.
* @return The result of the operation.
*/
DIF_WARN_UNUSED_RESULT
dif_i2c_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 devce.
* @return The result of the operation.
*/
DIF_WARN_UNUSED_RESULT
dif_i2c_result_t dif_i2c_reset_fmt_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 bekow, 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 device.
* @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.
*/
DIF_WARN_UNUSED_RESULT
dif_i2c_result_t dif_i2c_set_watermarks(const dif_i2c_t *i2c,
dif_i2c_level_t rx_level,
dif_i2c_level_t fmt_level);
/**
* Returns whether a particular interrupt is currently pending.
*
* @param i2c An I2C device.
* @param type An interrupt type.
* @param flag_out Out-param for whether the interrupt is pending.
* @return The result of the operation.
*/
DIF_WARN_UNUSED_RESULT
dif_i2c_result_t dif_i2c_irq_get(const dif_i2c_t *i2c, dif_i2c_irq_type_t type,
bool *flag_out);
/**
* Clears a particular interrupt.
*
* @param i2c An I2C device.
* @param type An interrupt type.
* @return The result of the operation.
*/
DIF_WARN_UNUSED_RESULT
dif_i2c_result_t dif_i2c_irq_clear(const dif_i2c_t *i2c,
dif_i2c_irq_type_t type);
/**
* Sets whether a particular interrupt is currently enabled or disabled.
*
* @param i2c An I2C device.
* @param type An interrupt type.
* @param state The new enablement state for the interrupt.
* @return The result of the operation.
*/
DIF_WARN_UNUSED_RESULT
dif_i2c_result_t dif_i2c_irq_set_enabled(const dif_i2c_t *i2c,
dif_i2c_irq_type_t type,
dif_i2c_enable_t state);
/**
* Forces a particular interrupt to fire.
*
* @param i2c An I2C device.
* @param type An interrupt type.
* @return The result of the operation.
*/
DIF_WARN_UNUSED_RESULT
dif_i2c_result_t dif_i2c_irq_force(const dif_i2c_t *i2c,
dif_i2c_irq_type_t type);
// TODO: Implement IRQ save/restore. (#2371)
/**
* Enables or disables the "Host I2C" functionality, effectively turning the
* I2C device on or off. This function should be called to enable the device
* once timings, interrupts, and watermarks are all configured.
*
* @param i2c An I2C device.
* @param state The enablement state for the host functionality.
* @return The result of the operation.
*/
DIF_WARN_UNUSED_RESULT
dif_i2c_result_t dif_i2c_host_set_enabled(const dif_i2c_t *i2c,
dif_i2c_enable_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 device.
* @param state The enablement state for override mode.'
* @return The result of the operation.
*/
DIF_WARN_UNUSED_RESULT
dif_i2c_result_t dif_i2c_override_set_enabled(const dif_i2c_t *i2c,
dif_i2c_enable_t state);
/**
* Drives the SCL and SDA pins to the given values when "override mode" is
* enabled.
*
* @param i2c An I2C device.
* @param scl The value to drive SCL to.
* @param sda The value to drive SDA to.
* @return The result of the operation.
*/
DIF_WARN_UNUSED_RESULT
dif_i2c_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 device.
* @param scl_samples Out-param for SCL sample bits; may be null.
* @param sda_samples Out-param for SDA sample bits; may be null.
* @return The result of the operation.
*/
DIF_WARN_UNUSED_RESULT
dif_i2c_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 and RX FIFOs.
* These values represent the number of entries pending for send by hardware,
* and entries pending for read by software, respectively.
*
* @param i2c An I2C device.
* @param fmt_fifo_level Out-param for the number of unsent FMT bytes; may be
* null.
* @param rx_fifo_level Out-param for the number of unread RX bytes; may be
* null.
* @return The result of the operation.
*/
DIF_WARN_UNUSED_RESULT
dif_i2c_result_t dif_i2c_get_fifo_levels(const dif_i2c_t *i2c,
uint8_t *fmt_fifo_level,
uint8_t *rx_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 device.
* @param byte Out-param for the popped byte; may be null.
* @return The result of the opeartion.
*/
DIF_WARN_UNUSED_RESULT
dif_i2c_result_t dif_i2c_read_byte(const dif_i2c_t *i2c, uint8_t *byte);
/**
* 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 `kDifi2cIrqTypeNak` interrupt). This flag
* disables that behavior.
*
* This flag cannot be set along with `read` or `read_cont`.
*/
bool supress_nak_irq;
} dif_i2c_fmt_flags_t;
/**
* 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 device.
* @param byte The value to push onto the FIFO.
* @param flags The flags to use for this write.
* @return The result of the operation.
*/
DIF_WARN_UNUSED_RESULT
dif_i2c_result_t dif_i2c_write_byte_raw(const dif_i2c_t *i2c, uint8_t byte,
dif_i2c_fmt_flags_t flags);
/**
* 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;
/**
* 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 device.
* @param byte The value to push onto the FIFO.
* @param code The code to use for this write.
* @param supress_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.
*/
DIF_WARN_UNUSED_RESULT
dif_i2c_result_t dif_i2c_write_byte(const dif_i2c_t *i2c, uint8_t byte,
dif_i2c_fmt_t code, bool supress_nak_irq);
#ifdef __cplusplus
} // extern "C"
#endif // __cplusplus
#endif // OPENTITAN_SW_DEVICE_LIB_DIF_DIF_I2C_H_