From 105af644bdadb70e3b8328e74284a8bd6985a72f Mon Sep 17 00:00:00 2001 From: Harry Ho Date: Mon, 13 Sep 2021 12:34:55 +0800 Subject: [PATCH] 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 --- artiq/firmware/libboard_misoc/lib.rs | 2 + artiq/firmware/libboard_misoc/mmcspi.rs | 187 ++++++++++++++++++++++++ 2 files changed, 189 insertions(+) create mode 100644 artiq/firmware/libboard_misoc/mmcspi.rs diff --git a/artiq/firmware/libboard_misoc/lib.rs b/artiq/firmware/libboard_misoc/lib.rs index 5e8a92972..12cc34044 100644 --- a/artiq/firmware/libboard_misoc/lib.rs +++ b/artiq/firmware/libboard_misoc/lib.rs @@ -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; \ No newline at end of file diff --git a/artiq/firmware/libboard_misoc/mmcspi.rs b/artiq/firmware/libboard_misoc/mmcspi.rs new file mode 100644 index 000000000..e68892b26 --- /dev/null +++ b/artiq/firmware/libboard_misoc/mmcspi.rs @@ -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 : 8-bit transfer (see FPGA_SPI_FRAME_SIZE) +// * Frame Format : SPI (see lpc17_ssp.c::ssp_init()) +// * Clock Out Polarity : CPOL=0 (CLK is low when idling) (see lpc17_ssp.c::ssp_init()) +// * Clock Out Phase : 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(()), + } +} \ No newline at end of file