Added an OpenTitan I2C driver
diff --git a/sdk/include/platform/sunburst/platform-i2c.hh b/sdk/include/platform/sunburst/platform-i2c.hh new file mode 100644 index 0000000..9628a43 --- /dev/null +++ b/sdk/include/platform/sunburst/platform-i2c.hh
@@ -0,0 +1,421 @@ +#pragma once +#include <cdefs.h> +#include <debug.hh> +#include <stdint.h> + +/** + * The interrupts of the OpenTitan's I2C block. + * + * Documentation source can be found at: + * https://github.com/lowRISC/opentitan/blob/9ddf276c64e2974ed8e528e8b2feb00b977861de/hw/ip/i2c/doc/interfaces.md + */ +enum class OpenTitanI2cInterrupt +{ + /** + * 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 there is no ACK in response to + * an address or data. + */ + Nak, + /** + * 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 OpenTitanI2cInterrupt Interrupt) +{ + return 1 << static_cast<uint32_t>(Interrupt); +}; + +/** + * Driver for the OpenTitan's I2C block. + * + * Documentation source can be found at: + * https://github.com/lowRISC/opentitan/tree/9ddf276c64e2974ed8e528e8b2feb00b977861de/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; + /** + * Timeout in Host-Mode for an unhandled NACK before hardware automatically + * ends the transaction. + */ + uint32_t targetAckControl; + + /// 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, + }; + + /// 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 Receive FIFO is full + StatusAcquiredFull = 1 << 7, + /// Target mode Transmit FIFO is empty + StatusTransmitEmpty = 1 << 8, + /// Target mode Aquired FIFO is empty + StatusAcquiredEmpty = 1 << 9, + /** + * A Host-Mode active transaction has been ended by the + * HostNackHandlerTimeout mechanism. This bit is cleared when + * Control.EnableHost is set by software to start a new transaction. + */ + StatusHostDisabledNackTimeout = 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, + /// Aquired 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, + }; + + /// 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 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); + } + + void blocking_write(const uint8_t Addr7, + const uint8_t data[], + const uint32_t NumBytes, + const bool SkipStop) volatile + { + if (NumBytes == 0) + { + return; + } + 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]); + } + + [[nodiscard]] bool blocking_read(const uint8_t Addr7, + uint8_t buf[], + const uint32_t NumBytes) volatile + { + for (uint32_t idx = 0; idx < NumBytes; idx += UINT8_MAX) + { + blocking_write_byte(FormatDataStart | (Addr7 << 1) | 1u); + while (!format_is_empty()) {} + if (interrupt_is_asserted(OpenTitanI2cInterrupt::Nak)) + { + interrupt_clear(OpenTitanI2cInterrupt::Nak); + return false; + } + uint32_t bytesRemaining = NumBytes - idx; + bool lastChunk = UINT8_MAX >= bytesRemaining; + uint8_t chunkSize = + lastChunk ? static_cast<uint8_t>(bytesRemaining) : UINT8_MAX; + + 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(OpenTitanI2cInterrupt interrupt) volatile + { + return 0 != (interruptState & interrupt_bit(interrupt)); + } + + /// Clears the given interrupt. + void interrupt_clear(OpenTitanI2cInterrupt interrupt) volatile + { + interruptState = interruptState & ~interrupt_bit(interrupt); + } + + /// Enables the given interrupt. + void interrupt_enable(OpenTitanI2cInterrupt interrupt) volatile + { + interruptEnable = interruptEnable | interrupt_bit(interrupt); + } + + /// Disables the given interrupt. + void interrupt_disable(OpenTitanI2cInterrupt 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); + } +};