Introduce Rust FSBL
Some checks failed
ci / Check build (pull_request) Has been cancelled
ci / Check formatting (pull_request) Has been cancelled
ci / Check Documentation Build (pull_request) Has been cancelled
ci / Clippy (pull_request) Has been cancelled
ci / Check build (push) Has been cancelled
ci / Check formatting (push) Has been cancelled
ci / Check Documentation Build (push) Has been cancelled
ci / Clippy (push) Has been cancelled

This PR introduces some major features while also changing the project structure to be more flexible
for multiple platforms (e.g. host tooling). It also includes a lot of
bugfixes, renamings for consistency purposes and dependency updates.

Added features:

1. Pure Rust FSBL for the Zedboard. This first variant is simplistic. It
   is currently only capable of QSPI boot. It searches for a bitstream
   and ELF file inside the boot binary, flashes them and jumps to them.
2. QSPI flasher for the Zedboard.
3. DDR, QSPI, DEVC, private CPU timer and PLL configuration modules
3. Tooling to auto-generate board specific DDR and DDRIOB config
   parameters from the vendor provided ps7init.tcl file

Changed project structure:

1. All target specific project are inside a dedicated workspace inside
   the `zynq` folder now.
2. All tool intended to be run on a host are inside a `tools` workspace
3. All other common projects are at the project root

Major bugfixes:

1. SPI module: CPOL was not configured properly
2. Logger flush implementation was empty, implemented properly now.
This commit is contained in:
2025-08-01 14:32:08 +02:00
committed by Robin Mueller
parent 0cf5bf6885
commit 5d0f2837d1
166 changed files with 9496 additions and 979 deletions

351
zynq/zynq7000/src/uart.rs Normal file
View File

@@ -0,0 +1,351 @@
//! PS UART register module.
use arbitrary_int::u6;
pub const UART_0_BASE: usize = 0xE000_0000;
pub const UART_1_BASE: usize = 0xE000_1000;
#[bitbybit::bitenum(u3, exhaustive = true)]
#[derive(Debug)]
pub enum Parity {
Even = 0b000,
Odd = 0b001,
/// Forced to 0 (Space)
ForcedTo0 = 0b010,
/// Forced to 1 (Mark)
ForcedTo1 = 0b011,
NoParity = 0b100,
NoParityAlt0 = 0b101,
NoParityAlt1 = 0b110,
NoParityAlt2 = 0b111,
}
#[bitbybit::bitenum(u2, exhaustive = true)]
#[derive(Default, Debug, PartialEq, Eq)]
pub enum CharLen {
SixBits = 0b11,
SevenBits = 0b10,
#[default]
EightBits = 0b00,
EightBitsAlt = 0b01,
}
#[bitbybit::bitenum(u1, exhaustive = true)]
#[derive(Default, Debug, PartialEq, Eq)]
pub enum ClockSelect {
#[default]
UartRefClk = 0b0,
UartRefClkDiv8 = 0b1,
}
#[bitbybit::bitenum(u2)]
#[derive(Default, Debug, PartialEq, Eq)]
pub enum Stopbits {
#[default]
One = 0b00,
OnePointFive = 0b01,
Two = 0b10,
}
#[bitbybit::bitenum(u2, exhaustive = true)]
#[derive(Debug, Default)]
pub enum ChMode {
#[default]
Normal = 0b00,
AutoEcho = 0b01,
LocalLoopback = 0b10,
RemoteLoopback = 0b11,
}
#[bitbybit::bitfield(u32, debug)]
pub struct Control {
/// Stop transmitter break.
#[bit(8, rw)]
stopbrk: bool,
/// Start transmitter break.
#[bit(7, rw)]
startbrk: bool,
/// Restart receiver timeout counter.
#[bit(6, rw)]
rstto: bool,
/// TX disable. If this is 1, TX is disabled, regardless of TXEN.
#[bit(5, rw)]
tx_dis: bool,
/// TX enable. TX will be enabled if this bit is 1 and the TXDIS is 0.
#[bit(4, rw)]
tx_en: bool,
/// RX disable. If this is 1, RX is disabled, regardless of RXEN.
#[bit(3, rw)]
rx_dis: bool,
/// RX enable. RX will be enabled if this bit is 1 and the RXDIS is 0.
#[bit(2, rw)]
rx_en: bool,
/// TX soft reset.
#[bit(1, rw)]
tx_rst: bool,
/// RX soft reset.
#[bit(0, rw)]
rx_rst: bool,
}
#[bitbybit::bitfield(u32, default = 0x0, debug)]
pub struct Mode {
#[bits(8..=9, rw)]
chmode: ChMode,
#[bits(6..=7, rw)]
nbstop: Option<Stopbits>,
#[bits(3..=5, rw)]
par: Parity,
/// Char length.
#[bits(1..=2, rw)]
chrl: CharLen,
#[bit(0, rw)]
clksel: ClockSelect,
}
#[bitbybit::bitfield(u32, default = 0, debug)]
pub struct Baudgen {
#[bits(0..=15, rw)]
cd: u16,
}
#[bitbybit::bitfield(u32, default = 0, debug)]
pub struct BaudRateDivisor {
#[bits(0..=7, rw)]
bdiv: u8,
}
#[bitbybit::bitfield(u32, debug)]
pub struct Fifo {
#[bits(0..=7, rw)]
fifo: u8,
}
#[bitbybit::bitenum(u1, exhaustive = true)]
#[derive(Debug)]
pub enum Ttrig {
LessThanTTrig = 0b0,
GreaterEqualTTrig = 0b1,
}
#[bitbybit::bitfield(u32, debug)]
pub struct Status {
#[bit(14, r)]
tx_near_full: bool,
#[bit(13, r)]
tx_trig: Ttrig,
#[bit(12, r)]
flowdel: bool,
/// Transmitter state machine active.
#[bit(11, r)]
tx_active: bool,
/// Receiver state machine active.
#[bit(10, r)]
rx_active: bool,
#[bit(4, r)]
tx_full: bool,
#[bit(3, r)]
tx_empty: bool,
#[bit(2, r)]
rx_full: bool,
#[bit(1, r)]
rx_empty: bool,
/// RX FIFO trigger level was reached.
#[bit(0, r)]
rx_trg: bool,
}
#[bitbybit::bitfield(u32, default = 0x0)]
#[derive(Debug)]
pub struct InterruptControl {
#[bit(12, w)]
tx_over: bool,
#[bit(11, w)]
tx_near_full: bool,
#[bit(10, w)]
tx_trig: bool,
#[bit(9, w)]
rx_dms: bool,
/// Receiver timeout error interrupt.
#[bit(8, w)]
rx_timeout: bool,
#[bit(7, w)]
rx_parity: bool,
#[bit(6, w)]
rx_framing: bool,
#[bit(5, w)]
rx_over: bool,
#[bit(4, w)]
tx_full: bool,
#[bit(3, w)]
tx_empty: bool,
#[bit(2, w)]
rx_full: bool,
#[bit(1, w)]
rx_empty: bool,
#[bit(0, w)]
rx_trg: bool,
}
#[bitbybit::bitfield(u32)]
#[derive(Debug)]
pub struct FifoTrigger {
#[bits(0..=5, rw)]
trig: u6,
}
#[bitbybit::bitfield(u32)]
#[derive(Debug)]
pub struct InterruptMask {
#[bit(12, r)]
tx_over: bool,
#[bit(11, r)]
tx_near_full: bool,
#[bit(10, r)]
tx_trig: bool,
#[bit(9, r)]
rx_dms: bool,
/// Receiver timeout error interrupt.
#[bit(8, r)]
rx_timeout: bool,
#[bit(7, r)]
rx_parity: bool,
#[bit(6, r)]
rx_framing: bool,
#[bit(5, r)]
rx_over: bool,
#[bit(4, r)]
tx_full: bool,
#[bit(3, r)]
tx_empty: bool,
#[bit(2, r)]
rx_full: bool,
#[bit(1, r)]
rx_empty: bool,
/// RX FIFO trigger level reached.
#[bit(0, r)]
rx_trg: bool,
}
#[bitbybit::bitfield(u32, default = 0x0, debug)]
pub struct InterruptStatus {
#[bit(12, rw)]
tx_over: bool,
#[bit(11, rw)]
tx_near_full: bool,
#[bit(10, rw)]
tx_trig: bool,
#[bit(9, rw)]
rx_dms: bool,
/// Receiver timeout error interrupt.
#[bit(8, rw)]
rx_timeout: bool,
#[bit(7, rw)]
rx_parity: bool,
#[bit(6, rw)]
rx_framing: bool,
#[bit(5, rw)]
rx_over: bool,
#[bit(4, rw)]
tx_full: bool,
#[bit(3, rw)]
tx_empty: bool,
#[bit(2, rw)]
rx_full: bool,
#[bit(1, rw)]
rx_empty: bool,
/// RX FIFO trigger level reached.
#[bit(0, rw)]
rx_trg: bool,
}
impl InterruptStatus {
pub fn new_for_clearing_rx_errors() -> Self {
Self::builder()
.with_tx_over(false)
.with_tx_near_full(false)
.with_tx_trig(false)
.with_rx_dms(false)
.with_rx_timeout(false)
.with_rx_parity(true)
.with_rx_framing(true)
.with_rx_over(true)
.with_tx_full(false)
.with_tx_empty(false)
.with_rx_full(false)
.with_rx_empty(false)
.with_rx_trg(false)
.build()
}
}
#[derive(derive_mmio::Mmio)]
#[repr(C)]
pub struct Uart {
/// Control Register
cr: Control,
/// Mode register
mr: Mode,
/// Interrupt enable register
#[mmio(Write)]
ier: InterruptControl,
/// Interrupt disable register
#[mmio(Write)]
idr: InterruptControl,
/// Interrupt mask register, showing enabled interrupts.
#[mmio(PureRead)]
imr: InterruptMask,
/// Interrupt status register
#[mmio(PureRead, Write)]
isr: InterruptStatus,
/// Baudgen register
baudgen: Baudgen,
/// RX timeout register
rx_tout: u32,
/// RX FIFO trigger level register
rx_fifo_trigger: FifoTrigger,
/// Modem control register
modem_cr: u32,
/// Modem status register
modem_sr: u32,
/// Channel status register
#[mmio(PureRead)]
sr: Status,
/// FIFO register
#[mmio(Read, Write)]
fifo: Fifo,
/// Baud rate divider register
baud_rate_div: BaudRateDivisor,
/// Flow control delay register
flow_delay: u32,
_reserved: [u32; 2],
/// TX fifo trigger level
tx_fifo_trigger: FifoTrigger,
}
static_assertions::const_assert_eq!(core::mem::size_of::<Uart>(), 0x48);
impl Uart {
/// Create a new UART MMIO instance for uart0 at address 0xE000_0000.
///
/// # Safety
///
/// This API can be used to potentially create a driver to the same peripheral structure
/// from multiple threads. The user must ensure that concurrent accesses are safe and do not
/// interfere with each other.
pub const unsafe fn new_mmio_fixed_0() -> MmioUart<'static> {
unsafe { Self::new_mmio_at(UART_0_BASE) }
}
/// Create a new UART MMIO instance for uart1 at address 0xE000_1000.
///
/// # Safety
///
/// This API can be used to potentially create a driver to the same peripheral structure
/// from multiple threads. The user must ensure that concurrent accesses are safe and do not
/// interfere with each other.
pub const unsafe fn new_mmio_fixed_1() -> MmioUart<'static> {
unsafe { Self::new_mmio_at(UART_1_BASE) }
}
}