-
Notifications
You must be signed in to change notification settings - Fork 45
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
7fa3d7f
commit 64d8c26
Showing
2 changed files
with
250 additions
and
3 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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); | ||
} | ||
} | ||
}; |