Skip to content

Commit

Permalink
Added Sonata simple SPI driver.
Browse files Browse the repository at this point in the history
  • Loading branch information
HU90m authored and davidchisnall committed Jun 3, 2024
1 parent 7fa3d7f commit 64d8c26
Show file tree
Hide file tree
Showing 2 changed files with 250 additions and 3 deletions.
18 changes: 15 additions & 3 deletions sdk/boards/sonata.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,10 @@
"start" : 0x30000000,
"end" : 0x30004000
},
"gpio" : {
"start" : 0x80000000,
"end" : 0x80000020
},
"clint": {
"start" : 0x80040000,
"end" : 0x80050000
Expand All @@ -12,9 +16,17 @@
"start" : 0x80100000,
"end" : 0x80100034
},
"gpio" : {
"start" : 0x80000000,
"end" : 0x80000020
"spi0": {
"start" : 0x80300000,
"end" : 0x80301000
},
"spi1": {
"start" : 0x80301000,
"end" : 0x80302000
},
"spi2": {
"start" : 0x80302000,
"end" : 0x80303000
},
"plic": {
"start" : 0x88000000,
Expand Down
235 changes: 235 additions & 0 deletions sdk/include/platform/sunburst/platform-spi.hh
Original file line number Diff line number Diff line change
@@ -0,0 +1,235 @@
#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);
}
}
};

0 comments on commit 64d8c26

Please sign in to comment.