blob: fd8aa19a08f03f591acac1a488700aba1f5d738f [file] [log] [blame]
#pragma once
#include <cdefs.h>
#include <debug.hh>
#include <stdint.h>
/**
* A Simple Driver for the Sonata's SPI.
*
* Documentation source can be found at:
* https://github.com/lowRISC/sonata-system/blob/1a59633d2515d4fe186a07d53e49ff95c18d9bbf/doc/ip/spi.md
*
* Rendered documentation is served from:
* https://lowrisc.org/sonata-system/doc/ip/spi.html
*/
struct SonataSpi
{
/**
* The Sonata SPI block doesn't currently have support for interrupts.
* The following registers are reserved for future use.
*/
uint32_t interruptState;
uint32_t interruptEnable;
uint32_t interruptTest;
/**
* Configuration register. Controls how the SPI block transmits and
* receives data. This register can be modified only whilst the SPI block
* is idle.
*/
uint32_t configuration;
/**
* Controls the operation of the SPI block. This register can
* be modified only whilst the SPI block is idle.
*/
uint32_t control;
/// Status information about the SPI block
uint32_t status;
/**
* Writes to this begin an SPI operation.
* Writes are ignored when the SPI block is active.
*/
uint32_t start;
/**
* Data from the receive FIFO. When read the data is popped from the FIFO.
* If the FIFO is empty data read is undefined.
*/
uint32_t receiveFifo;
/**
* Bytes written here are pushed to the transmit FIFO. If the FIFO is full
* writes are ignored.
*/
uint32_t transmitFifo;
/// Configuration Register Fields
enum : uint32_t
{
/**
* The length of a half period (i.e. positive edge to negative edge) of
* the SPI clock, measured in system clock cycles reduced by 1. For
* example, at a 50 MHz system clock, a value of 0 gives a 25 MHz SPI
* clock, a value of 1 gives a 12.5 MHz SPI clock, a value of 2 gives
* a 8.33 MHz SPI clock and so on.
*/
ConfigurationHalfClockPeriodMask = 0xffu << 0,
/*
* When set the most significant bit (MSB) is the first bit sent and
* received with each byte
*/
ConfigurationMSBFirst = 1u << 29,
/*
* The phase of the spi_clk signal. when clockphase is 0, data is
* sampled on the leading edge and changes on the trailing edge. The
* first data bit is immediately available before the first leading edge
* of the clock when transmission begins. When clockphase is 1, data is
* sampled on the trailing edge and change on the leading edge.
*/
ConfigurationClockPhase = 1u << 30,
/*
* The polarity of the spi_clk signal. When ClockPolarity is 0, clock is
* low when idle and the leading edge is positive. When ClkPolarity is
* 1, clock is high when idle and the leading edge is negative
*/
ConfigurationClockPolarity = 1u << 31,
};
/// Control Register Fields
enum : uint32_t
{
/// Write 1 to clear the transmit FIFO.
ControlTransmitClear = 1 << 0,
/// Write 1 to clear the receive FIFO.
ControlReceiveClear = 1 << 1,
/**
* When set bytes from the transmit FIFO are sent. When clear the state
* of the outgoing spi_cipo is undefined whilst the SPI clock is
* running.
*/
ControlTransmitEnable = 1 << 2,
/**
* When set incoming bits are written to the receive FIFO. When clear
* incoming bits are ignored.
*/
ControlReceiveEnable = 1 << 3,
/**
* The watermark level for the transmit FIFO, depending on the value
* the interrupt will trigger at different points
*/
ControlTransmitWatermarkMask = 0xf << 4,
/**
* The watermark level for the receive FIFO, depending on the value the
* interrupt will trigger at different points
*/
ControlReceiveWatermarkMask = 0xf << 8,
};
/// Status Register Fields
enum : uint32_t
{
/// Number of items in the transmit FIFO.
StatusTxFifoLevel = 0xffu << 0,
/// Number of items in the receive FIFO.
StatusRxFifoLevel = 0xffu << 8,
/**
* When set the transmit FIFO is full and any data written to it will
* be ignored.
*/
StatusTxFifoFull = 1u << 16,
/**
* When set the receive FIFO is empty and any data read from it will be
* undefined.
*/
StatusRxFifoEmpty = 1u << 17,
/// When set the SPI block is idle and can accept a new start command.
StatusIdle = 1u << 18,
};
/// Start Register Fields
enum : uint32_t
{
/// Number of bytes to receive/transmit in the SPI operation
StartByteCountMask = 0x7ffu,
};
/// Flag set when we're debugging this driver.
static constexpr bool DebugSonataSpi = false;
/// Helper for conditional debug logs and assertions.
using Debug = ConditionalDebug<DebugSonataSpi, "Sonata SPI">;
/**
* Initialises the SPI block
*
* @param ClockPolarity When false, the clock is low when idle and the
* leading edge is positive. When true, the opposite behaviour is
* set.
* @param ClockPhase When false, data is sampled on the leading edge and
* changes on the trailing edge. When true, the opposite behaviour is
* set.
* @param MsbFirst When true, the first bit of each byte sent is the most
* significant bit, as oppose to the least significant bit.
* @param HalfClockPeriod The length of a half period of the SPI clock,
* measured in system clock cycles reduced by 1.
*/
void init(const bool ClockPolarity,
const bool ClockPhase,
const bool MsbFirst,
const uint16_t HalfClockPeriod) volatile
{
configuration = (ClockPolarity ? ConfigurationClockPolarity : 0) |
(ClockPhase ? ConfigurationClockPhase : 0) |
(MsbFirst ? ConfigurationMSBFirst : 0) |
(HalfClockPeriod & ConfigurationHalfClockPeriodMask);
}
/// Waits for the SPI device to become idle
void wait_idle() volatile
{
// Wait whilst IDLE field in STATUS is low
while ((status & StatusIdle) == 0) {}
}
/**
* Sends `len` bytes from the given `data` buffer,
* where `len` is at most `0x7ff`.
*/
void blocking_write(const uint8_t data[], uint16_t len) volatile
{
Debug::Assert(len <= 0x7ff,
"You can't transfer more than 0x7ff bytes at a time.");
len &= StartByteCountMask;
wait_idle();
control = ControlTransmitEnable;
start = len;
uint32_t transmitAvailable = 0;
for (uint32_t i = 0; i < len; ++i)
{
if (transmitAvailable == 0)
{
while (transmitAvailable < 64)
{
// Read number of bytes in TX FIFO to calculate space
// available for more bytes
transmitAvailable = 64 - (status & StatusTxFifoLevel);
}
}
transmitFifo = data[i];
transmitAvailable--;
}
}
/*
* Receives `len` bytes and puts them in the `data` buffer,
* where `len` is at most `0x7ff`.
*
* This method will block until the requested number of bytes
* has been seen. There is currently no timeout.
*/
void blocking_read(uint8_t data[], uint16_t len) volatile
{
Debug::Assert(len <= 0x7ff,
"You can't receive more than 0x7ff bytes at a time.");
len &= StartByteCountMask;
wait_idle();
control = ControlReceiveEnable;
start = len;
for (uint32_t i = 0; i < len; ++i)
{
// Wait for at least one byte to be available in the RX FIFO
while ((status & StatusRxFifoLevel) == 0) {}
data[i] = static_cast<uint8_t>(receiveFifo);
}
}
};