forked from M-Labs/artiq
libboard_misoc: add MMCSPI bitbanging driver
* Requires changes to Sayma AMC openMMC firmware as in https://github.com/HarryMakes/openMMC/commits/sayma-devel-fix/eui * Primary supports reading EUI48 broadcasted from the MMC
This commit is contained in:
parent
04ee775a9f
commit
105af644bd
|
@ -43,3 +43,5 @@ pub mod io_expander;
|
|||
pub mod net_settings;
|
||||
#[cfg(has_slave_fpga_cfg)]
|
||||
pub mod slave_fpga;
|
||||
#[cfg(has_mmcspi)]
|
||||
pub mod mmcspi;
|
|
@ -0,0 +1,187 @@
|
|||
use super::csr;
|
||||
|
||||
// Sayma MMC SSP1 configuration:
|
||||
//
|
||||
// References:
|
||||
// (i) Sayma MMC FPGA SPI port initialisation: https://github.com/sinara-hw/openMMC/blob/sayma-devel/modules/fpga_spi.c
|
||||
// (ii) Sayma MMC configuration: https://github.com/sinara-hw/openMMC/blob/sayma-devel/port/ucontroller/nxp/lpc17xx/lpc17_ssp.c::ssp_init()
|
||||
// (iii) openMMC SSP driver: https://github.com/sinara-hw/openMMC/blob/sayma-devel/port/ucontroller/nxp/lpc17xx/lpcopen/src/ssp_17xx_40xx.c)
|
||||
//
|
||||
// * Data Size Select <DSS>: 8-bit transfer (see FPGA_SPI_FRAME_SIZE)
|
||||
// * Frame Format <FRF>: SPI (see lpc17_ssp.c::ssp_init())
|
||||
// * Clock Out Polarity <CPOL>: CPOL=0 (CLK is low when idling) (see lpc17_ssp.c::ssp_init())
|
||||
// * Clock Out Phase <CPHA>: CPHA=0 (data is captured on leading edge) (see lpc17_ssp.c::ssp_init())
|
||||
// * CPOL=0, CPHA=0 ==> data is captured at rising edge
|
||||
// * Clock Frequency: 10000000 == 10 MHz (see FPGA_SPI_BITRATE)
|
||||
|
||||
// TODO: consider making a generic SPI receiver for customisable configuration
|
||||
|
||||
static mut PREV_CS_N: bool = true; // High when idling
|
||||
static mut PREV_CLK: bool = false; // Low when idling
|
||||
|
||||
// List of expected values
|
||||
// openMMC modules/fpga_spi.h
|
||||
const WR_COMMAND: u8 = 0x80;
|
||||
const ADDR_HEADER: u16 = 0x0005; // "Data Valid Byte"
|
||||
const DATA_HEADER: u32 = 0x55555555;
|
||||
|
||||
// Layout of MMC-to-FPGA data
|
||||
// (see openMMC modules/fpga_spi.h board_diagnostic_t)
|
||||
// cardID: u32 array of length 4
|
||||
const ADDR_CARD_ID_0: u16 = 0; // cardID[0]: bits[31:24] = EUI48 byte 3
|
||||
// bits[23:16] = EUI48 byte 2 (0x3D)
|
||||
// bits[15: 8] = EUI48 byte 1 (0xC2)
|
||||
// bits[ 7: 0] = EUI48 byte 0 (0xFC)
|
||||
const ADDR_CARD_ID_1: u16 = 1; // cardID[1]: bits[47:40] = EUI48 byte 5
|
||||
// bits[39:32] = EUI48 byte 4
|
||||
const ADDR_SLOT_ID: u16 = 16; // Note: currently unused by FPGA
|
||||
const ADDR_IPMI_ADDR: u16 = 20; // Note: currently unused by FPGA
|
||||
const ADDR_DATA_VALID: u16 = 24; // Note: currently unused by FPGA
|
||||
const ADDR_SENSOR: u16 = 28; // u32 array of length 21; see openMMC modules/sdr.h NUM_SENSOR
|
||||
// Note: currently unused by FPGA
|
||||
const ADDR_FMC_SLOT: u16 = 112; // Note: currently unused by FPGA
|
||||
|
||||
|
||||
fn cs_n() -> bool {
|
||||
unsafe { csr::mmcspi::cs_n_in_read() == 1 }
|
||||
}
|
||||
|
||||
fn detect_cs_n_rise() {
|
||||
loop {
|
||||
if cs_n() && unsafe { !PREV_CS_N } {
|
||||
unsafe { PREV_CS_N = true; }
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn detect_cs_n_fall() {
|
||||
loop {
|
||||
if !cs_n() && unsafe { PREV_CS_N } {
|
||||
unsafe { PREV_CS_N = false; }
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn clk() -> bool {
|
||||
unsafe { csr::mmcspi::clk_in_read() == 1 }
|
||||
}
|
||||
|
||||
fn detect_clk_rise() {
|
||||
loop {
|
||||
if clk() && unsafe { !PREV_CLK } {
|
||||
unsafe { PREV_CLK = true; }
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn detect_clk_fall() {
|
||||
loop {
|
||||
if !clk() && unsafe { PREV_CLK } {
|
||||
unsafe { PREV_CLK = false; }
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn mosi() -> u8 {
|
||||
unsafe { csr::mmcspi::mosi_in_read() & 1 }
|
||||
}
|
||||
|
||||
/// Detects CS_n assertion and keeps reading until the buffer is full or CS_n is deasserted
|
||||
/// TODO: Generalise this driver for future possible changes to the MMC SPI master settings
|
||||
fn read_continuous(buf: &mut [u8]) {
|
||||
// Register CS_n and CLK states
|
||||
unsafe {
|
||||
PREV_CS_N = cs_n();
|
||||
PREV_CLK = clk();
|
||||
}
|
||||
|
||||
// Wait until CS_n falling edge is detected, which indicates a new transaction
|
||||
detect_cs_n_fall();
|
||||
|
||||
for byte_ind in 0..buf.len() {
|
||||
// Read bits from MSB to LSB
|
||||
for bit_ind in (0..8).rev() {
|
||||
// If CS_n goes high, return to indicate a complete SPI transaction
|
||||
if cs_n() { break }
|
||||
// Detect and register CLK rising edge
|
||||
detect_clk_rise();
|
||||
// Store the MOSI state as the current bit of the current byte
|
||||
if mosi() == 1 {
|
||||
buf[byte_ind] |= 1 << bit_ind;
|
||||
}
|
||||
// Detect and register CLK falling edge
|
||||
detect_clk_fall();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Convert bytes to u16 (from big-endian)
|
||||
fn to_u16(buf: &[u8]) -> u16 {
|
||||
let mut value = 0_u16;
|
||||
for i in 0..2 {
|
||||
value |= (buf[i] as u16) << ((1-i) * 8);
|
||||
}
|
||||
value
|
||||
}
|
||||
|
||||
/// Convert bytes to u32 (from big-endian)
|
||||
fn to_u32(buf: &[u8]) -> u32 {
|
||||
let mut value = 0_u32;
|
||||
for i in 0..4 {
|
||||
value |= (buf[i] as u32) << ((3-i) * 8);
|
||||
}
|
||||
value
|
||||
}
|
||||
|
||||
/// Check if the bytes are the MMC broadcast header
|
||||
fn is_broadcast_header(buf: &[u8]) -> bool {
|
||||
buf.len() == 7 &&
|
||||
buf[0] == WR_COMMAND &&
|
||||
to_u16(&buf[1..3]) == ADDR_HEADER &&
|
||||
to_u32(&buf[3..7]) == DATA_HEADER
|
||||
}
|
||||
|
||||
/// Read the SPI to wait and capture the EUI48, and store it to a u8 array;
|
||||
/// Returns Ok() to indicate if the data is captured
|
||||
pub fn read_eui48(buf: &mut [u8]) -> Result<(), ()> {
|
||||
assert!(buf.len() >= 6);
|
||||
let mut spi_buf = [0_u8; 21];
|
||||
let mut is_broadcast = false;
|
||||
|
||||
// Loop to read a continuous byte transaction until the header correspond to the MMC broadcast format
|
||||
while !is_broadcast {
|
||||
// Read 21 continguous bytes in a row
|
||||
read_continuous(&mut spi_buf);
|
||||
// Check the header
|
||||
is_broadcast = is_broadcast_header(&spi_buf[0..7]);
|
||||
}
|
||||
// Truncate the header to get all data captured
|
||||
let data = &spi_buf[7..];
|
||||
|
||||
let (mut eui48_lo_ok, mut eui48_hi_ok) = (false, false);
|
||||
for i in 0..data.len()/7 {
|
||||
match to_u16(&data[i*7+1..i*7+3]) {
|
||||
// EUI48[31:0], big-endian
|
||||
ADDR_CARD_ID_0 => {
|
||||
for j in 0..4 { buf[j] = data[i*7 + 6-j] }
|
||||
eui48_lo_ok = true;
|
||||
}
|
||||
// EUI48[47:32], big-endian
|
||||
ADDR_CARD_ID_1 => {
|
||||
for j in 0..2 { buf[4 + j] = data[i*7 + 6-j] }
|
||||
eui48_hi_ok = true;
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
match (eui48_lo_ok, eui48_hi_ok) {
|
||||
(true, true) => Ok(()),
|
||||
// This should never return Err()
|
||||
_ => Err(()),
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue