From ec2d06ca4810e8f3dba86e2ff6058c44ef07af5d Mon Sep 17 00:00:00 2001 From: Robin Mueller Date: Mon, 31 Mar 2025 19:52:19 +0200 Subject: [PATCH] Add first TTC driver --- Cargo.toml | 2 - axi-uart16550-rs/Cargo.toml | 32 --- axi-uart16550-rs/src/lib.rs | 372 ------------------------------ axi-uart16550-rs/src/registers.rs | 177 -------------- axi-uart16550-rs/src/rx.rs | 223 ------------------ axi-uart16550-rs/src/tx.rs | 152 ------------ axi-uart16550-rs/src/tx_async.rs | 259 --------------------- axi-uartlite-rs/.gitignore | 1 - axi-uartlite-rs/Cargo.toml | 27 --- axi-uartlite-rs/src/lib.rs | 264 --------------------- axi-uartlite-rs/src/registers.rs | 55 ----- axi-uartlite-rs/src/rx.rs | 172 -------------- axi-uartlite-rs/src/tx.rs | 142 ------------ axi-uartlite-rs/src/tx_async.rs | 221 ------------------ examples/zedboard/Cargo.toml | 4 +- zynq7000-hal/src/i2c.rs | 27 ++- zynq7000-hal/src/lib.rs | 1 + zynq7000-hal/src/spi.rs | 71 ++++-- zynq7000-hal/src/ttc.rs | 297 ++++++++++++++++++++++++ zynq7000-hal/src/uart/mod.rs | 13 +- zynq7000/src/i2c.rs | 1 + zynq7000/src/lib.rs | 5 + zynq7000/src/spi.rs | 1 + zynq7000/src/ttc.rs | 183 +++++++++++++++ zynq7000/src/uart.rs | 2 +- 25 files changed, 565 insertions(+), 2139 deletions(-) delete mode 100644 axi-uart16550-rs/Cargo.toml delete mode 100644 axi-uart16550-rs/src/lib.rs delete mode 100644 axi-uart16550-rs/src/registers.rs delete mode 100644 axi-uart16550-rs/src/rx.rs delete mode 100644 axi-uart16550-rs/src/tx.rs delete mode 100644 axi-uart16550-rs/src/tx_async.rs delete mode 100644 axi-uartlite-rs/.gitignore delete mode 100644 axi-uartlite-rs/Cargo.toml delete mode 100644 axi-uartlite-rs/src/lib.rs delete mode 100644 axi-uartlite-rs/src/registers.rs delete mode 100644 axi-uartlite-rs/src/rx.rs delete mode 100644 axi-uartlite-rs/src/tx.rs delete mode 100644 axi-uartlite-rs/src/tx_async.rs create mode 100644 zynq7000-hal/src/ttc.rs create mode 100644 zynq7000/src/ttc.rs diff --git a/Cargo.toml b/Cargo.toml index 342088b..8bc810b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -5,8 +5,6 @@ members = [ "zynq7000", "zynq7000-hal", "zynq7000-embassy", - "axi-uartlite-rs", - "axi-uart16550-rs", "examples/simple", "examples/embassy", "examples/zedboard", diff --git a/axi-uart16550-rs/Cargo.toml b/axi-uart16550-rs/Cargo.toml deleted file mode 100644 index 8433b2a..0000000 --- a/axi-uart16550-rs/Cargo.toml +++ /dev/null @@ -1,32 +0,0 @@ -[package] -name = "axi-uart16550" -version = "0.1.0" -edition = "2024" - -[dependencies] -derive-mmio = { git = "https://github.com/knurling-rs/derive-mmio.git", rev = "0806ce10b132ca15c6d9122a2d15a6e146b01520"} -bitbybit = "1.3" -arbitrary-int = "1.3" -nb = "1" -libm = "0.2" -critical-section = "1" -thiserror = { version = "2", default-features = false } -fugit = "0.3" -embedded-hal-async = "1" -embedded-hal-nb = "1" -embedded-io = "0.6" -embedded-io-async = "0.6" -embassy-sync = "0.6" -raw-slice = { git = "https://egit.irs.uni-stuttgart.de/rust/raw-slice.git" } - -[features] -default = ["1-waker"] -1-waker = [] -2-wakers = [] -4-wakers = [] -8-wakers = [] -16-wakers = [] -32-wakers = [] - -[dev-dependencies] -approx = "0.5" diff --git a/axi-uart16550-rs/src/lib.rs b/axi-uart16550-rs/src/lib.rs deleted file mode 100644 index f9329bc..0000000 --- a/axi-uart16550-rs/src/lib.rs +++ /dev/null @@ -1,372 +0,0 @@ -#![no_std] - -use core::convert::Infallible; - -use registers::{Fcr, Ier, Lcr, RxFifoTrigger, StopBits, WordLen}; -pub mod registers; - -pub mod tx; -pub use tx::*; - -pub mod tx_async; -pub use tx_async::*; - -pub mod rx; -pub use rx::*; - -pub const FIFO_DEPTH: usize = 16; -pub const DEFAULT_RX_TRIGGER_LEVEL: RxFifoTrigger = RxFifoTrigger::EightBytes; - -#[derive(Debug, PartialEq, Eq, Clone, Copy)] -pub struct ClkConfig { - pub div: u16, -} - -#[derive(Debug, thiserror::Error, PartialEq, Eq)] -#[error("divisor is zero")] -pub struct DivisorZeroError; - -/// Calculate the error rate of the baudrate with the given clock frequency, baudrate and -/// divisor as a floating point value between 0.0 and 1.0. -#[inline] -pub fn calculate_error_rate_from_div( - clk_in: fugit::HertzU32, - baudrate: u32, - div: u16, -) -> Result { - if baudrate == 0 || div == 0 { - return Err(DivisorZeroError); - } - let actual = (clk_in.raw() as f32) / (16.0 * div as f32); - Ok(libm::fabsf(actual - baudrate as f32) / baudrate as f32) -} - -/// If this error occurs, the calculated baudrate divisor is too large, either because the -/// used clock is too large, or the baudrate is too slow for the used clock frequency. -#[derive(Debug, thiserror::Error, PartialEq, Eq)] -#[error("divisor too large")] -pub enum ClkConfigError { - DivisorTooLargeError(u32), - DivisorZero(#[from] DivisorZeroError), -} - -impl ClkConfig { - pub fn new(div: u16) -> Self { - Self { div } - } - - #[inline(always)] - pub fn div_msb(&self) -> u8 { - (self.div >> 8) as u8 - } - - #[inline(always)] - pub fn div_lsb(&self) -> u8 { - self.div as u8 - } - - /// This function calculates the required divisor values for a given input clock and baudrate - /// as well as an baud error rate. - #[inline] - pub fn new_autocalc_with_error( - clk_in: fugit::HertzU32, - baudrate: u32, - ) -> Result<(Self, f32), ClkConfigError> { - let cfg = Self::new_autocalc(clk_in, baudrate)?; - Ok((cfg, cfg.calculate_error_rate(clk_in, baudrate)?)) - } - - /// This function calculates the required divisor values for a given input clock and baudrate. - /// - /// The function will not calculate the error rate. You can use [Self::calculate_error_rate] - /// to check the error rate, or use the [Self::new_autocalc_with_error] function to get both - /// the clock config and its baud error. - #[inline] - pub fn new_autocalc(clk_in: fugit::HertzU32, baudrate: u32) -> Result { - let div = Self::calc_div_with_integer_div(clk_in, baudrate)?; - if div > u16::MAX as u32 { - return Err(ClkConfigError::DivisorTooLargeError(div)); - } - Ok(Self { div: div as u16 }) - } - - /// Calculate the error rate of the baudrate with the given clock frequency, baudrate and the - /// current clock config as a floating point value between 0.0 and 1.0. - #[inline] - pub fn calculate_error_rate( - &self, - clk_in: fugit::HertzU32, - baudrate: u32, - ) -> Result { - calculate_error_rate_from_div(clk_in, baudrate, self.div) - } - - #[inline(always)] - pub const fn calc_div_with_integer_div( - clk_in: fugit::HertzU32, - baudrate: u32, - ) -> Result { - if baudrate == 0 { - return Err(DivisorZeroError); - } - // Rounding integer division, by adding half the divisor to the dividend. - Ok((clk_in.raw() + (8 * baudrate)) / (16 * baudrate)) - } -} - -#[derive(Default, Debug, PartialEq, Eq, Clone, Copy)] -pub enum Parity { - #[default] - None, - Odd, - Even, -} - -pub struct AxiUart16550 { - rx: Rx, - tx: Tx, - config: UartConfig, -} - -#[derive(Debug, PartialEq, Eq, Clone, Copy)] -pub struct UartConfig { - clk: ClkConfig, - word_len: WordLen, - parity: Parity, - stop_bits: StopBits, -} - -impl UartConfig { - pub const fn new_with_clk_config(clk: ClkConfig) -> Self { - Self { - clk, - word_len: WordLen::Eight, - parity: Parity::None, - stop_bits: StopBits::One, - } - } - - pub const fn new( - clk: ClkConfig, - word_len: WordLen, - parity: Parity, - stop_bits: StopBits, - ) -> Self { - Self { - clk, - word_len, - parity, - stop_bits, - } - } -} - -impl AxiUart16550 { - /// Create a new AXI UART16550 peripheral driver. - /// - /// # Safety - /// - /// - The `base_addr` must be a valid memory-mapped register address of an AXI UART 16550 - /// peripheral. - /// - Dereferencing an invalid or misaligned address results in **undefined behavior**. - /// - The caller must ensure that no other code concurrently modifies the same peripheral registers - /// in an unsynchronized manner to prevent data races. - /// - This function does not enforce uniqueness of driver instances. Creating multiple instances - /// with the same `base_addr` can lead to unintended behavior if not externally synchronized. - /// - The driver performs **volatile** reads and writes to the provided address. - pub unsafe fn new(base_addr: u32, config: UartConfig) -> Self { - let mut regs = unsafe { registers::AxiUart16550::new_mmio_at(base_addr as usize) }; - // This unlocks the divisor config registers. - regs.write_lcr(Lcr::new_for_divisor_access()); - regs.write_fifo_or_dll(config.clk.div_lsb() as u32); - regs.write_ier_or_dlm(config.clk.div_msb() as u32); - // Configure all other settings and reset the div acess latch. This is important - // for accessing IER and the FIFO control register again. - regs.write_lcr( - Lcr::builder() - .with_div_access_latch(false) - .with_set_break(false) - .with_stick_parity(false) - .with_even_parity(config.parity == Parity::Even) - .with_parity_enable(config.parity != Parity::None) - .with_stop_bits(config.stop_bits) - .with_word_len(config.word_len) - .build(), - ); - // Disable all interrupts. - regs.write_ier_or_dlm(Ier::new_with_raw_value(0x0).raw_value()); - // Enable FIFO, configure 8 bytes FIFO trigger by default. - regs.write_iir_or_fcr( - Fcr::builder() - .with_rx_fifo_trigger(DEFAULT_RX_TRIGGER_LEVEL) - .with_dma_mode_sel(false) - .with_reset_tx_fifo(true) - .with_reset_rx_fifo(true) - .with_fifo_enable(true) - .build() - .raw_value(), - ); - Self { - rx: Rx::new(unsafe { regs.clone() }), - tx: Tx::new(regs), - config, - } - } - - #[inline(always)] - pub const fn regs(&mut self) -> &mut registers::MmioAxiUart16550<'static> { - &mut self.rx.regs - } - - #[inline(always)] - pub const fn config(&mut self) -> &UartConfig { - &self.config - } - - /// Write into the UART Lite. - /// - /// Returns [nb::Error::WouldBlock] if the TX FIFO is full. - #[inline] - pub fn write_fifo(&mut self, data: u8) -> nb::Result<(), Infallible> { - self.tx.write_fifo(data) - } - - // TODO: Make this non-mut as soon as pure reads are available. - #[inline(always)] - pub fn thr_empty(&mut self) -> bool { - self.tx.thr_empty() - } - - #[inline(always)] - pub fn tx_empty(&mut self) -> bool { - self.tx.tx_empty() - } - - #[inline(always)] - pub fn rx_has_data(&mut self) -> bool { - self.rx.has_data() - } - - /// Write into the FIFO without checking the FIFO fill status. - /// - /// This can be useful to completely fill the FIFO if it is known to be empty. - #[inline(always)] - pub fn write_fifo_unchecked(&mut self, data: u8) { - self.tx.write_fifo_unchecked(data); - } - - #[inline] - pub fn read_fifo(&mut self) -> nb::Result { - self.rx.read_fifo() - } - - #[inline(always)] - pub fn read_fifo_unchecked(&mut self) -> u8 { - self.rx.read_fifo_unchecked() - } - - #[inline(always)] - pub fn enable_interrupts(&mut self, ier: Ier) { - self.regs().write_ier_or_dlm(ier.raw_value()); - } - - pub fn split(self) -> (Tx, Rx) { - (self.tx, self.rx) - } -} - -impl embedded_hal_nb::serial::ErrorType for AxiUart16550 { - type Error = Infallible; -} - -impl embedded_hal_nb::serial::Write for AxiUart16550 { - #[inline] - fn write(&mut self, word: u8) -> nb::Result<(), Self::Error> { - self.tx.write(word) - } - - #[inline] - fn flush(&mut self) -> nb::Result<(), Self::Error> { - self.tx.flush() - } -} - -impl embedded_hal_nb::serial::Read for AxiUart16550 { - #[inline] - fn read(&mut self) -> nb::Result { - self.rx.read() - } -} - -impl embedded_io::ErrorType for AxiUart16550 { - type Error = Infallible; -} - -impl embedded_io::Read for AxiUart16550 { - fn read(&mut self, buf: &mut [u8]) -> Result { - self.rx.read(buf) - } -} - -impl embedded_io::Write for AxiUart16550 { - fn write(&mut self, buf: &[u8]) -> Result { - self.tx.write(buf) - } - - fn flush(&mut self) -> Result<(), Self::Error> { - self.tx.flush() - } -} - -#[cfg(test)] -mod tests { - use crate::ClkConfigError; - - //extern crate std; - use super::{DivisorZeroError, calculate_error_rate_from_div}; - - use super::ClkConfig; - use approx::abs_diff_eq; - use fugit::RateExtU32; - - #[test] - fn test_clk_calc_example_0() { - let clk_cfg = ClkConfig::new_autocalc(100.MHz(), 56000).unwrap(); - // For some reason, the Xilinx example rounds up here.. - assert_eq!(clk_cfg.div, 0x0070); - assert_eq!(clk_cfg.div_msb(), 0x00); - assert_eq!(clk_cfg.div_lsb(), 0x70); - let error = clk_cfg.calculate_error_rate(100.MHz(), 56000).unwrap(); - assert!(abs_diff_eq!(error, 0.0035, epsilon = 0.001)); - let (clk_cfg_checked, error_checked) = - ClkConfig::new_autocalc_with_error(100.MHz(), 56000).unwrap(); - assert_eq!(clk_cfg, clk_cfg_checked); - assert!(abs_diff_eq!(error, error_checked, epsilon = 0.001)); - let error_calc = calculate_error_rate_from_div(100.MHz(), 56000, clk_cfg.div).unwrap(); - assert!(abs_diff_eq!(error, error_calc, epsilon = 0.001)); - } - - #[test] - fn test_clk_calc_example_1() { - let clk_cfg = ClkConfig::new_autocalc(1843200.Hz(), 56000).unwrap(); - assert_eq!(clk_cfg.div, 0x0002); - assert_eq!(clk_cfg.div_msb(), 0x00); - assert_eq!(clk_cfg.div_lsb(), 0x02); - } - - #[test] - fn test_invalid_baud() { - let clk_cfg = ClkConfig::new_autocalc_with_error(100.MHz(), 0); - assert_eq!(clk_cfg, Err(ClkConfigError::DivisorZero(DivisorZeroError))); - } - - #[test] - fn test_invalid_div() { - let error = calculate_error_rate_from_div(100.MHz(), 115200, 0); - assert_eq!(error.unwrap_err(), DivisorZeroError); - let error = calculate_error_rate_from_div(100.MHz(), 0, 0); - assert_eq!(error.unwrap_err(), DivisorZeroError); - let error = calculate_error_rate_from_div(100.MHz(), 0, 16); - assert_eq!(error.unwrap_err(), DivisorZeroError); - } -} diff --git a/axi-uart16550-rs/src/registers.rs b/axi-uart16550-rs/src/registers.rs deleted file mode 100644 index fc7759e..0000000 --- a/axi-uart16550-rs/src/registers.rs +++ /dev/null @@ -1,177 +0,0 @@ -use arbitrary_int::u2; - -/// Transmitter Holding Register. -#[bitbybit::bitfield(u32)] -pub struct Fifo { - #[bits(0..=7, rw)] - data: u8, -} - -#[bitbybit::bitfield(u32)] -pub struct Ier { - /// Enable Modem Status Interrupt - #[bit(3, rw)] - modem_status: bool, - /// Enable Receiver Line Status Interrupt - #[bit(2, rw)] - line_status: bool, - /// Enable Transmitter Holding Register Empty Interrupt - #[bit(1, rw)] - thr_empty: bool, - /// Enable Received Data Available Interrupt - #[bit(0, rw)] - rx_avl: bool, -} - -/// Interrupt identification ID -#[bitbybit::bitenum(u3, exhaustive = false)] -#[derive(Debug, PartialEq, Eq)] -pub enum IntId2 { - ReceiverLineStatus = 0b011, - RxDataAvailable = 0b010, - CharTimeout = 0b110, - ThrEmpty = 0b001, - ModemStatus = 0b000, -} - -/// Interrupt Identification Register -#[bitbybit::bitfield(u32)] -pub struct Iir { - /// 16550 mode enabled? - #[bits(6..=7, r)] - fifo_enabled: u2, - #[bits(1..=3, r)] - int_id: Option, - /// Interrupt Pending, active low. - #[bit(0, r)] - int_pend_n: bool, -} - -#[bitbybit::bitenum(u2, exhaustive = true)] -pub enum RxFifoTrigger { - OneByte = 0b00, - FourBytes = 0b01, - EightBytes = 0b10, - FourteenBytes = 0b11, -} - -impl RxFifoTrigger { - pub const fn as_num(self) -> u32 { - match self { - RxFifoTrigger::OneByte => 1, - RxFifoTrigger::FourBytes => 4, - RxFifoTrigger::EightBytes => 8, - RxFifoTrigger::FourteenBytes => 14, - } - } -} - -/// FIFO Control Register -#[bitbybit::bitfield(u32, default = 0x0)] -pub struct Fcr { - #[bits(4..=5, rw)] - rx_fifo_trigger: RxFifoTrigger, - #[bit(3, rw)] - dma_mode_sel: bool, - #[bit(2, rw)] - reset_tx_fifo: bool, - #[bit(1, rw)] - reset_rx_fifo: bool, - #[bit(0, rw)] - fifo_enable: bool, -} - -#[bitbybit::bitenum(u2, exhaustive = true)] -#[derive(Default, Debug, PartialEq, Eq)] -pub enum WordLen { - Five = 0b00, - Six = 0b01, - Seven = 0b10, - #[default] - Eight = 0b11, -} - -#[bitbybit::bitenum(u1, exhaustive = true)] -#[derive(Default, Debug, PartialEq, Eq)] -pub enum StopBits { - #[default] - One = 0b0, - /// 1.5 for 5 bits/char, 2 otherwise. - OnePointFiveOrTwo = 0b1, -} - -/// Line control register -#[bitbybit::bitfield(u32, default = 0x00)] -pub struct Lcr { - #[bit(7, rw)] - div_access_latch: bool, - #[bit(6, rw)] - set_break: bool, - #[bit(5, rw)] - stick_parity: bool, - #[bit(4, rw)] - even_parity: bool, - #[bit(3, rw)] - parity_enable: bool, - /// 0: 1 stop bit, 1: 2 stop bits or 1.5 if 5 bits/char selected - #[bit(2, rw)] - stop_bits: StopBits, - #[bits(0..=1, rw)] - word_len: WordLen, -} - -impl Lcr { - pub fn new_for_divisor_access() -> Self { - Self::new_with_raw_value(0x80) - } -} - -/// Line Status Register -#[bitbybit::bitfield(u32)] -#[derive(Debug)] -pub struct Lsr { - #[bit(7, rw)] - error_in_rx_fifo: bool, - /// In the FIFO mode, this is set to 1 when the TX FIFO and shift register are both empty. - #[bit(6, rw)] - tx_empty: bool, - /// In the FIFO mode, this is set to 1 when the TX FIFO is empty. There might still be a byte - /// in the TX shift register. - #[bit(5, rw)] - thr_empty: bool, - #[bit(4, rw)] - break_interrupt: bool, - #[bit(3, rw)] - framing_error: bool, - #[bit(2, rw)] - parity_error: bool, - #[bit(1, rw)] - overrun_error: bool, - #[bit(0, rw)] - data_ready: bool, -} - -#[derive(derive_mmio::Mmio)] -#[repr(C)] -pub struct AxiUart16550 { - _reserved: [u32; 0x400], - /// FIFO register for LCR[7] == 0 or Divisor Latch (LSB) register for LCR[7] == 1 - fifo_or_dll: u32, - /// Interrupt Enable Register for LCR[7] == 0 or Divisor Latch (MSB) register for LCR[7] == 1 - ier_or_dlm: u32, - /// Interrupt Identification Register or FIFO Control Register. FCR is not included in 16450 - /// mode. If LCR[7] == 1, this register will be the read-only FIFO control register. - /// If LCR[7] == 0, this register will be the read-only interrupt IIR register or the - /// write-only FIFO control register. - iir_or_fcr: u32, - /// Line Control Register - lcr: Lcr, - /// Modem Control Register - mcr: u32, - /// Line Status Register - lsr: Lsr, - /// Modem Status Register - msr: u32, - /// Scratch Register - scr: u32, -} diff --git a/axi-uart16550-rs/src/rx.rs b/axi-uart16550-rs/src/rx.rs deleted file mode 100644 index e5047aa..0000000 --- a/axi-uart16550-rs/src/rx.rs +++ /dev/null @@ -1,223 +0,0 @@ -use core::convert::Infallible; - -use crate::{ - DEFAULT_RX_TRIGGER_LEVEL, - registers::{self, Fcr, Ier, Iir, IntId2, Lsr}, -}; - -#[derive(Debug, Default, Copy, Clone, Eq, PartialEq)] -pub struct RxErrors { - parity: bool, - frame: bool, - overrun: bool, -} - -impl RxErrors { - pub const fn new() -> Self { - Self { - parity: false, - frame: false, - overrun: false, - } - } - - pub const fn parity(&self) -> bool { - self.parity - } - - pub const fn frame(&self) -> bool { - self.frame - } - - pub const fn overrun(&self) -> bool { - self.overrun - } - - pub const fn has_errors(&self) -> bool { - self.parity || self.frame || self.overrun - } -} - -pub struct Rx { - /// Internal MMIO register structure. - pub(crate) regs: registers::MmioAxiUart16550<'static>, - pub(crate) errors: Option, -} - -impl Rx { - /// Steal the RX part of the UART 16550. - /// - /// You should only use this if you can not use the regular [super::AxiUart16550] constructor - /// and the [super::AxiUart16550::split] method. - /// - /// This function assumes that the setup of the UART was already done. - /// It can be used to create an RX handle inside an interrupt handler without having to use - /// a [critical_section::Mutex] if the user can guarantee that the RX handle will only be - /// used by the interrupt handler or only interrupt specific API will be used. - /// - /// # Safety - /// - /// The same safey rules specified in [super::AxiUart16550::new] apply. - pub const unsafe fn steal(base_addr: usize) -> Self { - Self { - regs: unsafe { registers::AxiUart16550::new_mmio_at(base_addr) }, - errors: None, - } - } - - pub(crate) fn new(regs: registers::MmioAxiUart16550<'static>) -> Self { - Self { regs, errors: None } - } - - #[inline] - pub fn read_fifo(&mut self) -> nb::Result { - let status_reg = self.regs.read_lsr(); - if !status_reg.data_ready() { - return Err(nb::Error::WouldBlock); - } - if status_reg.error_in_rx_fifo() { - self.errors = Some(Self::lsr_to_errors(status_reg)); - } - Ok(self.read_fifo_unchecked()) - } - - #[inline(always)] - pub fn read_fifo_unchecked(&mut self) -> u8 { - self.regs.read_fifo_or_dll() as u8 - } - - /// Start interrupt driven reception. - /// - /// This function resets the FIFO with [Self::reset_fifo] and then enables the interrupts - /// with [Self::enable_interrupt]. - /// After this, you only need to call [Self::on_interrupt_receiver_line_status] and - /// [Self::on_interrupt_data_available_or_char_timeout] in your interrupt handler depending - /// on the value of the IIR register to continously receive data. - #[inline] - pub fn start_interrupt_driven_reception(&mut self) { - self.reset_fifo(); - self.enable_interrupt(); - } - - #[inline] - pub fn enable_interrupt(&mut self) { - self.regs.modify_ier_or_dlm(|val| { - let mut ier = Ier::new_with_raw_value(val); - ier.set_rx_avl(true); - ier.set_line_status(true); - ier.raw_value() - }); - } - - #[inline] - pub fn disable_interrupt(&mut self) { - self.regs.modify_ier_or_dlm(|val| { - let mut ier = Ier::new_with_raw_value(val); - ier.set_rx_avl(false); - ier.set_line_status(false); - ier.raw_value() - }); - } - - #[inline] - pub fn reset_fifo(&mut self) { - self.regs.write_iir_or_fcr( - Fcr::builder() - .with_rx_fifo_trigger(DEFAULT_RX_TRIGGER_LEVEL) - .with_dma_mode_sel(false) - .with_reset_tx_fifo(false) - .with_reset_rx_fifo(true) - .with_fifo_enable(true) - .build() - .raw_value(), - ); - } - - #[inline(always)] - pub fn has_data(&mut self) -> bool { - self.regs.read_lsr().data_ready() - } - - #[inline] - pub fn read_iir(&mut self) -> Iir { - Iir::new_with_raw_value(self.regs.read_iir_or_fcr()) - } - - #[inline] - pub fn on_interrupt_receiver_line_status(&mut self, _iir: Iir) -> RxErrors { - let lsr = self.regs.read_lsr(); - Self::lsr_to_errors(lsr) - } - - #[inline] - pub fn on_interrupt_data_available_or_char_timeout( - &mut self, - int_id2: IntId2, - buf: &mut [u8; 16], - ) -> usize { - let mut read = 0; - // It is guaranteed that we can read the FIFO trigger level. - if int_id2 == IntId2::RxDataAvailable { - let trigger_level = Fcr::new_with_raw_value(self.regs.read_iir_or_fcr()); - (0..trigger_level.rx_fifo_trigger().as_num() as usize).for_each(|i| { - buf[i] = self.read_fifo_unchecked(); - read += 1; - }); - } - // Read the rest of the FIFO. - while self.has_data() && read < 16 { - buf[read] = self.read_fifo_unchecked(); - read += 1; - } - read - } - - pub fn lsr_to_errors(status_reg: Lsr) -> RxErrors { - let mut errors = RxErrors::new(); - if status_reg.framing_error() { - errors.frame = true; - } - if status_reg.parity_error() { - errors.parity = true; - } - if status_reg.overrun_error() { - errors.overrun = true; - } - errors - } -} - -impl embedded_hal_nb::serial::ErrorType for Rx { - type Error = Infallible; -} - -impl embedded_hal_nb::serial::Read for Rx { - #[inline] - fn read(&mut self) -> nb::Result { - self.read_fifo() - } -} - -impl embedded_io::ErrorType for Rx { - type Error = Infallible; -} - -impl embedded_io::Read for Rx { - fn read(&mut self, buf: &mut [u8]) -> Result { - if buf.is_empty() { - return Ok(0); - } - while !self.has_data() {} - let mut read = 0; - for byte in buf.iter_mut() { - match self.read_fifo() { - Ok(data) => { - *byte = data; - read += 1; - } - Err(nb::Error::WouldBlock) => break, - } - } - Ok(read) - } -} diff --git a/axi-uart16550-rs/src/tx.rs b/axi-uart16550-rs/src/tx.rs deleted file mode 100644 index d693fb8..0000000 --- a/axi-uart16550-rs/src/tx.rs +++ /dev/null @@ -1,152 +0,0 @@ -use core::convert::Infallible; - -use crate::{ - DEFAULT_RX_TRIGGER_LEVEL, - registers::{self, Fcr, Ier}, -}; - -pub struct Tx { - /// Internal MMIO register structure. - pub(crate) regs: registers::MmioAxiUart16550<'static>, -} - -impl Tx { - /// Steal the TX part of the UART 16550. - /// - /// You should only use this if you can not use the regular [super::AxiUart16550] constructor - /// and the [super::AxiUart16550::split] method. - /// - /// This function assumes that the setup of the UART was already done. - /// It can be used to create a TX handle inside an interrupt handler without having to use - /// a [critical_section::Mutex] if the user can guarantee that the TX handle will only be - /// used by the interrupt handler, or only interrupt specific API will be used. - /// - /// # Safety - /// - /// The same safey rules specified in [super::AxiUart16550::new] apply. - pub const unsafe fn steal(base_addr: usize) -> Self { - Self { - regs: unsafe { registers::AxiUart16550::new_mmio_at(base_addr) }, - } - } - - pub(crate) fn new(regs: registers::MmioAxiUart16550<'static>) -> Self { - Self { regs } - } - - #[inline] - pub fn write_fifo(&mut self, data: u8) -> nb::Result<(), Infallible> { - if !self.thr_empty() { - return Err(nb::Error::WouldBlock); - } - self.write_fifo_unchecked(data); - Ok(()) - } - - #[inline] - pub fn enable_interrupt(&mut self) { - self.regs.modify_ier_or_dlm(|val| { - let mut ier = Ier::new_with_raw_value(val); - ier.set_thr_empty(true); - ier.raw_value() - }); - } - - #[inline] - pub fn disable_interrupt(&mut self) { - self.regs.modify_ier_or_dlm(|val| { - let mut ier = Ier::new_with_raw_value(val); - ier.set_thr_empty(false); - ier.raw_value() - }); - } - - /// Write into the FIFO without checking the FIFO fill status. - /// - /// This can be useful to completely fill the FIFO if it is known to be empty. - #[inline(always)] - pub fn write_fifo_unchecked(&mut self, data: u8) { - self.regs.write_fifo_or_dll(data as u32); - } - - // TODO: Make this non-mut as soon as pure reads are available. - #[inline(always)] - pub fn thr_empty(&mut self) -> bool { - self.regs.read_lsr().thr_empty() - } - - #[inline(always)] - pub fn tx_empty(&mut self) -> bool { - self.regs.read_lsr().tx_empty() - } - - #[inline] - pub fn reset_fifo(&mut self) { - self.regs.write_iir_or_fcr( - Fcr::builder() - .with_rx_fifo_trigger(DEFAULT_RX_TRIGGER_LEVEL) - .with_dma_mode_sel(false) - .with_reset_tx_fifo(true) - .with_reset_rx_fifo(false) - .with_fifo_enable(true) - .build() - .raw_value(), - ); - } - - #[inline] - pub fn on_interrupt_thr_empty(&mut self, next_write_chunk: &[u8]) -> usize { - if next_write_chunk.is_empty() { - return 0; - } - let mut written = 0; - while self.thr_empty() && written < next_write_chunk.len() { - self.write_fifo_unchecked(next_write_chunk[written]); - written += 1; - } - written - } -} - -impl embedded_hal_nb::serial::ErrorType for Tx { - type Error = Infallible; -} - -impl embedded_hal_nb::serial::Write for Tx { - #[inline] - fn write(&mut self, word: u8) -> nb::Result<(), Self::Error> { - self.write_fifo(word) - } - - #[inline] - fn flush(&mut self) -> nb::Result<(), Self::Error> { - while !self.tx_empty() {} - Ok(()) - } -} - -impl embedded_io::ErrorType for Tx { - type Error = Infallible; -} - -impl embedded_io::Write for Tx { - fn write(&mut self, buf: &[u8]) -> Result { - if buf.is_empty() { - return Ok(0); - } - while !self.thr_empty() {} - let mut written = 0; - for &byte in buf.iter() { - match self.write_fifo(byte) { - Ok(_) => written += 1, - Err(nb::Error::WouldBlock) => break, - } - } - Ok(written) - } - - fn flush(&mut self) -> Result<(), Self::Error> { - while !self.tx_empty() {} - Ok(()) - } -} diff --git a/axi-uart16550-rs/src/tx_async.rs b/axi-uart16550-rs/src/tx_async.rs deleted file mode 100644 index b6dd8e0..0000000 --- a/axi-uart16550-rs/src/tx_async.rs +++ /dev/null @@ -1,259 +0,0 @@ -//! # Asynchronous TX support. -//! -//! This module provides support for asynchronous non-blocking TX transfers. -//! -//! It provides a static number of async wakers to allow a configurable amount of pollable -//! [TxFuture]s. Each UARTLite [Tx] instance which performs asynchronous TX operations needs -//! to be to explicitely assigned a waker when creating an awaitable [TxAsync] structure -//! as well as when calling the [on_interrupt_tx] handler. -//! -//! The maximum number of available wakers is configured via the waker feature flags: -//! -//! - `1-waker` -//! - `2-wakers` -//! - `4-wakers` -//! - `8-wakers` -//! - `16-wakers` -//! - `32-wakers` -use core::{cell::RefCell, convert::Infallible, sync::atomic::AtomicBool}; - -use critical_section::Mutex; -use embassy_sync::waitqueue::AtomicWaker; -use embedded_hal_async::delay::DelayNs; -use raw_slice::RawBufSlice; - -use crate::{ - FIFO_DEPTH, Tx, - registers::{self, Ier}, -}; - -#[cfg(feature = "1-waker")] -pub const NUM_WAKERS: usize = 1; -#[cfg(feature = "2-wakers")] -pub const NUM_WAKERS: usize = 2; -#[cfg(feature = "4-wakers")] -pub const NUM_WAKERS: usize = 4; -#[cfg(feature = "8-wakers")] -pub const NUM_WAKERS: usize = 8; -#[cfg(feature = "16-wakers")] -pub const NUM_WAKERS: usize = 16; -#[cfg(feature = "32-wakers")] -pub const NUM_WAKERS: usize = 32; -static UART_TX_WAKERS: [AtomicWaker; NUM_WAKERS] = [const { AtomicWaker::new() }; NUM_WAKERS]; -static TX_CONTEXTS: [Mutex>; NUM_WAKERS] = - [const { Mutex::new(RefCell::new(TxContext::new())) }; NUM_WAKERS]; -// Completion flag. Kept outside of the context structure as an atomic to avoid -// critical section. -static TX_DONE: [AtomicBool; NUM_WAKERS] = [const { AtomicBool::new(false) }; NUM_WAKERS]; - -#[derive(Debug, thiserror::Error)] -#[error("invalid waker slot index: {0}")] -pub struct InvalidWakerIndex(pub usize); - -/// This is a generic interrupt handler to handle asynchronous UART TX operations for a given -/// UART peripheral. -/// -/// The user has to call this once in the interrupt handler responsible if the interrupt was -/// triggered by the UARTLite. The relevant [Tx] handle of the UARTLite and the waker slot used -/// for it must be passed as well. [Tx::steal] can be used to create the required handle. -pub fn on_interrupt_tx(tx: &mut Tx, waker_slot: usize) { - if waker_slot >= NUM_WAKERS { - return; - } - let status = tx.regs.read_lsr(); - let ier = Ier::new_with_raw_value(tx.regs.read_ier_or_dlm()); - // Interrupt are not even enabled. - if !ier.thr_empty() { - return; - } - let mut context = critical_section::with(|cs| { - let context_ref = TX_CONTEXTS[waker_slot].borrow(cs); - *context_ref.borrow() - }); - // No transfer active. - if context.slice.is_null() { - return; - } - let slice_len = context.slice.len().unwrap(); - // We have to use the THRE instead of the TEMT status flag here, because the interrupt - // is configured to trigger on the THRE flag and the UART might still be busy shifting the - // last byte out. - if (context.progress >= slice_len && status.thr_empty()) || slice_len == 0 { - // Write back updated context structure. - critical_section::with(|cs| { - let context_ref = TX_CONTEXTS[waker_slot].borrow(cs); - *context_ref.borrow_mut() = context; - }); - // Transfer is done. - TX_DONE[waker_slot].store(true, core::sync::atomic::Ordering::Relaxed); - tx.disable_interrupt(); - UART_TX_WAKERS[waker_slot].wake(); - return; - } - // Safety: We documented that the user provided slice must outlive the future, so we convert - // the raw pointer back to the slice here. - let slice = unsafe { context.slice.get() }.expect("slice is invalid"); - while context.progress < slice_len { - match tx.write_fifo(slice[context.progress]) { - Ok(_) => context.progress += 1, - Err(nb::Error::WouldBlock) => break, - } - } - // Write back updated context structure. - critical_section::with(|cs| { - let context_ref = TX_CONTEXTS[waker_slot].borrow(cs); - *context_ref.borrow_mut() = context; - }); -} - -#[derive(Debug, Copy, Clone)] -pub struct TxContext { - progress: usize, - slice: RawBufSlice, -} - -#[allow(clippy::new_without_default)] -impl TxContext { - pub const fn new() -> Self { - Self { - progress: 0, - slice: RawBufSlice::new_nulled(), - } - } -} - -pub struct TxFuture { - waker_idx: usize, - reg_block: registers::MmioAxiUart16550<'static>, -} - -impl TxFuture { - /// Create a new TX future which can be used for asynchronous TX operations. - /// - /// # Safety - /// - /// This function stores the raw pointer of the passed data slice. The user MUST ensure - /// that the slice outlives the data structure. - pub unsafe fn new( - tx: &mut Tx, - waker_idx: usize, - data: &[u8], - ) -> Result { - TX_DONE[waker_idx].store(false, core::sync::atomic::Ordering::Relaxed); - tx.disable_interrupt(); - tx.reset_fifo(); - - let init_fill_count = core::cmp::min(data.len(), FIFO_DEPTH); - critical_section::with(|cs| { - let context_ref = TX_CONTEXTS[waker_idx].borrow(cs); - let mut context = context_ref.borrow_mut(); - unsafe { - context.slice.set(data); - } - context.progress = init_fill_count; - }); - // We fill the FIFO with initial data. - for data in data.iter().take(init_fill_count) { - tx.write_fifo_unchecked(*data); - } - tx.enable_interrupt(); - Ok(Self { - waker_idx, - reg_block: unsafe { tx.regs.clone() }, - }) - } -} - -impl Future for TxFuture { - type Output = usize; - - fn poll( - self: core::pin::Pin<&mut Self>, - cx: &mut core::task::Context<'_>, - ) -> core::task::Poll { - UART_TX_WAKERS[self.waker_idx].register(cx.waker()); - if TX_DONE[self.waker_idx].swap(false, core::sync::atomic::Ordering::Relaxed) { - let progress = critical_section::with(|cs| { - let mut ctx = TX_CONTEXTS[self.waker_idx].borrow(cs).borrow_mut(); - ctx.slice.set_null(); - ctx.progress - }); - return core::task::Poll::Ready(progress); - } - core::task::Poll::Pending - } -} - -impl Drop for TxFuture { - fn drop(&mut self) { - let mut tx = Tx::new(unsafe { self.reg_block.clone() }); - tx.disable_interrupt(); - } -} - -pub struct TxAsync { - tx: Tx, - waker_idx: usize, - delay: D, -} - -impl TxAsync { - /// Create a new asynchronous TX structure. - /// - /// The delay function is a [DelayNs] provider which is used to allow flushing the - /// device properly. This is because even when a write finished, the UART might still - /// be busy shifting the last byte out. - pub fn new(tx: Tx, waker_idx: usize, delay: D) -> Result { - if waker_idx >= NUM_WAKERS { - return Err(InvalidWakerIndex(waker_idx)); - } - Ok(Self { - tx, - waker_idx, - delay, - }) - } - - /// Write a buffer asynchronously. - /// - /// This implementation is not side effect free, and a started future might have already - /// written part of the passed buffer. - pub async fn write(&mut self, buf: &[u8]) -> usize { - if buf.is_empty() { - return 0; - } - let fut = unsafe { TxFuture::new(&mut self.tx, self.waker_idx, buf).unwrap() }; - fut.await - } - - /// Flush this output stream, ensuring that all intermediately buffered contents reach their destination. - pub async fn flush(&mut self) { - while !self.tx.tx_empty() { - self.delay.delay_us(10).await; - } - } - - pub fn release(self) -> Tx { - self.tx - } -} - -impl embedded_io::ErrorType for TxAsync { - type Error = Infallible; -} - -impl embedded_io_async::Write for TxAsync { - /// Write a buffer asynchronously. - /// - /// This implementation is not side effect free, and a started future might have already - /// written part of the passed buffer. - async fn write(&mut self, buf: &[u8]) -> Result { - Ok(self.write(buf).await) - } - - /// Flush this output stream, ensuring that all intermediately buffered contents reach their destination. - async fn flush(&mut self) -> Result<(), Self::Error> { - self.flush().await; - Ok(()) - } -} diff --git a/axi-uartlite-rs/.gitignore b/axi-uartlite-rs/.gitignore deleted file mode 100644 index ea8c4bf..0000000 --- a/axi-uartlite-rs/.gitignore +++ /dev/null @@ -1 +0,0 @@ -/target diff --git a/axi-uartlite-rs/Cargo.toml b/axi-uartlite-rs/Cargo.toml deleted file mode 100644 index c3822d0..0000000 --- a/axi-uartlite-rs/Cargo.toml +++ /dev/null @@ -1,27 +0,0 @@ -[package] -name = "axi-uartlite" -version = "0.1.0" -description = "LogiCORE AXI UART Lite v2.0 driver" -edition = "2024" - -[dependencies] -derive-mmio = { git = "https://github.com/knurling-rs/derive-mmio.git", rev = "0806ce10b132ca15c6d9122a2d15a6e146b01520"} -bitbybit = "1.3" -arbitrary-int = "1.3" -nb = "1" -embedded-hal-nb = "1" -embedded-io = "0.6" -embedded-io-async = "0.6" -critical-section = "1" -thiserror = { version = "2", default-features = false } -embassy-sync = "0.6" -raw-slice = { git = "https://egit.irs.uni-stuttgart.de/rust/raw-slice.git" } - -[features] -default = ["1-waker"] -1-waker = [] -2-wakers = [] -4-wakers = [] -8-wakers = [] -16-wakers = [] -32-wakers = [] diff --git a/axi-uartlite-rs/src/lib.rs b/axi-uartlite-rs/src/lib.rs deleted file mode 100644 index 7569112..0000000 --- a/axi-uartlite-rs/src/lib.rs +++ /dev/null @@ -1,264 +0,0 @@ -//! # AXI UART Lite v2.0 driver -//! -//! This is a native Rust driver for the AMD AXI UART Lite v2.0 IP core. -//! -//! # Features -//! -//! If asynchronous TX operations are used, the number of wakers which defaults to 1 waker can -//! also be configured. The [tx_async] module provides more details on the meaning of this number. -//! -//! - `1-waker` which is also a `default` feature -//! - `2-wakers` -//! - `4-wakers` -//! - `8-wakers` -//! - `16-wakers` -//! - `32-wakers` -#![no_std] - -use core::convert::Infallible; -use registers::Control; -pub mod registers; - -pub mod tx; -pub use tx::*; - -pub mod rx; -pub use rx::*; - -pub mod tx_async; -pub use tx_async::*; - -pub const FIFO_DEPTH: usize = 16; - -#[derive(Debug, Default, Copy, Clone, Eq, PartialEq)] -pub struct RxErrorsCounted { - parity: u8, - frame: u8, - overrun: u8, -} - -impl RxErrorsCounted { - pub const fn new() -> Self { - Self { - parity: 0, - frame: 0, - overrun: 0, - } - } - - pub const fn parity(&self) -> u8 { - self.parity - } - - pub const fn frame(&self) -> u8 { - self.frame - } - - pub const fn overrun(&self) -> u8 { - self.overrun - } - - pub fn has_errors(&self) -> bool { - self.parity > 0 || self.frame > 0 || self.overrun > 0 - } -} - -pub struct AxiUartlite { - rx: Rx, - tx: Tx, - errors: RxErrorsCounted, -} - -impl AxiUartlite { - /// Create a new AXI UART Lite peripheral driver. - /// - /// # Safety - /// - /// - The `base_addr` must be a valid memory-mapped register address of an AXI UART Lite peripheral. - /// - Dereferencing an invalid or misaligned address results in **undefined behavior**. - /// - The caller must ensure that no other code concurrently modifies the same peripheral registers - /// in an unsynchronized manner to prevent data races. - /// - This function does not enforce uniqueness of driver instances. Creating multiple instances - /// with the same `base_addr` can lead to unintended behavior if not externally synchronized. - /// - The driver performs **volatile** reads and writes to the provided address. - pub const unsafe fn new(base_addr: u32) -> Self { - let regs = unsafe { registers::AxiUartlite::new_mmio_at(base_addr as usize) }; - Self { - rx: Rx { - regs: unsafe { regs.clone() }, - errors: None, - }, - tx: Tx { regs, errors: None }, - errors: RxErrorsCounted::new(), - } - } - - #[inline(always)] - pub const fn regs(&mut self) -> &mut registers::MmioAxiUartlite<'static> { - &mut self.tx.regs - } - - /// Write into the UART Lite. - /// - /// Returns [nb::Error::WouldBlock] if the TX FIFO is full. - #[inline] - pub fn write_fifo(&mut self, data: u8) -> nb::Result<(), Infallible> { - self.tx.write_fifo(data).unwrap(); - if let Some(errors) = self.tx.errors { - self.handle_status_reg_errors(errors); - } - Ok(()) - } - - /// Write into the FIFO without checking the FIFO fill status. - /// - /// This can be useful to completely fill the FIFO if it is known to be empty. - #[inline(always)] - pub fn write_fifo_unchecked(&mut self, data: u8) { - self.tx.write_fifo_unchecked(data); - } - - #[inline] - pub fn read_fifo(&mut self) -> nb::Result { - let val = self.rx.read_fifo().unwrap(); - if let Some(errors) = self.rx.errors { - self.handle_status_reg_errors(errors); - } - Ok(val) - } - - #[inline(always)] - pub fn read_fifo_unchecked(&mut self) -> u8 { - self.rx.read_fifo_unchecked() - } - - // TODO: Make this non-mut as soon as pure reads are available - #[inline(always)] - pub fn tx_fifo_empty(&mut self) -> bool { - self.tx.fifo_empty() - } - - // TODO: Make this non-mut as soon as pure reads are available - #[inline(always)] - pub fn tx_fifo_full(&mut self) -> bool { - self.tx.fifo_full() - } - - // TODO: Make this non-mut as soon as pure reads are available - #[inline(always)] - pub fn rx_has_data(&mut self) -> bool { - self.rx.has_data() - } - - /// Read the error counters and also resets them. - pub fn read_and_clear_errors(&mut self) -> RxErrorsCounted { - let errors = self.errors; - self.errors = RxErrorsCounted::new(); - errors - } - - #[inline(always)] - fn handle_status_reg_errors(&mut self, errors: RxErrors) { - if errors.frame() { - self.errors.frame = self.errors.frame.saturating_add(1); - } - if errors.parity() { - self.errors.parity = self.errors.parity.saturating_add(1); - } - if errors.overrun() { - self.errors.overrun = self.errors.overrun.saturating_add(1); - } - } - - #[inline] - pub fn reset_rx_fifo(&mut self) { - self.regs().write_ctrl_reg( - Control::builder() - .with_enable_interrupt(false) - .with_reset_rx_fifo(true) - .with_reset_tx_fifo(false) - .build(), - ); - } - - #[inline] - pub fn reset_tx_fifo(&mut self) { - self.regs().write_ctrl_reg( - Control::builder() - .with_enable_interrupt(false) - .with_reset_rx_fifo(false) - .with_reset_tx_fifo(true) - .build(), - ); - } - - #[inline] - pub fn split(self) -> (Tx, Rx) { - (self.tx, self.rx) - } - - #[inline] - pub fn enable_interrupt(&mut self) { - self.regs().write_ctrl_reg( - Control::builder() - .with_enable_interrupt(true) - .with_reset_rx_fifo(false) - .with_reset_tx_fifo(false) - .build(), - ); - } - - #[inline] - pub fn disable_interrupt(&mut self) { - self.regs().write_ctrl_reg( - Control::builder() - .with_enable_interrupt(false) - .with_reset_rx_fifo(false) - .with_reset_tx_fifo(false) - .build(), - ); - } -} - -impl embedded_hal_nb::serial::ErrorType for AxiUartlite { - type Error = Infallible; -} - -impl embedded_hal_nb::serial::Write for AxiUartlite { - #[inline] - fn write(&mut self, word: u8) -> nb::Result<(), Self::Error> { - self.tx.write(word) - } - - #[inline] - fn flush(&mut self) -> nb::Result<(), Self::Error> { - self.tx.flush() - } -} - -impl embedded_hal_nb::serial::Read for AxiUartlite { - #[inline] - fn read(&mut self) -> nb::Result { - self.rx.read() - } -} - -impl embedded_io::ErrorType for AxiUartlite { - type Error = Infallible; -} - -impl embedded_io::Read for AxiUartlite { - fn read(&mut self, buf: &mut [u8]) -> Result { - self.rx.read(buf) - } -} - -impl embedded_io::Write for AxiUartlite { - fn write(&mut self, buf: &[u8]) -> Result { - self.tx.write(buf) - } - - fn flush(&mut self) -> Result<(), Self::Error> { - self.tx.flush() - } -} diff --git a/axi-uartlite-rs/src/registers.rs b/axi-uartlite-rs/src/registers.rs deleted file mode 100644 index 128aed2..0000000 --- a/axi-uartlite-rs/src/registers.rs +++ /dev/null @@ -1,55 +0,0 @@ -#[bitbybit::bitfield(u32)] -pub struct RxFifo { - #[bits(0..=7, r)] - pub data: u8, -} - -#[bitbybit::bitfield(u32)] -pub struct TxFifo { - #[bits(0..=7, w)] - pub data: u8, -} - -#[bitbybit::bitfield(u32)] -pub struct Status { - #[bit(7, r)] - pub parity_error: bool, - #[bit(6, r)] - pub frame_error: bool, - #[bit(5, r)] - pub overrun_error: bool, - #[bit(4, r)] - pub intr_enabled: bool, - #[bit(3, r)] - pub tx_fifo_full: bool, - #[bit(2, r)] - pub tx_fifo_empty: bool, - #[bit(1, r)] - pub rx_fifo_full: bool, - /// RX FIFO contains valid data. - #[bit(0, r)] - pub rx_fifo_valid_data: bool, -} - -#[bitbybit::bitfield(u32, default = 0x0)] -pub struct Control { - #[bit(4, w)] - enable_interrupt: bool, - #[bit(1, w)] - reset_rx_fifo: bool, - #[bit(0, w)] - reset_tx_fifo: bool, -} - -#[derive(derive_mmio::Mmio)] -#[repr(C)] -pub struct AxiUartlite { - #[mmio(RO)] - rx_fifo: RxFifo, - tx_fifo: TxFifo, - #[mmio(RO)] - stat_reg: Status, - ctrl_reg: Control, -} - -unsafe impl Send for MmioAxiUartlite<'static> {} diff --git a/axi-uartlite-rs/src/rx.rs b/axi-uartlite-rs/src/rx.rs deleted file mode 100644 index 3e5b813..0000000 --- a/axi-uartlite-rs/src/rx.rs +++ /dev/null @@ -1,172 +0,0 @@ -use core::convert::Infallible; - -use crate::registers::{self, AxiUartlite, Status}; - -#[derive(Debug, Default, Copy, Clone, Eq, PartialEq)] -pub struct RxErrors { - parity: bool, - frame: bool, - overrun: bool, -} - -impl RxErrors { - pub const fn new() -> Self { - Self { - parity: false, - frame: false, - overrun: false, - } - } - - pub const fn parity(&self) -> bool { - self.parity - } - - pub const fn frame(&self) -> bool { - self.frame - } - - pub const fn overrun(&self) -> bool { - self.overrun - } - - pub const fn has_errors(&self) -> bool { - self.parity || self.frame || self.overrun - } -} - -pub struct Rx { - pub(crate) regs: registers::MmioAxiUartlite<'static>, - pub(crate) errors: Option, -} - -impl Rx { - /// Steal the RX part of the UART Lite. - /// - /// You should only use this if you can not use the regular [super::AxiUartlite] constructor - /// and the [super::AxiUartlite::split] method. - /// - /// This function assumes that the setup of the UART was already done. - /// It can be used to create an RX handle inside an interrupt handler without having to use - /// a [critical_section::Mutex] if the user can guarantee that the RX handle will only be - /// used by the interrupt handler or only interrupt specific API will be used. - /// - /// # Safety - /// - /// The same safey rules specified in [super::AxiUartlite] apply. - #[inline] - pub const unsafe fn steal(base_addr: usize) -> Self { - Self { - regs: unsafe { AxiUartlite::new_mmio_at(base_addr) }, - errors: None, - } - } - - #[inline] - pub fn read_fifo(&mut self) -> nb::Result { - let status_reg = self.regs.read_stat_reg(); - if !status_reg.rx_fifo_valid_data() { - return Err(nb::Error::WouldBlock); - } - let val = self.read_fifo_unchecked(); - if let Some(errors) = handle_status_reg_errors(&status_reg) { - self.errors = Some(errors); - } - Ok(val) - } - - #[inline(always)] - pub fn read_fifo_unchecked(&mut self) -> u8 { - self.regs.read_rx_fifo().data() - } - - // TODO: Make this non-mut as soon as pure reads are available - #[inline(always)] - pub fn has_data(&mut self) -> bool { - self.regs.read_stat_reg().rx_fifo_valid_data() - } - - /// This simply reads all available bytes in the RX FIFO. - /// - /// It returns the number of read bytes. - #[inline] - pub fn read_whole_fifo(&mut self, buf: &mut [u8; 16]) -> usize { - let mut read = 0; - while read < buf.len() { - match self.read_fifo() { - Ok(byte) => { - buf[read] = byte; - read += 1; - } - Err(nb::Error::WouldBlock) => break, - } - } - read - } - - /// Can be called in the interrupt handler for the UART Lite to handle RX reception. - /// - /// Simply calls [Rx::read_whole_fifo]. - #[inline] - pub fn on_interrupt_rx(&mut self, buf: &mut [u8; 16]) -> usize { - self.read_whole_fifo(buf) - } - - pub fn read_and_clear_last_error(&mut self) -> Option { - let errors = self.errors?; - self.errors = None; - Some(errors) - } -} - -impl embedded_hal_nb::serial::ErrorType for Rx { - type Error = Infallible; -} - -impl embedded_hal_nb::serial::Read for Rx { - #[inline] - fn read(&mut self) -> nb::Result { - self.read_fifo() - } -} - -impl embedded_io::ErrorType for Rx { - type Error = Infallible; -} - -impl embedded_io::Read for Rx { - fn read(&mut self, buf: &mut [u8]) -> Result { - if buf.is_empty() { - return Ok(0); - } - while !self.has_data() {} - let mut read = 0; - for byte in buf.iter_mut() { - match self.read_fifo() { - Ok(data) => { - *byte = data; - read += 1; - } - Err(nb::Error::WouldBlock) => break, - } - } - Ok(read) - } -} - -pub const fn handle_status_reg_errors(status_reg: &Status) -> Option { - let mut errors = RxErrors::new(); - if status_reg.frame_error() { - errors.frame = true; - } - if status_reg.parity_error() { - errors.parity = true; - } - if status_reg.overrun_error() { - errors.overrun = true; - } - if !errors.has_errors() { - return None; - } - Some(errors) -} diff --git a/axi-uartlite-rs/src/tx.rs b/axi-uartlite-rs/src/tx.rs deleted file mode 100644 index eb93f21..0000000 --- a/axi-uartlite-rs/src/tx.rs +++ /dev/null @@ -1,142 +0,0 @@ -use core::convert::Infallible; - -use crate::{ - RxErrors, handle_status_reg_errors, - registers::{self, Control, TxFifo}, -}; - -pub struct Tx { - pub(crate) regs: registers::MmioAxiUartlite<'static>, - pub(crate) errors: Option, -} - -impl Tx { - /// Steal the TX part of the UART Lite. - /// - /// You should only use this if you can not use the regular [super::AxiUartlite] constructor - /// and the [super::AxiUartlite::split] method. - /// - /// This function assumes that the setup of the UART was already done. - /// It can be used to create a TX handle inside an interrupt handler without having to use - /// a [critical_section::Mutex] if the user can guarantee that the TX handle will only be - /// used by the interrupt handler, or only interrupt specific API will be used. - /// - /// # Safety - /// - /// The same safey rules specified in [super::AxiUartlite] apply. - pub unsafe fn steal(base_addr: usize) -> Self { - let regs = unsafe { registers::AxiUartlite::new_mmio_at(base_addr) }; - Self { regs, errors: None } - } - - /// Write into the UART Lite. - /// - /// Returns [nb::Error::WouldBlock] if the TX FIFO is full. - #[inline] - pub fn write_fifo(&mut self, data: u8) -> nb::Result<(), Infallible> { - let status_reg = self.regs.read_stat_reg(); - if status_reg.tx_fifo_full() { - return Err(nb::Error::WouldBlock); - } - self.write_fifo_unchecked(data); - if let Some(errors) = handle_status_reg_errors(&status_reg) { - self.errors = Some(errors); - } - Ok(()) - } - - #[inline] - pub fn reset_fifo(&mut self) { - let status = self.regs.read_stat_reg(); - self.regs.write_ctrl_reg( - Control::builder() - .with_enable_interrupt(status.intr_enabled()) - .with_reset_rx_fifo(false) - .with_reset_tx_fifo(true) - .build(), - ); - } - - /// Write into the FIFO without checking the FIFO fill status. - /// - /// This can be useful to completely fill the FIFO if it is known to be empty. - #[inline(always)] - pub fn write_fifo_unchecked(&mut self, data: u8) { - self.regs - .write_tx_fifo(TxFifo::new_with_raw_value(data as u32)); - } - - // TODO: Make this non-mut as soon as pure reads are available - #[inline(always)] - pub fn fifo_empty(&mut self) -> bool { - self.regs.read_stat_reg().tx_fifo_empty() - } - - // TODO: Make this non-mut as soon as pure reads are available - #[inline(always)] - pub fn fifo_full(&mut self) -> bool { - self.regs.read_stat_reg().tx_fifo_full() - } - - /// Fills the FIFO with user provided data until the user data - /// is consumed or the FIFO is full. - /// - /// Returns the amount of written data, which might be smaller than the buffer size. - pub fn fill_fifo(&mut self, buf: &[u8]) -> usize { - let mut written = 0; - while written < buf.len() { - match self.write_fifo(buf[written]) { - Ok(_) => written += 1, - Err(nb::Error::WouldBlock) => break, - } - } - written - } - - pub fn read_and_clear_last_error(&mut self) -> Option { - let errors = self.errors?; - self.errors = None; - Some(errors) - } -} - -impl embedded_hal_nb::serial::ErrorType for Tx { - type Error = Infallible; -} - -impl embedded_hal_nb::serial::Write for Tx { - fn write(&mut self, word: u8) -> nb::Result<(), Self::Error> { - self.write_fifo(word) - } - - fn flush(&mut self) -> nb::Result<(), Self::Error> { - while !self.fifo_empty() {} - Ok(()) - } -} - -impl embedded_io::ErrorType for Tx { - type Error = Infallible; -} - -impl embedded_io::Write for Tx { - fn write(&mut self, buf: &[u8]) -> Result { - if buf.is_empty() { - return Ok(0); - } - while self.fifo_full() {} - let mut written = 0; - for &byte in buf.iter() { - match self.write_fifo(byte) { - Ok(_) => written += 1, - Err(nb::Error::WouldBlock) => break, - } - } - Ok(written) - } - - fn flush(&mut self) -> Result<(), Self::Error> { - while !self.fifo_empty() {} - Ok(()) - } -} diff --git a/axi-uartlite-rs/src/tx_async.rs b/axi-uartlite-rs/src/tx_async.rs deleted file mode 100644 index e5eec74..0000000 --- a/axi-uartlite-rs/src/tx_async.rs +++ /dev/null @@ -1,221 +0,0 @@ -//! # Asynchronous TX support. -//! -//! This module provides support for asynchronous non-blocking TX transfers. -//! -//! It provides a static number of async wakers to allow a configurable amount of pollable -//! [TxFuture]s. Each UARTLite [Tx] instance which performs asynchronous TX operations needs -//! to be to explicitely assigned a waker when creating an awaitable [TxAsync] structure -//! as well as when calling the [on_interrupt_tx] handler. -//! -//! The maximum number of available wakers is configured via the waker feature flags: -//! -//! - `1-waker` -//! - `2-wakers` -//! - `4-wakers` -//! - `8-wakers` -//! - `16-wakers` -//! - `32-wakers` -use core::{cell::RefCell, convert::Infallible, sync::atomic::AtomicBool}; - -use critical_section::Mutex; -use embassy_sync::waitqueue::AtomicWaker; -use raw_slice::RawBufSlice; - -use crate::{FIFO_DEPTH, Tx}; - -#[cfg(feature = "1-waker")] -pub const NUM_WAKERS: usize = 1; -#[cfg(feature = "2-wakers")] -pub const NUM_WAKERS: usize = 2; -#[cfg(feature = "4-wakers")] -pub const NUM_WAKERS: usize = 4; -#[cfg(feature = "8-wakers")] -pub const NUM_WAKERS: usize = 8; -#[cfg(feature = "16-wakers")] -pub const NUM_WAKERS: usize = 16; -#[cfg(feature = "32-wakers")] -pub const NUM_WAKERS: usize = 32; -static UART_TX_WAKERS: [AtomicWaker; NUM_WAKERS] = [const { AtomicWaker::new() }; NUM_WAKERS]; -static TX_CONTEXTS: [Mutex>; NUM_WAKERS] = - [const { Mutex::new(RefCell::new(TxContext::new())) }; NUM_WAKERS]; -// Completion flag. Kept outside of the context structure as an atomic to avoid -// critical section. -static TX_DONE: [AtomicBool; NUM_WAKERS] = [const { AtomicBool::new(false) }; NUM_WAKERS]; - -#[derive(Debug, thiserror::Error)] -#[error("invalid waker slot index: {0}")] -pub struct InvalidWakerIndex(pub usize); - -/// This is a generic interrupt handler to handle asynchronous UART TX operations for a given -/// UART peripheral. -/// -/// The user has to call this once in the interrupt handler responsible if the interrupt was -/// triggered by the UARTLite. The relevant [Tx] handle of the UARTLite and the waker slot used -/// for it must be passed as well. [Tx::steal] can be used to create the required handle. -pub fn on_interrupt_tx(uartlite_tx: &mut Tx, waker_slot: usize) { - if waker_slot >= NUM_WAKERS { - return; - } - let status = uartlite_tx.regs.read_stat_reg(); - // Interrupt are not even enabled. - if !status.intr_enabled() { - return; - } - let mut context = critical_section::with(|cs| { - let context_ref = TX_CONTEXTS[waker_slot].borrow(cs); - *context_ref.borrow() - }); - // No transfer active. - if context.slice.is_null() { - return; - } - let slice_len = context.slice.len().unwrap(); - if (context.progress >= slice_len && status.tx_fifo_empty()) || slice_len == 0 { - // Write back updated context structure. - critical_section::with(|cs| { - let context_ref = TX_CONTEXTS[waker_slot].borrow(cs); - *context_ref.borrow_mut() = context; - }); - // Transfer is done. - TX_DONE[waker_slot].store(true, core::sync::atomic::Ordering::Relaxed); - UART_TX_WAKERS[waker_slot].wake(); - return; - } - // Safety: We documented that the user provided slice must outlive the future, so we convert - // the raw pointer back to the slice here. - let slice = unsafe { context.slice.get() }.expect("slice is invalid"); - while context.progress < slice_len { - if uartlite_tx.regs.read_stat_reg().tx_fifo_full() { - break; - } - // Safety: TX structure is owned by the future which does not write into the the data - // register, so we can assume we are the only one writing to the data register. - uartlite_tx.write_fifo_unchecked(slice[context.progress]); - context.progress += 1; - } - // Write back updated context structure. - critical_section::with(|cs| { - let context_ref = TX_CONTEXTS[waker_slot].borrow(cs); - *context_ref.borrow_mut() = context; - }); -} - -#[derive(Debug, Copy, Clone)] -pub struct TxContext { - progress: usize, - slice: RawBufSlice, -} - -#[allow(clippy::new_without_default)] -impl TxContext { - pub const fn new() -> Self { - Self { - progress: 0, - slice: RawBufSlice::new_nulled(), - } - } -} - -pub struct TxFuture { - waker_idx: usize, -} - -impl TxFuture { - /// Create a new TX future which can be used for asynchronous TX operations. - /// - /// # Safety - /// - /// This function stores the raw pointer of the passed data slice. The user MUST ensure - /// that the slice outlives the data structure. - pub unsafe fn new( - tx: &mut Tx, - waker_idx: usize, - data: &[u8], - ) -> Result { - TX_DONE[waker_idx].store(false, core::sync::atomic::Ordering::Relaxed); - tx.reset_fifo(); - - let init_fill_count = core::cmp::min(data.len(), FIFO_DEPTH); - // We fill the FIFO with initial data. - for data in data.iter().take(init_fill_count) { - tx.write_fifo_unchecked(*data); - } - critical_section::with(|cs| { - let context_ref = TX_CONTEXTS[waker_idx].borrow(cs); - let mut context = context_ref.borrow_mut(); - unsafe { - context.slice.set(data); - } - context.progress = init_fill_count; - }); - Ok(Self { waker_idx }) - } -} - -impl Future for TxFuture { - type Output = usize; - - fn poll( - self: core::pin::Pin<&mut Self>, - cx: &mut core::task::Context<'_>, - ) -> core::task::Poll { - UART_TX_WAKERS[self.waker_idx].register(cx.waker()); - if TX_DONE[self.waker_idx].swap(false, core::sync::atomic::Ordering::Relaxed) { - let progress = critical_section::with(|cs| { - let mut ctx = TX_CONTEXTS[self.waker_idx].borrow(cs).borrow_mut(); - ctx.slice.set_null(); - ctx.progress - }); - return core::task::Poll::Ready(progress); - } - core::task::Poll::Pending - } -} - -impl Drop for TxFuture { - fn drop(&mut self) {} -} - -pub struct TxAsync { - tx: Tx, - waker_idx: usize, -} - -impl TxAsync { - pub fn new(tx: Tx, waker_idx: usize) -> Result { - if waker_idx >= NUM_WAKERS { - return Err(InvalidWakerIndex(waker_idx)); - } - Ok(Self { tx, waker_idx }) - } - - /// Write a buffer asynchronously. - /// - /// This implementation is not side effect free, and a started future might have already - /// written part of the passed buffer. - pub async fn write(&mut self, buf: &[u8]) -> usize { - if buf.is_empty() { - return 0; - } - let fut = unsafe { TxFuture::new(&mut self.tx, self.waker_idx, buf).unwrap() }; - fut.await - } - - pub fn release(self) -> Tx { - self.tx - } -} - -impl embedded_io::ErrorType for TxAsync { - type Error = Infallible; -} - -impl embedded_io_async::Write for TxAsync { - /// Write a buffer asynchronously. - /// - /// This implementation is not side effect free, and a started future might have already - /// written part of the passed buffer. - async fn write(&mut self, buf: &[u8]) -> Result { - Ok(self.write(buf).await) - } -} diff --git a/examples/zedboard/Cargo.toml b/examples/zedboard/Cargo.toml index f120f8b..c0031d6 100644 --- a/examples/zedboard/Cargo.toml +++ b/examples/zedboard/Cargo.toml @@ -34,5 +34,5 @@ embassy-executor = { path = "/home/rmueller/Rust/embassy/embassy-executor", feat ]} embassy-time = { path = "/home/rmueller/Rust/embassy/embassy-time", version = "0.4" } heapless = "0.8" -axi-uartlite = { path = "../../axi-uartlite-rs" } -axi-uart16550 = { path = "../../axi-uart16550-rs" } +axi-uartlite = { path = "/home/rmueller/Rust/axi-uartlite-rs" } +axi-uart16550 = { path = "/home/rmueller/Rust/axi-uart16550-rs" } diff --git a/zynq7000-hal/src/i2c.rs b/zynq7000-hal/src/i2c.rs index 240fe9d..7f6e523 100644 --- a/zynq7000-hal/src/i2c.rs +++ b/zynq7000-hal/src/i2c.rs @@ -13,9 +13,9 @@ use crate::gpio::{ use crate::{ enable_amba_peripheral_clock, gpio::{ - IoPeriph, Mio10, Mio11, Mio12, Mio13, Mio14, Mio15, Mio28, Mio29, Mio30, Mio31, Mio32, - Mio33, Mio34, Mio35, Mio36, Mio37, Mio38, Mio39, Mio48, Mio49, Mio52, Mio53, MioPin, - MuxConf, PinMode, + IoPeriph, IoPeriphPin, Mio10, Mio11, Mio12, Mio13, Mio14, Mio15, Mio28, Mio29, Mio30, + Mio31, Mio32, Mio33, Mio34, Mio35, Mio36, Mio37, Mio38, Mio39, Mio48, Mio49, Mio52, Mio53, + MioPin, MuxConf, PinMode, }, slcr::Slcr, time::Hertz, @@ -55,11 +55,11 @@ impl PsI2c for MmioI2c<'static> { } } -pub trait SdaPin { +pub trait SdaPin: IoPeriphPin { const ID: I2cId; } -pub trait SckPin { +pub trait SckPin: IoPeriphPin { const ID: I2cId; } @@ -291,14 +291,16 @@ impl ClockConfig { #[derive(Debug, thiserror::Error)] #[error("invalid I2C ID")] -pub struct InvalidI2cIdError; +pub struct InvalidPsI2cError; #[derive(Debug, thiserror::Error)] pub enum I2cConstructionError { #[error("invalid I2C ID {0}")] - InvalidI2cId(#[from] InvalidI2cIdError), + InvalidPsI2c(#[from] InvalidPsI2cError), #[error("pin invalid for I2C ID")] PinInvalidForI2cId, + #[error("invalid pin configuration for I2C")] + InvalidPinConf, } pub struct I2c { regs: MmioI2c<'static>, @@ -308,14 +310,17 @@ impl I2c { pub fn new_with_mio( i2c: impl PsI2c, clk_cfg: ClockConfig, - _i2c_pins: (Sck, Sda), + i2c_pins: (Sck, Sda), ) -> Result { if i2c.id().is_none() { - return Err(InvalidI2cIdError.into()); + return Err(InvalidPsI2cError.into()); } if Sck::ID != Sda::ID { return Err(I2cConstructionError::PinInvalidForI2cId); } + if i2c_pins.0.mux_conf() != I2C_MUX_CONF || i2c_pins.1.mux_conf() != I2C_MUX_CONF { + return Err(I2cConstructionError::InvalidPinConf); + } Ok(Self::new_generic( i2c.id().unwrap(), i2c.reg_block(), @@ -323,9 +328,9 @@ impl I2c { )) } - pub fn new_with_emio(i2c: impl PsI2c, clk_cfg: ClockConfig) -> Result { + pub fn new_with_emio(i2c: impl PsI2c, clk_cfg: ClockConfig) -> Result { if i2c.id().is_none() { - return Err(InvalidI2cIdError); + return Err(InvalidPsI2cError); } Ok(Self::new_generic( i2c.id().unwrap(), diff --git a/zynq7000-hal/src/lib.rs b/zynq7000-hal/src/lib.rs index 4b6be52..b241405 100644 --- a/zynq7000-hal/src/lib.rs +++ b/zynq7000-hal/src/lib.rs @@ -22,6 +22,7 @@ pub mod prelude; pub mod slcr; pub mod spi; pub mod time; +pub mod ttc; pub mod uart; /// This enumeration encodes the various boot sources. diff --git a/zynq7000-hal/src/spi.rs b/zynq7000-hal/src/spi.rs index 3fa2dfc..7ea7781 100644 --- a/zynq7000-hal/src/spi.rs +++ b/zynq7000-hal/src/spi.rs @@ -3,8 +3,8 @@ use core::convert::Infallible; use crate::clocks::Clocks; use crate::enable_amba_peripheral_clock; use crate::gpio::{ - IoPeriph, Mio10, Mio11, Mio12, Mio13, Mio14, Mio15, Mio28, Mio29, Mio30, Mio31, Mio32, Mio33, - Mio34, Mio35, Mio36, Mio37, Mio38, Mio39, MioPin, MuxConf, + IoPeriph, IoPeriphPin, Mio10, Mio11, Mio12, Mio13, Mio14, Mio15, Mio28, Mio29, Mio30, Mio31, + Mio32, Mio33, Mio34, Mio35, Mio36, Mio37, Mio38, Mio39, MioPin, MuxConf, }; #[cfg(not(feature = "7z010-7z007s-clg225"))] use crate::gpio::{ @@ -54,22 +54,22 @@ impl PsSpi for MmioSpi<'static> { } } -pub trait SckPin { +pub trait SckPin: IoPeriphPin { const SPI: SpiId; const GROUP: usize; } -pub trait MosiPin { +pub trait MosiPin: IoPeriphPin { const SPI: SpiId; const GROUP: usize; } -pub trait MisoPin { +pub trait MisoPin: IoPeriphPin { const SPI: SpiId; const GROUP: usize; } -pub trait SsPin { +pub trait SsPin: IoPeriphPin { const IDX: usize; const SPI: SpiId; const GROUP: usize; @@ -413,18 +413,21 @@ pub struct Spi { #[derive(Debug, thiserror::Error)] #[error("invalid SPI ID")] -pub struct InvalidSpiIdError; +pub struct InvalidPsSpiError; +// TODO: Add and handle MUX config check. #[derive(Debug, thiserror::Error)] pub enum SpiConstructionError { #[error("invalid SPI ID {0}")] - InvalidSpiId(#[from] InvalidSpiIdError), + InvalidPsSpi(#[from] InvalidPsSpiError), /// The specified pins are not compatible to the specified SPI peripheral. #[error("pin invalid for SPI ID")] PinInvalidForSpiId, /// The specified pins are not from the same pin group. #[error("pin group missmatch")] GroupMissmatch, + #[error("invalid pin configuration for SPI")] + InvalidPinConf, } impl Spi { @@ -432,11 +435,11 @@ impl Spi { spi: impl PsSpi, clocks: &IoClocks, config: Config, - _spi_pins: (Sck, Mosi, Miso), + spi_pins: (Sck, Mosi, Miso), ) -> Result { let spi_id = spi.id(); if spi_id.is_none() { - return Err(InvalidSpiIdError.into()); + return Err(InvalidPsSpiError.into()); } let spi_id = spi_id.unwrap(); if Sck::GROUP != Mosi::GROUP || Sck::GROUP != Miso::GROUP { @@ -445,6 +448,12 @@ impl Spi { if Sck::SPI != spi_id || Mosi::SPI != spi_id || Miso::SPI != spi_id { return Err(SpiConstructionError::PinInvalidForSpiId); } + if spi_pins.0.mux_conf() != SPI_MUX_CONF + || spi_pins.0.mux_conf() != spi_pins.2.mux_conf() + || spi_pins.1.mux_conf() != spi_pins.2.mux_conf() + { + return Err(SpiConstructionError::InvalidPinConf); + } Ok(Self::new_generic_unchecked( spi_id, spi.reg_block(), @@ -457,12 +466,12 @@ impl Spi { spi: impl PsSpi, clocks: &IoClocks, config: Config, - _spi_pins: (Sck, Mosi, Miso), - _ss_pin: Ss, + spi_pins: (Sck, Mosi, Miso), + ss_pin: Ss, ) -> Result { let spi_id = spi.id(); if spi_id.is_none() { - return Err(InvalidSpiIdError.into()); + return Err(InvalidPsSpiError.into()); } let spi_id = spi_id.unwrap(); if Sck::GROUP != Mosi::GROUP || Sck::GROUP != Miso::GROUP || Sck::GROUP != Ss::GROUP { @@ -471,6 +480,13 @@ impl Spi { if Sck::SPI != spi_id || Mosi::SPI != spi_id || Miso::SPI != spi_id || Ss::SPI != spi_id { return Err(SpiConstructionError::PinInvalidForSpiId); } + if spi_pins.0.mux_conf() != SPI_MUX_CONF + || spi_pins.0.mux_conf() != spi_pins.2.mux_conf() + || spi_pins.1.mux_conf() != spi_pins.2.mux_conf() + || ss_pin.mux_conf() != spi_pins.0.mux_conf() + { + return Err(SpiConstructionError::InvalidPinConf); + } Ok(Self::new_generic_unchecked( spi_id, spi.reg_block(), @@ -483,12 +499,12 @@ impl Spi { spi: impl PsSpi, clocks: &IoClocks, config: Config, - _spi_pins: (Sck, Mosi, Miso), - _ss_pins: (Ss0, Ss1), + spi_pins: (Sck, Mosi, Miso), + ss_pins: (Ss0, Ss1), ) -> Result { let spi_id = spi.id(); if spi_id.is_none() { - return Err(InvalidSpiIdError.into()); + return Err(InvalidPsSpiError.into()); } let spi_id = spi_id.unwrap(); if Sck::GROUP != Mosi::GROUP @@ -506,6 +522,14 @@ impl Spi { { return Err(SpiConstructionError::PinInvalidForSpiId); } + if spi_pins.0.mux_conf() != SPI_MUX_CONF + || spi_pins.0.mux_conf() != spi_pins.2.mux_conf() + || spi_pins.1.mux_conf() != spi_pins.2.mux_conf() + || ss_pins.0.mux_conf() != spi_pins.0.mux_conf() + || ss_pins.1.mux_conf() != spi_pins.0.mux_conf() + { + return Err(SpiConstructionError::InvalidPinConf); + } Ok(Self::new_generic_unchecked( spi_id, spi.reg_block(), @@ -525,12 +549,12 @@ impl Spi { spi: impl PsSpi, clocks: &IoClocks, config: Config, - _spi_pins: (Sck, Mosi, Miso), - _ss_pins: (Ss0, Ss1, Ss2), + spi_pins: (Sck, Mosi, Miso), + ss_pins: (Ss0, Ss1, Ss2), ) -> Result { let spi_id = spi.id(); if spi_id.is_none() { - return Err(InvalidSpiIdError.into()); + return Err(InvalidPsSpiError.into()); } let spi_id = spi_id.unwrap(); if Sck::GROUP != Mosi::GROUP @@ -550,6 +574,15 @@ impl Spi { { return Err(SpiConstructionError::PinInvalidForSpiId); } + if spi_pins.0.mux_conf() != SPI_MUX_CONF + || spi_pins.0.mux_conf() != spi_pins.2.mux_conf() + || spi_pins.1.mux_conf() != spi_pins.2.mux_conf() + || ss_pins.0.mux_conf() != spi_pins.0.mux_conf() + || ss_pins.1.mux_conf() != spi_pins.0.mux_conf() + || ss_pins.2.mux_conf() != spi_pins.2.mux_conf() + { + return Err(SpiConstructionError::InvalidPinConf); + } Ok(Self::new_generic_unchecked( spi_id, spi.reg_block(), diff --git a/zynq7000-hal/src/ttc.rs b/zynq7000-hal/src/ttc.rs new file mode 100644 index 0000000..b40b1d4 --- /dev/null +++ b/zynq7000-hal/src/ttc.rs @@ -0,0 +1,297 @@ +//! Triple-timer counter (TTC) high-level driver. + +use core::convert::Infallible; + +use arbitrary_int::{u3, u4}; +use zynq7000::ttc::{MmioTtc, TTC_0_BASE_ADDR, TTC_1_BASE_ADDR}; + +#[cfg(not(feature = "7z010-7z007s-clg225"))] +use crate::gpio::{Mio16, Mio17, Mio18, Mio19, Mio40, Mio41, Mio42, Mio43}; +use crate::{ + clocks::ArmClocks, + gpio::{IoPeriph, IoPeriphPin, Mio28, Mio29, Mio30, Mio31, MioPin, MuxConf, PinMode}, + time::Hertz, +}; + +/// Each TTC consists of three independent timers/counters. +#[derive(Debug, Copy, Clone)] +pub enum TtcId { + Ttc0 = 0, + Ttc1 = 1, +} + +#[derive(Debug, Copy, Clone)] +pub enum ChannelId { + Ch0 = 0, + Ch1 = 1, + Ch2 = 2, +} + +pub trait PsTtc { + fn reg_block(&self) -> MmioTtc<'static>; + fn id(&self) -> Option; +} + +impl PsTtc for MmioTtc<'static> { + #[inline] + fn reg_block(&self) -> MmioTtc<'static> { + unsafe { self.clone() } + } + + #[inline] + fn id(&self) -> Option { + let base_addr = unsafe { self.ptr() } as usize; + if base_addr == TTC_0_BASE_ADDR { + return Some(TtcId::Ttc0); + } else if base_addr == TTC_1_BASE_ADDR { + return Some(TtcId::Ttc1); + } + None + } +} + +pub const TTC_MUX_CONF: MuxConf = MuxConf::new_with_l3(u3::new(0b110)); + +pub trait ClockInPin: IoPeriphPin { + const ID: TtcId; +} + +pub trait WaveOutPin: IoPeriphPin { + const ID: TtcId; +} + +macro_rules! into_ttc { + ($($Mio:ident),+) => { + $( + impl MioPin<$Mio, M> { + /// Convert the pin into a TTC pin by configuring the pin routing via the + /// MIO multiplexer bits. + pub fn into_ttck(self) -> MioPin<$Mio, IoPeriph> { + self.into_io_periph(TTC_MUX_CONF, None) + } + } + )+ + }; +} + +#[cfg(not(feature = "7z010-7z007s-clg225"))] +into_ttc!(Mio16, Mio17, Mio18, Mio19, Mio40, Mio41, Mio42, Mio43); +into_ttc!(Mio28, Mio29, Mio30, Mio31); + +pub struct Ttc { + pub ch0: TtcChannel, + pub ch1: TtcChannel, + pub ch2: TtcChannel, +} + +impl Ttc { + /// Create a new TTC instance. The passed TTC peripheral instance MUST point to a valid + /// processing system TTC peripheral. + pub fn new(ps_ttc: impl PsTtc) -> Option { + ps_ttc.id()?; + let regs = ps_ttc.reg_block(); + let ch0 = TtcChannel { + regs: unsafe { regs.clone() }, + id: ChannelId::Ch0, + }; + let ch1 = TtcChannel { + regs: unsafe { regs.clone() }, + id: ChannelId::Ch1, + }; + let ch2 = TtcChannel { + regs, + id: ChannelId::Ch2, + }; + Some(Self { ch0, ch1, ch2 }) + } +} + +pub struct TtcChannel { + regs: MmioTtc<'static>, + id: ChannelId, +} + +impl TtcChannel { + pub fn regs_mut(&mut self) -> &mut MmioTtc<'static> { + &mut self.regs + } + + pub fn id(&self) -> ChannelId { + self.id + } +} + +#[derive(Debug, thiserror::Error)] +#[error("invalid TTC pin configuration")] +pub struct InvalidTtcPinConfigError(pub MuxConf); + +#[derive(Debug, thiserror::Error)] +#[error("frequency is zero")] +pub struct FrequencyIsZeroError; + +#[derive(Debug, thiserror::Error)] +pub enum TtcConstructionError { + #[error("invalid TTC pin configuration")] + InvalidTtcPinConfig(#[from] InvalidTtcPinConfigError), + #[error("frequency is zero")] + FrequencyIsZero(#[from] FrequencyIsZeroError), +} + +pub fn calculate_prescaler_reg_and_interval_ticks(mut ref_clk: Hertz, freq: Hertz) -> (u4, u16) { + // TODO: Can this be optimized? + let mut prescaler_reg = 0; + let mut tick_val = ref_clk / freq; + while tick_val > u16::MAX as u32 { + ref_clk /= 2; + prescaler_reg += 1; + tick_val = ref_clk / freq; + } + (u4::new(prescaler_reg), tick_val as u16) +} + +pub struct Pwm { + channel: TtcChannel, + ref_clk: Hertz, +} + +impl Pwm { + /// Create a new PWM instance which uses the CPU 1x clock as the clock source. + pub fn new_with_mio_wave_out( + channel: TtcChannel, + arm_clocks: &ArmClocks, + freq: Hertz, + wave_out: impl WaveOutPin, + ) -> Result { + if wave_out.mux_conf() != TTC_MUX_CONF { + return Err(InvalidTtcPinConfigError(wave_out.mux_conf()).into()); + } + if freq.raw() == 0 { + return Err(FrequencyIsZeroError.into()); + } + let (prescaler_reg, tick_val) = + calculate_prescaler_reg_and_interval_ticks(arm_clocks.cpu_1x_clk(), freq); + let id = channel.id() as usize; + let mut pwm = Self { + channel, + ref_clk: arm_clocks.cpu_1x_clk(), + }; + pwm.set_up_and_configure_pwm(id, prescaler_reg, tick_val); + Ok(pwm) + } + + /// Set a new frequency for the PWM cycle. + /// + /// This resets the duty cycle to 0%. + pub fn set_frequency(&mut self, freq: Hertz) -> Result<(), FrequencyIsZeroError> { + if freq.raw() == 0 { + return Err(FrequencyIsZeroError); + } + let id = self.channel.id() as usize; + let (prescaler_reg, tick_val) = + calculate_prescaler_reg_and_interval_ticks(self.ref_clk, freq); + self.set_up_and_configure_pwm(id, prescaler_reg, tick_val); + Ok(()) + } + + #[inline] + pub fn max_duty_cycle(&self) -> u16 { + self.channel + .regs + .read_interval_value(self.channel.id() as usize) + .unwrap() + .value() + } + + #[inline] + pub fn set_duty_cycle(&mut self, duty: u16) { + let id = self.channel.id() as usize; + self.channel + .regs + .modify_cnt_ctrl(id, |mut val| { + val.set_disable(true); + val + }) + .unwrap(); + self.channel + .regs + .write_match_value_0( + self.channel.id() as usize, + zynq7000::ttc::RwValue::new_with_raw_value(duty as u32), + ) + .unwrap(); + self.channel + .regs + .modify_cnt_ctrl(id, |mut val| { + val.set_disable(false); + val.set_reset(true); + val + }) + .unwrap(); + } + + fn set_up_and_configure_pwm(&mut self, id: usize, prescaler_reg: u4, tick_val: u16) { + // Disable the counter first. + self.channel + .regs + .write_cnt_ctrl(id, zynq7000::ttc::CounterControl::new_with_raw_value(1)) + .unwrap(); + + // Clock configuration + self.channel + .regs + .write_clk_cntr( + id, + zynq7000::ttc::ClockControl::builder() + .with_ext_clk_edge(false) + .with_clk_src(zynq7000::ttc::ClockSource::Pclk) + .with_prescaler(prescaler_reg) + .with_prescale_enable(prescaler_reg.value() > 0) + .build(), + ) + .unwrap(); + self.channel + .regs + .write_interval_value( + id, + zynq7000::ttc::RwValue::new_with_raw_value(tick_val as u32), + ) + .unwrap(); + // Corresponds to duty cycle 0. + self.channel + .regs + .write_match_value_0(id, zynq7000::ttc::RwValue::new_with_raw_value(0)) + .unwrap(); + self.channel + .regs + .write_cnt_ctrl( + id, + zynq7000::ttc::CounterControl::builder() + .with_wave_polarity(zynq7000::ttc::WavePolarity::HighToLowOnMatch1) + .with_wave_enable_n(zynq7000::ttc::WaveEnable::Enable) + .with_reset(true) + .with_match_enable(false) + .with_decrementing(false) + .with_mode(zynq7000::ttc::Mode::Interval) + .with_disable(false) + .build(), + ) + .unwrap(); + } +} + +impl embedded_hal::pwm::ErrorType for Pwm { + type Error = Infallible; +} + +impl embedded_hal::pwm::SetDutyCycle for Pwm { + #[inline] + fn max_duty_cycle(&self) -> u16 { + self.max_duty_cycle() + } + + #[inline] + fn set_duty_cycle(&mut self, duty: u16) -> Result<(), Self::Error> { + self.set_duty_cycle(duty); + Ok(()) + } +} diff --git a/zynq7000-hal/src/uart/mod.rs b/zynq7000-hal/src/uart/mod.rs index 83d6a0c..273afe8 100644 --- a/zynq7000-hal/src/uart/mod.rs +++ b/zynq7000-hal/src/uart/mod.rs @@ -99,7 +99,6 @@ pub trait UartPins {} #[error("divisor is zero")] pub struct DivisorZero; -/// TODO: Integrate into macro. macro_rules! pin_pairs { ($UartPeriph:path, ($( [$(#[$meta:meta], )? $TxMio:ident, $RxMio:ident] ),+ $(,)? )) => { $( @@ -424,12 +423,12 @@ pub struct Uart { #[derive(Debug, thiserror::Error)] #[error("invalid UART ID")] -pub struct InvalidUartIdError; +pub struct InvalidPsUart; #[derive(Debug, thiserror::Error)] pub enum UartConstructionError { - #[error("invalid UART ID: {0}")] - InvalidUartId(#[from] InvalidUartIdError), + #[error("invalid UART ID")] + InvalidPsUart(#[from] InvalidPsUart), #[error("missmatch between pins index and passed index")] IdxMissmatch, #[error("invalid pin mux conf for UART")] @@ -442,9 +441,9 @@ impl Uart { /// /// A valid PL design which routes the UART pins through into the PL must be used for this to /// work. - pub fn new_with_emio(uart: impl PsUart, cfg: UartConfig) -> Result { + pub fn new_with_emio(uart: impl PsUart, cfg: UartConfig) -> Result { if uart.uart_id().is_none() { - return Err(InvalidUartIdError); + return Err(InvalidPsUart); } Ok(Self::new_generic_unchecked( uart.reg_block(), @@ -464,7 +463,7 @@ impl Uart { { let id = uart.uart_id(); if id.is_none() { - return Err(InvalidUartIdError.into()); + return Err(InvalidPsUart.into()); } if id.unwrap() != TxPinI::UART_IDX || id.unwrap() != RxPinI::UART_IDX { return Err(UartConstructionError::IdxMissmatch); diff --git a/zynq7000/src/i2c.rs b/zynq7000/src/i2c.rs index 3f05b33..b06d2f8 100644 --- a/zynq7000/src/i2c.rs +++ b/zynq7000/src/i2c.rs @@ -1,3 +1,4 @@ +//! SPI register module. use arbitrary_int::{u2, u6, u10}; pub const I2C_0_BASE_ADDR: usize = 0xE000_4000; diff --git a/zynq7000/src/lib.rs b/zynq7000/src/lib.rs index fb8dc5f..5ad2a15 100644 --- a/zynq7000/src/lib.rs +++ b/zynq7000/src/lib.rs @@ -22,6 +22,7 @@ pub mod i2c; pub mod mpcore; pub mod slcr; pub mod spi; +pub mod ttc; pub mod uart; static PERIPHERALS_TAKEN: AtomicBool = AtomicBool::new(false); @@ -43,6 +44,8 @@ pub struct PsPeripherals { pub gtc: gtc::MmioGtc<'static>, pub gpio: gpio::MmioGpio<'static>, pub slcr: slcr::MmioSlcr<'static>, + pub ttc_0: ttc::MmioTtc<'static>, + pub ttc_1: ttc::MmioTtc<'static>, } impl PsPeripherals { @@ -74,6 +77,8 @@ impl PsPeripherals { spi_1: spi::Spi::new_mmio_fixed_1(), i2c_0: i2c::I2c::new_mmio_fixed_0(), i2c_1: i2c::I2c::new_mmio_fixed_1(), + ttc_0: ttc::Ttc::new_mmio_fixed_0(), + ttc_1: ttc::Ttc::new_mmio_fixed_1(), } } } diff --git a/zynq7000/src/spi.rs b/zynq7000/src/spi.rs index d87ccbf..74087fa 100644 --- a/zynq7000/src/spi.rs +++ b/zynq7000/src/spi.rs @@ -1,3 +1,4 @@ +//! SPI register module. use arbitrary_int::u4; pub const SPI_0_BASE_ADDR: usize = 0xE000_6000; diff --git a/zynq7000/src/ttc.rs b/zynq7000/src/ttc.rs new file mode 100644 index 0000000..8f00a38 --- /dev/null +++ b/zynq7000/src/ttc.rs @@ -0,0 +1,183 @@ +//! Triple-timer counter (TTC) register module. +use arbitrary_int::u4; + +pub const TTC_0_BASE_ADDR: usize = 0xF800_1004; +pub const TTC_1_BASE_ADDR: usize = 0xF800_2004; + +#[bitbybit::bitenum(u1, exhaustive = true)] +pub enum ClockSource { + /// PS internal bus clock. + Pclk = 0b0, + External = 0b1, +} + +#[bitbybit::bitfield(u32, default = 0x0)] +pub struct ClockControl { + /// When this bit is set and the external clock is selected, the counter clocks on the + /// negative edge of the external clock input. + #[bit(6, rw)] + ext_clk_edge: bool, + #[bit(5, rw)] + clk_src: ClockSource, + #[bits(1..=4, rw)] + prescaler: u4, + #[bit(0, rw)] + prescale_enable: bool, +} + +#[bitbybit::bitenum(u1, exhaustive = true)] +pub enum Mode { + Overflow = 0b0, + Interval = 0b1, +} + +#[derive(Default)] +#[bitbybit::bitenum(u1, exhaustive = true)] +pub enum WavePolarity { + /// The waveform output goes from high to low on a match 0 interrupt and returns high on + /// overflow or interval interrupt. + #[default] + HighToLowOnMatch1 = 0b0, + /// The waveform output goes from low to high on a match 0 interrupt and returns low on + /// overflow or interval interrupt. + LowToHighOnMatch1 = 0b1, +} + +#[bitbybit::bitenum(u1, exhaustive = true)] +pub enum WaveEnable { + Enable = 0b0, + Disable = 0b1, +} + +#[bitbybit::bitfield(u32, default = 0x0)] +pub struct CounterControl { + #[bit(6, rw)] + wave_polarity: WavePolarity, + /// Output waveform enable, active low. Reset value 1. + #[bit(5, rw)] + wave_enable_n: WaveEnable, + /// Resets the counter and restarts counting. Automatically cleared on restart. + #[bit(4, rw)] + reset: bool, + /// When this bit is set, an interrupt is generated when the count value matches one of the + /// three match registers and the corresponding bit is set in the IER register. + #[bit(3, rw)] + match_enable: bool, + /// When this bit is high, the timer counts down. + #[bit(2, rw)] + decrementing: bool, + #[bit(1, rw)] + mode: Mode, + #[bit(0, rw)] + disable: bool, +} + +#[bitbybit::bitfield(u32)] +pub struct Counter { + #[bits(0..=15, r)] + count: u16, +} + +#[bitbybit::bitfield(u32)] +pub struct RwValue { + #[bits(0..=15, rw)] + value: u16, +} + +#[bitbybit::bitfield(u32)] +pub struct InterruptStatus { + /// Even timer overflow interrupt. + #[bit(5, r)] + event: bool, + #[bit(4, r)] + counter_overflow: bool, + #[bit(3, r)] + match_2: bool, + #[bit(2, r)] + match_1: bool, + #[bit(1, r)] + match_0: bool, + #[bit(0, r)] + interval: bool, +} + +#[bitbybit::bitfield(u32)] +pub struct InterruptControl { + /// Even timer overflow interrupt. + #[bit(5, rw)] + event: bool, + #[bit(4, rw)] + counter_overflow: bool, + #[bit(3, rw)] + match_2: bool, + #[bit(2, rw)] + match_1: bool, + #[bit(1, rw)] + match_0: bool, + #[bit(0, rw)] + interval: bool, +} + +#[bitbybit::bitfield(u32)] +pub struct EventControl { + /// E_Ov bit. When set to 0, the event timer is disabled and set to 0 when an event timer + /// register overflow occurs. Otherwise, continue counting on overflow. + #[bit(2, rw)] + continuous_mode: bool, + /// E_Lo bit. When set to 1, counts PCLK cycles during low level duration of the external + /// clock. Otherwise, counts it during high level duration. + #[bit(1, rw)] + count_low_level_of_ext_clk: bool, + #[bit(0, rw)] + enable: bool, +} + +#[bitbybit::bitfield(u32)] +pub struct EventCount { + #[bits(0..=15, r)] + count: u16, +} + +/// Triple-timer counter +#[derive(derive_mmio::Mmio)] +#[repr(C)] +pub struct Ttc { + clk_cntr: [ClockControl; 3], + cnt_ctrl: [CounterControl; 3], + #[mmio(PureRead)] + current_counter: [Counter; 3], + interval_value: [RwValue; 3], + match_value_0: [RwValue; 3], + match_value_1: [RwValue; 3], + match_value_2: [RwValue; 3], + #[mmio(Read)] + isr: [InterruptStatus; 3], + ier: [InterruptControl; 3], + event_cntrl: [EventControl; 3], + #[mmio(PureRead)] + event_reg: [EventCount; 3], +} + +impl Ttc { + /// Create a new TTC MMIO instance for TTC0 at address [TTC_0_BASE_ADDR]. + /// + /// # 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() -> MmioTtc<'static> { + unsafe { Self::new_mmio_at(TTC_0_BASE_ADDR) } + } + + /// Create a new TTC MMIO instance for TTC1 at address [TTC_1_BASE_ADDR]. + /// + /// # 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() -> MmioTtc<'static> { + unsafe { Self::new_mmio_at(TTC_1_BASE_ADDR) } + } +} diff --git a/zynq7000/src/uart.rs b/zynq7000/src/uart.rs index 915e009..d04b42b 100644 --- a/zynq7000/src/uart.rs +++ b/zynq7000/src/uart.rs @@ -1,4 +1,4 @@ -//! UART register module. +//! PS UART register module. use arbitrary_int::u6; pub const UART_0_BASE: usize = 0xE000_0000;