sonata: v1.0 I2C driver
Notable changes from the v0.2 I2C device/driver:
1. The RX FIFO now has 8 entries.
- The previous driver assumed a 256 entry RX FIFO where it was only
64 entries.
2. Adds the additional / modified register and field descriptions from
the most recent update. It also replaces the clearing of what used to
be the "Nack" interrupt with handling of the "ControllerHalt"
interrupt by resetting all possible controller events (which include
Nack, but can also include timeouts or loss of arbitration).
3. Blocking write currently blocks only when the FIFO is full. This
makes it block till the FIFO is actually emptied, and checks in the
write whether the controller FSM halted (e.g. due to a NACK), and
clears it if so. This allows the I2C `blocking_write` function to be
reliably used and stops it hanging upon a write failure when e.g. the
device doesn't exist. Without this, the write will appear to be
successful but then a `blocking_read` will just halt indefinitely as
the format FIFO will never be empty, which is not ideal. Now you know
when calling `blocking_write` whether it failed or not, and can
accordingly choose whether to call `blocking_read`.
Co-authored-by: Alex Jones <alex.jones@lowrisc.org>
diff --git a/sdk/include/platform/sunburst/platform-i2c.hh b/sdk/include/platform/sunburst/platform-i2c.hh
new file mode 100644
index 0000000..ef93eee
--- /dev/null
+++ b/sdk/include/platform/sunburst/platform-i2c.hh
@@ -0,0 +1,502 @@
+#pragma once
+#include <cdefs.h>
+#include <debug.hh>
+#include <stdint.h>
+
+/**
+ * Driver for the OpenTitan's I2C block.
+ *
+ * Documentation source can be found at:
+ * https://github.com/lowRISC/opentitan/tree/4fe1b8dd1a09af9dbc242434481ae031955dfd85/hw/ip/i2c
+ */
+struct OpenTitanI2c
+{
+ /// Interrupt State Register
+ uint32_t interruptState;
+ /// Interrupt Enable Register
+ uint32_t interruptEnable;
+ /// Interrupt Test Register
+ uint32_t interruptTest;
+ /// Alert Test Register (Unused in Sonata)
+ uint32_t alertTest;
+ /// I2C Control Register
+ uint32_t control;
+ /// I2C Live Status Register for Host and Target modes
+ uint32_t status;
+ /// I2C Read Data
+ uint32_t readData;
+ /// I2C Host Format Data
+ uint32_t formatData;
+ /// I2C FIFO control register
+ uint32_t fifoCtrl;
+ /// Host mode FIFO configuration
+ uint32_t hostFifoConfiguration;
+ /// Target mode FIFO configuration
+ uint32_t targetFifoConfiguration;
+ /// Host mode FIFO status register
+ uint32_t hostFifoStatus;
+ /// Target mode FIFO status register
+ uint32_t targetFifoStatus;
+ /// I2C Override Control Register
+ uint32_t override;
+ /// Oversampled Receive values
+ uint32_t values;
+ /**
+ * Detailed I2C Timings (directly corresponding to table 10 in the I2C
+ * Specification).
+ */
+ uint32_t timing[5];
+ /// I2C clock stretching timeout control.
+ uint32_t timeoutControl;
+ /// I2C target address and mask pairs
+ uint32_t targetId;
+ /// I2C target acquired data
+ uint32_t acquiredData;
+ /// I2C target transmit data
+ uint32_t transmitData;
+ /**
+ * I2C host clock generation timeout value (in units of input clock
+ * frequency).
+ */
+ uint32_t hostTimeoutControl;
+ /// I2C target internal stretching timeout control.
+ uint32_t targetTimeoutControl;
+ /**
+ * Number of times the I2C target has NACK'ed a new transaction since the
+ * last read of this register.
+ */
+ uint32_t targetNackCount;
+ /**
+ * Controls for mid-transfer (N)ACK phase handling.
+ */
+ uint32_t targetAckControl;
+ /// The data byte pending to be written to the Acquire (ACQ) FIFO.
+ uint32_t acquireFifoNextData;
+ /**
+ * Timeout in Host-Mode for an unhandled NACK before hardware automatically
+ * ends the transaction.
+ */
+ uint32_t hostNackHandlerTimeout;
+ /// Latched events that explain why the controller halted.
+ uint32_t controllerEvents;
+ /**
+ * Latched events that can cause the target module to stretch the clock at
+ * the beginning of a read transfer.
+ */
+ uint32_t targetEvents;
+
+ /**
+ * The interrupts of the OpenTitan's I2C block.
+ *
+ * Documentation source can be found at:
+ * https://github.com/lowRISC/opentitan/blob/4fe1b8dd1a09af9dbc242434481ae031955dfd85/hw/ip/i2c/doc/interfaces.md
+ */
+ enum class Interrupt
+ {
+ /**
+ * A host mode interrupt. This is asserted whilst the Format FIFO level
+ * is below the low threshold. This is a level status interrupt.
+ */
+ FormatThreshold,
+ /**
+ * A host mode interrupt. This is asserted whilst the Receive FIFO level
+ * is above the high threshold. This is a level status interrupt.
+ */
+ ReceiveThreshold,
+ /**
+ * A target mode interrupt. This is asserted whilst the Aquired FIFO
+ * level is above the high threshold. This is a level status interrupt.
+ */
+ AcquiredThreshold,
+ /**
+ * A host mode interrupt. This is raised if the Receive FIFO has
+ * overflowed.
+ */
+ ReceiveOverflow,
+ /**
+ * A host mode interrupt. This is raised if the controller FSM is
+ * halted, such as on an unexpected NACK or lost arbitration. Check the
+ * `controllerEvents` register for the reason. The interrupt will only
+ * be released when the bits in `controllerEvents` are cleared.
+ */
+ ControllerHalt,
+ /**
+ * A host mode interrupt. This is raised if the SCL line drops early
+ * (not supported without clock synchronization).
+ */
+ SclInterference,
+ /**
+ * A host mode interrupt. This is raised if the SDA line goes low when
+ * host is trying to assert high.
+ */
+ SdaInterference,
+ /**
+ * A host mode interrupt. This is raised if target stretches the clock
+ * beyond the allowed timeout period.
+ */
+ StretchTimeout,
+ /**
+ * A host mode interrupt. This is raised if the target does not assert a
+ * constant value of SDA during transmission.
+ */
+ SdaUnstable,
+ /**
+ * A host and target mode interrupt. In host mode, raised if the host
+ * issues a repeated START or terminates the transaction by issuing
+ * STOP. In target mode, raised if the external host issues a STOP or
+ * repeated START.
+ */
+ CommandComplete,
+ /**
+ * A target mode interrupt. This is raised if the target is stretching
+ * clocks for a read command. This is a level status interrupt.
+ */
+ TransmitStretch,
+ /**
+ * A target mode interrupt. This is asserted whilst the Transmit FIFO
+ * level is below the low threshold. This is a level status interrupt.
+ */
+ TransmitThreshold,
+ /**
+ * A target mode interrupt. This is raised if the target is stretching
+ * clocks due to full Aquired FIFO or zero count in
+ * targetAckControl.NBYTES (if enabled). This is a level status
+ * interrupt.
+ */
+ AcquiredFull,
+ /**
+ * A target mode interrupt. This is raised if STOP is received without a
+ * preceding NACK during an external host read.
+ */
+ UnexpectedStop,
+ /**
+ * A target mode interrupt. This is raised if the host stops sending the
+ * clock during an ongoing transaction.
+ */
+ HostTimeout,
+ };
+
+ static constexpr uint32_t interrupt_bit(const Interrupt Interrupt)
+ {
+ return 1 << static_cast<uint32_t>(Interrupt);
+ };
+
+ /// Control Register Fields
+ enum [[clang::flag_enum]] : uint32_t{
+ /// Enable Host I2C functionality
+ ControlEnableHost = 1 << 0,
+ /// Enable Target I2C functionality
+ ControlEnableTarget = 1 << 1,
+ /// Enable I2C line loopback test If line loopback is enabled, the
+ /// internal design sees ACQ and RX data as "1"
+ ControlLineLoopback = 1 << 2,
+ /// Enable NACKing the address on a stretch timeout. This is a target
+ /// mode feature. If enabled, a stretch timeout will cause the device to
+ /// NACK the address byte. If disabled, it will ACK instead.
+ ControlNackAddressAfterTimeout = 1 << 3,
+ /// Enable ACK Control Mode, which works with the `targetAckControl`
+ /// register to allow software to control upper-layer (N)ACKing.
+ ControlAckControlEnable = 1 << 4,
+ /// Enable the bus monitor in multi-controller mode.
+ ControlMultiControllerMonitorEnable = 1 << 5,
+ /// If set, causes a read transfer addressed to the this target to set
+ /// the corresponding bit in the `targetEvents` register. While the
+ /// `transmitPending` field is 1, subsequent read transactions will
+ /// stretch the clock, even if there is data in the Transmit FIFO.
+ ControlTransmitStretchEnable = 1 << 6,
+ };
+
+ /// Status Register Fields
+ enum [[clang::flag_enum]] : uint32_t{
+ /// Host mode Format FIFO is full
+ StatusFormatFull = 1 << 0,
+ /// Host mode Receive FIFO is full
+ StatusReceiveFull = 1 << 1,
+ /// Host mode Format FIFO is empty
+ StatusFormatEmpty = 1 << 2,
+ /// Host functionality is idle. No Host transaction is in progress
+ StatusHostIdle = 1 << 3,
+ /// Target functionality is idle. No Target transaction is in progress
+ StatusTargetIdle = 1 << 4,
+ /// Host mode Receive FIFO is empty
+ SmatusReceiveEmpty = 1 << 5,
+ /// Target mode Transmit FIFO is full
+ StatusTransmitFull = 1 << 6,
+ /// Target mode Acquired FIFO is full
+ StatusAcquiredFull = 1 << 7,
+ /// Target mode Transmit FIFO is empty
+ StatusTransmitEmpty = 1 << 8,
+ /// Target mode Acquired FIFO is empty
+ StatusAcquiredEmpty = 1 << 9,
+ /// Target mode stretching at (N)ACK phase due to zero count
+ /// in the `targetAckControl` register.
+ StatusAckControlStretch = 1 << 10,
+ };
+
+ /// FormatData Register Fields
+ enum [[clang::flag_enum]] : uint32_t{
+ /// Issue a START condition before transmitting BYTE.
+ FormatDataStart = 1 << 8,
+ /// Issue a STOP condition after this operation
+ FormatDataStop = 1 << 9,
+ /// Read BYTE bytes from I2C. (256 if BYTE==0)
+ FormatDataReadBytes = 1 << 10,
+ /**
+ * Do not NACK the last byte read, let the read
+ * operation continue
+ */
+ FormatDataReadCount = 1 << 11,
+ /// Do not signal an exception if the current byte is not ACK’d
+ FormatDataNakOk = 1 << 12,
+ };
+
+ /// FifoControl Register Fields
+ enum [[clang::flag_enum]] : uint32_t{
+ /// Receive fifo reset. Write 1 to the register resets it. Read returns 0
+ FifoControlReceiveReset = 1 << 0,
+ /// Format fifo reset. Write 1 to the register resets it. Read returns 0
+ FifoControlFormatReset = 1 << 1,
+ /// Acquired FIFO reset. Write 1 to the register resets it. Read returns 0
+ FifoControlAcquiredReset = 1 << 7,
+ /// Transmit FIFO reset. Write 1 to the register resets it. Read returns 0
+ FifoControlTransmitReset = 1 << 8,
+ };
+
+ /// ControllerEvents Register Fields
+ enum [[clang::flag_enum]] : uint32_t{
+ /// Controller FSM is halted due to receiving an unexpected NACK.
+ ControllerEventsNack = 1 << 0,
+ /**
+ * Controller FSM is halted due to a Host-Mode active transaction being
+ * ended by the `hostNackHandlerTimeout` mechanism.
+ */
+ ControllerEventsUnhandledNackTimeout = 1 << 1,
+ /**
+ * Controller FSM is halted due to a Host-Mode active transaction being
+ * terminated because of a bus timeout activated by `timeoutControl`.
+ */
+ ControllerEventsBusTimeout = 1 << 2,
+ /**
+ * Controller FSM is halted due to a Host-Mode active transaction being
+ * terminated because of lost arbitration.
+ */
+ ControllerEventsArbitrationLost = 1 << 3,
+ };
+
+ // Referred to as 'RX FIFO' in the documentation
+ static constexpr uint32_t ReceiveFifoDepth = 8;
+
+ /// Flag set when we're debugging this driver.
+ static constexpr bool DebugOpenTitanI2c = true;
+
+ /// Helper for conditional debug logs and assertions.
+ using Debug = ConditionalDebug<DebugOpenTitanI2c, "OpenTitan I2C">;
+
+ /**
+ * Performs a 32-bit integer unsigned division, rounding up. The bottom
+ * 16 bits of the result are then returned.
+ *
+ * As usual, a divisor of 0 is still Undefined Behavior.
+ */
+ static uint16_t round_up_divide(uint32_t a, uint32_t b)
+ {
+ if (a == 0)
+ {
+ return 0;
+ }
+ const uint32_t Res = ((a - 1) / b) + 1;
+ Debug::Assert(Res <= UINT16_MAX,
+ "Division result too large to fit in uint16_t.");
+ return static_cast<uint16_t>(Res);
+ }
+
+ /**
+ * Reset the controller Events so that the `ControllerHalt` interrupt will
+ * be released, allowing the I2C driver to continue after the controller
+ * FSM has halted due to e.g. a NACK, configured timeout, or loss of
+ * arbitration.
+ *
+ * Returns the (masked) result of reading the register before clearing
+ * the controller events, so that you can use the `controllerEvent`
+ * register fields to determine why the Controller FSM was halted.
+ */
+ uint32_t reset_controller_events() volatile
+ {
+ constexpr uint32_t FieldMask =
+ (ControllerEventsNack | ControllerEventsUnhandledNackTimeout |
+ ControllerEventsBusTimeout | ControllerEventsArbitrationLost);
+ uint32_t events = controllerEvents & FieldMask;
+ controllerEvents = FieldMask;
+ return events;
+ }
+
+ /// Reset all of the fifos.
+ void reset_fifos() volatile
+ {
+ fifoCtrl = (FifoControlReceiveReset | FifoControlFormatReset |
+ FifoControlAcquiredReset | FifoControlTransmitReset);
+ }
+
+ /// Configure the I2C block to be in host mode.
+ void host_mode_set() volatile
+ {
+ control = ControlEnableHost;
+ }
+
+ /**
+ * Set the I2C timing parameters appropriately for the given bit rate.
+ * Distilled from:
+ * https://github.com/lowRISC/opentitan/blob/9ddf276c64e2974ed8e528e8b2feb00b977861de/hw/ip/i2c/doc/programmers_guide.md
+ */
+ void speed_set(const uint32_t SpeedKhz) volatile
+ {
+ // We must round up the system clock frequency to lengthen intervals.
+ const uint16_t SystemClockKhz = round_up_divide(CPU_TIMER_HZ, 1000);
+ // We want to underestimate the clock period, to lengthen the timings.
+ const uint16_t ClockPeriod = (1000 * 1000) / SystemClockKhz;
+
+ // Decide which bus mode this represents
+ uint32_t mode = (SpeedKhz > 100u) + (SpeedKhz > 400u);
+
+ // Minimum fall time when V_DD is 3.3V
+ constexpr uint16_t MinimumFallTime = 20 * 3 / 5;
+ // Specification minimum timings (Table 10) in nanoseconds for each bus
+ // mode.
+ constexpr uint16_t MinimumTimeValues[5][2][3] = {
+ {
+ {4700u, 1300u, 150u}, // Low Period
+ {4000u, 600u, 260u}, // High Period
+ },
+ {
+ // Fall time of SDA and SCL signals
+ {MinimumFallTime, MinimumFallTime, MinimumFallTime},
+ // Rise time of SDA and SCL signals
+ {120, 120, 120},
+ },
+ {
+ {4700u, 600u, 260u}, // Hold time for a repeated start condition
+ {4000u, 600u, 260u}, // Set-up time for a repeated start condition
+ },
+ {
+ {4000u, 1u, 1u}, // Data hold time
+ {500u, 100u, 50u}, // Data set-up time
+ },
+ {
+ // Bus free time between a STOP and START condition
+ {4700u, 1300u, 500u},
+ // Set-up time for a STOP condition
+ {4000u, 600u, 260u},
+ },
+ };
+ for (uint32_t i = 0; i < 5; ++i)
+ {
+ timing[i] =
+ (round_up_divide(MinimumTimeValues[i][0][mode], ClockPeriod)
+ << 16) |
+ round_up_divide(MinimumTimeValues[i][1][mode], ClockPeriod);
+ }
+ }
+
+ void blocking_write_byte(const uint32_t Fmt) volatile
+ {
+ while (0 != (StatusFormatFull & status)) {}
+ formatData = Fmt;
+ }
+
+ /// Returns true when the format fifo is empty
+ [[nodiscard]] bool format_is_empty() volatile
+ {
+ return 0 != (StatusFormatEmpty & status);
+ }
+
+ [[nodiscard]] bool blocking_write(const uint8_t Addr7,
+ const uint8_t data[],
+ const uint32_t NumBytes,
+ const bool SkipStop) volatile
+ {
+ if (NumBytes == 0)
+ {
+ return true;
+ }
+ blocking_write_byte(FormatDataStart | (Addr7 << 1) | 0u);
+ for (uint32_t i = 0; i < NumBytes - 1; ++i)
+ {
+ blocking_write_byte(data[i]);
+ }
+ blocking_write_byte((SkipStop ? 0u : FormatDataStop) |
+ data[NumBytes - 1]);
+ while (!format_is_empty())
+ {
+ if (interrupt_is_asserted(Interrupt::ControllerHalt))
+ {
+ reset_controller_events();
+ return false;
+ }
+ }
+ return true;
+ }
+
+ [[nodiscard]] bool blocking_read(const uint8_t Addr7,
+ uint8_t buf[],
+ const uint32_t NumBytes) volatile
+ {
+ for (uint32_t idx = 0; idx < NumBytes; idx += ReceiveFifoDepth)
+ {
+ blocking_write_byte(FormatDataStart | (Addr7 << 1) | 1u);
+ while (!format_is_empty()) {}
+ if (interrupt_is_asserted(Interrupt::ControllerHalt))
+ {
+ reset_controller_events();
+ return false;
+ }
+ uint32_t bytesRemaining = NumBytes - idx;
+ bool lastChunk = ReceiveFifoDepth >= bytesRemaining;
+ uint8_t chunkSize = lastChunk ? static_cast<uint8_t>(bytesRemaining)
+ : ReceiveFifoDepth;
+
+ blocking_write_byte((lastChunk ? FormatDataStop : 0) |
+ FormatDataReadBytes | chunkSize);
+ while (!format_is_empty()) {}
+
+ for (uint32_t chunkIdx = 0; chunkIdx < chunkSize; ++chunkIdx)
+ {
+ buf[idx + chunkIdx] = readData;
+ }
+ }
+ return true;
+ }
+
+ /// Returns true if the given interrupt is asserted.
+ [[nodiscard]] bool interrupt_is_asserted(Interrupt interrupt) volatile
+ {
+ return 0 != (interruptState & interrupt_bit(interrupt));
+ }
+
+ /// Clears the given interrupt.
+ void interrupt_clear(Interrupt interrupt) volatile
+ {
+ interruptState = interrupt_bit(interrupt);
+ }
+
+ /// Enables the given interrupt.
+ void interrupt_enable(Interrupt interrupt) volatile
+ {
+ interruptEnable = interruptEnable | interrupt_bit(interrupt);
+ }
+
+ /// Disables the given interrupt.
+ void interrupt_disable(Interrupt interrupt) volatile
+ {
+ interruptEnable = interruptEnable & ~interrupt_bit(interrupt);
+ }
+
+ /**
+ * Sets the thresholds for the format and receive fifos.
+ */
+ void host_thresholds_set(uint16_t formatThreshold,
+ uint16_t receiveThreshold) volatile
+ {
+ hostFifoConfiguration =
+ (formatThreshold & 0xfff) << 16 | (receiveThreshold & 0xfff);
+ }
+};