//! # UART module. //! //! Support for the processing system UARTs. use core::convert::Infallible; use arbitrary_int::u3; use libm::round; use zynq7000::{ slcr::reset::DualRefAndClockReset, uart::{ BaudRateDiv, Baudgen, ChMode, ClkSel, FifoTrigger, InterruptControl, MmioUart, Mode, UART_0_BASE, UART_1_BASE, }, }; use crate::{ enable_amba_peripheral_clock, gpio::{ IoPeriph, IoPeriphPin, Mio8, Mio9, 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, }; #[cfg(not(feature = "7z010-7z007s-clg225"))] use crate::gpio::{ Mio16, Mio17, Mio18, Mio19, Mio20, Mio21, Mio22, Mio23, Mio24, Mio25, Mio26, Mio27, Mio40, Mio41, Mio42, Mio43, Mio44, Mio45, Mio46, Mio47, Mio50, Mio51, }; use super::{clocks::IoClocks, time::Hertz}; 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 = 64; pub const DEFAULT_RX_TRIGGER_LEVEL: u8 = 32; pub const UART_MUX_CONF: MuxConf = MuxConf::new_with_l3(u3::new(0b111)); #[derive(Debug, Copy, Clone, PartialEq, Eq)] pub enum UartId { Uart0 = 0, Uart1 = 1, } pub trait PsUart { fn reg_block(&self) -> MmioUart<'static>; fn uart_id(&self) -> Option; } impl PsUart for MmioUart<'static> { #[inline] fn reg_block(&self) -> MmioUart<'static> { unsafe { self.clone() } } fn uart_id(&self) -> Option { let base_addr = unsafe { self.ptr() } as usize; if base_addr == UART_0_BASE { return Some(UartId::Uart0); } else if base_addr == UART_1_BASE { return Some(UartId::Uart1); } None } } impl UartId { /// Unsafely steal a peripheral MMIO block for the given UART. /// /// # Safety /// /// Circumvents ownership and safety guarantees by the HAL. pub const unsafe fn regs(&self) -> MmioUart<'static> { match self { UartId::Uart0 => unsafe { zynq7000::uart::Uart::new_mmio_fixed_0() }, UartId::Uart1 => unsafe { zynq7000::uart::Uart::new_mmio_fixed_1() }, } } } pub trait RxPin { const UART_IDX: UartId; } pub trait TxPin { const UART_IDX: UartId; } pub trait UartPins {} #[derive(Debug, thiserror::Error)] #[error("divisor is zero")] pub struct DivisorZero; /// TODO: Integrate into macro. macro_rules! pin_pairs { ($UartPeriph:path, ($( [$(#[$meta:meta], )? $TxMio:ident, $RxMio:ident] ),+ $(,)? )) => { $( $( #[$meta] )? impl TxPin for MioPin<$TxMio, IoPeriph> { const UART_IDX: UartId = $UartPeriph; } $( #[$meta] )? impl RxPin for MioPin<$RxMio, IoPeriph> { const UART_IDX: UartId = $UartPeriph; } impl UartPins for (MioPin<$TxMio, IoPeriph>, MioPin<$RxMio, IoPeriph>) {} )+ }; } macro_rules! impl_into_uart { (($($Mio:ident),+)) => { $( impl MioPin<$Mio, M> { pub fn into_uart(self) -> MioPin<$Mio, IoPeriph> { self.into_io_periph(UART_MUX_CONF, None) } } )+ }; } impl_into_uart!(( Mio10, Mio11, Mio15, Mio14, Mio31, Mio30, Mio35, Mio34, Mio39, Mio38, Mio8, Mio9, Mio12, Mio13, Mio28, Mio29, Mio32, Mio33, Mio36, Mio37, Mio48, Mio49, Mio52, Mio53 )); #[cfg(not(feature = "7z010-7z007s-clg225"))] impl_into_uart!(( Mio19, Mio18, Mio23, Mio22, Mio43, Mio42, Mio47, Mio46, Mio51, Mio50, Mio16, Mio17, Mio20, Mio21, Mio24, Mio25, Mio40, Mio41, Mio44, Mio45 )); pin_pairs!( UartId::Uart0, ( [Mio11, Mio10], [Mio15, Mio14], [#[cfg(not(feature ="7z010-7z007s-clg225"))], Mio19, Mio18], [#[cfg(not(feature ="7z010-7z007s-clg225"))], Mio23, Mio22], [#[cfg(not(feature ="7z010-7z007s-clg225"))], Mio27, Mio26], [Mio31, Mio30], [Mio35, Mio34], [Mio39, Mio38], [#[cfg(not(feature ="7z010-7z007s-clg225"))], Mio43, Mio42], [#[cfg(not(feature ="7z010-7z007s-clg225"))], Mio47, Mio46], [#[cfg(not(feature ="7z010-7z007s-clg225"))], Mio51, Mio50], ) ); pin_pairs!( UartId::Uart1, ( [Mio8, Mio9], [Mio12, Mio13], [#[cfg(not(feature ="7z010-7z007s-clg225"))], Mio16, Mio17], [#[cfg(not(feature ="7z010-7z007s-clg225"))], Mio20, Mio21], [#[cfg(not(feature ="7z010-7z007s-clg225"))], Mio24, Mio25], [Mio28, Mio29], [Mio32, Mio33], [Mio36, Mio37], [#[cfg(not(feature ="7z010-7z007s-clg225"))], Mio40, Mio41], [#[cfg(not(feature ="7z010-7z007s-clg225"))], Mio44, Mio45], [Mio48, Mio49], [Mio52, Mio53], ) ); /// Based on values provided by the vendor library. pub const MAX_BAUD_RATE: u32 = 6240000; /// Based on values provided by the vendor library. pub const MIN_BAUD_RATE: u32 = 110; #[derive(Debug, Default, Clone, Copy)] pub enum Parity { Even, Odd, #[default] None, } #[derive(Debug, Default, Clone, Copy)] pub enum Stopbits { #[default] One, OnePointFive, Two, } #[derive(Debug, Default, Clone, Copy)] pub enum CharLen { SixBits, SevenBits, #[default] EightBits, } #[derive(Debug, Clone, Copy)] pub struct ClkConfigRaw { cd: u16, bdiv: u8, } #[cfg(feature = "alloc")] pub fn calculate_viable_configs( mut uart_clk: Hertz, clk_sel: ClkSel, target_baud: u32, ) -> alloc::vec::Vec<(ClkConfigRaw, f64)> { let mut viable_cfgs = alloc::vec::Vec::new(); if clk_sel == ClkSel::UartRefClkDiv8 { uart_clk /= 8; } let mut current_clk_config = ClkConfigRaw::new(0, 0); for bdiv in 4..u8::MAX { let cd = round(uart_clk.raw() as f64 / ((bdiv as u32 + 1) as f64 * target_baud as f64)) as u64; if cd > u16::MAX as u64 { continue; } current_clk_config.cd = cd as u16; current_clk_config.bdiv = bdiv; let baud = current_clk_config.actual_baud(uart_clk); let error = ((baud - target_baud as f64).abs() / target_baud as f64) * 100.0; if error < MAX_BAUDERROR_RATE as f64 { viable_cfgs.push((current_clk_config, error)); } } viable_cfgs } /// Calculate the clock configuration for the smallest error to reach the desired target /// baud rate. /// /// You can also use [calculate_viable_configs] to get a list of all viable configurations. pub fn calculate_raw_baud_cfg_smallest_error( mut uart_clk: Hertz, clk_sel: ClkSel, target_baud: u32, ) -> Result<(ClkConfigRaw, f64), DivisorZero> { if target_baud == 0 { return Err(DivisorZero); } if clk_sel == ClkSel::UartRefClkDiv8 { uart_clk /= 8; } let mut current_clk_config = ClkConfigRaw::default(); let mut best_clk_config = ClkConfigRaw::default(); let mut smallest_error: f64 = 100.0; for bdiv in 4..u8::MAX { let cd = round(uart_clk.raw() as f64 / ((bdiv as u32 + 1) as f64 * target_baud as f64)) as u64; if cd > u16::MAX as u64 { continue; } current_clk_config.cd = cd as u16; current_clk_config.bdiv = bdiv; let baud = current_clk_config.actual_baud(uart_clk); let error = ((baud - target_baud as f64).abs() / target_baud as f64) * 100.0; if error < smallest_error { best_clk_config = current_clk_config; smallest_error = error; } } Ok((best_clk_config, smallest_error)) } impl ClkConfigRaw { #[inline] pub const fn new(cd: u16, bdiv: u8) -> Result { if cd == 0 { return Err(DivisorZero); } Ok(ClkConfigRaw { cd, bdiv }) } /// Auto-calculates the best clock configuration settings for the target baudrate. /// /// This function assumes [ClkSel::UartRefClk] as the clock source. It returns a tuple /// where the first entry is the clock configuration while the second entry is the associated /// baud error from 0.0 to 1.0. It is recommended to keep this error below 2-3 %. pub fn new_autocalc_with_error( io_clks: &IoClocks, target_baud: u32, ) -> Result<(Self, f64), DivisorZero> { Self::new_autocalc_generic(io_clks, ClkSel::UartRefClk, target_baud) } pub fn new_autocalc_generic( io_clks: &IoClocks, clk_sel: ClkSel, target_baud: u32, ) -> Result<(Self, f64), DivisorZero> { Self::new_autocalc_with_raw_clk(io_clks.uart_clk(), clk_sel, target_baud) } pub fn new_autocalc_with_raw_clk( uart_clk: Hertz, clk_sel: ClkSel, target_baud: u32, ) -> Result<(Self, f64), DivisorZero> { calculate_raw_baud_cfg_smallest_error(uart_clk, clk_sel, target_baud) } #[inline] pub const fn cd(&self) -> u16 { self.cd } #[inline] pub const fn bdiv(&self) -> u8 { self.bdiv } #[inline] pub fn rounded_baud(&self, sel_clk: Hertz) -> u32 { round(self.actual_baud(sel_clk)) as u32 } #[inline] pub fn actual_baud(&self, sel_clk: Hertz) -> f64 { sel_clk.raw() as f64 / (self.cd as f64 * (self.bdiv + 1) as f64) } } impl Default for ClkConfigRaw { #[inline] fn default() -> Self { ClkConfigRaw::new(1, 0).unwrap() } } #[derive(Debug)] pub struct UartConfig { clk_config: ClkConfigRaw, chmode: ChMode, parity: Parity, stopbits: Stopbits, chrl: CharLen, clk_sel: ClkSel, } impl UartConfig { pub fn new_with_clk_config(clk_config: ClkConfigRaw) -> Self { Self::new( clk_config, ChMode::default(), Parity::default(), Stopbits::default(), CharLen::default(), ClkSel::default(), ) } #[inline] pub const fn new( clk_config: ClkConfigRaw, chmode: ChMode, parity: Parity, stopbits: Stopbits, chrl: CharLen, clk_sel: ClkSel, ) -> Self { UartConfig { clk_config, chmode, parity, stopbits, chrl, clk_sel, } } #[inline] pub const fn raw_clk_config(&self) -> ClkConfigRaw { self.clk_config } #[inline] pub const fn chmode(&self) -> ChMode { self.chmode } #[inline] pub const fn parity(&self) -> Parity { self.parity } #[inline] pub const fn stopbits(&self) -> Stopbits { self.stopbits } #[inline] pub const fn chrl(&self) -> CharLen { self.chrl } #[inline] pub const fn clksel(&self) -> ClkSel { self.clk_sel } } // TODO: Impl Debug pub struct Uart { rx: Rx, tx: Tx, cfg: UartConfig, } #[derive(Debug, thiserror::Error)] #[error("invalid UART ID")] pub struct InvalidUartIdError; #[derive(Debug, thiserror::Error)] pub enum UartConstructionError { #[error("invalid UART ID: {0}")] InvalidUartId(#[from] InvalidUartIdError), #[error("missmatch between pins index and passed index")] IdxMissmatch, #[error("invalid pin mux conf for UART")] InvalidMuxConf(MuxConf), } impl Uart { /// This is the constructor to use the PS UART with EMIO pins to route the UART into the PL /// or expose them via the PL package pins. /// /// 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 { if uart.uart_id().is_none() { return Err(InvalidUartIdError); } Ok(Self::new_generic_unchecked( uart.reg_block(), uart.uart_id().unwrap(), cfg, )) } /// This is the constructor to use the PS UART with MIO pins. pub fn new_with_mio( uart: impl PsUart, cfg: UartConfig, pins: (TxPinI, RxPinI), ) -> Result where (TxPinI, RxPinI): UartPins, { let id = uart.uart_id(); if id.is_none() { return Err(InvalidUartIdError.into()); } if id.unwrap() != TxPinI::UART_IDX || id.unwrap() != RxPinI::UART_IDX { return Err(UartConstructionError::IdxMissmatch); } if pins.0.mux_conf() != UART_MUX_CONF { return Err(UartConstructionError::InvalidMuxConf(pins.0.mux_conf())); } if pins.1.mux_conf() != UART_MUX_CONF { return Err(UartConstructionError::InvalidMuxConf(pins.1.mux_conf())); } Ok(Self::new_generic_unchecked( uart.reg_block(), id.unwrap(), cfg, )) } /// This is the generic constructor used by all other constructors. /// /// It does not do any pin checks and resource control. It is recommended to use the other /// constructors instead. pub fn new_generic_unchecked( mut reg_block: MmioUart<'static>, uart_id: UartId, cfg: UartConfig, ) -> Uart { let periph_sel = match uart_id { UartId::Uart0 => crate::PeripheralSelect::Uart0, UartId::Uart1 => crate::PeripheralSelect::Uart1, }; enable_amba_peripheral_clock(periph_sel); reset(uart_id); reg_block.modify_cr(|mut v| { v.set_tx_dis(true); v.set_rx_dis(true); v }); // Disable all interrupts. reg_block.write_idr(InterruptControl::new_with_raw_value(0xFFFF_FFFF)); let mode = Mode::builder() .with_chmode(cfg.chmode) .with_nbstop(match cfg.stopbits { Stopbits::One => zynq7000::uart::Stopbits::One, Stopbits::OnePointFive => zynq7000::uart::Stopbits::OnePointFive, Stopbits::Two => zynq7000::uart::Stopbits::Two, }) .with_par(match cfg.parity { Parity::Even => zynq7000::uart::Parity::Even, Parity::Odd => zynq7000::uart::Parity::Odd, Parity::None => zynq7000::uart::Parity::NoParity, }) .with_chrl(match cfg.chrl { CharLen::SixBits => zynq7000::uart::Chrl::SixBits, CharLen::SevenBits => zynq7000::uart::Chrl::SevenBits, CharLen::EightBits => zynq7000::uart::Chrl::EightBits, }) .with_clksel(cfg.clk_sel) .build(); reg_block.write_mr(mode); reg_block.write_baudgen( Baudgen::builder() .with_cd(cfg.raw_clk_config().cd()) .build(), ); reg_block.write_baud_rate_div( BaudRateDiv::builder() .with_bdiv(cfg.raw_clk_config().bdiv()) .build(), ); // Soft reset for both TX and RX. reg_block.modify_cr(|mut v| { v.set_tx_rst(true); v.set_rx_rst(true); v }); // Write default value. reg_block.write_rx_fifo_trigger(FifoTrigger::new_with_raw_value( DEFAULT_RX_TRIGGER_LEVEL as u32, )); // Enable TX and RX. reg_block.modify_cr(|mut v| { v.set_tx_dis(false); v.set_rx_dis(false); v.set_tx_en(true); v.set_rx_en(true); v }); Uart { rx: Rx { regs: unsafe { reg_block.clone() }, }, tx: Tx { regs: reg_block, idx: uart_id, }, cfg, } } #[inline] pub fn set_mode(&mut self, mode: ChMode) { self.regs().modify_mr(|mut mr| { mr.set_chmode(mode); mr }); } #[inline] pub const fn regs(&mut self) -> &mut MmioUart<'static> { &mut self.rx.regs } #[inline] pub const fn cfg(&self) -> &UartConfig { &self.cfg } #[inline] pub const fn split(self) -> (Tx, Rx) { (self.tx, self.rx) } } impl embedded_hal_nb::serial::ErrorType for Uart { type Error = Infallible; } impl embedded_hal_nb::serial::Write for Uart { #[inline] fn write(&mut self, word: u8) -> nb::Result<(), Self::Error> { self.tx.write(word) } fn flush(&mut self) -> nb::Result<(), Self::Error> { self.tx.flush() } } impl embedded_hal_nb::serial::Read for Uart { /// Read one byte from the FIFO. /// /// This operation is infallible because pulling an available byte from the FIFO /// always succeeds. If you want to be informed about RX errors, you should look at the /// non-blocking API using interrupts, which also tracks the RX error bits. #[inline] fn read(&mut self) -> nb::Result { self.rx.read() } } impl embedded_io::ErrorType for Uart { type Error = Infallible; } impl embedded_io::Write for Uart { fn write(&mut self, buf: &[u8]) -> Result { self.tx.write(buf) } fn flush(&mut self) -> Result<(), Self::Error> { self.tx.flush() } } impl embedded_io::Read for Uart { fn read(&mut self, buf: &mut [u8]) -> Result { self.rx.read(buf) } } /// Reset the UART peripheral using the SLCR reset register for UART. /// /// Please note that this function will interfere with an already configured /// UART instance. #[inline] pub fn reset(id: UartId) { let assert_reset = match id { UartId::Uart0 => DualRefAndClockReset::builder() .with_periph1_ref_rst(false) .with_periph0_ref_rst(true) .with_periph1_cpu1x_rst(false) .with_periph0_cpu1x_rst(true) .build(), UartId::Uart1 => DualRefAndClockReset::builder() .with_periph1_ref_rst(true) .with_periph0_ref_rst(false) .with_periph1_cpu1x_rst(true) .with_periph0_cpu1x_rst(false) .build(), }; unsafe { Slcr::with(|regs| { regs.reset_ctrl().write_uart(assert_reset); // Keep it in reset for one cycle.. not sure if this is necessary. cortex_ar::asm::nop(); regs.reset_ctrl().write_uart(DualRefAndClockReset::DEFAULT); }); } } #[cfg(test)] mod tests { use super::*; use approx::abs_diff_eq; use fugit::HertzU32; use zynq7000::uart::ClkSel; const REF_UART_CLK: HertzU32 = HertzU32::from_raw(50_000_000); const REF_UART_CLK_DIV_8: HertzU32 = HertzU32::from_raw(6_250_000); #[test] fn test_error_calc_0() { // Baud 600 let cfg_0 = ClkConfigRaw::new(10417, 7).unwrap(); let actual_baud_0 = cfg_0.actual_baud(REF_UART_CLK); assert!(abs_diff_eq!(actual_baud_0, 599.980, epsilon = 0.01)); } #[test] fn test_error_calc_1() { // Baud 9600 let cfg = ClkConfigRaw::new(81, 7).unwrap(); let actual_baud = cfg.actual_baud(REF_UART_CLK_DIV_8); assert!(abs_diff_eq!(actual_baud, 9645.061, epsilon = 0.01)); } #[test] fn test_error_calc_2() { // Baud 9600 let cfg = ClkConfigRaw::new(651, 7).unwrap(); let actual_baud = cfg.actual_baud(REF_UART_CLK); assert!(abs_diff_eq!(actual_baud, 9600.614, epsilon = 0.01)); } #[test] fn test_error_calc_3() { // Baud 28800 let cfg = ClkConfigRaw::new(347, 4).unwrap(); let actual_baud = cfg.actual_baud(REF_UART_CLK); assert!(abs_diff_eq!(actual_baud, 28818.44, epsilon = 0.01)); } #[test] fn test_error_calc_4() { // Baud 921600 let cfg = ClkConfigRaw::new(9, 5).unwrap(); let actual_baud = cfg.actual_baud(REF_UART_CLK); assert!(abs_diff_eq!(actual_baud, 925925.92, epsilon = 0.01)); } #[test] fn test_best_calc_0() { let result = ClkConfigRaw::new_autocalc_with_raw_clk(REF_UART_CLK, ClkSel::UartRefClk, 600); assert!(result.is_ok()); let (cfg, _error) = result.unwrap(); assert_eq!(cfg.cd(), 499); assert_eq!(cfg.bdiv(), 166); } #[test] #[cfg(feature = "alloc")] fn test_viable_config_calculation() { let cfgs = calculate_viable_configs(REF_UART_CLK, ClkSel::UartRefClk, 115200); assert!( cfgs.iter() .find(|(cfg, _error)| { cfg.cd() == 62 && cfg.bdiv() == 6 }) .is_some() ); } }